mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 18:15:02 +00:00
fix(react-widget): integrate with current /stream contract
This commit is contained in:
1 parent
e0aff39a1b
commit
1281a040ef
4 files changed
+753
-262
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,7 @@ interface FetchAnswerStreamingProps {
|
||||
conversationId?: string | null;
|
||||
apiHost?: string;
|
||||
onEvent?: (event: MessageEvent) => void;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface FeedbackPayload {
|
||||
@@ -31,13 +32,17 @@ export function fetchAnswerStreaming({
|
||||
onEvent = () => {
|
||||
console.log('Event triggered, but no handler provided.');
|
||||
},
|
||||
signal,
|
||||
}: FetchAnswerStreamingProps): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const body = {
|
||||
question: question,
|
||||
history: JSON.stringify(history),
|
||||
history: JSON.stringify(
|
||||
history
|
||||
.filter((item) => item.prompt && item.response)
|
||||
.map(({ prompt, response }) => ({ prompt, response })),
|
||||
),
|
||||
conversation_id: conversationId,
|
||||
model: 'default',
|
||||
api_key: apiKey,
|
||||
};
|
||||
fetch(apiHost + '/stream', {
|
||||
@@ -46,49 +51,61 @@ export function fetchAnswerStreaming({
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.body) throw Error('No response body');
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let counter = 0; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
let buffer = '';
|
||||
|
||||
const emit = (rawLine: string) => {
|
||||
const line = rawLine.trim();
|
||||
if (line === '') return;
|
||||
if (!line.startsWith('data:')) return;
|
||||
|
||||
const payload = line.substring(5).trim();
|
||||
if (payload === '' || payload === '[DONE]') return;
|
||||
|
||||
onEvent(new MessageEvent('message', { data: payload }));
|
||||
};
|
||||
|
||||
const processStream = ({
|
||||
done,
|
||||
value,
|
||||
}: ReadableStreamReadResult<Uint8Array>) => {
|
||||
if (done) {
|
||||
if (buffer.trim() !== '') emit(buffer);
|
||||
buffer = '';
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
counter += 1;
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() ?? '';
|
||||
for (const line of lines) emit(line);
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.trim() == '') {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('data:')) {
|
||||
line = line.substring(5);
|
||||
}
|
||||
|
||||
const messageEvent = new MessageEvent('message', {
|
||||
data: line,
|
||||
});
|
||||
|
||||
onEvent(messageEvent); // handle each message
|
||||
}
|
||||
|
||||
reader.read().then(processStream).catch(reject);
|
||||
reader.read().then(processStream).catch(onReadError);
|
||||
};
|
||||
|
||||
reader.read().then(processStream).catch(reject);
|
||||
const onReadError = (error: unknown) => {
|
||||
if (signal?.aborted || (error as Error)?.name === 'AbortError') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
};
|
||||
|
||||
reader.read().then(processStream).catch(onReadError);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (signal?.aborted || (error as Error)?.name === 'AbortError') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
console.error('Connection failed:', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,11 @@ declare module 'styled-components' {
|
||||
};
|
||||
/** Present only in SearchBar theme */
|
||||
name?: string;
|
||||
/** Gradient stops for the swept status text. */
|
||||
shimmer?: {
|
||||
base: string;
|
||||
highlight: string;
|
||||
};
|
||||
/** Present only in DocsGPTWidget theme (always provided when these styled components render) */
|
||||
dimensions?: {
|
||||
size: string;
|
||||
@@ -41,6 +46,12 @@ export interface Query {
|
||||
sources?: { title: string; text: string; source: string }[];
|
||||
conversationId?: string | null;
|
||||
title?: string | null;
|
||||
/** Accumulated from thought events. */
|
||||
thought?: string;
|
||||
/** Latest notice or running workflow node; drives the status line. */
|
||||
notice?: string;
|
||||
/** Tool names from tool_calls / tool_call. */
|
||||
toolCalls?: string[];
|
||||
}
|
||||
|
||||
export interface WidgetProps {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
export interface StreamEvent {
|
||||
type?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ToolCallEvent {
|
||||
action_name?: string;
|
||||
tool_name?: string;
|
||||
}
|
||||
|
||||
/** internal_search -> Internal Search */
|
||||
export const prettifyName = (name: string): string =>
|
||||
name
|
||||
.split(/[_\s]+/)
|
||||
.filter(Boolean)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
|
||||
/**
|
||||
* Status-line text for a running workflow node, or null when the node is
|
||||
* scaffolding the user gains nothing from seeing. Keyed on node_type (a
|
||||
* closed enum) rather than node_title, which is free text like "agent node".
|
||||
*/
|
||||
export const workflowStepLabel = (event: StreamEvent): string | null => {
|
||||
if (event.status !== 'running') return null;
|
||||
switch (event.node_type) {
|
||||
case 'agent':
|
||||
return 'Thinking…';
|
||||
case 'code':
|
||||
return 'Running code…';
|
||||
case 'condition':
|
||||
return 'Deciding next step…';
|
||||
case 'start':
|
||||
case 'end':
|
||||
case 'state':
|
||||
case 'note':
|
||||
return null;
|
||||
default:
|
||||
return typeof event.node_title === 'string' && event.node_title
|
||||
? `${prettifyName(event.node_title)}…`
|
||||
: null;
|
||||
}
|
||||
};
|
||||
|
||||
export const toolNames = (calls: unknown): string[] => {
|
||||
if (!Array.isArray(calls)) return [];
|
||||
return calls
|
||||
.map((call: ToolCallEvent | null) => call?.action_name ?? call?.tool_name)
|
||||
.filter((name): name is string => Boolean(name));
|
||||
};
|
||||
Reference in new issue
Block a user