v2.3.0-sampling-params-ask-user: agent sampling params, ask_user_input in CoderPane, UX polish
Add top_p/top_k/min_p/presence_penalty to AGENTS.md frontmatter and thread through inference (agents.ts parser → Agent type → stream-phase → sentinel summaries). Null means omit from request body, preserving provider defaults. Wire ask_user_input interactive card into both BooCoder frontends: the CoderPane in BooChat's SPA (CoderMessageList now renders AskUserInputCard instead of ToolCallLine for ask_user_input tool calls) and the standalone coder SPA (MessageBubble + new AskUserInputCard + shadcn ui primitives). Additional fixes: SessionLandingPage uses ChatInput with slash-command support and lazy chat creation; Session.tsx hydrate-race fix for empty pane promotion; AgentPicker wider dropdown with line-clamp; ModelPicker min-width; Textarea converted to forwardRef; Recon agent added to AGENTS.md; codecontext host port exposed in docker-compose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Archive, MessageSquare, Send, ChevronDown, ChevronRight, RotateCcw, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import type { Chat } from '@/api/types';
|
||||
import { api } from '@/api/client';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ChatInput } from '@/components/ChatInput';
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -25,6 +27,8 @@ interface Props {
|
||||
chats: Chat[];
|
||||
onOpenChat: (chatId: string) => void;
|
||||
onSend: (content: string) => void;
|
||||
/** Create a chat and return its id. Used by slash-command handler. */
|
||||
createChat: () => Promise<{ id: string }>;
|
||||
onReopenChat: (chatId: string) => Promise<void>;
|
||||
onArchiveChat: (chatId: string) => Promise<void>;
|
||||
onRenameChat: (chatId: string, name: string) => Promise<void>;
|
||||
@@ -153,12 +157,15 @@ export function SessionLandingPage({
|
||||
chats,
|
||||
onOpenChat,
|
||||
onSend,
|
||||
projectId,
|
||||
createChat,
|
||||
onReopenChat,
|
||||
onArchiveChat,
|
||||
onRenameChat,
|
||||
onDeleteChat,
|
||||
}: Props) {
|
||||
const [composerValue, setComposerValue] = useState('');
|
||||
const [chatId, setChatId] = useState<string | null>(null);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const [renamingId, setRenamingId] = useState<string | null>(null);
|
||||
const [renameValue, setRenameValue] = useState('');
|
||||
@@ -172,13 +179,43 @@ export function SessionLandingPage({
|
||||
.filter((c) => c.status === 'archived')
|
||||
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());
|
||||
|
||||
function handleSend() {
|
||||
// Create a chat lazily on first send or slash command.
|
||||
const ensureChat = useCallback(async (): Promise<string> => {
|
||||
if (chatId) return chatId;
|
||||
try {
|
||||
const chat = await createChat();
|
||||
setChatId(chat.id);
|
||||
return chat.id;
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : 'Failed to create chat');
|
||||
throw err;
|
||||
}
|
||||
}, [chatId, createChat]);
|
||||
|
||||
async function handleSend() {
|
||||
const text = composerValue.trim();
|
||||
if (!text) return;
|
||||
onSend(text);
|
||||
setComposerValue('');
|
||||
try {
|
||||
const cid = await ensureChat();
|
||||
onSend(text);
|
||||
setComposerValue('');
|
||||
} catch {
|
||||
// Error already surfaced via toast.
|
||||
}
|
||||
}
|
||||
|
||||
// v2.3: slash-command dispatch on landing page. Creates a chat first if
|
||||
// one doesn't exist, then invokes the skill on that chat.
|
||||
const handleSlashCommand = useCallback(async (skillName: string, userMessage: string) => {
|
||||
try {
|
||||
const cid = await ensureChat();
|
||||
await api.chats.skillInvoke(cid, skillName, userMessage.length > 0 ? userMessage : null);
|
||||
setComposerValue('');
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : `/${skillName} failed`);
|
||||
}
|
||||
}, [ensureChat]);
|
||||
|
||||
function startRename(chat: Chat) {
|
||||
setRenamingId(chat.id);
|
||||
setRenameValue(chat.name ?? '');
|
||||
@@ -293,33 +330,17 @@ export function SessionLandingPage({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t px-4 py-3 flex items-end gap-2 shrink-0">
|
||||
<Textarea
|
||||
value={composerValue}
|
||||
onChange={(e) => setComposerValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
}}
|
||||
placeholder="Start a new chat..."
|
||||
rows={2}
|
||||
className="resize-none min-h-[52px] max-h-[160px]"
|
||||
{/* v2.3: ChatInput with slash-command support replaces the bare Textarea.
|
||||
chatId is created lazily on first send/slash. */}
|
||||
<div className="border-t px-4 py-3 shrink-0">
|
||||
<ChatInput
|
||||
projectId={projectId}
|
||||
onSend={handleSend}
|
||||
onSlashCommand={handleSlashCommand}
|
||||
chatId={chatId ?? undefined}
|
||||
chatLabel={chatId ? undefined : 'Chat'}
|
||||
disabled={false}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
disabled={!composerValue.trim()}
|
||||
size="icon-lg"
|
||||
aria-label="Send"
|
||||
>
|
||||
<Send />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Dialog open={archiveConfirm !== null} onOpenChange={(open) => { if (!open) setArchiveConfirm(null); }}>
|
||||
|
||||
Reference in New Issue
Block a user