ompchamber 1.0.0 → 1.2.0
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/dist/client/index.html +1 -1
- package/dist/client/static/css/index.b938f4a693.css +2 -0
- package/dist/client/static/js/77.8022a800b5.js +139 -0
- package/dist/client/static/js/async/{1k.5974df3b87.js → 1k.a411fe5b43.js} +2 -2
- package/dist/client/static/js/async/{3y.71f2ccf3b6.js → 3y.3186ecdd63.js} +5 -5
- package/dist/client/static/js/async/5b.d88304bc13.js +1 -0
- package/dist/client/static/js/async/66.86544ad741.js +93 -0
- package/dist/client/static/js/async/7g.db82aca7ba.js +1 -0
- package/dist/client/static/js/async/7l.fadbbc0583.js +1 -0
- package/dist/client/static/js/async/{dc.856fda6560.js → ar.ad5c8822a8.js} +2 -2
- package/dist/client/static/js/async/cx.9c8268d542.js +226 -0
- package/dist/client/static/js/async/e.6443e739a2.js +79 -0
- package/dist/client/static/js/async/f7.46971e9d6d.js +2 -0
- package/dist/client/static/js/async/g.ee41bfed7f.js +2 -0
- package/dist/client/static/js/async/h1.d7091cef78.js +1 -0
- package/dist/client/static/js/async/hs.e7c59d8090.js +41 -0
- package/dist/client/static/js/async/jl.4911fa3383.js +1 -0
- package/dist/client/static/js/async/{k.7466330d1a.js → k.d16cd09ae7.js} +2 -2
- package/dist/client/static/js/async/{m.c038c71b9f.js → m.2e1ea3db9e.js} +2 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js +2 -0
- package/dist/client/static/js/async/mt.853be2b5aa.js +1 -0
- package/dist/client/static/js/async/{oc.9c060c9779.js → oc.eb22093f05.js} +1 -1
- package/dist/client/static/js/async/{og.634fba99ac.js → og.53f068804c.js} +1 -1
- package/dist/client/static/js/async/{5b.0d6ae50fe8.js → q.b453193538.js} +10 -10
- package/dist/client/static/js/async/q5.88b50ffa95.js +286 -0
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +21 -0
- package/dist/client/static/js/async/{sy.d16be843a7.js → s.aad2ecebc6.js} +1 -1
- package/dist/client/static/js/async/{tr.e878658140.js → tr.2a0d2b7600.js} +1 -1
- package/dist/client/static/js/async/vt.20406a5604.js +1 -0
- package/dist/client/static/js/async/x1.fe8af17016.js +90 -0
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +149 -0
- package/dist/client/static/js/async/yx.af2936c368.js +10 -0
- package/dist/client/static/js/index.57b39ffa7f.js +34 -0
- package/package.json +1 -1
- package/src/client/components/common/ComposerPicker.tsx +16 -4
- package/src/client/components/common/ComposerTextarea.tsx +1 -0
- package/src/client/components/common/file-icon/index.tsx +4 -1
- package/src/client/components/common/image-viewer/index.tsx +224 -0
- package/src/client/components/layout/RightActivityBar.tsx +1 -1
- package/src/client/components/layout/desktop-layout/ResizeHandle.tsx +2 -2
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +13 -2
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +107 -56
- package/src/client/components/layout/desktop-layout/index.tsx +25 -96
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +79 -118
- package/src/client/components/layout/desktop-layout/resizer/separator-drag.ts +140 -0
- package/src/client/components/layout/session-sidebar/Header.tsx +1 -1
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +26 -12
- package/src/client/components/layout/session-sidebar/SubagentList.tsx +6 -4
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +57 -43
- package/src/client/components/mobile/mobile-right-sidebar/Header.tsx +31 -24
- package/src/client/components/settings/Modal.tsx +1 -0
- package/src/client/components/settings/categories/ChatSettings.tsx +6 -2
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +99 -0
- package/src/client/components/workspace/btw-panel/AskCard.tsx +155 -0
- package/src/client/components/workspace/btw-panel/Composer.tsx +181 -0
- package/src/client/components/workspace/btw-panel/Messages.tsx +100 -0
- package/src/client/components/workspace/btw-panel/TopicMenu.tsx +106 -0
- package/src/client/components/workspace/btw-panel/attachments.ts +80 -0
- package/src/client/components/workspace/btw-panel/index.tsx +83 -0
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +39 -20
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +3 -0
- package/src/client/components/workspace/chat-timeline/MinimapShortcuts.tsx +49 -12
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +3 -1
- package/src/client/components/workspace/chat-timeline/TimelineBody.tsx +10 -6
- package/src/client/components/workspace/chat-timeline/chat-input/AttachmentToolbar.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/chat-input/DropOverlay.tsx +17 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +67 -43
- package/src/client/components/workspace/chat-timeline/index.tsx +14 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Goal.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +25 -7
- package/src/client/components/workspace/editor/Toolbar.tsx +32 -23
- package/src/client/components/workspace/editor/index.tsx +45 -36
- package/src/client/components/workspace/model-dropdown/groups.ts +35 -2
- package/src/client/components/workspace/model-dropdown/index.tsx +48 -7
- package/src/client/components/workspace/model-dropdown/use-catalog.ts +28 -4
- package/src/client/hooks/chat/btw/index.ts +180 -0
- package/src/client/hooks/chat/btw/intercept.ts +43 -0
- package/src/client/hooks/chat/btw/mode.ts +179 -0
- package/src/client/hooks/chat/composer/attachments.ts +198 -0
- package/src/client/hooks/chat/composer/drop-entries.ts +204 -0
- package/src/client/hooks/chat/composer/drop-payload.test.ts +97 -0
- package/src/client/hooks/chat/composer/drop-payload.ts +130 -0
- package/src/client/hooks/chat/composer/drop-references.ts +106 -0
- package/src/client/hooks/chat/composer/file-drop.test.ts +194 -0
- package/src/client/hooks/chat/composer/file-drop.ts +226 -0
- package/src/client/hooks/chat/composer/file-reads.test.ts +211 -0
- package/src/client/hooks/chat/composer/file-reads.ts +238 -0
- package/src/client/hooks/chat/composer/index.ts +35 -9
- package/src/client/hooks/chat/composer/pipeline.ts +181 -0
- package/src/client/hooks/chat/omp/index.ts +3 -3
- package/src/client/hooks/chat/omp/prompt-send.ts +5 -5
- package/src/client/hooks/chat/timeline/actions.ts +40 -4
- package/src/client/hooks/chat/timeline/deferred-model.test.ts +219 -0
- package/src/client/hooks/chat/timeline/deferred-model.ts +92 -0
- package/src/client/hooks/chat/timeline/index.ts +29 -9
- package/src/client/hooks/chat/timeline/prompt-text.ts +6 -1
- package/src/client/hooks/chat/timeline/send.ts +45 -70
- package/src/client/hooks/chat/timeline/session-load.ts +2 -1
- package/src/client/hooks/chat/timeline/session-pagination.ts +161 -14
- package/src/client/hooks/chat/timeline/user-turns.ts +149 -0
- package/src/client/hooks/editor/file-actions.ts +49 -0
- package/src/client/hooks/editor/use-file-editor.ts +38 -27
- package/src/client/hooks/editor/use-file-transfer.ts +78 -0
- package/src/client/hooks/workspace/available-width.ts +33 -0
- package/src/client/hooks/workspace/panel-widths.ts +34 -37
- package/src/client/tailwind.css +22 -0
- package/src/server/db.server.ts +21 -287
- package/src/server/lib/btw/frames.test.ts +68 -0
- package/src/server/lib/btw/frames.ts +56 -0
- package/src/server/lib/btw/prompt.ts +28 -0
- package/src/server/lib/btw/registry.server.ts +141 -0
- package/src/server/lib/btw/runtime.server.ts +324 -0
- package/src/server/lib/btw/schema.server.ts +44 -0
- package/src/server/lib/btw/service.server.ts +171 -0
- package/src/server/lib/btw/session-copy.server.ts +213 -0
- package/src/server/lib/btw/session-copy.test.ts +166 -0
- package/src/server/lib/btw/store.server.ts +240 -0
- package/src/server/lib/db/schema.ts +110 -0
- package/src/server/lib/db/seed.ts +160 -0
- package/src/server/lib/db/workspace-sync.ts +70 -0
- package/src/server/lib/fs/root.ts +37 -0
- package/src/server/lib/models/preferences.server.ts +110 -0
- package/src/server/lib/models/registry.server.ts +206 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +207 -0
- package/src/server/lib/omp/rpc/manager.ts +27 -123
- package/src/server/lib/omp/rpc/utility.ts +7 -1
- package/src/server/lib/omp/session/auto-title.server.test.ts +137 -0
- package/src/server/lib/omp/session/auto-title.server.ts +152 -0
- package/src/server/lib/omp/session/blobs.server.test.ts +100 -0
- package/src/server/lib/omp/session/blobs.server.ts +76 -0
- package/src/server/lib/omp/session/file-scan.ts +174 -0
- package/src/server/lib/omp/session/files.test.ts +147 -0
- package/src/server/lib/omp/session/files.ts +52 -35
- package/src/server/lib/omp/session/locator.ts +2 -2
- package/src/server/lib/omp/session/merge-stored.test.ts +118 -0
- package/src/server/lib/omp/session/merge-stored.ts +171 -0
- package/src/server/lib/omp/session/messages.ts +34 -0
- package/src/server/lib/omp/session/reader.ts +2 -2
- package/src/server/lib/omp/session/subagent-presence.ts +9 -6
- package/src/server/lib/queue/delivery.server.test.ts +260 -0
- package/src/server/lib/queue/delivery.server.ts +63 -11
- package/src/server/plugins/ssr.ts +29 -2
- package/src/server/routes/btw/events.ts +70 -0
- package/src/server/routes/btw/index.ts +8 -0
- package/src/server/routes/btw/topics.ts +87 -0
- package/src/server/routes/btw/ws.ts +136 -0
- package/src/server/routes/chat/index.ts +2 -0
- package/src/server/routes/chat/session.ts +1 -115
- package/src/server/routes/chat/turns.ts +87 -0
- package/src/server/routes/fs/index.ts +4 -1
- package/src/server/routes/fs/raw.ts +85 -0
- package/src/server/routes/fs/read.ts +58 -3
- package/src/server/routes/index.ts +7 -1
- package/src/server/routes/models/root.ts +88 -215
- package/src/server/routes/settings/commands.ts +59 -18
- package/src/shared/lib/chat/attachments.test.ts +208 -0
- package/src/shared/lib/chat/attachments.ts +200 -15
- package/src/shared/lib/chat/btw/client.ts +94 -0
- package/src/shared/lib/chat/composer/client.test.ts +48 -0
- package/src/shared/lib/chat/composer/client.ts +23 -9
- package/src/shared/lib/chat/composer/commands.ts +290 -0
- package/src/shared/lib/chat/composer/composer.test.ts +195 -0
- package/src/shared/lib/chat/composer/filter.ts +21 -7
- package/src/shared/lib/chat/composer/trigger.ts +154 -21
- package/src/shared/lib/chat/omp/agent-events.ts +33 -80
- package/src/shared/lib/chat/omp/socket.ts +11 -6
- package/src/shared/lib/chat/omp/sse.ts +11 -6
- package/src/shared/lib/chat/omp/tool-results.ts +102 -0
- package/src/shared/lib/chat/omp/transport.ts +19 -3
- package/src/shared/lib/chat/text-file.ts +148 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -32
- package/src/shared/lib/chat/timeline/stream-coalescer.ts +66 -0
- package/src/shared/lib/chat/timeline/turns.ts +7 -0
- package/src/shared/lib/fs/file-kind.ts +50 -0
- package/src/shared/lib/fs/paths.ts +19 -1
- package/src/shared/lib/markdown/math.test.ts +20 -3
- package/src/shared/lib/models/preferences.test.ts +131 -0
- package/src/shared/lib/models/preferences.ts +88 -0
- package/src/shared/lib/models/provider-models.ts +8 -1
- package/src/shared/lib/omp/session/inlined-attachments.test.ts +82 -0
- package/src/shared/lib/omp/session/mapper.ts +24 -4
- package/src/shared/lib/omp/session/messages-map.ts +16 -3
- package/src/shared/lib/omp/session/messages-parse.ts +2 -2
- package/src/shared/lib/omp/session/parse-message-blocks.ts +85 -5
- package/src/shared/lib/omp/subagent/label.test.ts +94 -0
- package/src/shared/lib/omp/subagent/label.ts +84 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +113 -18
- package/src/shared/lib/workspace/panel-widths.ts +161 -30
- package/src/shared/lib/workspace/right-panels.ts +50 -11
- package/src/shared/lib/workspace/session-state/store.ts +3 -0
- package/src/shared/types/btw.ts +65 -0
- package/src/shared/types/chat.ts +77 -66
- package/src/shared/types/index.ts +2 -0
- package/src/shared/types/models.ts +101 -0
- package/src/shared/types/omp/agent.ts +14 -1
- package/src/shared/types/omp/session.ts +4 -1
- package/src/shared/types/omp/subagent.ts +4 -0
- package/src/shared/types/settings/command.ts +13 -0
- package/src/shared/types/settings/state.ts +5 -0
- package/dist/client/static/css/index.e880517fcd.css +0 -2
- package/dist/client/static/js/async/5bh.8b2363424f.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.7a6a377ecc.js +0 -1
- package/dist/client/static/js/async/cx.ca5feec570.js +0 -226
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/g.a06ca3b3e9.js +0 -2
- package/dist/client/static/js/async/h1.e8d5aa8e80.js +0 -1
- package/dist/client/static/js/async/hs.6d480d0ded.js +0 -41
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/nam.b5663e919f.js +0 -10
- package/dist/client/static/js/async/q.23af2a358f.js +0 -21
- package/dist/client/static/js/async/q5.e683f7c26e.js +0 -286
- package/dist/client/static/js/async/s.222ee974f6.js +0 -2
- package/dist/client/static/js/async/uf.46ad6209fa.js +0 -2
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/index.0de15c5e17.js +0 -33
- package/dist/client/static/js/jp.50817896f3.js +0 -139
- /package/dist/client/static/js/{jp.50817896f3.js.LICENSE.txt → 77.8022a800b5.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.5974df3b87.js.LICENSE.txt → 1k.a411fe5b43.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.71f2ccf3b6.js.LICENSE.txt → 3y.3186ecdd63.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{dc.856fda6560.js.LICENSE.txt → ar.ad5c8822a8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.c038c71b9f.js.LICENSE.txt → f7.46971e9d6d.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{5b.0d6ae50fe8.js.LICENSE.txt → g.ee41bfed7f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.23af2a358f.js.LICENSE.txt → m.2e1ea3db9e.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.222ee974f6.js.LICENSE.txt → m3.d2b05f9604.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.a06ca3b3e9.js.LICENSE.txt → q.b453193538.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.46ad6209fa.js.LICENSE.txt → q8.7f1b48e9cd.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{nam.b5663e919f.js.LICENSE.txt → yx.af2936c368.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.0de15c5e17.js.LICENSE.txt → index.57b39ffa7f.js.LICENSE.txt} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ompchamber",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Developer web console and diagnostic chamber for AI Oh-My-Pi — Elysia + Preact on Bun.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Wijanarko Putra Rajeb <wijanarko.rajeb@gmail.com>",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'preact/hooks';
|
|
2
2
|
import type { ReactElement } from 'preact/compat';
|
|
3
|
-
import type { ComposerMatchItem, ComposerPickItem, ComposerPickKind } from '@/shared/types';
|
|
3
|
+
import type { ComposerMatchItem, ComposerPickItem, ComposerPickKind, ComposerTriggerPhase } from '@/shared/types';
|
|
4
4
|
|
|
5
5
|
export interface ComposerPickerProps {
|
|
6
6
|
open: boolean;
|
|
7
7
|
kind: ComposerPickKind;
|
|
8
|
+
/** `args` swaps the header for the subcommand hint of the typed command. */
|
|
9
|
+
phase?: ComposerTriggerPhase;
|
|
8
10
|
items: ComposerMatchItem[];
|
|
9
11
|
activeIndex: number;
|
|
10
12
|
loading: boolean;
|
|
@@ -37,6 +39,7 @@ function OptionName({ item }: { item: ComposerMatchItem }) {
|
|
|
37
39
|
export function ComposerPicker({
|
|
38
40
|
open,
|
|
39
41
|
kind,
|
|
42
|
+
phase = 'name',
|
|
40
43
|
items,
|
|
41
44
|
activeIndex,
|
|
42
45
|
loading,
|
|
@@ -56,9 +59,18 @@ export function ComposerPicker({
|
|
|
56
59
|
if (!open) return null;
|
|
57
60
|
|
|
58
61
|
const isMention = kind === 'mention';
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
+
const isArgs = !isMention && phase === 'args';
|
|
63
|
+
const headerText = isMention
|
|
64
|
+
? 'Type to search files and agents'
|
|
65
|
+
: isArgs
|
|
66
|
+
? 'Pick a subcommand'
|
|
67
|
+
: 'Type to search commands and skills';
|
|
68
|
+
const ariaLabel = isMention ? 'Files and agents' : isArgs ? 'Subcommands' : 'Commands and skills';
|
|
69
|
+
const emptyText = isMention
|
|
70
|
+
? 'No matching files or agents'
|
|
71
|
+
: isArgs
|
|
72
|
+
? 'No matching subcommands'
|
|
73
|
+
: 'No matching commands or skills';
|
|
62
74
|
|
|
63
75
|
return (
|
|
64
76
|
<div
|
|
@@ -117,6 +117,7 @@ export function ComposerTextarea({
|
|
|
117
117
|
<ComposerPicker
|
|
118
118
|
open={composer.isOpen}
|
|
119
119
|
kind={composer.trigger?.kind ?? 'mention'}
|
|
120
|
+
phase={composer.trigger?.phase ?? 'name'}
|
|
120
121
|
items={composer.matches}
|
|
121
122
|
activeIndex={composer.activeIndex}
|
|
122
123
|
loading={composer.loading}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
FolderOpen,
|
|
10
10
|
} from 'lucide-preact';
|
|
11
11
|
import { BRAND_ICONS, BRAND_VIEWBOX, type BrandIconName } from '@/client/components/common/file-icon/brand-paths';
|
|
12
|
+
import { getImageMimeType } from '@/shared/lib/fs/file-kind';
|
|
12
13
|
|
|
13
14
|
interface FileIconProps {
|
|
14
15
|
name: string;
|
|
@@ -110,7 +111,9 @@ export function FileIcon({ name, isFolder, isOpen, size = 14, className = '' }:
|
|
|
110
111
|
if (lowerName.endsWith('.md') || lowerName.endsWith('.txt')) {
|
|
111
112
|
return <FileText size={size} className={`text-ink/70 ${className}`} />;
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
+
// SVG is text the editor edits as code, but it is still a picture to the eye,
|
|
115
|
+
// so the icon stays the image glyph for it.
|
|
116
|
+
if (getImageMimeType(lowerName) || lowerName.endsWith('.svg')) {
|
|
114
117
|
return <FileImage size={size} color="#c084fc" className={className} />;
|
|
115
118
|
}
|
|
116
119
|
if (lowerName.endsWith('.sh') || lowerName.endsWith('.bash')) {
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The picture surface used wherever a file is opened and turns out to be an
|
|
8
|
+
* image — the desktop editor panel, the phone's full-screen editor, and the
|
|
9
|
+
* chat `read` tool card. An image has no text, so this replaces the code
|
|
10
|
+
* surface entirely: an `<img>` against the raw-byte route, a checker backdrop
|
|
11
|
+
* for transparency, and zoom/pan that mirror the diagram viewer so the two
|
|
12
|
+
* full-size viewers behave the same way.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
16
|
+
import type { TargetedPointerEvent, TargetedWheelEvent } from 'preact';
|
|
17
|
+
import { AlertTriangle, Loader2 } from 'lucide-preact';
|
|
18
|
+
|
|
19
|
+
const MIN_SCALE = 0.1;
|
|
20
|
+
const MAX_SCALE = 16;
|
|
21
|
+
/** Zoom step for the buttons and the +/- keys. */
|
|
22
|
+
const ZOOM_STEP = 1.4;
|
|
23
|
+
/** Wheel `deltaY` → scale exponent, matching the diagram viewer's feel. */
|
|
24
|
+
const WHEEL_SENSITIVITY = 0.0015;
|
|
25
|
+
/** Pixels kept clear around a fitted picture. */
|
|
26
|
+
const FIT_MARGIN = 48;
|
|
27
|
+
|
|
28
|
+
interface ImageViewerProps {
|
|
29
|
+
/** Raw-byte URL; a different URL is a different picture. */
|
|
30
|
+
src: string;
|
|
31
|
+
name: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Drag {
|
|
36
|
+
pointerId: number;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The stateful surface, keyed on `src` by `ImageViewer` — remounting on a new
|
|
43
|
+
* URL is what guarantees a refresh cannot inherit the previous picture's fit,
|
|
44
|
+
* pan offset, or decode error.
|
|
45
|
+
*/
|
|
46
|
+
function ImageSurface({ src, name, className = '' }: ImageViewerProps) {
|
|
47
|
+
const stageRef = useRef<HTMLDivElement>(null);
|
|
48
|
+
const imageRef = useRef<HTMLImageElement>(null);
|
|
49
|
+
const dragRef = useRef<Drag | null>(null);
|
|
50
|
+
|
|
51
|
+
const [natural, setNatural] = useState<{ width: number; height: number } | null>(null);
|
|
52
|
+
const [scale, setScale] = useState(1);
|
|
53
|
+
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
54
|
+
const [dragging, setDragging] = useState(false);
|
|
55
|
+
const [error, setError] = useState<string | null>(null);
|
|
56
|
+
|
|
57
|
+
/** Read the decoded size off the element; false while it is not decodable yet. */
|
|
58
|
+
const measure = useCallback((): boolean => {
|
|
59
|
+
const image = imageRef.current;
|
|
60
|
+
if (!image || !image.complete || image.naturalWidth === 0) return false;
|
|
61
|
+
setNatural({ width: image.naturalWidth, height: image.naturalHeight });
|
|
62
|
+
return true;
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
// A cached picture can be complete before its `load` handler is attached, so
|
|
66
|
+
// the size is read once on mount as well — otherwise a re-opened image sat
|
|
67
|
+
// at 100% with no dimensions reported and Fit had nothing to fit to.
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
measure();
|
|
70
|
+
}, [measure]);
|
|
71
|
+
|
|
72
|
+
/** Scale that fits the whole picture without upscaling it past 1. */
|
|
73
|
+
const fitToView = useCallback(() => {
|
|
74
|
+
const image = imageRef.current;
|
|
75
|
+
const stage = stageRef.current?.getBoundingClientRect();
|
|
76
|
+
const width = image?.naturalWidth ?? 0;
|
|
77
|
+
const height = image?.naturalHeight ?? 0;
|
|
78
|
+
if (!stage || !width || !height) return;
|
|
79
|
+
setScale(
|
|
80
|
+
Math.min(
|
|
81
|
+
1,
|
|
82
|
+
Math.max(MIN_SCALE, (stage.width - FIT_MARGIN) / width, (stage.height - FIT_MARGIN) / height),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
setOffset({ x: 0, y: 0 });
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
// Fit once per picture. Re-measuring on every render would fight an
|
|
89
|
+
// in-progress zoom, and `natural` only changes for a new decode.
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (natural) fitToView();
|
|
92
|
+
}, [natural, fitToView]);
|
|
93
|
+
|
|
94
|
+
const zoomBy = useCallback((factor: number) => {
|
|
95
|
+
setScale((current) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, current * factor)));
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const handleWheel = useCallback(
|
|
99
|
+
(event: TargetedWheelEvent<HTMLDivElement>) => {
|
|
100
|
+
// Without this the editor panel scrolls while zooming.
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
zoomBy(Math.exp(-event.deltaY * WHEEL_SENSITIVITY));
|
|
103
|
+
},
|
|
104
|
+
[zoomBy],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const handlePointerDown = useCallback((event: TargetedPointerEvent<HTMLDivElement>) => {
|
|
108
|
+
if (event.button !== 0 || !natural) return;
|
|
109
|
+
dragRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };
|
|
110
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
111
|
+
setDragging(true);
|
|
112
|
+
}, [natural]);
|
|
113
|
+
|
|
114
|
+
const handlePointerMove = useCallback((event: TargetedPointerEvent<HTMLDivElement>) => {
|
|
115
|
+
const drag = dragRef.current;
|
|
116
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
117
|
+
const dx = event.clientX - drag.x;
|
|
118
|
+
const dy = event.clientY - drag.y;
|
|
119
|
+
drag.x = event.clientX;
|
|
120
|
+
drag.y = event.clientY;
|
|
121
|
+
setOffset((current) => ({ x: current.x + dx, y: current.y + dy }));
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const endDrag = useCallback((event: TargetedPointerEvent<HTMLDivElement>) => {
|
|
125
|
+
if (dragRef.current?.pointerId !== event.pointerId) return;
|
|
126
|
+
dragRef.current = null;
|
|
127
|
+
setDragging(false);
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
const handleDoubleClick = useCallback(() => {
|
|
131
|
+
if (Math.abs(scale - 1) < 0.01) fitToView();
|
|
132
|
+
else {
|
|
133
|
+
setScale(1);
|
|
134
|
+
setOffset({ x: 0, y: 0 });
|
|
135
|
+
}
|
|
136
|
+
}, [scale, fitToView]);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const onKeyDown = (event: globalThis.KeyboardEvent) => {
|
|
140
|
+
if (event.key !== '+' && event.key !== '=' && event.key !== '-' && event.key !== '_' && event.key !== '0') return;
|
|
141
|
+
// The stage owns these keys only while it has focus; a window-level
|
|
142
|
+
// listener would steal `-`/`0` from the composer and the file search box.
|
|
143
|
+
const target = event.target as HTMLElement | null;
|
|
144
|
+
if (!target || !stageRef.current?.contains(target)) return;
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
if (event.key === '0') fitToView();
|
|
147
|
+
else if (event.key === '+' || event.key === '=') zoomBy(ZOOM_STEP);
|
|
148
|
+
else zoomBy(1 / ZOOM_STEP);
|
|
149
|
+
};
|
|
150
|
+
window.addEventListener('keydown', onKeyDown);
|
|
151
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
152
|
+
}, [zoomBy, fitToView]);
|
|
153
|
+
|
|
154
|
+
const percent = Math.round(scale * 100);
|
|
155
|
+
const dimensions = natural ? `${natural.width} × ${natural.height} px` : '';
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className={`relative flex h-full w-full flex-col bg-paper ${className}`}>
|
|
159
|
+
<div
|
|
160
|
+
ref={stageRef}
|
|
161
|
+
tabIndex={-1}
|
|
162
|
+
onWheel={handleWheel}
|
|
163
|
+
onPointerDown={handlePointerDown}
|
|
164
|
+
onPointerMove={handlePointerMove}
|
|
165
|
+
onPointerUp={endDrag}
|
|
166
|
+
onPointerCancel={endDrag}
|
|
167
|
+
onDblClick={handleDoubleClick}
|
|
168
|
+
className={`image-stage relative flex-1 overflow-hidden outline-none ${dragging ? 'cursor-grabbing' : 'cursor-grab'}`}
|
|
169
|
+
>
|
|
170
|
+
{error ? (
|
|
171
|
+
<div className="flex h-full flex-col items-center justify-center gap-2 px-6 text-center">
|
|
172
|
+
<AlertTriangle size={20} className="text-error" />
|
|
173
|
+
<span className="font-sans text-xs text-error">Could not read {name}</span>
|
|
174
|
+
<span className="break-all font-mono text-[11px] text-ink/50">{error}</span>
|
|
175
|
+
</div>
|
|
176
|
+
) : (
|
|
177
|
+
<>
|
|
178
|
+
{!natural && (
|
|
179
|
+
<div className="absolute inset-0 flex items-center justify-center gap-2 text-xs text-ink/40">
|
|
180
|
+
<Loader2 size={14} className="animate-spin" />
|
|
181
|
+
<span>Loading image…</span>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
185
|
+
<img
|
|
186
|
+
ref={imageRef}
|
|
187
|
+
src={src}
|
|
188
|
+
alt={name}
|
|
189
|
+
draggable={false}
|
|
190
|
+
onLoad={measure}
|
|
191
|
+
onError={() => setError('The file could not be decoded as an image.')}
|
|
192
|
+
className="image-canvas max-h-none max-w-none select-none"
|
|
193
|
+
style={{
|
|
194
|
+
transform: `translate3d(${offset.x}px, ${offset.y}px, 0) scale(${scale})`,
|
|
195
|
+
visibility: natural ? 'visible' : 'hidden',
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div className="flex flex-shrink-0 items-center justify-between border-t border-ink/10 bg-canvas px-3 py-1.5 font-mono text-[10px] text-ink/50">
|
|
204
|
+
<span>{dimensions}</span>
|
|
205
|
+
<div className="flex items-center gap-3">
|
|
206
|
+
<button type="button" onClick={() => zoomBy(1 / ZOOM_STEP)} className="hover:text-ink" title="Zoom out (-)">
|
|
207
|
+
−
|
|
208
|
+
</button>
|
|
209
|
+
<span className="w-10 text-center tabular-nums">{percent}%</span>
|
|
210
|
+
<button type="button" onClick={() => zoomBy(ZOOM_STEP)} className="hover:text-ink" title="Zoom in (+)">
|
|
211
|
+
+
|
|
212
|
+
</button>
|
|
213
|
+
<button type="button" onClick={fitToView} className="hover:text-ink" title="Fit to view (0)">
|
|
214
|
+
Fit
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function ImageViewer(props: ImageViewerProps) {
|
|
223
|
+
return <ImageSurface key={props.src} {...props} />;
|
|
224
|
+
}
|
|
@@ -34,7 +34,7 @@ export function RightActivityBar({ activePanel, onChangePanel, isPanelOpen, hasA
|
|
|
34
34
|
const hasGitChanges = changes.length > 0;
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
|
-
<nav className="w-
|
|
37
|
+
<nav className="w-11 flex-shrink-0 border-l border-ink/10 bg-paper flex flex-col items-center py-3 space-y-2 z-10">
|
|
38
38
|
{/* Top Icons */}
|
|
39
39
|
<div className="flex flex-col items-center space-y-1 w-full">
|
|
40
40
|
{RIGHT_PANEL_TYPES.map((panel) => {
|
|
@@ -3,8 +3,8 @@ import { Separator } from '@/client/components/layout/desktop-layout/resizer';
|
|
|
3
3
|
/** Hairline drag handle shared by both resizable groups in the desktop layout. */
|
|
4
4
|
export function ResizeHandle() {
|
|
5
5
|
return (
|
|
6
|
-
<Separator className="relative w-
|
|
7
|
-
<div className="h-full w-[1px] bg-ink/10 group-hover:bg-ink/40 group-active:bg-ink/60 group-hover:w-
|
|
6
|
+
<Separator className="relative w-[3px] outline-none group flex justify-center cursor-col-resize z-10">
|
|
7
|
+
<div className="h-full w-[1px] bg-ink/10 group-hover:bg-ink/40 group-active:bg-ink/60 group-hover:w-[3px] transition-all" />
|
|
8
8
|
</Separator>
|
|
9
9
|
);
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LayoutTemplate, MoreHorizontal, PanelRight, PanelRightClose, Smartphone } from 'lucide-preact';
|
|
1
|
+
import { LayoutTemplate, MoreHorizontal, PanelLeft, PanelRight, PanelRightClose, Smartphone } from 'lucide-preact';
|
|
2
2
|
import { PWAInstallButton } from '@/client/components/common/PWAInstallButton';
|
|
3
3
|
import { StreamStatusDot } from '@/client/components/common/StreamStatusDot';
|
|
4
4
|
import type { AgentStreamStatus } from '@/shared/lib/chat/omp/status';
|
|
@@ -13,6 +13,7 @@ interface TopNavbarProps {
|
|
|
13
13
|
onSwitchToMobile?: () => void;
|
|
14
14
|
onToggleEditor: () => void;
|
|
15
15
|
onToggleRightPanel: () => void;
|
|
16
|
+
onToggleLeftPanel: () => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function TopNavbar({
|
|
@@ -24,10 +25,11 @@ export function TopNavbar({
|
|
|
24
25
|
onSwitchToMobile,
|
|
25
26
|
onToggleEditor,
|
|
26
27
|
onToggleRightPanel,
|
|
28
|
+
onToggleLeftPanel,
|
|
27
29
|
}: TopNavbarProps) {
|
|
28
30
|
return (
|
|
29
31
|
<header
|
|
30
|
-
className="h-
|
|
32
|
+
className="h-14 flex-shrink-0 border-b border-ink/10 bg-paper flex items-center justify-between pr-4 z-20 titlebar-drag-region select-none"
|
|
31
33
|
style={{
|
|
32
34
|
paddingLeft: !showLeftPanel ? 'max(1rem, env(titlebar-area-x, 0px))' : undefined,
|
|
33
35
|
paddingRight: 'max(1rem, calc(100vw - env(titlebar-area-width, 100vw)))',
|
|
@@ -36,6 +38,15 @@ export function TopNavbar({
|
|
|
36
38
|
<div className="flex items-center space-x-2 px-4 min-w-0">
|
|
37
39
|
{!showLeftPanel && (
|
|
38
40
|
<>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={onToggleLeftPanel}
|
|
44
|
+
className="p-1.5 rounded hover:bg-ink/10 transition-colors text-ink/60 hover:text-ink cursor-pointer flex-shrink-0 titlebar-no-drag"
|
|
45
|
+
title="Expand Sidebar"
|
|
46
|
+
aria-label="Expand Sidebar"
|
|
47
|
+
>
|
|
48
|
+
<PanelLeft size={16} />
|
|
49
|
+
</button>
|
|
39
50
|
<span className="font-bold text-sm tracking-tight hidden sm:flex items-center flex-shrink-0">
|
|
40
51
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-orange-600 to-amber-500 font-extrabold text-[15px] tracking-tighter">OMP</span>
|
|
41
52
|
<span className="ml-[1px]">Chamber</span>
|
|
@@ -6,15 +6,32 @@ import { ChatTimeline } from '@/client/components/workspace/chat-timeline/index'
|
|
|
6
6
|
import { getDesktopPanelView } from '@/client/components/common/lazy-panels';
|
|
7
7
|
import { RightActivityBar } from '@/client/components/layout/RightActivityBar';
|
|
8
8
|
import { ResizeHandle } from '@/client/components/layout/desktop-layout/ResizeHandle';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { useAvailableWidth } from '@/client/hooks/workspace/available-width';
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_RIGHT_PANEL_FRACTIONS,
|
|
12
|
+
DEFAULT_RIGHT_PANEL_WIDTHS,
|
|
13
|
+
MIN_RIGHT_PANEL_WIDTHS,
|
|
14
|
+
RIGHT_PANEL_TYPES,
|
|
15
|
+
type RightPanelType,
|
|
16
|
+
} from '@/shared/lib/workspace/right-panels';
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_EDITOR_FRACTIONS,
|
|
19
|
+
DEFAULT_EDITOR_WIDTHS,
|
|
20
|
+
MIN_CHAT_PANEL_WIDTH,
|
|
21
|
+
MIN_EDITOR_PANEL_WIDTH,
|
|
22
|
+
resolvePanelWidth,
|
|
23
|
+
type EditorWidthMode,
|
|
24
|
+
type PanelWidths,
|
|
25
|
+
} from '@/shared/lib/workspace/panel-widths';
|
|
11
26
|
|
|
12
27
|
const Editor = lazy(() => import('@/client/components/workspace/editor/index').then((m) => ({ default: m.Editor })));
|
|
13
28
|
|
|
14
29
|
function PanelSuspense({ children }: { children: ReactNode }) {
|
|
15
30
|
return (
|
|
16
31
|
<Suspense fallback={
|
|
17
|
-
<div className="w-full h-full flex items-center justify-center text-ink/40
|
|
32
|
+
<div className="w-full h-full flex items-center justify-center text-ink/40">
|
|
33
|
+
<span className="text-xs font-mono">Loading…</span>
|
|
34
|
+
</div>
|
|
18
35
|
}>
|
|
19
36
|
{children}
|
|
20
37
|
</Suspense>
|
|
@@ -72,9 +89,12 @@ export function WorkspacePanels(props: WorkspacePanelsProps) {
|
|
|
72
89
|
onPanelWidths,
|
|
73
90
|
} = props;
|
|
74
91
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
// The group's own area: the base every stored fraction is a share of, and the
|
|
93
|
+
// budget the chat floor is subtracted from. Measured through the group's
|
|
94
|
+
// element, not a wrapper — the wrapper also spans the activity bar, which is
|
|
95
|
+
// not area any panel may take.
|
|
96
|
+
const groupRef = useRef<HTMLDivElement>(null);
|
|
97
|
+
const available = useAvailableWidth(groupRef);
|
|
78
98
|
|
|
79
99
|
// One cached lazy per view, scoped to this layout instance: switching the
|
|
80
100
|
// right panel toggles CSS visibility instead of unmounting, so a view's
|
|
@@ -86,18 +106,55 @@ export function WorkspacePanels(props: WorkspacePanelsProps) {
|
|
|
86
106
|
[viewScope],
|
|
87
107
|
);
|
|
88
108
|
|
|
109
|
+
// Separators present in this group, which also consume width.
|
|
110
|
+
const handleCount = (showEditor ? 1 : 0) + (showRightPanel ? 1 : 0);
|
|
111
|
+
|
|
112
|
+
const editorWidth = resolvePanelWidth({
|
|
113
|
+
stored: panelWidths[editorWidthMode],
|
|
114
|
+
defaultFraction: DEFAULT_EDITOR_FRACTIONS[editorWidthMode],
|
|
115
|
+
defaultPx: DEFAULT_EDITOR_WIDTHS[editorWidthMode],
|
|
116
|
+
available,
|
|
117
|
+
min: MIN_EDITOR_PANEL_WIDTH,
|
|
118
|
+
// The right panel renders after the editor, so it absorbs the remainder.
|
|
119
|
+
// Reserving this view's own floor is enough: the editor's share is what
|
|
120
|
+
// the ceiling subtracts.
|
|
121
|
+
siblingMin: showRightPanel ? MIN_RIGHT_PANEL_WIDTHS[activeRightPanel] : 0,
|
|
122
|
+
handleCount,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const rightWidth = resolvePanelWidth({
|
|
126
|
+
stored: panelWidths.right?.[activeRightPanel],
|
|
127
|
+
defaultFraction: DEFAULT_RIGHT_PANEL_FRACTIONS[activeRightPanel],
|
|
128
|
+
defaultPx: DEFAULT_RIGHT_PANEL_WIDTHS[activeRightPanel],
|
|
129
|
+
available,
|
|
130
|
+
min: MIN_RIGHT_PANEL_WIDTHS[activeRightPanel],
|
|
131
|
+
// The editor took its share first; only its floor is reserved here.
|
|
132
|
+
siblingMin: showEditor ? MIN_EDITOR_PANEL_WIDTH : 0,
|
|
133
|
+
handleCount,
|
|
134
|
+
});
|
|
135
|
+
|
|
89
136
|
/**
|
|
90
137
|
* Widths of the fixed panels as they are right now, keyed by slot: the
|
|
91
138
|
* editor (source or diff, whichever is showing) and the right panel under
|
|
92
139
|
* the view it currently holds. The chat column is the group's filler, so it
|
|
93
140
|
* has no width of its own to report.
|
|
141
|
+
*
|
|
142
|
+
* Each is stored as a share of the area it was measured in, so the layout
|
|
143
|
+
* follows a window resize instead of holding a pixel value that was only
|
|
144
|
+
* right on the display it was dragged on.
|
|
94
145
|
*/
|
|
95
146
|
const readPanelWidths = (): PanelWidths => {
|
|
96
147
|
const patch: PanelWidths = {};
|
|
97
148
|
const editor = editorPanelRef.current?.getSize()?.inPixels;
|
|
98
149
|
const right = rightPanelRef.current?.getSize()?.inPixels;
|
|
99
|
-
if (editor != null && editor > 0)
|
|
100
|
-
|
|
150
|
+
if (editor != null && editor > 0) {
|
|
151
|
+
patch[editorWidthMode] = { px: Math.round(editor), ...(available ? { fraction: editor / available } : {}) };
|
|
152
|
+
}
|
|
153
|
+
if (right != null && right > 0) {
|
|
154
|
+
patch.right = {
|
|
155
|
+
[activeRightPanel]: { px: Math.round(right), ...(available ? { fraction: right / available } : {}) },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
101
158
|
return patch;
|
|
102
159
|
};
|
|
103
160
|
|
|
@@ -106,6 +163,8 @@ export function WorkspacePanels(props: WorkspacePanelsProps) {
|
|
|
106
163
|
<Group
|
|
107
164
|
orientation="horizontal"
|
|
108
165
|
id="ompchamber-layout"
|
|
166
|
+
className="flex-1 min-w-0"
|
|
167
|
+
groupRef={groupRef}
|
|
109
168
|
onLayoutChanged={() => {
|
|
110
169
|
// Persist only real drags: imperative restores and constraint
|
|
111
170
|
// recomputes would otherwise rewrite the saved widths with the
|
|
@@ -117,55 +176,47 @@ export function WorkspacePanels(props: WorkspacePanelsProps) {
|
|
|
117
176
|
<ChatTimeline className="w-full h-full" appSettings={appSettings} onSessionTitle={onSessionTitle} />
|
|
118
177
|
</Panel>
|
|
119
178
|
|
|
120
|
-
{showEditor &&
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</PanelSuspense>
|
|
135
|
-
</Panel>
|
|
136
|
-
</>
|
|
137
|
-
)}
|
|
179
|
+
{showEditor && <ResizeHandle />}
|
|
180
|
+
<Panel panelRef={editorPanelRef} id="editor-panel" defaultSize={editorWidth} minSize={MIN_EDITOR_PANEL_WIDTH} collapsed={!showEditor}>
|
|
181
|
+
<PanelSuspense>
|
|
182
|
+
<Editor
|
|
183
|
+
className="w-full h-full"
|
|
184
|
+
openedFiles={openedFiles}
|
|
185
|
+
activeFileId={activeFileId}
|
|
186
|
+
onSelectFile={onSetActiveFileId}
|
|
187
|
+
onCloseFile={onCloseFile}
|
|
188
|
+
refreshKey={refreshKey}
|
|
189
|
+
onFileSaved={onRefreshWorkspace}
|
|
190
|
+
/>
|
|
191
|
+
</PanelSuspense>
|
|
192
|
+
</Panel>
|
|
138
193
|
|
|
139
|
-
{showRightPanel &&
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
</PanelSuspense>
|
|
166
|
-
</Panel>
|
|
167
|
-
</>
|
|
168
|
-
)}
|
|
194
|
+
{showRightPanel && <ResizeHandle />}
|
|
195
|
+
<Panel panelRef={rightPanelRef} id="right-panel" defaultSize={rightWidth} minSize={MIN_RIGHT_PANEL_WIDTHS[activeRightPanel]} collapsed={!showRightPanel}>
|
|
196
|
+
<PanelSuspense>
|
|
197
|
+
{rightViews.map(({ view, Comp }) => {
|
|
198
|
+
const isActiveView = activeRightPanel === view;
|
|
199
|
+
const base = {
|
|
200
|
+
className: 'w-full h-full',
|
|
201
|
+
// The terminal must stay enabled while hidden: swapping it
|
|
202
|
+
// to the placeholder mid-command would unmount the live
|
|
203
|
+
// stream. Everything else polls/re-reads only while shown.
|
|
204
|
+
enabled: view === 'terminal' ? hasActiveContext : hasActiveContext && isActiveView,
|
|
205
|
+
active: isActiveView,
|
|
206
|
+
refreshKey,
|
|
207
|
+
rootPath: activeProjectPath ?? undefined,
|
|
208
|
+
onRefresh: onRefreshWorkspace,
|
|
209
|
+
onOpenFile,
|
|
210
|
+
onClose: onToggleRightPanel,
|
|
211
|
+
};
|
|
212
|
+
return (
|
|
213
|
+
<div key={view} className={isActiveView ? 'w-full h-full' : 'hidden'}>
|
|
214
|
+
<Comp {...base} />
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
})}
|
|
218
|
+
</PanelSuspense>
|
|
219
|
+
</Panel>
|
|
169
220
|
</Group>
|
|
170
221
|
|
|
171
222
|
<RightActivityBar activePanel={activeRightPanel} onChangePanel={onChangeRightPanel} isPanelOpen={showRightPanel} hasActiveContext={hasActiveContext} activeProjectPath={activeProjectPath} refreshKey={refreshKey} />
|