@vibe-forge/client 0.2.0-alpha.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/cli.cjs +6 -0
  3. package/index.html +27 -0
  4. package/package.json +42 -0
  5. package/src/App.tsx +174 -0
  6. package/src/api.ts +241 -0
  7. package/src/components/ArchiveView.scss +168 -0
  8. package/src/components/ArchiveView.tsx +299 -0
  9. package/src/components/AutomationView/AutomationView.scss +26 -0
  10. package/src/components/AutomationView/RuleFormPanel.scss +129 -0
  11. package/src/components/AutomationView/RuleFormPanel.tsx +257 -0
  12. package/src/components/AutomationView/RuleSidebar.scss +219 -0
  13. package/src/components/AutomationView/RuleSidebar.tsx +258 -0
  14. package/src/components/AutomationView/RunHistoryPanel.scss +286 -0
  15. package/src/components/AutomationView/RunHistoryPanel.tsx +320 -0
  16. package/src/components/AutomationView/TaskList.scss +128 -0
  17. package/src/components/AutomationView/TaskList.tsx +79 -0
  18. package/src/components/AutomationView/TriggerList.scss +153 -0
  19. package/src/components/AutomationView/TriggerList.tsx +217 -0
  20. package/src/components/AutomationView/index.tsx +228 -0
  21. package/src/components/AutomationView/types.ts +21 -0
  22. package/src/components/Chat.scss +89 -0
  23. package/src/components/Chat.tsx +92 -0
  24. package/src/components/ConfigView.scss +185 -0
  25. package/src/components/ConfigView.tsx +258 -0
  26. package/src/components/NavRail.scss +71 -0
  27. package/src/components/NavRail.tsx +188 -0
  28. package/src/components/Sidebar.scss +112 -0
  29. package/src/components/Sidebar.tsx +291 -0
  30. package/src/components/chat/ChatHeader.scss +401 -0
  31. package/src/components/chat/ChatHeader.tsx +342 -0
  32. package/src/components/chat/ChatHistoryView.tsx +122 -0
  33. package/src/components/chat/ChatSettingsView.tsx +22 -0
  34. package/src/components/chat/ChatTimelineView.scss +53 -0
  35. package/src/components/chat/ChatTimelineView.tsx +158 -0
  36. package/src/components/chat/CodeBlock.scss +87 -0
  37. package/src/components/chat/CodeBlock.tsx +179 -0
  38. package/src/components/chat/CompletionMenu.scss +70 -0
  39. package/src/components/chat/CompletionMenu.tsx +58 -0
  40. package/src/components/chat/CurrentTodoList.scss +217 -0
  41. package/src/components/chat/CurrentTodoList.tsx +103 -0
  42. package/src/components/chat/MarkdownContent.tsx +43 -0
  43. package/src/components/chat/MessageFooter.tsx +48 -0
  44. package/src/components/chat/MessageItem.scss +251 -0
  45. package/src/components/chat/MessageItem.tsx +78 -0
  46. package/src/components/chat/NewSessionGuide.scss +186 -0
  47. package/src/components/chat/NewSessionGuide.tsx +167 -0
  48. package/src/components/chat/Sender.scss +367 -0
  49. package/src/components/chat/Sender.tsx +541 -0
  50. package/src/components/chat/SessionTimelinePanel/EventList.scss +58 -0
  51. package/src/components/chat/SessionTimelinePanel/EventList.tsx +212 -0
  52. package/src/components/chat/SessionTimelinePanel/gantt.ts +177 -0
  53. package/src/components/chat/SessionTimelinePanel/git-graph.ts +518 -0
  54. package/src/components/chat/SessionTimelinePanel/index.scss +28 -0
  55. package/src/components/chat/SessionTimelinePanel/index.tsx +121 -0
  56. package/src/components/chat/SessionTimelinePanel/mermaid.ts +4 -0
  57. package/src/components/chat/SessionTimelinePanel/types.ts +64 -0
  58. package/src/components/chat/SessionTimelinePanel/utils.ts +20 -0
  59. package/src/components/chat/ThinkingStatus.scss +70 -0
  60. package/src/components/chat/ThinkingStatus.tsx +13 -0
  61. package/src/components/chat/ToolCallBox.scss +137 -0
  62. package/src/components/chat/ToolCallBox.tsx +55 -0
  63. package/src/components/chat/ToolGroup.scss +154 -0
  64. package/src/components/chat/ToolGroup.tsx +102 -0
  65. package/src/components/chat/ToolRenderer.tsx +45 -0
  66. package/src/components/chat/messageUtils.ts +171 -0
  67. package/src/components/chat/safeSerialize.ts +84 -0
  68. package/src/components/chat/tools/DefaultTool.tsx +63 -0
  69. package/src/components/chat/tools/adapter-claude/BashTool.scss +71 -0
  70. package/src/components/chat/tools/adapter-claude/BashTool.tsx +82 -0
  71. package/src/components/chat/tools/adapter-claude/GlobTool.scss +88 -0
  72. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +85 -0
  73. package/src/components/chat/tools/adapter-claude/GrepTool.scss +96 -0
  74. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +114 -0
  75. package/src/components/chat/tools/adapter-claude/LSTool.scss +85 -0
  76. package/src/components/chat/tools/adapter-claude/LSTool.tsx +94 -0
  77. package/src/components/chat/tools/adapter-claude/ReadTool.scss +57 -0
  78. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +87 -0
  79. package/src/components/chat/tools/adapter-claude/TodoTool.scss +78 -0
  80. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +60 -0
  81. package/src/components/chat/tools/adapter-claude/WriteTool.scss +92 -0
  82. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +86 -0
  83. package/src/components/chat/tools/adapter-claude/components/FileList.scss +65 -0
  84. package/src/components/chat/tools/adapter-claude/components/FileList.tsx +185 -0
  85. package/src/components/chat/tools/adapter-claude/index.ts +28 -0
  86. package/src/components/chat/tools/defineToolRender.ts +28 -0
  87. package/src/components/chat/tools/task/GetTaskInfoTool.scss +50 -0
  88. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +88 -0
  89. package/src/components/chat/tools/task/ListTasksTool.scss +56 -0
  90. package/src/components/chat/tools/task/ListTasksTool.tsx +83 -0
  91. package/src/components/chat/tools/task/StartTasksTool.scss +56 -0
  92. package/src/components/chat/tools/task/StartTasksTool.tsx +96 -0
  93. package/src/components/chat/tools/task/components/TaskToolCard.scss +127 -0
  94. package/src/components/chat/tools/task/components/TaskToolCard.tsx +177 -0
  95. package/src/components/chat/tools/task/index.ts +15 -0
  96. package/src/components/chat/useChatModels.tsx +206 -0
  97. package/src/components/chat/useChatSession.ts +370 -0
  98. package/src/components/config/ConfigAboutSection.scss +111 -0
  99. package/src/components/config/ConfigAboutSection.tsx +86 -0
  100. package/src/components/config/ConfigDisplayValue.scss +22 -0
  101. package/src/components/config/ConfigDisplayValue.tsx +62 -0
  102. package/src/components/config/ConfigEditors.scss +65 -0
  103. package/src/components/config/ConfigEditors.tsx +98 -0
  104. package/src/components/config/ConfigFieldRow.scss +97 -0
  105. package/src/components/config/ConfigFieldRow.tsx +36 -0
  106. package/src/components/config/ConfigSectionForm.scss +94 -0
  107. package/src/components/config/ConfigSectionForm.tsx +436 -0
  108. package/src/components/config/ConfigSectionPanel.tsx +67 -0
  109. package/src/components/config/ConfigShortcutInput.scss +11 -0
  110. package/src/components/config/ConfigShortcutInput.tsx +52 -0
  111. package/src/components/config/ConfigSourceSwitch.tsx +57 -0
  112. package/src/components/config/configSchema.ts +319 -0
  113. package/src/components/config/configUtils.ts +83 -0
  114. package/src/components/config/index.tsx +5 -0
  115. package/src/components/config/recordEditors/BooleanRecordEditor.scss +1 -0
  116. package/src/components/config/recordEditors/BooleanRecordEditor.tsx +75 -0
  117. package/src/components/config/recordEditors/KeyValueEditor.scss +1 -0
  118. package/src/components/config/recordEditors/KeyValueEditor.tsx +97 -0
  119. package/src/components/config/recordEditors/McpServersRecordEditor.scss +1 -0
  120. package/src/components/config/recordEditors/McpServersRecordEditor.tsx +258 -0
  121. package/src/components/config/recordEditors/ModelServicesRecordEditor.scss +1 -0
  122. package/src/components/config/recordEditors/ModelServicesRecordEditor.tsx +233 -0
  123. package/src/components/config/recordEditors/RecordEditors.scss +117 -0
  124. package/src/components/config/recordEditors/RecordJsonEditor.scss +1 -0
  125. package/src/components/config/recordEditors/RecordJsonEditor.tsx +113 -0
  126. package/src/components/config/recordEditors/index.tsx +5 -0
  127. package/src/components/knowledge-base/KnowledgeBaseView.scss +19 -0
  128. package/src/components/knowledge-base/KnowledgeBaseView.tsx +186 -0
  129. package/src/components/knowledge-base/components/ActionButton.scss +5 -0
  130. package/src/components/knowledge-base/components/ActionButton.tsx +9 -0
  131. package/src/components/knowledge-base/components/EmptyState.scss +19 -0
  132. package/src/components/knowledge-base/components/EmptyState.tsx +42 -0
  133. package/src/components/knowledge-base/components/EntitiesTab.scss +5 -0
  134. package/src/components/knowledge-base/components/EntitiesTab.tsx +80 -0
  135. package/src/components/knowledge-base/components/EntityItem.scss +82 -0
  136. package/src/components/knowledge-base/components/EntityItem.tsx +79 -0
  137. package/src/components/knowledge-base/components/EntityList.scss +5 -0
  138. package/src/components/knowledge-base/components/EntityList.tsx +70 -0
  139. package/src/components/knowledge-base/components/FilterBar.scss +21 -0
  140. package/src/components/knowledge-base/components/FilterBar.tsx +51 -0
  141. package/src/components/knowledge-base/components/FlowsTab.scss +5 -0
  142. package/src/components/knowledge-base/components/FlowsTab.tsx +80 -0
  143. package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +27 -0
  144. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +29 -0
  145. package/src/components/knowledge-base/components/KnowledgeList.scss +19 -0
  146. package/src/components/knowledge-base/components/KnowledgeList.tsx +19 -0
  147. package/src/components/knowledge-base/components/LoadingState.scss +5 -0
  148. package/src/components/knowledge-base/components/LoadingState.tsx +11 -0
  149. package/src/components/knowledge-base/components/MetaList.scss +19 -0
  150. package/src/components/knowledge-base/components/MetaList.tsx +18 -0
  151. package/src/components/knowledge-base/components/RulesTab.scss +5 -0
  152. package/src/components/knowledge-base/components/RulesTab.tsx +49 -0
  153. package/src/components/knowledge-base/components/SectionHeader.scss +22 -0
  154. package/src/components/knowledge-base/components/SectionHeader.tsx +21 -0
  155. package/src/components/knowledge-base/components/SkillsTab.scss +5 -0
  156. package/src/components/knowledge-base/components/SkillsTab.tsx +49 -0
  157. package/src/components/knowledge-base/components/SpecItem.scss +138 -0
  158. package/src/components/knowledge-base/components/SpecItem.tsx +131 -0
  159. package/src/components/knowledge-base/components/SpecList.scss +5 -0
  160. package/src/components/knowledge-base/components/SpecList.tsx +70 -0
  161. package/src/components/knowledge-base/components/TabContent.scss +8 -0
  162. package/src/components/knowledge-base/components/TabContent.tsx +17 -0
  163. package/src/components/knowledge-base/components/TabLabel.scss +10 -0
  164. package/src/components/knowledge-base/components/TabLabel.tsx +15 -0
  165. package/src/components/knowledge-base/index.tsx +1 -0
  166. package/src/components/sidebar/SessionItem.scss +256 -0
  167. package/src/components/sidebar/SessionItem.tsx +265 -0
  168. package/src/components/sidebar/SessionList.scss +92 -0
  169. package/src/components/sidebar/SessionList.tsx +166 -0
  170. package/src/components/sidebar/SidebarHeader.scss +79 -0
  171. package/src/components/sidebar/SidebarHeader.tsx +128 -0
  172. package/src/connectionManager.ts +172 -0
  173. package/src/hooks/useGlobalShortcut.ts +26 -0
  174. package/src/hooks/useQueryParams.ts +54 -0
  175. package/src/i18n.ts +22 -0
  176. package/src/main.tsx +41 -0
  177. package/src/resources/locales/en.json +765 -0
  178. package/src/resources/locales/zh.json +766 -0
  179. package/src/store/index.ts +23 -0
  180. package/src/styles/global.scss +100 -0
  181. package/src/utils/shortcutUtils.ts +88 -0
  182. package/src/vite-env.d.ts +12 -0
  183. package/src/ws.ts +33 -0
  184. package/vite.config.ts +26 -0
@@ -0,0 +1,212 @@
1
+ import './EventList.scss'
2
+
3
+ import { Input, Table } from 'antd'
4
+ import type { ColumnsType } from 'antd/es/table'
5
+ import React from 'react'
6
+ import { useTranslation } from 'react-i18next'
7
+
8
+ import type { Task, TimelineEvent } from './types'
9
+ import { normalizeTime, parseTime } from './utils'
10
+
11
+ interface TimelineListItem {
12
+ id: string
13
+ label: string
14
+ startTime: string
15
+ endTime: string
16
+ depth: number
17
+ input?: string
18
+ output?: string
19
+ payload?: TimelineEvent
20
+ }
21
+
22
+ interface TimelineLabels {
23
+ mainStart: string
24
+ mainEnd: string
25
+ startTasks: string
26
+ askUserQuestion: string
27
+ edit: string
28
+ resumeTask: string
29
+ userPrompt: string
30
+ }
31
+
32
+ const collectTimelineEntries = (task: Task, labels: TimelineLabels) => {
33
+ const entries: TimelineListItem[] = []
34
+ let counter = 0
35
+
36
+ const addEntry = (
37
+ label: string,
38
+ startTime: string,
39
+ endTime: string,
40
+ depth: number,
41
+ payload?: TimelineEvent
42
+ ) => {
43
+ entries.push({
44
+ id: `${label}-${startTime}-${endTime}-${counter}`,
45
+ label,
46
+ startTime,
47
+ endTime,
48
+ depth,
49
+ payload
50
+ })
51
+ counter += 1
52
+ }
53
+
54
+ const walkEvents = (events: TimelineEvent[], depth: number) => {
55
+ for (const event of events) {
56
+ if (event.type === 'tool__StartTasks') {
57
+ addEntry(labels.startTasks, event.startTime, event.endTime, depth, event)
58
+ } else if (event.type === 'tool__AskUserQuestion') {
59
+ addEntry(labels.askUserQuestion, event.startTime, event.endTime, depth, event)
60
+ } else if (event.type === 'tool__Edit') {
61
+ addEntry(labels.edit, event.startTime, event.endTime, depth, event)
62
+ } else if (event.type === 'tool__ResumeTask') {
63
+ addEntry(labels.resumeTask, event.startTime, event.endTime, depth, event)
64
+ } else if (event.type === 'user__Prompt') {
65
+ addEntry(labels.userPrompt, event.startTime, event.endTime, depth, event)
66
+ }
67
+ }
68
+ }
69
+
70
+ addEntry(labels.mainStart, task.startTime, task.startTime, 0)
71
+ walkEvents(task.events ?? [], 0)
72
+ addEntry(labels.mainEnd, task.endTime, task.endTime, 0)
73
+
74
+ return entries
75
+ .map((entry, index) => ({ ...entry, index }))
76
+ .sort((a, b) => {
77
+ const diff = parseTime(a.startTime) - parseTime(b.startTime)
78
+ if (diff !== 0) return diff
79
+ return a.index - b.index
80
+ })
81
+ .map(({ index: _index, ...entry }) => entry)
82
+ }
83
+
84
+ export function SessionTimelineEventList({ task }: { task: Task }) {
85
+ const { t } = useTranslation()
86
+ const [query, setQuery] = React.useState('')
87
+ const containerRef = React.useRef<HTMLDivElement | null>(null)
88
+ const [scrollY, setScrollY] = React.useState(240)
89
+ const labels = React.useMemo(() => ({
90
+ mainStart: t('chat.timeline.mainStart'),
91
+ mainEnd: t('chat.timeline.mainEnd'),
92
+ startTasks: t('chat.timeline.startTasks'),
93
+ askUserQuestion: t('chat.timeline.askUserQuestion'),
94
+ edit: t('chat.timeline.edit'),
95
+ resumeTask: t('chat.timeline.resumeTask'),
96
+ userPrompt: t('chat.timeline.userPrompt')
97
+ }), [t])
98
+ const items = React.useMemo(() => collectTimelineEntries(task, labels), [labels, task])
99
+ const filteredItems = React.useMemo(() => {
100
+ const keyword = query.trim().toLowerCase()
101
+ if (!keyword) return items
102
+ return items.filter((item) => {
103
+ const text = `${item.label} ${item.startTime} ${item.endTime}`.toLowerCase()
104
+ return text.includes(keyword)
105
+ })
106
+ }, [items, query])
107
+ const emptyValue = t('chat.timelineEmptyValue')
108
+ const columns: ColumnsType<TimelineListItem> = React.useMemo(() => [
109
+ {
110
+ title: t('chat.timelineEventName'),
111
+ key: 'label',
112
+ width: 180,
113
+ render: (_value, item) => (
114
+ <span
115
+ className='session-timeline-event-table__label'
116
+ style={{ paddingLeft: `${item.depth * 16}px` }}
117
+ >
118
+ {item.label}
119
+ </span>
120
+ )
121
+ },
122
+ {
123
+ title: t('chat.timelineInput'),
124
+ key: 'input',
125
+ width: 180,
126
+ render: (_value, item) => (
127
+ <span className='session-timeline-event-table__value'>
128
+ {item.input ?? emptyValue}
129
+ </span>
130
+ )
131
+ },
132
+ {
133
+ title: t('chat.timelineOutput'),
134
+ key: 'output',
135
+ width: 180,
136
+ render: (_value, item) => (
137
+ <span className='session-timeline-event-table__value'>
138
+ {item.output ?? emptyValue}
139
+ </span>
140
+ )
141
+ },
142
+ {
143
+ title: t('chat.timelineTiming'),
144
+ key: 'time',
145
+ width: 160,
146
+ render: (_value, item) => {
147
+ const startTime = normalizeTime(item.startTime)
148
+ const endTime = normalizeTime(item.endTime)
149
+ return (
150
+ <span className='session-timeline-event-table__time'>
151
+ {startTime === endTime ? startTime : `${startTime} → ${endTime}`}
152
+ </span>
153
+ )
154
+ }
155
+ }
156
+ ], [emptyValue, t])
157
+
158
+ React.useEffect(() => {
159
+ const element = containerRef.current
160
+ if (!element) return
161
+ const update = () => {
162
+ const header = element.querySelector<HTMLElement>('.session-timeline-event-table__header')
163
+ const pagination = element.querySelector<HTMLElement>('.ant-table-pagination')
164
+ const headerHeight = header?.offsetHeight ?? 0
165
+ const paginationHeight = pagination?.offsetHeight ?? 0
166
+ const next = Math.max(160, element.clientHeight - headerHeight - paginationHeight - 16)
167
+ setScrollY((prev) => (prev === next ? prev : next))
168
+ }
169
+ update()
170
+ const observer = new ResizeObserver(update)
171
+ observer.observe(element)
172
+ return () => {
173
+ observer.disconnect()
174
+ }
175
+ }, [])
176
+
177
+ return (
178
+ <div ref={containerRef} className='session-timeline-event-table'>
179
+ <div className='session-timeline-event-table__header'>
180
+ <span className='session-timeline-event-table__title'>{t('chat.timeline.eventListTitle')}</span>
181
+ <Input
182
+ className='session-timeline-event-table__search'
183
+ allowClear
184
+ value={query}
185
+ onChange={(event) => setQuery(event.target.value)}
186
+ placeholder={t('chat.timeline.eventListSearch')}
187
+ />
188
+ </div>
189
+ <Table<TimelineListItem>
190
+ className='session-timeline-event-table__table'
191
+ size='small'
192
+ columns={columns}
193
+ dataSource={filteredItems}
194
+ rowKey='id'
195
+ onRow={(record) => ({
196
+ onClick: () => {
197
+ if (record.payload) {
198
+ console.warn(record.payload)
199
+ }
200
+ }
201
+ })}
202
+ pagination={{
203
+ pageSize: 20,
204
+ showSizeChanger: true,
205
+ pageSizeOptions: ['10', '20', '50']
206
+ }}
207
+ scroll={{ y: scrollY }}
208
+ locale={{ emptyText: t('chat.timelineEmpty') }}
209
+ />
210
+ </div>
211
+ )
212
+ }
@@ -0,0 +1,177 @@
1
+ import type { Task, TimelineDiagram, TimelineEvent, TimelineInteraction } from './types'
2
+ import { normalizeTime, parseTime } from './utils'
3
+
4
+ interface GanttItem {
5
+ id: string
6
+ label: string
7
+ start: string
8
+ end?: string
9
+ sectionId: string
10
+ sectionLabel: string
11
+ type: 'task'
12
+ interaction?: TimelineInteraction
13
+ }
14
+
15
+ interface GanttLabels {
16
+ ganttTitle: string
17
+ ganttMainSection: string
18
+ ganttTasksSection: string
19
+ resumeTask: string
20
+ userPrompt: string
21
+ }
22
+
23
+ function createGanttItemIdFactory() {
24
+ let seq = 0
25
+ return (prefix: string) => `${prefix}_${seq++}`
26
+ }
27
+
28
+ function collectGanttItems(task: Task, labels: GanttLabels) {
29
+ const nextId = createGanttItemIdFactory()
30
+ const nextSectionId = createGanttItemIdFactory()
31
+ const interactions: TimelineInteraction[] = []
32
+ const items: GanttItem[] = []
33
+ const blankSectionLabel = '\u200B'
34
+ let blankSectionSequence = 0
35
+ const nextBlankSectionLabel = () => {
36
+ blankSectionSequence += 1
37
+ return blankSectionLabel.repeat(blankSectionSequence)
38
+ }
39
+
40
+ const addInteraction = (interaction: TimelineInteraction) => {
41
+ interactions.push(interaction)
42
+ }
43
+
44
+ const collectTaskSegments = (taskItem: Task) => {
45
+ const segments: Array<{ start: string; end: string }> = [
46
+ { start: taskItem.startTime, end: taskItem.endTime }
47
+ ]
48
+ const events = taskItem.events ?? []
49
+ const askIndex = events.findIndex((event) => event.type === 'tool__AskUserQuestion')
50
+ if (askIndex < 0) return segments
51
+ const askEvent = events[askIndex]
52
+ const askStartTime = parseTime(askEvent.startTime) <= parseTime(askEvent.endTime)
53
+ ? askEvent.startTime
54
+ : askEvent.endTime
55
+ const askEndTime = parseTime(askEvent.startTime) <= parseTime(askEvent.endTime)
56
+ ? askEvent.endTime
57
+ : askEvent.startTime
58
+ if (parseTime(askEndTime) <= parseTime(askStartTime)) return segments
59
+ segments[segments.length - 1] = {
60
+ start: segments[segments.length - 1].start,
61
+ end: askStartTime
62
+ }
63
+ segments.push({
64
+ start: askEndTime,
65
+ end: taskItem.endTime
66
+ })
67
+ return segments
68
+ }
69
+
70
+ const walkEvents = (events: TimelineEvent[]) => {
71
+ for (let index = 0; index < events.length; index += 1) {
72
+ const event = events[index]
73
+ const { type, startTime, endTime } = event
74
+ switch (type) {
75
+ case 'tool__StartTasks': {
76
+ const { tasks = {} } = event
77
+ const startTasksSectionId = nextSectionId('section')
78
+ const startTasksSectionLabel = nextBlankSectionLabel()
79
+ for (const [taskName, task] of Object.entries(tasks)) {
80
+ const segments = collectTaskSegments(task)
81
+ segments.forEach((segment, segmentIndex) => {
82
+ const itemId = nextId('task')
83
+ const label = segments.length > 1
84
+ ? `${taskName} - ${segmentIndex}`
85
+ : taskName
86
+ const interaction: TimelineInteraction = {
87
+ id: itemId,
88
+ label: taskName,
89
+ payload: {
90
+ kind: 'task',
91
+ name: taskName,
92
+ task
93
+ }
94
+ }
95
+ items.push({
96
+ id: itemId,
97
+ label,
98
+ start: normalizeTime(segment.start),
99
+ end: normalizeTime(segment.end),
100
+ sectionId: startTasksSectionId,
101
+ sectionLabel: startTasksSectionLabel,
102
+ type: 'task',
103
+ interaction
104
+ })
105
+ addInteraction(interaction)
106
+ })
107
+ const { events: taskEvents = [] } = task
108
+ if (taskEvents.length > 0) {
109
+ walkEvents(taskEvents)
110
+ }
111
+ }
112
+ break
113
+ }
114
+ case 'tool__ResumeTask': {
115
+ break
116
+ }
117
+ case 'user__Prompt': {
118
+ break
119
+ }
120
+ default:
121
+ break
122
+ }
123
+ }
124
+ }
125
+
126
+ const mainItemId = nextId('main')
127
+ const mainSectionId = nextSectionId('section')
128
+ items.push({
129
+ id: mainItemId,
130
+ label: labels.ganttMainSection,
131
+ start: normalizeTime(task.startTime),
132
+ end: normalizeTime(task.endTime),
133
+ sectionId: mainSectionId,
134
+ sectionLabel: labels.ganttMainSection,
135
+ type: 'task'
136
+ })
137
+ walkEvents(task.events ?? [])
138
+ return {
139
+ items,
140
+ interactions
141
+ }
142
+ }
143
+
144
+ function buildGanttLines(items: GanttItem[]) {
145
+ const lines = [
146
+ 'gantt',
147
+ 'dateFormat HH:mm:ss',
148
+ 'axisFormat %H:%M:%S'
149
+ ]
150
+
151
+ const sections = new Map<string, { label: string; items: GanttItem[] }>()
152
+ for (const item of items) {
153
+ if (!sections.has(item.sectionId)) {
154
+ sections.set(item.sectionId, { label: item.sectionLabel, items: [] })
155
+ }
156
+ sections.get(item.sectionId)?.items.push(item)
157
+ }
158
+
159
+ for (const section of sections.values()) {
160
+ lines.push(section.label ? `section ${section.label}` : 'section')
161
+ for (const item of section.items) {
162
+ if (item.end) {
163
+ lines.push(`${item.label} :${item.id}, ${item.start}, ${item.end}`)
164
+ }
165
+ }
166
+ }
167
+
168
+ return lines.join('\n')
169
+ }
170
+
171
+ export function buildGantt(task: Task, labels: GanttLabels): TimelineDiagram {
172
+ const { items, interactions } = collectGanttItems(task, labels)
173
+ return {
174
+ code: buildGanttLines(items),
175
+ interactions
176
+ }
177
+ }