Workspaces in Workspaces all around

This commit is contained in:
2026-02-19 01:49:15 +00:00
parent 72bca6cd42
commit dd8ab84df5
84 changed files with 3047 additions and 749 deletions
@@ -8,10 +8,12 @@ import { ArrowLeft, Pencil, Plus, Check, X, Trash2, Search, ChevronRight } from
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { useClient } from 'hooks/useClient';
import { useVisibleClaudeModels } from '@/state/useModels';
import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels';
import { useSettings } from '@/state/useSettings';
import { Card } from '@/components/Card';
import type { ChatMessage } from 'apps/Chat';
import { useClaude } from '@/Screens/Dashboard/Chat/useClaude';
import { useOpenCode } from '@/Screens/Dashboard/Chat/useOpenCode';
import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat';
type CapabilitySummary = {
dirName: string;
@@ -57,7 +59,11 @@ type CapabilityChatProps = {
onResponseEnd?: () => void;
};
export const CapabilityChat = ({
type CapabilityChatInnerProps = CapabilityChatProps & {
onProviderChange: (p: 'claude' | 'opencode') => void;
};
const CapabilityChatClaude = ({
kind,
endpoint,
dirName,
@@ -67,7 +73,8 @@ export const CapabilityChat = ({
isNew,
description,
onResponseEnd,
}: CapabilityChatProps) => {
onProviderChange,
}: CapabilityChatInnerProps) => {
const client = useClient();
const claudeModels = useVisibleClaudeModels();
const seedFile = `${kind.toUpperCase()}.md`;
@@ -117,6 +124,7 @@ export const CapabilityChat = ({
chat={claude}
provider="claude"
availableModels={claudeModels}
onProviderChange={onProviderChange}
defaultInput={defaultInput}
promptPrefix={promptFrontmatter}
className="h-full"
@@ -124,6 +132,59 @@ export const CapabilityChat = ({
);
};
const CapabilityChatOpenCode = ({
kind,
filePath,
resourceDir,
isNew,
description,
onResponseEnd,
onProviderChange,
}: CapabilityChatInnerProps) => {
const openCodeModels = useVisibleOpenCodeModels();
const seedFile = `${kind.toUpperCase()}.md`;
const promptFrontmatter = `<frontmatter>\ninput file: ${filePath}\n${seedFile}: ${filePath}\ndir: ${resourceDir}\n\nBe aware of any extra files alongside the same dir as the ${kind} file we're handling, for possible extra context. You can also, if pertinent, create scripts or other files that will help you in the future.\n</frontmatter>`;
const defaultInput = isNew
? description ?? `Help me create the content for this new ${kind} file`
: `Help me understand and improve this ${kind} file`;
const opencode = useOpenCode(undefined, undefined, { replaceUrl: false });
const onResponseEndRef = useRef(onResponseEnd);
onResponseEndRef.current = onResponseEnd;
const wasGenerating = useRef(false);
useEffect(() => {
if (wasGenerating.current && !opencode.isGenerating) {
onResponseEndRef.current?.();
}
wasGenerating.current = opencode.isGenerating;
}, [opencode.isGenerating]);
return (
<EmbeddableChat
chat={opencode}
provider="opencode"
availableModels={openCodeModels}
onProviderChange={onProviderChange}
defaultInput={defaultInput}
promptPrefix={promptFrontmatter}
className="h-full"
/>
);
};
export const CapabilityChat = (props: CapabilityChatProps) => {
const { settings } = useSettings();
const [provider, setProvider] = useState<'claude' | 'opencode'>(settings.chat.defaultProvider);
return provider === 'claude' ? (
<CapabilityChatClaude key="claude" {...props} onProviderChange={setProvider} />
) : (
<CapabilityChatOpenCode key="opencode" {...props} onProviderChange={setProvider} />
);
};
export const FrontmatterBlock = ({ yaml }: { yaml: string }) => {
const [open, setOpen] = useState(false);
@@ -182,7 +243,7 @@ export const CapabilityList = ({ kind, endpoint, queryKey, selected, onSelect, o
return (
<>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center justify-between">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-background/60 flex items-center justify-between">
<span className="text-sm font-medium text-duck-dark/70">{kind}s</span>
{!creating && (
<button
@@ -213,7 +274,7 @@ export const CapabilityList = ({ kind, endpoint, queryKey, selected, onSelect, o
}
}}
placeholder={`${kind} name...`}
className="flex-1 min-w-0 rounded border border-duck-dark/20 bg-white px-2 py-1 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="flex-1 min-w-0 rounded border border-duck-dark/20 bg-background px-2 py-1 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
<button
onClick={handleCreate}
@@ -241,7 +302,7 @@ export const CapabilityList = ({ kind, endpoint, queryKey, selected, onSelect, o
value={internalSearch}
onChange={(ev) => setInternalSearch(ev.target.value)}
placeholder={`Search ${kind.toLowerCase()}s...`}
className="w-full rounded border border-duck-dark/15 bg-white/80 pl-7 pr-2 py-1 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="w-full rounded border border-duck-dark/15 bg-background/80 pl-7 pr-2 py-1 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
</div>
@@ -353,7 +414,7 @@ export const CapabilityPage = ({ kind, endpoint, queryKey }: CapabilityPageProps
{/* Right panel — detail + chat */}
<div className={`flex-1 flex flex-col gap-4 min-h-0 ${showDetail ? 'flex' : 'hidden md:flex'}`}>
<Card className={`flex-1 overflow-hidden flex flex-col min-h-0 ${editing ? 'hidden md:flex' : ''}`}>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-background/60 flex items-center gap-2">
<button
onClick={() => setShowDetail(false)}
className="md:hidden p-1 -ml-1 rounded hover:bg-duck-dark/10 cursor-pointer"
@@ -400,7 +461,7 @@ export const CapabilityPage = ({ kind, endpoint, queryKey }: CapabilityPageProps
{editing && detail?.filePath && selected && (
<Card className="flex-1 overflow-hidden flex flex-col min-h-0">
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 bg-background/60 flex items-center gap-2">
<button
onClick={() => setEditing(false)}
className="md:hidden p-1 -ml-1 rounded hover:bg-duck-dark/10 cursor-pointer"