diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index cfa956d20f..fc34851baa 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -83,25 +83,28 @@ "restart": { "button": "Restart", "tooltip": "Restart Server" - } + }, + "start": "Start", + "stop": "Stop" + }, + "authHeader": { + "title": "Authorization Header" }, "authHeaderText": "Use in Authorization header:", "configuration": "Configuration", "description": "Expose Cherry Studio's AI capabilities through OpenAI-compatible HTTP APIs", "documentation": { - "title": "API Documentation", - "unavailable": { - "description": "Start the API server to view the interactive documentation", - "title": "API Documentation Unavailable" - } + "title": "API Documentation" }, "fields": { "apiKey": { "copyTooltip": "Copy API Key", + "description": "Secure authentication token for API access", "label": "API Key", "placeholder": "API key will be auto-generated" }, "port": { + "description": "TCP port number for the HTTP server (1000-65535)", "helpText": "Stop server to change port", "label": "Port" }, diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index bf52958260..31be073d7f 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -83,25 +83,28 @@ "restart": { "button": "再起動", "tooltip": "サーバーを再起動" - } + }, + "start": "開始", + "stop": "停止" + }, + "authHeader": { + "title": "認証ヘッダー" }, "authHeaderText": "認証ヘッダーで使用:", "configuration": "設定", "description": "OpenAI 互換の HTTP API を通じて Cherry Studio の AI 機能を公開します", "documentation": { - "title": "API ドキュメント", - "unavailable": { - "description": "インタラクティブドキュメントを表示するには API サーバーを開始してください", - "title": "API ドキュメントが利用できません" - } + "title": "API ドキュメント" }, "fields": { "apiKey": { "copyTooltip": "API キーをコピー", + "description": "API アクセスのための安全な認証トークン", "label": "API キー", "placeholder": "API キーは自動生成されます" }, "port": { + "description": "HTTP サーバーの TCP ポート番号 (1000-65535)", "helpText": "ポートを変更するにはサーバーを停止してください", "label": "ポート" }, @@ -1575,7 +1578,7 @@ "prompt_placeholder": "作成したい画像を説明します。例:夕日の湖畔、遠くに山々", "prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します", "prompt_placeholder_en": "「英語」の説明を入力します。Imagenは現在、英語のプロンプト語のみをサポートしています", - "proxy_required": "打開代理並開啟”TUN模式“查看生成圖片或複製到瀏覽器開啟,後續會支持國內直連", + "proxy_required": "打開代理並開啟TUN模式查看生成圖片或複製到瀏覽器開啟,後續會支持國內直連", "quality": "品質", "quality_options": { "auto": "自動", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 9de68649be..a368a910f5 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -83,25 +83,28 @@ "restart": { "button": "Перезапустить", "tooltip": "Перезапустить сервер" - } + }, + "start": "Запустить", + "stop": "Остановить" + }, + "authHeader": { + "title": "Авторизация" }, "authHeaderText": "Использовать в заголовке авторизации:", "configuration": "Конфигурация", "description": "Предоставляет возможности ИИ Cherry Studio через HTTP API, совместимые с OpenAI", "documentation": { - "title": "Документация API", - "unavailable": { - "description": "Запустите API сервер для просмотра интерактивной документации", - "title": "Документация API недоступна" - } + "title": "Документация API" }, "fields": { "apiKey": { "copyTooltip": "Копировать API ключ", + "description": "Безопасный токен для доступа к API", "label": "API Ключ", "placeholder": "API ключ будет сгенерирован автоматически" }, "port": { + "description": "TCP порт для HTTP сервера (1000-65535)", "helpText": "Остановите сервер для изменения порта", "label": "Порт" }, diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e13ce69ee6..1a7d633a33 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -83,25 +83,28 @@ "restart": { "button": "重启", "tooltip": "重启服务器" - } + }, + "start": "启动", + "stop": "停止" + }, + "authHeader": { + "title": "授权标头" }, "authHeaderText": "在授权标头中使用:", "configuration": "配置", "description": "通过 OpenAI 兼容的 HTTP API 暴露 Cherry Studio 的 AI 功能", "documentation": { - "title": "API 文档", - "unavailable": { - "description": "启动 API 服务器以查看交互式文档", - "title": "API 文档不可用" - } + "title": "API 文档" }, "fields": { "apiKey": { "copyTooltip": "复制 API 密钥", + "description": "用于 API 访问的安全认证令牌", "label": "API 密钥", "placeholder": "API 密钥将自动生成" }, "port": { + "description": "HTTP 服务器的 TCP 端口号 (1000-65535)", "helpText": "停止服务器以更改端口", "label": "端口" }, diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index d9f461c89a..13a6508873 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -83,25 +83,28 @@ "restart": { "button": "重新啟動", "tooltip": "重新啟動伺服器" - } + }, + "start": "啟動", + "stop": "停止" + }, + "authHeader": { + "title": "授權標頭" }, "authHeaderText": "在授權標頭中使用:", "configuration": "配置", "description": "透過 OpenAI 相容的 HTTP API 公開 Cherry Studio 的 AI 功能", "documentation": { - "title": "API 文件", - "unavailable": { - "description": "啟動 API 伺服器以檢視互動式文件", - "title": "API 文件無法使用" - } + "title": "API 文件" }, "fields": { "apiKey": { "copyTooltip": "複製 API 金鑰", + "description": "用於 API 訪問的安全認證令牌", "label": "API 金鑰", "placeholder": "API 金鑰將自動生成" }, "port": { + "description": "HTTP 伺服器的 TCP 連接埠 (1000-65535)", "helpText": "停止伺服器以變更連接埠", "label": "連接埠" }, @@ -1574,7 +1577,7 @@ "prompt_enhancement_tip": "開啟後將提示重寫為詳細的、適合模型的版本", "prompt_placeholder": "描述你想建立的圖片,例如:一個寧靜的湖泊,夕陽西下,遠處是群山", "prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 ' 雙引號 ' 包裹", - "prompt_placeholder_en": "輸入” 英文 “圖片描述,目前 Imagen 僅支持英文提示詞", + "prompt_placeholder_en": "輸入英文圖片描述,目前 Imagen 僅支持英文提示詞", "proxy_required": "打開代理並開啟”TUN 模式 “查看生成圖片或複製到瀏覽器開啟,後續會支持國內直連", "quality": "品質", "quality_options": { diff --git a/src/renderer/src/pages/settings/ApiServerSettings/ApiServerSettings.tsx b/src/renderer/src/pages/settings/ApiServerSettings/ApiServerSettings.tsx index fb81bad0fe..f612e2f93d 100644 --- a/src/renderer/src/pages/settings/ApiServerSettings/ApiServerSettings.tsx +++ b/src/renderer/src/pages/settings/ApiServerSettings/ApiServerSettings.tsx @@ -1,10 +1,10 @@ -import { CopyOutlined, GlobalOutlined, ReloadOutlined } from '@ant-design/icons' import { useTheme } from '@renderer/context/ThemeProvider' import { loggerService } from '@renderer/services/LoggerService' import { RootState, useAppDispatch } from '@renderer/store' import { setApiServerApiKey, setApiServerPort } from '@renderer/store/settings' import { IpcChannel } from '@shared/IpcChannel' -import { Button, Card, Input, Space, Switch, Tooltip, Typography } from 'antd' +import { Button, Input, InputNumber, Tooltip, Typography } from 'antd' +import { Copy, ExternalLink, Play, RotateCcw, Square } from 'lucide-react' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -16,175 +16,13 @@ import { SettingContainer } from '..' const logger = loggerService.withContext('ApiServerSettings') const { Text, Title } = Typography -const ConfigCard = styled(Card)` - margin-bottom: 24px; - border-radius: 12px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - border: 1px solid var(--color-border); - - .ant-card-head { - border-bottom: 1px solid var(--color-border); - padding: 16px 24px; - } - - .ant-card-body { - padding: 16px 20px; - } -` - -const SectionHeader = styled.div` - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 12px; - - h4 { - margin: 0; - font-size: 16px; - font-weight: 600; - color: var(--color-text-1); - } -` - -const FieldLabel = styled.div` - font-size: 14px; - font-weight: 500; - color: var(--color-text-1); - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 6px; -` - -const ActionButtonGroup = styled(Space)` - .ant-btn { - border-radius: 6px; - font-weight: 500; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - } - - .ant-btn-primary { - background: #1677ff; - border-color: #1677ff; - } - - .ant-btn:hover { - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } -` - -const StyledInput = styled(Input)` - border-radius: 6px; - border: 1.5px solid var(--color-border); - - &:focus, - &:focus-within { - border-color: #1677ff; - box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1); - } -` - -const ServerControlPanel = styled.div<{ status: boolean }>` - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - border-radius: 8px; - background: ${(props) => - props.status - ? 'linear-gradient(135deg, #f6ffed 0%, #f0f9ff 100%)' - : 'linear-gradient(135deg, #fff2f0 0%, #fafafa 100%)'}; - border: 1px solid ${(props) => (props.status ? '#d9f7be' : '#ffd6d6')}; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - } -` - -const StatusSection = styled.div<{ status: boolean }>` - display: flex; - align-items: center; - gap: 10px; - - .status-indicator { - position: relative; - width: 10px; - height: 10px; - border-radius: 50%; - background: ${(props) => (props.status ? '#52c41a' : '#ff4d4f')}; - - &::before { - content: ''; - position: absolute; - inset: -3px; - border-radius: 50%; - background: ${(props) => (props.status ? '#52c41a' : '#ff4d4f')}; - opacity: 0.2; - animation: ${(props) => (props.status ? 'pulse 2s infinite' : 'none')}; - } - } - - .status-content { - display: flex; - flex-direction: column; - gap: 2px; - } - - .status-text { - font-weight: 600; - font-size: 14px; - color: ${(props) => (props.status ? '#52c41a' : '#ff4d4f')}; - margin: 0; - } - - .status-subtext { - font-size: 12px; - color: var(--color-text-3); - margin: 0; - } - - @keyframes pulse { - 0%, - 100% { - transform: scale(1); - opacity: 0.2; - } - 50% { - transform: scale(1.5); - opacity: 0.1; - } - } -` - -const ControlSection = styled.div` - display: flex; - align-items: center; - gap: 12px; - - .restart-btn { - opacity: 0; - transform: translateX(10px); - transition: all 0.3s ease; - - &.visible { - opacity: 1; - transform: translateX(0); - } - } -` - const ApiServerSettings: FC = () => { const { theme } = useTheme() const dispatch = useAppDispatch() const { t } = useTranslation() // API Server state with proper defaults - const apiServerConfig = useSelector((state: RootState) => { - return state.settings.apiServer - }) + const apiServerConfig = useSelector((state: RootState) => state.settings.apiServer) const [apiServerRunning, setApiServerRunning] = useState(false) const [apiServerLoading, setApiServerLoading] = useState(false) @@ -265,179 +103,320 @@ const ApiServerSettings: FC = () => { } } + const openApiDocs = () => { + if (apiServerRunning) { + window.open(`http://localhost:${apiServerConfig.port}/api-docs`, '_blank') + } + } + return ( - + {/* Header Section */} -
- - {t('apiServer.title')} - - {t('apiServer.description')} -
- - {/* Server Status & Configuration Card */} - - -

{t('apiServer.configuration')}

- - }> - - {/* Server Control Panel */} - - -
-
-
- {apiServerRunning ? t('apiServer.status.running') : t('apiServer.status.stopped')} -
-
- {apiServerRunning ? `http://localhost:${apiServerConfig.port}` : t('apiServer.fields.port.helpText')} -
-
- - - - - - - - - - - {/* Configuration Fields */} -
- {/* Port Configuration */} - {!apiServerRunning && ( -
- {t('apiServer.fields.port.label')} - handlePortChange(e.target.value)} - style={{ width: 100 }} - min={1000} - max={65535} - disabled={apiServerRunning} - placeholder="23333" - size="small" - /> - {apiServerRunning && ( - - {t('apiServer.fields.port.helpText')} - - )} -
- )} - - {/* API Key Configuration */} -
- {t('apiServer.fields.apiKey.label')} - - - - - - {!apiServerRunning && ( - - )} - -
- - {/* Authorization header info */} - - {t('apiServer.authHeaderText')}{' '} - - Bearer {apiServerConfig.apiKey || 'your-api-key'} - - -
- - - - {/* API Documentation Card */} - -

{t('apiServer.documentation.title')}

- - }> - {apiServerRunning ? ( -