diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index f433fa2..202d2b9 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -157,6 +157,13 @@ "modelConfigDesc": "External calls use the model name; the system maps it to the channel model ID.", "modelId": "Model ID", "modelName": "Model Name", + "defaultParams": "Params", + "defaultParamsEmpty": "Set Params", + "defaultParamsConfigured": "{{count}} set", + "defaultParamsDialogTitle": "Edit Default Parameters", + "defaultParamsDialogDesc": "Enter a JSON object. Parameters explicitly sent by callers override matching defaults here.", + "defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}", + "formatJson": "Format", "modelStatusDelete": "Status / Delete", "modelIdPlaceholder": "Model ID, e.g. gpt-4.1-mini", "modelNamePlaceholder": "Model name, defaults to model ID", @@ -173,7 +180,7 @@ "noMatchingModels": "No matching models", "saveChannel": "Save Channel", "jsonConfig": "JSON Config", - "modelJsonStructure": "JSON array with `id`, `name`, `enabled` fields per item.", + "modelJsonStructure": "JSON array with `id`, `name`, `enabled`, and optional `default_params` object per item.", "fillRequired": "Please fill in all required fields", "fillChannelKey": "Please enter a channel key", "fillTypeEndpointKey": "Please fill in channel type, API endpoint, and API key first", @@ -186,7 +193,9 @@ "modelJsonInvalid": "Invalid model JSON format", "modelJsonMustBeArray": "Model JSON must be an array", "modelJsonItemMustBeObject": "Model JSON items must be objects", - "modelJsonIdEmpty": "Model ID in JSON cannot be empty" + "modelJsonIdEmpty": "Model ID in JSON cannot be empty", + "defaultParamsJsonInvalid": "Invalid default parameters JSON", + "defaultParamsJsonMustBeObject": "Default parameters must be a JSON object" } }, "tokens": { diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index b421259..6b078fc 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -157,6 +157,13 @@ "modelConfigDesc": "外部使用模型名称调用,程序会自动映射到渠道模型 ID。", "modelId": "模型 ID", "modelName": "模型名称", + "defaultParams": "参数", + "defaultParamsEmpty": "设置参数", + "defaultParamsConfigured": "已设置 {{count}} 项", + "defaultParamsDialogTitle": "编辑默认参数", + "defaultParamsDialogDesc": "输入 JSON 对象;调用接口时显式传入的同名参数会覆盖这里的默认值。", + "defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}", + "formatJson": "格式化", "modelStatusDelete": "状态 / 删除", "modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini", "modelNamePlaceholder": "模型名称,默认等于模型ID", @@ -173,7 +180,7 @@ "noMatchingModels": "没有匹配的模型", "saveChannel": "保存渠道", "jsonConfig": "JSON 配置", - "modelJsonStructure": "JSON 结构为数组,每项包含 `id`、`name`、`enabled`。", + "modelJsonStructure": "JSON 结构为数组,每项包含 `id`、`name`、`enabled`,可选 `default_params` 对象。", "fillRequired": "请填写所有必填字段", "fillChannelKey": "请填写渠道标识", "fillTypeEndpointKey": "请先填写渠道类型、API 端点和 API 密钥", @@ -186,7 +193,9 @@ "modelJsonInvalid": "模型 JSON 格式错误", "modelJsonMustBeArray": "模型 JSON 必须是数组", "modelJsonItemMustBeObject": "模型 JSON 项必须是对象", - "modelJsonIdEmpty": "模型 JSON 中的 id 不能为空" + "modelJsonIdEmpty": "模型 JSON 中的 id 不能为空", + "defaultParamsJsonInvalid": "默认参数 JSON 格式错误", + "defaultParamsJsonMustBeObject": "默认参数必须是 JSON 对象" } }, "tokens": { diff --git a/frontend/src/i18n/locales/zh-TW.json b/frontend/src/i18n/locales/zh-TW.json index e52ea7c..01cb2ba 100644 --- a/frontend/src/i18n/locales/zh-TW.json +++ b/frontend/src/i18n/locales/zh-TW.json @@ -157,6 +157,13 @@ "modelConfigDesc": "外部使用模型名稱呼叫,程式會自動對應到渠道模型 ID。", "modelId": "模型 ID", "modelName": "模型名稱", + "defaultParams": "參數", + "defaultParamsEmpty": "設定參數", + "defaultParamsConfigured": "已設定 {{count}} 項", + "defaultParamsDialogTitle": "編輯預設參數", + "defaultParamsDialogDesc": "輸入 JSON 物件;呼叫介面時明確傳入的同名參數會覆蓋這裡的預設值。", + "defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}", + "formatJson": "格式化", "modelStatusDelete": "狀態 / 刪除", "modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini", "modelNamePlaceholder": "模型名稱,預設等於模型ID", @@ -173,7 +180,7 @@ "noMatchingModels": "沒有匹配的模型", "saveChannel": "儲存渠道", "jsonConfig": "JSON 設定", - "modelJsonStructure": "JSON 結構為陣列,每項包含 `id`、`name`、`enabled`。", + "modelJsonStructure": "JSON 結構為陣列,每項包含 `id`、`name`、`enabled`,可選 `default_params` 物件。", "fillRequired": "請填寫所有必填欄位", "fillChannelKey": "請填寫渠道標識", "fillTypeEndpointKey": "請先填寫渠道類型、API 端點和 API 金鑰", @@ -186,7 +193,9 @@ "modelJsonInvalid": "模型 JSON 格式錯誤", "modelJsonMustBeArray": "模型 JSON 必須是陣列", "modelJsonItemMustBeObject": "模型 JSON 項必須是物件", - "modelJsonIdEmpty": "模型 JSON 中的 id 不能為空" + "modelJsonIdEmpty": "模型 JSON 中的 id 不能為空", + "defaultParamsJsonInvalid": "預設參數 JSON 格式錯誤", + "defaultParamsJsonMustBeObject": "預設參數必須是 JSON 物件" } }, "tokens": { diff --git a/frontend/src/pages/Channels.tsx b/frontend/src/pages/Channels.tsx index 2c77140..79bf666 100644 --- a/frontend/src/pages/Channels.tsx +++ b/frontend/src/pages/Channels.tsx @@ -13,6 +13,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Switch } from "@/components/ui/switch"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { useToast } from "@/components/ui/use-toast"; import { cn } from "@/lib/utils"; import { @@ -29,6 +30,7 @@ import { Search, Globe, Cpu, + SlidersHorizontal, } from "lucide-react"; import { PageContainer } from "@/components/ui/page-container"; import { useTranslation } from "react-i18next"; @@ -36,7 +38,8 @@ import i18n from "@/i18n"; type EditMode = "form" | "json"; type ModelEditorMode = "visual" | "json"; -type ModelRow = { id: string; name: string; enabled: boolean }; +type JsonObject = Record; +type ModelRow = { id: string; name: string; enabled: boolean; default_params?: JsonObject }; type FetchedModelCandidate = { id: string; label: string }; const channelTypes = [ @@ -102,23 +105,49 @@ const formatApiKeys = (apiKeys?: string[]): string => { return (apiKeys || []).join("\n"); }; +const normalizeDefaultParams = (defaultParams: unknown): JsonObject | undefined => { + if (!defaultParams || typeof defaultParams !== "object" || Array.isArray(defaultParams)) { + return undefined; + } + + if (Object.keys(defaultParams).length === 0) { + return undefined; + } + + return defaultParams as JsonObject; +}; + const normalizeModels = (models?: ChannelModelMapping[]): ChannelModelMapping[] => { if (!Array.isArray(models)) { return []; } return models - .map((model) => ({ - id: typeof model?.id === "string" ? model.id.trim() : "", - name: typeof model?.name === "string" ? model.name.trim() : "", - enabled: model?.enabled !== false, - })) + .map((model) => { + const normalizedModel: ChannelModelMapping = { + id: typeof model?.id === "string" ? model.id.trim() : "", + name: typeof model?.name === "string" ? model.name.trim() : "", + enabled: model?.enabled !== false, + }; + const defaultParams = normalizeDefaultParams(model?.default_params); + if (defaultParams) { + normalizedModel.default_params = defaultParams; + } + return normalizedModel; + }) .filter((model) => model.id.length > 0) - .map((model) => ({ - id: model.id, - name: model.name || model.id, - enabled: model.enabled, - })); + .map((model) => { + const normalizedModel: ChannelModelMapping = { + id: model.id, + name: model.name || model.id, + enabled: model.enabled, + }; + const defaultParams = normalizeDefaultParams(model.default_params); + if (defaultParams) { + normalizedModel.default_params = defaultParams; + } + return normalizedModel; + }); }; const getChannelModels = (config: ChannelConfig): ChannelModelMapping[] => { @@ -217,6 +246,7 @@ const buildRowsFromModels = (models: ChannelModelMapping[]): ModelRow[] => { id: model.id, name: model.name, enabled: model.enabled !== false, + default_params: normalizeDefaultParams(model.default_params), })), ); }; @@ -225,15 +255,47 @@ const serializeModels = (models: ChannelModelMapping[]): string => { return JSON.stringify(models, null, 2); }; +const formatDefaultParams = (defaultParams?: JsonObject): string => { + return defaultParams ? JSON.stringify(defaultParams, null, 2) : ""; +}; + +const validateDefaultParamsValue = (value: unknown): { defaultParams?: JsonObject; error?: string } => { + if (value == null || typeof value !== "object" || Array.isArray(value)) { + return { error: i18n.t("channels.validation.defaultParamsJsonMustBeObject") }; + } + + return { defaultParams: normalizeDefaultParams(value) }; +}; + +const parseDefaultParamsJson = (value: string): { defaultParams?: JsonObject; error?: string } => { + const trimmedValue = value.trim(); + if (!trimmedValue) { + return {}; + } + + let parsed: unknown; + try { + parsed = JSON.parse(trimmedValue); + } catch { + return { error: i18n.t("channels.validation.defaultParamsJsonInvalid") }; + } + + return validateDefaultParamsValue(parsed); +}; + +const getDefaultParamsCount = (defaultParams?: JsonObject): number => { + return defaultParams ? Object.keys(defaultParams).length : 0; +}; + const validateModels = (models: ChannelModelMapping[]): string | null => { if (models.length === 0) { - return i18n.t('channels.validation.atLeastOneModel'); + return i18n.t("channels.validation.atLeastOneModel"); } const names = new Set(); for (const model of models) { if (names.has(model.name)) { - return i18n.t('channels.validation.duplicateModelName', { name: model.name }); + return i18n.t("channels.validation.duplicateModelName", { name: model.name }); } names.add(model.name); } @@ -246,14 +308,19 @@ const parseModelsFromRows = (rows: ModelRow[]): { models: ChannelModelMapping[]; for (const row of activeRows) { if (!row.id.trim()) { - return { models: [], error: i18n.t('channels.validation.modelIdEmpty') }; + return { models: [], error: i18n.t("channels.validation.modelIdEmpty") }; } } const models = activeRows.map((row) => { const id = row.id.trim(); const name = row.name.trim() || id; - return { id, name, enabled: row.enabled !== false }; + const model: ChannelModelMapping = { id, name, enabled: row.enabled !== false }; + const defaultParams = normalizeDefaultParams(row.default_params); + if (defaultParams) { + model.default_params = defaultParams; + } + return model; }); const validationError = validateModels(models); @@ -270,33 +337,43 @@ const parseModelsFromJson = (value: string): { models: ChannelModelMapping[]; er try { parsed = JSON.parse(value); } catch { - return { models: [], error: i18n.t('channels.validation.modelJsonInvalid') }; + return { models: [], error: i18n.t("channels.validation.modelJsonInvalid") }; } if (!Array.isArray(parsed)) { - return { models: [], error: i18n.t('channels.validation.modelJsonMustBeArray') }; + return { models: [], error: i18n.t("channels.validation.modelJsonMustBeArray") }; } const models: ChannelModelMapping[] = []; for (const item of parsed) { if (!item || typeof item !== "object") { - return { models: [], error: i18n.t('channels.validation.modelJsonItemMustBeObject') }; + return { models: [], error: i18n.t("channels.validation.modelJsonItemMustBeObject") }; } const id = typeof (item as ChannelModelMapping).id === "string" ? (item as ChannelModelMapping).id.trim() : ""; const rawName = typeof (item as ChannelModelMapping).name === "string" ? (item as ChannelModelMapping).name.trim() : ""; + const rawDefaultParams = (item as ChannelModelMapping).default_params; if (!id) { - return { models: [], error: i18n.t('channels.validation.modelJsonIdEmpty') }; + return { models: [], error: i18n.t("channels.validation.modelJsonIdEmpty") }; } - models.push({ + const defaultParamsResult = rawDefaultParams == null ? {} : validateDefaultParamsValue(rawDefaultParams); + if (defaultParamsResult.error) { + return { models: [], error: defaultParamsResult.error }; + } + + const model: ChannelModelMapping = { id, name: rawName || id, enabled: (item as ChannelModelMapping).enabled !== false, - }); + }; + if (defaultParamsResult.defaultParams) { + model.default_params = defaultParamsResult.defaultParams; + } + models.push(model); } const validationError = validateModels(models); @@ -338,9 +415,7 @@ const buildInitialFetchedModelSelection = ( } const currentModelIds = new Set(currentModels.map((model) => model.id)); - return candidates - .filter((candidate) => currentModelIds.has(candidate.id)) - .map((candidate) => candidate.id); + return candidates.filter((candidate) => currentModelIds.has(candidate.id)).map((candidate) => candidate.id); }; const trimSlashes = (value: string): string => value.replace(/^\/+|\/+$/g, ""); @@ -507,13 +582,17 @@ export function Channels({ createMode = false, editRoute = false }: { createMode const [fetchedModelCandidates, setFetchedModelCandidates] = useState([]); const [selectedFetchedModelIds, setSelectedFetchedModelIds] = useState([]); const [fetchedModelsSearchQuery, setFetchedModelsSearchQuery] = useState(""); + const [isDefaultParamsDialogOpen, setIsDefaultParamsDialogOpen] = useState(false); + const [editingDefaultParamsRowIndex, setEditingDefaultParamsRowIndex] = useState(null); + const [defaultParamsJsonValue, setDefaultParamsJsonValue] = useState(""); + const [defaultParamsJsonError, setDefaultParamsJsonError] = useState(null); const { addToast } = useToast(); const queryClient = useQueryClient(); const modelEditorModeOptions = [ - { value: "visual" as const, label: t('common.visual') }, - { value: "json" as const, label: t('common.json') }, + { value: "visual" as const, label: t("common.visual") }, + { value: "json" as const, label: t("common.json") }, ]; const applyModels = (models: ChannelModelMapping[]) => { @@ -571,11 +650,11 @@ export function Channels({ createMode = false, editRoute = false }: { createMode }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["channels"] }); - addToast(editingKey ? t('channels.updateSuccess') : t('channels.addSuccess'), "success"); + addToast(editingKey ? t("channels.updateSuccess") : t("channels.addSuccess"), "success"); closeForm(); }, onError: (error: Error) => { - addToast(t('common.saveFailed', { message: error.message }), "error"); + addToast(t("common.saveFailed", { message: error.message }), "error"); }, }); @@ -585,10 +664,10 @@ export function Channels({ createMode = false, editRoute = false }: { createMode }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["channels"] }); - addToast(t('channels.deleteSuccess'), "success"); + addToast(t("channels.deleteSuccess"), "success"); }, onError: (error: Error) => { - addToast(t('common.deleteFailed', { message: error.message }), "error"); + addToast(t("common.deleteFailed", { message: error.message }), "error"); }, }); @@ -622,13 +701,13 @@ export function Channels({ createMode = false, editRoute = false }: { createMode return { previousChannels }; }, onSuccess: (_, { enabled }) => { - addToast(enabled ? t('channels.enabledToast') : t('channels.disabledToast'), "success"); + addToast(enabled ? t("channels.enabledToast") : t("channels.disabledToast"), "success"); }, onError: (error: Error, _variables, context) => { if (context?.previousChannels) { queryClient.setQueryData(["channels"], context.previousChannels); } - addToast(t('channels.toggleFailed', { message: error.message }), "error"); + addToast(t("channels.toggleFailed", { message: error.message }), "error"); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ["channels"] }); @@ -642,7 +721,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode onSuccess: (response) => { const candidates = normalizeFetchedModelCandidates((response.data as ChannelModelMapping[]) || []); if (candidates.length === 0) { - addToast(t('channels.noFetchedModels'), "error"); + addToast(t("channels.noFetchedModels"), "error"); return; } @@ -653,7 +732,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode setIsFetchedModelsDialogOpen(true); }, onError: (error: Error) => { - addToast(t('channels.fetchModelsFailed', { message: error.message }), "error"); + addToast(t("channels.fetchModelsFailed", { message: error.message }), "error"); }, }); @@ -669,6 +748,10 @@ export function Channels({ createMode = false, editRoute = false }: { createMode setFetchedModelCandidates([]); setSelectedFetchedModelIds([]); setFetchedModelsSearchQuery(""); + setIsDefaultParamsDialogOpen(false); + setEditingDefaultParamsRowIndex(null); + setDefaultParamsJsonValue(""); + setDefaultParamsJsonError(null); setEditingKey(null); setEditMode("form"); }, []); @@ -690,7 +773,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode if (!targetChannel) { resetForm(); setView("list"); - addToast(t('channels.notFound'), "error"); + addToast(t("channels.notFound"), "error"); navigate("/channels", { replace: true }); return; } @@ -722,7 +805,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode }; const handleDelete = (key: string) => { - if (confirm(t('channels.deleteConfirm'))) { + if (confirm(t("channels.deleteConfirm"))) { deleteMutation.mutate(key); } }; @@ -735,7 +818,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode const apiKeys = parseApiKeys(apiKeysInput); if (!formData.type || !formData.endpoint || apiKeys.length === 0) { - addToast(t('channels.fillTypeEndpointKey'), "error"); + addToast(t("channels.fillTypeEndpointKey"), "error"); return; } @@ -783,18 +866,18 @@ export function Channels({ createMode = false, editRoute = false }: { createMode ); if (selectedModels.length === 0) { - addToast(t('channels.selectAtLeastOneModel'), "error"); + addToast(t("channels.selectAtLeastOneModel"), "error"); return; } applyModels(selectedModels); setIsFetchedModelsDialogOpen(false); - addToast(t('channels.modelsApplied', { count: selectedModels.length }), "success"); + addToast(t("channels.modelsApplied", { count: selectedModels.length }), "success"); }; const handleSave = () => { if (!channelKey) { - addToast(t('channels.fillChannelKey'), "error"); + addToast(t("channels.fillChannelKey"), "error"); return; } @@ -807,7 +890,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode } if (!formData.name || !formData.endpoint || apiKeys.length === 0) { - addToast(t('channels.fillRequired'), "error"); + addToast(t("channels.fillRequired"), "error"); return; } @@ -823,7 +906,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode try { config = normalizeChannelFormConfig(JSON.parse(jsonValue)); } catch { - addToast(t('common.jsonFormatError'), "error"); + addToast(t("common.jsonFormatError"), "error"); return; } @@ -867,7 +950,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode loadFormConfig(config); setEditMode("form"); } catch { - addToast(t('common.jsonFormatError'), "error"); + addToast(t("common.jsonFormatError"), "error"); } }; @@ -912,7 +995,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode } }; - const updateModelRow = (index: number, field: keyof ModelRow, value: string) => { + const updateModelRow = (index: number, field: "id" | "name", value: string) => { const nextRows = [...modelRows]; const previousRow = nextRows[index] || createEmptyModelRow(); const nextRow = { @@ -928,6 +1011,66 @@ export function Channels({ createMode = false, editRoute = false }: { createMode updateModelRowsState(nextRows); }; + const closeDefaultParamsDialog = () => { + setIsDefaultParamsDialogOpen(false); + setEditingDefaultParamsRowIndex(null); + setDefaultParamsJsonValue(""); + setDefaultParamsJsonError(null); + }; + + const openDefaultParamsDialog = (index: number) => { + const row = modelRows[index]; + if (!row || isEmptyModelRow(row)) { + return; + } + + setEditingDefaultParamsRowIndex(index); + setDefaultParamsJsonValue(formatDefaultParams(row.default_params)); + setDefaultParamsJsonError(null); + setIsDefaultParamsDialogOpen(true); + }; + + const updateModelRowDefaultParams = (index: number, defaultParams?: JsonObject) => { + const nextRows = [...modelRows]; + const row = nextRows[index]; + if (!row) { + return; + } + + nextRows[index] = { + ...row, + default_params: defaultParams, + }; + updateModelRowsState(nextRows); + }; + + const handleFormatDefaultParamsJson = () => { + const result = parseDefaultParamsJson(defaultParamsJsonValue); + if (result.error) { + setDefaultParamsJsonError(result.error); + return; + } + + setDefaultParamsJsonValue(formatDefaultParams(result.defaultParams)); + setDefaultParamsJsonError(null); + }; + + const handleSaveDefaultParams = () => { + if (editingDefaultParamsRowIndex == null) { + closeDefaultParamsDialog(); + return; + } + + const result = parseDefaultParamsJson(defaultParamsJsonValue); + if (result.error) { + setDefaultParamsJsonError(result.error); + return; + } + + updateModelRowDefaultParams(editingDefaultParamsRowIndex, result.defaultParams); + closeDefaultParamsDialog(); + }; + const removeModelRow = (index: number) => { const meaningfulRows = modelRows.filter((row) => !isEmptyModelRow(row)); if (meaningfulRows.length === 0) { @@ -958,8 +1101,8 @@ export function Channels({ createMode = false, editRoute = false }: { createMode if (view === "list") { return ( } @@ -977,7 +1120,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
setSearchQuery(e.target.value)} className="pl-9" @@ -990,7 +1133,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
- {t('common.loading')} + {t("common.loading")}
) : !data || data.length === 0 ? ( @@ -999,13 +1142,13 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
-

{t('channels.emptyTitle')}

+

{t("channels.emptyTitle")}

- {t('channels.emptyDescription')} + {t("channels.emptyDescription")}

@@ -1019,8 +1162,8 @@ export function Channels({ createMode = false, editRoute = false }: { createMode const enabledModelCount = (config.models || []).filter((model) => model.enabled !== false).length; const modelSummary = enabledModelCount === modelCount - ? t('channels.modelCount', { count: modelCount }) - : t('channels.modelCountPartial', { enabled: enabledModelCount, total: modelCount }); + ? t("channels.modelCount", { count: modelCount }) + : t("channels.modelCountPartial", { enabled: enabledModelCount, total: modelCount }); const isMenuOpen = openMenu === channel.key; const isEnabled = config.enabled !== false; const isToggling = @@ -1053,14 +1196,14 @@ export function Channels({ createMode = false, editRoute = false }: { createMode onClick={() => handleEdit(channel)} > - {t('common.edit')} + {t("common.edit")}
)} @@ -1079,7 +1222,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
-
{t('channels.runningStatus')}
+
{t("channels.runningStatus")}
- {isEnabled ? t('common.enabled') : t('common.disabled')} + {isEnabled ? t("common.enabled") : t("common.disabled")}
handleToggleEnabled(channel, checked)} - aria-label={`${config.name || channel.key} ${t('channels.channelStatus')}`} + aria-label={`${config.name || channel.key} ${t("channels.channelStatus")}`} />
@@ -1128,7 +1271,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode checked={isEnabled} disabled={isToggling} onCheckedChange={(checked) => handleToggleEnabled(channel, checked)} - aria-label={`${config.name || channel.key} ${t('channels.channelStatus')}`} + aria-label={`${config.name || channel.key} ${t("channels.channelStatus")}`} />
@@ -1149,7 +1292,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode ); })} {filteredData?.length === 0 && searchQuery && ( -
{t('channels.noMatchingChannels')}
+
{t("channels.noMatchingChannels")}
)}
@@ -1164,7 +1307,7 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
- {t('channels.loadingChannel')} + {t("channels.loadingChannel")}
@@ -1177,13 +1320,15 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
-

{editingKey ? t('channels.editChannel') : t('channels.addChannel')}

+

+ {editingKey ? t("channels.editChannel") : t("channels.addChannel")} +

@@ -1191,12 +1336,12 @@ export function Channels({ createMode = false, editRoute = false }: { createMode
-

{t('channels.channelKey')}

-

{t('channels.channelKeyDesc')}

+

{t("channels.channelKey")}

+

{t("channels.channelKeyDesc")}

setChannelKey(e.target.value)} - placeholder={t('channels.channelKeyPlaceholder')} + placeholder={t("channels.channelKeyPlaceholder")} disabled={!!editingKey} className="font-mono text-sm" /> @@ -1207,21 +1352,21 @@ export function Channels({ createMode = false, editRoute = false }: { createMode <> -

{t('channels.basicInfo')}

+

{t("channels.basicInfo")}

setFormData({ ...formData, name: e.target.value })} - placeholder={t('channels.channelNamePlaceholder')} + placeholder={t("channels.channelNamePlaceholder")} />
setFormData({ ...formData, endpoint: e.target.value })} placeholder={endpointPlaceholder} /> -

{t('channels.apiEndpointPreview', { preview: endpointPreview || formData.type })}

+

+ {t("channels.apiEndpointPreview", { preview: endpointPreview || formData.type })} +