better panels
This commit is contained in:
@@ -46,7 +46,7 @@ export function App() {
|
|||||||
<Route path="/chat/opencode/new" element={<Dashboard.OpenCodeChat />} />
|
<Route path="/chat/opencode/new" element={<Dashboard.OpenCodeChat />} />
|
||||||
<Route path="/chat/opencode/:sessionId" element={<Dashboard.OpenCodeChat />} />
|
<Route path="/chat/opencode/:sessionId" element={<Dashboard.OpenCodeChat />} />
|
||||||
<Route path="/plans" element={<Dashboard.Plans />} />
|
<Route path="/plans" element={<Dashboard.Plans />} />
|
||||||
<Route path="/files" element={<Dashboard.Files />} />
|
<Route path="/files" element={<Dashboard.FilesPage />} />
|
||||||
<Route path="/terminal" element={<Dashboard.Terminal />} />
|
<Route path="/terminal" element={<Dashboard.Terminal />} />
|
||||||
<Route path="/code-editor" element={<Dashboard.CodeEditor />} />
|
<Route path="/code-editor" element={<Dashboard.CodeEditor />} />
|
||||||
<Route path="/skills" element={<Dashboard.Skills />} />
|
<Route path="/skills" element={<Dashboard.Skills />} />
|
||||||
|
|||||||
+12
-12
@@ -13,27 +13,27 @@ import { Card } from '@/components/Card';
|
|||||||
import type { ChatMessage } from 'widgets/Chat';
|
import type { ChatMessage } from 'widgets/Chat';
|
||||||
import { useClaude } from '@/Screens/Dashboard/Chat/useClaude';
|
import { useClaude } from '@/Screens/Dashboard/Chat/useClaude';
|
||||||
import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat';
|
import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat';
|
||||||
type ResourceSummary = {
|
type CapabilitySummary = {
|
||||||
dirName: string;
|
dirName: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
scope: 'global' | 'user';
|
scope: 'global' | 'user';
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResourceDetail = ResourceSummary & {
|
type CapabilityDetail = CapabilitySummary & {
|
||||||
body: string;
|
body: string;
|
||||||
rawFrontmatter: string;
|
rawFrontmatter: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
chatSessionId: string | null;
|
chatSessionId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResourcePageProps = {
|
type CapabilityPageProps = {
|
||||||
kind: string;
|
kind: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
queryKey: string;
|
queryKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResourceChatProps = {
|
type CapabilityChatProps = {
|
||||||
kind: string;
|
kind: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
dirName: string;
|
dirName: string;
|
||||||
@@ -44,7 +44,7 @@ type ResourceChatProps = {
|
|||||||
onResponseEnd?: () => void;
|
onResponseEnd?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ResourceChat = ({
|
const CapabilityChat = ({
|
||||||
kind,
|
kind,
|
||||||
endpoint,
|
endpoint,
|
||||||
dirName,
|
dirName,
|
||||||
@@ -53,7 +53,7 @@ const ResourceChat = ({
|
|||||||
chatSessionId,
|
chatSessionId,
|
||||||
isNew,
|
isNew,
|
||||||
onResponseEnd,
|
onResponseEnd,
|
||||||
}: ResourceChatProps) => {
|
}: CapabilityChatProps) => {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const claudeModels = useVisibleClaudeModels();
|
const claudeModels = useVisibleClaudeModels();
|
||||||
const seedFile = `${kind.toUpperCase()}.md`;
|
const seedFile = `${kind.toUpperCase()}.md`;
|
||||||
@@ -129,7 +129,7 @@ const FrontmatterBlock = ({ yaml }: { yaml: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) => {
|
export const CapabilityPage = ({ kind, endpoint, queryKey }: CapabilityPageProps) => {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const [selected, setSelected] = useState<string | null>(null);
|
const [selected, setSelected] = useState<string | null>(null);
|
||||||
@@ -142,9 +142,9 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
|||||||
const [showDetail, setShowDetail] = useState(false);
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
const newNameRef = useRef<HTMLInputElement | null>(null);
|
const newNameRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const { data: items = [] } = useQuery<ResourceSummary[]>({
|
const { data: items = [] } = useQuery<CapabilitySummary[]>({
|
||||||
queryKey: [queryKey],
|
queryKey: [queryKey],
|
||||||
queryFn: () => client.get<ResourceSummary[]>(endpoint),
|
queryFn: () => client.get<CapabilitySummary[]>(endpoint),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -153,9 +153,9 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
|||||||
}
|
}
|
||||||
}, [items, selected]);
|
}, [items, selected]);
|
||||||
|
|
||||||
const { data: detail } = useQuery<ResourceDetail>({
|
const { data: detail } = useQuery<CapabilityDetail>({
|
||||||
queryKey: [queryKey, selected],
|
queryKey: [queryKey, selected],
|
||||||
queryFn: () => client.get<ResourceDetail>(`${endpoint}/${selected}`),
|
queryFn: () => client.get<CapabilityDetail>(`${endpoint}/${selected}`),
|
||||||
enabled: !!selected,
|
enabled: !!selected,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
|||||||
<X className="h-3.5 w-3.5 text-duck-dark/50" />
|
<X className="h-3.5 w-3.5 text-duck-dark/50" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ResourceChat
|
<CapabilityChat
|
||||||
kind={kind.toLowerCase()}
|
kind={kind.toLowerCase()}
|
||||||
key={detail.filePath}
|
key={detail.filePath}
|
||||||
endpoint={endpoint}
|
endpoint={endpoint}
|
||||||
@@ -26,7 +26,6 @@ import { useFiles, type DirEntry, useTasks, type TaskSummary, Breadcrumb, Toolba
|
|||||||
import { useUserState } from '@/state/useUserState';
|
import { useUserState } from '@/state/useUserState';
|
||||||
import { useAuth } from 'hooks/useAuth';
|
import { useAuth } from 'hooks/useAuth';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { Card } from '@/components/Card';
|
|
||||||
import { FileGrid } from './FileGrid';
|
import { FileGrid } from './FileGrid';
|
||||||
import { FileViewer } from './FileViewer';
|
import { FileViewer } from './FileViewer';
|
||||||
import { TaskRunnerModal } from './TaskRunnerModal';
|
import { TaskRunnerModal } from './TaskRunnerModal';
|
||||||
@@ -374,12 +373,7 @@ export const Files = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-center h-full p-0 md:p-4">
|
<div className="flex flex-col h-full overflow-hidden">
|
||||||
<Card
|
|
||||||
className={`flex flex-col overflow-hidden transition-all duration-200 ${
|
|
||||||
fullscreen ? 'w-[90vw] h-[90%]' : 'w-full h-full md:w-[90vw] md:h-[90%] lg:w-[50vw]'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="shrink-0 flex items-center gap-2 md:gap-3 px-3 md:px-4 h-12 md:h-14 border-b border-duck-dark/10 overflow-hidden">
|
<div className="shrink-0 flex items-center gap-2 md:gap-3 px-3 md:px-4 h-12 md:h-14 border-b border-duck-dark/10 overflow-hidden">
|
||||||
<Toolbar
|
<Toolbar
|
||||||
@@ -638,7 +632,6 @@ export const Files = () => {
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileViewer
|
<FileViewer
|
||||||
|
|||||||
@@ -1,2 +1,14 @@
|
|||||||
export { Files } from './Screen';
|
import { Card } from '@/components/Card';
|
||||||
|
import { Files as FilesInner } from './Screen';
|
||||||
|
|
||||||
|
export { FilesInner as Files };
|
||||||
|
|
||||||
|
export const FilesPage = () => (
|
||||||
|
<div className="flex items-center justify-center h-full p-0 md:p-4">
|
||||||
|
<Card className="flex flex-col overflow-hidden w-full h-full md:w-[90vw] md:h-[90%] lg:w-[50vw]">
|
||||||
|
<FilesInner />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export { FileBrowser as FileBrowserWidget } from './Widget';
|
export { FileBrowser as FileBrowserWidget } from './Widget';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const DEFAULT_HOME_LAYOUT: LayoutNode = {
|
|||||||
id: 'home-left',
|
id: 'home-left',
|
||||||
direction: 'vertical',
|
direction: 'vertical',
|
||||||
children: [
|
children: [
|
||||||
{ size: 50, node: { type: 'panel', id: 'home-tl', widgetType: 'chat-launcher' } },
|
{ size: 50, node: { type: 'panel', id: 'home-tl', widgetType: 'chat' } },
|
||||||
{ size: 50, node: { type: 'panel', id: 'home-bl', widgetType: 'file-browser' } },
|
{ size: 50, node: { type: 'panel', id: 'home-bl', widgetType: 'file-browser' } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { ResourcePage } from '../ResourcePage';
|
import { CapabilityPage } from '../CapabilityPage';
|
||||||
|
|
||||||
export const Processes = () => <ResourcePage kind="Process" endpoint="/processes" queryKey="processes" />;
|
export const Processes = () => <CapabilityPage kind="Process" endpoint="/processes" queryKey="processes" />;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { ResourcePage } from '../ResourcePage';
|
import { CapabilityPage } from '../CapabilityPage';
|
||||||
|
|
||||||
export const Skills = () => <ResourcePage kind="Skill" endpoint="/skills" queryKey="skills" />;
|
export const Skills = () => <CapabilityPage kind="Skill" endpoint="/skills" queryKey="skills" />;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { ResourcePage } from '../ResourcePage';
|
import { CapabilityPage } from '../CapabilityPage';
|
||||||
|
|
||||||
export const Tasks = () => <ResourcePage kind="Task" endpoint="/tasks" queryKey="tasks" />;
|
export const Tasks = () => <CapabilityPage kind="Task" endpoint="/tasks" queryKey="tasks" />;
|
||||||
|
|||||||
@@ -1,20 +1,45 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { MessageSquare, FolderOpen, History, Music, Code, TerminalSquare, LayoutGrid } from 'lucide-react';
|
import { MessageSquare, FolderOpen, History, Music, Code, TerminalSquare, LayoutGrid } from 'lucide-react';
|
||||||
import type { WidgetRegistry } from '@/components/Workspace';
|
import type { WidgetRegistry } from '@/components/Workspace';
|
||||||
import { CodeEditorView } from 'widgets/CodeEditor';
|
import { CodeEditorView } from 'widgets/CodeEditor';
|
||||||
import { TerminalView } from 'widgets/Terminal';
|
import { TerminalView } from 'widgets/Terminal';
|
||||||
import { ChatLauncher } from '../Home/ChatLauncher';
|
import { useClaude } from '../Chat/useClaude';
|
||||||
|
import { useOpenCode } from '../Chat/useOpenCode';
|
||||||
|
import { ChatPanel } from '../Chat/ChatPanel';
|
||||||
|
import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels';
|
||||||
import { ChatHistoryWidget as ChatHistory } from '../ChatHistory';
|
import { ChatHistoryWidget as ChatHistory } from '../ChatHistory';
|
||||||
import { FileBrowserWidget as FileBrowser } from '../Files';
|
import { Files } from '../Files';
|
||||||
import { Catalog } from 'sounds';
|
import { Catalog } from 'sounds';
|
||||||
import { WorkspaceListWidget } from './WorkspaceListWidget';
|
import { WorkspaceListWidget } from './WorkspaceListWidget';
|
||||||
|
|
||||||
|
const ChatWidget = () => {
|
||||||
|
const [provider, setProvider] = useState<'claude' | 'opencode'>('claude');
|
||||||
|
return provider === 'claude' ? (
|
||||||
|
<ClaudeChatWidget key="claude" onProviderChange={setProvider} />
|
||||||
|
) : (
|
||||||
|
<OpenCodeChatWidget key="opencode" onProviderChange={setProvider} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClaudeChatWidget = ({ onProviderChange }: { onProviderChange: (p: 'claude' | 'opencode') => void }) => {
|
||||||
|
const claude = useClaude();
|
||||||
|
const models = useVisibleClaudeModels();
|
||||||
|
return <ChatPanel chat={claude} provider="claude" availableModels={models} onProviderChange={onProviderChange} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpenCodeChatWidget = ({ onProviderChange }: { onProviderChange: (p: 'claude' | 'opencode') => void }) => {
|
||||||
|
const opencode = useOpenCode();
|
||||||
|
const models = useVisibleOpenCodeModels();
|
||||||
|
return <ChatPanel chat={opencode} provider="opencode" availableModels={models} onProviderChange={onProviderChange} />;
|
||||||
|
};
|
||||||
|
|
||||||
const CodeEditorWrapper = () => <CodeEditorView className="h-full w-full" />;
|
const CodeEditorWrapper = () => <CodeEditorView className="h-full w-full" />;
|
||||||
|
|
||||||
const TerminalWrapper = () => <TerminalView className="h-full w-full p-2" />;
|
const TerminalWrapper = () => <TerminalView className="h-full w-full p-2" />;
|
||||||
|
|
||||||
export const widgetRegistry: WidgetRegistry = {
|
export const widgetRegistry: WidgetRegistry = {
|
||||||
'chat-launcher': { name: 'Chat', icon: MessageSquare, component: () => <ChatLauncher /> },
|
'chat': { name: 'Chat', icon: MessageSquare, component: ChatWidget },
|
||||||
'file-browser': { name: 'File Browser', icon: FolderOpen, component: () => <FileBrowser /> },
|
'file-browser': { name: 'File Browser', icon: FolderOpen, component: () => <Files /> },
|
||||||
'chat-history': { name: 'Chat History', icon: History, component: () => <ChatHistory /> },
|
'chat-history': { name: 'Chat History', icon: History, component: () => <ChatHistory /> },
|
||||||
'sound-library': { name: 'Sound Library', icon: Music, component: () => <Catalog /> },
|
'sound-library': { name: 'Sound Library', icon: Music, component: () => <Catalog /> },
|
||||||
'code-editor': { name: 'Code Editor', icon: Code, component: CodeEditorWrapper },
|
'code-editor': { name: 'Code Editor', icon: Code, component: CodeEditorWrapper },
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export * from './OnboardingAdmin';
|
|||||||
export * from './PasskeyGate';
|
export * from './PasskeyGate';
|
||||||
export * from './Plans';
|
export * from './Plans';
|
||||||
export * from './Processes';
|
export * from './Processes';
|
||||||
export * from './ResourcePage';
|
export * from './CapabilityPage';
|
||||||
export * from './Settings';
|
export * from './Settings';
|
||||||
export * from './Skills';
|
export * from './Skills';
|
||||||
export * from './TaskLogs';
|
export * from './TaskLogs';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { LayoutPanel, WidgetRegistry } from './types';
|
import type { LayoutPanel, WidgetRegistry } from './types';
|
||||||
|
import { Card } from '../Card';
|
||||||
import { WidgetPicker } from './WidgetPicker';
|
import { WidgetPicker } from './WidgetPicker';
|
||||||
import { LayoutEditor } from './LayoutEditor';
|
import { LayoutEditor } from './LayoutEditor';
|
||||||
|
|
||||||
@@ -20,10 +21,7 @@ export const PanelSlot = ({ panel, registry, editing, isLastPanel, onSetWidget,
|
|||||||
|
|
||||||
if (!WidgetComponent) {
|
if (!WidgetComponent) {
|
||||||
return (
|
return (
|
||||||
<div
|
<Card className="relative h-full w-full flex flex-col items-center justify-center gap-3 p-4">
|
||||||
className="relative h-full w-full flex flex-col items-center justify-center gap-3 p-4 rounded-lg border backdrop-blur-xl"
|
|
||||||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.12)', borderColor: 'rgba(255, 255, 255, 0.2)' }}
|
|
||||||
>
|
|
||||||
<WidgetPicker registry={registry} onSelect={(type) => onSetWidget(panel.id, type)} />
|
<WidgetPicker registry={registry} onSelect={(type) => onSetWidget(panel.id, type)} />
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<button
|
<button
|
||||||
@@ -50,18 +48,18 @@ export const PanelSlot = ({ panel, registry, editing, isLastPanel, onSetWidget,
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative h-full w-full overflow-hidden rounded-lg border backdrop-blur-xl"
|
className="relative h-full w-full overflow-hidden rounded-lg border backdrop-blur-xl p-2"
|
||||||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.12)', borderColor: 'rgba(255, 255, 255, 0.2)' }}
|
style={{ backgroundColor: 'rgba(255, 255, 255, 0.12)', borderColor: 'rgba(255, 255, 255, 0.2)' }}
|
||||||
>
|
>
|
||||||
<div className="h-full w-full [&>*]:!h-full [&>*]:!flex [&>*]:!flex-col [&>*]:!rounded-none [&>*]:!border-0 [&>*]:!shadow-none [&>*>*:last-child]:!flex-1 [&>*>*:last-child]:!min-h-0 [&>*>*:last-child]:!max-h-none [&>*>*:last-child]:!overflow-auto" style={{ ['--card-bg' as string]: 'transparent', ['--card-grid-color' as string]: 'transparent' }}>
|
<Card className="h-full w-full overflow-hidden p-0 [&>*]:!h-full [&>*]:!flex [&>*]:!flex-col [&>*]:!rounded-none [&>*]:!border-0 [&>*]:!shadow-none [&>*>*:last-child]:!flex-1 [&>*>*:last-child]:!min-h-0 [&>*>*:last-child]:!max-h-none [&>*>*:last-child]:!overflow-hidden">
|
||||||
<WidgetComponent panelId={panel.id} />
|
<WidgetComponent panelId={panel.id} />
|
||||||
</div>
|
</Card>
|
||||||
{editing && (
|
{editing && (
|
||||||
<LayoutEditor
|
<LayoutEditor
|
||||||
panelId={panel.id}
|
panelId={panel.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user