openclaw-agent-dashboard 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +55 -321
  2. package/frontend-dist/assets/index-B7XqKAxm.css +1 -0
  3. package/frontend-dist/assets/index-CxJaSYyo.js +24 -0
  4. package/{frontend → frontend-dist}/index.html +2 -1
  5. package/{plugin/openclaw.plugin.json → openclaw.plugin.json} +2 -2
  6. package/package.json +21 -13
  7. package/.github/workflows/release.yml +0 -56
  8. package/VERSION_DISPLAY_delivery.md +0 -242
  9. package/VERSION_DISPLAY_implementation_summary.md +0 -315
  10. package/design_manifest.md +0 -100
  11. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +0 -132
  12. package/docs/MAINTAINER_RELEASE_WORKFLOW.md +0 -211
  13. package/docs/Openclaw-Agent-Dashboard/345/217/221/345/270/203/344/270/216/346/233/264/346/226/260.md +0 -147
  14. package/docs/RELEASE-LATEST.md +0 -189
  15. package/docs/RELEASE-MODEL-CONFIG.md +0 -95
  16. package/docs/WINDOWS_INSTALL_TROUBLESHOOTING.md +0 -171
  17. package/docs/design/.gitkeep +0 -0
  18. package/docs/design/VERSION_DISPLAY_design.md +0 -1236
  19. package/docs/release-guide.md +0 -259
  20. package/docs/release-operations-manual.md +0 -167
  21. package/docs/reviews/.gitkeep +0 -0
  22. package/docs/reviews/approval_history.json +0 -14
  23. package/docs/reviews/cr_VERSION_DISPLAY.md +0 -397
  24. package/docs/reviews/traceability_manifest.json +0 -279
  25. package/docs/specs/VERSION_DISPLAY_spec.md +0 -371
  26. package/docs/specs/tr3-install-system.md +0 -580
  27. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  28. package/frontend/package-lock.json +0 -1240
  29. package/frontend/package.json +0 -19
  30. package/frontend/src/App.vue +0 -355
  31. package/frontend/src/components/AgentCard.vue +0 -796
  32. package/frontend/src/components/AgentConfigPanel.vue +0 -539
  33. package/frontend/src/components/AgentDetailPanel.vue +0 -738
  34. package/frontend/src/components/ErrorAnalysisView.vue +0 -546
  35. package/frontend/src/components/ErrorCenterPanel.vue +0 -844
  36. package/frontend/src/components/PerformanceMonitor.vue +0 -515
  37. package/frontend/src/components/SettingsPanel.vue +0 -236
  38. package/frontend/src/components/TokenAnalysisPanel.vue +0 -683
  39. package/frontend/src/components/chain/ChainEdge.vue +0 -85
  40. package/frontend/src/components/chain/ChainNode.vue +0 -166
  41. package/frontend/src/components/chain/TaskChainView.vue +0 -425
  42. package/frontend/src/components/chain/index.ts +0 -3
  43. package/frontend/src/components/chain/types.ts +0 -70
  44. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +0 -1032
  45. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +0 -113
  46. package/frontend/src/components/common/VersionDisplay.vue +0 -187
  47. package/frontend/src/components/performance/PerformancePanel.vue +0 -119
  48. package/frontend/src/components/performance/PerformanceSection.vue +0 -1137
  49. package/frontend/src/components/tasks/TaskStatusSection.vue +0 -973
  50. package/frontend/src/components/timeline/TimelineConnector.vue +0 -31
  51. package/frontend/src/components/timeline/TimelineRound.vue +0 -135
  52. package/frontend/src/components/timeline/TimelineStep.vue +0 -691
  53. package/frontend/src/components/timeline/TimelineToolLink.vue +0 -109
  54. package/frontend/src/components/timeline/TimelineView.vue +0 -540
  55. package/frontend/src/components/timeline/index.ts +0 -5
  56. package/frontend/src/components/timeline/types.ts +0 -120
  57. package/frontend/src/composables/index.ts +0 -7
  58. package/frontend/src/composables/useDebounce.ts +0 -48
  59. package/frontend/src/composables/useRealtime.ts +0 -52
  60. package/frontend/src/composables/useState.ts +0 -52
  61. package/frontend/src/composables/useThrottle.ts +0 -46
  62. package/frontend/src/composables/useVirtualScroll.ts +0 -106
  63. package/frontend/src/main.ts +0 -4
  64. package/frontend/src/managers/EventDispatcher.ts +0 -127
  65. package/frontend/src/managers/RealtimeDataManager.ts +0 -302
  66. package/frontend/src/managers/StateManager.ts +0 -128
  67. package/frontend/src/managers/index.ts +0 -5
  68. package/frontend/src/types/collaboration.ts +0 -135
  69. package/frontend/src/types/index.ts +0 -20
  70. package/frontend/src/types/performance.ts +0 -105
  71. package/frontend/src/types/task.ts +0 -38
  72. package/frontend/vite.config.ts +0 -18
  73. package/legacy_code_anatomy.md +0 -518
  74. package/plugin/README.md +0 -99
  75. package/plugin/config.json.example +0 -1
  76. package/plugin/package.json +0 -26
  77. package/scripts/build-plugin.js +0 -81
  78. package/scripts/bundle.sh +0 -62
  79. package/scripts/install-plugin.sh +0 -162
  80. package/scripts/install-python-deps.sh +0 -226
  81. package/scripts/install.js +0 -684
  82. package/scripts/install.sh +0 -367
  83. package/scripts/lib/common.sh +0 -137
  84. package/scripts/release-pack.sh +0 -110
  85. package/scripts/start.js +0 -50
  86. package/scripts/test_available_models.py +0 -284
  87. package/scripts/test_version_display.sh +0 -128
  88. package/scripts/test_websocket_ping.py +0 -44
  89. package/session_registry.json +0 -58
  90. package/tests/.gitkeep +0 -0
  91. package/tests/qa_regression_report.md +0 -359
  92. package/tests/qa_version_display_report.md +0 -598
  93. /package/{src/backend → dashboard}/agents.py +0 -0
  94. /package/{src/backend → dashboard}/api/__init__.py +0 -0
  95. /package/{src/backend → dashboard}/api/agent_config_api.py +0 -0
  96. /package/{src/backend → dashboard}/api/agents.py +0 -0
  97. /package/{src/backend → dashboard}/api/agents_config.py +0 -0
  98. /package/{src/backend → dashboard}/api/chains.py +0 -0
  99. /package/{src/backend → dashboard}/api/collaboration.py +0 -0
  100. /package/{src/backend → dashboard}/api/debug_paths.py +0 -0
  101. /package/{src/backend → dashboard}/api/error_analysis.py +0 -0
  102. /package/{src/backend → dashboard}/api/errors.py +0 -0
  103. /package/{src/backend → dashboard}/api/performance.py +0 -0
  104. /package/{src/backend → dashboard}/api/subagents.py +0 -0
  105. /package/{src/backend → dashboard}/api/timeline.py +0 -0
  106. /package/{src/backend → dashboard}/api/version.py +0 -0
  107. /package/{src/backend → dashboard}/api/websocket.py +0 -0
  108. /package/{src/backend → dashboard}/collaboration.py +0 -0
  109. /package/{src/backend → dashboard}/data/__init__.py +0 -0
  110. /package/{src/backend → dashboard}/data/agent_config_manager.py +0 -0
  111. /package/{src/backend → dashboard}/data/chain_reader.py +0 -0
  112. /package/{src/backend → dashboard}/data/config_reader.py +0 -0
  113. /package/{src/backend → dashboard}/data/error_analyzer.py +0 -0
  114. /package/{src/backend → dashboard}/data/session_reader.py +0 -0
  115. /package/{src/backend → dashboard}/data/subagent_reader.py +0 -0
  116. /package/{src/backend → dashboard}/data/task_history.py +0 -0
  117. /package/{src/backend → dashboard}/data/timeline_reader.py +0 -0
  118. /package/{src/backend → dashboard}/data/version_info_reader.py +0 -0
  119. /package/{src/backend → dashboard}/errors.py +0 -0
  120. /package/{src/backend → dashboard}/main.py +0 -0
  121. /package/{src/backend → dashboard}/mechanism_reader.py +0 -0
  122. /package/{src/backend → dashboard}/mechanisms.py +0 -0
  123. /package/{src/backend → dashboard}/performance.py +0 -0
  124. /package/{src/backend → dashboard}/requirements.txt +0 -0
  125. /package/{src/backend → dashboard}/session_reader.py +0 -0
  126. /package/{src/backend → dashboard}/status/__init__.py +0 -0
  127. /package/{src/backend → dashboard}/status/change_tracker.py +0 -0
  128. /package/{src/backend → dashboard}/status/error_detector.py +0 -0
  129. /package/{src/backend → dashboard}/status/status_cache.py +0 -0
  130. /package/{src/backend → dashboard}/status/status_calculator.py +0 -0
  131. /package/{src/backend → dashboard}/status_calculator.py +0 -0
  132. /package/{src/backend → dashboard}/subagent_reader.py +0 -0
  133. /package/{src/backend → dashboard}/watchers/__init__.py +0 -0
  134. /package/{src/backend → dashboard}/watchers/file_watcher.py +0 -0
  135. /package/{plugin/index.js → index.js} +0 -0
@@ -1,85 +0,0 @@
1
- <template>
2
- <div class="chain-edge" :class="edgeClass">
3
- <svg width="60" height="20" viewBox="0 0 60 20">
4
- <defs>
5
- <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
6
- <polygon points="0 0, 10 3.5, 0 7" :fill="arrowColor" />
7
- </marker>
8
- </defs>
9
- <line
10
- x1="0"
11
- y1="10"
12
- x2="50"
13
- y2="10"
14
- :stroke="lineColor"
15
- stroke-width="2"
16
- :stroke-dasharray="dashArray"
17
- marker-end="url(#arrowhead)"
18
- />
19
- </svg>
20
- </div>
21
- </template>
22
-
23
- <script setup lang="ts">
24
- import { computed } from 'vue'
25
- import type { ChainNodeStatus } from './types'
26
-
27
- const props = defineProps<{
28
- fromStatus?: ChainNodeStatus
29
- toStatus?: ChainNodeStatus
30
- }>()
31
-
32
- const lineColor = computed(() => {
33
- if (props.toStatus === 'running') return '#3b82f6'
34
- if (props.toStatus === 'completed') return '#22c55e'
35
- if (props.toStatus === 'error') return '#ef4444'
36
- return '#9ca3af'
37
- })
38
-
39
- const arrowColor = computed(() => {
40
- return lineColor.value
41
- })
42
-
43
- const dashArray = computed(() => {
44
- if (props.toStatus === 'pending') return '4 4'
45
- return 'none'
46
- })
47
-
48
- const edgeClass = computed(() => {
49
- return {
50
- 'edge-running': props.toStatus === 'running',
51
- 'edge-completed': props.toStatus === 'completed',
52
- 'edge-error': props.toStatus === 'error',
53
- 'edge-pending': props.toStatus === 'pending'
54
- }
55
- })
56
- </script>
57
-
58
- <style scoped>
59
- .chain-edge {
60
- display: flex;
61
- align-items: center;
62
- margin: 0 4px;
63
- }
64
-
65
- .edge-running svg line {
66
- animation: pulse 1.5s infinite;
67
- }
68
-
69
- .edge-completed {
70
- opacity: 1;
71
- }
72
-
73
- .edge-pending {
74
- opacity: 0.5;
75
- }
76
-
77
- .edge-error {
78
- opacity: 0.8;
79
- }
80
-
81
- @keyframes pulse {
82
- 0%, 100% { opacity: 1; }
83
- 50% { opacity: 0.5; }
84
- }
85
- </style>
@@ -1,166 +0,0 @@
1
- <template>
2
- <div
3
- class="chain-node"
4
- :class="[`status-${node.status}`, { selected: isSelected }]"
5
- @click="$emit('click')"
6
- >
7
- <!-- 状态图标 -->
8
- <div class="node-icon">{{ statusIcon }}</div>
9
-
10
- <!-- Agent 信息 -->
11
- <div class="node-info">
12
- <div class="node-name">{{ node.agentName }}</div>
13
- <div class="node-role">{{ node.role }}</div>
14
- </div>
15
-
16
- <!-- 状态标签 -->
17
- <div class="node-status">{{ statusLabel }}</div>
18
-
19
- <!-- 时间信息 -->
20
- <div class="node-time" v-if="node.startedAt">
21
- {{ formatDuration(node.duration) }}
22
- </div>
23
-
24
- <!-- 进度指示器 -->
25
- <div class="node-progress" v-if="node.status === 'running'">
26
- <div class="progress-spinner"></div>
27
- </div>
28
- </div>
29
- </template>
30
-
31
- <script setup lang="ts">
32
- import { computed } from 'vue'
33
- import type { ChainNode } from './types'
34
- import { nodeStatusConfig } from './types'
35
-
36
- const props = defineProps<{
37
- node: ChainNode
38
- isSelected?: boolean
39
- }>()
40
-
41
- defineEmits<{
42
- click: []
43
- }>()
44
-
45
- const statusIcon = computed(() => {
46
- return nodeStatusConfig[props.node.status]?.icon || '📄'
47
- })
48
-
49
- const statusLabel = computed(() => {
50
- return nodeStatusConfig[props.node.status]?.label || props.node.status
51
- })
52
-
53
- function formatDuration(ms: number | undefined): string {
54
- if (!ms) return ''
55
- if (ms < 1000) return `${ms}ms`
56
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
57
- return `${(ms / 60000).toFixed(1)}min`
58
- }
59
- </script>
60
-
61
- <style scoped>
62
- .chain-node {
63
- display: flex;
64
- flex-direction: column;
65
- align-items: center;
66
- padding: 12px 16px;
67
- min-width: 120px;
68
- background: #fff;
69
- border-radius: 8px;
70
- border: 2px solid #e5e7eb;
71
- cursor: pointer;
72
- transition: all 0.2s;
73
- }
74
-
75
- .chain-node:hover {
76
- transform: translateY(-2px);
77
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
78
- }
79
-
80
- .chain-node.selected {
81
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.3);
82
- }
83
-
84
- .chain-node.status-pending {
85
- border-color: #9ca3af;
86
- background: #f9fafb;
87
- }
88
-
89
- .chain-node.status-running {
90
- border-color: #3b82f6;
91
- background: #eff6ff;
92
- animation: pulse 2s infinite;
93
- }
94
-
95
- .chain-node.status-completed {
96
- border-color: #22c55e;
97
- background: #f0fdf4;
98
- }
99
-
100
- .chain-node.status-error {
101
- border-color: #ef4444;
102
- background: #fef2f2;
103
- }
104
-
105
- @keyframes pulse {
106
- 0%, 100% { opacity: 1; }
107
- 50% { opacity: 0.5; }
108
- 100% { opacity: 1; }
109
- }
110
-
111
- .node-icon {
112
- font-size: 24px;
113
- margin-bottom: 8px;
114
- }
115
-
116
- .node-info {
117
- text-align: center;
118
- }
119
-
120
- .node-name {
121
- font-size: 13px;
122
- font-weight: 600;
123
- color: #374151;
124
- margin-bottom: 4px;
125
- }
126
-
127
- .node-role {
128
- font-size: 11px;
129
- color: #6b7280;
130
- }
131
-
132
- .node-status {
133
- font-size: 11px;
134
- padding: 2px 6px;
135
- border-radius: 4px;
136
- margin-top: 8px;
137
- }
138
-
139
- .status-pending .node-status { background: #f3f4f6; color: #6b7280; }
140
- .status-running .node-status { background: #dbeafe; color: #1e40af; }
141
- .status-completed .node-status { background: #d1fae5; color: #065f46; }
142
- .status-error .node-status { background: #fee2e2; color: #991b1b; }
143
-
144
- .node-time {
145
- font-size: 11px;
146
- color: #9ca3af;
147
- margin-top: 8px;
148
- }
149
-
150
- .node-progress {
151
- margin-top: 8px;
152
- }
153
-
154
- .progress-spinner {
155
- width: 16px;
156
- height: 16px;
157
- border: 2px solid #e5e7eb;
158
- border-top-color: #3b82f6;
159
- border-radius: 50%;
160
- animation: spin 1s linear infinite;
161
- }
162
-
163
- @keyframes spin {
164
- to { transform: rotate(360deg); }
165
- }
166
- </style>
@@ -1,425 +0,0 @@
1
- <template>
2
- <div class="chain-view">
3
- <!-- 头部 -->
4
- <div class="chain-header">
5
- <div class="header-left">
6
- <span class="title">🔗 任务执行链路</span>
7
- <span class="project-info" v-if="activeChain">
8
- {{ activeChain.projectId || '当前任务' }}
9
- </span>
10
- </div>
11
- <div class="header-right">
12
- <span class="status-badge" :class="`status-${activeChain?.status || 'empty'}`">
13
- {{ statusLabel }}
14
- </span>
15
- <button class="refresh-btn" @click="refresh" :disabled="loading">
16
- {{ loading ? '加载中...' : '🔄 刷新' }}
17
- </button>
18
- </div>
19
- </div>
20
-
21
- <!-- 加载状态 -->
22
- <div v-if="loading && !activeChain" class="loading-state">
23
- <div class="spinner"></div>
24
- <span>加载链路数据...</span>
25
- </div>
26
-
27
- <!-- 空状态 -->
28
- <div v-else-if="!activeChain && chains.length === 0" class="empty-state">
29
- <span class="empty-icon">🔗</span>
30
- <span>暂无任务链路</span>
31
- <div class="empty-hint">当 Main Agent 派发任务给子 Agent 时,这里会显示执行链路</div>
32
- </div>
33
-
34
- <!-- 链路内容 -->
35
- <div v-else class="chain-content">
36
- <!-- 根任务信息 -->
37
- <div class="root-task" v-if="activeChain">
38
- <div class="task-label">根任务</div>
39
- <div class="task-text">{{ activeChain.rootTask }}</div>
40
- <div class="task-meta">
41
- <span v-if="activeChain.startedAt">开始: {{ formatTime(activeChain.startedAt) }}</span>
42
- </div>
43
- </div>
44
-
45
- <!-- 链路图 -->
46
- <div class="chain-diagram" v-if="activeChain">
47
- <div class="diagram-container">
48
- <template v-for="(node, index) in sortedNodes" :key="node.agentId">
49
- <!-- 连接线 -->
50
- <ChainEdge
51
- v-if="index > 0"
52
- :from-status="sortedNodes[index - 1]?.status"
53
- :to-status="node.status"
54
- />
55
- <!-- 节点 -->
56
- <ChainNode
57
- :node="node"
58
- :is-selected="selectedNode?.agentId === node.agentId"
59
- @click="selectNode(node)"
60
- />
61
- </template>
62
- </div>
63
- </div>
64
-
65
- <!-- 进度条 -->
66
- <div class="chain-progress" v-if="activeChain">
67
- <div class="progress-bar">
68
- <div class="progress-fill" :style="{ width: `${activeChain.progress * 100}%` }"></div>
69
- </div>
70
- <div class="progress-text">
71
- {{ Math.round(activeChain.progress * 100) }}% ({{ activeChain.completedNodes }}/{{ activeChain.totalNodes }} 完成)
72
- </div>
73
- </div>
74
-
75
- <!-- 节点详情 -->
76
- <div class="node-detail" v-if="selectedNode">
77
- <div class="detail-header">
78
- <span class="detail-title">{{ selectedNode.agentName }}</span>
79
- <span class="detail-status" :class="`status-${selectedNode.status}`">
80
- {{ nodeStatusConfig[selectedNode.status]?.label || selectedNode.status }}
81
- </span>
82
- </div>
83
-
84
- <div class="detail-content">
85
- <!-- 任务信息 -->
86
- <div class="detail-section" v-if="selectedNode.task">
87
- <div class="section-label">任务</div>
88
- <div class="section-value">{{ selectedNode.task }}</div>
89
- </div>
90
-
91
- <!-- 时间信息 -->
92
- <div class="detail-row">
93
- <div class="detail-item">
94
- <span class="item-label">开始时间</span>
95
- <span class="item-value">{{ selectedNode.startedAt ? formatTime(selectedNode.startedAt) : '-' }}</span>
96
- </div>
97
- <div class="detail-item">
98
- <span class="item-label">耗时</span>
99
- <span class="item-value">{{ formatDuration(selectedNode.duration) }}</span>
100
- </div>
101
- </div>
102
-
103
- <!-- Token 使用 -->
104
- <div class="detail-row" v-if="selectedNode.tokenUsage">
105
- <div class="detail-item">
106
- <span class="item-label">Token 输入</span>
107
- <span class="item-value">{{ formatNumber(selectedNode.tokenUsage.input) }}</span>
108
- </div>
109
- <div class="detail-item">
110
- <span class="item-label">Token 输出</span>
111
- <span class="item-value">{{ formatNumber(selectedNode.tokenUsage.output) }}</span>
112
- </div>
113
- </div>
114
-
115
- <!-- 工具调用 -->
116
- <div class="detail-section" v-if="selectedNode.toolCallCount > 0">
117
- <div class="section-label">工具调用</div>
118
- <div class="section-value">{{ selectedNode.toolCallCount }} 次</div>
119
- </div>
120
-
121
- <!-- 产出物 -->
122
- <div class="detail-section" v-if="selectedNode.artifacts && selectedNode.artifacts.length > 0">
123
- <div class="section-label">产出物</div>
124
- <div class="artifacts-list">
125
- <div v-for="artifact in selectedNode.artifacts" :key="artifact" class="artifact-item">
126
- 📄 {{ artifact }}
127
- </div>
128
- </div>
129
- </div>
130
- </div>
131
- </div>
132
- </div>
133
- </div>
134
- </template>
135
-
136
- <script setup lang="ts">
137
- import { ref, computed, onMounted, onUnmounted } from 'vue'
138
- import type { TaskChain, ChainNode as ChainNodeType, TaskChainResponse, ChainNodeStatus } from './types'
139
- import { nodeStatusConfig } from './types'
140
- import ChainNode from './ChainNode.vue'
141
- import ChainEdge from './ChainEdge.vue'
142
-
143
- const props = defineProps<{
144
- autoRefresh?: boolean
145
- refreshInterval?: number
146
- }>()
147
-
148
- const chains = ref<TaskChain[]>([])
149
- const activeChain = ref<TaskChain | null>(null)
150
- const selectedNode = ref<ChainNodeType | null>(null)
151
- const loading = ref(false)
152
-
153
- const statusLabel = computed(() => {
154
- const labels: Record<string, string> = {
155
- running: '🔄 进行中',
156
- completed: '✅ 已完成',
157
- error: '❌ 出错',
158
- empty: '空'
159
- }
160
- return labels[activeChain.value?.status || 'empty'] || '未知'
161
- })
162
-
163
- const sortedNodes = computed(() => {
164
- if (!activeChain.value?.nodes) return []
165
-
166
- // 按照 main -> analyst -> architect -> devops 顺序排序
167
- const roleOrder = ['main', 'analyst', 'architect', 'devops']
168
- return [...activeChain.value.nodes].sort((a, b) => {
169
- const aIndex = roleOrder.findIndex(r => a.role.includes(r))
170
- const bIndex = roleOrder.findIndex(r => b.role.includes(r))
171
- return aIndex - bIndex
172
- })
173
- })
174
-
175
- async function refresh() {
176
- loading.value = true
177
- try {
178
- const res = await fetch('/api/chains?limit=10')
179
- if (res.ok) {
180
- const data: TaskChainResponse = await res.json()
181
- chains.value = data.chains || []
182
- activeChain.value = data.activeChain || null
183
- }
184
- } catch (e) {
185
- console.error('Chain load error:', e)
186
- } finally {
187
- loading.value = false
188
- }
189
- }
190
-
191
- function selectNode(node: ChainNodeType) {
192
- selectedNode.value = selectedNode.value?.agentId === node.agentId ? null : node
193
- }
194
-
195
- function formatTime(ts: number): string {
196
- if (!ts) return '-'
197
- const d = new Date(ts)
198
- return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
199
- }
200
-
201
- function formatDuration(ms: number | undefined): string {
202
- if (!ms) return '-'
203
- if (ms < 1000) return `${ms}ms`
204
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
205
- return `${(ms / 60000).toFixed(1)}min`
206
- }
207
-
208
- function formatNumber(n: number | undefined): string {
209
- if (!n) return '0'
210
- if (n >= 1000) return `${(n / 1000).toFixed(1)}k`
211
- return String(n)
212
- }
213
-
214
- let refreshTimer: ReturnType<typeof setInterval> | null = null
215
-
216
- onMounted(() => {
217
- refresh()
218
- if (props.autoRefresh && props.refreshInterval) {
219
- refreshTimer = setInterval(refresh, props.refreshInterval * 1000)
220
- }
221
- })
222
-
223
- onUnmounted(() => {
224
- if (refreshTimer) {
225
- clearInterval(refreshTimer)
226
- refreshTimer = null
227
- }
228
- })
229
- </script>
230
-
231
- <style scoped>
232
- .chain-view {
233
- background: #fff;
234
- border-radius: 8px;
235
- border: 1px solid #e5e7eb;
236
- overflow: hidden;
237
- }
238
-
239
- .chain-header {
240
- display: flex;
241
- justify-content: space-between;
242
- align-items: center;
243
- padding: 12px 16px;
244
- background: #f9fafb;
245
- border-bottom: 1px solid #e5e7eb;
246
- }
247
-
248
- .header-left {
249
- display: flex;
250
- align-items: center;
251
- gap: 12px;
252
- }
253
-
254
- .title {
255
- font-weight: 600;
256
- font-size: 14px;
257
- color: #374151;
258
- }
259
-
260
- .project-info {
261
- font-size: 13px;
262
- color: #6b7280;
263
- }
264
-
265
- .header-right {
266
- display: flex;
267
- align-items: center;
268
- gap: 12px;
269
- }
270
-
271
- .status-badge {
272
- font-size: 12px;
273
- padding: 4px 8px;
274
- border-radius: 4px;
275
- }
276
-
277
- .status-badge.status-running { background: #fef3c7; color: #92400e; }
278
- .status-badge.status-completed { background: #d1fae5; color: #065f46; }
279
- .status-badge.status-error { background: #fee2e2; color: #991b1b; }
280
- .status-badge.status-empty { background: #f3f4f6; color: #6b7280; }
281
-
282
- .refresh-btn {
283
- font-size: 12px;
284
- padding: 4px 10px;
285
- border: 1px solid #e5e7eb;
286
- border-radius: 4px;
287
- background: #fff;
288
- cursor: pointer;
289
- color: #374151;
290
- }
291
-
292
- .refresh-btn:hover:not(:disabled) { background: #f3f4f6; }
293
- .refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }
294
-
295
- .loading-state, .empty-state {
296
- display: flex;
297
- flex-direction: column;
298
- align-items: center;
299
- justify-content: center;
300
- padding: 48px;
301
- color: #6b7280;
302
- gap: 12px;
303
- }
304
-
305
- .spinner {
306
- width: 24px;
307
- height: 24px;
308
- border: 3px solid #e5e7eb;
309
- border-top-color: #3b82f6;
310
- border-radius: 50%;
311
- animation: spin 1s linear infinite;
312
- }
313
-
314
- @keyframes spin { to { transform: rotate(360deg); } }
315
-
316
- .empty-icon { font-size: 32px; }
317
- .empty-hint {
318
- margin-top: 8px;
319
- font-size: 12px;
320
- color: #9ca3af;
321
- text-align: center;
322
- max-width: 280px;
323
- }
324
-
325
- .chain-content { padding: 16px; }
326
-
327
- .root-task {
328
- padding: 12px;
329
- background: #f9fafb;
330
- border-radius: 6px;
331
- margin-bottom: 16px;
332
- }
333
-
334
- .task-label {
335
- font-size: 11px;
336
- color: #9ca3af;
337
- margin-bottom: 4px;
338
- }
339
-
340
- .task-text {
341
- font-size: 14px;
342
- color: #374151;
343
- font-weight: 500;
344
- }
345
-
346
- .task-meta {
347
- margin-top: 8px;
348
- font-size: 12px;
349
- color: #6b7280;
350
- }
351
-
352
- .chain-diagram {
353
- margin: 20px 0;
354
- }
355
-
356
- .diagram-container {
357
- display: flex;
358
- align-items: center;
359
- justify-content: center;
360
- gap: 0;
361
- flex-wrap: wrap;
362
- }
363
-
364
- .chain-progress {
365
- margin-top: 16px;
366
- }
367
-
368
- .progress-bar {
369
- height: 8px;
370
- background: #e5e7eb;
371
- border-radius: 4px;
372
- overflow: hidden;
373
- }
374
-
375
- .progress-fill {
376
- height: 100%;
377
- background: linear-gradient(90deg, #22c55e, #3b82f6);
378
- transition: width 0.3s ease;
379
- }
380
-
381
- .progress-text {
382
- margin-top: 8px;
383
- font-size: 12px;
384
- color: #6b7280;
385
- text-align: center;
386
- }
387
-
388
- .node-detail {
389
- margin-top: 16px;
390
- padding: 16px;
391
- background: #f9fafb;
392
- border-radius: 8px;
393
- border: 1px solid #e5e7eb;
394
- }
395
-
396
- .detail-header {
397
- display: flex;
398
- justify-content: space-between;
399
- align-items: center;
400
- margin-bottom: 12px;
401
- }
402
-
403
- .detail-title {
404
- font-size: 14px;
405
- font-weight: 600;
406
- color: #374151;
407
- }
408
-
409
- .detail-status {
410
- font-size: 12px;
411
- padding: 4px 8px;
412
- border-radius: 4px;
413
- }
414
-
415
- .detail-content { display: flex; flex-direction: column; gap: 12px; }
416
- .detail-section { }
417
- .section-label { font-size: 11px; color: #9ca3af; margin-bottom: 4px; }
418
- .section-value { font-size: 13px; color: #374151; }
419
- .detail-row { display: flex; gap: 24px; }
420
- .detail-item { display: flex; flex-direction: column; }
421
- .item-label { font-size: 11px; color: #9ca3af; }
422
- .item-value { font-size: 13px; color: #374151; font-weight: 500; }
423
- .artifacts-list { display: flex; flex-direction: column; gap: 4px; }
424
- .artifact-item { font-size: 12px; color: #6b7280; padding: 4px 8px; background: #fff; border-radius: 4px; }
425
- </style>
@@ -1,3 +0,0 @@
1
- // Chain 组件导出
2
- export { default as TaskChainView } from './TaskChainView.vue'
3
- export * from './types'