feat: copilot context menu for tool
This commit is contained in:
parent
9c7ebc8778
commit
ac9c9ca887
@ -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 ? (
|
||||
<WebSearch />
|
||||
) : null}
|
||||
|
||||
<div>
|
||||
{props?.message_type && (
|
||||
<Tag color="blue">{props?.message_type}</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
{!editMode ? (
|
||||
props.isBot ? (
|
||||
<Markdown message={props.message} />
|
||||
) : (
|
||||
<p className="prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark">
|
||||
<p
|
||||
className={`prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${
|
||||
props.message_type &&
|
||||
"italic text-gray-500 dark:text-gray-400 text-xs"
|
||||
}`}>
|
||||
{props.message}
|
||||
</p>
|
||||
)
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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!)
|
||||
|
29
src/hooks/useBackgroundMessage.tsx
Normal file
29
src/hooks/useBackgroundMessage.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from "react"
|
||||
|
||||
interface Message {
|
||||
from: string
|
||||
type: string
|
||||
text: string
|
||||
}
|
||||
|
||||
function useBackgroundMessage() {
|
||||
const [message, setMessage] = useState<Message | null>(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
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<HTMLDivElement>(null)
|
||||
const [dropedFile, setDropedFile] = React.useState<File | undefined>()
|
||||
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 (
|
||||
<div
|
||||
ref={drop}
|
||||
|
162
src/services/application.ts
Normal file
162
src/services/application.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
const storage = new Storage()
|
||||
|
||||
const DEFAULT_SUMMARY_PROMPT = `Provide a concise summary of the following text, capturing its main ideas and key points:
|
||||
|
||||
Text:
|
||||
---------
|
||||
{text}
|
||||
---------
|
||||
|
||||
Summarize the text in no more than 3-4 sentences.
|
||||
|
||||
Response:`
|
||||
|
||||
const DEFAULT_REPHRASE_PROMPT = `Rewrite the following text in a different way, maintaining its original meaning but using alternative vocabulary and sentence structures:
|
||||
|
||||
Text:
|
||||
---------
|
||||
{text}
|
||||
---------
|
||||
|
||||
Ensure that your rephrased version conveys the same information and intent as the original.
|
||||
|
||||
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:
|
||||
|
||||
Text:
|
||||
---------
|
||||
{text}
|
||||
---------
|
||||
|
||||
Response:`
|
||||
|
||||
const DEFAULT_EXPLAIN_PROMPT = `Provide a detailed explanation of the following text, breaking down its key concepts, implications, and context:
|
||||
|
||||
Text:
|
||||
---------
|
||||
{text}
|
||||
---------
|
||||
|
||||
Your explanation should:
|
||||
|
||||
Clarify any complex terms or ideas
|
||||
Provide relevant background information
|
||||
Discuss the significance or implications of the content
|
||||
Address any potential questions a reader might have
|
||||
Use examples or analogies to illustrate points when appropriate
|
||||
|
||||
Aim for a comprehensive explanation that would help someone with little prior knowledge fully understand the text.
|
||||
|
||||
Response:`
|
||||
|
||||
const DEFAULT_CUSTOM_PROMPT = `{text}`
|
||||
|
||||
export const getSummaryPrompt = async () => {
|
||||
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 ""
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ export type ChatHistory = {
|
||||
role: "user" | "assistant" | "system"
|
||||
content: string
|
||||
image?: string
|
||||
messageType?: string
|
||||
}[]
|
||||
|
||||
type State = {
|
||||
|
@ -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 = {
|
||||
|
@ -14,5 +14,6 @@ type WebSearch = {
|
||||
sources: any[]
|
||||
images?: string[]
|
||||
search?: WebSearch
|
||||
messageType?: string
|
||||
id?: string
|
||||
}
|
77
src/utils/pull-ollama.ts
Normal file
77
src/utils/pull-ollama.ts
Normal file
@ -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)
|
||||
}
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user