fixdog 0.0.1
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 +478 -0
- package/USAGE.md +77 -0
- package/dist/client/index.d.mts +110 -0
- package/dist/client/index.d.ts +110 -0
- package/dist/client/index.js +1601 -0
- package/dist/client/index.mjs +1582 -0
- package/dist/client/init.d.mts +67 -0
- package/dist/client/init.d.ts +67 -0
- package/dist/client/init.js +1609 -0
- package/dist/client/init.mjs +1593 -0
- package/dist/index.d.mts +158 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +1635 -0
- package/dist/index.mjs +1606 -0
- package/package.json +57 -0
- package/src/api/client.ts +141 -0
- package/src/client/index.ts +75 -0
- package/src/client/init.tsx +78 -0
- package/src/components/ConversationalInputReact.tsx +406 -0
- package/src/components/ElementInfoDisplayReact.tsx +84 -0
- package/src/components/UiDogSidebarReact.tsx +49 -0
- package/src/element-detector.ts +186 -0
- package/src/index.ts +228 -0
- package/src/instrument.ts +171 -0
- package/src/sidebar-initializer.ts +171 -0
- package/src/source-resolver.ts +121 -0
- package/src/styles/sidebarStyles.ts +597 -0
- package/src/types/css.d.ts +9 -0
- package/src/types/sidebar.ts +56 -0
- package/src/types.ts +119 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +40 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
sendChatPrompt,
|
|
4
|
+
ChatRequest,
|
|
5
|
+
ChatResponse,
|
|
6
|
+
sendToDeveloper,
|
|
7
|
+
SendToDevResponse,
|
|
8
|
+
} from "../api/client";
|
|
9
|
+
import { ElementInfo } from "../types/sidebar";
|
|
10
|
+
|
|
11
|
+
interface Message {
|
|
12
|
+
id: string;
|
|
13
|
+
role: "user" | "assistant";
|
|
14
|
+
content: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
isLoading?: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ConversationalInputProps {
|
|
21
|
+
elementInfo: ElementInfo;
|
|
22
|
+
editorUrl: string;
|
|
23
|
+
apiEndpoint: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ConversationalInputReact(props: ConversationalInputProps) {
|
|
27
|
+
const [userInput, setUserInput] = useState("");
|
|
28
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
29
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
30
|
+
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
|
31
|
+
const [isCreatingPR, setIsCreatingPR] = useState(false);
|
|
32
|
+
const [prUrl, setPrUrl] = useState<string | null>(null);
|
|
33
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
34
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
textareaRef.current?.focus();
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
// Auto-resize textarea
|
|
42
|
+
const textarea = textareaRef.current;
|
|
43
|
+
if (textarea) {
|
|
44
|
+
textarea.style.height = "auto";
|
|
45
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
|
|
46
|
+
}
|
|
47
|
+
}, [userInput]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
// Auto-scroll to bottom when new messages are added
|
|
51
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
52
|
+
}, [messages]);
|
|
53
|
+
|
|
54
|
+
const handleSubmit = async () => {
|
|
55
|
+
const input = userInput.trim();
|
|
56
|
+
if (!input || isLoading) return;
|
|
57
|
+
|
|
58
|
+
// Add user message to history
|
|
59
|
+
const userMessage: Message = {
|
|
60
|
+
id: `user-${Date.now()}`,
|
|
61
|
+
role: "user",
|
|
62
|
+
content: input,
|
|
63
|
+
timestamp: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
67
|
+
setUserInput("");
|
|
68
|
+
setIsLoading(true);
|
|
69
|
+
|
|
70
|
+
// Add loading message
|
|
71
|
+
const loadingMessageId = `loading-${Date.now()}`;
|
|
72
|
+
const loadingMessage: Message = {
|
|
73
|
+
id: loadingMessageId,
|
|
74
|
+
role: "assistant",
|
|
75
|
+
content: "",
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
isLoading: true,
|
|
78
|
+
};
|
|
79
|
+
setMessages((prev) => [...prev, loadingMessage]);
|
|
80
|
+
|
|
81
|
+
const request: ChatRequest = {
|
|
82
|
+
prompt: `The component selected by the user: ${props.editorUrl}
|
|
83
|
+
User request: ${input}
|
|
84
|
+
Update multiple files (if necessary) to achieve the user's request.`,
|
|
85
|
+
sessionId: sessionId,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result: ChatResponse = await sendChatPrompt(
|
|
90
|
+
request,
|
|
91
|
+
props.apiEndpoint
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Save sessionId from response if provided
|
|
95
|
+
if (result.sessionId && !sessionId) {
|
|
96
|
+
setSessionId(result.sessionId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Remove loading message and add response
|
|
100
|
+
setMessages((prev) => {
|
|
101
|
+
const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
|
|
102
|
+
const assistantMessage: Message = {
|
|
103
|
+
id: `assistant-${Date.now()}`,
|
|
104
|
+
role: "assistant",
|
|
105
|
+
content: result.ok
|
|
106
|
+
? result.message
|
|
107
|
+
: result.error || "Request failed",
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
error: !result.ok ? result.error : undefined,
|
|
110
|
+
};
|
|
111
|
+
return [...filtered, assistantMessage];
|
|
112
|
+
});
|
|
113
|
+
} catch (err) {
|
|
114
|
+
// Remove loading message and add error
|
|
115
|
+
setMessages((prev) => {
|
|
116
|
+
const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
|
|
117
|
+
const errorMessage: Message = {
|
|
118
|
+
id: `error-${Date.now()}`,
|
|
119
|
+
role: "assistant",
|
|
120
|
+
content:
|
|
121
|
+
err instanceof Error ? err.message : "Unknown error occurred",
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
error: err instanceof Error ? err.message : "Unknown error occurred",
|
|
124
|
+
};
|
|
125
|
+
return [...filtered, errorMessage];
|
|
126
|
+
});
|
|
127
|
+
} finally {
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
133
|
+
// Submit on Cmd/Ctrl + Enter
|
|
134
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
handleSubmit();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleSendToDeveloper = async () => {
|
|
141
|
+
if (!sessionId || isCreatingPR) return;
|
|
142
|
+
|
|
143
|
+
setIsCreatingPR(true);
|
|
144
|
+
setPrUrl(null);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const result: SendToDevResponse = await sendToDeveloper(
|
|
148
|
+
{ sessionId },
|
|
149
|
+
props.apiEndpoint
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (result.ok && result.prUrl) {
|
|
153
|
+
setPrUrl(result.prUrl);
|
|
154
|
+
// Add success message
|
|
155
|
+
const successMessage: Message = {
|
|
156
|
+
id: `pr-success-${Date.now()}`,
|
|
157
|
+
role: "assistant",
|
|
158
|
+
content: `Pull request created successfully!`,
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
};
|
|
161
|
+
setMessages((prev) => [...prev, successMessage]);
|
|
162
|
+
} else {
|
|
163
|
+
// Add error message
|
|
164
|
+
const errorMessage: Message = {
|
|
165
|
+
id: `pr-error-${Date.now()}`,
|
|
166
|
+
role: "assistant",
|
|
167
|
+
content: result.error || "Failed to create pull request",
|
|
168
|
+
timestamp: Date.now(),
|
|
169
|
+
error: result.error || "Failed to create pull request",
|
|
170
|
+
};
|
|
171
|
+
setMessages((prev) => [...prev, errorMessage]);
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
const errorMessage: Message = {
|
|
175
|
+
id: `pr-error-${Date.now()}`,
|
|
176
|
+
role: "assistant",
|
|
177
|
+
content: err instanceof Error ? err.message : "Unknown error occurred",
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
error: err instanceof Error ? err.message : "Unknown error occurred",
|
|
180
|
+
};
|
|
181
|
+
setMessages((prev) => [...prev, errorMessage]);
|
|
182
|
+
} finally {
|
|
183
|
+
setIsCreatingPR(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleRetry = async (messageId: string) => {
|
|
188
|
+
// Find the user message that corresponds to this error
|
|
189
|
+
const errorIndex = messages.findIndex((msg) => msg.id === messageId);
|
|
190
|
+
if (errorIndex === -1) return;
|
|
191
|
+
|
|
192
|
+
// Find the previous user message
|
|
193
|
+
let userMessageIndex = -1;
|
|
194
|
+
for (let i = errorIndex - 1; i >= 0; i--) {
|
|
195
|
+
if (messages[i].role === "user") {
|
|
196
|
+
userMessageIndex = i;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (userMessageIndex === -1) return;
|
|
202
|
+
|
|
203
|
+
const userMessage = messages[userMessageIndex];
|
|
204
|
+
const input = userMessage.content;
|
|
205
|
+
|
|
206
|
+
// Remove error message
|
|
207
|
+
setMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
|
208
|
+
|
|
209
|
+
setIsLoading(true);
|
|
210
|
+
|
|
211
|
+
// Add loading message
|
|
212
|
+
const loadingMessageId = `loading-${Date.now()}`;
|
|
213
|
+
const loadingMessage: Message = {
|
|
214
|
+
id: loadingMessageId,
|
|
215
|
+
role: "assistant",
|
|
216
|
+
content: "",
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
isLoading: true,
|
|
219
|
+
};
|
|
220
|
+
setMessages((prev) => [...prev, loadingMessage]);
|
|
221
|
+
|
|
222
|
+
const request: ChatRequest = {
|
|
223
|
+
prompt: `The component selected by the user: ${props.editorUrl}
|
|
224
|
+
User request: ${input}
|
|
225
|
+
Update multiple files (if necessary) to achieve the user's request.`,
|
|
226
|
+
sessionId: sessionId,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const result: ChatResponse = await sendChatPrompt(
|
|
231
|
+
request,
|
|
232
|
+
props.apiEndpoint
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Save sessionId from response if provided
|
|
236
|
+
if (result.sessionId && !sessionId) {
|
|
237
|
+
setSessionId(result.sessionId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Remove loading message and add response
|
|
241
|
+
setMessages((prev) => {
|
|
242
|
+
const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
|
|
243
|
+
const assistantMessage: Message = {
|
|
244
|
+
id: `assistant-${Date.now()}`,
|
|
245
|
+
role: "assistant",
|
|
246
|
+
content: result.ok
|
|
247
|
+
? result.message
|
|
248
|
+
: result.error || "Request failed",
|
|
249
|
+
timestamp: Date.now(),
|
|
250
|
+
error: !result.ok ? result.error : undefined,
|
|
251
|
+
};
|
|
252
|
+
return [...filtered, assistantMessage];
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
// Remove loading message and add error
|
|
256
|
+
setMessages((prev) => {
|
|
257
|
+
const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
|
|
258
|
+
const errorMessage: Message = {
|
|
259
|
+
id: `error-${Date.now()}`,
|
|
260
|
+
role: "assistant",
|
|
261
|
+
content:
|
|
262
|
+
err instanceof Error ? err.message : "Unknown error occurred",
|
|
263
|
+
timestamp: Date.now(),
|
|
264
|
+
error: err instanceof Error ? err.message : "Unknown error occurred",
|
|
265
|
+
};
|
|
266
|
+
return [...filtered, errorMessage];
|
|
267
|
+
});
|
|
268
|
+
} finally {
|
|
269
|
+
setIsLoading(false);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<>
|
|
275
|
+
{/* Messages Area */}
|
|
276
|
+
<div className="uidog-messages-container">
|
|
277
|
+
{messages.length === 0 && (
|
|
278
|
+
<div className="uidog-empty-state">
|
|
279
|
+
<div className="uidog-empty-state-text">
|
|
280
|
+
What changes would you like to make?
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
{messages.map((message) => (
|
|
285
|
+
<div
|
|
286
|
+
key={message.id}
|
|
287
|
+
className={`uidog-message uidog-message-${message.role}`}
|
|
288
|
+
>
|
|
289
|
+
<div className="uidog-message-bubble">
|
|
290
|
+
{message.isLoading ? (
|
|
291
|
+
<div className="uidog-message-loading">
|
|
292
|
+
<div className="uidog-spinner"></div>
|
|
293
|
+
<span>Processing your request...</span>
|
|
294
|
+
</div>
|
|
295
|
+
) : message.error ? (
|
|
296
|
+
<>
|
|
297
|
+
<div className="uidog-message-content uidog-message-error">
|
|
298
|
+
{message.content}
|
|
299
|
+
</div>
|
|
300
|
+
<button
|
|
301
|
+
className="uidog-retry-btn"
|
|
302
|
+
onClick={() => handleRetry(message.id)}
|
|
303
|
+
>
|
|
304
|
+
Retry
|
|
305
|
+
</button>
|
|
306
|
+
</>
|
|
307
|
+
) : (
|
|
308
|
+
<div className="uidog-message-content">{message.content}</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
))}
|
|
313
|
+
<div ref={messagesEndRef} />
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
{/* Fixed Input Footer */}
|
|
317
|
+
<div className="uidog-input-footer-fixed">
|
|
318
|
+
<div className="uidog-input-wrapper">
|
|
319
|
+
<textarea
|
|
320
|
+
id="uidog-textarea"
|
|
321
|
+
ref={textareaRef}
|
|
322
|
+
className="uidog-textarea"
|
|
323
|
+
placeholder="Describe the changes you want..."
|
|
324
|
+
value={userInput}
|
|
325
|
+
onChange={(e) => setUserInput(e.target.value)}
|
|
326
|
+
onKeyDown={handleKeyDown}
|
|
327
|
+
disabled={isLoading}
|
|
328
|
+
/>
|
|
329
|
+
<button
|
|
330
|
+
className="uidog-submit-btn"
|
|
331
|
+
onClick={handleSubmit}
|
|
332
|
+
disabled={!userInput.trim() || isLoading}
|
|
333
|
+
title="Send message (Cmd/Ctrl + Enter)"
|
|
334
|
+
>
|
|
335
|
+
{isLoading ? (
|
|
336
|
+
<div className="uidog-spinner-small"></div>
|
|
337
|
+
) : (
|
|
338
|
+
<svg
|
|
339
|
+
width="16"
|
|
340
|
+
height="16"
|
|
341
|
+
viewBox="0 0 16 16"
|
|
342
|
+
fill="none"
|
|
343
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
344
|
+
>
|
|
345
|
+
<path
|
|
346
|
+
d="M8 2L8 14M8 2L2 8M8 2L14 8"
|
|
347
|
+
stroke="currentColor"
|
|
348
|
+
strokeWidth="2"
|
|
349
|
+
strokeLinecap="round"
|
|
350
|
+
strokeLinejoin="round"
|
|
351
|
+
/>
|
|
352
|
+
</svg>
|
|
353
|
+
)}
|
|
354
|
+
</button>
|
|
355
|
+
</div>
|
|
356
|
+
<div className="uidog-input-hint">Cmd/Ctrl + Enter to submit</div>
|
|
357
|
+
{sessionId && (
|
|
358
|
+
<div className="uidog-send-to-dev-section">
|
|
359
|
+
<button
|
|
360
|
+
className="uidog-send-to-dev-btn"
|
|
361
|
+
onClick={handleSendToDeveloper}
|
|
362
|
+
disabled={isCreatingPR || isLoading}
|
|
363
|
+
title="Create PR with changes"
|
|
364
|
+
>
|
|
365
|
+
{isCreatingPR ? (
|
|
366
|
+
<>
|
|
367
|
+
<div className="uidog-spinner-small"></div>
|
|
368
|
+
<span>Creating PR...</span>
|
|
369
|
+
</>
|
|
370
|
+
) : (
|
|
371
|
+
<>
|
|
372
|
+
<svg
|
|
373
|
+
width="16"
|
|
374
|
+
height="16"
|
|
375
|
+
viewBox="0 0 16 16"
|
|
376
|
+
fill="none"
|
|
377
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
378
|
+
>
|
|
379
|
+
<path
|
|
380
|
+
d="M8 1L8 15M8 1L1 8M8 1L15 8"
|
|
381
|
+
stroke="currentColor"
|
|
382
|
+
strokeWidth="2"
|
|
383
|
+
strokeLinecap="round"
|
|
384
|
+
strokeLinejoin="round"
|
|
385
|
+
/>
|
|
386
|
+
</svg>
|
|
387
|
+
<span>Submit to Developer</span>
|
|
388
|
+
</>
|
|
389
|
+
)}
|
|
390
|
+
</button>
|
|
391
|
+
{prUrl && (
|
|
392
|
+
<a
|
|
393
|
+
href={prUrl}
|
|
394
|
+
target="_blank"
|
|
395
|
+
rel="noopener noreferrer"
|
|
396
|
+
className="uidog-pr-link"
|
|
397
|
+
>
|
|
398
|
+
View PR →
|
|
399
|
+
</a>
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
</>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ElementInfo } from "../types";
|
|
2
|
+
|
|
3
|
+
interface ElementInfoDisplayProps {
|
|
4
|
+
elementInfo: ElementInfo;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ElementInfoDisplayReact(props: ElementInfoDisplayProps) {
|
|
9
|
+
const isDomSnapshot = props.elementInfo.kind === "dom";
|
|
10
|
+
|
|
11
|
+
if (isDomSnapshot) {
|
|
12
|
+
const dom = props.elementInfo.domSnapshot;
|
|
13
|
+
const outerHTML = dom?.outerHTML || "No HTML available";
|
|
14
|
+
const text = dom?.text || "";
|
|
15
|
+
const attributes = dom?.attributes || {};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="uidog-element-info">
|
|
19
|
+
<div className="uidog-element-info-content">
|
|
20
|
+
<div className="uidog-file-location">
|
|
21
|
+
Server-rendered DOM (no source available)
|
|
22
|
+
</div>
|
|
23
|
+
<button
|
|
24
|
+
className="uidog-close-btn"
|
|
25
|
+
onClick={props.onClose}
|
|
26
|
+
title="Close sidebar (ESC)"
|
|
27
|
+
aria-label="Close sidebar"
|
|
28
|
+
>
|
|
29
|
+
×
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="uidog-dom-snapshot">
|
|
34
|
+
<div className="uidog-dom-label">outerHTML (trimmed):</div>
|
|
35
|
+
<pre className="uidog-dom-snippet">{outerHTML}</pre>
|
|
36
|
+
|
|
37
|
+
{text && (
|
|
38
|
+
<>
|
|
39
|
+
<div className="uidog-dom-label">textContent (trimmed):</div>
|
|
40
|
+
<pre className="uidog-dom-snippet">{text}</pre>
|
|
41
|
+
</>
|
|
42
|
+
)}
|
|
43
|
+
|
|
44
|
+
{Object.keys(attributes).length > 0 && (
|
|
45
|
+
<div className="uidog-dom-attributes">
|
|
46
|
+
<div className="uidog-dom-label">attributes:</div>
|
|
47
|
+
<ul>
|
|
48
|
+
{Object.entries(attributes).map(([key, value]) => (
|
|
49
|
+
<li key={key}>
|
|
50
|
+
<code>{key}</code>=<code>{value}</code>
|
|
51
|
+
</li>
|
|
52
|
+
))}
|
|
53
|
+
</ul>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<div className="uidog-dom-hint">
|
|
58
|
+
To see file/line info, render this DOM through a small client
|
|
59
|
+
boundary.
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fileName = props.elementInfo.filePath?.split("/").pop() || "";
|
|
67
|
+
const fileLocation = `Selected element at ${fileName}`;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="uidog-element-info">
|
|
71
|
+
<div className="uidog-element-info-content">
|
|
72
|
+
<span className="uidog-file-location">{fileLocation}</span>
|
|
73
|
+
<button
|
|
74
|
+
className="uidog-close-btn"
|
|
75
|
+
onClick={props.onClose}
|
|
76
|
+
title="Close sidebar (ESC)"
|
|
77
|
+
aria-label="Close sidebar"
|
|
78
|
+
>
|
|
79
|
+
×
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import type { ElementInfo } from "../types";
|
|
3
|
+
import { ElementInfoDisplayReact } from "./ElementInfoDisplayReact";
|
|
4
|
+
import { ConversationalInputReact } from "./ConversationalInputReact";
|
|
5
|
+
|
|
6
|
+
interface UiDogSidebarProps {
|
|
7
|
+
elementInfo: ElementInfo;
|
|
8
|
+
editorUrl: string;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
apiEndpoint: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function UiDogSidebarReact(props: UiDogSidebarProps) {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleEscapeKey = (e: KeyboardEvent) => {
|
|
16
|
+
if (e.key === "Escape") {
|
|
17
|
+
props.onClose();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
document.addEventListener("keydown", handleEscapeKey);
|
|
22
|
+
return () => {
|
|
23
|
+
document.removeEventListener("keydown", handleEscapeKey);
|
|
24
|
+
};
|
|
25
|
+
}, [props]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="uidog-sidebar-overlay">
|
|
29
|
+
<div className="uidog-sidebar">
|
|
30
|
+
{/* Element Info Section */}
|
|
31
|
+
<div className="uidog-element-info-section">
|
|
32
|
+
<ElementInfoDisplayReact
|
|
33
|
+
elementInfo={props.elementInfo}
|
|
34
|
+
onClose={props.onClose}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{/* Chat Area - Messages + Input */}
|
|
39
|
+
<div className="uidog-chat-container">
|
|
40
|
+
<ConversationalInputReact
|
|
41
|
+
elementInfo={props.elementInfo}
|
|
42
|
+
editorUrl={props.editorUrl}
|
|
43
|
+
apiEndpoint={props.apiEndpoint}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|