ompchamber 0.7.0 → 1.0.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 (165) hide show
  1. package/dist/client/index.html +1 -1
  2. package/dist/client/static/css/index.e880517fcd.css +2 -0
  3. package/dist/client/static/js/async/{3y.4c2596f64c.js → 3y.71f2ccf3b6.js} +2 -2
  4. package/dist/client/static/js/async/{xw.ae67ba06fd.js → 5b.0d6ae50fe8.js} +10 -10
  5. package/dist/client/static/js/async/5bh.8b2363424f.js +1 -0
  6. package/dist/client/static/js/async/{vy.bbe114c4e0.js → bi.09688ba75c.js} +1 -2
  7. package/dist/client/static/js/async/c.797d933882.js +1 -0
  8. package/dist/client/static/js/async/{dc.6dc4b0a04b.js → dc.856fda6560.js} +2 -2
  9. package/dist/client/static/js/async/g.a06ca3b3e9.js +2 -0
  10. package/dist/client/static/js/async/g.a06ca3b3e9.js.LICENSE.txt +11 -0
  11. package/dist/client/static/js/async/{m.4b29d34f93.js → m.c038c71b9f.js} +2 -2
  12. package/dist/client/static/js/async/ma.2c261b04dd.js +2 -0
  13. package/dist/client/static/js/async/ms.98960af4eb.js +67 -0
  14. package/dist/client/static/js/async/nam.b5663e919f.js +10 -0
  15. package/dist/client/static/js/async/nam.b5663e919f.js.LICENSE.txt +11 -0
  16. package/dist/client/static/js/async/ot.84c71b308f.js +2 -0
  17. package/dist/client/static/js/async/ot.84c71b308f.js.LICENSE.txt +11 -0
  18. package/dist/client/static/js/async/{q.542c184fec.js → q.23af2a358f.js} +3 -3
  19. package/dist/client/static/js/async/{s.413c2a4683.js → s.222ee974f6.js} +2 -2
  20. package/dist/client/static/js/async/t.4dca0e2ba8.js +1 -0
  21. package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
  22. package/dist/client/static/js/async/xn.6360379f71.js +2 -0
  23. package/dist/client/static/js/async/xnl.feb0021e50.js +2 -0
  24. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +12 -0
  25. package/dist/client/static/js/index.0de15c5e17.js +33 -0
  26. package/dist/client/static/js/{uy.2e8929ed0c.js → jp.50817896f3.js} +6 -6
  27. package/package.json +2 -1
  28. package/src/client/components/common/MarkdownRenderer.tsx +69 -27
  29. package/src/client/components/common/code-editor/history.test.ts +118 -0
  30. package/src/client/components/common/code-editor/history.ts +101 -0
  31. package/src/client/components/common/code-editor/index.tsx +56 -66
  32. package/src/client/components/common/code-surface/index.tsx +84 -11
  33. package/src/client/components/common/code-surface/measure.ts +66 -17
  34. package/src/client/components/common/diagram-viewer/index.tsx +258 -0
  35. package/src/client/components/common/diagram-viewer/read-block.ts +53 -0
  36. package/src/client/components/layout/session-sidebar/CategoryItem.tsx +2 -1
  37. package/src/client/components/layout/session-sidebar/SessionItem.tsx +11 -4
  38. package/src/client/components/layout/session-sidebar/SessionList.tsx +1 -1
  39. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +1 -1
  40. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +1 -1
  41. package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +2 -2
  42. package/src/client/components/settings/LazyModal.tsx +1 -1
  43. package/src/client/components/settings/Modal.tsx +2 -1
  44. package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +11 -4
  45. package/src/client/components/workspace/chat-timeline/QueueList.tsx +18 -14
  46. package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +8 -1
  47. package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +11 -52
  48. package/src/client/components/workspace/chat-timeline/chat-input/selection.ts +57 -0
  49. package/src/client/components/workspace/chat-timeline/index.tsx +124 -108
  50. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/FrameControl.tsx +172 -0
  51. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +155 -0
  52. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/drafts.ts +113 -0
  53. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +92 -0
  54. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Footer.tsx +3 -3
  55. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Header.tsx +3 -3
  56. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Lazy.tsx +8 -8
  57. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/SelectBody.tsx +3 -3
  58. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/index.tsx +16 -10
  59. package/src/client/components/workspace/chat-timeline/tool-renderers/index.tsx +2 -2
  60. package/src/client/components/workspace/file-explorer/index.tsx +18 -5
  61. package/src/client/components/workspace/git-panel/BranchToolbar.tsx +2 -2
  62. package/src/client/components/workspace/git-panel/index.tsx +5 -1
  63. package/src/client/components/workspace/terminal-panel/Header.tsx +51 -19
  64. package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +86 -282
  65. package/src/client/components/workspace/terminal-panel/index.tsx +76 -50
  66. package/src/client/components/workspace/terminal-panel/mount.ts +275 -0
  67. package/src/client/data/theme/terminal.ts +27 -29
  68. package/src/client/hooks/chat/omp/session-list.tsx +23 -2
  69. package/src/client/hooks/chat/omp/session-sidebar-controller.ts +1 -1
  70. package/src/client/hooks/chat/timeline/actions.ts +23 -34
  71. package/src/client/hooks/chat/timeline/ask-frames.ts +102 -0
  72. package/src/client/hooks/chat/timeline/extension-dialog.ts +12 -9
  73. package/src/client/hooks/chat/timeline/index.ts +18 -28
  74. package/src/client/hooks/chat/timeline/prompt-text.ts +35 -0
  75. package/src/client/hooks/chat/timeline/queue.ts +110 -46
  76. package/src/client/hooks/chat/timeline/send.ts +13 -27
  77. package/src/client/hooks/chat/timeline/session-load.ts +48 -106
  78. package/src/client/hooks/chat/timeline/session-pagination.ts +144 -0
  79. package/src/client/hooks/editor/use-code-window.ts +118 -0
  80. package/src/client/hooks/ui/input-required-alert.ts +43 -0
  81. package/src/client/hooks/ui/notification-sound.ts +65 -0
  82. package/src/client/hooks/ui/theme.ts +22 -6
  83. package/src/client/hooks/workspace/terminal/index.ts +217 -0
  84. package/src/client/hooks/workspace/terminal/socket.ts +227 -0
  85. package/src/client/tailwind.css +38 -12
  86. package/src/server/db.server.ts +19 -0
  87. package/src/server/lib/fs/git-sync.ts +191 -0
  88. package/src/server/lib/omp/rpc/manager.ts +72 -6
  89. package/src/server/lib/omp/rpc/session-commands.ts +6 -0
  90. package/src/server/lib/omp/rpc/session-registry.ts +18 -0
  91. package/src/server/lib/omp/session/sidebar-data.server.ts +10 -4
  92. package/src/server/lib/queue/delivery.server.ts +114 -0
  93. package/src/server/lib/queue/schema-migration.server.ts +50 -0
  94. package/src/server/lib/queue/store.server.ts +185 -0
  95. package/src/server/lib/terminal/foreground.ts +73 -0
  96. package/src/server/lib/terminal/history.test.ts +118 -0
  97. package/src/server/lib/terminal/history.ts +205 -0
  98. package/src/server/lib/terminal/runtime.server.ts +338 -0
  99. package/src/server/lib/terminal/session.server.ts +215 -0
  100. package/src/server/lib/terminal/shell.test.ts +97 -0
  101. package/src/server/lib/terminal/shell.ts +103 -0
  102. package/src/server/routes/fs/git.ts +22 -12
  103. package/src/server/routes/index.ts +2 -1
  104. package/src/server/routes/sessions/index.ts +11 -3
  105. package/src/server/routes/sessions/queue.ts +121 -0
  106. package/src/server/routes/sessions/session.ts +1 -132
  107. package/src/server/routes/settings/commands.ts +1 -1
  108. package/src/server/routes/terminal/index.ts +2 -2
  109. package/src/server/routes/terminal/run.ts +8 -100
  110. package/src/server/routes/terminal/sessions.ts +24 -0
  111. package/src/server/routes/terminal/ws.ts +149 -0
  112. package/src/shared/lib/chat/ask-questions.test.ts +169 -0
  113. package/src/shared/lib/chat/ask-questions.ts +264 -0
  114. package/src/shared/lib/chat/omp/agent-events.test.ts +37 -1
  115. package/src/shared/lib/chat/omp/agent-events.ts +30 -0
  116. package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +9 -1
  117. package/src/shared/lib/chat/timeline/omp-callbacks.ts +45 -8
  118. package/src/shared/lib/code/lazy-window.test.ts +161 -0
  119. package/src/shared/lib/code/lazy-window.ts +156 -0
  120. package/src/shared/lib/code/syntax-highlight.test.ts +50 -1
  121. package/src/shared/lib/code/syntax-highlight.ts +14 -1
  122. package/src/shared/lib/code/windowed-highlight.ts +63 -0
  123. package/src/shared/lib/markdown/mermaid.ts +8 -6
  124. package/src/shared/lib/omp/session/stream-state.server.ts +2 -3
  125. package/src/shared/lib/workspace/session-state/store.ts +1 -4
  126. package/src/shared/lib/workspace/terminal/protocol.ts +222 -0
  127. package/src/shared/types/omp/agent.ts +14 -2
  128. package/src/shared/types/terminal.ts +17 -15
  129. package/src/shared/types/workspace.ts +6 -2
  130. package/dist/client/static/css/index.51f68210a5.css +0 -2
  131. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  132. package/dist/client/static/js/async/c.cd45b0ac45.js +0 -1
  133. package/dist/client/static/js/async/fw.6a51998a8e.js +0 -6
  134. package/dist/client/static/js/async/g.86e2256a70.js +0 -2
  135. package/dist/client/static/js/async/kc.bbdb35b771.js +0 -1
  136. package/dist/client/static/js/async/mw.ca1512563c.js +0 -10
  137. package/dist/client/static/js/async/rf.d626afc367.js +0 -2
  138. package/dist/client/static/js/async/t.cda4891526.js +0 -1
  139. package/dist/client/static/js/async/uf.24ca3d49c3.js +0 -2
  140. package/dist/client/static/js/async/uf.24ca3d49c3.js.LICENSE.txt +0 -6
  141. package/dist/client/static/js/async/v0.394ed37c6b.js +0 -2
  142. package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +0 -6
  143. package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +0 -6
  144. package/dist/client/static/js/async/xn.afecebbbd9.js +0 -2
  145. package/dist/client/static/js/index.eccad0b70a.js +0 -33
  146. package/dist/client/static/js/index.eccad0b70a.js.LICENSE.txt +0 -4
  147. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Ask.tsx +0 -205
  148. package/src/client/components/workspace/terminal-panel/InputBar.tsx +0 -82
  149. package/src/client/components/workspace/terminal-panel/LogEntry.tsx +0 -70
  150. package/src/client/hooks/workspace/terminal-output.ts +0 -66
  151. package/src/client/hooks/workspace/terminal.ts +0 -283
  152. package/src/server/lib/fs/terminal-cwd.ts +0 -49
  153. package/src/server/routes/terminal/stream.ts +0 -100
  154. /package/dist/client/static/js/async/{3y.4c2596f64c.js.LICENSE.txt → 3y.71f2ccf3b6.js.LICENSE.txt} +0 -0
  155. /package/dist/client/static/js/async/{g.86e2256a70.js.LICENSE.txt → 5b.0d6ae50fe8.js.LICENSE.txt} +0 -0
  156. /package/dist/client/static/js/async/{dc.6dc4b0a04b.js.LICENSE.txt → dc.856fda6560.js.LICENSE.txt} +0 -0
  157. /package/dist/client/static/js/async/{fw.6a51998a8e.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt} +0 -0
  158. /package/dist/client/static/js/async/{m.4b29d34f93.js.LICENSE.txt → ma.2c261b04dd.js.LICENSE.txt} +0 -0
  159. /package/dist/client/static/js/async/{xn.afecebbbd9.js.LICENSE.txt → ms.98960af4eb.js.LICENSE.txt} +0 -0
  160. /package/dist/client/static/js/async/{mw.ca1512563c.js.LICENSE.txt → q.23af2a358f.js.LICENSE.txt} +0 -0
  161. /package/dist/client/static/js/async/{q.542c184fec.js.LICENSE.txt → s.222ee974f6.js.LICENSE.txt} +0 -0
  162. /package/dist/client/static/js/async/{rf.d626afc367.js.LICENSE.txt → uf.46ad6209fa.js.LICENSE.txt} +0 -0
  163. /package/dist/client/static/js/async/{s.413c2a4683.js.LICENSE.txt → xn.6360379f71.js.LICENSE.txt} +0 -0
  164. /package/dist/client/static/js/{async/xw.ae67ba06fd.js.LICENSE.txt → index.0de15c5e17.js.LICENSE.txt} +0 -0
  165. /package/dist/client/static/js/{uy.2e8929ed0c.js.LICENSE.txt → jp.50817896f3.js.LICENSE.txt} +0 -0
@@ -1,4 +0,0 @@
1
- /**
2
- * @license
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
@@ -1,205 +0,0 @@
1
- import { useMemo } from 'preact/hooks';
2
- import { CheckCircle2, HelpCircle, XCircle } from 'lucide-preact';
3
- import type { ToolCallData } from '@/shared/types';
4
- import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
5
- import { highlightCode, isCodeLike, tryParseJson } from '@/shared/lib/code/syntax-highlight';
6
- import { useSyntaxReady } from '@/client/hooks/ui/syntax-ready';
7
- import { JsonCodeBlock } from '@/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock';
8
- import { CopyButton } from '@/client/components/common/CopyButton';
9
-
10
- interface OptionItem {
11
- label: string;
12
- description?: string;
13
- }
14
-
15
- /** Panel khusus untuk tool `ask` — dialog pertanyaan agent ke user atau konfirmasi. */
16
- export function Ask({ tool }: { tool: ToolCallData }) {
17
- const input = tool.input;
18
- const inputObj = typeof input === 'object' && input !== null ? (input as Record<string, any>) : undefined;
19
-
20
- // 1. Support omp questions array: input.questions[0]
21
- const questionEntry = Array.isArray(inputObj?.questions) && inputObj.questions.length > 0
22
- ? inputObj.questions[0]
23
- : undefined;
24
-
25
- const header = questionEntry?.header;
26
-
27
- const question =
28
- questionEntry?.question ||
29
- (typeof inputObj?.question === 'string'
30
- ? inputObj.question
31
- : typeof inputObj?.prompt === 'string'
32
- ? inputObj.prompt
33
- : typeof inputObj?.message === 'string'
34
- ? inputObj.message
35
- : typeof input === 'string'
36
- ? input
37
- : tool.detail || 'Question from agent');
38
-
39
- // Parse options: can be array of { label, description } or strings
40
- const rawOptions = questionEntry?.options || inputObj?.options || inputObj?.choices || [];
41
- const options: OptionItem[] = Array.isArray(rawOptions)
42
- ? rawOptions.map((opt: unknown) => {
43
- if (typeof opt === 'string') return { label: opt };
44
- if (typeof opt === 'object' && opt !== null) {
45
- const o = opt as Record<string, any>;
46
- return {
47
- label: typeof o.label === 'string' ? o.label : typeof o.text === 'string' ? o.text : String(opt),
48
- description: typeof o.description === 'string' ? o.description : undefined,
49
- };
50
- }
51
- return { label: String(opt) };
52
- })
53
- : [];
54
-
55
- const rawOutput = useMemo(() => {
56
- if (typeof tool.output === 'string') return tool.output;
57
- if (tool.output && typeof tool.output === 'object') return JSON.stringify(tool.output, null, 2);
58
- if (typeof inputObj?.answer === 'string') return inputObj.answer;
59
- if (inputObj?.answer && typeof inputObj?.answer === 'object') return JSON.stringify(inputObj.answer, null, 2);
60
- return '';
61
- }, [tool.output, inputObj?.answer]);
62
-
63
- const isCancelled =
64
- (typeof rawOutput === 'string' && /cancelled|aborted|interrupted|disconnected/i.test(rawOutput)) ||
65
- tool.status === 'aborted' ||
66
- (tool.status === 'error' && typeof rawOutput === 'string' && /disconnect|error/i.test(rawOutput));
67
-
68
- // Extract selected answer if e.g. "User selected: Blue"
69
- const selectedMatch = typeof rawOutput === 'string' ? rawOutput.match(/selected:\s*(.+)$/i) : null;
70
- const selectedAnswer = selectedMatch ? selectedMatch[1].trim() : rawOutput;
71
-
72
- const jsonResult = useMemo(
73
- () => (!isCancelled && rawOutput ? tryParseJson(rawOutput) : { isValid: false }),
74
- [rawOutput, isCancelled]
75
- );
76
-
77
- const isMultilineText = typeof rawOutput === 'string' && rawOutput.includes('\n') && !selectedMatch;
78
-
79
- const syntaxReady = useSyntaxReady();
80
- const highlightedResponse = useMemo(
81
- () => (typeof rawOutput === 'string' && isCodeLike(rawOutput) ? highlightCode(rawOutput, 'javascript') : ''),
82
- [rawOutput, syntaxReady]
83
- );
84
-
85
- return (
86
- <div className="space-y-2">
87
- <div className="rounded-lg border border-ink/10 bg-paper p-3 shadow-xs">
88
- <div className="flex items-start gap-2 min-w-0">
89
- <HelpCircle size={14} className="mt-0.5 shrink-0 text-ink/60" />
90
- <div className="min-w-0 flex-1">
91
- <span className="text-[10px] font-semibold uppercase tracking-wider text-ink/40">
92
- {header ? `Agent Question · ${header}` : 'Agent Question'}
93
- </span>
94
- <div className="mt-1 text-[12.5px] leading-relaxed text-ink/90 select-text">
95
- <MarkdownRenderer content={question} />
96
- </div>
97
- </div>
98
- </div>
99
-
100
- {options.length > 0 && (
101
- <div className="mt-3 space-y-1.5 border-t border-ink/6 pt-2.5">
102
- <span className="text-[9.5px] font-semibold uppercase tracking-wider text-ink/40">
103
- Options
104
- </span>
105
- <div className="grid gap-1.5">
106
- {options.map((opt, idx) => {
107
- const isSelected =
108
- !isCancelled &&
109
- Boolean(selectedAnswer) &&
110
- selectedAnswer.toLowerCase().includes(opt.label.toLowerCase());
111
-
112
- return (
113
- <div
114
- key={idx}
115
- className={`flex flex-col gap-0.5 rounded-md px-2.5 py-1.5 text-[11px] transition-colors ${
116
- isSelected
117
- ? 'border border-ink/30 bg-ink/5 font-medium text-ink'
118
- : 'border border-ink/6 bg-canvas/30 text-ink/75'
119
- }`}
120
- >
121
- <div className="flex items-center gap-2">
122
- <span className="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded bg-ink/5 font-mono text-[9px] text-ink/60 font-semibold">
123
- {idx + 1}
124
- </span>
125
- <span className="min-w-0 flex-1 truncate font-medium text-ink/90">
126
- {opt.label}
127
- </span>
128
- {isSelected && <CheckCircle2 size={12} className="shrink-0 text-success" />}
129
- </div>
130
- {opt.description && (
131
- <div className="pl-5.5 text-[10px] leading-relaxed text-ink/50">
132
- {opt.description}
133
- </div>
134
- )}
135
- </div>
136
- );
137
- })}
138
- </div>
139
- </div>
140
- )}
141
- </div>
142
-
143
- {/* User Response Section — compact, proportional styling */}
144
- {isCancelled && rawOutput && (
145
- <div className="flex items-center gap-2 rounded-md border border-ink/8 bg-ink/[0.02] px-2.5 py-1 text-[10.5px] text-ink/60">
146
- <XCircle size={11} className="shrink-0 text-ink/40" />
147
- <span className="text-[9px] font-semibold uppercase tracking-wider text-ink/40 shrink-0">
148
- Status:
149
- </span>
150
- <span className="font-mono text-ink/60 truncate select-text">{rawOutput}</span>
151
- </div>
152
- )}
153
-
154
- {!isCancelled && jsonResult.isValid && jsonResult.pretty && (
155
- <JsonCodeBlock
156
- jsonString={jsonResult.pretty}
157
- title="User Response"
158
- maxHeightClass="max-h-40"
159
- compact={true}
160
- icon={<CheckCircle2 size={10.5} className="text-success shrink-0" />}
161
- showHeader={true}
162
- />
163
- )}
164
-
165
- {!isCancelled && !jsonResult.isValid && rawOutput && (
166
- isMultilineText ? (
167
- <div className="overflow-hidden rounded-md border border-ink/8 bg-paper">
168
- <div className="flex items-center justify-between border-b border-ink/6 bg-canvas/40 px-2.5 py-1">
169
- <div className="flex items-center gap-1.5">
170
- <CheckCircle2 size={10.5} className="shrink-0 text-success" />
171
- <span className="text-[9px] font-semibold uppercase tracking-wider text-ink/50">
172
- User Response
173
- </span>
174
- </div>
175
- <CopyButton
176
- text={rawOutput}
177
- className="flex cursor-pointer items-center gap-1 rounded px-1.5 py-0.5 text-[9px] text-ink/45 transition-colors hover:bg-ink/5 hover:text-ink shrink-0"
178
- title="Copy response"
179
- label="Copy"
180
- />
181
- </div>
182
- <pre className="max-h-32 overflow-y-auto p-2 font-mono text-[10.5px] leading-snug whitespace-pre-wrap break-words text-ink/85 select-text">
183
- {highlightedResponse ? (
184
- <span dangerouslySetInnerHTML={{ __html: highlightedResponse }} />
185
- ) : (
186
- rawOutput
187
- )}
188
- </pre>
189
- </div>
190
- ) : (
191
- <div className="flex items-center gap-2 rounded-md border border-ink/8 bg-canvas/30 px-2.5 py-1 text-[10.5px]">
192
- <CheckCircle2 size={11} className="shrink-0 text-success" />
193
- <span className="text-[9px] font-semibold uppercase tracking-wider text-ink/40 shrink-0">
194
- User Response:
195
- </span>
196
- <span className="font-mono text-ink/90 font-medium truncate select-text">
197
- {selectedMatch ? selectedMatch[1].trim().replace(/^["']|["']$/g, '') : rawOutput}
198
- </span>
199
- </div>
200
- )
201
- )}
202
- </div>
203
- );
204
- }
205
-
@@ -1,82 +0,0 @@
1
- import { useEffect, useRef } from 'preact/hooks';
2
- import type { FormEvent, KeyboardEvent } from 'preact/compat';
3
- import { CornerDownLeft, Square } from 'lucide-preact';
4
-
5
- interface TerminalInputBarProps {
6
- value: string;
7
- onChange: (val: string) => void;
8
- onSubmit: () => void;
9
- onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
10
- onCancel: () => void;
11
- isRunning: boolean;
12
- }
13
-
14
- export function TerminalInputBar({
15
- value,
16
- onChange,
17
- onSubmit,
18
- onKeyDown,
19
- onCancel,
20
- isRunning,
21
- }: TerminalInputBarProps) {
22
- const inputRef = useRef<HTMLInputElement>(null);
23
-
24
- // Auto focus input
25
- useEffect(() => {
26
- inputRef.current?.focus();
27
- }, []);
28
-
29
- const handleSubmit = (e: FormEvent) => {
30
- e.preventDefault();
31
- if (!isRunning) {
32
- onSubmit();
33
- }
34
- };
35
-
36
- return (
37
- <form
38
- onSubmit={handleSubmit}
39
- className="p-2 border-t border-ink/10 bg-paper flex items-center space-x-2 flex-shrink-0"
40
- >
41
- <div className="flex items-center space-x-2 flex-1 min-w-0 bg-canvas border border-ink/20 rounded-lg px-2.5 py-1.5 focus-within:border-ink/50 focus-within:ring-1 focus-within:ring-ink/10 transition-all">
42
- <span className="text-warning font-mono text-xs select-none font-bold">
43
- $
44
- </span>
45
- <input
46
- ref={inputRef}
47
- type="text"
48
- value={value}
49
- onChange={e => onChange(e.currentTarget.value)}
50
- onKeyDown={onKeyDown}
51
- disabled={isRunning}
52
- placeholder={isRunning ? "Command is executing in live session..." : "bun run build, git status, ls -la..."}
53
- className="flex-1 bg-transparent font-mono text-xs text-ink placeholder:text-ink/40 outline-none min-w-0"
54
- />
55
- <span className="hidden sm:inline-block text-[10px] font-mono text-ink/40 select-none">
56
- ↑↓ history
57
- </span>
58
- </div>
59
-
60
- {isRunning ? (
61
- <button
62
- type="button"
63
- onClick={onCancel}
64
- className="px-2.5 py-1.5 rounded-lg bg-error text-white hover:bg-error/90 active:scale-95 transition-all flex items-center space-x-1 cursor-pointer shadow-xs font-mono text-xs"
65
- title="Interrupt process (Ctrl+C)"
66
- >
67
- <Square size={12} fill="currentColor" />
68
- <span className="hidden xs:inline text-[10px]">Stop</span>
69
- </button>
70
- ) : (
71
- <button
72
- type="submit"
73
- disabled={!value.trim()}
74
- className="p-2 rounded-lg bg-ink text-paper hover:bg-ink/85 active:scale-95 transition-all flex items-center justify-center disabled:opacity-40 disabled:pointer-events-none cursor-pointer shadow-xs"
75
- title="Execute command (Enter)"
76
- >
77
- <CornerDownLeft size={13} />
78
- </button>
79
- )}
80
- </form>
81
- );
82
- }
@@ -1,70 +0,0 @@
1
- import type { TerminalLogItem } from '@/shared/types';
2
- import { Check, Clock, Loader2, X } from 'lucide-preact';
3
-
4
- interface TerminalLogEntryProps {
5
- item: TerminalLogItem;
6
- }
7
-
8
- export function TerminalLogEntry({ item }: TerminalLogEntryProps) {
9
- const isRunning = item.exitCode === -1;
10
- const isError = !isRunning && item.exitCode !== 0;
11
- const isSuccess = !isRunning && item.exitCode === 0;
12
-
13
- return (
14
- <div className="space-y-1 font-mono text-[11px] leading-relaxed select-text">
15
- {/* Command prompt line */}
16
- <div className="flex items-center justify-between text-ink bg-canvas px-2.5 py-1 rounded border border-ink/15">
17
- <div className="flex items-center space-x-2 min-w-0">
18
- <span className="text-warning font-bold select-none">$</span>
19
- <span className="font-semibold text-ink truncate">{item.command}</span>
20
- </div>
21
-
22
- <div className="flex items-center space-x-2 text-[10px] text-ink/50 flex-shrink-0 ml-2 select-none">
23
- {isRunning ? (
24
- <span className="flex items-center space-x-1 text-warning">
25
- <Loader2 size={10} className="animate-spin" />
26
- <span>executing...</span>
27
- </span>
28
- ) : (
29
- <>
30
- {typeof item.durationMs === 'number' && (
31
- <span className="flex items-center space-x-0.5">
32
- <Clock size={9} />
33
- <span>{item.durationMs}ms</span>
34
- </span>
35
- )}
36
- {isSuccess && (
37
- <span className="flex items-center text-success" title="Exit code: 0">
38
- <Check size={11} />
39
- </span>
40
- )}
41
- {isError && (
42
- <span
43
- className="flex items-center space-x-0.5 text-error font-bold"
44
- title={`Exit code: ${item.exitCode}`}
45
- >
46
- <X size={11} />
47
- <span>{item.exitCode}</span>
48
- </span>
49
- )}
50
- </>
51
- )}
52
- </div>
53
- </div>
54
-
55
- {/* Stdout block */}
56
- {item.stdout && (
57
- <pre className="text-ink/85 whitespace-pre-wrap break-all px-2.5 py-1 overflow-x-auto selection:bg-ink/15 bg-canvas/40 rounded">
58
- {item.stdout}
59
- </pre>
60
- )}
61
-
62
- {/* Stderr block in signal red */}
63
- {item.stderr && (
64
- <pre className="text-error font-medium whitespace-pre-wrap break-all px-2.5 py-1 bg-error/10 rounded border border-error/20 selection:bg-error/30">
65
- {item.stderr}
66
- </pre>
67
- )}
68
- </div>
69
- );
70
- }
@@ -1,66 +0,0 @@
1
- import { useCallback, useEffect, useRef } from 'preact/hooks';
2
- import type { RefObject } from 'preact/compat';
3
- import type { Terminal } from '@xterm/xterm';
4
- import { useSessionState } from '@/client/hooks/workspace/session-state';
5
- import { useSessionStateContext } from '@/client/hooks/workspace/session-state/context';
6
- import { getSessionValue } from '@/shared/lib/workspace/session-state/store';
7
- import { appendTerminalSessionOutput, clearTerminalSessionOutput, getTerminalOutputSnapshot, setTerminalOutputSnapshot } from '@/client/data/theme/terminal';
8
-
9
- interface TerminalOutputSync {
10
- recordOutput: (chunk: string) => void;
11
- clearOutput: () => void;
12
- }
13
-
14
- const OUTPUT_PERSIST_INTERVAL_MS = 1000;
15
-
16
- export function useTerminalOutputSync(terminalRef: RefObject<Terminal>): TerminalOutputSync {
17
- const { sessionId, ready } = useSessionStateContext();
18
- const [, setStoredOutput] = useSessionState<string>('terminal.output', '');
19
- const setStoredOutputRef = useRef(setStoredOutput);
20
- setStoredOutputRef.current = setStoredOutput;
21
- const readyRef = useRef(ready);
22
- readyRef.current = ready;
23
- const lastPersistRef = useRef(0);
24
- const hydratedSessionRef = useRef<string | null>(null);
25
- const prevHydratedSessionRef = useRef<string | null>(null);
26
-
27
- const recordOutput = useCallback((chunk: string) => {
28
- appendTerminalSessionOutput(chunk);
29
- if (!readyRef.current) return;
30
- const now = Date.now();
31
- if (now - lastPersistRef.current < OUTPUT_PERSIST_INTERVAL_MS) return;
32
- lastPersistRef.current = now;
33
- setStoredOutputRef.current(getTerminalOutputSnapshot());
34
- }, []);
35
-
36
- const clearOutput = useCallback(() => {
37
- clearTerminalSessionOutput();
38
- if (!readyRef.current) return;
39
- lastPersistRef.current = Date.now();
40
- setStoredOutputRef.current('');
41
- }, []);
42
-
43
- // On restore, seed the module singleton before xterm rehydrates it; when the
44
- // term is already live, replace its buffer. A session switch always re-seeds,
45
- // while an initial mount without a stored snapshot keeps the live output.
46
- useEffect(() => {
47
- if (!ready || hydratedSessionRef.current === sessionId) return;
48
- const isSessionSwitch = prevHydratedSessionRef.current !== null;
49
- hydratedSessionRef.current = sessionId;
50
- prevHydratedSessionRef.current = sessionId;
51
- const restored = getSessionValue<string>(sessionId, 'terminal.output');
52
- if (restored === undefined && !isSessionSwitch) return;
53
- setTerminalOutputSnapshot(restored ?? '');
54
- const term = terminalRef.current;
55
- if (!term) return;
56
- term.clear();
57
- if (restored) {
58
- term.write(restored);
59
- term.scrollToBottom();
60
- } else {
61
- term.write('\x1b[33m$\x1b[0m ');
62
- }
63
- }, [ready, sessionId, terminalRef]);
64
-
65
- return { recordOutput, clearOutput };
66
- }