@effectnode/media 0.1.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 (133) hide show
  1. package/bin/effectnode-media +2 -0
  2. package/dist/backend/index.d.ts +8 -0
  3. package/dist/backend/index.js +13 -0
  4. package/dist/backend/movie-backend/agent/agent-backend.d.ts +16 -0
  5. package/dist/backend/movie-backend/agent/agent-backend.js +703 -0
  6. package/dist/backend/movie-backend/agent/storyprompt.d.ts +1 -0
  7. package/dist/backend/movie-backend/agent/storyprompt.js +90 -0
  8. package/dist/backend/movie-backend/agent/tools/edit-image.d.ts +3 -0
  9. package/dist/backend/movie-backend/agent/tools/edit-image.js +192 -0
  10. package/dist/backend/movie-backend/agent/tools/get-time.d.ts +3 -0
  11. package/dist/backend/movie-backend/agent/tools/get-time.js +7 -0
  12. package/dist/backend/movie-backend/agent/tools/grep-files.d.ts +3 -0
  13. package/dist/backend/movie-backend/agent/tools/grep-files.js +59 -0
  14. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.d.ts +3 -0
  15. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.js +160 -0
  16. package/dist/backend/movie-backend/agent/tools/index.d.ts +7 -0
  17. package/dist/backend/movie-backend/agent/tools/index.js +59 -0
  18. package/dist/backend/movie-backend/agent/tools/list-files.d.ts +3 -0
  19. package/dist/backend/movie-backend/agent/tools/list-files.js +17 -0
  20. package/dist/backend/movie-backend/agent/tools/read-file.d.ts +3 -0
  21. package/dist/backend/movie-backend/agent/tools/read-file.js +28 -0
  22. package/dist/backend/movie-backend/agent/tools/remove-file.d.ts +3 -0
  23. package/dist/backend/movie-backend/agent/tools/remove-file.js +22 -0
  24. package/dist/backend/movie-backend/agent/tools/rename-file.d.ts +3 -0
  25. package/dist/backend/movie-backend/agent/tools/rename-file.js +37 -0
  26. package/dist/backend/movie-backend/agent/tools/show-image.d.ts +3 -0
  27. package/dist/backend/movie-backend/agent/tools/show-image.js +28 -0
  28. package/dist/backend/movie-backend/agent/tools/stitch-videos.d.ts +3 -0
  29. package/dist/backend/movie-backend/agent/tools/stitch-videos.js +96 -0
  30. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.d.ts +3 -0
  31. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.js +125 -0
  32. package/dist/backend/movie-backend/agent/tools/types.d.ts +15 -0
  33. package/dist/backend/movie-backend/agent/tools/types.js +1 -0
  34. package/dist/backend/movie-backend/agent/tools/update-file.d.ts +3 -0
  35. package/dist/backend/movie-backend/agent/tools/update-file.js +27 -0
  36. package/dist/backend/movie-backend/agent/tools/write-file.d.ts +3 -0
  37. package/dist/backend/movie-backend/agent/tools/write-file.js +26 -0
  38. package/dist/backend/movie-backend/agent/workspace.d.ts +25 -0
  39. package/dist/backend/movie-backend/agent/workspace.js +124 -0
  40. package/dist/backend/movie-backend/core.d.ts +4 -0
  41. package/dist/backend/movie-backend/core.js +1167 -0
  42. package/dist/backend/movie-backend/generation-queue.d.ts +24 -0
  43. package/dist/backend/movie-backend/generation-queue.js +766 -0
  44. package/dist/backend/movie-backend/process.d.ts +21 -0
  45. package/dist/backend/movie-backend/process.js +98 -0
  46. package/dist/backend/movie-backend/render-media.d.ts +48 -0
  47. package/dist/backend/movie-backend/render-media.js +3311 -0
  48. package/dist/backend/routes/health.d.ts +1 -0
  49. package/dist/backend/routes/health.js +9 -0
  50. package/dist/backend/routes/index.d.ts +1 -0
  51. package/dist/backend/routes/index.js +11 -0
  52. package/dist/backend/routes/media.d.ts +1 -0
  53. package/dist/backend/routes/media.js +13 -0
  54. package/dist/backend/routes/projects.d.ts +8 -0
  55. package/dist/backend/routes/projects.js +68 -0
  56. package/dist/backend/routes/voice.d.ts +1 -0
  57. package/dist/backend/routes/voice.js +39 -0
  58. package/dist/backend/routes.d.ts +1 -0
  59. package/dist/backend/routes.js +20 -0
  60. package/dist/backend/server.d.ts +1 -0
  61. package/dist/backend/server.js +23 -0
  62. package/dist/backend/workspace.d.ts +5 -0
  63. package/dist/backend/workspace.js +27 -0
  64. package/dist/backend/ws/index.d.ts +3 -0
  65. package/dist/backend/ws/index.js +27 -0
  66. package/dist/backend/ws/messages.d.ts +8 -0
  67. package/dist/backend/ws/messages.js +21 -0
  68. package/dist/backend/ws.d.ts +3 -0
  69. package/dist/backend/ws.js +29 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +80 -0
  72. package/frontend/index.html +22 -0
  73. package/frontend/public/ffmpeg/ffmpeg-core.js +16 -0
  74. package/frontend/public/ffmpeg/ffmpeg-core.wasm +0 -0
  75. package/frontend/src/index.css +46 -0
  76. package/frontend/src/main.tsx +13 -0
  77. package/frontend/src/movie-app/AppRouter.tsx +14 -0
  78. package/frontend/src/movie-app/MediaStudio.tsx +9 -0
  79. package/frontend/src/movie-app/SetupPage.tsx +293 -0
  80. package/frontend/src/movie-app/components/AgentWorkspace.tsx +574 -0
  81. package/frontend/src/movie-app/components/ChatUI/ChatUI.tsx +655 -0
  82. package/frontend/src/movie-app/components/ChatUI/MarkdownMessage.tsx +267 -0
  83. package/frontend/src/movie-app/components/EditorTabs/AgentTab.tsx +61 -0
  84. package/frontend/src/movie-app/components/EditorTabs/BatchImageToVideoTab.tsx +927 -0
  85. package/frontend/src/movie-app/components/EditorTabs/BatchVideoTab.tsx +777 -0
  86. package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +913 -0
  87. package/frontend/src/movie-app/components/EditorTabs/CharacterSheet.tsx +234 -0
  88. package/frontend/src/movie-app/components/EditorTabs/CharactersTab.tsx +664 -0
  89. package/frontend/src/movie-app/components/EditorTabs/CropTool.tsx +251 -0
  90. package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +305 -0
  91. package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +249 -0
  92. package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +555 -0
  93. package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +722 -0
  94. package/frontend/src/movie-app/components/EditorTabs/LlmServerTab.tsx +40 -0
  95. package/frontend/src/movie-app/components/EditorTabs/MlxVlmServerPanel.tsx +490 -0
  96. package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +938 -0
  97. package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +881 -0
  98. package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +267 -0
  99. package/frontend/src/movie-app/components/EditorTabs/StoryWriterTab.tsx +51 -0
  100. package/frontend/src/movie-app/components/EditorTabs/TaskQueuePanel.tsx +246 -0
  101. package/frontend/src/movie-app/components/EditorTabs/TerminalLogPanel.tsx +49 -0
  102. package/frontend/src/movie-app/components/EditorTabs/TextToImageTab.tsx +574 -0
  103. package/frontend/src/movie-app/components/EditorTabs/VoiceChatPanel.tsx +320 -0
  104. package/frontend/src/movie-app/components/ProjectEditorPage.tsx +636 -0
  105. package/frontend/src/movie-app/components/ProjectManager.tsx +381 -0
  106. package/frontend/src/movie-app/index.css +46 -0
  107. package/frontend/src/movie-app/index.html +16 -0
  108. package/frontend/src/movie-app/lambobo.png +0 -0
  109. package/frontend/src/movie-app/lib/batchImageToVideoStorage.ts +69 -0
  110. package/frontend/src/movie-app/lib/batchVideoStorage.ts +68 -0
  111. package/frontend/src/movie-app/lib/batchVoiceStorage.ts +75 -0
  112. package/frontend/src/movie-app/lib/ffmpeg.ts +42 -0
  113. package/frontend/src/movie-app/lib/referencesToVideoStorage.ts +64 -0
  114. package/frontend/src/movie-app/lib/textToImageStorage.ts +61 -0
  115. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.js +16 -0
  116. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.wasm +0 -0
  117. package/frontend/src/movie-app/stores/batchImageToVideoStore.ts +736 -0
  118. package/frontend/src/movie-app/stores/batchVideoStore.ts +670 -0
  119. package/frontend/src/movie-app/stores/batchVoiceStore.ts +990 -0
  120. package/frontend/src/movie-app/stores/characterStore.ts +109 -0
  121. package/frontend/src/movie-app/stores/chatStore.ts +459 -0
  122. package/frontend/src/movie-app/stores/generationStore.ts +2140 -0
  123. package/frontend/src/movie-app/stores/logStore.ts +25 -0
  124. package/frontend/src/movie-app/stores/movieStudioStore.ts +709 -0
  125. package/frontend/src/movie-app/stores/projectStore.ts +124 -0
  126. package/frontend/src/movie-app/stores/queueStore.ts +214 -0
  127. package/frontend/src/movie-app/stores/referencesToVideoStore.ts +467 -0
  128. package/frontend/src/movie-app/stores/sceneVisualStore.ts +251 -0
  129. package/frontend/src/movie-app/stores/voiceChatStore.ts +170 -0
  130. package/frontend/src/movie-app/stores/workspaceStore.ts +158 -0
  131. package/frontend/src/vite-env.d.ts +1 -0
  132. package/frontend/tsconfig.json +19 -0
  133. package/package.json +65 -0
@@ -0,0 +1,40 @@
1
+ import MlxVlmServerPanel from "./MlxVlmServerPanel";
2
+
3
+ /**
4
+ * Dedicated LLM Server tab. Reuses the shared mlx-vlm server panel so it
5
+ * controls the same single backend server process as the Agent and
6
+ * References-to-Video pages.
7
+ */
8
+ export default function LlmServerTab() {
9
+ const ServerIcon = (
10
+ <svg
11
+ width="18"
12
+ height="18"
13
+ viewBox="0 0 24 24"
14
+ fill="none"
15
+ stroke="currentColor"
16
+ strokeWidth="2"
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ className="text-tiffany-600"
20
+ >
21
+ <rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
22
+ <rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
23
+ <line x1="6" y1="6" x2="6.01" y2="6" />
24
+ <line x1="6" y1="18" x2="6.01" y2="18" />
25
+ </svg>
26
+ );
27
+
28
+ return (
29
+ <div className="flex flex-col gap-7">
30
+ <div className="flex items-center gap-2">
31
+ {ServerIcon}
32
+ <h2 className="text-base font-semibold text-ink-900">
33
+ LLM Server
34
+ </h2>
35
+ </div>
36
+
37
+ <MlxVlmServerPanel />
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,490 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useGenerationStore } from "../../stores/generationStore";
3
+
4
+ const MODEL_PRESETS = [
5
+ "mlx-community/gemma-4-e2b-it-8bit",
6
+ "mlx-community/gemma-4-e4b-it-8bit",
7
+ "mlx-community/gemma-4-26b-a4b-it-8bit",
8
+ ];
9
+
10
+ const InstallIcon = (
11
+ <svg
12
+ width="16"
13
+ height="16"
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ stroke="currentColor"
17
+ strokeWidth="2"
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round"
20
+ >
21
+ <polyline points="16 18 22 12 16 6" />
22
+ <polyline points="8 6 2 12 8 18" />
23
+ </svg>
24
+ );
25
+
26
+ const PlayIcon = (
27
+ <svg
28
+ width="16"
29
+ height="16"
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ strokeWidth="2"
34
+ strokeLinecap="round"
35
+ strokeLinejoin="round"
36
+ >
37
+ <polygon points="6 3 20 12 6 21 6 3" />
38
+ </svg>
39
+ );
40
+
41
+ const StopIcon = (
42
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
43
+ <rect x="5" y="5" width="14" height="14" rx="2" />
44
+ </svg>
45
+ );
46
+
47
+ const SpinnerIcon = (
48
+ <svg
49
+ className="animate-spin text-tiffany-600"
50
+ width="16"
51
+ height="16"
52
+ viewBox="0 0 24 24"
53
+ fill="none"
54
+ stroke="currentColor"
55
+ strokeWidth="2"
56
+ >
57
+ <circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
58
+ <path d="M12 2a10 10 0 0 1 10 10" strokeOpacity="0.75" />
59
+ </svg>
60
+ );
61
+
62
+ const CheckCircleIcon = (
63
+ <svg
64
+ width="14"
65
+ height="14"
66
+ viewBox="0 0 24 24"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ strokeWidth="2"
70
+ strokeLinecap="round"
71
+ strokeLinejoin="round"
72
+ >
73
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
74
+ <polyline points="22 4 12 14.01 9 11.01" />
75
+ </svg>
76
+ );
77
+
78
+ const AlertCircleIcon = (
79
+ <svg
80
+ width="14"
81
+ height="14"
82
+ viewBox="0 0 24 24"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ strokeWidth="2"
86
+ strokeLinecap="round"
87
+ strokeLinejoin="round"
88
+ >
89
+ <circle cx="12" cy="12" r="10" />
90
+ <line x1="12" y1="8" x2="12" y2="12" />
91
+ <line x1="12" y1="16" x2="12.01" y2="16" />
92
+ </svg>
93
+ );
94
+
95
+ const ExternalLinkIcon = (
96
+ <svg
97
+ width="14"
98
+ height="14"
99
+ viewBox="0 0 24 24"
100
+ fill="none"
101
+ stroke="currentColor"
102
+ strokeWidth="2"
103
+ strokeLinecap="round"
104
+ strokeLinejoin="round"
105
+ >
106
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
107
+ <polyline points="15 3 21 3 21 9" />
108
+ <line x1="10" y1="14" x2="21" y2="3" />
109
+ </svg>
110
+ );
111
+
112
+ export function OnlyStartButton() {
113
+ const store = useGenerationStore();
114
+ // const serverUrl = `http://localhost:${store.agent.port}`;
115
+
116
+ return (
117
+ <>
118
+ <div className="flex flex-wrap items-end gap-3">
119
+ {/* <div>
120
+ <label className="block text-xs font-medium text-ink-700 mb-1.5">
121
+ Port
122
+ </label>
123
+ <input
124
+ type="number"
125
+ min={1}
126
+ max={65535}
127
+ value={store.agent.port}
128
+ onChange={(e) => store.setAgentPort(Number(e.target.value))}
129
+ disabled={store.agent.serverRunning || store.agent.starting}
130
+ className="w-32 px-3 py-2 bg-ink-50 border border-ink-200 rounded-xl text-ink-900 text-sm focus:outline-none focus:border-tiffany-500 focus:ring-2 focus:ring-tiffany-500/30 transition-all disabled:opacity-50"
131
+ />
132
+ </div> */}
133
+
134
+ {store.agent.serverRunning || store.agent.starting ? (
135
+ <button
136
+ onClick={() => store.stopAgentServer()}
137
+ className="flex items-center gap-2 px-4 py-2 text-xs font-medium rounded-xl border transition-all bg-red-50 border-red-200 text-red-600 hover:border-red-300"
138
+ >
139
+ {StopIcon}
140
+ Stop Server
141
+ </button>
142
+ ) : (
143
+ <button
144
+ onClick={() => store.startAgentServer()}
145
+ disabled={store.agent.installing || store.agent.installed === false}
146
+ className="flex items-center gap-2 px-4 py-2 text-xs font-medium rounded-xl border transition-all bg-white border-ink-200 text-ink-600 hover:border-ink-300 disabled:opacity-50"
147
+ >
148
+ {PlayIcon}
149
+ Start Server
150
+ </button>
151
+ )}
152
+ </div>
153
+ </>
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Shared mlx-vlm LLM server management panel (Setup + Server sections).
159
+ * Both the Agent page and the References-to-Video page reuse this so they
160
+ * control the same single backend server process.
161
+ */
162
+ export default function MlxVlmServerPanel() {
163
+ const store = useGenerationStore();
164
+ const serverLogsRef = useRef<HTMLDivElement>(null);
165
+
166
+ useEffect(() => {
167
+ store.checkAgentStatus();
168
+ store.checkServerOnline();
169
+ const interval = setInterval(() => {
170
+ store.checkServerOnline();
171
+ }, 3000);
172
+ return () => clearInterval(interval);
173
+ // eslint-disable-next-line react-hooks/exhaustive-deps
174
+ }, []);
175
+
176
+ useEffect(() => {
177
+ if (serverLogsRef.current) {
178
+ serverLogsRef.current.scrollTop = serverLogsRef.current.scrollHeight;
179
+ }
180
+ }, [store.agent.serverLogs]);
181
+
182
+ const serverUrl = `http://localhost:${store.agent.port}`;
183
+ const modelOptions = MODEL_PRESETS.includes(store.agent.model)
184
+ ? MODEL_PRESETS
185
+ : [store.agent.model, ...MODEL_PRESETS];
186
+
187
+ // ========== SVG Icons ==========
188
+
189
+ const InstallIcon = (
190
+ <svg
191
+ width="16"
192
+ height="16"
193
+ viewBox="0 0 24 24"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ strokeWidth="2"
197
+ strokeLinecap="round"
198
+ strokeLinejoin="round"
199
+ >
200
+ <polyline points="16 18 22 12 16 6" />
201
+ <polyline points="8 6 2 12 8 18" />
202
+ </svg>
203
+ );
204
+
205
+ const PlayIcon = (
206
+ <svg
207
+ width="16"
208
+ height="16"
209
+ viewBox="0 0 24 24"
210
+ fill="none"
211
+ stroke="currentColor"
212
+ strokeWidth="2"
213
+ strokeLinecap="round"
214
+ strokeLinejoin="round"
215
+ >
216
+ <polygon points="6 3 20 12 6 21 6 3" />
217
+ </svg>
218
+ );
219
+
220
+ const StopIcon = (
221
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
222
+ <rect x="5" y="5" width="14" height="14" rx="2" />
223
+ </svg>
224
+ );
225
+
226
+ const SpinnerIcon = (
227
+ <svg
228
+ className="animate-spin text-tiffany-600"
229
+ width="16"
230
+ height="16"
231
+ viewBox="0 0 24 24"
232
+ fill="none"
233
+ stroke="currentColor"
234
+ strokeWidth="2"
235
+ >
236
+ <circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
237
+ <path d="M12 2a10 10 0 0 1 10 10" strokeOpacity="0.75" />
238
+ </svg>
239
+ );
240
+
241
+ const CheckCircleIcon = (
242
+ <svg
243
+ width="14"
244
+ height="14"
245
+ viewBox="0 0 24 24"
246
+ fill="none"
247
+ stroke="currentColor"
248
+ strokeWidth="2"
249
+ strokeLinecap="round"
250
+ strokeLinejoin="round"
251
+ >
252
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
253
+ <polyline points="22 4 12 14.01 9 11.01" />
254
+ </svg>
255
+ );
256
+
257
+ const AlertCircleIcon = (
258
+ <svg
259
+ width="14"
260
+ height="14"
261
+ viewBox="0 0 24 24"
262
+ fill="none"
263
+ stroke="currentColor"
264
+ strokeWidth="2"
265
+ strokeLinecap="round"
266
+ strokeLinejoin="round"
267
+ >
268
+ <circle cx="12" cy="12" r="10" />
269
+ <line x1="12" y1="8" x2="12" y2="12" />
270
+ <line x1="12" y1="16" x2="12.01" y2="16" />
271
+ </svg>
272
+ );
273
+
274
+ const ExternalLinkIcon = (
275
+ <svg
276
+ width="14"
277
+ height="14"
278
+ viewBox="0 0 24 24"
279
+ fill="none"
280
+ stroke="currentColor"
281
+ strokeWidth="2"
282
+ strokeLinecap="round"
283
+ strokeLinejoin="round"
284
+ >
285
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
286
+ <polyline points="15 3 21 3 21 9" />
287
+ <line x1="10" y1="14" x2="21" y2="3" />
288
+ </svg>
289
+ );
290
+
291
+ const renderStatus = (
292
+ value: boolean | null,
293
+ okText: string,
294
+ missingText: string,
295
+ ) => {
296
+ if (value === null) {
297
+ return (
298
+ <span className="inline-flex items-center gap-1.5 text-ink-500">
299
+ {SpinnerIcon}
300
+ Checking...
301
+ </span>
302
+ );
303
+ }
304
+ if (value) {
305
+ return (
306
+ <span className="inline-flex items-center gap-1.5 text-emerald-600">
307
+ {CheckCircleIcon}
308
+ {okText}
309
+ </span>
310
+ );
311
+ }
312
+ return (
313
+ <span className="inline-flex items-center gap-1.5 text-amber-600">
314
+ {AlertCircleIcon}
315
+ {missingText}
316
+ </span>
317
+ );
318
+ };
319
+
320
+ return (
321
+ <>
322
+ {/* ===== Install mlx-vlm ===== */}
323
+ <div>
324
+ <label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
325
+ Setup
326
+ </label>
327
+ <div className="flex flex-wrap gap-2">
328
+ <button
329
+ onClick={() => store.installMlxVlm()}
330
+ disabled={store.agent.installing}
331
+ className="flex items-center gap-2 px-4 py-2 text-xs font-medium rounded-xl border transition-all bg-white border-ink-200 text-ink-600 hover:border-ink-300 disabled:opacity-50"
332
+ >
333
+ {store.agent.installing ? SpinnerIcon : InstallIcon}
334
+ Install mlx-vlm
335
+ </button>
336
+ </div>
337
+
338
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-2 text-xs font-medium">
339
+ {renderStatus(
340
+ store.agent.installed,
341
+ "mlx-vlm installed",
342
+ "mlx-vlm not installed",
343
+ )}
344
+ </div>
345
+
346
+ {store.agent.installingLogs.length > 0 && (
347
+ <div className="mt-2 p-4 bg-ink-50 border border-ink-200 rounded-2xl max-h-32 overflow-y-auto">
348
+ <pre className="text-xs text-ink-600 font-mono whitespace-pre-wrap">
349
+ {store.agent.installingLogs.join("")}
350
+ </pre>
351
+ </div>
352
+ )}
353
+ {store.agent.installingError && (
354
+ <div className="mt-2 p-4 bg-red-50 border border-red-200 rounded-2xl text-red-600 text-xs">
355
+ {store.agent.installingError}
356
+ </div>
357
+ )}
358
+ </div>
359
+
360
+ {/* ===== Server ===== */}
361
+ <div>
362
+ <label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
363
+ Server
364
+ </label>
365
+
366
+ <div className="flex flex-wrap items-end gap-3 mb-3">
367
+ <div className="flex-1 min-w-[260px]">
368
+ <label className="block text-xs font-medium text-ink-700 mb-1.5">
369
+ Model
370
+ </label>
371
+ <div className=" gap-2 mb-2">
372
+ <select
373
+ value={store.agent.model}
374
+ onChange={(e) => store.setAgentModel(e.target.value)}
375
+ disabled={store.agent.serverRunning || store.agent.starting}
376
+ title="Choose a model"
377
+ className="shrink-0 w-[350px] px-2 py-2 bg-ink-50 border border-ink-200 rounded-xl text-ink-900 text-sm focus:outline-none focus:border-tiffany-500 focus:ring-2 focus:ring-tiffany-500/30 transition-all disabled:opacity-50 cursor-pointer"
378
+ >
379
+ {modelOptions.map((m) => (
380
+ <option key={m} value={m}>
381
+ {m}
382
+ </option>
383
+ ))}
384
+ </select>
385
+ </div>
386
+ <div className=" gap-2">
387
+ <input
388
+ value={store.agent.model}
389
+ onChange={(e) => store.setAgentModel(e.target.value)}
390
+ disabled={store.agent.serverRunning || store.agent.starting}
391
+ title="enter a model name"
392
+ className="shrink-0 w-[350px] px-2 py-2 bg-ink-50 border border-ink-200 rounded-xl text-ink-900 text-sm focus:outline-none focus:border-tiffany-500 focus:ring-2 focus:ring-tiffany-500/30 transition-all disabled:opacity-50 cursor-pointer"
393
+ ></input>
394
+ </div>
395
+ </div>
396
+ </div>
397
+
398
+ <div className="flex flex-wrap items-end gap-3">
399
+ <div>
400
+ <label className="block text-xs font-medium text-ink-700 mb-1.5">
401
+ Port
402
+ </label>
403
+ <input
404
+ type="number"
405
+ min={1}
406
+ max={65535}
407
+ value={store.agent.port}
408
+ onChange={(e) => store.setAgentPort(Number(e.target.value))}
409
+ disabled={store.agent.serverRunning || store.agent.starting}
410
+ className="w-32 px-3 py-2 bg-ink-50 border border-ink-200 rounded-xl text-ink-900 text-sm focus:outline-none focus:border-tiffany-500 focus:ring-2 focus:ring-tiffany-500/30 transition-all disabled:opacity-50"
411
+ />
412
+ </div>
413
+
414
+ {store.agent.serverRunning || store.agent.starting ? (
415
+ <button
416
+ onClick={() => store.stopAgentServer()}
417
+ className="flex items-center gap-2 px-4 py-2 text-xs font-medium rounded-xl border transition-all bg-red-50 border-red-200 text-red-600 hover:border-red-300"
418
+ >
419
+ {StopIcon}
420
+ Stop Server
421
+ </button>
422
+ ) : (
423
+ <button
424
+ onClick={() => store.startAgentServer()}
425
+ disabled={
426
+ store.agent.installing || store.agent.installed === false
427
+ }
428
+ className="flex items-center gap-2 px-4 py-2 text-xs font-medium rounded-xl border transition-all bg-white border-ink-200 text-ink-600 hover:border-ink-300 disabled:opacity-50"
429
+ >
430
+ {PlayIcon}
431
+ Start Server
432
+ </button>
433
+ )}
434
+ </div>
435
+
436
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-2 text-xs font-medium">
437
+ {store.agent.starting ? (
438
+ <span className="inline-flex items-center gap-1.5 text-ink-500">
439
+ {SpinnerIcon}
440
+ Starting server...
441
+ </span>
442
+ ) : store.agent.serverOnline === true ? (
443
+ <span className="inline-flex items-center gap-1.5 text-emerald-600">
444
+ {CheckCircleIcon}
445
+ Server online at {serverUrl}
446
+ </span>
447
+ ) : store.agent.serverOnline === false ? (
448
+ <span className="inline-flex items-center gap-1.5 text-amber-600">
449
+ {AlertCircleIcon}
450
+ Server offline
451
+ </span>
452
+ ) : (
453
+ <span className="inline-flex items-center gap-1.5 text-ink-500">
454
+ {SpinnerIcon}
455
+ Checking server...
456
+ </span>
457
+ )}
458
+ {store.agent.serverOnline === true && (
459
+ <button
460
+ onClick={() => store.openAgentServer()}
461
+ className="inline-flex items-center gap-1.5 text-ink-600 hover:text-ink-900 transition-colors cursor-pointer"
462
+ >
463
+ {ExternalLinkIcon}
464
+ Open {serverUrl}
465
+ </button>
466
+ )}
467
+ </div>
468
+
469
+ {store.agent.serverLogs.length > 0 && (
470
+ <div
471
+ ref={serverLogsRef}
472
+ className="mt-2 p-4 bg-ink-50 border border-ink-200 rounded-2xl max-h-48 overflow-y-auto"
473
+ >
474
+ <p className="text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
475
+ Server Logs
476
+ </p>
477
+ <pre className="text-xs text-ink-600 font-mono whitespace-pre-wrap">
478
+ {store.agent.serverLogs.join("")}
479
+ </pre>
480
+ </div>
481
+ )}
482
+ {store.agent.serverError && (
483
+ <div className="mt-2 p-4 bg-red-50 border border-red-200 rounded-2xl text-red-600 text-xs">
484
+ {store.agent.serverError}
485
+ </div>
486
+ )}
487
+ </div>
488
+ </>
489
+ );
490
+ }