-
Notifications
You must be signed in to change notification settings - Fork 30
feat(chat): implement branching functionality and context management #2138
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
80fd3eb
feat(chat): implement branching functionality and context management
viniciusventura29 0b9eb89
refactor(chat): change useChatInputContext to a non-exported function
viniciusventura29 6fad021
fix(chat): update comment for clearing editing state after message send
viniciusventura29 18fbce8
fix(chat): update comment for clearing editing state after message send
viniciusventura29 e0dd42a
refactor(chat): simplify input handling in ChatInput component
viniciusventura29 73970d6
refactor(chat): remove unused state import in Chat component
viniciusventura29 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
apps/mesh/src/web/components/chat/chat-input-context.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| import { CornerUpLeft, X } from "@untitledui/icons"; | ||
| import { createContext, use, type PropsWithChildren } from "react"; | ||
| import { | ||
| useInputValue, | ||
| type InputController, | ||
| type BranchContext, | ||
| } from "../../hooks/use-persisted-chat"; | ||
|
|
||
| interface ChatInputContextValue { | ||
| inputController: InputController; | ||
| branchContext: BranchContext | null; | ||
| clearBranchContext: () => void; | ||
| onGoToOriginalMessage: () => void; | ||
| } | ||
|
|
||
| const ChatInputContext = createContext<ChatInputContextValue | null>(null); | ||
|
|
||
| function useChatInputContext() { | ||
| const ctx = use(ChatInputContext); | ||
| if (!ctx) { | ||
| throw new Error( | ||
| "useChatInputContext must be used within ChatInputProvider", | ||
| ); | ||
| } | ||
| return ctx; | ||
| } | ||
|
|
||
| export function ChatInputProvider({ | ||
| children, | ||
| inputController, | ||
| branchContext, | ||
| clearBranchContext, | ||
| onGoToOriginalMessage, | ||
| }: PropsWithChildren<ChatInputContextValue>) { | ||
| return ( | ||
| <ChatInputContext | ||
| value={{ | ||
| inputController, | ||
| branchContext, | ||
| clearBranchContext, | ||
| onGoToOriginalMessage, | ||
| }} | ||
| > | ||
| {children} | ||
| </ChatInputContext> | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Branch preview banner - shows when editing a message from a branch. | ||
| * Uses context internally, no props needed. | ||
| */ | ||
| export function BranchPreview() { | ||
| const { | ||
| branchContext, | ||
| clearBranchContext, | ||
| onGoToOriginalMessage, | ||
| inputController, | ||
| } = useChatInputContext(); | ||
|
|
||
| if (!branchContext) return null; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={onGoToOriginalMessage} | ||
| className="flex items-start gap-2 px-2 py-2 rounded-lg border border-dashed border-muted-foreground/30 bg-muted/50 text-sm hover:bg-muted transition-colors cursor-pointer text-left w-full" | ||
| title="Click to view original message" | ||
| > | ||
| <CornerUpLeft | ||
| size={14} | ||
| className="text-muted-foreground mt-0.5 shrink-0" | ||
| /> | ||
| <div className="flex-1 min-w-0"> | ||
| <div className="text-xs text-muted-foreground mb-1"> | ||
| Editing message (click to view original): | ||
| </div> | ||
| <div className="text-muted-foreground/70 line-clamp-2"> | ||
| {branchContext.originalMessageText} | ||
| </div> | ||
| </div> | ||
| <span | ||
| role="button" | ||
| tabIndex={0} | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| clearBranchContext(); | ||
| inputController.setValue(""); | ||
| }} | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter" || e.key === " ") { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| clearBranchContext(); | ||
| inputController.setValue(""); | ||
| } | ||
| }} | ||
| className="text-muted-foreground hover:text-foreground transition-colors shrink-0" | ||
| title="Cancel editing" | ||
| > | ||
| <X size={14} /> | ||
| </span> | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Hook to get controlled input value and handlers from context. | ||
| * Only the component using this hook re-renders on keystroke. | ||
| */ | ||
| export function useChatInputState() { | ||
| const { inputController, branchContext, clearBranchContext } = | ||
| useChatInputContext(); | ||
| const [inputValue, setInputValue] = useInputValue(inputController); | ||
|
|
||
| const handleInputChange = (value: string) => { | ||
| setInputValue(value); | ||
| }; | ||
|
|
||
| const handleSubmit = async ( | ||
| text: string, | ||
| onSubmit: (text: string) => Promise<void>, | ||
| ) => { | ||
| setInputValue(""); | ||
| await onSubmit(text); | ||
| }; | ||
|
|
||
| return { | ||
| inputValue, | ||
| setInputValue, | ||
| handleInputChange, | ||
| handleSubmit, | ||
| branchContext, | ||
| clearBranchContext, | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,10 +137,12 @@ function ChatMessages({ | |
| messages, | ||
| status, | ||
| minHeightOffset = 240, | ||
| onBranchFromMessage, | ||
| }: { | ||
| messages: ChatMessage[]; | ||
| status?: ChatStatus; | ||
| minHeightOffset?: number; | ||
| onBranchFromMessage?: (messageId: string, messageText: string) => void; | ||
| }) { | ||
| const sentinelRef = useRef<HTMLDivElement>(null); | ||
| useChatAutoScroll({ messageCount: messages.length, sentinelRef }); | ||
|
|
@@ -151,6 +153,7 @@ function ChatMessages({ | |
| message.role === "user" ? ( | ||
| <MessageUser | ||
| key={message.id} | ||
| onBranchFromMessage={onBranchFromMessage} | ||
| message={message as UIMessage<Metadata>} | ||
| /> | ||
| ) : message.role === "assistant" ? ( | ||
|
|
@@ -202,15 +205,23 @@ function ChatInput({ | |
| placeholder, | ||
| usageMessages, | ||
| children, | ||
| value, | ||
| onValueChange, | ||
| }: PropsWithChildren<{ | ||
| onSubmit: (text: string) => Promise<void>; | ||
| onStop: () => void; | ||
| disabled: boolean; | ||
| isStreaming: boolean; | ||
| placeholder: string; | ||
| usageMessages?: ChatMessage[]; | ||
| value?: string; | ||
| onValueChange?: (value: string) => void; | ||
| }>) { | ||
| const [input, setInput] = useState(""); | ||
| const [internalInput, setInternalInput] = useState(""); | ||
|
||
|
|
||
| // Use controlled value if provided, otherwise use internal state | ||
| const input = value !== undefined ? value : internalInput; | ||
| const setInput = onValueChange ?? setInternalInput; | ||
|
|
||
| const modelSelector = findChild(children, ChatInputModelSelector); | ||
| const gatewaySelector = findChild(children, ChatInputGatewaySelector); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.