@sampleapp.ai/sdk 1.0.36 → 1.0.37

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.
Files changed (82) hide show
  1. package/dist/components/sandbox/Sandbox.js +5 -0
  2. package/dist/components/sandbox/api.js +3 -2
  3. package/dist/components/sandbox/guardian/guardian-component.js +7 -38
  4. package/dist/components/sandbox/guardian/guardian-playground.js +1 -1
  5. package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +12 -10
  6. package/dist/components/sandbox/guardian/index.js +1 -1
  7. package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +1 -1
  8. package/dist/components/sandbox/guardian/ui/markdown.js +2 -2
  9. package/dist/components/sandbox/guardian/utils.js +0 -18
  10. package/dist/components/sandbox/index.js +1 -1
  11. package/dist/components/sandbox/sandbox-home/SandboxHome.js +5 -0
  12. package/dist/index.d.ts +8 -40
  13. package/dist/index.es.js +830 -867
  14. package/dist/index.js +1 -1
  15. package/dist/lib/api-client.js +9 -26
  16. package/package.json +1 -1
  17. package/dist/components/guardian/app-layout-no-sidebar.js +0 -8
  18. package/dist/components/guardian/ask-ai-view.js +0 -249
  19. package/dist/components/guardian/code-focus-section.d.ts +0 -41
  20. package/dist/components/guardian/code-focus-section.js +0 -174
  21. package/dist/components/guardian/context/guardian-context.js +0 -94
  22. package/dist/components/guardian/context/vm-context.js +0 -28
  23. package/dist/components/guardian/default-guide-view.js +0 -34
  24. package/dist/components/guardian/demo/guardian-demo.js +0 -35
  25. package/dist/components/guardian/demo/left-view/toggle.js +0 -28
  26. package/dist/components/guardian/demo/left-view.js +0 -49
  27. package/dist/components/guardian/guardian-component.js +0 -79
  28. package/dist/components/guardian/guardian-demo.js +0 -35
  29. package/dist/components/guardian/guardian-home.d.ts +0 -4
  30. package/dist/components/guardian/guardian-home.js +0 -61
  31. package/dist/components/guardian/guardian-playground.js +0 -45
  32. package/dist/components/guardian/guardian-style-wrapper.js +0 -29
  33. package/dist/components/guardian/guardian-upload-spec.d.ts +0 -14
  34. package/dist/components/guardian/guardian-upload-spec.js +0 -160
  35. package/dist/components/guardian/header/glassmorphic-combobox.d.ts +0 -15
  36. package/dist/components/guardian/header/glassmorphic-combobox.js +0 -30
  37. package/dist/components/guardian/header.js +0 -61
  38. package/dist/components/guardian/hooks/use-frame-messages.js +0 -65
  39. package/dist/components/guardian/hooks/use-frame-params.js +0 -44
  40. package/dist/components/guardian/hooks/use-sandbox-url-loader.js +0 -101
  41. package/dist/components/guardian/ide/browser.js +0 -538
  42. package/dist/components/guardian/index.js +0 -8
  43. package/dist/components/guardian/layout/app-layout-no-sidebar.js +0 -8
  44. package/dist/components/guardian/layout/header/glassmorphic-combobox.js +0 -48
  45. package/dist/components/guardian/layout/header.js +0 -63
  46. package/dist/components/guardian/right-view/code-view.js +0 -56
  47. package/dist/components/guardian/right-view/pill-file-selector.js +0 -233
  48. package/dist/components/guardian/right-view/preview-control-bar.js +0 -25
  49. package/dist/components/guardian/right-view/right-panel-view.js +0 -38
  50. package/dist/components/guardian/right-view/right-top-down-view.js +0 -289
  51. package/dist/components/guardian/right-view/right-view.js +0 -28
  52. package/dist/components/guardian/right-view/simplified-editor.js +0 -234
  53. package/dist/components/guardian/types/ide-types.js +0 -162
  54. package/dist/components/guardian/types.js +0 -3
  55. package/dist/components/guardian/ui/ai-loader.js +0 -48
  56. package/dist/components/guardian/ui/badge.js +0 -24
  57. package/dist/components/guardian/ui/button.js +0 -45
  58. package/dist/components/guardian/ui/command.js +0 -63
  59. package/dist/components/guardian/ui/console-with-app.js +0 -17
  60. package/dist/components/guardian/ui/dialog.js +0 -57
  61. package/dist/components/guardian/ui/dropdown-menu.js +0 -82
  62. package/dist/components/guardian/ui/markdown.js +0 -57
  63. package/dist/components/guardian/ui/popover.js +0 -25
  64. package/dist/components/guardian/ui/tooltip.js +0 -25
  65. package/dist/components/guardian/utils.js +0 -88
  66. package/dist/components/guardian/zip-to-codebase.js +0 -246
  67. package/dist/components/guardian/zip-to-filetree.js +0 -284
  68. package/dist/components/sandbox/SandboxHome.js +0 -141
  69. package/dist/components/sandbox/guardian/guardian-demo.js +0 -35
  70. package/dist/components/sandbox/guardian/guardian-home.d.ts +0 -4
  71. package/dist/components/sandbox/guardian/guardian-home.js +0 -61
  72. package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +0 -14
  73. package/dist/components/sandbox/guardian/guardian-upload-spec.js +0 -160
  74. package/dist/components/sandbox/guardian/ui/theme-color-context.d.ts +0 -6
  75. package/dist/components/sandbox/sandbox-control-bar.js +0 -91
  76. package/dist/components/sandbox/sandbox-header.js +0 -52
  77. package/dist/components/sandbox/sandbox-left-panel.js +0 -248
  78. package/dist/components/sandbox/sandbox-loading.js +0 -48
  79. package/dist/components/sandbox/sandbox-right-panel.js +0 -247
  80. package/dist/components/sandbox.js +0 -32
  81. package/dist/lib/api-client.example.js +0 -60
  82. package/dist/lib/ssr-safe-decode-entity.js +0 -16
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ export { ChatButton, ModalSearchAndChat, ChatBar, TailwindExample, SandboxHome }
16
16
  // Export Sandbox component and types
17
17
  export { Sandbox } from "./components/sandbox";
18
18
  // Export Guardian components (now nested under sandbox)
19
- export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, createSandboxUrlConfigs, } from "./components/sandbox/guardian";
19
+ export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, } from "./components/sandbox/guardian";
20
20
  // Export themes
21
21
  export { themes, getTheme, DEFAULT_THEME } from "./themes";
22
22
  // SDK class for loadScript programmatic usage
@@ -72,8 +72,7 @@ class ApiClient {
72
72
  getByPlayground: async (playgroundUid, skip = 0, limit = 100) => {
73
73
  return this.request(["sandbox-content", "playground", playgroundUid, "public"], {
74
74
  method: "GET",
75
- }, false, // Public endpoint, no API key required
76
- { skip, limit });
75
+ }, { skip, limit });
77
76
  },
78
77
  };
79
78
  /**
@@ -88,7 +87,6 @@ class ApiClient {
88
87
  *
89
88
  * @param request - The start sandbox request (env and chatUid)
90
89
  * @returns The sandbox response with container URL
91
- * @throws Error if apiKey is not set in client config
92
90
  *
93
91
  * @example
94
92
  * ```typescript
@@ -100,9 +98,6 @@ class ApiClient {
100
98
  * ```
101
99
  */
102
100
  startSandbox: async (request) => {
103
- if (!this.apiKey) {
104
- throw new Error("API key is required. Set apiKey in ApiClientConfig when creating the client.");
105
- }
106
101
  return this.request(["sdk", "start-sandbox"], {
107
102
  method: "POST",
108
103
  body: JSON.stringify(request),
@@ -116,12 +111,8 @@ class ApiClient {
116
111
  *
117
112
  * @param sandboxId - The sandbox content UID
118
113
  * @returns Promise that resolves when download is initiated
119
- * @throws Error if apiKey is not set in client config
120
114
  */
121
115
  downloadCode: async (sandboxId) => {
122
- if (!this.apiKey) {
123
- throw new Error("API key is required. Set apiKey in ApiClientConfig when creating the client.");
124
- }
125
116
  const url = `${this.baseUrl}/api/v1/sdk/download-code?sandbox_id=${encodeURIComponent(sandboxId)}`;
126
117
  // Make authenticated fetch request with Authorization header
127
118
  const response = await fetch(url, {
@@ -155,7 +146,6 @@ class ApiClient {
155
146
  *
156
147
  * @param request - The start VM request with url, optional mode, and optional resolution
157
148
  * @returns The sandbox response with sandboxId and vncUrl (if mode=json)
158
- * @throws Error if apiKey is not set in client config
159
149
  *
160
150
  * @example
161
151
  * ```typescript
@@ -168,9 +158,6 @@ class ApiClient {
168
158
  * ```
169
159
  */
170
160
  startVm: async (request) => {
171
- if (!this.apiKey) {
172
- throw new Error("API key is required. Set apiKey in ApiClientConfig when creating the client.");
173
- }
174
161
  const queryParams = {
175
162
  url: request.url,
176
163
  mode: request.mode || "json",
@@ -184,19 +171,21 @@ class ApiClient {
184
171
  }
185
172
  return this.request(["sdk", "start-vm"], {
186
173
  method: "GET",
187
- }, false, // API key is sent as Bearer token in Authorization header
188
- queryParams);
174
+ }, queryParams);
189
175
  },
190
176
  };
177
+ if (!config.apiKey || config.apiKey.trim() === "") {
178
+ throw new Error("apiKey is required when creating ApiClient");
179
+ }
191
180
  const defaultBaseUrl = getBaseUrl();
192
181
  this.baseUrl = defaultBaseUrl.replace(/\/$/, ""); // Remove trailing slash
193
182
  this.apiKey = config.apiKey;
194
183
  }
195
184
  /**
196
185
  * Makes a request to the API
197
- * Automatically includes Authorization: Bearer {apiKey} header if apiKey is set in config
186
+ * Automatically includes Authorization: Bearer {apiKey} header
198
187
  */
199
- async request(endpoint, options = {}, useApiKeyHeader = false, queryParams) {
188
+ async request(endpoint, options = {}, queryParams) {
200
189
  let url = `${this.baseUrl}/api/v1/${endpoint.join("/")}`;
201
190
  if (queryParams) {
202
191
  const params = new URLSearchParams();
@@ -212,14 +201,8 @@ class ApiClient {
212
201
  }
213
202
  }
214
203
  const headers = Object.assign({ "Content-Type": "application/json" }, options.headers);
215
- if (this.apiKey) {
216
- if (useApiKeyHeader) {
217
- headers["x-api-key"] = this.apiKey;
218
- }
219
- else {
220
- headers["Authorization"] = `Bearer ${this.apiKey}`;
221
- }
222
- }
204
+ // Always add Bearer token
205
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
223
206
  const response = await fetch(url, Object.assign(Object.assign({}, options), { headers }));
224
207
  if (!response.ok) {
225
208
  const errorText = await response.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sampleapp.ai/sdk",
3
- "version": "1.0.36",
3
+ "version": "1.0.37",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for your components",
6
6
  "main": "./dist/index.umd.js",
@@ -1,8 +0,0 @@
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
- }
@@ -1,249 +0,0 @@
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
- }
@@ -1,41 +0,0 @@
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;
@@ -1,174 +0,0 @@
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
- }