fixdog 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -433
  3. package/dist/api/client.d.ts +74 -0
  4. package/dist/components/ConversationalInputReact.d.ts +26 -0
  5. package/dist/components/ElementInfoDisplayReact.d.ts +9 -0
  6. package/dist/components/FixdogSidebarReact.d.ts +29 -0
  7. package/dist/fiber.d.ts +9 -0
  8. package/dist/index.cjs.js +33 -0
  9. package/dist/index.cjs.js.map +1 -0
  10. package/dist/index.d.ts +6 -158
  11. package/dist/index.esm.js +29 -0
  12. package/dist/index.esm.js.map +1 -0
  13. package/dist/inspector-B4F5CBT7.cjs.js +1159 -0
  14. package/dist/inspector-B4F5CBT7.cjs.js.map +1 -0
  15. package/dist/inspector-BL2pNjn-.cjs.js +1173 -0
  16. package/dist/inspector-BL2pNjn-.cjs.js.map +1 -0
  17. package/dist/inspector-Bg6uSvk0.esm.js +1273 -0
  18. package/dist/inspector-Bg6uSvk0.esm.js.map +1 -0
  19. package/dist/inspector-BuOffbVc.cjs.js +1280 -0
  20. package/dist/inspector-BuOffbVc.cjs.js.map +1 -0
  21. package/dist/inspector-CNgFkZOU.esm.js +1185 -0
  22. package/dist/inspector-CNgFkZOU.esm.js.map +1 -0
  23. package/dist/inspector-CPF1N9dL.esm.js +1185 -0
  24. package/dist/inspector-CPF1N9dL.esm.js.map +1 -0
  25. package/dist/inspector-CPGK5Lg7.esm.js +1155 -0
  26. package/dist/inspector-CPGK5Lg7.esm.js.map +1 -0
  27. package/dist/inspector-CWcTSREy.cjs.js +1174 -0
  28. package/dist/inspector-CWcTSREy.cjs.js.map +1 -0
  29. package/dist/inspector-Cn_bl9Io.cjs.js +1189 -0
  30. package/dist/inspector-Cn_bl9Io.cjs.js.map +1 -0
  31. package/dist/inspector-D9DuXirp.cjs.js +1189 -0
  32. package/dist/inspector-D9DuXirp.cjs.js.map +1 -0
  33. package/dist/inspector-DQEtAjyM.esm.js +1129 -0
  34. package/dist/inspector-DQEtAjyM.esm.js.map +1 -0
  35. package/dist/inspector-DVlU9p44.cjs.js +1189 -0
  36. package/dist/inspector-DVlU9p44.cjs.js.map +1 -0
  37. package/dist/inspector-DaRVppX9.cjs.js +1134 -0
  38. package/dist/inspector-DaRVppX9.cjs.js.map +1 -0
  39. package/dist/inspector-huqtI2MD.esm.js +1170 -0
  40. package/dist/inspector-huqtI2MD.esm.js.map +1 -0
  41. package/dist/inspector-spoCY1tf.esm.js +1169 -0
  42. package/dist/inspector-spoCY1tf.esm.js.map +1 -0
  43. package/dist/inspector-tY1kJK5_.esm.js +1185 -0
  44. package/dist/inspector-tY1kJK5_.esm.js.map +1 -0
  45. package/dist/inspector.d.ts +43 -0
  46. package/dist/keyboard.d.ts +10 -0
  47. package/dist/overlay.d.ts +31 -0
  48. package/dist/react/InspectorProvider.d.ts +6 -0
  49. package/dist/react/index.cjs.js +32 -0
  50. package/dist/react/index.cjs.js.map +1 -0
  51. package/dist/react/index.esm.js +30 -0
  52. package/dist/react/index.esm.js.map +1 -0
  53. package/dist/sidebar/SidebarRuntime.d.ts +8 -0
  54. package/dist/sidebar-runtime.esm.js +2122 -0
  55. package/dist/sidebar-runtime.esm.js.map +1 -0
  56. package/dist/sidebar-runtime.iife.js +2991 -0
  57. package/dist/styles/sidebarStyles.d.ts +2 -0
  58. package/dist/styles.d.ts +8 -0
  59. package/dist/types/sidebar.d.ts +62 -0
  60. package/dist/types.d.ts +47 -0
  61. package/dist/utils/cookies.d.ts +10 -0
  62. package/dist/utils/devMode.d.ts +17 -0
  63. package/dist/utils/sessionStorage.d.ts +19 -0
  64. package/package.json +57 -40
  65. package/USAGE.md +0 -77
  66. package/dist/client/index.d.mts +0 -110
  67. package/dist/client/index.d.ts +0 -110
  68. package/dist/client/index.js +0 -1601
  69. package/dist/client/index.mjs +0 -1582
  70. package/dist/client/init.d.mts +0 -67
  71. package/dist/client/init.d.ts +0 -67
  72. package/dist/client/init.js +0 -1609
  73. package/dist/client/init.mjs +0 -1593
  74. package/dist/index.d.mts +0 -158
  75. package/dist/index.js +0 -1635
  76. package/dist/index.mjs +0 -1606
  77. package/src/api/client.ts +0 -141
  78. package/src/client/index.ts +0 -75
  79. package/src/client/init.tsx +0 -78
  80. package/src/components/ConversationalInputReact.tsx +0 -406
  81. package/src/components/ElementInfoDisplayReact.tsx +0 -84
  82. package/src/components/UiDogSidebarReact.tsx +0 -49
  83. package/src/element-detector.ts +0 -186
  84. package/src/index.ts +0 -228
  85. package/src/instrument.ts +0 -171
  86. package/src/sidebar-initializer.ts +0 -171
  87. package/src/source-resolver.ts +0 -121
  88. package/src/styles/sidebarStyles.ts +0 -597
  89. package/src/types/css.d.ts +0 -9
  90. package/src/types/sidebar.ts +0 -56
  91. package/src/types.ts +0 -119
  92. package/tsconfig.json +0 -23
  93. package/tsup.config.ts +0 -40
package/src/api/client.ts DELETED
@@ -1,141 +0,0 @@
1
- export interface EditRequest {
2
- editorUrl: string;
3
- userInput: string;
4
- }
5
-
6
- export interface EditResponse {
7
- sessionId: string;
8
- message: string;
9
- status: "success" | "error";
10
- error?: string;
11
- }
12
-
13
- export interface ChatRequest {
14
- prompt: string;
15
- sessionId?: string;
16
- }
17
-
18
- export interface ChatResponse {
19
- ok: boolean;
20
- message: string;
21
- sessionId?: string;
22
- error?: string;
23
- }
24
-
25
- export interface SendToDevRequest {
26
- sessionId: string;
27
- }
28
-
29
- export interface SendToDevResponse {
30
- ok: boolean;
31
- prUrl?: string;
32
- prNumber?: number;
33
- branchName?: string;
34
- error?: string;
35
- }
36
-
37
- export async function createEditSession(
38
- request: EditRequest,
39
- apiEndpoint: string = "https://api.ui.dog"
40
- ): Promise<EditResponse> {
41
- try {
42
- const response = await fetch(`${apiEndpoint}/api/sessions`, {
43
- method: "POST",
44
- headers: {
45
- "Content-Type": "application/json",
46
- },
47
- body: JSON.stringify(request),
48
- });
49
-
50
- if (!response.ok) {
51
- const errorText = await response.text().catch(() => response.statusText);
52
- throw new Error(`API error (${response.status}): ${errorText}`);
53
- }
54
-
55
- return await response.json();
56
- } catch (error) {
57
- if (error instanceof Error) {
58
- throw error;
59
- }
60
- throw new Error("Unknown error occurred");
61
- }
62
- }
63
-
64
- export async function sendChatPrompt(
65
- request: ChatRequest,
66
- apiEndpoint: string = "http://localhost:3000"
67
- ): Promise<ChatResponse> {
68
- try {
69
- const response = await fetch(`${apiEndpoint}/chat`, {
70
- method: "POST",
71
- headers: {
72
- "Content-Type": "application/json",
73
- },
74
- body: JSON.stringify(request),
75
- });
76
-
77
- if (!response.ok) {
78
- const errorData = await response
79
- .json()
80
- .catch(() => ({ error: response.statusText }));
81
- return {
82
- ok: false,
83
- message: "",
84
- error: errorData.error || `API error (${response.status})`,
85
- };
86
- }
87
-
88
- return await response.json();
89
- } catch (error) {
90
- if (error instanceof Error) {
91
- return {
92
- ok: false,
93
- message: "",
94
- error: error.message,
95
- };
96
- }
97
- return {
98
- ok: false,
99
- message: "",
100
- error: "Unknown error occurred",
101
- };
102
- }
103
- }
104
-
105
- export async function sendToDeveloper(
106
- request: SendToDevRequest,
107
- apiEndpoint: string = "http://localhost:3000"
108
- ): Promise<SendToDevResponse> {
109
- try {
110
- const response = await fetch(`${apiEndpoint}/send-to-dev`, {
111
- method: "POST",
112
- headers: {
113
- "Content-Type": "application/json",
114
- },
115
- body: JSON.stringify(request),
116
- });
117
-
118
- if (!response.ok) {
119
- const errorData = await response
120
- .json()
121
- .catch(() => ({ error: response.statusText }));
122
- return {
123
- ok: false,
124
- error: errorData.error || `API error (${response.status})`,
125
- };
126
- }
127
-
128
- return await response.json();
129
- } catch (error) {
130
- if (error instanceof Error) {
131
- return {
132
- ok: false,
133
- error: error.message,
134
- };
135
- }
136
- return {
137
- ok: false,
138
- error: "Unknown error occurred",
139
- };
140
- }
141
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Client entry point for UiDog Next.js integration
3
- *
4
- * This file is designed to be imported in:
5
- * - Next.js 15.3+: instrumentation-client.ts at project root
6
- * - Next.js 14.x / Pages Router: top of _app.tsx (must be first import)
7
- *
8
- * IMPORTANT: This file MUST be imported BEFORE React loads to properly
9
- * install Bippy's React DevTools hook.
10
- *
11
- * Usage:
12
- * ```typescript
13
- * // instrumentation-client.ts (Next.js 15.3+)
14
- * import 'uidog-sdk-next/client';
15
- * ```
16
- *
17
- * ```typescript
18
- * // pages/_app.tsx (Next.js 14.x / Pages Router)
19
- * import 'uidog-sdk-next/client'; // MUST be first import
20
- * import type { AppProps } from 'next/app';
21
- * // ... rest of your imports
22
- * ```
23
- */
24
-
25
- // Import bippy FIRST to install the React DevTools hook
26
- // This must happen before React is imported anywhere
27
- import "bippy";
28
-
29
- import { initializeUiDogNext } from "../index";
30
- import type { UiDogNextOptions } from "../types";
31
-
32
- // Store options for deferred initialization
33
- let pendingOptions: UiDogNextOptions = {};
34
- let isAutoInitEnabled = true;
35
-
36
- /**
37
- * Configure UiDog options before initialization
38
- * Call this before auto-initialization happens (at DOMContentLoaded)
39
- */
40
- export function configureUiDogNext(options: UiDogNextOptions): void {
41
- pendingOptions = { ...pendingOptions, ...options };
42
- }
43
-
44
- /**
45
- * Disable auto-initialization
46
- * Use this if you want to manually initialize UiDog later
47
- */
48
- export function disableAutoInit(): void {
49
- isAutoInitEnabled = false;
50
- }
51
-
52
- /**
53
- * Initialize UiDog with the configured options
54
- */
55
- function initialize(): void {
56
- if (!isAutoInitEnabled) return;
57
-
58
- initializeUiDogNext(pendingOptions);
59
- }
60
-
61
- // Auto-initialize when imported (browser only)
62
- if (typeof window !== "undefined") {
63
- // Initialize when DOM is ready
64
- if (document.readyState === "loading") {
65
- document.addEventListener("DOMContentLoaded", initialize);
66
- } else {
67
- // DOM is already ready, initialize immediately
68
- // Use setTimeout to ensure this runs after any synchronous configuration
69
- setTimeout(initialize, 0);
70
- }
71
- }
72
-
73
- // Re-export main functions for convenience
74
- export { initializeUiDogNext } from "../index";
75
- export type { UiDogNextOptions } from "../types";
@@ -1,78 +0,0 @@
1
- /**
2
- * UiDogProvider - React component for configuring UiDog in Next.js App Router
3
- *
4
- * This component provides a declarative way to configure UiDog options.
5
- * It should be used in your root layout.tsx for App Router apps.
6
- *
7
- * Usage:
8
- * ```tsx
9
- * // app/layout.tsx
10
- * import { UiDogProvider } from 'uidog-sdk-next/init';
11
- *
12
- * export default function RootLayout({ children }) {
13
- * return (
14
- * <html>
15
- * <body>
16
- * <UiDogProvider editor="cursor" enableSidebar>
17
- * {children}
18
- * </UiDogProvider>
19
- * </body>
20
- * </html>
21
- * );
22
- * }
23
- * ```
24
- *
25
- * Note: You still need to import 'uidog-sdk-next/client' in instrumentation-client.ts
26
- * This provider is for configuration, not initialization.
27
- */
28
-
29
- "use client";
30
-
31
- import { useEffect, type ReactNode } from "react";
32
- import { initializeUiDogNext, cleanupUiDogNext, isUiDogNextInitialized } from "../index";
33
- import type { UiDogNextOptions, EditorType } from "../types";
34
-
35
- export interface UiDogProviderProps extends UiDogNextOptions {
36
- children: ReactNode;
37
- }
38
-
39
- export function UiDogProvider({
40
- children,
41
- editor = "cursor",
42
- projectPath = "",
43
- modifier = "alt",
44
- enableSidebar = true,
45
- apiEndpoint = "https://api.ui.dog",
46
- }: UiDogProviderProps) {
47
- useEffect(() => {
48
- // Only initialize in development mode
49
- if (process.env.NODE_ENV !== "development") {
50
- return;
51
- }
52
-
53
- // Don't re-initialize if already done
54
- if (isUiDogNextInitialized()) {
55
- return;
56
- }
57
-
58
- initializeUiDogNext({
59
- editor,
60
- projectPath,
61
- modifier,
62
- enableSidebar,
63
- apiEndpoint,
64
- });
65
-
66
- // Cleanup on unmount (though this rarely happens for root providers)
67
- return () => {
68
- // Note: We typically don't cleanup on unmount as this is a root-level provider
69
- // Uncomment if needed for testing:
70
- // cleanupUiDogNext();
71
- };
72
- }, [editor, projectPath, modifier, enableSidebar, apiEndpoint]);
73
-
74
- return <>{children}</>;
75
- }
76
-
77
- // Re-export types for convenience
78
- export type { UiDogNextOptions, EditorType };
@@ -1,406 +0,0 @@
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
- }