forked from QuantStack/jupyter-ai-tutor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel.ts
More file actions
131 lines (114 loc) · 3.16 KB
/
Copy pathmodel.ts
File metadata and controls
131 lines (114 loc) · 3.16 KB
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
import {
AbstractChatModel,
IAttachment,
IChatContext,
IChatModel,
IMessageContent,
INewMessage,
IUser
} from '@jupyter/chat';
import { UUID } from '@lumino/coreutils';
import { streamExplanation } from './api';
import { AI_AVATAR } from './icons';
interface ITutorNewMessage extends INewMessage {
attachments?: IAttachment[];
}
export const TUTOR_USER: IUser = {
username: 'tutor',
display_name: 'Tutor',
initials: 'T',
bot: true,
avatar_url: AI_AVATAR
};
export interface ITutorChatContext extends IChatContext {
/**
* The stop streaming callback.
*/
stopStreaming: () => void;
/**
* The clear messages callback.
*/
clearMessages: () => Promise<void>;
}
/**
* Chat model for the AI tutor panel.
* Routes messages to the tutor backend and streams responses into the chat.
*/
export class TutorChatModel extends AbstractChatModel {
private _abortController: AbortController | null = null;
constructor(options?: IChatModel.IOptions) {
super(options);
this.name = 'Tutor';
this.setReady();
}
get user(): IUser {
return { username: 'user', display_name: 'You' };
}
sendMessage(message: ITutorNewMessage): void {
const userMsg: IMessageContent = {
type: 'msg',
id: UUID.uuid4(),
time: Date.now() / 1000,
body: message.body,
sender: this.user,
attachments: message.attachments
};
this.messageAdded(userMsg);
}
async sendMessageToAI(message: ITutorNewMessage): Promise<void> {
if (!message.body.trim()) return;
this.sendMessage(message);
this.updateWriters([{ user: TUTOR_USER }]);
// Add an initial empty tutor message slightly later to preserve order.
const tutorMsgContent: IMessageContent = {
type: 'msg',
id: UUID.uuid4(),
time: Date.now() / 1000 + 0.001,
body: '',
sender: TUTOR_USER
};
this.messageAdded(tutorMsgContent);
const streamingMsg = this.messages[this.messages.length - 1];
this._abortController = new AbortController();
try {
let accumulated = '';
for await (const chunk of streamExplanation(
message.body,
this._abortController.signal
)) {
accumulated += chunk;
streamingMsg.update({ body: accumulated });
}
} catch (err) {
if (err instanceof Error && err.name === 'AbortError') return;
const errorText = err instanceof Error ? err.message : String(err);
streamingMsg.update({
body: `Sorry, an error occurred: ${errorText}`
});
console.error('Tutor explanation failed:', err);
} finally {
this._abortController = null;
this.updateWriters([]);
}
}
createChatContext(): ITutorChatContext {
return {
name: this.name,
user: this.user,
users: [],
messages: this.messages,
stopStreaming: () => this.stopStreaming(),
clearMessages: () => this.clearMessages()
};
}
stopStreaming = (): void => {
this._abortController?.abort();
};
/**
* Clears all messages from the chat and resets conversation state.
*/
clearMessages = async (): Promise<void> => {
this.stopStreaming();
this.messagesDeleted(0, this.messages.length);
};
}