diff --git a/.gitignore b/.gitignore index d155618..d1b25be 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ node_modules dist dist-ssr *.local +skills # Editor directories and files settings.local.json diff --git a/package-lock.json b/package-lock.json index 0ee1f30..abe3fdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1244,6 +1244,18 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, + "node_modules/@openai/codex": { + "version": "0.98.0", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.98.0.tgz", + "integrity": "sha512-CKjrhAmzTvWn7Vbsi27iZRKBAJw9a7ZTTkWQDbLgQZP1weGbDIBk1r6wiLEp1ZmDO7w0fHPLYgnVspiOrYgcxg==", + "license": "Apache-2.0", + "bin": { + "codex": "bin/codex.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", diff --git a/src/components/common/PageTransition.tsx b/src/components/common/PageTransition.tsx index 5efd7ad..2231120 100644 --- a/src/components/common/PageTransition.tsx +++ b/src/components/common/PageTransition.tsx @@ -1,14 +1,13 @@ import { ReactNode, - createContext, useCallback, - useContext, useLayoutEffect, useRef, useState, } from 'react'; import { useLocation, type Location } from 'react-router-dom'; import gsap from 'gsap'; +import { PageTransitionLayerContext, type LayerStatus } from './PageTransitionLayer'; import './PageTransition.scss'; interface PageTransitionProps { @@ -27,8 +26,6 @@ const IOS_EXIT_TO_X_PERCENT_BACKWARD = 100; const IOS_ENTER_FROM_X_PERCENT_BACKWARD = -30; const IOS_EXIT_DIM_OPACITY = 0.72; -type LayerStatus = 'current' | 'exiting' | 'stacked'; - type Layer = { key: string; location: Location; @@ -39,16 +36,6 @@ type TransitionDirection = 'forward' | 'backward'; type TransitionVariant = 'vertical' | 'ios'; -type PageTransitionLayerContextValue = { - status: LayerStatus; -}; - -const PageTransitionLayerContext = createContext(null); - -export function usePageTransitionLayer() { - return useContext(PageTransitionLayerContext); -} - export function PageTransition({ render, getRouteOrder, diff --git a/src/components/common/PageTransitionLayer.ts b/src/components/common/PageTransitionLayer.ts new file mode 100644 index 0000000..3e33e86 --- /dev/null +++ b/src/components/common/PageTransitionLayer.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react'; + +export type LayerStatus = 'current' | 'exiting' | 'stacked'; + +type PageTransitionLayerContextValue = { + status: LayerStatus; +}; + +export const PageTransitionLayerContext = + createContext(null); + +export function usePageTransitionLayer() { + return useContext(PageTransitionLayerContext); +} + diff --git a/src/components/config/VisualConfigEditor.module.scss b/src/components/config/VisualConfigEditor.module.scss new file mode 100644 index 0000000..cf4e77d --- /dev/null +++ b/src/components/config/VisualConfigEditor.module.scss @@ -0,0 +1,37 @@ +.payloadRuleModelRow { + display: grid; + grid-template-columns: 1fr 160px auto; + gap: 8px; + align-items: center; +} + +.payloadRuleModelRowProtocolFirst { + grid-template-columns: 160px 1fr auto; +} + +.payloadRuleParamRow { + display: grid; + grid-template-columns: 1fr 140px 1fr auto; + gap: 8px; + align-items: center; +} + +.payloadFilterModelRow { + display: grid; + grid-template-columns: 1fr 160px auto; + gap: 8px; + align-items: center; +} + +@media (max-width: 900px) { + .payloadRuleModelRow, + .payloadRuleModelRowProtocolFirst, + .payloadRuleParamRow, + .payloadFilterModelRow { + grid-template-columns: minmax(0, 1fr); + } + + .payloadRowActionButton { + width: 100%; + } +} diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx index eff48ed..7f41220 100644 --- a/src/components/config/VisualConfigEditor.tsx +++ b/src/components/config/VisualConfigEditor.tsx @@ -6,6 +6,8 @@ import { Modal } from '@/components/ui/Modal'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { IconChevronDown } from '@/components/ui/icons'; import { ConfigSection } from '@/components/config/ConfigSection'; +import { useNotificationStore } from '@/stores'; +import styles from './VisualConfigEditor.module.scss'; import type { PayloadFilterRule, PayloadModelEntry, @@ -200,6 +202,7 @@ function ApiKeysCardEditor({ onChange: (nextValue: string) => void; }) { const { t } = useTranslation(); + const { showNotification } = useNotificationStore(); const apiKeys = useMemo( () => value @@ -262,6 +265,34 @@ function ApiKeysCardEditor({ closeModal(); }; + const handleCopy = async (apiKey: string) => { + const copyByExecCommand = () => { + const textarea = document.createElement('textarea'); + textarea.value = apiKey; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.pointerEvents = 'none'; + document.body.appendChild(textarea); + textarea.select(); + textarea.setSelectionRange(0, textarea.value.length); + const copied = document.execCommand('copy'); + document.body.removeChild(textarea); + if (!copied) throw new Error('copy_failed'); + }; + + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(apiKey); + } else { + copyByExecCommand(); + } + showNotification(t('notification.link_copied'), 'success'); + } catch { + showNotification(t('notification.copy_failed'), 'error'); + } + }; + return (
@@ -293,6 +324,9 @@ function ApiKeysCardEditor({
{maskApiKey(String(key || ''))}
+ @@ -358,7 +392,7 @@ function StringListEditor({ return (
{items.map((item, index) => ( -
+
-
+
{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}
@@ -547,7 +593,7 @@ function PayloadRulesEditor({
{t('config_management.visual.payload_rules.params')}
{(rule.params.length ? rule.params : []).map((param, paramIndex) => ( -
+
updateParam(ruleIndex, paramIndex, { value: e.target.value })} disabled={disabled} /> -
@@ -658,7 +710,15 @@ function PayloadFilterRulesEditor({ gap: 12, }} > -
+
{t('config_management.visual.payload_rules.rule')} {ruleIndex + 1}
@@ -891,15 +957,6 @@ export function VisualConfigEditor({ values, disabled = false, onChange }: Visua onChange={(e) => onChange({ logsMaxTotalSizeMb: e.target.value })} disabled={disabled} /> - onChange({ usageRecordsRetentionDays: e.target.value })} - disabled={disabled} - hint={t('config_management.visual.sections.system.usage_retention_hint')} - />
diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 81c6311..c06b762 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -10,8 +10,6 @@ import { import { NavLink, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; -import { Modal } from '@/components/ui/Modal'; -import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { PageTransition } from '@/components/common/PageTransition'; import { MainRoutes } from '@/router/MainRoutes'; import { @@ -34,8 +32,10 @@ import { useNotificationStore, useThemeStore, } from '@/stores'; -import { configApi, versionApi } from '@/services/api'; +import { versionApi } from '@/services/api'; import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh'; +import { LANGUAGE_LABEL_KEYS, LANGUAGE_ORDER } from '@/utils/constants'; +import { isSupportedLanguage } from '@/utils/language'; const sidebarIcons: Record = { dashboard: , @@ -174,44 +174,36 @@ const compareVersions = (latest?: string | null, current?: string | null) => { }; export function MainLayout() { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const { showNotification } = useNotificationStore(); const location = useLocation(); const apiBase = useAuthStore((state) => state.apiBase); const serverVersion = useAuthStore((state) => state.serverVersion); - const serverBuildDate = useAuthStore((state) => state.serverBuildDate); const connectionStatus = useAuthStore((state) => state.connectionStatus); const logout = useAuthStore((state) => state.logout); const config = useConfigStore((state) => state.config); const fetchConfig = useConfigStore((state) => state.fetchConfig); const clearCache = useConfigStore((state) => state.clearCache); - const updateConfigValue = useConfigStore((state) => state.updateConfigValue); const theme = useThemeStore((state) => state.theme); const cycleTheme = useThemeStore((state) => state.cycleTheme); - const toggleLanguage = useLanguageStore((state) => state.toggleLanguage); + const language = useLanguageStore((state) => state.language); + const setLanguage = useLanguageStore((state) => state.setLanguage); const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [checkingVersion, setCheckingVersion] = useState(false); + const [languageMenuOpen, setLanguageMenuOpen] = useState(false); const [brandExpanded, setBrandExpanded] = useState(true); - const [requestLogModalOpen, setRequestLogModalOpen] = useState(false); - const [requestLogDraft, setRequestLogDraft] = useState(false); - const [requestLogTouched, setRequestLogTouched] = useState(false); - const [requestLogSaving, setRequestLogSaving] = useState(false); const contentRef = useRef(null); + const languageMenuRef = useRef(null); const brandCollapseTimer = useRef | null>(null); const headerRef = useRef(null); - const versionTapCount = useRef(0); - const versionTapTimer = useRef | null>(null); const fullBrandName = 'CLI Proxy API Management Center'; const abbrBrandName = t('title.abbr'); - const requestLogEnabled = config?.requestLog ?? false; - const requestLogDirty = requestLogDraft !== requestLogEnabled; - const canEditRequestLog = connectionStatus === 'connected' && Boolean(config); const isLogsPage = location.pathname.startsWith('/logs'); // 将顶栏高度写入 CSS 变量,确保侧栏/内容区计算一致,防止滚动时抖动 @@ -243,7 +235,7 @@ export function MainLayout() { }; }, []); - // 将主内容区的中心点写入 CSS 变量,供底部浮层(如配置面板操作栏)对齐到内容区而非整窗 + // 将主内容区的中心点写入 CSS 变量,供底部浮层(配置面板操作栏、提供商导航)对齐到内容区 useLayoutEffect(() => { const updateContentCenter = () => { const el = contentRef.current; @@ -271,6 +263,7 @@ export function MainLayout() { resizeObserver.disconnect(); } window.removeEventListener('resize', updateContentCenter); + document.documentElement.style.removeProperty('--content-center-x'); }; }, []); @@ -288,18 +281,30 @@ export function MainLayout() { }, []); useEffect(() => { - if (requestLogModalOpen && !requestLogTouched) { - setRequestLogDraft(requestLogEnabled); + if (!languageMenuOpen) { + return; } - }, [requestLogModalOpen, requestLogTouched, requestLogEnabled]); - useEffect(() => { - return () => { - if (versionTapTimer.current) { - clearTimeout(versionTapTimer.current); + const handlePointerDown = (event: MouseEvent) => { + if (!languageMenuRef.current?.contains(event.target as Node)) { + setLanguageMenuOpen(false); } }; - }, []); + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setLanguageMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handlePointerDown); + document.addEventListener('keydown', handleEscape); + + return () => { + document.removeEventListener('mousedown', handlePointerDown); + document.removeEventListener('keydown', handleEscape); + }; + }, [languageMenuOpen]); const handleBrandClick = useCallback(() => { if (!brandExpanded) { @@ -314,59 +319,20 @@ export function MainLayout() { } }, [brandExpanded]); - const openRequestLogModal = useCallback(() => { - setRequestLogTouched(false); - setRequestLogDraft(requestLogEnabled); - setRequestLogModalOpen(true); - }, [requestLogEnabled]); - - const handleRequestLogClose = useCallback(() => { - setRequestLogModalOpen(false); - setRequestLogTouched(false); + const toggleLanguageMenu = useCallback(() => { + setLanguageMenuOpen((prev) => !prev); }, []); - const handleVersionTap = useCallback(() => { - versionTapCount.current += 1; - if (versionTapTimer.current) { - clearTimeout(versionTapTimer.current); - } - versionTapTimer.current = setTimeout(() => { - versionTapCount.current = 0; - }, 1500); - - if (versionTapCount.current >= 7) { - versionTapCount.current = 0; - if (versionTapTimer.current) { - clearTimeout(versionTapTimer.current); - versionTapTimer.current = null; + const handleLanguageSelect = useCallback( + (nextLanguage: string) => { + if (!isSupportedLanguage(nextLanguage)) { + return; } - openRequestLogModal(); - } - }, [openRequestLogModal]); - - const handleRequestLogSave = async () => { - if (!canEditRequestLog) return; - if (!requestLogDirty) { - setRequestLogModalOpen(false); - return; - } - - const previous = requestLogEnabled; - setRequestLogSaving(true); - updateConfigValue('request-log', requestLogDraft); - - try { - await configApi.updateRequestLog(requestLogDraft); - clearCache('request-log'); - showNotification(t('notification.request_log_updated'), 'success'); - setRequestLogModalOpen(false); - } catch (error: any) { - updateConfigValue('request-log', previous); - showNotification(`${t('notification.update_failed')}: ${error?.message || ''}`, 'error'); - } finally { - setRequestLogSaving(false); - } - }; + setLanguage(nextLanguage); + setLanguageMenuOpen(false); + }, + [setLanguage] + ); useEffect(() => { fetchConfig().catch(() => { @@ -478,7 +444,8 @@ export function MainLayout() { setCheckingVersion(true); try { const data = await versionApi.checkLatest(); - const latest = data?.['latest-version'] ?? data?.latest_version ?? data?.latest ?? ''; + const latestRaw = data?.['latest-version'] ?? data?.latest_version ?? data?.latest ?? ''; + const latest = typeof latestRaw === 'string' ? latestRaw : String(latestRaw ?? ''); const comparison = compareVersions(latest, serverVersion); if (!latest) { @@ -496,8 +463,11 @@ export function MainLayout() { } else { showNotification(t('system_info.version_is_latest'), 'success'); } - } catch (error: any) { - showNotification(`${t('system_info.version_check_error')}: ${error?.message || ''}`, 'error'); + } catch (error: unknown) { + const message = + error instanceof Error ? error.message : typeof error === 'string' ? error : ''; + const suffix = message ? `: ${message}` : ''; + showNotification(`${t('system_info.version_check_error')}${suffix}`, 'error'); } finally { setCheckingVersion(false); } @@ -569,9 +539,36 @@ export function MainLayout() { > {headerIcons.update} - +
+ + {languageMenuOpen && ( +
+ {LANGUAGE_ORDER.map((lang) => ( + + ))} +
+ )} +
- - - - - - } - > -
-
{t('basic_settings.request_log_warning')}
- { - setRequestLogDraft(value); - setRequestLogTouched(true); - }} - /> -
-
); } diff --git a/src/components/modelAlias/ModelMappingDiagram.tsx b/src/components/modelAlias/ModelMappingDiagram.tsx index 209a181..b207882 100644 --- a/src/components/modelAlias/ModelMappingDiagram.tsx +++ b/src/components/modelAlias/ModelMappingDiagram.tsx @@ -285,7 +285,6 @@ export const ModelMappingDiagram = forwardRef { // updateLines is called after layout is calculated, ensuring elements are in place. - updateLines(); const raf = requestAnimationFrame(updateLines); window.addEventListener('resize', updateLines); return () => { @@ -295,7 +294,6 @@ export const ModelMappingDiagram = forwardRef { - updateLines(); const raf = requestAnimationFrame(updateLines); return () => cancelAnimationFrame(raf); }, [providerGroupHeights, updateLines]); diff --git a/src/components/providers/AmpcodeSection/AmpcodeModal.tsx b/src/components/providers/AmpcodeSection/AmpcodeModal.tsx deleted file mode 100644 index 876a272..0000000 --- a/src/components/providers/AmpcodeSection/AmpcodeModal.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { Modal } from '@/components/ui/Modal'; -import { ModelInputList } from '@/components/ui/ModelInputList'; -import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; -import { useConfigStore, useNotificationStore } from '@/stores'; -import { ampcodeApi } from '@/services/api'; -import type { AmpcodeConfig } from '@/types'; -import { maskApiKey } from '@/utils/format'; -import { buildAmpcodeFormState, entriesToAmpcodeMappings } from '../utils'; -import type { AmpcodeFormState } from '../types'; - -interface AmpcodeModalProps { - isOpen: boolean; - disableControls: boolean; - onClose: () => void; - onBusyChange?: (busy: boolean) => void; -} - -export function AmpcodeModal({ isOpen, disableControls, onClose, onBusyChange }: AmpcodeModalProps) { - const { t } = useTranslation(); - const { showNotification, showConfirmation } = useNotificationStore(); - const config = useConfigStore((state) => state.config); - const updateConfigValue = useConfigStore((state) => state.updateConfigValue); - const clearCache = useConfigStore((state) => state.clearCache); - - const [form, setForm] = useState(() => buildAmpcodeFormState(null)); - const [loading, setLoading] = useState(false); - const [loaded, setLoaded] = useState(false); - const [mappingsDirty, setMappingsDirty] = useState(false); - const [error, setError] = useState(''); - const [saving, setSaving] = useState(false); - const initializedRef = useRef(false); - - const getErrorMessage = (err: unknown) => { - if (err instanceof Error) return err.message; - if (typeof err === 'string') return err; - return ''; - }; - - useEffect(() => { - onBusyChange?.(loading || saving); - }, [loading, saving, onBusyChange]); - - useEffect(() => { - if (!isOpen) { - initializedRef.current = false; - setLoading(false); - setSaving(false); - setError(''); - setLoaded(false); - setMappingsDirty(false); - setForm(buildAmpcodeFormState(null)); - onBusyChange?.(false); - return; - } - if (initializedRef.current) return; - initializedRef.current = true; - - setLoading(true); - setLoaded(false); - setMappingsDirty(false); - setError(''); - setForm(buildAmpcodeFormState(config?.ampcode ?? null)); - - void (async () => { - try { - const ampcode = await ampcodeApi.getAmpcode(); - setLoaded(true); - updateConfigValue('ampcode', ampcode); - clearCache('ampcode'); - setForm(buildAmpcodeFormState(ampcode)); - } catch (err: unknown) { - setError(getErrorMessage(err) || t('notification.refresh_failed')); - } finally { - setLoading(false); - } - })(); - }, [clearCache, config?.ampcode, isOpen, onBusyChange, t, updateConfigValue]); - - const clearAmpcodeUpstreamApiKey = async () => { - showConfirmation({ - title: t('ai_providers.ampcode_clear_upstream_api_key_title', { defaultValue: 'Clear Upstream API Key' }), - message: t('ai_providers.ampcode_clear_upstream_api_key_confirm'), - variant: 'danger', - confirmText: t('common.confirm'), - onConfirm: async () => { - setSaving(true); - setError(''); - try { - await ampcodeApi.clearUpstreamApiKey(); - const previous = config?.ampcode ?? {}; - const next: AmpcodeConfig = { ...previous }; - delete next.upstreamApiKey; - updateConfigValue('ampcode', next); - clearCache('ampcode'); - showNotification(t('notification.ampcode_upstream_api_key_cleared'), 'success'); - } catch (err: unknown) { - const message = getErrorMessage(err); - setError(message); - showNotification(`${t('notification.update_failed')}: ${message}`, 'error'); - } finally { - setSaving(false); - } - }, - }); - }; - - const performSaveAmpcode = async () => { - setSaving(true); - setError(''); - try { - const upstreamUrl = form.upstreamUrl.trim(); - const overrideKey = form.upstreamApiKey.trim(); - const modelMappings = entriesToAmpcodeMappings(form.mappingEntries); - - if (upstreamUrl) { - await ampcodeApi.updateUpstreamUrl(upstreamUrl); - } else { - await ampcodeApi.clearUpstreamUrl(); - } - - await ampcodeApi.updateForceModelMappings(form.forceModelMappings); - - if (loaded || mappingsDirty) { - if (modelMappings.length) { - await ampcodeApi.saveModelMappings(modelMappings); - } else { - await ampcodeApi.clearModelMappings(); - } - } - - if (overrideKey) { - await ampcodeApi.updateUpstreamApiKey(overrideKey); - } - - const previous = config?.ampcode ?? {}; - const next: AmpcodeConfig = { - upstreamUrl: upstreamUrl || undefined, - forceModelMappings: form.forceModelMappings, - }; - - if (previous.upstreamApiKey) { - next.upstreamApiKey = previous.upstreamApiKey; - } - - if (Array.isArray(previous.modelMappings)) { - next.modelMappings = previous.modelMappings; - } - - if (overrideKey) { - next.upstreamApiKey = overrideKey; - } - - if (loaded || mappingsDirty) { - if (modelMappings.length) { - next.modelMappings = modelMappings; - } else { - delete next.modelMappings; - } - } - - updateConfigValue('ampcode', next); - clearCache('ampcode'); - showNotification(t('notification.ampcode_updated'), 'success'); - onClose(); - } catch (err: unknown) { - const message = getErrorMessage(err); - setError(message); - showNotification(`${t('notification.update_failed')}: ${message}`, 'error'); - } finally { - setSaving(false); - } - }; - - const saveAmpcode = async () => { - if (!loaded && mappingsDirty) { - showConfirmation({ - title: t('ai_providers.ampcode_mappings_overwrite_title', { defaultValue: 'Overwrite Mappings' }), - message: t('ai_providers.ampcode_mappings_overwrite_confirm'), - variant: 'secondary', // Not dangerous, just a warning - confirmText: t('common.confirm'), - onConfirm: performSaveAmpcode, - }); - return; - } - - await performSaveAmpcode(); - }; - - return ( - - - - - } - > - {error &&
{error}
} - setForm((prev) => ({ ...prev, upstreamUrl: e.target.value }))} - disabled={loading || saving} - hint={t('ai_providers.ampcode_upstream_url_hint')} - /> - setForm((prev) => ({ ...prev, upstreamApiKey: e.target.value }))} - disabled={loading || saving} - hint={t('ai_providers.ampcode_upstream_api_key_hint')} - /> -
-
- {t('ai_providers.ampcode_upstream_api_key_current', { - key: config?.ampcode?.upstreamApiKey - ? maskApiKey(config.ampcode.upstreamApiKey) - : t('common.not_set'), - })} -
- -
- -
- setForm((prev) => ({ ...prev, forceModelMappings: value }))} - disabled={loading || saving} - /> -
{t('ai_providers.ampcode_force_model_mappings_hint')}
-
- -
- - { - setMappingsDirty(true); - setForm((prev) => ({ ...prev, mappingEntries: entries })); - }} - addLabel={t('ai_providers.ampcode_model_mappings_add_btn')} - namePlaceholder={t('ai_providers.ampcode_model_mappings_from_placeholder')} - aliasPlaceholder={t('ai_providers.ampcode_model_mappings_to_placeholder')} - disabled={loading || saving} - /> -
{t('ai_providers.ampcode_model_mappings_hint')}
-
-
- ); -} diff --git a/src/components/providers/ClaudeSection/ClaudeModal.tsx b/src/components/providers/ClaudeSection/ClaudeModal.tsx deleted file mode 100644 index 980a933..0000000 --- a/src/components/providers/ClaudeSection/ClaudeModal.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@/components/ui/Button'; -import { HeaderInputList } from '@/components/ui/HeaderInputList'; -import { Input } from '@/components/ui/Input'; -import { Modal } from '@/components/ui/Modal'; -import { ModelInputList } from '@/components/ui/ModelInputList'; -import { modelsToEntries } from '@/components/ui/modelInputListUtils'; -import type { ProviderKeyConfig } from '@/types'; -import { headersToEntries } from '@/utils/headers'; -import { excludedModelsToText } from '../utils'; -import type { ProviderFormState, ProviderModalProps } from '../types'; - -interface ClaudeModalProps extends ProviderModalProps { - isSaving: boolean; -} - -const buildEmptyForm = (): ProviderFormState => ({ - apiKey: '', - prefix: '', - baseUrl: '', - proxyUrl: '', - headers: [], - models: [], - excludedModels: [], - modelEntries: [{ name: '', alias: '' }], - excludedText: '', -}); - -export function ClaudeModal({ - isOpen, - editIndex, - initialData, - onClose, - onSave, - isSaving, -}: ClaudeModalProps) { - const { t } = useTranslation(); - const [form, setForm] = useState(buildEmptyForm); - - useEffect(() => { - if (!isOpen) return; - if (initialData) { - // eslint-disable-next-line react-hooks/set-state-in-effect - setForm({ - ...initialData, - headers: headersToEntries(initialData.headers), - modelEntries: modelsToEntries(initialData.models), - excludedText: excludedModelsToText(initialData.excludedModels), - }); - return; - } - setForm(buildEmptyForm()); - }, [initialData, isOpen]); - - return ( - - - - - } - > - setForm((prev) => ({ ...prev, apiKey: e.target.value }))} - /> - setForm((prev) => ({ ...prev, prefix: e.target.value }))} - hint={t('ai_providers.prefix_hint')} - /> - setForm((prev) => ({ ...prev, baseUrl: e.target.value }))} - /> - setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))} - /> - setForm((prev) => ({ ...prev, headers: entries }))} - addLabel={t('common.custom_headers_add')} - keyPlaceholder={t('common.custom_headers_key_placeholder')} - valuePlaceholder={t('common.custom_headers_value_placeholder')} - /> -
- - setForm((prev) => ({ ...prev, modelEntries: entries }))} - addLabel={t('ai_providers.claude_models_add_btn')} - namePlaceholder={t('common.model_name_placeholder')} - aliasPlaceholder={t('common.model_alias_placeholder')} - disabled={isSaving} - /> -
-
- -