-
Notifications
You must be signed in to change notification settings - Fork 46
feat(chat): quote transcript selections into the composer #28
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
Open
tulsi-builder
wants to merge
17
commits into
main
Choose a base branch
from
quote-to-composer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
52f59a2
feat(chat): stage transcript quotes in composer
morgmart f80ffbd
fix(chat): detect transcript selection in WebKit
morgmart f28ada8
fix(chat): map rendered markdown quote selections
morgmart d7777e3
feat(chat): send staged quote callbacks
morgmart 8347c41
refactor(chat): unify staged quote presentation
morgmart 66cf64a
refactor(chat): use Tabler quote icon
morgmart 856d794
test(chat): expect role provenance in quote selection sources
tulsi-builder 539df6e
feat(chat): map quote selections through renderer source segments
tulsi-builder 9422446
feat(chat): persist submitted quote provenance across replay
tulsi-builder dc4644e
feat(chat): map selections across blocks and messages into one quote
tulsi-builder 9283bd5
feat(chat): serialize staged quotes at the authoritative send attempt
tulsi-builder 1df6e66
feat(chat): govern quoting with one composer control policy
tulsi-builder 2c40a83
fix(chat): infer source segments for list subcontent after hard breaks
tulsi-builder 4d6f960
fix(chat): show quote pill after selection ends, centered on first line
tulsi-builder 90839d9
feat(chat): unify quote preview into one scrollable hover panel
tulsi-builder 62cba6f
refactor(chat): harden quote dispatch, coordinates, and send policy
tulsi-builder e46f5ae
fix(chat): center quote pill over the full first line of inline segments
tulsi-builder 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
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
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
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
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
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
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
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,67 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import type { StagedQuoteItem } from "@/shared/types/messages"; | ||
| import { | ||
| stagedQuoteLabel, | ||
| stagedQuoteSourceKind, | ||
| stagedQuoteWordCount, | ||
| } from "./stagedItemPresentation"; | ||
|
|
||
| function quote(overrides: Partial<StagedQuoteItem> = {}): StagedQuoteItem { | ||
| return { | ||
| id: "quote-1", | ||
| kind: "quote", | ||
| excerpt: "Saturn", | ||
| sources: [ | ||
| { | ||
| messageId: "message-1", | ||
| role: "assistant", | ||
| contentBlockIndex: 0, | ||
| start: 0, | ||
| end: 6, | ||
| }, | ||
| ], | ||
| ...overrides, | ||
| }; | ||
| } | ||
|
|
||
| describe("staged quote presentation", () => { | ||
| it("keeps short selections verbatim", () => { | ||
| expect(stagedQuoteLabel(quote())).toBe("Saturn"); | ||
| }); | ||
|
|
||
| it("creates a stable verbatim anchor for long selections", () => { | ||
| const label = stagedQuoteLabel( | ||
| quote({ excerpt: "A deliberately long selection ".repeat(5) }), | ||
| ); | ||
| expect(label.endsWith("…")).toBe(true); | ||
| expect(label.length).toBeLessThanOrEqual(73); | ||
| }); | ||
|
|
||
| it("describes source and extent without replacing the excerpt", () => { | ||
| expect(stagedQuoteSourceKind(quote())).toBe("agentResponse"); | ||
| expect(stagedQuoteWordCount(quote())).toBe(1); | ||
| expect( | ||
| stagedQuoteSourceKind( | ||
| quote({ | ||
| sources: [ | ||
| quote().sources[0], | ||
| { ...quote().sources[0], messageId: "message-2" }, | ||
| ], | ||
| }), | ||
| ), | ||
| ).toBe("multipleMessages"); | ||
| }); | ||
|
|
||
| it("treats multiple blocks of one message as a single-message quote", () => { | ||
| expect( | ||
| stagedQuoteSourceKind( | ||
| quote({ | ||
| sources: [ | ||
| quote().sources[0], | ||
| { ...quote().sources[0], contentBlockIndex: 1 }, | ||
| ], | ||
| }), | ||
| ), | ||
| ).toBe("agentResponse"); | ||
| }); | ||
| }); |
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,47 @@ | ||
| import type { StagedQuoteItem } from "@/shared/types/messages"; | ||
|
|
||
| const SHORT_QUOTE_CHARACTER_LIMIT = 72; | ||
|
|
||
| function compactWhitespace(value: string): string { | ||
| return value.replace(/\s+/g, " ").trim(); | ||
| } | ||
|
|
||
| function wordCount(value: string): number { | ||
| return compactWhitespace(value).split(" ").filter(Boolean).length; | ||
| } | ||
|
|
||
| export function stagedQuoteLabel(quote: StagedQuoteItem): string { | ||
| const excerpt = compactWhitespace(quote.excerpt); | ||
| if (excerpt.length <= SHORT_QUOTE_CHARACTER_LIMIT) return excerpt; | ||
| return `${excerpt.slice(0, SHORT_QUOTE_CHARACTER_LIMIT).trimEnd()}…`; | ||
| } | ||
|
|
||
| export type StagedQuoteSourceKind = | ||
| | "agentResponse" | ||
| | "yourMessage" | ||
| | "systemMessage" | ||
| | "multipleMessages"; | ||
|
|
||
| /** Distinct messages the quote draws from; multiple blocks of one message | ||
| * still count as one message. */ | ||
| export function stagedQuoteMessageCount(quote: StagedQuoteItem): number { | ||
| return new Set(quote.sources.map((source) => source.messageId)).size; | ||
| } | ||
|
|
||
| export function stagedQuoteSourceKind( | ||
| quote: StagedQuoteItem, | ||
| ): StagedQuoteSourceKind { | ||
| if (stagedQuoteMessageCount(quote) > 1) return "multipleMessages"; | ||
| switch (quote.sources[0]?.role) { | ||
| case "user": | ||
| return "yourMessage"; | ||
| case "system": | ||
| return "systemMessage"; | ||
| default: | ||
| return "agentResponse"; | ||
| } | ||
| } | ||
|
|
||
| export function stagedQuoteWordCount(quote: StagedQuoteItem): number { | ||
| return wordCount(quote.excerpt); | ||
| } |
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.
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.
🤖 P1 · Clear quotes after voice submission (blocking)
Voice auto-submit includes stagedItemsRef.current in the send at this line, but its accepted path clears only selected skills. useVoiceDictation subsequently clears text and attachments, and this path bypasses submitCurrentMessage, which contains the staged-item cleanup used by manual sends. The accepted quote consequently remains staged.
User effect: The sent quote chip remains in the empty composer and is silently included again in the next typed or dictated message unless the user notices and removes it, causing the agent to answer against stale context.
Recommended fix: Apply the same snapshot-aware staged-item cleanup to voice auto-submit as manual submission: after acceptance, remove only the submitted items if the current staged snapshot still matches, preserving any quote added while the send was pending.
Test: Voice-auto-submit valid text with one staged quote, assert the send receives it, resolve acceptance, and assert the submitted chip is removed. Send a second message and assert the first quote is absent. Add a race case where a different quote is staged before acceptance and must remain.