more
This commit is contained in:
parent
3cbd591bc6
commit
c58566c82e
|
|
@ -7,11 +7,11 @@ import type { Conversation, LLMMessage } from './llm';
|
||||||
export function AIConversation({ history, conversation }: { history: LLMMessage[], conversation: Conversation }) {
|
export function AIConversation({ history, conversation }: { history: LLMMessage[], conversation: Conversation }) {
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
|
|
||||||
const onSubmit = useCallback(async (event: React.FormEvent<HTMLFormElement>) => {
|
const onSubmit = useCallback(() => {
|
||||||
event.preventDefault();
|
setInput(content => {
|
||||||
setInput('');
|
conversation.send(content);
|
||||||
const content = new FormData(event.target as any).get('content') as string;
|
return '';
|
||||||
await conversation.send(content);
|
});
|
||||||
}, [conversation]);
|
}, [conversation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -34,29 +34,33 @@ export function AIConversation({ history, conversation }: { history: LLMMessage[
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="input-form">
|
<div className="input-form">
|
||||||
<input
|
<textarea
|
||||||
type="text"
|
|
||||||
name='content'
|
name='content'
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Ask a question..."
|
placeholder="Ask a question..."
|
||||||
className="message-input"
|
className="message-input"
|
||||||
/>
|
/>
|
||||||
{conversation.isSending() ? (
|
{conversation.isSending() ? (
|
||||||
<button type="button" className="send-button" onClick={(evt) => {
|
<button type="button" className="send-button" onClick={(evt) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
console.log("aborting")
|
|
||||||
conversation.abortSending();
|
conversation.abortSending();
|
||||||
}}>
|
}}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button type="submit" className="send-button" disabled={!input.trim()}>
|
<button className="send-button" disabled={!input.trim()} onClick={onSubmit}>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ class LLMChat {
|
||||||
getConversation(id: string, systemPrompt: string) {
|
getConversation(id: string, systemPrompt: string) {
|
||||||
if (!this.conversations.has(id)) {
|
if (!this.conversations.has(id)) {
|
||||||
const conversation = new Conversation(this, systemPrompt);
|
const conversation = new Conversation(this, systemPrompt);
|
||||||
this.conversations.set(id, conversation);
|
this.conversations.set(id, conversation); // TODO: cleanup
|
||||||
}
|
}
|
||||||
return this.conversations.get(id)!;
|
return this.conversations.get(id)!;
|
||||||
}
|
}
|
||||||
|
|
@ -178,36 +178,37 @@ class LLMChat {
|
||||||
export class Conversation {
|
export class Conversation {
|
||||||
history: LLMMessage[];
|
history: LLMMessage[];
|
||||||
onChange = new EventEmitter<void>();
|
onChange = new EventEmitter<void>();
|
||||||
private _abortController: AbortController | undefined;
|
private _abortControllers = new Set<AbortController>();
|
||||||
|
|
||||||
constructor(private chat: LLMChat, systemPrompt: string) {
|
constructor(private chat: LLMChat, systemPrompt: string) {
|
||||||
this.history = [{ role: 'developer', content: systemPrompt }];
|
this.history = [{ role: 'developer', content: systemPrompt }];
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(content: string, displayContent?: string) {
|
async send(content: string, displayContent?: string) {
|
||||||
if (this.isSending())
|
|
||||||
throw new Error('Already sending');
|
|
||||||
|
|
||||||
const response: LLMMessage = { role: 'assistant', content: '' };
|
const response: LLMMessage = { role: 'assistant', content: '' };
|
||||||
this.history.push({ role: 'user', content, displayContent }, response);
|
this.history.push({ role: 'user', content, displayContent }, response);
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this._abortControllers.add(abortController);
|
||||||
this.onChange.fire();
|
this.onChange.fire();
|
||||||
this._abortController = new AbortController();
|
|
||||||
try {
|
try {
|
||||||
for await (const chunk of this.chat.api.chatCompletion(this.history, this._abortController.signal)) {
|
for await (const chunk of this.chat.api.chatCompletion(this.history, abortController.signal)) {
|
||||||
response.content += chunk;
|
response.content += chunk;
|
||||||
this.onChange.fire();
|
this.onChange.fire();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._abortController = undefined;
|
this._abortControllers.delete(abortController);
|
||||||
|
this.onChange.fire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isSending(): boolean {
|
isSending(): boolean {
|
||||||
return this._abortController !== undefined;
|
return this._abortControllers.size > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
abortSending() {
|
abortSending() {
|
||||||
this._abortController!.abort();
|
for (const controller of this._abortControllers)
|
||||||
|
controller.abort();
|
||||||
|
this._abortControllers.clear();
|
||||||
this.onChange.fire();
|
this.onChange.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue