-
- 公网地址
+
+ 公网地址
-
+
{url}
@@ -216,14 +247,10 @@ function TunnelSection({
)}
{status === 'error' && error && (
-
-
隧道连接失败: {error}
+
)}
-
- {isDisabled && (
-
请先确保本地服务器已连接
- )}
);
}
@@ -245,10 +272,7 @@ function CopyButton({ text }: { text: string }) {
};
return (
-
diff --git a/packages/desktop/src/renderer/components/SettingRow.tsx b/packages/desktop/src/renderer/components/SettingRow.tsx
new file mode 100644
index 0000000..5a7e5fc
--- /dev/null
+++ b/packages/desktop/src/renderer/components/SettingRow.tsx
@@ -0,0 +1,37 @@
+/**
+ * @file src/renderer/components/SettingRow.tsx
+ * @description 通用设置行组件,提供左标签 + 右控件的统一布局,以及细分割线
+ */
+
+import type { ReactNode } from 'react';
+
+interface SettingRowProps {
+ label: string;
+ desc?: string;
+ children?: ReactNode;
+}
+
+/**
+ * 设置行组件,左侧显示标签和描述,右侧放置控件
+ * @param label - 设置项标签
+ * @param desc - 可选的描述文字
+ * @param children - 右侧控件区域
+ */
+export function SettingRow({ label, desc, children }: SettingRowProps) {
+ return (
+
+
+
{label}
+ {desc &&
{desc}
}
+
+ {children &&
{children}
}
+
+ );
+}
+
+/**
+ * 设置项之间的细分割线
+ */
+export function SettingDivider() {
+ return
;
+}
diff --git a/packages/desktop/src/renderer/components/SettingsPage.tsx b/packages/desktop/src/renderer/components/SettingsPage.tsx
index c49d147..dc7f57e 100644
--- a/packages/desktop/src/renderer/components/SettingsPage.tsx
+++ b/packages/desktop/src/renderer/components/SettingsPage.tsx
@@ -2,10 +2,11 @@
* @file src/renderer/components/SettingsPage.tsx
* @description 设置中心页面组件,嵌入主窗口右侧内容区域
* 包含通用设置、模型供应商、MCP 服务、Skills 和语音服务五个标签页
+ * 使用浅灰背景 + 白色卡片 + 左侧圆角 Tab 的 Demo 视觉风格
*/
import React, { useEffect, useState } from 'react';
-import { Key, Mic, Server, Settings, Zap } from 'lucide-react';
+import { ArrowLeft, Key, Mic, Server, Settings, Zap } from 'lucide-react';
import { ProviderConfigPanel } from './ProviderConfigPanel';
import { ConfigForm } from './ConfigForm';
import { McpListTab } from './McpListTab';
@@ -14,7 +15,7 @@ import { VoiceConfigPanel } from './VoiceConfigPanel';
import { useConfigStore } from '../stores/configStore';
import { useProviderStore } from '../stores/providerStore';
import { useViewStore } from '../stores/viewStore';
-import { toast } from '../stores/toastStore';
+import { cn } from '../lib/utils';
type TabKey = 'general' | 'providers' | 'mcp' | 'skills' | 'voice';
@@ -31,8 +32,7 @@ const tabs: { key: TabKey; label: string; icon: React.ReactNode }[] = [
* 提供通用设置、模型供应商、MCP 服务、Skills 和语音服务五个标签页的切换和内容展示
*/
export const SettingsPage: React.FC = () => {
- const { config, loading, saving, error, loadConfig, saveConfig } =
- useConfigStore();
+ const { loading, error, loadConfig } = useConfigStore();
const { saveAllPending } = useProviderStore();
const setView = useViewStore((s) => s.setView);
const [activeTab, setActiveTab] = useState
('general');
@@ -41,21 +41,6 @@ export const SettingsPage: React.FC = () => {
loadConfig();
}, [loadConfig]);
- /**
- * 保存当前通用配置到后端
- */
- const handleSave = async () => {
- if (!config) return;
-
- try {
- await saveConfig(config);
- toast.success('配置已保存');
- } catch (err) {
- const message = err instanceof Error ? err.message : '保存失败';
- toast.error(`保存失败:${message}`);
- }
- };
-
/**
* 保存所有待写入的 Provider 配置后切回聊天视图
*/
@@ -66,37 +51,44 @@ export const SettingsPage: React.FC = () => {
if (loading) {
return (
-
-
加载中...
+
);
}
- if (error && !config) {
+ if (error) {
return (
-
-
加载配置失败:{error}
+
);
}
return (
-
-
-
-
设置中心
+
+
+
-
-
-
- {activeTab === 'providers' &&
}
- {activeTab === 'mcp' &&
}
- {activeTab === 'skills' &&
}
- {activeTab === 'general' &&
}
- {activeTab === 'voice' &&
}
-
-
-
-
-
- 关闭
-
- {activeTab === 'general' && (
-
- {saving ? '保存中...' : '保存'}
-
- )}
-
-
+
+ {activeTab === 'providers' &&
}
+ {activeTab === 'mcp' &&
}
+ {activeTab === 'skills' &&
}
+ {activeTab === 'general' &&
}
+ {activeTab === 'voice' &&
}
);
diff --git a/packages/desktop/src/renderer/components/SkillListTab.tsx b/packages/desktop/src/renderer/components/SkillListTab.tsx
index 4e03fca..945b59a 100644
--- a/packages/desktop/src/renderer/components/SkillListTab.tsx
+++ b/packages/desktop/src/renderer/components/SkillListTab.tsx
@@ -1,6 +1,7 @@
/**
* @file src/renderer/components/SkillListTab.tsx
* @description Skills 列表标签页,展示后端已注册的技能模块名称和描述
+ * 使用外层白色卡片 + 列表项浅灰背景的 Demo 视觉风格
*/
import React, { useEffect, useState } from 'react';
@@ -45,47 +46,51 @@ export const SkillListTab: React.FC = () => {
if (error) {
return (
-
-
加载失败: {error}
-
- 重试
-
+
+
+
加载失败: {error}
+
+ 重试
+
+
);
}
return (
-
-
+
+
+
Skills
+
查看可用的技能模块
- {skills.length === 0 ? (
-
-
暂无 Skills
-
请在后端配置文件中添加 Skills
-
- ) : (
-
- {skills.map((skill) => (
-
-
{skill.name}
- {skill.description && (
-
- {skill.description}
-
- )}
-
- ))}
-
- )}
+ {skills.length === 0 ? (
+
+
暂无 Skills
+
请在后端配置文件中添加 Skills
+
+ ) : (
+
+ {skills.map((skill) => (
+
+
+ {skill.name}
+
+ {skill.description && (
+
+ {skill.description}
+
+ )}
+
+ ))}
+
+ )}
+
);
};
diff --git a/packages/desktop/src/renderer/components/VoiceConfigPanel.tsx b/packages/desktop/src/renderer/components/VoiceConfigPanel.tsx
index a747660..2ce3abb 100644
--- a/packages/desktop/src/renderer/components/VoiceConfigPanel.tsx
+++ b/packages/desktop/src/renderer/components/VoiceConfigPanel.tsx
@@ -2,12 +2,14 @@
* @file src/renderer/components/VoiceConfigPanel.tsx
* @description 语音服务配置面板,管理 MiniMax TTS API Key 和语音摘要阈值
* 设置页面是独立 Electron 窗口,Toast 不可见,因此使用内联 UI 反馈
+ * 使用卡片分组 + SettingRow 统一行布局
*/
import React, { useEffect, useState } from 'react';
-import { CheckCircle, Eye, EyeOff, HelpCircle, XCircle } from 'lucide-react';
+import { CheckCircle, Eye, EyeOff, XCircle } from 'lucide-react';
import { useVoiceStore } from '../stores/voiceStore';
import { synthesize } from '../lib/tts';
+import { SettingRow } from './SettingRow';
const VERIFY_TEXT = '测试语音功能连接';
@@ -93,80 +95,79 @@ export const VoiceConfigPanel: React.FC = () => {
};
return (
-
-
-
MiniMax(语音合成)
-
+
+
+
+ MiniMax(语音合成)
+
+
配置 MiniMax API Key 以启用语音播报功能
-
-
-
-
-
- setInputKey(e.target.value)}
- placeholder="输入 MiniMax API Key"
- className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 pr-10"
- />
- setShowKey(!showKey)}
- className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
- >
- {showKey ? (
-
- ) : (
-
- )}
-
-
-
+
+
+
+ setInputKey(e.target.value)}
+ placeholder="输入 MiniMax API Key"
+ className="w-64 h-8 px-3 text-[13px] border border-[#e0e0e0] rounded-lg focus:outline-none focus:ring-1 focus:ring-[#999] pr-10"
+ />
+ setShowKey(!showKey)}
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
+ >
+ {showKey ? (
+
+ ) : (
+
+ )}
+
+
{verifying ? '验证中...' : '验证并保存'}
- {feedback && (
-
- {feedback.type === 'success' ? (
-
- ) : (
-
- )}
- {feedback.message}
-
- )}
-
+
-
-
-
-
-
-
- 当回复超过此字符数时,会自动将内容总结后再播放,避免播报过长
-
-
+ {feedback && (
+
+ {feedback.type === 'success' ? (
+
+ ) : (
+
+ )}
+ {feedback.message}
+
+ )}
+
+
+
+
);
diff --git a/packages/desktop/src/renderer/components/ui/Switch.tsx b/packages/desktop/src/renderer/components/ui/Switch.tsx
new file mode 100644
index 0000000..f4137e6
--- /dev/null
+++ b/packages/desktop/src/renderer/components/ui/Switch.tsx
@@ -0,0 +1,31 @@
+/**
+ * @file src/renderer/components/ui/Switch.tsx
+ * @description 开关组件,基于 @radix-ui/react-switch 封装
+ */
+
+import * as React from 'react';
+import * as SwitchPrimitives from '@radix-ui/react-switch';
+import { cn } from '../../lib/utils';
+
+const Switch = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };
diff --git a/packages/desktop/src/renderer/index.css b/packages/desktop/src/renderer/index.css
index 3d6234c..6ed3e90 100644
--- a/packages/desktop/src/renderer/index.css
+++ b/packages/desktop/src/renderer/index.css
@@ -60,6 +60,7 @@
}
body {
@apply bg-background text-foreground;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
overflow: hidden;
}
diff --git a/packages/desktop/src/renderer/stores/viewStore.ts b/packages/desktop/src/renderer/stores/viewStore.ts
index 03f5ca1..bc338ed 100644
--- a/packages/desktop/src/renderer/stores/viewStore.ts
+++ b/packages/desktop/src/renderer/stores/viewStore.ts
@@ -1,17 +1,24 @@
/**
* @file src/renderer/stores/viewStore.ts
- * @description 主窗口视图状态管理,控制当前显示聊天界面还是设置页面
+ * @description 主窗口视图状态管理,控制当前显示聊天界面、设置页面还是 Agent 编辑页面
*/
import { create } from 'zustand';
-type ViewType = 'chat' | 'settings';
+type ViewType = 'chat' | 'settings' | 'agent-editor';
interface ViewStore {
currentView: ViewType;
+ editingAgentId: string | null;
setView: (view: ViewType) => void;
+ openAgentEditor: (agentId: string | null) => void;
+ closeAgentEditor: () => void;
}
export const useViewStore = create()((set) => ({
currentView: 'chat',
+ editingAgentId: null,
setView: (view) => set({ currentView: view }),
+ openAgentEditor: (agentId) =>
+ set({ currentView: 'agent-editor', editingAgentId: agentId }),
+ closeAgentEditor: () => set({ currentView: 'chat', editingAgentId: null }),
}));