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"