This commit is contained in:
2026-02-25 16:04:11 +00:00
parent 05c5b648fa
commit b6a0a57893
11 changed files with 698 additions and 22 deletions
@@ -113,6 +113,8 @@ export function ChatLauncher({
isConnected={true}
isGenerating={false}
hasStarted={false}
thinkingLevel={null}
onThinkingChange={() => {}}
/>
<WebpageDialog open={urlDialogOpen} onOpenChange={setUrlDialogOpen} onSubmit={attachWebpage} />
@@ -38,7 +38,9 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams) {
model,
selectedModel,
hasStarted,
thinkingLevel,
setSelectedModel,
setThinkingLevel,
sendPrompt,
stopGeneration,
} = chat;
@@ -83,6 +85,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams) {
cwdForFirst,
undefined,
sandboxed,
thinkingLevel,
);
attachmentManager.clearAttachments();
@@ -197,7 +200,9 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams) {
model,
selectedModel,
hasStarted,
thinkingLevel,
setSelectedModel,
setThinkingLevel,
sendPrompt,
stopGeneration,
input,
@@ -34,6 +34,8 @@ export const InputArea = ({ manager }: InputAreaProps) => {
attachImage,
removeAttachment,
appendToInput,
thinkingLevel,
setThinkingLevel,
} = manager;
const { recording, transcribing, toggleRecording } = useAudioRecording(appendToInput);
@@ -96,6 +98,8 @@ export const InputArea = ({ manager }: InputAreaProps) => {
isConnected={isConnected}
isGenerating={isGenerating}
hasStarted={hasStarted}
thinkingLevel={thinkingLevel}
onThinkingChange={setThinkingLevel}
/>
<WebpageDialog open={urlDialogOpen} onOpenChange={setUrlDialogOpen} onSubmit={attachWebpage} />
@@ -29,6 +29,8 @@ type ModelSelectorProps = {
isConnected: boolean;
isGenerating: boolean;
hasStarted: boolean;
thinkingLevel: string | null;
onThinkingChange: (level: string | null) => void;
};
export function ModelSelector({
@@ -40,6 +42,8 @@ export function ModelSelector({
isConnected,
isGenerating,
hasStarted,
thinkingLevel,
onThinkingChange,
}: ModelSelectorProps) {
const providers = [...new Set(availableModels.map((m) => m.provider).filter(Boolean))] as string[];
@@ -99,28 +103,57 @@ export function ModelSelector({
))}
</div>
)}
<div className="text-xs text-duck-dark/50">
{providerModels.length > 0 ? (
<Select
value={displayModel ?? fallbackModelId ?? undefined}
onValueChange={(v) => !isLocked && onModelChange(v)}
disabled={isLocked}
>
<SelectTrigger className="h-auto border-0 bg-transparent p-0 text-xs text-duck-dark/50 shadow-none focus:ring-0 gap-1 cursor-pointer">
<SelectValue />
</SelectTrigger>
<SelectContent className="z-[800]" side="top">
{providerModels.map((m) => (
<SelectItem key={m.id} value={m.id}>
{m.name}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<span>{getModelDisplayText()}</span>
<div className="flex items-center gap-2">
{availableModels.find((m) => m.id === displayModel)?.reasoning && (
<ThinkingToggle enabled={thinkingLevel === 'high'} onToggle={() => onThinkingChange(thinkingLevel === 'high' ? 'off' : 'high')} disabled={isGenerating} />
)}
<div className="text-xs text-duck-dark/50">
{providerModels.length > 0 ? (
<Select
value={displayModel ?? fallbackModelId ?? undefined}
onValueChange={(v) => !isLocked && onModelChange(v)}
disabled={isLocked}
>
<SelectTrigger className="h-auto border-0 bg-transparent p-0 text-xs text-duck-dark/50 shadow-none focus:ring-0 gap-1 cursor-pointer">
<SelectValue />
</SelectTrigger>
<SelectContent className="z-[800]" side="top">
{providerModels.map((m) => (
<SelectItem key={m.id} value={m.id}>
{m.name}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<span>{getModelDisplayText()}</span>
)}
</div>
</div>
</div>
);
}
// ── Thinking Toggle ──
type ThinkingToggleProps = {
enabled: boolean;
onToggle: () => void;
disabled: boolean;
};
const ThinkingToggle = ({ enabled, onToggle, disabled }: ThinkingToggleProps) => (
<button
type="button"
onClick={onToggle}
disabled={disabled}
title={enabled ? 'Thinking enabled (click to disable)' : 'Thinking disabled (click to enable)'}
className={`rounded-md px-2 py-0.5 text-xs font-medium transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed ${
enabled
? 'bg-duck-teal/15 text-duck-teal'
: 'text-duck-dark/40 hover:text-duck-dark/60'
}`}
>
{enabled ? 'think' : 'no think'}
</button>
);
@@ -135,6 +135,8 @@ const PiMonoInner = ({ defaultInput, cwd, initialModel, taskInfo, sandboxed }: P
isConnected={chat.isConnected}
isGenerating={false}
hasStarted={false}
thinkingLevel={chat.thinkingLevel}
onThinkingChange={chat.setThinkingLevel}
/>
</div>
</>
@@ -28,6 +28,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
const [model, setModel] = useState<string | null>(null);
const [selectedModel, setSelectedModel] = useState<string | null>(initialModel ?? null);
const [cwd, setCwd] = useState<string | null>(null);
const [thinkingLevel, setThinkingLevel] = useState<string | null>(null);
// Track if session has started (first message sent)
const [hasStarted, setHasStarted] = useState(false);
@@ -265,6 +266,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
cwdParam?: { root?: string; path: string },
groupSlug?: string | null,
sandboxed?: boolean,
thinking?: string | null,
) {
// Mark session as started on first message
if (!hasStarted) {
@@ -297,6 +299,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
...(imageData?.length ? { images: imageData } : {}),
...(resourceChatDir ? { resourceChatDir } : {}),
...(taskInfo ? { taskInfo } : {}),
...(thinking ? { thinking } : {}),
});
}
@@ -314,7 +317,9 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
selectedModel,
hasStarted,
cwd,
thinkingLevel,
setSelectedModel,
setThinkingLevel,
sendPrompt,
stopGeneration,
};