From 74e98e95e1429db5b467da1549851efdff6d0f28 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Tue, 17 Feb 2026 19:52:41 +0000 Subject: [PATCH] extracted chat component to widgets --- src/apps/officer-web/App.tsx | 1 + .../Screens/Dashboard/Chat}/ChatPanel.tsx | 9 +- .../Dashboard/Chat}/EmbeddableChat.tsx | 2 +- .../Screens/Dashboard/Chat}/InputArea.tsx | 2 +- .../Dashboard/Chat}/OpenCodeModelPicker.tsx | 0 .../Screens/Dashboard/Chat}/Settings.tsx | 2 +- .../Screens/Dashboard/Chat/index.tsx | 6 +- .../Screens/Dashboard/Chat}/useClaude.ts | 4 +- .../Screens/Dashboard/Chat}/useOpenCode.ts | 4 +- .../Screens/Dashboard/ChatHistory}/Screen.tsx | 3 +- .../Screens/Dashboard/ChatHistory}/Widget.tsx | 4 +- .../Screens/Dashboard/ChatHistory/index.tsx | 2 + .../Files/Screen/TaskRunnerModal.tsx | 5 +- .../Screens/Dashboard/Home/ChatLauncher.tsx | 2 +- .../Screens/Dashboard/Home/index.tsx | 2 +- .../Screens/Dashboard/ResourcePage.tsx | 4 +- .../Settings/ResourceSettings/index.tsx | 4 +- .../Screens/Dashboard/TaskLogs/index.tsx | 2 +- .../officer-web/Screens/Dashboard/index.tsx | 1 + src/apps/officer-web/state/useInitialData.ts | 2 +- src/workspaces/plugins/Chat/index.ts | 7 - .../plugins/ChatHistory/client/index.ts | 7 - src/workspaces/plugins/ChatHistory/index.ts | 7 - src/workspaces/plugins/PLUGINS.md | 259 ------------------ src/workspaces/plugins/package.json | 12 - .../client => widgets/Chat}/MessageBubble.tsx | 0 .../client => widgets/Chat}/MessageList.tsx | 0 .../Chat}/QuestionActivity.tsx | 0 .../client => widgets/Chat}/ToolActivity.tsx | 0 .../Chat/client => widgets/Chat}/index.ts | 6 - .../Chat/client => widgets/Chat}/types.ts | 0 .../ChatHistory}/SessionBar.tsx | 0 src/workspaces/widgets/ChatHistory/index.ts | 4 + .../ChatHistory}/useOpenCodeSessions.ts | 2 +- .../ChatHistory}/useSessions.ts | 2 +- .../ChatHistory}/useSlashCommands.ts | 0 src/workspaces/widgets/package.json | 4 +- 37 files changed, 44 insertions(+), 327 deletions(-) rename src/{workspaces/plugins/ChatHistory/client => apps/officer-web/Screens/Dashboard/Chat}/ChatPanel.tsx (92%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/EmbeddableChat.tsx (99%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/InputArea.tsx (99%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/OpenCodeModelPicker.tsx (100%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/Settings.tsx (98%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/useClaude.ts (98%) rename src/{workspaces/plugins/Chat/client => apps/officer-web/Screens/Dashboard/Chat}/useOpenCode.ts (98%) rename src/{workspaces/plugins/ChatHistory/client => apps/officer-web/Screens/Dashboard/ChatHistory}/Screen.tsx (97%) rename src/{workspaces/plugins/ChatHistory/client => apps/officer-web/Screens/Dashboard/ChatHistory}/Widget.tsx (97%) create mode 100644 src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx delete mode 100644 src/workspaces/plugins/Chat/index.ts delete mode 100644 src/workspaces/plugins/ChatHistory/client/index.ts delete mode 100644 src/workspaces/plugins/ChatHistory/index.ts delete mode 100644 src/workspaces/plugins/PLUGINS.md delete mode 100644 src/workspaces/plugins/package.json rename src/workspaces/{plugins/Chat/client => widgets/Chat}/MessageBubble.tsx (100%) rename src/workspaces/{plugins/Chat/client => widgets/Chat}/MessageList.tsx (100%) rename src/workspaces/{plugins/Chat/client => widgets/Chat}/QuestionActivity.tsx (100%) rename src/workspaces/{plugins/Chat/client => widgets/Chat}/ToolActivity.tsx (100%) rename src/workspaces/{plugins/Chat/client => widgets/Chat}/index.ts (50%) rename src/workspaces/{plugins/Chat/client => widgets/Chat}/types.ts (100%) rename src/workspaces/{plugins/ChatHistory/client => widgets/ChatHistory}/SessionBar.tsx (100%) create mode 100644 src/workspaces/widgets/ChatHistory/index.ts rename src/workspaces/{plugins/ChatHistory/client/state => widgets/ChatHistory}/useOpenCodeSessions.ts (95%) rename src/workspaces/{plugins/ChatHistory/client/state => widgets/ChatHistory}/useSessions.ts (96%) rename src/workspaces/{plugins/ChatHistory/client/state => widgets/ChatHistory}/useSlashCommands.ts (100%) diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 340d5c8f..af71fffe 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -40,6 +40,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/workspaces/plugins/ChatHistory/client/ChatPanel.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx similarity index 92% rename from src/workspaces/plugins/ChatHistory/client/ChatPanel.tsx rename to src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx index b14991ff..f5368a94 100644 --- a/src/workspaces/plugins/ChatHistory/client/ChatPanel.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx @@ -1,13 +1,10 @@ import { useRef, useEffect, useState } from 'react'; import { useNavigate, useLocation } from 'react-router'; -import { useSessions } from './state/useSessions'; +import { useSessions, useOpenCodeSessions, useSlashCommands, SessionBar } from 'widgets/ChatHistory'; import type { ModelOption } from '@/state/useModels'; -import { useOpenCodeSessions } from './state/useOpenCodeSessions'; -import type { useClaude, Attachment } from 'plugins/Chat/client'; -import { EmbeddableChat } from 'plugins/Chat/client'; -import { useSlashCommands } from './state/useSlashCommands'; +import type { useClaude } from './useClaude'; +import { EmbeddableChat, type Attachment } from './EmbeddableChat'; import { Card } from '@/components/Card'; -import { SessionBar } from './SessionBar'; export type { Attachment }; diff --git a/src/workspaces/plugins/Chat/client/EmbeddableChat.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/EmbeddableChat.tsx similarity index 99% rename from src/workspaces/plugins/Chat/client/EmbeddableChat.tsx rename to src/apps/officer-web/Screens/Dashboard/Chat/EmbeddableChat.tsx index ad5c2b25..34af76c8 100644 --- a/src/workspaces/plugins/Chat/client/EmbeddableChat.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Chat/EmbeddableChat.tsx @@ -4,7 +4,7 @@ import { toast } from 'sonner'; import { useClient } from 'hooks/useClient'; import type { ModelOption } from '@/state/useModels'; import type { useClaude } from './useClaude'; -import { MessageList } from './MessageList'; +import { MessageList } from 'widgets/Chat'; import { InputArea } from './InputArea'; export type Attachment = diff --git a/src/workspaces/plugins/Chat/client/InputArea.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/InputArea.tsx similarity index 99% rename from src/workspaces/plugins/Chat/client/InputArea.tsx rename to src/apps/officer-web/Screens/Dashboard/Chat/InputArea.tsx index 703b5847..b11b649b 100644 --- a/src/workspaces/plugins/Chat/client/InputArea.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Chat/InputArea.tsx @@ -10,7 +10,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import type { ModelOption } from '@/state/useModels'; -import type { ChatMessage } from './types'; +import type { ChatMessage } from 'widgets/Chat'; import type { Attachment } from './EmbeddableChat'; import { Settings } from './Settings'; diff --git a/src/workspaces/plugins/Chat/client/OpenCodeModelPicker.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/OpenCodeModelPicker.tsx similarity index 100% rename from src/workspaces/plugins/Chat/client/OpenCodeModelPicker.tsx rename to src/apps/officer-web/Screens/Dashboard/Chat/OpenCodeModelPicker.tsx diff --git a/src/workspaces/plugins/Chat/client/Settings.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/Settings.tsx similarity index 98% rename from src/workspaces/plugins/Chat/client/Settings.tsx rename to src/apps/officer-web/Screens/Dashboard/Chat/Settings.tsx index a211b3d2..16f932dd 100644 --- a/src/workspaces/plugins/Chat/client/Settings.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Chat/Settings.tsx @@ -1,7 +1,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useAuth } from 'hooks/useAuth'; import type { ModelOption } from '@/state/useModels'; -import type { ChatMessage } from './types'; +import type { ChatMessage } from 'widgets/Chat'; import { OpenCodeModelPicker } from './OpenCodeModelPicker'; type SettingsProps = { diff --git a/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx index f2242134..582f7afe 100644 --- a/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx @@ -1,8 +1,10 @@ import { useState } from 'react'; import { useParams } from 'react-router'; import { useQueryClient } from '@tanstack/react-query'; -import { useClaude, useOpenCode, type SessionEntry } from 'plugins/Chat/client'; -import { ChatPanel } from 'plugins/ChatHistory/client'; +import type { SessionEntry } from 'widgets/Chat'; +import { useClaude } from './useClaude'; +import { useOpenCode } from './useOpenCode'; +import { ChatPanel } from './ChatPanel'; import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels'; export const ClaudeChat = () => { diff --git a/src/workspaces/plugins/Chat/client/useClaude.ts b/src/apps/officer-web/Screens/Dashboard/Chat/useClaude.ts similarity index 98% rename from src/workspaces/plugins/Chat/client/useClaude.ts rename to src/apps/officer-web/Screens/Dashboard/Chat/useClaude.ts index ece4dfbf..6f5fd439 100644 --- a/src/workspaces/plugins/Chat/client/useClaude.ts +++ b/src/apps/officer-web/Screens/Dashboard/Chat/useClaude.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { useChatWebSocket } from 'hooks/useChatWebSocket'; -import { useSessions } from 'plugins/ChatHistory/client'; -import type { ChatMessage, ServerMessage, TaskInfo } from './types'; +import { useSessions } from 'widgets/ChatHistory'; +import type { ChatMessage, ServerMessage, TaskInfo } from 'widgets/Chat'; const SAVE_DEBOUNCE_MS = 1000; diff --git a/src/workspaces/plugins/Chat/client/useOpenCode.ts b/src/apps/officer-web/Screens/Dashboard/Chat/useOpenCode.ts similarity index 98% rename from src/workspaces/plugins/Chat/client/useOpenCode.ts rename to src/apps/officer-web/Screens/Dashboard/Chat/useOpenCode.ts index d86e5594..22d3d607 100644 --- a/src/workspaces/plugins/Chat/client/useOpenCode.ts +++ b/src/apps/officer-web/Screens/Dashboard/Chat/useOpenCode.ts @@ -2,8 +2,8 @@ import { useState, useEffect, useRef } from 'react'; import { useChatWebSocket } from 'hooks/useChatWebSocket'; import { useSettings } from '@/state/useSettings'; import { useVisibleOpenCodeModels } from '@/state/useModels'; -import { useOpenCodeSessions } from 'plugins/ChatHistory/client'; -import type { ChatMessage, ServerMessage, TaskInfo } from './types'; +import { useOpenCodeSessions } from 'widgets/ChatHistory'; +import type { ChatMessage, ServerMessage, TaskInfo } from 'widgets/Chat'; type UseOpenCodeOptions = { replaceUrl?: boolean; diff --git a/src/workspaces/plugins/ChatHistory/client/Screen.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx similarity index 97% rename from src/workspaces/plugins/ChatHistory/client/Screen.tsx rename to src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx index 7ec337c6..2f40e34a 100644 --- a/src/workspaces/plugins/ChatHistory/client/Screen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx @@ -3,8 +3,7 @@ import { Link } from 'react-router'; import { Plus, MessageSquare, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/Card'; -import { useSessions } from './state/useSessions'; -import { useOpenCodeSessions } from './state/useOpenCodeSessions'; +import { useSessions, useOpenCodeSessions } from 'widgets/ChatHistory'; type Filter = 'all' | 'claude' | 'opencode'; diff --git a/src/workspaces/plugins/ChatHistory/client/Widget.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx similarity index 97% rename from src/workspaces/plugins/ChatHistory/client/Widget.tsx rename to src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx index 232fde4e..9e9745c1 100644 --- a/src/workspaces/plugins/ChatHistory/client/Widget.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx @@ -2,8 +2,8 @@ import { useMemo } from 'react'; import { Link } from 'react-router'; import { MessageSquare, Trash2, ChevronDown, ChevronUp } from 'lucide-react'; import { Card } from '@/components/Card'; -import { useSessions } from './state/useSessions'; -import { useOpenCodeSessions } from './state/useOpenCodeSessions'; +import { useSessions } from 'widgets/ChatHistory'; +import { useOpenCodeSessions } from 'widgets/ChatHistory'; import { useUserState } from '@/state/useUserState'; export const ChatHistory = () => { diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx new file mode 100644 index 00000000..031f0f94 --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx @@ -0,0 +1,2 @@ +export { ChatHistory as ChatHistoryWidget } from './Widget'; +export { SessionList } from './Screen'; diff --git a/src/apps/officer-web/Screens/Dashboard/Files/Screen/TaskRunnerModal.tsx b/src/apps/officer-web/Screens/Dashboard/Files/Screen/TaskRunnerModal.tsx index 1e82b128..86a4cdc2 100644 --- a/src/apps/officer-web/Screens/Dashboard/Files/Screen/TaskRunnerModal.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Files/Screen/TaskRunnerModal.tsx @@ -3,7 +3,10 @@ import { X } from 'lucide-react'; import { Dialog, DialogOverlay, DialogPortal } from '@/components/ui/dialog'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { cardStyle } from '@/components/Card'; -import { useClaude, useOpenCode, EmbeddableChat, type TaskInfo } from 'plugins/Chat/client'; +import type { TaskInfo } from 'widgets/Chat'; +import { useClaude } from '@/Screens/Dashboard/Chat/useClaude'; +import { useOpenCode } from '@/Screens/Dashboard/Chat/useOpenCode'; +import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat'; import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels'; import { useSettings } from '@/state/useSettings'; import type { TaskSummary } from 'widgets/FileBrowser'; diff --git a/src/apps/officer-web/Screens/Dashboard/Home/ChatLauncher.tsx b/src/apps/officer-web/Screens/Dashboard/Home/ChatLauncher.tsx index b117a486..c37d25b5 100644 --- a/src/apps/officer-web/Screens/Dashboard/Home/ChatLauncher.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Home/ChatLauncher.tsx @@ -26,7 +26,7 @@ import { } from '@/components/ui/dropdown-menu'; import { useSettings } from '@/state/useSettings'; import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels'; -import type { Attachment } from 'plugins/Chat/client'; +import type { Attachment } from '@/Screens/Dashboard/Chat/EmbeddableChat'; export const ChatLauncher = () => { const navigate = useNavigate(); diff --git a/src/apps/officer-web/Screens/Dashboard/Home/index.tsx b/src/apps/officer-web/Screens/Dashboard/Home/index.tsx index 46351a48..917c51b0 100644 --- a/src/apps/officer-web/Screens/Dashboard/Home/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Home/index.tsx @@ -1,6 +1,6 @@ import { ChatLauncher } from './ChatLauncher'; import { FileBrowserWidget as FileBrowser } from '@/Screens/Dashboard/Files'; -import { Widget as ChatHistory } from 'plugins/ChatHistory/client'; +import { ChatHistoryWidget as ChatHistory } from '@/Screens/Dashboard/ChatHistory'; import { Catalog } from 'sounds'; export const HomeScreen = () => { diff --git a/src/apps/officer-web/Screens/Dashboard/ResourcePage.tsx b/src/apps/officer-web/Screens/Dashboard/ResourcePage.tsx index a2e1aa58..d6a8be20 100644 --- a/src/apps/officer-web/Screens/Dashboard/ResourcePage.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ResourcePage.tsx @@ -10,7 +10,9 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } f import { useClient } from 'hooks/useClient'; import { useVisibleClaudeModels } from '@/state/useModels'; import { Card } from '@/components/Card'; -import { useClaude, EmbeddableChat, type ChatMessage } from 'plugins/Chat/client'; +import type { ChatMessage } from 'widgets/Chat'; +import { useClaude } from '@/Screens/Dashboard/Chat/useClaude'; +import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat'; type ResourceSummary = { dirName: string; name: string; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx index 990f7b17..efa1012a 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx @@ -5,7 +5,9 @@ import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/Card'; import { useClient } from 'hooks/useClient'; -import { EmbeddableChat, useClaude, useOpenCode } from 'plugins/Chat/client'; +import { useClaude } from '@/Screens/Dashboard/Chat/useClaude'; +import { useOpenCode } from '@/Screens/Dashboard/Chat/useOpenCode'; +import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat'; import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels'; type AppStatus = { diff --git a/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx b/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx index 285e2f70..a5e53d08 100644 --- a/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { Search, AlertCircle, CheckCircle2, Clock, ArrowLeft } from 'lucide-react'; import { useClient } from 'hooks/useClient'; import { Card } from '@/components/Card'; -import { MessageBubble, type ChatMessage } from 'plugins/Chat/client'; +import { MessageBubble, type ChatMessage } from 'widgets/Chat'; type LogMetadata = { filename: string; diff --git a/src/apps/officer-web/Screens/Dashboard/index.tsx b/src/apps/officer-web/Screens/Dashboard/index.tsx index 662215cb..eb91c027 100644 --- a/src/apps/officer-web/Screens/Dashboard/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/index.tsx @@ -12,3 +12,4 @@ export * from './TaskLogs'; export * from './Tasks'; export * from './Terminal'; export * from './Files'; +export * from './ChatHistory'; diff --git a/src/apps/officer-web/state/useInitialData.ts b/src/apps/officer-web/state/useInitialData.ts index 48ae9cb0..f1f17cc0 100644 --- a/src/apps/officer-web/state/useInitialData.ts +++ b/src/apps/officer-web/state/useInitialData.ts @@ -1,4 +1,4 @@ -import { useSessions } from 'plugins/ChatHistory/client'; +import { useSessions } from 'widgets/ChatHistory'; import { usePlans } from './usePlans'; import { useSettings } from './useSettings'; import { useThemeSync } from './useThemeSync'; diff --git a/src/workspaces/plugins/Chat/index.ts b/src/workspaces/plugins/Chat/index.ts deleted file mode 100644 index dda14b28..00000000 --- a/src/workspaces/plugins/Chat/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './client'; - -export const plugin = { - id: 'Chat', - name: 'Chat', - description: 'Embeddable chat UI', -}; diff --git a/src/workspaces/plugins/ChatHistory/client/index.ts b/src/workspaces/plugins/ChatHistory/client/index.ts deleted file mode 100644 index 964d9c50..00000000 --- a/src/workspaces/plugins/ChatHistory/client/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { ChatHistory as Widget } from './Widget'; -export { SessionList as Screen } from './Screen'; -export { ChatPanel } from './ChatPanel'; -export { SessionBar } from './SessionBar'; -export { useSessions } from './state/useSessions'; -export { useOpenCodeSessions } from './state/useOpenCodeSessions'; -export { useSlashCommands, type SlashCommandResult } from './state/useSlashCommands'; diff --git a/src/workspaces/plugins/ChatHistory/index.ts b/src/workspaces/plugins/ChatHistory/index.ts deleted file mode 100644 index 7ccfde25..00000000 --- a/src/workspaces/plugins/ChatHistory/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { Widget, Screen, ChatPanel, SessionBar } from './client'; - -export const plugin = { - id: 'ChatHistory', - name: 'Chat History', - description: 'Session management for chat history', -}; diff --git a/src/workspaces/plugins/PLUGINS.md b/src/workspaces/plugins/PLUGINS.md deleted file mode 100644 index 557ae6b1..00000000 --- a/src/workspaces/plugins/PLUGINS.md +++ /dev/null @@ -1,259 +0,0 @@ -# Building a Plugin - -Plugins are self-contained modules that extend Officer with new features. They live under `src/workspaces/plugins/` and are auto-discovered at startup — no registration step required. - -## Directory Structure - -``` -src/workspaces/plugins/ -├── package.json # Workspace exports (update when adding a plugin) -└── MyPlugin/ - ├── index.ts # Metadata + re-exports - ├── server/ - │ ├── index.ts # Exports router and apiPath - │ └── router.ts # Hono API routes - └── client/ - ├── index.ts # Exports Widget and/or Screen - ├── Widget.tsx # Compact component (dashboard home) - ├── Screen/ - │ └── index.tsx # Full-page component (dedicated route) - └── state/ - └── useMyPlugin.ts # API client hook -``` - -A plugin can be server-only, client-only, or both. The discovery system checks for the presence of `server/index.ts` and `client/index.ts` to determine what the plugin provides. - -## Step-by-Step - -### 1. Create the plugin directory - -``` -mkdir -p src/workspaces/plugins/MyPlugin/{server,client/state,client/Screen} -``` - -### 2. Plugin metadata — `MyPlugin/index.ts` - -Every plugin must export a `plugin` object with metadata. This is read by the settings UI and the `/plugins` API. - -```ts -export { Widget, Screen } from './client'; -export { router, apiPath } from './server'; - -export const plugin = { - id: 'MyPlugin', // Must match directory name - name: 'My Plugin', // Display name in settings - description: 'What this plugin does', -}; -``` - -If your plugin is server-only, omit the client export. If client-only, omit the server export. - -### 3. Server router — `MyPlugin/server/` - -**`server/index.ts`** — Exports the router instance and the API path prefix: - -```ts -export * from './router'; - -export const apiPath = 'my-plugin'; -``` - -The `apiPath` determines the URL prefix. This router gets mounted at `/api/my-plugin`. - -**`server/router.ts`** — Define your API endpoints: - -```ts -import { createRouter } from '@@/create-router'; -import * as errors from '@@/custom-errors'; - -export const router = createRouter(); - -router.get('/items', async (ctx) => { - const user = ctx.get('user'); // Authenticated user - // ... - return ctx.json({ items: [] }); -}); - -router.post('/items', async (ctx) => { - const body = ctx.get('body'); // Parsed request body - if (!body.name) throw errors.BAD_REQUEST('Name is required'); - // ... - return ctx.json({ created: true }); -}); -``` - -Key points: -- `createRouter()` from `@@/create-router` gives you a typed Hono router -- All plugin routes are **protected** — user authentication is enforced automatically -- Access the authenticated user with `ctx.get('user')` (returns `User` from types) -- Access parsed body with `ctx.get('body')` -- Throw `CustomError` instances for error responses — they're caught by the global error handler - -**Available error helpers** (`@@/custom-errors`): -- `BAD_REQUEST(msg?)` — 400 -- `UNAUTHORIZED(msg?)` — 401 -- `FORBIDDEN(msg?)` — 403 -- `NOT_FOUND(msg?)` — 404 -- `CONFLICT(msg?)` — 409 -- `INTERNAL_SERVER_ERROR(msg?)` — 500 -- `TOO_MANY_REQUESTS(msg?, retryAfter?)` — 429 - -**User data helpers** (`@@/data-path`): -- `DATA_PATH` — base data directory -- `getHomeDir(email)` — user's home directory - -### 4. Client components — `MyPlugin/client/` - -**`client/index.ts`** — Export your components with standardized names: - -```ts -export { MyPluginWidget as Widget } from './Widget'; -export { MyPluginScreen as Screen } from './Screen'; -``` - -**`client/state/useMyPlugin.ts`** — API client hook: - -```ts -import { useClient } from 'hooks/useClient'; - -type Item = { - id: string; - name: string; -}; - -export const useMyPlugin = () => { - const client = useClient(); - - return { - listItems: () => client.get('/my-plugin/items'), - createItem: (name: string) => client.post('/my-plugin/items', { name }), - }; -}; -``` - -The `useClient()` hook provides an authenticated HTTP client. The path must match your `apiPath` from the server. - -**`client/Widget.tsx`** — Compact component for the dashboard home: - -```tsx -import { useState, useEffect } from 'react'; -import { useMyPlugin } from './state/useMyPlugin'; - -export const MyPluginWidget = () => { - // Widget implementation -}; -``` - -**`client/Screen/index.tsx`** — Full-page component: - -```tsx -import { DashboardLayout } from '@/Screens/Dashboard/Layout'; - -export const MyPluginScreen = () => { - return ( - - {/* Screen implementation */} - - ); -}; -``` - -### 5. Register workspace exports — `plugins/package.json` - -Add subpath exports so the monorepo can import your plugin: - -```json -{ - "exports": { - "./FileBrowser": "./FileBrowser/index.ts", - "./FileBrowser/client": "./FileBrowser/client/index.ts", - "./FileBrowser/server": "./FileBrowser/server/index.ts", - "./MyPlugin": "./MyPlugin/index.ts", - "./MyPlugin/client": "./MyPlugin/client/index.ts", - "./MyPlugin/server": "./MyPlugin/server/index.ts" - } -} -``` - -This lets other code import your plugin as: - -```ts -import { Widget, Screen } from 'plugins/MyPlugin/client'; -import { router, apiPath } from 'plugins/MyPlugin/server'; -import { plugin } from 'plugins/MyPlugin'; -``` - -### 6. Wire into the app (optional) - -The server router is auto-discovered and mounted — no changes needed. But if your plugin has client components that should appear in the main app, you'll need to add them manually: - -**Route** — `src/apps/officer-web/App.tsx`: - -```tsx -import { Screen as MyPluginScreen } from 'plugins/MyPlugin/client'; - -// Inside the authenticated routes: -{plugins?.MyPlugin !== false && } />} -``` - -**Nav item** — `src/apps/officer-web/Screens/Dashboard/Layout.tsx`: - -```tsx -{plugins?.MyPlugin !== false && ( - - - - My Plugin - - -)} -``` - -**Home widget** — `src/apps/officer-web/Screens/Dashboard/Home/index.tsx`: - -```tsx -import { Widget as MyPluginWidget } from 'plugins/MyPlugin/client'; - -// Inside the layout: -{plugins?.MyPlugin !== false && } -``` - -The `plugins` object comes from `useServerSettings()` and is `undefined` when no overrides exist — so `plugins?.MyPlugin !== false` defaults to showing the plugin (opt-out model). - -## Enable/Disable - -Plugins are **enabled by default**. Admins can disable them in Server Settings > Plugins, which writes to `~/.config/officer.dev/server-settings.json`: - -```json -{ - "plugins": { - "MyPlugin": false - } -} -``` - -When disabled: -- **Server-side**: The plugin router is not mounted (requires server restart) -- **Client-side**: Routes, nav items, and widgets are hidden immediately - -## Import Aliases - -| Alias | Resolves to | Use in | -|-------|-------------|--------| -| `@@/` | `src/servers/` | Server code (`createRouter`, `custom-errors`, `data-path`) | -| `@/` | `src/apps/officer-web/` | Client code (`components/ui/*`, `Screens/*`, `state/*`) | -| `hooks/` | `src/workspaces/hooks/src/` | Both (`useClient`, `useAuth`) | -| `types` | `src/workspaces/types/` | Both | -| `config` | `src/workspaces/config/` | Client code | -| `plugins/` | `src/workspaces/plugins/` | Both | - -## Checklist - -- [ ] `MyPlugin/index.ts` exports `plugin` metadata with `id` matching directory name -- [ ] `MyPlugin/server/index.ts` exports `router` and `apiPath` -- [ ] `MyPlugin/client/index.ts` exports `Widget` and/or `Screen` -- [ ] `plugins/package.json` has subpath exports for the new plugin -- [ ] App routes gated with `plugins?.MyPlugin !== false` -- [ ] Nav items gated with `plugins?.MyPlugin !== false` -- [ ] Home widgets gated with `plugins?.MyPlugin !== false` -- [ ] `bunx tsgo` — no new type errors diff --git a/src/workspaces/plugins/package.json b/src/workspaces/plugins/package.json deleted file mode 100644 index 1302524f..00000000 --- a/src/workspaces/plugins/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "plugins", - "version": "0.0.1", - "private": true, - "type": "module", - "exports": { - "./Chat": "./Chat/index.ts", - "./Chat/client": "./Chat/client/index.ts", - "./ChatHistory": "./ChatHistory/index.ts", - "./ChatHistory/client": "./ChatHistory/client/index.ts" - } -} diff --git a/src/workspaces/plugins/Chat/client/MessageBubble.tsx b/src/workspaces/widgets/Chat/MessageBubble.tsx similarity index 100% rename from src/workspaces/plugins/Chat/client/MessageBubble.tsx rename to src/workspaces/widgets/Chat/MessageBubble.tsx diff --git a/src/workspaces/plugins/Chat/client/MessageList.tsx b/src/workspaces/widgets/Chat/MessageList.tsx similarity index 100% rename from src/workspaces/plugins/Chat/client/MessageList.tsx rename to src/workspaces/widgets/Chat/MessageList.tsx diff --git a/src/workspaces/plugins/Chat/client/QuestionActivity.tsx b/src/workspaces/widgets/Chat/QuestionActivity.tsx similarity index 100% rename from src/workspaces/plugins/Chat/client/QuestionActivity.tsx rename to src/workspaces/widgets/Chat/QuestionActivity.tsx diff --git a/src/workspaces/plugins/Chat/client/ToolActivity.tsx b/src/workspaces/widgets/Chat/ToolActivity.tsx similarity index 100% rename from src/workspaces/plugins/Chat/client/ToolActivity.tsx rename to src/workspaces/widgets/Chat/ToolActivity.tsx diff --git a/src/workspaces/plugins/Chat/client/index.ts b/src/workspaces/widgets/Chat/index.ts similarity index 50% rename from src/workspaces/plugins/Chat/client/index.ts rename to src/workspaces/widgets/Chat/index.ts index 2cc184b9..96c5f870 100644 --- a/src/workspaces/plugins/Chat/client/index.ts +++ b/src/workspaces/widgets/Chat/index.ts @@ -1,11 +1,5 @@ -export { EmbeddableChat, type Attachment } from './EmbeddableChat'; export { MessageList } from './MessageList'; -export { InputArea } from './InputArea'; -export { Settings } from './Settings'; export { MessageBubble, StreamingBubble } from './MessageBubble'; export { ToolActivity } from './ToolActivity'; export { QuestionActivity } from './QuestionActivity'; -export { OpenCodeModelPicker } from './OpenCodeModelPicker'; -export { useClaude } from './useClaude'; -export { useOpenCode } from './useOpenCode'; export type { ChatMessage, SessionEntry, ServerMessage, TaskInfo } from './types'; diff --git a/src/workspaces/plugins/Chat/client/types.ts b/src/workspaces/widgets/Chat/types.ts similarity index 100% rename from src/workspaces/plugins/Chat/client/types.ts rename to src/workspaces/widgets/Chat/types.ts diff --git a/src/workspaces/plugins/ChatHistory/client/SessionBar.tsx b/src/workspaces/widgets/ChatHistory/SessionBar.tsx similarity index 100% rename from src/workspaces/plugins/ChatHistory/client/SessionBar.tsx rename to src/workspaces/widgets/ChatHistory/SessionBar.tsx diff --git a/src/workspaces/widgets/ChatHistory/index.ts b/src/workspaces/widgets/ChatHistory/index.ts new file mode 100644 index 00000000..2d149bce --- /dev/null +++ b/src/workspaces/widgets/ChatHistory/index.ts @@ -0,0 +1,4 @@ +export { SessionBar } from './SessionBar'; +export { useSessions } from './useSessions'; +export { useOpenCodeSessions } from './useOpenCodeSessions'; +export { useSlashCommands, type SlashCommandResult } from './useSlashCommands'; diff --git a/src/workspaces/plugins/ChatHistory/client/state/useOpenCodeSessions.ts b/src/workspaces/widgets/ChatHistory/useOpenCodeSessions.ts similarity index 95% rename from src/workspaces/plugins/ChatHistory/client/state/useOpenCodeSessions.ts rename to src/workspaces/widgets/ChatHistory/useOpenCodeSessions.ts index 40b9cfc8..39bae256 100644 --- a/src/workspaces/plugins/ChatHistory/client/state/useOpenCodeSessions.ts +++ b/src/workspaces/widgets/ChatHistory/useOpenCodeSessions.ts @@ -1,4 +1,4 @@ -import type { SessionEntry, ChatMessage } from 'plugins/Chat/client'; +import type { SessionEntry, ChatMessage } from 'widgets/Chat'; import { useAuth } from 'hooks/useAuth'; import { useClient } from 'hooks/useClient'; import { useQuery, useQueryClient } from '@tanstack/react-query'; diff --git a/src/workspaces/plugins/ChatHistory/client/state/useSessions.ts b/src/workspaces/widgets/ChatHistory/useSessions.ts similarity index 96% rename from src/workspaces/plugins/ChatHistory/client/state/useSessions.ts rename to src/workspaces/widgets/ChatHistory/useSessions.ts index c3266700..922ecdce 100644 --- a/src/workspaces/plugins/ChatHistory/client/state/useSessions.ts +++ b/src/workspaces/widgets/ChatHistory/useSessions.ts @@ -1,4 +1,4 @@ -import type { SessionEntry, ChatMessage } from 'plugins/Chat/client'; +import type { SessionEntry, ChatMessage } from 'widgets/Chat'; import type { SlashCommandResult } from './useSlashCommands'; import { useAuth } from 'hooks/useAuth'; import { useClient } from 'hooks/useClient'; diff --git a/src/workspaces/plugins/ChatHistory/client/state/useSlashCommands.ts b/src/workspaces/widgets/ChatHistory/useSlashCommands.ts similarity index 100% rename from src/workspaces/plugins/ChatHistory/client/state/useSlashCommands.ts rename to src/workspaces/widgets/ChatHistory/useSlashCommands.ts diff --git a/src/workspaces/widgets/package.json b/src/workspaces/widgets/package.json index 635f0ca4..57cc316a 100644 --- a/src/workspaces/widgets/package.json +++ b/src/workspaces/widgets/package.json @@ -3,6 +3,8 @@ "private": true, "exports": { "./Terminal": "./Terminal/index.ts", - "./FileBrowser": "./FileBrowser/index.ts" + "./FileBrowser": "./FileBrowser/index.ts", + "./ChatHistory": "./ChatHistory/index.ts", + "./Chat": "./Chat/index.ts" } }