@vibe-forge/client 0.2.0-alpha.9 → 0.4.0

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 (166) hide show
  1. package/cli.cjs +1 -1
  2. package/dist/assets/{arc-CybT1Fs2.js → arc-DgIxeTMg.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-BY5Aoa-D.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-F42hTbzS.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
  5. package/dist/assets/channel-DhtnrNJ6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-D-tIPp-3.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-J57aCe6u.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
  8. package/dist/assets/clone-7bHB6YkC.js +1 -0
  9. package/dist/assets/{createText-1719965b-ByfEqOF-.js → createText-1719965b-C1_HJcCc.js} +1 -1
  10. package/dist/assets/devicon-BWlTeAUU.woff +0 -0
  11. package/dist/assets/devicon-CirD-cQx.ttf +0 -0
  12. package/dist/assets/devicon-Dg8iWy0i.svg +1211 -0
  13. package/dist/assets/devicon-TqfHp33-.eot +0 -0
  14. package/dist/assets/{edges-96097737-CMEArkOa.js → edges-96097737-BU8qStzd.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-Cf8mX2aj.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-DG6WKyo7.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-CstUxz-w.js → flowDiagram-50d868cf-CSDi0-RD.js} +1 -1
  18. package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +1 -0
  19. package/dist/assets/{flowchart-elk-definition-6af322e1--4CRoQ-H.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-DYgHcKd-.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DDSVpfsd.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
  22. package/dist/assets/{graph-CRWF39gX.js → graph-CIkpD4Kx.js} +1 -1
  23. package/dist/assets/{index-5325376f-W1hft795.js → index-5325376f-aVVRRTIu.js} +1 -1
  24. package/dist/assets/index-D1giUI7r.css +1 -0
  25. package/dist/assets/index-DRSI_ZIL.js +514 -0
  26. package/dist/assets/{infoDiagram-8eee0895-D4SHcix6.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-MWgCkVoE.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
  28. package/dist/assets/{layout-C88ObkCf.js → layout-Tnmha8Nh.js} +1 -1
  29. package/dist/assets/{line-C7WAYMt5.js → line-BQR2SOyl.js} +1 -1
  30. package/dist/assets/{linear-C4msxfcU.js → linear-DlG0eemV.js} +1 -1
  31. package/dist/assets/{mermaid.core-Cabag9SZ.js → mermaid.core-BnwYO0He.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-CeS8ETXx.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-BvjyKnq5.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-DzYvpbNM.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-DHIoDbyo.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-BFSGnQGs.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-_LM3BJ5-.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-DwORjOzl.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-B4cAWWz1.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-D_rmV3B_.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-BFx4VuFc.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-BE3106vL.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-DwDTO1op.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-C4b8qUQZ.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-BRJ9Z4u-.js → xychartDiagram-f5964ef8-BJhXS4dG.js} +1 -1
  46. package/dist/index.html +2 -7
  47. package/index.html +0 -5
  48. package/package.json +11 -6
  49. package/src/App.tsx +2 -0
  50. package/src/api/README.md +26 -0
  51. package/src/api/automation.ts +88 -0
  52. package/src/api/base.ts +54 -0
  53. package/src/api/benchmark.ts +45 -0
  54. package/src/api/config.ts +24 -0
  55. package/src/api/knowledge.ts +72 -0
  56. package/src/api/projects.ts +15 -0
  57. package/src/api/sessions.ts +82 -0
  58. package/src/api/types.ts +20 -0
  59. package/src/api.ts +44 -241
  60. package/src/components/AutomationView/AutomationView.scss +5 -1
  61. package/src/components/AutomationView/RuleFormPanel.tsx +3 -2
  62. package/src/components/AutomationView/TaskList.scss +4 -6
  63. package/src/components/AutomationView/TaskList.tsx +2 -1
  64. package/src/components/AutomationView/TriggerList.scss +4 -1
  65. package/src/components/BenchmarkView/BenchmarkCasePanel.scss +267 -0
  66. package/src/components/BenchmarkView/BenchmarkCasePanel.tsx +309 -0
  67. package/src/components/BenchmarkView/BenchmarkSidebar.scss +182 -0
  68. package/src/components/BenchmarkView/BenchmarkSidebar.tsx +262 -0
  69. package/src/components/BenchmarkView/BenchmarkView.scss +78 -0
  70. package/src/components/BenchmarkView/index.tsx +197 -0
  71. package/src/components/BenchmarkView/types.ts +10 -0
  72. package/src/components/BenchmarkView/utils.ts +21 -0
  73. package/src/components/Chat.tsx +37 -29
  74. package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
  75. package/src/components/ConfigView.tsx +13 -1
  76. package/src/components/{chat/MarkdownContent.tsx → MarkdownContent.tsx} +1 -1
  77. package/src/components/NavRail.tsx +7 -0
  78. package/src/components/chat/ChatHeader.scss +37 -19
  79. package/src/components/chat/ChatHeader.tsx +6 -9
  80. package/src/components/chat/ChatHistoryView.tsx +89 -45
  81. package/src/components/chat/CurrentTodoList.tsx +10 -9
  82. package/src/components/chat/{MessageItem.scss → Messages/MessageItem.scss} +14 -0
  83. package/src/components/chat/{MessageItem.tsx → Messages/MessageItem.tsx} +30 -8
  84. package/src/components/chat/{messageUtils.ts → Messages/message-utils.ts} +1 -1
  85. package/src/components/chat/NewSessionGuide.scss +35 -13
  86. package/src/components/chat/NewSessionGuide.tsx +20 -10
  87. package/src/components/chat/{Sender.scss → Sender/Sender.scss} +80 -0
  88. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -5
  89. package/src/components/chat/tools/DefaultTool.tsx +184 -21
  90. package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
  91. package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
  92. package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
  93. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
  94. package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
  95. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
  96. package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
  97. package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
  98. package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
  99. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
  100. package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
  101. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
  102. package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
  103. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
  104. package/src/components/chat/tools/adapter-claude/index.ts +4 -10
  105. package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
  106. package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
  107. package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
  108. package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
  109. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
  110. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
  111. package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
  112. package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
  113. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
  114. package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
  115. package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
  116. package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
  117. package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
  118. package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
  119. package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
  120. package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
  121. package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
  122. package/src/components/config/AppSettingsPanel.tsx +33 -0
  123. package/src/components/config/ConfigSectionForm.tsx +12 -1
  124. package/src/components/config/channelDefinitions.ts +6 -0
  125. package/src/components/config/configSchema.ts +10 -1
  126. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  127. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  128. package/src/components/config/recordEditors/index.tsx +1 -0
  129. package/src/components/knowledge-base/KnowledgeBaseView.tsx +51 -3
  130. package/src/components/knowledge-base/components/RuleItem.tsx +79 -0
  131. package/src/components/knowledge-base/components/RuleList.scss +5 -0
  132. package/src/components/knowledge-base/components/RuleList.tsx +70 -0
  133. package/src/components/knowledge-base/components/RulesTab.tsx +32 -7
  134. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  135. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  136. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +65 -16
  137. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  138. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  139. package/src/hooks/chat/use-chat-session-actions.ts +147 -0
  140. package/src/hooks/chat/use-chat-session-messages.ts +250 -0
  141. package/src/hooks/chat/use-chat-session.ts +57 -0
  142. package/src/hooks/chat/use-chat-view.ts +39 -0
  143. package/src/main.tsx +10 -13
  144. package/src/resources/locales/en.json +73 -0
  145. package/src/resources/locales/zh.json +73 -0
  146. package/src/runtime-config.ts +52 -0
  147. package/src/store/index.ts +2 -0
  148. package/src/vite-env.d.ts +11 -0
  149. package/src/ws.ts +5 -3
  150. package/vite.config.ts +12 -4
  151. package/dist/assets/channel-DrWdSpqV.js +0 -1
  152. package/dist/assets/clone-D0cC8LLB.js +0 -1
  153. package/dist/assets/flowDiagram-v2-4f6560a1-Bf_DH7dp.js +0 -1
  154. package/dist/assets/index-CNMzWvKV.js +0 -497
  155. package/dist/assets/index-PEmISxiy.css +0 -1
  156. package/src/components/chat/ToolCallBox.scss +0 -137
  157. package/src/components/chat/useChatSession.ts +0 -370
  158. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  159. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  160. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  161. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  162. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  163. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  164. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  165. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  166. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -0,0 +1,267 @@
1
+ .benchmark-view {
2
+ // Case header
3
+ &__case-header {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 8px;
7
+ padding: 4px 0 16px;
8
+ }
9
+
10
+ &__case-title-row {
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ align-items: center;
14
+ justify-content: space-between;
15
+ gap: 10px;
16
+ }
17
+
18
+ &__case-title.ant-typography {
19
+ margin: 0;
20
+ }
21
+
22
+ &__case-desc.ant-typography {
23
+ margin: 0;
24
+ }
25
+
26
+ &__case-meta {
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ gap: 12px;
30
+ }
31
+
32
+ &__meta-item {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: 4px;
36
+ font-size: 12px;
37
+ color: var(--sub-text-color);
38
+ }
39
+
40
+ // Sections
41
+ &__section {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 12px;
45
+ padding: 16px 0;
46
+ }
47
+
48
+ &__section--last {
49
+ padding-bottom: 24px;
50
+ }
51
+
52
+ &__twin-grid {
53
+ display: grid;
54
+ grid-template-columns: 1fr 1fr;
55
+ gap: 24px;
56
+ align-items: start;
57
+ }
58
+
59
+ &__goal-body {
60
+ max-height: 360px;
61
+ overflow-y: auto;
62
+ overflow-x: hidden;
63
+ }
64
+
65
+ &__task-goal {
66
+ min-width: 0;
67
+ }
68
+
69
+ &__section-label {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ gap: 12px;
74
+ }
75
+
76
+ &__section-title {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: 6px;
80
+ font-weight: 600;
81
+ font-size: 14px;
82
+ color: var(--text-color);
83
+ }
84
+
85
+ &__section-icon {
86
+ font-size: 16px;
87
+ color: var(--primary-color);
88
+ }
89
+
90
+ // Info grid
91
+ &__info-grid {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 10px;
95
+ }
96
+
97
+ &__info-row {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: flex-start;
101
+ gap: 16px;
102
+ font-size: 13px;
103
+
104
+ label {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ gap: 4px;
108
+ color: var(--sub-text-color);
109
+ flex-shrink: 0;
110
+ }
111
+
112
+ &--block {
113
+ flex-direction: column;
114
+ align-items: flex-start;
115
+ }
116
+
117
+ code,
118
+ span:last-child {
119
+ max-width: 70%;
120
+ text-align: right;
121
+ white-space: pre-wrap;
122
+ overflow-wrap: anywhere;
123
+ color: var(--text-color);
124
+ }
125
+ }
126
+
127
+ &__hint {
128
+ font-size: 13px;
129
+ }
130
+
131
+ // Run state
132
+ &__run-block {
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: 12px;
136
+ }
137
+
138
+ &__run-metric {
139
+ display: flex;
140
+ align-items: baseline;
141
+ justify-content: space-between;
142
+ gap: 12px;
143
+ font-size: 13px;
144
+
145
+ label {
146
+ color: var(--sub-text-color);
147
+ }
148
+
149
+ strong {
150
+ font-size: 18px;
151
+ line-height: 1;
152
+ }
153
+ }
154
+
155
+ // Result
156
+ &__result {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 16px;
160
+ }
161
+
162
+ &__score-strip {
163
+ display: grid;
164
+ grid-template-columns: repeat(4, minmax(0, 1fr));
165
+ gap: 10px;
166
+ }
167
+
168
+ &__score-item {
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 4px;
172
+ padding: 10px 12px;
173
+ border-radius: 8px;
174
+ border: 1px solid var(--border-color);
175
+
176
+ label {
177
+ font-size: 12px;
178
+ color: var(--sub-text-color);
179
+ }
180
+
181
+ strong {
182
+ font-size: 20px;
183
+ line-height: 1;
184
+ color: var(--text-color);
185
+ }
186
+ }
187
+
188
+ &__judge-summary.ant-typography {
189
+ margin: 0;
190
+ font-size: 13px;
191
+ }
192
+
193
+ &__subgrid {
194
+ display: grid;
195
+ grid-template-columns: repeat(2, minmax(0, 1fr));
196
+ gap: 12px;
197
+ }
198
+
199
+ &__subpanel {
200
+ border-radius: 8px;
201
+ border: 1px solid var(--border-color);
202
+ background: var(--sub-bg-color);
203
+ padding: 12px;
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 10px;
207
+ }
208
+
209
+ &__subpanel-title {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ gap: 6px;
213
+ font-size: 13px;
214
+ font-weight: 600;
215
+ color: var(--text-color);
216
+ }
217
+
218
+ &__file-list {
219
+ display: flex;
220
+ flex-wrap: wrap;
221
+ gap: 6px;
222
+ }
223
+
224
+ &__file-tag {
225
+ padding: 3px 8px;
226
+ border-radius: 4px;
227
+ background: var(--code-bg);
228
+ color: var(--text-color);
229
+ font-size: 12px;
230
+ font-family: 'SFMono-Regular', 'SF Mono', Monaco, Consolas, monospace;
231
+ }
232
+
233
+ &__issues-list {
234
+ margin: 0;
235
+ padding-left: 16px;
236
+ font-size: 13px;
237
+
238
+ li + li {
239
+ margin-top: 6px;
240
+ }
241
+ }
242
+
243
+ // Ensure Material Symbols font renders inside markdown-body
244
+ .markdown-body .material-symbols-rounded {
245
+ font-family: 'Material Symbols Rounded';
246
+ }
247
+
248
+ // Status tag (shown next to result section label)
249
+ &__status-tag {
250
+ display: inline-flex;
251
+ align-items: center;
252
+ gap: 4px;
253
+ margin-inline-end: 0;
254
+ }
255
+
256
+ &__tag-icon {
257
+ font-size: 14px;
258
+ }
259
+ }
260
+
261
+ @media (max-width: 1280px) {
262
+ .benchmark-view {
263
+ &__score-strip {
264
+ grid-template-columns: repeat(2, minmax(0, 1fr));
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,309 @@
1
+ import './BenchmarkCasePanel.scss'
2
+
3
+ import { Button, Divider, Empty, Progress, Tag, Typography } from 'antd'
4
+ import React from 'react'
5
+ import { useTranslation } from 'react-i18next'
6
+
7
+ import type { BenchmarkCase, BenchmarkResult, BenchmarkRunSummary } from '@vibe-forge/core'
8
+ import { MarkdownContent } from '#~/components/MarkdownContent'
9
+
10
+ import { formatTimestamp, getResultStatusMeta } from './utils.js'
11
+
12
+ function ResultStatusTag({ result }: { result: BenchmarkResult | null | undefined }) {
13
+ const { t } = useTranslation()
14
+ const meta = getResultStatusMeta(result)
15
+ const colorMap: Record<string, string> = {
16
+ 'no-result': 'default',
17
+ pass: 'success',
18
+ partial: 'warning',
19
+ fail: 'error'
20
+ }
21
+ return (
22
+ <Tag color={colorMap[meta.statusKey]} className='benchmark-view__status-tag'>
23
+ <span className='material-symbols-rounded benchmark-view__tag-icon'>{meta.icon}</span>
24
+ {meta.statusKey === 'no-result' ? t('benchmark.noResult') : t(`benchmark.status.${meta.statusKey}`)}
25
+ </Tag>
26
+ )
27
+ }
28
+
29
+ function SectionLabel({ icon, title, extra }: { icon: string; title: string; extra?: React.ReactNode }) {
30
+ return (
31
+ <div className='benchmark-view__section-label'>
32
+ <div className='benchmark-view__section-title'>
33
+ <span className='material-symbols-rounded benchmark-view__section-icon'>{icon}</span>
34
+ <span>{title}</span>
35
+ </div>
36
+ {extra != null ? <div className='benchmark-view__section-extra'>{extra}</div> : null}
37
+ </div>
38
+ )
39
+ }
40
+
41
+ // ─── Component ────────────────────────────────────────────────────────────────
42
+
43
+ interface BenchmarkCasePanelProps {
44
+ selectedCase: BenchmarkCase | null
45
+ latestResult: BenchmarkResult | null
46
+ activeRun: BenchmarkRunSummary | null | undefined
47
+ activeRunId: string
48
+ progressPercent: number
49
+ onRunCase: () => void
50
+ }
51
+
52
+ export function BenchmarkCasePanel({
53
+ selectedCase,
54
+ latestResult,
55
+ activeRun,
56
+ activeRunId,
57
+ progressPercent,
58
+ onRunCase
59
+ }: BenchmarkCasePanelProps) {
60
+ const { t } = useTranslation()
61
+
62
+ if (selectedCase == null) {
63
+ return (
64
+ <div className='benchmark-view__empty'>
65
+ <Empty description={t('benchmark.emptyCases')} />
66
+ </div>
67
+ )
68
+ }
69
+
70
+ const displayTitle = selectedCase.frontmatter.title ?? selectedCase.title
71
+ const displayDescription = selectedCase.frontmatter.description ?? selectedCase.summary ?? ''
72
+
73
+ return (
74
+ <div className='benchmark-view__canvas'>
75
+ <div className='benchmark-view__case-header'>
76
+ <div className='benchmark-view__case-title-row'>
77
+ <Typography.Title level={3} className='benchmark-view__case-title'>
78
+ {displayTitle}
79
+ </Typography.Title>
80
+ <Button
81
+ type='primary'
82
+ icon={<span className='material-symbols-rounded'>play_circle</span>}
83
+ onClick={onRunCase}
84
+ />
85
+ </div>
86
+ {displayDescription
87
+ ? (
88
+ <Typography.Paragraph type='secondary' className='benchmark-view__case-desc'>
89
+ {displayDescription}
90
+ </Typography.Paragraph>
91
+ )
92
+ : null}
93
+ <div className='benchmark-view__case-meta'>
94
+ <span className='benchmark-view__meta-item'>
95
+ <span className='material-symbols-rounded'>history</span>
96
+ {t('benchmark.lastRunAt')}
97
+ {': '}
98
+ {formatTimestamp(latestResult?.timestamp)}
99
+ </span>
100
+ </div>
101
+ </div>
102
+
103
+ <Divider className='benchmark-view__sep' />
104
+
105
+ {/* Config + Goal */}
106
+ <div className='benchmark-view__twin-grid'>
107
+ <div className='benchmark-view__section'>
108
+ <SectionLabel icon='tune' title={t('benchmark.configTitle')} />
109
+ <div className='benchmark-view__info-grid'>
110
+ <div className='benchmark-view__info-row'>
111
+ <label>
112
+ <span className='material-symbols-rounded'>commit</span>
113
+ {t('benchmark.baseCommit')}
114
+ </label>
115
+ <code>{selectedCase.frontmatter.baseCommit}</code>
116
+ </div>
117
+ <div className='benchmark-view__info-row'>
118
+ <label>
119
+ <span className='material-symbols-rounded'>timer</span>
120
+ {t('benchmark.timeoutSec')}
121
+ </label>
122
+ <span>{selectedCase.frontmatter.timeoutSec}</span>
123
+ </div>
124
+ <div className='benchmark-view__info-row benchmark-view__info-row--block'>
125
+ <label>
126
+ <span className='material-symbols-rounded'>build</span>
127
+ {t('benchmark.setupCommand')}
128
+ </label>
129
+ <MarkdownContent content={`\`\`\`bash\n${selectedCase.frontmatter.setupCommand}\n\`\`\``} />
130
+ </div>
131
+ <div className='benchmark-view__info-row benchmark-view__info-row--block'>
132
+ <label>
133
+ <span className='material-symbols-rounded'>science</span>
134
+ {t('benchmark.testCommand')}
135
+ </label>
136
+ <MarkdownContent content={`\`\`\`bash\n${selectedCase.frontmatter.testCommand}\n\`\`\``} />
137
+ </div>
138
+ </div>
139
+ </div>
140
+
141
+ <div className='benchmark-view__section benchmark-view__task-goal'>
142
+ <SectionLabel icon='assignment' title={t('benchmark.taskGoal')} />
143
+ <div className='benchmark-view__goal-body'>
144
+ <MarkdownContent content={selectedCase.rfcBody} />
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <Divider className='benchmark-view__sep' />
150
+
151
+ {/* Run state + Result */}
152
+ <div className='benchmark-view__twin-grid benchmark-view__section--last'>
153
+ <RunStateSection
154
+ activeRunId={activeRunId}
155
+ activeRun={activeRun}
156
+ progressPercent={progressPercent}
157
+ t={t}
158
+ />
159
+ <ResultSection latestResult={latestResult} t={t} />
160
+ </div>
161
+ </div>
162
+ )
163
+ }
164
+
165
+ // ─── Sub-sections ─────────────────────────────────────────────────────────────
166
+
167
+ function RunStateSection({
168
+ activeRunId,
169
+ activeRun,
170
+ progressPercent,
171
+ t
172
+ }: {
173
+ activeRunId: string
174
+ activeRun: BenchmarkRunSummary | null | undefined
175
+ progressPercent: number
176
+ t: ReturnType<typeof useTranslation>['t']
177
+ }) {
178
+ return (
179
+ <div className='benchmark-view__section'>
180
+ <SectionLabel
181
+ icon='pulse_alert'
182
+ title={t('benchmark.runState')}
183
+ extra={activeRun ? <Tag>{activeRun.status}</Tag> : null}
184
+ />
185
+ {activeRunId === ''
186
+ ? (
187
+ <Typography.Text type='secondary' className='benchmark-view__hint'>
188
+ {t('benchmark.noActiveRun')}
189
+ </Typography.Text>
190
+ )
191
+ : (
192
+ <div className='benchmark-view__run-block'>
193
+ <div className='benchmark-view__run-metric'>
194
+ <label>{t('benchmark.progress')}</label>
195
+ <strong>{`${activeRun?.completedCount ?? 0}/${activeRun?.totalCount ?? '-'}`}</strong>
196
+ </div>
197
+ <Progress percent={progressPercent} showInfo={false} />
198
+ <div className='benchmark-view__info-grid'>
199
+ <div className='benchmark-view__info-row'>
200
+ <label>{t('benchmark.runStatus')}</label>
201
+ <span>{activeRun?.status ?? '-'}</span>
202
+ </div>
203
+ <div className='benchmark-view__info-row'>
204
+ <label>{t('benchmark.lastMessage')}</label>
205
+ <Typography.Text>{activeRun?.lastMessage ?? '-'}</Typography.Text>
206
+ </div>
207
+ </div>
208
+ </div>
209
+ )}
210
+ </div>
211
+ )
212
+ }
213
+
214
+ function ResultSection({
215
+ latestResult,
216
+ t
217
+ }: {
218
+ latestResult: BenchmarkResult | null
219
+ t: ReturnType<typeof useTranslation>['t']
220
+ }) {
221
+ return (
222
+ <div className='benchmark-view__section'>
223
+ <SectionLabel
224
+ icon='insights'
225
+ title={t('benchmark.resultTitle')}
226
+ extra={latestResult ? <ResultStatusTag result={latestResult} /> : null}
227
+ />
228
+ {latestResult == null
229
+ ? (
230
+ <Typography.Text type='secondary' className='benchmark-view__hint'>
231
+ {t('benchmark.noResult')}
232
+ </Typography.Text>
233
+ )
234
+ : (
235
+ <div className='benchmark-view__result'>
236
+ <div className='benchmark-view__score-strip'>
237
+ <div className='benchmark-view__score-item'>
238
+ <label>{t('benchmark.finalScore')}</label>
239
+ <strong>{latestResult.finalScore}</strong>
240
+ </div>
241
+ <div className='benchmark-view__score-item'>
242
+ <label>{t('benchmark.testScore')}</label>
243
+ <strong>{latestResult.scores.testScore}</strong>
244
+ </div>
245
+ <div className='benchmark-view__score-item'>
246
+ <label>{t('benchmark.goalScore')}</label>
247
+ <strong>{latestResult.scores.goalScore}</strong>
248
+ </div>
249
+ <div className='benchmark-view__score-item'>
250
+ <label>{t('benchmark.referenceScore')}</label>
251
+ <strong>{latestResult.scores.referenceScore}</strong>
252
+ </div>
253
+ </div>
254
+ <div className='benchmark-view__info-grid'>
255
+ <div className='benchmark-view__info-row'>
256
+ <label>
257
+ <span className='material-symbols-rounded'>schedule</span>
258
+ {t('benchmark.durationMs')}
259
+ </label>
260
+ <span>{latestResult.durationMs}</span>
261
+ </div>
262
+ <div className='benchmark-view__info-row'>
263
+ <label>
264
+ <span className='material-symbols-rounded'>monitoring</span>
265
+ {t('benchmark.testExitCode')}
266
+ </label>
267
+ <span>{latestResult.testExitCode}</span>
268
+ </div>
269
+ </div>
270
+ <Typography.Paragraph className='benchmark-view__judge-summary'>
271
+ {latestResult.judgeSummary}
272
+ </Typography.Paragraph>
273
+ <div className='benchmark-view__subgrid'>
274
+ <div className='benchmark-view__subpanel'>
275
+ <div className='benchmark-view__subpanel-title'>
276
+ <span className='material-symbols-rounded'>plagiarism</span>
277
+ <span>{t('benchmark.changedFiles')}</span>
278
+ </div>
279
+ {latestResult.changedFiles != null && latestResult.changedFiles.length > 0
280
+ ? (
281
+ <div className='benchmark-view__file-list'>
282
+ {latestResult.changedFiles.map(file => (
283
+ <span key={file} className='benchmark-view__file-tag'>{file}</span>
284
+ ))}
285
+ </div>
286
+ )
287
+ : (
288
+ <Typography.Text type='secondary'>{t('benchmark.noChangedFiles')}</Typography.Text>
289
+ )}
290
+ </div>
291
+ <div className='benchmark-view__subpanel'>
292
+ <div className='benchmark-view__subpanel-title'>
293
+ <span className='material-symbols-rounded'>report</span>
294
+ <span>{t('benchmark.issues')}</span>
295
+ </div>
296
+ {latestResult.issues.length === 0
297
+ ? <Typography.Text type='secondary'>{t('benchmark.noIssues')}</Typography.Text>
298
+ : (
299
+ <ul className='benchmark-view__issues-list'>
300
+ {latestResult.issues.map(issue => <li key={issue}>{issue}</li>)}
301
+ </ul>
302
+ )}
303
+ </div>
304
+ </div>
305
+ </div>
306
+ )}
307
+ </div>
308
+ )
309
+ }