1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
import { useState, useCallback, useEffect } from "react";
import {
getMeshChatHistory,
clearMeshChatHistory,
chatWithMeshContext,
type MeshChatMessageRecord,
type MeshChatContext,
type MeshChatResponse,
type LlmModel,
} from "../lib/api";
export interface MeshChatState {
conversationId: string | null;
messages: MeshChatMessageRecord[];
loading: boolean;
error: string | null;
sending: boolean;
}
export function useMeshChatHistory() {
const [state, setState] = useState<MeshChatState>({
conversationId: null,
messages: [],
loading: true,
error: null,
sending: false,
});
const fetchHistory = useCallback(async () => {
setState((prev) => ({ ...prev, loading: true, error: null }));
try {
const response = await getMeshChatHistory();
setState((prev) => ({
...prev,
conversationId: response.conversationId,
messages: response.messages,
loading: false,
}));
} catch (e) {
setState((prev) => ({
...prev,
error: e instanceof Error ? e.message : "Failed to fetch chat history",
loading: false,
}));
}
}, []);
const clearHistory = useCallback(async (): Promise<boolean> => {
setState((prev) => ({ ...prev, loading: true, error: null }));
try {
const response = await clearMeshChatHistory();
setState({
conversationId: response.conversationId,
messages: [],
loading: false,
error: null,
sending: false,
});
return true;
} catch (e) {
setState((prev) => ({
...prev,
error: e instanceof Error ? e.message : "Failed to clear chat history",
loading: false,
}));
return false;
}
}, []);
const sendMessage = useCallback(
async (
message: string,
context: MeshChatContext,
model?: LlmModel
): Promise<MeshChatResponse | null> => {
setState((prev) => ({ ...prev, sending: true, error: null }));
// Optimistically add user message (will be refetched after response)
const tempUserMessage: MeshChatMessageRecord = {
id: `temp-${Date.now()}`,
conversationId: state.conversationId || "",
role: "user",
content: message,
contextType: context.type,
contextTaskId: context.taskId || null,
toolCalls: null,
pendingQuestions: null,
createdAt: new Date().toISOString(),
};
setState((prev) => ({
...prev,
messages: [...prev.messages, tempUserMessage],
}));
try {
const response = await chatWithMeshContext(message, context, model);
// Refetch to get the actual saved messages (with proper IDs)
await fetchHistory();
setState((prev) => ({ ...prev, sending: false }));
return response;
} catch (e) {
// Remove optimistic message on error
setState((prev) => ({
...prev,
messages: prev.messages.filter((m) => m.id !== tempUserMessage.id),
error: e instanceof Error ? e.message : "Failed to send message",
sending: false,
}));
return null;
}
},
[state.conversationId, fetchHistory]
);
// Initial fetch on mount
useEffect(() => {
fetchHistory();
}, [fetchHistory]);
return {
conversationId: state.conversationId,
messages: state.messages,
loading: state.loading,
error: state.error,
sending: state.sending,
fetchHistory,
clearHistory,
sendMessage,
};
}
|