Skip to content

Commit 3e3bbf6

Browse files
committed
Merge branch 'dev' into Responsive-Embodiment-Tabs-86evbh920
2 parents dd8a7a0 + 177753f commit 3e3bbf6

File tree

17 files changed

+512
-240
lines changed

17 files changed

+512
-240
lines changed

packages/app/src/builder-ui/pages/builder/agent-settings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -815,13 +815,13 @@ export async function openChatGPTEmbodiment() {
815815
<li>Scroll down and Click on "Create new action" button.<br />
816816
${
817817
testDomain
818-
? '<li>- Click Import URL and enter the following URL if you want to use your test agent: ' +
818+
? '<li>Click Import URL and enter the following URL if you want to use your test agent: ' +
819819
`<b>${scheme}://${testDomain}/api-docs/openapi-gpt.json</b></li>`
820820
: ''
821821
}
822822
${
823823
prodDomain
824-
? '<li> - Click Import URL and enter the following URL if you want to use your production agent: ' +
824+
? '<li>Click Import URL and enter the following URL if you want to use your production agent: ' +
825825
`<b>${scheme}://${prodDomain}/api-docs/openapi-gpt.json</b></li>`
826826
: ''
827827
}

packages/app/src/builder-ui/ui/form/fields.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,12 @@ async function setupMetroUIBracketSelection(textarea: HTMLTextAreaElement): Prom
782782
} else {
783783
addBracketSelection(textarea);
784784
}
785+
786+
// Remove Metro UI wrapper borders to let Tailwind classes work
787+
if (textareaWrapper) {
788+
(textareaWrapper as HTMLElement).style.border = 'none';
789+
(textareaWrapper as HTMLElement).style.boxShadow = 'none';
790+
}
785791
}
786792

787793
/**
@@ -1016,16 +1022,27 @@ async function handleExpandTextarea(
10161022
textareaWrapper.style.flexDirection = 'column';
10171023
textareaWrapper.style.overflow = 'hidden';
10181024

1019-
// Create modal textarea
1025+
// Create modal textarea with Tailwind classes for border styling
10201026
const modalTextarea = document.createElement('textarea') as TextAreaWithEditor;
10211027
modalTextarea.value = originalTextarea.value;
1022-
modalTextarea.classList.add('form-control', 'flex-1', 'resize-none');
1028+
modalTextarea.classList.add(
1029+
'form-control',
1030+
'flex-1',
1031+
'resize-none',
1032+
'border',
1033+
'border-gray-200',
1034+
'border-b-gray-500',
1035+
'focus:border-b-2',
1036+
'focus:border-b-blue-500',
1037+
'focus:outline-none'
1038+
);
10231039
modalTextarea.id = 'expanded-textarea';
10241040

10251041
// Add Metro UI data-role for regular textareas (non-code editor)
10261042
if (!hasCodeEditor) {
10271043
modalTextarea.setAttribute('data-role', 'textarea');
10281044
modalTextarea.setAttribute('data-auto-size', 'false');
1045+
modalTextarea.setAttribute('data-clear-button', 'false');
10291046
}
10301047

10311048
// Copy validation attributes from original textarea for Metro UI validation

packages/app/src/builder-ui/workspace/Workspace.class.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,10 @@ export class Workspace extends EventEmitter {
671671
await delay(100);
672672
this.domElement.style.opacity = '1';
673673
zoomElement.style.transition = origTransition;
674+
} else if (isRemixedTemplate) {
675+
// For remixed templates, set a default zoom level to show more of the canvas
676+
await delay(100);
677+
await this.zoomTo(0.5); // Zoom out to 50% to see more components
674678
}
675679

676680
// triggered when agent is loaded ONLY

packages/app/src/react/features/ai-chat/components/header.tsx

Lines changed: 113 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ interface ILLMModels {
4848
value: string;
4949
tags: string[];
5050
default?: boolean;
51+
provider: string;
5152
}
5253

5354
interface ModelAgent {
@@ -110,6 +111,7 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
110111
value: model.entryId,
111112
tags: model.tags,
112113
default: model?.default || false,
114+
provider: model.provider || '',
113115
}),
114116
);
115117

@@ -128,13 +130,9 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
128130
}
129131
};
130132

131-
if (isDropdownOpen) {
132-
document.addEventListener('mousedown', handleClickOutside);
133-
}
133+
if (isDropdownOpen) document.addEventListener('mousedown', handleClickOutside);
134134

135-
return () => {
136-
document.removeEventListener('mousedown', handleClickOutside);
137-
};
135+
return () => document.removeEventListener('mousedown', handleClickOutside);
138136
}, [isDropdownOpen]);
139137

140138
/**
@@ -165,6 +163,46 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
165163
setIsDropdownOpen((prev) => !prev);
166164
}, []);
167165

166+
// Badge priority order for sorting (lower number = higher priority)
167+
const BADGE_PRIORITY: Record<string, number> = {
168+
enterprise: 1,
169+
personal: 2,
170+
limited: 3,
171+
smythos: 999, // SmythOS models come last
172+
};
173+
174+
/**
175+
* Get badge priority for sorting
176+
* @param tags - Array of model tags
177+
* @returns Priority number (lower = higher priority)
178+
*/
179+
const getBadgePriority = (tags: string[]) => {
180+
return BADGE_PRIORITY[getTempBadge(tags).toLowerCase()] || 999;
181+
};
182+
183+
// Get unique providers and group models by provider
184+
const providers = Array.from(new Set(llmModels.map((model) => model.provider)));
185+
const modelsByProvider = providers.map((provider) => {
186+
const providerModels = llmModels.filter((model) => model.provider === provider);
187+
188+
// Sort models with multiple criteria:
189+
// 1. Badge priority (Enterprise > Personal > Limited > SmythOS)
190+
// 2. Then by default flag (default models first)
191+
// 3. Finally alphabetically by label
192+
const sortedModels = [...providerModels].sort((a, b) => {
193+
// First priority: Badge priority (Enterprise > Personal > Limited > SmythOS)
194+
const priorityA = getBadgePriority(a.tags);
195+
const priorityB = getBadgePriority(b.tags);
196+
if (priorityA !== priorityB) return priorityA - priorityB;
197+
// Second priority: Default models come first
198+
if (a.default !== b.default) return a.default ? 1 : -1;
199+
// Third priority: Alphabetically by label
200+
return a.label.localeCompare(b.label);
201+
});
202+
203+
return { name: provider, models: sortedModels };
204+
});
205+
168206
return (
169207
<div className="w-full bg-white border-b border-[#e5e5e5] h-14 flex justify-center absolute top-0 left-0 z-10 px-2.5 lg:px-0">
170208
<div className="w-full max-w-4xl flex justify-between items-center">
@@ -182,7 +220,7 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
182220
)}
183221
</figure>
184222

185-
<div className="flex items-start justify-center flex-col">
223+
<div className="flex items-start justify-center flex-col w-full">
186224
{isLoading.agent ? (
187225
<Skeleton
188226
className={cn(
@@ -197,18 +235,16 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
197235
)}
198236

199237
{/* Model selection */}
200-
<div className="flex items-center group">
238+
<div className="flex items-center group w-full">
201239
{isLoading.settings || isModelsLoading ? (
202240
<Skeleton
203241
className={cn('w-25 h-4 rounded ', isLoading.settings && 'rounded-t-none')}
204242
/>
205243
) : (
206-
<div ref={dropdownRef} className="relative leading-none">
244+
<div ref={dropdownRef} className="relative leading-none w-full">
207245
{/* Selected value display - clickable trigger */}
208246
<Tooltip
209-
content={
210-
isModelAgent ? 'Model selection is disabled for model agents' : 'Select model'
211-
}
247+
content={isModelAgent ? 'Default agents have a fixed model' : 'Select model'}
212248
placement="bottom"
213249
>
214250
<button
@@ -247,27 +283,71 @@ export const ChatHeader: FC<ChatHeaderProps> = (props) => {
247283

248284
{/* Dropdown menu - only show if not a model agent */}
249285
{isDropdownOpen && !isModelAgent && (
250-
<div className="absolute top-full -left-3 mt-1 bg-slate-100 border border-slate-200 rounded-md shadow-lg z-50 min-w-[200px] max-h-[300px] overflow-y-auto divide-y divide-slate-200">
251-
{llmModels.map((model) => {
252-
let badge = getTempBadge(model.tags);
253-
badge = badge ? ' (' + badge + ')' : '';
254-
const isSelected = model.value === currentModel;
255-
256-
return (
257-
<button
258-
key={model.value}
259-
type="button"
260-
onClick={() => handleModelChange(model.value)}
261-
className={`w-full text-left px-3 py-2 text-xs hover:bg-slate-200 transition-colors focus:outline-none ${
262-
isSelected
263-
? 'bg-slate-300 text-slate-800 font-medium'
264-
: 'text-slate-600'
265-
}`}
266-
>
267-
{model.label + badge}
268-
</button>
269-
);
270-
})}
286+
<div className="absolute top-full -left-3 z-50 mt-1 bg-slate-100 rounded-md shadow-xl border-t border-slate-200 min-w-[250px] max-h-[500px] overflow-y-auto divide-y divide-slate-200">
287+
<div className="py-1">
288+
{modelsByProvider.map((provider, providerIndex) => (
289+
<div key={providerIndex} className="mb-2 last:mb-1">
290+
<div className="px-4 py-1.5 flex items-center gap-2">
291+
<span className="font-semibold text-sm text-slate-900">
292+
{provider.name}
293+
</span>
294+
</div>
295+
296+
<div className="pl-2">
297+
{provider.models.map((model, modelIndex) => {
298+
const badge = getTempBadge(model.tags);
299+
const isSelected = model.value === currentModel;
300+
301+
return (
302+
<button
303+
key={modelIndex}
304+
type="button"
305+
onClick={() => handleModelChange(model.value)}
306+
className={cn(
307+
'w-full px-4 py-1.5 text-left hover:bg-slate-200 transition-colors flex items-center justify-between gap-2',
308+
isSelected
309+
? 'font-semibold bg-slate-200/90 text-slate-900 border-l-2 border-slate-700'
310+
: 'text-slate-700',
311+
)}
312+
>
313+
<span className="text-sm flex items-center gap-1">
314+
{model.label}
315+
{badge && (
316+
<span
317+
className={cn(
318+
'text-[10px] rounded-full px-1.5',
319+
badge === 'SmythOS'
320+
? 'bg-primary-100/50 text-slate-700'
321+
: 'bg-primary-300 text-slate-700',
322+
)}
323+
>
324+
{badge}
325+
</span>
326+
)}
327+
</span>
328+
329+
{isSelected && (
330+
<svg
331+
className="w-5 h-5 text-slate-700 shrink-0"
332+
fill="none"
333+
stroke="currentColor"
334+
viewBox="0 0 24 24"
335+
>
336+
<path
337+
strokeLinecap="round"
338+
strokeLinejoin="round"
339+
strokeWidth={2.5}
340+
d="M5 13l4 4L19 7"
341+
/>
342+
</svg>
343+
)}
344+
</button>
345+
);
346+
})}
347+
</div>
348+
</div>
349+
))}
350+
</div>
271351
</div>
272352
)}
273353
</div>

packages/app/src/react/features/ai-chat/hooks/use-chat.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -359,26 +359,30 @@ export const useChat = (config: IUseChatConfig): IUseChatReturn => {
359359
onThinking: (thinkingMsg: string, type: TThinkingType, conversationTurnId?: string) => {
360360
if (conversationTurnId && !currentTurnIdRef.current) {
361361
currentTurnIdRef.current = conversationTurnId; // Capture turn ID from thinking messages
362-
363-
// Optimized: Consistent with other state updates
364-
setMessages((prev) => {
365-
const lastIndex = prev.length - 1;
366-
if (lastIndex >= 0) {
367-
// Only update last element without copying entire array
368-
const newMessages = prev.slice(0, -1);
369-
newMessages.push({ ...prev[lastIndex], conversationTurnId });
370-
return newMessages;
371-
}
372-
return prev;
373-
});
374362
}
375363

376364
// Mark that we're in thinking state
377365
isThinkingRef.current = true;
378366
hasThinkingOccurredRef.current = true;
379367

380-
// Update thinking message
381-
updateThinkingMessage(thinkingMsg);
368+
// Update thinking message and change type from 'loading' to 'system' to close loading indicator
369+
setMessages((prev) => {
370+
const lastIndex = prev.length - 1;
371+
if (lastIndex >= 0) {
372+
const lastMsg = prev[lastIndex];
373+
// Only update last element without copying entire array
374+
const newMessages = prev.slice(0, -1);
375+
newMessages.push({
376+
...lastMsg,
377+
thinkingMessage: thinkingMsg,
378+
conversationTurnId: conversationTurnId || lastMsg.conversationTurnId,
379+
// Change type from 'loading' to 'system' to close loading indicator
380+
type: lastMsg.type === 'loading' ? 'system' : lastMsg.type,
381+
});
382+
return newMessages;
383+
}
384+
return prev;
385+
});
382386
},
383387
onToolCall: (
384388
toolName: string,

packages/app/src/react/features/ai-chat/hooks/use-fileupload.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export const useFileUpload = (params?: {
118118
const result = await uploadFile(file, agentId, chatId);
119119

120120
if (result.success) {
121-
// Normalize response from runtime (/aichat/upload)
122121
const data = result.data as {
123122
files?: Array<{ url?: string; mimetype?: string }>;
124123
file?: { url?: string; type?: string };

0 commit comments

Comments
 (0)