From 4d051c2f25a21f5a55aa99c4de1657473b78a398 Mon Sep 17 00:00:00 2001 From: chiguyong Date: Sat, 13 Jun 2026 02:47:51 +0800 Subject: [PATCH] feat(gui): add transitions, responsive breakpoints and bug fixes (U7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add transitions.css with fade, slide, collapse, scale, stagger-list, skeleton-pulse, pulse-dot animations - Add responsive.css with breakpoints (≥1440px full, 1280-1439px compact, <1280px prompt) - Add small-screen prompt in AgentLayout with DesktopOutlined icon - Fix SPA serving in app.py for Vue build output - Fix TypeScript errors in kb.ts, skills.ts, workflow.ts, FlowCanvas.vue, SideNav.vue - Fix unused imports in ExperienceTimeline, PathOptimizerPanel, PitfallPanel --- src/agentkit/server/app.py | 24 + src/agentkit/server/frontend/src/api/kb.ts | 1 + .../server/frontend/src/api/skills.ts | 30 + .../evolution/ExperienceTimeline.vue | 4 +- .../evolution/PathOptimizerPanel.vue | 2 +- .../src/components/evolution/PitfallPanel.vue | 2 +- .../src/components/layout/AgentLayout.vue | 6 + .../src/components/layout/SideNav.vue | 4 +- .../src/components/workflow/FlowCanvas.vue | 22 +- .../server/frontend/src/stores/workflow.ts | 25 +- .../server/frontend/src/styles/index.ts | 2 + .../server/frontend/src/styles/responsive.css | 89 ++ .../frontend/src/styles/transitions.css | 132 +++ src/agentkit/server/static/index.html | 917 +----------------- 14 files changed, 323 insertions(+), 937 deletions(-) create mode 100644 src/agentkit/server/frontend/src/styles/responsive.css create mode 100644 src/agentkit/server/frontend/src/styles/transitions.css diff --git a/src/agentkit/server/app.py b/src/agentkit/server/app.py index eabfb14..133131b 100644 --- a/src/agentkit/server/app.py +++ b/src/agentkit/server/app.py @@ -680,6 +680,7 @@ def create_app( if gui_mode: from pathlib import Path as _Path from fastapi.responses import HTMLResponse, FileResponse + from fastapi.staticfiles import StaticFiles _static_dir = _Path(__file__).parent / "static" @@ -691,4 +692,27 @@ def create_app( return FileResponse(str(index_path)) return HTMLResponse("

AgentKit GUI not found

", status_code=404) + # SPA fallback: serve index.html for all non-API, non-static routes + @app.get("/{path:path}", response_class=HTMLResponse, include_in_schema=False) + async def spa_fallback(path: str): + """Serve index.html for SPA client-side routing.""" + # Don't intercept API routes + if path.startswith("api/"): + return HTMLResponse("

Not Found

", status_code=404) + # Try to serve a real static file first + file_path = _static_dir / path + if file_path.is_file(): + return FileResponse(str(file_path)) + # Fallback to index.html for SPA routing + index_path = _static_dir / "index.html" + if index_path.exists(): + return FileResponse(str(index_path)) + return HTMLResponse("

Not Found

", status_code=404) + + # Mount static assets last (js, css, images, etc.) + # mount() is checked after route matching, so API routes take priority + assets_dir = _static_dir / "assets" + if assets_dir.exists(): + app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="static-assets") + return app diff --git a/src/agentkit/server/frontend/src/api/kb.ts b/src/agentkit/server/frontend/src/api/kb.ts index 82319d4..1b70373 100644 --- a/src/agentkit/server/frontend/src/api/kb.ts +++ b/src/agentkit/server/frontend/src/api/kb.ts @@ -11,6 +11,7 @@ export interface IKbSource { status: string document_count: number last_synced: string | null + config?: Record } export interface IAddSourceRequest { diff --git a/src/agentkit/server/frontend/src/api/skills.ts b/src/agentkit/server/frontend/src/api/skills.ts index d34794b..d3bdbbb 100644 --- a/src/agentkit/server/frontend/src/api/skills.ts +++ b/src/agentkit/server/frontend/src/api/skills.ts @@ -71,6 +71,36 @@ class SkillsApiClient extends BaseApiClient { return this.request<{ capabilities: ICapabilityInfo[] }>('/capabilities') } + /** Install a skill by name (searches GitHub) or from a source URL */ + async installSkill( + name: string, + source?: string + ): Promise<{ status: string; name: string; path: string }> { + // The install endpoint is on /api/v1/skills/install, not under skill-management + const url = `/api/v1/skills/install` + const resp = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, source }), + }) + if (!resp.ok) { + const err = await resp.json().catch(() => ({ detail: resp.statusText })) + throw new Error(err.detail ?? resp.statusText) + } + return resp.json() + } + + /** Uninstall a skill */ + async uninstallSkill(skillName: string): Promise<{ status: string; name: string }> { + const url = `/api/v1/skills/${encodeURIComponent(skillName)}` + const resp = await fetch(url, { method: 'DELETE' }) + if (!resp.ok) { + const err = await resp.json().catch(() => ({ detail: resp.statusText })) + throw new Error(err.detail ?? resp.statusText) + } + return resp.json() + } + /** Reload a skill */ async reloadSkill( skillName: string diff --git a/src/agentkit/server/frontend/src/components/evolution/ExperienceTimeline.vue b/src/agentkit/server/frontend/src/components/evolution/ExperienceTimeline.vue index 6139cec..b1270cc 100644 --- a/src/agentkit/server/frontend/src/components/evolution/ExperienceTimeline.vue +++ b/src/agentkit/server/frontend/src/components/evolution/ExperienceTimeline.vue @@ -61,11 +61,11 @@ diff --git a/src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue b/src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue index 5d954c9..6a758d4 100644 --- a/src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue +++ b/src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue @@ -21,8 +21,8 @@ () @@ -69,15 +67,13 @@ const emit = defineEmits<{ save: [] execute: [] clear: [] - 'update:nodes': [nodes: any[]] - 'update:edges': [edges: any[]] 'node-select': [nodeId: string | null] 'node-drop': [nodeType: string, position: { x: number; y: number }] }>() const canvasRef = ref() -const nodeTypes = { +const nodeTypes: Record = { skill: markRaw(SkillNode), condition: markRaw(ConditionNode), approval: markRaw(ApprovalNode), @@ -164,17 +160,17 @@ onUnmounted(() => { function onValidate() { // Basic validation - if (props.nodes.length === 0) { + if (store.flowNodes.length === 0) { message.warning('工作流为空,请添加节点') return } // Check for nodes without connections (except the first) - const connectedSources = new Set(props.edges.map((e: any) => e.source)) - const connectedTargets = new Set(props.edges.map((e: any) => e.target)) + const connectedSources = new Set(store.flowEdges.map((e: any) => e.source)) + const connectedTargets = new Set(store.flowEdges.map((e: any) => e.target)) const allConnected = new Set([...connectedSources, ...connectedTargets]) - const orphanNodes = props.nodes.filter((n: any) => !allConnected.has(n.id)) - if (orphanNodes.length > 0 && props.nodes.length > 1) { + const orphanNodes = store.flowNodes.filter((n: any) => !allConnected.has(n.id)) + if (orphanNodes.length > 0 && store.flowNodes.length > 1) { message.warning(`存在未连接的节点: ${orphanNodes.map((n: any) => n.data?.label).join(', ')}`) return } diff --git a/src/agentkit/server/frontend/src/stores/workflow.ts b/src/agentkit/server/frontend/src/stores/workflow.ts index 55a821a..16e5d64 100644 --- a/src/agentkit/server/frontend/src/stores/workflow.ts +++ b/src/agentkit/server/frontend/src/stores/workflow.ts @@ -30,9 +30,9 @@ export const useWorkflowStore = defineStore('workflow', () => { const isLoading = ref(false) const error = ref(null) - // Vue Flow state - const flowNodes = ref[]>([]) - const flowEdges = ref([]) + // Vue Flow state — use any[] to avoid @vue-flow/core deep type recursion with Vue 3.5+ + const flowNodes = ref([]) + const flowEdges = ref([]) const selectedNodeId = ref(null) // Undo/Redo state @@ -51,9 +51,9 @@ export const useWorkflowStore = defineStore('workflow', () => { const executionHistoryTotal = ref(0) // --- Getters --- - const selectedNode = computed | null>(() => { + const selectedNode = computed(() => { if (!selectedNodeId.value) return null - return flowNodes.value.find((n) => n.id === selectedNodeId.value) || null + return flowNodes.value.find((n: any) => n.id === selectedNodeId.value) || null }) const selectedNodeData = computed(() => { @@ -65,12 +65,12 @@ export const useWorkflowStore = defineStore('workflow', () => { // --- Internal mutation methods (no command tracking) --- - function _addNodeDirect(node: Node): void { - flowNodes.value = [...flowNodes.value, node] + function _addNodeDirect(node: any): void { + flowNodes.value.push(node) } - function _removeNodeDirect(nodeId: string): { node: Node; edges: Edge[] } | null { - const node = flowNodes.value.find((n) => n.id === nodeId) + function _removeNodeDirect(nodeId: string): { node: any; edges: any[] } | null { + const node = flowNodes.value.find((n: any) => n.id === nodeId) if (!node) return null const removedEdges = flowEdges.value.filter( (e) => e.source === nodeId || e.target === nodeId @@ -97,11 +97,12 @@ export const useWorkflowStore = defineStore('workflow', () => { } function _updateNodeDataDirect(nodeId: string, data: Partial): void { - const index = flowNodes.value.findIndex((n) => n.id === nodeId) + const index = flowNodes.value.findIndex((n: any) => n.id === nodeId) if (index !== -1) { + const existing = flowNodes.value[index] flowNodes.value[index] = { - ...flowNodes.value[index], - data: { ...flowNodes.value[index].data, ...data }, + ...existing, + data: { ...existing.data, ...data }, } } } diff --git a/src/agentkit/server/frontend/src/styles/index.ts b/src/agentkit/server/frontend/src/styles/index.ts index b063da7..ca09a45 100644 --- a/src/agentkit/server/frontend/src/styles/index.ts +++ b/src/agentkit/server/frontend/src/styles/index.ts @@ -5,4 +5,6 @@ */ import './tokens.css' +import './transitions.css' +import './responsive.css' export { themeConfig } from './theme' diff --git a/src/agentkit/server/frontend/src/styles/responsive.css b/src/agentkit/server/frontend/src/styles/responsive.css new file mode 100644 index 0000000..be282f2 --- /dev/null +++ b/src/agentkit/server/frontend/src/styles/responsive.css @@ -0,0 +1,89 @@ +/** + * Fischer AgentKit Responsive Breakpoints + * + * ≥1440px: Four quadrants fully visible + * 1280-1440px: Bottom-right quadrant auto-collapsed + * <1280px: Prompt to use larger screen + */ + +/* ── Full four-quadrant layout ── */ +@media (min-width: 1440px) { + .agent-layout__body { + display: flex; + } +} + +/* ── Compact: bottom-right quadrant collapsed ── */ +@media (min-width: 1280px) and (max-width: 1439px) { + .agent-layout__body { + display: flex; + } + + /* Auto-collapse bottom-right quadrant via CSS */ + .quadrant-panel--bottom-right-collapsed { + height: auto !important; + } +} + +/* ── Too small: show prompt ── */ +@media (max-width: 1279px) { + .agent-layout__body { + display: none; + } + + .agent-layout__small-screen { + display: flex; + } +} + +/* ── Default: hide small screen prompt ── */ +.agent-layout__small-screen { + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + gap: var(--space-4); + color: var(--text-tertiary); + text-align: center; + padding: var(--space-8); +} + +.agent-layout__small-screen h2 { + font-size: var(--font-lg); + color: var(--text-primary); +} + +.agent-layout__small-screen p { + font-size: var(--font-base); + max-width: 400px; +} + +/* ── Quadrant min-size for readability ── */ +.quadrant-panel { + min-width: var(--quadrant-min-size); + min-height: var(--quadrant-min-size); +} + +/* ── TopNav responsive ── */ +@media (max-width: 768px) { + .top-nav__center { + display: none; + } +} + +/* ── Chat sidebar responsive ── */ +@media (max-width: 1024px) { + .chat-sidebar:not(.chat-sidebar--collapsed) { + width: 200px; + } +} + +/* ── Print: hide interactive elements ── */ +@media print { + .top-nav, + .split-pane__handle, + .quadrant-panel__collapse-btn { + display: none !important; + } +} diff --git a/src/agentkit/server/frontend/src/styles/transitions.css b/src/agentkit/server/frontend/src/styles/transitions.css new file mode 100644 index 0000000..7d8eb20 --- /dev/null +++ b/src/agentkit/server/frontend/src/styles/transitions.css @@ -0,0 +1,132 @@ +/** + * Fischer AgentKit Transition Animations + * + * Unified transition classes for Vue components. + * All durations reference Design Token variables for consistency. + */ + +/* ── Fade ── */ +.fade-enter-active, +.fade-leave-active { + transition: opacity var(--transition-fast); +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +/* ── Slide Up ── */ +.slide-up-enter-active, +.slide-up-leave-active { + transition: transform var(--transition-normal), opacity var(--transition-fast); +} + +.slide-up-enter-from, +.slide-up-leave-to { + transform: translateY(8px); + opacity: 0; +} + +/* ── Slide Down ── */ +.slide-down-enter-active, +.slide-down-leave-active { + transition: transform var(--transition-normal), opacity var(--transition-fast); +} + +.slide-down-enter-from, +.slide-down-leave-to { + transform: translateY(-8px); + opacity: 0; +} + +/* ── Slide Right ── */ +.slide-right-enter-active, +.slide-right-leave-active { + transition: transform var(--transition-normal), opacity var(--transition-fast); +} + +.slide-right-enter-from, +.slide-right-leave-to { + transform: translateX(-8px); + opacity: 0; +} + +/* ── Collapse (height) ── */ +.collapse-enter-active, +.collapse-leave-active { + transition: max-height var(--transition-slow) ease, opacity var(--transition-fast); + overflow: hidden; +} + +.collapse-enter-from, +.collapse-leave-to { + max-height: 0; + opacity: 0; +} + +.collapse-enter-to, +.collapse-leave-from { + max-height: 500px; + opacity: 1; +} + +/* ── Scale ── */ +.scale-enter-active, +.scale-leave-active { + transition: transform var(--transition-fast), opacity var(--transition-fast); +} + +.scale-enter-from, +.scale-leave-to { + transform: scale(0.95); + opacity: 0; +} + +/* ── Stagger list items ── */ +.stagger-list-enter-active { + transition: transform var(--transition-normal), opacity var(--transition-fast); +} + +.stagger-list-leave-active { + transition: transform var(--transition-fast), opacity var(--transition-fast); +} + +.stagger-list-enter-from, +.stagger-list-leave-to { + transform: translateY(8px); + opacity: 0; +} + +.stagger-list-move { + transition: transform var(--transition-normal); +} + +/* ── Skeleton pulse ── */ +@keyframes skeleton-pulse { + 0% { opacity: 1; } + 50% { opacity: 0.4; } + 100% { opacity: 1; } +} + +.skeleton-loading { + animation: skeleton-pulse 1.5s ease-in-out infinite; + background: linear-gradient( + 90deg, + var(--bg-tertiary) 25%, + var(--border-color) 50%, + var(--bg-tertiary) 75% + ); + background-size: 200% 100%; + border-radius: var(--radius-sm); +} + +/* ── Pulse dot (running status) ── */ +@keyframes pulse-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +.pulse-dot { + animation: pulse-dot 1.5s ease-in-out infinite; +} diff --git a/src/agentkit/server/static/index.html b/src/agentkit/server/static/index.html index cf42de2..1cfb518 100644 --- a/src/agentkit/server/static/index.html +++ b/src/agentkit/server/static/index.html @@ -1,909 +1,14 @@ - - - -AgentKit - - - - - - - - -
- - - - - -
-
- - AgentKit - 未连接 - -
-
-
-
🤖
-

欢迎使用 AgentKit

-

开始一段新对话,或从侧边栏选择已有会话。

-
-
-
-
- - -
-
-
- - - -
- - - + + + + + Fischer AgentKit + + + + +
+