sillyspec 3.20.3 → 3.20.4
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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -17
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
- package/.husky/pre-push +13 -0
- package/CLAUDE.md +18 -0
- package/README.md +198 -186
- package/SKILL.md +90 -87
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -0
- package/docs/plan-execute-contract.md +123 -0
- package/docs/platform-scan-protocol.md +298 -0
- package/docs/revision-mode.md +115 -0
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
- package/docs/sillyspec/file-lifecycle.md +125 -0
- package/docs/workflow-contract-regression.md +106 -0
- package/docs/worktree-isolation.md +252 -0
- package/package.json +40 -34
- package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -508
- package/packages/dashboard/server/parser.js +526 -458
- package/packages/dashboard/server/watcher.js +350 -342
- package/packages/dashboard/src/App.vue +558 -325
- package/packages/dashboard/src/components/ActionBar.vue +93 -84
- package/packages/dashboard/src/components/CommandPalette.vue +96 -92
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -75
- package/packages/dashboard/src/components/PipelineView.vue +156 -129
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/StageBadge.vue +67 -53
- package/packages/dashboard/src/components/StepCard.vue +94 -89
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +192 -164
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -0
- package/src/change-list.js +52 -0
- package/src/change-risk-profile.js +352 -0
- package/src/classify-change.js +73 -0
- package/src/constants.js +70 -0
- package/src/contract-matrix.js +278 -0
- package/src/db.js +201 -0
- package/src/endpoint-extractor.js +315 -0
- package/src/hooks/claude-pre-tool-use.cjs +125 -0
- package/src/hooks/worktree-guard.js +761 -0
- package/src/index.js +922 -252
- package/src/init.js +431 -375
- package/src/knowledge-match.js +130 -0
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -0
- package/src/progress.js +1734 -508
- package/src/run.js +3465 -652
- package/src/scan-postcheck.js +387 -0
- package/src/setup.js +398 -460
- package/src/stage-contract.js +700 -0
- package/src/stages/archive.js +160 -54
- package/src/stages/brainstorm-auto.js +229 -0
- package/src/stages/brainstorm.js +645 -239
- package/src/stages/doctor.js +365 -312
- package/src/stages/execute.js +625 -264
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +29 -35
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +511 -0
- package/src/stages/plan.js +582 -279
- package/src/stages/propose.js +174 -115
- package/src/stages/quick.js +102 -63
- package/src/stages/scan.js +558 -141
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -135
- package/src/sync.js +497 -0
- package/src/task-review.js +346 -0
- package/src/workflow.js +785 -0
- package/src/worktree-apply.js +549 -0
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -0
- package/templates/workflows/archive-impact.yaml +79 -0
- package/templates/workflows/scan-docs.yaml +132 -0
- package/test/brainstorm-plan-contract.test.mjs +273 -0
- package/test/check-syntax.mjs +26 -0
- package/test/cli-top-level-aliases.test.mjs +174 -0
- package/test/contract-artifacts.test.mjs +323 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/knowledge-match.test.mjs +231 -0
- package/test/plan-execute-contract.test.mjs +330 -0
- package/test/plan-optimization.test.mjs +572 -0
- package/test/platform-artifacts.test.mjs +190 -0
- package/test/platform-failure-samples.test.mjs +199 -0
- package/test/platform-recovery-chain.test.mjs +179 -0
- package/test/platform-recovery.test.mjs +167 -0
- package/test/platform-scan-p0.test.mjs +175 -0
- package/test/revision-v1.test.mjs +1145 -0
- package/test/run-sanitize-project-name.test.mjs +51 -0
- package/test/run-scan-postcheck-fail.test.mjs +64 -0
- package/test/run-scan-project-parse.test.mjs +200 -0
- package/test/run-tests.mjs +48 -0
- package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
- package/test/scan-knowledge.test.mjs +175 -0
- package/test/scan-paths.test.mjs +68 -0
- package/test/scan-postcheck-project-priority.test.mjs +85 -0
- package/test/scan-postcheck.test.mjs +197 -0
- package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
- package/test/spec-dir.test.mjs +206 -0
- package/test/stage-contract-failed-post-check.test.mjs +102 -0
- package/test/stage-contract.test.mjs +299 -0
- package/test/stage-definitions.test.mjs +39 -0
- package/test/wait-gates.test.mjs +501 -0
- package/test/workflow-spec-base.test.mjs +142 -0
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +136 -0
- package/test/worktree-native-overlay.test.mjs +188 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
- package/src/derive.js +0 -147
- package/src/step.js +0 -543
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="project-card"
|
|
4
|
+
:class="{ selected: isSelected }"
|
|
5
|
+
@click="$emit('select', project)"
|
|
6
|
+
>
|
|
7
|
+
<!-- 卡片头部 -->
|
|
8
|
+
<div class="card-header">
|
|
9
|
+
<span class="project-name">{{ project.name }}</span>
|
|
10
|
+
<span class="last-active">{{ formatTime(project.lastActive) }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- 阶段标签 -->
|
|
14
|
+
<div>
|
|
15
|
+
<span class="stage-badge" :class="stageClass">{{ stageLabel }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- 进度条 -->
|
|
19
|
+
<div class="progress-section">
|
|
20
|
+
<div class="progress-bar">
|
|
21
|
+
<div class="progress-fill" :class="stageClass" :style="{ width: progressPercent + '%' }"></div>
|
|
22
|
+
</div>
|
|
23
|
+
<span class="progress-text">{{ progressPercent }}%</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { computed } from 'vue'
|
|
30
|
+
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
project: { type: Object, required: true },
|
|
33
|
+
isSelected: { type: Boolean, default: false }
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
defineEmits(['select'])
|
|
37
|
+
|
|
38
|
+
const stageLabel = computed(() => {
|
|
39
|
+
const stage = props.project?.state?.currentStage
|
|
40
|
+
if (!stage) return '未开始'
|
|
41
|
+
const stageNames = {
|
|
42
|
+
brainstorm: '需求探索',
|
|
43
|
+
plan: '实现计划',
|
|
44
|
+
execute: '波次执行',
|
|
45
|
+
verify: '验证确认'
|
|
46
|
+
}
|
|
47
|
+
return stageNames[stage] || stage
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const stageClass = computed(() => {
|
|
51
|
+
const stage = props.project?.state?.currentStage
|
|
52
|
+
if (!stage) return 'pending'
|
|
53
|
+
const progress = props.project?.state?.progress?.stages?.[stage]
|
|
54
|
+
if (progress?.status === 'completed') return 'completed'
|
|
55
|
+
if (progress?.status === 'in-progress') return 'in-progress'
|
|
56
|
+
return 'pending'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const progressPercent = computed(() => {
|
|
60
|
+
const stage = props.project?.state?.currentStage
|
|
61
|
+
if (!stage) return 0
|
|
62
|
+
const progress = props.project?.state?.progress?.stages?.[stage]
|
|
63
|
+
if (!progress) return 0
|
|
64
|
+
if (progress.completedSteps !== undefined && progress.steps !== undefined) {
|
|
65
|
+
return Math.round((progress.completedSteps / progress.steps) * 100)
|
|
66
|
+
}
|
|
67
|
+
if (progress.status === 'completed') return 100
|
|
68
|
+
if (progress.status === 'in-progress') return 50
|
|
69
|
+
return 0
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
function formatTime(iso) {
|
|
73
|
+
if (!iso) return '昨天'
|
|
74
|
+
const d = new Date(iso)
|
|
75
|
+
const now = new Date()
|
|
76
|
+
const diffMs = now - d
|
|
77
|
+
if (diffMs < 60000) return '刚刚'
|
|
78
|
+
if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}分钟前`
|
|
79
|
+
if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)}小时前`
|
|
80
|
+
return '昨天'
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped>
|
|
85
|
+
.project-card {
|
|
86
|
+
width: 280px;
|
|
87
|
+
height: 120px;
|
|
88
|
+
background: white;
|
|
89
|
+
border: 2px solid #E5E7EB;
|
|
90
|
+
border-radius: 12px;
|
|
91
|
+
padding: 16px;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
transition: all 0.2s;
|
|
97
|
+
flex-shrink: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.project-card:hover {
|
|
101
|
+
border-color: #D97706;
|
|
102
|
+
box-shadow: 0 4px 12px rgba(217, 119, 6, 0.15);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.project-card.selected {
|
|
106
|
+
border-color: #D97706;
|
|
107
|
+
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.card-header {
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
align-items: flex-start;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.project-name {
|
|
117
|
+
font-size: 16px;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
color: #1A1A1A;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.last-active {
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
color: #9CA3AF;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.stage-badge {
|
|
128
|
+
display: inline-block;
|
|
129
|
+
padding: 4px 10px;
|
|
130
|
+
border-radius: 12px;
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.stage-badge.in-progress {
|
|
136
|
+
background: #DBEAFE;
|
|
137
|
+
color: #1D4ED8;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.stage-badge.completed {
|
|
141
|
+
background: #D1FAE5;
|
|
142
|
+
color: #047857;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.stage-badge.pending {
|
|
146
|
+
background: #F3F4F6;
|
|
147
|
+
color: #6B7280;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.progress-section {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.progress-bar {
|
|
157
|
+
flex: 1;
|
|
158
|
+
height: 6px;
|
|
159
|
+
background: #E5E7EB;
|
|
160
|
+
border-radius: 3px;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.progress-fill {
|
|
165
|
+
height: 100%;
|
|
166
|
+
border-radius: 3px;
|
|
167
|
+
transition: width 0.3s;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.progress-fill.in-progress {
|
|
171
|
+
background: #3B82F6;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.progress-fill.completed {
|
|
175
|
+
background: #10B981;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.progress-fill.pending {
|
|
179
|
+
background: #9CA3AF;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.progress-text {
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
color: #374151;
|
|
186
|
+
}
|
|
187
|
+
</style>
|
|
@@ -1,210 +1,210 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="h-full flex flex-col noise-bg">
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<div class="relative z-10 px-5 pt-5 pb-4">
|
|
5
|
-
<div class="flex items-center gap-3">
|
|
6
|
-
<div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
|
|
7
|
-
<span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
|
|
8
|
-
</div>
|
|
9
|
-
<img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
|
|
10
|
-
<div class="flex-1">
|
|
11
|
-
<h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
|
|
12
|
-
SillySpec
|
|
13
|
-
</h1>
|
|
14
|
-
<p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
|
|
15
|
-
</div>
|
|
16
|
-
<!-- Scan paths gear button -->
|
|
17
|
-
<n-button quaternary size="tiny" @click="showScanPanel = !showScanPanel" :type="showScanPanel ? 'primary' : 'default'">
|
|
18
|
-
<template #icon>
|
|
19
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
21
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
22
|
-
</svg>
|
|
23
|
-
</template>
|
|
24
|
-
</n-button>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<!-- Scan paths panel (inline) -->
|
|
28
|
-
<Transition name="slide">
|
|
29
|
-
<div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #FFFFFF; border: 1px solid #F0F0F3;">
|
|
30
|
-
<div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">扫描路径</div>
|
|
31
|
-
|
|
32
|
-
<div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
|
|
33
|
-
<div v-else class="space-y-1 mb-2">
|
|
34
|
-
<div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
|
|
35
|
-
<span class="flex-1 truncate font-mono-log" style="color: #636366;">{{ p }}</span>
|
|
36
|
-
<n-button quaternary size="tiny" type="error" @click="removePath(p)">✕</n-button>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<!-- Add path -->
|
|
41
|
-
<div v-if="showAddInput" class="flex items-center gap-2">
|
|
42
|
-
<n-input v-model:value="newPath" size="tiny" placeholder="输入目录路径..." @keydown.enter="addPath" @keydown.escape="showAddInput = false" ref="pathInput" />
|
|
43
|
-
<n-button size="tiny" type="primary" @click="addPath">添加</n-button>
|
|
44
|
-
</div>
|
|
45
|
-
<n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
|
|
46
|
-
</div>
|
|
47
|
-
</Transition>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<!-- Divider -->
|
|
51
|
-
<div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
|
|
52
|
-
|
|
53
|
-
<!-- Projects List -->
|
|
54
|
-
<div class="flex-1 overflow-y-auto py-3 relative z-10">
|
|
55
|
-
<!-- Loading skeleton -->
|
|
56
|
-
<div v-if="isLoading" class="px-4 space-y-2">
|
|
57
|
-
<n-card v-for="i in 4" :key="i" size="small" :bordered="false">
|
|
58
|
-
<n-skeleton text :width="80" size="small" />
|
|
59
|
-
<n-skeleton text :width="140" size="small" style="margin-top: 6px;" />
|
|
60
|
-
</n-card>
|
|
61
|
-
<p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
62
|
-
正在扫描项目...
|
|
63
|
-
</p>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<!-- Empty state -->
|
|
67
|
-
<n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
|
|
68
|
-
|
|
69
|
-
<!-- Projects -->
|
|
70
|
-
<div v-else class="px-3 space-y-0.5">
|
|
71
|
-
<div
|
|
72
|
-
v-for="project in projects"
|
|
73
|
-
:key="project.path"
|
|
74
|
-
:class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
|
|
75
|
-
:style="{
|
|
76
|
-
background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
|
|
77
|
-
borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
|
|
78
|
-
}"
|
|
79
|
-
@mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.08)' : 'rgba(255,255,255,0.02)'"
|
|
80
|
-
@mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent'"
|
|
81
|
-
@click="$emit('select', project)"
|
|
82
|
-
>
|
|
83
|
-
<div class="px-3 py-2.5">
|
|
84
|
-
<div class="flex items-center justify-between gap-2">
|
|
85
|
-
<div class="flex-1 min-w-0">
|
|
86
|
-
<h3
|
|
87
|
-
class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
|
|
88
|
-
:style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
|
|
89
|
-
>
|
|
90
|
-
{{ project.name }}
|
|
91
|
-
</h3>
|
|
92
|
-
<p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
|
|
93
|
-
{{ project.path }}
|
|
94
|
-
</p>
|
|
95
|
-
</div>
|
|
96
|
-
<n-tag
|
|
97
|
-
v-if="project.state?.currentStage"
|
|
98
|
-
:type="statusTagType(getProjectStatus(project))"
|
|
99
|
-
size="small"
|
|
100
|
-
:bordered="false"
|
|
101
|
-
round
|
|
102
|
-
>
|
|
103
|
-
{{ stageLabel(project) }}
|
|
104
|
-
</n-tag>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<!-- Progress -->
|
|
108
|
-
<div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
|
|
109
|
-
<div
|
|
110
|
-
class="h-full rounded-full transition-all duration-500 progress-gradient"
|
|
111
|
-
:style="{ width: getProjectProgress(project) + '%' }"
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<!-- Footer -->
|
|
120
|
-
<div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
|
|
121
|
-
<div class="flex items-center justify-between">
|
|
122
|
-
<span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ projects.length }} 个项目</span>
|
|
123
|
-
<kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #6B7280; background: #FFFFFF; border: 1px solid #E5E5EA;">⌘K</kbd>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</template>
|
|
128
|
-
|
|
129
|
-
<script setup>
|
|
130
|
-
import { ref, nextTick, watch } from 'vue'
|
|
131
|
-
|
|
132
|
-
const props = defineProps({
|
|
133
|
-
projects: { type: Array, default: () => [] },
|
|
134
|
-
activeProject: { type: Object, default: null },
|
|
135
|
-
isLoading: { type: Boolean, default: false },
|
|
136
|
-
scanPaths: { type: Array, default: () => [] }
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
const emit = defineEmits(['select', 'scan:add-path', 'scan:remove-path'])
|
|
140
|
-
|
|
141
|
-
const showScanPanel = ref(false)
|
|
142
|
-
const showAddInput = ref(false)
|
|
143
|
-
const newPath = ref('')
|
|
144
|
-
const pathInput = ref(null)
|
|
145
|
-
|
|
146
|
-
watch(showAddInput, (v) => {
|
|
147
|
-
if (v) nextTick(() => { pathInput.value?.focus() })
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
function isActive(project) {
|
|
151
|
-
return props.activeProject?.path === project.path
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function statusTagType(status) {
|
|
155
|
-
const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
|
|
156
|
-
return map[status] || 'default'
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function getProjectStatus(project) {
|
|
160
|
-
const stage = project.state?.currentStage
|
|
161
|
-
if (!stage) return 'pending'
|
|
162
|
-
const stageData = project.state?.progress?.stages?.[stage]
|
|
163
|
-
if (!stageData) return 'pending'
|
|
164
|
-
const steps = stageData.steps || []
|
|
165
|
-
if (steps.length === 0) return 'pending'
|
|
166
|
-
if (steps.some(s => s.status === 'failed')) return 'failed'
|
|
167
|
-
if (steps.some(s => s.status === 'blocked')) return 'blocked'
|
|
168
|
-
if (steps.some(s => s.status === 'in-progress')) return 'in-progress'
|
|
169
|
-
if (steps.every(s => s.status === 'completed')) return 'completed'
|
|
170
|
-
return 'pending'
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function stageLabel(project) {
|
|
174
|
-
const stage = project.state?.currentStage
|
|
175
|
-
const labels = { 'brainstorm': '头脑风暴', 'plan': '规划', 'execute': '执行', 'verify': '验证' }
|
|
176
|
-
return labels[stage] || stage || '未知'
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function getProjectProgress(project) {
|
|
180
|
-
const progress = project.state?.progress
|
|
181
|
-
if (!progress) return 0
|
|
182
|
-
const stages = progress.stages || {}
|
|
183
|
-
const stageNames = ['brainstorm', 'plan', 'execute', 'verify']
|
|
184
|
-
let total = 0, done = 0
|
|
185
|
-
for (const s of stageNames) {
|
|
186
|
-
const st = stages[s]
|
|
187
|
-
if (st?.steps) { total += st.steps.length; done += st.steps.filter(x => x.status === 'completed').length }
|
|
188
|
-
}
|
|
189
|
-
return total === 0 ? 0 : Math.round((done / total) * 100)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function addPath() {
|
|
193
|
-
const p = newPath.value.trim()
|
|
194
|
-
if (p) {
|
|
195
|
-
emit('scan:add-path', p)
|
|
196
|
-
newPath.value = ''
|
|
197
|
-
showAddInput.value = false
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function removePath(p) {
|
|
202
|
-
emit('scan:remove-path', p)
|
|
203
|
-
}
|
|
204
|
-
</script>
|
|
205
|
-
|
|
206
|
-
<style scoped>
|
|
207
|
-
.slide-enter-active, .slide-leave-active { transition: all 200ms ease; }
|
|
208
|
-
.slide-enter-from, .slide-leave-to { opacity: 0; max-height: 0; overflow: hidden; }
|
|
209
|
-
.slide-enter-to, .slide-leave-from { max-height: 300px; }
|
|
210
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full flex flex-col noise-bg">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="relative z-10 px-5 pt-5 pb-4">
|
|
5
|
+
<div class="flex items-center gap-3">
|
|
6
|
+
<div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
|
|
7
|
+
<span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
|
|
8
|
+
</div>
|
|
9
|
+
<img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
|
|
10
|
+
<div class="flex-1">
|
|
11
|
+
<h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
|
|
12
|
+
SillySpec
|
|
13
|
+
</h1>
|
|
14
|
+
<p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
|
|
15
|
+
</div>
|
|
16
|
+
<!-- Scan paths gear button -->
|
|
17
|
+
<n-button quaternary size="tiny" @click="showScanPanel = !showScanPanel" :type="showScanPanel ? 'primary' : 'default'">
|
|
18
|
+
<template #icon>
|
|
19
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
22
|
+
</svg>
|
|
23
|
+
</template>
|
|
24
|
+
</n-button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Scan paths panel (inline) -->
|
|
28
|
+
<Transition name="slide">
|
|
29
|
+
<div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #FFFFFF; border: 1px solid #F0F0F3;">
|
|
30
|
+
<div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">扫描路径</div>
|
|
31
|
+
|
|
32
|
+
<div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
|
|
33
|
+
<div v-else class="space-y-1 mb-2">
|
|
34
|
+
<div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
|
|
35
|
+
<span class="flex-1 truncate font-mono-log" style="color: #636366;">{{ p }}</span>
|
|
36
|
+
<n-button quaternary size="tiny" type="error" @click="removePath(p)">✕</n-button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Add path -->
|
|
41
|
+
<div v-if="showAddInput" class="flex items-center gap-2">
|
|
42
|
+
<n-input v-model:value="newPath" size="tiny" placeholder="输入目录路径..." @keydown.enter="addPath" @keydown.escape="showAddInput = false" ref="pathInput" />
|
|
43
|
+
<n-button size="tiny" type="primary" @click="addPath">添加</n-button>
|
|
44
|
+
</div>
|
|
45
|
+
<n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
|
|
46
|
+
</div>
|
|
47
|
+
</Transition>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<!-- Divider -->
|
|
51
|
+
<div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
|
|
52
|
+
|
|
53
|
+
<!-- Projects List -->
|
|
54
|
+
<div class="flex-1 overflow-y-auto py-3 relative z-10">
|
|
55
|
+
<!-- Loading skeleton -->
|
|
56
|
+
<div v-if="isLoading" class="px-4 space-y-2">
|
|
57
|
+
<n-card v-for="i in 4" :key="i" size="small" :bordered="false">
|
|
58
|
+
<n-skeleton text :width="80" size="small" />
|
|
59
|
+
<n-skeleton text :width="140" size="small" style="margin-top: 6px;" />
|
|
60
|
+
</n-card>
|
|
61
|
+
<p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
62
|
+
正在扫描项目...
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Empty state -->
|
|
67
|
+
<n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
|
|
68
|
+
|
|
69
|
+
<!-- Projects -->
|
|
70
|
+
<div v-else class="px-3 space-y-0.5">
|
|
71
|
+
<div
|
|
72
|
+
v-for="project in projects"
|
|
73
|
+
:key="project.path"
|
|
74
|
+
:class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
|
|
75
|
+
:style="{
|
|
76
|
+
background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
|
|
77
|
+
borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
|
|
78
|
+
}"
|
|
79
|
+
@mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.08)' : 'rgba(255,255,255,0.02)'"
|
|
80
|
+
@mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent'"
|
|
81
|
+
@click="$emit('select', project)"
|
|
82
|
+
>
|
|
83
|
+
<div class="px-3 py-2.5">
|
|
84
|
+
<div class="flex items-center justify-between gap-2">
|
|
85
|
+
<div class="flex-1 min-w-0">
|
|
86
|
+
<h3
|
|
87
|
+
class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
|
|
88
|
+
:style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
|
|
89
|
+
>
|
|
90
|
+
{{ project.name }}
|
|
91
|
+
</h3>
|
|
92
|
+
<p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
|
|
93
|
+
{{ project.path }}
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
<n-tag
|
|
97
|
+
v-if="project.state?.currentStage"
|
|
98
|
+
:type="statusTagType(getProjectStatus(project))"
|
|
99
|
+
size="small"
|
|
100
|
+
:bordered="false"
|
|
101
|
+
round
|
|
102
|
+
>
|
|
103
|
+
{{ stageLabel(project) }}
|
|
104
|
+
</n-tag>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Progress -->
|
|
108
|
+
<div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
|
|
109
|
+
<div
|
|
110
|
+
class="h-full rounded-full transition-all duration-500 progress-gradient"
|
|
111
|
+
:style="{ width: getProjectProgress(project) + '%' }"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Footer -->
|
|
120
|
+
<div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
|
|
121
|
+
<div class="flex items-center justify-between">
|
|
122
|
+
<span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ projects.length }} 个项目</span>
|
|
123
|
+
<kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #6B7280; background: #FFFFFF; border: 1px solid #E5E5EA;">⌘K</kbd>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<script setup>
|
|
130
|
+
import { ref, nextTick, watch } from 'vue'
|
|
131
|
+
|
|
132
|
+
const props = defineProps({
|
|
133
|
+
projects: { type: Array, default: () => [] },
|
|
134
|
+
activeProject: { type: Object, default: null },
|
|
135
|
+
isLoading: { type: Boolean, default: false },
|
|
136
|
+
scanPaths: { type: Array, default: () => [] }
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const emit = defineEmits(['select', 'scan:add-path', 'scan:remove-path'])
|
|
140
|
+
|
|
141
|
+
const showScanPanel = ref(false)
|
|
142
|
+
const showAddInput = ref(false)
|
|
143
|
+
const newPath = ref('')
|
|
144
|
+
const pathInput = ref(null)
|
|
145
|
+
|
|
146
|
+
watch(showAddInput, (v) => {
|
|
147
|
+
if (v) nextTick(() => { pathInput.value?.focus() })
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
function isActive(project) {
|
|
151
|
+
return props.activeProject?.path === project.path
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function statusTagType(status) {
|
|
155
|
+
const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
|
|
156
|
+
return map[status] || 'default'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getProjectStatus(project) {
|
|
160
|
+
const stage = project.state?.currentStage
|
|
161
|
+
if (!stage) return 'pending'
|
|
162
|
+
const stageData = project.state?.progress?.stages?.[stage]
|
|
163
|
+
if (!stageData) return 'pending'
|
|
164
|
+
const steps = stageData.steps || []
|
|
165
|
+
if (steps.length === 0) return 'pending'
|
|
166
|
+
if (steps.some(s => s.status === 'failed')) return 'failed'
|
|
167
|
+
if (steps.some(s => s.status === 'blocked')) return 'blocked'
|
|
168
|
+
if (steps.some(s => s.status === 'in-progress')) return 'in-progress'
|
|
169
|
+
if (steps.every(s => s.status === 'completed')) return 'completed'
|
|
170
|
+
return 'pending'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function stageLabel(project) {
|
|
174
|
+
const stage = project.state?.currentStage
|
|
175
|
+
const labels = { 'brainstorm': '头脑风暴', 'plan': '规划', 'execute': '执行', 'verify': '验证' }
|
|
176
|
+
return labels[stage] || stage || '未知'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getProjectProgress(project) {
|
|
180
|
+
const progress = project.state?.progress
|
|
181
|
+
if (!progress) return 0
|
|
182
|
+
const stages = progress.stages || {}
|
|
183
|
+
const stageNames = ['brainstorm', 'plan', 'execute', 'verify']
|
|
184
|
+
let total = 0, done = 0
|
|
185
|
+
for (const s of stageNames) {
|
|
186
|
+
const st = stages[s]
|
|
187
|
+
if (st?.steps) { total += st.steps.length; done += st.steps.filter(x => x.status === 'completed').length }
|
|
188
|
+
}
|
|
189
|
+
return total === 0 ? 0 : Math.round((done / total) * 100)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function addPath() {
|
|
193
|
+
const p = newPath.value.trim()
|
|
194
|
+
if (p) {
|
|
195
|
+
emit('scan:add-path', p)
|
|
196
|
+
newPath.value = ''
|
|
197
|
+
showAddInput.value = false
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function removePath(p) {
|
|
202
|
+
emit('scan:remove-path', p)
|
|
203
|
+
}
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<style scoped>
|
|
207
|
+
.slide-enter-active, .slide-leave-active { transition: all 200ms ease; }
|
|
208
|
+
.slide-enter-from, .slide-leave-to { opacity: 0; max-height: 0; overflow: hidden; }
|
|
209
|
+
.slide-enter-to, .slide-leave-from { max-height: 300px; }
|
|
210
|
+
</style>
|