11"use client"
22
33import { useState , useEffect } from "react"
4- import { Send , Sparkles , X , Maximize2 , Minimize2 , Eye } from "lucide-react"
4+ import { Send , Sparkles , X , Maximize2 , Minimize2 , Eye , Quote } from "lucide-react"
55import { useAskable } from "@askable-ui/react"
66import { Button } from "@/components/ui/button"
77import { Input } from "@/components/ui/input"
@@ -15,6 +15,8 @@ interface Message {
1515 timestamp : Date
1616 context ?: string
1717 isContext ?: boolean
18+ contextKind ?: "text" | "ui"
19+ selectedText ?: string
1820}
1921
2022const initialMessages : Message [ ] = [
@@ -33,7 +35,9 @@ export function ChatSidebar() {
3335 const [ isMinimized , setIsMinimized ] = useState ( false )
3436 const [ showContext , setShowContext ] = useState ( true )
3537
36- const { promptContext } = useAskable ( { inspector : true } ) ;
38+ const { promptContext, focus } = useAskable ( { inspector : true } )
39+ const hasSelectedText = isTextSelectionMeta ( focus ?. meta )
40+ const selectedText = hasSelectedText ? focus ?. text ?? "" : ""
3741
3842 const handleSend = ( ) => {
3943 if ( ! input . trim ( ) ) return
@@ -44,6 +48,8 @@ export function ChatSidebar() {
4448 content : input ,
4549 timestamp : new Date ( ) ,
4650 context : promptContext || undefined ,
51+ contextKind : hasSelectedText ? "text" : "ui" ,
52+ selectedText,
4753 }
4854
4955 setMessages ( ( prev ) => [ ...prev , userMessage ] )
@@ -56,6 +62,8 @@ export function ChatSidebar() {
5662 content : promptContext ,
5763 timestamp : new Date ( ) ,
5864 isContext : true ,
65+ contextKind : hasSelectedText ? "text" : "ui" ,
66+ selectedText,
5967 }
6068 setMessages ( ( prev ) => [ ...prev , assistantMessage ] )
6169 }
@@ -119,26 +127,43 @@ export function ChatSidebar() {
119127 { /* Context Panel - Main Feature Showcase */ }
120128 < div className = "border-b border-border" >
121129 { promptContext ? (
122- < div className = "bg-gradient-to-b from-emerald-500/10 to-emerald-500/5 p-4" >
130+ < div className = { cn (
131+ "p-4" ,
132+ hasSelectedText
133+ ? "bg-gradient-to-b from-violet-500/10 to-violet-500/5"
134+ : "bg-gradient-to-b from-emerald-500/10 to-emerald-500/5"
135+ ) } >
123136 < div className = "mb-3 flex items-center justify-between" >
124137 < div className = "flex items-center gap-2" >
125- < div className = "flex h-7 w-7 items-center justify-center rounded-full bg-emerald-500" >
126- < Eye className = "h-4 w-4 text-white" />
138+ < div className = { cn (
139+ "flex h-7 w-7 items-center justify-center rounded-full" ,
140+ hasSelectedText ? "bg-violet-500" : "bg-emerald-500"
141+ ) } >
142+ { hasSelectedText ? < Quote className = "h-4 w-4 text-white" /> : < Eye className = "h-4 w-4 text-white" /> }
127143 </ div >
128144 < div >
129- < span className = "text-sm font-semibold text-emerald-400" > Context Captured</ span >
130- < p className = "text-xs text-emerald-400/70" > AI can see this element</ p >
145+ < span className = { cn ( "text-sm font-semibold" , hasSelectedText ? "text-violet-400" : "text-emerald-400" ) } >
146+ { hasSelectedText ? "Selected Text Captured" : "Context Captured" }
147+ </ span >
148+ < p className = { cn ( "text-xs" , hasSelectedText ? "text-violet-400/70" : "text-emerald-400/70" ) } >
149+ { hasSelectedText ? "This exact highlight will be sent to chat" : "AI can see this element" }
150+ </ p >
131151 </ div >
132152 </ div >
133153 < button
134154 onClick = { ( ) => setShowContext ( ! showContext ) }
135- className = "rounded-md border border-emerald-500/30 bg-emerald-500/10 px-2 py-1 text-xs font-medium text-emerald-400 transition-colors hover:bg-emerald-500/20"
155+ className = { cn (
156+ "rounded-md border px-2 py-1 text-xs font-medium transition-colors" ,
157+ hasSelectedText
158+ ? "border-violet-500/30 bg-violet-500/10 text-violet-400 hover:bg-violet-500/20"
159+ : "border-emerald-500/30 bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20"
160+ ) }
136161 >
137162 { showContext ? "Collapse" : "Expand" }
138163 </ button >
139164 </ div >
140165 { showContext && (
141- < ContextPanel content = { promptContext } />
166+ < ContextPanel content = { promptContext } selectedText = { selectedText } />
142167 ) }
143168 </ div >
144169 ) : (
@@ -176,13 +201,20 @@ export function ChatSidebar() {
176201 ) }
177202 < div className = "max-w-[85%]" >
178203 { message . context && message . role === "user" && (
179- < div className = "mb-1 flex items-center gap-1 text-xs text-chart-1" >
180- < Eye className = "h-3 w-3" />
181- < span > with context</ span >
204+ < div className = { cn (
205+ "mb-1 flex items-center gap-1 text-xs" ,
206+ message . contextKind === "text" ? "text-violet-400" : "text-chart-1"
207+ ) } >
208+ { message . contextKind === "text" ? < Quote className = "h-3 w-3" /> : < Eye className = "h-3 w-3" /> }
209+ < span > { message . contextKind === "text" ? "with selected text" : "with context" } </ span >
182210 </ div >
183211 ) }
184212 { message . isContext ? (
185- < ContextCard content = { message . content } />
213+ < ContextCard
214+ content = { message . content }
215+ kind = { message . contextKind ?? "ui" }
216+ selectedText = { message . selectedText }
217+ />
186218 ) : (
187219 < div
188220 className = { cn (
@@ -247,6 +279,14 @@ function parseContext(content: string): Record<string, unknown> | string[] | nul
247279 return lines . length > 1 ? lines : null
248280}
249281
282+ function isTextSelectionMeta ( meta : unknown ) {
283+ return Boolean (
284+ meta &&
285+ typeof meta === "object" &&
286+ ( meta as Record < string , unknown > ) . capture === "text-selection"
287+ )
288+ }
289+
250290function renderContextValue ( value : unknown , tone : "panel" | "card" = "panel" ) {
251291 if ( value !== null && typeof value === "object" ) {
252292 return (
@@ -266,7 +306,28 @@ function renderContextValue(value: unknown, tone: "panel" | "card" = "panel") {
266306 return < span className = "text-xs font-medium text-foreground break-words whitespace-pre-wrap" > { String ( value ) } </ span >
267307}
268308
269- function ContextPanel ( { content } : { content : string } ) {
309+ function SelectedTextPreview ( { text, tone = "panel" } : { text : string ; tone ?: "panel" | "card" } ) {
310+ return (
311+ < div className = { cn (
312+ "rounded-lg border p-3" ,
313+ tone === "panel"
314+ ? "border-violet-500/20 bg-background/80"
315+ : "border-violet-500/30 bg-violet-500/10"
316+ ) } >
317+ < div className = "mb-2 flex items-center gap-1.5 text-xs font-semibold text-violet-400" >
318+ < Quote className = "h-3.5 w-3.5" />
319+ < span > Selected text passed to chat</ span >
320+ </ div >
321+ < blockquote className = "text-xs leading-relaxed text-foreground break-words whitespace-pre-wrap" >
322+ { text }
323+ </ blockquote >
324+ </ div >
325+ )
326+ }
327+
328+ function ContextPanel ( { content, selectedText } : { content : string ; selectedText ?: string } ) {
329+ if ( selectedText ) return < SelectedTextPreview text = { selectedText } />
330+
270331 const parsed = parseContext ( content )
271332
272333 if ( parsed && ! Array . isArray ( parsed ) ) {
@@ -299,7 +360,17 @@ function ContextPanel({ content }: { content: string }) {
299360 )
300361}
301362
302- function ContextCard ( { content } : { content : string } ) {
363+ function ContextCard ( {
364+ content,
365+ kind = "ui" ,
366+ selectedText,
367+ } : {
368+ content : string
369+ kind ?: "text" | "ui"
370+ selectedText ?: string
371+ } ) {
372+ if ( kind === "text" && selectedText ) return < SelectedTextPreview text = { selectedText } tone = "card" />
373+
303374 const parsed = parseContext ( content )
304375 const entries = parsed && ! Array . isArray ( parsed ) ? Object . entries ( parsed ) : null
305376 const lines = Array . isArray ( parsed ) ? parsed : content . split ( / \s * — \s * / ) . filter ( Boolean )
0 commit comments