@@ -19,7 +19,14 @@ import {
1919 InteractionCardInputRow ,
2020 InteractionCardRecap ,
2121} from '@/app/workspace/[workspaceId]/home/components/message-content/components/interaction-card'
22- import type { QuestionItem } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags'
22+ import { ResourceQuestionRows } from '@/app/workspace/[workspaceId]/home/components/message-content/components/question/resource-question-rows'
23+ import type {
24+ InteractionAnswerHandler ,
25+ QuestionItem ,
26+ } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags'
27+ import { mapResourceToContext } from '@/app/workspace/[workspaceId]/home/components/user-input/components/constants'
28+ import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
29+ import type { ChatContext } from '@/stores/panel'
2330
2431/**
2532 * Builds the single user message sent after the final question is answered:
@@ -88,8 +95,11 @@ interface QuestionDisplayProps {
8895 * — it IS the user turn; the paired message bubble is hidden by the chat.
8996 */
9097 answers ?: string [ ]
91- /** Reports the combined answer; undefined renders the card inert. */
92- onSelect ?: ( message : string ) => void
98+ /**
99+ * Reports the combined answer, with any resources picked by
100+ * `resource_select` steps attached as contexts; undefined renders the card inert.
101+ */
102+ onSelect ?: InteractionAnswerHandler
93103 /** Reports that the active card was dismissed so its message actions can return. */
94104 onDismiss ?: ( ) => void
95105 /** Whether the active card can be dismissed without answering. */
@@ -102,7 +112,9 @@ interface QuestionDisplayProps {
102112 * (and a `‹ N of M ›` stepper for multi-step batches) at the top right, and
103113 * suggested-action option rows beneath, always followed by a custom-answer
104114 * text field whose placeholder reads "Something else". `single_select`
105- * answers and advances on click (or on submitting typed text); `multi_select`
115+ * answers and advances on click (or on submitting typed text);
116+ * `resource_select` does the same over the workspace's live resources of one
117+ * family, with the text field filtering them; `multi_select`
106118 * rows toggle checkboxes and an option-styled Submit row confirms the step.
107119 * Answering the last question sends one combined user message and collapses
108120 * the div to a question/answer recap.
@@ -121,6 +133,10 @@ export function QuestionDisplay({
121133 const [ step , setStep ] = useState ( 0 )
122134 const [ selectedByStep , setSelectedByStep ] = useState < string [ ] [ ] > ( ( ) => data . map ( ( ) => [ ] ) )
123135 const [ customByStep , setCustomByStep ] = useState < string [ ] > ( ( ) => data . map ( ( ) => '' ) )
136+ // resource_select only: the resource behind a step's picked row, sent as a
137+ // chat context so the agent gets its id, not just the displayed name.
138+ // Only read on submit, so it never needs to re-render the card.
139+ const resourceByStep = useRef < ( MothershipResource | null ) [ ] > ( data . map ( ( ) => null ) )
124140 const [ freeText , setFreeText ] = useState ( '' )
125141 // multi_select only: whether the typed "Something else" text is included in
126142 // the answer. Unchecking keeps the text; it just stops counting.
@@ -157,7 +173,8 @@ export function QuestionDisplay({
157173
158174 const question = data [ step ]
159175 const isLast = step === data . length - 1
160- const options = question . options
176+ const isResource = question . type === 'resource_select'
177+ const options = isResource ? [ ] : question . options
161178 const selected = selectedByStep [ step ] ?? [ ]
162179 const isMulti = question . type === 'multi_select'
163180
@@ -183,21 +200,26 @@ export function QuestionDisplay({
183200 return
184201 }
185202 setPhase ( 'answered' )
203+ const contexts = resourceByStep . current
204+ . map ( ( resource ) => ( resource ? mapResourceToContext ( resource ) : null ) )
205+ . filter ( ( context ) : context is ChatContext => context !== null )
186206 onSelect ?.(
187207 formatQuestionAnswerMessage (
188208 data ,
189209 data . map ( ( q , i ) => answerFor ( q , selections [ i ] ?? [ ] , customFor ( i , customs ) ) )
190- )
210+ ) ,
211+ contexts . length > 0 ? contexts : undefined
191212 )
192213 }
193214
194- const handleSingleSelect = ( label : string ) => {
215+ const handleSingleSelect = ( label : string , resource : MothershipResource | null = null ) => {
195216 const selections = [ ...selectedByStep ]
196217 selections [ step ] = [ label ]
197218 setSelectedByStep ( selections )
198219 const customs = [ ...customByStep ]
199220 customs [ step ] = ''
200221 setCustomByStep ( customs )
222+ resourceByStep . current [ step ] = resource
201223 setFreeText ( '' )
202224 finishStep ( selections , customs )
203225 }
@@ -239,6 +261,7 @@ export function QuestionDisplay({
239261 const selections = [ ...selectedByStep ]
240262 selections [ step ] = [ ]
241263 setSelectedByStep ( selections )
264+ resourceByStep . current [ step ] = null
242265 finishStep ( selections , customs )
243266 }
244267
@@ -316,6 +339,14 @@ export function QuestionDisplay({
316339 }
317340 >
318341 < div className = 'flex flex-col' >
342+ { question . type === 'resource_select' && (
343+ < ResourceQuestionRows
344+ resourceType = { question . resourceType }
345+ query = { freeText }
346+ disabled = { disabled }
347+ onPick = { ( resource ) => handleSingleSelect ( resource . title , resource ) }
348+ />
349+ ) }
319350 { options . map ( ( option , i ) => {
320351 const isSelected = selected . includes ( option . label )
321352 return (
@@ -343,7 +374,7 @@ export function QuestionDisplay({
343374 } ) }
344375 < InteractionCardInputRow
345376 ref = { freeTextInputRef }
346- divided = { options . length > 0 }
377+ divided = { options . length > 0 || isResource }
347378 leading = {
348379 isMulti ? (
349380 < div className = 'flex size-[16px] shrink-0 items-center justify-center' >
@@ -389,7 +420,7 @@ export function QuestionDisplay({
389420 }
390421 type = 'text'
391422 value = { freeText }
392- placeholder = ' Something else'
423+ placeholder = { isResource ? 'Search or type something else' : ' Something else'}
393424 disabled = { disabled }
394425 onFocus = { ( ) => {
395426 if ( isMulti ) setCustomChecked ( true )
@@ -438,9 +469,12 @@ export function QuestionDisplay({
438469/**
439470 * A step's combined answer: selected option labels in option order, with the
440471 * typed "Something else" entry appended last. single_select carries at most
441- * one selection, so this collapses to the chosen label or the typed text.
472+ * one selection, so this collapses to the chosen label or the typed text; a
473+ * resource_select pick is not one of the question's options, so it is the
474+ * picked resource's name or the typed text.
442475 */
443476function answerFor ( question : QuestionItem , selected : string [ ] , custom : string ) : string {
477+ if ( question . type === 'resource_select' ) return selected [ 0 ] ?? custom . trim ( )
444478 const ordered = question . options
445479 . map ( ( option ) => option . label )
446480 . filter ( ( label ) => selected . includes ( label ) )
0 commit comments