diff --git a/.gitignore b/.gitignore index a86135f87..c3cde5d39 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ web/bun.lock electron/node_modules electron/dist +data/ diff --git a/web/src/components/playground/CustomInputRender.jsx b/web/src/components/playground/CustomInputRender.jsx index 464cfa3b1..f83d6bcbf 100644 --- a/web/src/components/playground/CustomInputRender.jsx +++ b/web/src/components/playground/CustomInputRender.jsx @@ -17,12 +17,87 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React from 'react'; +import React, { useRef, useEffect, useCallback } from 'react'; +import { Toast } from '@douyinfe/semi-ui'; +import { useTranslation } from 'react-i18next'; +import { usePlayground } from '../../contexts/PlaygroundContext'; const CustomInputRender = (props) => { + const { t } = useTranslation(); + const { onPasteImage, imageEnabled } = usePlayground(); const { detailProps } = props; const { clearContextNode, uploadNode, inputNode, sendNode, onClick } = detailProps; + const containerRef = useRef(null); + + const handlePaste = useCallback(async (e) => { + const items = e.clipboardData?.items; + if (!items) return; + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + + if (item.type.indexOf('image') !== -1) { + e.preventDefault(); + const file = item.getAsFile(); + + if (file) { + try { + if (!imageEnabled) { + Toast.warning({ + content: t('请先在设置中启用图片功能'), + duration: 3, + }); + return; + } + + const reader = new FileReader(); + reader.onload = (event) => { + const base64 = event.target.result; + + if (onPasteImage) { + onPasteImage(base64); + Toast.success({ + content: t('图片已添加'), + duration: 2, + }); + } else { + Toast.error({ + content: t('无法添加图片'), + duration: 2, + }); + } + }; + reader.onerror = () => { + console.error('Failed to read image file:', reader.error); + Toast.error({ + content: t('粘贴图片失败'), + duration: 2, + }); + }; + reader.readAsDataURL(file); + } catch (error) { + console.error('Failed to paste image:', error); + Toast.error({ + content: t('粘贴图片失败'), + duration: 2, + }); + } + } + break; + } + } + }, [onPasteImage, imageEnabled, t]); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + container.addEventListener('paste', handlePaste); + return () => { + container.removeEventListener('paste', handlePaste); + }; + }, [handlePaste]); // 清空按钮 const styledClearNode = clearContextNode @@ -57,11 +132,12 @@ const CustomInputRender = (props) => { }); return ( -
+
{/* 清空对话按钮 - 左边 */} {styledClearNode} diff --git a/web/src/components/playground/CustomRequestEditor.jsx b/web/src/components/playground/CustomRequestEditor.jsx index 26b3ff504..786ccdc4a 100644 --- a/web/src/components/playground/CustomRequestEditor.jsx +++ b/web/src/components/playground/CustomRequestEditor.jsx @@ -82,7 +82,7 @@ const CustomRequestEditor = ({ return true; } catch (error) { setIsValid(false); - setErrorMessage(`JSON格式错误: ${error.message}`); + setErrorMessage(`${t('JSON格式错误')}: ${error.message}`); return false; } }; @@ -123,14 +123,14 @@ const CustomRequestEditor = ({
- 自定义请求体模式 + {t('自定义请求体模式')}
@@ -140,7 +140,7 @@ const CustomRequestEditor = ({ {/* 提示信息 */} } className='!rounded-lg' closeIcon={null} @@ -150,21 +150,21 @@ const CustomRequestEditor = ({
- 请求体 JSON + {t('请求体 JSON')}
{isValid ? (
- 格式正确 + {t('格式正确')}
) : (
- 格式错误 + {t('格式错误')}
)} @@ -177,7 +177,7 @@ const CustomRequestEditor = ({ disabled={!isValid} className='!rounded-lg' > - 格式化 + {t('格式化')}
@@ -201,7 +201,7 @@ const CustomRequestEditor = ({ )} - 请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。 + {t('请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。')}
diff --git a/web/src/components/playground/DebugPanel.jsx b/web/src/components/playground/DebugPanel.jsx index d931ff61c..ee4a0b60d 100644 --- a/web/src/components/playground/DebugPanel.jsx +++ b/web/src/components/playground/DebugPanel.jsx @@ -29,6 +29,7 @@ import { import { Code, Zap, Clock, X, Eye, Send } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import CodeViewer from './CodeViewer'; +import SSEViewer from './SSEViewer'; const DebugPanel = ({ debugData, @@ -180,15 +181,27 @@ const DebugPanel = ({
{t('响应')} + {debugData.sseMessages && debugData.sseMessages.length > 0 && ( + + SSE ({debugData.sseMessages.length}) + + )}
} itemKey='response' > - + {debugData.sseMessages && debugData.sseMessages.length > 0 ? ( + + ) : ( + + )}
diff --git a/web/src/components/playground/ImageUrlInput.jsx b/web/src/components/playground/ImageUrlInput.jsx index 3aeb2ef92..28a287818 100644 --- a/web/src/components/playground/ImageUrlInput.jsx +++ b/web/src/components/playground/ImageUrlInput.jsx @@ -21,6 +21,7 @@ import React from 'react'; import { Input, Typography, Button, Switch } from '@douyinfe/semi-ui'; import { IconFile } from '@douyinfe/semi-icons'; import { FileText, Plus, X, Image } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; const ImageUrlInput = ({ imageUrls, @@ -29,6 +30,7 @@ const ImageUrlInput = ({ onImageEnabledChange, disabled = false, }) => { + const { t } = useTranslation(); const handleAddImageUrl = () => { const newUrls = [...imageUrls, '']; onImageUrlsChange(newUrls); @@ -56,11 +58,11 @@ const ImageUrlInput = ({ } /> - 图片地址 + {t('图片地址')} {disabled && ( - (已在自定义模式中忽略) + ({t('已在自定义模式中忽略')}) )}
@@ -68,8 +70,8 @@ const ImageUrlInput = ({ {disabled - ? '图片功能在自定义请求体模式下不可用' - : '启用后可添加图片URL进行多模态对话'} + ? t('图片功能在自定义请求体模式下不可用') + : t('启用后可添加图片URL进行多模态对话')} ) : imageUrls.length === 0 ? ( {disabled - ? '图片功能在自定义请求体模式下不可用' - : '点击 + 按钮添加图片URL进行多模态对话'} + ? t('图片功能在自定义请求体模式下不可用') + : t('点击 + 按钮添加图片URL进行多模态对话')} ) : ( - 已添加 {imageUrls.length} 张图片 - {disabled ? ' (自定义模式下不可用)' : ''} + {t('已添加')} {imageUrls.length} {t('张图片')} + {disabled ? ` (${t('自定义模式下不可用')})` : ''} )} diff --git a/web/src/components/playground/ParameterControl.jsx b/web/src/components/playground/ParameterControl.jsx index 3ea387a78..e06c39731 100644 --- a/web/src/components/playground/ParameterControl.jsx +++ b/web/src/components/playground/ParameterControl.jsx @@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com import React from 'react'; import { Input, Slider, Typography, Button, Tag } from '@douyinfe/semi-ui'; +import { useTranslation } from 'react-i18next'; import { Hash, Thermometer, @@ -37,6 +38,8 @@ const ParameterControl = ({ onParameterToggle, disabled = false, }) => { + const { t } = useTranslation(); + return ( <> {/* Temperature */} @@ -70,7 +73,7 @@ const ParameterControl = ({ /> - 控制输出的随机性和创造性 + {t('控制输出的随机性和创造性')} - 核采样,控制词汇选择的多样性 + {t('核采样,控制词汇选择的多样性')} - 频率惩罚,减少重复词汇的出现 + {t('频率惩罚,减少重复词汇的出现')} - 存在惩罚,鼓励讨论新话题 + {t('存在惩罚,鼓励讨论新话题')} - (可选,用于复现结果) + ({t('可选,用于复现结果')}) + + + + + + + + {/* SSE 数据列表 */} +
+ + {parsedSSEData.map((item) => ( + + + {item.isDone ? ( + [DONE] + ) : item.error ? ( + {t('解析错误')} + ) : ( + <> + + {item.parsed?.id || item.parsed?.object || t('SSE 事件')} + + {item.parsed?.choices?.[0]?.delta && ( + + • {Object.keys(item.parsed.choices[0].delta).filter(k => item.parsed.choices[0].delta[k]).join(', ')} + + )} + + )} +
+ } + > + {renderSSEItem(item)} + + ))} + + + + ); +}; + +export default SSEViewer; diff --git a/web/src/components/playground/SettingsPanel.jsx b/web/src/components/playground/SettingsPanel.jsx index dee629bc7..3899e596f 100644 --- a/web/src/components/playground/SettingsPanel.jsx +++ b/web/src/components/playground/SettingsPanel.jsx @@ -122,7 +122,7 @@ const SettingsPanel = ({ {customRequestMode && ( - (已在自定义模式中忽略) + ({t('已在自定义模式中忽略')}) )} @@ -154,7 +154,7 @@ const SettingsPanel = ({ {customRequestMode && ( - (已在自定义模式中忽略) + ({t('已在自定义模式中忽略')}) )} @@ -206,19 +206,19 @@ const SettingsPanel = ({
- 流式输出 + {t('流式输出')} {customRequestMode && ( - (已在自定义模式中忽略) + ({t('已在自定义模式中忽略')}) )}
onInputChange('stream', checked)} - checkedText='开' - uncheckedText='关' + checkedText={t('开')} + uncheckedText={t('关')} size='small' disabled={customRequestMode} /> diff --git a/web/src/components/playground/index.js b/web/src/components/playground/index.js index 65a23ceaf..8c470351b 100644 --- a/web/src/components/playground/index.js +++ b/web/src/components/playground/index.js @@ -23,6 +23,7 @@ export { default as DebugPanel } from './DebugPanel'; export { default as MessageContent } from './MessageContent'; export { default as MessageActions } from './MessageActions'; export { default as CustomInputRender } from './CustomInputRender'; +export { default as SSEViewer } from './SSEViewer'; export { default as ParameterControl } from './ParameterControl'; export { default as ImageUrlInput } from './ImageUrlInput'; export { default as FloatingButtons } from './FloatingButtons'; diff --git a/web/src/constants/playground.constants.js b/web/src/constants/playground.constants.js index a5cc666be..9ba88621c 100644 --- a/web/src/constants/playground.constants.js +++ b/web/src/constants/playground.constants.js @@ -30,19 +30,37 @@ export const MESSAGE_ROLES = { SYSTEM: 'system', }; -// 默认消息示例 -export const DEFAULT_MESSAGES = [ +// 默认消息示例 - 使用函数生成以支持 i18n +export const getDefaultMessages = (t) => [ { role: MESSAGE_ROLES.USER, id: '2', createAt: 1715676751919, - content: '你好', + content: t('默认用户消息'), }, { role: MESSAGE_ROLES.ASSISTANT, id: '3', createAt: 1715676751919, - content: '你好,请问有什么可以帮助您的吗?', + content: t('默认助手消息'), + reasoningContent: '', + isReasoningExpanded: false, + }, +]; + +// 保留旧的导出以保持向后兼容 +export const DEFAULT_MESSAGES = [ + { + role: MESSAGE_ROLES.USER, + id: '2', + createAt: 1715676751919, + content: 'Hello', + }, + { + role: MESSAGE_ROLES.ASSISTANT, + id: '3', + createAt: 1715676751919, + content: 'Hello! How can I help you today?', reasoningContent: '', isReasoningExpanded: false, }, diff --git a/web/src/contexts/PlaygroundContext.jsx b/web/src/contexts/PlaygroundContext.jsx new file mode 100644 index 000000000..cb971153a --- /dev/null +++ b/web/src/contexts/PlaygroundContext.jsx @@ -0,0 +1,60 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React, { createContext, useContext } from 'react'; + +/** + * Context for Playground component to share image handling functionality + */ +const PlaygroundContext = createContext(null); + +/** + * Hook to access Playground context + * @returns {Object} Context value with onPasteImage, imageUrls, and imageEnabled + */ +export const usePlayground = () => { + const context = useContext(PlaygroundContext); + if (!context) { + return { + onPasteImage: () => { + console.warn('PlaygroundContext not provided'); + }, + imageUrls: [], + imageEnabled: false, + }; + } + return context; +}; + +/** + * Provider component for Playground context + * @param {Object} props - Component props + * @param {React.ReactNode} props.children - Child components + * @param {Object} props.value - Context value to provide + * @returns {JSX.Element} Provider component + */ +export const PlaygroundProvider = ({ children, value }) => { + return ( + + {children} + + ); +}; + +export default PlaygroundContext; diff --git a/web/src/hooks/playground/useApiRequest.jsx b/web/src/hooks/playground/useApiRequest.jsx index d73db741b..12db9f5ca 100644 --- a/web/src/hooks/playground/useApiRequest.jsx +++ b/web/src/hooks/playground/useApiRequest.jsx @@ -179,6 +179,8 @@ export const useApiRequest = ( request: payload, timestamp: new Date().toISOString(), response: null, + sseMessages: null, // 非流式请求清除 SSE 消息 + isStreaming: false, })); setActiveDebugTab(DEBUG_TABS.REQUEST); @@ -291,6 +293,8 @@ export const useApiRequest = ( request: payload, timestamp: new Date().toISOString(), response: null, + sseMessages: [], // 新增:存储 SSE 消息数组 + isStreaming: true, // 新增:标记流式状态 })); setActiveDebugTab(DEBUG_TABS.REQUEST); @@ -314,7 +318,12 @@ export const useApiRequest = ( isStreamComplete = true; // 标记流正常完成 source.close(); sseSourceRef.current = null; - setDebugData((prev) => ({ ...prev, response: responseData })); + setDebugData((prev) => ({ + ...prev, + response: responseData, + sseMessages: [...(prev.sseMessages || []), '[DONE]'], // 添加 DONE 标记 + isStreaming: false, + })); completeMessage(); return; } @@ -328,6 +337,12 @@ export const useApiRequest = ( hasReceivedFirstResponse = true; } + // 新增:将 SSE 消息添加到数组 + setDebugData((prev) => ({ + ...prev, + sseMessages: [...(prev.sseMessages || []), e.data], + })); + const delta = payload.choices?.[0]?.delta; if (delta) { if (delta.reasoning_content) { @@ -347,6 +362,8 @@ export const useApiRequest = ( setDebugData((prev) => ({ ...prev, response: responseData + `\n\nError: ${errorInfo}`, + sseMessages: [...(prev.sseMessages || []), e.data], // 即使解析失败也保存原始数据 + isStreaming: false, })); setActiveDebugTab(DEBUG_TABS.RESPONSE); diff --git a/web/src/hooks/playground/usePlaygroundState.js b/web/src/hooks/playground/usePlaygroundState.js index cd00381f7..9574a4c3c 100644 --- a/web/src/hooks/playground/usePlaygroundState.js +++ b/web/src/hooks/playground/usePlaygroundState.js @@ -18,8 +18,10 @@ For commercial licensing, please contact support@quantumnous.com */ import { useState, useCallback, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { DEFAULT_MESSAGES, + getDefaultMessages, DEFAULT_CONFIG, DEBUG_TABS, MESSAGE_STATUS, @@ -33,9 +35,27 @@ import { import { processIncompleteThinkTags } from '../../helpers'; export const usePlaygroundState = () => { + const { t } = useTranslation(); + // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息 const [savedConfig] = useState(() => loadConfig()); - const [initialMessages] = useState(() => loadMessages() || DEFAULT_MESSAGES); + const [initialMessages] = useState(() => { + const loaded = loadMessages(); + // 检查是否是旧的中文默认消息,如果是则清除 + if (loaded && loaded.length === 2 && loaded[0].id === '2' && loaded[1].id === '3') { + const hasOldChinese = + loaded[0].content === '你好' || + loaded[1].content === '你好,请问有什么可以帮助您的吗?' || + loaded[1].content === '你好!很高兴见到你。有什么我可以帮助你的吗?'; + + if (hasOldChinese) { + // 清除旧的默认消息 + localStorage.removeItem('playground_messages'); + return null; + } + } + return loaded; + }); // 基础配置状态 const [inputs, setInputs] = useState( @@ -60,8 +80,16 @@ export const usePlaygroundState = () => { const [groups, setGroups] = useState([]); const [status, setStatus] = useState({}); - // 消息相关状态 - 使用加载的消息初始化 - const [message, setMessage] = useState(initialMessages); + // 消息相关状态 - 使用加载的消息或默认消息初始化 + const [message, setMessage] = useState(() => initialMessages || getDefaultMessages(t)); + + // 当语言改变时,如果是默认消息则更新 + useEffect(() => { + // 只在没有保存的消息时才更新默认消息 + if (!initialMessages) { + setMessage(getDefaultMessages(t)); + } + }, [t, initialMessages]); // 当语言改变时 // 调试状态 const [debugData, setDebugData] = useState({ @@ -168,7 +196,7 @@ export const usePlaygroundState = () => { if (resetMessages) { setMessage([]); setTimeout(() => { - setMessage(DEFAULT_MESSAGES); + setMessage(getDefaultMessages(t)); }, 0); } }, []); diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 7295914f4..ec8e2b990 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -407,7 +407,7 @@ "共 {{count}} 个密钥_other": "{{count}} keys", "共 {{count}} 个模型": "{{count}} models", "共 {{total}} 项,当前显示 {{start}}-{{end}} 项": "{{total}} items total, showing {{start}}-{{end}} items", - "关": "close", + "关": "Off", "关于": "About", "关于我们": "About Us", "关于系统的详细信息": "Detailed information about the system", @@ -788,7 +788,7 @@ "应用覆盖": "Apply overwrite", "建立连接时发生错误": "Error occurred while establishing connection", "建议在生产环境中使用 MySQL 或 PostgreSQL 数据库,或确保 SQLite 数据库文件已映射到宿主机的持久化存储。": "It is recommended to use MySQL or PostgreSQL databases in production environments, or ensure that the SQLite database file is mapped to the persistent storage of the host machine.", - "开": "open", + "开": "On", "开启之后会清除用户提示词中的": "After enabling, the user prompt will be cleared", "开启之后将上游地址替换为服务器地址": "After enabling, the upstream address will be replaced with the server address", "开启后,仅\"消费\"和\"错误\"日志将记录您的客户端IP地址": "After enabling, only \"consumption\" and \"error\" logs will record your client IP address", @@ -2004,7 +2004,7 @@ "重试": "Retry", "钱包管理": "Wallet Management", "链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1": "The {key} in the link will be automatically replaced with sk-xxxx, the {address} will be automatically replaced with the server address in system settings, and the end will not have / and /v1", - "错误": "Error", + "错误": "errors", "键为分组名称,值为另一个 JSON 对象,键为分组名称,值为该分组的用户的特殊分组倍率,例如:{\"vip\": {\"default\": 0.5, \"test\": 1}},表示 vip 分组的用户在使用default分组的令牌时倍率为0.5,使用test分组时倍率为1": "The key is the group name, and the value is another JSON object. The key is the group name, and the value is the special group ratio for users in that group. For example: {\"vip\": {\"default\": 0.5, \"test\": 1}} means that users in the vip group have a ratio of 0.5 when using tokens from the default group, and a ratio of 1 when using tokens from the test group", "键为原状态码,值为要复写的状态码,仅影响本地判断": "The key is the original status code, and the value is the status code to override, only affects local judgment", "键为端点类型,值为路径和方法对象": "The key is the endpoint type, the value is the path and method object", @@ -2113,6 +2113,46 @@ "统一的": "The Unified", "大模型接口网关": "LLM API Gateway", "正在跳转 GitHub...": "Redirecting to GitHub...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "Request timed out, please refresh and restart GitHub login" + "请求超时,请刷新页面后重新发起 GitHub 登录": "Request timed out, please refresh and restart GitHub login", + "请先在设置中启用图片功能": "Please enable image function in settings first", + "图片已添加": "Image added", + "无法添加图片": "Cannot add image", + "粘贴图片失败": "Failed to paste image", + "支持 Ctrl+V 粘贴图片": "Support Ctrl+V to paste image", + "已复制全部数据": "All data copied", + "流式响应完成": "Stream completed", + "图片地址": "Image URL", + "已在自定义模式中忽略": "Ignored in custom mode", + "停用": "Disable", + "图片功能在自定义请求体模式下不可用": "Image function is not available in custom request mode", + "启用后可添加图片URL进行多模态对话": "Enable to add image URLs for multimodal conversation", + "点击 + 按钮添加图片URL进行多模态对话": "Click + to add image URLs for multimodal conversation", + "已添加": "Added", + "张图片": "images", + "自定义模式下不可用": "Not available in custom mode", + "控制输出的随机性和创造性": "Controls randomness and creativity of output", + "核采样,控制词汇选择的多样性": "Nucleus sampling, controls diversity of vocabulary selection", + "频率惩罚,减少重复词汇的出现": "Frequency penalty, reduces repetition of words", + "存在惩罚,鼓励讨论新话题": "Presence penalty, encourages new topics", + "流式输出": "Stream Output", + "暂无SSE响应数据": "No SSE response data", + "SSE数据流": "SSE Data Stream", + "解析错误": "Parse Error", + "有 Reasoning": "Has Reasoning", + "全部收起": "Collapse All", + "全部展开": "Expand All", + "SSE 事件": "SSE Event", + "JSON格式错误": "JSON Format Error", + "自定义请求体模式": "Custom Request Body Mode", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "When enabled, your custom request body will be used for API requests, and parameter settings in the model configuration panel will be ignored.", + "请求体 JSON": "Request Body JSON", + "格式正确": "Valid Format", + "格式错误": "Invalid Format", + "格式化": "Format", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "Please enter a valid JSON format request body. You can refer to the default request body format in the preview panel.", + "默认用户消息": "Hello", + "默认助手消息": "Hello! How can I help you today?", + "可选,用于复现结果": "Optional, for reproducible results", + "随机种子 (留空为随机)": "Random seed (leave empty for random)" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index ded4de6d0..9d29e05d0 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -2093,6 +2093,46 @@ "统一的": "La Passerelle", "大模型接口网关": "API LLM Unifiée", "正在跳转 GitHub...": "Redirection vers GitHub...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "Délai dépassé, veuillez actualiser la page puis relancer la connexion GitHub" + "请求超时,请刷新页面后重新发起 GitHub 登录": "Délai dépassé, veuillez actualiser la page puis relancer la connexion GitHub", + "请先在设置中启用图片功能": "Veuillez d'abord activer la fonction image dans les paramètres", + "图片已添加": "Image ajoutée", + "无法添加图片": "Impossible d'ajouter l'image", + "粘贴图片失败": "Échec du collage de l'image", + "支持 Ctrl+V 粘贴图片": "Supporte Ctrl+V pour coller l'image", + "已复制全部数据": "Toutes les données copiées", + "流式响应完成": "Flux terminé", + "图片地址": "URL de l'image", + "已在自定义模式中忽略": "Ignoré en mode personnalisé", + "停用": "Désactiver", + "图片功能在自定义请求体模式下不可用": "La fonction image n'est pas disponible en mode requête personnalisée", + "启用后可添加图片URL进行多模态对话": "Activer pour ajouter des URL d'images pour une conversation multimodale", + "点击 + 按钮添加图片URL进行多模态对话": "Cliquez sur + pour ajouter des URL d'images pour une conversation multimodale", + "已添加": "Ajouté", + "张图片": "images", + "自定义模式下不可用": "Non disponible en mode personnalisé", + "控制输出的随机性和创造性": "Contrôle l'aléatoire et la créativité de la sortie", + "核采样,控制词汇选择的多样性": "Échantillonnage nucléaire, contrôle la diversité de la sélection du vocabulaire", + "频率惩罚,减少重复词汇的出现": "Pénalité de fréquence, réduit la répétition des mots", + "存在惩罚,鼓励讨论新话题": "Pénalité de présence, encourage de nouveaux sujets", + "流式输出": "Sortie en flux", + "暂无SSE响应数据": "Aucune donnée de réponse SSE", + "SSE数据流": "Flux de données SSE", + "解析错误": "Erreur d'analyse", + "有 Reasoning": "A un raisonnement", + "全部收起": "Tout réduire", + "全部展开": "Tout développer", + "SSE 事件": "Événement SSE", + "JSON格式错误": "Erreur de format JSON", + "自定义请求体模式": "Mode de corps de requête personnalisé", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "Lorsqu'il est activé, votre corps de requête personnalisé sera utilisé pour les requêtes API et les paramètres du panneau de configuration du modèle seront ignorés.", + "请求体 JSON": "Corps de requête JSON", + "格式正确": "Format valide", + "格式错误": "Format invalide", + "格式化": "Formater", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "Veuillez entrer un corps de requête au format JSON valide. Vous pouvez vous référer au format de corps de requête par défaut dans le panneau d'aperçu.", + "默认用户消息": "Bonjour", + "默认助手消息": "Bonjour ! Comment puis-je vous aider aujourd'hui ?", + "可选,用于复现结果": "Optionnel, pour des résultats reproductibles", + "随机种子 (留空为随机)": "Graine aléatoire (laisser vide pour aléatoire)" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index a9ea3d0d8..ba70cb475 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -2084,6 +2084,46 @@ "统一的": "統合型", "大模型接口网关": "LLM APIゲートウェイ", "正在跳转 GitHub...": "GitHub にリダイレクトしています...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "タイムアウトしました。ページをリロードして GitHub ログインをやり直してください" + "请求超时,请刷新页面后重新发起 GitHub 登录": "タイムアウトしました。ページをリロードして GitHub ログインをやり直してください", + "请先在设置中启用图片功能": "まず設定で画像機能を有効にしてください", + "图片已添加": "画像が追加されました", + "无法添加图片": "画像を追加できません", + "粘贴图片失败": "画像の貼り付けに失敗しました", + "支持 Ctrl+V 粘贴图片": "Ctrl+V で画像を貼り付け可能", + "已复制全部数据": "すべてのデータをコピーしました", + "流式响应完成": "ストリーム完了", + "图片地址": "画像URL", + "已在自定义模式中忽略": "カスタムモードで無視されました", + "停用": "無効", + "图片功能在自定义请求体模式下不可用": "カスタムリクエストモードでは画像機能は利用できません", + "启用后可添加图片URL进行多模态对话": "有効にすると画像URLを追加してマルチモーダル会話ができます", + "点击 + 按钮添加图片URL进行多模态对话": "+ ボタンをクリックして画像URLを追加し、マルチモーダル会話を行います", + "已添加": "追加済み", + "张图片": "枚の画像", + "自定义模式下不可用": "カスタムモードでは利用できません", + "控制输出的随机性和创造性": "出力のランダム性と創造性を制御", + "核采样,控制词汇选择的多样性": "ニュークリアスサンプリング、語彙選択の多様性を制御", + "频率惩罚,减少重复词汇的出现": "頻度ペナルティ、単語の繰り返しを減少", + "存在惩罚,鼓励讨论新话题": "存在ペナルティ、新しいトピックを促進", + "流式输出": "ストリーム出力", + "暂无SSE响应数据": "SSE応答データがありません", + "SSE数据流": "SSEデータストリーム", + "解析错误": "解析エラー", + "有 Reasoning": "推論あり", + "全部收起": "すべて折りたたむ", + "全部展开": "すべて展開", + "SSE 事件": "SSEイベント", + "JSON格式错误": "JSON形式エラー", + "自定义请求体模式": "カスタムリクエストボディモード", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "このモードを有効にすると、カスタムリクエストボディがAPIリクエストに使用され、モデル設定パネルのパラメータ設定は無視されます。", + "请求体 JSON": "リクエストボディJSON", + "格式正确": "有効な形式", + "格式错误": "無効な形式", + "格式化": "フォーマット", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "有効なJSON形式のリクエストボディを入力してください。プレビューパネルのデフォルトのリクエストボディ形式を参照できます。", + "默认用户消息": "こんにちは", + "默认助手消息": "こんにちは!何かお手伝いできることはありますか?", + "可选,用于复现结果": "オプション、結果の再現用", + "随机种子 (留空为随机)": "ランダムシード(空欄でランダム)" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index df9e52a38..b241e1123 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -2102,6 +2102,46 @@ "统一的": "Единый", "大模型接口网关": "Шлюз API LLM", "正在跳转 GitHub...": "Перенаправление на GitHub...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "Время ожидания истекло, обновите страницу и снова запустите вход через GitHub" + "请求超时,请刷新页面后重新发起 GitHub 登录": "Время ожидания истекло, обновите страницу и снова запустите вход через GitHub", + "请先在设置中启用图片功能": "Сначала включите функцию изображений в настройках", + "图片已添加": "Изображение добавлено", + "无法添加图片": "Невозможно добавить изображение", + "粘贴图片失败": "Ошибка вставки изображения", + "支持 Ctrl+V 粘贴图片": "Поддержка Ctrl+V для вставки изображения", + "已复制全部数据": "Все данные скопированы", + "流式响应完成": "Поток завершён", + "图片地址": "URL изображения", + "已在自定义模式中忽略": "Игнорируется в пользовательском режиме", + "停用": "Отключить", + "图片功能在自定义请求体模式下不可用": "Функция изображений недоступна в режиме пользовательского запроса", + "启用后可添加图片URL进行多模态对话": "Включите для добавления URL изображений для мультимодального диалога", + "点击 + 按钮添加图片URL进行多模态对话": "Нажмите + для добавления URL изображений для мультимодального диалога", + "已添加": "Добавлено", + "张图片": "изображений", + "自定义模式下不可用": "Недоступно в пользовательском режиме", + "控制输出的随机性和创造性": "Управляет случайностью и креативностью вывода", + "核采样,控制词汇选择的多样性": "Ядерная выборка, управляет разнообразием выбора слов", + "频率惩罚,减少重复词汇的出现": "Штраф за частоту, уменьшает повторение слов", + "存在惩罚,鼓励讨论新话题": "Штраф за присутствие, поощряет новые темы", + "流式输出": "Потоковый вывод", + "暂无SSE响应数据": "Нет данных ответа SSE", + "SSE数据流": "Поток данных SSE", + "解析错误": "Ошибка разбора", + "有 Reasoning": "Есть рассуждение", + "全部收起": "Свернуть всё", + "全部展开": "Развернуть всё", + "SSE 事件": "Событие SSE", + "JSON格式错误": "Ошибка формата JSON", + "自定义请求体模式": "Режим пользовательского тела запроса", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "При включении ваше пользовательское тело запроса будет использоваться для API-запросов, а настройки параметров на панели конфигурации модели будут игнорироваться.", + "请求体 JSON": "Тело запроса JSON", + "格式正确": "Действительный формат", + "格式错误": "Недействительный формат", + "格式化": "Форматировать", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "Пожалуйста, введите тело запроса в действительном формате JSON. Вы можете обратиться к формату тела запроса по умолчанию на панели предварительного просмотра.", + "默认用户消息": "Здравствуйте", + "默认助手消息": "Здравствуйте! Чем я могу вам помочь?", + "可选,用于复现结果": "Необязательно, для воспроизводимых результатов", + "随机种子 (留空为随机)": "Случайное зерно (оставьте пустым для случайного)" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 242292185..b170f43c6 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -2387,10 +2387,10 @@ "例如:100000": "Ví dụ: 100000", "请填写完整的产品信息": "Vui lòng điền đầy đủ thông tin sản phẩm", "产品ID已存在": "ID sản phẩm đã tồn tại", - "统一的": "Thống nhất", + "统一的": "Cổng thống nhất", "大模型接口网关": "Cổng API LLM", "正在跳转 GitHub...": "Đang chuyển hướng đến GitHub...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "Yêu cầu đã hết thời gian, vui lòng làm mới trang và thử đăng nhập lại GitHub", + "请求超时,请刷新页面后重新发起 GitHub 登录": "Hết thời gian chờ, vui lòng làm mới trang và đăng nhập GitHub lại", "模型: {{ratio}}": "Mô hình: {{ratio}}", "模型专用区域": "Khu vực dành riêng cho mô hình", "模型价格": "Giá mô hình", @@ -2695,6 +2695,46 @@ "线路描述": "Mô tả tuyến", "组列表": "Danh sách nhóm", "组名": "Tên nhóm", - "组织,不填则为默认组织": "Tổ chức, mặc định nếu để trống" + "组织,不填则为默认组织": "Tổ chức, mặc định nếu để trống", + "请先在设置中启用图片功能": "Vui lòng bật chức năng hình ảnh trong cài đặt trước", + "图片已添加": "Hình ảnh đã được thêm", + "无法添加图片": "Không thể thêm hình ảnh", + "粘贴图片失败": "Dán hình ảnh thất bại", + "支持 Ctrl+V 粘贴图片": "Hỗ trợ Ctrl+V để dán hình ảnh", + "已复制全部数据": "Tất cả dữ liệu đã được sao chép", + "流式响应完成": "Luồng hoàn tất", + "图片地址": "URL hình ảnh", + "已在自定义模式中忽略": "Bị bỏ qua trong chế độ tùy chỉnh", + "停用": "Vô hiệu hóa", + "图片功能在自定义请求体模式下不可用": "Chức năng hình ảnh không khả dụng trong chế độ yêu cầu tùy chỉnh", + "启用后可添加图片URL进行多模态对话": "Bật để thêm URL hình ảnh cho cuộc trò chuyện đa phương thức", + "点击 + 按钮添加图片URL进行多模态对话": "Nhấp + để thêm URL hình ảnh cho cuộc trò chuyện đa phương thức", + "已添加": "Đã thêm", + "张图片": "hình ảnh", + "自定义模式下不可用": "Không khả dụng trong chế độ tùy chỉnh", + "控制输出的随机性和创造性": "Kiểm soát tính ngẫu nhiên và sáng tạo của đầu ra", + "核采样,控制词汇选择的多样性": "Lấy mẫu hạt nhân, kiểm soát sự đa dạng của lựa chọn từ vựng", + "频率惩罚,减少重复词汇的出现": "Phạt tần suất, giảm sự lặp lại của từ", + "存在惩罚,鼓励讨论新话题": "Phạt sự hiện diện, khuyến khích chủ đề mới", + "流式输出": "Đầu ra luồng", + "暂无SSE响应数据": "Không có dữ liệu phản hồi SSE", + "SSE数据流": "Luồng dữ liệu SSE", + "解析错误": "Lỗi phân tích", + "有 Reasoning": "Có lập luận", + "全部收起": "Thu gọn tất cả", + "全部展开": "Mở rộng tất cả", + "SSE 事件": "Sự kiện SSE", + "JSON格式错误": "Lỗi định dạng JSON", + "自定义请求体模式": "Chế độ nội dung yêu cầu tùy chỉnh", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "Khi được bật, nội dung yêu cầu tùy chỉnh của bạn sẽ được sử dụng cho các yêu cầu API và cài đặt tham số trong bảng cấu hình mô hình sẽ bị bỏ qua.", + "请求体 JSON": "Nội dung yêu cầu JSON", + "格式正确": "Định dạng hợp lệ", + "格式错误": "Định dạng không hợp lệ", + "格式化": "Định dạng", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "Vui lòng nhập nội dung yêu cầu có định dạng JSON hợp lệ. Bạn có thể tham khảo định dạng nội dung yêu cầu mặc định trong bảng xem trước.", + "默认用户消息": "Xin chào", + "默认助手消息": "Xin chào! Tôi có thể giúp gì cho bạn?", + "可选,用于复现结果": "Tùy chọn, để tái tạo kết quả", + "随机种子 (留空为随机)": "Hạt giống ngẫu nhiên (để trống cho ngẫu nhiên)" } } \ No newline at end of file diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 541912b20..042f82af1 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -2076,6 +2076,46 @@ "Creem 介绍": "Creem 是一个简单的支付处理平台,支持固定金额产品销售,以及订阅销售。", "Creem Setting Tips": "Creem 只支持预设的固定金额产品,这产品以及价格需要提前在Creem网站内创建配置,所以不支持自定义动态金额充值。在Creem端配置产品的名字以及价格,获取Product Id 后填到下面的产品,在new-api为该产品设置充值额度,以及展示价格。", "正在跳转 GitHub...": "正在跳转 GitHub...", - "请求超时,请刷新页面后重新发起 GitHub 登录": "请求超时,请刷新页面后重新发起 GitHub 登录" + "请求超时,请刷新页面后重新发起 GitHub 登录": "请求超时,请刷新页面后重新发起 GitHub 登录", + "请先在设置中启用图片功能": "请先在设置中启用图片功能", + "图片已添加": "图片已添加", + "无法添加图片": "无法添加图片", + "粘贴图片失败": "粘贴图片失败", + "支持 Ctrl+V 粘贴图片": "支持 Ctrl+V 粘贴图片", + "已复制全部数据": "已复制全部数据", + "流式响应完成": "流式响应完成", + "图片地址": "图片地址", + "已在自定义模式中忽略": "已在自定义模式中忽略", + "停用": "停用", + "图片功能在自定义请求体模式下不可用": "图片功能在自定义请求体模式下不可用", + "启用后可添加图片URL进行多模态对话": "启用后可添加图片URL进行多模态对话", + "点击 + 按钮添加图片URL进行多模态对话": "点击 + 按钮添加图片URL进行多模态对话", + "已添加": "已添加", + "张图片": "张图片", + "自定义模式下不可用": "自定义模式下不可用", + "控制输出的随机性和创造性": "控制输出的随机性和创造性", + "核采样,控制词汇选择的多样性": "核采样,控制词汇选择的多样性", + "频率惩罚,减少重复词汇的出现": "频率惩罚,减少重复词汇的出现", + "存在惩罚,鼓励讨论新话题": "存在惩罚,鼓励讨论新话题", + "流式输出": "流式输出", + "暂无SSE响应数据": "暂无SSE响应数据", + "SSE数据流": "SSE数据流", + "解析错误": "解析错误", + "有 Reasoning": "有 Reasoning", + "全部收起": "全部收起", + "全部展开": "全部展开", + "SSE 事件": "SSE 事件", + "JSON格式错误": "JSON格式错误", + "自定义请求体模式": "自定义请求体模式", + "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。": "启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。", + "请求体 JSON": "请求体 JSON", + "格式正确": "格式正确", + "格式错误": "格式错误", + "格式化": "格式化", + "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。": "请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。", + "默认用户消息": "你好", + "默认助手消息": "你好!有什么我可以帮助你的吗?", + "可选,用于复现结果": "可选,用于复现结果", + "随机种子 (留空为随机)": "随机种子 (留空为随机)" } -} +} \ No newline at end of file diff --git a/web/src/pages/Playground/index.jsx b/web/src/pages/Playground/index.jsx index c2083fdea..6b7f8d16a 100644 --- a/web/src/pages/Playground/index.jsx +++ b/web/src/pages/Playground/index.jsx @@ -59,6 +59,7 @@ import { } from '../../components/playground/OptimizedComponents'; import ChatArea from '../../components/playground/ChatArea'; import FloatingButtons from '../../components/playground/FloatingButtons'; +import { PlaygroundProvider } from '../../contexts/PlaygroundContext'; // 生成头像 const generateAvatarDataUrl = (username) => { @@ -436,8 +437,26 @@ const Playground = () => { setTimeout(() => saveMessagesImmediately([]), 0); }, [setMessage, saveMessagesImmediately]); + // 处理粘贴图片 + const handlePasteImage = useCallback((base64Data) => { + if (!inputs.imageEnabled) { + return; + } + // 添加图片到 imageUrls 数组 + const newUrls = [...(inputs.imageUrls || []), base64Data]; + handleInputChange('imageUrls', newUrls); + }, [inputs.imageEnabled, inputs.imageUrls, handleInputChange]); + + // Playground Context 值 + const playgroundContextValue = { + onPasteImage: handlePasteImage, + imageUrls: inputs.imageUrls || [], + imageEnabled: inputs.imageEnabled || false, + }; + return ( -
+ +
{(showSettings || !isMobile) && ( {
+
); };