diff --git a/.gitignore b/.gitignore index c6bfe81..449ac36 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,16 @@ dist/ build/ .vite/ +# Generated media +public/audio/ +public/podcasts/ + # Environment variables .env .env.local .env.production .env.development +.env.procore # IDE .vscode/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6ab08cb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# CLAUDE.md - PlexifyBID Repository Rules + +## Who I Am +Ken, solo founder of Plexify AI. I coordinate development across: +- Claude Sonnet: Strategy, specs, prompts +- Code Droid (Factory.ai): Frontend implementation +- Claude Code: Backend development, architecture +- GitHub Desktop + VSCode: Local development + +## Repository Architecture +This is NOT a traditional frontend/backend split. The app runs as React + Vite with backend middleware embedded in Vite's dev server. + +### Backend Routes (Vite middleware): +- /api/agents/ — Claude-powered analysis +- /api/tts/generate — OpenAI text-to-speech +- /api/podcast/generate — ElevenLabs synthesis +- /api/export/docx — Word document export + +## Current State +- Stable tag: v0.1.0-demo-ready +- Active branch: develop +- Backend readiness: ~40% (works for demos, not production) +- Known gaps: No database, no auth, no rate limiting, no tests + +## Safety Rules (NON-NEGOTIABLE) + +### Before ANY code changes: +1. State what you intend to change and why +2. Wait for my explicit approval +3. Never force push, never delete branches + +### Branch discipline: +- Feature work: feature/[description] +- Backups before risky ops: backup/[description]-[date] +- PRs required for merging to develop + +### NEVER do: +- Delete functionality without approval +- Modify .env files or commit secrets +- Push directly to develop or main +- Run destructive operations +- Install packages without stating why + +## Known Technical Debt (Don't Fix Without Asking) +- @ts-nocheck in App.tsx — needs careful migration +- 85+ console.log statements — cleanup later +- as any casts — address incrementally +- Backup files in repo — cleanup later + +## Current Priorities +1. Backend stability for pilot demos +2. NotebookBD RAG pipeline reliability +3. API error handling improvements +4. Security hardening (auth, rate limiting) + +## Communication Style +- Be direct, not verbose +- Tables for comparisons +- Never use: delve, leverage, seamless, transformative +- Hit a blocker? Tell me immediately +- Uncertain? Propose 2-3 options, let me choose diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..f4570d2 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,57 @@ +# Troubleshooting + +## `Invalid hook call` when consuming `plexify-shared-ui` + +**Symptom** + +- Workspace (or other shared-ui components) white-screens and the console shows: + - `Invalid hook call. Hooks can only be called inside of the body of a function component...` + - Often followed by `Cannot read properties of null (reading 'useMemo')` coming from `@dnd-kit/*` hooks. + +**Cause** + +This almost always means the app has **multiple copies of React** (and/or hook-based dependencies like `@dnd-kit/*`) loaded at runtime. + +This is easy to trigger during local development when `plexify-shared-ui` is linked via `file:../plexify-shared-ui` and that linked folder contains its own `node_modules`. + +**Fix (Vite)** + +In the consuming app's `vite.config.ts`, ensure Vite dedupes these packages so a single instance is used: + +```ts +// vite.config.ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { resolve } from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + dedupe: [ + 'react', + 'react-dom', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + '@dnd-kit/core', + '@dnd-kit/sortable', + '@dnd-kit/utilities', + 'zustand', + '@tiptap/react', + '@tiptap/starter-kit', + ], + + // If you are using a linked `../plexify-shared-ui` that has its own node_modules, + // aliases are the most reliable way to force a single instance. + alias: { + react: resolve(__dirname, 'node_modules/react'), + 'react-dom': resolve(__dirname, 'node_modules/react-dom'), + 'react/jsx-runtime': resolve(__dirname, 'node_modules/react/jsx-runtime.js'), + 'react/jsx-dev-runtime': resolve(__dirname, 'node_modules/react/jsx-dev-runtime.js'), + }, + }, +}); +``` + +**Also recommended** + +- If you're linking locally, consider removing `../plexify-shared-ui/node_modules` (or avoid installing React there) so React is only resolved from the consuming app. diff --git a/docs/DEV_WORKFLOW.md b/docs/DEV_WORKFLOW.md new file mode 100644 index 0000000..16b371d --- /dev/null +++ b/docs/DEV_WORKFLOW.md @@ -0,0 +1,85 @@ +# PlexifyBID Development Workflow + +## AI-Assisted Development Stack + +| Tool | Role | When to Use | +|------|------|-------------| +| Claude Sonnet | Strategy, specs, prompts | Planning sessions, PRD work | +| Code Droid (Factory.ai) | Frontend implementation | UI components, React work | +| Claude Code | Backend development | API endpoints, middleware, architecture | +| GitHub Desktop + VSCode | Local development | Manual edits, debugging, git operations | + +## Claude Code Workflow + +### Before Starting +1. Pull latest from develop +2. Confirm CLAUDE.md is present at repo root +3. Claude Code will read CLAUDE.md and follow its rules + +### The Edit Cycle +branch → spec → review proposal → approve edits → test → PR + +**Step 1: Create Feature Branch** +Create a new branch called feature/[description] from develop + +**Step 2: Give a Clear Spec** +Include: +- What the feature should do +- Expected inputs/outputs +- Any patterns to follow +- End with: "Show me the approach before making changes" + +**Step 3: Review Proposal** +Claude Code should: +- Explore existing code first +- Propose where changes will go +- Show code before writing +- Wait for explicit approval + +If it skips this, say: +Stop. Show me the change before making it. That's in CLAUDE.md. + +**Step 4: Approve Edits** +- Review each file change +- Choose "Yes" for single edit approval +- Only use "Yes to all" for trusted, low-risk sessions + +**Step 5: Test Locally** +npm run dev +Verify the feature works before pushing. + +**Step 6: Push and PR** +Push this branch and create a PR to merge into develop + +## Commit Message Convention + +Format: `[type]: brief description` + +| Type | Use For | +|------|---------| +| feat | New feature | +| fix | Bug fix | +| refactor | Code restructure, no behavior change | +| docs | Documentation only | +| chore | Maintenance, dependencies | + +Examples: +- `feat: add /api/health endpoint` +- `fix(security): add .env.procore to gitignore` +- `docs: add DEV_WORKFLOW.md` + +## Safety Rules (from CLAUDE.md) + +- Always work on feature branches +- Never push directly to develop or main +- Create backup branches before risky operations +- PRs required for merging +- No destructive operations without explicit approval + +## Testing Checklist + +Before creating a PR: +- [ ] Feature works locally +- [ ] No console errors +- [ ] Existing features still work +- [ ] Commit messages follow convention diff --git a/package.json b/package.json index c74d0d2..275c620 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,79 @@ { - "name": "plexifybid", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "test": "echo \"No tests yet\"" - }, - "keywords": ["BID", "business-improvement-district", "operations", "management"], - "author": "Ken D'Amato ", - "license": "MIT", - "description": "PlexifyBID - Operations Management Platform for Business Improvement Districts", - "repository": { - "type": "git", - "url": "https://github.com/Plexify-AI/plexifybid.git" - }, - "dependencies": { - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@types/uuid": "^10.0.0", - "@vitejs/plugin-react": "^5.0.3", - "autoprefixer": "^10.4.21", - "axios": "^1.12.2", - "cors": "^2.8.5", - "crypto-js": "^4.2.0", - "dotenv": "^17.2.3", - "express": "^5.1.0", - "express-rate-limit": "^8.1.0", - "helmet": "^8.1.0", - "js-cookie": "^3.0.5", - "lucide-react": "^0.544.0", - "node-fetch": "^3.3.2", - "pdf-parse": "^2.2.2", - "postcss": "^8.5.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^7.9.1", - "tailwindcss": "^3.4.17", - "typescript": "^5.9.2", - "uuid": "^13.0.0", - "vite": "^7.1.6", - "zustand": "^5.0.8" - }, - "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.3", - "@types/js-cookie": "^3.0.6", - "@types/node": "^24.7.0" - } + "name": "plexifybid", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "assets:check": "node scripts/assets-check.mjs", + "assets:fix": "node scripts/assets-check.mjs --fix", + "test": "echo \"No tests yet\"", + "type-check": "tsc -p tsconfig.typecheck.json --noEmit" + }, + "keywords": [ + "BID", + "business-improvement-district", + "operations", + "management" + ], + "author": "Ken D'Amato ", + "license": "MIT", + "description": "PlexifyBID - Operations Management Platform for Business Improvement Districts", + "repository": { + "type": "git", + "url": "https://github.com/Plexify-AI/plexifybid.git" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@elevenlabs/elevenlabs-js": "^2.28.0", + "@tiptap/extension-highlight": "^2.1.0", + "@tiptap/extension-placeholder": "^2.1.0", + "@tiptap/extension-text-align": "^2.1.0", + "@tiptap/extension-underline": "^2.1.0", + "@tiptap/react": "^2.1.0", + "@tiptap/starter-kit": "^2.1.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.21", + "axios": "^1.12.2", + "cors": "^2.8.5", + "crypto-js": "^4.2.0", + "docx": "^9.5.1", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "express-rate-limit": "^8.1.0", + "file-saver": "^2.0.5", + "helmet": "^8.1.0", + "js-cookie": "^3.0.5", + "lucide-react": "^0.544.0", + "node-fetch": "^3.3.2", + "openai": "^6.15.0", + "pdf-parse": "1.1.1", + "plexify-shared-ui": "file:./plexify-shared-ui", + "plyr": "^3.8.4", + "postcss": "^8.5.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-h5-audio-player": "^3.10.1", + "react-router-dom": "^7.9.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.9.2", + "uuid": "^13.0.0", + "vite": "^5.4.11", + "zustand": "^4.5.7" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/file-saver": "^2.0.7", + "@types/js-cookie": "^3.0.6", + "@types/node": "^24.7.0", + "@types/pdf-parse": "^1.1.5", + "svgo": "^3.3.2" + } } diff --git a/plexify-shared-ui/.gitignore b/plexify-shared-ui/.gitignore new file mode 100644 index 0000000..40ec9fb --- /dev/null +++ b/plexify-shared-ui/.gitignore @@ -0,0 +1,11 @@ +node_modules +dist +*.log +.DS_Store +.env +.env.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* diff --git a/plexify-shared-ui/README.md b/plexify-shared-ui/README.md new file mode 100644 index 0000000..664b94a --- /dev/null +++ b/plexify-shared-ui/README.md @@ -0,0 +1,361 @@ +# Plexify Shared UI Components + +Shared React component library for PlexifyAEC, PlexifyBID, and PlexifyBIZ. + +## Overview + +This package provides theme-agnostic UI components that adapt to each Plexify product's branding: +- **PlexifyAEC**: Purple theme for Construction BD +- **PlexifyBID**: Navy blue theme for BID Directors +- **PlexifyBIZ**: Slate-to-copper gradient theme for Business Owners + +## Installation + +### For Local Development (npm link) +```bash +# In plexify-shared-ui/ +npm install +npm run build +npm link + +# In your app (plexifyaec, plexifybid, plexifybiz) +npm link plexify-shared-ui +``` + +### For Production (npm package) +```bash +npm install plexify-shared-ui +``` + +## Components + +### Report Editor Workspace + +AI-powered report editing workspace with 3-column layout. + +**Features:** +- Left Panel: Audio/video sources and draggable source materials +- Center Panel: Rich text block editor with AI regeneration +- Right Panel: AI research assistant chat interface + +**Usage:** +```typescript +import { ReportEditorWorkspace, PlexifyTheme } from 'plexify-shared-ui'; + +const myTheme: PlexifyTheme = { + name: 'bid', + primaryColor: '#1e3a8a', + secondaryColor: '#3b82f6', + accentColor: '#10b981', + gradientStart: '#1e3a8a', + gradientEnd: '#1e3a8a', + sidebarColor: '#1e3a8a', +}; + +function MyPage() { + return ( + console.log('Closed')} + theme={myTheme} + terminology="bid" + /> + ); +} +``` + +### Individual Components + +All workspace components can be used independently: + +```typescript +import { + AudioBriefingCard, + VideoSummaryCard, + SourceMaterialsList, + BlockEditor, + EditorToolbar, + RegenerateWithAIButton, + AIAssistantPanel, + AIMessageBubble, +} from 'plexify-shared-ui'; +``` + +## Theme Configuration + +Each Plexify product defines its own theme: + +### PlexifyAEC (Purple) +```typescript +import { aecTheme } from 'plexify-shared-ui'; + +// Or define custom: +export const aecTheme: PlexifyTheme = { + name: 'aec', + primaryColor: '#7c3aed', + secondaryColor: '#a78bfa', + accentColor: '#10b981', + gradientStart: '#7c3aed', + gradientEnd: '#7c3aed', + sidebarColor: '#7c3aed', +}; +``` + +### PlexifyBID (Navy Blue) +```typescript +import { bidTheme } from 'plexify-shared-ui'; + +// Or define custom: +export const bidTheme: PlexifyTheme = { + name: 'bid', + primaryColor: '#1e3a8a', + secondaryColor: '#3b82f6', + accentColor: '#10b981', + gradientStart: '#1e3a8a', + gradientEnd: '#1e3a8a', + sidebarColor: '#1e3a8a', +}; +``` + +### PlexifyBIZ (Slate-to-Copper Gradient) +```typescript +import { bizTheme } from 'plexify-shared-ui'; + +// Or define custom: +export const bizTheme: PlexifyTheme = { + name: 'biz', + primaryColor: '#353c56', + secondaryColor: '#d79973', + accentColor: '#d97706', + gradientStart: '#353c56', + gradientEnd: '#d79973', + sidebarColor: '#7c3aed', +}; +``` + +## Terminology Sets + +Components can adapt their labels based on product context: + +- **'construction'**: For PlexifyAEC (e.g., "Proposal" instead of "Report") +- **'bid'**: For PlexifyBID (e.g., "Board Report") +- **'business'**: For PlexifyBIZ (e.g., "Compliance Report") + +```typescript +import { terminologyConfigs } from 'plexify-shared-ui'; + +// Access terminology: +const config = terminologyConfigs['bid']; +console.log(config.reportTitle); // "Board Report" +console.log(config.regenerateButton); // "Regenerate Report" +console.log(config.aiAssistantName); // "Plexify AI - BID Assistant" +console.log(config.sourceMaterialsLabel); // "Source Materials" +``` + +## State Management + +Use the included Zustand store for workspace state: + +```typescript +import { useWorkspaceStore } from 'plexify-shared-ui'; + +function MyComponent() { + const { + content, + setContent, + messages, + addMessage, + sourceMaterials, + isWorkspaceOpen, + openWorkspace, + closeWorkspace, + } = useWorkspaceStore(); + + // Use state and actions... +} +``` + +## Export Utilities + +Export reports to PDF or PowerPoint: + +```typescript +import { exportReportToPDF, exportReportToPPTX } from 'plexify-shared-ui'; + +// Export to PDF +await exportReportToPDF(content, 'my-report.pdf', { + title: 'Board Report', + author: 'Plexify AI', + theme: bidTheme, +}); + +// Export to PowerPoint +await exportReportToPPTX(content, 'Project Name', { + title: 'Board Report', + theme: bidTheme, +}); +``` + +## Development + +```bash +# Install dependencies +npm install + +# Build in watch mode (for development) +npm run dev + +# Build for production +npm run build + +# Type check +npm run type-check +``` + +## Peer Dependencies + +This package requires the following peer dependencies (provided by your app): + +- react ^19.0.0 +- react-dom ^19.0.0 +- @tiptap/react ^2.1.0 +- @tiptap/starter-kit ^2.1.0 +- @tiptap/extension-placeholder ^2.1.0 +- zustand ^4.4.0 +- @dnd-kit/core ^6.1.0 +- @dnd-kit/sortable ^8.0.0 +- @dnd-kit/utilities ^3.2.0 +- react-h5-audio-player ^3.9.0 + +## Architecture + +**Tech Stack:** +- React 19 for UI components +- TypeScript for type safety +- Zustand for state management +- TipTap for rich text editing +- dnd-kit for drag and drop +- Vite for building + +**Design Principles:** +- Theme-agnostic components +- Dependency injection via props +- Type-safe interfaces +- Zero hardcoded colors or branding + +## Project Structure + +``` +plexify-shared-ui/ +├── src/ +│ ├── components/ +│ │ └── workspace/ +│ │ ├── ReportEditorWorkspace.tsx +│ │ ├── AudioBriefingCard.tsx +│ │ ├── VideoSummaryCard.tsx +│ │ ├── SourceMaterialsList.tsx +│ │ ├── BlockEditor.tsx +│ │ ├── EditorToolbar.tsx +│ │ ├── RegenerateWithAIButton.tsx +│ │ ├── AIAssistantPanel.tsx +│ │ ├── AIMessageBubble.tsx +│ │ └── index.ts +│ ├── stores/ +│ │ ├── workspaceStore.ts +│ │ └── index.ts +│ ├── utils/ +│ │ ├── exportToPDF.ts +│ │ ├── exportToPPTX.ts +│ │ └── index.ts +│ ├── types/ +│ │ ├── theme.ts +│ │ ├── workspace.ts +│ │ └── index.ts +│ └── index.ts +├── package.json +├── tsconfig.json +├── vite.config.ts +├── .gitignore +└── README.md +``` + +## Contributing + +When adding new shared components: + +1. Create component in `src/components/` +2. Accept `theme: PlexifyTheme` prop for all colors +3. Use relative imports (no `@/` aliases) +4. Export from appropriate index.ts +5. Add usage example to this README +6. Rebuild: `npm run build` +7. Test in all 3 products (AEC, BID, BIZ) + +## API Reference + +### PlexifyTheme Interface + +```typescript +interface PlexifyTheme { + name: 'aec' | 'bid' | 'biz'; + primaryColor: string; + secondaryColor: string; + accentColor: string; + gradientStart: string; + gradientEnd: string; + sidebarColor: string; + sidebarHoverColor?: string; + sidebarActiveColor?: string; + textPrimary?: string; + textSecondary?: string; + textInverse?: string; + successColor?: string; + warningColor?: string; + errorColor?: string; +} +``` + +### ReportEditorWorkspaceProps + +```typescript +interface ReportEditorWorkspaceProps { + projectId: string; + isOpen: boolean; + onClose: () => void; + theme: PlexifyTheme; + terminology?: TerminologySet; + config?: WorkspaceConfig; + initialContent?: string; + sourceMaterials?: SourceMaterial[]; + audioUrl?: string; + audioDuration?: string; + audioChapters?: AudioChapter[]; + videoUrl?: string; + videoThumbnail?: string; + videoDuration?: string; + onSave?: (content: string) => Promise; + onRegenerate?: (instructions?: string) => Promise; + onAIMessage?: (message: string) => Promise; + onExportPDF?: () => Promise; + onExportPPTX?: () => Promise; +} +``` + +## Version History + +**v1.0.0** - Initial release +- Report Editor Workspace +- Audio/Video components +- AI Assistant Panel +- Export utilities (PDF/PowerPoint) +- Zustand store for workspace state +- Full TypeScript support + +## License + +MIT License - Plexify AI + +## Support + +For issues or questions: ken@plexify.ai diff --git a/plexify-shared-ui/package.json b/plexify-shared-ui/package.json new file mode 100644 index 0000000..8b58024 --- /dev/null +++ b/plexify-shared-ui/package.json @@ -0,0 +1,56 @@ +{ + "name": "plexify-shared-ui", + "version": "1.0.0", + "type": "module", + "description": "Shared UI components for PlexifyAEC, PlexifyBID, and PlexifyBIZ", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "dev": "vite build --watch", + "build": "tsc && vite build", + "type-check": "tsc --noEmit" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "@tiptap/react": "^2.1.0", + "@tiptap/starter-kit": "^2.1.0", + "@tiptap/extension-placeholder": "^2.1.0", + "zustand": "^4.4.0", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.0", + "react-h5-audio-player": "^3.9.0" + }, + "dependencies": { + "jspdf": "^2.5.1", + "html2canvas": "^1.4.1", + "pptxgenjs": "^3.12.0" + }, + "devDependencies": { + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^4.2.0", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vite-plugin-dts": "^3.7.0" + }, + "keywords": [ + "plexify", + "shared-ui", + "react", + "components", + "report-editor", + "workspace" + ], + "author": "Ken D'Amato ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Plexify-AI/plexify-shared-ui.git" + } +} diff --git a/plexify-shared-ui/postcss.config.js b/plexify-shared-ui/postcss.config.js new file mode 100644 index 0000000..3e0d24c --- /dev/null +++ b/plexify-shared-ui/postcss.config.js @@ -0,0 +1,3 @@ +export default { + plugins: {}, +}; diff --git a/plexify-shared-ui/src/assets/brand/plexify-chatburger.svg b/plexify-shared-ui/src/assets/brand/plexify-chatburger.svg new file mode 100644 index 0000000..645cd0d --- /dev/null +++ b/plexify-shared-ui/src/assets/brand/plexify-chatburger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plexify-shared-ui/src/assets/brand/plexify-mark-gray.png b/plexify-shared-ui/src/assets/brand/plexify-mark-gray.png new file mode 100644 index 0000000..7a7cc00 Binary files /dev/null and b/plexify-shared-ui/src/assets/brand/plexify-mark-gray.png differ diff --git a/plexify-shared-ui/src/components/shared/BrandMark.tsx b/plexify-shared-ui/src/components/shared/BrandMark.tsx new file mode 100644 index 0000000..d37d7b2 --- /dev/null +++ b/plexify-shared-ui/src/components/shared/BrandMark.tsx @@ -0,0 +1,45 @@ +import plexifyChatburger from '../../assets/brand/plexify-chatburger.svg'; +import plexifyMarkGray from '../../assets/brand/plexify-mark-gray.png'; + +export type BrandMarkVariant = 'grayP' | 'chatburger'; + +type BrandMarkSize = 'sm' | 'md' | 'lg' | number; + +const sizePxFor = (size: BrandMarkSize): number => { + if (typeof size === 'number') return size; + switch (size) { + case 'sm': + return 24; + case 'lg': + return 40; + case 'md': + default: + return 32; + } +}; + +export default function BrandMark({ + variant = 'grayP', + size = 'md', + alt = 'Plexify', + className, +}: { + variant?: BrandMarkVariant; + size?: BrandMarkSize; + alt?: string; + className?: string; +}) { + const px = sizePxFor(size); + const src = variant === 'chatburger' ? plexifyChatburger : plexifyMarkGray; + + return ( + {alt} + ); +} diff --git a/plexify-shared-ui/src/components/shared/PlexifyLogo.tsx b/plexify-shared-ui/src/components/shared/PlexifyLogo.tsx new file mode 100644 index 0000000..e19661c --- /dev/null +++ b/plexify-shared-ui/src/components/shared/PlexifyLogo.tsx @@ -0,0 +1,18 @@ +export default function PlexifyLogo({ size = 32 }: { size?: number }) { + return ( + + + + + + ); +} diff --git a/plexify-shared-ui/src/components/workspace/AIAssistantPanel.tsx b/plexify-shared-ui/src/components/workspace/AIAssistantPanel.tsx new file mode 100644 index 0000000..645fb0b --- /dev/null +++ b/plexify-shared-ui/src/components/workspace/AIAssistantPanel.tsx @@ -0,0 +1,322 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { PlexifyTheme } from '../../types/theme'; +import { Message, SuggestedAction } from '../../types/workspace'; +import AIMessageBubble from './AIMessageBubble'; +import BrandMark from '../shared/BrandMark'; + +interface AIAssistantPanelProps { + theme: PlexifyTheme; + title?: string; + embedded?: boolean; + messages?: Message[]; + onSendMessage?: (message: string) => Promise; + onRunAgent?: (agentId: string) => Promise; + onSuggestedAction?: (action: SuggestedAction) => void; + placeholder?: string; + isLoading?: boolean; + agentsDisabled?: boolean; + agentsDisabledReason?: string; +} + +function PaperclipIcon({ className }: { className?: string }) { + return ( + + ); +} + +function MicIcon({ className }: { className?: string }) { + return ( + + ); +} + +function SendIcon({ className }: { className?: string }) { + return ( + + ); +} + +export default function AIAssistantPanel({ + theme, + title = 'Plexify AI Assistant', + embedded = false, + messages = [], + onSendMessage, + onRunAgent, + onSuggestedAction, + placeholder = 'Ask me anything about this report...', + isLoading = false, + agentsDisabled = false, + agentsDisabledReason = 'Select documents first', +}: AIAssistantPanelProps) { + const [input, setInput] = useState(''); + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + const message = input.trim(); + setInput(''); + await onSendMessage?.(message); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(e); + } + }; + + const bidAgentChips = [ + { + id: 'board-brief', + label: 'Generate Board Brief', + icon: '📋', + prompt: 'Generate a Board Brief from the selected sources', + }, + { + id: 'assessment-trends', + label: 'Extract Assessment Trends', + icon: '📊', + prompt: 'Extract assessment collection trends from the selected sources', + }, + { + id: 'ozrf-section', + label: 'Draft OZRF Section', + icon: '📝', + prompt: 'Draft an OZRF compliance section from the selected sources', + }, + ]; + + return ( +
+ {/* Header */} +
+ +
+

{title}

+ + {isLoading ? 'Thinking...' : 'Online'} + +
+
+ + {/* Messages */} +
+ {messages.length === 0 ? ( +
+
+ +
+

+ {isLoading + ? 'Generating…' + : 'How can I help you with this report?'} +

+
+ {bidAgentChips.map((chip) => ( + + ))} +
+ {agentsDisabled ? ( +

{agentsDisabledReason}

+ ) : null} +
+ ) : ( + messages.map((message) => ( + + )) + )} + + {isLoading && ( +
+
+ + + +
+
+
+
+
+
+
+
+
+ )} + +
+
+ + {/* Input */} +
+
+
+