Skip to content

Commit 2de4cec

Browse files
ivicacclaude
andcommitted
1570 client - Address PR review comments in AI chat pickers and Copilot runtime
- SelectPropertyOptionMessage: fix missing space in opacity className, disable the picker when superseded, and resolve the picked option by its unique value instead of an ambiguous label match (label still drives search). - SelectConnectionMessage: fix missing space in opacity className. - ModelPicker: fall back to BrainCircuitIcon when a provider has no icon, and gate the 'Choose model by ID' affordance behind provider.supportsModelById. - CopilotRuntimeProvider: stop pre-adding an empty assistant message so a tool-result/run-error-only turn no longer leaves a blank bubble. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fe5d1df commit 2de4cec

4 files changed

Lines changed: 49 additions & 50 deletions

File tree

client/src/shared/components/ai-chat/messages/SelectConnectionMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const SelectConnectionMessage = ({data}: DataMessagePartProps<SelectConnectionDa
9494
}
9595

9696
return (
97-
<div className={`mt-2 flex w-full min-w-0 items-center gap-2${supersededByLaterMessage ? 'opacity-60' : ''}`}>
97+
<div className={`mt-2 flex w-full min-w-0 items-center gap-2 ${supersededByLaterMessage ? 'opacity-60' : ''}`}>
9898
<Select disabled={supersededByLaterMessage} onValueChange={handleSelectChange}>
9999
<div className="min-w-0 flex-1">
100100
<SelectTrigger>

client/src/shared/components/ai-chat/messages/SelectPropertyOptionMessage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,20 @@ const SelectPropertyOptionMessage = ({data}: DataMessagePartProps<SelectProperty
6060
);
6161
}
6262

63-
const items = options.map((option) => ({label: option.label, value: option.label}));
63+
const items = options.map((option) => ({label: option.label, optionValue: option.value, value: option.label}));
6464

6565
return (
66-
<div className={`mt-2 flex w-full min-w-0 flex-col gap-1${superseded ? 'opacity-60' : ''}`}>
66+
<div className={`mt-2 flex w-full min-w-0 flex-col gap-1 ${superseded ? 'opacity-60' : ''}`}>
6767
<ComboBox
68+
disabled={superseded}
6869
emptyMessage="No match"
6970
items={items}
7071
onChange={(item) => {
7172
if (!item) {
7273
return;
7374
}
7475

75-
const option = options.find((candidate) => candidate.label === item.value);
76+
const option = options.find((candidate) => candidate.value === item.optionValue);
7677

7778
if (!option) {
7879
return;

client/src/shared/components/ai/model-picker/ModelPicker.tsx

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,11 @@ const ModelPicker = ({
329329
sortedProviders.map((provider) => (
330330
<DropdownMenuSub key={provider.key}>
331331
<DropdownMenuSubTrigger>
332-
<InlineSVG className="size-4 shrink-0" src={provider.icon ?? ''} />
332+
{provider.icon ? (
333+
<InlineSVG className="size-4 shrink-0" src={provider.icon} />
334+
) : (
335+
<BrainCircuitIcon className="size-4 shrink-0 text-muted-foreground" />
336+
)}
333337

334338
<span className="truncate">{provider.name}</span>
335339
</DropdownMenuSubTrigger>
@@ -353,39 +357,45 @@ const ModelPicker = ({
353357
</DropdownMenuItem>
354358
))}
355359

356-
{provider.models.length > 0 && <DropdownMenuSeparator />}
357-
358-
{modelByIdProvider === provider.key ? (
359-
<div className="px-2 py-1.5">
360-
<input
361-
aria-label="Model id"
362-
autoFocus
363-
className="w-full rounded-sm border border-input bg-background px-2 py-1 text-sm focus:ring-1 focus:ring-ring focus:outline-none"
364-
onChange={(event) => setModelByIdValue(event.target.value)}
365-
onKeyDown={(event) => {
366-
event.stopPropagation();
367-
368-
if (event.key === 'Enter') {
369-
handleModelByIdSubmit(provider.key);
370-
}
371-
}}
372-
placeholder="model-id"
373-
type="text"
374-
value={modelByIdValue}
375-
/>
376-
</div>
377-
) : (
378-
<DropdownMenuItem
379-
onSelect={(event) => {
380-
event.preventDefault();
381-
setModelByIdProvider(provider.key);
382-
setModelByIdValue('');
383-
}}
384-
>
385-
<PlusIcon className="text-muted-foreground" />
386-
387-
<span>Choose model by ID</span>
388-
</DropdownMenuItem>
360+
{provider.supportsModelById && (
361+
<>
362+
{provider.models.length > 0 && <DropdownMenuSeparator />}
363+
364+
{modelByIdProvider === provider.key ? (
365+
<div className="px-2 py-1.5">
366+
<input
367+
aria-label="Model id"
368+
autoFocus
369+
className="w-full rounded-sm border border-input bg-background px-2 py-1 text-sm focus:ring-1 focus:ring-ring focus:outline-none"
370+
onChange={(event) =>
371+
setModelByIdValue(event.target.value)
372+
}
373+
onKeyDown={(event) => {
374+
event.stopPropagation();
375+
376+
if (event.key === 'Enter') {
377+
handleModelByIdSubmit(provider.key);
378+
}
379+
}}
380+
placeholder="model-id"
381+
type="text"
382+
value={modelByIdValue}
383+
/>
384+
</div>
385+
) : (
386+
<DropdownMenuItem
387+
onSelect={(event) => {
388+
event.preventDefault();
389+
setModelByIdProvider(provider.key);
390+
setModelByIdValue('');
391+
}}
392+
>
393+
<PlusIcon className="text-muted-foreground" />
394+
395+
<span>Choose model by ID</span>
396+
</DropdownMenuItem>
397+
)}
398+
</>
389399
)}
390400
</>
391401
)}

client/src/shared/components/copilot/runtime-providers/CopilotRuntimeProvider.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,30 +94,18 @@ export function CopilotRuntimeProvider({
9494
...contextWithoutError,
9595
...useCopilotStateContributorRegistry.getState().contribute(),
9696
environmentId: String(environmentStore.getState().currentEnvironmentId ?? 0),
97-
// The connection/property picker tools resolve options against this workspace. userId is NOT
98-
// sent — the server derives it from the authenticated request (never trust the client for it).
9997
...(currentWorkspaceId != null ? {workspaceId: String(currentWorkspaceId)} : {}),
100-
// Drop half-set picker values client-side rather than sending them. The server
101-
// tolerates partial input (logs once, falls back to workspace default), but omitting
102-
// here keeps the wire format clean and reserves the warning log for genuinely-broken state.
10398
...(selectedLlmProvider != null && selectedLlmModel != null
10499
? {userSelectedLlmModel: selectedLlmModel, userSelectedLlmProvider: selectedLlmProvider}
105100
: {}),
106101
};
107102

108103
agent.setState(stateToSend);
109104

110-
// Prepare an empty assistant message to stream into
111-
addMessage({content: '', role: 'assistant'});
112-
113-
// Tracks the tool name per tool-call id so the result handler can map the result payload to the
114-
// right shared data-part renderer (the result event carries only the id, not the name).
115105
const toolCallNamesById = new Map<string, string>();
116106

117107
const subscriber: AgentSubscriber = {
118108
onRunErrorEvent: ({event}) => {
119-
// Surface a whole-run failure inline as a distinct red callout bubble (shared RunErrorMessage),
120-
// humanized to strip Java FQCNs / unwrap provider JSON envelopes — same treatment AI Hub gives it.
121109
const rawMessage = typeof event?.message === 'string' ? event.message.trim() : '';
122110
const message =
123111
rawMessage.length > 0

0 commit comments

Comments
 (0)