feat(ui): improve CloneManager with error handling
- Add loading state during clone creation - Add error display for failed operations - Add retry/refresh functionality - Improve gateway-client error handling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { toChatAgent, useChatStore } from '../store/chatStore';
|
||||
import { Bot, Plus, X, Globe, Cat, Search, BarChart2 } from 'lucide-react';
|
||||
import { Bot, Plus, X, Globe, Cat, Search, BarChart2, AlertCircle, RefreshCw } from 'lucide-react';
|
||||
|
||||
interface CloneFormData {
|
||||
name: string;
|
||||
@@ -42,10 +42,12 @@ function createFormFromDraft(quickConfig: {
|
||||
}
|
||||
|
||||
export function CloneManager() {
|
||||
const { clones, loadClones, createClone, deleteClone, connectionState, quickConfig, saveQuickConfig } = useGatewayStore();
|
||||
const { clones, loadClones, createClone, deleteClone, connectionState, quickConfig, saveQuickConfig, error: storeError } = useGatewayStore();
|
||||
const { agents, currentAgent, setCurrentAgent } = useChatStore();
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [form, setForm] = useState<CloneFormData>(createFormFromDraft({}));
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [createError, setCreateError] = useState<string | null>(null);
|
||||
|
||||
const connected = connectionState === 'connected';
|
||||
|
||||
@@ -63,47 +65,64 @@ export function CloneManager() {
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!form.name.trim()) return;
|
||||
const scenarios = form.scenarios
|
||||
? form.scenarios.split(',').map((s) => s.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
await saveQuickConfig({
|
||||
agentName: form.name,
|
||||
agentRole: form.role || undefined,
|
||||
agentNickname: form.nickname || undefined,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir || undefined,
|
||||
userName: form.userName || undefined,
|
||||
userRole: form.userRole || undefined,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
});
|
||||
const clone = await createClone({
|
||||
name: form.name,
|
||||
role: form.role || undefined,
|
||||
nickname: form.nickname || undefined,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir || undefined,
|
||||
userName: form.userName || undefined,
|
||||
userRole: form.userRole || undefined,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
});
|
||||
if (clone) {
|
||||
setCurrentAgent(toChatAgent(clone));
|
||||
setCreateError(null);
|
||||
setIsCreating(true);
|
||||
|
||||
try {
|
||||
const scenarios = form.scenarios
|
||||
? form.scenarios.split(',').map((s) => s.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
|
||||
await saveQuickConfig({
|
||||
agentName: form.name,
|
||||
agentRole: form.role || undefined,
|
||||
agentNickname: form.nickname || undefined,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir || undefined,
|
||||
userName: form.userName || undefined,
|
||||
userRole: form.userRole || undefined,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
});
|
||||
|
||||
const clone = await createClone({
|
||||
name: form.name,
|
||||
role: form.role || undefined,
|
||||
nickname: form.nickname || undefined,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir || undefined,
|
||||
userName: form.userName || undefined,
|
||||
userRole: form.userRole || undefined,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
});
|
||||
|
||||
if (clone) {
|
||||
setCurrentAgent(toChatAgent(clone));
|
||||
setForm(createFormFromDraft({
|
||||
...quickConfig,
|
||||
agentName: form.name,
|
||||
agentRole: form.role,
|
||||
agentNickname: form.nickname,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir,
|
||||
userName: form.userName,
|
||||
userRole: form.userRole,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
}));
|
||||
setShowForm(false);
|
||||
} else {
|
||||
// Show error from store or generic message
|
||||
const errorMsg = storeError || '创建分身失败。请检查 Gateway 连接状态和后端日志。';
|
||||
setCreateError(errorMsg);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setCreateError(`创建失败: ${errorMsg}`);
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
setForm(createFormFromDraft({
|
||||
...quickConfig,
|
||||
agentName: form.name,
|
||||
agentRole: form.role,
|
||||
agentNickname: form.nickname,
|
||||
scenarios,
|
||||
workspaceDir: form.workspaceDir,
|
||||
userName: form.userName,
|
||||
userRole: form.userRole,
|
||||
restrictFiles: form.restrictFiles,
|
||||
privacyOptIn: form.privacyOptIn,
|
||||
}));
|
||||
setShowForm(false);
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
@@ -222,12 +241,28 @@ export function CloneManager() {
|
||||
onChange={e => setForm({ ...form, privacyOptIn: e.target.checked })}
|
||||
/>
|
||||
</label>
|
||||
{createError && (
|
||||
<div className="flex items-center gap-2 text-xs text-red-600 bg-red-50 px-2 py-1.5 rounded">
|
||||
<AlertCircle className="w-3.5 h-3.5 flex-shrink-0" />
|
||||
<span className="flex-1">{createError}</span>
|
||||
<button onClick={() => setCreateError(null)} className="hover:text-red-800">
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
disabled={!form.name.trim()}
|
||||
className="w-full text-xs bg-gray-900 text-white rounded py-1.5 hover:bg-gray-800 disabled:opacity-50"
|
||||
disabled={!form.name.trim() || isCreating}
|
||||
className="w-full text-xs bg-gray-900 text-white rounded py-1.5 hover:bg-gray-800 disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
完成配置
|
||||
{isCreating ? (
|
||||
<>
|
||||
<RefreshCw className="w-3 h-3 animate-spin" />
|
||||
创建中...
|
||||
</>
|
||||
) : (
|
||||
'完成配置'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user