From 3e2acde9e2c965e8ba11a72c5f64aaf8dabb02e9 Mon Sep 17 00:00:00 2001 From: icarus Date: Tue, 23 Sep 2025 10:19:24 +0800 Subject: [PATCH 01/13] feat(SessionModal): add multiline support for tools selection Enable multiline display for the tools selection dropdown to improve visibility when multiple tools are selected --- src/renderer/src/components/Popups/agent/SessionModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx index 98906f52b1..df77792d71 100644 --- a/src/renderer/src/components/Popups/agent/SessionModal.tsx +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -361,6 +361,7 @@ export const SessionModal: React.FC = ({ /> + {permissionOptions.map((option) => ( + + {option.label} + + ))} + + + + + + + }> + {t('agent.settings.advance.maxTurns.label')} + +
+ { + if (event.key === 'Enter') { + commitMaxTurns() + } + }} + aria-label={t('agent.settings.advance.maxTurns.label')} + /> + + {t('agent.settings.advance.maxTurns.helper')} + +
+
+ + ) +} + +export default AgentAdvanceSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/index.tsx b/src/renderer/src/pages/settings/AgentSettings/index.tsx index da6cb5355c..a117573825 100644 --- a/src/renderer/src/pages/settings/AgentSettings/index.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/index.tsx @@ -7,6 +7,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +import AgentAdvanceSettings from './AgentAdvanceSettings' import AgentEssentialSettings from './AgentEssentialSettings' import AgentMCPSettings from './AgentMCPSettings' import AgentPromptSettings from './AgentPromptSettings' @@ -21,7 +22,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams { resolve: () => void } -type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'session-mcps' +type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'advance' | 'session-mcps' const AgentSettingPopupContainer: React.FC = ({ tab, agentId, resolve }) => { const [open, setOpen] = useState(true) @@ -56,6 +57,10 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag { key: 'mcps', label: t('agent.settings.mcps', 'MCP Servers') + }, + { + key: 'advance', + label: t('agent.settings.advance.title', 'Advanced Settings') } ] as const satisfies { key: AgentSettingPopupTab; label: string }[] ).filter(Boolean) @@ -87,6 +92,7 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag {menu === 'essential' && } {menu === 'prompt' && } {menu === 'mcps' && } + {menu === 'advance' && } ) diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index 9ba470b20d..9a7172fb23 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -169,6 +169,7 @@ export type BaseAgentForm = { accessible_paths: string[] allowed_tools: string[] mcps?: string[] + configuration?: AgentConfiguration } export type AddAgentForm = Omit & { id?: never } From 7ca9dcd2fb2bda5ac514545f9cc94df2b604c117 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Tue, 23 Sep 2025 10:28:44 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20improve?= =?UTF-8?q?=20agent=20tool=20approval=20UI=20with=20dedicated=20settings?= =?UTF-8?q?=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move tool selection from essential settings to dedicated "Pre-approved tools" tab - Update terminology from "Allowed tools" to "Pre-approved tools" for clarity - Add new AgentToolSettings component with enhanced card-based layout - Include warning alert about pre-approved tools bypassing review - Update all language files with new terminology and translation keys - Add i18n sync guidance to CLAUDE.md development commands --- CLAUDE.md | 2 +- src/renderer/src/i18n/locales/en-us.json | 17 +- src/renderer/src/i18n/locales/zh-cn.json | 15 +- src/renderer/src/i18n/locales/zh-tw.json | 15 +- src/renderer/src/i18n/translate/el-gr.json | 15 +- src/renderer/src/i18n/translate/es-es.json | 15 +- src/renderer/src/i18n/translate/fr-fr.json | 15 +- src/renderer/src/i18n/translate/ja-jp.json | 15 +- src/renderer/src/i18n/translate/pt-pt.json | 15 +- src/renderer/src/i18n/translate/ru-ru.json | 15 +- .../AgentSettings/AgentEssentialSettings.tsx | 110 +------------ .../AgentSettings/AgentToolSettings.tsx | 149 ++++++++++++++++++ .../pages/settings/AgentSettings/index.tsx | 8 +- 13 files changed, 269 insertions(+), 137 deletions(-) create mode 100644 src/renderer/src/pages/settings/AgentSettings/AgentToolSettings.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 3a47b0de5f..4119019336 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ This file provides guidance to AI coding assistants when working with code in th - **Install**: `yarn install` - **Development**: `yarn dev` - Runs Electron app in development mode - **Debug**: `yarn debug` - Starts with debugging enabled, use chrome://inspect -- **Build Check**: `yarn build:check` - REQUIRED before commits (lint + test + typecheck) +- **Build Check**: `yarn build:check` - REQUIRED before commits (lint + test + typecheck), if having i18n sort issues, run `yarn i18n:sync` first to sync template. - **Test**: `yarn test` - Run all tests (Vitest) - **Single Test**: `yarn test:main` or `yarn test:renderer` - **Lint**: `yarn lint` - Fix linting issues and run typecheck diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index f78d8abd3c..ab6b2332fd 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -46,9 +46,9 @@ }, "allowed_tools": { "empty": "No tools available for this agent.", - "helper": "Choose which tools are pre-approved. Unselected tools require approval before use.", - "label": "Allowed tools", - "placeholder": "Select allowed tools" + "helper": "Pre-approved tools run without manual approval. Unselected tools require approval before use.", + "label": "Pre-approved tools", + "placeholder": "Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Essential Settings", "mcps": "MCP Servers", - "prompt": "Prompt Settings" + "prompt": "Prompt Settings", + "tools": { + "approved": "approved", + "caution": "Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "Choose which tools can run without manual approval.", + "requiresPermission": "Requires permission when not pre-approved.", + "tab": "Pre-approved tools", + "title": "Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Agent Type", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 76781468e1..e798d9000d 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "当前智能体暂无可用工具。", "helper": "选择预先授权的工具,未选中的工具在使用时需要手动审批。", - "label": "允许的工具", - "placeholder": "选择允许使用的工具" + "label": "预先授权工具", + "placeholder": "选择预先授权的工具" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "基础设置", "mcps": "MCP 服务器", - "prompt": "提示词设置" + "prompt": "提示词设置", + "tools": { + "approved": "已授权", + "caution": "预先授权的工具会跳过人工审核,请仅启用可信的工具。", + "description": "选择哪些工具可以在无需人工审批的情况下执行。", + "requiresPermission": "未预先授权时需要人工审批。", + "tab": "预先授权工具", + "title": "预先授权工具", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "智能体类型", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bbcef66ad7..6abfcd0c62 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "目前此代理沒有可用的工具。", "helper": "選擇預先授權的工具,未選取的工具在使用時需要手動審批。", - "label": "允許的工具", - "placeholder": "選擇允許使用的工具" + "label": "預先授權工具", + "placeholder": "選擇預先授權的工具" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "必要設定", "mcps": "MCP 伺服器", - "prompt": "提示設定" + "prompt": "提示設定", + "tools": { + "approved": "已授權", + "caution": "預先授權的工具會略過人工審查,請僅啟用可信任的工具。", + "description": "選擇哪些工具可在無需人工核准的情況下執行。", + "requiresPermission": "未預先授權時需要人工核准。", + "tab": "預先授權工具", + "title": "預先授權工具", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "代理類型", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 51e2e7df2c..982956f49c 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Δεν υπάρχουν διαθέσιμα εργαλεία για αυτόν τον agent.", "helper": "Επιλέξτε ποια εργαλεία είναι προεγκεκριμένα. Τα μη επιλεγμένα θα χρειαστούν έγκριση πριν από τη χρήση.", - "label": "Επιτρεπόμενα εργαλεία", - "placeholder": "Επιλέξτε επιτρεπόμενα εργαλεία" + "label": "[to be translated]:Pre-approved tools", + "placeholder": "[to be translated]:Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Βασικές Ρυθμίσεις", "mcps": "[to be translated]:MCP 服务器", - "prompt": "Ρυθμίσεις Προτροπής" + "prompt": "Ρυθμίσεις Προτροπής", + "tools": { + "approved": "[to be translated]:approved", + "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "[to be translated]:Choose which tools can run without manual approval.", + "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", + "tab": "[to be translated]:Pre-approved tools", + "title": "[to be translated]:Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Τύπος Πράκτορα", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 91c125a5e6..f0f1ec3b5e 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "No hay herramientas disponibles para este agente.", "helper": "Elige qué herramientas quedan preaprobadas. Las no seleccionadas requerirán aprobación manual antes de usarse.", - "label": "Herramientas permitidas", - "placeholder": "Selecciona las herramientas permitidas" + "label": "[to be translated]:Pre-approved tools", + "placeholder": "[to be translated]:Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Configuraciones esenciales", "mcps": "[to be translated]:MCP 服务器", - "prompt": "Configuración de indicaciones" + "prompt": "Configuración de indicaciones", + "tools": { + "approved": "[to be translated]:approved", + "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "[to be translated]:Choose which tools can run without manual approval.", + "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", + "tab": "[to be translated]:Pre-approved tools", + "title": "[to be translated]:Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Tipo de Agente", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 119344967d..7fa0ed81ca 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Aucun outil disponible pour cet agent.", "helper": "Choisissez les outils préapprouvés. Les outils non sélectionnés nécessiteront une approbation avant utilisation.", - "label": "Outils autorisés", - "placeholder": "Sélectionner les outils autorisés" + "label": "[to be translated]:Pre-approved tools", + "placeholder": "[to be translated]:Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Paramètres essentiels", "mcps": "[to be translated]:MCP 服务器", - "prompt": "Paramètres de l'invite" + "prompt": "Paramètres de l'invite", + "tools": { + "approved": "[to be translated]:approved", + "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "[to be translated]:Choose which tools can run without manual approval.", + "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", + "tab": "[to be translated]:Pre-approved tools", + "title": "[to be translated]:Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Type d'agent", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 4322ff6cc2..375326d047 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "このエージェントが利用できるツールはありません。", "helper": "事前承認済みのツールを選択します。未選択のツールは使用時に承認が必要になります。", - "label": "許可されたツール", - "placeholder": "許可するツールを選択" + "label": "事前承認済みツール", + "placeholder": "事前承認するツールを選択" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "必須設定", "mcps": "[to be translated]:MCP 服务器", - "prompt": "プロンプト設定" + "prompt": "プロンプト設定", + "tools": { + "approved": "承認済み", + "caution": "事前承認したツールは人によるレビューをスキップします。信頼できるツールのみ有効にしてください。", + "description": "人による承認なしで実行できるツールを選択します。", + "requiresPermission": "事前承認されていない場合は承認が必要です。", + "tab": "事前承認済みツール", + "title": "事前承認済みツール", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "エージェントタイプ", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 55670d7578..57fd837c40 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Não há ferramentas disponíveis para este agente.", "helper": "Escolha quais ferramentas ficam pré-autorizadas. As não selecionadas exigirão aprovação antes do uso.", - "label": "Ferramentas permitidas", - "placeholder": "Selecione as ferramentas permitidas" + "label": "[to be translated]:Pre-approved tools", + "placeholder": "[to be translated]:Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Configurações Essenciais", "mcps": "[to be translated]:MCP 服务器", - "prompt": "Configurações de Prompt" + "prompt": "Configurações de Prompt", + "tools": { + "approved": "[to be translated]:approved", + "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "[to be translated]:Choose which tools can run without manual approval.", + "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", + "tab": "[to be translated]:Pre-approved tools", + "title": "[to be translated]:Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Tipo de Agente", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 9604315c62..175c54b585 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Для этого агента нет доступных инструментов.", "helper": "Выберите инструменты с предварительным допуском. Неотмеченные инструменты потребуют подтверждения перед использованием.", - "label": "Разрешённые инструменты", - "placeholder": "Выберите разрешённые инструменты" + "label": "[to be translated]:Pre-approved tools", + "placeholder": "[to be translated]:Select pre-approved tools" }, "create": { "error": { @@ -101,7 +101,16 @@ }, "essential": "Основные настройки", "mcps": "[to be translated]:MCP 服务器", - "prompt": "Настройки подсказки" + "prompt": "Настройки подсказки", + "tools": { + "approved": "[to be translated]:approved", + "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", + "description": "[to be translated]:Choose which tools can run without manual approval.", + "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", + "tab": "[to be translated]:Pre-approved tools", + "title": "[to be translated]:Pre-approved tools", + "toggle": "{{defaultValue}}" + } }, "type": { "label": "Тип агента", diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx index f6cdb5704b..4373f0205f 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx @@ -1,14 +1,13 @@ -import { Button, Chip, Select as HeroSelect, SelectedItems, SelectItem, Tooltip } from '@heroui/react' +import { Button, Tooltip } from '@heroui/react' import { loggerService } from '@logger' -import type { Selection } from '@react-types/shared' import { ApiModelLabel } from '@renderer/components/ApiModelLabel' import { useApiModels } from '@renderer/hooks/agents/useModels' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { GetAgentResponse, Tool, UpdateAgentForm } from '@renderer/types' +import { GetAgentResponse, UpdateAgentForm } from '@renderer/types' import { Input, Select } from 'antd' import { DefaultOptionType } from 'antd/es/select' import { Plus } from 'lucide-react' -import { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { FC, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared' @@ -24,9 +23,6 @@ const AgentEssentialSettings: FC = ({ agent, update const { t } = useTranslation() const [name, setName] = useState((agent?.name ?? '').trim()) const { models } = useApiModels({ providerType: 'anthropic' }) - const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools]) - const [allowedToolIds, setAllowedToolIds] = useState([]) - const selectedToolKeys = useMemo(() => new Set(allowedToolIds), [allowedToolIds]) const updateName = (name: string) => { if (!agent) return @@ -46,14 +42,6 @@ const AgentEssentialSettings: FC = ({ agent, update [agent, update] ) - const updateAllowedTools = useCallback( - (allowed_tools: UpdateAgentForm['allowed_tools']) => { - if (!agent) return - update({ id: agent.id, allowed_tools }) - }, - [agent, update] - ) - const modelOptions = useMemo(() => { return models.map((model) => ({ value: model.id, @@ -61,27 +49,6 @@ const AgentEssentialSettings: FC = ({ agent, update })) satisfies DefaultOptionType[] }, [models]) - useEffect(() => { - if (!agent) { - setAllowedToolIds((prev) => (prev.length === 0 ? prev : [])) - return - } - - const allowed = agent.allowed_tools ?? [] - const filtered = availableTools.length - ? allowed.filter((id) => availableTools.some((tool) => tool.id === id)) - : allowed - - setAllowedToolIds((prev) => { - const prevSet = new Set(prev) - const isSame = filtered.length === prevSet.size && filtered.every((id) => prevSet.has(id)) - if (isSame) { - return prev - } - return filtered - }) - }, [agent, availableTools]) - const addAccessiblePath = useCallback(async () => { if (!agent) return @@ -116,52 +83,6 @@ const AgentEssentialSettings: FC = ({ agent, update [agent, t, updateAccessiblePaths] ) - const renderSelectedTools = useCallback((items: SelectedItems) => { - if (!items.length) { - return null - } - - return ( -
- {items.map((item) => ( - - {item.data?.name ?? item.textValue ?? item.key} - - ))} -
- ) - }, []) - - const onAllowedToolsChange = useCallback( - (keys: Selection) => { - if (!agent) return - - const nextIds = keys === 'all' ? availableTools.map((tool) => tool.id) : Array.from(keys).map(String) - const filtered = availableTools.length - ? nextIds.filter((id) => availableTools.some((tool) => tool.id === id)) - : nextIds - - setAllowedToolIds((prev) => { - const prevSet = new Set(prev) - const isSame = filtered.length === prevSet.size && filtered.every((id) => prevSet.has(id)) - if (isSame) { - return prev - } - return filtered - }) - - const previous = agent.allowed_tools ?? [] - const previousSet = new Set(previous) - const isSameSelection = filtered.length === previousSet.size && filtered.every((id) => previousSet.has(id)) - - if (isSameSelection) { - return - } - - updateAllowedTools(filtered) - }, - [agent, availableTools, updateAllowedTools] - ) if (!agent) return null @@ -197,31 +118,6 @@ const AgentEssentialSettings: FC = ({ agent, update placeholder={t('common.placeholders.select.model')} /> - - {t('agent.session.allowed_tools.label')} - - {(tool) => ( - -
- {tool.name} - {tool.description ? {tool.description} : null} -
-
- )} -
-
Promise | void +} + +const isSameSelection = (next: string[], previous: string[]) => { + if (next.length !== previous.length) { + return false + } + const previousSet = new Set(previous) + return next.every((id) => previousSet.has(id)) +} + +export const AgentToolSettings: FC = ({ agent, updateAgent }) => { + const { t } = useTranslation() + const [approvedIds, setApprovedIds] = useState([]) + + const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools]) + + useEffect(() => { + if (!agent) { + setApprovedIds((prev) => (prev.length === 0 ? prev : [])) + return + } + const allowed = agent.allowed_tools ?? [] + const validIds = allowed.filter((id) => availableTools.some((tool) => tool.id === id)) + setApprovedIds((prev) => { + if (isSameSelection(prev, validIds)) { + return prev + } + return validIds + }) + }, [agent, availableTools]) + + const handleToggle = useCallback( + (toolId: string, isApproved: boolean) => { + if (!agent) return + + setApprovedIds((prev) => { + const exists = prev.includes(toolId) + if (isApproved === exists) { + return prev + } + const next = isApproved ? [...prev, toolId] : prev.filter((id) => id !== toolId) + const previous = agent.allowed_tools ?? [] + if (!isSameSelection(next, previous)) { + updateAgent({ id: agent.id, allowed_tools: next }) + } + return next + }) + }, + [agent, updateAgent] + ) + + const approvedCount = useMemo(() => { + return approvedIds.filter((id) => availableTools.some((tool) => tool.id === id)).length + }, [approvedIds, availableTools]) + + if (!agent) { + return null + } + + return ( + + +
+
+ + {t('agent.settings.tools.title', 'Pre-approved tools')} + + + + + {availableTools.length > 0 ? ( + + {approvedCount} / {availableTools.length} {t('agent.settings.tools.approved', 'approved')} + + ) : null} +
+ + + + {availableTools.length > 0 ? ( +
+ {availableTools.map((tool) => { + const isApproved = approvedIds.includes(tool.id) + return ( + + +
+ {tool.name} + {tool.description ? ( + {tool.description} + ) : null} +
+ handleToggle(tool.id, value)} + /> +
+ {tool.requirePermissions ? ( + + + {t( + 'agent.settings.tools.requiresPermission', + 'Requires permission when not pre-approved.' + )} + + + ) : null} +
+ ) + })} +
+ ) : ( +
+ {t('agent.session.allowed_tools.empty')} +
+ )} +
+
+
+ ) +} + +export default AgentToolSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/index.tsx b/src/renderer/src/pages/settings/AgentSettings/index.tsx index a117573825..a23b14a7e0 100644 --- a/src/renderer/src/pages/settings/AgentSettings/index.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/index.tsx @@ -11,6 +11,7 @@ import AgentAdvanceSettings from './AgentAdvanceSettings' import AgentEssentialSettings from './AgentEssentialSettings' import AgentMCPSettings from './AgentMCPSettings' import AgentPromptSettings from './AgentPromptSettings' +import AgentToolSettings from './AgentToolSettings' import { AgentLabel } from './shared' interface AgentSettingPopupShowParams { @@ -22,7 +23,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams { resolve: () => void } -type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'advance' | 'session-mcps' +type AgentSettingPopupTab = 'essential' | 'prompt' | 'tools' | 'mcps' | 'advance' | 'session-mcps' const AgentSettingPopupContainer: React.FC = ({ tab, agentId, resolve }) => { const [open, setOpen] = useState(true) @@ -54,6 +55,10 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag key: 'prompt', label: t('agent.settings.prompt') }, + { + key: 'tools', + label: t('agent.settings.tools.tab', 'Pre-approved tools') + }, { key: 'mcps', label: t('agent.settings.mcps', 'MCP Servers') @@ -91,6 +96,7 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag {menu === 'essential' && } {menu === 'prompt' && } + {menu === 'tools' && } {menu === 'mcps' && } {menu === 'advance' && } From 46a5ea88f3d6e68a901c30a2b1122250c6a8e1fd Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 23 Sep 2025 02:35:34 +0000 Subject: [PATCH 06/13] fix(i18n): Auto update translations for PR #10096 --- src/renderer/src/i18n/translate/el-gr.json | 40 +++++++++++----------- src/renderer/src/i18n/translate/es-es.json | 40 +++++++++++----------- src/renderer/src/i18n/translate/fr-fr.json | 40 +++++++++++----------- src/renderer/src/i18n/translate/ja-jp.json | 24 ++++++------- src/renderer/src/i18n/translate/pt-pt.json | 40 +++++++++++----------- src/renderer/src/i18n/translate/ru-ru.json | 40 +++++++++++----------- 6 files changed, 112 insertions(+), 112 deletions(-) diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 982956f49c..f73eb9c3dd 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Δεν υπάρχουν διαθέσιμα εργαλεία για αυτόν τον agent.", "helper": "Επιλέξτε ποια εργαλεία είναι προεγκεκριμένα. Τα μη επιλεγμένα θα χρειαστούν έγκριση πριν από τη χρήση.", - "label": "[to be translated]:Pre-approved tools", - "placeholder": "[to be translated]:Select pre-approved tools" + "label": "Προεγκεκριμένα εργαλεία", + "placeholder": "Επιλέξτε προεγκεκριμένα εργαλεία" }, "create": { "error": { @@ -82,33 +82,33 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "Ορίστε τον αριθμό γύρων αιτήματος/απάντησης που θα εκτελούνται αυτόματα από τον διαμεσολαβητ.", + "helper": "Όσο υψηλότερη είναι η τιμή, τόσο περισσότερο μπορεί να λειτουργεί αυτόνομα· όσο χαμηλότερη είναι, τόσο πιο εύκολα ελέγχεται.", + "label": "Όριο αριθμού γύρων συνεδρίας" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "Ο τρόπος με τον οποίο ο πληρεξούσιος χειρίζεται την κατάσταση όταν απαιτείται εξουσιοδότηση.", + "label": "Λειτουργία δικαιωμάτων", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "Αυτόματη αποδοχή επεξεργασίας", + "bypassPermissions": "παράλειψη ελέγχου δικαιωμάτων", + "default": "Προεπιλογή (να ερωτηθεί πριν από τη συνέχεια)", + "plan": "Λειτουργία σχεδιασμού (απαιτείται έγκριση σχεδίου)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "Επιλέξτε λειτουργία δικαιωμάτων" }, - "title": "[to be translated]:高级设置" + "title": "Ρυθμίσεις για προχωρημένους" }, "essential": "Βασικές Ρυθμίσεις", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "Διακομιστής MCP", "prompt": "Ρυθμίσεις Προτροπής", "tools": { - "approved": "[to be translated]:approved", - "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", - "description": "[to be translated]:Choose which tools can run without manual approval.", - "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", - "tab": "[to be translated]:Pre-approved tools", - "title": "[to be translated]:Pre-approved tools", + "approved": "εγκεκριμένο", + "caution": "Εργαλεία προεγκεκριμένα παρακάμπτουν την ανθρώπινη αξιολόγηση. Ενεργοποιήστε μόνο έμπιστα εργαλεία.", + "description": "Επιλέξτε ποια εργαλεία μπορούν να εκτελούνται χωρίς χειροκίνητη έγκριση.", + "requiresPermission": "Απαιτείται άδεια όταν δεν έχει προεγκριθεί.", + "tab": "Προεγκεκριμένα εργαλεία", + "title": "Προεγκεκριμένα εργαλεία", "toggle": "{{defaultValue}}" } }, diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index f0f1ec3b5e..ddf2b5b597 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "No hay herramientas disponibles para este agente.", "helper": "Elige qué herramientas quedan preaprobadas. Las no seleccionadas requerirán aprobación manual antes de usarse.", - "label": "[to be translated]:Pre-approved tools", - "placeholder": "[to be translated]:Select pre-approved tools" + "label": "Herramientas preaprobadas", + "placeholder": "Seleccionar herramientas preaprobadas" }, "create": { "error": { @@ -82,33 +82,33 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "Establece el número de rondas de solicitud/respuesta que el agente ejecutará automáticamente.", + "helper": "Cuanto mayor es el valor, más tiempo puede funcionar de forma autónoma; cuanto menor es el valor, más fácil es de controlar.", + "label": "Límite máximo de turnos de conversación" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "Cómo el agente de control maneja las situaciones que requieren autorización.", + "label": "modo de permisos", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "Aceptar ediciones automáticamente", + "bypassPermissions": "Omitir verificación de permisos", + "default": "Predeterminado (preguntar antes de continuar)", + "plan": "Modo de planificación (requiere aprobación del plan)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "Seleccionar modo de permisos" }, - "title": "[to be translated]:高级设置" + "title": "Configuración avanzada" }, "essential": "Configuraciones esenciales", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "Servidor MCP", "prompt": "Configuración de indicaciones", "tools": { - "approved": "[to be translated]:approved", - "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", - "description": "[to be translated]:Choose which tools can run without manual approval.", - "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", - "tab": "[to be translated]:Pre-approved tools", - "title": "[to be translated]:Pre-approved tools", + "approved": "aprobado", + "caution": "Herramientas preaprobadas omiten la revisión humana. Habilita solo herramientas de confianza.", + "description": "Elige qué herramientas pueden ejecutarse sin aprobación manual.", + "requiresPermission": "Requiere permiso cuando no está preaprobado.", + "tab": "Herramientas preaprobadas", + "title": "Herramientas preaprobadas", "toggle": "{{defaultValue}}" } }, diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 7fa0ed81ca..ead69c1bb9 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Aucun outil disponible pour cet agent.", "helper": "Choisissez les outils préapprouvés. Les outils non sélectionnés nécessiteront une approbation avant utilisation.", - "label": "[to be translated]:Pre-approved tools", - "placeholder": "[to be translated]:Select pre-approved tools" + "label": "Outils pré-approuvés", + "placeholder": "Sélectionner des outils pré-approuvés" }, "create": { "error": { @@ -82,33 +82,33 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "Définir le nombre de cycles de requête/réponse exécutés automatiquement par l'agent.", + "helper": "Une valeur plus élevée permet une autonomie prolongée ; une valeur plus faible facilite le contrôle.", + "label": "Limite maximale de tours de conversation" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "Contrôle la manière dont l'agent gère les demandes d'autorisation.", + "label": "mode d'autorisation", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "Accepter automatiquement les modifications", + "bypassPermissions": "sauter la vérification des autorisations", + "default": "Par défaut (demander avant de continuer)", + "plan": "Mode de planification (plan soumis à approbation)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "Choisir le mode d'autorisation" }, - "title": "[to be translated]:高级设置" + "title": "Paramètres avancés" }, "essential": "Paramètres essentiels", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "Serveur MCP", "prompt": "Paramètres de l'invite", "tools": { - "approved": "[to be translated]:approved", - "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", - "description": "[to be translated]:Choose which tools can run without manual approval.", - "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", - "tab": "[to be translated]:Pre-approved tools", - "title": "[to be translated]:Pre-approved tools", + "approved": "approuvé", + "caution": "Outils pré-approuvés contournent la révision humaine. Activez uniquement les outils de confiance.", + "description": "Choisissez quels outils peuvent s'exécuter sans approbation manuelle.", + "requiresPermission": "Nécessite une autorisation lorsqu'elle n'est pas préapprouvée.", + "tab": "Outils pré-approuvés", + "title": "Outils pré-approuvés", "toggle": "{{defaultValue}}" } }, diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 375326d047..8401752b77 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -82,25 +82,25 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "プロキシが自動的に実行するリクエスト/レスポンスのラウンド数を設定します。", + "helper": "数値が高いほど自律動作の時間が長くなり、数値が低いほど制御しやすくなります。", + "label": "会話ラウンド数の上限" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "制御エージェントが認可を必要とする場合の処理方法。", + "label": "権限モード", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "自動的に編集を受け入れる", + "bypassPermissions": "権限チェックをスキップ", + "default": "デフォルト(続行する前に確認)", + "plan": "計画モード(承認が必要な計画)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "権限モードを選択" }, - "title": "[to be translated]:高级设置" + "title": "高級設定" }, "essential": "必須設定", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "MCPサーバー", "prompt": "プロンプト設定", "tools": { "approved": "承認済み", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 57fd837c40..7e5f36b287 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Não há ferramentas disponíveis para este agente.", "helper": "Escolha quais ferramentas ficam pré-autorizadas. As não selecionadas exigirão aprovação antes do uso.", - "label": "[to be translated]:Pre-approved tools", - "placeholder": "[to be translated]:Select pre-approved tools" + "label": "Ferramentas pré-aprovadas", + "placeholder": "Selecionar ferramentas pré-aprovadas" }, "create": { "error": { @@ -82,33 +82,33 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "Define o número de ciclos de solicitação/resposta executados automaticamente pelo agente.", + "helper": "Quanto maior o valor, mais tempo pode funcionar de forma autônoma; quanto menor o valor, mais fácil de controlar.", + "label": "Limite máximo de turnos de conversa" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "Controla como o agente lida com situações que exigem autorização.", + "label": "Modo de permissão", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "Aceitar edições automaticamente", + "bypassPermissions": "忽略检查 de permissão", + "default": "Padrão (perguntar antes de continuar)", + "plan": "Modo de planejamento (plano sujeito a aprovação)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "Selecione o modo de permissão" }, - "title": "[to be translated]:高级设置" + "title": "Configurações avançadas" }, "essential": "Configurações Essenciais", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "Servidor MCP", "prompt": "Configurações de Prompt", "tools": { - "approved": "[to be translated]:approved", - "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", - "description": "[to be translated]:Choose which tools can run without manual approval.", - "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", - "tab": "[to be translated]:Pre-approved tools", - "title": "[to be translated]:Pre-approved tools", + "approved": "aprovado", + "caution": "Ferramentas pré-aprovadas ignoram a revisão humana. Ative apenas ferramentas confiáveis.", + "description": "Escolha quais ferramentas podem ser executadas sem aprovação manual.", + "requiresPermission": "Requer permissão quando não pré-aprovado.", + "tab": "Ferramentas pré-aprovadas", + "title": "Ferramentas pré-aprovadas", "toggle": "{{defaultValue}}" } }, diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 175c54b585..02b11d9284 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -47,8 +47,8 @@ "allowed_tools": { "empty": "Для этого агента нет доступных инструментов.", "helper": "Выберите инструменты с предварительным допуском. Неотмеченные инструменты потребуют подтверждения перед использованием.", - "label": "[to be translated]:Pre-approved tools", - "placeholder": "[to be translated]:Select pre-approved tools" + "label": "Предварительно одобренные инструменты", + "placeholder": "Выберите предварительно одобренные инструменты" }, "create": { "error": { @@ -82,33 +82,33 @@ "settings": { "advance": { "maxTurns": { - "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", - "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", - "label": "[to be translated]:会话轮次数上限" + "description": "Установить количество циклов запрос/ответ, выполняемых автоматически через прокси.", + "helper": "Чем выше значение, тем дольше может работать автономно; чем ниже значение, тем легче контролировать.", + "label": "Максимальное количество раундов в сеансе" }, "permissionMode": { - "description": "[to be translated]:控制代理在需要授权时的处理方式。", - "label": "[to be translated]:权限模式", + "description": "Как агент управления обрабатывает ситуации, требующие авторизации.", + "label": "Режим разрешений", "options": { - "acceptEdits": "[to be translated]:自动接受编辑", - "bypassPermissions": "[to be translated]:跳过权限检查", - "default": "[to be translated]:默认(继续前询问)", - "plan": "[to be translated]:规划模式(需审批计划)" + "acceptEdits": "Автоматически принимать правки", + "bypassPermissions": "Пропустить проверку разрешений", + "default": "По умолчанию (спросить перед продолжением)", + "plan": "Режим планирования (требуется утверждение плана)" }, - "placeholder": "[to be translated]:选择权限模式" + "placeholder": "Выбрать режим разрешений" }, - "title": "[to be translated]:高级设置" + "title": "Расширенные настройки" }, "essential": "Основные настройки", - "mcps": "[to be translated]:MCP 服务器", + "mcps": "MCP сервер", "prompt": "Настройки подсказки", "tools": { - "approved": "[to be translated]:approved", - "caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.", - "description": "[to be translated]:Choose which tools can run without manual approval.", - "requiresPermission": "[to be translated]:Requires permission when not pre-approved.", - "tab": "[to be translated]:Pre-approved tools", - "title": "[to be translated]:Pre-approved tools", + "approved": "одобрено", + "caution": "Предварительно одобренные инструменты обходят проверку человеком. Включайте только доверенные инструменты.", + "description": "Выберите, какие инструменты могут запускаться без ручного подтверждения.", + "requiresPermission": "Требуется разрешение, если не предварительно одобрено.", + "tab": "Предварительно одобренные инструменты", + "title": "Предварительно одобренные инструменты", "toggle": "{{defaultValue}}" } }, From d4d25108345c432063a1cdb708797229723226d7 Mon Sep 17 00:00:00 2001 From: icarus Date: Tue, 23 Sep 2025 11:13:11 +0800 Subject: [PATCH 07/13] feat(agent): add AllowedToolsSelect component and integrate into forms extract tool selection logic into reusable component to reduce code duplication and improve maintainability --- .../components/Popups/agent/SessionModal.tsx | 44 ++---------- .../components/agent/AllowedToolsSelect.tsx | 54 ++++++++++++++ src/renderer/src/components/agent/index.tsx | 1 + .../AgentSettings/AgentEssentialSettings.tsx | 70 ++++++------------- 4 files changed, 80 insertions(+), 89 deletions(-) create mode 100644 src/renderer/src/components/agent/AllowedToolsSelect.tsx create mode 100644 src/renderer/src/components/agent/index.tsx diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx index df77792d71..8fc869b172 100644 --- a/src/renderer/src/components/Popups/agent/SessionModal.tsx +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -1,6 +1,5 @@ import { Button, - Chip, cn, Form, Input, @@ -18,6 +17,7 @@ import { } from '@heroui/react' import { loggerService } from '@logger' import type { Selection } from '@react-types/shared' +import { AllowedToolsSelect } from '@renderer/components/agent' import { getModelLogo } from '@renderer/config/models' import { useAgent } from '@renderer/hooks/agents/useAgent' import { useApiModels } from '@renderer/hooks/agents/useModels' @@ -197,21 +197,6 @@ export const SessionModal: React.FC = ({ [availableTools] ) - const renderSelectedTools = useCallback((items: SelectedItems) => { - if (!items.length) { - return null - } - return ( -
- {items.map((item) => ( - - {item.data?.name ?? item.textValue ?? item.key} - - ))} -
- ) - }, []) - const modelOptions = useMemo(() => { // mocked data. not final version return (models ?? []).map((model) => ({ @@ -359,32 +344,11 @@ export const SessionModal: React.FC = ({ value={form.description ?? ''} onValueChange={onDescChange} /> - + />