@sampleapp.ai/sdk 1.0.29 → 1.0.30
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/dist/components/guardian/app-layout-no-sidebar.js +8 -0
- package/dist/components/guardian/ask-ai-view.js +249 -0
- package/dist/components/guardian/code-focus-section.d.ts +41 -0
- package/dist/components/guardian/code-focus-section.js +174 -0
- package/dist/components/guardian/context/guardian-context.js +94 -0
- package/dist/components/guardian/context/vm-context.js +28 -0
- package/dist/components/guardian/default-guide-view.js +34 -0
- package/dist/components/guardian/demo/guardian-demo.js +35 -0
- package/dist/components/guardian/demo/left-view/toggle.js +28 -0
- package/dist/components/guardian/demo/left-view.js +49 -0
- package/dist/components/guardian/guardian-component.js +79 -0
- package/dist/components/guardian/guardian-demo.js +35 -0
- package/dist/components/guardian/guardian-home.d.ts +4 -0
- package/dist/components/guardian/guardian-home.js +61 -0
- package/dist/components/guardian/guardian-playground.js +45 -0
- package/dist/components/guardian/guardian-style-wrapper.js +29 -0
- package/dist/components/guardian/guardian-upload-spec.d.ts +14 -0
- package/dist/components/guardian/guardian-upload-spec.js +160 -0
- package/dist/components/guardian/header/glassmorphic-combobox.d.ts +15 -0
- package/dist/components/guardian/header/glassmorphic-combobox.js +30 -0
- package/dist/components/guardian/header.js +61 -0
- package/dist/components/guardian/hooks/use-frame-messages.js +65 -0
- package/dist/components/guardian/hooks/use-frame-params.js +44 -0
- package/dist/components/guardian/hooks/use-sandbox-url-loader.js +101 -0
- package/dist/components/guardian/ide/browser.js +538 -0
- package/dist/components/guardian/index.js +8 -0
- package/dist/components/guardian/layout/app-layout-no-sidebar.js +8 -0
- package/dist/components/guardian/layout/header/glassmorphic-combobox.js +48 -0
- package/dist/components/guardian/layout/header.js +63 -0
- package/dist/components/guardian/right-view/code-view.js +56 -0
- package/dist/components/guardian/right-view/pill-file-selector.js +233 -0
- package/dist/components/guardian/right-view/preview-control-bar.js +25 -0
- package/dist/components/guardian/right-view/right-panel-view.js +38 -0
- package/dist/components/guardian/right-view/right-top-down-view.js +289 -0
- package/dist/components/guardian/right-view/right-view.js +28 -0
- package/dist/components/guardian/right-view/simplified-editor.js +234 -0
- package/dist/components/guardian/types/ide-types.js +162 -0
- package/dist/components/guardian/types.js +3 -0
- package/dist/components/guardian/ui/ai-loader.js +48 -0
- package/dist/components/guardian/ui/badge.js +24 -0
- package/dist/components/guardian/ui/button.js +45 -0
- package/dist/components/guardian/ui/command.js +63 -0
- package/dist/components/guardian/ui/console-with-app.js +17 -0
- package/dist/components/guardian/ui/dialog.js +57 -0
- package/dist/components/guardian/ui/dropdown-menu.js +82 -0
- package/dist/components/guardian/ui/markdown.js +57 -0
- package/dist/components/guardian/ui/popover.js +25 -0
- package/dist/components/guardian/ui/tooltip.js +25 -0
- package/dist/components/guardian/utils.js +88 -0
- package/dist/components/guardian/zip-to-codebase.js +246 -0
- package/dist/components/guardian/zip-to-filetree.js +284 -0
- package/dist/components/icons.js +22 -0
- package/dist/components/sandbox/Sandbox.js +87 -0
- package/dist/components/sandbox/SandboxHome.js +141 -0
- package/dist/components/sandbox/api.js +108 -0
- package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +8 -0
- package/dist/components/sandbox/guardian/ask-ai-view.js +249 -0
- package/dist/components/sandbox/guardian/code-focus-section.js +174 -0
- package/dist/components/sandbox/guardian/context/guardian-context.js +94 -0
- package/dist/components/sandbox/guardian/context/vm-context.js +28 -0
- package/dist/components/sandbox/guardian/default-guide-view.js +34 -0
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +35 -0
- package/dist/components/sandbox/guardian/demo/left-view/toggle.js +28 -0
- package/dist/components/sandbox/guardian/demo/left-view.js +58 -0
- package/dist/components/sandbox/guardian/guardian-component.js +97 -0
- package/dist/components/sandbox/guardian/guardian-demo.js +35 -0
- package/dist/components/sandbox/guardian/guardian-home.d.ts +4 -0
- package/dist/components/sandbox/guardian/guardian-home.js +61 -0
- package/dist/components/sandbox/guardian/guardian-playground.js +45 -0
- package/dist/components/sandbox/guardian/guardian-style-wrapper.js +33 -0
- package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +14 -0
- package/dist/components/sandbox/guardian/guardian-upload-spec.js +160 -0
- package/dist/components/sandbox/guardian/header/glassmorphic-combobox.js +30 -0
- package/dist/components/sandbox/guardian/header.js +61 -0
- package/dist/components/sandbox/guardian/hooks/use-frame-messages.js +65 -0
- package/dist/components/sandbox/guardian/hooks/use-frame-params.js +44 -0
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +145 -0
- package/dist/components/sandbox/guardian/ide/browser.js +538 -0
- package/dist/components/sandbox/guardian/index.js +8 -0
- package/dist/components/sandbox/guardian/right-view/code-view.js +60 -0
- package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +233 -0
- package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +25 -0
- package/dist/components/sandbox/guardian/right-view/right-panel-view.js +38 -0
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +289 -0
- package/dist/components/sandbox/guardian/right-view/right-view.js +28 -0
- package/dist/components/sandbox/guardian/right-view/simplified-editor.js +234 -0
- package/dist/components/sandbox/guardian/types/ide-types.js +162 -0
- package/dist/components/sandbox/guardian/types.js +3 -0
- package/dist/components/sandbox/guardian/ui/ai-loader.js +48 -0
- package/dist/components/sandbox/guardian/ui/badge.js +24 -0
- package/dist/components/sandbox/guardian/ui/button.js +45 -0
- package/dist/components/sandbox/guardian/ui/command.js +63 -0
- package/dist/components/sandbox/guardian/ui/console-with-app.js +17 -0
- package/dist/components/sandbox/guardian/ui/dialog.js +57 -0
- package/dist/components/sandbox/guardian/ui/dropdown-menu.js +82 -0
- package/dist/components/sandbox/guardian/ui/markdown/accordion-group/accordion.js +62 -0
- package/dist/components/sandbox/guardian/ui/markdown/accordion-group.js +23 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-check.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-error.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-info.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-note.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-tip.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/callout-warning.js +4 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/shared/callout.js +9 -0
- package/dist/components/sandbox/guardian/ui/markdown/callout/shared/types.js +1 -0
- package/dist/components/sandbox/guardian/ui/markdown/card-group/card.js +18 -0
- package/dist/components/sandbox/guardian/ui/markdown/card-group.js +25 -0
- package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +87 -0
- package/dist/components/sandbox/guardian/ui/markdown/code-group.js +101 -0
- package/dist/components/sandbox/guardian/ui/markdown/icon.js +31 -0
- package/dist/components/sandbox/guardian/ui/markdown.js +786 -0
- package/dist/components/sandbox/guardian/ui/popover.js +25 -0
- package/dist/components/sandbox/guardian/ui/tooltip.js +25 -0
- package/dist/components/sandbox/guardian/utils.js +88 -0
- package/dist/components/sandbox/guardian/zip-to-codebase.js +259 -0
- package/dist/components/sandbox/guardian/zip-to-filetree.js +284 -0
- package/dist/components/sandbox/index.js +4 -0
- package/dist/components/sandbox/sandbox-control-bar.js +91 -0
- package/dist/components/sandbox/sandbox-header.js +52 -0
- package/dist/components/sandbox/sandbox-home/SandboxCard.js +59 -0
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +174 -0
- package/dist/components/sandbox/sandbox-home/SearchBar.js +12 -0
- package/dist/components/sandbox/sandbox-home/index.js +3 -0
- package/dist/components/sandbox/sandbox-left-panel.js +248 -0
- package/dist/components/sandbox/sandbox-loading.js +48 -0
- package/dist/components/sandbox/sandbox-right-panel.js +247 -0
- package/dist/components/sandbox/types.js +1 -0
- package/dist/components/sandbox.js +32 -0
- package/dist/components/tailwind-example.js +46 -0
- package/dist/index.d.ts +336 -1
- package/dist/index.es.js +90131 -421
- package/dist/index.js +13 -2
- package/dist/index.standalone.js +61 -53
- package/dist/index.standalone.umd.js +17 -24
- package/dist/lib/api-client.example.js +60 -0
- package/dist/lib/api-client.js +98 -0
- package/dist/lib/generated-css.js +4 -0
- package/dist/lib/inject-styles.js +42 -0
- package/dist/lib/shadow-dom-wrapper.js +42 -0
- package/dist/lib/utils.js +5 -0
- package/dist/sdk.css +1 -1
- package/dist/tailwind.css +1 -0
- package/package.json +32 -5
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
export default function AppLayoutNoSidebar({ header, hasBodyPadding = true, children, bodyHeight = "h-[calc(100vh-6rem)]", }) {
|
|
4
|
+
return (React.createElement("div", null,
|
|
5
|
+
header && (React.createElement("header", { className: "flex h-24 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 w-full" },
|
|
6
|
+
React.createElement("div", { className: "flex items-center gap-2 px-4 w-full" }, header))),
|
|
7
|
+
React.createElement("div", { className: cn("flex flex-1 flex-col pt-0", hasBodyPadding && "gap-4 p-4", bodyHeight) }, children)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useState, useRef, useEffect, useCallback, useMemo } from "react";
|
|
3
|
+
import { useChat } from "@ai-sdk/react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
import { Markdown } from "./ui/markdown";
|
|
6
|
+
import { zipPathToCodebase } from "./zip-to-codebase";
|
|
7
|
+
// Loading animation component - matching mcp-chat-view exactly
|
|
8
|
+
function MessageLoading() {
|
|
9
|
+
return (React.createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", className: "text-foreground" },
|
|
10
|
+
React.createElement("circle", { cx: "4", cy: "12", r: "2", fill: "currentColor" },
|
|
11
|
+
React.createElement("animate", { id: "spinner_qFRN", begin: "0;spinner_OcgL.end+0.25s", attributeName: "cy", calcMode: "spline", dur: "0.6s", values: "12;6;12", keySplines: ".33,.66,.66,1;.33,0,.66,.33" })),
|
|
12
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor" },
|
|
13
|
+
React.createElement("animate", { begin: "spinner_qFRN.begin+0.1s", attributeName: "cy", calcMode: "spline", dur: "0.6s", values: "12;6;12", keySplines: ".33,.66,.66,1;.33,0,.66,.33" })),
|
|
14
|
+
React.createElement("circle", { cx: "20", cy: "12", r: "2", fill: "currentColor" },
|
|
15
|
+
React.createElement("animate", { id: "spinner_OcgL", begin: "spinner_qFRN.begin+0.2s", attributeName: "cy", calcMode: "spline", dur: "0.6s", values: "12;6;12", keySplines: ".33,.66,.66,1;.33,0,.66,.33" }))));
|
|
16
|
+
}
|
|
17
|
+
export default function AskAiView({ codeZipFile, playgroundUid, themeColor, }) {
|
|
18
|
+
const [input, setInput] = useState("");
|
|
19
|
+
const messagesEndRef = useRef(null);
|
|
20
|
+
// Track current message ID from stream
|
|
21
|
+
const currentMessageIdRef = useRef(null);
|
|
22
|
+
const [generatedCode, setGeneratedCode] = useState(null);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
zipPathToCodebase(codeZipFile).then((generatedCode) => {
|
|
25
|
+
setGeneratedCode(generatedCode);
|
|
26
|
+
});
|
|
27
|
+
}, [codeZipFile]);
|
|
28
|
+
// Custom fetch to intercept stream and process events (matching mcp-chat-view pattern)
|
|
29
|
+
const customFetch = useCallback(async (input, init) => {
|
|
30
|
+
const response = await fetch(input, init);
|
|
31
|
+
if (!response.body)
|
|
32
|
+
return response;
|
|
33
|
+
// Create a new readable stream that processes the original stream
|
|
34
|
+
const reader = response.body.getReader();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
const stream = new ReadableStream({
|
|
38
|
+
async start(controller) {
|
|
39
|
+
while (true) {
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (done) {
|
|
42
|
+
// Process any remaining buffer
|
|
43
|
+
if (buffer.trim()) {
|
|
44
|
+
controller.enqueue(new TextEncoder().encode(buffer));
|
|
45
|
+
}
|
|
46
|
+
controller.close();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
buffer += decoder.decode(value, { stream: true });
|
|
50
|
+
const lines = buffer.split("\n");
|
|
51
|
+
// Keep the last incomplete line in buffer
|
|
52
|
+
buffer = lines.pop() || "";
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
if (line.trim() === "")
|
|
55
|
+
continue;
|
|
56
|
+
if (line.startsWith("data: ")) {
|
|
57
|
+
const dataStr = line.slice(6).trim();
|
|
58
|
+
if (dataStr === "[DONE]") {
|
|
59
|
+
controller.enqueue(new TextEncoder().encode(`data: ${dataStr}\n\n`));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const data = JSON.parse(dataStr);
|
|
64
|
+
const dataType = data.type;
|
|
65
|
+
// Track message ID from start event
|
|
66
|
+
if (dataType === "start" && data.messageId) {
|
|
67
|
+
currentMessageIdRef.current = data.messageId;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (_a) {
|
|
71
|
+
// Not JSON, pass through
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Pass through the original line
|
|
75
|
+
controller.enqueue(new TextEncoder().encode(line + "\n"));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return new Response(stream, {
|
|
81
|
+
headers: response.headers,
|
|
82
|
+
status: response.status,
|
|
83
|
+
statusText: response.statusText,
|
|
84
|
+
});
|
|
85
|
+
}, []);
|
|
86
|
+
const initialMessages = [
|
|
87
|
+
{
|
|
88
|
+
id: "welcome",
|
|
89
|
+
role: "assistant",
|
|
90
|
+
content: "Hi! Ask me anything about feature flags or this demo. Try: 'How do I enable the sample-feature for beta users?'",
|
|
91
|
+
parts: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: "Hi! Ask me anything about feature flags or this demo. Try: 'How do I enable the sample-feature for beta users?'",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
// const {messages, sendMessage, status, setMessages} = useChat({
|
|
100
|
+
const { messages, sendMessage, status } = useChat({
|
|
101
|
+
// @ts-expect-error - api property exists in ChatInit but types may not be properly inferred
|
|
102
|
+
api: "/api/chat",
|
|
103
|
+
fetch: customFetch,
|
|
104
|
+
initialMessages,
|
|
105
|
+
});
|
|
106
|
+
// Auto-scroll to bottom when new messages arrive
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
var _a;
|
|
109
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
110
|
+
}, [messages]);
|
|
111
|
+
// Format generatedCode into a readable string for the system prompt, skipping any .env files
|
|
112
|
+
const codebaseContext = useMemo(() => {
|
|
113
|
+
if (!generatedCode || Object.keys(generatedCode).length === 0) {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
const codebaseLines = [];
|
|
117
|
+
codebaseLines.push("Here is the codebase context:\n");
|
|
118
|
+
// Sort files by path for consistent ordering
|
|
119
|
+
const sortedFiles = Object.entries(generatedCode)
|
|
120
|
+
.filter(([filePath]) => !filePath.endsWith(".env")) // skip .env files
|
|
121
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
122
|
+
for (const [filePath, codeOutput] of sortedFiles) {
|
|
123
|
+
codebaseLines.push(`\nFile: ${filePath}`);
|
|
124
|
+
if (codeOutput.code_language) {
|
|
125
|
+
codebaseLines.push(`Language: ${codeOutput.code_language}`);
|
|
126
|
+
}
|
|
127
|
+
if (codeOutput.full_code) {
|
|
128
|
+
codebaseLines.push("Code:");
|
|
129
|
+
codebaseLines.push("```");
|
|
130
|
+
codebaseLines.push(codeOutput.full_code);
|
|
131
|
+
codebaseLines.push("```");
|
|
132
|
+
}
|
|
133
|
+
codebaseLines.push(""); // Empty line between files
|
|
134
|
+
}
|
|
135
|
+
return codebaseLines.join("\n");
|
|
136
|
+
}, [generatedCode]);
|
|
137
|
+
const handleSend = () => {
|
|
138
|
+
const trimmed = input.trim();
|
|
139
|
+
if (!trimmed || status === "submitted" || status === "streaming")
|
|
140
|
+
return;
|
|
141
|
+
// Build system prompt with codebase context
|
|
142
|
+
const baseSystemPrompt = `You are a helpful AI assistant. You help users understand the codebase and the project and how things are being used, specifically focused on the ${playgroundUid} or related APIs and SDKs and why they are designed in a certain way.
|
|
143
|
+
|
|
144
|
+
Response style:
|
|
145
|
+
- VERY VERY IMPORTANT: Be concise, like 50% of the usual response length.
|
|
146
|
+
- Be concise: Answer directly without unnecessary preamble or repetition
|
|
147
|
+
- Use code examples when they clarify better than words
|
|
148
|
+
- For complex topics, break down into key points rather than lengthy prose
|
|
149
|
+
- Prioritize relevance over completeness`;
|
|
150
|
+
const systemPrompt = codebaseContext
|
|
151
|
+
? `${baseSystemPrompt}\n\n${codebaseContext}\n\nWhen answering questions, you can reference the codebase above to provide accurate and context-aware responses.`
|
|
152
|
+
: baseSystemPrompt;
|
|
153
|
+
// Send message without mcpUrl to use simple AI chat
|
|
154
|
+
// Include system prompt with codebase context
|
|
155
|
+
sendMessage({ text: trimmed }, {
|
|
156
|
+
body: {
|
|
157
|
+
system: systemPrompt,
|
|
158
|
+
model: "Claude Haiku 4.5",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
setInput("");
|
|
162
|
+
};
|
|
163
|
+
const isLoading = status === "submitted" || status === "streaming";
|
|
164
|
+
// Filter out system messages (only show user and assistant)
|
|
165
|
+
const visibleMessages = messages.filter((m) => m.role === "user" || m.role === "assistant");
|
|
166
|
+
// const handleClearChat = () => {
|
|
167
|
+
// // Clear all messages and reset to initial state
|
|
168
|
+
// // Use the initialMessages directly since they now match the UIMessage structure
|
|
169
|
+
// setMessages(initialMessages as unknown as typeof messages);
|
|
170
|
+
// setInput("");
|
|
171
|
+
// };
|
|
172
|
+
return (React.createElement("div", { className: "flex-1 flex flex-col min-h-0 h-full" },
|
|
173
|
+
React.createElement("div", { className: "flex-1 overflow-y-auto min-w-0 w-full px-4 py-4" },
|
|
174
|
+
visibleMessages.length > 0 ? (React.createElement("div", { className: "space-y-4 max-w-4xl mx-auto" },
|
|
175
|
+
visibleMessages.map((message) => {
|
|
176
|
+
return (React.createElement("div", { key: message.id }, message.role === "user" ? (React.createElement(UserMessage, { message: message, themeColor: themeColor })) : (React.createElement(AssistantMessage, { message: message }))));
|
|
177
|
+
}),
|
|
178
|
+
status === "submitted" && (React.createElement("div", { className: "flex justify-start" },
|
|
179
|
+
React.createElement("div", { className: "flex items-center gap-2 px-2" },
|
|
180
|
+
React.createElement(MessageLoading, null),
|
|
181
|
+
React.createElement("span", { className: "text-muted-foreground text-sm" }, "Thinking...")))))) : (React.createElement("div", { className: "flex h-full items-center justify-center" },
|
|
182
|
+
React.createElement("div", { className: "flex flex-col items-start space-y-4 px-4 max-w-4xl" },
|
|
183
|
+
React.createElement("div", { className: "text-start" },
|
|
184
|
+
React.createElement("h2", { className: "mb-2 text-2xl font-semibold tracking-tight text-gray-200" }, "Hi! Ask me anything"),
|
|
185
|
+
React.createElement("p", { className: "text-muted-foreground text-base" }, "Try: \u201CHow does this sample app work?\u201D"))))),
|
|
186
|
+
React.createElement("div", { ref: messagesEndRef })),
|
|
187
|
+
React.createElement("div", { className: "shrink-0 border-t border-gray-800/50 bg-[#0a0b0f]/95 backdrop-blur-sm" },
|
|
188
|
+
React.createElement("div", { className: "max-w-4xl mx-auto px-4 py-4" },
|
|
189
|
+
React.createElement("div", { className: "flex items-end gap-3 bg-[#1a1b23] rounded-2xl border border-gray-800/50 px-4 py-3 transition-all", style: {
|
|
190
|
+
boxShadow: `0 0 0 1px transparent`,
|
|
191
|
+
} },
|
|
192
|
+
React.createElement("div", { className: "flex-1 min-w-0" },
|
|
193
|
+
React.createElement("textarea", { value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => {
|
|
194
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
handleSend();
|
|
197
|
+
}
|
|
198
|
+
}, placeholder: "Type your message...", disabled: isLoading, rows: 1, className: "w-full bg-transparent border-0 resize-none text-base text-gray-200 placeholder:text-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed max-h-40 overflow-y-auto leading-relaxed", style: {
|
|
199
|
+
height: "auto",
|
|
200
|
+
minHeight: "32px",
|
|
201
|
+
}, onInput: (e) => {
|
|
202
|
+
const target = e.target;
|
|
203
|
+
target.style.height = "auto";
|
|
204
|
+
target.style.height = `${Math.min(target.scrollHeight, 160)}px`;
|
|
205
|
+
} })),
|
|
206
|
+
React.createElement("button", { onClick: handleSend, disabled: isLoading || input.trim().length === 0, className: cn("shrink-0 w-10 h-10 rounded-full flex items-center justify-center transition-all", isLoading || input.trim().length === 0
|
|
207
|
+
? "bg-gray-800 text-gray-500 cursor-not-allowed"
|
|
208
|
+
: "text-white hover:scale-105 active:scale-95"), style: isLoading || input.trim().length === 0
|
|
209
|
+
? undefined
|
|
210
|
+
: {
|
|
211
|
+
backgroundColor: themeColor,
|
|
212
|
+
boxShadow: `0 10px 25px -10px ${themeColor}4d`,
|
|
213
|
+
}, title: "Send message" }, isLoading ? (React.createElement(MessageLoading, null)) : (React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: "text-current" },
|
|
214
|
+
React.createElement("path", { d: "M22 2L11 13", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
215
|
+
React.createElement("path", { d: "M22 2L15 22L11 13L2 9L22 2Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })))))))));
|
|
216
|
+
}
|
|
217
|
+
function UserMessage({ message, themeColor, }) {
|
|
218
|
+
var _a;
|
|
219
|
+
return (React.createElement("div", { className: "flex justify-end" },
|
|
220
|
+
React.createElement("div", { className: "max-w-[70%] rounded-2xl px-4 py-3 text-sm text-white shadow-lg", style: { backgroundColor: themeColor } }, (_a = message.parts) === null || _a === void 0 ? void 0 :
|
|
221
|
+
_a.map((part, i) => (React.createElement("div", { key: `${message.id}-${i}`, className: "leading-relaxed" }, part.type === "text" && part.text))),
|
|
222
|
+
(!message.parts || message.parts.length === 0) && message.content && (React.createElement("div", { className: "leading-relaxed" }, message.content)))));
|
|
223
|
+
}
|
|
224
|
+
function AssistantMessage({ message }) {
|
|
225
|
+
var _a;
|
|
226
|
+
const getMessageContent = () => {
|
|
227
|
+
var _a;
|
|
228
|
+
// First try to get content from parts (custom format)
|
|
229
|
+
const partsContent = (_a = message.parts) === null || _a === void 0 ? void 0 : _a.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
230
|
+
// Fall back to direct content property (useChat format)
|
|
231
|
+
return partsContent || message.content || "";
|
|
232
|
+
};
|
|
233
|
+
const content = getMessageContent();
|
|
234
|
+
// Only render if there's actual content
|
|
235
|
+
if (!content || content.trim() === "") {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return (React.createElement("div", { className: "flex justify-start" },
|
|
239
|
+
React.createElement("div", { className: "max-w-[70%] rounded-2xl px-4 py-3 text-sm bg-muted border border-border" }, (_a = message.parts) === null || _a === void 0 ? void 0 :
|
|
240
|
+
_a.map((part, i) => {
|
|
241
|
+
switch (part.type) {
|
|
242
|
+
case "text":
|
|
243
|
+
return (React.createElement(Markdown, { key: `${message.id}-${i}` }, part.text || ""));
|
|
244
|
+
default:
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}),
|
|
248
|
+
(!message.parts || message.parts.length === 0) && content && (React.createElement(Markdown, null, content)))));
|
|
249
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface CodeFocusSectionProps {
|
|
3
|
+
/**
|
|
4
|
+
* The file path to focus on (e.g., "src/App.jsx")
|
|
5
|
+
*/
|
|
6
|
+
filePath: string;
|
|
7
|
+
/**
|
|
8
|
+
* Optional line range to highlight. If not provided, just focuses on the file.
|
|
9
|
+
* If only start is provided, highlights from start to end of file.
|
|
10
|
+
*/
|
|
11
|
+
lineRange?: {
|
|
12
|
+
start: number;
|
|
13
|
+
end?: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Title for this section
|
|
17
|
+
*/
|
|
18
|
+
title: string;
|
|
19
|
+
/**
|
|
20
|
+
* Description for this section
|
|
21
|
+
*/
|
|
22
|
+
description?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Optional children to render in the section
|
|
25
|
+
*/
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Threshold for intersection observer (0-1, default ~0.66)
|
|
29
|
+
* When at least this percentage of the element is visible, trigger the code focus
|
|
30
|
+
*/
|
|
31
|
+
threshold?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Hex color (e.g. "#3b82f6") used for the active file accent.
|
|
34
|
+
*/
|
|
35
|
+
themeColor: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Component that focuses on a specific file and line range in the code editor
|
|
39
|
+
* when it comes into view while scrolling, or when the user clicks "View Code".
|
|
40
|
+
*/
|
|
41
|
+
export default function CodeFocusSection({ filePath, lineRange, title, description, children, threshold, themeColor, }: CodeFocusSectionProps): React.JSX.Element;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { useGuardianContext } from "./context/guardian-context";
|
|
4
|
+
// Helper function to find a node in the file tree by path
|
|
5
|
+
function getTargetNode(fileTree, targetPath) {
|
|
6
|
+
const parts = targetPath.split("/").filter(Boolean);
|
|
7
|
+
let current = fileTree;
|
|
8
|
+
for (const part of parts) {
|
|
9
|
+
if (!current.children || !current.children[part]) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
current = current.children[part];
|
|
13
|
+
}
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Component that focuses on a specific file and line range in the code editor
|
|
18
|
+
* when it comes into view while scrolling, or when the user clicks "View Code".
|
|
19
|
+
*/
|
|
20
|
+
export default function CodeFocusSection({ filePath, lineRange, title, description, children, threshold = 0, themeColor, }) {
|
|
21
|
+
const sectionRef = useRef(null);
|
|
22
|
+
const { generatedCode, fileTree, setActiveFilePath, setActiveFileName, setActiveLineNumber, setActiveLineRange, updateCode, setLanguage, filesEdited, activeFilePath, activeLineRange, } = useGuardianContext();
|
|
23
|
+
// Moves the editor's focus to this file/lines
|
|
24
|
+
const focusCode = () => {
|
|
25
|
+
// Get the file code from generatedCode or fileTree
|
|
26
|
+
// Always search by file_path property first, as that's the source of truth
|
|
27
|
+
let code = "";
|
|
28
|
+
let language;
|
|
29
|
+
let resolvedFilePath;
|
|
30
|
+
let codeOutput;
|
|
31
|
+
// First, search through all code outputs to find one matching file_path
|
|
32
|
+
const matchingCodeOutput = Object.values(generatedCode).find((output) => output.file_path === filePath);
|
|
33
|
+
if (matchingCodeOutput) {
|
|
34
|
+
codeOutput = matchingCodeOutput;
|
|
35
|
+
code = codeOutput.full_code;
|
|
36
|
+
language = codeOutput.code_language;
|
|
37
|
+
resolvedFilePath = codeOutput.file_path;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Fallback: try to get from generatedCode by key, but still use file_path from the object
|
|
41
|
+
if (generatedCode[filePath]) {
|
|
42
|
+
codeOutput = generatedCode[filePath];
|
|
43
|
+
code = codeOutput.full_code;
|
|
44
|
+
language = codeOutput.code_language;
|
|
45
|
+
// Always use the file_path from the code output object as source of truth
|
|
46
|
+
resolvedFilePath = codeOutput.file_path || filePath;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Fallback to fileTree
|
|
50
|
+
try {
|
|
51
|
+
const targetNode = getTargetNode(fileTree, filePath);
|
|
52
|
+
if (targetNode === null || targetNode === void 0 ? void 0 : targetNode.metadata) {
|
|
53
|
+
code = targetNode.metadata.full_code || "";
|
|
54
|
+
language = targetNode.metadata.code_language;
|
|
55
|
+
// Use file_path from metadata if available, otherwise use the provided filePath
|
|
56
|
+
resolvedFilePath = targetNode.metadata.file_path || filePath;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn(`Failed to get code for file ${filePath}:`, error);
|
|
61
|
+
resolvedFilePath = filePath;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// If we still don't have a resolved file path, use the provided filePath
|
|
66
|
+
if (!resolvedFilePath) {
|
|
67
|
+
resolvedFilePath = filePath;
|
|
68
|
+
}
|
|
69
|
+
// Check if file has been edited - use resolvedFilePath for lookup
|
|
70
|
+
const editedCode = filesEdited[resolvedFilePath] || filesEdited[filePath];
|
|
71
|
+
const finalCode = editedCode ? editedCode.full_code : code;
|
|
72
|
+
if (finalCode) {
|
|
73
|
+
// Extract filename from path - prefer code_file_name from code output
|
|
74
|
+
const fileName = (codeOutput === null || codeOutput === void 0 ? void 0 : codeOutput.code_file_name) ||
|
|
75
|
+
resolvedFilePath.split("/").pop() ||
|
|
76
|
+
resolvedFilePath;
|
|
77
|
+
// Set the active file using the resolved file path
|
|
78
|
+
setActiveFilePath(resolvedFilePath);
|
|
79
|
+
setActiveFileName(fileName);
|
|
80
|
+
updateCode(finalCode);
|
|
81
|
+
// Set language if available
|
|
82
|
+
if (language) {
|
|
83
|
+
setLanguage(language);
|
|
84
|
+
}
|
|
85
|
+
// Set line number/range if provided
|
|
86
|
+
if (lineRange) {
|
|
87
|
+
setActiveLineRange({
|
|
88
|
+
start: lineRange.start,
|
|
89
|
+
end: lineRange.end,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
setActiveLineRange(undefined);
|
|
94
|
+
setActiveLineNumber(undefined);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const element = sectionRef.current;
|
|
100
|
+
if (!element)
|
|
101
|
+
return;
|
|
102
|
+
const observer = new IntersectionObserver((entries) => {
|
|
103
|
+
entries.forEach((entry) => {
|
|
104
|
+
// Trigger when the element enters the middle of the viewport
|
|
105
|
+
if (entry.isIntersecting) {
|
|
106
|
+
focusCode();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}, {
|
|
110
|
+
threshold,
|
|
111
|
+
rootMargin: "-40% 0px -40% 0px", // Trigger when element enters the middle 20% of viewport
|
|
112
|
+
});
|
|
113
|
+
observer.observe(element);
|
|
114
|
+
return () => {
|
|
115
|
+
observer.disconnect();
|
|
116
|
+
};
|
|
117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
|
+
}, [
|
|
119
|
+
filePath,
|
|
120
|
+
lineRange,
|
|
121
|
+
generatedCode,
|
|
122
|
+
fileTree,
|
|
123
|
+
setActiveFilePath,
|
|
124
|
+
setActiveFileName,
|
|
125
|
+
setActiveLineNumber,
|
|
126
|
+
setActiveLineRange,
|
|
127
|
+
updateCode,
|
|
128
|
+
setLanguage,
|
|
129
|
+
filesEdited,
|
|
130
|
+
threshold,
|
|
131
|
+
]);
|
|
132
|
+
// Get the resolved file path for comparison
|
|
133
|
+
// This ensures we compare against the actual file_path from the code structure
|
|
134
|
+
const resolvedFilePathForComparison = useMemo(() => {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
// Search by file_path property first (this is the source of truth)
|
|
137
|
+
const matchingCodeOutput = Object.values(generatedCode).find((output) => output.file_path === filePath);
|
|
138
|
+
if (matchingCodeOutput) {
|
|
139
|
+
return matchingCodeOutput.file_path;
|
|
140
|
+
}
|
|
141
|
+
// Fallback to key lookup
|
|
142
|
+
if ((_a = generatedCode[filePath]) === null || _a === void 0 ? void 0 : _a.file_path) {
|
|
143
|
+
return generatedCode[filePath].file_path;
|
|
144
|
+
}
|
|
145
|
+
// Fallback to fileTree
|
|
146
|
+
try {
|
|
147
|
+
const targetNode = getTargetNode(fileTree, filePath);
|
|
148
|
+
if ((_b = targetNode === null || targetNode === void 0 ? void 0 : targetNode.metadata) === null || _b === void 0 ? void 0 : _b.file_path) {
|
|
149
|
+
return targetNode.metadata.file_path;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (_c) {
|
|
153
|
+
// Ignore errors
|
|
154
|
+
}
|
|
155
|
+
return filePath;
|
|
156
|
+
}, [filePath, generatedCode, fileTree]);
|
|
157
|
+
return (React.createElement("div", { ref: sectionRef, className: `relative transition-colors py-6 cursor-pointer`, onClick: focusCode },
|
|
158
|
+
React.createElement("div", { className: "pointer-events-none absolute inset-x-[-2rem] top-0 border-t border-border" }),
|
|
159
|
+
React.createElement("div", { className: "pointer-events-none absolute inset-x-[-2rem] bottom-0 border-b border-border" }),
|
|
160
|
+
(activeFilePath === resolvedFilePathForComparison ||
|
|
161
|
+
activeFilePath === filePath) &&
|
|
162
|
+
(activeLineRange
|
|
163
|
+
? lineRange &&
|
|
164
|
+
activeLineRange.start === lineRange.start &&
|
|
165
|
+
activeLineRange.end === lineRange.end
|
|
166
|
+
: !lineRange) && (React.createElement("div", { className: "absolute left-0 top-0 h-full w-1", style: {
|
|
167
|
+
backgroundColor: themeColor,
|
|
168
|
+
marginLeft: "-2rem",
|
|
169
|
+
} })),
|
|
170
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
171
|
+
React.createElement("h3", { className: "text-md font-semibold text-gray-900 dark:text-gray-100" }, title)),
|
|
172
|
+
description && (React.createElement("p", { className: "text-sm text-gray-400 mt-3" }, description)),
|
|
173
|
+
children && React.createElement("div", { className: "mt-3" }, children)));
|
|
174
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { createContext, useContext, useState, useCallback, } from "react";
|
|
3
|
+
import { CodeLanguage, } from "../types/ide-types";
|
|
4
|
+
const GuardianContext = createContext(undefined);
|
|
5
|
+
export function GuardianProvider({ children }) {
|
|
6
|
+
const [previewUrl, setPreviewUrlState] = useState("");
|
|
7
|
+
const [isBrowserMaximized, setIsBrowserMaximized] = useState(false);
|
|
8
|
+
const [currentView, setCurrentViewState] = useState("preview");
|
|
9
|
+
// Code state
|
|
10
|
+
const [code, setCode] = useState("");
|
|
11
|
+
const [language, setLanguage] = useState(CodeLanguage.TEXT);
|
|
12
|
+
const [activeFileName, setActiveFileName] = useState("");
|
|
13
|
+
const [activeFilePath, setActiveFilePath] = useState("");
|
|
14
|
+
const [activeLineNumber, setActiveLineNumber] = useState(undefined);
|
|
15
|
+
const [activeLineRange, setActiveLineRange] = useState(undefined);
|
|
16
|
+
const [isGeneratingCode, setIsGeneratingCode] = useState(false);
|
|
17
|
+
// Generated code and file tree
|
|
18
|
+
const [generatedCode, setGeneratedCodeState] = useState({});
|
|
19
|
+
const [fileTree, setFileTree] = useState({
|
|
20
|
+
name: "root",
|
|
21
|
+
children: {},
|
|
22
|
+
type: "folder",
|
|
23
|
+
metadata: {
|
|
24
|
+
response_type: "CodeOutput",
|
|
25
|
+
file_path: "",
|
|
26
|
+
code_file_name: "",
|
|
27
|
+
code_language: CodeLanguage.TEXT,
|
|
28
|
+
dependencies_to_install: [],
|
|
29
|
+
framework: null,
|
|
30
|
+
full_code: "",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// Dependencies and edited files
|
|
34
|
+
const [activeDependenciesToInstall, setActiveDependenciesToInstall] = useState([]);
|
|
35
|
+
const [filesEdited, setFilesEdited] = useState({});
|
|
36
|
+
const setPreviewUrl = useCallback((url) => {
|
|
37
|
+
setPreviewUrlState(url);
|
|
38
|
+
}, []);
|
|
39
|
+
const setIsBrowserMaximizedState = useCallback((maximized) => {
|
|
40
|
+
setIsBrowserMaximized(maximized);
|
|
41
|
+
}, []);
|
|
42
|
+
const setCurrentView = useCallback((view) => {
|
|
43
|
+
setCurrentViewState(view);
|
|
44
|
+
}, []);
|
|
45
|
+
const updateCode = useCallback((newCode) => {
|
|
46
|
+
setCode(newCode);
|
|
47
|
+
}, []);
|
|
48
|
+
const setGeneratedCode = useCallback((generatedCode) => {
|
|
49
|
+
if (typeof generatedCode === "function") {
|
|
50
|
+
setGeneratedCodeState((prev) => generatedCode(prev));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
setGeneratedCodeState(generatedCode);
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
const value = {
|
|
57
|
+
previewUrl,
|
|
58
|
+
setPreviewUrl,
|
|
59
|
+
isBrowserMaximized,
|
|
60
|
+
setIsBrowserMaximized: setIsBrowserMaximizedState,
|
|
61
|
+
currentView,
|
|
62
|
+
setCurrentView,
|
|
63
|
+
code,
|
|
64
|
+
updateCode,
|
|
65
|
+
language,
|
|
66
|
+
setLanguage,
|
|
67
|
+
activeFileName,
|
|
68
|
+
setActiveFileName,
|
|
69
|
+
activeFilePath,
|
|
70
|
+
setActiveFilePath,
|
|
71
|
+
activeLineNumber,
|
|
72
|
+
setActiveLineNumber,
|
|
73
|
+
activeLineRange,
|
|
74
|
+
setActiveLineRange,
|
|
75
|
+
isGeneratingCode,
|
|
76
|
+
setIsGeneratingCode,
|
|
77
|
+
generatedCode,
|
|
78
|
+
setGeneratedCode,
|
|
79
|
+
fileTree,
|
|
80
|
+
setFileTree,
|
|
81
|
+
activeDependenciesToInstall,
|
|
82
|
+
setActiveDependenciesToInstall,
|
|
83
|
+
filesEdited,
|
|
84
|
+
setFilesEdited,
|
|
85
|
+
};
|
|
86
|
+
return (React.createElement(GuardianContext.Provider, { value: value }, children));
|
|
87
|
+
}
|
|
88
|
+
export function useGuardianContext() {
|
|
89
|
+
const context = useContext(GuardianContext);
|
|
90
|
+
if (!context) {
|
|
91
|
+
throw new Error("useGuardianContext must be used within a GuardianProvider");
|
|
92
|
+
}
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { createContext, useContext, useState } from "react";
|
|
3
|
+
const VmContext = createContext(undefined);
|
|
4
|
+
export const VmProvider = ({ children }) => {
|
|
5
|
+
const [vmUrlMap, setVmUrlMap] = useState({});
|
|
6
|
+
const setVmUrl = (uid, url) => {
|
|
7
|
+
setVmUrlMap((prev) => (Object.assign(Object.assign({}, prev), { [uid]: url })));
|
|
8
|
+
};
|
|
9
|
+
const getVmUrl = (uid) => {
|
|
10
|
+
const value = vmUrlMap[uid];
|
|
11
|
+
// Return undefined if it's an error marker
|
|
12
|
+
return value === "error" ? undefined : value;
|
|
13
|
+
};
|
|
14
|
+
const setVmError = (uid) => {
|
|
15
|
+
setVmUrlMap((prev) => (Object.assign(Object.assign({}, prev), { [uid]: "error" })));
|
|
16
|
+
};
|
|
17
|
+
const hasError = (uid) => {
|
|
18
|
+
return vmUrlMap[uid] === "error";
|
|
19
|
+
};
|
|
20
|
+
return (React.createElement(VmContext.Provider, { value: { vmUrlMap, setVmUrl, getVmUrl, setVmError, hasError } }, children));
|
|
21
|
+
};
|
|
22
|
+
export const useVmContext = () => {
|
|
23
|
+
const context = useContext(VmContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error("useVmContext must be used within a VmProvider");
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Markdown } from "./ui/markdown";
|
|
4
|
+
export default function DefaultGuideView({ themeColor, }) {
|
|
5
|
+
return (React.createElement("div", { className: "h-full flex flex-col" },
|
|
6
|
+
React.createElement("div", { className: "flex-1 px-8 py-6" },
|
|
7
|
+
React.createElement(Markdown, { className: "text-gray-100", themeColor: themeColor }, `# Guardian Guide
|
|
8
|
+
|
|
9
|
+
Learn how to use Guardian to build and deploy your application with confidence.
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
Guardian provides a comprehensive development environment with real-time preview, code editing, and AI assistance.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- **Live Preview**: See your changes instantly as you code
|
|
18
|
+
- **Code Editor**: Edit files directly in the browser
|
|
19
|
+
- **AI Assistant**: Get help with your code using the Ask AI feature
|
|
20
|
+
|
|
21
|
+
### Navigation
|
|
22
|
+
|
|
23
|
+
Use the tabs at the top to switch between:
|
|
24
|
+
- **Guide**: View this documentation
|
|
25
|
+
- **Ask AI**: Get AI-powered assistance with your code
|
|
26
|
+
|
|
27
|
+
### Code Editing
|
|
28
|
+
|
|
29
|
+
Select files from the file selector to view and edit them. The editor supports syntax highlighting and automatic formatting.
|
|
30
|
+
|
|
31
|
+
### Preview
|
|
32
|
+
|
|
33
|
+
The preview panel shows your running application. Use the reload button to refresh the preview when needed.`))));
|
|
34
|
+
}
|