返回 JoyAI-Echo
usePromptStack.ts
根目录 / echo_longvideo / Director_Agent / webui / src / hooks / usePromptStack.ts
1 import { useState, useEffect, useCallback, useRef } from "react";
2
3 export interface PromptStackSession {
4 id: string;
5 filename: string;
6 turn_count: number;
7 size_bytes: number;
8 modified: string;
9 last_model: string;
10 last_session_key: string;
11 }
12
13 export interface PromptPart {
14 label: string;
15 content: string;
16 char_count: number;
17 }
18
19 export interface TraceMessage {
20 role: string;
21 preview: string;
22 content: string;
23 char_count: number;
24 tool_calls?: { name: string; id: string; arguments?: string }[];
25 tool_call_id?: string;
26 tool_name?: string;
27 }
28
29 export interface TraceRecord {
30 id: string;
31 timestamp: string;
32 session_key: string;
33 iteration: number;
34 model: string;
35 parts: PromptPart[];
36 messages_count: number;
37 messages: TraceMessage[];
38 response: {
39 content?: string;
40 tool_calls?: { name: string; arguments: string }[];
41 reasoning_content?: string;
42 usage?: Record<string, number>;
43 };
44 }
45
46 export function usePromptStack() {
47 const [sessions, setSessions] = useState<PromptStackSession[]>([]);
48 const [activeSession, setActiveSession] = useState<string | null>(null);
49 const [trace, setTrace] = useState<TraceRecord[]>([]);
50 const [loading, setLoading] = useState(false);
51
52 // Guard against stale fetch responses arriving after session switch
53 const fetchIdRef = useRef(0);
54
55 const fetchSessions = useCallback(async () => {
56 try {
57 const res = await fetch("/api/promptstack/sessions");
58 if (res.ok) {
59 const data = await res.json();
60 setSessions(data);
61 }
62 } catch (e) {
63 console.error("Failed to fetch promptstack sessions", e);
64 }
65 }, []);
66
67 const fetchTrace = useCallback(async (sessionId: string) => {
68 // Bump fetch ID so any in-flight request for a previous session is ignored
69 const thisId = ++fetchIdRef.current;
70 setLoading(true);
71 try {
72 const res = await fetch(`/api/promptstack/traces/${sessionId}`);
73 if (!res.ok) return;
74 const data = await res.json();
75 // Only apply if this is still the most recent fetch
76 if (fetchIdRef.current === thisId) {
77 setTrace(data);
78 }
79 } catch (e) {
80 console.error("Failed to fetch trace", e);
81 } finally {
82 if (fetchIdRef.current === thisId) {
83 setLoading(false);
84 }
85 }
86 }, []);
87
88 // Wrap setActiveSession to immediately clear stale trace data
89 const switchSession = useCallback(
90 (id: string | null) => {
91 setActiveSession((prev) => {
92 if (prev !== id) {
93 // Clear old data synchronously so UI never shows mismatched session/trace
94 setTrace([]);
95 }
96 return id;
97 });
98 },
99 [],
100 );
101
102 useEffect(() => {
103 fetchSessions();
104 }, [fetchSessions]);
105
106 useEffect(() => {
107 if (activeSession) {
108 fetchTrace(activeSession);
109 } else {
110 // No session selected — ensure trace is empty
111 fetchIdRef.current++;
112 setTrace([]);
113 setLoading(false);
114 }
115 }, [activeSession, fetchTrace]);
116
117 return {
118 sessions,
119 activeSession,
120 setActiveSession: switchSession,
121 trace,
122 loading,
123 refresh: useCallback(async () => {
124 await fetchSessions();
125 // Also re-fetch current trace to pick up new turns
126 if (activeSession) {
127 fetchTrace(activeSession);
128 }
129 }, [fetchSessions, fetchTrace, activeSession]),
130 };
131 }
132
132 lines TYPESCRIPT