From ac9c9ca887a56e95bfe3fb228a6ca0be8b3c6cbf Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 3 Aug 2024 23:00:57 +0530 Subject: [PATCH 01/14] feat: copilot context menu for tool --- src/components/Common/Playground/Message.tsx | 15 +- src/components/Sidepanel/Chat/body.tsx | 1 + src/db/index.ts | 7 +- src/entries/background.ts | 184 +++++++------ src/hooks/chat-helper/index.ts | 34 ++- src/hooks/useBackgroundMessage.tsx | 29 ++ src/hooks/useMessage.tsx | 271 +++++++++++++++++-- src/routes/sidepanel-chat.tsx | 40 ++- src/services/application.ts | 162 +++++++++++ src/store/index.tsx | 1 + src/store/option.tsx | 4 +- src/types/message.ts | 1 + src/utils/pull-ollama.ts | 77 ++++++ wxt.config.ts | 2 +- 14 files changed, 696 insertions(+), 132 deletions(-) create mode 100644 src/hooks/useBackgroundMessage.tsx create mode 100644 src/services/application.ts create mode 100644 src/utils/pull-ollama.ts diff --git a/src/components/Common/Playground/Message.tsx b/src/components/Common/Playground/Message.tsx index c23c90e..fed8b4f 100644 --- a/src/components/Common/Playground/Message.tsx +++ b/src/components/Common/Playground/Message.tsx @@ -1,6 +1,6 @@ import Markdown from "../../Common/Markdown" import React from "react" -import { Image, Tooltip } from "antd" +import { Tag, Image, Tooltip } from "antd" import { WebSearch } from "./WebSearch" import { CheckIcon, @@ -17,6 +17,7 @@ import { useTTS } from "@/hooks/useTTS" type Props = { message: string + message_type?: string hideCopy?: boolean botAvatar?: JSX.Element userAvatar?: JSX.Element @@ -76,13 +77,21 @@ export const PlaygroundMessage = (props: Props) => { props.currentMessageIndex === props.totalMessages - 1 ? ( ) : null} - +
+ {props?.message_type && ( + {props?.message_type} + )} +
{!editMode ? ( props.isBot ? ( ) : ( -

+

{props.message}

) diff --git a/src/components/Sidepanel/Chat/body.tsx b/src/components/Sidepanel/Chat/body.tsx index d9a2d8e..61d9071 100644 --- a/src/components/Sidepanel/Chat/body.tsx +++ b/src/components/Sidepanel/Chat/body.tsx @@ -35,6 +35,7 @@ export const SidePanelBody = () => { currentMessageIndex={index} totalMessages={messages.length} onRengerate={regenerateLastMessage} + message_type={message.messageType} isProcessing={streaming} isSearchingInternet={isSearchingInternet} sources={message.sources} diff --git a/src/db/index.ts b/src/db/index.ts index ea60233..6e27b3f 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -31,6 +31,7 @@ type Message = { sources?: string[] search?: WebSearch createdAt: number + messageType?: string } type Webshare = { @@ -241,7 +242,8 @@ export const saveMessage = async ( content: string, images: string[], source?: any[], - time?: number + time?: number, + message_type?: string ) => { const id = generateID() let createdAt = Date.now() @@ -256,7 +258,8 @@ export const saveMessage = async ( content, images, createdAt, - sources: source + sources: source, + messageType: message_type } const db = new PageAssitDatabase() await db.addMessage(message) diff --git a/src/entries/background.ts b/src/entries/background.ts index b238b9a..8972369 100644 --- a/src/entries/background.ts +++ b/src/entries/background.ts @@ -1,84 +1,10 @@ import { getOllamaURL, isOllamaRunning } from "../services/ollama" import { browser } from "wxt/browser" -import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action" - -const progressHuman = (completed: number, total: number) => { - return ((completed / total) * 100).toFixed(0) + "%" -} - -const clearBadge = () => { - setBadgeText({ text: "" }) - setTitle({ title: "" }) -} -const streamDownload = async (url: string, model: string) => { - url += "/api/pull" - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ model, stream: true }) - }) - - const reader = response.body?.getReader() - - const decoder = new TextDecoder() - - let isSuccess = true - while (true) { - if (!reader) { - break - } - const { done, value } = await reader.read() - - if (done) { - break - } - - const text = decoder.decode(value) - try { - const json = JSON.parse(text.trim()) as { - status: string - total?: number - completed?: number - } - if (json.total && json.completed) { - setBadgeText({ - text: progressHuman(json.completed, json.total) - }) - setBadgeBackgroundColor({ color: "#0000FF" }) - } else { - setBadgeText({ text: "🏋️‍♂️" }) - setBadgeBackgroundColor({ color: "#FFFFFF" }) - } - - setTitle({ title: json.status }) - - if (json.status === "success") { - isSuccess = true - } - } catch (e) { - console.error(e) - } - } - - if (isSuccess) { - setBadgeText({ text: "✅" }) - setBadgeBackgroundColor({ color: "#00FF00" }) - setTitle({ title: "Model pulled successfully" }) - } else { - setBadgeText({ text: "❌" }) - setBadgeBackgroundColor({ color: "#FF0000" }) - setTitle({ title: "Model pull failed" }) - } - - setTimeout(() => { - clearBadge() - }, 5000) -} +import { clearBadge, streamDownload } from "@/utils/pull-ollama" export default defineBackground({ main() { + let isCopilotRunning: boolean = false browser.runtime.onMessage.addListener(async (message) => { if (message.type === "sidepanel") { await browser.sidebarAction.open() @@ -100,6 +26,15 @@ export default defineBackground({ } }) + browser.runtime.onConnect.addListener((port) => { + if (port.name === "pgCopilot") { + isCopilotRunning = true + port.onDisconnect.addListener(() => { + isCopilotRunning = false + }) + } + }) + if (import.meta.env.BROWSER === "chrome") { chrome.action.onClicked.addListener((tab) => { chrome.tabs.create({ url: chrome.runtime.getURL("/options.html") }) @@ -124,10 +59,41 @@ export default defineBackground({ browser.contextMenus.create({ id: contextMenuId["sidePanel"], title: contextMenuTitle["sidePanel"], - contexts: ["all"] + contexts: ["page", "selection"] }) + + browser.contextMenus.create({ + id: "summarize-pa", + title: "Summarize", + contexts: ["selection"] + }) + + browser.contextMenus.create({ + id: "explain-pa", + title: "Explain", + contexts: ["selection"] + }) + + browser.contextMenus.create({ + id: "rephrase-pa", + title: "Rephrase", + contexts: ["selection"] + }) + + browser.contextMenus.create({ + id: "translate-pg", + title: "Translate", + contexts: ["selection"] + }) + + // browser.contextMenus.create({ + // id: "custom-pg", + // title: "Custom", + // contexts: ["selection"] + // }) + if (import.meta.env.BROWSER === "chrome") { - browser.contextMenus.onClicked.addListener((info, tab) => { + browser.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === "open-side-panel-pa") { chrome.sidePanel.open({ tabId: tab.id! @@ -136,6 +102,68 @@ export default defineBackground({ browser.tabs.create({ url: browser.runtime.getURL("/options.html") }) + } else if (info.menuItemId === "summarize-pa") { + chrome.sidePanel.open({ + tabId: tab.id! + }) + // this is a bad method hope somone can fix it :) + setTimeout(async () => { + await browser.runtime.sendMessage({ + from: "background", + type: "summary", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + + } else if (info.menuItemId === "rephrase-pa") { + chrome.sidePanel.open({ + tabId: tab.id! + }) + setTimeout(async () => { + + await browser.runtime.sendMessage({ + type: "rephrase", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + + } else if (info.menuItemId === "translate-pg") { + chrome.sidePanel.open({ + tabId: tab.id! + }) + + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "translate", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "explain-pa") { + chrome.sidePanel.open({ + tabId: tab.id! + }) + + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "explain", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "custom-pg") { + chrome.sidePanel.open({ + tabId: tab.id! + }) + + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "custom", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) } }) diff --git a/src/hooks/chat-helper/index.ts b/src/hooks/chat-helper/index.ts index f7b690f..f2426ee 100644 --- a/src/hooks/chat-helper/index.ts +++ b/src/hooks/chat-helper/index.ts @@ -13,7 +13,8 @@ export const saveMessageOnError = async ({ selectedModel, setHistoryId, isRegenerating, - message_source = "web-ui" + message_source = "web-ui", + message_type }: { e: any setHistory: (history: ChatHistory) => void @@ -26,6 +27,7 @@ export const saveMessageOnError = async ({ setHistoryId: (historyId: string) => void isRegenerating: boolean message_source?: "copilot" | "web-ui" + message_type?: string }) => { if ( e?.name === "AbortError" || @@ -55,7 +57,8 @@ export const saveMessageOnError = async ({ userMessage, [image], [], - 1 + 1, + message_type ) } await saveMessage( @@ -65,7 +68,8 @@ export const saveMessageOnError = async ({ botMessage, [], [], - 2 + 2, + message_type ) await setLastUsedChatModel(historyId, selectedModel) } else { @@ -78,7 +82,8 @@ export const saveMessageOnError = async ({ userMessage, [image], [], - 1 + 1, + message_type ) } await saveMessage( @@ -88,7 +93,8 @@ export const saveMessageOnError = async ({ botMessage, [], [], - 2 + 2, + message_type ) setHistoryId(newHistoryId.id) await setLastUsedChatModel(newHistoryId.id, selectedModel) @@ -109,7 +115,8 @@ export const saveMessageOnSuccess = async ({ image, fullText, source, - message_source = "web-ui" + message_source = "web-ui", + message_type }: { historyId: string | null setHistoryId: (historyId: string) => void @@ -119,7 +126,8 @@ export const saveMessageOnSuccess = async ({ image: string fullText: string source: any[] - message_source?: "copilot" | "web-ui" + message_source?: "copilot" | "web-ui", + message_type?: string }) => { if (historyId) { if (!isRegenerate) { @@ -130,7 +138,8 @@ export const saveMessageOnSuccess = async ({ message, [image], [], - 1 + 1, + message_type ) } await saveMessage( @@ -140,7 +149,8 @@ export const saveMessageOnSuccess = async ({ fullText, [], source, - 2 + 2, + message_type ) await setLastUsedChatModel(historyId, selectedModel!) } else { @@ -152,7 +162,8 @@ export const saveMessageOnSuccess = async ({ message, [image], [], - 1 + 1, + message_type ) await saveMessage( newHistoryId.id, @@ -161,7 +172,8 @@ export const saveMessageOnSuccess = async ({ fullText, [], source, - 2 + 2, + message_type ) setHistoryId(newHistoryId.id) await setLastUsedChatModel(newHistoryId.id, selectedModel!) diff --git a/src/hooks/useBackgroundMessage.tsx b/src/hooks/useBackgroundMessage.tsx new file mode 100644 index 0000000..a7e282f --- /dev/null +++ b/src/hooks/useBackgroundMessage.tsx @@ -0,0 +1,29 @@ +import { useState, useEffect } from "react" + +interface Message { + from: string + type: string + text: string +} + +function useBackgroundMessage() { + const [message, setMessage] = useState(null) + + useEffect(() => { + const messageListener = (request: Message) => { + if (request.from === "background") { + setMessage(request) + } + } + browser.runtime.connect({ name: 'pgCopilot' }) + browser.runtime.onMessage.addListener(messageListener) + + return () => { + browser.runtime.onMessage.removeListener(messageListener) + } + }, []) + + return message +} + +export default useBackgroundMessage \ No newline at end of file diff --git a/src/hooks/useMessage.tsx b/src/hooks/useMessage.tsx index 896a2d2..3d61528 100644 --- a/src/hooks/useMessage.tsx +++ b/src/hooks/useMessage.tsx @@ -31,6 +31,7 @@ import { useStoreChatModelSettings } from "@/store/model" import { getAllDefaultModelSettings } from "@/services/model-settings" import { getSystemPromptForWeb } from "@/web/web" import { pageAssistModel } from "@/models" +import { getPrompt } from "@/services/application" export const useMessage = () => { const { @@ -51,8 +52,10 @@ export const useMessage = () => { isSearchingInternet } = useStoreMessageOption() - - const [chatWithWebsiteEmbedding] = useStorage("chatWithWebsiteEmbedding", true) + const [chatWithWebsiteEmbedding] = useStorage( + "chatWithWebsiteEmbedding", + true + ) const [maxWebsiteContext] = useStorage("maxWebsiteContext", 4028) const { @@ -857,13 +860,206 @@ export const useMessage = () => { } } + const presetChatMode = async ( + message: string, + image: string, + isRegenerate: boolean, + messages: Message[], + history: ChatHistory, + signal: AbortSignal, + messageType: string + ) => { + setStreaming(true) + const url = await getOllamaURL() + const userDefaultModelSettings = await getAllDefaultModelSettings() + + if (image.length > 0) { + image = `data:image/jpeg;base64,${image.split(",")[1]}` + } + + const ollama = await pageAssistModel({ + model: selectedModel!, + baseUrl: cleanUrl(url), + keepAlive: + currentChatModelSettings?.keepAlive ?? + userDefaultModelSettings?.keepAlive, + temperature: + currentChatModelSettings?.temperature ?? + userDefaultModelSettings?.temperature, + topK: currentChatModelSettings?.topK ?? userDefaultModelSettings?.topK, + topP: currentChatModelSettings?.topP ?? userDefaultModelSettings?.topP, + numCtx: + currentChatModelSettings?.numCtx ?? userDefaultModelSettings?.numCtx, + seed: currentChatModelSettings?.seed + }) + + let newMessage: Message[] = [] + let generateMessageId = generateID() + + if (!isRegenerate) { + newMessage = [ + ...messages, + { + isBot: false, + name: "You", + message, + sources: [], + images: [image], + messageType: messageType + }, + { + isBot: true, + name: selectedModel, + message: "▋", + sources: [], + id: generateMessageId + } + ] + } else { + newMessage = [ + ...messages, + { + isBot: true, + name: selectedModel, + message: "▋", + sources: [], + id: generateMessageId + } + ] + } + setMessages(newMessage) + let fullText = "" + let contentToSave = "" + + try { + + + const prompt = await getPrompt(messageType) + let humanMessage = new HumanMessage({ + content: [ + { + text: prompt.replace("{text}", message), + type: "text" + } + ] + }) + if (image.length > 0) { + humanMessage = new HumanMessage({ + content: [ + { + text: prompt.replace("{text}", message), + type: "text" + }, + { + image_url: image, + type: "image_url" + } + ] + }) + } + + const chunks = await ollama.stream([humanMessage], { + signal: signal + }) + let count = 0 + for await (const chunk of chunks) { + contentToSave += chunk.content + fullText += chunk.content + if (count === 0) { + setIsProcessing(true) + } + setMessages((prev) => { + return prev.map((message) => { + if (message.id === generateMessageId) { + return { + ...message, + message: fullText + "▋" + } + } + return message + }) + }) + count++ + } + + setMessages((prev) => { + return prev.map((message) => { + if (message.id === generateMessageId) { + return { + ...message, + message: fullText + } + } + return message + }) + }) + + setHistory([ + ...history, + { + role: "user", + content: message, + image, + messageType + }, + { + role: "assistant", + content: fullText + } + ]) + + await saveMessageOnSuccess({ + historyId, + setHistoryId, + isRegenerate, + selectedModel: selectedModel, + message, + image, + fullText, + source: [], + message_source: "copilot", + message_type: messageType + }) + + setIsProcessing(false) + setStreaming(false) + } catch (e) { + const errorSave = await saveMessageOnError({ + e, + botMessage: fullText, + history, + historyId, + image, + selectedModel, + setHistory, + setHistoryId, + userMessage: message, + isRegenerating: isRegenerate, + message_source: "copilot", + message_type: messageType + }) + + if (!errorSave) { + notification.error({ + message: t("error"), + description: e?.message || t("somethingWentWrong") + }) + } + setIsProcessing(false) + setStreaming(false) + } finally { + setAbortController(null) + } + } + const onSubmit = async ({ message, image, isRegenerate, controller, memory, - messages: chatHistory + messages: chatHistory, + messageType }: { message: string image: string @@ -871,6 +1067,7 @@ export const useMessage = () => { messages?: Message[] memory?: ChatHistory controller?: AbortController + messageType?: string }) => { let signal: AbortSignal if (!controller) { @@ -882,39 +1079,52 @@ export const useMessage = () => { signal = controller.signal } - if (chatMode === "normal") { - if (webSearch) { - await searchChatMode( - message, - image, - isRegenerate || false, - messages, - memory || history, - signal - ) - } else { - await normalChatMode( - message, - image, - isRegenerate, - chatHistory || messages, - memory || history, - signal - ) - } - } else { - const newEmbeddingController = new AbortController() - let embeddingSignal = newEmbeddingController.signal - setEmbeddingController(newEmbeddingController) - await chatWithWebsiteMode( + // this means that the user is trying to send something from a selected text on the web + if (messageType) { + await presetChatMode( message, image, isRegenerate, chatHistory || messages, memory || history, signal, - embeddingSignal + messageType ) + } else { + if (chatMode === "normal") { + if (webSearch) { + await searchChatMode( + message, + image, + isRegenerate || false, + messages, + memory || history, + signal + ) + } else { + await normalChatMode( + message, + image, + isRegenerate, + chatHistory || messages, + memory || history, + signal + ) + } + } else { + const newEmbeddingController = new AbortController() + let embeddingSignal = newEmbeddingController.signal + setEmbeddingController(newEmbeddingController) + await chatWithWebsiteMode( + message, + image, + isRegenerate, + chatHistory || messages, + memory || history, + signal, + embeddingSignal + ) + } } } @@ -982,7 +1192,8 @@ export const useMessage = () => { image: lastMessage.image || "", isRegenerate: true, memory: newHistory, - controller: newController + controller: newController, + messageType: lastMessage.messageType }) } } diff --git a/src/routes/sidepanel-chat.tsx b/src/routes/sidepanel-chat.tsx index 112cdc5..60205a9 100644 --- a/src/routes/sidepanel-chat.tsx +++ b/src/routes/sidepanel-chat.tsx @@ -3,8 +3,11 @@ import { formatToMessage, getRecentChatFromCopilot } from "@/db" +import useBackgroundMessage from "@/hooks/useBackgroundMessage" import { copilotResumeLastChat } from "@/services/app" +import { notification } from "antd" import React from "react" +import { useTranslation } from "react-i18next" import { SidePanelBody } from "~/components/Sidepanel/Chat/body" import { SidepanelForm } from "~/components/Sidepanel/Chat/form" import { SidepanelHeader } from "~/components/Sidepanel/Chat/header" @@ -13,17 +16,27 @@ import { useMessage } from "~/hooks/useMessage" const SidepanelChat = () => { const drop = React.useRef(null) const [dropedFile, setDropedFile] = React.useState() + const { t } = useTranslation(["playground"]) const [dropState, setDropState] = React.useState< "idle" | "dragging" | "error" >("idle") - const { chatMode, messages, setHistory, setHistoryId, setMessages } = - useMessage() + const { + chatMode, + streaming, + onSubmit, + messages, + setHistory, + setHistoryId, + setMessages, + selectedModel + } = useMessage() + + const bgMsg = useBackgroundMessage() const setRecentMessagesOnLoad = async () => { - - const isEnabled = await copilotResumeLastChat(); + const isEnabled = await copilotResumeLastChat() if (!isEnabled) { - return; + return } if (messages.length === 0) { const recentChat = await getRecentChatFromCopilot() @@ -92,11 +105,26 @@ const SidepanelChat = () => { } }, []) - React.useEffect(() => { setRecentMessagesOnLoad() }, []) + React.useEffect(() => { + if (bgMsg && !streaming) { + if (selectedModel) { + onSubmit({ + message: bgMsg.text, + messageType: bgMsg.type, + image: "" + }) + } else { + notification.error({ + message: t("formError.noModel") + }) + } + } + }, [bgMsg]) + return (
{ + return (await storage.get("copilotSummaryPrompt")) || DEFAULT_SUMMARY_PROMPT +} + +export const setSummaryPrompt = async (prompt: string) => { + await storage.set("copilotSummaryPrompt", prompt) +} + +export const getRephrasePrompt = async () => { + return (await storage.get("copilotRephrasePrompt")) || DEFAULT_REPHRASE_PROMPT +} + +export const setRephrasePrompt = async (prompt: string) => { + await storage.set("copilotRephrasePrompt", prompt) +} + +export const getTranslatePrompt = async () => { + return ( + (await storage.get("copilotTranslatePrompt")) || DEFAULT_TRANSLATE_PROMPT + ) +} + +export const setTranslatePrompt = async (prompt: string) => { + await storage.set("copilotTranslatePrompt", prompt) +} + +export const getExplainPrompt = async () => { + return (await storage.get("copilotExplainPrompt")) || DEFAULT_EXPLAIN_PROMPT +} + +export const setExplainPrompt = async (prompt: string) => { + await storage.set("copilotExplainPrompt", prompt) +} + +export const getCustomPrompt = async () => { + return (await storage.get("copilotCustomPrompt")) || DEFAULT_CUSTOM_PROMPT +} + +export const setCustomPrompt = async (prompt: string) => { + await storage.set("copilotCustomPrompt", prompt) +} + +export const getAllCopilotPrompts = async () => { + const [ + summaryPrompt, + rephrasePrompt, + translatePrompt, + explainPrompt, + customPrompt + ] = await Promise.all([ + getSummaryPrompt(), + getRephrasePrompt(), + getTranslatePrompt(), + getExplainPrompt(), + getCustomPrompt() + ]) + + return [ + { key: "summary", prompt: summaryPrompt }, + { key: "rephrase", prompt: rephrasePrompt }, + { key: "translate", prompt: translatePrompt }, + { key: "explain", prompt: explainPrompt }, + { key: "custom", prompt: customPrompt } + ] +} + +export const setAllCopilotPrompts = async ( + prompts: { key: string; prompt: string }[] +) => { + for (const { key, prompt } of prompts) { + switch (key) { + case "summary": + await setSummaryPrompt(prompt) + break + case "rephrase": + await setRephrasePrompt(prompt) + break + case "translate": + await setTranslatePrompt(prompt) + break + case "explain": + await setExplainPrompt(prompt) + break + case "custom": + await setCustomPrompt(prompt) + break + + } + } +} + +export const getPrompt = async (key: string) => { + switch (key) { + case "summary": + return await getSummaryPrompt() + case "rephrase": + return await getRephrasePrompt() + case "translate": + return await getTranslatePrompt() + case "explain": + return await getExplainPrompt() + case "custom": + return await getCustomPrompt() + default: + return "" + } +} diff --git a/src/store/index.tsx b/src/store/index.tsx index 8910705..c1929d3 100644 --- a/src/store/index.tsx +++ b/src/store/index.tsx @@ -12,6 +12,7 @@ export type ChatHistory = { role: "user" | "assistant" | "system" content: string image?: string + messageType?: string }[] type State = { diff --git a/src/store/option.tsx b/src/store/option.tsx index 8e1e845..5184b8f 100644 --- a/src/store/option.tsx +++ b/src/store/option.tsx @@ -18,12 +18,14 @@ export type Message = { images?: string[] search?: WebSearch id?: string + messageType?: string } export type ChatHistory = { role: "user" | "assistant" | "system" content: string - image?: string + image?: string, + messageType?: string }[] type State = { diff --git a/src/types/message.ts b/src/types/message.ts index 66e954e..3be7cdc 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -14,5 +14,6 @@ type WebSearch = { sources: any[] images?: string[] search?: WebSearch + messageType?: string id?: string } \ No newline at end of file diff --git a/src/utils/pull-ollama.ts b/src/utils/pull-ollama.ts new file mode 100644 index 0000000..d15ff2b --- /dev/null +++ b/src/utils/pull-ollama.ts @@ -0,0 +1,77 @@ +import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action" + + +export const progressHuman = (completed: number, total: number) => { + return ((completed / total) * 100).toFixed(0) + "%" +} + +export const clearBadge = () => { + setBadgeText({ text: "" }) + setTitle({ title: "" }) +} +export const streamDownload = async (url: string, model: string) => { + url += "/api/pull" + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ model, stream: true }) + }) + + const reader = response.body?.getReader() + + const decoder = new TextDecoder() + + let isSuccess = true + while (true) { + if (!reader) { + break + } + const { done, value } = await reader.read() + + if (done) { + break + } + + const text = decoder.decode(value) + try { + const json = JSON.parse(text.trim()) as { + status: string + total?: number + completed?: number + } + if (json.total && json.completed) { + setBadgeText({ + text: progressHuman(json.completed, json.total) + }) + setBadgeBackgroundColor({ color: "#0000FF" }) + } else { + setBadgeText({ text: "🏋️‍♂️" }) + setBadgeBackgroundColor({ color: "#FFFFFF" }) + } + + setTitle({ title: json.status }) + + if (json.status === "success") { + isSuccess = true + } + } catch (e) { + console.error(e) + } + } + + if (isSuccess) { + setBadgeText({ text: "✅" }) + setBadgeBackgroundColor({ color: "#00FF00" }) + setTitle({ title: "Model pulled successfully" }) + } else { + setBadgeText({ text: "❌" }) + setBadgeBackgroundColor({ color: "#FF0000" }) + setTitle({ title: "Model pull failed" }) + } + + setTimeout(() => { + clearBadge() + }, 5000) +} \ No newline at end of file diff --git a/wxt.config.ts b/wxt.config.ts index 74fda20..10b2eff 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -50,7 +50,7 @@ export default defineConfig({ outDir: "build", manifest: { - version: "1.1.16", + version: "1.2.0", name: process.env.TARGET === "firefox" ? "Page Assist - A Web UI for Local AI Models" From 58304d9ca707666bb81034d54e176c684909a659 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 3 Aug 2024 23:51:17 +0530 Subject: [PATCH 02/14] feat: Update translations for copilot context menu options --- src/assets/locale/en/common.json | 7 + src/assets/locale/es/common.json | 8 +- src/assets/locale/fr/common.json | 8 +- src/assets/locale/it/common.json | 8 +- src/assets/locale/ja-JP/common.json | 168 ++++++++++--------- src/assets/locale/ml/common.json | 6 + src/assets/locale/pt-BR/common.json | 6 + src/assets/locale/ru/common.json | 8 +- src/assets/locale/zh/common.json | 6 + src/components/Common/Playground/Message.tsx | 14 +- src/entries/background.ts | 10 +- 11 files changed, 157 insertions(+), 92 deletions(-) diff --git a/src/assets/locale/en/common.json b/src/assets/locale/en/common.json index ffa6e81..05ef169 100644 --- a/src/assets/locale/en/common.json +++ b/src/assets/locale/en/common.json @@ -84,5 +84,12 @@ } }, "advanced": "More Model Settings" + }, + "copilot": { + "summary": "Summarize", + "explain": "Explain", + "rephrase": "Rephrase", + "translate": "Translate", + "custom": "Custom" } } \ No newline at end of file diff --git a/src/assets/locale/es/common.json b/src/assets/locale/es/common.json index e316a0b..06d0a00 100644 --- a/src/assets/locale/es/common.json +++ b/src/assets/locale/es/common.json @@ -84,5 +84,11 @@ } }, "advanced": "Más Configuraciones del Modelo" + }, + "copilot": { + "summary": "Resumir", + "explain": "Explicar", + "rephrase": "Reformular", + "translate": "Traducir" } -} +} \ No newline at end of file diff --git a/src/assets/locale/fr/common.json b/src/assets/locale/fr/common.json index 328acae..cf601db 100644 --- a/src/assets/locale/fr/common.json +++ b/src/assets/locale/fr/common.json @@ -51,7 +51,7 @@ "chatWithCurrentPage": "Discuter avec la page actuelle", "beta": "Bêta", "tts": "Synthèse vocale", - "currentChatModelSettings":"Paramètres actuels du modèle de chat", + "currentChatModelSettings": "Paramètres actuels du modèle de chat", "modelSettings": { "label": "Paramètres du modèle", "description": "Définissez les options de modèle globale pour tous les chats", @@ -84,5 +84,11 @@ } }, "advanced": "Plus de paramètres du modèle" + }, + "copilot": { + "summary": "Résumer", + "explain": "Expliquer", + "rephrase": "Reformuler", + "translate": "Traduire" } } \ No newline at end of file diff --git a/src/assets/locale/it/common.json b/src/assets/locale/it/common.json index df4f4c3..0b0708e 100644 --- a/src/assets/locale/it/common.json +++ b/src/assets/locale/it/common.json @@ -84,5 +84,11 @@ } }, "advanced": "Altre Impostazioni del Modello" + }, + "copilot": { + "summary": "Riassumere", + "explain": "Spiegare", + "rephrase": "Riformulare", + "translate": "Tradurre" } -} +} \ No newline at end of file diff --git a/src/assets/locale/ja-JP/common.json b/src/assets/locale/ja-JP/common.json index 7519408..4fcde04 100644 --- a/src/assets/locale/ja-JP/common.json +++ b/src/assets/locale/ja-JP/common.json @@ -1,88 +1,94 @@ { - "pageAssist": "ページアシスト", - "selectAModel": "モデルを選択", - "save": "保存", - "saved": "保存済み", - "cancel": "キャンセル", - "retry": "再試行", - "share": { - "tooltip": { - "share": "共有" + "pageAssist": "ページアシスト", + "selectAModel": "モデルを選択", + "save": "保存", + "saved": "保存済み", + "cancel": "キャンセル", + "retry": "再試行", + "share": { + "tooltip": { + "share": "共有" + }, + "modal": { + "title": "チャットリンクを共有" + }, + "form": { + "defaultValue": { + "name": "匿名", + "title": "無題のチャット" }, - "modal": { - "title": "チャットリンクを共有" + "title": { + "label": "チャットタイトル", + "placeholder": "チャットタイトルを入力", + "required": "チャットタイトルは必須です" }, - "form": { - "defaultValue": { - "name": "匿名", - "title": "無題のチャット" - }, - "title": { - "label": "チャットタイトル", - "placeholder": "チャットタイトルを入力", - "required": "チャットタイトルは必須です" - }, - "name": { - "label": "あなたの名前", - "placeholder": "名前を入力", - "required": "名前は必須です" - }, - "btn": { - "save": "リンクを生成", - "saving": "リンクを生成中..." - } + "name": { + "label": "あなたの名前", + "placeholder": "名前を入力", + "required": "名前は必須です" }, - "notification": { - "successGenerate": "リンクがクリップボードにコピーされました", - "failGenerate": "リンクの生成に失敗しました" + "btn": { + "save": "リンクを生成", + "saving": "リンクを生成中..." } }, - "copyToClipboard": "クリップボードにコピー", - "webSearch": "ウェブを検索中", - "regenerate": "再生成", - "edit": "編集", - "saveAndSubmit": "保存して送信", - "editMessage": { - "placeholder": "メッセージを入力..." - }, - "submit": "送信", - "noData": "データがありません", - "noHistory": "チャット履歴がありません", - "chatWithCurrentPage": "現在のページでチャット", - "beta": "ベータ", - "tts": "読み上げ", - "currentChatModelSettings": "現在のチャットモデル設定", - "modelSettings": { - "label": "モデル設定", - "description": "すべてのチャットに対してモデルオプションをグローバルに設定します", - "form": { - "keepAlive": { - "label": "キープアライブ", - "help": "リクエスト後にモデルがメモリに保持される時間をコントロールします(デフォルト: 5 分)", - "placeholder": "キープアライブの期間を入力してください(例:5分、10分、1時間)" - }, - "temperature": { - "label": "温度", - "placeholder": "温度値を入力してください(例:0.7、1.0)" - }, - "numCtx": { - "label": "コンテキストの数", - "placeholder": "コンテキスト数を入力してください(デフォルト:2048)" - }, - "seed": { - "label": "シード", - "placeholder": "シード値を入力してください(例:1234)", - "help": "モデル出力の再現性" - }, - "topK": { - "label": "Top K", - "placeholder": "Top K値を入力してください(例:40、100)" - }, - "topP": { - "label": "Top P", - "placeholder": "Top P値を入力してください(例:0.9、0.95)" - } - }, - "advanced": "その他のモデル設定" + "notification": { + "successGenerate": "リンクがクリップボードにコピーされました", + "failGenerate": "リンクの生成に失敗しました" } - } \ No newline at end of file + }, + "copyToClipboard": "クリップボードにコピー", + "webSearch": "ウェブを検索中", + "regenerate": "再生成", + "edit": "編集", + "saveAndSubmit": "保存して送信", + "editMessage": { + "placeholder": "メッセージを入力..." + }, + "submit": "送信", + "noData": "データがありません", + "noHistory": "チャット履歴がありません", + "chatWithCurrentPage": "現在のページでチャット", + "beta": "ベータ", + "tts": "読み上げ", + "currentChatModelSettings": "現在のチャットモデル設定", + "modelSettings": { + "label": "モデル設定", + "description": "すべてのチャットに対してモデルオプションをグローバルに設定します", + "form": { + "keepAlive": { + "label": "キープアライブ", + "help": "リクエスト後にモデルがメモリに保持される時間をコントロールします(デフォルト: 5 分)", + "placeholder": "キープアライブの期間を入力してください(例:5分、10分、1時間)" + }, + "temperature": { + "label": "温度", + "placeholder": "温度値を入力してください(例:0.7、1.0)" + }, + "numCtx": { + "label": "コンテキストの数", + "placeholder": "コンテキスト数を入力してください(デフォルト:2048)" + }, + "seed": { + "label": "シード", + "placeholder": "シード値を入力してください(例:1234)", + "help": "モデル出力の再現性" + }, + "topK": { + "label": "Top K", + "placeholder": "Top K値を入力してください(例:40、100)" + }, + "topP": { + "label": "Top P", + "placeholder": "Top P値を入力してください(例:0.9、0.95)" + } + }, + "advanced": "その他のモデル設定" + }, + "copilot": { + "summary": "要約", + "explain": "説明", + "rephrase": "言い換え", + "translate": "翻訳" + } +} \ No newline at end of file diff --git a/src/assets/locale/ml/common.json b/src/assets/locale/ml/common.json index ad3d9cc..4d923c0 100644 --- a/src/assets/locale/ml/common.json +++ b/src/assets/locale/ml/common.json @@ -83,5 +83,11 @@ } }, "advanced": "കൂടുതൽ മോഡൽ ക്രമീകരണങ്ങൾ" + }, + "copilot": { + "summary": "സംഗ്രഹിക്കുക", + "explain": "വിശദീകരിക്കുക", + "rephrase": "പുനഃരൂപീകരിക്കുക", + "translate": "വിവർത്തനം ചെയ്യുക" } } \ No newline at end of file diff --git a/src/assets/locale/pt-BR/common.json b/src/assets/locale/pt-BR/common.json index 6f3b70b..a322b7c 100644 --- a/src/assets/locale/pt-BR/common.json +++ b/src/assets/locale/pt-BR/common.json @@ -84,5 +84,11 @@ } }, "advanced": "Mais Configurações do Modelo" + }, + "copilot": { + "summary": "Resumir", + "explain": "Explicar", + "rephrase": "Reformular", + "translate": "Traduzir" } } \ No newline at end of file diff --git a/src/assets/locale/ru/common.json b/src/assets/locale/ru/common.json index 7deac9a..b14c855 100644 --- a/src/assets/locale/ru/common.json +++ b/src/assets/locale/ru/common.json @@ -84,5 +84,11 @@ } }, "advanced": "Больше настроек модели" + }, + "copilot": { + "summary": "Обобщить", + "explain": "Объяснить", + "rephrase": "Перефразировать", + "translate": "Перевести" } -} +} \ No newline at end of file diff --git a/src/assets/locale/zh/common.json b/src/assets/locale/zh/common.json index dca8241..9145e48 100644 --- a/src/assets/locale/zh/common.json +++ b/src/assets/locale/zh/common.json @@ -84,5 +84,11 @@ } }, "advanced": "更多模型设置" + }, + "copilot": { + "summary": "总结", + "explain": "解释", + "rephrase": "重述", + "translate": "翻译" } } \ No newline at end of file diff --git a/src/components/Common/Playground/Message.tsx b/src/components/Common/Playground/Message.tsx index fed8b4f..bcdbd23 100644 --- a/src/components/Common/Playground/Message.tsx +++ b/src/components/Common/Playground/Message.tsx @@ -37,6 +37,14 @@ type Props = { isTTSEnabled?: boolean } +const tagColors = { + summary: "blue", + explain: "green", + translate: "purple", + custom: "orange", + rephrase: "yellow" +} + export const PlaygroundMessage = (props: Props) => { const [isBtnPressed, setIsBtnPressed] = React.useState(false) const [editMode, setEditMode] = React.useState(false) @@ -79,7 +87,9 @@ export const PlaygroundMessage = (props: Props) => { ) : null}
{props?.message_type && ( - {props?.message_type} + + {t(`copilot.${props?.message_type}`)} + )}
@@ -90,7 +100,7 @@ export const PlaygroundMessage = (props: Props) => {

{props.message}

diff --git a/src/entries/background.ts b/src/entries/background.ts index 8972369..34f6679 100644 --- a/src/entries/background.ts +++ b/src/entries/background.ts @@ -86,11 +86,11 @@ export default defineBackground({ contexts: ["selection"] }) - // browser.contextMenus.create({ - // id: "custom-pg", - // title: "Custom", - // contexts: ["selection"] - // }) + browser.contextMenus.create({ + id: "custom-pg", + title: "Custom", + contexts: ["selection"] + }) if (import.meta.env.BROWSER === "chrome") { browser.contextMenus.onClicked.addListener(async (info, tab) => { From 80413a3c268ece4722e25d0079f7e295e357fd5b Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 4 Aug 2024 00:08:10 +0530 Subject: [PATCH 03/14] feat: Add segmented control for custom and copilot prompts in PromptBody component --- src/components/Option/Prompt/index.tsx | 59 +++++++++++++++++++------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx index 141bdf6..f0d1c19 100644 --- a/src/components/Option/Prompt/index.tsx +++ b/src/components/Option/Prompt/index.tsx @@ -8,16 +8,12 @@ import { Input, Form, Switch, + Segmented } from "antd" import { Trash2, Pen, Computer, Zap } from "lucide-react" import { useState } from "react" import { useTranslation } from "react-i18next" -import { - deletePromptById, - getAllPrompts, - savePrompt, - updatePrompt -} from "@/db" +import { deletePromptById, getAllPrompts, savePrompt, updatePrompt } from "@/db" export const PromptBody = () => { const queryClient = useQueryClient() @@ -27,7 +23,9 @@ export const PromptBody = () => { const [createForm] = Form.useForm() const [editForm] = Form.useForm() const { t } = useTranslation("settings") - + const [selectedSegment, setSelectedSegment] = useState<"custom" | "copilot">( + "custom" + ) const { data, status } = useQuery({ queryKey: ["fetchAllPrompts"], queryFn: getAllPrompts @@ -103,8 +101,8 @@ export const PromptBody = () => { } }) - return ( -
+ function customPrompts() { + return (
@@ -127,30 +125,38 @@ export const PromptBody = () => { title: t("managePrompts.columns.title"), dataIndex: "title", key: "title", - render: (content) => ({content}) + render: (content) => ( + {content} + ) }, { title: t("managePrompts.columns.prompt"), dataIndex: "content", key: "content", - render: (content) => ({content}) + render: (content) => ( + {content} + ) }, { title: t("managePrompts.columns.type"), dataIndex: "is_system", key: "is_system", - render: (is_system) => + render: (is_system) => ( {is_system ? ( <> - {t("managePrompts.systemPrompt")} + {" "} + {t("managePrompts.systemPrompt")} ) : ( <> - {t("managePrompts.quickPrompt")} + {" "} + {t("managePrompts.quickPrompt")} )} - }, + + ) + }, { title: t("managePrompts.columns.actions"), render: (_, record) => ( @@ -189,6 +195,29 @@ export const PromptBody = () => { /> )}
+ ) + } + return ( +
+
+ { + setSelectedSegment(value as "custom" | "copilot") + }} + /> +
+ {selectedSegment === "custom" && customPrompts()} Date: Sun, 4 Aug 2024 11:23:29 +0530 Subject: [PATCH 04/14] feat: Add tag colors for custom and copilot prompts in PromptBody component --- src/components/Common/Playground/Message.tsx | 9 +-- src/components/Option/Prompt/index.tsx | 70 +++++++++++++++++++- src/utils/color.ts | 8 +++ 3 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/utils/color.ts diff --git a/src/components/Common/Playground/Message.tsx b/src/components/Common/Playground/Message.tsx index bcdbd23..b21bbe1 100644 --- a/src/components/Common/Playground/Message.tsx +++ b/src/components/Common/Playground/Message.tsx @@ -14,6 +14,7 @@ import { EditMessageForm } from "./EditMessageForm" import { useTranslation } from "react-i18next" import { MessageSource } from "./MessageSource" import { useTTS } from "@/hooks/useTTS" +import { tagColors } from "@/utils/color" type Props = { message: string @@ -37,14 +38,6 @@ type Props = { isTTSEnabled?: boolean } -const tagColors = { - summary: "blue", - explain: "green", - translate: "purple", - custom: "orange", - rephrase: "yellow" -} - export const PlaygroundMessage = (props: Props) => { const [isBtnPressed, setIsBtnPressed] = React.useState(false) const [editMode, setEditMode] = React.useState(false) diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx index f0d1c19..90642b8 100644 --- a/src/components/Option/Prompt/index.tsx +++ b/src/components/Option/Prompt/index.tsx @@ -8,12 +8,15 @@ import { Input, Form, Switch, - Segmented + Segmented, + Tag } from "antd" import { Trash2, Pen, Computer, Zap } from "lucide-react" import { useState } from "react" import { useTranslation } from "react-i18next" import { deletePromptById, getAllPrompts, savePrompt, updatePrompt } from "@/db" +import { getAllCopilotPrompts } from "@/services/application" +import { tagColors } from "@/utils/color" export const PromptBody = () => { const queryClient = useQueryClient() @@ -22,7 +25,7 @@ export const PromptBody = () => { const [editId, setEditId] = useState("") const [createForm] = Form.useForm() const [editForm] = Form.useForm() - const { t } = useTranslation("settings") + const { t } = useTranslation(["settings", "common"]) const [selectedSegment, setSelectedSegment] = useState<"custom" | "copilot">( "custom" ) @@ -31,6 +34,11 @@ export const PromptBody = () => { queryFn: getAllPrompts }) + const { data: copilotData, status: copilotStatus } = useQuery({ + queryKey: ["fetchCopilotPrompts"], + queryFn: getAllCopilotPrompts + }) + const { mutate: deletePrompt } = useMutation({ mutationFn: deletePromptById, onSuccess: () => { @@ -197,6 +205,63 @@ export const PromptBody = () => {
) } + + function copilotPrompts() { + return ( +
+ {copilotStatus === "pending" && } + + {copilotStatus === "success" && ( + ( + + + {t(`common:copilot.${content}`)} + + + ) + }, + { + title: t("managePrompts.columns.prompt"), + dataIndex: "prompt", + key: "prompt", + render: (content) => ( + {content} + ) + }, + { + title: t("managePrompts.columns.actions"), + render: (_, record) => ( +
+ + + +
+ ) + } + ]} + bordered + dataSource={copilotData} + rowKey={(record) => record.key} + /> + )} + + ) + } + return (
@@ -218,6 +283,7 @@ export const PromptBody = () => { />
{selectedSegment === "custom" && customPrompts()} + {selectedSegment === "copilot" && copilotPrompts()} Date: Sun, 4 Aug 2024 16:47:28 +0530 Subject: [PATCH 05/14] feat: Add missing text placeholder in prompt help messages for different locales --- src/assets/locale/en/settings.json | 3 +- src/assets/locale/es/settings.json | 3 +- src/assets/locale/fr/settings.json | 3 +- src/assets/locale/it/settings.json | 3 +- src/assets/locale/ja-JP/settings.json | 3 +- src/assets/locale/ml/settings.json | 3 +- src/assets/locale/pt-BR/settings.json | 3 +- src/assets/locale/ru/settings.json | 3 +- src/assets/locale/zh/settings.json | 3 +- src/components/Option/Prompt/index.tsx | 99 ++++++++++++++++++++++++-- 10 files changed, 113 insertions(+), 13 deletions(-) diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json index c4405fe..91b7711 100644 --- a/src/assets/locale/en/settings.json +++ b/src/assets/locale/en/settings.json @@ -170,7 +170,8 @@ "label": "Prompt", "placeholder": "Enter Prompt", "required": "Please enter a prompt", - "help": "You can use {key} as variable in your prompt." + "help": "You can use {key} as variable in your prompt.", + "missingTextPlaceholder": "The {text} variable is missing in the prompt. Please add it." }, "isSystem": { "label": "Is System Prompt" diff --git a/src/assets/locale/es/settings.json b/src/assets/locale/es/settings.json index 7884f46..2c67192 100644 --- a/src/assets/locale/es/settings.json +++ b/src/assets/locale/es/settings.json @@ -170,7 +170,8 @@ "label": "Prompt", "placeholder": "Ingrese un prompt", "required": "Por favor, ingrese un prompt", - "help": "Puede usar {key} como variable en su prompt." + "help": "Puede usar {key} como variable en su prompt.", + "missingTextPlaceholder": "Falta la variable {text} en el mensaje. Por favor, añádela." }, "isSystem": { "label": "Es un Prompt del Sistema" diff --git a/src/assets/locale/fr/settings.json b/src/assets/locale/fr/settings.json index eaf89af..5e786e4 100644 --- a/src/assets/locale/fr/settings.json +++ b/src/assets/locale/fr/settings.json @@ -170,7 +170,8 @@ "label": "Prompt", "placeholder": "Entrer Prompt", "required": "Veuillez entrer un prompt", - "help": "Vous pouvez utiliser {key} comme variable dans votre prompt." + "help": "Vous pouvez utiliser {key} comme variable dans votre prompt.", + "missingTextPlaceholder": "La variable {text} est manquante dans le message. Veuillez l'ajouter." }, "isSystem": { "label": "Est un prompt système" diff --git a/src/assets/locale/it/settings.json b/src/assets/locale/it/settings.json index 07f236f..a147bb9 100644 --- a/src/assets/locale/it/settings.json +++ b/src/assets/locale/it/settings.json @@ -170,7 +170,8 @@ "label": "Prompt", "placeholder": "Inserisci Prompt", "required": "Scrivi il prompt", - "help": "Puoi usare {key} come variabile nel tuo prompt." + "help": "Puoi usare {key} come variabile nel tuo prompt.", + "missingTextPlaceholder": "La variabile {text} manca nel prompt. Per favore, aggiungila." }, "isSystem": { "label": "Prompt di Sistema" diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json index 546f5fd..6a78efd 100644 --- a/src/assets/locale/ja-JP/settings.json +++ b/src/assets/locale/ja-JP/settings.json @@ -173,7 +173,8 @@ "label": "プロンプト", "placeholder": "プロンプトを入力", "required": "プロンプトを入力してください", - "help": "プロンプト内で{key}を変数として使用できます。" + "help": "プロンプト内で{key}を変数として使用できます。", + "missingTextPlaceholder": "プロンプトに{text}変数がありません。追加してください。" }, "isSystem": { "label": "システムプロンプト" diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json index 9578064..025c706 100644 --- a/src/assets/locale/ml/settings.json +++ b/src/assets/locale/ml/settings.json @@ -173,7 +173,8 @@ "label": "പ്രോംപ്റ്റ്", "placeholder": "പ്രോംപ്റ്റ് നല്കുക", "required": "ദയവായി ഒരു പ്രോംപ്റ്റ് നല്കുക", - "help": "നിങ്ങള്‍ക്ക് {key} എന്ന രീതിയില്‍ പ്രോംപ്റ്റില്‍ വേരിയബിളുകള്‍ ഉപയോഗിക്കാവുന്നതാണ്." + "help": "നിങ്ങള്‍ക്ക് {key} എന്ന രീതിയില്‍ പ്രോംപ്റ്റില്‍ വേരിയബിളുകള്‍ ഉപയോഗിക്കാവുന്നതാണ്.", + "missingTextPlaceholder": "പ്രോംപ്റ്റിൽ {text} വേരിയബിൾ കാണുന്നില്ല. ദയവായി അത് ചേർക്കുക." }, "isSystem": { "label": "സിസ്റ്റം പ്രോംപ്റ്റ് ആണോ" diff --git a/src/assets/locale/pt-BR/settings.json b/src/assets/locale/pt-BR/settings.json index 5de4779..b7d2ae4 100644 --- a/src/assets/locale/pt-BR/settings.json +++ b/src/assets/locale/pt-BR/settings.json @@ -170,7 +170,8 @@ "label": "Prompt", "placeholder": "Digite o Prompt", "required": "Por favor, insira um prompt", - "help": "Você pode usar {key} como variável em seu prompt." + "help": "Você pode usar {key} como variável em seu prompt.", + "missingTextPlaceholder": "A variável {text} está faltando no prompt. Por favor, adicione-a." }, "isSystem": { "label": "É um Prompt do Sistema" diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json index f2f4619..619a03b 100644 --- a/src/assets/locale/ru/settings.json +++ b/src/assets/locale/ru/settings.json @@ -171,7 +171,8 @@ "label": "Подсказка", "placeholder": "Введите подсказку", "required": "Пожалуйста, введите подсказку", - "help": "Вы можете использовать {key} в качестве переменной в своей подсказке." + "help": "Вы можете использовать {key} в качестве переменной в своей подсказке.", + "missingTextPlaceholder": "Переменная {text} отсутствует в подсказке. Пожалуйста, добавьте ее." }, "isSystem": { "label": "Это системная подсказка" diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json index 6cfbad2..78d2e7c 100644 --- a/src/assets/locale/zh/settings.json +++ b/src/assets/locale/zh/settings.json @@ -174,7 +174,8 @@ "label": "提示词", "placeholder": "输入提示词", "required": "请输入提示词", - "help": "您可以在提示词中使用 {key} 作为变量。" + "help": "您可以在提示词中使用 {key} 作为变量。", + "missingTextPlaceholder": "提示中缺少{text}变量。请添加它。" }, "isSystem": { "label": "是否为系统提示词" diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx index 90642b8..fada0fe 100644 --- a/src/components/Option/Prompt/index.tsx +++ b/src/components/Option/Prompt/index.tsx @@ -15,7 +15,10 @@ import { Trash2, Pen, Computer, Zap } from "lucide-react" import { useState } from "react" import { useTranslation } from "react-i18next" import { deletePromptById, getAllPrompts, savePrompt, updatePrompt } from "@/db" -import { getAllCopilotPrompts } from "@/services/application" +import { + getAllCopilotPrompts, + setAllCopilotPrompts +} from "@/services/application" import { tagColors } from "@/utils/color" export const PromptBody = () => { @@ -29,6 +32,11 @@ export const PromptBody = () => { const [selectedSegment, setSelectedSegment] = useState<"custom" | "copilot">( "custom" ) + + const [openCopilotEdit, setOpenCopilotEdit] = useState(false) + const [editCopilotId, setEditCopilotId] = useState("") + const [editCopilotForm] = Form.useForm() + const { data, status } = useQuery({ queryKey: ["fetchAllPrompts"], queryFn: getAllPrompts @@ -109,6 +117,36 @@ export const PromptBody = () => { } }) + const { mutate: updateCopilotPrompt, isPending: isUpdatingCopilotPrompt } = + useMutation({ + mutationFn: async (data: any) => { + return await setAllCopilotPrompts([ + { + key: data.key, + prompt: data.prompt + } + ]) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["fetchCopilotPrompts"] + }) + setOpenCopilotEdit(false) + editCopilotForm.resetFields() + notification.success({ + message: t("managePrompts.notification.updatedSuccess"), + description: t("managePrompts.notification.updatedSuccessDesc") + }) + }, + onError: (error) => { + notification.error({ + message: t("managePrompts.notification.error"), + description: + error?.message || t("managePrompts.notification.someError") + }) + } + }) + function customPrompts() { return (
@@ -241,9 +279,9 @@ export const PromptBody = () => { + + +
) } From 6c44fcfca75eea8bc94b14d9b40a7ba2f9c8cdf5 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 4 Aug 2024 18:03:51 +0530 Subject: [PATCH 06/14] feat: Remove 'managePrompts.columns.actions' title from PromptBody component --- src/components/Option/Prompt/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx index fada0fe..d976842 100644 --- a/src/components/Option/Prompt/index.tsx +++ b/src/components/Option/Prompt/index.tsx @@ -273,7 +273,6 @@ export const PromptBody = () => { ) }, { - title: t("managePrompts.columns.actions"), render: (_, record) => (
From 36fc8b6be1b958809af0b06c4941db53c3e80bbc Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 4 Aug 2024 18:12:09 +0530 Subject: [PATCH 07/14] feat: Add segmented control for custom and copilot prompts in PromptBody component --- src/assets/locale/en/settings.json | 4 ++++ src/assets/locale/es/settings.json | 4 ++++ src/assets/locale/fr/settings.json | 4 ++++ src/assets/locale/it/settings.json | 4 ++++ src/assets/locale/ja-JP/settings.json | 4 ++++ src/assets/locale/ml/settings.json | 4 ++++ src/assets/locale/pt-BR/settings.json | 4 ++++ src/assets/locale/ru/settings.json | 4 ++++ src/assets/locale/zh/settings.json | 4 ++++ src/components/Option/Prompt/index.tsx | 8 ++++++-- 10 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json index 91b7711..d88c537 100644 --- a/src/assets/locale/en/settings.json +++ b/src/assets/locale/en/settings.json @@ -141,6 +141,10 @@ "option1": "Normal", "option2": "RAG", "questionPrompt": "Question Prompt", + "segmented": { + "custom": "Custom Prompts", + "copilot": "Copilot Prompts" + }, "columns": { "title": "Title", "prompt": "Prompt", diff --git a/src/assets/locale/es/settings.json b/src/assets/locale/es/settings.json index 2c67192..8f1f0de 100644 --- a/src/assets/locale/es/settings.json +++ b/src/assets/locale/es/settings.json @@ -147,6 +147,10 @@ "type": "Tipo de Prompt", "actions": "Acciones" }, + "segmented": { + "custom": "Invites personnalisées", + "copilot": "Invites Copilot" + }, "systemPrompt": "Prompt del Sistema", "quickPrompt": "Prompt Rápido", "tooltip": { diff --git a/src/assets/locale/fr/settings.json b/src/assets/locale/fr/settings.json index 5e786e4..7abcc46 100644 --- a/src/assets/locale/fr/settings.json +++ b/src/assets/locale/fr/settings.json @@ -160,6 +160,10 @@ "addTitle": "Ajouter un nouveau prompt", "editTitle": "Modifier le prompt" }, + "segmented": { + "custom": "Invites personnalisées", + "copilot": "Invites Copilot" + }, "form": { "title": { "label": "Titre", diff --git a/src/assets/locale/it/settings.json b/src/assets/locale/it/settings.json index a147bb9..de5b1d6 100644 --- a/src/assets/locale/it/settings.json +++ b/src/assets/locale/it/settings.json @@ -160,6 +160,10 @@ "addTitle": "Aggiungi Nuovo Prompt", "editTitle": "Modifica Prompt" }, + "segmented": { + "custom": "Prompt personalizzati", + "copilot": "Prompt Copilot" + }, "form": { "title": { "label": "Titolo", diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json index 6a78efd..4521d8d 100644 --- a/src/assets/locale/ja-JP/settings.json +++ b/src/assets/locale/ja-JP/settings.json @@ -163,6 +163,10 @@ "addTitle": "新しいプロンプトを追加", "editTitle": "プロンプトを編集" }, + "segmented": { + "custom": "カスタムプロンプト", + "copilot": "Copilotプロンプト" + }, "form": { "title": { "label": "タイトル", diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json index 025c706..979aabb 100644 --- a/src/assets/locale/ml/settings.json +++ b/src/assets/locale/ml/settings.json @@ -163,6 +163,10 @@ "addTitle": "പുതിയ പ്രോംപ്റ്റ് ചേര്‍ക്കുക", "editTitle": "പ്രോംപ്റ്റ് എഡിറ്റുചെയ്യുക" }, + "segmented": { + "custom": "കസ്റ്റം പ്രോംപ്റ്റുകൾ", + "copilot": "കോപൈലറ്റ് പ്രോംപ്റ്റുകൾ" + }, "form": { "title": { "label": "തലക്കെട്ട്", diff --git a/src/assets/locale/pt-BR/settings.json b/src/assets/locale/pt-BR/settings.json index b7d2ae4..b5be879 100644 --- a/src/assets/locale/pt-BR/settings.json +++ b/src/assets/locale/pt-BR/settings.json @@ -160,6 +160,10 @@ "addTitle": "Adicionar Novo Prompt", "editTitle": "Editar Prompt" }, + "segmented": { + "custom": "Prompts personalizados", + "copilot": "Prompts do Copilot" + }, "form": { "title": { "label": "Título", diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json index 619a03b..42baef9 100644 --- a/src/assets/locale/ru/settings.json +++ b/src/assets/locale/ru/settings.json @@ -161,6 +161,10 @@ "addTitle": "Добавить новую подсказку", "editTitle": "Редактировать подсказку" }, + "segmented": { + "custom": "Пользовательские подсказки", + "copilot": "Подсказки Copilot" + }, "form": { "title": { "label": "Название", diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json index 78d2e7c..bcb5d18 100644 --- a/src/assets/locale/zh/settings.json +++ b/src/assets/locale/zh/settings.json @@ -163,6 +163,10 @@ "addTitle": "添加新提示词", "editTitle": "编辑提示词" }, + "segmented": { + "custom": "自定义提示", + "copilot": "Copilot 提示" + }, "form": { "title": { "label": "标题", diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx index d976842..56e4362 100644 --- a/src/components/Option/Prompt/index.tsx +++ b/src/components/Option/Prompt/index.tsx @@ -306,11 +306,15 @@ export const PromptBody = () => { size="large" options={[ { - label: "Custom Prompts", + label: t( + "managePrompts.segmented.custom" + ), value: "custom" }, { - label: "Copilot Prompts", + label: t( + "managePrompts.segmented.copilot" + ), value: "copilot" } ]} From 48a4af50ee8696a5917199cfd0110d30fa51c982 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 4 Aug 2024 18:40:48 +0530 Subject: [PATCH 08/14] feat: Add context menu options for summarizing, rephrasing, translating, and explaining text selections in background.ts --- src/entries/background.ts | 55 +++++++++++++++++++++++++++++++++++++ src/services/application.ts | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/entries/background.ts b/src/entries/background.ts index 34f6679..1f0de4b 100644 --- a/src/entries/background.ts +++ b/src/entries/background.ts @@ -194,6 +194,61 @@ export default defineBackground({ browser.tabs.create({ url: browser.runtime.getURL("/options.html") }) + } else if (info.menuItemId === "summarize-pa") { + if (!isCopilotRunning) { + browser.sidebarAction.toggle() + } + setTimeout(async () => { + await browser.runtime.sendMessage({ + from: "background", + type: "summary", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "rephrase-pa") { + if (!isCopilotRunning) { + browser.sidebarAction.toggle() + } + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "rephrase", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "translate-pg") { + if (!isCopilotRunning) { + browser.sidebarAction.toggle() + } + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "translate", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "explain-pa") { + if (!isCopilotRunning) { + browser.sidebarAction.toggle() + } + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "explain", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) + } else if (info.menuItemId === "custom-pg") { + if (!isCopilotRunning) { + browser.sidebarAction.toggle() + } + setTimeout(async () => { + await browser.runtime.sendMessage({ + type: "custom", + from: "background", + text: info.selectionText + }) + }, isCopilotRunning ? 0 : 5000) } }) diff --git a/src/services/application.ts b/src/services/application.ts index 6543618..63a860d 100644 --- a/src/services/application.ts +++ b/src/services/application.ts @@ -23,7 +23,7 @@ Ensure that your rephrased version conveys the same information and intent as th Response:` -const DEFAULT_TRANSLATE_PROMPT = `Translate the following text from its original language into "english"0. Maintain the tone and style of the original text as much as possible: +const DEFAULT_TRANSLATE_PROMPT = `Translate the following text from its original language into "english". Maintain the tone and style of the original text as much as possible: Text: --------- From eccf8f8a883fba13eb5f018d925411440947e049 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Mon, 5 Aug 2024 00:49:27 +0530 Subject: [PATCH 09/14] feat: Enable title generation in GeneralSettings component --- .../Option/Settings/general-settings.tsx | 15 ++++ src/hooks/chat-helper/index.ts | 9 ++- src/models/index.ts | 12 ++-- src/services/title.ts | 72 +++++++++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/services/title.ts diff --git a/src/components/Option/Settings/general-settings.tsx b/src/components/Option/Settings/general-settings.tsx index 21f622a..c37b551 100644 --- a/src/components/Option/Settings/general-settings.tsx +++ b/src/components/Option/Settings/general-settings.tsx @@ -29,6 +29,8 @@ export const GeneralSettings = () => { false ) + const [generateTitle, setGenerateTitle] = useStorage("titleGenEnabled", false) + const [hideCurrentChatModelSettings, setHideCurrentChatModelSettings] = useStorage("hideCurrentChatModelSettings", false) @@ -141,6 +143,19 @@ export const GeneralSettings = () => { />
+
+
+ + {t("generalSettings.settings.generateTitle.label")} + +
+ + setGenerateTitle(checked)} + /> +
+
{t("generalSettings.settings.darkMode.label")} diff --git a/src/hooks/chat-helper/index.ts b/src/hooks/chat-helper/index.ts index f2426ee..18f89be 100644 --- a/src/hooks/chat-helper/index.ts +++ b/src/hooks/chat-helper/index.ts @@ -1,5 +1,6 @@ import { saveHistory, saveMessage } from "@/db" import { setLastUsedChatModel } from "@/services/model-settings" +import { generateTitle } from "@/services/title" import { ChatHistory } from "@/store/option" export const saveMessageOnError = async ({ @@ -14,7 +15,7 @@ export const saveMessageOnError = async ({ setHistoryId, isRegenerating, message_source = "web-ui", - message_type + message_type }: { e: any setHistory: (history: ChatHistory) => void @@ -73,7 +74,8 @@ export const saveMessageOnError = async ({ ) await setLastUsedChatModel(historyId, selectedModel) } else { - const newHistoryId = await saveHistory(userMessage, false, message_source) + const title = await generateTitle(selectedModel, userMessage, userMessage) + const newHistoryId = await saveHistory(title, false, message_source) if (!isRegenerating) { await saveMessage( newHistoryId.id, @@ -154,7 +156,8 @@ export const saveMessageOnSuccess = async ({ ) await setLastUsedChatModel(historyId, selectedModel!) } else { - const newHistoryId = await saveHistory(message, false, message_source) + const title = await generateTitle(selectedModel, message, message) + const newHistoryId = await saveHistory(title, false, message_source) await saveMessage( newHistoryId.id, selectedModel, diff --git a/src/models/index.ts b/src/models/index.ts index d627a5d..4048cb3 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -13,12 +13,12 @@ export const pageAssistModel = async ({ }: { model: string baseUrl: string - keepAlive: string - temperature: number - topK: number - topP: number - numCtx: number - seed: number + keepAlive?: string + temperature?: number + topK?: number + topP?: number + numCtx?: number + seed?: number }) => { switch (model) { case "chrome::gemini-nano::page-assist": diff --git a/src/services/title.ts b/src/services/title.ts new file mode 100644 index 0000000..c7a9cd4 --- /dev/null +++ b/src/services/title.ts @@ -0,0 +1,72 @@ +import { pageAssistModel } from "@/models" +import { Storage } from "@plasmohq/storage" +import { getOllamaURL } from "./ollama" +import { cleanUrl } from "@/libs/clean-url" +import { HumanMessage } from "langchain/schema" +const storage = new Storage() + +// this prompt is copied from the OpenWebUI codebase +export const DEFAULT_TITLE_GEN_PROMPT = `Here is the query: + +-------------- + +{{query}} + +-------------- + +Create a concise, 3-5 word phrase as a title for the previous query. Suitable emojis for the summary can be used to enhance understanding. Avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT. ANSWER USING THE SAME LANGUAGE AS THE QUERY. + + +Examples of titles: + +Stellar Achievement Celebration +Family Bonding Activities +🇫🇷 Voyage à Paris +🍜 Receta de Ramen Casero +Shakespeare Analyse Literarische +日本の春祭り体験 +Древнегреческая Философия Обзор + +Response:` + + +export const isTitleGenEnabled = async () => { + const enabled = await storage.get("titleGenEnabled") + return enabled ?? false +} + +export const setTitleGenEnabled = async (enabled: boolean) => { + await storage.set("titleGenEnabled", enabled) +} + + +export const generateTitle = async (model: string, query: string, fallBackTitle: string) => { + + const isEnabled = await isTitleGenEnabled() + + if (!isEnabled) { + return fallBackTitle + } + + try { + const url = await getOllamaURL() + + const titleModel = await pageAssistModel({ + baseUrl: cleanUrl(url), + model + }) + + const prompt = DEFAULT_TITLE_GEN_PROMPT.replace("{{query}}", query) + + const title = await titleModel.invoke([ + new HumanMessage({ + content: prompt + }) + ]) + + return title.content.toString() + } catch (error) { + console.log(`Error generating title: ${error}`) + return fallBackTitle + } +} \ No newline at end of file From f1214f314dbe34db8181c11b27bb0367bd16b798 Mon Sep 17 00:00:00 2001 From: Ased Mammad Date: Tue, 6 Aug 2024 11:49:36 +0330 Subject: [PATCH 10/14] i18n: Add persian (farsi) --- src/assets/locale/fa/chrome.json | 13 ++ src/assets/locale/fa/common.json | 88 +++++++ src/assets/locale/fa/knowledge.json | 43 ++++ src/assets/locale/fa/option.json | 12 + src/assets/locale/fa/playground.json | 29 +++ src/assets/locale/fa/settings.json | 328 +++++++++++++++++++++++++++ src/assets/locale/fa/sidepanel.json | 7 + src/entries/options/App.tsx | 9 + src/entries/sidepanel/App.tsx | 8 + src/i18n/index.ts | 5 +- src/i18n/lang/fa.ts | 17 ++ src/i18n/support-language.ts | 4 + src/public/_locales/fa/messages.json | 14 ++ 13 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 src/assets/locale/fa/chrome.json create mode 100644 src/assets/locale/fa/common.json create mode 100644 src/assets/locale/fa/knowledge.json create mode 100644 src/assets/locale/fa/option.json create mode 100644 src/assets/locale/fa/playground.json create mode 100644 src/assets/locale/fa/settings.json create mode 100644 src/assets/locale/fa/sidepanel.json create mode 100644 src/i18n/lang/fa.ts create mode 100644 src/public/_locales/fa/messages.json diff --git a/src/assets/locale/fa/chrome.json b/src/assets/locale/fa/chrome.json new file mode 100644 index 0000000..307388b --- /dev/null +++ b/src/assets/locale/fa/chrome.json @@ -0,0 +1,13 @@ +{ + "heading": "تنظیمات AI کروم", + "status": { + "label": "فعال یا غیر فعال کردن پشتیبانی از AI کروم" + }, + "error": { + "browser_not_supported": "این نسخه از کروم توسط مدل Gemini Nano پشتیبانی نمی شود. لطفا به نسخه ۱۲۷ یا بالاتر به روزرسانی کنید.", + "ai_not_supported": "تنظیم chrome://flags/#prompt-api-for-gemini-nano فعال نشده است. لطفا فعالش کنید..", + "ai_not_ready": "Gemini Nano هنوز آماده نیست; تنظیمات کروم را مجددا بررسی کنید.", + "internal_error": "یک خطای داخلی رخ داد. لطفا بعدا مجددا تلاش نمایید." + }, + "errorDescription": "برای استفاده از AI کروم، به نسخه مرورگر بالاتر از ۱۲۷ احتیاج دارید که اکنون در کانال های Dev و Canary در دسترس است. بعد از دریافت نسخه پشتیبانی شده این گام ها را دنبال کنید: \n\n۱. به `chrome://flags/#prompt-api-for-gemini-nano` بروید و \"Enable\" را انتخاب کنید.\n۲. به `chrome://flags/#optimization-guide-on-device-model` بروید و \"EnabledBypassPrefRequirement\" را انتخاب کنید.\n۳. به `chrome://components` بروید و \"Optimization Guide On Device Model\" را جستجو کنید، بر روی \"Check for Update\" کلیک کنید. این کار مدل را دریافت خواهد کرد. اگر این تنظیمات را نمی بینید، گام های ۱ و ۲ را تکرار و مرورگر را مجددا اجرا کنید." +} diff --git a/src/assets/locale/fa/common.json b/src/assets/locale/fa/common.json new file mode 100644 index 0000000..3c02c21 --- /dev/null +++ b/src/assets/locale/fa/common.json @@ -0,0 +1,88 @@ +{ + "pageAssist": "دستیار صفحه", + "selectAModel": "یک مدل انتخاب کنید", + "save": "ذخیره", + "saved": "ذخیره شد", + "cancel": "لغو", + "retry": "تلاش مجدد", + "share": { + "tooltip": { + "share": "اشتراک گذاری" + }, + "modal": { + "title": "اشتراک گذاری لینک به گپ" + }, + "form": { + "defaultValue": { + "name": "ناشناس", + "title": "گپ بی نام" + }, + "title": { + "label": "عنوان گپ", + "placeholder": "عنوان گپ را وارد کنید", + "required": "وارد کردن عنوان چت الزامی است" + }, + "name": { + "label": "نام شما", + "placeholder": "نام خود را وارد کنید", + "required": "وارد کردن نام الزامی است" + }, + "btn": { + "save": "ایجاد لینک", + "saving": "در حال ایجاد لینک..." + } + }, + "notification": { + "successGenerate": "پیوند در کلیپ بورد کپی شد", + "failGenerate": "پیوند ایجاد نشد" + } + }, + "copyToClipboard": "کپی به کلیپ بورد", + "webSearch": "جستجوی وب", + "regenerate": "ایجاد مجدد", + "edit": "ویرایش", + "saveAndSubmit": "ذخیره و ارسال", + "editMessage": { + "placeholder": "یک پیام وارد کنید..." + }, + "submit": "ارسال", + "noData": "اطلاعاتی وجود ندارد", + "noHistory": "تاریخچه گپ وجود ندارد", + "chatWithCurrentPage": "گپ زدن با این صفحه", + "beta": "بتا", + "tts": "بلند بخوان", + "currentChatModelSettings": "تنظیمات مدل گپ فعلی", + "modelSettings": { + "label": "تنظیمات مدل", + "description": "گزینه های مدل را به صورت کلی برای همه گپ ها تنظیم کنید", + "form": { + "keepAlive": { + "label": "Keep Alive", + "help": "کنترل می کند که چه مدت مدل پس از درخواست در حافظه باقی می ماند (پیش فرض: 5 دقیقه)", + "placeholder": "مدت زمان Keep Alive را وارد کنید (مثلا 5m, 10m, 1h)" + }, + "temperature": { + "label": "Temperature", + "placeholder": "مقدار Temperature را وارد کنید (مثلا 0.7, 1.0)" + }, + "numCtx": { + "label": "Number of Contexts", + "placeholder": "مقدار Number of Contexts را وارد کنید (پیش فرض: 2048)" + }, + "seed": { + "label": "Seed", + "placeholder": "مقدار Seed را وارد کنید (e.g. 1234)", + "help": "تکرارپذیری خروجی مدل" + }, + "topK": { + "label": "Top K", + "placeholder": "مقدار Top K را وارد کنید (مثلا 40, 100)" + }, + "topP": { + "label": "Top P", + "placeholder": "مقدار Top P را وارد کنید (مثلا 0.9, 0.95)" + } + }, + "advanced": "تنظیمات بیشتر مدل" + } +} diff --git a/src/assets/locale/fa/knowledge.json b/src/assets/locale/fa/knowledge.json new file mode 100644 index 0000000..c955226 --- /dev/null +++ b/src/assets/locale/fa/knowledge.json @@ -0,0 +1,43 @@ +{ + "addBtn": "افزودن دانش جدید", + "columns": { + "title": "عنوان", + "status": "وضعیت", + "embeddings": "مدل جاسازی", + "createdAt": "ایجاد شده در", + "action": "اقدامات" + }, + "expandedColumns": { + "name": "نام" + }, + "tooltip": { + "delete": "حذف" + }, + "confirm": { + "delete": "آیا مطمئن هستید که می خواهید این دانش را حذف کنید؟" + }, + "deleteSuccess": "دانش با موفقیت حذف شد", + "status": { + "pending": "انتظار", + "finished": "تمام", + "processing": "در حال پردازش", + "failed": "ناموفق" + }, + "addKnowledge": "دانش را اضافه کنید", + "form": { + "title": { + "label": "عنوان دانش", + "placeholder": "عنوان دانش را وارد کنید", + "required": "وارد کردن عنوان دانش الزامی است" + }, + "uploadFile": { + "label": "آپلود فایل", + "uploadText": "یک فایل را در اینجا بکشید و رها کنید یا برای آپلود کلیک کنید", + "uploadHint": "انواع فایل های پشتیبانی شده: .pdf, .csv, .txt, .md, .docx", + "required": "وارد کردن فایل مورد نیاز است" + }, + "submit": "ارسال", + "success": "دانش با موفقیت اضافه شد" + }, + "noEmbeddingModel": "لطفا ابتدا یک مدل جاسازی را از صفحه تنظیمات RAG اضافه کنید" +} diff --git a/src/assets/locale/fa/option.json b/src/assets/locale/fa/option.json new file mode 100644 index 0000000..9188710 --- /dev/null +++ b/src/assets/locale/fa/option.json @@ -0,0 +1,12 @@ +{ + "newChat": "گپ جدید", + "selectAPrompt": "یک پرامپت را انتخاب کنید", + "githubRepository": "مخزن GitHub", + "settings": "تنظیمات", + "sidebarTitle": "تاریخچه گپ", + "error": "خطا", + "somethingWentWrong": "مشکلی پیش آمد", + "validationSelectModel": "لطفا یک مدل را برای ادامه انتخاب کنید", + "deleteHistoryConfirmation": "آیا مطمئن هستید که می خواهید این تاریخچه را حذف کنید؟", + "editHistoryTitle": "یک عنوان جدید وارد کنید" +} diff --git a/src/assets/locale/fa/playground.json b/src/assets/locale/fa/playground.json new file mode 100644 index 0000000..e9161a7 --- /dev/null +++ b/src/assets/locale/fa/playground.json @@ -0,0 +1,29 @@ +{ + "ollamaState": { + "searching": "در حال جستجوی Ollama شما 🦙", + "running": "Ollama در حال اجرا است 🦙", + "notRunning": "امکان اتصال به Ollama وجود ندارد 🦙", + "connectionError": "به نظر می رسد که خطای اتصال دارید. لطفا برای عیب یابی به این مستندات مراجعه کنید." + }, + "formError": { + "noModel": "لطفا یک مدل را انتخاب کنید", + "noEmbeddingModel": "لطفا یک مدل جاسازی در صفحه تنظیمات > RAG تنظیم کنید" + }, + "form": { + "textarea": { + "placeholder": "یک پیام تایپ کنید..." + }, + "webSearch": { + "on": "روشن", + "off": "خاموش" + } + }, + "tooltip": { + "searchInternet": "جستجوی اینترنت", + "speechToText": "گفتار به متن", + "uploadImage": "آپلود تصویر", + "stopStreaming": "توقف Streaming", + "knowledge": "دانش" + }, + "sendWhenEnter": "با فشار دادن Enter ارسال شود" +} diff --git a/src/assets/locale/fa/settings.json b/src/assets/locale/fa/settings.json new file mode 100644 index 0000000..0cbafdc --- /dev/null +++ b/src/assets/locale/fa/settings.json @@ -0,0 +1,328 @@ +{ + "generalSettings": { + "title": "تنظیمات عمومی", + "settings": { + "heading": "تنظیمات رابط کاربری وب", + "speechRecognitionLang": { + "label": "زبان تشخیص گفتار", + "placeholder": "یک زبان را انتخاب کنید" + }, + "language": { + "label": "زبان", + "placeholder": "یک زبان را انتخاب کنید" + }, + "darkMode": { + "label": "تغییر تم", + "options": { + "light": "روشن", + "dark": "تیره" + } + }, + "copilotResumeLastChat": { + "label": "آخرین گفتگو را هنگام باز کردن SidePanel (Copilot) از سر بگیر" + }, + "hideCurrentChatModelSettings": { + "label": "مخفی کردن تنظیمات مدل گپ فعلی را" + }, + "restoreLastChatModel": { + "label": "بازیابی آخرین مدل استفاده شده برای گپ‌های قبلی" + }, + "sendNotificationAfterIndexing": { + "label": "ارسال نوتیفیکیشن پس از اتمام پردازش پایگاه دانش" + } + }, + "sidepanelRag": { + "heading": "تنظیمات گپ Copilot با وب سایت", + "ragEnabled": { + "label": "چت با وب سایت با استفاده از بردارهای جاسازی" + }, + "maxWebsiteContext": { + "label": "اندازه محتوای وب سایت حالت عادی", + "placeholder": "اندازه محتوا (پیش فرض 4028)" + } + }, + "webSearch": { + "heading": "مدیریت جستجوی وب", + "searchMode": { + "label": "انجام جستجوی ساده اینترنتی" + }, + "provider": { + "label": "موتور جستجو", + "placeholder": "یک موتور جستجو را انتخاب کنید" + }, + "totalSearchResults": { + "label": "مجموع نتایج جستجو", + "placeholder": "کل نتایج جستجو را وارد کنید" + }, + "visitSpecificWebsite": { + "label": "مراجعه به وب سایت ذکر شده در پیام" + } + }, + "system": { + "heading": "تنظیمات سیستم", + "deleteChatHistory": { + "label": "حذف تاریخچه گفتگو", + "button": "حذف", + "confirm": "آیا مطمئن هستید که می خواهید تاریخچه گفتگوهای خود را حذف کنید؟ این عمل قابل برگشت نیست." + }, + "export": { + "label": "تاریخچه گپ، پایگاه دانش و پرامپت‌ها را اکسپورت کنید", + "button": "اکسپورت داده‌ها", + "success": "موفقیت در اکسپورت" + }, + "import": { + "label": "تاریخچه گپ، پایگاه دانش و پرامپت‌ها را ایمپورت کنید", + "button": "ایمپورت داده‌ها", + "success": "موفقیت در ایمپورت", + "error": "خطا در ایمپورت" + } + }, + "tts": { + "heading": "تنظیمات تبدیل متن به گفتار", + "ttsEnabled": { + "label": "فعال کردن تبدیل متن به گفتار" + }, + "ttsProvider": { + "label": "ارائه دهنده تبدیل متن به گفتار", + "placeholder": "یک ارائه دهنده را انتخاب کنید" + }, + "ttsVoice": { + "label": "صدای تبدیل متن به گفتار", + "placeholder": "صدا را انتخاب کنید" + }, + "ssmlEnabled": { + "label": "فعال کردن SSML (Speech Synthesis Markup Language)" + } + } + }, + "manageModels": { + "title": "مدیریت مدل‌ها", + "addBtn": "افزودن مدل جدید", + "columns": { + "name": "نام", + "digest": "هضم", + "modifiedAt": "اصلاح شده در", + "size": "اندازه", + "actions": "اقدامات" + }, + "expandedColumns": { + "parentModel": "مدل والد", + "format": "فرمت", + "family": "خانواده", + "parameterSize": "اندازه پارامترها", + "quantizationLevel": "سطح کوانتیزاسیون" + }, + "tooltip": { + "delete": "حذف مدل", + "repull": "دریافت دوباره مدل" + }, + "confirm": { + "delete": "آیا مطمئن هستید که می خواهید این مدل را حذف کنید؟", + "repull": "آیا مطمئن هستید که می خواهید این مدل را دوباره دریافت کنید؟" + }, + "modal": { + "title": "افزودن مدل جدید", + "placeholder": "نام مدل را وارد کنید", + "pull": "دریافت مدل" + }, + "notification": { + "pullModel": "در حال دریافت مدل", + "pullModelDescription": "در حال دریافت مدل {{modelName}}. برای جزئیات بیشتر، آیکون افزونه را بررسی کنید.", + "success": "موفقیت", + "error": "خطا", + "successDescription": "مدل با موفقیت دریافت شد", + "successDeleteDescription": "مدل با موفقیت حذف شد", + "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید" + } + }, + "managePrompts": { + "title": "مدیریت پرامپت‌ها", + "addBtn": "اضافه کردن پرامپت جدید", + "option1": "عادی", + "option2": "RAG", + "questionPrompt": "سوال پرامپت", + "columns": { + "title": "عنولن", + "prompt": "پرامپت", + "type": "نوع پرامپت", + "actions": "اقدامات" + }, + "systemPrompt": "پرامپت سیستم ", + "quickPrompt": "پرامپت سریع", + "tooltip": { + "delete": "پاک کردن پرامپت", + "edit": "ویرایش پرامپت" + }, + "confirm": { + "delete": "آیا مطمئن هستید که می خواهید این پرامپت را حذف کنید؟ این عمل قابل برگشت نیست." + }, + "modal": { + "addTitle": "اضافه کردن پرامپت جدید", + "editTitle": "ویرایش پرامپت" + }, + "form": { + "title": { + "label": "عنوان", + "placeholder": "پرامپت عالی من", + "required": "لطفا یک عنوان وارد کنید" + }, + "prompt": { + "label": "پرامپت", + "placeholder": "پرامپت وارد کنید", + "required": "لطفا یک پرامپت وارد کنید", + "help": "می توانید از {key} به عنوان متغیر در درخواست خود استفاده کنید." + }, + "isSystem": { + "label": "یک پرامپت سیستمی باشد" + }, + "btnSave": { + "saving": "در حال اضافه کردن پرامپت...", + "save": "اضافه کردن پرامپت" + }, + "btnEdit": { + "saving": "در حال به روزرسانی پرامپت...", + "save": "به روزرسانی پرامپت" + } + }, + "notification": { + "addSuccess": "پرامپت اضافه شد", + "addSuccessDesc": "پرامپت با موفقیت اضافه شد", + "error": "خطا", + "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید", + "updatedSuccess": "پرامپت به روز شد", + "updatedSuccessDesc": "پرامپت با موفقیت به روز شد", + "deletedSuccess": "پرامپت حذف شد", + "deletedSuccessDesc": "پرامپت با موفقیت حذف شد" + } + }, + "manageShare": { + "title": "مدیریت اشتراک گذاری", + "heading": "پیکربندی URL اشتراک گذاری صفحه", + "form": { + "url": { + "label": "آدرس اشتراک گذاری صفحه", + "placeholder": "URL اشتراک گذاری صفحه را وارد کنید", + "required": "لطفا URL اشتراک گذاری صفحه خود را وارد کنید!", + "help": "به دلایل حفظ حریم خصوصی، می توانید اشتراک گذاری صفحه را خودتان میزبانی کنید و URL را در اینجا ارائه دهید. بیشتر بدانید." + } + }, + "webshare": { + "heading": "اشتراک گذاری وب", + "columns": { + "title": "عنوان", + "url": "URL", + "actions": "اقدامات" + }, + "tooltip": { + "delete": "حذف اشتراک گذاری" + }, + "confirm": { + "delete": "آیا مطمئن هستید که می خواهید این اشتراک گذاری را حذف کنید؟ این عمل قابل برگشت نیست." + }, + "label": "مدیریت اشتراک گذاری صفحه", + "description": "ویژگی اشتراک گذاری صفحه را فعال یا غیرفعال کنید" + }, + "notification": { + "pageShareSuccess": "URL اشتراک گذاری صفحه با موفقیت به روز شد", + "someError": "مشکلی پیش آمد. لطفا بعدا دوباره امتحان کنید", + "webShareDeleteSuccess": "اشتراک گذاری وب با موفقیت حذف شد" + } + }, + "ollamaSettings": { + "title": "تنظیمات Ollama", + "heading": "پیکربندی Ollama", + "settings": { + "ollamaUrl": { + "label": "آدرس Ollama", + "placeholder": "URL Ollama را وارد کنید" + }, + "advanced": { + "label": "پیکربندی پیشرفته URL Ollama", + "urlRewriteEnabled": { + "label": "URL مبدا سفارشی را فعال یا غیرفعال کنید" + }, + "rewriteUrl": { + "label": "URL مبدا سفارشی", + "placeholder": "URL مبدا سفارشی را وارد کنید" + }, + "headers": { + "label": "هدرهای سفارشی", + "add": "افزودن هدر", + "key": { + "label": "کلید هدر", + "placeholder": "Authorization" + }, + "value": { + "label": "مقدار هدر", + "placeholder": "Bearer token" + } + }, + "help": "اگر با Ollama در Page Assist مشکل اتصال دارید، می توانید یک URL اصلی سفارشی پیکربندی کنید. برای کسب اطلاعات بیشتر در مورد پیکربندی، اینجا را کلیک کنید." + } + } + }, + "manageSearch": { + "title": "مدیریت جستجوی وب", + "heading": "پیکربندی جستجوی وب" + }, + "about": { + "title": "درباره", + "heading": "درباره", + "chromeVersion": "نسخه دستیار صفحه", + "ollamaVersion": "نسخه Ollama", + "support": "شما می توانید با کمک مالی یا حمایت مالی از طریق پلتفرم های زیر از پروژه Page Assist حمایت کنید:", + "koFi": "پشتیبانی در Ko-fi", + "githubSponsor": "حمایت مالی در GitHub", + "githubRepo": "مخزن GitHub" + }, + "manageKnowledge": { + "title": "مدیریت دانش", + "heading": "پیکربندی پایگاه دانش" + }, + "rag": { + "title": "تنظیمات RAG", + "ragSettings": { + "label": "تنظیمات RAG", + "model": { + "label": "مدل جاسازی", + "required": "لطفا یک مدل را انتخاب کنید", + "help": "به شدت توصیه می شود از مدل های جاسازی مانند `nomic-embed-text` استفاده کنید.", + "placeholder": "یک مدل را انتخاب کنید" + }, + "chunkSize": { + "label": "اندازه تکه", + "placeholder": "اندازه تکه را وارد کنید", + "required": "لطفا اندازه تکه را وارد کنید" + }, + "chunkOverlap": { + "label": "همپوشانی تکه", + "placeholder": "داخل تکه همپوشانی شوید", + "required": "لطفا یک همپوشانی تکه ای وارد کنید" + }, + "totalFilePerKB": { + "label": "محدودیت فایل پیش فرض پایگاه دانش", + "placeholder": "محدودیت فایل پیش فرض را وارد کنید (به عنوان مثال، 10)", + "required": "لطفا محدودیت پیش فرض فایل را وارد کنید" + } + }, + "prompt": { + "label": "پیکربندی پرامپت RAG", + "option1": "عادی", + "option2": "وب", + "alert": "پیکربندی اعلان سیستم در اینجا منسوخ شده است. لطفا از بخش مدیریت پرامپت‌ها برای افزودن یا ویرایش درخواست‌ها استفاده کنید. این بخش در نسخه بعدی حذف خواهد شد", + "systemPrompt": "پرامپت سیستم", + "systemPromptPlaceholder": "پرامپت سیستم را وارد کنید", + "webSearchPrompt": "پرامپت جستجوی وب", + "webSearchPromptHelp": "«{search_results}» را از پرامپت حذف نکنید.", + "webSearchPromptError": "لطفا یک پرامپت جستجوی وب وارد کنید", + "webSearchPromptPlaceholder": "پرامپت جستجوی وب را وارد کنید", + "webSearchFollowUpPrompt": "پرامپت پیگیری جستجوی وب", + "webSearchFollowUpPromptHelp": "«{chat_history}» و «{question}» را از پرامپت حذف نکنید.", + "webSearchFollowUpPromptError": "لطفا پرامپت پیگیری جستجوی وب خود را وارد کنید!", + "webSearchFollowUpPromptPlaceholder": "پرامپت پیگیری جستجوی وب شما" + } + }, + "chromeAiSettings": { + "title": "تنظیمات هوش مصنوعی کروم" + } +} diff --git a/src/assets/locale/fa/sidepanel.json b/src/assets/locale/fa/sidepanel.json new file mode 100644 index 0000000..8ef8579 --- /dev/null +++ b/src/assets/locale/fa/sidepanel.json @@ -0,0 +1,7 @@ +{ + "tooltip": { + "embed": "ممکن است چند دقیقه طول بکشد تا صفحه جاسازی شود. لطفاً منتظر بمانید...", + "clear": "پاک کردن تاریخچه گپ", + "history": "تاریخچه گپ" + } +} diff --git a/src/entries/options/App.tsx b/src/entries/options/App.tsx index 620b183..b385df8 100644 --- a/src/entries/options/App.tsx +++ b/src/entries/options/App.tsx @@ -1,5 +1,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { MemoryRouter } from "react-router-dom" +import { useEffect } from "react" const queryClient = new QueryClient() import { ConfigProvider, Empty, theme } from "antd" import { StyleProvider } from "@ant-design/cssinjs" @@ -12,6 +13,14 @@ import { PageAssistProvider } from "@/components/Common/PageAssistProvider" function IndexOption() { const { mode } = useDarkMode() const { t, i18n } = useTranslation() + + useEffect(() => { + if (i18n.resolvedLanguage) { + document.documentElement.lang = i18n.resolvedLanguage; + document.documentElement.dir = i18n.dir(i18n.resolvedLanguage); + } + }, [i18n, i18n.resolvedLanguage]); + return ( { + if (i18n.resolvedLanguage) { + document.documentElement.lang = i18n.resolvedLanguage; + document.documentElement.dir = i18n.dir(i18n.resolvedLanguage); + } + }, [i18n, i18n.resolvedLanguage]); + return ( Date: Tue, 6 Aug 2024 23:32:31 +0530 Subject: [PATCH 11/14] feat: Add segmented control for custom and copilot prompts in PromptBody component --- src/assets/locale/fa/settings.json | 4 ++++ src/entries/options/App.tsx | 13 ++++++++----- src/services/title.ts | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/assets/locale/fa/settings.json b/src/assets/locale/fa/settings.json index 0cbafdc..02098b6 100644 --- a/src/assets/locale/fa/settings.json +++ b/src/assets/locale/fa/settings.json @@ -147,6 +147,10 @@ "type": "نوع پرامپت", "actions": "اقدامات" }, + "segmented": { + "custom": "پرامپت‌های سفارشی", + "copilot": "پرامپت‌های کوپایلوت" + }, "systemPrompt": "پرامپت سیستم ", "quickPrompt": "پرامپت سریع", "tooltip": { diff --git a/src/entries/options/App.tsx b/src/entries/options/App.tsx index b385df8..997e1de 100644 --- a/src/entries/options/App.tsx +++ b/src/entries/options/App.tsx @@ -1,6 +1,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { MemoryRouter } from "react-router-dom" -import { useEffect } from "react" +import { useEffect, useState } from "react" const queryClient = new QueryClient() import { ConfigProvider, Empty, theme } from "antd" import { StyleProvider } from "@ant-design/cssinjs" @@ -13,13 +13,15 @@ import { PageAssistProvider } from "@/components/Common/PageAssistProvider" function IndexOption() { const { mode } = useDarkMode() const { t, i18n } = useTranslation() + const [direction, setDirection] = useState<"ltr" | "rtl">("ltr") useEffect(() => { if (i18n.resolvedLanguage) { - document.documentElement.lang = i18n.resolvedLanguage; - document.documentElement.dir = i18n.dir(i18n.resolvedLanguage); + document.documentElement.lang = i18n.resolvedLanguage + document.documentElement.dir = i18n.dir(i18n.resolvedLanguage) + setDirection(i18n.dir(i18n.resolvedLanguage)) } - }, [i18n, i18n.resolvedLanguage]); + }, [i18n, i18n.resolvedLanguage]) return ( @@ -38,7 +40,8 @@ function IndexOption() { }} description={t("common:noData")} /> - )}> + )} + direction={direction}> diff --git a/src/services/title.ts b/src/services/title.ts index c7a9cd4..ac4b8e8 100644 --- a/src/services/title.ts +++ b/src/services/title.ts @@ -14,7 +14,7 @@ export const DEFAULT_TITLE_GEN_PROMPT = `Here is the query: -------------- -Create a concise, 3-5 word phrase as a title for the previous query. Suitable emojis for the summary can be used to enhance understanding. Avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT. ANSWER USING THE SAME LANGUAGE AS THE QUERY. +Create a concise, 3-5 word phrase as a title for the previous query. Avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT. ANSWER USING THE SAME LANGUAGE AS THE QUERY. Examples of titles: From f6d7020fe02358b5f977b8fd046e62ab40f97ffc Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Tue, 6 Aug 2024 23:59:54 +0530 Subject: [PATCH 12/14] feat: Add AI-based title generation feature --- src/assets/locale/en/settings.json | 3 +++ src/assets/locale/es/settings.json | 3 +++ src/assets/locale/fa/settings.json | 3 +++ src/assets/locale/fr/settings.json | 3 +++ src/assets/locale/it/settings.json | 3 +++ src/assets/locale/ja-JP/settings.json | 3 +++ src/assets/locale/ml/settings.json | 3 +++ src/assets/locale/pt-BR/settings.json | 3 +++ src/assets/locale/ru/settings.json | 3 +++ src/assets/locale/zh/settings.json | 3 +++ 10 files changed, 30 insertions(+) diff --git a/src/assets/locale/en/settings.json b/src/assets/locale/en/settings.json index d88c537..fa16a1a 100644 --- a/src/assets/locale/en/settings.json +++ b/src/assets/locale/en/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Send Notification After Finishing Processing the Knowledge Base" + }, + "generateTitle" :{ + "label": "Generate Title using AI" } }, "sidepanelRag": { diff --git a/src/assets/locale/es/settings.json b/src/assets/locale/es/settings.json index 8f1f0de..bd0d2ad 100644 --- a/src/assets/locale/es/settings.json +++ b/src/assets/locale/es/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Enviar notificación después de terminar el procesamiento de la base de conocimientos" + }, + "generateTitle" :{ + "label": "Generar título usando IA" } }, "sidepanelRag": { diff --git a/src/assets/locale/fa/settings.json b/src/assets/locale/fa/settings.json index 02098b6..f1d2d13 100644 --- a/src/assets/locale/fa/settings.json +++ b/src/assets/locale/fa/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "ارسال نوتیفیکیشن پس از اتمام پردازش پایگاه دانش" + }, + "generateTitle" :{ + "label": "تولید عنوان با استفاده از هوش مصنوعی" } }, "sidepanelRag": { diff --git a/src/assets/locale/fr/settings.json b/src/assets/locale/fr/settings.json index 7abcc46..3310afd 100644 --- a/src/assets/locale/fr/settings.json +++ b/src/assets/locale/fr/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Envoyer une notification après avoir terminé le traitement de la base de connaissances" + }, + "generateTitle" :{ + "label": "Générer le titre en utilisant l'IA" } }, "sidepanelRag": { diff --git a/src/assets/locale/it/settings.json b/src/assets/locale/it/settings.json index de5b1d6..ba6ce98 100644 --- a/src/assets/locale/it/settings.json +++ b/src/assets/locale/it/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Inviare notifica dopo aver terminato l'elaborazione della base di conoscenza" + }, + "generateTitle" :{ + "label": "Genera titolo utilizzando l'IA" } }, "sidepanelRag": { diff --git a/src/assets/locale/ja-JP/settings.json b/src/assets/locale/ja-JP/settings.json index 4521d8d..1180f4c 100644 --- a/src/assets/locale/ja-JP/settings.json +++ b/src/assets/locale/ja-JP/settings.json @@ -32,6 +32,9 @@ }, "sendNotificationAfterIndexing": { "label": "ナレッジベースの処理完了後に通知を送信" + }, + "generateTitle": { + "label": "AIを使用してタイトルを生成" } }, "sidepanelRag": { diff --git a/src/assets/locale/ml/settings.json b/src/assets/locale/ml/settings.json index 979aabb..c151ff9 100644 --- a/src/assets/locale/ml/settings.json +++ b/src/assets/locale/ml/settings.json @@ -32,6 +32,9 @@ }, "sendNotificationAfterIndexing": { "label": "അറിവ് ശേഖരം പ്രോസസ്സ് ചെയ്ത് കഴിഞ്ഞതിന് ശേഷം അറിയിപ്പ് അയയ്ക്കുക" + }, + "generateTitle": { + "label": "എഐ ഉപയോഗിച്ച് ശീർഷകം സൃഷ്ടിക്കുക" } }, "sidepanelRag": { diff --git a/src/assets/locale/pt-BR/settings.json b/src/assets/locale/pt-BR/settings.json index b5be879..7840f31 100644 --- a/src/assets/locale/pt-BR/settings.json +++ b/src/assets/locale/pt-BR/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Enviar notificação após concluir o processamento da base de conhecimento" + }, + "generateTitle": { + "label": "Gerar título usando IA" } }, "sidepanelRag": { diff --git a/src/assets/locale/ru/settings.json b/src/assets/locale/ru/settings.json index 42baef9..2a8f95b 100644 --- a/src/assets/locale/ru/settings.json +++ b/src/assets/locale/ru/settings.json @@ -29,6 +29,9 @@ }, "sendNotificationAfterIndexing": { "label": "Отправить уведомление после завершения обработки базы знаний" + }, + "generateTitle": { + "label": "Сгенерировать заголовок с помощью ИИ" } }, "sidepanelRag": { diff --git a/src/assets/locale/zh/settings.json b/src/assets/locale/zh/settings.json index bcb5d18..4716831 100644 --- a/src/assets/locale/zh/settings.json +++ b/src/assets/locale/zh/settings.json @@ -32,6 +32,9 @@ }, "sendNotificationAfterIndexing": { "label": "完成知识库处理后发送通知" + }, + "generateTitle": { + "label": "使用人工智能生成标题" } }, "sidepanelRag": { From 8bbb3c14f8f7750cae7172a3bae6f304827fd159 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Wed, 7 Aug 2024 22:14:49 +0530 Subject: [PATCH 13/14] refactor: Improve textarea focus behavior in PlaygroundForm component --- src/components/Option/Playground/PlaygroundForm.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx index 8d248f8..8aee6e3 100644 --- a/src/components/Option/Playground/PlaygroundForm.tsx +++ b/src/components/Option/Playground/PlaygroundForm.tsx @@ -39,15 +39,26 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { selectedKnowledge } = useMessageOption() + const isMobile = () => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) + } + const textAreaFocus = () => { if (textareaRef.current) { if ( textareaRef.current.selectionStart === textareaRef.current.selectionEnd ) { - textareaRef.current.focus() + if (!isMobile()) { + textareaRef.current.focus() + } else { + textareaRef.current.blur() + } } } } + const form = useForm({ initialValues: { message: "", From 5557ef3ab525beb4ffd030561540b523f2dd1603 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Wed, 7 Aug 2024 22:16:48 +0530 Subject: [PATCH 14/14] refactor: Remove unused import in PlaygroundForm component --- src/components/Option/Playground/PlaygroundForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx index 8aee6e3..274600e 100644 --- a/src/components/Option/Playground/PlaygroundForm.tsx +++ b/src/components/Option/Playground/PlaygroundForm.tsx @@ -14,7 +14,6 @@ import { useTranslation } from "react-i18next" import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect" import { useSpeechRecognition } from "@/hooks/useSpeechRecognition" import { PiGlobe } from "react-icons/pi" -import { extractReadabilityContent } from "@/parser/reader" type Props = { dropedFile: File | undefined