aidevops 3.24.1 → 3.25.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/README.md +1 -1
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-api/src/status-adapter.ts +88 -1
- package/packages/gui-shared/src/contracts.ts +17 -0
- package/packages/gui-shared/src/fixtures.ts +6 -0
- package/packages/gui-web/src/App.tsx +29 -6
- package/packages/gui-web/src/AppNavigation.tsx +130 -29
- package/packages/gui-web/src/AppWorkspace.tsx +71 -6
- package/packages/gui-web/src/app-model.ts +12 -0
- package/packages/gui-web/src/status-client.ts +1 -0
- package/packages/gui-web/src/styles.css +191 -0
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
58
58
|
[](https://github.com/marcusquinn)
|
|
59
59
|
|
|
60
60
|
<!-- Release & Version Info -->
|
|
61
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
62
62
|
[](https://www.npmjs.com/package/aidevops)
|
|
63
63
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
64
64
|
[](https://github.com/marcusquinn/aidevops)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.25.0
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,11 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { hostname, networkInterfaces, userInfo } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
5
6
|
import {
|
|
7
|
+
type GuiLocalRepoSetupSummary,
|
|
8
|
+
type GuiOpenCodeSessionRegistrySummary,
|
|
9
|
+
type GuiOpenCodeSessionSummary,
|
|
6
10
|
assertNoSecretSentinels,
|
|
7
11
|
createEnvelope,
|
|
8
12
|
VAULT_STATUS_ROUTE_MANIFEST,
|
|
@@ -51,11 +55,13 @@ export function readStatus(
|
|
|
51
55
|
const settingsPathRef = "~/.config/aidevops/settings.json";
|
|
52
56
|
const reposPathRef = "~/.config/aidevops/repos.json";
|
|
53
57
|
const oauthPoolPathRef = "~/.aidevops/oauth-pool.json";
|
|
58
|
+
const opencodeDbPathRef = "~/.local/share/opencode/opencode.db";
|
|
54
59
|
const agentsPathRef = "~/.aidevops/agents";
|
|
55
60
|
const vaultPathRef = statusFixture.vault.path_ref;
|
|
56
61
|
const settingsPath = expandHome(settingsPathRef);
|
|
57
62
|
const reposPath = expandHome(reposPathRef);
|
|
58
63
|
const oauthPoolPath = expandHome(oauthPoolPathRef);
|
|
64
|
+
const opencodeDbPath = expandHome(opencodeDbPathRef);
|
|
59
65
|
const aidevopsVersion = existsSync(versionPath)
|
|
60
66
|
? readFileSync(versionPath, "utf8").trim()
|
|
61
67
|
: statusFixture.aidevops_version;
|
|
@@ -63,12 +69,15 @@ export function readStatus(
|
|
|
63
69
|
const restartRequired = installedVersion !== aidevopsVersion;
|
|
64
70
|
const setupTargets = readSetupTargets(aidevopsVersion || "unknown", installedVersion || "unknown");
|
|
65
71
|
const aiApps = readAiApps(aidevopsVersion || "unknown", installedVersion || "unknown");
|
|
72
|
+
const localRepos = readLocalReposSetupSummary(reposPath);
|
|
73
|
+
const opencodeSessions = readOpenCodeSessions(opencodeDbPath, opencodeDbPathRef, localRepos.repos);
|
|
66
74
|
const vault = readVaultSummary(repoRoot);
|
|
67
75
|
const sourcePathRefs = [
|
|
68
76
|
agentsPathRef,
|
|
69
77
|
settingsPathRef,
|
|
70
78
|
reposPathRef,
|
|
71
79
|
oauthPoolPathRef,
|
|
80
|
+
opencodeDbPathRef,
|
|
72
81
|
vaultPathRef,
|
|
73
82
|
"VERSION",
|
|
74
83
|
...setupTargets.map((target) => target.path_ref),
|
|
@@ -114,7 +123,8 @@ export function readStatus(
|
|
|
114
123
|
],
|
|
115
124
|
settings: readSettingsSummary(settingsPath, settingsPathRef),
|
|
116
125
|
repos: readRepoRegistrySummary(reposPath, reposPathRef),
|
|
117
|
-
local_repos:
|
|
126
|
+
local_repos: localRepos,
|
|
127
|
+
opencode_sessions: opencodeSessions,
|
|
118
128
|
oauth_pool: readOAuthPoolSummary(oauthPoolPath, oauthPoolPathRef),
|
|
119
129
|
setup_targets: setupTargets,
|
|
120
130
|
ai_apps: aiApps,
|
|
@@ -419,6 +429,83 @@ function resolveBinary(binary: string): string | null {
|
|
|
419
429
|
}
|
|
420
430
|
}
|
|
421
431
|
|
|
432
|
+
interface OpenCodeSessionRow {
|
|
433
|
+
id: string;
|
|
434
|
+
directory: string;
|
|
435
|
+
title: string;
|
|
436
|
+
time_updated: number;
|
|
437
|
+
model: string | null;
|
|
438
|
+
agent: string | null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function readOpenCodeSessions(dbPath: string, dbPathRef: string, repos: GuiLocalRepoSetupSummary[]): GuiOpenCodeSessionRegistrySummary {
|
|
442
|
+
if (!existsSync(dbPath)) {
|
|
443
|
+
return {
|
|
444
|
+
path_ref: dbPathRef,
|
|
445
|
+
health: "missing",
|
|
446
|
+
value_policy: "metadata_only_no_message_payloads",
|
|
447
|
+
sessions: [],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const database = new Database(dbPath, { readonly: true });
|
|
453
|
+
try {
|
|
454
|
+
const rows = database.query(`
|
|
455
|
+
SELECT id, directory, title, time_updated, model, agent
|
|
456
|
+
FROM session
|
|
457
|
+
WHERE time_archived IS NULL
|
|
458
|
+
ORDER BY time_updated DESC
|
|
459
|
+
LIMIT 200
|
|
460
|
+
`).all() as OpenCodeSessionRow[];
|
|
461
|
+
const sessions = rows
|
|
462
|
+
.map((row) => opencodeSessionFromRow(row, repos))
|
|
463
|
+
.filter((session): session is GuiOpenCodeSessionSummary => session !== null);
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
path_ref: dbPathRef,
|
|
467
|
+
health: "present",
|
|
468
|
+
value_policy: "metadata_only_no_message_payloads",
|
|
469
|
+
sessions,
|
|
470
|
+
};
|
|
471
|
+
} finally {
|
|
472
|
+
database.close();
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
return {
|
|
476
|
+
path_ref: dbPathRef,
|
|
477
|
+
health: "invalid",
|
|
478
|
+
value_policy: "metadata_only_no_message_payloads",
|
|
479
|
+
sessions: [],
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function opencodeSessionFromRow(row: OpenCodeSessionRow, repos: GuiLocalRepoSetupSummary[]): GuiOpenCodeSessionSummary | null {
|
|
485
|
+
const repo = repos.find((candidate) => row.directory === expandHome(candidate.path_ref) || row.directory.startsWith(`${expandHome(candidate.path_ref)}/`));
|
|
486
|
+
|
|
487
|
+
if (!repo) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
id_ref: row.id,
|
|
493
|
+
repo_path_ref: repo.path_ref,
|
|
494
|
+
title: row.title.trim().length > 0 ? row.title : "Untitled session",
|
|
495
|
+
updated_at: formatUnixMillis(row.time_updated),
|
|
496
|
+
model: row.model ?? "unknown",
|
|
497
|
+
agent: row.agent ?? "unknown",
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function formatUnixMillis(value: number): string {
|
|
502
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
503
|
+
return "unknown";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return new Date(value).toISOString();
|
|
507
|
+
}
|
|
508
|
+
|
|
422
509
|
function readBinaryVersion(binaryPath: string, args: string[]): string {
|
|
423
510
|
if (args.length === 0) {
|
|
424
511
|
return "unknown";
|
|
@@ -131,6 +131,22 @@ export interface GuiLocalReposSetupSummary {
|
|
|
131
131
|
repos: GuiLocalRepoSetupSummary[];
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export interface GuiOpenCodeSessionSummary {
|
|
135
|
+
id_ref: string;
|
|
136
|
+
repo_path_ref: string;
|
|
137
|
+
title: string;
|
|
138
|
+
updated_at: string;
|
|
139
|
+
model: string;
|
|
140
|
+
agent: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface GuiOpenCodeSessionRegistrySummary {
|
|
144
|
+
path_ref: string;
|
|
145
|
+
health: "present" | "missing" | "invalid" | "unchecked";
|
|
146
|
+
value_policy: "metadata_only_no_message_payloads";
|
|
147
|
+
sessions: GuiOpenCodeSessionSummary[];
|
|
148
|
+
}
|
|
149
|
+
|
|
134
150
|
export type GuiAiProviderId = "anthropic" | "openai" | "cursor" | "google";
|
|
135
151
|
|
|
136
152
|
export interface GuiOAuthPoolAccountSummary {
|
|
@@ -298,6 +314,7 @@ export interface GuiStatusData {
|
|
|
298
314
|
settings: GuiSettingsSummary;
|
|
299
315
|
repos: GuiRepoRegistrySummary;
|
|
300
316
|
local_repos: GuiLocalReposSetupSummary;
|
|
317
|
+
opencode_sessions: GuiOpenCodeSessionRegistrySummary;
|
|
301
318
|
oauth_pool: GuiOAuthPoolSummary;
|
|
302
319
|
setup_targets: GuiSetupTargetSummary[];
|
|
303
320
|
ai_apps: GuiAiAppSummary[];
|
|
@@ -68,6 +68,12 @@ export const statusFixture: GuiStatusData = {
|
|
|
68
68
|
excluded_worktrees: 0,
|
|
69
69
|
repos: [],
|
|
70
70
|
},
|
|
71
|
+
opencode_sessions: {
|
|
72
|
+
path_ref: "~/.local/share/opencode/opencode.db",
|
|
73
|
+
health: "unchecked",
|
|
74
|
+
value_policy: "metadata_only_no_message_payloads",
|
|
75
|
+
sessions: [],
|
|
76
|
+
},
|
|
71
77
|
oauth_pool: {
|
|
72
78
|
path_ref: "~/.aidevops/oauth-pool.json",
|
|
73
79
|
health: "unchecked",
|
|
@@ -2,7 +2,7 @@ import type { GuiResponseEnvelope, GuiStatusData } from "@aidevops/gui-shared";
|
|
|
2
2
|
import { type ReactElement, useEffect, useState } from "react";
|
|
3
3
|
import { MachineRail, Sidebar } from "./AppNavigation";
|
|
4
4
|
import { Workspace } from "./AppWorkspace";
|
|
5
|
-
import type { FontPreference, FontSizePreference, SurfaceId, ThemePreference } from "./app-model";
|
|
5
|
+
import type { ConversationMode, FontPreference, FontSizePreference, ShellMode, SurfaceId, ThemePreference } from "./app-model";
|
|
6
6
|
import { DEFAULT_ACCENT_HUE, DEFAULT_FONT, DEFAULT_FONT_SIZE, fileRootBySurface, findSurface, findSurfaceSectionLabel, fontFamilyForPreference, fontSizeForPreference, getSystemTheme, isFontPreference, isFontSizePreference } from "./app-model";
|
|
7
7
|
import { fetchStatus, mockedStatus } from "./status-client";
|
|
8
8
|
|
|
@@ -73,6 +73,9 @@ export function App(): ReactElement {
|
|
|
73
73
|
const [machineRailVisible, setMachineRailVisible] = useState(storedAppearancePreferences.machineRailVisible);
|
|
74
74
|
const [showNavCounts, setShowNavCounts] = useState(storedAppearancePreferences.showNavCounts);
|
|
75
75
|
const [showBorders, setShowBorders] = useState(storedAppearancePreferences.showBorders);
|
|
76
|
+
const [shellMode, setShellMode] = useState<ShellMode>("devices");
|
|
77
|
+
const [conversationMode, setConversationMode] = useState<ConversationMode>("ai");
|
|
78
|
+
const [selectedLocalRepoIndex, setSelectedLocalRepoIndex] = useState(0);
|
|
76
79
|
const [systemTheme, setSystemTheme] = useState<"light" | "dark">("light");
|
|
77
80
|
const resolvedTheme = themePreference === "system" ? systemTheme : themePreference;
|
|
78
81
|
const activeSurface: SurfaceId = navigation.entries[navigation.index] ?? "overview";
|
|
@@ -166,6 +169,10 @@ export function App(): ReactElement {
|
|
|
166
169
|
persistAppearancePreference(appearanceStorageKeys.machineRail, String(machineRailVisible));
|
|
167
170
|
}, [machineRailVisible]);
|
|
168
171
|
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
setSelectedLocalRepoIndex((current) => Math.min(current, Math.max(0, status.data.local_repos.repos.length - 1)));
|
|
174
|
+
}, [status.data.local_repos.repos.length]);
|
|
175
|
+
|
|
169
176
|
return (
|
|
170
177
|
<main className={machineRailVisible ? "app-shell" : "app-shell machine-rail-collapsed"}>
|
|
171
178
|
<div className="desktop-titlebar-tagline" aria-hidden="true">Your data protected. Your systems managed. Your creations published.</div>
|
|
@@ -173,25 +180,41 @@ export function App(): ReactElement {
|
|
|
173
180
|
<Sidebar
|
|
174
181
|
activeSurface={activeSurface}
|
|
175
182
|
accentHue={accentHue}
|
|
176
|
-
|
|
177
|
-
canGoForward={canGoForward}
|
|
183
|
+
conversationMode={conversationMode}
|
|
178
184
|
fontSizePreference={fontSizePreference}
|
|
179
185
|
fontPreference={fontPreference}
|
|
180
|
-
|
|
181
|
-
goForward={goForward}
|
|
186
|
+
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
182
187
|
setAccentHue={setAccentHue}
|
|
183
188
|
setActiveSurface={setActiveSurface}
|
|
189
|
+
setConversationMode={setConversationMode}
|
|
184
190
|
setFontSizePreference={setFontSizePreference}
|
|
185
191
|
setFontPreference={setFontPreference}
|
|
192
|
+
setSelectedLocalRepoIndex={setSelectedLocalRepoIndex}
|
|
193
|
+
setShellMode={setShellMode}
|
|
186
194
|
setShowBorders={setShowBorders}
|
|
187
195
|
setShowNavCounts={setShowNavCounts}
|
|
188
196
|
setThemePreference={setThemePreference}
|
|
197
|
+
shellMode={shellMode}
|
|
189
198
|
showBorders={showBorders}
|
|
190
199
|
showNavCounts={showNavCounts}
|
|
191
200
|
status={status.data}
|
|
192
201
|
themePreference={themePreference}
|
|
193
202
|
/>
|
|
194
|
-
<Workspace
|
|
203
|
+
<Workspace
|
|
204
|
+
activeItem={activeItem}
|
|
205
|
+
activeSectionLabel={activeSectionLabel}
|
|
206
|
+
activeSurface={activeSurface}
|
|
207
|
+
canGoBack={canGoBack}
|
|
208
|
+
canGoForward={canGoForward}
|
|
209
|
+
conversationMode={conversationMode}
|
|
210
|
+
fileRoot={fileRoot}
|
|
211
|
+
goBack={goBack}
|
|
212
|
+
goForward={goForward}
|
|
213
|
+
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
214
|
+
setActiveSurface={setActiveSurface}
|
|
215
|
+
shellMode={shellMode}
|
|
216
|
+
status={status.data}
|
|
217
|
+
/>
|
|
195
218
|
<DesktopStatusBar status={status.data} />
|
|
196
219
|
</main>
|
|
197
220
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import type { GuiMachineSummary, GuiStatusData } from "@aidevops/gui-shared";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
4
4
|
import type { IconType } from "react-icons";
|
|
5
5
|
import {
|
|
6
6
|
FiBookmark,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
FiGitBranch,
|
|
19
19
|
FiGlobe,
|
|
20
20
|
FiGrid,
|
|
21
|
+
FiHash,
|
|
21
22
|
FiHardDrive,
|
|
22
23
|
FiLink,
|
|
23
24
|
FiLink2,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
FiTerminal,
|
|
35
36
|
FiUsers,
|
|
36
37
|
} from "react-icons/fi";
|
|
37
|
-
import type { FontPreference, FontSizePreference, SidebarMode, SurfaceIconName, SurfaceId, SurfaceNavGroup, SurfaceNavItem, ThemePreference } from "./app-model";
|
|
38
|
+
import type { ConversationMode, FontPreference, FontSizePreference, ShellMode, SidebarMode, SurfaceIconName, SurfaceId, SurfaceNavGroup, SurfaceNavItem, ThemePreference } from "./app-model";
|
|
38
39
|
import { DEFAULT_ACCENT_HUE, dashboardNavItem, fontFamilyForPreference, fontOptions, fontSizeOptions, navGroups, sidebarModeForSurface, surfaceRecordCounts, text } from "./app-model";
|
|
39
40
|
import { VaultPadlock, vaultCollectionForSurface } from "./VaultBadges";
|
|
40
41
|
|
|
@@ -106,22 +107,24 @@ export function MachineRail({ machine }: { machine?: GuiMachineSummary }) {
|
|
|
106
107
|
);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
export function Sidebar({ activeSurface, accentHue,
|
|
110
|
+
export function Sidebar({ activeSurface, accentHue, conversationMode, fontPreference, fontSizePreference, selectedLocalRepoIndex, setAccentHue, setActiveSurface, setConversationMode, setFontPreference, setFontSizePreference, setSelectedLocalRepoIndex, setShellMode, setShowBorders, setShowNavCounts, setThemePreference, shellMode, showBorders, showNavCounts, status, themePreference }: {
|
|
110
111
|
activeSurface: SurfaceId;
|
|
111
112
|
accentHue: number;
|
|
112
|
-
|
|
113
|
-
canGoForward: boolean;
|
|
113
|
+
conversationMode: ConversationMode;
|
|
114
114
|
fontPreference: FontPreference;
|
|
115
115
|
fontSizePreference: FontSizePreference;
|
|
116
|
-
|
|
117
|
-
goForward: () => void;
|
|
116
|
+
selectedLocalRepoIndex: number;
|
|
118
117
|
setAccentHue: (hue: number) => void;
|
|
119
118
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
119
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
120
120
|
setFontPreference: (font: FontPreference) => void;
|
|
121
121
|
setFontSizePreference: (size: FontSizePreference) => void;
|
|
122
|
+
setSelectedLocalRepoIndex: (index: number) => void;
|
|
123
|
+
setShellMode: (mode: ShellMode) => void;
|
|
122
124
|
setShowBorders: (show: boolean) => void;
|
|
123
125
|
setShowNavCounts: (show: boolean) => void;
|
|
124
126
|
setThemePreference: (theme: ThemePreference) => void;
|
|
127
|
+
shellMode: ShellMode;
|
|
125
128
|
showBorders: boolean;
|
|
126
129
|
showNavCounts: boolean;
|
|
127
130
|
status: GuiStatusData;
|
|
@@ -137,13 +140,21 @@ export function Sidebar({ activeSurface, accentHue, canGoBack, canGoForward, fon
|
|
|
137
140
|
|
|
138
141
|
return (
|
|
139
142
|
<aside className="app-sidebar" aria-label={text.navigationLabel}>
|
|
140
|
-
<SidebarHeader
|
|
143
|
+
<SidebarHeader setShellMode={setShellMode} shellMode={shellMode} />
|
|
141
144
|
<nav className="sidebar-content">
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
{shellMode === "devices" ? <>
|
|
146
|
+
<SidebarModeTabs mode={sidebarMode} setMode={(mode) => setSidebarMode(mode as SidebarMode)} />
|
|
147
|
+
<ul className="sidebar-top-link">
|
|
148
|
+
<SidebarItem activeSurface={activeSurface} item={dashboardNavItem} setActiveSurface={setActiveSurface} showCount={false} status={status} />
|
|
149
|
+
</ul>
|
|
150
|
+
{visibleGroups.map((group) => <SidebarGroup activeSurface={activeSurface} group={group} key={group.label} recordCounts={recordCounts} setActiveSurface={setActiveSurface} showNavCounts={showNavCounts} status={status} />)}
|
|
151
|
+
</> : <ConversationSidebar
|
|
152
|
+
conversationMode={conversationMode}
|
|
153
|
+
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
154
|
+
setConversationMode={setConversationMode}
|
|
155
|
+
setSelectedLocalRepoIndex={setSelectedLocalRepoIndex}
|
|
156
|
+
status={status}
|
|
157
|
+
/>}
|
|
147
158
|
</nav>
|
|
148
159
|
<SidebarFooter
|
|
149
160
|
accentHue={accentHue}
|
|
@@ -163,14 +174,102 @@ export function Sidebar({ activeSurface, accentHue, canGoBack, canGoForward, fon
|
|
|
163
174
|
);
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
177
|
+
function IconSwitch<TValue extends string>({ ariaLabel, options, value }: {
|
|
178
|
+
ariaLabel: string;
|
|
179
|
+
options: Array<{ icon: ReactNode; label: string; onSelect: () => void; value: TValue }>;
|
|
180
|
+
value: TValue;
|
|
169
181
|
}) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
return (
|
|
183
|
+
<div className="icon-switch" role="tablist" aria-label={ariaLabel}>
|
|
184
|
+
{options.map((option) => (
|
|
185
|
+
<button
|
|
186
|
+
aria-label={option.label}
|
|
187
|
+
aria-selected={value === option.value}
|
|
188
|
+
className={value === option.value ? "active" : ""}
|
|
189
|
+
key={option.value}
|
|
190
|
+
onClick={option.onSelect}
|
|
191
|
+
role="tab"
|
|
192
|
+
title={option.label}
|
|
193
|
+
type="button"
|
|
194
|
+
>
|
|
195
|
+
{option.icon}
|
|
196
|
+
<span>{option.label}</span>
|
|
197
|
+
</button>
|
|
198
|
+
))}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function ConversationSidebar({ conversationMode, selectedLocalRepoIndex, setConversationMode, setSelectedLocalRepoIndex, status }: {
|
|
204
|
+
conversationMode: ConversationMode;
|
|
205
|
+
selectedLocalRepoIndex: number;
|
|
206
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
207
|
+
setSelectedLocalRepoIndex: (index: number) => void;
|
|
208
|
+
status: GuiStatusData;
|
|
209
|
+
}) {
|
|
210
|
+
const repos = status.local_repos.repos;
|
|
211
|
+
const selectedRepo = repos[selectedLocalRepoIndex] ?? repos[0];
|
|
212
|
+
const sessions = selectedRepo === undefined
|
|
213
|
+
? []
|
|
214
|
+
: status.opencode_sessions.sessions.filter((session) => session.repo_path_ref === selectedRepo.path_ref);
|
|
215
|
+
const canSelectPreviousRepo = selectedLocalRepoIndex > 0;
|
|
216
|
+
const canSelectNextRepo = selectedLocalRepoIndex < repos.length - 1;
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<section className="conversation-panel" aria-label={text.opencodeSessions}>
|
|
220
|
+
<SidebarModeTabs mode={conversationMode} setMode={setConversationMode} />
|
|
221
|
+
{conversationMode === "people" ? <PeopleChannelList /> : <>
|
|
222
|
+
<button className="new-session-button" disabled title="Creating sessions needs an audited OpenCode write route." type="button">{text.newSession}</button>
|
|
223
|
+
<section className="repo-session-selector" aria-label={text.localRepoSelector}>
|
|
224
|
+
<button aria-label="Previous local repo" disabled={!canSelectPreviousRepo} onClick={() => setSelectedLocalRepoIndex(Math.max(0, selectedLocalRepoIndex - 1))} type="button"><FiChevronLeft /></button>
|
|
225
|
+
<label>
|
|
226
|
+
<span>{text.localRepos}</span>
|
|
227
|
+
<select onChange={(event) => setSelectedLocalRepoIndex(Number.parseInt(event.currentTarget.value, 10))} value={Math.min(selectedLocalRepoIndex, Math.max(0, repos.length - 1))}>
|
|
228
|
+
{repos.length === 0 ? <option value={0}>No local repos</option> : repos.map((repo, index) => <option key={repo.path_ref} value={index}>{repo.name}</option>)}
|
|
229
|
+
</select>
|
|
230
|
+
</label>
|
|
231
|
+
<button aria-label="Next local repo" disabled={!canSelectNextRepo} onClick={() => setSelectedLocalRepoIndex(Math.min(repos.length - 1, selectedLocalRepoIndex + 1))} type="button"><FiChevronRight /></button>
|
|
232
|
+
</section>
|
|
233
|
+
<section className="sidebar-group session-history-list">
|
|
234
|
+
<h2>{text.sessionHistory}</h2>
|
|
235
|
+
{selectedRepo ? <ul>
|
|
236
|
+
{sessions.length === 0
|
|
237
|
+
? <li><button className="surface-link active" type="button"><span className="surface-icon" aria-hidden="true"><FiHash /></span><span className="surface-copy"><strong>{selectedRepo.name}</strong><small>No OpenCode sessions found for this repo yet.</small></span><em>{text.planned}</em></button></li>
|
|
238
|
+
: sessions.map((session, index) => <li key={session.id_ref}><button className={index === 0 ? "surface-link active" : "surface-link"} type="button"><span className="surface-icon" aria-hidden="true"><FiHash /></span><span className="surface-copy"><strong>{session.title}</strong><small>{session.updated_at}</small></span></button></li>)}
|
|
239
|
+
</ul> : <p className="empty-sidebar-state">No local repos discovered.</p>}
|
|
240
|
+
</section>
|
|
241
|
+
</>}
|
|
242
|
+
</section>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function PeopleChannelList() {
|
|
247
|
+
return (
|
|
248
|
+
<section aria-label="People channels">
|
|
249
|
+
<div className="notice compact-notice">{text.simplexReady}</div>
|
|
250
|
+
<section className="sidebar-group session-history-list">
|
|
251
|
+
<h2>{text.teams}</h2>
|
|
252
|
+
<ul>
|
|
253
|
+
{["aidevops", "clients", "ops"].map((team) => <li key={team}><button className="surface-link" type="button"><span className="surface-icon" aria-hidden="true"><FiHash /></span><span className="surface-copy"><strong>{team}</strong><small>SimpleX team channel placeholder</small></span></button></li>)}
|
|
254
|
+
</ul>
|
|
255
|
+
</section>
|
|
256
|
+
<section className="sidebar-group session-history-list">
|
|
257
|
+
<h2>{text.directMessages}</h2>
|
|
258
|
+
<ul>
|
|
259
|
+
{["Marcus", "AI DevOps"].map((person) => <li key={person}><button className="surface-link" type="button"><span className="surface-icon" aria-hidden="true"><FiMessageSquare /></span><span className="surface-copy"><strong>{person}</strong><small>encrypted DM placeholder</small></span></button></li>)}
|
|
260
|
+
</ul>
|
|
261
|
+
</section>
|
|
262
|
+
</section>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function SidebarModeTabs<TMode extends SidebarMode | ConversationMode>({ mode, setMode }: {
|
|
267
|
+
mode: TMode;
|
|
268
|
+
setMode: (mode: TMode) => void;
|
|
269
|
+
}) {
|
|
270
|
+
const modes = (mode === "ai" || mode === "people"
|
|
271
|
+
? [{ label: text.ai, value: "ai" }, { label: text.people, value: "people" }]
|
|
272
|
+
: [{ label: text.devops, value: "devops" }, { label: text.comms, value: "comms" }]) as Array<{ label: string; value: TMode }>;
|
|
174
273
|
|
|
175
274
|
return (
|
|
176
275
|
<div className="sidebar-mode-tabs" role="tablist" aria-label="Sidebar sections">
|
|
@@ -246,11 +345,9 @@ function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showC
|
|
|
246
345
|
);
|
|
247
346
|
}
|
|
248
347
|
|
|
249
|
-
function SidebarHeader({
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
goBack: () => void;
|
|
253
|
-
goForward: () => void;
|
|
348
|
+
function SidebarHeader({ setShellMode, shellMode }: {
|
|
349
|
+
setShellMode: (mode: ShellMode) => void;
|
|
350
|
+
shellMode: ShellMode;
|
|
254
351
|
}) {
|
|
255
352
|
return (
|
|
256
353
|
<header className="sidebar-header">
|
|
@@ -259,10 +356,14 @@ function SidebarHeader({ canGoBack, canGoForward, goBack, goForward }: {
|
|
|
259
356
|
<span className="terminal-mark" aria-hidden="true">›_</span>
|
|
260
357
|
<strong>{text.aidevops}</strong>
|
|
261
358
|
</div>
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
359
|
+
<IconSwitch
|
|
360
|
+
ariaLabel="Navigation scope"
|
|
361
|
+
options={[
|
|
362
|
+
{ icon: <FiMonitor aria-hidden="true" />, label: text.devices, onSelect: () => setShellMode("devices"), value: "devices" },
|
|
363
|
+
{ icon: <FiHash aria-hidden="true" />, label: text.opencodeSessions, onSelect: () => setShellMode("sessions"), value: "sessions" },
|
|
364
|
+
]}
|
|
365
|
+
value={shellMode}
|
|
366
|
+
/>
|
|
266
367
|
</div>
|
|
267
368
|
</header>
|
|
268
369
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { type ReactElement, type ReactNode, useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { FiBell, FiCommand, FiCpu, FiGlobe, FiLogOut, FiMessageSquare, FiSearch, FiSettings, FiShield, FiUser } from "react-icons/fi";
|
|
3
|
+
import { FiBell, FiChevronLeft, FiChevronRight, FiCommand, FiCpu, FiGlobe, FiHash, FiLogOut, FiMessageSquare, FiSearch, FiSettings, FiShield, FiUser } from "react-icons/fi";
|
|
4
4
|
import type { GuiFileRootId, GuiStatusData } from "../../gui-shared/src";
|
|
5
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
|
-
import type { SurfaceId, SurfaceNavItem } from "./app-model";
|
|
6
|
+
import type { ConversationMode, ShellMode, SurfaceId, SurfaceNavItem } from "./app-model";
|
|
7
7
|
import { inventorySurfaceConfigs, orderedNavItems, text } from "./app-model";
|
|
8
8
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
9
9
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
@@ -15,28 +15,41 @@ const communityLinks = {
|
|
|
15
15
|
x: "https://x.com/marcuswquinn",
|
|
16
16
|
} as const;
|
|
17
17
|
|
|
18
|
-
export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileRoot, setActiveSurface, status }: {
|
|
18
|
+
export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, conversationMode, fileRoot, goBack, goForward, selectedLocalRepoIndex, setActiveSurface, shellMode, status }: {
|
|
19
19
|
activeItem: SurfaceNavItem;
|
|
20
20
|
activeSectionLabel: string;
|
|
21
21
|
activeSurface: SurfaceId;
|
|
22
|
+
canGoBack: boolean;
|
|
23
|
+
canGoForward: boolean;
|
|
24
|
+
conversationMode: ConversationMode;
|
|
22
25
|
fileRoot: GuiFileRootId | undefined;
|
|
26
|
+
goBack: () => void;
|
|
27
|
+
goForward: () => void;
|
|
28
|
+
selectedLocalRepoIndex: number;
|
|
23
29
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
30
|
+
shellMode: ShellMode;
|
|
24
31
|
status: GuiStatusData;
|
|
25
32
|
}) {
|
|
26
33
|
return (
|
|
27
34
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
28
|
-
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} setActiveSurface={setActiveSurface} status={status} />
|
|
35
|
+
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} status={status} />
|
|
29
36
|
<div className="workspace-scroll">
|
|
30
|
-
|
|
37
|
+
{shellMode === "sessions"
|
|
38
|
+
? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} status={status} />
|
|
39
|
+
: <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} status={status} />}
|
|
31
40
|
</div>
|
|
32
41
|
</section>
|
|
33
42
|
);
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, setActiveSurface, status }: {
|
|
45
|
+
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, goBack, goForward, setActiveSurface, status }: {
|
|
37
46
|
activeItem: SurfaceNavItem;
|
|
38
47
|
activeSectionLabel: string;
|
|
39
48
|
activeSurface: SurfaceId;
|
|
49
|
+
canGoBack: boolean;
|
|
50
|
+
canGoForward: boolean;
|
|
51
|
+
goBack: () => void;
|
|
52
|
+
goForward: () => void;
|
|
40
53
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
41
54
|
status: GuiStatusData;
|
|
42
55
|
}): ReactElement {
|
|
@@ -80,6 +93,10 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, setAct
|
|
|
80
93
|
</div>
|
|
81
94
|
</div>
|
|
82
95
|
<div className="header-actions">
|
|
96
|
+
<div className="sidebar-history-controls workspace-history-controls">
|
|
97
|
+
<button aria-label="Previous surface" disabled={!canGoBack} onClick={goBack} type="button"><FiChevronLeft /></button>
|
|
98
|
+
<button aria-label="Next surface" disabled={!canGoForward} onClick={goForward} type="button"><FiChevronRight /></button>
|
|
99
|
+
</div>
|
|
83
100
|
<button className="workspace-search command-trigger" onClick={() => setCommandOpen(true)} type="button">
|
|
84
101
|
<FiSearch aria-hidden="true" />
|
|
85
102
|
<span>⌘K</span>
|
|
@@ -118,6 +135,54 @@ function NotificationsMenu({ openSurface }: { openSurface: (surface: SurfaceId)
|
|
|
118
135
|
);
|
|
119
136
|
}
|
|
120
137
|
|
|
138
|
+
function ConversationWorkspace({ conversationMode, selectedLocalRepoIndex, status }: {
|
|
139
|
+
conversationMode: ConversationMode;
|
|
140
|
+
selectedLocalRepoIndex: number;
|
|
141
|
+
status: GuiStatusData;
|
|
142
|
+
}) {
|
|
143
|
+
const selectedRepo = status.local_repos.repos[selectedLocalRepoIndex] ?? status.local_repos.repos[0];
|
|
144
|
+
const selectedSession = selectedRepo === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: status.opencode_sessions.sessions.find((session) => session.repo_path_ref === selectedRepo.path_ref);
|
|
147
|
+
const title = conversationMode === "ai" ? selectedRepo?.name ?? text.opencodeSessions : text.teams;
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<section className="chat-surface" aria-label={conversationMode === "ai" ? text.opencodeSessions : "People chat"}>
|
|
151
|
+
<div className="chat-thread-panel">
|
|
152
|
+
<header className="chat-thread-header">
|
|
153
|
+
<div>
|
|
154
|
+
<p className="eyebrow">{conversationMode === "ai" ? text.opencodeSessions : "SimpleX channels"}</p>
|
|
155
|
+
<h2><FiHash aria-hidden="true" /> {title}</h2>
|
|
156
|
+
</div>
|
|
157
|
+
<span className="count-pill">{text.readOnly}</span>
|
|
158
|
+
</header>
|
|
159
|
+
<div className="chat-message-list">
|
|
160
|
+
{conversationMode === "ai" ? <>
|
|
161
|
+
<ChatBubble speaker="assistant" title={selectedSession?.title ?? "AI session bridge"} body={selectedSession ? `Most recent OpenCode session metadata: ${selectedSession.model} via ${selectedSession.agent}, updated ${selectedSession.updated_at}. Message payloads stay out of the status API until the turbostarter/ai chat bridge lands.` : "OpenCode session creation and continuation need an audited write route. This panel is ready for the turbostarter/ai chat surface once that adapter is connected."} />
|
|
162
|
+
<ChatBubble speaker="user" title={selectedRepo?.name ?? "Local repo"} body={selectedRepo ? `Selected repo: ${selectedRepo.path_ref}. Session metadata is grouped per local repo and sorted newest first from ${status.opencode_sessions.path_ref}.` : "No local repos were discovered yet."} />
|
|
163
|
+
</> : <>
|
|
164
|
+
<ChatBubble speaker="assistant" title="SimpleX transport" body={text.simplexReady} />
|
|
165
|
+
<ChatBubble speaker="user" title="People channel" body="Teams and direct messages share the same Slack-like channel layout while protected message payloads remain behind Vault policy." />
|
|
166
|
+
</>}
|
|
167
|
+
</div>
|
|
168
|
+
<form className="chat-composer" aria-label="Chat composer">
|
|
169
|
+
<textarea disabled placeholder={text.chatInputPlaceholder} />
|
|
170
|
+
<button disabled type="button">Send</button>
|
|
171
|
+
</form>
|
|
172
|
+
</div>
|
|
173
|
+
</section>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function ChatBubble({ body, speaker, title }: { body: string; speaker: "assistant" | "user"; title: string }) {
|
|
178
|
+
return (
|
|
179
|
+
<article className={`chat-bubble ${speaker}`}>
|
|
180
|
+
<strong>{title}</strong>
|
|
181
|
+
<p>{body}</p>
|
|
182
|
+
</article>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
121
186
|
function ProfileMenu({ openSurface, userName }: { openSurface: (surface: SurfaceId) => void; userName: string }): ReactElement {
|
|
122
187
|
return (
|
|
123
188
|
<div className="popover-menu profile-menu" role="menu">
|
|
@@ -2,6 +2,8 @@ import type { GuiFileRootId, GuiStatusData } from "@aidevops/gui-shared";
|
|
|
2
2
|
|
|
3
3
|
export type ThemePreference = "system" | "light" | "dark";
|
|
4
4
|
export type SidebarMode = "devops" | "comms";
|
|
5
|
+
export type ShellMode = "devices" | "sessions";
|
|
6
|
+
export type ConversationMode = "ai" | "people";
|
|
5
7
|
export type FontPreference =
|
|
6
8
|
| "IBM Plex Mono"
|
|
7
9
|
| "IBM Plex Sans"
|
|
@@ -208,6 +210,16 @@ export const text = {
|
|
|
208
210
|
plannedHomes: "Planned homes",
|
|
209
211
|
plannedNotice: "This surface is a local UI placeholder. Persistence and actions need explicit trust-boundary work.",
|
|
210
212
|
comms: "Comms",
|
|
213
|
+
ai: "AI",
|
|
214
|
+
people: "People",
|
|
215
|
+
newSession: "New Session",
|
|
216
|
+
sessionHistory: "Session history",
|
|
217
|
+
opencodeSessions: "OpenCode sessions",
|
|
218
|
+
simplexReady: "SimpleX transport placeholder: encrypted channel adapters will mount here behind Vault and trust-boundary checks.",
|
|
219
|
+
teams: "Teams",
|
|
220
|
+
directMessages: "Direct Messages",
|
|
221
|
+
chatInputPlaceholder: "Write a message when audited session write routes are enabled.",
|
|
222
|
+
localRepoSelector: "Local repo selector",
|
|
211
223
|
performance: "Performance",
|
|
212
224
|
projectWorkIntro: "Document folders for inboxes, campaigns, cases, configuration, feedback, knowledge, maintenance, performance, and reports are planned.",
|
|
213
225
|
projectWork: "Documents",
|
|
@@ -82,6 +82,7 @@ function normalizeStatusEnvelope(envelope: GuiResponseEnvelope<Partial<GuiStatus
|
|
|
82
82
|
settings: { ...statusFixture.settings, ...data.settings },
|
|
83
83
|
repos: { ...statusFixture.repos, ...data.repos, repos: data.repos?.repos ?? statusFixture.repos.repos },
|
|
84
84
|
local_repos: { ...statusFixture.local_repos, ...data.local_repos, repos: data.local_repos?.repos ?? statusFixture.local_repos.repos },
|
|
85
|
+
opencode_sessions: { ...statusFixture.opencode_sessions, ...data.opencode_sessions, sessions: data.opencode_sessions?.sessions ?? statusFixture.opencode_sessions.sessions },
|
|
85
86
|
oauth_pool: { ...statusFixture.oauth_pool, ...data.oauth_pool, providers: data.oauth_pool?.providers ?? statusFixture.oauth_pool.providers },
|
|
86
87
|
setup_targets: data.setup_targets ?? statusFixture.setup_targets,
|
|
87
88
|
ai_apps: data.ai_apps ?? statusFixture.ai_apps,
|
|
@@ -206,6 +206,7 @@ code {
|
|
|
206
206
|
.brand-lockup,
|
|
207
207
|
.sidebar-titlebar,
|
|
208
208
|
.sidebar-history-controls,
|
|
209
|
+
.icon-switch,
|
|
209
210
|
.sidebar-mode-tabs,
|
|
210
211
|
.theme-control-heading,
|
|
211
212
|
.appearance-panel-tab,
|
|
@@ -279,6 +280,40 @@ code {
|
|
|
279
280
|
width: 26px;
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
.icon-switch {
|
|
284
|
+
background: color-mix(in srgb, var(--surface-muted) 72%, transparent);
|
|
285
|
+
border: 1px solid var(--border);
|
|
286
|
+
border-radius: 999px;
|
|
287
|
+
gap: 2px;
|
|
288
|
+
padding: 2px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.icon-switch button {
|
|
292
|
+
align-items: center;
|
|
293
|
+
background: transparent;
|
|
294
|
+
border: 0;
|
|
295
|
+
border-radius: 999px;
|
|
296
|
+
color: var(--text-secondary);
|
|
297
|
+
display: inline-flex;
|
|
298
|
+
gap: 4px;
|
|
299
|
+
height: 28px;
|
|
300
|
+
justify-content: center;
|
|
301
|
+
min-width: 32px;
|
|
302
|
+
padding: 0 8px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.icon-switch button span {
|
|
306
|
+
display: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.icon-switch button.active,
|
|
310
|
+
.icon-switch button:hover,
|
|
311
|
+
.icon-switch button:focus-visible {
|
|
312
|
+
background: var(--surface-elevated);
|
|
313
|
+
color: var(--accent);
|
|
314
|
+
outline: none;
|
|
315
|
+
}
|
|
316
|
+
|
|
282
317
|
.sidebar-history-controls button:hover:not(:disabled),
|
|
283
318
|
.sidebar-history-controls button:focus-visible {
|
|
284
319
|
background: var(--surface-elevated);
|
|
@@ -326,6 +361,71 @@ code {
|
|
|
326
361
|
outline: none;
|
|
327
362
|
}
|
|
328
363
|
|
|
364
|
+
.conversation-panel {
|
|
365
|
+
display: grid;
|
|
366
|
+
gap: 12px;
|
|
367
|
+
padding: 0 6px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.new-session-button {
|
|
371
|
+
background: color-mix(in srgb, var(--accent) 15%, transparent);
|
|
372
|
+
border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
|
|
373
|
+
border-radius: 12px;
|
|
374
|
+
color: var(--accent);
|
|
375
|
+
font-weight: 900;
|
|
376
|
+
padding: 10px 12px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.repo-session-selector {
|
|
380
|
+
align-items: end;
|
|
381
|
+
display: grid;
|
|
382
|
+
gap: 8px;
|
|
383
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.repo-session-selector button {
|
|
387
|
+
align-items: center;
|
|
388
|
+
background: var(--surface-muted);
|
|
389
|
+
border: 1px solid var(--border);
|
|
390
|
+
border-radius: 999px;
|
|
391
|
+
color: var(--text-secondary);
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
height: 34px;
|
|
394
|
+
justify-content: center;
|
|
395
|
+
width: 34px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.repo-session-selector label {
|
|
399
|
+
display: grid;
|
|
400
|
+
gap: 5px;
|
|
401
|
+
min-width: 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.repo-session-selector label span {
|
|
405
|
+
color: var(--text-muted);
|
|
406
|
+
font-size: 0.72rem;
|
|
407
|
+
font-weight: 900;
|
|
408
|
+
text-transform: uppercase;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.repo-session-selector select {
|
|
412
|
+
background: var(--surface-muted);
|
|
413
|
+
border: 1px solid var(--border);
|
|
414
|
+
border-radius: 10px;
|
|
415
|
+
color: var(--text-primary);
|
|
416
|
+
min-width: 0;
|
|
417
|
+
padding: 8px;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.session-history-list .surface-link {
|
|
421
|
+
min-height: 46px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.empty-sidebar-state {
|
|
425
|
+
color: var(--text-muted);
|
|
426
|
+
margin: 8px;
|
|
427
|
+
}
|
|
428
|
+
|
|
329
429
|
.sidebar-group {
|
|
330
430
|
margin-bottom: 14px;
|
|
331
431
|
}
|
|
@@ -752,6 +852,10 @@ code {
|
|
|
752
852
|
white-space: nowrap;
|
|
753
853
|
}
|
|
754
854
|
|
|
855
|
+
.workspace-history-controls {
|
|
856
|
+
flex: 0 0 auto;
|
|
857
|
+
}
|
|
858
|
+
|
|
755
859
|
.workspace-search span,
|
|
756
860
|
.workspace-search input {
|
|
757
861
|
color: var(--text-muted);
|
|
@@ -1015,6 +1119,93 @@ code {
|
|
|
1015
1119
|
gap: 16px;
|
|
1016
1120
|
}
|
|
1017
1121
|
|
|
1122
|
+
.chat-surface {
|
|
1123
|
+
display: grid;
|
|
1124
|
+
min-height: 100%;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.chat-thread-panel {
|
|
1128
|
+
background: var(--surface-elevated);
|
|
1129
|
+
border: 1px solid var(--border);
|
|
1130
|
+
border-radius: 20px;
|
|
1131
|
+
display: grid;
|
|
1132
|
+
grid-template-rows: auto minmax(320px, 1fr) auto;
|
|
1133
|
+
min-height: 620px;
|
|
1134
|
+
overflow: hidden;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.chat-thread-header {
|
|
1138
|
+
align-items: center;
|
|
1139
|
+
border-bottom: 1px solid var(--border);
|
|
1140
|
+
display: flex;
|
|
1141
|
+
justify-content: space-between;
|
|
1142
|
+
padding: 18px;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
.chat-thread-header h2 {
|
|
1146
|
+
align-items: center;
|
|
1147
|
+
display: flex;
|
|
1148
|
+
gap: 8px;
|
|
1149
|
+
margin: 0;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.chat-message-list {
|
|
1153
|
+
align-content: start;
|
|
1154
|
+
display: grid;
|
|
1155
|
+
gap: 12px;
|
|
1156
|
+
padding: 18px;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.chat-bubble {
|
|
1160
|
+
border: 1px solid var(--border);
|
|
1161
|
+
border-radius: 16px;
|
|
1162
|
+
display: grid;
|
|
1163
|
+
gap: 6px;
|
|
1164
|
+
max-width: min(680px, 90%);
|
|
1165
|
+
padding: 14px;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.chat-bubble.assistant {
|
|
1169
|
+
background: var(--surface-muted);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.chat-bubble.user {
|
|
1173
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
1174
|
+
justify-self: end;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.chat-bubble p {
|
|
1178
|
+
color: var(--text-secondary);
|
|
1179
|
+
margin: 0;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.chat-composer {
|
|
1183
|
+
border-top: 1px solid var(--border);
|
|
1184
|
+
display: grid;
|
|
1185
|
+
gap: 10px;
|
|
1186
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1187
|
+
padding: 14px;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
.chat-composer textarea {
|
|
1191
|
+
background: var(--surface-muted);
|
|
1192
|
+
border: 1px solid var(--border);
|
|
1193
|
+
border-radius: 14px;
|
|
1194
|
+
color: var(--text-primary);
|
|
1195
|
+
min-height: 54px;
|
|
1196
|
+
padding: 12px;
|
|
1197
|
+
resize: vertical;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.chat-composer button {
|
|
1201
|
+
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
1202
|
+
border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
|
|
1203
|
+
border-radius: 14px;
|
|
1204
|
+
color: var(--accent);
|
|
1205
|
+
font-weight: 900;
|
|
1206
|
+
padding: 0 18px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1018
1209
|
.hero-panel,
|
|
1019
1210
|
.panel,
|
|
1020
1211
|
.metric-card,
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.
|
|
15
|
+
# Version: 3.25.0
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|