Skip to content

Commit 67acb6d

Browse files
author
克隆(宗可龙)
committed
Merge branch 'main' of https://github.com/onlook-dev/onlook into translation_zh
* 'main' of https://github.com/onlook-dev/onlook: fix: error when add new color to group (onlook-dev#1683) update searching brand color picker (onlook-dev#1681) Quicker build with freestyle (onlook-dev#1685) Add tool result support (onlook-dev#1684) feat: Include tool-result in chat (onlook-dev#1682)
2 parents 51973a9 + 0ab072b commit 67acb6d

File tree

22 files changed

+798
-428
lines changed

22 files changed

+798
-428
lines changed

apps/studio/electron/main/assets/styles.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export async function updateTailwindColorConfig(
5656
}
5757

5858
const camelCaseName = newName === DEFAULT_COLOR_NAME ? newName : camelCase(newName);
59+
5960
return originalKey
6061
? updateTailwindColorVariable(colorUpdate, originalKey, newColor, camelCaseName, theme)
6162
: createTailwindColorVariable(colorUpdate, newColor, camelCaseName, parentName);
@@ -217,7 +218,7 @@ function updateTailwindConfigFile(
217218
colorProp.key.name = newName;
218219
keyUpdated = true;
219220

220-
// Then we need to update the child css variables
221+
// Then we need to update the child css variables or direct color values
221222
if (colorProp.value.type === 'ObjectExpression') {
222223
colorProp.value.properties.forEach((nestedProp) => {
223224
if (
@@ -241,6 +242,11 @@ function updateTailwindConfigFile(
241242
);
242243
}
243244
});
245+
} else if (colorProp.value.type === 'StringLiteral') {
246+
colorProp.value.value = colorProp.value.value.replace(
247+
new RegExp(`--${parentKey}`, 'g'),
248+
`--${newName}`,
249+
);
244250
}
245251
}
246252
} else {
@@ -399,20 +405,37 @@ async function updateTailwindCssVariable(
399405
Once(root: Root) {
400406
let rootValue: string | undefined;
401407
let darkValue: string | undefined;
408+
let hasRootVar = false;
409+
let hasDarkVar = false;
402410

403411
root.walkRules(':root', (rule) => {
404412
rule.walkDecls(`--${originalName}`, (decl) => {
405413
rootValue = decl.value;
414+
hasRootVar = true;
406415
});
407416
});
408417

409418
root.walkRules('.dark', (rule) => {
410419
rule.walkDecls(`--${originalName}`, (decl) => {
411420
darkValue = decl.value;
421+
hasDarkVar = true;
412422
});
413423
});
414424

415-
// Process both :root and .dark rules
425+
// Create new variables if they don't exist and we have both newVarName and newColor
426+
if (newVarName && newColor) {
427+
if (!hasRootVar) {
428+
root.walkRules(':root', (rule) => {
429+
rule.append({ prop: `--${newVarName}`, value: newColor });
430+
});
431+
}
432+
if (!hasDarkVar) {
433+
root.walkRules('.dark', (rule) => {
434+
rule.append({ prop: `--${newVarName}`, value: newColor });
435+
});
436+
}
437+
}
438+
416439
root.walkRules(/^(:root|\.dark)$/, (rule) => {
417440
const isDarkTheme = rule.selector === '.dark';
418441
const shouldUpdateValue =
@@ -464,6 +487,16 @@ async function updateTailwindCssVariable(
464487
}
465488
}
466489

490+
// Handle variable usages in other declarations
491+
if (decl.value.includes(`var(--${originalName})`)) {
492+
if (newVarName && newVarName !== originalName) {
493+
decl.value = decl.value.replace(
494+
new RegExp(`var\\(--${originalName}\\)`, 'g'),
495+
`var(--${newVarName})`,
496+
);
497+
}
498+
}
499+
467500
// Handle nested variables rename if existed
468501
if (
469502
newVarName &&
@@ -479,6 +512,37 @@ async function updateTailwindCssVariable(
479512
}
480513
});
481514
});
515+
516+
// update Tailwind classes that use the variable
517+
root.walkAtRules('layer', (layerRule) => {
518+
layerRule.walkRules((rule) => {
519+
rule.nodes?.forEach((node) => {
520+
// Check if this is an @apply at-rule
521+
if (node.type === 'atrule' && 'name' in node && node.name === 'apply') {
522+
const value = 'params' in node ? node.params : '';
523+
524+
const utilityPattern = new RegExp(
525+
`[a-z-]+-${originalName}\\b`,
526+
'g',
527+
);
528+
const hasMatch = utilityPattern.test(value);
529+
530+
if (hasMatch) {
531+
const newValue = value.replace(utilityPattern, (match) => {
532+
const replaced = match.replace(
533+
originalName,
534+
newVarName || originalName,
535+
);
536+
return replaced;
537+
});
538+
if ('params' in node) {
539+
node.params = newValue;
540+
}
541+
}
542+
}
543+
});
544+
});
545+
});
482546
},
483547
},
484548
]);
@@ -636,7 +700,7 @@ async function updateClassReferences(
636700
const oldClassPattern = new RegExp(`(^|-)${oldClass}(-|$)`);
637701
if (oldClassPattern.test(currentClass)) {
638702
hasChanges = true;
639-
return currentClass.replace(oldClass, newClass);
703+
return newClass ? currentClass.replace(oldClass, newClass) : '';
640704
}
641705
}
642706
return currentClass;
@@ -669,6 +733,7 @@ async function updateClassReferences(
669733
async function deleteColorGroup(
670734
{ configPath, cssPath, configContent, cssContent }: ColorUpdate,
671735
groupName: string,
736+
projectRoot: string,
672737
colorName?: string,
673738
): Promise<UpdateResult> {
674739
const camelCaseName = camelCase(groupName);
@@ -757,6 +822,14 @@ async function deleteColorGroup(
757822
const output = generate(updateAst, { retainLines: true, compact: false }, configContent);
758823
fs.writeFileSync(configPath, output.code);
759824

825+
// Also delete the color group in the class references
826+
const replacements: ClassReplacement[] = [];
827+
replacements.push({
828+
oldClass: camelCaseName,
829+
newClass: '',
830+
});
831+
await updateClassReferences(projectRoot, replacements);
832+
760833
return { success: true };
761834
}
762835

@@ -771,7 +844,7 @@ export async function deleteTailwindColorGroup(
771844
return { success: false, error: 'Failed to prepare color update' };
772845
}
773846

774-
return deleteColorGroup(colorUpdate, groupName, colorName);
847+
return deleteColorGroup(colorUpdate, groupName, projectRoot, colorName);
775848
} catch (error) {
776849
return {
777850
success: false,

apps/studio/electron/main/chat/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ class LlmManager {
9696
headers: {
9797
'anthropic-beta': 'output-128k-2025-02-19',
9898
},
99+
onStepFinish: ({ toolResults }) => {
100+
for (const toolResult of toolResults) {
101+
this.emitMessagePart(toolResult);
102+
}
103+
},
99104
});
100105
const streamParts: TextStreamPart<ToolSet>[] = [];
101106
for await (const partialStream of fullStream) {

apps/studio/electron/main/hosting/index.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,15 @@ import {
2121
} from '@onlook/models/hosting';
2222
import { isEmptyString, isNullOrUndefined } from '@onlook/utility';
2323
import {
24+
type DeploymentSource,
2425
type FreestyleDeployWebConfiguration,
2526
type FreestyleDeployWebSuccessResponse,
2627
} from 'freestyle-sandboxes';
28+
import { prepareDirForDeployment } from 'freestyle-sandboxes/utils';
2729
import { mainWindow } from '..';
2830
import analytics from '../analytics';
2931
import { getRefreshedAuthTokens } from '../auth';
30-
import {
31-
postprocessNextBuild,
32-
preprocessNextBuild,
33-
serializeFiles,
34-
updateGitignore,
35-
type FileRecord,
36-
} from './helpers';
32+
import { postprocessNextBuild, preprocessNextBuild, updateGitignore } from './helpers';
3733
import { runBuildScript } from './run';
3834
import { LogTimer } from '/common/helpers/timer';
3935

@@ -85,12 +81,12 @@ class HostingManager {
8581

8682
// Serialize the files for deployment
8783
const NEXT_BUILD_OUTPUT_PATH = `${folderPath}/${CUSTOM_OUTPUT_DIR}/standalone`;
88-
const files: FileRecord = serializeFiles(NEXT_BUILD_OUTPUT_PATH);
84+
const source: DeploymentSource = await prepareDirForDeployment(NEXT_BUILD_OUTPUT_PATH);
8985

9086
this.emitState(PublishStatus.LOADING, 'Deploying project...');
9187
timer.log('Files serialized, sending to Freestyle...');
9288

93-
const id = await this.sendHostingPostRequest(files, urls);
89+
const id = await this.sendHostingPostRequest(source, urls);
9490
timer.log('Deployment completed');
9591

9692
this.emitState(PublishStatus.PUBLISHED, 'Deployment successful, deployment ID: ' + id);
@@ -186,7 +182,7 @@ class HostingManager {
186182

187183
async unpublish(urls: string[]): Promise<PublishResponse> {
188184
try {
189-
const id = await this.sendHostingPostRequest({}, urls);
185+
const id = await this.sendHostingPostRequest({ files: {}, kind: 'files' }, urls);
190186
this.emitState(PublishStatus.UNPUBLISHED, 'Deployment deleted with ID: ' + id);
191187

192188
analytics.track('hosting unpublish', {
@@ -210,23 +206,23 @@ class HostingManager {
210206
}
211207
}
212208

213-
async sendHostingPostRequest(files: FileRecord, urls: string[]): Promise<string> {
209+
async sendHostingPostRequest(source: DeploymentSource, urls: string[]): Promise<string> {
214210
const authTokens = await getRefreshedAuthTokens();
215211
const config: FreestyleDeployWebConfiguration = {
216212
domains: urls,
217213
entrypoint: 'server.js',
218214
};
219215

220216
const res: Response = await fetch(
221-
`${import.meta.env.VITE_SUPABASE_API_URL}${FUNCTIONS_ROUTE}${BASE_API_ROUTE}${ApiRoutes.HOSTING_V2}${HostingRoutes.DEPLOY_WEB}`,
217+
`${import.meta.env.VITE_SUPABASE_API_URL}${FUNCTIONS_ROUTE}${BASE_API_ROUTE}${ApiRoutes.HOSTING_V2}${HostingRoutes.DEPLOY_WEB_V2}`,
222218
{
223219
method: 'POST',
224220
headers: {
225221
'Content-Type': 'application/json',
226222
Authorization: `Bearer ${authTokens.accessToken}`,
227223
},
228224
body: JSON.stringify({
229-
files,
225+
source,
230226
config,
231227
}),
232228
},

apps/studio/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"@shikijs/monaco": "^1.22.0",
4848
"@supabase/supabase-js": "^2.45.6",
4949
"@xterm/xterm": "^5.6.0-beta.98",
50-
"ai": "^4.1.61",
50+
"ai": "^4.2.6",
5151
"browser-image-compression": "^2.0.2",
5252
"detect-port": "^2.1.0",
5353
"download": "^8.0.0",
@@ -57,7 +57,7 @@
5757
"embla-carousel-wheel-gestures": "^8.0.1",
5858
"fflate": "^0.8.2",
5959
"fix-path": "^4.0.0",
60-
"freestyle-sandboxes": "^0.0.20",
60+
"freestyle-sandboxes": "^0.0.35",
6161
"i18next": "^24.2.2",
6262
"istextorbinary": "^9.5.0",
6363
"js-string-escape": "^1.0.1",

apps/studio/src/lib/editor/engine/chat/conversation/conversation.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import type { CoreMessage } from 'ai';
44
import { makeAutoObservable } from 'mobx';
55
import { nanoid } from 'nanoid/non-secure';
66
import { AssistantChatMessageImpl } from '../message/assistant';
7+
import { ToolChatMessageImpl } from '../message/tool';
78
import { UserChatMessageImpl } from '../message/user';
89

10+
type ChatMessageImpl = UserChatMessageImpl | AssistantChatMessageImpl | ToolChatMessageImpl;
11+
912
export class ChatConversationImpl implements ChatConversation {
1013
id: string;
1114
projectId: string;
1215
displayName: string | null = null;
13-
messages: (UserChatMessageImpl | AssistantChatMessageImpl)[];
16+
messages: ChatMessageImpl[];
1417
createdAt: string;
1518
updatedAt: string;
1619

@@ -26,7 +29,7 @@ export class ChatConversationImpl implements ChatConversation {
2629
totalTokens: 0,
2730
};
2831

29-
constructor(projectId: string, messages: (UserChatMessageImpl | AssistantChatMessageImpl)[]) {
32+
constructor(projectId: string, messages: ChatMessageImpl[]) {
3033
makeAutoObservable(this);
3134
this.id = nanoid();
3235
this.projectId = projectId;
@@ -54,7 +57,7 @@ export class ChatConversationImpl implements ChatConversation {
5457
return null;
5558
}
5659
})
57-
.filter((m) => m !== null) as (UserChatMessageImpl | AssistantChatMessageImpl)[];
60+
.filter((m) => m !== null) as ChatMessageImpl[];
5861
conversation.createdAt = data.createdAt;
5962
conversation.updatedAt = data.updatedAt;
6063

@@ -86,7 +89,6 @@ export class ChatConversationImpl implements ChatConversation {
8689
} else {
8790
messages.push(...this.messages.map((m) => m.toCoreMessage()));
8891
}
89-
9092
return messages;
9193
}
9294

@@ -96,12 +98,12 @@ export class ChatConversationImpl implements ChatConversation {
9698
);
9799
}
98100

99-
appendMessage(message: UserChatMessageImpl | AssistantChatMessageImpl) {
101+
appendMessage(message: ChatMessageImpl) {
100102
this.messages = [...this.messages, message];
101103
this.updatedAt = new Date().toISOString();
102104
}
103105

104-
removeAllMessagesAfter(message: UserChatMessageImpl | AssistantChatMessageImpl) {
106+
removeAllMessagesAfter(message: ChatMessageImpl) {
105107
const index = this.messages.findIndex((m) => m.id === message.id);
106108
this.messages = this.messages.slice(0, index + 1);
107109
this.updatedAt = new Date().toISOString();
@@ -117,7 +119,7 @@ export class ChatConversationImpl implements ChatConversation {
117119
return this.messages.findLast((message) => message.role === ChatMessageRole.USER);
118120
}
119121

120-
updateMessage(message: UserChatMessageImpl | AssistantChatMessageImpl) {
122+
updateMessage(message: ChatMessageImpl) {
121123
const index = this.messages.findIndex((m) => m.id === message.id);
122124
this.messages[index] = message;
123125
this.updatedAt = new Date().toISOString();

apps/studio/src/lib/editor/engine/chat/conversation/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { invokeMainChannel, sendAnalytics } from '@/lib/utils';
33
import { type ChatConversation, type ChatMessageContext } from '@onlook/models/chat';
44
import { MainChannels } from '@onlook/models/constants';
55
import type { Project } from '@onlook/models/projects';
6-
import type { CoreAssistantMessage, CoreUserMessage } from 'ai';
6+
import type { CoreAssistantMessage, CoreToolMessage, CoreUserMessage } from 'ai';
77
import { makeAutoObservable, reaction } from 'mobx';
88
import type { EditorEngine } from '../..';
99
import { AssistantChatMessageImpl } from '../message/assistant';
10+
import { ToolChatMessageImpl } from '../message/tool';
1011
import { UserChatMessageImpl } from '../message/user';
1112
import { MOCK_CHAT_MESSAGES } from '../mockData';
1213
import { ChatConversationImpl } from './conversation';
@@ -196,4 +197,15 @@ export class ConversationManager {
196197
this.saveConversationToStorage();
197198
return newMessage;
198199
}
200+
201+
addCoreToolMessage(coreMessage: CoreToolMessage): ToolChatMessageImpl | undefined {
202+
if (!this.current) {
203+
console.error('No conversation found');
204+
return;
205+
}
206+
const newMessage = ToolChatMessageImpl.fromCoreMessage(coreMessage);
207+
this.current.appendMessage(newMessage);
208+
this.saveConversationToStorage();
209+
return newMessage;
210+
}
199211
}

apps/studio/src/lib/editor/engine/chat/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ export class ChatManager {
219219
if (!userMessage) {
220220
console.error('Failed to add user message');
221221
}
222+
} else if (message.role === ChatMessageRole.TOOL) {
223+
const toolMessage = this.conversation.addCoreToolMessage(message);
224+
if (!toolMessage) {
225+
console.error('Failed to add tool message');
226+
}
222227
}
223228
}
224229
}

apps/studio/src/lib/editor/engine/chat/message/assistant.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,9 @@ export class AssistantChatMessageImpl implements AssistantChatMessage {
2020
}
2121

2222
toCoreMessage(): CoreAssistantMessage {
23-
if (typeof this.content === 'string') {
24-
return this;
25-
}
26-
// TODO: Perhaps we should add tool-result instead of filtering tool-call?
27-
const filteredContent = this.content.filter((part) => part.type !== 'tool-call');
2823
return {
2924
...this,
30-
content: filteredContent,
25+
content: this.content,
3126
};
3227
}
3328

0 commit comments

Comments
 (0)