@ryanfw/prompt-orchestration-pipeline 0.11.0 → 0.13.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.
- package/package.json +11 -1
- package/src/cli/analyze-task.js +51 -0
- package/src/cli/index.js +8 -0
- package/src/components/AddPipelineSidebar.jsx +144 -0
- package/src/components/AnalysisProgressTray.jsx +87 -0
- package/src/components/DAGGrid.jsx +157 -47
- package/src/components/JobTable.jsx +4 -3
- package/src/components/Layout.jsx +142 -139
- package/src/components/MarkdownRenderer.jsx +149 -0
- package/src/components/PipelineDAGGrid.jsx +404 -0
- package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
- package/src/components/SchemaPreviewPanel.jsx +97 -0
- package/src/components/StageTimeline.jsx +36 -0
- package/src/components/TaskAnalysisDisplay.jsx +227 -0
- package/src/components/TaskCreationSidebar.jsx +447 -0
- package/src/components/TaskDetailSidebar.jsx +119 -117
- package/src/components/TaskFilePane.jsx +94 -39
- package/src/components/ui/RestartJobModal.jsx +26 -6
- package/src/components/ui/StopJobModal.jsx +183 -0
- package/src/components/ui/button.jsx +59 -27
- package/src/components/ui/sidebar.jsx +118 -0
- package/src/config/models.js +99 -67
- package/src/core/config.js +11 -4
- package/src/core/lifecycle-policy.js +62 -0
- package/src/core/pipeline-runner.js +312 -217
- package/src/core/status-writer.js +84 -0
- package/src/llm/index.js +129 -9
- package/src/pages/Code.jsx +8 -1
- package/src/pages/PipelineDetail.jsx +84 -2
- package/src/pages/PipelineList.jsx +214 -0
- package/src/pages/PipelineTypeDetail.jsx +234 -0
- package/src/pages/PromptPipelineDashboard.jsx +10 -11
- package/src/providers/deepseek.js +76 -16
- package/src/providers/openai.js +61 -34
- package/src/task-analysis/enrichers/analysis-writer.js +62 -0
- package/src/task-analysis/enrichers/schema-deducer.js +145 -0
- package/src/task-analysis/enrichers/schema-writer.js +74 -0
- package/src/task-analysis/extractors/artifacts.js +137 -0
- package/src/task-analysis/extractors/llm-calls.js +176 -0
- package/src/task-analysis/extractors/stages.js +51 -0
- package/src/task-analysis/index.js +103 -0
- package/src/task-analysis/parser.js +28 -0
- package/src/task-analysis/utils/ast.js +43 -0
- package/src/ui/client/adapters/job-adapter.js +60 -0
- package/src/ui/client/api.js +233 -8
- package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
- package/src/ui/client/hooks/useJobList.js +14 -1
- package/src/ui/client/index.css +64 -0
- package/src/ui/client/main.jsx +4 -0
- package/src/ui/client/sse-fetch.js +120 -0
- package/src/ui/dist/app.js +262 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
- package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
- package/src/ui/dist/favicon.svg +12 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
- package/src/ui/endpoints/file-endpoints.js +330 -0
- package/src/ui/endpoints/job-control-endpoints.js +1001 -0
- package/src/ui/endpoints/job-endpoints.js +62 -0
- package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
- package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
- package/src/ui/endpoints/pipelines-endpoint.js +133 -0
- package/src/ui/endpoints/schema-file-endpoint.js +105 -0
- package/src/ui/endpoints/sse-endpoints.js +223 -0
- package/src/ui/endpoints/state-endpoint.js +85 -0
- package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
- package/src/ui/endpoints/task-creation-endpoint.js +114 -0
- package/src/ui/endpoints/task-save-endpoint.js +101 -0
- package/src/ui/endpoints/upload-endpoints.js +406 -0
- package/src/ui/express-app.js +227 -0
- package/src/ui/lib/analysis-lock.js +67 -0
- package/src/ui/lib/sse.js +30 -0
- package/src/ui/server.js +42 -1880
- package/src/ui/sse-broadcast.js +93 -0
- package/src/ui/utils/http-utils.js +139 -0
- package/src/ui/utils/mime-types.js +196 -0
- package/src/ui/utils/slug.js +31 -0
- package/src/ui/vite.config.js +22 -0
- package/src/ui/watcher.js +28 -2
- package/src/utils/jobs.js +39 -0
- package/src/ui/dist/assets/index-DeDzq-Kk.js +0 -23863
- package/src/ui/dist/assets/style-aBtD_Yrs.css +0 -62
|
@@ -208,6 +208,26 @@ export async function writeJobStatus(jobDir, updateFn) {
|
|
|
208
208
|
logger.error("Failed to emit SSE event:", error);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Emit lifecycle_block event if update contains lifecycle block reason
|
|
212
|
+
if (snapshot.lifecycleBlockReason) {
|
|
213
|
+
try {
|
|
214
|
+
const lifecycleEventData = {
|
|
215
|
+
jobId,
|
|
216
|
+
taskId: snapshot.lifecycleBlockTaskId,
|
|
217
|
+
op: snapshot.lifecycleBlockOp,
|
|
218
|
+
reason: snapshot.lifecycleBlockReason,
|
|
219
|
+
};
|
|
220
|
+
await logger.sse("lifecycle_block", lifecycleEventData);
|
|
221
|
+
logger.log(
|
|
222
|
+
"lifecycle_block SSE event broadcasted successfully",
|
|
223
|
+
lifecycleEventData
|
|
224
|
+
);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Don't fail the write if SSE emission fails
|
|
227
|
+
logger.error("Failed to emit lifecycle_block SSE event:", error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
logger.groupEnd();
|
|
212
232
|
resultSnapshot = snapshot;
|
|
213
233
|
})
|
|
@@ -485,6 +505,70 @@ export async function resetJobToCleanSlate(
|
|
|
485
505
|
});
|
|
486
506
|
}
|
|
487
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Reset a single task to pending state without affecting other tasks
|
|
510
|
+
*
|
|
511
|
+
* @param {string} jobDir - Job directory path containing tasks-status.json
|
|
512
|
+
* @param {string} taskId - Task identifier to reset
|
|
513
|
+
* @param {Object} options - Reset options
|
|
514
|
+
* @param {boolean} [options.clearTokenUsage=true] - Whether to clear token usage arrays
|
|
515
|
+
* @returns {Promise<Object>} The updated status snapshot
|
|
516
|
+
*/
|
|
517
|
+
export async function resetSingleTask(
|
|
518
|
+
jobDir,
|
|
519
|
+
taskId,
|
|
520
|
+
{ clearTokenUsage = true } = {}
|
|
521
|
+
) {
|
|
522
|
+
if (!jobDir || typeof jobDir !== "string") {
|
|
523
|
+
throw new Error("jobDir must be a non-empty string");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (!taskId || typeof taskId !== "string") {
|
|
527
|
+
throw new Error("taskId must be a non-empty string");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return writeJobStatus(jobDir, (snapshot) => {
|
|
531
|
+
// Ensure tasks object exists
|
|
532
|
+
if (!snapshot.tasks || typeof snapshot.tasks !== "object") {
|
|
533
|
+
snapshot.tasks = {};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Ensure the target task exists
|
|
537
|
+
if (!snapshot.tasks[taskId]) {
|
|
538
|
+
snapshot.tasks[taskId] = {};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const task = snapshot.tasks[taskId];
|
|
542
|
+
|
|
543
|
+
// Reset only the target task state and metadata
|
|
544
|
+
task.state = TaskState.PENDING;
|
|
545
|
+
task.currentStage = null;
|
|
546
|
+
|
|
547
|
+
// Remove error-related fields
|
|
548
|
+
delete task.failedStage;
|
|
549
|
+
delete task.error;
|
|
550
|
+
|
|
551
|
+
// Reset counters
|
|
552
|
+
task.attempts = 0;
|
|
553
|
+
task.refinementAttempts = 0;
|
|
554
|
+
|
|
555
|
+
// Clear token usage if requested
|
|
556
|
+
if (clearTokenUsage) {
|
|
557
|
+
task.tokenUsage = [];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Update lastUpdated timestamp
|
|
561
|
+
snapshot.lastUpdated = new Date().toISOString();
|
|
562
|
+
|
|
563
|
+
// Do not modify:
|
|
564
|
+
// - Any other tasks within snapshot.tasks
|
|
565
|
+
// - snapshot.files.artifacts|logs|tmp
|
|
566
|
+
// - Root-level fields other than lastUpdated
|
|
567
|
+
|
|
568
|
+
return snapshot;
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
488
572
|
/**
|
|
489
573
|
* Consolidated path jail security validation with generic error messages
|
|
490
574
|
* @param {string} filename - Filename to validate
|
package/src/llm/index.js
CHANGED
|
@@ -91,6 +91,15 @@ export function calculateCost(provider, model, usage) {
|
|
|
91
91
|
|
|
92
92
|
// Core chat function - no metrics handling needed!
|
|
93
93
|
export async function chat(options) {
|
|
94
|
+
console.log("[llm] chat() called with options:", {
|
|
95
|
+
provider: options.provider,
|
|
96
|
+
model: options.model,
|
|
97
|
+
messageCount: options.messages?.length || 0,
|
|
98
|
+
hasTemperature: options.temperature !== undefined,
|
|
99
|
+
hasMaxTokens: options.maxTokens !== undefined,
|
|
100
|
+
responseFormat: options.responseFormat,
|
|
101
|
+
});
|
|
102
|
+
|
|
94
103
|
const {
|
|
95
104
|
provider,
|
|
96
105
|
model,
|
|
@@ -103,6 +112,7 @@ export async function chat(options) {
|
|
|
103
112
|
presencePenalty,
|
|
104
113
|
stop,
|
|
105
114
|
responseFormat,
|
|
115
|
+
stream = false,
|
|
106
116
|
...rest
|
|
107
117
|
} = options;
|
|
108
118
|
|
|
@@ -111,27 +121,41 @@ export async function chat(options) {
|
|
|
111
121
|
|
|
112
122
|
const available = getAvailableProviders();
|
|
113
123
|
|
|
124
|
+
console.log("[llm] Available providers:", available);
|
|
125
|
+
console.log("[llm] Requested provider:", provider);
|
|
126
|
+
|
|
114
127
|
if (!available[provider]) {
|
|
128
|
+
console.error("[llm] Provider not available:", provider);
|
|
115
129
|
throw new Error(`Provider ${provider} not available. Check API keys.`);
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
const startTime = Date.now();
|
|
119
133
|
const requestId = `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
120
134
|
|
|
121
|
-
// Default to JSON mode if not specified
|
|
122
|
-
const finalResponseFormat = responseFormat ?? "json";
|
|
123
|
-
|
|
124
135
|
// Extract system and user messages
|
|
125
136
|
const systemMsg = messages.find((m) => m.role === "system")?.content || "";
|
|
126
137
|
const userMessages = messages.filter((m) => m.role === "user");
|
|
127
138
|
const userMsg = userMessages.map((m) => m.content).join("\n");
|
|
128
139
|
|
|
140
|
+
console.log("[llm] Message analysis:", {
|
|
141
|
+
hasSystemMessage: !!systemMsg,
|
|
142
|
+
systemMessageLength: systemMsg.length,
|
|
143
|
+
userMessageCount: userMessages.length,
|
|
144
|
+
userMessageLength: userMsg.length,
|
|
145
|
+
totalMessageLength: systemMsg.length + userMsg.length,
|
|
146
|
+
});
|
|
147
|
+
|
|
129
148
|
// DEBUG write_to_file messages to /tmp/messages.log for debugging
|
|
130
149
|
fs.writeFileSync(
|
|
131
150
|
"/tmp/messages.log",
|
|
132
151
|
JSON.stringify({ messages, systemMsg, userMsg, provider, model }, null, 2)
|
|
133
152
|
);
|
|
134
153
|
|
|
154
|
+
console.log(
|
|
155
|
+
"[llm] Emitting llm:request:start event for requestId:",
|
|
156
|
+
requestId
|
|
157
|
+
);
|
|
158
|
+
|
|
135
159
|
// Emit request start event
|
|
136
160
|
llmEvents.emit("llm:request:start", {
|
|
137
161
|
id: requestId,
|
|
@@ -142,10 +166,12 @@ export async function chat(options) {
|
|
|
142
166
|
});
|
|
143
167
|
|
|
144
168
|
try {
|
|
169
|
+
console.log("[llm] Starting provider call for:", provider);
|
|
145
170
|
let response;
|
|
146
171
|
let usage;
|
|
147
172
|
|
|
148
173
|
if (provider === "mock") {
|
|
174
|
+
console.log("[llm] Using mock provider");
|
|
149
175
|
if (!mockProviderInstance) {
|
|
150
176
|
throw new Error(
|
|
151
177
|
"Mock provider not registered. Call registerMockProvider() first."
|
|
@@ -159,6 +185,7 @@ export async function chat(options) {
|
|
|
159
185
|
maxTokens,
|
|
160
186
|
...rest,
|
|
161
187
|
});
|
|
188
|
+
console.log("[llm] Mock provider returned result");
|
|
162
189
|
|
|
163
190
|
response = {
|
|
164
191
|
content: result.content,
|
|
@@ -171,6 +198,7 @@ export async function chat(options) {
|
|
|
171
198
|
totalTokens: result.usage.total_tokens,
|
|
172
199
|
};
|
|
173
200
|
} else if (provider === "openai") {
|
|
201
|
+
console.log("[llm] Using OpenAI provider");
|
|
174
202
|
const openaiArgs = {
|
|
175
203
|
messages,
|
|
176
204
|
model: model || "gpt-5-chat-latest",
|
|
@@ -178,7 +206,14 @@ export async function chat(options) {
|
|
|
178
206
|
maxTokens,
|
|
179
207
|
...rest,
|
|
180
208
|
};
|
|
181
|
-
|
|
209
|
+
console.log("[llm] OpenAI call parameters:", {
|
|
210
|
+
model: openaiArgs.model,
|
|
211
|
+
hasMessages: !!openaiArgs.messages,
|
|
212
|
+
messageCount: openaiArgs.messages?.length,
|
|
213
|
+
});
|
|
214
|
+
if (responseFormat !== undefined) {
|
|
215
|
+
openaiArgs.responseFormat = responseFormat;
|
|
216
|
+
}
|
|
182
217
|
if (topP !== undefined) openaiArgs.topP = topP;
|
|
183
218
|
if (frequencyPenalty !== undefined)
|
|
184
219
|
openaiArgs.frequencyPenalty = frequencyPenalty;
|
|
@@ -186,7 +221,13 @@ export async function chat(options) {
|
|
|
186
221
|
openaiArgs.presencePenalty = presencePenalty;
|
|
187
222
|
if (stop !== undefined) openaiArgs.stop = stop;
|
|
188
223
|
|
|
224
|
+
console.log("[llm] Calling openaiChat()...");
|
|
189
225
|
const result = await openaiChat(openaiArgs);
|
|
226
|
+
console.log("[llm] openaiChat() returned:", {
|
|
227
|
+
hasResult: !!result,
|
|
228
|
+
hasContent: !!result?.content,
|
|
229
|
+
hasUsage: !!result?.usage,
|
|
230
|
+
});
|
|
190
231
|
|
|
191
232
|
response = {
|
|
192
233
|
content:
|
|
@@ -213,22 +254,43 @@ export async function chat(options) {
|
|
|
213
254
|
};
|
|
214
255
|
}
|
|
215
256
|
} else if (provider === "deepseek") {
|
|
257
|
+
console.log("[llm] Using DeepSeek provider");
|
|
216
258
|
const deepseekArgs = {
|
|
217
259
|
messages,
|
|
218
|
-
model: model ||
|
|
260
|
+
model: model || MODEL_CONFIG[DEFAULT_MODEL_BY_PROVIDER.deepseek].model,
|
|
219
261
|
temperature,
|
|
220
262
|
maxTokens,
|
|
221
263
|
...rest,
|
|
222
264
|
};
|
|
265
|
+
console.log("[llm] DeepSeek call parameters:", {
|
|
266
|
+
model: deepseekArgs.model,
|
|
267
|
+
hasMessages: !!deepseekArgs.messages,
|
|
268
|
+
messageCount: deepseekArgs.messages?.length,
|
|
269
|
+
});
|
|
270
|
+
if (stream !== undefined) deepseekArgs.stream = stream;
|
|
223
271
|
if (topP !== undefined) deepseekArgs.topP = topP;
|
|
224
272
|
if (frequencyPenalty !== undefined)
|
|
225
273
|
deepseekArgs.frequencyPenalty = frequencyPenalty;
|
|
226
274
|
if (presencePenalty !== undefined)
|
|
227
275
|
deepseekArgs.presencePenalty = presencePenalty;
|
|
228
276
|
if (stop !== undefined) deepseekArgs.stop = stop;
|
|
229
|
-
|
|
277
|
+
if (responseFormat !== undefined) {
|
|
278
|
+
deepseekArgs.responseFormat = responseFormat;
|
|
279
|
+
}
|
|
230
280
|
|
|
281
|
+
console.log("[llm] Calling deepseekChat()...");
|
|
231
282
|
const result = await deepseekChat(deepseekArgs);
|
|
283
|
+
console.log("[llm] deepseekChat() returned:", {
|
|
284
|
+
hasResult: !!result,
|
|
285
|
+
isStream: typeof result?.[Symbol.asyncIterator] !== "undefined",
|
|
286
|
+
hasContent: !!result?.content,
|
|
287
|
+
hasUsage: !!result?.usage,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Streaming mode - return async generator directly
|
|
291
|
+
if (stream && typeof result?.[Symbol.asyncIterator] !== "undefined") {
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
232
294
|
|
|
233
295
|
response = {
|
|
234
296
|
content: result.content,
|
|
@@ -254,6 +316,7 @@ export async function chat(options) {
|
|
|
254
316
|
};
|
|
255
317
|
}
|
|
256
318
|
} else if (provider === "anthropic") {
|
|
319
|
+
console.log("[llm] Using Anthropic provider");
|
|
257
320
|
const defaultAlias = DEFAULT_MODEL_BY_PROVIDER.anthropic;
|
|
258
321
|
const defaultModelConfig = MODEL_CONFIG[defaultAlias];
|
|
259
322
|
const defaultModel = defaultModelConfig?.model;
|
|
@@ -265,11 +328,24 @@ export async function chat(options) {
|
|
|
265
328
|
maxTokens,
|
|
266
329
|
...rest,
|
|
267
330
|
};
|
|
331
|
+
console.log("[llm] Anthropic call parameters:", {
|
|
332
|
+
model: anthropicArgs.model,
|
|
333
|
+
hasMessages: !!anthropicArgs.messages,
|
|
334
|
+
messageCount: anthropicArgs.messages?.length,
|
|
335
|
+
});
|
|
268
336
|
if (topP !== undefined) anthropicArgs.topP = topP;
|
|
269
337
|
if (stop !== undefined) anthropicArgs.stop = stop;
|
|
270
|
-
|
|
338
|
+
if (responseFormat !== undefined) {
|
|
339
|
+
anthropicArgs.responseFormat = responseFormat;
|
|
340
|
+
}
|
|
271
341
|
|
|
342
|
+
console.log("[llm] Calling anthropicChat()...");
|
|
272
343
|
const result = await anthropicChat(anthropicArgs);
|
|
344
|
+
console.log("[llm] anthropicChat() returned:", {
|
|
345
|
+
hasResult: !!result,
|
|
346
|
+
hasContent: !!result?.content,
|
|
347
|
+
hasUsage: !!result?.usage,
|
|
348
|
+
});
|
|
273
349
|
|
|
274
350
|
response = {
|
|
275
351
|
content: result.content,
|
|
@@ -296,6 +372,7 @@ export async function chat(options) {
|
|
|
296
372
|
};
|
|
297
373
|
}
|
|
298
374
|
} else if (provider === "gemini") {
|
|
375
|
+
console.log("[llm] Using Gemini provider");
|
|
299
376
|
const geminiArgs = {
|
|
300
377
|
messages,
|
|
301
378
|
model: model || "gemini-2.5-flash",
|
|
@@ -303,11 +380,24 @@ export async function chat(options) {
|
|
|
303
380
|
maxTokens,
|
|
304
381
|
...rest,
|
|
305
382
|
};
|
|
383
|
+
console.log("[llm] Gemini call parameters:", {
|
|
384
|
+
model: geminiArgs.model,
|
|
385
|
+
hasMessages: !!geminiArgs.messages,
|
|
386
|
+
messageCount: geminiArgs.messages?.length,
|
|
387
|
+
});
|
|
306
388
|
if (topP !== undefined) geminiArgs.topP = topP;
|
|
307
389
|
if (stop !== undefined) geminiArgs.stop = stop;
|
|
308
|
-
|
|
390
|
+
if (responseFormat !== undefined) {
|
|
391
|
+
geminiArgs.responseFormat = responseFormat;
|
|
392
|
+
}
|
|
309
393
|
|
|
394
|
+
console.log("[llm] Calling geminiChat()...");
|
|
310
395
|
const result = await geminiChat(geminiArgs);
|
|
396
|
+
console.log("[llm] geminiChat() returned:", {
|
|
397
|
+
hasResult: !!result,
|
|
398
|
+
hasContent: !!result?.content,
|
|
399
|
+
hasUsage: !!result?.usage,
|
|
400
|
+
});
|
|
311
401
|
|
|
312
402
|
response = {
|
|
313
403
|
content: result.content,
|
|
@@ -334,6 +424,7 @@ export async function chat(options) {
|
|
|
334
424
|
};
|
|
335
425
|
}
|
|
336
426
|
} else if (provider === "zhipu") {
|
|
427
|
+
console.log("[llm] Using Zhipu provider");
|
|
337
428
|
const defaultAlias = DEFAULT_MODEL_BY_PROVIDER.zhipu;
|
|
338
429
|
const defaultModelConfig = MODEL_CONFIG[defaultAlias];
|
|
339
430
|
const defaultModel = defaultModelConfig?.model;
|
|
@@ -345,11 +436,24 @@ export async function chat(options) {
|
|
|
345
436
|
maxTokens,
|
|
346
437
|
...rest,
|
|
347
438
|
};
|
|
439
|
+
console.log("[llm] Zhipu call parameters:", {
|
|
440
|
+
model: zhipuArgs.model,
|
|
441
|
+
hasMessages: !!zhipuArgs.messages,
|
|
442
|
+
messageCount: zhipuArgs.messages?.length,
|
|
443
|
+
});
|
|
348
444
|
if (topP !== undefined) zhipuArgs.topP = topP;
|
|
349
445
|
if (stop !== undefined) zhipuArgs.stop = stop;
|
|
350
|
-
|
|
446
|
+
if (responseFormat !== undefined) {
|
|
447
|
+
zhipuArgs.responseFormat = responseFormat;
|
|
448
|
+
}
|
|
351
449
|
|
|
450
|
+
console.log("[llm] Calling zhipuChat()...");
|
|
352
451
|
const result = await zhipuChat(zhipuArgs);
|
|
452
|
+
console.log("[llm] zhipuChat() returned:", {
|
|
453
|
+
hasResult: !!result,
|
|
454
|
+
hasContent: !!result?.content,
|
|
455
|
+
hasUsage: !!result?.usage,
|
|
456
|
+
});
|
|
353
457
|
|
|
354
458
|
response = {
|
|
355
459
|
content: result.content,
|
|
@@ -376,12 +480,21 @@ export async function chat(options) {
|
|
|
376
480
|
};
|
|
377
481
|
}
|
|
378
482
|
} else {
|
|
483
|
+
console.error("[llm] Unknown provider:", provider);
|
|
379
484
|
throw new Error(`Provider ${provider} not yet implemented`);
|
|
380
485
|
}
|
|
381
486
|
|
|
487
|
+
console.log("[llm] Processing response from provider:", provider);
|
|
488
|
+
|
|
382
489
|
const duration = Date.now() - startTime;
|
|
383
490
|
const cost = calculateCost(provider, model, usage);
|
|
384
491
|
|
|
492
|
+
console.log("[llm] Request completed:", {
|
|
493
|
+
duration: `${duration}ms`,
|
|
494
|
+
cost,
|
|
495
|
+
usage,
|
|
496
|
+
});
|
|
497
|
+
|
|
385
498
|
// Emit success event with metrics
|
|
386
499
|
llmEvents.emit("llm:request:complete", {
|
|
387
500
|
id: requestId,
|
|
@@ -402,6 +515,13 @@ export async function chat(options) {
|
|
|
402
515
|
} catch (error) {
|
|
403
516
|
const duration = Date.now() - startTime;
|
|
404
517
|
|
|
518
|
+
console.error("[llm] Error in chat():", {
|
|
519
|
+
error: error.message,
|
|
520
|
+
name: error.name,
|
|
521
|
+
stack: error.stack,
|
|
522
|
+
duration: `${duration}ms`,
|
|
523
|
+
});
|
|
524
|
+
|
|
405
525
|
// Emit error event
|
|
406
526
|
llmEvents.emit("llm:request:error", {
|
|
407
527
|
id: requestId,
|
package/src/pages/Code.jsx
CHANGED
|
@@ -86,7 +86,14 @@ export default function CodePage() {
|
|
|
86
86
|
useEffect(() => {
|
|
87
87
|
fetch("/api/llm/functions")
|
|
88
88
|
.then((res) => res.json())
|
|
89
|
-
.then(
|
|
89
|
+
.then(({ ok, data }) => {
|
|
90
|
+
if (!ok || typeof data !== "object" || data === null) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Invalid /api/llm/functions response: expected { ok:true, data:Object }"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
setLlmFunctions(data);
|
|
96
|
+
})
|
|
90
97
|
.catch(console.error);
|
|
91
98
|
}, []);
|
|
92
99
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { data, useParams } from "react-router-dom";
|
|
3
3
|
import { Box, Flex, Text } from "@radix-ui/themes";
|
|
4
4
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
@@ -8,9 +8,16 @@ import Layout from "../components/Layout.jsx";
|
|
|
8
8
|
import PageSubheader from "../components/PageSubheader.jsx";
|
|
9
9
|
import { statusBadge } from "../utils/ui.jsx";
|
|
10
10
|
import { formatCurrency4, formatTokensCompact } from "../utils/formatters.js";
|
|
11
|
+
import { rescanJob } from "../ui/client/api.js";
|
|
12
|
+
import StopJobModal from "../components/ui/StopJobModal.jsx";
|
|
13
|
+
import { stopJob } from "../ui/client/api.js";
|
|
14
|
+
import { Button } from "../components/ui/button.jsx";
|
|
11
15
|
|
|
12
16
|
export default function PipelineDetail() {
|
|
13
17
|
const { jobId } = useParams();
|
|
18
|
+
const [isRescanning, setIsRescanning] = useState(false);
|
|
19
|
+
const [isStopModalOpen, setIsStopModalOpen] = useState(false);
|
|
20
|
+
const [isStopping, setIsStopping] = useState(false);
|
|
14
21
|
|
|
15
22
|
// Handle missing job ID (undefined/null)
|
|
16
23
|
if (jobId === undefined || jobId === null) {
|
|
@@ -143,6 +150,12 @@ export default function PipelineDetail() {
|
|
|
143
150
|
...(job.name ? [{ label: job.name }] : []),
|
|
144
151
|
];
|
|
145
152
|
|
|
153
|
+
// Determine if job is currently running
|
|
154
|
+
const isRunning =
|
|
155
|
+
job?.status === "running" ||
|
|
156
|
+
(job?.tasks &&
|
|
157
|
+
Object.values(job.tasks).some((task) => task?.state === "running"));
|
|
158
|
+
|
|
146
159
|
// Derive cost data from job object with safe fallbacks
|
|
147
160
|
const totalCost = job?.totalCost || job?.costs?.summary?.totalCost || 0;
|
|
148
161
|
const totalTokens = job?.totalTokens || job?.costs?.summary?.totalTokens || 0;
|
|
@@ -193,13 +206,74 @@ export default function PipelineDetail() {
|
|
|
193
206
|
costIndicator
|
|
194
207
|
);
|
|
195
208
|
|
|
196
|
-
|
|
209
|
+
const handleRescan = async () => {
|
|
210
|
+
setIsRescanning(true);
|
|
211
|
+
try {
|
|
212
|
+
const result = await rescanJob(jobId);
|
|
213
|
+
if (result.ok) {
|
|
214
|
+
const addedCount = result.added ? result.added.length : 0;
|
|
215
|
+
const removedCount = result.removed ? result.removed.length : 0;
|
|
216
|
+
let message = "Rescan complete.";
|
|
217
|
+
if (addedCount > 0 && removedCount > 0) {
|
|
218
|
+
message += ` Added ${addedCount} task${addedCount > 1 ? "s" : ""}: ${JSON.stringify(result.added)}. Removed ${removedCount} task${removedCount > 1 ? "s" : ""}: ${JSON.stringify(result.removed)}.`;
|
|
219
|
+
} else if (addedCount > 0) {
|
|
220
|
+
message += ` Added ${addedCount} task${addedCount > 1 ? "s" : ""}: ${JSON.stringify(result.added)}.`;
|
|
221
|
+
} else if (removedCount > 0) {
|
|
222
|
+
message += ` Removed ${removedCount} task${removedCount > 1 ? "s" : ""}: ${JSON.stringify(result.removed)}.`;
|
|
223
|
+
} else {
|
|
224
|
+
message += " No changes found.";
|
|
225
|
+
}
|
|
226
|
+
console.log(message);
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.error("Rescan failed:", err);
|
|
230
|
+
alert("Rescan failed: " + err.message);
|
|
231
|
+
} finally {
|
|
232
|
+
setIsRescanning(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const openStopModal = () => setIsStopModalOpen(true);
|
|
237
|
+
const closeStopModal = () => setIsStopModalOpen(false);
|
|
238
|
+
|
|
239
|
+
const handleStopConfirm = async () => {
|
|
240
|
+
setIsStopping(true);
|
|
241
|
+
try {
|
|
242
|
+
await stopJob(jobId);
|
|
243
|
+
closeStopModal();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.warn("Failed to stop job:", error);
|
|
246
|
+
closeStopModal();
|
|
247
|
+
} finally {
|
|
248
|
+
setIsStopping(false);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Right side content for PageSubheader: job ID, cost indicator, status badge, and Stop control
|
|
197
253
|
const subheaderRightContent = (
|
|
198
254
|
<Flex align="center" gap="3" className="shrink-0 flex-wrap">
|
|
199
255
|
<Text size="2" color="gray">
|
|
200
256
|
ID: {job.id || jobId}
|
|
201
257
|
</Text>
|
|
202
258
|
{costIndicatorWithTooltip}
|
|
259
|
+
{isRunning && (
|
|
260
|
+
<Button
|
|
261
|
+
size="sm"
|
|
262
|
+
variant="destructive"
|
|
263
|
+
disabled={isStopping}
|
|
264
|
+
onClick={openStopModal}
|
|
265
|
+
>
|
|
266
|
+
{isStopping ? "Stopping..." : "Stop"}
|
|
267
|
+
</Button>
|
|
268
|
+
)}
|
|
269
|
+
<Button
|
|
270
|
+
size="sm"
|
|
271
|
+
variant="outline"
|
|
272
|
+
disabled={isRescanning}
|
|
273
|
+
onClick={handleRescan}
|
|
274
|
+
>
|
|
275
|
+
{isRescanning ? "Rescanning..." : "Rescan"}
|
|
276
|
+
</Button>
|
|
203
277
|
{statusBadge(job.status)}
|
|
204
278
|
</Flex>
|
|
205
279
|
);
|
|
@@ -215,6 +289,14 @@ export default function PipelineDetail() {
|
|
|
215
289
|
)}
|
|
216
290
|
</PageSubheader>
|
|
217
291
|
<JobDetail job={job} pipeline={pipeline} />
|
|
292
|
+
<StopJobModal
|
|
293
|
+
isOpen={isStopModalOpen}
|
|
294
|
+
onClose={closeStopModal}
|
|
295
|
+
onConfirm={handleStopConfirm}
|
|
296
|
+
runningJobs={[{ id: job.id, name: job.name, progress: job.progress }]}
|
|
297
|
+
defaultJobId={job.id}
|
|
298
|
+
isSubmitting={isStopping}
|
|
299
|
+
/>
|
|
218
300
|
</Layout>
|
|
219
301
|
);
|
|
220
302
|
}
|