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}
-
+
{!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"