feat(ui): 集成 Provider 管理到 web 和 desktop 应用

- 新增 ProviderEditor 组件用于编辑提供商配置
- ProvidersPanel 添加编辑按钮集成 ProviderEditor
- ChatPage 添加 onOpenProviders 工具栏按钮
- web/desktop App.tsx 集成 ProvidersPanel 面板
This commit is contained in:
2025-12-13 02:01:09 +08:00
parent 6ec6fe2f9f
commit 26e8646518
7 changed files with 627 additions and 4 deletions
@@ -20,6 +20,7 @@ import {
Key,
Globe,
Zap,
Settings,
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { toast } from 'sonner';
@@ -41,6 +42,7 @@ import {
type ModelInfo,
type CustomProviderDefinition,
} from '../api/client.js';
import { ProviderEditor } from './ProviderEditor.js';
interface ProvidersPanelProps {
onClose: () => void;
@@ -63,6 +65,7 @@ export function ProvidersPanel({ onClose, responsive = false }: ProvidersPanelPr
// Editor state
const [showAddProvider, setShowAddProvider] = useState(false);
const [showAddModel, setShowAddModel] = useState<string | null>(null);
const [editingProviderId, setEditingProviderId] = useState<string | null>(null);
const [newProvider, setNewProvider] = useState<Partial<CustomProviderDefinition>>({});
const [newModel, setNewModel] = useState<Partial<ModelInfo>>({});
@@ -356,6 +359,17 @@ export function ProvidersPanel({ onClose, responsive = false }: ProvidersPanelPr
)}
</Button>
{/* Edit Button */}
<Button
variant="ghost"
size="sm"
onClick={() => setEditingProviderId(provider.id)}
className="text-gray-400 hover:text-gray-300"
title="Configure"
>
<Settings size={14} />
</Button>
{/* Delete (only for custom) */}
{!provider.builtin && (
<Button
@@ -744,6 +758,25 @@ export function ProvidersPanel({ onClose, responsive = false }: ProvidersPanelPr
</motion.div>
)}
</AnimatePresence>
{/* Provider Editor */}
{editingProviderId && (
<ProviderEditor
providerId={editingProviderId}
onClose={() => setEditingProviderId(null)}
onSave={() => {
setEditingProviderId(null);
// Clear cached details and reload
setProviderDetails((prev) => {
const newDetails = { ...prev };
delete newDetails[editingProviderId];
return newDetails;
});
loadProviders();
}}
responsive={responsive}
/>
)}
</motion.div>
</AnimatePresence>
);