Workspaces in Workspaces all around
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user