ompchamber 3.1.1 → 3.3.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/index.js +306 -298
- package/dist/client/static/js/index-bsxxt89j.css +1 -0
- package/dist/client/static/js/{index-qhzsybkn.js → index-qkt4z7x0.js} +658 -658
- package/package.json +4 -3
- package/public/sw.js +54 -5
- package/src/cli/lib/output.js +15 -8
- package/src/client/components/common/Modal.tsx +14 -4
- package/src/client/components/{layout/session-sidebar/SubagentList.tsx → common/subagent-list/index.tsx} +8 -10
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +6 -30
- package/src/client/components/layout/session-sidebar/Toolbar.tsx +11 -5
- package/src/client/components/layout/session-sidebar/index.tsx +4 -2
- package/src/client/components/mobile/LayoutWrapper.tsx +6 -0
- package/src/client/components/mobile/SessionSidebar.tsx +4 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +27 -8
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +67 -29
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.test.ts +84 -0
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +59 -12
- package/src/client/components/mobile/mobile-session-sidebar/Toolbar.tsx +21 -1
- package/src/client/components/settings/Modal.tsx +45 -23
- package/src/client/components/settings/SettingsSidebarList.tsx +1 -1
- package/src/client/components/settings/categories/AgentSettings.tsx +45 -28
- package/src/client/components/settings/categories/AppearanceSettings.tsx +3 -0
- package/src/client/components/settings/categories/CommandSettings.tsx +45 -28
- package/src/client/components/settings/categories/McpSettings.tsx +48 -31
- package/src/client/components/settings/categories/ProjectSettings.tsx +34 -19
- package/src/client/components/settings/categories/ProviderSettings.tsx +77 -53
- package/src/client/components/settings/categories/SkillSettings.tsx +34 -17
- package/src/client/components/settings/categories/UsageSettings.tsx +53 -39
- package/src/client/components/settings/categories/appearance-settings/EditorFontSection.tsx +78 -0
- package/src/client/components/settings/categories/behavior-settings/Editor.tsx +7 -7
- package/src/client/components/settings/categories/mcp-settings/ImportModal.tsx +1 -1
- package/src/client/components/settings/categories/mcp-settings/SidebarList.tsx +1 -1
- package/src/client/components/settings/categories/project-settings/SidebarList.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +8 -3
- package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +9 -4
- package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +1 -1
- package/src/client/components/settings/categories/skill-catalog/AddSourceModal.tsx +1 -1
- package/src/client/components/settings/categories/skill-settings/SidebarList.tsx +1 -1
- package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +1 -1
- package/src/client/components/settings/master-detail/index.tsx +68 -0
- package/src/client/components/workspace/SearchPanel.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +7 -1
- package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +19 -12
- package/src/client/components/workspace/chat-timeline/TaskResultContent.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/index.tsx +1 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/hashline-patch/index.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/AstEdit.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Bash.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/SearchTool.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.tsx +2 -1
- package/src/client/components/workspace/diff-panel/SplitView.tsx +31 -5
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +77 -6
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +18 -5
- package/src/client/components/workspace/diff-panel/index.tsx +36 -7
- package/src/client/components/workspace/editor/Toolbar.tsx +11 -4
- package/src/client/components/workspace/editor/index.tsx +15 -7
- package/src/client/components/workspace/git-panel/commit-modal/CommitDiffViewer.tsx +2 -1
- package/src/client/components/workspace/terminal-panel/KeyBar.tsx +194 -0
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +24 -0
- package/src/client/components/workspace/terminal-panel/index.tsx +98 -3
- package/src/client/components/workspace/terminal-panel/mount.ts +47 -0
- package/src/client/data/theme/terminal.ts +25 -8
- package/src/client/hooks/chat/omp/session-list.tsx +31 -1
- package/src/client/hooks/chat/omp/session-sidebar-controller.ts +7 -1
- package/src/client/hooks/chat/subagent/index.ts +37 -11
- package/src/client/hooks/editor/font.ts +53 -0
- package/src/client/hooks/editor/use-file-editor.ts +1 -1
- package/src/client/hooks/settings/master-detail.ts +33 -0
- package/src/client/hooks/ui/clipboard.ts +17 -0
- package/src/client/hooks/ui/keyboard-inset.test.ts +46 -0
- package/src/client/hooks/ui/keyboard-inset.ts +113 -0
- package/src/client/hooks/ui/text-overflow.test.ts +152 -0
- package/src/client/hooks/ui/text-overflow.ts +78 -0
- package/src/client/hooks/ui/touch-device.ts +46 -0
- package/src/client/hooks/workspace/expanded-sessions.ts +62 -0
- package/src/client/main.tsx +1 -0
- package/src/server/index.ts +4 -3
- package/src/server/lib/assets/conditional.server.test.ts +74 -0
- package/src/server/lib/assets/conditional.server.ts +54 -0
- package/src/server/lib/assets/dev-assets.server.test.ts +44 -0
- package/src/server/lib/assets/dev-assets.server.ts +121 -0
- package/src/server/lib/assets/fonts.server.ts +9 -4
- package/src/server/lib/bundler/build-errors.server.ts +136 -0
- package/src/server/lib/bundler/build-errors.test.ts +103 -0
- package/src/server/lib/bundler/dependencies.test.ts +134 -0
- package/src/server/lib/db/client.ts +5 -1
- package/src/server/lib/fs/git-diff.ts +34 -10
- package/src/server/lib/fs/git-repos.ts +81 -0
- package/src/server/lib/lifecycle/flock.ts +199 -0
- package/src/server/lib/lifecycle/identity.ts +5 -1
- package/src/server/lib/lifecycle/listener.ts +29 -0
- package/src/server/lib/lifecycle/port-guard.ts +18 -3
- package/src/server/lib/lifecycle/proc/darwin.ts +52 -17
- package/src/server/lib/lifecycle/proc/linux.ts +29 -7
- package/src/server/lib/lifecycle/proc/ps.ts +7 -0
- package/src/server/lib/lifecycle/proc/types.ts +17 -0
- package/src/server/lib/lifecycle/proc/win32.ts +5 -0
- package/src/server/lib/observer-ws.ts +150 -0
- package/src/server/lib/omp/rpc/constants.ts +9 -0
- package/src/server/lib/omp/rpc/subagent-liveness.ts +2 -12
- package/src/server/lib/omp/session/file-scan.ts +7 -1
- package/src/server/lib/omp/session/files.ts +95 -96
- package/src/server/lib/omp/session/locator.ts +18 -8
- package/src/server/lib/omp/session/messages.ts +7 -7
- package/src/server/lib/omp/session/scan.ts +108 -0
- package/src/server/lib/omp/session/stats.ts +50 -50
- package/src/server/lib/omp/session/telemetry/scan.ts +89 -7
- package/src/server/lib/omp/session/telemetry-raw.ts +24 -8
- package/src/server/lib/omp/session/usage/aggregate.ts +170 -55
- package/src/server/lib/terminal/runtime.server.ts +3 -0
- package/src/server/lib/terminal/session.server.ts +30 -3
- package/src/server/plugins/shell.server.ts +52 -13
- package/src/server/plugins/ssr.ts +43 -7
- package/src/server/plugins/static.ts +51 -18
- package/src/server/routes/agent/command.test.ts +69 -0
- package/src/server/routes/agent/command.ts +14 -0
- package/src/server/routes/agent/ws.test.ts +159 -0
- package/src/server/routes/agent/ws.ts +22 -97
- package/src/server/routes/btw/ws.ts +22 -95
- package/src/server/routes/fs/git.ts +61 -70
- package/src/server/routes/omp/session-stats.ts +1 -1
- package/src/server/routes/telemetry/tokens.ts +6 -4
- package/src/server/routes/terminal/run.ts +4 -8
- package/src/server/routes/terminal/ws.ts +24 -2
- package/src/shared/lib/chat/omp/agent-events.test.ts +33 -1
- package/src/shared/lib/chat/omp/agent-events.ts +10 -0
- package/src/shared/lib/code/editor/typography.test.ts +93 -0
- package/src/shared/lib/code/editor/typography.ts +86 -0
- package/src/shared/lib/code/language.ts +146 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +2 -1
- package/src/shared/lib/code/syntax-highlight.ts +0 -122
- package/src/shared/lib/fonts/NOTICE.md +44 -0
- package/src/shared/lib/fonts/nerd-symbols.css +55 -0
- package/src/shared/lib/fonts/nerd-symbols.test.ts +134 -0
- package/src/shared/lib/fonts/symbols-nerd-font-mono.woff2 +0 -0
- package/src/shared/lib/fs/diff-parser.test.ts +5 -5
- package/src/shared/lib/fs/diff-parser.ts +67 -42
- package/src/shared/lib/omp/subagent/parse.test.ts +88 -0
- package/src/shared/lib/omp/subagent/parse.ts +17 -0
- package/src/shared/lib/omp/subagent/transcript-client.test.ts +100 -0
- package/src/shared/lib/omp/subagent/transcript-client.ts +17 -1
- package/src/shared/lib/settings/diff.test.ts +1 -1
- package/src/shared/lib/workspace/terminal/keys.test.ts +189 -0
- package/src/shared/lib/workspace/terminal/keys.ts +225 -0
- package/src/shared/types/omp/subagent.ts +6 -0
- package/src/shared/types/settings/state.ts +7 -16
- package/dist/client/static/js/index-5yfre1kw.css +0 -1
|
@@ -8,6 +8,8 @@ import { McpImportModal } from '@/client/components/settings/categories/mcp-sett
|
|
|
8
8
|
import { LoadingState } from '@/client/components/settings/LoadingState';
|
|
9
9
|
import { useSidebarData } from '@/client/hooks/chat/omp/session-list';
|
|
10
10
|
import { useCrudList } from '@/client/hooks/settings/crud-list';
|
|
11
|
+
import { useSettingsMasterDetail } from '@/client/hooks/settings/master-detail';
|
|
12
|
+
import { SettingsMasterDetail } from '@/client/components/settings/master-detail';
|
|
11
13
|
|
|
12
14
|
interface McpSettingsProps {
|
|
13
15
|
settings: SettingsState;
|
|
@@ -17,6 +19,7 @@ interface McpSettingsProps {
|
|
|
17
19
|
const GLOBAL_PROJECT: McpProjectOption = { id: 'global', name: 'Global (all projects)', path: '' };
|
|
18
20
|
|
|
19
21
|
export const McpSettings: FunctionComponent<McpSettingsProps> = () => {
|
|
22
|
+
const masterDetail = useSettingsMasterDetail();
|
|
20
23
|
const { folders } = useSidebarData();
|
|
21
24
|
const projects = useMemo<McpProjectOption[]>(() => {
|
|
22
25
|
const bound = folders.flatMap<McpProjectOption>((folder) =>
|
|
@@ -98,39 +101,53 @@ export const McpSettings: FunctionComponent<McpSettingsProps> = () => {
|
|
|
98
101
|
|
|
99
102
|
return (
|
|
100
103
|
<div className="flex h-full w-full overflow-hidden bg-paper">
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<McpDetailPane
|
|
121
|
-
key={selectedServer.id}
|
|
122
|
-
server={selectedServer}
|
|
123
|
-
isNew={false}
|
|
124
|
-
onSave={save}
|
|
125
|
-
onDelete={remove}
|
|
126
|
-
onOpenImportModal={() => setIsImportModalOpen(true)}
|
|
104
|
+
<SettingsMasterDetail
|
|
105
|
+
pane={masterDetail.pane}
|
|
106
|
+
onBack={masterDetail.back}
|
|
107
|
+
listLabel="MCP Servers"
|
|
108
|
+
list={
|
|
109
|
+
<McpSidebarList
|
|
110
|
+
servers={servers}
|
|
111
|
+
projects={projects}
|
|
112
|
+
selectedServerId={isCreatingNew ? null : selectedId}
|
|
113
|
+
onSelectServer={(id) => {
|
|
114
|
+
select(id);
|
|
115
|
+
masterDetail.openDetail();
|
|
116
|
+
}}
|
|
117
|
+
onAddNewServer={() => {
|
|
118
|
+
startCreate();
|
|
119
|
+
masterDetail.openDetail();
|
|
120
|
+
}}
|
|
121
|
+
selectedProject={selectedProject}
|
|
122
|
+
onChangeProject={setSelectedProject}
|
|
127
123
|
/>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
}
|
|
125
|
+
detail={
|
|
126
|
+
<div className="flex-1 h-full overflow-hidden flex flex-col">
|
|
127
|
+
{isCreatingNew ? (
|
|
128
|
+
<McpDetailPane
|
|
129
|
+
server={emptyServerTemplate}
|
|
130
|
+
isNew={true}
|
|
131
|
+
onSave={save}
|
|
132
|
+
onOpenImportModal={() => setIsImportModalOpen(true)}
|
|
133
|
+
/>
|
|
134
|
+
) : selectedServer ? (
|
|
135
|
+
<McpDetailPane
|
|
136
|
+
key={selectedServer.id}
|
|
137
|
+
server={selectedServer}
|
|
138
|
+
isNew={false}
|
|
139
|
+
onSave={save}
|
|
140
|
+
onDelete={remove}
|
|
141
|
+
onOpenImportModal={() => setIsImportModalOpen(true)}
|
|
142
|
+
/>
|
|
143
|
+
) : (
|
|
144
|
+
<div className="flex-1 flex items-center justify-center text-xs font-mono text-ink/40">
|
|
145
|
+
Select an MCP server or click + to configure one
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
131
148
|
</div>
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
}
|
|
150
|
+
/>
|
|
134
151
|
|
|
135
152
|
<McpImportModal
|
|
136
153
|
isOpen={isImportModalOpen}
|
|
@@ -4,8 +4,11 @@ import { ProjectSidebarList } from '@/client/components/settings/categories/proj
|
|
|
4
4
|
import { ProjectDetailsForm } from '@/client/components/settings/categories/project-settings/DetailsForm';
|
|
5
5
|
import { LoadingState } from '@/client/components/settings/LoadingState';
|
|
6
6
|
import { useChamberEvent } from '@/client/hooks/ui/window-event';
|
|
7
|
+
import { useSettingsMasterDetail } from '@/client/hooks/settings/master-detail';
|
|
8
|
+
import { SettingsMasterDetail } from '@/client/components/settings/master-detail';
|
|
7
9
|
|
|
8
10
|
export function ProjectSettings() {
|
|
11
|
+
const masterDetail = useSettingsMasterDetail();
|
|
9
12
|
const [projects, setProjects] = useState<ProjectConfigItem[]>([]);
|
|
10
13
|
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
|
11
14
|
const [accentColorOptions, setAccentColorOptions] = useState<AccentColorOption[]>([]);
|
|
@@ -94,6 +97,9 @@ export function ProjectSettings() {
|
|
|
94
97
|
};
|
|
95
98
|
setProjects((current) => [...current, newProject]);
|
|
96
99
|
setSelectedProjectId(newProject.id);
|
|
100
|
+
// On a phone the new project's details are the point of adding it; the
|
|
101
|
+
// list pane would hide the row that was just created.
|
|
102
|
+
masterDetail.openDetail();
|
|
97
103
|
window.dispatchEvent(new CustomEvent('omp:workspace-updated', { detail: { folderId } }));
|
|
98
104
|
} catch (error) {
|
|
99
105
|
console.error('Failed to add project:', error);
|
|
@@ -151,26 +157,35 @@ export function ProjectSettings() {
|
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
return (
|
|
154
|
-
<div className="w-full h-full flex flex-col
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
<div className="w-full h-full flex flex-col overflow-hidden bg-paper text-ink">
|
|
161
|
+
<SettingsMasterDetail
|
|
162
|
+
pane={masterDetail.pane}
|
|
163
|
+
onBack={masterDetail.back}
|
|
164
|
+
listLabel="Projects"
|
|
165
|
+
list={
|
|
166
|
+
<ProjectSidebarList
|
|
167
|
+
projects={projects}
|
|
168
|
+
selectedProjectId={selectedProject?.id || ''}
|
|
169
|
+
onSelectProject={(id) => {
|
|
170
|
+
setSelectedProjectId(id);
|
|
171
|
+
masterDetail.openDetail();
|
|
172
|
+
}}
|
|
173
|
+
onAddProject={handleAddProject}
|
|
174
|
+
/>
|
|
175
|
+
}
|
|
176
|
+
detail={
|
|
177
|
+
selectedProject ? (
|
|
178
|
+
<ProjectDetailsForm
|
|
179
|
+
project={selectedProject}
|
|
180
|
+
canDelete={projects.length > 1}
|
|
181
|
+
onUpdateField={handleUpdateField}
|
|
182
|
+
onDeleteProject={handleDeleteProject}
|
|
183
|
+
availableModels={availableModels}
|
|
184
|
+
accentColorOptions={accentColorOptions}
|
|
185
|
+
/>
|
|
186
|
+
) : null
|
|
187
|
+
}
|
|
161
188
|
/>
|
|
162
|
-
|
|
163
|
-
{/* Right Column: Project details & controls */}
|
|
164
|
-
{selectedProject && (
|
|
165
|
-
<ProjectDetailsForm
|
|
166
|
-
project={selectedProject}
|
|
167
|
-
canDelete={projects.length > 1}
|
|
168
|
-
onUpdateField={handleUpdateField}
|
|
169
|
-
onDeleteProject={handleDeleteProject}
|
|
170
|
-
availableModels={availableModels}
|
|
171
|
-
accentColorOptions={accentColorOptions}
|
|
172
|
-
/>
|
|
173
|
-
)}
|
|
174
189
|
</div>
|
|
175
190
|
);
|
|
176
191
|
}
|
|
@@ -13,6 +13,8 @@ import { AddModelModal } from '@/client/components/settings/categories/provider-
|
|
|
13
13
|
import { DeleteProviderModal } from '@/client/components/settings/categories/provider-settings/DeleteProviderModal';
|
|
14
14
|
import { providerEmptyReason } from '@/shared/lib/models/provider/dialect';
|
|
15
15
|
import { configuredProviderSlugs } from '@/shared/lib/models/provider/presets';
|
|
16
|
+
import { useSettingsMasterDetail } from '@/client/hooks/settings/master-detail';
|
|
17
|
+
import { SettingsMasterDetail } from '@/client/components/settings/master-detail';
|
|
16
18
|
|
|
17
19
|
interface ProviderSettingsProps {
|
|
18
20
|
autoOpenAdd?: boolean;
|
|
@@ -23,6 +25,7 @@ export function ProviderSettings({
|
|
|
23
25
|
autoOpenAdd = false,
|
|
24
26
|
onAddModalClose,
|
|
25
27
|
}: ProviderSettingsProps) {
|
|
28
|
+
const masterDetail = useSettingsMasterDetail();
|
|
26
29
|
const {
|
|
27
30
|
providers,
|
|
28
31
|
connectedProviders,
|
|
@@ -66,61 +69,76 @@ export function ProviderSettings({
|
|
|
66
69
|
} = useProviderSettings({ autoOpenAdd, onAddModalClose });
|
|
67
70
|
|
|
68
71
|
return (
|
|
69
|
-
<div className="w-full h-full flex flex-col
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
<div className="w-full h-full flex flex-col overflow-hidden bg-paper text-ink">
|
|
73
|
+
<SettingsMasterDetail
|
|
74
|
+
// The "add provider" dialog lives inside the detail pane, so a hidden
|
|
75
|
+
// pane would hide the dialog with it — and `autoOpenAdd` (the model
|
|
76
|
+
// dropdown's "no provider configured" path) opens it without any row
|
|
77
|
+
// having been tapped. While that dialog is open the pane stays up.
|
|
78
|
+
pane={isAddModalOpen ? 'detail' : masterDetail.pane}
|
|
79
|
+
onBack={masterDetail.back}
|
|
80
|
+
listLabel="Providers"
|
|
81
|
+
list={
|
|
82
|
+
<ProviderSidebarList
|
|
83
|
+
providers={connectedProviders}
|
|
84
|
+
selectedProviderId={selectedProvider?.id || ''}
|
|
85
|
+
onSelectProvider={(id) => {
|
|
86
|
+
setSelectedProviderId(id);
|
|
87
|
+
masterDetail.openDetail();
|
|
88
|
+
}}
|
|
89
|
+
onOpenAddModal={() => setIsAddModalOpen(true)}
|
|
90
|
+
currentProject={currentProject}
|
|
91
|
+
onSelectProject={setCurrentProject}
|
|
92
|
+
/>
|
|
93
|
+
}
|
|
94
|
+
detail={
|
|
95
|
+
<div className="flex-1 scrollbar-overlay-container scrollbar-overlay-static p-6 md:p-8 space-y-6">
|
|
96
|
+
{selectedProvider ? (
|
|
97
|
+
<>
|
|
98
|
+
<ProviderHeader provider={selectedProvider} />
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
<ProviderAuthSection
|
|
101
|
+
provider={selectedProvider}
|
|
102
|
+
onOpenReconnectModal={() => {
|
|
103
|
+
// A provider omp signs in through `/login` needs the OAuth
|
|
104
|
+
// dialog; one whose endpoint and key live in models.yml (or the
|
|
105
|
+
// chamber overlay) is edited in place. Testing the provider ID
|
|
106
|
+
// for `omp-auth-` was wrong: a native models.yml entry shares
|
|
107
|
+
// that ID prefix once merged, and a keyless local server has no
|
|
108
|
+
// login flow to complete at all.
|
|
109
|
+
if (selectedProvider.credentialSource === 'omp-auth' && selectedProvider.auth !== 'none') {
|
|
110
|
+
setIsLoginModalOpen(true);
|
|
111
|
+
} else {
|
|
112
|
+
setIsReconnectModalOpen(true);
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
onToggleDisconnect={handleToggleDisconnect}
|
|
116
|
+
onToggleDisabled={handleToggleProviderDisabled}
|
|
117
|
+
onDelete={() => setIsDeleteModalOpen(true)}
|
|
118
|
+
/>
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
<ProviderModelsList
|
|
121
|
+
models={selectedProvider.models}
|
|
122
|
+
emptyReason={providerEmptyReason(selectedProvider)}
|
|
123
|
+
onToggleModelVisibility={handleToggleModelVisibility}
|
|
124
|
+
onHideAll={handleHideAll}
|
|
125
|
+
onShowAll={handleShowAll}
|
|
126
|
+
onFetchModels={handleFetchModelsFromList}
|
|
127
|
+
onAddModel={() => setIsAddModelModalOpen(true)}
|
|
128
|
+
canFetchModels={Boolean(selectedProvider.baseUrl)}
|
|
129
|
+
isFetchingModels={isFetchingModels}
|
|
130
|
+
onOpenModelConfig={(model) => setConfigModel(model)}
|
|
131
|
+
onOpenModelCapabilities={(model) => setCapabilitiesModel(model)}
|
|
132
|
+
/>
|
|
133
|
+
</>
|
|
134
|
+
) : (
|
|
135
|
+
<div className="py-12 text-center text-xs text-ink/40">
|
|
136
|
+
Select a provider from the sidebar to view connection details
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
121
139
|
</div>
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
124
142
|
|
|
125
143
|
<ToastStack toasts={toasts} onDismiss={dismissToast} />
|
|
126
144
|
|
|
@@ -130,7 +148,13 @@ export function ProviderSettings({
|
|
|
130
148
|
setIsAddModalOpen(false);
|
|
131
149
|
onAddModalClose?.();
|
|
132
150
|
}}
|
|
133
|
-
onAddProvider={
|
|
151
|
+
onAddProvider={async (provider, options) => {
|
|
152
|
+
await handleAddProvider(provider, options);
|
|
153
|
+
// The row now exists in the list; on a phone the newly registered
|
|
154
|
+
// provider is what the user came to configure, so land on its detail
|
|
155
|
+
// rather than dropping them back on the list.
|
|
156
|
+
masterDetail.openDetail();
|
|
157
|
+
}}
|
|
134
158
|
presets={availablePresetProviders}
|
|
135
159
|
existingSlugs={configuredProviderSlugs(providers)}
|
|
136
160
|
/>
|
|
@@ -4,6 +4,8 @@ import { SkillSidebarList } from '@/client/components/settings/categories/skill-
|
|
|
4
4
|
import { SkillDetailPane } from '@/client/components/settings/categories/skill-settings/DetailPane';
|
|
5
5
|
import { LoadingState } from '@/client/components/settings/LoadingState';
|
|
6
6
|
import { useCrudList } from '@/client/hooks/settings/crud-list';
|
|
7
|
+
import { useSettingsMasterDetail } from '@/client/hooks/settings/master-detail';
|
|
8
|
+
import { SettingsMasterDetail } from '@/client/components/settings/master-detail';
|
|
7
9
|
|
|
8
10
|
interface SkillSettingsProps {
|
|
9
11
|
settings?: SettingsState;
|
|
@@ -12,6 +14,7 @@ interface SkillSettingsProps {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function SkillSettings({ onNavigateToCatalog }: SkillSettingsProps) {
|
|
17
|
+
const masterDetail = useSettingsMasterDetail();
|
|
15
18
|
const [selectedProjectId, setSelectedProjectId] = useState('ompchamber');
|
|
16
19
|
const { items: skills, selectedId, selected: selectedSkill, isCreatingNew, isLoading, select, startCreate, save, remove } =
|
|
17
20
|
useCrudList<SkillItem, Partial<SkillItem>>({
|
|
@@ -46,23 +49,37 @@ export function SkillSettings({ onNavigateToCatalog }: SkillSettingsProps) {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
return (
|
|
49
|
-
<div className="flex-1 flex flex-col
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
<div className="flex-1 flex flex-col h-full w-full overflow-hidden bg-paper">
|
|
53
|
+
<SettingsMasterDetail
|
|
54
|
+
pane={masterDetail.pane}
|
|
55
|
+
onBack={masterDetail.back}
|
|
56
|
+
listLabel="Skills"
|
|
57
|
+
list={
|
|
58
|
+
<SkillSidebarList
|
|
59
|
+
skills={skills}
|
|
60
|
+
selectedSkillId={selectedId}
|
|
61
|
+
isCreatingNew={isCreatingNew}
|
|
62
|
+
onSelectSkill={(id) => {
|
|
63
|
+
select(id);
|
|
64
|
+
masterDetail.openDetail();
|
|
65
|
+
}}
|
|
66
|
+
onAddNewSkill={() => {
|
|
67
|
+
startCreate();
|
|
68
|
+
masterDetail.openDetail();
|
|
69
|
+
}}
|
|
70
|
+
selectedProject={selectedProjectId}
|
|
71
|
+
onSelectProject={setSelectedProjectId}
|
|
72
|
+
/>
|
|
73
|
+
}
|
|
74
|
+
detail={
|
|
75
|
+
<SkillDetailPane
|
|
76
|
+
skill={selectedSkill ?? null}
|
|
77
|
+
isCreatingNew={isCreatingNew}
|
|
78
|
+
onSave={save}
|
|
79
|
+
onDelete={remove}
|
|
80
|
+
onOpenCatalog={onNavigateToCatalog}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
66
83
|
/>
|
|
67
84
|
</div>
|
|
68
85
|
);
|
|
@@ -5,9 +5,12 @@ import { buildProviders, type UsageProviderId } from '@/client/components/settin
|
|
|
5
5
|
import { ProviderDetail } from '@/client/components/settings/categories/usage-settings/ProviderDetail';
|
|
6
6
|
import { formatDateTime } from '@/client/components/settings/categories/usage-settings/format';
|
|
7
7
|
import { useUsageReport } from '@/client/hooks/settings/useUsageReport';
|
|
8
|
+
import { useSettingsMasterDetail } from '@/client/hooks/settings/master-detail';
|
|
9
|
+
import { SettingsMasterDetail } from '@/client/components/settings/master-detail';
|
|
8
10
|
|
|
9
11
|
/** Settings → Usage: provider quota, balances, and locally recorded burn. */
|
|
10
12
|
export function UsageSettings() {
|
|
13
|
+
const masterDetail = useSettingsMasterDetail();
|
|
11
14
|
const { report, isLoading, error, reload } = useUsageReport();
|
|
12
15
|
const [selectedProviderId, setSelectedProviderId] = useState<UsageProviderId>('');
|
|
13
16
|
|
|
@@ -46,47 +49,58 @@ export function UsageSettings() {
|
|
|
46
49
|
|
|
47
50
|
return (
|
|
48
51
|
<div className="flex h-full w-full overflow-hidden bg-paper text-xs text-ink">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
52
|
+
<SettingsMasterDetail
|
|
53
|
+
pane={masterDetail.pane}
|
|
54
|
+
onBack={masterDetail.back}
|
|
55
|
+
listLabel="Usage"
|
|
56
|
+
list={
|
|
57
|
+
<UsageSidebarList
|
|
58
|
+
report={report}
|
|
59
|
+
selectedProviderId={selected?.id ?? ''}
|
|
60
|
+
onSelectProvider={(id) => {
|
|
61
|
+
setSelectedProviderId(id);
|
|
62
|
+
masterDetail.openDetail();
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
65
|
+
}
|
|
66
|
+
detail={
|
|
67
|
+
<div className="flex-1 h-full overflow-hidden flex flex-col">
|
|
68
|
+
<div className="px-3.5 py-2.5 border-b border-ink/10 flex items-center justify-between gap-3 flex-shrink-0">
|
|
69
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
70
|
+
<span className="text-xs font-semibold text-ink/75 truncate">
|
|
71
|
+
Updated {formatDateTime(report.generatedAt)}
|
|
72
|
+
</span>
|
|
73
|
+
{report.isMock && (
|
|
74
|
+
<span className="text-[10px] font-mono uppercase tracking-wider bg-ink/5 border border-ink/15 rounded px-1.5 py-0.5 text-ink/60">
|
|
75
|
+
Mock
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
onClick={() => reload({ force: true })}
|
|
82
|
+
disabled={isLoading}
|
|
83
|
+
className="p-1.5 bg-paper border border-ink/15 rounded-md hover:bg-ink/5 text-ink/80 transition-colors disabled:opacity-50 flex-shrink-0"
|
|
84
|
+
title="Refresh usage"
|
|
85
|
+
>
|
|
86
|
+
<RefreshCw size={12} className={isLoading ? 'animate-spin text-ink' : ''} />
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
<div className="flex-1 overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static p-3.5 space-y-4">
|
|
91
|
+
{error && <p className="text-error text-xs">{error}</p>}
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
{selected ? (
|
|
94
|
+
<ProviderDetail summary={selected.summary} />
|
|
95
|
+
) : (
|
|
96
|
+
<p className="text-[11px] text-ink/50 leading-relaxed">
|
|
97
|
+
No provider credentials detected. Add an API key in Settings → Providers and it will appear here.
|
|
98
|
+
</p>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
90
104
|
</div>
|
|
91
105
|
);
|
|
92
106
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Check, Type } from 'lucide-preact';
|
|
7
|
+
import type { SettingsState } from '@/shared/types';
|
|
8
|
+
import { EDITOR_FONT_CHOICES } from '@/shared/lib/code/editor/typography';
|
|
9
|
+
|
|
10
|
+
interface EditorFontSectionProps {
|
|
11
|
+
settings: SettingsState;
|
|
12
|
+
onUpdate: (updater: Partial<SettingsState> | ((prev: SettingsState) => SettingsState)) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The code editor's typeface.
|
|
17
|
+
*
|
|
18
|
+
* Only Fira Code is bundled, so each row states whether the face ships with the
|
|
19
|
+
* app or has to come from the device. That is the difference between a choice
|
|
20
|
+
* that always works and one that silently falls back — and the fallback is
|
|
21
|
+
* visible in the row's own preview, which is set in the face it names.
|
|
22
|
+
*/
|
|
23
|
+
export function EditorFontSection({ settings, onUpdate }: EditorFontSectionProps) {
|
|
24
|
+
const active = settings.editorFont || EDITOR_FONT_CHOICES[0].id;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<section className="space-y-3">
|
|
28
|
+
<div>
|
|
29
|
+
<h3 className="font-semibold text-sm uppercase tracking-wider text-ink/80 flex items-center gap-2">
|
|
30
|
+
<Type size={16} className="text-ink/60" />
|
|
31
|
+
Editor Font
|
|
32
|
+
</h3>
|
|
33
|
+
<p className="text-[11px] text-ink/60 mt-1">
|
|
34
|
+
Typeface for the code editor and its line numbers, in both the desktop panel and the phone's
|
|
35
|
+
full-screen editor. Only Fira Code ships with the app; the others are used when your device
|
|
36
|
+
has them, and fall back to Fira Code when it does not.
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
41
|
+
{EDITOR_FONT_CHOICES.map((choice) => {
|
|
42
|
+
const selected = active === choice.id;
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
key={choice.id}
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => onUpdate({ editorFont: choice.id })}
|
|
48
|
+
aria-pressed={selected}
|
|
49
|
+
className={`flex items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left transition-colors ${
|
|
50
|
+
selected ? 'border-ink/40 bg-ink/5' : 'border-ink/12 hover:bg-ink/5'
|
|
51
|
+
}`}
|
|
52
|
+
>
|
|
53
|
+
<span className="min-w-0">
|
|
54
|
+
{/* Preview in the face it names: an uninstalled choice shows its
|
|
55
|
+
own fallback here rather than after the user commits to it. */}
|
|
56
|
+
<span
|
|
57
|
+
className="block truncate text-xs text-ink"
|
|
58
|
+
style={{ fontFamily: `"${choice.id}", monospace` }}
|
|
59
|
+
>
|
|
60
|
+
const greet = () => 'hi'
|
|
61
|
+
</span>
|
|
62
|
+
<span className="mt-0.5 flex items-center gap-1.5 text-[10px] text-ink/50">
|
|
63
|
+
<span>{choice.label}</span>
|
|
64
|
+
{choice.bundled && (
|
|
65
|
+
<span className="rounded bg-ink/8 px-1 py-px text-[9px] uppercase tracking-wide">
|
|
66
|
+
Bundled
|
|
67
|
+
</span>
|
|
68
|
+
)}
|
|
69
|
+
</span>
|
|
70
|
+
</span>
|
|
71
|
+
{selected && <Check size={14} className="text-ink flex-shrink-0" />}
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -105,24 +105,24 @@ export const BehaviorEditor: FunctionComponent<BehaviorEditorProps> = ({
|
|
|
105
105
|
return (
|
|
106
106
|
<div className="flex-1 flex flex-col h-full bg-paper text-ink p-6 md:p-8 overflow-hidden">
|
|
107
107
|
{/* Header Bar */}
|
|
108
|
-
<div className="flex items-center justify-between pb-4 border-b border-ink/10">
|
|
109
|
-
<div className="flex items-center gap-2">
|
|
108
|
+
<div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2 pb-4 border-b border-ink/10">
|
|
109
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
110
110
|
<h2 className="text-sm font-bold tracking-tight text-ink flex items-center gap-2">
|
|
111
|
-
<span>{copy.title}</span>
|
|
112
|
-
<span className="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded border border-ink/15 text-ink/50 font-medium">
|
|
111
|
+
<span className="truncate">{copy.title}</span>
|
|
112
|
+
<span className="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded border border-ink/15 text-ink/50 font-medium flex-shrink-0">
|
|
113
113
|
{copy.badge}
|
|
114
114
|
</span>
|
|
115
115
|
</h2>
|
|
116
|
-
<div className="group relative">
|
|
116
|
+
<div className="group relative flex-shrink-0">
|
|
117
117
|
<Info className="w-4 h-4 text-ink/40 cursor-help" />
|
|
118
|
-
<div className="absolute left-0 top-6 hidden group-hover:block w-80 p-2.5 rounded-lg bg-ink text-paper text-xs shadow-lg z-20 leading-relaxed pointer-events-none">
|
|
118
|
+
<div className="absolute left-0 top-6 hidden group-hover:block w-80 max-w-[80vw] p-2.5 rounded-lg bg-ink text-paper text-xs shadow-lg z-20 leading-relaxed pointer-events-none">
|
|
119
119
|
{copy.tooltip}
|
|
120
120
|
</div>
|
|
121
121
|
</div>
|
|
122
122
|
</div>
|
|
123
123
|
|
|
124
124
|
{/* Action Buttons Top */}
|
|
125
|
-
<div className="flex items-center gap-2">
|
|
125
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
126
126
|
<button
|
|
127
127
|
type="button"
|
|
128
128
|
onClick={handleCopy}
|
|
@@ -100,7 +100,7 @@ export const McpImportModal: FunctionComponent<McpImportModalProps> = ({
|
|
|
100
100
|
|
|
101
101
|
return (
|
|
102
102
|
<div className="fixed inset-0 bg-ink/40 backdrop-blur-[2px] z-50 flex items-center justify-center p-4 animate-in fade-in duration-150">
|
|
103
|
-
<div className="w-full max-w-lg rounded-lg border border-ink/20 bg-paper text-ink p-5 shadow-2xl space-y-4">
|
|
103
|
+
<div className="w-full max-w-lg rounded-lg border border-ink/20 bg-paper text-ink p-5 shadow-2xl space-y-4 max-h-full overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static">
|
|
104
104
|
<div className="flex items-center justify-between pb-3 border-b border-ink/10">
|
|
105
105
|
<div className="flex items-center gap-2">
|
|
106
106
|
<Code className="w-4 h-4 text-ink/70" />
|