@guuey/create-agentic-app 0.15.1 → 0.16.1
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/templates/agentic-app/claude-agent-sdk/package.json +3 -3
- package/dist/templates/agentic-app/claude-agent-sdk/prompts/system.md +6 -0
- package/dist/templates/agentic-app/claude-agent-sdk/web/package.json +3 -2
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AgentChat.tsx +5 -1
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AppShell.tsx +104 -53
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/main.tsx +1 -0
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/styles-app.css +15 -8
- package/dist/templates/agentic-app/google-adk/package.json +2 -2
- package/dist/templates/agentic-app/google-adk/prompts/system.md +6 -0
- package/dist/templates/agentic-app/google-adk/web/package.json +3 -2
- package/dist/templates/agentic-app/google-adk/web/src/components/AgentChat.tsx +5 -1
- package/dist/templates/agentic-app/google-adk/web/src/components/AppShell.tsx +104 -53
- package/dist/templates/agentic-app/google-adk/web/src/main.tsx +1 -0
- package/dist/templates/agentic-app/google-adk/web/src/styles-app.css +15 -8
- package/dist/templates/agentic-app/openai-agents-sdk/package.json +3 -3
- package/dist/templates/agentic-app/openai-agents-sdk/prompts/system.md +6 -0
- package/dist/templates/agentic-app/openai-agents-sdk/web/package.json +3 -2
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AgentChat.tsx +5 -1
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AppShell.tsx +104 -53
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/main.tsx +1 -0
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/styles-app.css +15 -8
- package/dist/templates/base/claude-agent-sdk/package.json +3 -3
- package/dist/templates/base/claude-agent-sdk/prompts/system.md +6 -0
- package/dist/templates/base/claude-agent-sdk/web/package.json +1 -1
- package/dist/templates/base/claude-agent-sdk/web/src/components/AgentChat.tsx +5 -1
- package/dist/templates/base/google-adk/package.json +2 -2
- package/dist/templates/base/google-adk/prompts/system.md +6 -0
- package/dist/templates/base/google-adk/web/package.json +1 -1
- package/dist/templates/base/google-adk/web/src/components/AgentChat.tsx +5 -1
- package/dist/templates/base/openai-agents-sdk/package.json +3 -3
- package/dist/templates/base/openai-agents-sdk/prompts/system.md +6 -0
- package/dist/templates/base/openai-agents-sdk/web/package.json +1 -1
- package/dist/templates/base/openai-agents-sdk/web/src/components/AgentChat.tsx +5 -1
- package/package.json +3 -3
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@anthropic-ai/claude-agent-sdk": "^0.3.199",
|
|
15
|
-
"@guuey/config": "0.
|
|
16
|
-
"@guuey/worker": "0.
|
|
15
|
+
"@guuey/config": "0.16.1",
|
|
16
|
+
"@guuey/worker": "0.16.1"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@ggui-ai/cli": "0.12.0",
|
|
20
|
-
"@guuey/cli": "0.
|
|
20
|
+
"@guuey/cli": "0.16.1",
|
|
21
21
|
"tsup": "^8.5.0",
|
|
22
22
|
"tsx": "^4.19.0",
|
|
23
23
|
"typescript": "^5.8.0"
|
|
@@ -7,3 +7,9 @@ loop by default; keep plain prose for one-line answers with nothing to
|
|
|
7
7
|
show. After changing todos (create, toggle, delete), render the updated
|
|
8
8
|
list, so every turn ends on a surface that reflects the current state.
|
|
9
9
|
Use only the fields the surface's schema declares.
|
|
10
|
+
|
|
11
|
+
An under-specified ask is NEVER answered with a text-only list of
|
|
12
|
+
questions: render the closest useful surface FIRST — the current list,
|
|
13
|
+
the options, a form — and ask your one narrowing question in a short
|
|
14
|
+
line beside it. Clarification happens ON a surface, from the very first
|
|
15
|
+
turn.
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@guuey/
|
|
13
|
-
"@guuey/
|
|
12
|
+
"@guuey/agent-layout": "0.16.1",
|
|
13
|
+
"@guuey/chat": "0.16.1",
|
|
14
|
+
"@guuey/mcp-apps-host": "0.16.1",
|
|
14
15
|
"oidc-client-ts": "^3.1.0",
|
|
15
16
|
"qrcode": "^1.5.4",
|
|
16
17
|
"react": "^19.0.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { useEffect, useState, type CSSProperties } from "react";
|
|
12
12
|
import { GuueyChat } from "@guuey/chat/react";
|
|
13
|
-
import type { GuueyChatHandle } from "@guuey/chat/react";
|
|
13
|
+
import type { GuueyChatActivityEvent, GuueyChatHandle } from "@guuey/chat/react";
|
|
14
14
|
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
15
15
|
import { agentEndpointUrl, appConfig, historyBaseUrl } from "../config";
|
|
16
16
|
import { currentIdentityMode, ensureGuestSecret } from "../lib/identity";
|
|
@@ -40,12 +40,15 @@ export function AgentChat({
|
|
|
40
40
|
style,
|
|
41
41
|
viewsBridge,
|
|
42
42
|
onReady,
|
|
43
|
+
onActivity,
|
|
43
44
|
}: {
|
|
44
45
|
className?: string;
|
|
45
46
|
style?: CSSProperties;
|
|
46
47
|
viewsBridge?: ViewsBridge;
|
|
47
48
|
/** Receives the chat handle — the AppShell mounts canvas views with `handle.viewSlotProps()`. */
|
|
48
49
|
onReady?: (handle: GuueyChatHandle) => void;
|
|
50
|
+
/** Turn-lifecycle edges (guuey#403) — the agent-layout bridge's seam. */
|
|
51
|
+
onActivity?: (event: GuueyChatActivityEvent) => void;
|
|
49
52
|
}) {
|
|
50
53
|
// The user's CHOSEN mode wins: an explicit "Continue as guest" must never
|
|
51
54
|
// be shadowed by a cached OIDC session. Only when no choice is recorded
|
|
@@ -76,6 +79,7 @@ export function AgentChat({
|
|
|
76
79
|
className,
|
|
77
80
|
style,
|
|
78
81
|
...(onReady !== undefined ? { onReady } : {}),
|
|
82
|
+
...(onActivity !== undefined ? { onActivity } : {}),
|
|
79
83
|
...(viewsBridge !== undefined
|
|
80
84
|
? {
|
|
81
85
|
policy: RAIL_POLICY,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The CHAT-RAIL shell (guuey#303, founder-confirmed)
|
|
2
|
+
* The CHAT-RAIL shell (guuey#303, founder-confirmed), on
|
|
3
|
+
* `@guuey/agent-layout` (guuey#403 — this migration is the lib's
|
|
4
|
+
* acceptance test):
|
|
3
5
|
*
|
|
4
6
|
* ┌──────────┬────────────────────────┐
|
|
5
7
|
* │ ☰ Menu 1 │ │
|
|
@@ -18,20 +20,54 @@
|
|
|
18
20
|
* re-selects its render onto the canvas ("just like a web browser's
|
|
19
21
|
* history"); a new render navigates forward automatically; picking a
|
|
20
22
|
* menu swaps the canvas back to your pages.
|
|
23
|
+
*
|
|
24
|
+
* The layout lib owns the AGENT-MODE physics — the two-tone sidebar, the
|
|
25
|
+
* pane's ground following the user's attention (submit → agent tone; any
|
|
26
|
+
* navigation → app tone, derived from the route change), the working
|
|
27
|
+
* state while the agent has the room, light/dark via the SAME mode you
|
|
28
|
+
* give the chat. The shell wires it once: `navigationKey` from the
|
|
29
|
+
* router, `bindGuueyChat` onto the rail, `agentViewClosed` on the back
|
|
30
|
+
* affordance. Zero per-link wiring.
|
|
21
31
|
*/
|
|
22
|
-
import { useCallback, useRef, useState } from "react";
|
|
32
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
23
33
|
import type { GuueyChatHandle } from "@guuey/chat/react";
|
|
24
|
-
import { NavLink, Outlet, useNavigate } from "react-router-dom";
|
|
34
|
+
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
|
25
35
|
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
26
36
|
import { GuueyView } from "@guuey/mcp-apps-host/react";
|
|
37
|
+
import {
|
|
38
|
+
ActivePane,
|
|
39
|
+
AgentModeProvider,
|
|
40
|
+
AgentModeShell,
|
|
41
|
+
AgentModeSidebar,
|
|
42
|
+
SidebarPanel,
|
|
43
|
+
bindGuueyChat,
|
|
44
|
+
useAgentMode,
|
|
45
|
+
} from "@guuey/agent-layout/react";
|
|
27
46
|
import { appConfig } from "../config";
|
|
28
47
|
import { AgentChat } from "../components/AgentChat";
|
|
29
48
|
import { currentIdentityMode, logOut } from "../lib/identity";
|
|
30
49
|
import { oidcConfigured, signOutOidc } from "../lib/oidc";
|
|
31
50
|
|
|
32
51
|
export function AppShell() {
|
|
52
|
+
const location = useLocation();
|
|
53
|
+
return (
|
|
54
|
+
<AgentModeProvider
|
|
55
|
+
mode={appConfig.theme.mode}
|
|
56
|
+
navigationKey={location.pathname}
|
|
57
|
+
identity={<span className="brand-mark">{appConfig.brand.logoText}</span>}
|
|
58
|
+
>
|
|
59
|
+
<ShellBody />
|
|
60
|
+
</AgentModeProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ShellBody() {
|
|
33
65
|
const navigate = useNavigate();
|
|
34
66
|
const mode = currentIdentityMode();
|
|
67
|
+
const { dispatch } = useAgentMode();
|
|
68
|
+
// The lib's chat bridge: submit/settle/view-mount flow into the
|
|
69
|
+
// active-panel machine; the shell composes its own roster handling on top.
|
|
70
|
+
const binding = useMemo(() => bindGuueyChat(dispatch), [dispatch]);
|
|
35
71
|
|
|
36
72
|
// The rail↔canvas bridge: the kit's view roster (mount material lives
|
|
37
73
|
// here, the rail shows only chips), the selected key, and whether the
|
|
@@ -45,31 +81,42 @@ export function AppShell() {
|
|
|
45
81
|
const [canvasShowsView, setCanvasShowsView] = useState(false);
|
|
46
82
|
const newestKeyRef = useRef<string | undefined>(undefined);
|
|
47
83
|
|
|
48
|
-
const onViewsChange = useCallback(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
const onViewsChange = useCallback(
|
|
85
|
+
(next: PlanViewSummary[]) => {
|
|
86
|
+
binding.onViewsChange(next); // the (d) working state clears on a live mount
|
|
87
|
+
setViews(next);
|
|
88
|
+
// Browser-history forward-navigation: a NEW live render takes the
|
|
89
|
+
// canvas. (Reversed find = newest mountable; live entries sit after
|
|
90
|
+
// history in the roster's transcript order.)
|
|
91
|
+
const newest = [...next].reverse().find((v) => v.mount !== null && v.phase !== "expired");
|
|
92
|
+
if (newest !== undefined && newest.key !== newestKeyRef.current) {
|
|
93
|
+
newestKeyRef.current = newest.key;
|
|
94
|
+
setSelectedKey(newest.key);
|
|
95
|
+
setCanvasShowsView(true);
|
|
96
|
+
// The demo-tour hook (guuey#303): step machines outside the app can
|
|
97
|
+
// key on "the agent just drew UI".
|
|
98
|
+
window.dispatchEvent(
|
|
99
|
+
new CustomEvent("demo:render-complete", {
|
|
100
|
+
detail: { key: newest.key, title: newest.title },
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[binding],
|
|
106
|
+
);
|
|
67
107
|
|
|
68
108
|
const onViewRef = useCallback((item: ViewRefItem) => {
|
|
69
109
|
setSelectedKey(item.key);
|
|
70
110
|
setCanvasShowsView(true);
|
|
71
111
|
}, []);
|
|
72
112
|
|
|
113
|
+
const closeView = useCallback(() => {
|
|
114
|
+
setCanvasShowsView(false);
|
|
115
|
+
// The lib rule: closing an agent view returns the tone to the menu
|
|
116
|
+
// UNLESS a turn is still streaming.
|
|
117
|
+
dispatch({ type: "agentViewClosed" });
|
|
118
|
+
}, [dispatch]);
|
|
119
|
+
|
|
73
120
|
const selected = views.find((v) => v.key === selectedKey && v.mount !== null);
|
|
74
121
|
|
|
75
122
|
async function handleLogOut() {
|
|
@@ -80,44 +127,48 @@ export function AppShell() {
|
|
|
80
127
|
|
|
81
128
|
return (
|
|
82
129
|
<div className="app-shell">
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
<AgentModeShell className="app-body">
|
|
131
|
+
<AgentModeSidebar className="sidebar">
|
|
132
|
+
<SidebarPanel section="app" className="sidebar-top">
|
|
133
|
+
<div className="sidebar-brand">
|
|
134
|
+
<span className="brand-mark">{appConfig.brand.logoText}</span>
|
|
135
|
+
<span>{appConfig.brand.name}</span>
|
|
136
|
+
</div>
|
|
137
|
+
<nav className="sidebar-menus">
|
|
138
|
+
{/* Picking a menu swaps the canvas back to your pages — the
|
|
139
|
+
TONE follow is the lib's (route change + capture-phase). */}
|
|
140
|
+
<NavLink to="/app" end onClick={() => setCanvasShowsView(false)}>
|
|
141
|
+
Dashboard
|
|
142
|
+
</NavLink>
|
|
143
|
+
<NavLink to="/app/reports" onClick={() => setCanvasShowsView(false)}>
|
|
144
|
+
Reports
|
|
145
|
+
</NavLink>
|
|
146
|
+
<NavLink to="/app/setup" onClick={() => setCanvasShowsView(false)}>
|
|
147
|
+
Setup
|
|
148
|
+
</NavLink>
|
|
149
|
+
<NavLink to="/app/mobile" onClick={() => setCanvasShowsView(false)}>
|
|
150
|
+
📱 Talk on mobile
|
|
151
|
+
</NavLink>
|
|
152
|
+
<button type="button" className="dock-logout" onClick={() => void handleLogOut()}>
|
|
153
|
+
Log out{mode === "guest" ? " (guest)" : ""}
|
|
154
|
+
</button>
|
|
155
|
+
</nav>
|
|
156
|
+
</SidebarPanel>
|
|
157
|
+
<SidebarPanel section="agent" className="agent-rail" data-tour="agent-rail">
|
|
108
158
|
<AgentChat
|
|
109
159
|
className="rail-chat"
|
|
110
160
|
viewsBridge={{ promotedViewKey: selectedKey, onViewRef, onViewsChange }}
|
|
111
161
|
onReady={setChat}
|
|
162
|
+
onActivity={binding.onActivity}
|
|
112
163
|
/>
|
|
113
|
-
</
|
|
114
|
-
</
|
|
115
|
-
<
|
|
164
|
+
</SidebarPanel>
|
|
165
|
+
</AgentModeSidebar>
|
|
166
|
+
<ActivePane className="canvas" data-tour="canvas">
|
|
116
167
|
{canvasShowsView && selected !== undefined && selected.mount !== null ? (
|
|
117
168
|
<div className="canvas-view">
|
|
118
169
|
<header className="canvas-view-bar">
|
|
119
170
|
<span>{selected.title}</span>
|
|
120
|
-
<button type="button" className="btn" onClick={
|
|
171
|
+
<button type="button" className="btn" onClick={closeView}>
|
|
121
172
|
Back to {appConfig.brand.name}
|
|
122
173
|
</button>
|
|
123
174
|
</header>
|
|
@@ -146,8 +197,8 @@ export function AppShell() {
|
|
|
146
197
|
) : (
|
|
147
198
|
<Outlet />
|
|
148
199
|
)}
|
|
149
|
-
</
|
|
150
|
-
</
|
|
200
|
+
</ActivePane>
|
|
201
|
+
</AgentModeShell>
|
|
151
202
|
</div>
|
|
152
203
|
);
|
|
153
204
|
}
|
|
@@ -2,6 +2,7 @@ import { StrictMode } from "react";
|
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
4
4
|
import "@guuey/chat/styles.css";
|
|
5
|
+
import "@guuey/agent-layout/styles.css";
|
|
5
6
|
import "./styles.css";
|
|
6
7
|
import "./styles-app.css";
|
|
7
8
|
import { appConfig } from "./config";
|
|
@@ -7,19 +7,26 @@
|
|
|
7
7
|
flex-direction: column;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/* The lib's AgentModeShell grid carries this class; the app states its own
|
|
11
|
+
column width (app-tier override of the lib's minmax default). Display
|
|
12
|
+
comes from the lib sheet. */
|
|
10
13
|
.app-body {
|
|
11
14
|
flex: 1;
|
|
12
|
-
display: flex;
|
|
13
15
|
min-height: 0;
|
|
16
|
+
grid-template-columns: 320px 1fr;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/* Tones come from @guuey/agent-layout (the two-panel pair — light AND
|
|
20
|
+
dark ride the lib's mode-aware tokens); the app keeps its structure. */
|
|
16
21
|
.sidebar {
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
border-right: 1px solid var(--line);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sidebar-top {
|
|
26
|
+
flex: 1;
|
|
19
27
|
display: flex;
|
|
20
28
|
flex-direction: column;
|
|
21
|
-
|
|
22
|
-
background: var(--bg-soft);
|
|
29
|
+
min-height: 0;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
.sidebar-brand {
|
|
@@ -52,12 +59,12 @@
|
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
.agent-rail {
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
/* Explicit: the app's fixed rail height wins over the lib's flex:1
|
|
63
|
+
default (deterministic, not load-order luck). */
|
|
64
|
+
flex: 0 0 auto;
|
|
57
65
|
min-height: 0;
|
|
58
66
|
height: 46vh;
|
|
59
67
|
border-top: 1px solid var(--line);
|
|
60
|
-
background: var(--bg);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
.rail-chat {
|
|
@@ -7,3 +7,9 @@ loop by default; keep plain prose for one-line answers with nothing to
|
|
|
7
7
|
show. After changing todos (create, toggle, delete), render the updated
|
|
8
8
|
list, so every turn ends on a surface that reflects the current state.
|
|
9
9
|
Use only the fields the surface's schema declares.
|
|
10
|
+
|
|
11
|
+
An under-specified ask is NEVER answered with a text-only list of
|
|
12
|
+
questions: render the closest useful surface FIRST — the current list,
|
|
13
|
+
the options, a form — and ask your one narrowing question in a short
|
|
14
|
+
line beside it. Clarification happens ON a surface, from the very first
|
|
15
|
+
turn.
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@guuey/
|
|
13
|
-
"@guuey/
|
|
12
|
+
"@guuey/agent-layout": "0.16.1",
|
|
13
|
+
"@guuey/chat": "0.16.1",
|
|
14
|
+
"@guuey/mcp-apps-host": "0.16.1",
|
|
14
15
|
"oidc-client-ts": "^3.1.0",
|
|
15
16
|
"qrcode": "^1.5.4",
|
|
16
17
|
"react": "^19.0.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { useEffect, useState, type CSSProperties } from "react";
|
|
12
12
|
import { GuueyChat } from "@guuey/chat/react";
|
|
13
|
-
import type { GuueyChatHandle } from "@guuey/chat/react";
|
|
13
|
+
import type { GuueyChatActivityEvent, GuueyChatHandle } from "@guuey/chat/react";
|
|
14
14
|
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
15
15
|
import { agentEndpointUrl, appConfig, historyBaseUrl } from "../config";
|
|
16
16
|
import { currentIdentityMode, ensureGuestSecret } from "../lib/identity";
|
|
@@ -40,12 +40,15 @@ export function AgentChat({
|
|
|
40
40
|
style,
|
|
41
41
|
viewsBridge,
|
|
42
42
|
onReady,
|
|
43
|
+
onActivity,
|
|
43
44
|
}: {
|
|
44
45
|
className?: string;
|
|
45
46
|
style?: CSSProperties;
|
|
46
47
|
viewsBridge?: ViewsBridge;
|
|
47
48
|
/** Receives the chat handle — the AppShell mounts canvas views with `handle.viewSlotProps()`. */
|
|
48
49
|
onReady?: (handle: GuueyChatHandle) => void;
|
|
50
|
+
/** Turn-lifecycle edges (guuey#403) — the agent-layout bridge's seam. */
|
|
51
|
+
onActivity?: (event: GuueyChatActivityEvent) => void;
|
|
49
52
|
}) {
|
|
50
53
|
// The user's CHOSEN mode wins: an explicit "Continue as guest" must never
|
|
51
54
|
// be shadowed by a cached OIDC session. Only when no choice is recorded
|
|
@@ -76,6 +79,7 @@ export function AgentChat({
|
|
|
76
79
|
className,
|
|
77
80
|
style,
|
|
78
81
|
...(onReady !== undefined ? { onReady } : {}),
|
|
82
|
+
...(onActivity !== undefined ? { onActivity } : {}),
|
|
79
83
|
...(viewsBridge !== undefined
|
|
80
84
|
? {
|
|
81
85
|
policy: RAIL_POLICY,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The CHAT-RAIL shell (guuey#303, founder-confirmed)
|
|
2
|
+
* The CHAT-RAIL shell (guuey#303, founder-confirmed), on
|
|
3
|
+
* `@guuey/agent-layout` (guuey#403 — this migration is the lib's
|
|
4
|
+
* acceptance test):
|
|
3
5
|
*
|
|
4
6
|
* ┌──────────┬────────────────────────┐
|
|
5
7
|
* │ ☰ Menu 1 │ │
|
|
@@ -18,20 +20,54 @@
|
|
|
18
20
|
* re-selects its render onto the canvas ("just like a web browser's
|
|
19
21
|
* history"); a new render navigates forward automatically; picking a
|
|
20
22
|
* menu swaps the canvas back to your pages.
|
|
23
|
+
*
|
|
24
|
+
* The layout lib owns the AGENT-MODE physics — the two-tone sidebar, the
|
|
25
|
+
* pane's ground following the user's attention (submit → agent tone; any
|
|
26
|
+
* navigation → app tone, derived from the route change), the working
|
|
27
|
+
* state while the agent has the room, light/dark via the SAME mode you
|
|
28
|
+
* give the chat. The shell wires it once: `navigationKey` from the
|
|
29
|
+
* router, `bindGuueyChat` onto the rail, `agentViewClosed` on the back
|
|
30
|
+
* affordance. Zero per-link wiring.
|
|
21
31
|
*/
|
|
22
|
-
import { useCallback, useRef, useState } from "react";
|
|
32
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
23
33
|
import type { GuueyChatHandle } from "@guuey/chat/react";
|
|
24
|
-
import { NavLink, Outlet, useNavigate } from "react-router-dom";
|
|
34
|
+
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
|
25
35
|
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
26
36
|
import { GuueyView } from "@guuey/mcp-apps-host/react";
|
|
37
|
+
import {
|
|
38
|
+
ActivePane,
|
|
39
|
+
AgentModeProvider,
|
|
40
|
+
AgentModeShell,
|
|
41
|
+
AgentModeSidebar,
|
|
42
|
+
SidebarPanel,
|
|
43
|
+
bindGuueyChat,
|
|
44
|
+
useAgentMode,
|
|
45
|
+
} from "@guuey/agent-layout/react";
|
|
27
46
|
import { appConfig } from "../config";
|
|
28
47
|
import { AgentChat } from "../components/AgentChat";
|
|
29
48
|
import { currentIdentityMode, logOut } from "../lib/identity";
|
|
30
49
|
import { oidcConfigured, signOutOidc } from "../lib/oidc";
|
|
31
50
|
|
|
32
51
|
export function AppShell() {
|
|
52
|
+
const location = useLocation();
|
|
53
|
+
return (
|
|
54
|
+
<AgentModeProvider
|
|
55
|
+
mode={appConfig.theme.mode}
|
|
56
|
+
navigationKey={location.pathname}
|
|
57
|
+
identity={<span className="brand-mark">{appConfig.brand.logoText}</span>}
|
|
58
|
+
>
|
|
59
|
+
<ShellBody />
|
|
60
|
+
</AgentModeProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ShellBody() {
|
|
33
65
|
const navigate = useNavigate();
|
|
34
66
|
const mode = currentIdentityMode();
|
|
67
|
+
const { dispatch } = useAgentMode();
|
|
68
|
+
// The lib's chat bridge: submit/settle/view-mount flow into the
|
|
69
|
+
// active-panel machine; the shell composes its own roster handling on top.
|
|
70
|
+
const binding = useMemo(() => bindGuueyChat(dispatch), [dispatch]);
|
|
35
71
|
|
|
36
72
|
// The rail↔canvas bridge: the kit's view roster (mount material lives
|
|
37
73
|
// here, the rail shows only chips), the selected key, and whether the
|
|
@@ -45,31 +81,42 @@ export function AppShell() {
|
|
|
45
81
|
const [canvasShowsView, setCanvasShowsView] = useState(false);
|
|
46
82
|
const newestKeyRef = useRef<string | undefined>(undefined);
|
|
47
83
|
|
|
48
|
-
const onViewsChange = useCallback(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
const onViewsChange = useCallback(
|
|
85
|
+
(next: PlanViewSummary[]) => {
|
|
86
|
+
binding.onViewsChange(next); // the (d) working state clears on a live mount
|
|
87
|
+
setViews(next);
|
|
88
|
+
// Browser-history forward-navigation: a NEW live render takes the
|
|
89
|
+
// canvas. (Reversed find = newest mountable; live entries sit after
|
|
90
|
+
// history in the roster's transcript order.)
|
|
91
|
+
const newest = [...next].reverse().find((v) => v.mount !== null && v.phase !== "expired");
|
|
92
|
+
if (newest !== undefined && newest.key !== newestKeyRef.current) {
|
|
93
|
+
newestKeyRef.current = newest.key;
|
|
94
|
+
setSelectedKey(newest.key);
|
|
95
|
+
setCanvasShowsView(true);
|
|
96
|
+
// The demo-tour hook (guuey#303): step machines outside the app can
|
|
97
|
+
// key on "the agent just drew UI".
|
|
98
|
+
window.dispatchEvent(
|
|
99
|
+
new CustomEvent("demo:render-complete", {
|
|
100
|
+
detail: { key: newest.key, title: newest.title },
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[binding],
|
|
106
|
+
);
|
|
67
107
|
|
|
68
108
|
const onViewRef = useCallback((item: ViewRefItem) => {
|
|
69
109
|
setSelectedKey(item.key);
|
|
70
110
|
setCanvasShowsView(true);
|
|
71
111
|
}, []);
|
|
72
112
|
|
|
113
|
+
const closeView = useCallback(() => {
|
|
114
|
+
setCanvasShowsView(false);
|
|
115
|
+
// The lib rule: closing an agent view returns the tone to the menu
|
|
116
|
+
// UNLESS a turn is still streaming.
|
|
117
|
+
dispatch({ type: "agentViewClosed" });
|
|
118
|
+
}, [dispatch]);
|
|
119
|
+
|
|
73
120
|
const selected = views.find((v) => v.key === selectedKey && v.mount !== null);
|
|
74
121
|
|
|
75
122
|
async function handleLogOut() {
|
|
@@ -80,44 +127,48 @@ export function AppShell() {
|
|
|
80
127
|
|
|
81
128
|
return (
|
|
82
129
|
<div className="app-shell">
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
<AgentModeShell className="app-body">
|
|
131
|
+
<AgentModeSidebar className="sidebar">
|
|
132
|
+
<SidebarPanel section="app" className="sidebar-top">
|
|
133
|
+
<div className="sidebar-brand">
|
|
134
|
+
<span className="brand-mark">{appConfig.brand.logoText}</span>
|
|
135
|
+
<span>{appConfig.brand.name}</span>
|
|
136
|
+
</div>
|
|
137
|
+
<nav className="sidebar-menus">
|
|
138
|
+
{/* Picking a menu swaps the canvas back to your pages — the
|
|
139
|
+
TONE follow is the lib's (route change + capture-phase). */}
|
|
140
|
+
<NavLink to="/app" end onClick={() => setCanvasShowsView(false)}>
|
|
141
|
+
Dashboard
|
|
142
|
+
</NavLink>
|
|
143
|
+
<NavLink to="/app/reports" onClick={() => setCanvasShowsView(false)}>
|
|
144
|
+
Reports
|
|
145
|
+
</NavLink>
|
|
146
|
+
<NavLink to="/app/setup" onClick={() => setCanvasShowsView(false)}>
|
|
147
|
+
Setup
|
|
148
|
+
</NavLink>
|
|
149
|
+
<NavLink to="/app/mobile" onClick={() => setCanvasShowsView(false)}>
|
|
150
|
+
📱 Talk on mobile
|
|
151
|
+
</NavLink>
|
|
152
|
+
<button type="button" className="dock-logout" onClick={() => void handleLogOut()}>
|
|
153
|
+
Log out{mode === "guest" ? " (guest)" : ""}
|
|
154
|
+
</button>
|
|
155
|
+
</nav>
|
|
156
|
+
</SidebarPanel>
|
|
157
|
+
<SidebarPanel section="agent" className="agent-rail" data-tour="agent-rail">
|
|
108
158
|
<AgentChat
|
|
109
159
|
className="rail-chat"
|
|
110
160
|
viewsBridge={{ promotedViewKey: selectedKey, onViewRef, onViewsChange }}
|
|
111
161
|
onReady={setChat}
|
|
162
|
+
onActivity={binding.onActivity}
|
|
112
163
|
/>
|
|
113
|
-
</
|
|
114
|
-
</
|
|
115
|
-
<
|
|
164
|
+
</SidebarPanel>
|
|
165
|
+
</AgentModeSidebar>
|
|
166
|
+
<ActivePane className="canvas" data-tour="canvas">
|
|
116
167
|
{canvasShowsView && selected !== undefined && selected.mount !== null ? (
|
|
117
168
|
<div className="canvas-view">
|
|
118
169
|
<header className="canvas-view-bar">
|
|
119
170
|
<span>{selected.title}</span>
|
|
120
|
-
<button type="button" className="btn" onClick={
|
|
171
|
+
<button type="button" className="btn" onClick={closeView}>
|
|
121
172
|
Back to {appConfig.brand.name}
|
|
122
173
|
</button>
|
|
123
174
|
</header>
|
|
@@ -146,8 +197,8 @@ export function AppShell() {
|
|
|
146
197
|
) : (
|
|
147
198
|
<Outlet />
|
|
148
199
|
)}
|
|
149
|
-
</
|
|
150
|
-
</
|
|
200
|
+
</ActivePane>
|
|
201
|
+
</AgentModeShell>
|
|
151
202
|
</div>
|
|
152
203
|
);
|
|
153
204
|
}
|
|
@@ -2,6 +2,7 @@ import { StrictMode } from "react";
|
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
4
4
|
import "@guuey/chat/styles.css";
|
|
5
|
+
import "@guuey/agent-layout/styles.css";
|
|
5
6
|
import "./styles.css";
|
|
6
7
|
import "./styles-app.css";
|
|
7
8
|
import { appConfig } from "./config";
|