- saas-client.ts: SaaS HTTP 客户端 (登录/注册/Token/模型列表/Chat Relay/配置同步) - saasStore.ts: Zustand 状态管理 (登录态、连接模式、可用模型、localStorage 持久化) - connectionStore.ts: 集成 SaaS 模式分支 (connect() 优先检查 SaaS 连接模式) - llm-service.ts: SaasLLMAdapter 实现 (通过 SaaS Relay 代理 LLM 调用) - SaaSLogin.tsx: 登录/注册表单 (服务器地址、用户名、密码、邮箱) - SaaSStatus.tsx: 连接状态展示 (账号信息、健康检查、可用模型列表) - SaaSSettings.tsx: SaaS 设置页面入口 (登录态切换、功能列表) - SettingsLayout.tsx: 添加 SaaS 平台菜单项 - store/index.ts: 导出 useSaaSStore
223 lines
7.8 KiB
TypeScript
223 lines
7.8 KiB
TypeScript
import { useState } from 'react';
|
||
import { useSecurityStore } from '../../store/securityStore';
|
||
import {
|
||
Settings as SettingsIcon,
|
||
BarChart3,
|
||
Puzzle,
|
||
MessageSquare,
|
||
FolderOpen,
|
||
Shield,
|
||
Info,
|
||
ArrowLeft,
|
||
Coins,
|
||
Cpu,
|
||
Zap,
|
||
HelpCircle,
|
||
ClipboardList,
|
||
Clock,
|
||
Heart,
|
||
Key,
|
||
Database,
|
||
Cloud,
|
||
} from 'lucide-react';
|
||
import { silentErrorHandler } from '../../lib/error-utils';
|
||
import { General } from './General';
|
||
import { UsageStats } from './UsageStats';
|
||
import { ModelsAPI } from './ModelsAPI';
|
||
import { MCPServices } from './MCPServices';
|
||
import { Skills } from './Skills';
|
||
import { IMChannels } from './IMChannels';
|
||
import { Workspace } from './Workspace';
|
||
import { Privacy } from './Privacy';
|
||
import { About } from './About';
|
||
import { Credits } from './Credits';
|
||
import { AuditLogsPanel } from '../AuditLogsPanel';
|
||
import { SecurityStatus } from '../SecurityStatus';
|
||
import { SecurityLayersPanel } from '../SecurityLayersPanel';
|
||
import { TaskList } from '../TaskList';
|
||
import { HeartbeatConfig } from '../HeartbeatConfig';
|
||
import { SecureStorage } from './SecureStorage';
|
||
import { VikingPanel } from '../VikingPanel';
|
||
import { SaaSSettings } from '../SaaS/SaaSSettings';
|
||
|
||
interface SettingsLayoutProps {
|
||
onBack: () => void;
|
||
}
|
||
|
||
type SettingsPage =
|
||
| 'general'
|
||
| 'usage'
|
||
| 'credits'
|
||
| 'models'
|
||
| 'mcp'
|
||
| 'skills'
|
||
| 'im'
|
||
| 'workspace'
|
||
| 'privacy'
|
||
| 'security'
|
||
| 'storage'
|
||
| 'saas'
|
||
| 'viking'
|
||
| 'audit'
|
||
| 'tasks'
|
||
| 'heartbeat'
|
||
| 'feedback'
|
||
| 'about';
|
||
|
||
const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode }[] = [
|
||
{ id: 'general', label: '通用', icon: <SettingsIcon className="w-4 h-4" /> },
|
||
{ id: 'usage', label: '用量统计', icon: <BarChart3 className="w-4 h-4" /> },
|
||
{ id: 'credits', label: '积分详情', icon: <Coins className="w-4 h-4" /> },
|
||
{ id: 'models', label: '模型与 API', icon: <Cpu className="w-4 h-4" /> },
|
||
{ id: 'mcp', label: 'MCP 服务', icon: <Puzzle className="w-4 h-4" /> },
|
||
{ id: 'skills', label: '技能', icon: <Zap className="w-4 h-4" /> },
|
||
{ id: 'im', label: 'IM 频道', icon: <MessageSquare className="w-4 h-4" /> },
|
||
{ id: 'workspace', label: '工作区', icon: <FolderOpen className="w-4 h-4" /> },
|
||
{ id: 'privacy', label: '数据与隐私', icon: <Shield className="w-4 h-4" /> },
|
||
{ id: 'storage', label: '安全存储', icon: <Key className="w-4 h-4" /> },
|
||
{ id: 'saas', label: 'SaaS 平台', icon: <Cloud className="w-4 h-4" /> },
|
||
{ id: 'viking', label: '语义记忆', icon: <Database className="w-4 h-4" /> },
|
||
{ id: 'security', label: '安全状态', icon: <Shield className="w-4 h-4" /> },
|
||
{ id: 'audit', label: '审计日志', icon: <ClipboardList className="w-4 h-4" /> },
|
||
{ id: 'tasks', label: '定时任务', icon: <Clock className="w-4 h-4" /> },
|
||
{ id: 'heartbeat', label: '心跳配置', icon: <Heart className="w-4 h-4" /> },
|
||
{ id: 'feedback', label: '提交反馈', icon: <HelpCircle className="w-4 h-4" /> },
|
||
{ id: 'about', label: '关于', icon: <Info className="w-4 h-4" /> },
|
||
];
|
||
|
||
export function SettingsLayout({ onBack }: SettingsLayoutProps) {
|
||
const [activePage, setActivePage] = useState<SettingsPage>('general');
|
||
const securityStatus = useSecurityStore((s) => s.securityStatus);
|
||
|
||
const renderPage = () => {
|
||
switch (activePage) {
|
||
case 'general': return <General />;
|
||
case 'usage': return <UsageStats />;
|
||
case 'credits': return <Credits />;
|
||
case 'models': return <ModelsAPI />;
|
||
case 'mcp': return <MCPServices />;
|
||
case 'skills': return <Skills />;
|
||
case 'im': return <IMChannels />;
|
||
case 'workspace': return <Workspace />;
|
||
case 'privacy': return <Privacy />;
|
||
case 'storage': return <SecureStorage />;
|
||
case 'saas': return <SaaSSettings />;
|
||
case 'security': return (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h1 className="text-xl font-bold text-gray-900 mb-4">安全状态</h1>
|
||
<SecurityStatus />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-lg font-semibold text-gray-900 mb-4">安全架构详情</h2>
|
||
<SecurityLayersPanel
|
||
status={securityStatus || {
|
||
layers: [],
|
||
enabledCount: 0,
|
||
totalCount: 16,
|
||
securityLevel: 'low',
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
case 'audit': return <AuditLogsPanel />;
|
||
case 'tasks': return (
|
||
<div className="max-w-3xl">
|
||
<h1 className="text-xl font-bold text-gray-900 mb-6">定时任务</h1>
|
||
<div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
|
||
<TaskList />
|
||
</div>
|
||
</div>
|
||
);
|
||
case 'heartbeat': return (
|
||
<div className="max-w-3xl h-full">
|
||
<HeartbeatConfig />
|
||
</div>
|
||
);
|
||
case 'viking': return <VikingPanel />;
|
||
case 'feedback': return <Feedback />;
|
||
case 'about': return <About />;
|
||
default: return <General />;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="h-screen flex bg-f9fafb overflow-hidden text-gray-800 text-sm">
|
||
{/* Left navigation */}
|
||
<aside className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0">
|
||
{/* 返回按钮 */}
|
||
<div className="p-4 border-b border-gray-200">
|
||
<button
|
||
onClick={onBack}
|
||
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 transition-colors"
|
||
>
|
||
<ArrowLeft className="w-4 h-4" />
|
||
<span>返回应用</span>
|
||
</button>
|
||
</div>
|
||
|
||
{/* 导航菜单 */}
|
||
<nav className="flex-1 overflow-y-auto custom-scrollbar py-2 px-3 space-y-1">
|
||
{menuItems.map((item) => (
|
||
<button
|
||
key={item.id}
|
||
onClick={() => setActivePage(item.id)}
|
||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all ${
|
||
activePage === item.id
|
||
? 'bg-gray-200 text-gray-900 font-medium'
|
||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-700'
|
||
}`}
|
||
>
|
||
{item.icon}
|
||
<span>{item.label}</span>
|
||
</button>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
|
||
{/* Main content */}
|
||
<main className="flex-1 overflow-y-auto custom-scrollbar bg-white p-8">
|
||
{renderPage()}
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Simple feedback page (inline)
|
||
function Feedback() {
|
||
const [text, setText] = useState('');
|
||
const [copied, setCopied] = useState(false);
|
||
|
||
const handleCopy = async () => {
|
||
await navigator.clipboard.writeText(text.trim());
|
||
setCopied(true);
|
||
};
|
||
return (
|
||
<div className="max-w-3xl">
|
||
<h1 className="text-xl font-bold text-gray-900 mb-6">提交反馈</h1>
|
||
<div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
|
||
<p className="text-sm text-gray-500 mb-4">当前版本尚未接入在线反馈通道。你可以先复制下面的反馈内容,再连同截图和日志一起发给开发者。</p>
|
||
<textarea
|
||
value={text}
|
||
onChange={(e) => {
|
||
setText(e.target.value);
|
||
if (copied) {
|
||
setCopied(false);
|
||
}
|
||
}}
|
||
placeholder="请尽量详细描述复现步骤、期望结果和实际结果"
|
||
className="w-full h-40 border border-gray-300 rounded-lg p-3 text-sm resize-none focus:outline-none focus:border-orange-400"
|
||
/>
|
||
<button
|
||
onClick={() => { handleCopy().catch(silentErrorHandler('SettingsLayout')); }}
|
||
disabled={!text.trim()}
|
||
className="mt-4 px-6 py-2 bg-orange-500 text-white text-sm rounded-lg hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
{copied ? '已复制' : '复制反馈内容'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|