@myrialabs/clopen 0.1.1 → 0.1.3
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/backend/index.ts +4 -1
- package/backend/lib/engine/adapters/opencode/message-converter.ts +53 -2
- package/backend/lib/engine/adapters/opencode/stream.ts +89 -5
- package/backend/lib/project/status-manager.ts +221 -181
- package/frontend/lib/components/chat/message/ChatMessages.svelte +16 -4
- package/frontend/lib/components/chat/tools/AgentTool.svelte +12 -11
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/BashTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +3 -1
- package/frontend/lib/components/chat/tools/EditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/GlobTool.svelte +12 -12
- package/frontend/lib/components/chat/tools/GrepTool.svelte +5 -5
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +2 -2
- package/frontend/lib/components/chat/tools/ReadTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/TaskStopTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TaskTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/WriteTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +5 -5
- package/frontend/lib/components/common/Button.svelte +1 -1
- package/frontend/lib/components/common/Card.svelte +3 -3
- package/frontend/lib/components/common/Input.svelte +3 -3
- package/frontend/lib/components/common/LoadingSpinner.svelte +1 -1
- package/frontend/lib/components/common/Select.svelte +6 -6
- package/frontend/lib/components/common/Textarea.svelte +3 -3
- package/frontend/lib/components/files/FileViewer.svelte +1 -1
- package/frontend/lib/components/git/ChangesSection.svelte +2 -4
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +9 -29
- package/frontend/lib/components/preview/browser/components/Container.svelte +17 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +2 -2
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +0 -6
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +15 -15
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +2 -2
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +380 -383
- package/frontend/lib/components/workspace/MobileNavigator.svelte +391 -395
- package/frontend/lib/components/workspace/PanelHeader.svelte +115 -4
- package/frontend/lib/components/workspace/ViewMenu.svelte +9 -25
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +29 -4
- package/frontend/lib/services/notification/global-stream-monitor.ts +77 -86
- package/frontend/lib/services/project/status.service.ts +160 -159
- package/frontend/lib/stores/ui/workspace.svelte.ts +326 -283
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
const { toolInput }: { toolInput: GrepToolInput } = $props();
|
|
9
9
|
|
|
10
|
-
const pattern = toolInput.input.pattern || '';
|
|
11
|
-
const searchPath = toolInput.input.path || 'current directory';
|
|
10
|
+
const pattern = $derived(toolInput.input.pattern || '');
|
|
11
|
+
const searchPath = $derived(toolInput.input.path || 'current directory');
|
|
12
12
|
|
|
13
13
|
// Get active search parameters for display
|
|
14
14
|
function getActiveParameters() {
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
return params;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const activeParameters = getActiveParameters();
|
|
58
|
+
const activeParameters = $derived(getActiveParameters());
|
|
59
59
|
|
|
60
|
-
const formattedPattern = truncateText(pattern, 40);
|
|
61
|
-
const formattedPath = formatPath(searchPath);
|
|
60
|
+
const formattedPattern = $derived(truncateText(pattern, 40));
|
|
61
|
+
const formattedPath = $derived(formatPath(searchPath));
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
64
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: ListMcpResourcesToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const server = toolInput.input.server;
|
|
8
|
+
const server = $derived(toolInput.input.server);
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: NotebookEditToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const notebookPath = toolInput.input.notebook_path;
|
|
9
|
-
const fileName = notebookPath.split(/[/\\]/).pop() || notebookPath;
|
|
10
|
-
const cellId = toolInput.input.cell_id;
|
|
11
|
-
const cellType = toolInput.input.cell_type || 'code';
|
|
12
|
-
const editMode = toolInput.input.edit_mode || 'replace';
|
|
13
|
-
const newSource = toolInput.input.new_source;
|
|
8
|
+
const notebookPath = $derived(toolInput.input.notebook_path);
|
|
9
|
+
const fileName = $derived(notebookPath.split(/[/\\]/).pop() || notebookPath);
|
|
10
|
+
const cellId = $derived(toolInput.input.cell_id);
|
|
11
|
+
const cellType = $derived(toolInput.input.cell_type || 'code');
|
|
12
|
+
const editMode = $derived(toolInput.input.edit_mode || 'replace');
|
|
13
|
+
const newSource = $derived(toolInput.input.new_source);
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<FileHeader filePath={notebookPath} fileName={fileName} />
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: ReadMcpResourceToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const server = toolInput.input.server;
|
|
9
|
-
const uri = toolInput.input.uri;
|
|
8
|
+
const server = $derived(toolInput.input.server);
|
|
9
|
+
const uri = $derived(toolInput.input.uri);
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: ReadToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const filePath = toolInput.input.file_path || '';
|
|
9
|
-
const fileName = filePath.split(/[/\\]/).pop() || filePath || 'unknown';
|
|
10
|
-
const hasLimit = toolInput.input.limit !== undefined;
|
|
11
|
-
const hasOffset = toolInput.input.offset !== undefined;
|
|
8
|
+
const filePath = $derived(toolInput.input.file_path || '');
|
|
9
|
+
const fileName = $derived(filePath.split(/[/\\]/).pop() || filePath || 'unknown');
|
|
10
|
+
const hasLimit = $derived(toolInput.input.limit !== undefined);
|
|
11
|
+
const hasOffset = $derived(toolInput.input.offset !== undefined);
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: TaskStopToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const taskId = toolInput.input.task_id || toolInput.input.shell_id || 'unknown';
|
|
8
|
+
const taskId = $derived(toolInput.input.task_id || toolInput.input.shell_id || 'unknown');
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
return desc;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const formattedDesc = formatDescription(toolInput.input.description || '');
|
|
18
|
+
const formattedDesc = $derived(formatDescription(toolInput.input.description || ''));
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: TodoWriteToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const todos = toolInput.input.todos;
|
|
9
|
-
const totalTodos = todos.length;
|
|
10
|
-
const completedTodos = todos.filter((t) => t.status === 'completed').length;
|
|
11
|
-
const percentage = totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0;
|
|
8
|
+
const todos = $derived(toolInput.input.todos);
|
|
9
|
+
const totalTodos = $derived(todos.length);
|
|
10
|
+
const completedTodos = $derived(todos.filter((t) => t.status === 'completed').length);
|
|
11
|
+
const percentage = $derived(totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0);
|
|
12
12
|
|
|
13
13
|
// Helper functions for todo items
|
|
14
14
|
function getStatusIcon(status: string): IconName {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
return query;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const formattedQuery = formatQuery(toolInput.input.query || '');
|
|
18
|
+
const formattedQuery = $derived(formatQuery(toolInput.input.query || ''));
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
const { toolInput }: { toolInput: WriteToolInput } = $props();
|
|
7
7
|
|
|
8
|
-
const filePath = toolInput.input.file_path || '';
|
|
9
|
-
const fileName = filePath.split(/[/\\]/).pop() || filePath || 'unknown';
|
|
10
|
-
const content = toolInput.input.content || '';
|
|
8
|
+
const filePath = $derived(toolInput.input.file_path || '');
|
|
9
|
+
const fileName = $derived(filePath.split(/[/\\]/).pop() || filePath || 'unknown');
|
|
10
|
+
const content = $derived(toolInput.input.content || '');
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<FileHeader
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const style = styles[type];
|
|
60
|
-
const formattedCode = formatCode(code);
|
|
61
|
-
const isTerminal = isTerminalOutput(code);
|
|
59
|
+
const style = $derived(styles[type]);
|
|
60
|
+
const formattedCode = $derived(formatCode(code));
|
|
61
|
+
const isTerminal = $derived(isTerminalOutput(code));
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
64
|
<div>
|
|
@@ -325,8 +325,8 @@
|
|
|
325
325
|
return result;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
const diffGroups = computeDiff(oldString, newString);
|
|
329
|
-
const hasChanges = diffGroups.some(group => group.type === 'change');
|
|
328
|
+
const diffGroups = $derived(computeDiff(oldString, newString));
|
|
329
|
+
const hasChanges = $derived(diffGroups.some(group => group.type === 'change'));
|
|
330
330
|
</script>
|
|
331
331
|
|
|
332
332
|
<div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
const { filePath, fileName, iconColor, badges = [], box = true }: Props = $props();
|
|
16
16
|
|
|
17
|
-
const displayFileName = fileName || filePath.split(/[/\\]/).pop() || filePath;
|
|
17
|
+
const displayFileName = $derived(fileName || filePath.split(/[/\\]/).pop() || filePath);
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<div class={box ? "bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3" : ""}>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const { icon, text, iconColor = 'text-slate-500', textColor = 'text-slate-600 dark:text-slate-400', title="" }: Props = $props();
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<div class="flex items-
|
|
17
|
-
<Icon name={icon} class="{iconColor} w-3.5 h-3.5" />
|
|
16
|
+
<div class="flex items-start gap-1.5" {title}>
|
|
17
|
+
<Icon name={icon} class="{iconColor} w-3.5 h-3.5 flex-none" />
|
|
18
18
|
<span class="text-xs {textColor}">{text}</span>
|
|
19
19
|
</div>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const { stats, columns = 3 }: Props = $props();
|
|
14
14
|
|
|
15
|
-
const gridClass = columns === 2 ? 'grid-cols-2' : columns === 4 ? 'grid-cols-4' : 'grid-cols-3';
|
|
15
|
+
const gridClass = $derived(columns === 2 ? 'grid-cols-2' : columns === 4 ? 'grid-cols-4' : 'grid-cols-3');
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<div class="grid {gridClass} gap-4 text-center text-sm">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
return { mainCommand, args };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const parsedCommand = $derived(parseCommandParts(command));
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<!-- Description (if provided) -->
|
|
@@ -38,15 +38,15 @@
|
|
|
38
38
|
</div>
|
|
39
39
|
{/if}
|
|
40
40
|
</div>
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
<!-- Terminal-style command display -->
|
|
43
43
|
<div class="bg-slate-50 dark:bg-slate-950 border border-slate-200/60 dark:border-slate-800/60 rounded-md p-2.5 font-mono text-sm">
|
|
44
44
|
<div class="flex items-start gap-2">
|
|
45
45
|
<span class="text-green-600 dark:text-green-400 select-none">$</span>
|
|
46
46
|
<div class="flex-1 text-slate-900 dark:text-slate-200 break-all">
|
|
47
|
-
<span class="text-violet-600 dark:text-violet-300 font-medium">{mainCommand}</span>
|
|
48
|
-
{#if args.length > 0}
|
|
49
|
-
<span class="text-slate-700 dark:text-slate-300"> {args.join(' ')}</span>
|
|
47
|
+
<span class="text-violet-600 dark:text-violet-300 font-medium">{parsedCommand.mainCommand}</span>
|
|
48
|
+
{#if parsedCommand.args.length > 0}
|
|
49
|
+
<span class="text-slate-700 dark:text-slate-300"> {parsedCommand.args.join(' ')}</span>
|
|
50
50
|
{/if}
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
lg: 'px-4 md:px-6 py-3 text-sm md:text-base rounded-lg'
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}
|
|
36
|
+
const buttonClasses = $derived(`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`);
|
|
37
37
|
|
|
38
38
|
function handleClick() {
|
|
39
39
|
if (!disabled && !loading && onclick) {
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
lg: 'p-4 md:p-6'
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
const clickableClasses = clickable
|
|
34
|
+
const clickableClasses = $derived(clickable
|
|
35
35
|
? 'cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800'
|
|
36
|
-
: '';
|
|
36
|
+
: '');
|
|
37
37
|
|
|
38
|
-
const cardClasses = `${baseClasses} ${variantClasses[variant]} ${paddingClasses[padding]} ${clickableClasses} ${className}
|
|
38
|
+
const cardClasses = $derived(`${baseClasses} ${variantClasses[variant]} ${paddingClasses[padding]} ${clickableClasses} ${className}`);
|
|
39
39
|
|
|
40
40
|
function handleClick() {
|
|
41
41
|
if (clickable && onclick) {
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
const baseClasses =
|
|
23
23
|
'block w-full px-3 py-3 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium';
|
|
24
24
|
|
|
25
|
-
const stateClasses = error
|
|
25
|
+
const stateClasses = $derived(error
|
|
26
26
|
? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
|
|
27
|
-
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
|
|
27
|
+
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
|
|
28
28
|
|
|
29
29
|
const backgroundClasses =
|
|
30
30
|
'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500';
|
|
31
31
|
|
|
32
|
-
const inputClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${className}
|
|
32
|
+
const inputClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${className}`);
|
|
33
33
|
|
|
34
34
|
function handleInput(event: Event) {
|
|
35
35
|
const target = event.target as HTMLInputElement;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
xl: 'text-xl'
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const spinnerClasses = `animate-spin rounded-full border-2 ${sizeClasses[size]} ${color ? colorClasses[color] : ''} border-t-transparent ${className}
|
|
38
|
+
const spinnerClasses = $derived(`animate-spin rounded-full border-2 ${sizeClasses[size]} ${color ? colorClasses[color] : ''} border-t-transparent ${className}`);
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
41
|
<div class="flex flex-col items-center space-y-2" {...props}>
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
const baseClasses =
|
|
23
23
|
'block w-full px-3 py-3 pr-10 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium appearance-none cursor-pointer';
|
|
24
24
|
|
|
25
|
-
const stateClasses = error
|
|
25
|
+
const stateClasses = $derived(error
|
|
26
26
|
? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
|
|
27
|
-
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
|
|
27
|
+
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
|
|
28
28
|
|
|
29
29
|
const backgroundClasses =
|
|
30
30
|
'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100';
|
|
31
31
|
|
|
32
|
-
const disabledClasses = disabled
|
|
33
|
-
? 'cursor-not-allowed opacity-60'
|
|
34
|
-
: '';
|
|
32
|
+
const disabledClasses = $derived(disabled
|
|
33
|
+
? 'cursor-not-allowed opacity-60'
|
|
34
|
+
: '');
|
|
35
35
|
|
|
36
|
-
const selectClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${disabledClasses} ${className}
|
|
36
|
+
const selectClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${disabledClasses} ${className}`);
|
|
37
37
|
|
|
38
38
|
function handleChange(event: Event) {
|
|
39
39
|
const target = event.target as HTMLSelectElement;
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
const baseClasses =
|
|
24
24
|
'block w-full px-3 py-3 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium';
|
|
25
25
|
|
|
26
|
-
const stateClasses = error
|
|
26
|
+
const stateClasses = $derived(error
|
|
27
27
|
? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
|
|
28
|
-
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
|
|
28
|
+
: 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
|
|
29
29
|
|
|
30
30
|
const backgroundClasses =
|
|
31
31
|
'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500';
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
vertical: 'resize-y'
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const textareaClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${resizeClasses[resize]} ${className}
|
|
40
|
+
const textareaClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${resizeClasses[resize]} ${className}`);
|
|
41
41
|
|
|
42
42
|
function handleInput(event: Event) {
|
|
43
43
|
const target = event.target as HTMLTextAreaElement;
|
|
@@ -439,7 +439,7 @@
|
|
|
439
439
|
<!-- External change badge + refresh button -->
|
|
440
440
|
{#if externallyChanged && onForceReload}
|
|
441
441
|
<div class="flex items-center gap-1 mr-1">
|
|
442
|
-
<span class="text-
|
|
442
|
+
<span class="text-3xs px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-400 font-medium whitespace-nowrap">
|
|
443
443
|
Changed externally
|
|
444
444
|
</span>
|
|
445
445
|
<button
|
|
@@ -20,15 +20,13 @@
|
|
|
20
20
|
onResolve?: (path: string) => void;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
let {
|
|
24
24
|
title, icon, files, section,
|
|
25
|
-
collapsed = false,
|
|
25
|
+
collapsed: isCollapsed = $bindable(false),
|
|
26
26
|
onStage, onUnstage, onDiscard,
|
|
27
27
|
onStageAll, onUnstageAll, onDiscardAll,
|
|
28
28
|
onViewDiff, onResolve
|
|
29
29
|
}: Props = $props();
|
|
30
|
-
|
|
31
|
-
let isCollapsed = $state(collapsed);
|
|
32
30
|
</script>
|
|
33
31
|
|
|
34
32
|
{#if files.length > 0}
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
onMcpCursorHide: () => {
|
|
115
|
-
|
|
115
|
+
// Cursor stays visible between automated steps until user interacts manually
|
|
116
116
|
},
|
|
117
117
|
transformBrowserToDisplayCoordinates: (browserX, browserY) => {
|
|
118
118
|
return transformBrowserToDisplayCoordinates(browserX, browserY);
|
|
@@ -360,44 +360,33 @@
|
|
|
360
360
|
function handleCanvasInteraction(action: any) {
|
|
361
361
|
const tab = activeTab;
|
|
362
362
|
if (tab && tab.sessionId) {
|
|
363
|
+
// Hide the AI cursor instantly if the human explicitly interacts
|
|
364
|
+
mcpVirtualCursor = { ...mcpVirtualCursor, visible: false };
|
|
363
365
|
coordinator.sendInteraction(action);
|
|
364
366
|
}
|
|
365
367
|
}
|
|
366
368
|
|
|
367
369
|
function transformBrowserToDisplayCoordinates(browserX: number, browserY: number): { x: number, y: number } | null {
|
|
368
370
|
let canvasElement: HTMLCanvasElement | null = null;
|
|
369
|
-
|
|
370
|
-
// Try to get canvas element from canvasAPI first (preferred method)
|
|
371
371
|
if (canvasAPI && canvasAPI.getCanvasElement) {
|
|
372
372
|
canvasElement = canvasAPI.getCanvasElement();
|
|
373
373
|
}
|
|
374
|
-
|
|
375
|
-
// Fallback: Try to get canvas element directly from DOM if canvasAPI not ready yet
|
|
376
|
-
// This handles race condition where context menu event arrives before canvasAPI is set
|
|
377
374
|
if (!canvasElement && typeof document !== 'undefined') {
|
|
378
|
-
|
|
379
|
-
const foundCanvas = document.querySelector('canvas[tabindex="0"]') as HTMLCanvasElement;
|
|
380
|
-
if (foundCanvas) {
|
|
381
|
-
canvasElement = foundCanvas;
|
|
382
|
-
debug.log('preview', `Using fallback DOM query to get canvas element`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (!canvasElement) {
|
|
387
|
-
debug.warn('preview', `Transform coordinates failed: canvasElement not found (browser: ${browserX}, ${browserY})`);
|
|
388
|
-
return null;
|
|
375
|
+
canvasElement = document.querySelector('canvas[tabindex="0"]') as HTMLCanvasElement;
|
|
389
376
|
}
|
|
377
|
+
if (!canvasElement) return null;
|
|
390
378
|
|
|
391
379
|
try {
|
|
392
380
|
const canvasRect = canvasElement.getBoundingClientRect();
|
|
381
|
+
if (!canvasRect || canvasElement.width === 0 || canvasElement.height === 0) return null;
|
|
382
|
+
|
|
393
383
|
const scaleX = canvasRect.width / canvasElement.width;
|
|
394
384
|
const scaleY = canvasRect.height / canvasElement.height;
|
|
395
385
|
const screenX = canvasRect.left + (browserX * scaleX);
|
|
396
386
|
const screenY = canvasRect.top + (browserY * scaleY);
|
|
397
387
|
|
|
398
388
|
return { x: screenX, y: screenY };
|
|
399
|
-
} catch (
|
|
400
|
-
debug.error('preview', 'Error transforming coordinates:', error);
|
|
389
|
+
} catch (e) {
|
|
401
390
|
return null;
|
|
402
391
|
}
|
|
403
392
|
}
|
|
@@ -500,6 +489,7 @@
|
|
|
500
489
|
bind:isStreamReady
|
|
501
490
|
bind:errorMessage
|
|
502
491
|
bind:virtualCursor
|
|
492
|
+
bind:mcpVirtualCursor
|
|
503
493
|
bind:canvasAPI
|
|
504
494
|
bind:previewDimensions
|
|
505
495
|
bind:lastFrameData={currentTabLastFrameData}
|
|
@@ -509,16 +499,6 @@
|
|
|
509
499
|
/>
|
|
510
500
|
</div>
|
|
511
501
|
|
|
512
|
-
<!-- Virtual Cursor - User -->
|
|
513
|
-
{#if !isCurrentTabMcpControlled()}
|
|
514
|
-
<VirtualCursor cursor={virtualCursor} />
|
|
515
|
-
{/if}
|
|
516
|
-
|
|
517
|
-
<!-- MCP Virtual Cursor -->
|
|
518
|
-
{#if mcpHandler.mcpControlState.isControlled}
|
|
519
|
-
<VirtualCursor cursor={mcpVirtualCursor} />
|
|
520
|
-
{/if}
|
|
521
|
-
|
|
522
502
|
<!-- Native UI Overlays -->
|
|
523
503
|
<SelectDropdown
|
|
524
504
|
bind:selectInfo={currentSelectInfo}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
3
|
import Canvas from './Canvas.svelte';
|
|
4
|
+
import VirtualCursor from './VirtualCursor.svelte';
|
|
4
5
|
import { scale } from 'svelte/transition';
|
|
5
6
|
import { cubicOut } from 'svelte/easing';
|
|
6
7
|
import { onDestroy } from 'svelte';
|
|
@@ -27,6 +28,11 @@
|
|
|
27
28
|
y: 0,
|
|
28
29
|
visible: false
|
|
29
30
|
}),
|
|
31
|
+
mcpVirtualCursor = $bindable<{ x: number; y: number; visible: boolean; clicking?: boolean }>({
|
|
32
|
+
x: 0,
|
|
33
|
+
y: 0,
|
|
34
|
+
visible: false
|
|
35
|
+
}),
|
|
30
36
|
lastFrameData = $bindable<any>(null), // Add lastFrameData prop
|
|
31
37
|
|
|
32
38
|
// MCP Control State
|
|
@@ -419,6 +425,7 @@
|
|
|
419
425
|
</div> -->
|
|
420
426
|
</div>
|
|
421
427
|
{/if}
|
|
428
|
+
|
|
422
429
|
</div>
|
|
423
430
|
{:else}
|
|
424
431
|
<div
|
|
@@ -431,6 +438,16 @@
|
|
|
431
438
|
<p class="text-sm">Enter a URL to preview your web application</p>
|
|
432
439
|
</div>
|
|
433
440
|
{/if}
|
|
441
|
+
|
|
442
|
+
<!-- Virtual Cursor - User -->
|
|
443
|
+
{#if !isMcpControlled}
|
|
444
|
+
<VirtualCursor cursor={virtualCursor} />
|
|
445
|
+
{/if}
|
|
446
|
+
|
|
447
|
+
<!-- MCP Virtual Cursor -->
|
|
448
|
+
{#if mcpVirtualCursor.visible}
|
|
449
|
+
<VirtualCursor cursor={mcpVirtualCursor} />
|
|
450
|
+
{/if}
|
|
434
451
|
</div>
|
|
435
452
|
|
|
436
453
|
<style>
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<!-- Virtual Cursor for Autonomous Testing -->
|
|
8
8
|
{#if cursor.visible}
|
|
9
9
|
<div
|
|
10
|
-
class="fixed pointer-events-none z-50 transition-all duration-100"
|
|
11
|
-
style="left: {cursor.x
|
|
10
|
+
class="fixed pointer-events-none z-50 transition-all duration-100 ease-out"
|
|
11
|
+
style="left: {cursor.x}px; top: {cursor.y}px; margin-left: -4.167px; margin-top: -2.5px;"
|
|
12
12
|
>
|
|
13
13
|
<!-- Cursor body with fixed size -->
|
|
14
14
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { themeStore, toggleDarkMode } from '$frontend/lib/stores/ui/theme.svelte';
|
|
3
3
|
import Icon from '../../common/Icon.svelte';
|
|
4
|
-
import LayoutPresetSettings from './LayoutPresetSettings.svelte';
|
|
5
4
|
</script>
|
|
6
5
|
|
|
7
6
|
<div class="py-1">
|
|
@@ -44,8 +43,3 @@
|
|
|
44
43
|
</div>
|
|
45
44
|
</div>
|
|
46
45
|
</div>
|
|
47
|
-
|
|
48
|
-
<!-- Layout Presets -->
|
|
49
|
-
<div class="mt-6">
|
|
50
|
-
<LayoutPresetSettings />
|
|
51
|
-
</div>
|
|
@@ -8,34 +8,34 @@
|
|
|
8
8
|
// Group presets by category
|
|
9
9
|
const presetCategories = [
|
|
10
10
|
{
|
|
11
|
-
name: 'Single Panel
|
|
12
|
-
description: 'Full screen
|
|
11
|
+
name: 'Single Panel',
|
|
12
|
+
description: 'Full screen focus mode',
|
|
13
13
|
icon: 'lucide:maximize-2',
|
|
14
|
-
presets: builtInPresets.slice(0,
|
|
14
|
+
presets: builtInPresets.slice(0, 1)
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
name: 'Two
|
|
18
|
-
description: 'Dual
|
|
17
|
+
name: 'Two Panels',
|
|
18
|
+
description: 'Dual panel layouts',
|
|
19
19
|
icon: 'lucide:columns-2',
|
|
20
|
-
presets: builtInPresets.slice(
|
|
20
|
+
presets: builtInPresets.slice(1, 4)
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
name: 'Three
|
|
24
|
-
description: '
|
|
23
|
+
name: 'Three Panels',
|
|
24
|
+
description: 'Multi-panel arrangements',
|
|
25
25
|
icon: 'lucide:columns-3',
|
|
26
|
-
presets: builtInPresets.slice(
|
|
26
|
+
presets: builtInPresets.slice(4, 8)
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
name: 'Four
|
|
30
|
-
description: '
|
|
29
|
+
name: 'Four Panels',
|
|
30
|
+
description: 'Complex grid layouts',
|
|
31
31
|
icon: 'lucide:layout-grid',
|
|
32
|
-
presets: builtInPresets.slice(
|
|
32
|
+
presets: builtInPresets.slice(8, 11)
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
name: 'Five
|
|
36
|
-
description: 'Full workspace
|
|
35
|
+
name: 'Five Panels',
|
|
36
|
+
description: 'Full workspace grid',
|
|
37
37
|
icon: 'lucide:layout-dashboard',
|
|
38
|
-
presets: builtInPresets.slice(
|
|
38
|
+
presets: builtInPresets.slice(11, 12)
|
|
39
39
|
}
|
|
40
40
|
];
|
|
41
41
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const { layout, size = 'small' }: Props = $props();
|
|
10
10
|
|
|
11
|
-
const height = size === 'small' ? 'h-8' : 'h-12';
|
|
11
|
+
const height = $derived(size === 'small' ? 'h-8' : 'h-12');
|
|
12
12
|
|
|
13
13
|
// Panel colors - softer colors that blend with theme
|
|
14
14
|
const panelColors: Record<PanelId, string> = {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
files: 'bg-blue-200/60 dark:bg-blue-700/40',
|
|
17
17
|
preview: 'bg-emerald-200/60 dark:bg-emerald-700/40',
|
|
18
18
|
terminal: 'bg-amber-200/60 dark:bg-amber-700/40',
|
|
19
|
-
git: 'bg-
|
|
19
|
+
git: 'bg-pink-200/60 dark:bg-pink-700/40'
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
// Panel labels (optional, for larger previews)
|