@messenger-box/tailwind-ui-inbox 10.0.3-alpha.144 → 10.0.3-alpha.158
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/CHANGELOG.md +32 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
- package/lib/components/AIAgent/AIAgent.js +1 -55
- package/lib/components/AIAgent/AIAgent.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +1 -2
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +2 -2
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
- package/lib/components/ModelConfigPanel.d.ts +11 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -1
- package/lib/components/ModelConfigPanel.js +29 -2
- package/lib/components/ModelConfigPanel.js.map +1 -1
- package/lib/components/filler-components/RightSiderBar.d.ts +1 -28
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -1
- package/lib/components/filler-components/RightSiderBar.js +531 -458
- package/lib/components/filler-components/RightSiderBar.js.map +1 -1
- package/lib/container/Inbox.js +1 -1
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/TestInboxWithAiLoader.d.ts.map +1 -1
- package/lib/container/TestInboxWithAiLoader.js +1 -11
- package/lib/container/TestInboxWithAiLoader.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/hooks/index.d.ts +0 -1
- package/lib/hooks/index.d.ts.map +1 -1
- package/lib/hooks/usePersistentModelConfig.d.ts +1 -0
- package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -1
- package/lib/hooks/usePersistentModelConfig.js +1 -0
- package/lib/hooks/usePersistentModelConfig.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/module.js +1 -1
- package/lib/module.js.map +1 -1
- package/lib/templates/InboxWithAi.d.ts.map +1 -1
- package/lib/templates/InboxWithAi.js +2 -15
- package/lib/templates/InboxWithAi.js.map +1 -1
- package/lib/templates/InboxWithAi.tsx +2 -24
- package/package.json +4 -4
- package/src/components/AIAgent/AIAgent.tsx +3 -54
- package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +1 -2
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +2 -2
- package/src/components/ModelConfigPanel.tsx +59 -0
- package/src/components/filler-components/RightSiderBar.tsx +570 -566
- package/src/container/TestInboxWithAiLoader.tsx +0 -8
- package/src/hooks/index.ts +0 -1
- package/src/hooks/usePersistentModelConfig.ts +2 -0
- package/src/templates/InboxWithAi.tsx +2 -24
- package/lib/components/live-code-editor/hybrid-live-editor.js +0 -68
- package/lib/components/live-code-editor/hybrid-live-editor.js.map +0 -1
- package/lib/components/live-code-editor/live-code-editor.js +0 -207
- package/lib/components/live-code-editor/live-code-editor.js.map +0 -1
- package/lib/hooks/use-file-sync.d.ts +0 -16
- package/lib/hooks/use-file-sync.d.ts.map +0 -1
- package/lib/hooks/use-file-sync.js +0 -63
- package/lib/hooks/use-file-sync.js.map +0 -1
- package/lib/utils/utils.js +0 -3
- package/lib/utils/utils.js.map +0 -1
- package/lib/xstate/rightSidebar.machine.js +0 -325
- package/lib/xstate/rightSidebar.machine.js.map +0 -1
- package/src/hooks/use-file-sync.ts +0 -91
|
@@ -11,10 +11,8 @@ import React, {
|
|
|
11
11
|
} from 'react';
|
|
12
12
|
import { Outlet, useNavigate, useParams, useLocation } from '@remix-run/react';
|
|
13
13
|
import { RightSidebarAi } from '../components/InboxMessage/RightSidebarAi';
|
|
14
|
-
import { useRecreateSandboxMutation } from 'common/graphql';
|
|
15
14
|
import { usePersistentModelConfig } from '../hooks/usePersistentModelConfig';
|
|
16
15
|
import { ICreateChannelInput } from 'common';
|
|
17
|
-
import { useFileSync } from '../hooks/use-file-sync';
|
|
18
16
|
|
|
19
17
|
// Context for sharing tab state between header and sidebar
|
|
20
18
|
const TabContext = createContext<{
|
|
@@ -128,7 +126,6 @@ const InboxWithAiInternal = (props: InboxProps) => {
|
|
|
128
126
|
const { activeTab, setActiveTab } = useContext(TabContext);
|
|
129
127
|
const [messages, setMessages] = useState<any[]>([]);
|
|
130
128
|
const [selectedPost, setSelectedPost] = useState<any>(null);
|
|
131
|
-
const [recreateSandbox] = useRecreateSandboxMutation();
|
|
132
129
|
const { modelConfig, getValidatedConfig, hasApiKey } = usePersistentModelConfig();
|
|
133
130
|
const [isLoading, setIsLoading] = useState(true);
|
|
134
131
|
const [isCreatingSandbox, setIsCreatingSandbox] = useState(false);
|
|
@@ -138,12 +135,7 @@ const InboxWithAiInternal = (props: InboxProps) => {
|
|
|
138
135
|
const showContextTab = props.showContextTab ?? true;
|
|
139
136
|
const contextData = useMemo(() => {
|
|
140
137
|
const cfg = selectedPost?.propsConfiguration;
|
|
141
|
-
return
|
|
142
|
-
cfg?.contents?.fragment?.context ||
|
|
143
|
-
cfg?.content?.fragment?.context ||
|
|
144
|
-
cfg?.fragment?.context ||
|
|
145
|
-
null
|
|
146
|
-
);
|
|
138
|
+
return cfg?.contents?.fragment?.context || cfg?.content?.fragment?.context || cfg?.fragment?.context || null;
|
|
147
139
|
}, [selectedPost]);
|
|
148
140
|
// Extract channelId from query parameters (priority) or path parameters (fallback)
|
|
149
141
|
const channelId = urlParams?.get('id') || pathChannelId;
|
|
@@ -167,27 +159,13 @@ const InboxWithAiInternal = (props: InboxProps) => {
|
|
|
167
159
|
setError(null);
|
|
168
160
|
|
|
169
161
|
props.handleRecreateSandbox?.(messageId);
|
|
170
|
-
|
|
171
|
-
// const response = await recreateSandbox({
|
|
172
|
-
// variables: {
|
|
173
|
-
// messageId,
|
|
174
|
-
// },
|
|
175
|
-
// });
|
|
176
|
-
|
|
177
|
-
// if (response.data?.recreateSandbox?.success) {
|
|
178
|
-
// console.log('Sandbox recreation initiated successfully');
|
|
179
|
-
// // The subscription will handle updating the UI with the new sandbox URL
|
|
180
|
-
// } else {
|
|
181
|
-
// const errorMsg = response.data?.recreateSandbox?.message || 'Failed to recreate sandbox';
|
|
182
|
-
// throw new Error(errorMsg);
|
|
183
|
-
// }
|
|
184
162
|
} catch (err) {
|
|
185
163
|
console.error('Error recreating sandbox:', err);
|
|
186
164
|
setError(err instanceof Error ? err.message : 'Failed to recreate sandbox');
|
|
187
165
|
setIsCreatingSandbox(false);
|
|
188
166
|
}
|
|
189
167
|
},
|
|
190
|
-
[
|
|
168
|
+
[channelId],
|
|
191
169
|
);
|
|
192
170
|
|
|
193
171
|
// Handle refresh sandbox
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@messenger-box/tailwind-ui-inbox",
|
|
3
|
-
"version": "10.0.3-alpha.
|
|
3
|
+
"version": "10.0.3-alpha.158",
|
|
4
4
|
"description": "Inbox UI components built with TailwindCSS",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"watch": "npm run build:lib:watch"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@messenger-box/core": "10.0.3-alpha.
|
|
25
|
-
"@messenger-box/platform-client": "10.0.3-alpha.
|
|
24
|
+
"@messenger-box/core": "10.0.3-alpha.158",
|
|
25
|
+
"@messenger-box/platform-client": "10.0.3-alpha.158",
|
|
26
26
|
"@monaco-editor/react": "^4.7.0",
|
|
27
27
|
"date-fns": "^4.1.0",
|
|
28
28
|
"date-fns-tz": "^3.2.0",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"typescript": {
|
|
59
59
|
"definition": "lib/index.d.ts"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "3c0470617c57636194851348aea9d87533d10837"
|
|
62
62
|
}
|
|
@@ -6,7 +6,7 @@ import { format, isToday, isYesterday, formatDistanceToNow } from 'date-fns';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { ModernMessageGroupComponent } from '../InboxMessage/message-widgets/ModernMessageGroup';
|
|
8
8
|
import { useUploadFiles } from '@messenger-box/platform-client';
|
|
9
|
-
import { IFileInfo, PostTypeEnum,
|
|
9
|
+
import { IFileInfo, PostTypeEnum, AiAgentMessageRole, ICreateChannelInput } from 'common';
|
|
10
10
|
import { useSelector, shallowEqual } from 'react-redux';
|
|
11
11
|
import { Store, userSelector } from '@adminide-stack/user-auth0-client';
|
|
12
12
|
import { IUserState } from '@adminide-stack/core';
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
20
20
|
useCreateChannelWorkflowJobMutation,
|
|
21
21
|
useOnChatMessageAddedSubscription,
|
|
22
|
-
useSandboxErrorSubscription,
|
|
23
22
|
} from 'common/graphql';
|
|
24
23
|
import { config } from '../../config';
|
|
25
24
|
import { orderBy, uniqBy } from 'lodash-es';
|
|
@@ -90,10 +89,6 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
// Subscribe to sandbox errors if projectId is provided
|
|
93
|
-
const { data: sandboxErrorData } = useSandboxErrorSubscription({
|
|
94
|
-
variables: { projectId: actualChannelId?.toString() || '' },
|
|
95
|
-
skip: !actualChannelId,
|
|
96
|
-
});
|
|
97
92
|
|
|
98
93
|
// Direct message query from InboxWithAiLoader.tsx
|
|
99
94
|
const messagesQuery = useMessagesQuery({
|
|
@@ -123,7 +118,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
123
118
|
const [selectedElement, setSelectedElement] = useState<any>(null);
|
|
124
119
|
const [activeTab, setActiveTab] = useState<'chat' | 'history'>('chat');
|
|
125
120
|
const bottomRef = useRef<HTMLDivElement | null>(null);
|
|
126
|
-
const [sandboxErrors, setSandboxErrors] = useState<
|
|
121
|
+
const [sandboxErrors, setSandboxErrors] = useState<any[]>([]);
|
|
127
122
|
const [currentFiles, setCurrentFiles] = useState<Record<string, string>>({});
|
|
128
123
|
const [canvasLayers, setCanvasLayers] = useState<any[]>([]);
|
|
129
124
|
// New state variables for message display control
|
|
@@ -155,7 +150,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
155
150
|
'chatMessageAddedData?.chatMessageAdded',
|
|
156
151
|
JSON.stringify(chatMessageAddedData?.chatMessageAdded, null, 2),
|
|
157
152
|
);
|
|
158
|
-
|
|
153
|
+
|
|
159
154
|
// Stop the success thinking loader once a new message arrives
|
|
160
155
|
if (isSuccessThinking) {
|
|
161
156
|
setIsSuccessThinking(false);
|
|
@@ -174,7 +169,6 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
174
169
|
useEffect(() => {
|
|
175
170
|
if (messagesData?.messages?.data?.length == 1) {
|
|
176
171
|
setPostId(messagesData.messages.data[0]?.id);
|
|
177
|
-
///handleGenerateAiCode(messagesData.messages.data[0].id);
|
|
178
172
|
}
|
|
179
173
|
// console.log('messagesData?.messages?.data',JSON.stringify(messagesData?.messages?.data,null,2))
|
|
180
174
|
}, [messagesData?.messages?.data]);
|
|
@@ -187,13 +181,6 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
187
181
|
}, [sendMessageInput?.postData?.content]);
|
|
188
182
|
|
|
189
183
|
// Handle sandbox error subscription updates
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
if (sandboxErrorData?.sandboxError) {
|
|
192
|
-
const errorData = sandboxErrorData.sandboxError;
|
|
193
|
-
console.log('Sandbox error received:', errorData);
|
|
194
|
-
setSandboxErrors((prev) => [...prev, errorData.error]);
|
|
195
|
-
}
|
|
196
|
-
}, [sandboxErrorData]);
|
|
197
184
|
|
|
198
185
|
const regularMessages = useMemo(() => {
|
|
199
186
|
if (!messagesData?.messages?.data) return [];
|
|
@@ -238,44 +225,6 @@ export const AIAgent: React.FC<AIAgentProps> = ({
|
|
|
238
225
|
}
|
|
239
226
|
}, [regularMessages, send]);
|
|
240
227
|
|
|
241
|
-
// const handleGenerateAiCode = useCallback(
|
|
242
|
-
// (messageId: string) => {
|
|
243
|
-
// generateAiCode({
|
|
244
|
-
// variables: {
|
|
245
|
-
// messageId: messageId,
|
|
246
|
-
// modelConfig: modelConfig,
|
|
247
|
-
// },
|
|
248
|
-
// onCompleted: (res) => {
|
|
249
|
-
// if (res.generateAiCode.success) {
|
|
250
|
-
// console.log('generated_result', res);
|
|
251
|
-
// // Show AI is thinking loader on success
|
|
252
|
-
// if (successThinkingTimeoutRef.current) {
|
|
253
|
-
// clearTimeout(successThinkingTimeoutRef.current);
|
|
254
|
-
// successThinkingTimeoutRef.current = null;
|
|
255
|
-
// }
|
|
256
|
-
// setIsSuccessThinking(true);
|
|
257
|
-
// // Fallback auto-hide after 15s in case no subsequent event arrives
|
|
258
|
-
// successThinkingTimeoutRef.current = setTimeout(() => {
|
|
259
|
-
// setIsSuccessThinking(false);
|
|
260
|
-
// // setIsLoading(false);
|
|
261
|
-
// successThinkingTimeoutRef.current = null;
|
|
262
|
-
// }, 15000);
|
|
263
|
-
// }
|
|
264
|
-
// },
|
|
265
|
-
// onError: (err) => {
|
|
266
|
-
// console.log('err', JSON.stringify(err, null, 2));
|
|
267
|
-
// // Also ensure loader is hidden on error
|
|
268
|
-
// if (successThinkingTimeoutRef.current) {
|
|
269
|
-
// clearTimeout(successThinkingTimeoutRef.current);
|
|
270
|
-
// successThinkingTimeoutRef.current = null;
|
|
271
|
-
// }
|
|
272
|
-
// setIsSuccessThinking(false);
|
|
273
|
-
// },
|
|
274
|
-
// });
|
|
275
|
-
// },
|
|
276
|
-
// [generateAiCode, modelConfig],
|
|
277
|
-
// );
|
|
278
|
-
|
|
279
228
|
const onOpen = (element?: any) => {
|
|
280
229
|
setSelectedElement(element);
|
|
281
230
|
setIsOpen(true);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { ISandboxError } from 'common';
|
|
3
2
|
|
|
4
3
|
const IconAlertTriangle: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
5
4
|
<svg
|
|
@@ -66,7 +65,7 @@ const IconExternalLink: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
|
|
66
65
|
);
|
|
67
66
|
|
|
68
67
|
interface ErrorFixCardProps {
|
|
69
|
-
error:
|
|
68
|
+
error: any;
|
|
70
69
|
onFixError: (errorMessage: string) => Promise<void>;
|
|
71
70
|
currentFiles?: Record<string, string>;
|
|
72
71
|
isFixing?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { format, differenceInMinutes } from 'date-fns';
|
|
3
|
-
import { AiAgentMessageRole, IAuthUser, IPost,
|
|
3
|
+
import { AiAgentMessageRole, IAuthUser, IPost, PostTypeEnum } from 'common';
|
|
4
4
|
import { FilesList } from '../../inbox';
|
|
5
5
|
import { ErrorFixCard } from './ErrorFixCard';
|
|
6
6
|
import ReactMarkdown from 'react-markdown';
|
|
@@ -409,7 +409,7 @@ interface ModernMessageGroupProps {
|
|
|
409
409
|
onMessageClick: (msg: IPost) => void;
|
|
410
410
|
isDesktopView?: boolean;
|
|
411
411
|
isSmallScreen?: boolean;
|
|
412
|
-
sandboxErrors?:
|
|
412
|
+
sandboxErrors?: any[];
|
|
413
413
|
currentFiles?: Record<string, string>;
|
|
414
414
|
onFixError?: (errorMessage: string) => Promise<void>;
|
|
415
415
|
onRecreateSandbox?: (fragmentId: string) => void;
|
|
@@ -12,6 +12,18 @@ interface ModelConfigPanelProps {
|
|
|
12
12
|
showTemplate?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface ModelToolbarProps {
|
|
16
|
+
modelConfig?: PersistentModelConfig;
|
|
17
|
+
onModelConfigChange?: (config: PersistentModelConfig) => void;
|
|
18
|
+
sending: boolean;
|
|
19
|
+
canSend: boolean;
|
|
20
|
+
onSend: () => void;
|
|
21
|
+
onUploadImageChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
22
|
+
showProjectSettings?: boolean;
|
|
23
|
+
isShowMeta?: boolean;
|
|
24
|
+
showModeSelector?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
const providerIcons: Record<ModelConfig['provider'], string> = {
|
|
16
28
|
openai: '🤖',
|
|
17
29
|
anthropic: '🧠',
|
|
@@ -449,6 +461,7 @@ export const ModelToolbar: React.FC<ModelToolbarProps> = ({
|
|
|
449
461
|
onUploadImageChange,
|
|
450
462
|
showProjectSettings = true,
|
|
451
463
|
isShowMeta = false,
|
|
464
|
+
showModeSelector = true,
|
|
452
465
|
}) => {
|
|
453
466
|
const [showModelDropdown, setShowModelDropdown] = useState(false);
|
|
454
467
|
const [showToolbarModelDropdown, setShowToolbarModelDropdown] = useState(false);
|
|
@@ -458,10 +471,18 @@ export const ModelToolbar: React.FC<ModelToolbarProps> = ({
|
|
|
458
471
|
const [templateSearch, setTemplateSearch] = useState('');
|
|
459
472
|
const [modelSearch, setModelSearch] = useState('');
|
|
460
473
|
const [toolbarModelSearch, setToolbarModelSearch] = useState('');
|
|
474
|
+
const [mode, setMode] = useState<'plan' | 'build'>(modelConfig?.mode || 'plan');
|
|
461
475
|
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
|
462
476
|
const toolbarModelButtonRef = useRef<HTMLButtonElement>(null);
|
|
463
477
|
const templateDropdownRef = useRef<HTMLDivElement>(null);
|
|
464
478
|
|
|
479
|
+
// Sync mode with modelConfig
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
if (modelConfig?.mode && modelConfig.mode !== mode) {
|
|
482
|
+
setMode(modelConfig.mode);
|
|
483
|
+
}
|
|
484
|
+
}, [modelConfig?.mode, mode]);
|
|
485
|
+
|
|
465
486
|
useEffect(() => {
|
|
466
487
|
const handleClickOutside = (event: MouseEvent) => {
|
|
467
488
|
const target = event.target as HTMLElement;
|
|
@@ -565,9 +586,47 @@ export const ModelToolbar: React.FC<ModelToolbarProps> = ({
|
|
|
565
586
|
[modelConfig, onModelConfigChange],
|
|
566
587
|
);
|
|
567
588
|
|
|
589
|
+
const handleModeChange = useCallback(
|
|
590
|
+
(newMode: 'plan' | 'build') => {
|
|
591
|
+
setMode(newMode);
|
|
592
|
+
if (onModelConfigChange && modelConfig) {
|
|
593
|
+
onModelConfigChange({ ...modelConfig, mode: newMode });
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
[modelConfig, onModelConfigChange],
|
|
597
|
+
);
|
|
598
|
+
|
|
568
599
|
return (
|
|
569
600
|
<>
|
|
570
601
|
<div className="flex items-center gap-2 overflow-x-auto overflow-y-visible whitespace-nowrap py-1 px-2 sm:px-0">
|
|
602
|
+
{/* Mode Selection */}
|
|
603
|
+
{showModeSelector && (
|
|
604
|
+
<div className="flex items-center gap-0 mr-2">
|
|
605
|
+
<button
|
|
606
|
+
type="button"
|
|
607
|
+
onClick={() => handleModeChange('plan')}
|
|
608
|
+
className={`px-2 py-1 text-xs rounded-l border ${
|
|
609
|
+
mode === 'plan'
|
|
610
|
+
? 'bg-gray-200 text-gray-900 font-medium border-gray-300'
|
|
611
|
+
: 'bg-white border-gray-300 hover:bg-gray-50'
|
|
612
|
+
}`}
|
|
613
|
+
>
|
|
614
|
+
Plan
|
|
615
|
+
</button>
|
|
616
|
+
<button
|
|
617
|
+
type="button"
|
|
618
|
+
onClick={() => handleModeChange('build')}
|
|
619
|
+
className={`px-2 py-1 text-xs rounded-r border-t border-r border-b ${
|
|
620
|
+
mode === 'build'
|
|
621
|
+
? 'bg-gray-200 text-gray-900 font-medium border-gray-300'
|
|
622
|
+
: 'bg-white border-gray-300 hover:bg-gray-50'
|
|
623
|
+
}`}
|
|
624
|
+
>
|
|
625
|
+
Build
|
|
626
|
+
</button>
|
|
627
|
+
</div>
|
|
628
|
+
)}
|
|
629
|
+
|
|
571
630
|
{/* Template selection moved to Project Settings modal */}
|
|
572
631
|
|
|
573
632
|
{/* 2. Model Selection Dropdown */}
|