mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
style(ModelList): adjust GroupHeader height and update icon in ModelListItem
This commit is contained in:
parent
c9837eaa71
commit
ce804ce02b
@ -128,3 +128,4 @@ releaseInfo:
|
|||||||
内存泄漏修复:优化代码逻辑,解决内存泄漏问题,提升运行稳定性
|
内存泄漏修复:优化代码逻辑,解决内存泄漏问题,提升运行稳定性
|
||||||
嵌入模型简化:降低嵌入模型配置复杂度,提高易用性
|
嵌入模型简化:降低嵌入模型配置复杂度,提高易用性
|
||||||
MCP Tool 长时间运行:增强 MCP 工具的稳定性,支持长时间任务执行
|
MCP Tool 长时间运行:增强 MCP 工具的稳定性,支持长时间任务执行
|
||||||
|
设置页面优化:优化设置页面布局,提升用户体验
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
import ModelEditContent from '@renderer/components/ModelList/ModelEditContent'
|
|
||||||
import { TopView } from '@renderer/components/TopView'
|
|
||||||
import { Model, Provider } from '@renderer/types'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
interface ShowParams {
|
|
||||||
provider: Provider
|
|
||||||
model: Model
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props extends ShowParams {
|
|
||||||
resolve: (data?: Model) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopupContainer: React.FC<Props> = ({ provider, model, resolve }) => {
|
|
||||||
const handleUpdateModel = (updatedModel: Model) => {
|
|
||||||
resolve(updatedModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
resolve(undefined) // Resolve with no data on close
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelEditContent
|
|
||||||
provider={provider}
|
|
||||||
model={model}
|
|
||||||
onUpdateModel={handleUpdateModel}
|
|
||||||
open={true} // Always open when rendered by TopView
|
|
||||||
onClose={handleClose}
|
|
||||||
key={model.id} // Ensure re-mount when model changes
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const TopViewKey = 'EditModelPopup'
|
|
||||||
|
|
||||||
export default class EditModelPopup {
|
|
||||||
static hide() {
|
|
||||||
TopView.hide(TopViewKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
static show(props: ShowParams) {
|
|
||||||
return new Promise<Model | undefined>((resolve) => {
|
|
||||||
TopView.show(
|
|
||||||
<PopupContainer
|
|
||||||
{...props}
|
|
||||||
resolve={(v) => {
|
|
||||||
resolve(v)
|
|
||||||
this.hide()
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
TopViewKey
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "Tool",
|
"function_calling": "Tool",
|
||||||
"reasoning": "Reasoning",
|
"reasoning": "Reasoning",
|
||||||
"rerank": "Reranker",
|
"rerank": "Reranker",
|
||||||
"select": "Select Model Types",
|
"select": "Model Types",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"vision": "Vision",
|
"vision": "Vision",
|
||||||
"websearch": "WebSearch"
|
"websearch": "WebSearch"
|
||||||
@ -2716,6 +2716,8 @@
|
|||||||
"jsonSaveError": "Failed to save JSON configuration.",
|
"jsonSaveError": "Failed to save JSON configuration.",
|
||||||
"jsonSaveSuccess": "JSON configuration has been saved.",
|
"jsonSaveSuccess": "JSON configuration has been saved.",
|
||||||
"logoUrl": "Logo URL",
|
"logoUrl": "Logo URL",
|
||||||
|
"longRunning": "Long Running Mode",
|
||||||
|
"longRunningTooltip": "When enabled, the server supports long-running tasks. When receiving progress notifications, the timeout will be reset and the maximum execution time will be extended to 10 minutes.",
|
||||||
"missingDependencies": "is Missing, please install it to continue.",
|
"missingDependencies": "is Missing, please install it to continue.",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Curated MCP Server List",
|
"awesome": "Curated MCP Server List",
|
||||||
@ -2962,8 +2964,8 @@
|
|||||||
"tooltip": "Optional e.g. GPT-4"
|
"tooltip": "Optional e.g. GPT-4"
|
||||||
},
|
},
|
||||||
"supported_text_delta": {
|
"supported_text_delta": {
|
||||||
"label": "Incremental text output",
|
"label": "Support incremental text output",
|
||||||
"tooltip": "When the model is not supported, close the button"
|
"tooltip": "The model returns text incrementally, rather than all at once. Enabled by default, if the model does not support it, please disable this option"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_key": "API Key",
|
"api_key": "API Key",
|
||||||
@ -3019,7 +3021,6 @@
|
|||||||
"provider_name": "Provider Name",
|
"provider_name": "Provider Name",
|
||||||
"quick_assistant_default_tag": "Default",
|
"quick_assistant_default_tag": "Default",
|
||||||
"quick_assistant_model": "Quick Assistant Model",
|
"quick_assistant_model": "Quick Assistant Model",
|
||||||
"quick_assistant_model_description": "Default model used by Quick Assistant",
|
|
||||||
"quick_assistant_selection": "Select Assistant",
|
"quick_assistant_selection": "Select Assistant",
|
||||||
"topic_naming_model": "Topic Naming Model",
|
"topic_naming_model": "Topic Naming Model",
|
||||||
"topic_naming_model_description": "Model used when automatically naming a new topic",
|
"topic_naming_model_description": "Model used when automatically naming a new topic",
|
||||||
@ -3337,7 +3338,7 @@
|
|||||||
"title": "Pre Process",
|
"title": "Pre Process",
|
||||||
"tooltip": "In Settings -> Tools, set a document preprocessing service provider. Document preprocessing can effectively improve the retrieval performance of complex format documents and scanned documents."
|
"tooltip": "In Settings -> Tools, set a document preprocessing service provider. Document preprocessing can effectively improve the retrieval performance of complex format documents and scanned documents."
|
||||||
},
|
},
|
||||||
"title": "Tools Settings",
|
"title": "Other Settings",
|
||||||
"websearch": {
|
"websearch": {
|
||||||
"apikey": "API key",
|
"apikey": "API key",
|
||||||
"blacklist": "Blacklist",
|
"blacklist": "Blacklist",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "ツール",
|
"function_calling": "ツール",
|
||||||
"reasoning": "推論",
|
"reasoning": "推論",
|
||||||
"rerank": "再順序付け",
|
"rerank": "再順序付け",
|
||||||
"select": "モデルタイプを選択",
|
"select": "モデルタイプ",
|
||||||
"text": "テキスト",
|
"text": "テキスト",
|
||||||
"vision": "画像",
|
"vision": "画像",
|
||||||
"websearch": "ウェブ検索"
|
"websearch": "ウェブ検索"
|
||||||
@ -2716,6 +2716,8 @@
|
|||||||
"jsonSaveError": "JSON設定の保存に失敗しました",
|
"jsonSaveError": "JSON設定の保存に失敗しました",
|
||||||
"jsonSaveSuccess": "JSON設定が保存されました。",
|
"jsonSaveSuccess": "JSON設定が保存されました。",
|
||||||
"logoUrl": "ロゴURL",
|
"logoUrl": "ロゴURL",
|
||||||
|
"longRunning": "長時間運行モード",
|
||||||
|
"longRunningTooltip": "このオプションを有効にすると、サーバーは長時間のタスクをサポートします。進行状況通知を受信すると、タイムアウトがリセットされ、最大実行時間が10分に延長されます。",
|
||||||
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "厳選された MCP サーバーリスト",
|
"awesome": "厳選された MCP サーバーリスト",
|
||||||
@ -2962,8 +2964,8 @@
|
|||||||
"tooltip": "例:GPT-4"
|
"tooltip": "例:GPT-4"
|
||||||
},
|
},
|
||||||
"supported_text_delta": {
|
"supported_text_delta": {
|
||||||
"label": "インクリメンタルテキスト出力",
|
"label": "インクリメンタルテキスト出力のサポート",
|
||||||
"tooltip": "モデルがサポートされていない場合は、ボタンを閉じます"
|
"tooltip": "モデルがテキストをチャンクで返す場合、デフォルトで有効になっています。モデルがサポートしていない場合は、このオプションを無効にしてください"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_key": "API キー",
|
"api_key": "API キー",
|
||||||
@ -3019,7 +3021,6 @@
|
|||||||
"provider_name": "プロバイダー名",
|
"provider_name": "プロバイダー名",
|
||||||
"quick_assistant_default_tag": "デフォルト",
|
"quick_assistant_default_tag": "デフォルト",
|
||||||
"quick_assistant_model": "クイックアシスタントモデル",
|
"quick_assistant_model": "クイックアシスタントモデル",
|
||||||
"quick_assistant_model_description": "クイックアシスタントで使用されるデフォルトモデル",
|
|
||||||
"quick_assistant_selection": "アシスタントを選択します",
|
"quick_assistant_selection": "アシスタントを選択します",
|
||||||
"topic_naming_model": "トピック命名モデル",
|
"topic_naming_model": "トピック命名モデル",
|
||||||
"topic_naming_model_description": "新しいトピックを自動的に命名する際に使用されるモデル",
|
"topic_naming_model_description": "新しいトピックを自動的に命名する際に使用されるモデル",
|
||||||
@ -3337,7 +3338,7 @@
|
|||||||
"title": "前処理",
|
"title": "前処理",
|
||||||
"tooltip": "設定 → ツールで、ドキュメント前処理サービスプロバイダーを設定します。ドキュメント前処理は、複雑な形式のドキュメントやスキャンされたドキュメントの検索性能を効果的に向上させます。"
|
"tooltip": "設定 → ツールで、ドキュメント前処理サービスプロバイダーを設定します。ドキュメント前処理は、複雑な形式のドキュメントやスキャンされたドキュメントの検索性能を効果的に向上させます。"
|
||||||
},
|
},
|
||||||
"title": "ツール設定",
|
"title": "その他の設定",
|
||||||
"websearch": {
|
"websearch": {
|
||||||
"apikey": "APIキー",
|
"apikey": "APIキー",
|
||||||
"blacklist": "ブラックリスト",
|
"blacklist": "ブラックリスト",
|
||||||
|
|||||||
@ -2716,6 +2716,8 @@
|
|||||||
"jsonSaveError": "Не удалось сохранить конфигурацию JSON",
|
"jsonSaveError": "Не удалось сохранить конфигурацию JSON",
|
||||||
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
||||||
"logoUrl": "URL логотипа",
|
"logoUrl": "URL логотипа",
|
||||||
|
"longRunning": "Длительный режим работы",
|
||||||
|
"longRunningTooltip": "Включив эту опцию, сервер будет поддерживать длительные задачи. При получении уведомлений о ходе выполнения будет сброшен тайм-аут и максимальное время выполнения будет увеличено до 10 минут.",
|
||||||
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Кураторский список серверов MCP",
|
"awesome": "Кураторский список серверов MCP",
|
||||||
@ -2962,8 +2964,8 @@
|
|||||||
"tooltip": "Необязательно, например, GPT-4"
|
"tooltip": "Необязательно, например, GPT-4"
|
||||||
},
|
},
|
||||||
"supported_text_delta": {
|
"supported_text_delta": {
|
||||||
"label": "Инкрементный текст вывод",
|
"label": "Поддержка инкрементного текстового вывода",
|
||||||
"tooltip": "Когда модель не поддерживается, закройте кнопку"
|
"tooltip": "Модель возвращает текст по частям, а не одним блоком, по умолчанию включено, если модель не поддерживает, закройте эту опцию"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_key": "API ключ",
|
"api_key": "API ключ",
|
||||||
@ -3019,7 +3021,6 @@
|
|||||||
"provider_name": "Имя провайдера",
|
"provider_name": "Имя провайдера",
|
||||||
"quick_assistant_default_tag": "умолчанию",
|
"quick_assistant_default_tag": "умолчанию",
|
||||||
"quick_assistant_model": "Модель быстрого помощника",
|
"quick_assistant_model": "Модель быстрого помощника",
|
||||||
"quick_assistant_model_description": "Модель по умолчанию, используемая быстрым помощником",
|
|
||||||
"quick_assistant_selection": "Выберите помощника",
|
"quick_assistant_selection": "Выберите помощника",
|
||||||
"topic_naming_model": "Модель именования топика",
|
"topic_naming_model": "Модель именования топика",
|
||||||
"topic_naming_model_description": "Модель, используемая при автоматическом именовании нового топика",
|
"topic_naming_model_description": "Модель, используемая при автоматическом именовании нового топика",
|
||||||
@ -3337,7 +3338,7 @@
|
|||||||
"title": "Предварительная обработка",
|
"title": "Предварительная обработка",
|
||||||
"tooltip": "В настройках (Настройки -> Инструменты) укажите поставщика услуги предварительной обработки документов. Предварительная обработка документов может значительно повысить эффективность поиска для документов сложных форматов и отсканированных документов."
|
"tooltip": "В настройках (Настройки -> Инструменты) укажите поставщика услуги предварительной обработки документов. Предварительная обработка документов может значительно повысить эффективность поиска для документов сложных форматов и отсканированных документов."
|
||||||
},
|
},
|
||||||
"title": "Настройки инструментов",
|
"title": "Другие настройки",
|
||||||
"websearch": {
|
"websearch": {
|
||||||
"apikey": "API ключ",
|
"apikey": "API ключ",
|
||||||
"blacklist": "Черный список",
|
"blacklist": "Черный список",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "工具",
|
"function_calling": "工具",
|
||||||
"reasoning": "推理",
|
"reasoning": "推理",
|
||||||
"rerank": "重排",
|
"rerank": "重排",
|
||||||
"select": "选择模型类型",
|
"select": "模型类型",
|
||||||
"text": "文本",
|
"text": "文本",
|
||||||
"vision": "视觉",
|
"vision": "视觉",
|
||||||
"websearch": "联网"
|
"websearch": "联网"
|
||||||
@ -2716,6 +2716,8 @@
|
|||||||
"jsonSaveError": "保存 JSON 配置失败",
|
"jsonSaveError": "保存 JSON 配置失败",
|
||||||
"jsonSaveSuccess": "JSON 配置已保存",
|
"jsonSaveSuccess": "JSON 配置已保存",
|
||||||
"logoUrl": "标志网址",
|
"logoUrl": "标志网址",
|
||||||
|
"longRunning": "长时间运行模式",
|
||||||
|
"longRunningTooltip": "启用后,服务器支持长时间任务,接收到进度通知时会重置超时计时器,并延长最大超时时间至10分钟",
|
||||||
"missingDependencies": "缺失,请安装它以继续",
|
"missingDependencies": "缺失,请安装它以继续",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "精选的 MCP 服务器列表",
|
"awesome": "精选的 MCP 服务器列表",
|
||||||
@ -2962,8 +2964,8 @@
|
|||||||
"tooltip": "例如 GPT-4"
|
"tooltip": "例如 GPT-4"
|
||||||
},
|
},
|
||||||
"supported_text_delta": {
|
"supported_text_delta": {
|
||||||
"label": "增量文本输出",
|
"label": "支持增量文本输出",
|
||||||
"tooltip": "当模型不支持的时候,将该按钮关闭"
|
"tooltip": "模型每次返回文本增量,而不是一次性返回所有文本,默认开启,如果模型不支持,请关闭"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_key": "API 密钥",
|
"api_key": "API 密钥",
|
||||||
@ -3019,7 +3021,6 @@
|
|||||||
"provider_name": "服务商名称",
|
"provider_name": "服务商名称",
|
||||||
"quick_assistant_default_tag": "默认",
|
"quick_assistant_default_tag": "默认",
|
||||||
"quick_assistant_model": "快捷助手模型",
|
"quick_assistant_model": "快捷助手模型",
|
||||||
"quick_assistant_model_description": "快捷助手使用的默认模型",
|
|
||||||
"quick_assistant_selection": "选择助手",
|
"quick_assistant_selection": "选择助手",
|
||||||
"topic_naming_model": "话题命名模型",
|
"topic_naming_model": "话题命名模型",
|
||||||
"topic_naming_model_description": "自动命名新话题时使用的模型",
|
"topic_naming_model_description": "自动命名新话题时使用的模型",
|
||||||
@ -3337,7 +3338,7 @@
|
|||||||
"title": "文档预处理",
|
"title": "文档预处理",
|
||||||
"tooltip": "在设置 -> 工具中设置文档预处理服务商,文档预处理可以有效提升复杂格式文档与扫描版文档的检索效果"
|
"tooltip": "在设置 -> 工具中设置文档预处理服务商,文档预处理可以有效提升复杂格式文档与扫描版文档的检索效果"
|
||||||
},
|
},
|
||||||
"title": "工具设置",
|
"title": "其他设置",
|
||||||
"websearch": {
|
"websearch": {
|
||||||
"apikey": "API 密钥",
|
"apikey": "API 密钥",
|
||||||
"blacklist": "黑名单",
|
"blacklist": "黑名单",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "工具",
|
"function_calling": "工具",
|
||||||
"reasoning": "推理",
|
"reasoning": "推理",
|
||||||
"rerank": "重排",
|
"rerank": "重排",
|
||||||
"select": "選擇模型類型",
|
"select": "模型類型",
|
||||||
"text": "文字",
|
"text": "文字",
|
||||||
"vision": "視覺",
|
"vision": "視覺",
|
||||||
"websearch": "網路搜尋"
|
"websearch": "網路搜尋"
|
||||||
@ -2716,6 +2716,8 @@
|
|||||||
"jsonSaveError": "保存 JSON 配置失敗",
|
"jsonSaveError": "保存 JSON 配置失敗",
|
||||||
"jsonSaveSuccess": "JSON 配置已儲存",
|
"jsonSaveSuccess": "JSON 配置已儲存",
|
||||||
"logoUrl": "標誌網址",
|
"logoUrl": "標誌網址",
|
||||||
|
"longRunning": "長時間運行模式",
|
||||||
|
"longRunningTooltip": "啟用後,伺服器支援長時間任務,接收到進度通知時會重置超時計時器,並延長最大超時時間至10分鐘",
|
||||||
"missingDependencies": "缺失,請安裝它以繼續",
|
"missingDependencies": "缺失,請安裝它以繼續",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "精選的 MCP 伺服器清單",
|
"awesome": "精選的 MCP 伺服器清單",
|
||||||
@ -2962,8 +2964,8 @@
|
|||||||
"tooltip": "例如 GPT-4"
|
"tooltip": "例如 GPT-4"
|
||||||
},
|
},
|
||||||
"supported_text_delta": {
|
"supported_text_delta": {
|
||||||
"label": "增量文本輸出",
|
"label": "支持增量文本輸出",
|
||||||
"tooltip": "當模型不支持的時候,將該按鈕關閉"
|
"tooltip": "模型每次返回文本增量,而不是一次性返回所有文本,預設開啟,如果模型不支持,請關閉"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_key": "API 密鑰",
|
"api_key": "API 密鑰",
|
||||||
@ -3019,7 +3021,6 @@
|
|||||||
"provider_name": "提供者名稱",
|
"provider_name": "提供者名稱",
|
||||||
"quick_assistant_default_tag": "預設",
|
"quick_assistant_default_tag": "預設",
|
||||||
"quick_assistant_model": "快捷助手模型",
|
"quick_assistant_model": "快捷助手模型",
|
||||||
"quick_assistant_model_description": "快捷助手使用的預設模型",
|
|
||||||
"quick_assistant_selection": "選擇助手",
|
"quick_assistant_selection": "選擇助手",
|
||||||
"topic_naming_model": "話題命名模型",
|
"topic_naming_model": "話題命名模型",
|
||||||
"topic_naming_model_description": "自動命名新話題時使用的模型",
|
"topic_naming_model_description": "自動命名新話題時使用的模型",
|
||||||
@ -3337,7 +3338,7 @@
|
|||||||
"title": "前置處理",
|
"title": "前置處理",
|
||||||
"tooltip": "在「設定」->「工具」中設定文件預處理服務供應商。文件預處理可有效提升複雜格式文件及掃描文件的檢索效能"
|
"tooltip": "在「設定」->「工具」中設定文件預處理服務供應商。文件預處理可有效提升複雜格式文件及掃描文件的檢索效能"
|
||||||
},
|
},
|
||||||
"title": "工具設定",
|
"title": "其他設定",
|
||||||
"websearch": {
|
"websearch": {
|
||||||
"apikey": "API 金鑰",
|
"apikey": "API 金鑰",
|
||||||
"blacklist": "黑名單",
|
"blacklist": "黑名單",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "κλήση συνάρτησης",
|
"function_calling": "κλήση συνάρτησης",
|
||||||
"reasoning": "λογική",
|
"reasoning": "λογική",
|
||||||
"rerank": "Τακτοποιώ",
|
"rerank": "Τακτοποιώ",
|
||||||
"select": "Επιλέξτε τύπο μοντέλου",
|
"select": "Τύποι μοντέλου",
|
||||||
"text": "κείμενο",
|
"text": "κείμενο",
|
||||||
"vision": "εικόνα",
|
"vision": "εικόνα",
|
||||||
"websearch": "δικτύωση"
|
"websearch": "δικτύωση"
|
||||||
@ -3018,7 +3018,6 @@
|
|||||||
"provider_name": "Όνομα Παρόχου",
|
"provider_name": "Όνομα Παρόχου",
|
||||||
"quick_assistant_default_tag": "Προεπιλογή",
|
"quick_assistant_default_tag": "Προεπιλογή",
|
||||||
"quick_assistant_model": "Μοντέλο Γρήγορου Βοηθού",
|
"quick_assistant_model": "Μοντέλο Γρήγορου Βοηθού",
|
||||||
"quick_assistant_model_description": "Προεπιλεγμένο μοντέλο που χρησιμοποιείται από το Γρήγορο Βοηθό",
|
|
||||||
"quick_assistant_selection": "Επιλογή Βοηθού",
|
"quick_assistant_selection": "Επιλογή Βοηθού",
|
||||||
"topic_naming_model": "Μοντέλο αναδόμησης θεμάτων",
|
"topic_naming_model": "Μοντέλο αναδόμησης θεμάτων",
|
||||||
"topic_naming_model_description": "Το μοντέλο που χρησιμοποιείται όταν αυτόματα ονομάζεται ένα νέο θέμα",
|
"topic_naming_model_description": "Το μοντέλο που χρησιμοποιείται όταν αυτόματα ονομάζεται ένα νέο θέμα",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "Llamada a función",
|
"function_calling": "Llamada a función",
|
||||||
"reasoning": "Razonamiento",
|
"reasoning": "Razonamiento",
|
||||||
"rerank": "Reclasificar",
|
"rerank": "Reclasificar",
|
||||||
"select": "Seleccionar tipo de modelo",
|
"select": "Tipos de modelo",
|
||||||
"text": "Texto",
|
"text": "Texto",
|
||||||
"vision": "Imagen",
|
"vision": "Imagen",
|
||||||
"websearch": "Búsqueda en línea"
|
"websearch": "Búsqueda en línea"
|
||||||
@ -3018,7 +3018,6 @@
|
|||||||
"provider_name": "Nombre del proveedor",
|
"provider_name": "Nombre del proveedor",
|
||||||
"quick_assistant_default_tag": "Predeterminado",
|
"quick_assistant_default_tag": "Predeterminado",
|
||||||
"quick_assistant_model": "Modelo del asistente rápido",
|
"quick_assistant_model": "Modelo del asistente rápido",
|
||||||
"quick_assistant_model_description": "Modelo predeterminado utilizado por el asistente rápido",
|
|
||||||
"quick_assistant_selection": "Seleccionar asistente",
|
"quick_assistant_selection": "Seleccionar asistente",
|
||||||
"topic_naming_model": "Modelo de nombramiento de temas",
|
"topic_naming_model": "Modelo de nombramiento de temas",
|
||||||
"topic_naming_model_description": "Modelo utilizado para nombrar temas automáticamente",
|
"topic_naming_model_description": "Modelo utilizado para nombrar temas automáticamente",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "Appel de fonction",
|
"function_calling": "Appel de fonction",
|
||||||
"reasoning": "Raisonnement",
|
"reasoning": "Raisonnement",
|
||||||
"rerank": "Reclasser",
|
"rerank": "Reclasser",
|
||||||
"select": "Sélectionnez le type de modèle",
|
"select": "Types de modèle",
|
||||||
"text": "Texte",
|
"text": "Texte",
|
||||||
"vision": "Image",
|
"vision": "Image",
|
||||||
"websearch": "Recherche web"
|
"websearch": "Recherche web"
|
||||||
@ -3018,7 +3018,6 @@
|
|||||||
"provider_name": "Nom du fournisseur",
|
"provider_name": "Nom du fournisseur",
|
||||||
"quick_assistant_default_tag": "Par défaut",
|
"quick_assistant_default_tag": "Par défaut",
|
||||||
"quick_assistant_model": "Modèle de l'assistant rapide",
|
"quick_assistant_model": "Modèle de l'assistant rapide",
|
||||||
"quick_assistant_model_description": "Modèle par défaut utilisé par l'assistant rapide",
|
|
||||||
"quick_assistant_selection": "Sélectionner l'assistant",
|
"quick_assistant_selection": "Sélectionner l'assistant",
|
||||||
"topic_naming_model": "Modèle de renommage des sujets",
|
"topic_naming_model": "Modèle de renommage des sujets",
|
||||||
"topic_naming_model_description": "Modèle utilisé pour le renommage automatique des nouveaux sujets",
|
"topic_naming_model_description": "Modèle utilisé pour le renommage automatique des nouveaux sujets",
|
||||||
|
|||||||
@ -1463,7 +1463,7 @@
|
|||||||
"function_calling": "chamada de função",
|
"function_calling": "chamada de função",
|
||||||
"reasoning": "raciocínio",
|
"reasoning": "raciocínio",
|
||||||
"rerank": "Reclassificar",
|
"rerank": "Reclassificar",
|
||||||
"select": "selecione o tipo de modelo",
|
"select": "Tipos de modelo",
|
||||||
"text": "texto",
|
"text": "texto",
|
||||||
"vision": "imagem",
|
"vision": "imagem",
|
||||||
"websearch": "Procurar na web"
|
"websearch": "Procurar na web"
|
||||||
@ -3018,7 +3018,6 @@
|
|||||||
"provider_name": "Nome do Provedor",
|
"provider_name": "Nome do Provedor",
|
||||||
"quick_assistant_default_tag": "Padrão",
|
"quick_assistant_default_tag": "Padrão",
|
||||||
"quick_assistant_model": "Modelo do Assistente Rápido",
|
"quick_assistant_model": "Modelo do Assistente Rápido",
|
||||||
"quick_assistant_model_description": "Modelo padrão usado pelo assistente rápido",
|
|
||||||
"quick_assistant_selection": "Selecionar Assistente",
|
"quick_assistant_selection": "Selecionar Assistente",
|
||||||
"topic_naming_model": "Modelo de nomenclatura de tópicos",
|
"topic_naming_model": "Modelo de nomenclatura de tópicos",
|
||||||
"topic_naming_model_description": "Modelo usado para nomear tópicos automaticamente",
|
"topic_naming_model_description": "Modelo usado para nomear tópicos automaticamente",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { ImportOutlined, PlusOutlined } from '@ant-design/icons'
|
import { ImportOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import CustomTag from '@renderer/components/CustomTag'
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import ListItem from '@renderer/components/ListItem'
|
import ListItem from '@renderer/components/ListItem'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
@ -213,11 +214,11 @@ const AgentsPage: FC = () => {
|
|||||||
{getLocalizedGroupName(group)}
|
{getLocalizedGroupName(group)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{
|
{
|
||||||
<div style={{ minWidth: 40, textAlign: 'center' }}>
|
<HStack alignItems="center" justifyContent="center" style={{ minWidth: 40 }}>
|
||||||
<CustomTag color="#A0A0A0" size={8}>
|
<CustomTag color="#A0A0A0" size={8}>
|
||||||
{agentGroups[group].length}
|
{agentGroups[group].length}
|
||||||
</CustomTag>
|
</CustomTag>
|
||||||
</div>
|
</HStack>
|
||||||
}
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,6 +63,7 @@ const FilesPage: FC = () => {
|
|||||||
okText={t('common.confirm')}
|
okText={t('common.confirm')}
|
||||||
cancelText={t('common.cancel')}
|
cancelText={t('common.cancel')}
|
||||||
onConfirm={() => handleDelete(file.id, t)}
|
onConfirm={() => handleDelete(file.id, t)}
|
||||||
|
placement="left"
|
||||||
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
|
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
|
||||||
<Button type="text" danger icon={<DeleteIcon size={14} className="lucide-custom" />} />
|
<Button type="text" danger icon={<DeleteIcon size={14} className="lucide-custom" />} />
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|||||||
@ -289,9 +289,12 @@ export const ItemHeader = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(var(--navbar-height) + 14px);
|
|
||||||
right: 16px;
|
right: 16px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
top: calc(var(--navbar-height) + 12px);
|
||||||
|
[navbar-position='top'] & {
|
||||||
|
top: calc(var(--navbar-height) + 10px);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StatusIconWrapper = styled.div`
|
export const StatusIconWrapper = styled.div`
|
||||||
|
|||||||
@ -92,8 +92,8 @@ const LaunchpadPage: FC = () => {
|
|||||||
<SectionTitle>{t('launchpad.minapps')}</SectionTitle>
|
<SectionTitle>{t('launchpad.minapps')}</SectionTitle>
|
||||||
<Grid>
|
<Grid>
|
||||||
{sortedMinapps.map((app) => (
|
{sortedMinapps.map((app) => (
|
||||||
<AppWrapper key={app.id} onClick={() => setTimeout(() => tabsService.closeTab('launchpad'), 350)}>
|
<AppWrapper key={app.id}>
|
||||||
<App app={app} size={56} />
|
<App app={app} size={56} onClick={() => setTimeout(() => tabsService.closeTab('launchpad'), 350)} />
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -633,8 +633,13 @@ const McpSettings: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Form.Item name="longRunning" label={t('settings.mcp.longRunning', 'Long Running')} valuePropName="checked">
|
<Form.Item
|
||||||
<Switch />
|
name="longRunning"
|
||||||
|
label={t('settings.mcp.longRunning', 'Long Running')}
|
||||||
|
tooltip={t('settings.mcp.longRunningTooltip')}
|
||||||
|
layout="horizontal"
|
||||||
|
valuePropName="checked">
|
||||||
|
<Switch size="small" style={{ marginLeft: 10 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="timeout"
|
name="timeout"
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
import { RedoOutlined } from '@ant-design/icons'
|
import { RedoOutlined } from '@ant-design/icons'
|
||||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import ModelSelector from '@renderer/components/ModelSelector'
|
import ModelSelector from '@renderer/components/ModelSelector'
|
||||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||||
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
||||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAssistants, useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant'
|
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
import { useProviders } from '@renderer/hooks/useProvider'
|
import { useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
||||||
import { useAppSelector } from '@renderer/store'
|
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setQuickAssistantId } from '@renderer/store/llm'
|
|
||||||
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { Button, Select, Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
import { find } from 'lodash'
|
import { find } from 'lodash'
|
||||||
import { CircleHelp, FolderPen, Languages, MessageSquareMore, Rocket, Settings2 } from 'lucide-react'
|
import { FolderPen, Languages, MessageSquareMore, Settings2 } from 'lucide-react'
|
||||||
import { FC, useCallback, useMemo } from 'react'
|
import { FC, useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..'
|
import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..'
|
||||||
import DefaultAssistantSettings from './DefaultAssistantSettings'
|
import DefaultAssistantSettings from './DefaultAssistantSettings'
|
||||||
@ -29,8 +25,6 @@ import TopicNamingModalPopup from './TopicNamingModalPopup'
|
|||||||
const ModelSettings: FC = () => {
|
const ModelSettings: FC = () => {
|
||||||
const { defaultModel, topicNamingModel, translateModel, setDefaultModel, setTopicNamingModel, setTranslateModel } =
|
const { defaultModel, topicNamingModel, translateModel, setDefaultModel, setTopicNamingModel, setTranslateModel } =
|
||||||
useDefaultModel()
|
useDefaultModel()
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
|
||||||
const { assistants } = useAssistants()
|
|
||||||
const { providers } = useProviders()
|
const { providers } = useProviders()
|
||||||
const allModels = providers.map((p) => p.models).flat()
|
const allModels = providers.map((p) => p.models).flat()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -38,7 +32,6 @@ const ModelSettings: FC = () => {
|
|||||||
const { translateModelPrompt } = useSettings()
|
const { translateModelPrompt } = useSettings()
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { quickAssistantId } = useAppSelector((state) => state.llm)
|
|
||||||
|
|
||||||
const modelPredicate = useCallback(
|
const modelPredicate = useCallback(
|
||||||
(m: Model) => !isEmbeddingModel(m) && !isRerankModel(m) && !isTextToImageModel(m),
|
(m: Model) => !isEmbeddingModel(m) && !isRerankModel(m) && !isTextToImageModel(m),
|
||||||
@ -149,127 +142,8 @@ const ModelSettings: FC = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
<SettingDescription>{t('settings.models.translate_model_description')}</SettingDescription>
|
<SettingDescription>{t('settings.models.translate_model_description')}</SettingDescription>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
<SettingGroup theme={theme}>
|
|
||||||
<HStack alignItems="center" style={{ marginBottom: 12 }}>
|
|
||||||
<SettingTitle>
|
|
||||||
<HStack alignItems="center" gap={10}>
|
|
||||||
<Rocket size={18} color="var(--color-text)" />
|
|
||||||
{t('settings.models.quick_assistant_model')}
|
|
||||||
<Tooltip title={t('selection.settings.user_modal.model.tooltip')} arrow>
|
|
||||||
<QuestionIcon size={12} />
|
|
||||||
</Tooltip>
|
|
||||||
<Spacer />
|
|
||||||
</HStack>
|
|
||||||
<HStack alignItems="center" gap={0}>
|
|
||||||
<StyledButton
|
|
||||||
type={!quickAssistantId ? 'primary' : 'default'}
|
|
||||||
onClick={() => dispatch(setQuickAssistantId(''))}
|
|
||||||
selected={!quickAssistantId}>
|
|
||||||
{t('settings.models.use_model')}
|
|
||||||
</StyledButton>
|
|
||||||
<StyledButton
|
|
||||||
type={quickAssistantId ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setQuickAssistantId(defaultAssistant.id))
|
|
||||||
}}
|
|
||||||
selected={!!quickAssistantId}>
|
|
||||||
{t('settings.models.use_assistant')}
|
|
||||||
</StyledButton>
|
|
||||||
</HStack>
|
|
||||||
</SettingTitle>
|
|
||||||
</HStack>
|
|
||||||
{!quickAssistantId ? null : (
|
|
||||||
<HStack alignItems="center" style={{ marginTop: 12 }}>
|
|
||||||
<Select
|
|
||||||
value={quickAssistantId || defaultAssistant.id}
|
|
||||||
style={{ width: 360 }}
|
|
||||||
onChange={(value) => dispatch(setQuickAssistantId(value))}
|
|
||||||
placeholder={t('settings.models.quick_assistant_selection')}>
|
|
||||||
<Select.Option key={defaultAssistant.id} value={defaultAssistant.id}>
|
|
||||||
<AssistantItem>
|
|
||||||
<ModelAvatar model={defaultAssistant.model || defaultModel} size={18} />
|
|
||||||
<AssistantName>{defaultAssistant.name}</AssistantName>
|
|
||||||
<Spacer />
|
|
||||||
<DefaultTag isCurrent={true}>{t('settings.models.quick_assistant_default_tag')}</DefaultTag>
|
|
||||||
</AssistantItem>
|
|
||||||
</Select.Option>
|
|
||||||
{assistants
|
|
||||||
.filter((a) => a.id !== defaultAssistant.id)
|
|
||||||
.map((a) => (
|
|
||||||
<Select.Option key={a.id} value={a.id}>
|
|
||||||
<AssistantItem>
|
|
||||||
<ModelAvatar model={a.model || defaultModel} size={18} />
|
|
||||||
<AssistantName>{a.name}</AssistantName>
|
|
||||||
<Spacer />
|
|
||||||
</AssistantItem>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
<SettingDescription>{t('settings.models.quick_assistant_model_description')}</SettingDescription>
|
|
||||||
</SettingGroup>
|
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuestionIcon = styled(CircleHelp)`
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)<{ selected: boolean }>`
|
|
||||||
border-radius: ${(props) => (props.selected ? '6px' : '6px')};
|
|
||||||
z-index: ${(props) => (props.selected ? 1 : 0)};
|
|
||||||
min-width: 80px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-right-width: 0; // No right border for the first button when not selected
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-left-width: 1px; // Ensure left border for the last button
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override Ant Design's default hover and focus styles for a cleaner look
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
z-index: 1;
|
|
||||||
border-color: ${(props) => (props.selected ? 'var(--ant-primary-color)' : 'var(--ant-primary-color-hover)')};
|
|
||||||
box-shadow: ${(props) =>
|
|
||||||
props.selected ? '0 0 0 2px var(--ant-primary-color-outline)' : '0 0 0 2px var(--ant-primary-color-outline)'};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const AssistantItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
height: 28px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const AssistantName = styled.span`
|
|
||||||
max-width: calc(100% - 60px);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Spacer = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
`
|
|
||||||
|
|
||||||
const DefaultTag = styled.span<{ isCurrent: boolean }>`
|
|
||||||
color: ${(props) => (props.isCurrent ? 'var(--color-primary)' : 'var(--color-text-3)')};
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ModelSettings
|
export default ModelSettings
|
||||||
|
|||||||
@ -0,0 +1,88 @@
|
|||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import ModelEditContent from '@renderer/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent'
|
||||||
|
import { useAppDispatch } from '@renderer/store'
|
||||||
|
import { setModel } from '@renderer/store/assistants'
|
||||||
|
import { Model, Provider } from '@renderer/types'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
interface ShowParams {
|
||||||
|
provider: Provider
|
||||||
|
model: Model
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends ShowParams {
|
||||||
|
resolve: (data?: Model) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupContainer: React.FC<Props> = ({ provider: _provider, model, resolve }) => {
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const { provider, updateProvider, models } = useProvider(_provider.id)
|
||||||
|
const { assistants } = useAssistants()
|
||||||
|
const { defaultModel, setDefaultModel } = useDefaultModel()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const onOk = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
EditModelPopup.hide()
|
||||||
|
resolve(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateModel = useCallback(
|
||||||
|
(updatedModel: Model) => {
|
||||||
|
const updatedModels = models.map((m) => (m.id === updatedModel.id ? updatedModel : m))
|
||||||
|
|
||||||
|
updateProvider({ models: updatedModels })
|
||||||
|
|
||||||
|
assistants.forEach((assistant) => {
|
||||||
|
if (assistant?.model?.id === updatedModel.id && assistant.model.provider === provider.id) {
|
||||||
|
dispatch(
|
||||||
|
setModel({
|
||||||
|
assistantId: assistant.id,
|
||||||
|
model: updatedModel
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (defaultModel?.id === updatedModel.id && defaultModel?.provider === provider.id) {
|
||||||
|
setDefaultModel(updatedModel)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[models, updateProvider, provider.id, assistants, defaultModel, dispatch, setDefaultModel]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelEditContent
|
||||||
|
provider={provider}
|
||||||
|
model={model}
|
||||||
|
open={open}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
onUpdateModel={onUpdateModel}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'EditModelPopup'
|
||||||
|
|
||||||
|
export default class EditModelPopup {
|
||||||
|
static hide() {
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
static show(props: ShowParams) {
|
||||||
|
return new Promise<Model | undefined>((resolve) => {
|
||||||
|
TopView.show(<PopupContainer {...props} resolve={resolve} />, TopViewKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,20 @@ import {
|
|||||||
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
|
||||||
import { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
|
import { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
|
||||||
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
|
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
|
||||||
import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select, Switch } from 'antd'
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
ModalProps,
|
||||||
|
Select,
|
||||||
|
Switch
|
||||||
|
} from 'antd'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { ChevronDown, ChevronUp, SaveIcon } from 'lucide-react'
|
import { ChevronDown, ChevronUp, SaveIcon } from 'lucide-react'
|
||||||
import { FC, useEffect, useRef, useState } from 'react'
|
import { FC, useEffect, useRef, useState } from 'react'
|
||||||
@ -22,12 +35,10 @@ interface ModelEditContentProps {
|
|||||||
provider: Provider
|
provider: Provider
|
||||||
model: Model
|
model: Model
|
||||||
onUpdateModel: (model: Model) => void
|
onUpdateModel: (model: Model) => void
|
||||||
open: boolean
|
|
||||||
onClose: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const symbols = ['$', '¥', '€', '£']
|
const symbols = ['$', '¥', '€', '£']
|
||||||
const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdateModel, open, onClose }) => {
|
const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, model, onUpdateModel, ...props }) => {
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showMoreSettings, setShowMoreSettings] = useState(false)
|
const [showMoreSettings, setShowMoreSettings] = useState(false)
|
||||||
@ -40,6 +51,36 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
|
|
||||||
const labelWidth = useDynamicLabelWidth([t('settings.models.add.endpoint_type.label')])
|
const labelWidth = useDynamicLabelWidth([t('settings.models.add.endpoint_type.label')])
|
||||||
|
|
||||||
|
// 自动保存函数
|
||||||
|
const autoSave = (overrides?: {
|
||||||
|
capabilities?: ModelCapability[]
|
||||||
|
supported_text_delta?: boolean
|
||||||
|
currencySymbol?: string
|
||||||
|
isCustomCurrency?: boolean
|
||||||
|
}) => {
|
||||||
|
const formValues = form.getFieldsValue()
|
||||||
|
const currentIsCustomCurrency = overrides?.isCustomCurrency ?? isCustomCurrency
|
||||||
|
const currentCurrencySymbol = overrides?.currencySymbol ?? currencySymbol
|
||||||
|
const finalCurrencySymbol = currentIsCustomCurrency
|
||||||
|
? formValues.customCurrencySymbol || currentCurrencySymbol
|
||||||
|
: formValues.currencySymbol || currentCurrencySymbol || '$'
|
||||||
|
const updatedModel: Model = {
|
||||||
|
...model,
|
||||||
|
id: formValues.id || model.id,
|
||||||
|
name: formValues.name || model.name,
|
||||||
|
group: formValues.group || model.group,
|
||||||
|
endpoint_type: provider.id === 'new-api' ? formValues.endpointType : model.endpoint_type,
|
||||||
|
capabilities: overrides?.capabilities ?? modelCapabilities,
|
||||||
|
supported_text_delta: overrides?.supported_text_delta ?? supportedTextDelta,
|
||||||
|
pricing: {
|
||||||
|
input_per_million_tokens: Number(formValues.input_per_million_tokens) || 0,
|
||||||
|
output_per_million_tokens: Number(formValues.output_per_million_tokens) || 0,
|
||||||
|
currencySymbol: finalCurrencySymbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdateModel(updatedModel)
|
||||||
|
}
|
||||||
|
|
||||||
const onFinish = (values: any) => {
|
const onFinish = (values: any) => {
|
||||||
const finalCurrencySymbol = isCustomCurrency ? values.customCurrencySymbol : values.currencySymbol
|
const finalCurrencySymbol = isCustomCurrency ? values.customCurrencySymbol : values.currencySymbol
|
||||||
const updatedModel: Model = {
|
const updatedModel: Model = {
|
||||||
@ -58,13 +99,7 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
}
|
}
|
||||||
onUpdateModel(updatedModel)
|
onUpdateModel(updatedModel)
|
||||||
setShowMoreSettings(false)
|
setShowMoreSettings(false)
|
||||||
onClose()
|
props.onOk?.(undefined as any)
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setShowMoreSettings(false)
|
|
||||||
setModelCapabilities(model.capabilities || [])
|
|
||||||
onClose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currencyOptions = [
|
const currencyOptions = [
|
||||||
@ -107,120 +142,15 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [showMoreSettings])
|
}, [showMoreSettings])
|
||||||
|
|
||||||
return (
|
// 监听modelCapabilities变化,自动保存(但跳过初始化时的保存)
|
||||||
<Modal
|
useEffect(() => {
|
||||||
title={t('models.edit')}
|
if (hasUserModified && showMoreSettings) {
|
||||||
open={open}
|
autoSave()
|
||||||
onCancel={handleClose}
|
|
||||||
footer={null}
|
|
||||||
transitionName="animation-move-down"
|
|
||||||
centered
|
|
||||||
afterOpenChange={(visible) => {
|
|
||||||
if (visible) {
|
|
||||||
form.getFieldInstance('id')?.focus()
|
|
||||||
} else {
|
|
||||||
setShowMoreSettings(false)
|
|
||||||
}
|
}
|
||||||
}}>
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
<Form
|
}, [modelCapabilities])
|
||||||
form={form}
|
|
||||||
labelCol={{ flex: provider.id === 'new-api' ? labelWidth : '110px' }}
|
const ModelCapability = () => {
|
||||||
labelAlign="left"
|
|
||||||
colon={false}
|
|
||||||
style={{ marginTop: 15 }}
|
|
||||||
initialValues={{
|
|
||||||
id: model.id,
|
|
||||||
name: model.name,
|
|
||||||
group: model.group,
|
|
||||||
endpointType: model.endpoint_type,
|
|
||||||
input_per_million_tokens: model.pricing?.input_per_million_tokens ?? 0,
|
|
||||||
output_per_million_tokens: model.pricing?.output_per_million_tokens ?? 0,
|
|
||||||
currencySymbol: symbols.includes(model.pricing?.currencySymbol || '$')
|
|
||||||
? model.pricing?.currencySymbol || '$'
|
|
||||||
: 'custom',
|
|
||||||
customCurrencySymbol: symbols.includes(model.pricing?.currencySymbol || '$')
|
|
||||||
? ''
|
|
||||||
: model.pricing?.currencySymbol || ''
|
|
||||||
}}
|
|
||||||
onFinish={onFinish}>
|
|
||||||
<Form.Item
|
|
||||||
name="id"
|
|
||||||
label={t('settings.models.add.model_id.label')}
|
|
||||||
tooltip={t('settings.models.add.model_id.tooltip')}
|
|
||||||
rules={[{ required: true }]}>
|
|
||||||
<Flex justify="space-between" gap={5}>
|
|
||||||
<Input
|
|
||||||
placeholder={t('settings.models.add.model_id.placeholder')}
|
|
||||||
spellCheck={false}
|
|
||||||
maxLength={200}
|
|
||||||
disabled={true}
|
|
||||||
value={model.id}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value
|
|
||||||
form.setFieldValue('name', value)
|
|
||||||
form.setFieldValue('group', getDefaultGroupName(value))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
//copy model id
|
|
||||||
const val = form.getFieldValue('name')
|
|
||||||
navigator.clipboard.writeText((val.id || model.id) as string)
|
|
||||||
message.success(t('message.copied'))
|
|
||||||
}}
|
|
||||||
icon={<CopyIcon size={16} />}>
|
|
||||||
{t('chat.topics.copy.title')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
label={t('settings.models.add.model_name.label')}
|
|
||||||
tooltip={t('settings.models.add.model_name.tooltip')}>
|
|
||||||
<Input placeholder={t('settings.models.add.model_name.placeholder')} spellCheck={false} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="group"
|
|
||||||
label={t('settings.models.add.group_name.label')}
|
|
||||||
tooltip={t('settings.models.add.group_name.tooltip')}>
|
|
||||||
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
|
||||||
</Form.Item>
|
|
||||||
{provider.id === 'new-api' && (
|
|
||||||
<Form.Item
|
|
||||||
name="endpointType"
|
|
||||||
label={t('settings.models.add.endpoint_type.label')}
|
|
||||||
tooltip={t('settings.models.add.endpoint_type.tooltip')}
|
|
||||||
rules={[{ required: true, message: t('settings.models.add.endpoint_type.required') }]}>
|
|
||||||
<Select placeholder={t('settings.models.add.endpoint_type.placeholder')}>
|
|
||||||
{endpointTypeOptions.map((opt) => (
|
|
||||||
<Select.Option key={opt.value} value={opt.value}>
|
|
||||||
{t(opt.label)}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
<Form.Item style={{ marginBottom: 8, textAlign: 'center' }}>
|
|
||||||
<Flex justify="space-between" align="center" style={{ position: 'relative' }}>
|
|
||||||
<Button
|
|
||||||
color="default"
|
|
||||||
variant="filled"
|
|
||||||
icon={showMoreSettings ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
|
||||||
iconPosition="end"
|
|
||||||
onClick={() => setShowMoreSettings(!showMoreSettings)}
|
|
||||||
style={{ color: 'var(--color-text-3)' }}>
|
|
||||||
{t('settings.moresetting.label')}
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" htmlType="submit" icon={<SaveIcon size={16} />}>
|
|
||||||
{t('common.save')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
{showMoreSettings && (
|
|
||||||
<div style={{ marginBottom: 8 }}>
|
|
||||||
<Divider style={{ margin: '16px 0 16px 0' }} />
|
|
||||||
<TypeTitle>{t('models.type.select')}:</TypeTitle>
|
|
||||||
{(() => {
|
|
||||||
const isDisabled = selectedTypes.includes('rerank') || selectedTypes.includes('embedding')
|
const isDisabled = selectedTypes.includes('rerank') || selectedTypes.includes('embedding')
|
||||||
|
|
||||||
const isRerankDisabled = selectedTypes.includes('embedding')
|
const isRerankDisabled = selectedTypes.includes('embedding')
|
||||||
@ -373,25 +303,151 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})()}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title={t('models.edit')} footer={null} transitionName="animation-move-down" centered {...props}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
labelCol={{ flex: provider.id === 'new-api' ? labelWidth : '110px' }}
|
||||||
|
labelAlign="left"
|
||||||
|
colon={false}
|
||||||
|
style={{ marginTop: 15 }}
|
||||||
|
initialValues={{
|
||||||
|
id: model.id,
|
||||||
|
name: model.name,
|
||||||
|
group: model.group,
|
||||||
|
endpointType: model.endpoint_type,
|
||||||
|
input_per_million_tokens: model.pricing?.input_per_million_tokens ?? 0,
|
||||||
|
output_per_million_tokens: model.pricing?.output_per_million_tokens ?? 0,
|
||||||
|
currencySymbol: symbols.includes(model.pricing?.currencySymbol || '$')
|
||||||
|
? model.pricing?.currencySymbol || '$'
|
||||||
|
: 'custom',
|
||||||
|
customCurrencySymbol: symbols.includes(model.pricing?.currencySymbol || '$')
|
||||||
|
? ''
|
||||||
|
: model.pricing?.currencySymbol || ''
|
||||||
|
}}
|
||||||
|
onFinish={onFinish}>
|
||||||
|
<Form.Item
|
||||||
|
name="id"
|
||||||
|
label={t('settings.models.add.model_id.label')}
|
||||||
|
tooltip={t('settings.models.add.model_id.tooltip')}
|
||||||
|
rules={[{ required: true }]}>
|
||||||
|
<Flex justify="space-between" gap={5}>
|
||||||
|
<Input
|
||||||
|
placeholder={t('settings.models.add.model_id.placeholder')}
|
||||||
|
spellCheck={false}
|
||||||
|
maxLength={200}
|
||||||
|
disabled={true}
|
||||||
|
value={model.id}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
form.setFieldValue('name', value)
|
||||||
|
form.setFieldValue('group', getDefaultGroupName(value))
|
||||||
|
}}
|
||||||
|
suffix={
|
||||||
|
<CopyIcon
|
||||||
|
size={14}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
const val = form.getFieldValue('name')
|
||||||
|
navigator.clipboard.writeText((val.id || model.id) as string)
|
||||||
|
message.success(t('message.copied'))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label={t('settings.models.add.model_name.label')}
|
||||||
|
tooltip={t('settings.models.add.model_name.tooltip')}>
|
||||||
|
<Input placeholder={t('settings.models.add.model_name.placeholder')} spellCheck={false} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="group"
|
||||||
|
label={t('settings.models.add.group_name.label')}
|
||||||
|
tooltip={t('settings.models.add.group_name.tooltip')}>
|
||||||
|
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
||||||
|
</Form.Item>
|
||||||
|
{provider.id === 'new-api' && (
|
||||||
|
<Form.Item
|
||||||
|
name="endpointType"
|
||||||
|
label={t('settings.models.add.endpoint_type.label')}
|
||||||
|
tooltip={t('settings.models.add.endpoint_type.tooltip')}
|
||||||
|
rules={[{ required: true, message: t('settings.models.add.endpoint_type.required') }]}>
|
||||||
|
<Select placeholder={t('settings.models.add.endpoint_type.placeholder')}>
|
||||||
|
{endpointTypeOptions.map((opt) => (
|
||||||
|
<Select.Option key={opt.value} value={opt.value}>
|
||||||
|
{t(opt.label)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Form.Item style={{ marginBottom: 8, textAlign: 'center' }}>
|
||||||
|
<Flex justify="space-between" align="center" style={{ position: 'relative' }}>
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="filled"
|
||||||
|
icon={showMoreSettings ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||||
|
iconPosition="end"
|
||||||
|
onClick={() => setShowMoreSettings(!showMoreSettings)}
|
||||||
|
style={{ color: 'var(--color-text-3)' }}>
|
||||||
|
{t('settings.moresetting.label')}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit" icon={<SaveIcon size={16} />}>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
{showMoreSettings && (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<Divider style={{ margin: '16px 0 16px 0' }} />
|
||||||
|
<TypeTitle>{t('models.type.select')}</TypeTitle>
|
||||||
|
<ModelCapability />
|
||||||
|
<Divider style={{ margin: '16px 0 12px 0' }} />
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="supported_text_delta"
|
name="supported_text_delta"
|
||||||
|
style={{ marginBottom: 10 }}
|
||||||
|
labelCol={{ flex: 1 }}
|
||||||
label={t('settings.models.add.supported_text_delta.label')}
|
label={t('settings.models.add.supported_text_delta.label')}
|
||||||
tooltip={t('settings.models.add.supported_text_delta.tooltip')}>
|
tooltip={t('settings.models.add.supported_text_delta.tooltip')}>
|
||||||
<Switch checked={supportedTextDelta} onChange={(checked) => setSupportedTextDelta(checked)} />
|
<Switch
|
||||||
|
checked={supportedTextDelta}
|
||||||
|
style={{ marginLeft: 'auto' }}
|
||||||
|
size="small"
|
||||||
|
onChange={(checked) => {
|
||||||
|
setSupportedTextDelta(checked)
|
||||||
|
// 直接传递新值给autoSave
|
||||||
|
autoSave({ supported_text_delta: checked })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<TypeTitle>{t('models.price.price')}</TypeTitle>
|
<Divider style={{ margin: '12px 0 16px 0' }} />
|
||||||
<Form.Item name="currencySymbol" label={t('models.price.currency')} style={{ marginBottom: 10 }}>
|
<Form.Item name="currencySymbol" label={t('models.price.currency')} style={{ marginBottom: 10 }}>
|
||||||
<Select
|
<Select
|
||||||
style={{ width: '100px' }}
|
style={{ width: '100px' }}
|
||||||
options={currencyOptions}
|
options={currencyOptions}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value === 'custom') {
|
if (value === 'custom') {
|
||||||
|
const customSymbol = form.getFieldValue('customCurrencySymbol') || ''
|
||||||
setIsCustomCurrency(true)
|
setIsCustomCurrency(true)
|
||||||
setCurrencySymbol(form.getFieldValue('customCurrencySymbol') || '')
|
setCurrencySymbol(customSymbol)
|
||||||
|
// 自动保存
|
||||||
|
autoSave({
|
||||||
|
isCustomCurrency: true,
|
||||||
|
currencySymbol: customSymbol
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
setIsCustomCurrency(false)
|
setIsCustomCurrency(false)
|
||||||
setCurrencySymbol(value)
|
setCurrencySymbol(value)
|
||||||
|
// 自动保存
|
||||||
|
autoSave({
|
||||||
|
isCustomCurrency: false,
|
||||||
|
currencySymbol: value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
dropdownMatchSelectWidth={false}
|
dropdownMatchSelectWidth={false}
|
||||||
@ -409,12 +465,20 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
placeholder={t('models.price.custom_currency_placeholder')}
|
placeholder={t('models.price.custom_currency_placeholder')}
|
||||||
defaultValue={model.pricing?.currencySymbol}
|
defaultValue={model.pricing?.currencySymbol}
|
||||||
maxLength={5}
|
maxLength={5}
|
||||||
onChange={(e) => setCurrencySymbol(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const newValue = e.target.value
|
||||||
|
setCurrencySymbol(newValue)
|
||||||
|
// 自动保存
|
||||||
|
autoSave({
|
||||||
|
currencySymbol: newValue,
|
||||||
|
isCustomCurrency: true
|
||||||
|
})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item label={t('models.price.input')} name="input_per_million_tokens">
|
<Form.Item label={t('models.price.input')} style={{ marginBottom: 10 }} name="input_per_million_tokens">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
defaultValue={model.pricing?.input_per_million_tokens}
|
defaultValue={model.pricing?.input_per_million_tokens}
|
||||||
@ -423,9 +487,13 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
precision={2}
|
precision={2}
|
||||||
style={{ width: '240px' }}
|
style={{ width: '240px' }}
|
||||||
addonAfter={`${currencySymbol} / ${t('models.price.million_tokens')}`}
|
addonAfter={`${currencySymbol} / ${t('models.price.million_tokens')}`}
|
||||||
|
onChange={() => {
|
||||||
|
// 自动保存
|
||||||
|
autoSave()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('models.price.output')} name="output_per_million_tokens">
|
<Form.Item label={t('models.price.output')} style={{ marginBottom: 10 }} name="output_per_million_tokens">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
defaultValue={model.pricing?.output_per_million_tokens}
|
defaultValue={model.pricing?.output_per_million_tokens}
|
||||||
@ -434,6 +502,10 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
|
|||||||
precision={2}
|
precision={2}
|
||||||
style={{ width: '240px' }}
|
style={{ width: '240px' }}
|
||||||
addonAfter={`${currencySymbol} / ${t('models.price.million_tokens')}`}
|
addonAfter={`${currencySymbol} / ${t('models.price.million_tokens')}`}
|
||||||
|
onChange={() => {
|
||||||
|
// 自动保存
|
||||||
|
autoSave()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
import { ModelCapability, ModelType } from '@renderer/types'
|
||||||
|
import { getDifference, uniqueObjectArray } from '@renderer/utils'
|
||||||
|
import { Button, Checkbox, Flex } from 'antd'
|
||||||
|
import { FC, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface ModelTypeSelectorProps {
|
||||||
|
modelCapabilities: ModelCapability[]
|
||||||
|
originalModelCapabilities: ModelCapability[]
|
||||||
|
selectedTypes: string[]
|
||||||
|
onCapabilitiesChange: (capabilities: ModelCapability[]) => void
|
||||||
|
onUserModified: (modified: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelTypeSelector: FC<ModelTypeSelectorProps> = ({
|
||||||
|
modelCapabilities,
|
||||||
|
originalModelCapabilities,
|
||||||
|
selectedTypes,
|
||||||
|
onCapabilitiesChange,
|
||||||
|
onUserModified
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [hasUserModified, setHasUserModified] = useState(false)
|
||||||
|
const changedTypesRef = useRef<string[]>([])
|
||||||
|
|
||||||
|
const isDisabled = selectedTypes.includes('rerank') || selectedTypes.includes('embedding')
|
||||||
|
const isRerankDisabled = selectedTypes.includes('embedding')
|
||||||
|
const isEmbeddingDisabled = selectedTypes.includes('rerank')
|
||||||
|
|
||||||
|
const showTypeConfirmModal = (newCapability: ModelCapability) => {
|
||||||
|
const onUpdateType = selectedTypes?.find((t) => t === newCapability.type)
|
||||||
|
window.modal.confirm({
|
||||||
|
title: t('settings.moresetting.warn'),
|
||||||
|
content: t('settings.moresetting.check.warn'),
|
||||||
|
okText: t('settings.moresetting.check.confirm'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
okButtonProps: { danger: true },
|
||||||
|
cancelButtonProps: { type: 'primary' },
|
||||||
|
onOk: () => {
|
||||||
|
if (onUpdateType) {
|
||||||
|
const updatedModelCapabilities = modelCapabilities?.map((t) => {
|
||||||
|
if (t.type === newCapability.type) {
|
||||||
|
return { ...t, isUserSelected: true }
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
((onUpdateType !== t.type && onUpdateType === 'rerank') ||
|
||||||
|
(onUpdateType === 'embedding' && onUpdateType !== t.type)) &&
|
||||||
|
t.isUserSelected !== false
|
||||||
|
) {
|
||||||
|
changedTypesRef.current.push(t.type)
|
||||||
|
return { ...t, isUserSelected: false }
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
onCapabilitiesChange(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
|
||||||
|
} else {
|
||||||
|
const updatedModelCapabilities = modelCapabilities?.map((t) => {
|
||||||
|
if (
|
||||||
|
((newCapability.type !== t.type && newCapability.type === 'rerank') ||
|
||||||
|
(newCapability.type === 'embedding' && newCapability.type !== t.type)) &&
|
||||||
|
t.isUserSelected !== false
|
||||||
|
) {
|
||||||
|
changedTypesRef.current.push(t.type)
|
||||||
|
return { ...t, isUserSelected: false }
|
||||||
|
}
|
||||||
|
if (newCapability.type === t.type) {
|
||||||
|
return { ...t, isUserSelected: true }
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
updatedModelCapabilities.push(newCapability as any)
|
||||||
|
onCapabilitiesChange(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel: () => {},
|
||||||
|
centered: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTypeChange = (types: string[]) => {
|
||||||
|
setHasUserModified(true)
|
||||||
|
onUserModified(true)
|
||||||
|
const diff = types.length > selectedTypes.length
|
||||||
|
if (diff) {
|
||||||
|
const newCapability = getDifference(types, selectedTypes) // checkbox的特性,确保了newCapability只有一个元素
|
||||||
|
showTypeConfirmModal({
|
||||||
|
type: newCapability[0] as ModelType,
|
||||||
|
isUserSelected: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const disabledTypes = getDifference(selectedTypes, types)
|
||||||
|
const onUpdateType = modelCapabilities?.find((t) => t.type === disabledTypes[0])
|
||||||
|
if (onUpdateType) {
|
||||||
|
const updatedTypes = modelCapabilities?.map((t) => {
|
||||||
|
if (t.type === disabledTypes[0]) {
|
||||||
|
return { ...t, isUserSelected: false }
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
((onUpdateType !== t && onUpdateType.type === 'rerank') ||
|
||||||
|
(onUpdateType.type === 'embedding' && onUpdateType !== t)) &&
|
||||||
|
t.isUserSelected === false
|
||||||
|
) {
|
||||||
|
if (changedTypesRef.current.includes(t.type)) {
|
||||||
|
return { ...t, isUserSelected: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
onCapabilitiesChange(uniqueObjectArray(updatedTypes as ModelCapability[]))
|
||||||
|
} else {
|
||||||
|
const updatedModelCapabilities = modelCapabilities?.map((t) => {
|
||||||
|
if (
|
||||||
|
(disabledTypes[0] === 'rerank' && t.type !== 'rerank') ||
|
||||||
|
(disabledTypes[0] === 'embedding' && t.type !== 'embedding' && t.isUserSelected === false)
|
||||||
|
) {
|
||||||
|
return { ...t, isUserSelected: true }
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
updatedModelCapabilities.push({ type: disabledTypes[0] as ModelType, isUserSelected: false })
|
||||||
|
onCapabilitiesChange(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
|
||||||
|
}
|
||||||
|
changedTypesRef.current.length = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetTypes = () => {
|
||||||
|
onCapabilitiesChange(originalModelCapabilities)
|
||||||
|
setHasUserModified(false)
|
||||||
|
onUserModified(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Flex justify="space-between" align="center" style={{ marginBottom: 8 }}>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={selectedTypes}
|
||||||
|
onChange={handleTypeChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: t('models.type.vision'),
|
||||||
|
value: 'vision',
|
||||||
|
disabled: isDisabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('models.type.websearch'),
|
||||||
|
value: 'web_search',
|
||||||
|
disabled: isDisabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('models.type.rerank'),
|
||||||
|
value: 'rerank',
|
||||||
|
disabled: isRerankDisabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('models.type.embedding'),
|
||||||
|
value: 'embedding',
|
||||||
|
disabled: isEmbeddingDisabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('models.type.reasoning'),
|
||||||
|
value: 'reasoning',
|
||||||
|
disabled: isDisabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('models.type.function_calling'),
|
||||||
|
value: 'function_calling',
|
||||||
|
disabled: isDisabled
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{hasUserModified && (
|
||||||
|
<Button size="small" onClick={handleResetTypes}>
|
||||||
|
{t('common.reset')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelTypeSelector
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import CustomTag from '@renderer/components/CustomTag'
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
import ExpandableText from '@renderer/components/ExpandableText'
|
import ExpandableText from '@renderer/components/ExpandableText'
|
||||||
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
|
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
|
||||||
import NewApiBatchAddModelPopup from '@renderer/components/ModelList/NewApiBatchAddModelPopup'
|
|
||||||
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
import { DynamicVirtualList } from '@renderer/components/VirtualList'
|
||||||
import { getModelLogo } from '@renderer/config/models'
|
import { getModelLogo } from '@renderer/config/models'
|
||||||
import FileItem from '@renderer/pages/files/FileItem'
|
import FileItem from '@renderer/pages/files/FileItem'
|
||||||
|
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
||||||
import { Model, Provider } from '@renderer/types'
|
import { Model, Provider } from '@renderer/types'
|
||||||
import { Button, Flex, Tooltip } from 'antd'
|
import { Button, Flex, Tooltip } from 'antd'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
@ -221,7 +221,7 @@ const GroupHeader = styled.div<{ isCollapsed: boolean }>`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 13px;
|
padding: 0 13px;
|
||||||
min-height: 35px;
|
min-height: 38px;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`
|
`
|
||||||
@ -1,7 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { LoadingIcon } from '@renderer/components/Icons'
|
import { LoadingIcon } from '@renderer/components/Icons'
|
||||||
import NewApiAddModelPopup from '@renderer/components/ModelList/NewApiAddModelPopup'
|
|
||||||
import NewApiBatchAddModelPopup from '@renderer/components/ModelList/NewApiBatchAddModelPopup'
|
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import {
|
import {
|
||||||
groupQwenModels,
|
groupQwenModels,
|
||||||
@ -15,6 +13,8 @@ import {
|
|||||||
SYSTEM_MODELS
|
SYSTEM_MODELS
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup'
|
||||||
|
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
||||||
import { fetchModels } from '@renderer/services/ApiService'
|
import { fetchModels } from '@renderer/services/ApiService'
|
||||||
import { Model, Provider } from '@renderer/types'
|
import { Model, Provider } from '@renderer/types'
|
||||||
import { filterModelsByKeywords, getDefaultGroupName, getFancyProviderName, isFreeModel } from '@renderer/utils'
|
import { filterModelsByKeywords, getDefaultGroupName, getFancyProviderName, isFreeModel } from '@renderer/utils'
|
||||||
@ -27,7 +27,7 @@ import { useCallback, useEffect, useMemo, useOptimistic, useRef, useState, useTr
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { HStack } from '../Layout'
|
import { HStack } from '../../../../components/Layout'
|
||||||
import ManageModelsList from './ManageModelsList'
|
import ManageModelsList from './ManageModelsList'
|
||||||
import { isModelInProvider, isValidNewApiModel } from './utils'
|
import { isModelInProvider, isValidNewApiModel } from './utils'
|
||||||
|
|
||||||
@ -2,17 +2,14 @@ import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
|
|||||||
import CustomTag from '@renderer/components/CustomTag'
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
|
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import AddModelPopup from '@renderer/components/ModelList/AddModelPopup'
|
|
||||||
import EditModelPopup from '@renderer/components/ModelList/EditModelPopup'
|
|
||||||
import ManageModelsPopup from '@renderer/components/ModelList/ManageModelsPopup'
|
|
||||||
import NewApiAddModelPopup from '@renderer/components/ModelList/NewApiAddModelPopup'
|
|
||||||
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
||||||
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
|
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
|
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import EditModelPopup from '@renderer/pages/settings/ProviderSettings/EditModelPopup/EditModelPopup'
|
||||||
import { setModel } from '@renderer/store/assistants'
|
import AddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/AddModelPopup'
|
||||||
|
import ManageModelsPopup from '@renderer/pages/settings/ProviderSettings/ModelList/ManageModelsPopup'
|
||||||
|
import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { filterModelsByKeywords } from '@renderer/utils'
|
import { filterModelsByKeywords } from '@renderer/utils'
|
||||||
import { Button, Empty, Flex, Spin, Tooltip } from 'antd'
|
import { Button, Empty, Flex, Spin, Tooltip } from 'antd'
|
||||||
@ -47,11 +44,8 @@ const calculateModelGroups = (models: Model[], searchText: string): ModelGroups
|
|||||||
* 模型列表组件,用于 CRUD 操作和健康检查
|
* 模型列表组件,用于 CRUD 操作和健康检查
|
||||||
*/
|
*/
|
||||||
const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
||||||
const dispatch = useAppDispatch()
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { provider, updateProvider, models, removeModel } = useProvider(providerId)
|
const { provider, models, removeModel } = useProvider(providerId)
|
||||||
const { assistants } = useAssistants()
|
|
||||||
const { defaultModel, setDefaultModel } = useDefaultModel()
|
|
||||||
|
|
||||||
const providerConfig = PROVIDER_CONFIG[provider.id]
|
const providerConfig = PROVIDER_CONFIG[provider.id]
|
||||||
const docsWebsite = providerConfig?.websites?.docs
|
const docsWebsite = providerConfig?.websites?.docs
|
||||||
@ -99,40 +93,6 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
|||||||
}
|
}
|
||||||
}, [provider, t])
|
}, [provider, t])
|
||||||
|
|
||||||
const onUpdateModel = useCallback(
|
|
||||||
(updatedModel: Model) => {
|
|
||||||
const updatedModels = models.map((m) => (m.id === updatedModel.id ? updatedModel : m))
|
|
||||||
|
|
||||||
updateProvider({ models: updatedModels })
|
|
||||||
|
|
||||||
assistants.forEach((assistant) => {
|
|
||||||
if (assistant?.model?.id === updatedModel.id && assistant.model.provider === provider.id) {
|
|
||||||
dispatch(
|
|
||||||
setModel({
|
|
||||||
assistantId: assistant.id,
|
|
||||||
model: updatedModel
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (defaultModel?.id === updatedModel.id && defaultModel?.provider === provider.id) {
|
|
||||||
setDefaultModel(updatedModel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[models, updateProvider, provider.id, assistants, defaultModel, dispatch, setDefaultModel]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onEditModel = useCallback(
|
|
||||||
async (model: Model) => {
|
|
||||||
const updatedModel = await EditModelPopup.show({ provider, model })
|
|
||||||
if (updatedModel) {
|
|
||||||
onUpdateModel(updatedModel)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[provider, onUpdateModel]
|
|
||||||
)
|
|
||||||
|
|
||||||
const isLoading = useMemo(() => displayedModelGroups === null, [displayedModelGroups])
|
const isLoading = useMemo(() => displayedModelGroups === null, [displayedModelGroups])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -170,7 +130,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
|||||||
modelStatuses={modelStatuses}
|
modelStatuses={modelStatuses}
|
||||||
defaultOpen={i <= 5}
|
defaultOpen={i <= 5}
|
||||||
disabled={isHealthChecking}
|
disabled={isHealthChecking}
|
||||||
onEditModel={onEditModel}
|
onEditModel={(model) => EditModelPopup.show({ provider, model })}
|
||||||
onRemoveModel={removeModel}
|
onRemoveModel={removeModel}
|
||||||
onRemoveGroup={() => displayedModelGroups[group].forEach((model) => removeModel(model))}
|
onRemoveGroup={() => displayedModelGroups[group].forEach((model) => removeModel(model))}
|
||||||
/>
|
/>
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
|
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
|
||||||
import { EditIcon } from '@renderer/components/Icons'
|
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
|
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
|
||||||
import { getModelLogo } from '@renderer/config/models'
|
import { getModelLogo } from '@renderer/config/models'
|
||||||
@ -7,7 +6,7 @@ import { Model } from '@renderer/types'
|
|||||||
import { ModelWithStatus } from '@renderer/types/healthCheck'
|
import { ModelWithStatus } from '@renderer/types/healthCheck'
|
||||||
import { maskApiKey } from '@renderer/utils/api'
|
import { maskApiKey } from '@renderer/utils/api'
|
||||||
import { Avatar, Button, Tooltip } from 'antd'
|
import { Avatar, Button, Tooltip } from 'antd'
|
||||||
import { Minus } from 'lucide-react'
|
import { Bolt, Minus } from 'lucide-react'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -52,7 +51,7 @@ const ModelListItem: React.FC<ModelListItemProps> = ({ ref, model, modelStatus,
|
|||||||
<HealthStatusIndicator results={healthResults} loading={isChecking} showLatency />
|
<HealthStatusIndicator results={healthResults} loading={isChecking} showLatency />
|
||||||
<HStack alignItems="center" gap={0}>
|
<HStack alignItems="center" gap={0}>
|
||||||
<Tooltip title={t('models.edit')} mouseLeaveDelay={0}>
|
<Tooltip title={t('models.edit')} mouseLeaveDelay={0}>
|
||||||
<Button type="text" onClick={() => onEdit(model)} disabled={disabled} icon={<EditIcon size={14} />} />
|
<Button type="text" onClick={() => onEdit(model)} disabled={disabled} icon={<Bolt size={14} />} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={t('settings.models.manage.remove_model')} mouseLeaveDelay={0}>
|
<Tooltip title={t('settings.models.manage.remove_model')} mouseLeaveDelay={0}>
|
||||||
<Button type="text" onClick={() => onRemove(model)} disabled={disabled} icon={<Minus size={14} />} />
|
<Button type="text" onClick={() => onRemove(model)} disabled={disabled} icon={<Minus size={14} />} />
|
||||||
@ -1,8 +1,8 @@
|
|||||||
|
export { default as EditModelPopup } from '../EditModelPopup/EditModelPopup'
|
||||||
|
export { default as ModelEditContent } from '../EditModelPopup/ModelEditContent'
|
||||||
export { default as AddModelPopup } from './AddModelPopup'
|
export { default as AddModelPopup } from './AddModelPopup'
|
||||||
export { default as EditModelPopup } from './EditModelPopup'
|
|
||||||
export { default as HealthCheckPopup } from './HealthCheckPopup'
|
export { default as HealthCheckPopup } from './HealthCheckPopup'
|
||||||
export { default as ManageModelsPopup } from './ManageModelsPopup'
|
export { default as ManageModelsPopup } from './ManageModelsPopup'
|
||||||
export { default as ModelEditContent } from './ModelEditContent'
|
|
||||||
export { default as ModelList } from './ModelList'
|
export { default as ModelList } from './ModelList'
|
||||||
export { default as NewApiAddModelPopup } from './NewApiAddModelPopup'
|
export { default as NewApiAddModelPopup } from './NewApiAddModelPopup'
|
||||||
export { default as NewApiBatchAddModelPopup } from './NewApiBatchAddModelPopup'
|
export { default as NewApiBatchAddModelPopup } from './NewApiBatchAddModelPopup'
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert'
|
import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert'
|
||||||
import { LoadingIcon } from '@renderer/components/Icons'
|
import { LoadingIcon } from '@renderer/components/Icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { ModelList } from '@renderer/components/ModelList'
|
|
||||||
import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
|
import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
|
||||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||||
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
|
import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList'
|
||||||
import { checkApi } from '@renderer/services/ApiService'
|
import { checkApi } from '@renderer/services/ApiService'
|
||||||
import { isProviderSupportAuth } from '@renderer/services/ProviderService'
|
import { isProviderSupportAuth } from '@renderer/services/ProviderService'
|
||||||
import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck'
|
import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck'
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||||
|
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
|
import { useAssistants, useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
|
import { setQuickAssistantId } from '@renderer/store/llm'
|
||||||
import {
|
import {
|
||||||
setClickTrayToShowQuickAssistant,
|
setClickTrayToShowQuickAssistant,
|
||||||
setEnableQuickAssistant,
|
setEnableQuickAssistant,
|
||||||
setReadClipboardAtStartup
|
setReadClipboardAtStartup
|
||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
import HomeWindow from '@renderer/windows/mini/home/HomeWindow'
|
import HomeWindow from '@renderer/windows/mini/home/HomeWindow'
|
||||||
import { Switch, Tooltip } from 'antd'
|
import { Button, Select, Switch, Tooltip } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -20,6 +24,10 @@ const QuickAssistantSettings: FC = () => {
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { enableQuickAssistant, clickTrayToShowQuickAssistant, setTray, readClipboardAtStartup } = useSettings()
|
const { enableQuickAssistant, clickTrayToShowQuickAssistant, setTray, readClipboardAtStartup } = useSettings()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const { assistants } = useAssistants()
|
||||||
|
const { quickAssistantId } = useAppSelector((state) => state.llm)
|
||||||
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
|
const { defaultModel } = useDefaultModel()
|
||||||
|
|
||||||
const handleEnableQuickAssistant = async (enable: boolean) => {
|
const handleEnableQuickAssistant = async (enable: boolean) => {
|
||||||
dispatch(setEnableQuickAssistant(enable))
|
dispatch(setEnableQuickAssistant(enable))
|
||||||
@ -86,9 +94,69 @@ const QuickAssistantSettings: FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
{enableQuickAssistant && (
|
||||||
|
<SettingGroup theme={theme}>
|
||||||
|
<HStack alignItems="center" justifyContent="space-between">
|
||||||
|
<HStack alignItems="center" gap={10}>
|
||||||
|
{t('settings.models.quick_assistant_model')}
|
||||||
|
<Tooltip title={t('selection.settings.user_modal.model.tooltip')} arrow>
|
||||||
|
<InfoCircleOutlined style={{ cursor: 'pointer' }} />
|
||||||
|
</Tooltip>
|
||||||
|
<Spacer />
|
||||||
|
</HStack>
|
||||||
|
<HStack alignItems="center" gap={10}>
|
||||||
|
{!quickAssistantId ? null : (
|
||||||
|
<HStack alignItems="center">
|
||||||
|
<Select
|
||||||
|
value={quickAssistantId || defaultAssistant.id}
|
||||||
|
style={{ width: 300, height: 34 }}
|
||||||
|
onChange={(value) => dispatch(setQuickAssistantId(value))}
|
||||||
|
placeholder={t('settings.models.quick_assistant_selection')}>
|
||||||
|
<Select.Option key={defaultAssistant.id} value={defaultAssistant.id}>
|
||||||
|
<AssistantItem>
|
||||||
|
<ModelAvatar model={defaultAssistant.model || defaultModel} size={18} />
|
||||||
|
<AssistantName>{defaultAssistant.name}</AssistantName>
|
||||||
|
<Spacer />
|
||||||
|
<DefaultTag isCurrent={true}>{t('settings.models.quick_assistant_default_tag')}</DefaultTag>
|
||||||
|
</AssistantItem>
|
||||||
|
</Select.Option>
|
||||||
|
{assistants
|
||||||
|
.filter((a) => a.id !== defaultAssistant.id)
|
||||||
|
.map((a) => (
|
||||||
|
<Select.Option key={a.id} value={a.id}>
|
||||||
|
<AssistantItem>
|
||||||
|
<ModelAvatar model={a.model || defaultModel} size={18} />
|
||||||
|
<AssistantName>{a.name}</AssistantName>
|
||||||
|
<Spacer />
|
||||||
|
</AssistantItem>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
<HStack alignItems="center" gap={0}>
|
||||||
|
<StyledButton
|
||||||
|
type={quickAssistantId ? 'primary' : 'default'}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setQuickAssistantId(defaultAssistant.id))
|
||||||
|
}}
|
||||||
|
selected={!!quickAssistantId}>
|
||||||
|
{t('settings.models.use_assistant')}
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
type={!quickAssistantId ? 'primary' : 'default'}
|
||||||
|
onClick={() => dispatch(setQuickAssistantId(''))}
|
||||||
|
selected={!quickAssistantId}>
|
||||||
|
{t('settings.models.use_model')}
|
||||||
|
</StyledButton>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</SettingGroup>
|
||||||
|
)}
|
||||||
{enableQuickAssistant && (
|
{enableQuickAssistant && (
|
||||||
<AssistantContainer>
|
<AssistantContainer>
|
||||||
<HomeWindow />
|
<HomeWindow draggable={false} />
|
||||||
</AssistantContainer>
|
</AssistantContainer>
|
||||||
)}
|
)}
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
@ -105,4 +173,58 @@ const AssistantContainer = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const AssistantItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
height: 28px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const AssistantName = styled.span`
|
||||||
|
max-width: calc(100% - 60px);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Spacer = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DefaultTag = styled.span<{ isCurrent: boolean }>`
|
||||||
|
color: ${(props) => (props.isCurrent ? 'var(--color-primary)' : 'var(--color-text-3)')};
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)<{ selected: boolean }>`
|
||||||
|
border-radius: ${(props) => (props.selected ? '6px' : '6px')};
|
||||||
|
z-index: ${(props) => (props.selected ? 1 : 0)};
|
||||||
|
min-width: 80px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-right-width: 0; // No right border for the first button when not selected
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left-width: 1px; // Ensure left border for the last button
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override Ant Design's default hover and focus styles for a cleaner look
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
z-index: 1;
|
||||||
|
border-color: ${(props) => (props.selected ? 'var(--ant-primary-color)' : 'var(--ant-primary-color-hover)')};
|
||||||
|
box-shadow: ${(props) =>
|
||||||
|
props.selected ? '0 0 0 2px var(--ant-primary-color-outline)' : '0 0 0 2px var(--ant-primary-color-outline)'};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export default QuickAssistantSettings
|
export default QuickAssistantSettings
|
||||||
|
|||||||
@ -91,7 +91,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer theme={theme}>
|
<SettingContainer theme={theme}>
|
||||||
<SettingGroup>
|
<SettingGroup theme={theme}>
|
||||||
<Row align="middle">
|
<Row align="middle">
|
||||||
<SettingTitle>{t('selection.name')}</SettingTitle>
|
<SettingTitle>{t('selection.name')}</SettingTitle>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
@ -124,11 +124,9 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
{selectionEnabled && (
|
{selectionEnabled && (
|
||||||
<>
|
<>
|
||||||
<SettingGroup>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle>{t('selection.settings.toolbar.title')}</SettingTitle>
|
<SettingTitle>{t('selection.settings.toolbar.title')}</SettingTitle>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>
|
<SettingRowTitle>
|
||||||
@ -167,9 +165,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.toolbar.compact_mode.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.toolbar.compact_mode.title')}</SettingRowTitle>
|
||||||
@ -179,11 +175,9 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
||||||
<SettingGroup>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle>{t('selection.settings.window.title')}</SettingTitle>
|
<SettingTitle>{t('selection.settings.window.title')}</SettingTitle>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.window.follow_toolbar.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.window.follow_toolbar.title')}</SettingRowTitle>
|
||||||
@ -191,9 +185,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Switch checked={isFollowToolbar} onChange={(checked) => setIsFollowToolbar(checked)} />
|
<Switch checked={isFollowToolbar} onChange={(checked) => setIsFollowToolbar(checked)} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.window.remember_size.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.window.remember_size.title')}</SettingRowTitle>
|
||||||
@ -201,9 +193,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Switch checked={isRemeberWinSize} onChange={(checked) => setIsRemeberWinSize(checked)} />
|
<Switch checked={isRemeberWinSize} onChange={(checked) => setIsRemeberWinSize(checked)} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.window.auto_close.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.window.auto_close.title')}</SettingRowTitle>
|
||||||
@ -211,9 +201,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Switch checked={isAutoClose} onChange={(checked) => setIsAutoClose(checked)} />
|
<Switch checked={isAutoClose} onChange={(checked) => setIsAutoClose(checked)} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.window.auto_pin.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.window.auto_pin.title')}</SettingRowTitle>
|
||||||
@ -221,9 +209,7 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
</SettingLabel>
|
</SettingLabel>
|
||||||
<Switch checked={isAutoPin} onChange={(checked) => setIsAutoPin(checked)} />
|
<Switch checked={isAutoPin} onChange={(checked) => setIsAutoPin(checked)} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.window.opacity.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.window.opacity.title')}</SettingRowTitle>
|
||||||
@ -245,11 +231,9 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
|
|
||||||
<SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} />
|
<SelectionActionsList actionItems={actionItems} setActionItems={setActionItems} />
|
||||||
|
|
||||||
<SettingGroup>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>
|
<SettingTitle>{t('selection.settings.advanced.title')}</SettingTitle>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingLabel>
|
<SettingLabel>
|
||||||
<SettingRowTitle>{t('selection.settings.advanced.filter_mode.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('selection.settings.advanced.filter_mode.title')}</SettingRowTitle>
|
||||||
@ -277,7 +261,6 @@ const SelectionAssistantSettings: FC = () => {
|
|||||||
{t('common.edit')}
|
{t('common.edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SelectionFilterListModal
|
<SelectionFilterListModal
|
||||||
open={isFilterListModalOpen}
|
open={isFilterListModalOpen}
|
||||||
onClose={() => setIsFilterListModalOpen(false)}
|
onClose={() => setIsFilterListModalOpen(false)}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { DragDropContext } from '@hello-pangea/dnd'
|
import { DragDropContext } from '@hello-pangea/dnd'
|
||||||
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { defaultActionItems } from '@renderer/store/selectionStore'
|
import { defaultActionItems } from '@renderer/store/selectionStore'
|
||||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
import type { ActionItem } from '@renderer/types/selectionTypes'
|
||||||
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
|
import SelectionToolbar from '@renderer/windows/selection/toolbar/SelectionToolbar'
|
||||||
@ -45,12 +46,14 @@ const SelectionActionsList: FC<SelectionActionsListProps> = ({ actionItems, setA
|
|||||||
MAX_ENABLED_ITEMS
|
MAX_ENABLED_ITEMS
|
||||||
} = useActionItems(actionItems, setActionItems)
|
} = useActionItems(actionItems, setActionItems)
|
||||||
|
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
if (!actionItems || actionItems.length === 0) {
|
if (!actionItems || actionItems.length === 0) {
|
||||||
setActionItems(defaultActionItems)
|
setActionItems(defaultActionItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup>
|
<SettingGroup theme={theme}>
|
||||||
<SettingsActionsListHeader
|
<SettingsActionsListHeader
|
||||||
customItemsCount={customItemsCount}
|
customItemsCount={customItemsCount}
|
||||||
maxCustomItems={MAX_CUSTOM_ITEMS}
|
maxCustomItems={MAX_CUSTOM_ITEMS}
|
||||||
|
|||||||
@ -4,16 +4,15 @@ import {
|
|||||||
Brain,
|
Brain,
|
||||||
Cloud,
|
Cloud,
|
||||||
Command,
|
Command,
|
||||||
|
FolderCog,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Info,
|
Info,
|
||||||
MonitorCog,
|
MonitorCog,
|
||||||
Package,
|
Package,
|
||||||
PencilRuler,
|
PictureInPicture2,
|
||||||
Rocket,
|
|
||||||
Settings2,
|
Settings2,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
TextCursorInput,
|
TextCursorInput
|
||||||
Zap
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -28,7 +27,6 @@ import MCPSettings from './MCPSettings'
|
|||||||
import MemorySettings from './MemorySettings'
|
import MemorySettings from './MemorySettings'
|
||||||
import ProvidersList from './ProviderSettings'
|
import ProvidersList from './ProviderSettings'
|
||||||
import QuickAssistantSettings from './QuickAssistantSettings'
|
import QuickAssistantSettings from './QuickAssistantSettings'
|
||||||
import QuickPhraseSettings from './QuickPhraseSettings'
|
|
||||||
import SelectionAssistantSettings from './SelectionAssistantSettings/SelectionAssistantSettings'
|
import SelectionAssistantSettings from './SelectionAssistantSettings/SelectionAssistantSettings'
|
||||||
import ShortcutSettings from './ShortcutSettings'
|
import ShortcutSettings from './ShortcutSettings'
|
||||||
import ToolSettings from './ToolSettings'
|
import ToolSettings from './ToolSettings'
|
||||||
@ -70,6 +68,12 @@ const SettingsPage: FC = () => {
|
|||||||
{t('settings.display.title')}
|
{t('settings.display.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
|
<MenuItemLink to="/settings/data">
|
||||||
|
<MenuItem className={isRoute('/settings/data')}>
|
||||||
|
<HardDrive size={18} />
|
||||||
|
{t('settings.data.title')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/mcp">
|
<MenuItemLink to="/settings/mcp">
|
||||||
<MenuItem className={isRoute('/settings/mcp')}>
|
<MenuItem className={isRoute('/settings/mcp')}>
|
||||||
<SquareTerminal size={18} />
|
<SquareTerminal size={18} />
|
||||||
@ -82,21 +86,21 @@ const SettingsPage: FC = () => {
|
|||||||
{t('memory.title')}
|
{t('memory.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/tool">
|
|
||||||
<MenuItem className={isRoute('/settings/tool')}>
|
|
||||||
<PencilRuler size={18} />
|
|
||||||
{t('settings.tool.title')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItemLink>
|
|
||||||
<MenuItemLink to="/settings/shortcut">
|
<MenuItemLink to="/settings/shortcut">
|
||||||
<MenuItem className={isRoute('/settings/shortcut')}>
|
<MenuItem className={isRoute('/settings/shortcut')}>
|
||||||
<Command size={18} />
|
<Command size={18} />
|
||||||
{t('settings.shortcuts.title')}
|
{t('settings.shortcuts.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
|
<MenuItemLink to="/settings/tool">
|
||||||
|
<MenuItem className={isRoute('/settings/tool')}>
|
||||||
|
<FolderCog size={18} />
|
||||||
|
{t('settings.tool.title')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/quickAssistant">
|
<MenuItemLink to="/settings/quickAssistant">
|
||||||
<MenuItem className={isRoute('/settings/quickAssistant')}>
|
<MenuItem className={isRoute('/settings/quickAssistant')}>
|
||||||
<Rocket size={18} />
|
<PictureInPicture2 size={18} />
|
||||||
{t('settings.quickAssistant.title')}
|
{t('settings.quickAssistant.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
@ -106,18 +110,6 @@ const SettingsPage: FC = () => {
|
|||||||
{t('selection.name')}
|
{t('selection.name')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/quickPhrase">
|
|
||||||
<MenuItem className={isRoute('/settings/quickPhrase')}>
|
|
||||||
<Zap size={18} />
|
|
||||||
{t('settings.quickPhrase.title')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItemLink>
|
|
||||||
<MenuItemLink to="/settings/data">
|
|
||||||
<MenuItem className={isRoute('/settings/data')}>
|
|
||||||
<HardDrive size={18} />
|
|
||||||
{t('settings.data.title')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItemLink>
|
|
||||||
<MenuItemLink to="/settings/about">
|
<MenuItemLink to="/settings/about">
|
||||||
<MenuItem className={isRoute('/settings/about')}>
|
<MenuItem className={isRoute('/settings/about')}>
|
||||||
<Info size={18} />
|
<Info size={18} />
|
||||||
@ -139,7 +131,6 @@ const SettingsPage: FC = () => {
|
|||||||
<Route path="selectionAssistant" element={<SelectionAssistantSettings />} />
|
<Route path="selectionAssistant" element={<SelectionAssistantSettings />} />
|
||||||
<Route path="data" element={<DataSettings />} />
|
<Route path="data" element={<DataSettings />} />
|
||||||
<Route path="about" element={<AboutSettings />} />
|
<Route path="about" element={<AboutSettings />} />
|
||||||
<Route path="quickPhrase" element={<QuickPhraseSettings />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</SettingContent>
|
</SettingContent>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
|
|||||||
@ -1,42 +1,55 @@
|
|||||||
import { GlobalOutlined } from '@ant-design/icons'
|
import { GlobalOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import ListItem from '@renderer/components/ListItem'
|
import ListItem from '@renderer/components/ListItem'
|
||||||
import { FileCode } from 'lucide-react'
|
import { FileCode, Zap } from 'lucide-react'
|
||||||
import { FC, useState } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Link, Route, Routes, useLocation } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import PreprocessSettings from './PreprocessSettings'
|
import PreprocessSettings from './PreprocessSettings'
|
||||||
|
import QuickPhraseSettings from './QuickPhraseSettings'
|
||||||
import WebSearchSettings from './WebSearchSettings'
|
import WebSearchSettings from './WebSearchSettings'
|
||||||
|
|
||||||
let _menu: string = 'web-search'
|
|
||||||
|
|
||||||
const ToolSettings: FC = () => {
|
const ToolSettings: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [menu, setMenu] = useState<string>(_menu)
|
const { pathname } = useLocation()
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ key: 'web-search', title: 'settings.tool.websearch.title', icon: <GlobalOutlined style={{ fontSize: 16 }} /> },
|
{ key: 'web-search', title: 'settings.tool.websearch.title', icon: <GlobalOutlined style={{ fontSize: 16 }} /> },
|
||||||
{ key: 'preprocess', title: 'settings.tool.preprocess.title', icon: <FileCode size={16} /> }
|
{ key: 'preprocess', title: 'settings.tool.preprocess.title', icon: <FileCode size={16} /> },
|
||||||
|
{ key: 'quick-phrase', title: 'settings.quickPhrase.title', icon: <Zap size={16} /> }
|
||||||
]
|
]
|
||||||
|
|
||||||
_menu = menu
|
const isActive = (key: string): boolean => {
|
||||||
|
const basePath = '/settings/tool'
|
||||||
|
if (key === 'web-search') {
|
||||||
|
return pathname === basePath || pathname === `${basePath}/` || pathname === `${basePath}/${key}`
|
||||||
|
}
|
||||||
|
return pathname === `${basePath}/${key}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
|
<Link key={item.key} to={`/settings/tool/${item.key}`} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||||
<ListItem
|
<ListItem
|
||||||
key={item.key}
|
|
||||||
title={t(item.title)}
|
title={t(item.title)}
|
||||||
active={menu === item.key}
|
active={isActive(item.key)}
|
||||||
onClick={() => setMenu(item.key)}
|
|
||||||
titleStyle={{ fontWeight: 500 }}
|
titleStyle={{ fontWeight: 500 }}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
/>
|
/>
|
||||||
|
</Link>
|
||||||
))}
|
))}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
{menu == 'web-search' && <WebSearchSettings />}
|
<ContentArea>
|
||||||
{menu == 'preprocess' && <PreprocessSettings />}
|
<Routes>
|
||||||
|
<Route path="/" element={<WebSearchSettings />} />
|
||||||
|
<Route path="/web-search" element={<WebSearchSettings />} />
|
||||||
|
<Route path="/preprocess" element={<PreprocessSettings />} />
|
||||||
|
<Route path="/quick-phrase" element={<QuickPhraseSettings />} />
|
||||||
|
</Routes>
|
||||||
|
</ContentArea>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -44,6 +57,7 @@ const ToolSettings: FC = () => {
|
|||||||
const Container = styled(HStack)`
|
const Container = styled(HStack)`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
const MenuList = styled.div`
|
const MenuList = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -56,4 +70,10 @@ const MenuList = styled.div`
|
|||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const ContentArea = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
`
|
||||||
export default ToolSettings
|
export default ToolSettings
|
||||||
|
|||||||
@ -381,7 +381,7 @@ export const initialState: SettingsState = {
|
|||||||
// Developer mode
|
// Developer mode
|
||||||
enableDeveloperMode: false,
|
enableDeveloperMode: false,
|
||||||
// UI
|
// UI
|
||||||
navbarPosition: 'left',
|
navbarPosition: 'top',
|
||||||
// API Server
|
// API Server
|
||||||
apiServer: {
|
apiServer: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@ -89,7 +89,7 @@ const MessageContentContainer = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 5px;
|
margin-top: 20px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default memo(MessageItem)
|
export default memo(MessageItem)
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import InputBar from './components/InputBar'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('HomeWindow')
|
const logger = loggerService.withContext('HomeWindow')
|
||||||
|
|
||||||
const HomeWindow: FC = () => {
|
const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
||||||
const { language, readClipboardAtStartup, windowStyle } = useSettings()
|
const { language, readClipboardAtStartup, windowStyle } = useSettings()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -487,7 +487,7 @@ const HomeWindow: FC = () => {
|
|||||||
case 'summary':
|
case 'summary':
|
||||||
case 'explanation':
|
case 'explanation':
|
||||||
return (
|
return (
|
||||||
<Container style={{ backgroundColor }}>
|
<Container style={{ backgroundColor }} $draggable={draggable}>
|
||||||
{route === 'chat' && (
|
{route === 'chat' && (
|
||||||
<>
|
<>
|
||||||
<InputBar
|
<InputBar
|
||||||
@ -523,7 +523,7 @@ const HomeWindow: FC = () => {
|
|||||||
|
|
||||||
case 'translate':
|
case 'translate':
|
||||||
return (
|
return (
|
||||||
<Container style={{ backgroundColor }}>
|
<Container style={{ backgroundColor }} $draggable={draggable}>
|
||||||
<TranslateWindow text={referenceText} />
|
<TranslateWindow text={referenceText} />
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
<Footer key="footer" {...baseFooterProps} />
|
<Footer key="footer" {...baseFooterProps} />
|
||||||
@ -533,7 +533,7 @@ const HomeWindow: FC = () => {
|
|||||||
// Home
|
// Home
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<Container style={{ backgroundColor }}>
|
<Container style={{ backgroundColor }} $draggable={draggable}>
|
||||||
<InputBar
|
<InputBar
|
||||||
text={userInputText}
|
text={userInputText}
|
||||||
assistant={currentAssistant}
|
assistant={currentAssistant}
|
||||||
@ -566,13 +566,13 @@ const HomeWindow: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div<{ $draggable: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: ${({ $draggable }) => ($draggable ? 'drag' : 'no-drag')};
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user