@vibe-forge/client 0.3.0 → 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 (158) hide show
  1. package/cli.cjs +1 -1
  2. package/dist/assets/{arc-CwMXUVsq.js → arc-DgIxeTMg.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
  5. package/dist/assets/channel-DhtnrNJ6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
  8. package/dist/assets/clone-7bHB6YkC.js +1 -0
  9. package/dist/assets/{createText-1719965b-2XqnWjQY.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-B7e32Jeg.js → edges-96097737-BU8qStzd.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.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-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
  22. package/dist/assets/{graph-BKbBNGPf.js → graph-CIkpD4Kx.js} +1 -1
  23. package/dist/assets/{index-5325376f-BK7F9nSl.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-BLFL77_D.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
  28. package/dist/assets/{layout-By3JZZGt.js → layout-Tnmha8Nh.js} +1 -1
  29. package/dist/assets/{line-9GUsXbwv.js → line-BQR2SOyl.js} +1 -1
  30. package/dist/assets/{linear-DzGV4E9N.js → linear-DlG0eemV.js} +1 -1
  31. package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-BnwYO0He.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.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 -269
  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 +7 -0
  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/{Sender.scss → Sender/Sender.scss} +80 -0
  86. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -5
  87. package/src/components/chat/tools/DefaultTool.tsx +184 -21
  88. package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
  89. package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
  90. package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
  91. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
  92. package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
  93. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
  94. package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
  95. package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
  96. package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
  97. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
  98. package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
  99. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
  100. package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
  101. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
  102. package/src/components/chat/tools/adapter-claude/index.ts +4 -10
  103. package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
  104. package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
  105. package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
  106. package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
  107. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
  108. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
  109. package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
  110. package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
  111. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
  112. package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
  113. package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
  114. package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
  115. package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
  116. package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
  117. package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
  118. package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
  119. package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
  120. package/src/components/config/ConfigSectionForm.tsx +12 -1
  121. package/src/components/config/channelDefinitions.ts +6 -0
  122. package/src/components/config/configSchema.ts +10 -1
  123. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  124. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  125. package/src/components/config/recordEditors/index.tsx +1 -0
  126. package/src/components/knowledge-base/components/RuleItem.tsx +1 -1
  127. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  128. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  129. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +46 -15
  130. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  131. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  132. package/src/hooks/chat/use-chat-session-actions.ts +147 -0
  133. package/src/hooks/chat/use-chat-session-messages.ts +250 -0
  134. package/src/hooks/chat/use-chat-session.ts +57 -0
  135. package/src/hooks/chat/use-chat-view.ts +39 -0
  136. package/src/main.tsx +10 -13
  137. package/src/resources/locales/en.json +66 -0
  138. package/src/resources/locales/zh.json +66 -0
  139. package/src/runtime-config.ts +52 -0
  140. package/src/vite-env.d.ts +11 -0
  141. package/src/ws.ts +5 -3
  142. package/vite.config.ts +12 -4
  143. package/dist/assets/channel-jbCEHqbG.js +0 -1
  144. package/dist/assets/clone-CCRKqS4L.js +0 -1
  145. package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
  146. package/dist/assets/index-B0qfCb1G.css +0 -1
  147. package/dist/assets/index-CNo75dYr.js +0 -497
  148. package/src/components/chat/ToolCallBox.scss +0 -137
  149. package/src/components/chat/useChatSession.ts +0 -370
  150. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  151. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  152. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  153. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  154. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  155. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  156. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  157. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  158. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -0,0 +1,197 @@
1
+ import './BenchmarkView.scss'
2
+
3
+ import { App } from 'antd'
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
+ import { useTranslation } from 'react-i18next'
6
+ import useSWR from 'swr'
7
+
8
+ import {
9
+ getBenchmarkResult,
10
+ getBenchmarkRun,
11
+ listBenchmarkCases,
12
+ listBenchmarkCategories,
13
+ startBenchmarkRun
14
+ } from '#~/api.js'
15
+ import { useQueryParams } from '#~/hooks/useQueryParams.js'
16
+ import type { BenchmarkCase } from '@vibe-forge/core'
17
+
18
+ import { BenchmarkCasePanel } from './BenchmarkCasePanel.js'
19
+ import { BenchmarkSidebar } from './BenchmarkSidebar.js'
20
+ import type { BenchmarkQueryParams } from './types.js'
21
+ import { isTerminalRun } from './utils.js'
22
+
23
+ export function BenchmarkView() {
24
+ const { t } = useTranslation()
25
+ const { message } = App.useApp()
26
+
27
+ // State
28
+ const [activeRunId, setActiveRunId] = useState('')
29
+ const [treeQuery, setTreeQuery] = useState('')
30
+ const [expandedKeys, setExpandedKeys] = useState<string[]>([])
31
+ const [checkedKeys, setCheckedKeys] = useState<string[]>([])
32
+
33
+ const { values, update } = useQueryParams<BenchmarkQueryParams>({
34
+ keys: ['category', 'title'],
35
+ defaults: { category: '', title: '' },
36
+ omit: {
37
+ category: (value: string) => value === '',
38
+ title: (value: string) => value === ''
39
+ }
40
+ })
41
+
42
+ // Data
43
+ const { data: categoriesData, mutate: mutateCategories } = useSWR(
44
+ '/api/benchmark/categories',
45
+ listBenchmarkCategories
46
+ )
47
+ const categories = categoriesData?.categories ?? []
48
+
49
+ const { data: casesData, mutate: mutateCases } = useSWR(
50
+ '/api/benchmark/cases',
51
+ () => listBenchmarkCases()
52
+ )
53
+ const cases = casesData?.cases ?? []
54
+
55
+ const selectedCase = useMemo<BenchmarkCase | null>(() => {
56
+ if (cases.length === 0) return null
57
+ const exact = cases.find(item => item.category === values.category && item.title === values.title)
58
+ if (exact) return exact
59
+ const sameCategoryFirst = values.category
60
+ ? cases.find(item => item.category === values.category) ?? null
61
+ : null
62
+ return sameCategoryFirst ?? cases[0] ?? null
63
+ }, [cases, values.category, values.title])
64
+
65
+ const { data: resultData, mutate: mutateResult } = useSWR(
66
+ selectedCase ? `/api/benchmark/results/${selectedCase.category}/${selectedCase.title}` : null,
67
+ () => getBenchmarkResult(selectedCase?.category ?? '', selectedCase?.title ?? '')
68
+ )
69
+ const latestResult = resultData?.result ?? selectedCase?.latestResult ?? null
70
+
71
+ const { data: runData, mutate: mutateRun } = useSWR(
72
+ activeRunId ? `/api/benchmark/runs/${activeRunId}` : null,
73
+ () => getBenchmarkRun(activeRunId),
74
+ { refreshInterval: activeRunId ? 2000 : 0 }
75
+ )
76
+ const activeRun = runData?.run
77
+
78
+ const progressPercent = useMemo(() => {
79
+ const totalCount = activeRun?.totalCount ?? 0
80
+ if (totalCount <= 0) return 0
81
+ const completedCount = activeRun?.completedCount ?? 0
82
+ return Math.min(100, Math.round((completedCount / totalCount) * 100))
83
+ }, [activeRun?.completedCount, activeRun?.totalCount])
84
+
85
+ // Handlers
86
+ const handleRunSpecificCase = useCallback(async (item: BenchmarkCase) => {
87
+ try {
88
+ const res = await startBenchmarkRun({ category: item.category, title: item.title })
89
+ setActiveRunId(res.run.runId)
90
+ void message.success(t('benchmark.runStarted'))
91
+ } catch {
92
+ void message.error(t('benchmark.runFailed'))
93
+ }
94
+ }, [message, t])
95
+
96
+ const handleRunCategory = useCallback(async (category: string) => {
97
+ try {
98
+ const res = await startBenchmarkRun({ category })
99
+ setActiveRunId(res.run.runId)
100
+ void message.success(t('benchmark.categoryRunStarted'))
101
+ } catch {
102
+ void message.error(t('benchmark.runFailed'))
103
+ }
104
+ }, [message, t])
105
+
106
+ const handleBatchRun = useCallback(async () => {
107
+ const caseKeys = checkedKeys.filter(k => k.startsWith('case:'))
108
+ const casesToRun = cases.filter(item => caseKeys.includes(`case:${item.category}/${item.title}`))
109
+ if (casesToRun.length === 0) {
110
+ for (const item of cases) {
111
+ try { await startBenchmarkRun({ category: item.category, title: item.title }) } catch { /* continue */ }
112
+ }
113
+ void message.success(t('benchmark.runStarted'))
114
+ return
115
+ }
116
+ let started = 0
117
+ for (const item of casesToRun) {
118
+ try {
119
+ await startBenchmarkRun({ category: item.category, title: item.title })
120
+ started++
121
+ } catch { /* continue */ }
122
+ }
123
+ if (started > 0) {
124
+ setCheckedKeys([])
125
+ void mutateCases()
126
+ void mutateCategories()
127
+ void message.success(t('benchmark.runStarted'))
128
+ }
129
+ }, [checkedKeys, cases, mutateCases, mutateCategories, message, t])
130
+
131
+ // Effects
132
+ useEffect(() => {
133
+ const nextKeys = categories.map(item => `category:${item.category}`)
134
+ setExpandedKeys((prev) => {
135
+ if (prev.length > 0 && treeQuery.trim() === '') return prev
136
+ return nextKeys
137
+ })
138
+ }, [categories, treeQuery])
139
+
140
+ useEffect(() => {
141
+ if (selectedCase == null) return
142
+ if (values.category !== selectedCase.category || values.title !== selectedCase.title) {
143
+ update({ category: selectedCase.category, title: selectedCase.title })
144
+ }
145
+ }, [selectedCase, update, values.category, values.title])
146
+
147
+ useEffect(() => {
148
+ if (!isTerminalRun(activeRun)) return
149
+ void mutateCases()
150
+ void mutateCategories()
151
+ void mutateResult()
152
+ if (activeRunId !== '') void mutateRun()
153
+ const summary = activeRun?.status === 'completed'
154
+ ? t('benchmark.runCompleted')
155
+ : t('benchmark.runFailed')
156
+ void message.info(summary)
157
+ setActiveRunId('')
158
+ }, [activeRun, activeRunId, message, mutateCases, mutateCategories, mutateResult, mutateRun, t])
159
+
160
+ const checkedCaseCount = checkedKeys.filter(k => k.startsWith('case:')).length
161
+
162
+ return (
163
+ <div className='benchmark-view'>
164
+ <div className='benchmark-view__left'>
165
+ <BenchmarkSidebar
166
+ cases={cases}
167
+ categories={categories}
168
+ selectedCase={selectedCase}
169
+ query={treeQuery}
170
+ expandedKeys={expandedKeys}
171
+ checkedKeys={checkedKeys}
172
+ checkedCaseCount={checkedCaseCount}
173
+ onQueryChange={setTreeQuery}
174
+ onExpandedKeysChange={setExpandedKeys}
175
+ onCheckedKeysChange={setCheckedKeys}
176
+ onSelectCase={(category, title) => update({ category, title })}
177
+ onRunCase={(item) => void handleRunSpecificCase(item)}
178
+ onRunCategory={(category) => void handleRunCategory(category)}
179
+ onBatchRun={() => void handleBatchRun()}
180
+ />
181
+ </div>
182
+
183
+ <div className='benchmark-view__divider' />
184
+
185
+ <div className='benchmark-view__right'>
186
+ <BenchmarkCasePanel
187
+ selectedCase={selectedCase}
188
+ latestResult={latestResult}
189
+ activeRun={activeRun}
190
+ activeRunId={activeRunId}
191
+ progressPercent={progressPercent}
192
+ onRunCase={() => void (selectedCase && handleRunSpecificCase(selectedCase))}
193
+ />
194
+ </div>
195
+ </div>
196
+ )
197
+ }
@@ -0,0 +1,10 @@
1
+ import type { BenchmarkCase } from '@vibe-forge/core'
2
+
3
+ export interface BenchmarkQueryParams extends Record<string, string> {
4
+ category: string
5
+ title: string
6
+ }
7
+
8
+ export interface TreeNodeCase extends BenchmarkCase {
9
+ key: string
10
+ }
@@ -0,0 +1,21 @@
1
+ import type { BenchmarkResult, BenchmarkRunSummary } from '@vibe-forge/core'
2
+
3
+ // ─── Formatting ───────────────────────────────────────────────────────────────
4
+
5
+ export const isTerminalRun = (run?: BenchmarkRunSummary | null) =>
6
+ run?.status === 'completed' || run?.status === 'failed'
7
+
8
+ export const formatTimestamp = (value?: string | null): string => {
9
+ if (!value) return '-'
10
+ return new Date(value).toLocaleString()
11
+ }
12
+
13
+ // ─── Result status helpers ────────────────────────────────────────────────────
14
+
15
+ export const getResultStatusMeta = (result?: BenchmarkResult | null) => {
16
+ if (result == null) return { icon: 'radio_button_unchecked', statusKey: 'no-result' }
17
+ if (result.status === 'pass') return { icon: 'check_circle', statusKey: 'pass' }
18
+ if (result.status === 'partial') return { icon: 'rule', statusKey: 'partial' }
19
+ return { icon: 'cancel', statusKey: 'fail' }
20
+ }
21
+
@@ -1,40 +1,48 @@
1
1
  import './Chat.scss'
2
2
 
3
- import { useTranslation } from 'react-i18next'
4
-
5
- import type { Session } from '@vibe-forge/core'
3
+ import type { ChatMessage, ChatMessageContent, Session } from '@vibe-forge/core'
6
4
  import { ChatHeader } from './chat/ChatHeader.js'
7
5
  import { ChatHistoryView } from './chat/ChatHistoryView.js'
8
6
  import { ChatSettingsView } from './chat/ChatSettingsView.js'
9
7
  import { ChatTimelineView } from './chat/ChatTimelineView.js'
10
- import { useChatModels } from './chat/useChatModels.js'
11
- import { useChatSession } from './chat/useChatSession.js'
8
+ import { useChatSession } from '#~/hooks/chat/use-chat-session'
12
9
 
13
10
  export function Chat({
14
11
  session
15
12
  }: {
16
13
  session?: Session
17
14
  }) {
18
- const { t } = useTranslation()
19
- const { selectedModel, setSelectedModel, modelOptions, hasAvailableModels } = useChatModels()
20
15
  const {
21
16
  messages,
22
17
  sessionInfo,
23
18
  interactionRequest,
24
- isCreating,
25
19
  isReady,
26
- isThinking,
27
20
  activeView,
28
21
  setActiveView,
29
- messagesEndRef,
30
- messagesContainerRef,
31
- showScrollBottom,
32
- scrollToBottom,
33
- send,
34
- interrupt,
35
- clearMessages,
36
- handleInteractionResponse
37
- } = useChatSession({ session, selectedModel, hasAvailableModels })
22
+ handleInteractionResponse,
23
+ setMessages,
24
+ placeholder,
25
+ modelOptions,
26
+ selectedModel,
27
+ modelForQuery,
28
+ setSelectedModel,
29
+ permissionMode,
30
+ setPermissionMode,
31
+ permissionModeOptions,
32
+ hasAvailableModels,
33
+ modelUnavailable
34
+ } = useChatSession({ session })
35
+ const buildUserMessage = (content: string | ChatMessageContent[]): ChatMessage => {
36
+ const id = globalThis.crypto?.randomUUID
37
+ ? globalThis.crypto.randomUUID()
38
+ : `local-${Date.now()}-${Math.random().toString(16).slice(2)}`
39
+ return {
40
+ id,
41
+ role: 'user' as const,
42
+ content,
43
+ createdAt: Date.now()
44
+ }
45
+ }
38
46
 
39
47
  return (
40
48
  <div className={`chat-container ${isReady ? 'ready' : ''} ${!session?.id ? 'is-new-session' : ''}`}>
@@ -59,26 +67,26 @@ export function Chat({
59
67
  messages={messages}
60
68
  session={session}
61
69
  sessionInfo={sessionInfo}
62
- isCreating={isCreating}
63
- showScrollBottom={showScrollBottom}
64
- messagesContainerRef={messagesContainerRef}
65
- messagesEndRef={messagesEndRef}
66
- scrollToBottom={scrollToBottom}
67
70
  interactionRequest={interactionRequest}
68
71
  onInteractionResponse={handleInteractionResponse}
69
- onSend={send}
70
- onInterrupt={interrupt}
71
- onClear={clearMessages}
72
- placeholder={!session?.id ? t('chat.newSessionPlaceholder') : undefined}
72
+ onClearMessages={() => setMessages([])}
73
+ onSend={(text) => setMessages((prev) => [...prev, buildUserMessage(text)])}
74
+ onSendContent={(content) => setMessages((prev) => [...prev, buildUserMessage(content)])}
75
+ placeholder={placeholder}
73
76
  modelOptions={modelOptions}
74
77
  selectedModel={selectedModel}
78
+ modelForQuery={modelForQuery}
75
79
  onModelChange={setSelectedModel}
76
- modelUnavailable={!hasAvailableModels}
80
+ permissionMode={permissionMode}
81
+ permissionModeOptions={permissionModeOptions}
82
+ onPermissionModeChange={setPermissionMode}
83
+ modelUnavailable={modelUnavailable}
84
+ hasAvailableModels={hasAvailableModels}
77
85
  />
78
86
  )}
79
87
 
80
88
  {activeView === 'timeline' && (
81
- <ChatTimelineView messages={messages} isThinking={isThinking} />
89
+ <ChatTimelineView messages={messages} />
82
90
  )}
83
91
 
84
92
  {activeView === 'settings' && session?.id && (
@@ -1,8 +1,10 @@
1
1
  import './CodeBlock.scss'
2
+
2
3
  import { useAtomValue } from 'jotai'
3
4
  import React, { useEffect, useState } from 'react'
4
5
  import { codeToHtml } from 'shiki'
5
- import { themeAtom } from '../../store'
6
+
7
+ import { themeAtom } from '#~/store/index.js'
6
8
 
7
9
  export function CodeBlock({
8
10
  code,
@@ -45,6 +45,7 @@ export function ConfigView() {
45
45
  'general',
46
46
  'conversation',
47
47
  'modelServices',
48
+ 'channels',
48
49
  'adapters',
49
50
  'plugins',
50
51
  'mcp',
@@ -66,6 +67,12 @@ export function ConfigView() {
66
67
  label: t('config.sections.modelServices'),
67
68
  value: currentSource?.modelServices
68
69
  },
70
+ {
71
+ key: 'channels',
72
+ icon: 'campaign',
73
+ label: t('config.sections.channels'),
74
+ value: currentSource?.channels
75
+ },
69
76
  {
70
77
  key: 'adapters',
71
78
  icon: 'settings_input_component',
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import ReactMarkdown from 'react-markdown'
3
3
  import remarkGfm from 'remark-gfm'
4
- import { CodeBlock } from './CodeBlock'
4
+ import { CodeBlock } from '#~/components/CodeBlock'
5
5
 
6
6
  export function MarkdownContent({
7
7
  content
@@ -108,6 +108,13 @@ export function NavRail({
108
108
  path: '/automation',
109
109
  active: currentPath === '/automation'
110
110
  },
111
+ {
112
+ key: 'benchmark',
113
+ icon: 'speed',
114
+ label: t('common.benchmark'),
115
+ path: '/benchmark',
116
+ active: currentPath === '/benchmark'
117
+ },
111
118
  {
112
119
  key: 'archive',
113
120
  icon: 'archive',
@@ -64,22 +64,16 @@
64
64
  }
65
65
 
66
66
  .chat-header-view-group {
67
- .ant-radio-button-wrapper {
68
- height: 30px;
69
- line-height: 28px;
70
- padding: 0 12px;
71
- font-size: 12px;
67
+ .ant-radio-button-wrapper .ant-radio-button-label {
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ height: 100%;
72
72
  }
73
73
  }
74
74
 
75
- .chat-header-view-option {
76
- display: inline-flex;
77
- align-items: center;
78
- gap: 6px;
79
-
80
- .material-symbols-rounded {
81
- font-size: 16px;
82
- }
75
+ .chat-header-view-option.material-symbols-rounded {
76
+ font-size: 16px;
83
77
  }
84
78
 
85
79
  .material-symbols-rounded.is-filled {
@@ -271,15 +265,27 @@
271
265
  &.archive-action {
272
266
  &:hover {
273
267
  border-color: var(--danger-color);
274
- background-color: color-mix(in srgb, var(--danger-color) 12%, transparent);
268
+ background-color: color-mix(
269
+ in srgb,
270
+ var(--danger-color) 12%,
271
+ transparent
272
+ );
275
273
  color: var(--danger-color);
276
274
  }
277
275
  &.active {
278
- background-color: color-mix(in srgb, var(--danger-color) 18%, transparent);
276
+ background-color: color-mix(
277
+ in srgb,
278
+ var(--danger-color) 18%,
279
+ transparent
280
+ );
279
281
  border-color: var(--danger-color);
280
282
  color: var(--danger-color);
281
283
  &:hover {
282
- background-color: color-mix(in srgb, var(--danger-color) 24%, transparent);
284
+ background-color: color-mix(
285
+ in srgb,
286
+ var(--danger-color) 24%,
287
+ transparent
288
+ );
283
289
  }
284
290
  }
285
291
  }
@@ -349,15 +355,27 @@ html.dark {
349
355
 
350
356
  &.archive-action {
351
357
  &:hover {
352
- background-color: color-mix(in srgb, var(--danger-color) 22%, transparent);
358
+ background-color: color-mix(
359
+ in srgb,
360
+ var(--danger-color) 22%,
361
+ transparent
362
+ );
353
363
  color: var(--danger-color);
354
364
  }
355
365
  &.active {
356
- background-color: color-mix(in srgb, var(--danger-color) 26%, transparent);
366
+ background-color: color-mix(
367
+ in srgb,
368
+ var(--danger-color) 26%,
369
+ transparent
370
+ );
357
371
  border-color: var(--danger-color);
358
372
  color: var(--danger-color);
359
373
  &:hover {
360
- background-color: color-mix(in srgb, var(--danger-color) 32%, transparent);
374
+ background-color: color-mix(
375
+ in srgb,
376
+ var(--danger-color) 32%,
377
+ transparent
378
+ );
361
379
  }
362
380
  }
363
381
  }
@@ -147,27 +147,24 @@ export function ChatHeader({
147
147
  options={[
148
148
  {
149
149
  label: (
150
- <span className='chat-header-view-option'>
151
- <span className='material-symbols-rounded'>forum</span>
152
- <span>{t('chat.viewHistory')}</span>
150
+ <span className='chat-header-view-option material-symbols-rounded'>
151
+ forum
153
152
  </span>
154
153
  ),
155
154
  value: 'history'
156
155
  },
157
156
  {
158
157
  label: (
159
- <span className='chat-header-view-option'>
160
- <span className='material-symbols-rounded'>timeline</span>
161
- <span>{t('chat.viewTimeline')}</span>
158
+ <span className='chat-header-view-option material-symbols-rounded'>
159
+ timeline
162
160
  </span>
163
161
  ),
164
162
  value: 'timeline'
165
163
  },
166
164
  {
167
165
  label: (
168
- <span className='chat-header-view-option'>
169
- <span className='material-symbols-rounded'>tune</span>
170
- <span>{t('chat.viewSettings')}</span>
166
+ <span className='chat-header-view-option material-symbols-rounded'>
167
+ tune
171
168
  </span>
172
169
  ),
173
170
  value: 'settings'