@myrialabs/clopen 0.2.5 → 0.2.7
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/chat/stream-manager.ts +136 -10
- package/backend/database/queries/session-queries.ts +9 -0
- package/backend/engine/adapters/claude/error-handler.ts +7 -2
- package/backend/engine/adapters/claude/stream.ts +16 -7
- package/backend/index.ts +25 -3
- package/backend/mcp/servers/browser-automation/browser.ts +23 -6
- package/backend/preview/browser/browser-mcp-control.ts +32 -16
- package/backend/preview/browser/browser-pool.ts +3 -1
- package/backend/preview/browser/browser-preview-service.ts +16 -17
- package/backend/preview/browser/browser-tab-manager.ts +1 -1
- package/backend/preview/browser/browser-video-capture.ts +199 -156
- package/backend/preview/browser/scripts/audio-stream.ts +11 -0
- package/backend/preview/browser/scripts/video-stream.ts +3 -5
- package/backend/snapshot/helpers.ts +15 -2
- package/backend/ws/chat/stream.ts +1 -1
- package/backend/ws/preview/browser/tab-info.ts +5 -2
- package/backend/ws/snapshot/restore.ts +43 -2
- package/frontend/components/chat/input/ChatInput.svelte +6 -4
- package/frontend/components/chat/input/components/ChatInputActions.svelte +10 -0
- package/frontend/components/chat/input/composables/use-chat-actions.svelte.ts +6 -2
- package/frontend/components/chat/message/MessageBubble.svelte +22 -1
- package/frontend/components/chat/tools/components/TerminalCommand.svelte +2 -2
- package/frontend/components/files/FileViewer.svelte +13 -2
- package/frontend/components/history/HistoryModal.svelte +1 -1
- package/frontend/components/preview/browser/BrowserPreview.svelte +15 -0
- package/frontend/components/preview/browser/components/Canvas.svelte +432 -69
- package/frontend/components/preview/browser/components/Container.svelte +23 -1
- package/frontend/components/preview/browser/components/Toolbar.svelte +1 -8
- package/frontend/components/preview/browser/core/coordinator.svelte.ts +27 -4
- package/frontend/components/preview/browser/core/mcp-handlers.svelte.ts +36 -3
- package/frontend/components/preview/browser/core/tab-operations.svelte.ts +1 -0
- package/frontend/components/terminal/TerminalTabs.svelte +1 -2
- package/frontend/components/workspace/PanelHeader.svelte +15 -0
- package/frontend/components/workspace/panels/FilesPanel.svelte +1 -0
- package/frontend/components/workspace/panels/GitPanel.svelte +27 -13
- package/frontend/components/workspace/panels/PreviewPanel.svelte +2 -0
- package/frontend/services/chat/chat.service.ts +9 -8
- package/frontend/services/preview/browser/browser-webcodecs.service.ts +43 -138
- package/frontend/stores/core/app.svelte.ts +4 -3
- package/frontend/stores/core/presence.svelte.ts +3 -2
- package/frontend/stores/core/sessions.svelte.ts +2 -0
- package/frontend/stores/ui/notification.svelte.ts +4 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
isLoading: boolean;
|
|
7
|
+
isCancelling: boolean;
|
|
7
8
|
hasActiveProject: boolean;
|
|
8
9
|
messageText: string;
|
|
9
10
|
attachedFiles: FileAttachment[];
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
const {
|
|
18
19
|
isLoading,
|
|
20
|
+
isCancelling,
|
|
19
21
|
hasActiveProject,
|
|
20
22
|
messageText,
|
|
21
23
|
attachedFiles,
|
|
@@ -65,6 +67,14 @@
|
|
|
65
67
|
>
|
|
66
68
|
<Icon name="lucide:circle-stop" class="text-white w-5 h-5" />
|
|
67
69
|
</button>
|
|
70
|
+
{:else if isCancelling}
|
|
71
|
+
<button
|
|
72
|
+
disabled
|
|
73
|
+
class="w-10 h-10 bg-red-500 opacity-70 rounded-xl flex items-center justify-center transition-all duration-200 cursor-not-allowed"
|
|
74
|
+
aria-label="Cancelling..."
|
|
75
|
+
>
|
|
76
|
+
<Icon name="lucide:loader-circle" class="text-white w-5 h-5 animate-spin" />
|
|
77
|
+
</button>
|
|
68
78
|
{:else}
|
|
69
79
|
<button
|
|
70
80
|
onclick={onSend}
|
|
@@ -10,7 +10,6 @@ import { debug } from '$shared/utils/logger';
|
|
|
10
10
|
import type { FileAttachment } from './use-file-handling.svelte';
|
|
11
11
|
|
|
12
12
|
interface ChatActionsParams {
|
|
13
|
-
messageText: string;
|
|
14
13
|
attachedFiles: FileAttachment[];
|
|
15
14
|
clearAllAttachments: () => void;
|
|
16
15
|
adjustTextareaHeight: () => void;
|
|
@@ -27,7 +26,6 @@ export function useChatActions(params: ChatActionsParams) {
|
|
|
27
26
|
function handleCancelEdit() {
|
|
28
27
|
cancelEdit();
|
|
29
28
|
clearInput();
|
|
30
|
-
params.messageText = ''; // This won't work directly, need to pass setter
|
|
31
29
|
params.clearAllAttachments();
|
|
32
30
|
params.adjustTextareaHeight();
|
|
33
31
|
}
|
|
@@ -52,6 +50,12 @@ export function useChatActions(params: ChatActionsParams) {
|
|
|
52
50
|
await snapshotService.restore(restoreTargetId, sessionState.currentSession.id);
|
|
53
51
|
}
|
|
54
52
|
|
|
53
|
+
// Set skip and clear draft BEFORE reloading messages — editing the first message
|
|
54
|
+
// causes messages to become empty (welcome state), which remounts ChatInput.
|
|
55
|
+
// Without this, the new instance restores stale server state into the input.
|
|
56
|
+
setSkipNextRestore(true);
|
|
57
|
+
params.clearDraft();
|
|
58
|
+
|
|
55
59
|
// Reload messages from database to update UI
|
|
56
60
|
if (sessionState.currentSession?.id) {
|
|
57
61
|
await loadMessagesForSession(sessionState.currentSession.id);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
|
+
import { tick } from 'svelte';
|
|
12
13
|
import type { SDKMessageFormatter } from '$shared/types/database/schema';
|
|
13
14
|
import type { IconName } from '$shared/types/ui/icons';
|
|
14
15
|
import Card from '$frontend/components/common/display/Card.svelte';
|
|
@@ -46,6 +47,23 @@
|
|
|
46
47
|
onShowTokenUsage: () => void;
|
|
47
48
|
onShowDebug: () => void;
|
|
48
49
|
} = $props();
|
|
50
|
+
|
|
51
|
+
let scrollContainer: HTMLDivElement | undefined = $state();
|
|
52
|
+
|
|
53
|
+
// Auto-scroll reasoning/system content to bottom while receiving partial text
|
|
54
|
+
$effect(() => {
|
|
55
|
+
if (roleCategory !== 'reasoning' && roleCategory !== 'system') return;
|
|
56
|
+
if (!scrollContainer) return;
|
|
57
|
+
// Track message content changes (partialText for streaming, message for final)
|
|
58
|
+
const _track = message.type === 'stream_event' && 'partialText' in message
|
|
59
|
+
? message.partialText
|
|
60
|
+
: message;
|
|
61
|
+
tick().then(() => {
|
|
62
|
+
if (scrollContainer) {
|
|
63
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
49
67
|
</script>
|
|
50
68
|
|
|
51
69
|
<div class="relative overflow-hidden">
|
|
@@ -73,7 +91,10 @@
|
|
|
73
91
|
/>
|
|
74
92
|
|
|
75
93
|
<!-- Message Content -->
|
|
76
|
-
<div
|
|
94
|
+
<div
|
|
95
|
+
bind:this={scrollContainer}
|
|
96
|
+
class="p-3 md:p-4 {roleCategory === 'reasoning' || roleCategory === 'system' ? 'max-h-80 overflow-y-auto' : ''}"
|
|
97
|
+
>
|
|
77
98
|
<div class="max-w-none space-y-4">
|
|
78
99
|
<!-- Content rendering using MessageFormatter component -->
|
|
79
100
|
<MessageFormatter {message} />
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
<span class="text-xs font-medium text-slate-700 dark:text-slate-300">Command:</span>
|
|
34
34
|
</div>
|
|
35
35
|
{#if timeout}
|
|
36
|
-
<div class="inline-block ml-auto text-
|
|
36
|
+
<div class="inline-block ml-auto text-3xs bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300 px-2 py-0.5 rounded">
|
|
37
37
|
Timeout: {timeout}ms
|
|
38
38
|
</div>
|
|
39
39
|
{/if}
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
42
|
<!-- Terminal-style command display -->
|
|
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">
|
|
43
|
+
<div class="max-h-64 overflow-y-auto 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">
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
externallyChanged?: boolean;
|
|
41
41
|
onForceReload?: () => void;
|
|
42
42
|
isBinary?: boolean;
|
|
43
|
+
projectPath?: string;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const {
|
|
@@ -56,9 +57,19 @@
|
|
|
56
57
|
onToggleWordWrap,
|
|
57
58
|
externallyChanged = false,
|
|
58
59
|
onForceReload,
|
|
59
|
-
isBinary = false
|
|
60
|
+
isBinary = false,
|
|
61
|
+
projectPath = ''
|
|
60
62
|
}: Props = $props();
|
|
61
63
|
|
|
64
|
+
// Relative path for display
|
|
65
|
+
const displayPath = $derived.by(() => {
|
|
66
|
+
if (!file) return '';
|
|
67
|
+
if (projectPath && file.path.startsWith(projectPath)) {
|
|
68
|
+
return file.path.slice(projectPath.length).replace(/^[/\\]/, '');
|
|
69
|
+
}
|
|
70
|
+
return file.path;
|
|
71
|
+
});
|
|
72
|
+
|
|
62
73
|
// Theme state
|
|
63
74
|
const isDark = $derived(themeStore.isDark);
|
|
64
75
|
const monacoTheme = $derived(isDark ? 'vs-dark' : 'vs-light');
|
|
@@ -273,7 +284,7 @@
|
|
|
273
284
|
{file.name}
|
|
274
285
|
</h3>
|
|
275
286
|
<p class="text-xs text-slate-600 dark:text-slate-400 truncate mt-0.5">
|
|
276
|
-
<span class="hidden sm:inline">{
|
|
287
|
+
<span class="hidden sm:inline">{displayPath} • </span> {formatFileSize(file.size || 0)}
|
|
277
288
|
</p>
|
|
278
289
|
</div>
|
|
279
290
|
</div>
|
|
@@ -428,7 +428,7 @@
|
|
|
428
428
|
<span
|
|
429
429
|
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 {isSessionWaitingInput(session.id, projectState.currentProject?.id) ? 'bg-amber-500' : 'bg-emerald-500'}"
|
|
430
430
|
></span>
|
|
431
|
-
{:else if isSessionUnread(session.id)}
|
|
431
|
+
{:else if isSessionUnread(session.id) && session.id !== sessionState.currentSession?.id}
|
|
432
432
|
<span
|
|
433
433
|
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-blue-500"
|
|
434
434
|
></span>
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
// Flag to prevent URL watcher from double-launching during MCP session creation
|
|
62
62
|
const mcpLaunchInProgress = $state(false);
|
|
63
63
|
|
|
64
|
+
// Touch interaction mode for canvas
|
|
65
|
+
let touchMode = $state<'scroll' | 'cursor'>('scroll');
|
|
66
|
+
|
|
64
67
|
// Flag to track if sessions were recovered (prevents creating empty tab on mount)
|
|
65
68
|
let sessionsRecovered = $state(false);
|
|
66
69
|
|
|
@@ -160,6 +163,7 @@
|
|
|
160
163
|
if (currentProjectId && currentProjectId !== previousProjectId && previousProjectId !== '') {
|
|
161
164
|
debug.log('preview', `🔄 Project changed: ${previousProjectId} → ${currentProjectId}`);
|
|
162
165
|
previousProjectId = currentProjectId;
|
|
166
|
+
sessionsRecovered = false; // Reset for new project (enables empty tab creation if needed)
|
|
163
167
|
coordinator.switchProject();
|
|
164
168
|
}
|
|
165
169
|
});
|
|
@@ -416,6 +420,14 @@
|
|
|
416
420
|
return mcpHandler.isCurrentTabMcpControlled();
|
|
417
421
|
}
|
|
418
422
|
|
|
423
|
+
// Hide MCP virtual cursor when switching to a non-MCP-controlled tab
|
|
424
|
+
$effect(() => {
|
|
425
|
+
void activeTabId; // track activeTabId changes
|
|
426
|
+
if (!isCurrentTabMcpControlled()) {
|
|
427
|
+
mcpVirtualCursor = { x: 0, y: 0, visible: false, clicking: false };
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
419
431
|
// Stream message handling
|
|
420
432
|
$effect(() => {
|
|
421
433
|
if (activeTabId && sessionId) {
|
|
@@ -425,6 +437,8 @@
|
|
|
425
437
|
|
|
426
438
|
// Expose methods for parent (PreviewPanel)
|
|
427
439
|
export const browserActions = {
|
|
440
|
+
getTouchMode: () => touchMode,
|
|
441
|
+
setTouchMode: (mode: 'scroll' | 'cursor') => { touchMode = mode; },
|
|
428
442
|
changeDeviceSize: (size: DeviceSize) => {
|
|
429
443
|
coordinator.changeDeviceSize(size, previewDimensions?.scale);
|
|
430
444
|
},
|
|
@@ -493,6 +507,7 @@
|
|
|
493
507
|
bind:canvasAPI
|
|
494
508
|
bind:previewDimensions
|
|
495
509
|
bind:lastFrameData={currentTabLastFrameData}
|
|
510
|
+
bind:touchMode
|
|
496
511
|
isMcpControlled={isCurrentTabMcpControlled()}
|
|
497
512
|
onInteraction={handleCanvasInteraction}
|
|
498
513
|
onRetry={handleGoClick}
|