diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 78fc74e6..991113ea 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -51,7 +51,8 @@ export function App() { } /> } /> } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/defaultLayout.ts b/src/apps/officer-web/Screens/Dashboard/ChatHistory/defaultLayout.ts new file mode 100644 index 00000000..70c8d2df --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/defaultLayout.ts @@ -0,0 +1,11 @@ +import type { LayoutNode } from '@/components/Workspace'; + +export const defaultLayout: LayoutNode = { + type: 'group', + id: 'chat-history-root', + direction: 'horizontal', + children: [ + { node: { type: 'panel', id: 'chat-list', appType: 'chat-session-list' }, size: 35 }, + { node: { type: 'panel', id: 'chat-detail', appType: 'chat-detail' }, size: 65 }, + ], +}; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx index e2222060..852f0912 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx @@ -1,25 +1,14 @@ -import { useMemo, useEffect } from 'react'; +import { useEffect } from 'react'; import { useParams } from 'react-router'; -import type { LayoutNode, PanelComponents } from '@/components/Workspace'; -import { WorkspaceLayout } from '@/components/Workspace'; +import { WorkspaceView } from '@/components/Workspace'; +import type { LayoutNode } from '@/components/Workspace'; +import { useWorkspacesState } from 'state/useWorkspacesState'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { useChatSessions } from 'state/useChatSessions'; - -import { SessionList } from './Screen'; -import { ChatDetailPanel, type SelectedSession } from './ChatDetailPanel'; +import type { SelectedSession } from 'officerdev'; +import { defaultLayout } from './defaultLayout'; export { ChatHistory as ChatHistoryApp } from './Widget'; -export { SessionList }; - -const layout: LayoutNode = { - type: 'group', - id: 'chat-history-root', - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: 'chat-list', appType: null }, size: 35 }, - { node: { type: 'panel', id: 'chat-detail', appType: null }, size: 65 }, - ], -}; type SessionListPageProps = { isNew?: boolean; @@ -29,6 +18,7 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => { const { sessionId } = useParams<{ sessionId: string }>(); const { sessions } = useChatSessions(); const [, setSelected] = usePanelChannel('chat:selected-session', null); + const workspace = useWorkspacesState('screens/chat', defaultLayout); useEffect(() => { if (isNew) { @@ -40,17 +30,9 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => { setSelected({ id: sessionId, model: session?.model ?? null }); }, [sessionId, isNew]); - const panelComponents: PanelComponents = useMemo( - () => ({ - 'chat-list': SessionList, - 'chat-detail': ChatDetailPanel, - }), - [], - ); - return (
- {}} components={panelComponents} /> +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx b/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx deleted file mode 100644 index 5ebf205e..00000000 --- a/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useRef, useEffect, useState } from 'react'; -import { useNavigate, useLocation } from 'react-router'; -import { useChatSessions } from 'state/useChatSessions'; -import { SessionBar, EmbeddableChat, type UsePiChatType, type Attachment } from 'officerdev'; -import { Card } from '@/components/Card'; - -export type { Attachment }; - -type ChatPanelProps = { - chat: UsePiChatType; -}; - -export const ChatScreen = ({ chat }: ChatPanelProps) => { - const { isConnected, isGenerating, sessionId, setSelectedModel, sendPrompt } = chat; - - const location = useLocation(); - const navigate = useNavigate(); - const [fullscreen, setFullscreen] = useState(false); - - const { sessions, deleteSession } = useChatSessions(); - const sessionTitle = sessionId ? sessions.find((s) => s.id === sessionId)?.title : undefined; - const listPath = '/chat'; - - // Capture initial state from navigation - const locationState = location.state as { - initialMessage?: string; - prefillInput?: string; - model?: string; - cwd?: { root?: string; path: string }; - attachmentIds?: string[]; - images?: { filename: string; dataUrl: string }[]; - } | null; - - const initialMessage = locationState?.initialMessage - ? { - text: locationState.initialMessage, - attachmentIds: locationState.attachmentIds, - images: locationState.images, - cwd: locationState.cwd, - } - : undefined; - - const defaultInput = locationState?.prefillInput ?? ''; - const initialModel = locationState?.model ?? null; - - // Clear location state after capturing - useEffect(() => { - if (locationState) { - window.history.replaceState({}, '', location.pathname); - } - }, []); - - return ( - - { - if (!sessionId) return; - await deleteSession(sessionId); - navigate(listPath); - }} - onToggleFullscreen={() => setFullscreen((f) => !f)} - /> - - - - ); -}; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx deleted file mode 100644 index 18188300..00000000 --- a/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './ChatScreen'; diff --git a/src/apps/officer-web/Screens/Dashboard/CodeEditor/index.tsx b/src/apps/officer-web/Screens/Dashboard/CodeEditor/index.tsx deleted file mode 100644 index 7cb4c18e..00000000 --- a/src/apps/officer-web/Screens/Dashboard/CodeEditor/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CodeEditorView } from 'officerdev'; -import { Widget } from 'widgets/Widget'; - -export const CodeEditor = () => ( -
- - - -
-); diff --git a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx index a7aeb758..74dbf363 100644 --- a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx @@ -1,15 +1,10 @@ -import { useEffect, useState } from 'react'; -import { Link, useNavigate, useSearchParams } from 'react-router'; -import { FolderKanban, Plus, ArrowRight, Type, Rocket, FileText, Layout } from 'lucide-react'; -import { useQueryClient } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { useNavigate, useSearchParams } from 'react-router'; import { useGlobal } from 'hooks/useGlobal'; -import { useClient } from 'hooks/useClient'; -import { useProjectsState } from 'state/useProjectsState'; -import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '@/components/Workspace'; -import type { LayoutNode, ProjectDefinition, ProjectType } from '@/components/Workspace'; -import { Button } from '@/components/ui/button'; -import { generateSlug, slugify } from 'helpers/slug'; -import { useAppRegistry } from 'officerdev'; +import { useWorkspacesState } from 'state/useWorkspacesState'; +import { WorkspaceView } from '@/components/Workspace'; +import type { LayoutNode, ProjectType } from '@/components/Workspace'; +import { generateSlug } from 'helpers/slug'; import { SELECTED_PROJECT, CREATING_PROJECT, @@ -20,600 +15,15 @@ import { NEW_PROJ_TYPE, NEW_PROJ_HAS_BACKEND, NEW_PROJ_HAS_AUTH, - NEW_PROJ_PREVIEW_LAYOUT, -} from './constants'; -import { ProjectListApp } from './ProjectListApp'; - -const defaultLayout: LayoutNode = { - type: 'group', - id: 'proj-home-root', - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: 'proj-home-list', appType: 'project-list' }, size: 25 }, - { node: { type: 'panel', id: 'proj-home-right', appType: 'project-preview' }, size: 75 }, - ], -}; - -// --- Layout Templates --- - -let tplCounter = 0; -const tplUid = () => `ptpl-${++tplCounter}`; - -type LayoutTemplate = { - name: string; - layout: () => LayoutNode; -}; - -const templates: LayoutTemplate[] = [ - { - name: 'Single', - layout: () => ({ type: 'panel', id: tplUid(), appType: null }), - }, - { - name: '2 Columns', - layout: () => ({ - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - ], - }), - }, - { - name: 'Main + Side', - layout: () => ({ - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { - node: { - type: 'group', - id: tplUid(), - direction: 'vertical', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - ], - }, - size: 50, - }, - ], - }), - }, - { - name: 'Sidebar', - layout: () => ({ - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 25 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 75 }, - ], - }), - }, - { - name: '2x2 Grid', - layout: () => ({ - type: 'group', - id: tplUid(), - direction: 'vertical', - children: [ - { - node: { - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - ], - }, - size: 50, - }, - { - node: { - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - ], - }, - size: 50, - }, - ], - }), - }, - { - name: 'Cols + Bottom', - layout: () => ({ - type: 'group', - id: tplUid(), - direction: 'vertical', - children: [ - { - node: { - type: 'group', - id: tplUid(), - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 50 }, - ], - }, - size: 70, - }, - { node: { type: 'panel', id: tplUid(), appType: null }, size: 30 }, - ], - }), - }, -]; - -// --- Template Thumbnails --- - -const ThumbnailNode = ({ node }: { node: LayoutNode }) => { - if (node.type === 'panel') { - return
; - } - const isH = node.direction === 'horizontal'; - return ( -
- {node.children.map((child) => ( -
- -
- ))} -
- ); -}; - -const TemplateCell = ({ index }: { index: number }) => { - const tpl = templates[index]!; - const [selected, setSelected] = useGlobal(NEW_PROJ_TEMPLATE, 0); - const node = tpl.layout(); - const isSelected = selected === index; - - return ( -
- -
- ); -}; - -// --- Panel: Template Picker (3x2 workspace) --- - -const tplPanelLayout: LayoutNode = { - type: 'group', - id: 'ptpl-root', - direction: 'vertical', - children: [ - { - node: { - type: 'group', - id: 'ptpl-row-0', - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: 'ptpl-0', appType: 'ptpl-0' }, size: 33.33 }, - { node: { type: 'panel', id: 'ptpl-1', appType: 'ptpl-1' }, size: 33.33 }, - { node: { type: 'panel', id: 'ptpl-2', appType: 'ptpl-2' }, size: 33.34 }, - ], - }, - size: 50, - }, - { - node: { - type: 'group', - id: 'ptpl-row-1', - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: 'ptpl-3', appType: 'ptpl-3' }, size: 33.33 }, - { node: { type: 'panel', id: 'ptpl-4', appType: 'ptpl-4' }, size: 33.33 }, - { node: { type: 'panel', id: 'ptpl-5', appType: 'ptpl-5' }, size: 33.34 }, - ], - }, - size: 50, - }, - ], -}; - -const tplRegistry = Object.fromEntries( - templates.map((tpl, i) => [`ptpl-${i}`, { name: tpl.name, icon: Layout, component: () => }]), -); - -const TemplatePanel = () => ( - {}} registry={tplRegistry} /> -); - -// --- Panel: Details (Name + Description + Project Type + Backend/Auth toggles) --- - -const PROJECT_TYPE_OPTIONS: { value: ProjectType; label: string }[] = [ - { value: 'landing-page', label: 'Landing Page' }, - { value: 'website', label: 'Website' }, - { value: 'app', label: 'App' }, -]; - -const DetailsPanel = () => { - const [name, setName] = useGlobal(NEW_PROJ_NAME, ''); - const [description, setDescription] = useGlobal(NEW_PROJ_DESC, ''); - const [projectType, setProjectType] = useGlobal(NEW_PROJ_TYPE, 'app'); - const [hasBackend, setHasBackend] = useGlobal(NEW_PROJ_HAS_BACKEND, false); - const [hasAuth, setHasAuth] = useGlobal(NEW_PROJ_HAS_AUTH, false); - - return ( -
-
-
- - setName(ev.target.value)} - placeholder="My Project" - autoFocus - className="rounded-lg border border-duck-dark/20 bg-background px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-2 focus:ring-emerald-500/30 focus:border-emerald-500/50" - /> -
-
- -