diff --git a/src/assets/locale/en/option.json b/src/assets/locale/en/option.json index 95b89a3..ff38044 100644 --- a/src/assets/locale/en/option.json +++ b/src/assets/locale/en/option.json @@ -1,29 +1,30 @@ { - "newChat": "New Chat", - "selectAPrompt": "Select a Prompt", - "githubRepository": "GitHub Repository", - "settings": "Settings", - "sidebarTitle": "Chat History", - "error": "Error", - "somethingWentWrong": "Something went wrong", - "validationSelectModel": "Please select a model to continue", - "deleteHistoryConfirmation": "Are you sure you want to delete this history?", - "editHistoryTitle": "Enter a new title", - "temporaryChat": "Temporary Chat", - "more": { - "copy": { - "group": "Copy", - "asText": "Copy as Text", - "asMarkdown": "Copy as Markdown", - "success": "Copied to clipboard!" - }, - "download": { - "group": "Download", - "text": "Text File (.txt)", - "markdown": "Markdown (.md)", - "json": "JSON File (.json)", - "image": "Image (.png)" - }, - "share": "Share" - } -} \ No newline at end of file + "newChat": "New Chat", + "selectAPrompt": "Select a Prompt", + "githubRepository": "GitHub Repository", + "settings": "Settings", + "metering": "Metering", + "sidebarTitle": "Chat History", + "error": "Error", + "somethingWentWrong": "Something went wrong", + "validationSelectModel": "Please select a model to continue", + "deleteHistoryConfirmation": "Are you sure you want to delete this history?", + "editHistoryTitle": "Enter a new title", + "temporaryChat": "Temporary Chat", + "more": { + "copy": { + "group": "Copy", + "asText": "Copy as Text", + "asMarkdown": "Copy as Markdown", + "success": "Copied to clipboard!" + }, + "download": { + "group": "Download", + "text": "Text File (.txt)", + "markdown": "Markdown (.md)", + "json": "JSON File (.json)", + "image": "Image (.png)" + }, + "share": "Share" + } +} diff --git a/src/assets/locale/zh/option.json b/src/assets/locale/zh/option.json index d4ed2ee..460f2e2 100644 --- a/src/assets/locale/zh/option.json +++ b/src/assets/locale/zh/option.json @@ -1,28 +1,29 @@ { - "newChat": "新聊天", - "selectAPrompt": "选择一个提示词", - "githubRepository": "GitHub 仓库", - "settings": "设置", - "sidebarTitle": "聊天历史", - "error": "错误", - "somethingWentWrong": "出现了错误", - "validationSelectModel": "请选择一个模型以继续", - "deleteHistoryConfirmation": "你确定要删除这个历史记录吗?", - "editHistoryTitle": "输入一个新的标题", - "temporaryChat": "临时聊天", - "more": { - "copy": { - "group": "复制", - "asText": "复制为文本", - "asMarkdown": "复制为 Markdown", - "success": "已复制到剪贴板!" - }, - "download": { - "group": "下载", - "text": "文本文件 (.txt)", - "markdown": "Markdown 文件 (.md)", - "json": "JSON 文件 (.json)" - }, - "share": "分享" - } -} \ No newline at end of file + "newChat": "新聊天", + "selectAPrompt": "选择一个提示词", + "githubRepository": "GitHub 仓库", + "settings": "设置", + "metering": "计量", + "sidebarTitle": "聊天历史", + "error": "错误", + "somethingWentWrong": "出现了错误", + "validationSelectModel": "请选择一个模型以继续", + "deleteHistoryConfirmation": "你确定要删除这个历史记录吗?", + "editHistoryTitle": "输入一个新的标题", + "temporaryChat": "临时聊天", + "more": { + "copy": { + "group": "复制", + "asText": "复制为文本", + "asMarkdown": "复制为 Markdown", + "success": "已复制到剪贴板!" + }, + "download": { + "group": "下载", + "text": "文本文件 (.txt)", + "markdown": "Markdown 文件 (.md)", + "json": "JSON 文件 (.json)" + }, + "share": "分享" + } +} diff --git a/src/components/Layouts/Header.tsx b/src/components/Layouts/Header.tsx index e4e2567..0002d9c 100644 --- a/src/components/Layouts/Header.tsx +++ b/src/components/Layouts/Header.tsx @@ -5,6 +5,7 @@ import { ChevronRight, CogIcon, ComputerIcon, + Slice, GithubIcon, PanelLeftIcon, ZapIcon @@ -240,7 +241,14 @@ export const Header: React.FC = ({ - + + + + + + diff --git a/src/components/Option/Metering/ListDetail.tsx b/src/components/Option/Metering/ListDetail.tsx new file mode 100644 index 0000000..a991b08 --- /dev/null +++ b/src/components/Option/Metering/ListDetail.tsx @@ -0,0 +1,168 @@ +import { + Card, + List, + Table, + Tag, + Space, + TableProps, + Divider, + Typography +} from "antd" +import { NavLink } from "react-router-dom" + +const data = [ + { + key: "输出token数", + value: 2 + }, + { + key: "输入token数", + value: 2 + }, + { + key: "模型", + value: "xxx" + } +] + +const outputTokenData = [ + { + key: "关键词提示", + value: "xxx" + }, + { + key: "问题", + value: "xxx" + }, + { + key: "数联网引用数据", + value: "xxx" + }, + { + key: "提供方", + value: "xxx" + }, + { + key: "token数量", + value: 2 + }, + { + key: "内容", + value: "xxx" + } +] + +interface DataType { + key: string + name: string + age: number + address: string + tags: string[] +} + +const columns: TableProps["columns"] = [ + { + title: "Name", + dataIndex: "name", + key: "name", + render: (text) => {text} + }, + { + title: "Age", + dataIndex: "age", + key: "age" + }, + { + title: "Address", + dataIndex: "address", + key: "address" + }, + { + title: "Tags", + key: "tags", + dataIndex: "tags", + render: (_, { tags }) => ( + <> + {tags.map((tag) => { + let color = tag.length > 5 ? "geekblue" : "green" + if (tag === "loser") { + color = "volcano" + } + return ( + + {tag.toUpperCase()} + + ) + })} + + ) + }, + { + title: "Action", + key: "action", + render: (_, record) => ( + + {/* Invite {record.name} */} + + + Detail + + + ) + } +] + +const data1: DataType[] = [ + { + key: "1", + name: "John Brown", + age: 32, + address: "New York No. 1 Lake Park", + tags: ["nice", "developer"] + }, + { + key: "2", + name: "Jim Green", + age: 42, + address: "London No. 1 Lake Park", + tags: ["loser"] + }, + { + key: "3", + name: "Joe Black", + age: 32, + address: "Sydney No. 1 Lake Park", + tags: ["cool", "teacher"] + } +] + +export const ListDetail = () => { + return ( +
+ ( + + {item.value} + + )} + /> + +
+ 输出token详情 + ( + + {item.key} {item.value} + + )} + /> +
+ + columns={columns} dataSource={data1} /> +
+ ) +} diff --git a/src/components/Option/Metering/detail.tsx b/src/components/Option/Metering/detail.tsx new file mode 100644 index 0000000..944dc90 --- /dev/null +++ b/src/components/Option/Metering/detail.tsx @@ -0,0 +1,137 @@ +import React from "react" +import { ChatMessage, useStoreMessageOption } from "@/store/option" +import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd" +import { NavLink } from "react-router-dom" +import { formatDate } from "@/utils/date" + +const data = [ + { + key: "对话数量", + value: 2 + }, + { + key: "输出token数", + value: 2 + }, + { + key: "输入token数", + value: 2 + } +] + +const columns: TableProps["columns"] = [ + { + title: "id", + dataIndex: "id", + key: "id", + width: "13%" + }, + { + title: "问题", + dataIndex: "query", + key: "query" + }, + { + title: "提示词全文", + dataIndex: "prompt", + key: "prompt", + ellipsis: { + showTitle: false + }, + render: (prompt) => ( + + {prompt} + + ), + width: "10%" + }, + { + title: "思维链", + key: "thinkingChain", + dataIndex: "thinkingChain", + width: "10%" + }, + + { + title: "回答", + dataIndex: "answer", + key: "answer", + ellipsis: { + showTitle: false + }, + render: (answer) => ( + + {answer} + + ), + width: "10%" + }, + { + title: "关联数据个数", + dataIndex: "relatedDataCount", + key: "relatedDataCount" + }, + { + title: "数联网token", + dataIndex: "iodOutputToken", + key: "iodOutputToken", + render: (iodOutputToken) =>
{iodOutputToken.length}
+ }, + { + title: "大模型token", + key: "largeModelToken", + dataIndex: "largeModelToken", + render: (_, record) => { + return ( +
{record.iodInputToken.length + record.iodOutputToken.length}
+ ) + } + }, + { + title: "日期", + dataIndex: "date", + key: "date", + render: (date) => { + return
{formatDate(date)}
+ } + }, + { + title: "耗时", + key: "timeTaken", + dataIndex: "timeTaken" + }, + { + title: "操作", + key: "action", + render: (_, record) => ( + + {/* Invite {record.name} */} + + + Detail + + + ) + } +] + +export const MeteringDetail = () => { + const { chatMessages } = useStoreMessageOption() + console.log(chatMessages, "opppp") + + return ( +
+ ( + + {item.value} + + )} + /> + + columns={columns} dataSource={chatMessages} /> +
+ ) +} diff --git a/src/hooks/useMessageOption.tsx b/src/hooks/useMessageOption.tsx index 8215230..d48134d 100644 --- a/src/hooks/useMessageOption.tsx +++ b/src/hooks/useMessageOption.tsx @@ -8,7 +8,7 @@ import { promptForRag, systemPromptForNonRagOption } from "~/services/ollama" -import { type ChatHistory, type Message } from "~/store/option" +import { type ChatHistory, ChatMessage, type Message } from "~/store/option" import { SystemMessage } from "@langchain/core/messages" import { useStoreMessageOption } from "~/store/option" import { @@ -114,6 +114,26 @@ export const useMessageOption = () => { setWebSearch(true) } } + // 从最后的结果中解析出 思维链 和 结果 + const responseResolver = (msg: string) => { + const thinkStart = msg.indexOf("") + const thinkEnd = msg.indexOf("") + let think = "" + let content = "" + if (thinkStart > -1 && thinkEnd > -1) { + think = msg.substring(thinkStart + 7, thinkEnd) + content = msg.substring(thinkEnd + 8) + } else { + content = msg + } + // 去掉换行符 + think = think.replace(/\n/g, "") + content = content.replace(/\n/g, "") + return { + think, + content + } + } const searchChatMode = async ( webSearch: boolean, @@ -170,6 +190,10 @@ export const useMessageOption = () => { }) let newMessage: Message[] = [] let generateMessageId = generateID() + const chatMessage: ChatMessage = { + id: generateMessageId, + queryContent: message + } as ChatMessage if (!isRegenerate) { newMessage = [ @@ -300,6 +324,7 @@ export const useMessageOption = () => { ) console.log("prompt:\n" + prompt) setIsSearchingInternet(false) + chatMessage.prompt = prompt // message = message.trim().replaceAll("\n", " ") @@ -460,23 +485,13 @@ export const useMessageOption = () => { setIsProcessing(false) setStreaming(false) - setChatMessages([ - ...chatMessages, - { - id: generateMessageId, - query: message, - prompt: prompt, - thinkingChain: "", - answer: fullText, - relatedDataCount: count, - iodInputToken: "", - iodOutputToken: "", - modelInputToken: "", - modelOutputToken: "", - date: reasoningStartTime, - timeTaken: timetaken - } - ]) + chatMessage.relatedDataCount = keywords.length + chatMessage.timeTaken = timetaken + chatMessage.date = reasoningStartTime + const { think, content } = responseResolver(fullText) + chatMessage.thinkingChain = think + chatMessage.responseContent = content + setChatMessages([...chatMessages, chatMessage]) } catch (e) { const errorSave = await saveMessageOnError({ e, diff --git a/src/routes/chrome.tsx b/src/routes/chrome.tsx index 647bb33..c32968c 100644 --- a/src/routes/chrome.tsx +++ b/src/routes/chrome.tsx @@ -12,6 +12,8 @@ import SidepanelSettings from "./sidepanel-settings" import OptionRagSettings from "./option-rag" import OptionChrome from "./option-settings-chrome" import OptionOpenAI from "./option-settings-openai" +import OptionMetering from "./option-metering" +import MeteringListDetail from "./metering-list-detail" export const OptionRoutingChrome = () => { return ( @@ -27,6 +29,8 @@ export const OptionRoutingChrome = () => { } /> } /> } /> + } /> + } /> ) } diff --git a/src/routes/firefox.tsx b/src/routes/firefox.tsx index 901e584..432f008 100644 --- a/src/routes/firefox.tsx +++ b/src/routes/firefox.tsx @@ -10,6 +10,8 @@ const OptionModal = lazy(() => import("./option-settings-model")) const OptionPrompt = lazy(() => import("./option-settings-prompt")) const OptionOllamaSettings = lazy(() => import("./options-settings-ollama")) const OptionSettings = lazy(() => import("./option-settings")) +const OptionMetering = lazy(() => import("./option-metering")) +const MeteringListDetail = lazy(() => import("./metering-list-detail")) const OptionShare = lazy(() => import("./option-settings-share")) const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge")) const OptionAbout = lazy(() => import("./option-settings-about")) @@ -29,6 +31,8 @@ export const OptionRoutingFirefox = () => { } /> } /> } /> + } /> + } /> ) } diff --git a/src/routes/metering-list-detail.tsx b/src/routes/metering-list-detail.tsx new file mode 100644 index 0000000..119ebe8 --- /dev/null +++ b/src/routes/metering-list-detail.tsx @@ -0,0 +1,12 @@ +import OptionLayout from "~/components/Layouts/Layout" +import { ListDetail } from "~/components/Option/Metering/listDetail" + +const OptionSettings = () => { + return ( + + + + ) +} + +export default OptionSettings diff --git a/src/routes/option-metering.tsx b/src/routes/option-metering.tsx new file mode 100644 index 0000000..26fb4ef --- /dev/null +++ b/src/routes/option-metering.tsx @@ -0,0 +1,12 @@ +import OptionLayout from "~/components/Layouts/Layout" +import { MeteringDetail } from "~/components/Option/Metering/detail" + +const OptionSettings = () => { + return ( + + + + ) +} + +export default OptionSettings diff --git a/src/store/option.tsx b/src/store/option.tsx index df56d16..9266c59 100644 --- a/src/store/option.tsx +++ b/src/store/option.tsx @@ -33,13 +33,13 @@ export type ChatHistory = { export type ChatMessage = { id: string // 问题 - query: string + queryContent: string // 提示词全文 prompt: string // 思维链(只有深度思考时有) thinkingChain?: string // 回答 - answer: string + responseContent: string // 关联数据个数 relatedDataCount: number // 数联网输入token @@ -54,15 +54,15 @@ export type ChatMessage = { date: Date // 耗时 timeTaken: number -}[] +} type State = { messages: Message[] setMessages: (messages: Message[]) => void history: ChatHistory setHistory: (history: ChatHistory) => void - chatMessages: ChatMessage - setChatMessages: (chatMessages: ChatMessage) => void + chatMessages: ChatMessage[] + setChatMessages: (chatMessages: ChatMessage[]) => void streaming: boolean setStreaming: (streaming: boolean) => void isFirstMessage: boolean diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..fbcb9ec --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,22 @@ +export function formatDate(date) { + // 获取年份 + const year = date.getFullYear() + + // 获取月份,注意月份是从0开始计数的,所以需要加1,并且确保月份是两位数 + const month = String(date.getMonth() + 1).padStart(2, "0") + + // 获取日期,确保日期是两位数 + const day = String(date.getDate()).padStart(2, "0") + + // 获取小时,24小时制,并确保小时是两位数 + const hours = String(date.getHours()).padStart(2, "0") + + // 获取分钟,并确保分钟是两位数 + const minutes = String(date.getMinutes()).padStart(2, "0") + + // 组合成所需的格式 + return `${year}-${month}-${day} ${hours}:${minutes}` +} + +// 示例使用 +const now = new Date()