claudeship 0.2.12 → 0.2.15
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 +18 -0
- package/apps/server/dist/app.module.js +10 -0
- package/apps/server/dist/app.module.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.js +156 -0
- package/apps/server/dist/chat/prompts/web-system-prompt.js.map +1 -1
- package/apps/server/dist/checkpoint/checkpoint.controller.d.ts +19 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js +93 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.module.d.ts +2 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js +25 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.service.d.ts +41 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js +261 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js.map +1 -0
- package/apps/server/dist/database/database.controller.d.ts +23 -0
- package/apps/server/dist/database/database.controller.js +109 -0
- package/apps/server/dist/database/database.controller.js.map +1 -0
- package/apps/server/dist/database/database.module.d.ts +2 -0
- package/apps/server/dist/database/database.module.js +25 -0
- package/apps/server/dist/database/database.module.js.map +1 -0
- package/apps/server/dist/database/database.service.d.ts +32 -0
- package/apps/server/dist/database/database.service.js +238 -0
- package/apps/server/dist/database/database.service.js.map +1 -0
- package/apps/server/dist/env/env.controller.d.ts +14 -0
- package/apps/server/dist/env/env.controller.js +84 -0
- package/apps/server/dist/env/env.controller.js.map +1 -0
- package/apps/server/dist/env/env.module.d.ts +2 -0
- package/apps/server/dist/env/env.module.js +25 -0
- package/apps/server/dist/env/env.module.js.map +1 -0
- package/apps/server/dist/env/env.service.d.ts +21 -0
- package/apps/server/dist/env/env.service.js +194 -0
- package/apps/server/dist/env/env.service.js.map +1 -0
- package/apps/server/dist/preview/preview.controller.d.ts +5 -0
- package/apps/server/dist/preview/preview.controller.js +41 -0
- package/apps/server/dist/preview/preview.controller.js.map +1 -1
- package/apps/server/dist/preview/preview.service.d.ts +20 -0
- package/apps/server/dist/preview/preview.service.js +51 -2
- package/apps/server/dist/preview/preview.service.js.map +1 -1
- package/apps/server/dist/project/project.controller.d.ts +10 -1
- package/apps/server/dist/project/project.controller.js +57 -0
- package/apps/server/dist/project/project.controller.js.map +1 -1
- package/apps/server/dist/project/project.service.d.ts +15 -0
- package/apps/server/dist/project/project.service.js +111 -0
- package/apps/server/dist/project/project.service.js.map +1 -1
- package/apps/server/dist/project-context/project-context.controller.d.ts +42 -0
- package/apps/server/dist/project-context/project-context.controller.js +127 -0
- package/apps/server/dist/project-context/project-context.controller.js.map +1 -0
- package/apps/server/dist/project-context/project-context.module.d.ts +2 -0
- package/apps/server/dist/project-context/project-context.module.js +25 -0
- package/apps/server/dist/project-context/project-context.module.js.map +1 -0
- package/apps/server/dist/project-context/project-context.service.d.ts +36 -0
- package/apps/server/dist/project-context/project-context.service.js +260 -0
- package/apps/server/dist/project-context/project-context.service.js.map +1 -0
- package/apps/server/dist/testing/testing.controller.d.ts +24 -0
- package/apps/server/dist/testing/testing.controller.js +126 -0
- package/apps/server/dist/testing/testing.controller.js.map +1 -0
- package/apps/server/dist/testing/testing.module.d.ts +2 -0
- package/apps/server/dist/testing/testing.module.js +26 -0
- package/apps/server/dist/testing/testing.module.js.map +1 -0
- package/apps/server/dist/testing/testing.service.d.ts +62 -0
- package/apps/server/dist/testing/testing.service.js +269 -0
- package/apps/server/dist/testing/testing.service.js.map +1 -0
- package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/server/package.json +1 -1
- package/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/app-build-manifest.json +5 -5
- package/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/cache/.previewinfo +1 -1
- package/apps/web/.next/cache/.rscinfo +1 -1
- package/apps/web/.next/cache/.tsbuildinfo +1 -1
- package/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/prerender-manifest.json +10 -10
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/_not-found.html +1 -1
- package/apps/web/.next/server/app/_not-found.rsc +2 -2
- package/apps/web/.next/server/app/index.html +1 -1
- package/apps/web/.next/server/app/index.rsc +3 -3
- package/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/project/[id]/page.js +2 -2
- package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings.html +1 -1
- package/apps/web/.next/server/app/settings.rsc +3 -3
- package/apps/web/.next/server/chunks/392.js +1 -1
- package/apps/web/.next/server/pages/404.html +1 -1
- package/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/static/chunks/574-1fe2bcd6cfb41646.js +1 -0
- package/apps/web/.next/static/chunks/app/page-f19cfa58541ca83d.js +1 -0
- package/apps/web/.next/static/chunks/app/project/[id]/page-dffaa1d02f012216.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-d1318c2fd58729a5.js +1 -0
- package/apps/web/.next/static/css/0a24552d9794f8c8.css +3 -0
- package/apps/web/.next/trace +18 -17
- package/apps/web/node_modules/.bin/eslint +2 -2
- package/apps/web/package.json +2 -1
- package/apps/web/src/components/checkpoint/CheckpointPanel.tsx +384 -0
- package/apps/web/src/components/database/DatabasePanel.tsx +405 -0
- package/apps/web/src/components/env/EnvPanel.tsx +356 -0
- package/apps/web/src/components/preview/ConsoleViewer.tsx +270 -0
- package/apps/web/src/components/preview/ErrorOverlay.tsx +189 -0
- package/apps/web/src/components/preview/PreviewPanel.tsx +148 -6
- package/apps/web/src/components/testing/TestRunner.tsx +481 -0
- package/apps/web/src/components/ui/tabs.tsx +55 -0
- package/apps/web/src/components/visual-editor/VisualEditor.tsx +382 -0
- package/apps/web/src/components/workspace/WorkspaceLayout.tsx +66 -4
- package/apps/web/src/lib/api.ts +5 -2
- package/package.json +1 -1
- package/apps/web/.next/static/chunks/298-6f3d6b321c288cd3.js +0 -1
- package/apps/web/.next/static/chunks/app/page-3d093f7f480a8599.js +0 -1
- package/apps/web/.next/static/chunks/app/project/[id]/page-e5cda6f9050b0a52.js +0 -1
- package/apps/web/.next/static/chunks/app/settings/page-92d28565c3d8c755.js +0 -1
- package/apps/web/.next/static/css/8f946046a2047594.css +0 -3
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X, ExternalLink, Copy, Check } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
export interface ErrorInfo {
|
|
8
|
+
type: "compile" | "runtime" | "network";
|
|
9
|
+
message: string;
|
|
10
|
+
stack?: string;
|
|
11
|
+
location?: {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ErrorOverlayProps {
|
|
19
|
+
error: ErrorInfo;
|
|
20
|
+
onDismiss: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ErrorOverlay({ error, onDismiss }: ErrorOverlayProps) {
|
|
24
|
+
const [copied, setCopied] = useState(false);
|
|
25
|
+
|
|
26
|
+
const handleCopy = async () => {
|
|
27
|
+
const text = [
|
|
28
|
+
error.message,
|
|
29
|
+
error.location ? `at ${error.location.file}:${error.location.line}:${error.location.column}` : "",
|
|
30
|
+
error.stack || "",
|
|
31
|
+
].filter(Boolean).join("\n\n");
|
|
32
|
+
|
|
33
|
+
await navigator.clipboard.writeText(text);
|
|
34
|
+
setCopied(true);
|
|
35
|
+
setTimeout(() => setCopied(false), 2000);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const errorTypeLabels = {
|
|
39
|
+
compile: "Compilation Error",
|
|
40
|
+
runtime: "Runtime Error",
|
|
41
|
+
network: "Network Error",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const errorTypeColors = {
|
|
45
|
+
compile: "text-red-500",
|
|
46
|
+
runtime: "text-orange-500",
|
|
47
|
+
network: "text-yellow-500",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm z-50 overflow-auto">
|
|
52
|
+
<div className="min-h-full p-6">
|
|
53
|
+
<div className="max-w-2xl mx-auto bg-zinc-900 rounded-lg border border-red-500/50 overflow-hidden">
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="flex items-center justify-between px-4 py-3 bg-red-500/10 border-b border-red-500/30">
|
|
56
|
+
<div className="flex items-center gap-2">
|
|
57
|
+
<div className="w-3 h-3 rounded-full bg-red-500" />
|
|
58
|
+
<span className={`font-medium ${errorTypeColors[error.type]}`}>
|
|
59
|
+
{errorTypeLabels[error.type]}
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="flex items-center gap-1">
|
|
63
|
+
<Button
|
|
64
|
+
variant="ghost"
|
|
65
|
+
size="sm"
|
|
66
|
+
onClick={handleCopy}
|
|
67
|
+
className="h-7 px-2 text-zinc-400 hover:text-white"
|
|
68
|
+
>
|
|
69
|
+
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
70
|
+
</Button>
|
|
71
|
+
<Button
|
|
72
|
+
variant="ghost"
|
|
73
|
+
size="sm"
|
|
74
|
+
onClick={onDismiss}
|
|
75
|
+
className="h-7 px-2 text-zinc-400 hover:text-white"
|
|
76
|
+
>
|
|
77
|
+
<X className="h-4 w-4" />
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Error Message */}
|
|
83
|
+
<div className="p-4 space-y-4">
|
|
84
|
+
<p className="text-lg font-mono text-red-400 whitespace-pre-wrap">
|
|
85
|
+
{error.message}
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
{/* Location */}
|
|
89
|
+
{error.location && (
|
|
90
|
+
<div className="flex items-center gap-2 text-sm text-zinc-400">
|
|
91
|
+
<ExternalLink className="h-4 w-4" />
|
|
92
|
+
<code className="bg-zinc-800 px-2 py-0.5 rounded">
|
|
93
|
+
{error.location.file}:{error.location.line}:{error.location.column}
|
|
94
|
+
</code>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{/* Stack Trace */}
|
|
99
|
+
{error.stack && (
|
|
100
|
+
<div className="mt-4">
|
|
101
|
+
<p className="text-xs text-zinc-500 mb-2 uppercase tracking-wide">Stack Trace</p>
|
|
102
|
+
<pre className="bg-zinc-950 p-3 rounded-lg text-xs text-zinc-400 overflow-x-auto font-mono">
|
|
103
|
+
{error.stack}
|
|
104
|
+
</pre>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Footer */}
|
|
110
|
+
<div className="px-4 py-3 bg-zinc-800/50 border-t border-zinc-700 flex justify-end">
|
|
111
|
+
<Button
|
|
112
|
+
variant="outline"
|
|
113
|
+
size="sm"
|
|
114
|
+
onClick={onDismiss}
|
|
115
|
+
className="text-zinc-300"
|
|
116
|
+
>
|
|
117
|
+
Dismiss
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parse error from stderr log message
|
|
128
|
+
*/
|
|
129
|
+
export function parseErrorFromLog(message: string): ErrorInfo | null {
|
|
130
|
+
// Skip common non-error messages
|
|
131
|
+
if (message.includes("Compiling...") ||
|
|
132
|
+
message.includes("Ready in") ||
|
|
133
|
+
message.includes("Compiled successfully") ||
|
|
134
|
+
message.includes("Fast Refresh") ||
|
|
135
|
+
message.includes("○ Compiling") ||
|
|
136
|
+
message.includes("✓ Compiled")) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Next.js / TypeScript error pattern
|
|
141
|
+
// Example: "Error: Cannot find module 'xxx'"
|
|
142
|
+
// Example: "./src/components/Test.tsx:10:5"
|
|
143
|
+
const nextjsErrorPattern = /(?:Error|TypeError|SyntaxError|ReferenceError):\s*(.+)/;
|
|
144
|
+
const locationPattern = /(?:\.\/)?([^:]+):(\d+):(\d+)/;
|
|
145
|
+
|
|
146
|
+
const errorMatch = message.match(nextjsErrorPattern);
|
|
147
|
+
|
|
148
|
+
if (errorMatch) {
|
|
149
|
+
const locationMatch = message.match(locationPattern);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
type: "compile",
|
|
153
|
+
message: errorMatch[1],
|
|
154
|
+
stack: message,
|
|
155
|
+
location: locationMatch ? {
|
|
156
|
+
file: locationMatch[1],
|
|
157
|
+
line: parseInt(locationMatch[2], 10),
|
|
158
|
+
column: parseInt(locationMatch[3], 10),
|
|
159
|
+
} : undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check for "Failed to compile" message
|
|
164
|
+
if (message.includes("Failed to compile") || message.includes("Compilation failed")) {
|
|
165
|
+
return {
|
|
166
|
+
type: "compile",
|
|
167
|
+
message: "Failed to compile. Check the console for more details.",
|
|
168
|
+
stack: message,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check for module not found errors
|
|
173
|
+
const moduleNotFound = message.match(/Module not found:\s*(.+)/);
|
|
174
|
+
if (moduleNotFound) {
|
|
175
|
+
const locationMatch = message.match(locationPattern);
|
|
176
|
+
return {
|
|
177
|
+
type: "compile",
|
|
178
|
+
message: moduleNotFound[1],
|
|
179
|
+
stack: message,
|
|
180
|
+
location: locationMatch ? {
|
|
181
|
+
file: locationMatch[1],
|
|
182
|
+
line: parseInt(locationMatch[2], 10),
|
|
183
|
+
column: parseInt(locationMatch[3], 10),
|
|
184
|
+
} : undefined,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useRef, useCallback } from "react";
|
|
4
|
-
import { Play, Square, RefreshCw, ExternalLink, Package, Loader2, Zap, RotateCcw } from "lucide-react";
|
|
4
|
+
import { Play, Square, RefreshCw, ExternalLink, Package, Loader2, Zap, RotateCcw, Terminal, Monitor, Smartphone, Tablet } from "lucide-react";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
6
7
|
import { usePreviewStore } from "@/stores/usePreviewStore";
|
|
7
8
|
import { useTranslation } from "@/lib/i18n";
|
|
9
|
+
import { ConsoleViewer } from "./ConsoleViewer";
|
|
10
|
+
import { ErrorOverlay, ErrorInfo, parseErrorFromLog } from "./ErrorOverlay";
|
|
8
11
|
|
|
9
12
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:14000";
|
|
10
13
|
|
|
14
|
+
// Device presets for responsive preview
|
|
15
|
+
const devicePresets = {
|
|
16
|
+
desktop: { width: "100%", height: "100%", label: "Desktop" },
|
|
17
|
+
tablet: { width: "768px", height: "1024px", label: "Tablet" },
|
|
18
|
+
mobile: { width: "375px", height: "667px", label: "Mobile" },
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
type DeviceType = keyof typeof devicePresets;
|
|
22
|
+
|
|
11
23
|
interface PreviewPanelProps {
|
|
12
24
|
projectId: string;
|
|
13
25
|
}
|
|
@@ -196,6 +208,63 @@ export function PreviewPanel({ projectId }: PreviewPanelProps) {
|
|
|
196
208
|
}
|
|
197
209
|
};
|
|
198
210
|
|
|
211
|
+
const [activeTab, setActiveTab] = useState<"preview" | "console">("preview");
|
|
212
|
+
const [device, setDevice] = useState<DeviceType>("desktop");
|
|
213
|
+
const [buildError, setBuildError] = useState<ErrorInfo | null>(null);
|
|
214
|
+
const logEventSourceRef = useRef<EventSource | null>(null);
|
|
215
|
+
|
|
216
|
+
// Subscribe to log stream for error detection
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (!isMounted || status !== "running") {
|
|
219
|
+
if (logEventSourceRef.current) {
|
|
220
|
+
logEventSourceRef.current.close();
|
|
221
|
+
logEventSourceRef.current = null;
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const eventSource = new EventSource(
|
|
227
|
+
`${API_BASE}/projects/${projectId}/preview/logs/stream`
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
eventSource.onmessage = (event) => {
|
|
231
|
+
try {
|
|
232
|
+
const data = JSON.parse(event.data);
|
|
233
|
+
if (data.type === "log" && data.entry && data.entry.level === "stderr") {
|
|
234
|
+
const errorInfo = parseErrorFromLog(data.entry.message);
|
|
235
|
+
if (errorInfo) {
|
|
236
|
+
setBuildError(errorInfo);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// Ignore parse errors
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
eventSource.onerror = () => {
|
|
245
|
+
eventSource.close();
|
|
246
|
+
logEventSourceRef.current = null;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
logEventSourceRef.current = eventSource;
|
|
250
|
+
|
|
251
|
+
return () => {
|
|
252
|
+
eventSource.close();
|
|
253
|
+
logEventSourceRef.current = null;
|
|
254
|
+
};
|
|
255
|
+
}, [isMounted, status, projectId]);
|
|
256
|
+
|
|
257
|
+
// Clear error when preview restarts
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (status === "starting") {
|
|
260
|
+
setBuildError(null);
|
|
261
|
+
}
|
|
262
|
+
}, [status]);
|
|
263
|
+
|
|
264
|
+
const handleDismissError = useCallback(() => {
|
|
265
|
+
setBuildError(null);
|
|
266
|
+
}, []);
|
|
267
|
+
|
|
199
268
|
return (
|
|
200
269
|
<div className="flex h-full flex-col border-l">
|
|
201
270
|
{/* Controls */}
|
|
@@ -228,6 +297,61 @@ export function PreviewPanel({ projectId }: PreviewPanelProps) {
|
|
|
228
297
|
</div>
|
|
229
298
|
|
|
230
299
|
<div className="flex items-center gap-1">
|
|
300
|
+
{/* Device Selector (only visible in preview tab when running) */}
|
|
301
|
+
{activeTab === "preview" && status === "running" && (
|
|
302
|
+
<div className="flex items-center gap-0.5 mr-2 border-r pr-2">
|
|
303
|
+
<Button
|
|
304
|
+
variant={device === "desktop" ? "secondary" : "ghost"}
|
|
305
|
+
size="sm"
|
|
306
|
+
onClick={() => setDevice("desktop")}
|
|
307
|
+
className="h-7 px-2"
|
|
308
|
+
title="Desktop"
|
|
309
|
+
>
|
|
310
|
+
<Monitor className="h-4 w-4" />
|
|
311
|
+
</Button>
|
|
312
|
+
<Button
|
|
313
|
+
variant={device === "tablet" ? "secondary" : "ghost"}
|
|
314
|
+
size="sm"
|
|
315
|
+
onClick={() => setDevice("tablet")}
|
|
316
|
+
className="h-7 px-2"
|
|
317
|
+
title="Tablet (768x1024)"
|
|
318
|
+
>
|
|
319
|
+
<Tablet className="h-4 w-4" />
|
|
320
|
+
</Button>
|
|
321
|
+
<Button
|
|
322
|
+
variant={device === "mobile" ? "secondary" : "ghost"}
|
|
323
|
+
size="sm"
|
|
324
|
+
onClick={() => setDevice("mobile")}
|
|
325
|
+
className="h-7 px-2"
|
|
326
|
+
title="Mobile (375x667)"
|
|
327
|
+
>
|
|
328
|
+
<Smartphone className="h-4 w-4" />
|
|
329
|
+
</Button>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{/* Tab Switcher */}
|
|
334
|
+
<div className="flex items-center gap-0.5 mr-2 border-r pr-2">
|
|
335
|
+
<Button
|
|
336
|
+
variant={activeTab === "preview" ? "secondary" : "ghost"}
|
|
337
|
+
size="sm"
|
|
338
|
+
onClick={() => setActiveTab("preview")}
|
|
339
|
+
className="h-7 px-2"
|
|
340
|
+
title="Preview"
|
|
341
|
+
>
|
|
342
|
+
<Monitor className="h-4 w-4" />
|
|
343
|
+
</Button>
|
|
344
|
+
<Button
|
|
345
|
+
variant={activeTab === "console" ? "secondary" : "ghost"}
|
|
346
|
+
size="sm"
|
|
347
|
+
onClick={() => setActiveTab("console")}
|
|
348
|
+
className="h-7 px-2"
|
|
349
|
+
title="Console"
|
|
350
|
+
>
|
|
351
|
+
<Terminal className="h-4 w-4" />
|
|
352
|
+
</Button>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
231
355
|
{status === "running" ? (
|
|
232
356
|
<>
|
|
233
357
|
<Button
|
|
@@ -294,20 +418,38 @@ export function PreviewPanel({ projectId }: PreviewPanelProps) {
|
|
|
294
418
|
</div>
|
|
295
419
|
</div>
|
|
296
420
|
|
|
297
|
-
{/*
|
|
298
|
-
<div className="flex-1 bg-muted/30">
|
|
299
|
-
{
|
|
421
|
+
{/* Tab Content */}
|
|
422
|
+
<div className="flex-1 bg-muted/30 overflow-hidden">
|
|
423
|
+
{activeTab === "console" ? (
|
|
424
|
+
<ConsoleViewer projectId={projectId} isRunning={status === "running"} />
|
|
425
|
+
) : !isMounted ? (
|
|
300
426
|
<div className="flex h-full items-center justify-center">
|
|
301
427
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
302
428
|
</div>
|
|
303
429
|
) : status === "running" && url ? (
|
|
304
|
-
<div className=
|
|
430
|
+
<div className={`relative h-full w-full ${device !== "desktop" ? "flex items-center justify-center bg-muted/50 p-4" : ""}`}>
|
|
305
431
|
<iframe
|
|
306
432
|
ref={iframeRef}
|
|
307
433
|
key={url}
|
|
308
434
|
src={url}
|
|
309
|
-
|
|
435
|
+
style={{
|
|
436
|
+
width: devicePresets[device].width,
|
|
437
|
+
height: devicePresets[device].height,
|
|
438
|
+
maxWidth: "100%",
|
|
439
|
+
maxHeight: "100%",
|
|
440
|
+
}}
|
|
441
|
+
className={`border-0 ${device !== "desktop" ? "border rounded-lg shadow-lg bg-white" : ""}`}
|
|
310
442
|
/>
|
|
443
|
+
{/* Error Overlay */}
|
|
444
|
+
{buildError && (
|
|
445
|
+
<ErrorOverlay error={buildError} onDismiss={handleDismissError} />
|
|
446
|
+
)}
|
|
447
|
+
{/* Device size indicator */}
|
|
448
|
+
{device !== "desktop" && (
|
|
449
|
+
<div className="absolute bottom-4 left-4 px-2 py-1 bg-black/70 text-white text-xs rounded">
|
|
450
|
+
{devicePresets[device].label} ({devicePresets[device].width} x {devicePresets[device].height})
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
311
453
|
{/* File change notification */}
|
|
312
454
|
{lastChange && (
|
|
313
455
|
<div className="absolute bottom-4 right-4 flex items-center gap-2 px-3 py-2 bg-green-500/90 text-white text-xs rounded-lg shadow-lg animate-pulse">
|