mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-23 14:33:36 +08:00
- Add 7 frequency modes (every N min/hours, daily, weekdays, specific days, monthly, custom cron) with progressive disclosure UI - Add "Run Now" button with confirmation popover and fire-and-forget API - Add execution logs panel (TaskRunsPanel) with auto-polling and accordion behavior - Add task edit mode with cron reverse-parsing (parseCron) to populate form - Add server-side extractAssistantText to store meaningful AI responses instead of raw NDJSON - Fix session linking: pass --session-id to CLI subprocess so "View conversation" navigates to actual content - Fix MACRO undefined error by adding --preload to Bun.spawn - Add confirmation popovers for all destructive actions (run/disable/delete) - Add DayOfWeekPicker component for specific-days scheduling - Add cronDescribe utility with i18n support and unit tests - Display task creation time and last run time - Add ~50 i18n keys (en/zh) for all new UI elements Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
/**
|
|
* Parse task run output into displayable text.
|
|
*
|
|
* The output may be in one of two formats:
|
|
*
|
|
* 1. **Extracted text** (new runs) — The server's `extractAssistantText` has
|
|
* already parsed the raw NDJSON and stored only the AI's text response.
|
|
* This is plain text / markdown that should be returned as-is.
|
|
*
|
|
* 2. **Raw NDJSON** (old runs before the server-side extraction was added) —
|
|
* Each line is a JSON object from the CLI's stream-json output. We parse
|
|
* these and extract assistant text blocks + result messages.
|
|
*
|
|
* Detection: if at least one line parses as JSON with a recognized `type`
|
|
* field, treat as NDJSON. Otherwise return as-is.
|
|
*/
|
|
export function parseRunOutput(raw: string): string {
|
|
if (!raw || !raw.trim()) return ''
|
|
|
|
const lines = raw.trim().split('\n')
|
|
|
|
// Quick check: does this look like NDJSON? (first non-empty line starts with '{')
|
|
const firstLine = lines.find((l) => l.trim())
|
|
if (!firstLine || !firstLine.trim().startsWith('{')) {
|
|
// Already extracted plain text — return as-is
|
|
return raw.trim()
|
|
}
|
|
|
|
// Try to parse as NDJSON (legacy format)
|
|
const textParts: string[] = []
|
|
let anyRecognized = false
|
|
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue
|
|
|
|
let parsed: any
|
|
try {
|
|
parsed = JSON.parse(line)
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
const type = parsed?.type
|
|
|
|
if (type === 'assistant') {
|
|
anyRecognized = true
|
|
const content = parsed?.message?.content
|
|
if (!Array.isArray(content)) continue
|
|
for (const block of content) {
|
|
if (block.type === 'text' && block.text?.trim()) {
|
|
textParts.push(block.text.trim())
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type === 'result') {
|
|
anyRecognized = true
|
|
const result = parsed?.result
|
|
if (typeof result === 'string' && result.trim()) {
|
|
textParts.push(result.trim())
|
|
} else if (result?.message?.trim()) {
|
|
textParts.push(result.message.trim())
|
|
}
|
|
}
|
|
|
|
if (type === 'system' || type === 'user') {
|
|
anyRecognized = true
|
|
// Skip these — not useful to display
|
|
}
|
|
}
|
|
|
|
// If we recognized NDJSON structure, return extracted text
|
|
if (anyRecognized) {
|
|
return textParts.join('\n\n')
|
|
}
|
|
|
|
// Fallback: the JSON lines didn't have recognized types — return raw
|
|
return raw.trim()
|
|
}
|