sillyspec 3.7.13 → 3.7.15
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 +77 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +591 -0
- package/.claude/skills/sillyspec-continue/SKILL.md +44 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +233 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +171 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +263 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +248 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +102 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/{templates/scan.md → .claude/skills/sillyspec-scan/SKILL.md} +22 -60
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +131 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +146 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +4 -0
- package/logo.jpg +0 -0
- package/package.json +7 -1
- package/packages/dashboard/dist/assets/index-Bx0cgoK_.js +7446 -0
- package/packages/dashboard/dist/assets/index-DbkUSsNO.css +1 -0
- package/packages/dashboard/dist/favicon.jpg +0 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/dist/logo.jpg +0 -0
- package/packages/dashboard/package-lock.json +220 -0
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/favicon.jpg +0 -0
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/server/index.js +92 -4
- package/packages/dashboard/server/parser.js +252 -28
- package/packages/dashboard/src/App.vue +139 -9
- package/packages/dashboard/src/components/ActionBar.vue +23 -39
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +137 -20
- package/packages/dashboard/src/components/DocTree.vue +48 -26
- package/packages/dashboard/src/components/LogStream.vue +12 -32
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +35 -43
- package/packages/dashboard/src/components/ProjectList.vue +52 -77
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +11 -11
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +13 -13
- package/src/index.js +55 -8
- package/src/init.js +66 -196
- package/src/migrate.js +1 -18
- package/src/progress.js +279 -281
- package/src/run.js +320 -0
- package/src/setup.js +1 -9
- package/src/stages/brainstorm.js +210 -0
- package/src/stages/execute.js +190 -0
- package/src/stages/index.js +22 -0
- package/src/stages/plan.js +118 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/verify.js +98 -0
- package/packages/dashboard/dist/assets/index-5Rrvs0Rl.css +0 -1
- package/packages/dashboard/dist/assets/index-ZNToqi9V.js +0 -17
- package/templates/archive.md +0 -121
- package/templates/brainstorm.md +0 -246
- package/templates/commit.md +0 -123
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -314
- package/templates/explore.md +0 -60
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -157
- package/templates/progress-format.md +0 -90
- package/templates/propose.md +0 -73
- package/templates/quick.md +0 -135
- package/templates/resume-dialog.md +0 -55
- package/templates/resume.md +0 -53
- package/templates/scan-quick.md +0 -49
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -72
- package/templates/verify.md +0 -253
- package/templates/workspace-sync.md +0 -89
- package/templates/workspace.md +0 -67
- /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-unified-docs-design.md +0 -0
- /package/.sillyspec/{specs/2026-04-05-dashboard-design.md → changes/dashboard/design.md.braindraft} +0 -0
- /package/.sillyspec/{specs/2026-04-05-unified-docs-design.md → changes/unified-docs-design/design.md} +0 -0
|
@@ -1,67 +1,49 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
15
|
-
</svg>
|
|
16
|
-
<input
|
|
17
|
-
ref="searchInput"
|
|
18
|
-
v-model="searchQuery"
|
|
19
|
-
type="text"
|
|
20
|
-
placeholder="搜索项目或命令..."
|
|
21
|
-
class="flex-1 bg-transparent border-none outline-none text-[12px] font-[JetBrains_Mono,monospace]"
|
|
22
|
-
style="color: #E4E4E7;"
|
|
23
|
-
@keydown="handleKeydown"
|
|
24
|
-
/>
|
|
25
|
-
<kbd v-if="searchQuery" class="text-[9px] px-1 py-0.5 rounded-sm font-mono-log" style="color: #525252; background: #0A0A0B; border: 1px solid #2A2A2D;">ESC</kbd>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
2
|
+
<n-modal :show="isOpen" @update:show="$emit('close')" :mask-closable="true" transform-origin="center" style="max-width: 480px;">
|
|
3
|
+
<div class="overflow-hidden rounded-md" style="background: #FFFFFF; border: 1px solid #E5E5EA; box-shadow: 0 25px 60px rgba(0,0,0,0.1);">
|
|
4
|
+
<!-- Search -->
|
|
5
|
+
<div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
6
|
+
<n-input v-model:value="searchQuery" placeholder="搜索项目或命令..." ref="searchInput" @keydown="handleKeydown" size="small">
|
|
7
|
+
<template #prefix>
|
|
8
|
+
<svg class="w-3.5 h-3.5" style="color: #6B7280;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
9
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
10
|
+
</svg>
|
|
11
|
+
</template>
|
|
12
|
+
</n-input>
|
|
13
|
+
</div>
|
|
28
14
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
<!-- Results -->
|
|
16
|
+
<div class="max-h-72 overflow-y-auto">
|
|
17
|
+
<n-empty v-if="filteredItems.length === 0" description="无结果" style="padding: 40px 0;" />
|
|
18
|
+
<div v-else class="py-0.5">
|
|
19
|
+
<div
|
|
20
|
+
v-for="(item, index) in filteredItems"
|
|
21
|
+
:key="item.id"
|
|
22
|
+
:class="['px-4 py-2.5 cursor-pointer transition-colors duration-100 flex items-center gap-3']"
|
|
23
|
+
:style="{ background: selectedIndex === index ? 'rgba(217,119,6,0.06)' : 'transparent' }"
|
|
24
|
+
@click="selectItem(item)"
|
|
25
|
+
@mouseenter="selectedIndex = index"
|
|
26
|
+
>
|
|
27
|
+
<div class="w-6 h-6 rounded-sm flex items-center justify-center text-[10px] font-[JetBrains_Mono,monospace] flex-shrink-0" style="background: #F0F0F3; border: 1px solid #F0F0F3; color: #6B7280;">
|
|
28
|
+
{{ item.type === 'project' ? '□' : '◇' }}
|
|
33
29
|
</div>
|
|
34
|
-
<div
|
|
35
|
-
<div
|
|
36
|
-
|
|
37
|
-
:key="item.id"
|
|
38
|
-
:class="['px-4 py-2.5 cursor-pointer transition-colors duration-100 flex items-center gap-3']"
|
|
39
|
-
:style="{ background: selectedIndex === index ? 'rgba(251,191,36,0.06)' : 'transparent' }"
|
|
40
|
-
@click="selectItem(item)"
|
|
41
|
-
@mouseenter="selectedIndex = index"
|
|
42
|
-
>
|
|
43
|
-
<div class="w-6 h-6 rounded-sm flex items-center justify-center text-[10px] font-[JetBrains_Mono,monospace] flex-shrink-0" style="background: #0A0A0B; border: 1px solid #1F1F22; color: #525252;">
|
|
44
|
-
{{ item.type === 'project' ? '□' : '◇' }}
|
|
45
|
-
</div>
|
|
46
|
-
<div class="flex-1 min-w-0">
|
|
47
|
-
<div class="text-[12px] font-medium truncate font-[JetBrains_Mono,monospace]" style="color: #E4E4E7;">{{ item.title }}</div>
|
|
48
|
-
<div class="text-[10px] truncate" style="color: #525252;">{{ item.subtitle }}</div>
|
|
49
|
-
</div>
|
|
50
|
-
<StageBadge v-if="item.status" :status="item.status" size="sm" />
|
|
51
|
-
</div>
|
|
30
|
+
<div class="flex-1 min-w-0">
|
|
31
|
+
<div class="text-[12px] font-medium truncate font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">{{ item.title }}</div>
|
|
32
|
+
<div class="text-[10px] truncate" style="color: #6B7280;">{{ item.subtitle }}</div>
|
|
52
33
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<!-- Footer -->
|
|
56
|
-
<div class="px-4 py-2 flex items-center gap-4 text-[9px] font-mono-log" style="border-top: 1px solid #1F1F22; color: #3A3A3D;">
|
|
57
|
-
<span><kbd class="px-1 rounded-sm" style="background: #0A0A0B; border: 1px solid #1F1F22;">↑↓</kbd> 导航</span>
|
|
58
|
-
<span><kbd class="px-1 rounded-sm" style="background: #0A0A0B; border: 1px solid #1F1F22;">↵</kbd> 打开</span>
|
|
59
|
-
<span><kbd class="px-1 rounded-sm" style="background: #0A0A0B; border: 1px solid #1F1F22;">esc</kbd> 关闭</span>
|
|
34
|
+
<StageBadge v-if="item.status" :status="item.status" size="sm" />
|
|
60
35
|
</div>
|
|
61
36
|
</div>
|
|
62
37
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
|
|
39
|
+
<!-- Footer -->
|
|
40
|
+
<div class="px-4 py-2 flex items-center gap-4 text-[9px] font-mono-log" style="border-top: 1px solid #F0F0F3; color: #D1D1D6;">
|
|
41
|
+
<span><kbd class="px-1 rounded-sm" style="background: #F0F0F3; border: 1px solid #F0F0F3;">↑↓</kbd> 导航</span>
|
|
42
|
+
<span><kbd class="px-1 rounded-sm" style="background: #F0F0F3; border: 1px solid #F0F0F3;">↵</kbd> 打开</span>
|
|
43
|
+
<span><kbd class="px-1 rounded-sm" style="background: #F0F0F3; border: 1px solid #F0F0F3;">esc</kbd> 关闭</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</n-modal>
|
|
65
47
|
</template>
|
|
66
48
|
|
|
67
49
|
<script setup>
|
|
@@ -106,12 +88,5 @@ function handleKeydown(e) {
|
|
|
106
88
|
else if (e.key === 'Escape') { e.preventDefault(); close() }
|
|
107
89
|
}
|
|
108
90
|
watch(filteredItems, () => { selectedIndex.value = 0 })
|
|
109
|
-
watch(() => props.isOpen, (v) => { if (v) nextTick(() => { searchInput.value?.focus() }) })
|
|
91
|
+
watch(() => props.isOpen, (v) => { if (v) { nextTick(() => { searchInput.value?.focus() }) } })
|
|
110
92
|
</script>
|
|
111
|
-
|
|
112
|
-
<style scoped>
|
|
113
|
-
.backdrop-enter-active, .backdrop-leave-active { transition: opacity 150ms ease; }
|
|
114
|
-
.backdrop-enter-from, .backdrop-leave-to { opacity: 0; }
|
|
115
|
-
.palette-enter-active, .palette-leave-active { transition: all 200ms cubic-bezier(0.16, 1, 0.3, 1); }
|
|
116
|
-
.palette-enter-from, .palette-leave-to { opacity: 0; transform: translate(-50%, -8px) scale(0.98); }
|
|
117
|
-
</style>
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
:class="['flex flex-col transition-all duration-300', isOpen ? 'w-[340px]' : 'w-0 opacity-0 overflow-hidden']"
|
|
4
|
-
style="background: #
|
|
4
|
+
style="background: #F5F5F7;"
|
|
5
5
|
>
|
|
6
6
|
<!-- Header -->
|
|
7
|
-
<div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #
|
|
8
|
-
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #
|
|
9
|
-
<button
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</svg>
|
|
17
|
-
</button>
|
|
7
|
+
<div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #F0F0F3;">
|
|
8
|
+
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ detailType ? detailTitle : '详情' }}</h2>
|
|
9
|
+
<n-button quaternary size="tiny" @click="handleClose">
|
|
10
|
+
<template #icon>
|
|
11
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
</n-button>
|
|
18
16
|
</div>
|
|
19
17
|
|
|
20
18
|
<!-- Content -->
|
|
21
19
|
<div class="flex-1 overflow-y-auto">
|
|
22
20
|
<!-- Empty state -->
|
|
23
|
-
<
|
|
24
|
-
<div class="text-center">
|
|
25
|
-
<div class="w-10 h-10 mx-auto mb-3 rounded-md flex items-center justify-center" style="border: 1px dashed #2A2A2D; transform: rotate(45deg);">
|
|
26
|
-
<svg class="w-4 h-4" style="color: #3A3A3D; transform: rotate(-45deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
27
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
28
|
-
</svg>
|
|
29
|
-
</div>
|
|
30
|
-
<p class="text-[11px] font-[JetBrains_Mono,monospace]" style="color: #3A3A3D;">选择一个步骤</p>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
21
|
+
<n-empty v-if="!activeStep && !detailType" description="选择一个步骤" style="margin: auto; padding: 48px 0;" />
|
|
33
22
|
|
|
34
|
-
<!--
|
|
35
|
-
<
|
|
23
|
+
<!-- Detail views -->
|
|
24
|
+
<template v-if="detailType === 'git'">
|
|
25
|
+
<GitDetail :data="detailData" />
|
|
26
|
+
</template>
|
|
27
|
+
<template v-else-if="detailType === 'tech'">
|
|
28
|
+
<TechDetail :data="detailData" />
|
|
29
|
+
</template>
|
|
30
|
+
<template v-else-if="detailType === 'docs'">
|
|
31
|
+
<DocsDetail :data="detailData" @open-file="$emit('open-doc-file', $event)" />
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<!-- Step detail (original) -->
|
|
35
|
+
<div v-else-if="activeStep">
|
|
36
36
|
<!-- Title -->
|
|
37
|
-
<div class="px-4 py-3" style="border-bottom: 1px solid #
|
|
38
|
-
<h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #
|
|
37
|
+
<div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
38
|
+
<h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #D97706;">
|
|
39
39
|
{{ activeStep.title || activeStep.name }}
|
|
40
40
|
</h3>
|
|
41
41
|
<div v-if="activeStep.status" class="mt-2">
|
|
@@ -44,48 +44,46 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<!-- Description -->
|
|
47
|
-
<div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
48
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
49
|
-
<p class="text-[11px] leading-relaxed" style="color: #
|
|
47
|
+
<div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
48
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">描述</h4>
|
|
49
|
+
<p class="text-[11px] leading-relaxed" style="color: #636366;">{{ activeStep.description || activeStep.summary }}</p>
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
52
|
<!-- Conclusion -->
|
|
53
|
-
<div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
54
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
55
|
-
<p class="text-[11px] leading-relaxed" style="color: #
|
|
53
|
+
<div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
54
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">结论</h4>
|
|
55
|
+
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.conclusion }}</p>
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
58
|
<!-- Decision -->
|
|
59
|
-
<div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
60
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
61
|
-
<p class="text-[11px] leading-relaxed" style="color: #
|
|
59
|
+
<div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
60
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">决策</h4>
|
|
61
|
+
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.decision }}</p>
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
64
|
<!-- User Query -->
|
|
65
|
-
<div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
66
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
67
|
-
<div class="px-3 py-2 rounded-md" style="background: #
|
|
68
|
-
<p class="text-[11px] italic" style="color: #
|
|
65
|
+
<div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
66
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">用户提问</h4>
|
|
67
|
+
<div class="px-3 py-2 rounded-md" style="background: #F5F5F7; border: 1px solid #F0F0F3;">
|
|
68
|
+
<p class="text-[11px] italic" style="color: #636366;">"{{ activeStep.userQuery }}"</p>
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
72
|
<!-- Metadata -->
|
|
73
|
-
<div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
74
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
75
|
-
<div class="space-y-1 text-[11px]" style="color: #
|
|
76
|
-
<div v-if="activeStep.duration"><span style="color: #
|
|
77
|
-
<div v-if="activeStep.timestamp"><span style="color: #
|
|
73
|
+
<div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
74
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">元信息</h4>
|
|
75
|
+
<div class="space-y-1 text-[11px]" style="color: #6B7280;">
|
|
76
|
+
<div v-if="activeStep.duration"><span style="color: #636366;">耗时:</span> {{ activeStep.duration }}</div>
|
|
77
|
+
<div v-if="activeStep.timestamp"><span style="color: #636366;">时间:</span> {{ formatTimestamp(activeStep.timestamp) }}</div>
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
81
|
<!-- Output -->
|
|
82
|
-
<div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #
|
|
83
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #
|
|
84
|
-
<
|
|
85
|
-
<pre class="text-[10px] whitespace-pre-wrap font-mono-log" style="color: #8B8B8E;">{{ activeStep.output }}</pre>
|
|
86
|
-
</div>
|
|
82
|
+
<div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
83
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">输出</h4>
|
|
84
|
+
<n-code v-if="activeStep.output" :code="activeStep.output" language="text" word-wrap style="max-height: 160px; overflow-y: auto;" />
|
|
87
85
|
<div v-if="activeStep.files" class="mt-2 space-y-1">
|
|
88
|
-
<div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #
|
|
86
|
+
<div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #6B7280;">
|
|
89
87
|
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
90
88
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
91
89
|
</svg>
|
|
@@ -97,23 +95,40 @@
|
|
|
97
95
|
</div>
|
|
98
96
|
|
|
99
97
|
<!-- Log Stream -->
|
|
100
|
-
<div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #
|
|
98
|
+
<div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #F0F0F3;">
|
|
101
99
|
<LogStream :logs="logs" @clear="$emit('clear-logs')" />
|
|
102
100
|
</div>
|
|
103
101
|
</div>
|
|
104
102
|
</template>
|
|
105
103
|
|
|
106
104
|
<script setup>
|
|
105
|
+
import { computed } from 'vue'
|
|
107
106
|
import StageBadge from './StageBadge.vue'
|
|
108
107
|
import LogStream from './LogStream.vue'
|
|
108
|
+
import GitDetail from './detail/GitDetail.vue'
|
|
109
|
+
import TechDetail from './detail/TechDetail.vue'
|
|
110
|
+
import DocsDetail from './detail/DocsDetail.vue'
|
|
109
111
|
|
|
110
112
|
const props = defineProps({
|
|
111
113
|
isOpen: { type: Boolean, default: true },
|
|
112
114
|
activeStep: { type: Object, default: null },
|
|
113
|
-
logs: { type: Array, default: () => [] }
|
|
115
|
+
logs: { type: Array, default: () => [] },
|
|
116
|
+
detailType: { type: String, default: null },
|
|
117
|
+
detailData: { type: [Object, Array], default: null }
|
|
114
118
|
})
|
|
115
119
|
|
|
116
|
-
const emit = defineEmits(['close', 'clear-logs'])
|
|
120
|
+
const emit = defineEmits(['close', 'clear-logs', 'open-doc-file'])
|
|
121
|
+
|
|
122
|
+
const detailTitle = computed(() => {
|
|
123
|
+
if (props.detailType === 'git') return 'Git 详情'
|
|
124
|
+
if (props.detailType === 'tech') return '技术栈详情'
|
|
125
|
+
if (props.detailType === 'docs') return '文档列表'
|
|
126
|
+
return '详情'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
function handleClose() {
|
|
130
|
+
emit('close')
|
|
131
|
+
}
|
|
117
132
|
|
|
118
133
|
function formatTimestamp(ts) {
|
|
119
134
|
if (!ts) return ''
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="h-full overflow-y-auto px-6 py-4">
|
|
3
3
|
<div v-if="!content && !loading" class="flex items-center justify-center h-full">
|
|
4
|
-
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #
|
|
4
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">选择一个文档查看内容</p>
|
|
5
5
|
</div>
|
|
6
6
|
<div v-else-if="loading" class="flex items-center justify-center h-full">
|
|
7
|
-
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #
|
|
7
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">加载中...</p>
|
|
8
8
|
</div>
|
|
9
9
|
<div v-else class="doc-preview" v-html="renderedContent"></div>
|
|
10
10
|
</div>
|
|
@@ -12,32 +12,149 @@
|
|
|
12
12
|
|
|
13
13
|
<script setup>
|
|
14
14
|
import { computed } from 'vue'
|
|
15
|
+
import { marked } from 'marked'
|
|
15
16
|
|
|
16
17
|
const props = defineProps({
|
|
17
18
|
content: { type: String, default: '' },
|
|
18
19
|
loading: { type: Boolean, default: false }
|
|
19
20
|
})
|
|
20
21
|
|
|
22
|
+
marked.setOptions({
|
|
23
|
+
breaks: true,
|
|
24
|
+
gfm: true
|
|
25
|
+
})
|
|
26
|
+
|
|
21
27
|
const renderedContent = computed(() => {
|
|
22
28
|
if (!props.content) return ''
|
|
23
|
-
return
|
|
29
|
+
return marked.parse(props.content)
|
|
24
30
|
})
|
|
31
|
+
</script>
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.replace(/^# (.+)$/gm, '<h1 style="color:#FBBF24;font-size:18px;font-weight:700;margin:0 0 16px;">$1</h1>')
|
|
32
|
-
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre style="background:#0A0A0B;border:1px solid #1F1F22;border-radius:6px;padding:12px;overflow-x:auto;font-size:12px;margin:8px 0;"><code>$2</code></pre>')
|
|
33
|
-
.replace(/`([^`]+)`/g, '<code style="background:#0A0A0B;padding:1px 4px;border-radius:3px;font-size:11px;">$1</code>')
|
|
34
|
-
.replace(/\*\*([^*]+)\*\*/g, '<strong style="color:#E5E5E5;">$1</strong>')
|
|
35
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color:#FBBF24;">$1</a>')
|
|
36
|
-
.replace(/^- \[x\] (.+)$/gm, '<div style="color:#34D399;">☑ $1</div>')
|
|
37
|
-
.replace(/^- \[ \] (.+)$/gm, '<div style="color:#8B8B8E;">☐ $1</div>')
|
|
38
|
-
.replace(/^- (.+)$/gm, '<div style="padding-left:12px;color:#8B8B8E;">• $1</div>')
|
|
39
|
-
.replace(/\n\n/g, '<br/><br/>')
|
|
40
|
-
.replace(/\n/g, '<br/>')
|
|
41
|
-
return html
|
|
33
|
+
<style scoped>
|
|
34
|
+
.doc-preview {
|
|
35
|
+
font-size: 13px;
|
|
36
|
+
line-height: 1.7;
|
|
37
|
+
color: #374151;
|
|
42
38
|
}
|
|
43
|
-
|
|
39
|
+
|
|
40
|
+
.doc-preview :deep(h1) {
|
|
41
|
+
color: #D97706;
|
|
42
|
+
font-size: 18px;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
margin: 0 0 16px;
|
|
45
|
+
border-bottom: 1px solid #E5E5EA;
|
|
46
|
+
padding-bottom: 8px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.doc-preview :deep(h2) {
|
|
50
|
+
color: #1C1C1E;
|
|
51
|
+
font-size: 15px;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
margin: 20px 0 10px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.doc-preview :deep(h3) {
|
|
57
|
+
color: #D97706;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
margin: 16px 0 8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.doc-preview :deep(p) {
|
|
64
|
+
margin: 8px 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.doc-preview :deep(strong) {
|
|
68
|
+
color: #1C1C1E;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.doc-preview :deep(a) {
|
|
73
|
+
color: #D97706;
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.doc-preview :deep(a:hover) {
|
|
78
|
+
text-decoration: underline;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.doc-preview :deep(code) {
|
|
82
|
+
background: #E5E5EA;
|
|
83
|
+
color: #1C1C1E;
|
|
84
|
+
padding: 1px 5px;
|
|
85
|
+
border-radius: 3px;
|
|
86
|
+
font-size: 12px;
|
|
87
|
+
font-family: 'JetBrains Mono', monospace;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.doc-preview :deep(pre) {
|
|
91
|
+
background: #1C1C1E;
|
|
92
|
+
color: #E5E5E7;
|
|
93
|
+
border: 1px solid #E5E5EA;
|
|
94
|
+
border-radius: 6px;
|
|
95
|
+
padding: 12px 16px;
|
|
96
|
+
overflow-x: auto;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
margin: 8px 0;
|
|
99
|
+
line-height: 1.5;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.doc-preview :deep(pre code) {
|
|
103
|
+
background: none;
|
|
104
|
+
color: inherit;
|
|
105
|
+
padding: 0;
|
|
106
|
+
border-radius: 0;
|
|
107
|
+
font-size: inherit;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.doc-preview :deep(ul),
|
|
111
|
+
.doc-preview :deep(ol) {
|
|
112
|
+
padding-left: 20px;
|
|
113
|
+
margin: 8px 0;
|
|
114
|
+
color: #374151;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.doc-preview :deep(li) {
|
|
118
|
+
margin: 4px 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.doc-preview :deep(blockquote) {
|
|
122
|
+
border-left: 3px solid #D97706;
|
|
123
|
+
padding-left: 12px;
|
|
124
|
+
margin: 12px 0;
|
|
125
|
+
color: #636366;
|
|
126
|
+
font-style: italic;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.doc-preview :deep(table) {
|
|
130
|
+
width: 100%;
|
|
131
|
+
border-collapse: collapse;
|
|
132
|
+
margin: 12px 0;
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.doc-preview :deep(thead th) {
|
|
137
|
+
background: #F0F0F3;
|
|
138
|
+
color: #1C1C1E;
|
|
139
|
+
font-weight: 600;
|
|
140
|
+
text-align: left;
|
|
141
|
+
padding: 8px 12px;
|
|
142
|
+
border: 1px solid #E5E5EA;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.doc-preview :deep(tbody td) {
|
|
146
|
+
padding: 6px 12px;
|
|
147
|
+
border: 1px solid #E5E5EA;
|
|
148
|
+
color: #374151;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.doc-preview :deep(tbody tr:hover) {
|
|
152
|
+
background: #F9FAFB;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.doc-preview :deep(hr) {
|
|
156
|
+
border: none;
|
|
157
|
+
border-top: 1px solid #E5E5EA;
|
|
158
|
+
margin: 16px 0;
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
@@ -1,36 +1,58 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-col h-full">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:key="file.path"
|
|
15
|
-
@click="$emit('select-file', file)"
|
|
16
|
-
class="w-full text-left px-2.5 py-1.5 rounded text-[12px] font-[JetBrains_Mono,monospace] transition-colors"
|
|
17
|
-
:style="{
|
|
18
|
-
color: selectedFile?.path === file.path ? '#FBBF24' : '#8B8B8E',
|
|
19
|
-
background: selectedFile?.path === file.path ? 'rgba(251,191,36,0.08)' : 'transparent'
|
|
20
|
-
}"
|
|
21
|
-
>
|
|
22
|
-
📄 {{ file.title }}
|
|
23
|
-
</button>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
3
|
+
<n-empty v-if="groups.length === 0" description="暂无文档" style="margin: auto;" />
|
|
4
|
+
<n-tree
|
|
5
|
+
v-else
|
|
6
|
+
:data="treeData"
|
|
7
|
+
:selected-keys="selectedKeys"
|
|
8
|
+
:default-expand-all="true"
|
|
9
|
+
selectable
|
|
10
|
+
block-line
|
|
11
|
+
@update:selected-keys="handleSelect"
|
|
12
|
+
style="padding: 8px 12px;"
|
|
13
|
+
/>
|
|
27
14
|
</div>
|
|
28
15
|
</template>
|
|
29
16
|
|
|
30
17
|
<script setup>
|
|
31
|
-
|
|
18
|
+
import { computed, h } from 'vue'
|
|
19
|
+
import { NIcon } from 'naive-ui'
|
|
20
|
+
|
|
21
|
+
const props = defineProps({
|
|
32
22
|
groups: { type: Array, default: () => [] },
|
|
33
23
|
selectedFile: { type: Object, default: null }
|
|
34
24
|
})
|
|
35
|
-
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits(['select-file'])
|
|
27
|
+
|
|
28
|
+
const selectedKeys = computed(() => {
|
|
29
|
+
return props.selectedFile?.path ? [props.selectedFile.path] : []
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const groupIcons = {
|
|
33
|
+
'设计文档': '📋',
|
|
34
|
+
'实现计划': '📐',
|
|
35
|
+
'归档': '📦',
|
|
36
|
+
' proposals': '📝'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const treeData = computed(() => {
|
|
40
|
+
return props.groups.map(group => ({
|
|
41
|
+
key: `group-${group.key}`,
|
|
42
|
+
label: group.label,
|
|
43
|
+
prefix: () => groupIcons[group.label] || '📄',
|
|
44
|
+
children: group.files.map(file => ({
|
|
45
|
+
key: file.path,
|
|
46
|
+
label: file.title,
|
|
47
|
+
prefix: () => '📄',
|
|
48
|
+
data: file
|
|
49
|
+
}))
|
|
50
|
+
}))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
function handleSelect(keys, option) {
|
|
54
|
+
if (option?.[0]?.data) {
|
|
55
|
+
emit('select-file', option[0].data)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
36
58
|
</script>
|
|
@@ -1,51 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col h-full" style="background: #
|
|
2
|
+
<div class="flex flex-col h-full" style="background: #F5F5F7;">
|
|
3
3
|
<!-- Header -->
|
|
4
|
-
<div class="px-3 py-2 flex items-center gap-2" style="border-bottom: 1px solid #
|
|
5
|
-
<input
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
placeholder="过滤日志..."
|
|
9
|
-
class="flex-1 px-2 py-1 rounded-sm text-[10px] font-mono-log outline-none transition-colors duration-100"
|
|
10
|
-
style="background: #141416; border: 1px solid #1F1F22; color: #8B8B8E;"
|
|
11
|
-
/>
|
|
12
|
-
<button
|
|
13
|
-
@click="clearLogs"
|
|
14
|
-
class="px-2 py-1 text-[10px] rounded-sm transition-colors duration-100"
|
|
15
|
-
style="color: #525252; border: 1px solid #1F1F22;"
|
|
16
|
-
>
|
|
17
|
-
清空
|
|
18
|
-
</button>
|
|
19
|
-
<button
|
|
20
|
-
@click="toggleAutoScroll"
|
|
21
|
-
class="px-2 py-1 text-[10px] rounded-sm font-mono-log transition-colors duration-100"
|
|
22
|
-
:style="{
|
|
23
|
-
color: autoScroll ? '#FBBF24' : '#525252',
|
|
24
|
-
background: autoScroll ? 'rgba(251,191,36,0.08)' : 'transparent',
|
|
25
|
-
border: autoScroll ? '1px solid rgba(251,191,36,0.2)' : '1px solid #1F1F22'
|
|
26
|
-
}"
|
|
27
|
-
>
|
|
4
|
+
<div class="px-3 py-2 flex items-center gap-2" style="border-bottom: 1px solid #F0F0F3;">
|
|
5
|
+
<n-input v-model:value="searchQuery" size="tiny" placeholder="过滤日志..." clearable style="flex: 1;" />
|
|
6
|
+
<n-button size="tiny" @click="clearLogs">清空</n-button>
|
|
7
|
+
<n-button size="tiny" :type="autoScroll ? 'primary' : 'default'" @click="toggleAutoScroll">
|
|
28
8
|
{{ autoScroll ? '自动' : '暂停' }}
|
|
29
|
-
</button>
|
|
9
|
+
</n-button>
|
|
30
10
|
</div>
|
|
31
11
|
|
|
32
12
|
<!-- Log output -->
|
|
33
|
-
<div ref="logContainer" class="flex-1 overflow-y-auto px-2 py-1.5 font-mono-log text-[10px]" style="background: #
|
|
13
|
+
<div ref="logContainer" class="flex-1 overflow-y-auto px-2 py-1.5 font-mono-log text-[10px]" style="background: #F0F0F3;" @scroll="handleScroll">
|
|
34
14
|
<div v-if="filteredLogs.length === 0" class="flex items-center justify-center h-full">
|
|
35
|
-
<span class="font-mono-log" style="color: #
|
|
15
|
+
<span class="font-mono-log" style="color: #E5E5EA;">{{ logs.length === 0 ? '暂无日志' : '无匹配' }}</span>
|
|
36
16
|
</div>
|
|
37
17
|
<div v-else class="space-y-px">
|
|
38
18
|
<div v-for="log in filteredLogs" :key="log.id" class="px-1.5 py-px rounded-sm" :style="{ background: logBg(log.type) }">
|
|
39
|
-
<span style="color: #
|
|
19
|
+
<span style="color: #D1D1D6;" class="select-none">[{{ formatTime(log.timestamp) }}]</span>
|
|
40
20
|
<span :style="{ color: logColor(log.type) }">{{ escapeHtml(log.content) }}</span>
|
|
41
21
|
</div>
|
|
42
22
|
</div>
|
|
43
23
|
</div>
|
|
44
24
|
|
|
45
25
|
<!-- Footer -->
|
|
46
|
-
<div class="px-3 py-1 flex items-center justify-between text-[9px] font-mono-log" style="border-top: 1px solid #
|
|
26
|
+
<div class="px-3 py-1 flex items-center justify-between text-[9px] font-mono-log" style="border-top: 1px solid #F0F0F3; background: #F5F5F7; color: #D1D1D6;">
|
|
47
27
|
<span>{{ filteredLogs.length }}/{{ logs.length }}</span>
|
|
48
|
-
<span v-if="!autoScroll" style="color: #
|
|
28
|
+
<span v-if="!autoScroll" style="color: #EA580C;">已暂停</span>
|
|
49
29
|
</div>
|
|
50
30
|
</div>
|
|
51
31
|
</template>
|
|
@@ -69,7 +49,7 @@ const filteredLogs = computed(() => {
|
|
|
69
49
|
function formatTime(ts) { if (!ts) return ''; const d = new Date(ts); return d.toLocaleTimeString('zh-CN', { hour12: false }) }
|
|
70
50
|
function escapeHtml(t) { if (!t) return ''; return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') }
|
|
71
51
|
function logBg(t) { return t === 'error' ? 'rgba(239,68,68,0.05)' : t === 'warn' ? 'rgba(251,146,60,0.05)' : 'transparent' }
|
|
72
|
-
function logColor(t) { return t === 'error' ? '#
|
|
52
|
+
function logColor(t) { return t === 'error' ? '#DC2626' : t === 'warn' ? '#EA580C' : t === 'debug' ? '#6B7280' : '#636366' }
|
|
73
53
|
function clearLogs() { emit('clear') }
|
|
74
54
|
function toggleAutoScroll() { autoScroll.value = !autoScroll.value; if (autoScroll.value) scrollToBottom() }
|
|
75
55
|
function handleScroll() {
|