From 0ab36753b22fcade74ee39616be29e772f9941a2 Mon Sep 17 00:00:00 2001 From: Zhiqiang ZHOU Date: Thu, 23 Oct 2025 14:23:08 -0700 Subject: [PATCH] chore: commit as stash Signed-off-by: Zhiqiang ZHOU --- app.go | 25 +++++++ frontend/src/pages/TaskPage.tsx | 56 ++++++++++++++- internal/services/summarizer.go | 120 ++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 5196807..ed4635d 100644 --- a/app.go +++ b/app.go @@ -517,6 +517,31 @@ func (a *App) processTask(task *types.Task) { } } + // Generate long-form post article using the structured prompt + videoURL := task.URL + if videoURL == "" && task.VideoID != "" { + videoURL = "https://youtube.com/watch?v=" + task.VideoID + } + postBytes, postErr := a.summarizer.GeneratePostArticle(a.ctx, a.settings.APIKey, string(srtBytes), task.Title, task.Channel, videoURL, a.settings.Temperature, a.settings.MaxTokens) + postPath := fmt.Sprintf("%s/post_article.md", workDir) + if postErr != nil { + a.logger.Error("Post generation failed", "taskId", task.ID, "error", postErr) + _ = a.storage.SaveLog(workDir, "post", fmt.Sprintf("Post generation failed: %v", postErr)) + placeholder := fmt.Sprintf("Article generation failed: %v\n", postErr) + if werr := os.WriteFile(postPath, []byte(placeholder), 0644); werr != nil { + a.logger.Error("Failed to write placeholder post", "taskId", task.ID, "error", werr) + } + } else { + content := strings.TrimSpace(string(postBytes)) + "\n" + if werr := os.WriteFile(postPath, []byte(content), 0644); werr != nil { + a.logger.Error("Failed to write post article", "taskId", task.ID, "error", werr) + _ = a.storage.SaveLog(workDir, "post", fmt.Sprintf("Failed to write post article: %v", werr)) + } else { + _ = a.storage.SaveLog(workDir, "post", "Post article generated via OpenRouter") + a.logger.Info("Post article generated", "taskId", task.ID, "path", postPath) + } + } + // Mark as done a.taskManager.UpdateTaskStatus(types.TaskStatusDone, 100) a.emitReloadEvent() diff --git a/frontend/src/pages/TaskPage.tsx b/frontend/src/pages/TaskPage.tsx index d9cf5ad..a24118c 100644 --- a/frontend/src/pages/TaskPage.tsx +++ b/frontend/src/pages/TaskPage.tsx @@ -36,6 +36,9 @@ export default function TaskPage() { const [subtitles, setSubtitles] = useState([]) const [transcriptSubtitles, setTranscriptSubtitles] = useState([]) const [summary, setSummary] = useState(null) + const [postContent, setPostContent] = useState(null) + const [postStatus, setPostStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle') + const [postError, setPostError] = useState(null) const videoPlayerRef = useRef(null) useEffect(() => { @@ -44,13 +47,16 @@ export default function TaskPage() { const loadTask = async () => { if (!taskId) return - + try { + setPostStatus('idle') + setPostContent(null) + setPostError(null) const tasks = await GetAllTasks() const task = tasks.find(t => t.id === taskId) if (task) { setVideo(task) - + // Load video file if task is completed if (task.status === 'done') { try { @@ -111,7 +117,7 @@ export default function TaskPage() { src: `/media/${task.id}/${sub.file}`, label: sub.label, language: sub.lang, - default: index === 0 // 第一个字幕轨道设为默认 + default: index === 0 // Set the first subtitle track as default })) setSubtitles(loadedSubs) } catch (err) { @@ -136,6 +142,29 @@ export default function TaskPage() { } catch (err) { console.error('Failed to load summary:', err) } + + // Load generated post article if available + try { + setPostStatus('loading') + const res = await fetch(`/media/${task.id}/post_article.md`) + if (res.ok) { + const text = await res.text() + setPostContent(text) + setPostStatus('success') + setPostError(null) + } else if (res.status === 404) { + setPostStatus('success') + setPostContent(null) + setPostError(null) + } else { + setPostStatus('error') + setPostError(`Unable to load article: ${res.status} ${res.statusText}`) + } + } catch (err) { + console.error('Failed to load post article:', err) + setPostStatus('error') + setPostError('Failed to load article content') + } } } } catch (err) { @@ -284,6 +313,7 @@ export default function TaskPage() { About Summary + Post Transcript @@ -356,6 +386,26 @@ export default function TaskPage() { + + {video.status === 'done' ? ( + postStatus === 'loading' ? ( +
Loading article...
+ ) : postStatus === 'error' ? ( +
+

{postError || 'Article not available.'}

+
+ ) : postContent ? ( +
+ {postContent} +
+ ) : ( +
Article is not available yet.
+ ) + ) : ( +
Article will be available after processing completes.
+ )} +
+ {transcriptSubtitles.length > 0 ? ( = 300 { + b, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("openrouter error: %s: %s", resp.Status, string(b)) + } + + var parsed struct { + Choices []struct { + Message struct { + Content string `json:"content"` + } `json:"message"` + } `json:"choices"` + } + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if err := json.Unmarshal(b, &parsed); err != nil { + return nil, fmt.Errorf("failed to parse OpenRouter response: %v", err) + } + if len(parsed.Choices) == 0 || strings.TrimSpace(parsed.Choices[0].Message.Content) == "" { + return nil, fmt.Errorf("empty post response") + } + + return []byte(parsed.Choices[0].Message.Content), nil +}