aidevops 3.31.13 → 3.31.14
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-web/src/AppWorkspace.tsx +43 -13
- package/packages/gui-web/src/StatusSurfaces.tsx +2 -2
- package/packages/gui-web/src/WorkspaceTour.tsx +108 -0
- package/packages/gui-web/src/styles.css +28 -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.31.
|
|
1
|
+
3.31.14
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { type ReactElement, type ReactNode, useEffect, useState } from "react";
|
|
3
|
-
import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash,
|
|
3
|
+
import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash, FiInfo, FiLogOut, FiMap, FiMessageSquare, FiPaperclip, FiSearch, FiSettings, FiShield, FiTerminal, FiTool, FiUser } from "react-icons/fi";
|
|
4
4
|
import type { GuiFileRootId, GuiNotificationSummary, GuiStatusData } from "../../gui-shared/src";
|
|
5
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
6
|
import type { ConversationMode, ShellMode, SurfaceId, SurfaceNavItem } from "./app-model";
|
|
@@ -14,6 +14,7 @@ import { PulseWorkersSurface } from "./PulseWorkersSurface";
|
|
|
14
14
|
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
15
15
|
import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
|
|
16
16
|
import { applyCommandPaletteSelection, useHeaderMenuState } from "./workspace-header-state";
|
|
17
|
+
import { useWorkspaceTour, WorkspaceTourProvider } from "./WorkspaceTour";
|
|
17
18
|
|
|
18
19
|
const communityLinks = {
|
|
19
20
|
github: "https://github.com/marcusquinn/aidevops",
|
|
@@ -42,12 +43,14 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
42
43
|
}) {
|
|
43
44
|
return (
|
|
44
45
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
<WorkspaceTourProvider activeSurface={activeSurface}>
|
|
47
|
+
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setSelectedLocalRepoIndex={setSelectedLocalRepoIndex} setSelectedSessionId={setSelectedSessionId} setShellMode={setShellMode} status={status} />
|
|
48
|
+
<div className="workspace-scroll">
|
|
49
|
+
{shellMode === "sessions"
|
|
50
|
+
? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} selectedSessionId={selectedSessionId} status={status} />
|
|
51
|
+
: <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} openSurface={setActiveSurface} status={status} />}
|
|
52
|
+
</div>
|
|
53
|
+
</WorkspaceTourProvider>
|
|
51
54
|
</section>
|
|
52
55
|
);
|
|
53
56
|
}
|
|
@@ -73,6 +76,7 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
73
76
|
const userInitials = status.machine.initials || "AI";
|
|
74
77
|
const userName = status.machine.username || "Local user";
|
|
75
78
|
const activeNotifications = status.notifications.filter((notification) => notification.status === "active");
|
|
79
|
+
const tour = useWorkspaceTour();
|
|
76
80
|
|
|
77
81
|
useEffect(() => {
|
|
78
82
|
const openCommandPalette = (event: KeyboardEvent) => {
|
|
@@ -125,13 +129,14 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
125
129
|
</div>
|
|
126
130
|
<div className="header-actions" onPointerEnter={headerMenus.clearScheduledHeaderClose} onPointerLeave={headerMenus.scheduleHeaderMenusClose} ref={headerMenus.headerActionsRef}>
|
|
127
131
|
<div className="header-action-menu">
|
|
128
|
-
<button aria-label=
|
|
129
|
-
<
|
|
132
|
+
<button aria-disabled={!tour.hasTour} aria-label={tour.hasTour ? `Start ${activeItem.label} tour` : `No tour available for ${activeItem.label}`} className={tour.activeStep ? "header-icon-button active" : "header-icon-button"} disabled={!tour.hasTour} onClick={() => { headerMenus.closeHeaderMenus(); tour.startTour(); }} title={tour.hasTour ? `Start ${activeItem.label} tour` : `No tour available for ${activeItem.label}`} type="button">
|
|
133
|
+
<FiMap aria-hidden="true" />
|
|
130
134
|
</button>
|
|
131
135
|
<button aria-expanded={headerMenus.notificationsOpen} aria-label="Open notifications" className="header-icon-button" onClick={headerMenus.toggleNotifications} type="button">
|
|
132
136
|
<FiBell aria-hidden="true" />
|
|
133
137
|
{activeNotifications.length > 0 ? <span className="notification-dot" aria-hidden="true" /> : null}
|
|
134
138
|
</button>
|
|
139
|
+
{tour.activeStep ? <WorkspaceSignposts label={activeItem.label} /> : null}
|
|
135
140
|
{headerMenus.notificationsOpen ? <NotificationsMenu notifications={status.notifications} openSurface={(surface) => openItem({ surface })} /> : null}
|
|
136
141
|
</div>
|
|
137
142
|
<button aria-pressed={headerMenus.assistantOpen} aria-label="Toggle AI Assistant" className={headerMenus.assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={headerMenus.toggleAssistant} title="AI sessions (_)" type="button">
|
|
@@ -150,6 +155,31 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
150
155
|
);
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
function WorkspaceSignposts({ label }: { label: string }): ReactElement | null {
|
|
159
|
+
const tour = useWorkspaceTour();
|
|
160
|
+
const step = tour.activeStep;
|
|
161
|
+
|
|
162
|
+
if (!step) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div className="popover-menu signposts-tour" role="dialog" aria-label={`${label} tour`}>
|
|
168
|
+
<div className="signposts-heading">
|
|
169
|
+
<strong>{step.title}</strong>
|
|
170
|
+
<span>{tour.activeStepIndex + 1}/{tour.stepCount}</span>
|
|
171
|
+
</div>
|
|
172
|
+
<p>{step.body}</p>
|
|
173
|
+
<code>{step.target}</code>
|
|
174
|
+
{tour.missingTarget ? <p className="tour-missing-target">Target is not visible on this page; continuing safely.</p> : null}
|
|
175
|
+
<div className="signposts-actions">
|
|
176
|
+
<button onClick={tour.nextStep} type="button">Next</button>
|
|
177
|
+
<button onClick={tour.closeTour} type="button">Close</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
function handleWorkspaceShortcut(event: KeyboardEvent, actions: { goBack: () => void; goForward: () => void; openCommand: (query: string) => void }): void {
|
|
154
184
|
const shortcutQuery = commandPaletteShortcutQuery(event);
|
|
155
185
|
const isEditableTarget = isEditableShortcutTarget(event.target);
|
|
@@ -386,9 +416,9 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
|
|
|
386
416
|
aiSessions: <AiSessionsSurface selectedRepoIndex={0} status={status} />,
|
|
387
417
|
channels: <CommsConversationSurface mode="channels" />,
|
|
388
418
|
directMessages: <CommsConversationSurface mode="directMessages" />,
|
|
389
|
-
workers: <PulseWorkersSurface status={status}
|
|
390
|
-
repos: <PlannedSurface label={text.repos} detail={text.reposIntro} />,
|
|
391
|
-
deployments: <PlannedSurface label={text.deployments} detail={text.deploymentsIntro} />,
|
|
419
|
+
workers: <div data-tour="workers-surface"><PulseWorkersSurface status={status} /></div>,
|
|
420
|
+
repos: <PlannedSurface label={text.repos} detail={text.reposIntro} tourId="repos-surface" />,
|
|
421
|
+
deployments: <PlannedSurface label={text.deployments} detail={text.deploymentsIntro} tourId="deployments-surface" />,
|
|
392
422
|
routines: <PlannedSurface label={text.routines} detail={text.routineDetail} />,
|
|
393
423
|
devices: <PlannedSurface label={text.devices} detail={text.devicesIntro} />,
|
|
394
424
|
vpnsProxies: <PlannedSurface label={text.vpnsProxies} detail={text.vpnsProxiesIntro} />,
|
|
@@ -474,7 +504,7 @@ function HelpSurface(): ReactElement {
|
|
|
474
504
|
|
|
475
505
|
function SettingsSurface({ status }: { status: GuiStatusData }): ReactElement {
|
|
476
506
|
return (
|
|
477
|
-
<section className="settings-surface">
|
|
507
|
+
<section className="settings-surface" data-tour="settings-surface">
|
|
478
508
|
<div className="planned-card">
|
|
479
509
|
<h2>General settings</h2>
|
|
480
510
|
<p>{text.settingsAccountIntro}</p>
|
|
@@ -532,9 +532,9 @@ function isActionablePathRef(pathRef: string): boolean {
|
|
|
532
532
|
return pathRef.startsWith("~/") || pathRef.startsWith("/");
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
-
export function PlannedSurface({ detail, label }: { detail: string; label: string }) {
|
|
535
|
+
export function PlannedSurface({ detail, label, tourId }: { detail: string; label: string; tourId?: string }) {
|
|
536
536
|
return (
|
|
537
|
-
<section className="panel" aria-label={label}>
|
|
537
|
+
<section className="panel" aria-label={label} data-tour={tourId}>
|
|
538
538
|
<div className="section-heading">
|
|
539
539
|
<p className="eyebrow">{text.planned}</p>
|
|
540
540
|
<h2>{label}</h2>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import type { SurfaceId } from "./app-model";
|
|
3
|
+
|
|
4
|
+
export interface WorkspaceTourStep {
|
|
5
|
+
body: string;
|
|
6
|
+
target: string;
|
|
7
|
+
title: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type WorkspaceTourRegistry = Partial<Record<SurfaceId, WorkspaceTourStep[]>>;
|
|
11
|
+
|
|
12
|
+
interface WorkspaceTourContextValue {
|
|
13
|
+
activeStep: WorkspaceTourStep | undefined;
|
|
14
|
+
activeStepIndex: number;
|
|
15
|
+
closeTour: () => void;
|
|
16
|
+
hasTour: boolean;
|
|
17
|
+
missingTarget: boolean;
|
|
18
|
+
nextStep: () => void;
|
|
19
|
+
startTour: () => void;
|
|
20
|
+
stepCount: number;
|
|
21
|
+
steps: WorkspaceTourStep[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const WorkspaceTourContext = createContext<WorkspaceTourContextValue | undefined>(undefined);
|
|
25
|
+
|
|
26
|
+
export const workspaceTourRegistry = {
|
|
27
|
+
aiSessions: [
|
|
28
|
+
{ body: "Choose a local repo/session context before the Turbostarter AI transport adapter is enabled.", target: '[data-tour="ai-session-list"]', title: "Session list" },
|
|
29
|
+
{ body: "Read the current AI transcript, attachments, tool status, and GenUI cards in one MessageScroller-compatible lane.", target: '[data-tour="ai-session-transcript"]', title: "AI transcript" },
|
|
30
|
+
{ body: "The prompt composer is disabled until audited write routes land, but its stable selector is ready for the session bridge.", target: '[data-tour="ai-composer"]', title: "Composer" },
|
|
31
|
+
],
|
|
32
|
+
channels: [
|
|
33
|
+
{ body: "Channels use the shared conversation model and encrypted transport placeholder.", target: '[data-tour="comms-conversations"]', title: "Channel workspace" },
|
|
34
|
+
{ body: "Messages, system events, reactions, and Tambo DevOps cards render through the shared timeline.", target: '[data-tour="comms-message-timeline"]', title: "Channel timeline" },
|
|
35
|
+
],
|
|
36
|
+
directMessages: [
|
|
37
|
+
{ body: "Direct and group DM threads share the same conversation shell as channels.", target: '[data-tour="comms-conversations"]', title: "DM workspace" },
|
|
38
|
+
{ body: "Protected payloads remain read-only until Vault policy and audited transport routes are connected.", target: '[data-tour="comms-message-timeline"]', title: "DM timeline" },
|
|
39
|
+
],
|
|
40
|
+
workers: [
|
|
41
|
+
{ body: "Pulse worker health, systemic findings, and safe diagnostic actions collect on this operational dashboard.", target: '[data-tour="workers-surface"]', title: "Workers dashboard" },
|
|
42
|
+
],
|
|
43
|
+
repos: [
|
|
44
|
+
{ body: "Repository context will connect local and remote repo adapters once audited read/write routes land.", target: '[data-tour="repos-surface"]', title: "Repos" },
|
|
45
|
+
],
|
|
46
|
+
deployments: [
|
|
47
|
+
{ body: "Deployment activity, environments, releases, and rollout checks will appear here after deployment adapters land.", target: '[data-tour="deployments-surface"]', title: "Deployments" },
|
|
48
|
+
],
|
|
49
|
+
settings: [
|
|
50
|
+
{ body: "Account, appearance, language, and notification preferences are grouped here while writes remain disabled.", target: '[data-tour="settings-surface"]', title: "Settings" },
|
|
51
|
+
],
|
|
52
|
+
} satisfies WorkspaceTourRegistry;
|
|
53
|
+
|
|
54
|
+
export function WorkspaceTourProvider({ activeSurface, children, registry = workspaceTourRegistry }: { activeSurface: SurfaceId; children: ReactNode; registry?: WorkspaceTourRegistry }): ReactElement {
|
|
55
|
+
const steps = registry[activeSurface] ?? [];
|
|
56
|
+
const [activeStepIndex, setActiveStepIndex] = useState(0);
|
|
57
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
58
|
+
const [missingTarget, setMissingTarget] = useState(false);
|
|
59
|
+
const activeStep = isOpen ? steps[activeStepIndex] : undefined;
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const nextSurface = activeSurface;
|
|
63
|
+
if (!nextSurface) return;
|
|
64
|
+
setIsOpen(false);
|
|
65
|
+
setActiveStepIndex(0);
|
|
66
|
+
setMissingTarget(false);
|
|
67
|
+
}, [activeSurface]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!activeStep || typeof document === "undefined") {
|
|
71
|
+
setMissingTarget(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const target = document.querySelector(activeStep.target);
|
|
76
|
+
setMissingTarget(target === null);
|
|
77
|
+
if (target instanceof HTMLElement) {
|
|
78
|
+
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
79
|
+
}
|
|
80
|
+
}, [activeStep]);
|
|
81
|
+
|
|
82
|
+
const value = useMemo(() => ({
|
|
83
|
+
activeStep,
|
|
84
|
+
activeStepIndex,
|
|
85
|
+
closeTour: () => setIsOpen(false),
|
|
86
|
+
hasTour: steps.length > 0,
|
|
87
|
+
missingTarget,
|
|
88
|
+
nextStep: () => setActiveStepIndex((current) => current + 1 >= steps.length ? 0 : current + 1),
|
|
89
|
+
startTour: () => {
|
|
90
|
+
if (steps.length === 0) return;
|
|
91
|
+
setActiveStepIndex(0);
|
|
92
|
+
setIsOpen(true);
|
|
93
|
+
},
|
|
94
|
+
stepCount: steps.length,
|
|
95
|
+
steps,
|
|
96
|
+
}), [activeStep, activeStepIndex, missingTarget, steps]);
|
|
97
|
+
|
|
98
|
+
return <WorkspaceTourContext.Provider value={value}>{children}</WorkspaceTourContext.Provider>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function useWorkspaceTour(): WorkspaceTourContextValue {
|
|
102
|
+
const context = useContext(WorkspaceTourContext);
|
|
103
|
+
if (!context) {
|
|
104
|
+
throw new Error("useWorkspaceTour must be used inside WorkspaceTourProvider");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
@@ -1364,6 +1364,34 @@ code {
|
|
|
1364
1364
|
min-width: 320px;
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
+
.signposts-tour {
|
|
1368
|
+
min-width: min(360px, 88vw);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.signposts-heading,
|
|
1372
|
+
.signposts-actions {
|
|
1373
|
+
align-items: center;
|
|
1374
|
+
display: flex;
|
|
1375
|
+
justify-content: space-between;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.signposts-heading span {
|
|
1379
|
+
background: var(--surface-muted);
|
|
1380
|
+
border-radius: 999px;
|
|
1381
|
+
color: var(--text-muted);
|
|
1382
|
+
font-size: 0.76rem;
|
|
1383
|
+
font-weight: 900;
|
|
1384
|
+
padding: 4px 8px;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
.signposts-tour code {
|
|
1388
|
+
overflow-wrap: anywhere;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.tour-missing-target {
|
|
1392
|
+
color: var(--notification-warning-fg) !important;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1367
1395
|
.notifications-menu-heading,
|
|
1368
1396
|
.notification-preview {
|
|
1369
1397
|
align-items: center;
|
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.31.
|
|
15
|
+
# Version: 3.31.14
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|