diff --git a/package-lock.json b/package-lock.json index a6553749..fb01fb13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "react-router-dom": "^6.28.1", "react-syntax-highlighter": "^15.6.1", "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "semver": "^7.7.1", @@ -12372,6 +12373,46 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.3.tgz", @@ -12444,6 +12485,64 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-parse5/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-parse5/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/hast-util-to-text": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", @@ -12588,6 +12687,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -18548,6 +18657,30 @@ "@types/unist": "*" } }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", diff --git a/package.json b/package.json index 1d1d276f..9130a45f 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "react-router-dom": "^6.28.1", "react-syntax-highlighter": "^15.6.1", "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "semver": "^7.7.1", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 9325fdc5..f9e8f97e 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -26,7 +26,8 @@ "edit": "Edit", "editDescription": "Editing this message will regenerate a new response", "cancel": "Cancel", - "save": "Save" + "save": "Save", + "toolCalls": "🛠 Tool Calls Result from {{name}}" }, "welcome": { "title": "Welcome to Dive AI", diff --git a/public/locales/zh-CN/translation.json b/public/locales/zh-CN/translation.json index 2633a564..071a4859 100644 --- a/public/locales/zh-CN/translation.json +++ b/public/locales/zh-CN/translation.json @@ -26,7 +26,8 @@ "edit": "编辑", "editDescription": "编辑此消息将重新生成一个新回复", "cancel": "取消", - "save": "保存" + "save": "保存", + "toolCalls": "🛠 请求 {{name}} 工具结果" }, "welcome": { "title": "欢迎使用 Dive AI", diff --git a/public/locales/zh-TW/translation.json b/public/locales/zh-TW/translation.json index 8994aed3..02f087da 100644 --- a/public/locales/zh-TW/translation.json +++ b/public/locales/zh-TW/translation.json @@ -26,7 +26,8 @@ "edit": "編輯", "editDescription": "編輯此訊息將會重新生成一個新的回應", "cancel": "取消", - "save": "儲存" + "save": "儲存", + "toolCalls": "🛠 呼叫 {{name}} 工具結果" }, "welcome": { "title": "歡迎使用 Dive AI", diff --git a/src/styles/overlay/_Tools.scss b/src/styles/overlay/_Tools.scss index 5abca99f..3b87ba3d 100644 --- a/src/styles/overlay/_Tools.scss +++ b/src/styles/overlay/_Tools.scss @@ -50,17 +50,12 @@ margin: 10px 0; border-radius: 8px; overflow: hidden; - background: var(--bg-op-dark-extremeweak); summary { - padding: 10px 15px; cursor: pointer; user-select: none; font-weight: 500; - - &:hover { - background: var(--bg-op-dark-extremeweak); - } + color: var(--text-inverted-weak); } .tool-content { @@ -191,11 +186,11 @@ .verify-btn { background: var(--bg-success); color: var(--text-light); - + &:hover:not(:disabled) { background: var(--bg-hover-success); } - + &:active:not(:disabled) { background: var(--bg-active-success); } @@ -204,11 +199,11 @@ .submit-btn { background: var(--bg-pri-blue); color: var(--text-light); - + &:hover:not(:disabled) { background: var(--bg-hover-blue); } - + &:active:not(:disabled) { background: var(--bg-active-blue); } diff --git a/src/util.ts b/src/util.ts index 100a9b71..1ffbf285 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,14 +2,34 @@ import { ModelConfig } from "./atoms/configState" export const getModelPrefix = (config: ModelConfig, length: number = 5) => { - if(config.apiKey) return config.apiKey.slice(-length) - try { - if(config.baseURL) { - const url = new URL(config.baseURL) - return url.hostname.slice(0, length) - } - } catch (error) { - return config.baseURL - } - return "" + if(config.apiKey) return config.apiKey.slice(-length) + try { + if(config.baseURL) { + const url = new URL(config.baseURL) + return url.hostname.slice(0, length) + } + } catch (error) { + return config.baseURL + } + return "" +} + +export function safeBase64Encode(str: string): string { + try { + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, + (_, p1) => String.fromCharCode(parseInt(p1, 16)))) + } catch (e) { + console.error("Encoding error:", e) + return "" + } +} + +export function safeBase64Decode(str: string): string { + try { + return decodeURIComponent(Array.prototype.map.call(atob(str), + c => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")) + } catch (e) { + console.error("Decoding error:", e) + return str + } } \ No newline at end of file diff --git a/src/views/Chat/ChatMessages.tsx b/src/views/Chat/ChatMessages.tsx index b7cf4fe1..3f039e38 100644 --- a/src/views/Chat/ChatMessages.tsx +++ b/src/views/Chat/ChatMessages.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef } from "react" import Message from "./Message" -import { ToolCall, ToolResult } from "./ToolPanel" import { isChatStreamingAtom } from "../../atoms/chatState" import { useAtomValue } from "jotai" @@ -11,8 +10,6 @@ export interface Message { timestamp: number files?: File[] isError?: boolean - toolCalls?: ToolCall[] - toolResults?: ToolResult[] } interface Props { @@ -69,8 +66,6 @@ const ChatMessages = ({ messages, isLoading, onRetry, onEdit }: Props) => { files={message.files} isError={message.isError} isLoading={!message.isSent && index === messages.length - 1 && isLoading} - toolCalls={message.toolCalls} - toolResults={message.toolResults} messageId={message.id} onRetry={() => onRetry(message.id)} onEdit={(newText: string) => onEdit(message.id, newText)} diff --git a/src/views/Chat/Message.tsx b/src/views/Chat/Message.tsx index c7e758a3..a66561e4 100644 --- a/src/views/Chat/Message.tsx +++ b/src/views/Chat/Message.tsx @@ -5,17 +5,29 @@ import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import remarkMath from "remark-math" import rehypeKatex from "rehype-katex" +import rehypeRaw from "rehype-raw" import { PrismAsyncLight as SyntaxHighlighter } from "react-syntax-highlighter"; import { tomorrow, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { codeStreamingAtom } from '../../atoms/codeStreaming' -import ToolPanel, { ToolCall, ToolResult } from './ToolPanel' +import ToolPanel from './ToolPanel' import FilePreview from './FilePreview' import { useTranslation } from 'react-i18next' import { themeAtom } from "../../atoms/themeState"; import Textarea from "../../components/WrappedTextarea" import { isChatStreamingAtom } from "../../atoms/chatState" +declare global { + namespace JSX { + interface IntrinsicElements { + "tool-call": { + children: any + name: string + }; + } + } +} + interface MessageProps { messageId: string text: string @@ -24,13 +36,11 @@ interface MessageProps { files?: (File | string)[] isError?: boolean isLoading?: boolean - toolCalls?: ToolCall[] - toolResults?: ToolResult[] onRetry: () => void onEdit: (editedText: string) => void } -const Message = ({ messageId, text, isSent, files, isError, isLoading, toolCalls, toolResults, onRetry, onEdit }: MessageProps) => { +const Message = ({ messageId, text, isSent, files, isError, isLoading, onRetry, onEdit }: MessageProps) => { const { t } = useTranslation() const [theme] = useAtom(themeAtom) const updateStreamingCode = useSetAtom(codeStreamingAtom) @@ -127,8 +137,20 @@ const Message = ({ messageId, text, isSent, files, isError, isLoading, toolCalls singleDollarTextMath: false, inlineMathDouble: false }], remarkGfm]} - rehypePlugins={[rehypeKatex]} + rehypePlugins={[rehypeKatex, rehypeRaw]} components={{ + "tool-call"({children, name}) { + if (typeof children !== "string") { + return <> + } + + return ( + + ) + }, a(props) { return ( @@ -234,19 +256,6 @@ const Message = ({ messageId, text, isSent, files, isError, isLoading, toolCalls return (
- {toolCalls && ( - - )} - {toolResults && ( - - )} {formattedText} {files && files.length > 0 && } {isLoading && ( @@ -259,21 +268,6 @@ const Message = ({ messageId, text, isSent, files, isError, isLoading, toolCalls
{!isLoading && !isChatStreaming && (
- {/* {messageId.includes("-") && ( -
- -
- 1 - / - 2 -
- -
- )} */}