@guuey/create-agentic-app 0.10.0 → 0.11.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/templates/agentic-app/claude-agent-sdk/README.md +6 -4
- package/dist/templates/agentic-app/claude-agent-sdk/package.json +3 -3
- package/dist/templates/agentic-app/claude-agent-sdk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/agentic-app/claude-agent-sdk/web/package.json +2 -1
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AppShell.tsx +95 -21
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/main.tsx +2 -3
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/Dashboard.tsx +2 -5
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/TalkOnMobile.tsx +2 -18
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/routes.ts +3 -3
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/styles-app.css +22 -24
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/vite-env.d.ts +13 -0
- package/dist/templates/agentic-app/google-adk/package.json +2 -2
- package/dist/templates/agentic-app/google-adk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/agentic-app/google-adk/web/package.json +2 -1
- package/dist/templates/agentic-app/google-adk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/agentic-app/google-adk/web/src/components/AppShell.tsx +95 -21
- package/dist/templates/agentic-app/google-adk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/agentic-app/google-adk/web/src/main.tsx +2 -3
- package/dist/templates/agentic-app/google-adk/web/src/pages/Dashboard.tsx +2 -5
- package/dist/templates/agentic-app/google-adk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/agentic-app/google-adk/web/src/pages/TalkOnMobile.tsx +2 -18
- package/dist/templates/agentic-app/google-adk/web/src/routes.ts +3 -3
- package/dist/templates/agentic-app/google-adk/web/src/styles-app.css +22 -24
- package/dist/templates/agentic-app/google-adk/web/src/vite-env.d.ts +13 -0
- package/dist/templates/agentic-app/openai-agents-sdk/README.md +6 -4
- package/dist/templates/agentic-app/openai-agents-sdk/package.json +3 -3
- package/dist/templates/agentic-app/openai-agents-sdk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/agentic-app/openai-agents-sdk/web/package.json +2 -1
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AppShell.tsx +95 -21
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/main.tsx +2 -3
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/Dashboard.tsx +2 -5
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/TalkOnMobile.tsx +2 -18
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/routes.ts +3 -3
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/styles-app.css +22 -24
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/vite-env.d.ts +13 -0
- package/dist/templates/base/claude-agent-sdk/README.md +6 -4
- package/dist/templates/base/claude-agent-sdk/package.json +3 -3
- package/dist/templates/base/claude-agent-sdk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/base/claude-agent-sdk/web/package.json +3 -1
- package/dist/templates/base/claude-agent-sdk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/base/claude-agent-sdk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/base/claude-agent-sdk/web/src/main.tsx +14 -0
- package/dist/templates/base/claude-agent-sdk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/base/claude-agent-sdk/web/src/vite-env.d.ts +13 -0
- package/dist/templates/base/google-adk/package.json +2 -2
- package/dist/templates/base/google-adk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/base/google-adk/web/package.json +3 -1
- package/dist/templates/base/google-adk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/base/google-adk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/base/google-adk/web/src/main.tsx +14 -0
- package/dist/templates/base/google-adk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/base/google-adk/web/src/vite-env.d.ts +13 -0
- package/dist/templates/base/openai-agents-sdk/README.md +6 -4
- package/dist/templates/base/openai-agents-sdk/package.json +3 -3
- package/dist/templates/base/openai-agents-sdk/scripts/bootstrap.mjs +22 -0
- package/dist/templates/base/openai-agents-sdk/web/package.json +3 -1
- package/dist/templates/base/openai-agents-sdk/web/src/components/AgentChat.tsx +41 -3
- package/dist/templates/base/openai-agents-sdk/web/src/components/QrLink.tsx +25 -0
- package/dist/templates/base/openai-agents-sdk/web/src/main.tsx +14 -0
- package/dist/templates/base/openai-agents-sdk/web/src/pages/Landing.tsx +17 -2
- package/dist/templates/base/openai-agents-sdk/web/src/vite-env.d.ts +13 -0
- package/package.json +3 -3
- package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/AgentCanvas.tsx +0 -14
- package/dist/templates/agentic-app/google-adk/web/src/pages/AgentCanvas.tsx +0 -14
- package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/AgentCanvas.tsx +0 -14
|
@@ -15,9 +15,11 @@ locally with one command, and deployable to guuey with one more.
|
|
|
15
15
|
├── prompts/system.md # system prompt, referenced from guuey.json#agent.systemPrompt.file
|
|
16
16
|
├── mcps/todo/ # the "copy-me" custom MCP server — @modelcontextprotocol/sdk,
|
|
17
17
|
│ # Streamable HTTP, in-memory todo list, port :6782
|
|
18
|
-
├── ggui/ # ggui.json + blueprints/
|
|
19
|
-
│ # `ggui serve` runs against this locally;
|
|
20
|
-
│ #
|
|
18
|
+
├── ggui/ # ggui.json + blueprints/ — generative-UI config.
|
|
19
|
+
│ # `ggui serve` runs against this locally; `guuey deploy` pushes
|
|
20
|
+
│ # generation config + blueprints (the deployed RENDER THEME is
|
|
21
|
+
│ # platform data, not this dir — ggui.json#theme is local-preview
|
|
22
|
+
│ # only, kept in step with guuey.app.json by `pnpm bootstrap`).
|
|
21
23
|
├── web/ # the product frontend (Vite + React on @guuey/chat):
|
|
22
24
|
│ # landing (widget) · login (guest + BYO-OIDC seam) · home
|
|
23
25
|
│ # (status + distribution guide) · the chat surface
|
|
@@ -127,7 +129,7 @@ anything user-visible changes:
|
|
|
127
129
|
as its own hosted MCP server (build → deploy → registry), then writes the
|
|
128
130
|
resulting server id back into `guuey.json`. This scaffold has none — the
|
|
129
131
|
todo MCP is `colocated`, so this leg is a no-op here.
|
|
130
|
-
2. **ggui asset leg** — pushes `ggui/` (
|
|
132
|
+
2. **ggui asset leg** — pushes `ggui/` (generation config, blueprints) to your
|
|
131
133
|
app's guuey-managed ggui instance.
|
|
132
134
|
3. **Agent leg** — builds `src/worker.ts` into `guuey.worker.js`, packs the
|
|
133
135
|
project root (including `mcps/`), and deploys it as a gVisor-isolated,
|
|
@@ -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.11.0",
|
|
16
|
+
"@guuey/worker": "0.11.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@ggui-ai/cli": "0.6.3",
|
|
20
|
-
"@guuey/cli": "0.
|
|
20
|
+
"@guuey/cli": "0.11.0",
|
|
21
21
|
"tsup": "^8.5.0",
|
|
22
22
|
"tsx": "^4.19.0",
|
|
23
23
|
"typescript": "^5.8.0"
|
|
@@ -121,6 +121,27 @@ async function guuey(args) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// ── ggui.json theme sync (guuey#302 scaffold hygiene) ───────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Keep `ggui/ggui.json`'s theme MODE in step with guuey.app.json so the
|
|
128
|
+
* local `ggui serve` preview matches the site (the scaffold used to ship
|
|
129
|
+
* the two contradicting each other: light site, dark ggui preview). Only
|
|
130
|
+
* `mode` syncs — `preset` is ggui-side vocabulary and stays the author's
|
|
131
|
+
* choice. NOTE: the deployed render's theme is platform data (`guuey
|
|
132
|
+
* deploy` deliberately ignores this block; guuey#304 owns the production
|
|
133
|
+
* side) — this sync is about local-preview honesty.
|
|
134
|
+
*/
|
|
135
|
+
function syncGguiTheme(config) {
|
|
136
|
+
const gguiJsonPath = join(projectRoot, "ggui", "ggui.json");
|
|
137
|
+
if (!existsSync(gguiJsonPath)) return;
|
|
138
|
+
const ggui = JSON.parse(readFileSync(gguiJsonPath, "utf8"));
|
|
139
|
+
if (ggui.theme?.mode === config.theme.mode) return;
|
|
140
|
+
ggui.theme = { ...(ggui.theme ?? {}), mode: config.theme.mode };
|
|
141
|
+
writeFileSync(gguiJsonPath, `${JSON.stringify(ggui, null, 2)}\n`, "utf8");
|
|
142
|
+
console.log(`ggui/ggui.json theme mode → ${config.theme.mode} (local preview matches the site).`);
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
// ── AGENTS.md managed block ─────────────────────────────────────────────────
|
|
125
146
|
|
|
126
147
|
function managedBlock(config) {
|
|
@@ -367,6 +388,7 @@ async function main() {
|
|
|
367
388
|
// extraction (`--example`), the demo chrome turns off here.
|
|
368
389
|
config.demoMode = false;
|
|
369
390
|
writeConfig(config);
|
|
391
|
+
syncGguiTheme(config);
|
|
370
392
|
regenerateAgentsMd(config);
|
|
371
393
|
|
|
372
394
|
console.log(`\nConfigured "${name}" — guuey.app.json written, AGENTS.md updated.`);
|
|
@@ -10,11 +10,39 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { useEffect, useState, type CSSProperties } from "react";
|
|
12
12
|
import { GuueyChat } from "@guuey/chat/react";
|
|
13
|
+
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
13
14
|
import { agentEndpointUrl, appConfig, historyBaseUrl } from "../config";
|
|
14
15
|
import { currentIdentityMode, ensureGuestSecret } from "../lib/identity";
|
|
15
16
|
import { getBearerToken, currentUser, oidcConfigured } from "../lib/oidc";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The chat-rail bridge (agentic-app shell): with `viewsBridge` set, the
|
|
20
|
+
* chat runs the kit's CHIPS presentation — generative views collapse to
|
|
21
|
+
* compact chips in the rail, the full renders belong to the host's main
|
|
22
|
+
* canvas (fed by `onViewsChange`), and chip clicks re-select
|
|
23
|
+
* (`onViewRef` → `promotedViewKey`, the browser-history mechanic).
|
|
24
|
+
*/
|
|
25
|
+
export interface ViewsBridge {
|
|
26
|
+
promotedViewKey?: string | undefined;
|
|
27
|
+
onViewRef: (item: ViewRefItem) => void;
|
|
28
|
+
onViewsChange: (views: PlanViewSummary[]) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Module scope on purpose: props want STABLE identities. An inline
|
|
32
|
+
// `policy={{ … }}` literal (or per-render arrow getters) re-mints every
|
|
33
|
+
// render; the kit stabilizes structurally since 0.11.0, but the template
|
|
34
|
+
// models the correct shape — one identity for the app's whole life.
|
|
35
|
+
const RAIL_POLICY = { view: { timeoutMs: 8000, presentation: "chips" as const } };
|
|
36
|
+
|
|
37
|
+
export function AgentChat({
|
|
38
|
+
className,
|
|
39
|
+
style,
|
|
40
|
+
viewsBridge,
|
|
41
|
+
}: {
|
|
42
|
+
className?: string;
|
|
43
|
+
style?: CSSProperties;
|
|
44
|
+
viewsBridge?: ViewsBridge;
|
|
45
|
+
}) {
|
|
18
46
|
// The user's CHOSEN mode wins: an explicit "Continue as guest" must never
|
|
19
47
|
// be shadowed by a cached OIDC session. Only when no choice is recorded
|
|
20
48
|
// does a live session imply oidc.
|
|
@@ -43,11 +71,21 @@ export function AgentChat({ className, style }: { className?: string; style?: CS
|
|
|
43
71
|
mode: appConfig.theme.mode,
|
|
44
72
|
className,
|
|
45
73
|
style,
|
|
74
|
+
...(viewsBridge !== undefined
|
|
75
|
+
? {
|
|
76
|
+
policy: RAIL_POLICY,
|
|
77
|
+
promotedViewKey: viewsBridge.promotedViewKey,
|
|
78
|
+
onViewRef: viewsBridge.onViewRef,
|
|
79
|
+
onViewsChange: viewsBridge.onViewsChange,
|
|
80
|
+
}
|
|
81
|
+
: {}),
|
|
46
82
|
};
|
|
47
83
|
|
|
84
|
+
// The lib functions ARE the stable getters — no wrapping arrows (a fresh
|
|
85
|
+
// closure per render is the identity churn RAIL_POLICY exists to avoid).
|
|
48
86
|
return identity === "oidc" ? (
|
|
49
|
-
<GuueyChat {...shared} getAccessToken={
|
|
87
|
+
<GuueyChat {...shared} getAccessToken={getBearerToken} />
|
|
50
88
|
) : (
|
|
51
|
-
<GuueyChat {...shared} getGuestSecret={
|
|
89
|
+
<GuueyChat {...shared} getGuestSecret={ensureGuestSecret} />
|
|
52
90
|
);
|
|
53
91
|
}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The CHAT-RAIL shell (guuey#303, founder-confirmed):
|
|
3
3
|
*
|
|
4
4
|
* ┌──────────┬────────────────────────┐
|
|
5
5
|
* │ ☰ Menu 1 │ │
|
|
6
|
-
* │ ☰ Menu 2 │
|
|
7
|
-
* │ ☰ Menu 3 │
|
|
8
|
-
* ├──────────┤
|
|
9
|
-
* │
|
|
10
|
-
* │
|
|
11
|
-
* │
|
|
6
|
+
* │ ☰ Menu 2 │ MAIN CANVAS │
|
|
7
|
+
* │ ☰ Menu 3 │ your pages — or the │
|
|
8
|
+
* ├──────────┤ full generated UI │
|
|
9
|
+
* │ AGENT │ when the agent draws │
|
|
10
|
+
* │ RAIL │ one │
|
|
11
|
+
* │ (chat) │ │
|
|
12
12
|
* └──────────┴────────────────────────┘
|
|
13
13
|
*
|
|
14
|
-
* Upper sidebar = your product's menus.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Upper sidebar = your product's menus. LOWER sidebar = the embed-SDK
|
|
15
|
+
* agent rail — the visitor TYPES THERE; generative views collapse to
|
|
16
|
+
* compact CHIPS in the rail (the kit's chips presentation) and the full
|
|
17
|
+
* render takes the MAIN canvas. Chips are the history: clicking one
|
|
18
|
+
* re-selects its render onto the canvas ("just like a web browser's
|
|
19
|
+
* history"); a new render navigates forward automatically; picking a
|
|
20
|
+
* menu swaps the canvas back to your pages.
|
|
18
21
|
*/
|
|
22
|
+
import { useCallback, useRef, useState } from "react";
|
|
19
23
|
import { NavLink, Outlet, useNavigate } from "react-router-dom";
|
|
24
|
+
import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
|
|
25
|
+
import { GuueyView } from "@guuey/mcp-apps-host/react";
|
|
20
26
|
import { appConfig } from "../config";
|
|
27
|
+
import { AgentChat } from "../components/AgentChat";
|
|
21
28
|
import { currentIdentityMode, logOut } from "../lib/identity";
|
|
22
29
|
import { oidcConfigured, signOutOidc } from "../lib/oidc";
|
|
23
30
|
|
|
@@ -25,6 +32,41 @@ export function AppShell() {
|
|
|
25
32
|
const navigate = useNavigate();
|
|
26
33
|
const mode = currentIdentityMode();
|
|
27
34
|
|
|
35
|
+
// The rail↔canvas bridge: the kit's view roster (mount material lives
|
|
36
|
+
// here, the rail shows only chips), the selected key, and whether the
|
|
37
|
+
// canvas currently shows a view or the routed pages.
|
|
38
|
+
const [views, setViews] = useState<PlanViewSummary[]>([]);
|
|
39
|
+
const [selectedKey, setSelectedKey] = useState<string | undefined>(undefined);
|
|
40
|
+
const [canvasShowsView, setCanvasShowsView] = useState(false);
|
|
41
|
+
const newestKeyRef = useRef<string | undefined>(undefined);
|
|
42
|
+
|
|
43
|
+
const onViewsChange = useCallback((next: PlanViewSummary[]) => {
|
|
44
|
+
setViews(next);
|
|
45
|
+
// Browser-history forward-navigation: a NEW live render takes the
|
|
46
|
+
// canvas. (Reversed find = newest mountable; live entries sit after
|
|
47
|
+
// history in the roster's transcript order.)
|
|
48
|
+
const newest = [...next].reverse().find((v) => v.mount !== null && v.phase !== "expired");
|
|
49
|
+
if (newest !== undefined && newest.key !== newestKeyRef.current) {
|
|
50
|
+
newestKeyRef.current = newest.key;
|
|
51
|
+
setSelectedKey(newest.key);
|
|
52
|
+
setCanvasShowsView(true);
|
|
53
|
+
// The demo-tour hook (guuey#303): step machines outside the app can
|
|
54
|
+
// key on "the agent just drew UI".
|
|
55
|
+
window.dispatchEvent(
|
|
56
|
+
new CustomEvent("demo:render-complete", {
|
|
57
|
+
detail: { key: newest.key, title: newest.title },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const onViewRef = useCallback((item: ViewRefItem) => {
|
|
64
|
+
setSelectedKey(item.key);
|
|
65
|
+
setCanvasShowsView(true);
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const selected = views.find((v) => v.key === selectedKey && v.mount !== null);
|
|
69
|
+
|
|
28
70
|
async function handleLogOut() {
|
|
29
71
|
if (mode === "oidc" && oidcConfigured()) await signOutOidc();
|
|
30
72
|
logOut();
|
|
@@ -40,24 +82,56 @@ export function AppShell() {
|
|
|
40
82
|
<span>{appConfig.brand.name}</span>
|
|
41
83
|
</div>
|
|
42
84
|
<nav className="sidebar-menus">
|
|
43
|
-
|
|
85
|
+
{/* Picking a menu swaps the canvas back to your pages. */}
|
|
86
|
+
<NavLink to="/app" end onClick={() => setCanvasShowsView(false)}>
|
|
44
87
|
Dashboard
|
|
45
88
|
</NavLink>
|
|
46
|
-
<NavLink to="/app/reports">
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
<NavLink to="/app/reports" onClick={() => setCanvasShowsView(false)}>
|
|
90
|
+
Reports
|
|
91
|
+
</NavLink>
|
|
92
|
+
<NavLink to="/app/setup" onClick={() => setCanvasShowsView(false)}>
|
|
93
|
+
Setup
|
|
94
|
+
</NavLink>
|
|
95
|
+
<NavLink to="/app/mobile" onClick={() => setCanvasShowsView(false)}>
|
|
96
|
+
📱 Talk on mobile
|
|
52
97
|
</NavLink>
|
|
53
|
-
<NavLink to="/app/mobile">📱 Talk on mobile</NavLink>
|
|
54
98
|
<button type="button" className="dock-logout" onClick={() => void handleLogOut()}>
|
|
55
99
|
Log out{mode === "guest" ? " (guest)" : ""}
|
|
56
100
|
</button>
|
|
57
101
|
</nav>
|
|
102
|
+
<div className="agent-rail" data-tour="agent-rail">
|
|
103
|
+
<AgentChat
|
|
104
|
+
className="rail-chat"
|
|
105
|
+
viewsBridge={{ promotedViewKey: selectedKey, onViewRef, onViewsChange }}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
58
108
|
</aside>
|
|
59
|
-
<main className="canvas">
|
|
60
|
-
|
|
109
|
+
<main className="canvas" data-tour="canvas">
|
|
110
|
+
{canvasShowsView && selected !== undefined && selected.mount !== null ? (
|
|
111
|
+
<div className="canvas-view">
|
|
112
|
+
<header className="canvas-view-bar">
|
|
113
|
+
<span>{selected.title}</span>
|
|
114
|
+
<button type="button" className="btn" onClick={() => setCanvasShowsView(false)}>
|
|
115
|
+
Back to {appConfig.brand.name}
|
|
116
|
+
</button>
|
|
117
|
+
</header>
|
|
118
|
+
{selected.mount.channel !== "locator" ? (
|
|
119
|
+
<GuueyView
|
|
120
|
+
key={selected.key}
|
|
121
|
+
mount={selected.mount}
|
|
122
|
+
title={selected.title}
|
|
123
|
+
className="canvas-view-mount"
|
|
124
|
+
/>
|
|
125
|
+
) : (
|
|
126
|
+
// A locator still resolving — the kit reads it and the next
|
|
127
|
+
// roster emission carries the material (a FAILED read
|
|
128
|
+
// surfaces as the chip's expired state instead).
|
|
129
|
+
<p className="calm canvas-view-loading">Loading card…</p>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
) : (
|
|
133
|
+
<Outlet />
|
|
134
|
+
)}
|
|
61
135
|
</main>
|
|
62
136
|
</div>
|
|
63
137
|
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A QR code for a URL, with the plain link beneath — mobile hand-off. */
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import QRCode from "qrcode";
|
|
4
|
+
|
|
5
|
+
export function QrLink({ url, size = 200 }: { url: string; size?: number }) {
|
|
6
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
7
|
+
const [error, setError] = useState<string | null>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!canvasRef.current) return;
|
|
11
|
+
QRCode.toCanvas(canvasRef.current, url, { width: size, margin: 1 }).catch((err: unknown) => {
|
|
12
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
13
|
+
});
|
|
14
|
+
}, [url, size]);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<span className="qr-link">
|
|
18
|
+
<canvas ref={canvasRef} className="qr" />
|
|
19
|
+
<a href={url} target="_blank" rel="noreferrer">
|
|
20
|
+
{url}
|
|
21
|
+
</a>
|
|
22
|
+
{error ? <span className="error">QR render failed: {error}</span> : null}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -13,7 +13,6 @@ import { Login } from "./pages/Login";
|
|
|
13
13
|
import { Home } from "./pages/Home";
|
|
14
14
|
import { Dashboard } from "./pages/Dashboard";
|
|
15
15
|
import { Reports } from "./pages/Reports";
|
|
16
|
-
import { AgentCanvas } from "./pages/AgentCanvas";
|
|
17
16
|
import { TalkOnMobile } from "./pages/TalkOnMobile";
|
|
18
17
|
|
|
19
18
|
document.documentElement.dataset.mode = appConfig.theme.mode;
|
|
@@ -30,14 +29,14 @@ const router = createBrowserRouter([
|
|
|
30
29
|
],
|
|
31
30
|
},
|
|
32
31
|
{
|
|
33
|
-
// The product:
|
|
32
|
+
// The product: chat-rail shell — the agent rail in the lower
|
|
33
|
+
// sidebar, generated UI on the main canvas (guuey#303).
|
|
34
34
|
path: "/app",
|
|
35
35
|
element: <AppShell />,
|
|
36
36
|
children: [
|
|
37
37
|
{ index: true, element: <Dashboard /> },
|
|
38
38
|
{ path: "reports", element: <Reports /> },
|
|
39
39
|
{ path: "setup", element: <Home /> },
|
|
40
|
-
{ path: "agent", element: <AgentCanvas /> },
|
|
41
40
|
{ path: "mobile", element: <TalkOnMobile /> },
|
|
42
41
|
],
|
|
43
42
|
},
|
|
@@ -12,15 +12,12 @@ export function Dashboard() {
|
|
|
12
12
|
<h1>Dashboard</h1>
|
|
13
13
|
<p className="calm">
|
|
14
14
|
This is your product's canvas — replace this page with the real thing. The agent lives in
|
|
15
|
-
the
|
|
16
|
-
|
|
15
|
+
the RAIL at the bottom of the sidebar: type there, and any UI the agent draws takes this
|
|
16
|
+
whole canvas (the rail's chips are the history — click one to bring its render back).
|
|
17
17
|
</p>
|
|
18
18
|
<section className="card">
|
|
19
19
|
<h2>Getting oriented</h2>
|
|
20
20
|
<ul>
|
|
21
|
-
<li>
|
|
22
|
-
<Link to="/app/agent">● Agent</Link> — the fullscreen chat with generative UI
|
|
23
|
-
</li>
|
|
24
21
|
<li>
|
|
25
22
|
<Link to="/app/mobile">📱 Talk on mobile</Link> — the same agent, on a phone via the
|
|
26
23
|
guuey portal
|
|
@@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
|
|
|
8
8
|
import { appConfig, isLinked } from "../config";
|
|
9
9
|
import { CHAT_PATH, HOME_PATH } from "../routes";
|
|
10
10
|
import { mountWidget, type WidgetOutcome } from "../lib/widget";
|
|
11
|
+
import { QrLink } from "../components/QrLink";
|
|
11
12
|
|
|
12
13
|
export function Landing() {
|
|
13
14
|
const [widget, setWidget] = useState<WidgetOutcome | "unlinked" | "loading">(
|
|
@@ -19,19 +20,33 @@ export function Landing() {
|
|
|
19
20
|
mountWidget((outcome) => setWidget(outcome));
|
|
20
21
|
}, []);
|
|
21
22
|
|
|
23
|
+
// The demo posture (spec §2.3): a REAL product site whose hero's one job
|
|
24
|
+
// is getting the visitor INTO the demo — big CTA to the console page +
|
|
25
|
+
// a QR to continue on mobile via the portal.
|
|
26
|
+
const portalTarget =
|
|
27
|
+
appConfig.link !== null
|
|
28
|
+
? `${appConfig.link.portalUrl}/agent/${appConfig.link.slug ?? appConfig.link.appId}`
|
|
29
|
+
: null;
|
|
30
|
+
|
|
22
31
|
return (
|
|
23
32
|
<main className="page landing">
|
|
24
|
-
<section className="hero">
|
|
33
|
+
<section className="hero" data-tour="hero">
|
|
25
34
|
<h1>{appConfig.copy.landing.headline}</h1>
|
|
26
35
|
<p>{appConfig.copy.landing.sub}</p>
|
|
27
36
|
<div className="hero-actions">
|
|
28
37
|
<Link to={CHAT_PATH} className="btn btn-accent">
|
|
29
|
-
Open the chat
|
|
38
|
+
{appConfig.demoMode ? "Enter the demo" : "Open the chat"}
|
|
30
39
|
</Link>
|
|
31
40
|
<Link to={HOME_PATH} className="btn">
|
|
32
41
|
How this app is wired
|
|
33
42
|
</Link>
|
|
34
43
|
</div>
|
|
44
|
+
{appConfig.demoMode && portalTarget !== null ? (
|
|
45
|
+
<div className="hero-qr">
|
|
46
|
+
<p className="hint">Or continue on your phone:</p>
|
|
47
|
+
<QrLink url={portalTarget} size={140} />
|
|
48
|
+
</div>
|
|
49
|
+
) : null}
|
|
35
50
|
{widget === "unlinked" ? (
|
|
36
51
|
<p className="hint">
|
|
37
52
|
The floating agent launcher appears here once the app is bound to a
|
|
@@ -5,23 +5,13 @@
|
|
|
5
5
|
* never the chat route directly. Plain URL today; upgrades to a native
|
|
6
6
|
* deep link when portal applinks land (same path, so the QR stays valid).
|
|
7
7
|
*/
|
|
8
|
-
import { useEffect, useRef, useState } from "react";
|
|
9
|
-
import QRCode from "qrcode";
|
|
10
8
|
import { appConfig } from "../config";
|
|
9
|
+
import { QrLink } from "../components/QrLink";
|
|
11
10
|
|
|
12
11
|
export function TalkOnMobile() {
|
|
13
|
-
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
14
|
-
const [error, setError] = useState<string | null>(null);
|
|
15
12
|
const link = appConfig.link;
|
|
16
13
|
const target = link ? `${link.portalUrl}/agent/${link.slug ?? link.appId}` : null;
|
|
17
14
|
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!target || !canvasRef.current) return;
|
|
20
|
-
QRCode.toCanvas(canvasRef.current, target, { width: 240, margin: 1 }).catch((err: unknown) => {
|
|
21
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
22
|
-
});
|
|
23
|
-
}, [target]);
|
|
24
|
-
|
|
25
15
|
return (
|
|
26
16
|
<div className="page mobile-page">
|
|
27
17
|
<h1>Talk on mobile</h1>
|
|
@@ -31,13 +21,7 @@ export function TalkOnMobile() {
|
|
|
31
21
|
Scan with a phone — the same agent, in the guuey portal. (The portal
|
|
32
22
|
has its own sign-in, so phone conversations are separate threads.)
|
|
33
23
|
</p>
|
|
34
|
-
<
|
|
35
|
-
<p>
|
|
36
|
-
<a href={target} target="_blank" rel="noreferrer">
|
|
37
|
-
{target}
|
|
38
|
-
</a>
|
|
39
|
-
</p>
|
|
40
|
-
{error ? <p className="error">QR render failed: {error}</p> : null}
|
|
24
|
+
<QrLink url={target} size={240} />
|
|
41
25
|
</>
|
|
42
26
|
) : (
|
|
43
27
|
<p className="calm">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route constants shared by the chrome and pages. In the agentic-app
|
|
3
|
-
* template the
|
|
4
|
-
*
|
|
3
|
+
* template the agent RAIL lives inside the app shell (guuey#303's
|
|
4
|
+
* chat-rail layout) — "open the chat" means entering the app.
|
|
5
5
|
*/
|
|
6
|
-
export const CHAT_PATH = "/app
|
|
6
|
+
export const CHAT_PATH = "/app";
|
|
7
7
|
export const HOME_PATH = "/app/setup";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.sidebar {
|
|
17
|
-
width:
|
|
17
|
+
width: 320px;
|
|
18
18
|
flex-shrink: 0;
|
|
19
19
|
display: flex;
|
|
20
20
|
flex-direction: column;
|
|
@@ -51,42 +51,40 @@
|
|
|
51
51
|
font-weight: 600;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.
|
|
54
|
+
.agent-rail {
|
|
55
55
|
display: flex;
|
|
56
56
|
flex-direction: column;
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
min-height: 0;
|
|
58
|
+
height: 46vh;
|
|
59
59
|
border-top: 1px solid var(--line);
|
|
60
|
+
background: var(--bg);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
color: var(--fg-soft);
|
|
66
|
-
text-decoration: none;
|
|
63
|
+
.rail-chat {
|
|
64
|
+
flex: 1;
|
|
65
|
+
min-height: 0;
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
.canvas-view {
|
|
69
|
+
flex: 1;
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
min-height: 0;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
.
|
|
75
|
+
.canvas-view-bar {
|
|
76
76
|
display: flex;
|
|
77
77
|
align-items: center;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
height: 8px;
|
|
84
|
-
border-radius: 50%;
|
|
85
|
-
background: var(--ok);
|
|
78
|
+
justify-content: space-between;
|
|
79
|
+
gap: 12px;
|
|
80
|
+
padding: 8px 16px;
|
|
81
|
+
border-bottom: 1px solid var(--line);
|
|
82
|
+
font-weight: 600;
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
.
|
|
89
|
-
|
|
85
|
+
.canvas-view-mount {
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-height: 0;
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
.dock-logout {
|
|
@@ -1 +1,14 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
/**
|
|
5
|
+
* Optional external-script hook (guuey#303): a URL loaded at boot — the
|
|
6
|
+
* demo tour's bundle rides this in the demo apps; unset in customer
|
|
7
|
+
* builds.
|
|
8
|
+
*/
|
|
9
|
+
readonly VITE_DEMO_TOUR_SRC?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ImportMeta {
|
|
13
|
+
readonly env: ImportMetaEnv;
|
|
14
|
+
}
|
|
@@ -121,6 +121,27 @@ async function guuey(args) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// ── ggui.json theme sync (guuey#302 scaffold hygiene) ───────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Keep `ggui/ggui.json`'s theme MODE in step with guuey.app.json so the
|
|
128
|
+
* local `ggui serve` preview matches the site (the scaffold used to ship
|
|
129
|
+
* the two contradicting each other: light site, dark ggui preview). Only
|
|
130
|
+
* `mode` syncs — `preset` is ggui-side vocabulary and stays the author's
|
|
131
|
+
* choice. NOTE: the deployed render's theme is platform data (`guuey
|
|
132
|
+
* deploy` deliberately ignores this block; guuey#304 owns the production
|
|
133
|
+
* side) — this sync is about local-preview honesty.
|
|
134
|
+
*/
|
|
135
|
+
function syncGguiTheme(config) {
|
|
136
|
+
const gguiJsonPath = join(projectRoot, "ggui", "ggui.json");
|
|
137
|
+
if (!existsSync(gguiJsonPath)) return;
|
|
138
|
+
const ggui = JSON.parse(readFileSync(gguiJsonPath, "utf8"));
|
|
139
|
+
if (ggui.theme?.mode === config.theme.mode) return;
|
|
140
|
+
ggui.theme = { ...(ggui.theme ?? {}), mode: config.theme.mode };
|
|
141
|
+
writeFileSync(gguiJsonPath, `${JSON.stringify(ggui, null, 2)}\n`, "utf8");
|
|
142
|
+
console.log(`ggui/ggui.json theme mode → ${config.theme.mode} (local preview matches the site).`);
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
// ── AGENTS.md managed block ─────────────────────────────────────────────────
|
|
125
146
|
|
|
126
147
|
function managedBlock(config) {
|
|
@@ -367,6 +388,7 @@ async function main() {
|
|
|
367
388
|
// extraction (`--example`), the demo chrome turns off here.
|
|
368
389
|
config.demoMode = false;
|
|
369
390
|
writeConfig(config);
|
|
391
|
+
syncGguiTheme(config);
|
|
370
392
|
regenerateAgentsMd(config);
|
|
371
393
|
|
|
372
394
|
console.log(`\nConfigured "${name}" — guuey.app.json written, AGENTS.md updated.`);
|