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/:sessionId" element={<Dashboard.OpenCodeChat />} />
|
||||
<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="/code-editor" element={<Dashboard.CodeEditor />} />
|
||||
<Route path="/skills" element={<Dashboard.Skills />} />
|
||||
|
||||
+12
-12
@@ -13,27 +13,27 @@ import { Card } from '@/components/Card';
|
||||
import type { ChatMessage } from 'widgets/Chat';
|
||||
import { useClaude } from '@/Screens/Dashboard/Chat/useClaude';
|
||||
import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat';
|
||||
type ResourceSummary = {
|
||||
type CapabilitySummary = {
|
||||
dirName: string;
|
||||
name: string;
|
||||
description: string;
|
||||
scope: 'global' | 'user';
|
||||
};
|
||||
|
||||
type ResourceDetail = ResourceSummary & {
|
||||
type CapabilityDetail = CapabilitySummary & {
|
||||
body: string;
|
||||
rawFrontmatter: string;
|
||||
filePath: string;
|
||||
chatSessionId: string | null;
|
||||
};
|
||||
|
||||
type ResourcePageProps = {
|
||||
type CapabilityPageProps = {
|
||||
kind: string;
|
||||
endpoint: string;
|
||||
queryKey: string;
|
||||
};
|
||||
|
||||
type ResourceChatProps = {
|
||||
type CapabilityChatProps = {
|
||||
kind: string;
|
||||
endpoint: string;
|
||||
dirName: string;
|
||||
@@ -44,7 +44,7 @@ type ResourceChatProps = {
|
||||
onResponseEnd?: () => void;
|
||||
};
|
||||
|
||||
const ResourceChat = ({
|
||||
const CapabilityChat = ({
|
||||
kind,
|
||||
endpoint,
|
||||
dirName,
|
||||
@@ -53,7 +53,7 @@ const ResourceChat = ({
|
||||
chatSessionId,
|
||||
isNew,
|
||||
onResponseEnd,
|
||||
}: ResourceChatProps) => {
|
||||
}: CapabilityChatProps) => {
|
||||
const client = useClient();
|
||||
const claudeModels = useVisibleClaudeModels();
|
||||
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 qc = useQueryClient();
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
@@ -142,9 +142,9 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
||||
const [showDetail, setShowDetail] = useState(false);
|
||||
const newNameRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const { data: items = [] } = useQuery<ResourceSummary[]>({
|
||||
const { data: items = [] } = useQuery<CapabilitySummary[]>({
|
||||
queryKey: [queryKey],
|
||||
queryFn: () => client.get<ResourceSummary[]>(endpoint),
|
||||
queryFn: () => client.get<CapabilitySummary[]>(endpoint),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -153,9 +153,9 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
||||
}
|
||||
}, [items, selected]);
|
||||
|
||||
const { data: detail } = useQuery<ResourceDetail>({
|
||||
const { data: detail } = useQuery<CapabilityDetail>({
|
||||
queryKey: [queryKey, selected],
|
||||
queryFn: () => client.get<ResourceDetail>(`${endpoint}/${selected}`),
|
||||
queryFn: () => client.get<CapabilityDetail>(`${endpoint}/${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" />
|
||||
</button>
|
||||
</div>
|
||||
<ResourceChat
|
||||
<CapabilityChat
|
||||
kind={kind.toLowerCase()}
|
||||
key={detail.filePath}
|
||||
endpoint={endpoint}
|
||||
@@ -26,7 +26,6 @@ import { useFiles, type DirEntry, useTasks, type TaskSummary, Breadcrumb, Toolba
|
||||
import { useUserState } from '@/state/useUserState';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Card } from '@/components/Card';
|
||||
import { FileGrid } from './FileGrid';
|
||||
import { FileViewer } from './FileViewer';
|
||||
import { TaskRunnerModal } from './TaskRunnerModal';
|
||||
@@ -374,12 +373,7 @@ export const Files = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-center h-full p-0 md:p-4">
|
||||
<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]'
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* 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">
|
||||
<Toolbar
|
||||
@@ -638,7 +632,6 @@ export const Files = () => {
|
||||
</ContextMenu>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<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';
|
||||
|
||||
@@ -15,7 +15,7 @@ const DEFAULT_HOME_LAYOUT: LayoutNode = {
|
||||
id: 'home-left',
|
||||
direction: 'vertical',
|
||||
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' } },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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 type { WidgetRegistry } from '@/components/Workspace';
|
||||
import { CodeEditorView } from 'widgets/CodeEditor';
|
||||
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 { FileBrowserWidget as FileBrowser } from '../Files';
|
||||
import { Files } from '../Files';
|
||||
import { Catalog } from 'sounds';
|
||||
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 TerminalWrapper = () => <TerminalView className="h-full w-full p-2" />;
|
||||
|
||||
export const widgetRegistry: WidgetRegistry = {
|
||||
'chat-launcher': { name: 'Chat', icon: MessageSquare, component: () => <ChatLauncher /> },
|
||||
'file-browser': { name: 'File Browser', icon: FolderOpen, component: () => <FileBrowser /> },
|
||||
'chat': { name: 'Chat', icon: MessageSquare, component: ChatWidget },
|
||||
'file-browser': { name: 'File Browser', icon: FolderOpen, component: () => <Files /> },
|
||||
'chat-history': { name: 'Chat History', icon: History, component: () => <ChatHistory /> },
|
||||
'sound-library': { name: 'Sound Library', icon: Music, component: () => <Catalog /> },
|
||||
'code-editor': { name: 'Code Editor', icon: Code, component: CodeEditorWrapper },
|
||||
|
||||
@@ -5,7 +5,7 @@ export * from './OnboardingAdmin';
|
||||
export * from './PasskeyGate';
|
||||
export * from './Plans';
|
||||
export * from './Processes';
|
||||
export * from './ResourcePage';
|
||||
export * from './CapabilityPage';
|
||||
export * from './Settings';
|
||||
export * from './Skills';
|
||||
export * from './TaskLogs';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LayoutPanel, WidgetRegistry } from './types';
|
||||
import { Card } from '../Card';
|
||||
import { WidgetPicker } from './WidgetPicker';
|
||||
import { LayoutEditor } from './LayoutEditor';
|
||||
|
||||
@@ -20,10 +21,7 @@ export const PanelSlot = ({ panel, registry, editing, isLastPanel, onSetWidget,
|
||||
|
||||
if (!WidgetComponent) {
|
||||
return (
|
||||
<div
|
||||
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)' }}
|
||||
>
|
||||
<Card className="relative h-full w-full flex flex-col items-center justify-center gap-3 p-4">
|
||||
<WidgetPicker registry={registry} onSelect={(type) => onSetWidget(panel.id, type)} />
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
@@ -50,18 +48,18 @@ export const PanelSlot = ({ panel, registry, editing, isLastPanel, onSetWidget,
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<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)' }}
|
||||
>
|
||||
<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} />
|
||||
</div>
|
||||
</Card>
|
||||
{editing && (
|
||||
<LayoutEditor
|
||||
panelId={panel.id}
|
||||
|
||||
Reference in New Issue
Block a user