@builderos/create-agent-os 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/bin/cli.js +133 -0
- package/package.json +40 -0
- package/src/template/App.tsx +68 -0
- package/src/template/agent-os/commands/create-tasks/1-get-spec-requirements.md +19 -0
- package/src/template/agent-os/commands/create-tasks/2-create-tasks-list.md +234 -0
- package/src/template/agent-os/commands/create-tasks/create-tasks.md +254 -0
- package/src/template/agent-os/commands/design-screen/design-screen.md +32 -0
- package/src/template/agent-os/commands/design-shell/design-shell.md +34 -0
- package/src/template/agent-os/commands/design-tokens/design-tokens.md +36 -0
- package/src/template/agent-os/commands/export-product/export-product.md +44 -0
- package/src/template/agent-os/commands/implement-tasks/1-determine-tasks.md +13 -0
- package/src/template/agent-os/commands/implement-tasks/2-implement-tasks.md +63 -0
- package/src/template/agent-os/commands/implement-tasks/3-verify-implementation.md +113 -0
- package/src/template/agent-os/commands/implement-tasks/implement-tasks.md +207 -0
- package/src/template/agent-os/commands/initialize-design/initialize-design.md +54 -0
- package/src/template/agent-os/commands/orchestrate-tasks/orchestrate-tasks.md +180 -0
- package/src/template/agent-os/commands/plan-product/1-product-concept.md +53 -0
- package/src/template/agent-os/commands/plan-product/2-create-mission.md +78 -0
- package/src/template/agent-os/commands/plan-product/3-create-roadmap.md +73 -0
- package/src/template/agent-os/commands/plan-product/4-create-tech-stack.md +46 -0
- package/src/template/agent-os/commands/plan-product/plan-product.md +241 -0
- package/src/template/agent-os/commands/sample-data/sample-data.md +51 -0
- package/src/template/agent-os/commands/scaffold-implementation/scaffold-implementation.md +36 -0
- package/src/template/agent-os/commands/screenshot-design/screenshot-design.md +21 -0
- package/src/template/agent-os/commands/shape-spec/1-initialize-spec.md +95 -0
- package/src/template/agent-os/commands/shape-spec/2-shape-spec.md +300 -0
- package/src/template/agent-os/commands/shape-spec/shape-spec.md +40 -0
- package/src/template/agent-os/commands/write-spec/write-spec.md +134 -0
- package/src/template/agent-os/config.yml +13 -0
- package/src/template/agent-os/product/mission.md +29 -0
- package/src/template/agent-os/product/roadmap.md +9 -0
- package/src/template/agent-os/product/tech-stack.md +14 -0
- package/src/template/agent-os/specs/README.md +1 -0
- package/src/template/agent-os/standards/backend/api.md +10 -0
- package/src/template/agent-os/standards/backend/migrations.md +9 -0
- package/src/template/agent-os/standards/backend/models.md +10 -0
- package/src/template/agent-os/standards/backend/queries.md +9 -0
- package/src/template/agent-os/standards/frontend/accessibility.md +10 -0
- package/src/template/agent-os/standards/frontend/components.md +11 -0
- package/src/template/agent-os/standards/frontend/css.md +7 -0
- package/src/template/agent-os/standards/frontend/responsive.md +11 -0
- package/src/template/agent-os/standards/global/coding-style.md +10 -0
- package/src/template/agent-os/standards/global/commenting.md +5 -0
- package/src/template/agent-os/standards/global/conventions.md +11 -0
- package/src/template/agent-os/standards/global/error-handling.md +9 -0
- package/src/template/agent-os/standards/global/tech-stack.md +31 -0
- package/src/template/agent-os/standards/global/validation.md +11 -0
- package/src/template/agent-os/standards/testing/test-writing.md +9 -0
- package/src/template/agent-os-ui/README.md +73 -0
- package/src/template/agent-os-ui/package-lock.json +5028 -0
- package/src/template/agent-os-ui/package.json +52 -0
- package/src/template/agent-os-ui/postcss.config.js +6 -0
- package/src/template/agent-os-ui/src/components/AgentShell.tsx +31 -0
- package/src/template/agent-os-ui/src/components/AgentSidebar.tsx +65 -0
- package/src/template/agent-os-ui/src/components/GuidanceCard.tsx +75 -0
- package/src/template/agent-os-ui/src/components/MarkdownViewer.tsx +25 -0
- package/src/template/agent-os-ui/src/components/PromptButton.tsx +28 -0
- package/src/template/agent-os-ui/src/components/StatusItem.tsx +45 -0
- package/src/template/agent-os-ui/src/components/ThemeToggle.tsx +72 -0
- package/src/template/agent-os-ui/src/index.ts +11 -0
- package/src/template/agent-os-ui/src/style.css +3 -0
- package/src/template/agent-os-ui/tailwind.config.js +50 -0
- package/src/template/agent-os-ui/tsconfig.json +33 -0
- package/src/template/agent-os-ui/vite.config.ts +32 -0
- package/src/template/control-center/backend/backend.log +2 -0
- package/src/template/control-center/backend/index.js +228 -0
- package/src/template/control-center/backend/package-lock.json +951 -0
- package/src/template/control-center/backend/package.json +19 -0
- package/src/template/control-center/frontend/README.md +73 -0
- package/src/template/control-center/frontend/eslint.config.js +23 -0
- package/src/template/control-center/frontend/index.html +21 -0
- package/src/template/control-center/frontend/package-lock.json +5752 -0
- package/src/template/control-center/frontend/package.json +42 -0
- package/src/template/control-center/frontend/public/runtime-config.json +11 -0
- package/src/template/control-center/frontend/public/vite.svg +1 -0
- package/src/template/control-center/frontend/src/App.css +42 -0
- package/src/template/control-center/frontend/src/App.tsx +738 -0
- package/src/template/control-center/frontend/src/assets/react.svg +1 -0
- package/src/template/control-center/frontend/src/components/ThemeToggle.tsx +64 -0
- package/src/template/control-center/frontend/src/components/ui/ToastContext.tsx +81 -0
- package/src/template/control-center/frontend/src/index.css +194 -0
- package/src/template/control-center/frontend/src/main.tsx +14 -0
- package/src/template/control-center/frontend/src/vite-env.d.ts +1 -0
- package/src/template/control-center/frontend/tsconfig.app.json +28 -0
- package/src/template/control-center/frontend/tsconfig.json +7 -0
- package/src/template/control-center/frontend/tsconfig.node.json +26 -0
- package/src/template/control-center/frontend/vite.config.ts +22 -0
- package/src/template/design/.claude/commands/design-os/data-model.md +122 -0
- package/src/template/design/.claude/commands/design-os/design-screen.md +309 -0
- package/src/template/design/.claude/commands/design-os/design-shell.md +238 -0
- package/src/template/design/.claude/commands/design-os/design-tokens.md +166 -0
- package/src/template/design/.claude/commands/design-os/export-product.md +1105 -0
- package/src/template/design/.claude/commands/design-os/product-roadmap.md +121 -0
- package/src/template/design/.claude/commands/design-os/product-vision.md +99 -0
- package/src/template/design/.claude/commands/design-os/sample-data.md +263 -0
- package/src/template/design/.claude/commands/design-os/screenshot-design.md +112 -0
- package/src/template/design/.claude/commands/design-os/shape-section.md +138 -0
- package/src/template/design/.claude/skills/frontend-design/SKILL.md +42 -0
- package/src/template/design/.github/CODE_OF_CONDUCT.md +5 -0
- package/src/template/design/.github/CONTRIBUTING.md +51 -0
- package/src/template/design/.github/ISSUE_TEMPLATE/config.yml +22 -0
- package/src/template/design/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- package/src/template/design/.github/SECURITY.yml +5 -0
- package/src/template/design/.github/SUPPORT.md +19 -0
- package/src/template/design/.github/workflows/pr-decline.yml +135 -0
- package/src/template/design/.github/workflows/stale.yml +25 -0
- package/src/template/design/CHANGELOG.md +13 -0
- package/src/template/design/LICENSE +21 -0
- package/src/template/design/README.md +54 -0
- package/src/template/design/agents.md +218 -0
- package/src/template/design/claude.md +1 -0
- package/src/template/design/components.json +22 -0
- package/src/template/design/docs/codebase-implementation.md +153 -0
- package/src/template/design/docs/design-section.md +135 -0
- package/src/template/design/docs/export.md +149 -0
- package/src/template/design/docs/getting-started.md +59 -0
- package/src/template/design/docs/index.md +56 -0
- package/src/template/design/docs/product-planning.md +113 -0
- package/src/template/design/docs/requirements.md +22 -0
- package/src/template/design/docs/usage.md +62 -0
- package/src/template/design/eslint.config.js +23 -0
- package/src/template/design/index.html +21 -0
- package/src/template/design/package-lock.json +5473 -0
- package/src/template/design/package.json +47 -0
- package/src/template/design/product-plan.zip +0 -0
- package/src/template/design/public/vite.svg +1 -0
- package/src/template/design/src/assets/react.svg +1 -0
- package/src/template/design/src/components/AppLayout.tsx +95 -0
- package/src/template/design/src/components/DataCard.tsx +139 -0
- package/src/template/design/src/components/DataModelPage.tsx +120 -0
- package/src/template/design/src/components/DesignPage.tsx +284 -0
- package/src/template/design/src/components/EmptyState.tsx +155 -0
- package/src/template/design/src/components/ExportPage.tsx +344 -0
- package/src/template/design/src/components/NextPhaseButton.tsx +33 -0
- package/src/template/design/src/components/PhaseNav.tsx +152 -0
- package/src/template/design/src/components/PhaseWarningBanner.tsx +81 -0
- package/src/template/design/src/components/ProductOverviewCard.tsx +102 -0
- package/src/template/design/src/components/ProductPage.tsx +97 -0
- package/src/template/design/src/components/ScreenDesignPage.tsx +370 -0
- package/src/template/design/src/components/ScreenDesignsCard.tsx +49 -0
- package/src/template/design/src/components/SectionPage.tsx +256 -0
- package/src/template/design/src/components/SectionsCard.tsx +47 -0
- package/src/template/design/src/components/SectionsPage.tsx +181 -0
- package/src/template/design/src/components/ShellCard.tsx +85 -0
- package/src/template/design/src/components/ShellDesignPage.tsx +242 -0
- package/src/template/design/src/components/SpecCard.tsx +121 -0
- package/src/template/design/src/components/StepIndicator.tsx +75 -0
- package/src/template/design/src/components/ThemeToggle.tsx +86 -0
- package/src/template/design/src/components/ui/ToastContext.tsx +81 -0
- package/src/template/design/src/components/ui/avatar.tsx +53 -0
- package/src/template/design/src/components/ui/badge.tsx +46 -0
- package/src/template/design/src/components/ui/button.tsx +60 -0
- package/src/template/design/src/components/ui/card.tsx +92 -0
- package/src/template/design/src/components/ui/collapsible.tsx +48 -0
- package/src/template/design/src/components/ui/dialog.tsx +143 -0
- package/src/template/design/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/template/design/src/components/ui/input.tsx +21 -0
- package/src/template/design/src/components/ui/label.tsx +22 -0
- package/src/template/design/src/components/ui/progress.tsx +24 -0
- package/src/template/design/src/components/ui/scroll-area.tsx +18 -0
- package/src/template/design/src/components/ui/select.tsx +67 -0
- package/src/template/design/src/components/ui/separator.tsx +28 -0
- package/src/template/design/src/components/ui/sheet.tsx +137 -0
- package/src/template/design/src/components/ui/skeleton.tsx +13 -0
- package/src/template/design/src/components/ui/switch.tsx +46 -0
- package/src/template/design/src/components/ui/table.tsx +116 -0
- package/src/template/design/src/components/ui/tabs.tsx +64 -0
- package/src/template/design/src/index.css +284 -0
- package/src/template/design/src/lib/data-model-loader.ts +91 -0
- package/src/template/design/src/lib/design-system-loader.ts +101 -0
- package/src/template/design/src/lib/product-loader.ts +221 -0
- package/src/template/design/src/lib/router.tsx +52 -0
- package/src/template/design/src/lib/section-loader.ts +272 -0
- package/src/template/design/src/lib/shell-loader.ts +175 -0
- package/src/template/design/src/lib/utils.ts +6 -0
- package/src/template/design/src/main.tsx +15 -0
- package/src/template/design/src/sections/.gitkeep +0 -0
- package/src/template/design/src/sections/ai-orchestration-engine-oai/OrchestrationEngine.tsx +348 -0
- package/src/template/design/src/sections/core-platform-shell/AppShell.tsx +403 -0
- package/src/template/design/src/sections/gemini-live-integration/GeminiIntegration.tsx +332 -0
- package/src/template/design/src/sections/interactive-2d-canvas/WhiteboardCanvas.tsx +334 -0
- package/src/template/design/src/sections/participation-equity-tracker/EquityTracker.tsx +383 -0
- package/src/template/design/src/sections/persistent-memory-system/PersistentMemory.tsx +308 -0
- package/src/template/design/src/sections/real-time-communication-layer/VideoSession.tsx +342 -0
- package/src/template/design/src/sections/visual-intelligence-agents/VisualAgents.tsx +311 -0
- package/src/template/design/src/types/product.ts +97 -0
- package/src/template/design/src/types/section.ts +33 -0
- package/src/template/design/tsconfig.app.json +34 -0
- package/src/template/design/tsconfig.json +13 -0
- package/src/template/design/tsconfig.node.json +26 -0
- package/src/template/design/vite.config.ts +18 -0
- package/src/template/package.json +27 -0
- package/src/template/vite.config.ts +16 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Mic,
|
|
4
|
+
MicOff,
|
|
5
|
+
Settings,
|
|
6
|
+
Activity,
|
|
7
|
+
Wifi,
|
|
8
|
+
Volume2,
|
|
9
|
+
VolumeX,
|
|
10
|
+
User,
|
|
11
|
+
Bot,
|
|
12
|
+
MoreHorizontal,
|
|
13
|
+
Sparkles,
|
|
14
|
+
Zap,
|
|
15
|
+
Radio
|
|
16
|
+
} from 'lucide-react';
|
|
17
|
+
import { Button } from '@/components/ui/button';
|
|
18
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from '@/components/ui/card';
|
|
19
|
+
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
20
|
+
import { Badge } from '@/components/ui/badge';
|
|
21
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
22
|
+
import { Switch } from '@/components/ui/switch';
|
|
23
|
+
import { Separator } from '@/components/ui/separator';
|
|
24
|
+
import { cn } from '@/lib/utils';
|
|
25
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
26
|
+
|
|
27
|
+
// Mock Data Types
|
|
28
|
+
interface Persona {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
voiceId: string;
|
|
33
|
+
active: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface TranscriptItem {
|
|
37
|
+
id: string;
|
|
38
|
+
sender: 'user' | 'ai';
|
|
39
|
+
name: string;
|
|
40
|
+
text: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
interventionType?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const MOCK_DATA = {
|
|
46
|
+
"connection": {
|
|
47
|
+
"status": "listening",
|
|
48
|
+
"latencyMs": 145,
|
|
49
|
+
"uptime": "00:12:45",
|
|
50
|
+
"region": "us-central1"
|
|
51
|
+
},
|
|
52
|
+
"personas": [
|
|
53
|
+
{
|
|
54
|
+
"id": "p_coach",
|
|
55
|
+
"name": "Empathetic Coach",
|
|
56
|
+
"description": "Warm, encouraging, focuses on psychological safety.",
|
|
57
|
+
"voiceId": "en-US-Standard-C",
|
|
58
|
+
"active": true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "p_keeper",
|
|
62
|
+
"name": "Strict Timekeeper",
|
|
63
|
+
"description": "Concise, focused on agenda and pacing.",
|
|
64
|
+
"voiceId": "en-US-Standard-D",
|
|
65
|
+
"active": false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "p_devil",
|
|
69
|
+
"name": "Devil's Advocate",
|
|
70
|
+
"description": "Challenger, encourages critical thinking.",
|
|
71
|
+
"voiceId": "en-US-Standard-E",
|
|
72
|
+
"active": false
|
|
73
|
+
}
|
|
74
|
+
] as Persona[],
|
|
75
|
+
"transcript": [
|
|
76
|
+
{
|
|
77
|
+
"id": "msg_1",
|
|
78
|
+
"sender": "user",
|
|
79
|
+
"name": "David Kim",
|
|
80
|
+
"text": "I'm just not sure we have the budget for Q3 marketing if we do the re-brand.",
|
|
81
|
+
"timestamp": "2023-11-20T14:20:10Z"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "msg_2",
|
|
85
|
+
"sender": "user",
|
|
86
|
+
"name": "Maria Garcia",
|
|
87
|
+
"text": "But we can't delay the re-brand again. Clients are confused.",
|
|
88
|
+
"timestamp": "2023-11-20T14:20:15Z"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "msg_3",
|
|
92
|
+
"sender": "ai",
|
|
93
|
+
"name": "Agent OS",
|
|
94
|
+
"text": "I'm sensing a trade-off here between budget constraints and brand clarity. Would it help to list the risks of delaying the re-brand vs. the risks of a slim marketing budget?",
|
|
95
|
+
"timestamp": "2023-11-20T14:20:25Z",
|
|
96
|
+
"interventionType": "reframing"
|
|
97
|
+
}
|
|
98
|
+
] as TranscriptItem[],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const AudioVisualizer = ({ isActive }: { isActive: boolean }) => {
|
|
102
|
+
return (
|
|
103
|
+
<div className="flex items-center justify-center gap-1 h-32 w-full">
|
|
104
|
+
{[...Array(24)].map((_, i) => (
|
|
105
|
+
<div
|
|
106
|
+
key={i}
|
|
107
|
+
className={cn(
|
|
108
|
+
"w-2 rounded-full transition-all duration-75",
|
|
109
|
+
isActive ? "bg-indigo-500" : "bg-stone-300 dark:bg-stone-800"
|
|
110
|
+
)}
|
|
111
|
+
style={{
|
|
112
|
+
height: isActive ? `${Math.max(20, Math.random() * 100)}%` : '10%',
|
|
113
|
+
opacity: isActive ? 1 : 0.5,
|
|
114
|
+
animation: isActive ? `pulse 0.5s infinite ${i * 0.05}s` : 'none'
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default function GeminiIntegration() {
|
|
123
|
+
const [isMicOn, setIsMicOn] = useState(true);
|
|
124
|
+
const [activePersona, setActivePersona] = useState("p_coach");
|
|
125
|
+
const [transcript, setTranscript] = useState(MOCK_DATA.transcript);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex h-screen w-full bg-stone-50 dark:bg-stone-950 font-sans overflow-hidden">
|
|
129
|
+
|
|
130
|
+
{/* Sidebar / Configuration */}
|
|
131
|
+
<aside className="w-80 bg-white dark:bg-stone-900 border-r border-stone-200 dark:border-stone-800 flex flex-col z-20 shadow-md">
|
|
132
|
+
<div className="p-6 border-b border-stone-100 dark:border-stone-800">
|
|
133
|
+
<div className="flex items-center gap-2 mb-1">
|
|
134
|
+
<div className="p-2 bg-indigo-600 text-white rounded-lg">
|
|
135
|
+
<Sparkles size={20} className="fill-current" />
|
|
136
|
+
</div>
|
|
137
|
+
<div>
|
|
138
|
+
<h1 className="font-bold text-stone-900 dark:text-stone-100 leading-tight">Gemini Live</h1>
|
|
139
|
+
<p className="text-xs text-stone-500">Multimodal Facilitator</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div className="p-6 space-y-8 flex-1">
|
|
145
|
+
{/* Connection Status */}
|
|
146
|
+
<div className="space-y-3">
|
|
147
|
+
<label className="text-xs font-semibold uppercase tracking-wider text-stone-500">Connection Status</label>
|
|
148
|
+
<div className="flex items-center justify-between p-3 bg-stone-50 dark:bg-stone-800/50 rounded-lg border border-stone-100 dark:border-stone-800">
|
|
149
|
+
<div className="flex items-center gap-3">
|
|
150
|
+
<span className="relative flex h-3 w-3">
|
|
151
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
152
|
+
<span className="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
|
|
153
|
+
</span>
|
|
154
|
+
<span className="text-sm font-medium text-stone-700 dark:text-stone-200 uppercase">Live</span>
|
|
155
|
+
</div>
|
|
156
|
+
<Badge variant="outline" className="font-mono text-[10px] text-stone-400">
|
|
157
|
+
{MOCK_DATA.connection.latencyMs}ms
|
|
158
|
+
</Badge>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Persona Selector */}
|
|
163
|
+
<div className="space-y-3">
|
|
164
|
+
<label className="text-xs font-semibold uppercase tracking-wider text-stone-500">Facilitator Persona</label>
|
|
165
|
+
<Select value={activePersona} onValueChange={setActivePersona}>
|
|
166
|
+
<SelectTrigger className="w-full bg-white dark:bg-stone-900 border-stone-200 dark:border-stone-700">
|
|
167
|
+
<SelectValue />
|
|
168
|
+
</SelectTrigger>
|
|
169
|
+
<SelectContent>
|
|
170
|
+
{MOCK_DATA.personas.map(p => (
|
|
171
|
+
<SelectItem key={p.id} value={p.id}>
|
|
172
|
+
<span className="font-medium">{p.name}</span>
|
|
173
|
+
</SelectItem>
|
|
174
|
+
))}
|
|
175
|
+
</SelectContent>
|
|
176
|
+
</Select>
|
|
177
|
+
<p className="text-xs text-stone-500 leading-snug">
|
|
178
|
+
{MOCK_DATA.personas.find(p => p.id === activePersona)?.description}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Controls */}
|
|
183
|
+
<div className="space-y-3">
|
|
184
|
+
<label className="text-xs font-semibold uppercase tracking-wider text-stone-500">Audio Controls</label>
|
|
185
|
+
<div className="flex items-center justify-between">
|
|
186
|
+
<span className="text-sm text-stone-700 dark:text-stone-300">Input (Mic)</span>
|
|
187
|
+
<Switch checked={isMicOn} onCheckedChange={setIsMicOn} />
|
|
188
|
+
</div>
|
|
189
|
+
<div className="flex items-center justify-between">
|
|
190
|
+
<span className="text-sm text-stone-700 dark:text-stone-300">Output (Speaker)</span>
|
|
191
|
+
<Switch defaultChecked />
|
|
192
|
+
</div>
|
|
193
|
+
<div className="pt-4">
|
|
194
|
+
<div className="h-1 bg-stone-100 dark:bg-stone-800 rounded-full overflow-hidden">
|
|
195
|
+
<div className="h-full bg-indigo-500 w-[45%]" />
|
|
196
|
+
</div>
|
|
197
|
+
<div className="flex justify-between text-[10px] text-stone-400 mt-1">
|
|
198
|
+
<span>Input Gain</span>
|
|
199
|
+
<span>45%</span>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="p-4 border-t border-stone-200 dark:border-stone-800 text-xs text-stone-400 flex items-center justify-center gap-2">
|
|
206
|
+
<Zap size={12} /> Powered by Gemini 1.5 Pro
|
|
207
|
+
</div>
|
|
208
|
+
</aside>
|
|
209
|
+
|
|
210
|
+
{/* Main Content */}
|
|
211
|
+
<main className="flex-1 flex flex-col relative overflow-hidden bg-stone-50 dark:bg-stone-950">
|
|
212
|
+
|
|
213
|
+
{/* Header */}
|
|
214
|
+
<header className="h-16 px-8 flex items-center justify-between z-10">
|
|
215
|
+
<div className="flex items-center gap-4">
|
|
216
|
+
<Badge variant="outline" className="bg-white dark:bg-stone-900 text-stone-500 border-stone-200 dark:border-stone-800 gap-1.5 shadow-sm">
|
|
217
|
+
<Radio size={12} className="text-stone-400" />
|
|
218
|
+
Full Duplex Audio
|
|
219
|
+
</Badge>
|
|
220
|
+
<div className="h-4 w-px bg-stone-300 dark:bg-stone-700" />
|
|
221
|
+
<span className="text-sm text-stone-500">Session ID: <span className="font-mono text-stone-700 dark:text-stone-300">#GEM-8821</span></span>
|
|
222
|
+
</div>
|
|
223
|
+
<Button variant="ghost" size="icon" className="text-stone-400 hover:text-stone-700">
|
|
224
|
+
<Settings size={20} />
|
|
225
|
+
</Button>
|
|
226
|
+
</header>
|
|
227
|
+
|
|
228
|
+
{/* Visualization Area */}
|
|
229
|
+
<div className="flex-1 flex flex-col items-center justify-center relative min-h-[300px]">
|
|
230
|
+
{/* Background Effects */}
|
|
231
|
+
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-white dark:to-stone-950 opacity-50 z-0" />
|
|
232
|
+
<div className="absolute w-[600px] h-[600px] bg-indigo-500/5 blur-[120px] rounded-full pointer-events-none" />
|
|
233
|
+
|
|
234
|
+
{/* Visualizer */}
|
|
235
|
+
<div className="relative z-10 w-full max-w-2xl px-8">
|
|
236
|
+
<div className="text-center mb-8">
|
|
237
|
+
<h2 className={cn(
|
|
238
|
+
"text-2xl font-semibold transition-all duration-300",
|
|
239
|
+
isMicOn ? "text-stone-800 dark:text-stone-100" : "text-stone-400"
|
|
240
|
+
)}>
|
|
241
|
+
{isMicOn ? "Adjusting to conversation flow..." : "Microphone Muted"}
|
|
242
|
+
</h2>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<Card className="border-0 shadow-2xl bg-white/80 dark:bg-stone-900/80 backdrop-blur-xl ring-1 ring-black/5 dark:ring-white/10">
|
|
246
|
+
<CardContent className="p-8">
|
|
247
|
+
<AudioVisualizer isActive={isMicOn} />
|
|
248
|
+
</CardContent>
|
|
249
|
+
</Card>
|
|
250
|
+
|
|
251
|
+
<div className="flex justify-center mt-8 gap-4">
|
|
252
|
+
<Button
|
|
253
|
+
size="lg"
|
|
254
|
+
variant={isMicOn ? "default" : "destructive"}
|
|
255
|
+
className={cn(
|
|
256
|
+
"rounded-full w-16 h-16 shadow-lg transition-all duration-200 hover:scale-105",
|
|
257
|
+
isMicOn ? "bg-indigo-600 hover:bg-indigo-700" : ""
|
|
258
|
+
)}
|
|
259
|
+
onClick={() => setIsMicOn(!isMicOn)}
|
|
260
|
+
>
|
|
261
|
+
{isMicOn ? <Mic size={28} /> : <MicOff size={28} />}
|
|
262
|
+
</Button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Transcript Panel */}
|
|
268
|
+
<div className="h-1/3 bg-white dark:bg-stone-900 border-t border-stone-200 dark:border-stone-800 flex flex-col shadow-[0_-10px_40px_rgba(0,0,0,0.05)] z-20">
|
|
269
|
+
<div className="px-6 py-3 border-b border-stone-100 dark:border-stone-800 flex items-center justify-between bg-stone-50/50 dark:bg-stone-900/50 backdrop-blur">
|
|
270
|
+
<h3 className="text-xs font-semibold uppercase tracking-wider text-stone-500 flex items-center gap-2">
|
|
271
|
+
<Activity size={14} /> Live Transcript
|
|
272
|
+
</h3>
|
|
273
|
+
<Badge variant="secondary" className="text-[10px]">Real-time</Badge>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<ScrollArea className="flex-1 p-6">
|
|
277
|
+
<div className="space-y-6 max-w-3xl mx-auto">
|
|
278
|
+
{transcript.map((item) => (
|
|
279
|
+
<div key={item.id} className={cn("flex gap-4", item.sender === 'ai' ? "flex-row-reverse" : "")}>
|
|
280
|
+
<Avatar className="w-8 h-8 mt-1 border border-stone-100 dark:border-stone-700 shadow-sm">
|
|
281
|
+
{item.sender === 'ai' ? (
|
|
282
|
+
<AvatarImage src="" className="bg-gradient-to-br from-indigo-500 to-purple-500 p-1.5" />
|
|
283
|
+
) : (
|
|
284
|
+
<AvatarImage src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`} />
|
|
285
|
+
)}
|
|
286
|
+
<AvatarFallback className={item.sender === 'ai' ? "bg-indigo-600 text-white" : "bg-stone-200 text-stone-600"}>
|
|
287
|
+
{item.sender === 'ai' ? <Sparkles size={14} /> : item.name[0]}
|
|
288
|
+
</AvatarFallback>
|
|
289
|
+
</Avatar>
|
|
290
|
+
|
|
291
|
+
<div className={cn("flex flex-col max-w-[80%]", item.sender === 'ai' ? "items-end" : "items-start")}>
|
|
292
|
+
<div className="flex items-center gap-2 mb-1">
|
|
293
|
+
<span className="text-xs font-semibold text-stone-900 dark:text-stone-100">{item.name}</span>
|
|
294
|
+
<span className="text-[10px] text-stone-400 font-mono">
|
|
295
|
+
{new Date(item.timestamp).toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
<div className={cn(
|
|
299
|
+
"p-3 rounded-2xl text-sm leading-relaxed shadow-sm",
|
|
300
|
+
item.sender === 'ai'
|
|
301
|
+
? "bg-indigo-600 text-white rounded-tr-none"
|
|
302
|
+
: "bg-white dark:bg-stone-800 border border-stone-100 dark:border-stone-700 text-stone-700 dark:text-stone-300 rounded-tl-none"
|
|
303
|
+
)}>
|
|
304
|
+
{item.text}
|
|
305
|
+
</div>
|
|
306
|
+
{item.interventionType && (
|
|
307
|
+
<Badge variant="outline" className="mt-1 text-[10px] border-indigo-200 text-indigo-500 bg-indigo-50 dark:bg-indigo-900/20 dark:border-indigo-800">
|
|
308
|
+
{item.interventionType}
|
|
309
|
+
</Badge>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
))}
|
|
314
|
+
|
|
315
|
+
{/* Typing Indicator */}
|
|
316
|
+
<div className="flex gap-4 opacity-50">
|
|
317
|
+
<Avatar className="w-8 h-8 mt-1 border border-stone-100 shadow-sm opacity-0">
|
|
318
|
+
<AvatarFallback>DK</AvatarFallback>
|
|
319
|
+
</Avatar>
|
|
320
|
+
<div className="flex items-center gap-1 h-8 px-2">
|
|
321
|
+
<div className="w-1.5 h-1.5 bg-stone-300 rounded-full animate-bounce" style={{ animationDelay: '0s' }} />
|
|
322
|
+
<div className="w-1.5 h-1.5 bg-stone-300 rounded-full animate-bounce" style={{ animationDelay: '0.15s' }} />
|
|
323
|
+
<div className="w-1.5 h-1.5 bg-stone-300 rounded-full animate-bounce" style={{ animationDelay: '0.3s' }} />
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</ScrollArea>
|
|
328
|
+
</div>
|
|
329
|
+
</main>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
MousePointer2,
|
|
4
|
+
Hand,
|
|
5
|
+
StickyNote,
|
|
6
|
+
Square,
|
|
7
|
+
Circle,
|
|
8
|
+
Type,
|
|
9
|
+
Image as ImageIcon,
|
|
10
|
+
Eraser,
|
|
11
|
+
Undo2,
|
|
12
|
+
Redo2,
|
|
13
|
+
Minus,
|
|
14
|
+
Plus,
|
|
15
|
+
MoreHorizontal,
|
|
16
|
+
Share2,
|
|
17
|
+
Download,
|
|
18
|
+
Settings,
|
|
19
|
+
Grid
|
|
20
|
+
} from 'lucide-react';
|
|
21
|
+
import { Button } from '@/components/ui/button';
|
|
22
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
23
|
+
import { cn } from '@/lib/utils';
|
|
24
|
+
|
|
25
|
+
// Mock Data Types
|
|
26
|
+
interface CanvasObject {
|
|
27
|
+
id: string;
|
|
28
|
+
type: 'sticky' | 'shape' | 'text' | 'connector';
|
|
29
|
+
content?: string;
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
color?: string;
|
|
35
|
+
fill?: string;
|
|
36
|
+
stroke?: string;
|
|
37
|
+
rotation?: number;
|
|
38
|
+
shapeType?: 'circle' | 'rectangle';
|
|
39
|
+
from?: string;
|
|
40
|
+
to?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Cursor {
|
|
44
|
+
userId: string;
|
|
45
|
+
userName: string;
|
|
46
|
+
color: string;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const MOCK_DATA = {
|
|
52
|
+
"objects": [
|
|
53
|
+
{
|
|
54
|
+
"id": "obj_1",
|
|
55
|
+
"type": "sticky",
|
|
56
|
+
"content": "Define Core User Personas",
|
|
57
|
+
"x": 100,
|
|
58
|
+
"y": 100,
|
|
59
|
+
"width": 200,
|
|
60
|
+
"height": 200,
|
|
61
|
+
"color": "#fff740",
|
|
62
|
+
"rotation": -2
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "obj_2",
|
|
66
|
+
"type": "sticky",
|
|
67
|
+
"content": "Map out the user journey",
|
|
68
|
+
"x": 350,
|
|
69
|
+
"y": 100,
|
|
70
|
+
"width": 200,
|
|
71
|
+
"height": 200,
|
|
72
|
+
"color": "#ff7eb9",
|
|
73
|
+
"rotation": 2
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "obj_3",
|
|
77
|
+
"type": "shape",
|
|
78
|
+
"shapeType": "circle",
|
|
79
|
+
"x": 650,
|
|
80
|
+
"y": 150,
|
|
81
|
+
"width": 100,
|
|
82
|
+
"height": 100,
|
|
83
|
+
"fill": "#a7f3d0",
|
|
84
|
+
"stroke": "#059669"
|
|
85
|
+
},
|
|
86
|
+
// Connector simulated as line for DOM implementation
|
|
87
|
+
{
|
|
88
|
+
"id": "obj_4",
|
|
89
|
+
"type": "connector",
|
|
90
|
+
"from": "obj_1",
|
|
91
|
+
"to": "obj_2",
|
|
92
|
+
"stroke": "#94a3b8",
|
|
93
|
+
"x": 300,
|
|
94
|
+
"y": 200,
|
|
95
|
+
"width": 50,
|
|
96
|
+
"rotation": 0
|
|
97
|
+
}
|
|
98
|
+
] as CanvasObject[],
|
|
99
|
+
"cursors": [
|
|
100
|
+
{
|
|
101
|
+
"userId": "p_2",
|
|
102
|
+
"userName": "David Kim",
|
|
103
|
+
"color": "#3b82f6",
|
|
104
|
+
"x": 450,
|
|
105
|
+
"y": 250
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"userId": "p_3",
|
|
109
|
+
"userName": "Maria Garcia",
|
|
110
|
+
"color": "#ec4899",
|
|
111
|
+
"x": 180,
|
|
112
|
+
"y": 180
|
|
113
|
+
}
|
|
114
|
+
] as Cursor[],
|
|
115
|
+
"viewport": {
|
|
116
|
+
"zoom": 1,
|
|
117
|
+
"x": 0,
|
|
118
|
+
"y": 0,
|
|
119
|
+
"gridVisible": true
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Toolbar Component
|
|
124
|
+
const Toolbar = ({ activeTool, setActiveTool }: { activeTool: string, setActiveTool: (t: string) => void }) => {
|
|
125
|
+
const tools = [
|
|
126
|
+
{ id: 'select', icon: MousePointer2, label: 'Select' },
|
|
127
|
+
{ id: 'hand', icon: Hand, label: 'Pan' },
|
|
128
|
+
{ id: 'sticky', icon: StickyNote, label: 'Sticky Note' },
|
|
129
|
+
{ id: 'shape', icon: Square, label: 'Shape' },
|
|
130
|
+
{ id: 'text', icon: Type, label: 'Text' },
|
|
131
|
+
{ id: 'connector', icon: Minus, label: 'Connector', className: 'rotate-45' },
|
|
132
|
+
{ id: 'image', icon: ImageIcon, label: 'Image' },
|
|
133
|
+
{ id: 'eraser', icon: Eraser, label: 'Eraser' },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className="bg-white dark:bg-stone-900 shadow-lg border border-stone-200 dark:border-stone-800 rounded-lg p-1.5 flex flex-col gap-1">
|
|
138
|
+
{tools.map(tool => (
|
|
139
|
+
<button
|
|
140
|
+
key={tool.id}
|
|
141
|
+
onClick={() => setActiveTool(tool.id)}
|
|
142
|
+
className={cn(
|
|
143
|
+
"p-2 rounded-md transition-colors relative group",
|
|
144
|
+
activeTool === tool.id
|
|
145
|
+
? "bg-indigo-50 text-indigo-600 dark:bg-indigo-900/30 dark:text-indigo-400"
|
|
146
|
+
: "text-stone-500 hover:bg-stone-100 dark:hover:bg-stone-800 dark:text-stone-400"
|
|
147
|
+
)}
|
|
148
|
+
title={tool.label}
|
|
149
|
+
>
|
|
150
|
+
<tool.icon size={20} className={tool.className} />
|
|
151
|
+
</button>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Canvas Object Renderers
|
|
158
|
+
const StickyNoteObject = ({ object }: { object: CanvasObject }) => (
|
|
159
|
+
<div
|
|
160
|
+
className="absolute shadow-md p-4 flex items-center justify-center text-center font-handwriting text-stone-800 select-none cursor-move transition-transform hover:scale-[1.02] active:scale-95"
|
|
161
|
+
style={{
|
|
162
|
+
left: object.x,
|
|
163
|
+
top: object.y,
|
|
164
|
+
width: object.width,
|
|
165
|
+
height: object.height,
|
|
166
|
+
backgroundColor: object.color,
|
|
167
|
+
transform: `rotate(${object.rotation}deg)`,
|
|
168
|
+
fontFamily: '"Nevermind", "Comic Sans MS", cursive', // Simulated handwriting font
|
|
169
|
+
fontSize: '24px',
|
|
170
|
+
lineHeight: '1.4'
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
{object.content}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const ShapeObject = ({ object }: { object: CanvasObject }) => (
|
|
178
|
+
<div
|
|
179
|
+
className="absolute flex items-center justify-center select-none cursor-move"
|
|
180
|
+
style={{
|
|
181
|
+
left: object.x,
|
|
182
|
+
top: object.y,
|
|
183
|
+
width: object.width,
|
|
184
|
+
height: object.height,
|
|
185
|
+
backgroundColor: object.fill,
|
|
186
|
+
border: `2px solid ${object.stroke}`,
|
|
187
|
+
borderRadius: object.shapeType === 'circle' ? '50%' : '4px',
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const ConnectorObject = ({ object }: { object: CanvasObject }) => (
|
|
193
|
+
<svg
|
|
194
|
+
className="absolute pointer-events-none"
|
|
195
|
+
style={{
|
|
196
|
+
left: 0,
|
|
197
|
+
top: 0,
|
|
198
|
+
width: '100%',
|
|
199
|
+
height: '100%',
|
|
200
|
+
zIndex: -1
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{/* Hardcoded path for visual check based on mock objects positions */}
|
|
204
|
+
<path
|
|
205
|
+
d="M 300 200 C 325 200, 325 200, 350 200"
|
|
206
|
+
stroke={object.stroke}
|
|
207
|
+
strokeWidth="2"
|
|
208
|
+
fill="none"
|
|
209
|
+
markerEnd="url(#arrowhead)"
|
|
210
|
+
/>
|
|
211
|
+
<defs>
|
|
212
|
+
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
213
|
+
<polygon points="0 0, 10 3.5, 0 7" fill={object.stroke} />
|
|
214
|
+
</marker>
|
|
215
|
+
</defs>
|
|
216
|
+
</svg>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
// Cursor Renderer
|
|
221
|
+
const CursorOverlay = ({ cursor }: { cursor: Cursor }) => (
|
|
222
|
+
<div
|
|
223
|
+
className="absolute pointer-events-none transition-all duration-100 ease-linear z-50"
|
|
224
|
+
style={{ left: cursor.x, top: cursor.y }}
|
|
225
|
+
>
|
|
226
|
+
<MousePointer2
|
|
227
|
+
className="w-4 h-4"
|
|
228
|
+
style={{ fill: cursor.color, color: cursor.color }}
|
|
229
|
+
/>
|
|
230
|
+
<div
|
|
231
|
+
className="ml-4 px-2 py-0.5 rounded text-xs text-white font-medium whitespace-nowrap"
|
|
232
|
+
style={{ backgroundColor: cursor.color }}
|
|
233
|
+
>
|
|
234
|
+
{cursor.userName}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
export default function WhiteboardCanvas() {
|
|
240
|
+
const [activeTool, setActiveTool] = useState('select');
|
|
241
|
+
const [zoom, setZoom] = useState(100);
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<div className="h-screen w-full bg-stone-100 dark:bg-stone-950 relative overflow-hidden font-sans">
|
|
245
|
+
|
|
246
|
+
{/* Dot Grid Background */}
|
|
247
|
+
<div
|
|
248
|
+
className="absolute inset-0 opacity-[0.15] pointer-events-none"
|
|
249
|
+
style={{
|
|
250
|
+
backgroundImage: 'radial-gradient(#6b7280 1px, transparent 1px)',
|
|
251
|
+
backgroundSize: '24px 24px'
|
|
252
|
+
}}
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
{/* Floating Header */}
|
|
256
|
+
<div className="absolute top-4 left-4 right-4 flex items-center justify-between z-10 pointer-events-none">
|
|
257
|
+
<div className="pointer-events-auto bg-white dark:bg-stone-900 shadow-sm border border-stone-200 dark:border-stone-800 rounded-lg px-4 py-2 flex items-center gap-3">
|
|
258
|
+
<h1 className="font-semibold text-stone-800 dark:text-stone-200">Product Strategy Brainstorm</h1>
|
|
259
|
+
<span className="text-stone-300">|</span>
|
|
260
|
+
<div className="flex -space-x-2">
|
|
261
|
+
<Avatar className="w-6 h-6 border-2 border-white dark:border-stone-900">
|
|
262
|
+
<AvatarImage src="https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah" />
|
|
263
|
+
<AvatarFallback>SC</AvatarFallback>
|
|
264
|
+
</Avatar>
|
|
265
|
+
<Avatar className="w-6 h-6 border-2 border-white dark:border-stone-900">
|
|
266
|
+
<AvatarImage src="https://api.dicebear.com/7.x/avataaars/svg?seed=David" />
|
|
267
|
+
<AvatarFallback>DK</AvatarFallback>
|
|
268
|
+
</Avatar>
|
|
269
|
+
<Avatar className="w-6 h-6 border-2 border-white dark:border-stone-900">
|
|
270
|
+
<AvatarImage src="https://api.dicebear.com/7.x/avataaars/svg?seed=Maria" />
|
|
271
|
+
<AvatarFallback>MG</AvatarFallback>
|
|
272
|
+
</Avatar>
|
|
273
|
+
<div className="w-6 h-6 border-2 border-white dark:border-stone-900 bg-stone-100 dark:bg-stone-800 rounded-full flex items-center justify-center text-[10px] text-stone-500 font-medium">
|
|
274
|
+
+2
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div className="pointer-events-auto flex items-center gap-2">
|
|
280
|
+
<Button variant="outline" size="sm" className="bg-white dark:bg-stone-900"><Share2 className="w-4 h-4 mr-2" /> Share</Button>
|
|
281
|
+
<Button size="sm" className="bg-indigo-600 hover:bg-indigo-700 text-white"><Download className="w-4 h-4 mr-2" /> Export</Button>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{/* Main Toolbar */}
|
|
286
|
+
<div className="absolute top-1/2 left-4 -translate-y-1/2 z-10">
|
|
287
|
+
<Toolbar activeTool={activeTool} setActiveTool={setActiveTool} />
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
{/* Canvas Area (Simulated) */}
|
|
291
|
+
<div className="w-full h-full relative cursor-grab active:cursor-grabbing transform-gpu">
|
|
292
|
+
{/* Objects */}
|
|
293
|
+
{MOCK_DATA.objects.map(obj => {
|
|
294
|
+
if (obj.type === 'connector') return <ConnectorObject key={obj.id} object={obj} />;
|
|
295
|
+
if (obj.type === 'shape') return <ShapeObject key={obj.id} object={obj} />;
|
|
296
|
+
if (obj.type === 'sticky') return <StickyNoteObject key={obj.id} object={obj} />;
|
|
297
|
+
return null;
|
|
298
|
+
})}
|
|
299
|
+
|
|
300
|
+
{/* Cursors */}
|
|
301
|
+
{MOCK_DATA.cursors.map(cursor => (
|
|
302
|
+
<CursorOverlay key={cursor.userId} cursor={cursor} />
|
|
303
|
+
))}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
{/* Zoom / Reference Controls */}
|
|
307
|
+
<div className="absolute bottom-4 right-4 flex items-center gap-2 z-10">
|
|
308
|
+
<div className="bg-white dark:bg-stone-900 shadow-sm border border-stone-200 dark:border-stone-800 rounded-lg p-1 flex items-center">
|
|
309
|
+
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setZoom(z => Math.max(10, z - 10))}><Minus size={16} /></Button>
|
|
310
|
+
<span className="w-12 text-center text-xs font-medium text-stone-600 dark:text-stone-300">{zoom}%</span>
|
|
311
|
+
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setZoom(z => Math.min(200, z + 10))}><Plus size={16} /></Button>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div className="bg-white dark:bg-stone-900 shadow-sm border border-stone-200 dark:border-stone-800 rounded-lg p-2 cursor-pointer hover:bg-stone-50 dark:hover:bg-stone-800 transition-colors">
|
|
315
|
+
<div className="w-16 h-10 bg-stone-100 dark:bg-stone-800 rounded border border-stone-200 dark:border-stone-700 relative overflow-hidden">
|
|
316
|
+
{/* Minimap content */}
|
|
317
|
+
<div className="absolute top-1 left-1 w-4 h-4 bg-indigo-500/20 rounded-sm" />
|
|
318
|
+
<div className="absolute top-2 right-4 w-2 h-2 bg-stone-400/20 rounded-full" />
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Help / Undo Controls */}
|
|
324
|
+
<div className="absolute bottom-4 left-4 flex items-center gap-2 z-10">
|
|
325
|
+
<div className="bg-white dark:bg-stone-900 shadow-sm border border-stone-200 dark:border-stone-800 rounded-lg p-1 flex items-center">
|
|
326
|
+
<Button variant="ghost" size="icon" className="h-8 w-8"><Undo2 size={16} /></Button>
|
|
327
|
+
<Button variant="ghost" size="icon" className="h-8 w-8"><Redo2 size={16} /></Button>
|
|
328
|
+
</div>
|
|
329
|
+
<Button variant="outline" size="icon" className="h-10 w-10 bg-white dark:bg-stone-900 rounded-lg"><Settings size={18} /></Button>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|