-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: add more context to chat #2886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -93,7 +93,7 @@ export const ChatInput = observer(({ | |||||||||||||||||||||||||||||||||
| return () => window.removeEventListener('keydown', handleGlobalKeyDown, true); | ||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const disabled = isStreaming | ||||||||||||||||||||||||||||||||||
| const disabled = false; // Allow input while streaming | ||||||||||||||||||||||||||||||||||
| const inputEmpty = !inputValue || inputValue.trim().length === 0; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function handleInput(e: React.ChangeEvent<HTMLTextAreaElement>) { | ||||||||||||||||||||||||||||||||||
|
|
@@ -136,22 +136,33 @@ export const ChatInput = observer(({ | |||||||||||||||||||||||||||||||||
| console.warn('Empty message'); | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (isStreaming) { | ||||||||||||||||||||||||||||||||||
| console.warn('Already waiting for response'); | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const savedInput = inputValue.trim(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| await onSendMessage(savedInput, chatMode); | ||||||||||||||||||||||||||||||||||
| if (isStreaming) { | ||||||||||||||||||||||||||||||||||
| // Queue the message if streaming | ||||||||||||||||||||||||||||||||||
| const queuedMessage = editorEngine.chat.queue.enqueue(savedInput, chatMode); | ||||||||||||||||||||||||||||||||||
| console.log('Message queued:', queuedMessage); | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| // Send immediately if not streaming | ||||||||||||||||||||||||||||||||||
| await onSendMessage(savedInput, chatMode); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| // Clear input | ||||||||||||||||||||||||||||||||||
| setInputValue(''); | ||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||
| console.error('Error sending message', error); | ||||||||||||||||||||||||||||||||||
| toast.error('Failed to send message. Please try again.'); | ||||||||||||||||||||||||||||||||||
| setInputValue(savedInput); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const getPlaceholderText = () => { | ||||||||||||||||||||||||||||||||||
| if (isStreaming && editorEngine.chat.queue.length > 0) { | ||||||||||||||||||||||||||||||||||
| return `${editorEngine.chat.queue.length} message${editorEngine.chat.queue.length > 1 ? 's' : ''} queued - type to add more...`; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (isStreaming) { | ||||||||||||||||||||||||||||||||||
| return 'Type to queue message while AI responds...'; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
157
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Localize new placeholders (next-intl). Avoid hardcoded user-facing strings; add keys with pluralization. - if (isStreaming && editorEngine.chat.queue.length > 0) {
- return `${editorEngine.chat.queue.length} message${editorEngine.chat.queue.length > 1 ? 's' : ''} queued - type to add more...`;
- }
- if (isStreaming) {
- return 'Type to queue message while AI responds...';
- }
+ if (isStreaming && editorEngine.chat.queue.length > 0) {
+ return t(transKeys.editor.chat.input.placeholder.streamingQueued, {
+ count: editorEngine.chat.queue.length,
+ });
+ }
+ if (isStreaming) {
+ return t(transKeys.editor.chat.input.placeholder.streaming);
+ }Please add the corresponding keys to your messages file. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| if (chatMode === ChatType.ASK) { | ||||||||||||||||||||||||||||||||||
| return 'Ask a question about your project...'; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
@@ -422,10 +433,11 @@ export const ChatInput = observer(({ | |||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||
| variant={'secondary'} | ||||||||||||||||||||||||||||||||||
| className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary" | ||||||||||||||||||||||||||||||||||
| disabled={inputEmpty || disabled} | ||||||||||||||||||||||||||||||||||
| disabled={inputEmpty} | ||||||||||||||||||||||||||||||||||
| onClick={() => void sendMessage()} | ||||||||||||||||||||||||||||||||||
| title={isStreaming ? 'Queue message' : 'Send message'} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <Icons.ArrowRight /> | ||||||||||||||||||||||||||||||||||
| {isStreaming ? <Icons.CounterClockwiseClock /> : <Icons.ArrowRight />} | ||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,11 +4,13 @@ import { makeAutoObservable } from 'mobx'; | |||||
| import type { EditorEngine } from '../engine'; | ||||||
| import { ChatContext } from './context'; | ||||||
| import { ConversationManager } from './conversation'; | ||||||
| import { MessageQueue } from './queue'; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use path alias for internal imports. Align with configured @/* aliases. -import { MessageQueue } from './queue';
+import { MessageQueue } from '@/components/store/editor/chat/queue';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| export const FOCUS_CHAT_INPUT_EVENT = 'focus-chat-input'; | ||||||
| export class ChatManager { | ||||||
| conversation: ConversationManager; | ||||||
| context: ChatContext; | ||||||
| queue: MessageQueue; | ||||||
|
|
||||||
| // Content sent from useChat hook | ||||||
| _sendMessageAction: SendMessage | null = null; | ||||||
|
|
@@ -17,6 +19,7 @@ export class ChatManager { | |||||
| constructor(private editorEngine: EditorEngine) { | ||||||
| this.context = new ChatContext(this.editorEngine); | ||||||
| this.conversation = new ConversationManager(this.editorEngine); | ||||||
| this.queue = new MessageQueue(this.editorEngine); | ||||||
| makeAutoObservable(this); | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -51,5 +54,6 @@ export class ChatManager { | |||||
| clear() { | ||||||
| this.context.clear(); | ||||||
| this.conversation.clear(); | ||||||
| this.queue.clear(); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,93 @@ | ||||||||||||||
| import { type ChatType } from '@onlook/models'; | ||||||||||||||
| import { makeAutoObservable } from 'mobx'; | ||||||||||||||
| import type { EditorEngine } from '../engine'; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use path alias for internal imports. Replace relative import with the configured alias per guidelines. -import type { EditorEngine } from '../engine';
+import type { EditorEngine } from '@/components/store/editor/engine';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| export interface QueuedMessage { | ||||||||||||||
| content: string; | ||||||||||||||
| type: ChatType; | ||||||||||||||
| id: string; | ||||||||||||||
| timestamp: number; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export class MessageQueue { | ||||||||||||||
| private _queue: QueuedMessage[] = []; | ||||||||||||||
| private _isProcessing = false; | ||||||||||||||
|
|
||||||||||||||
| constructor(private editorEngine: EditorEngine) { | ||||||||||||||
| makeAutoObservable(this); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+16
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t make EditorEngine observable. Avoid deep observability/memory churn by excluding heavy refs from MobX. - constructor(private editorEngine: EditorEngine) {
- makeAutoObservable(this);
- }
+ constructor(private editorEngine: EditorEngine) {
+ makeAutoObservable(this, { editorEngine: false });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| get queue(): QueuedMessage[] { | ||||||||||||||
| return [...this._queue]; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| get length(): number { | ||||||||||||||
| return this._queue.length; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| get isProcessing(): boolean { | ||||||||||||||
| return this._isProcessing; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| get isEmpty(): boolean { | ||||||||||||||
| return this._queue.length === 0; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| enqueue(content: string, type: ChatType): QueuedMessage { | ||||||||||||||
| const message: QueuedMessage = { | ||||||||||||||
| content: content.trim(), | ||||||||||||||
| type, | ||||||||||||||
| id: `queued_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, | ||||||||||||||
| timestamp: Date.now(), | ||||||||||||||
|
Comment on lines
+40
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Replace deprecated substr(). Use slice() to avoid deprecation warnings. - id: `queued_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ id: `queued_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| this._queue.push(message); | ||||||||||||||
| return message; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| dequeue(): QueuedMessage | null { | ||||||||||||||
| return this._queue.shift() || null; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| removeMessage(id: string): boolean { | ||||||||||||||
| const index = this._queue.findIndex(msg => msg.id === id); | ||||||||||||||
| if (index !== -1) { | ||||||||||||||
| this._queue.splice(index, 1); | ||||||||||||||
| return true; | ||||||||||||||
| } | ||||||||||||||
| return false; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| clear(): void { | ||||||||||||||
| this._queue = []; | ||||||||||||||
| this._isProcessing = false; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| async processNext(): Promise<void> { | ||||||||||||||
| if (this._isProcessing || this.isEmpty || this.editorEngine.chat.isStreaming) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| this._isProcessing = true; | ||||||||||||||
|
|
||||||||||||||
| const nextMessage = this.dequeue(); | ||||||||||||||
| if (!nextMessage) { | ||||||||||||||
| this._isProcessing = false; | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| try { | ||||||||||||||
| await this.editorEngine.chat.sendMessage(nextMessage.content, nextMessage.type); | ||||||||||||||
| } catch (error) { | ||||||||||||||
| console.error('Error processing queued message:', error); | ||||||||||||||
| } finally { | ||||||||||||||
| this._isProcessing = false; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| async processAll(): Promise<void> { | ||||||||||||||
| while (!this.isEmpty && !this.editorEngine.chat.isStreaming) { | ||||||||||||||
| await this.processNext(); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handler no longer restores the input value on send failure. Consider whether you want to recover the user's input to prevent accidental text loss.