create-lykomi-app 0.9.22 → 0.10.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 CHANGED
@@ -10,6 +10,7 @@ LYKOMI UI is maintained at [lykomi/ui](https://github.com/lykomi/ui).
10
10
  npm create lykomi-app my-agent-app
11
11
  cd my-agent-app
12
12
  cp agent/.env.example agent/.env
13
+ # Edit agent/.env with your project endpoint and model deployment.
13
14
  az login
14
15
  pnpm dev
15
16
  ```
@@ -20,7 +21,7 @@ Set these in `agent/.env`:
20
21
 
21
22
  ```bash
22
23
  FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
23
- FOUNDRY_MODEL=gpt-5.4
24
+ FOUNDRY_MODEL=<model-deployment-name>
24
25
  ```
25
26
 
26
27
  The generated app starts:
@@ -29,11 +30,13 @@ The generated app starts:
29
30
  - Agent service: `http://localhost:8020`
30
31
  - AG-UI endpoint: `/api/agent`
31
32
 
32
- The first screen is the chat. The bundled agent can call `hello_world` and `get_weather`.
33
+ Home opens with a chat composer and clickable prompts. Guide demonstrates a page beside the persistent chat. The bundled agent can call `hello_world` and `get_weather`.
34
+
35
+ The lower interface menu links to the Guide in local demo mode. Connecting a real auth provider makes it the signed-in user menu.
33
36
 
34
37
  ## Theme The App
35
38
 
36
- Generated apps default to `LYKOMI UI` for both the shell title and browser title. Change branding, logos, colors, radius, and fonts through the `theme` layers in `src/main.tsx`.
39
+ Generated apps use LYKOMI's neutral and deep-petrol palette, light and dark V2 favicons, a locally bundled Manrope font, and `LYKOMI UI` for the shell and browser title. Edit `src/theme.ts` to change branding, colors, radius, and typography.
37
40
 
38
41
  ## Two Ways To Use It
39
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lykomi-app",
3
- "version": "0.9.22",
3
+ "version": "0.10.0",
4
4
  "license": "MIT",
5
5
  "description": "Scaffold a new LYKOMI app — AG-UI protocol + Microsoft Agent Framework + Azure Foundry, ready to run.",
6
6
  "keywords": [
@@ -79,11 +79,11 @@ export default defineConfig({
79
79
  )
80
80
  return "lykomi-workbench";
81
81
  if (pkg.startsWith("@lykomi/web-ui/")) return "vendor-lykomi-ui";
82
- if (pkg.startsWith("@lykomi/web-shell/")) return "vendor-foundry-shell";
82
+ if (pkg.startsWith("@lykomi/web-shell/")) return "vendor-lykomi-shell";
83
83
  if (pkg.startsWith("@lykomi/agent/")) return "vendor-lykomi-agent";
84
84
  // Voice is optional at runtime and heavy at build; its own chunk
85
85
  // keeps the main path light and the size gate honest.
86
- if (pkg.startsWith("@lykomi/web-voice/")) return "vendor-foundry-voice";
86
+ if (pkg.startsWith("@lykomi/web-voice/")) return "vendor-lykomi-voice";
87
87
  // Before the react rule: recharts drags react-smooth + the d3
88
88
  // sub-packages along, and "react-smooth" would land in
89
89
  // vendor-react and push it past the chunk-size gate.
@@ -5,7 +5,7 @@
5
5
  "extends": "default",
6
6
  "filesDir": "files",
7
7
  "extraDependencies": {
8
- "@lykomi/web-graph": "0.3.17"
8
+ "@lykomi/web-graph": "0.3.18"
9
9
  },
10
10
  "extraScripts": {
11
11
  "graph:up": "docker compose up -d",
@@ -4,7 +4,11 @@ This is a LYKOMI UI starter app: React chat UI, AG-UI transport, and a Microsoft
4
4
 
5
5
  ```text
6
6
  src/main.tsx LYKOMI UI shell bootstrap
7
- src/pages/chat-page.tsx Chat screen and starter prompts
7
+ src/pages/chat-page.tsx Chat route
8
+ src/pages/starter-prompts.tsx Home prompts
9
+ src/pages/guide-page.tsx Example content page
10
+ src/starter-module.ts Navigation, agent context, interface menu
11
+ src/theme.ts App palette and branding
8
12
  src/index.css Tailwind v4 plus @lykomi/web-ui/styles.css
9
13
  agent/app/main.py FastAPI app and AG-UI endpoint
10
14
  agent/app/agent.py FoundryChatClient + Agent configuration
@@ -28,7 +32,7 @@ Set these in `agent/.env`:
28
32
 
29
33
  ```bash
30
34
  FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
31
- FOUNDRY_MODEL=gpt-5.4
35
+ FOUNDRY_MODEL=your-model-deployment
32
36
  ```
33
37
 
34
38
  `FoundryChatClient` reads the Microsoft Agent Framework env vars directly. The Python agent passes `DefaultAzureCredential`; prefer `az login` locally and managed identity or service principal env vars in hosted environments.
@@ -50,11 +54,12 @@ FOUNDRY_MODEL=gpt-5.4
50
54
  # UI
51
55
 
52
56
  - Keep the starter chat-first. Do not add landing pages, marketing heroes, or domain-specific clutter.
53
- - Configure quick starts in `src/pages/chat-page.tsx`.
54
- - Configure app name, browser title, logos, colors, radius, and fonts through `theme` layers in `src/main.tsx`.
57
+ - Configure quick starts in `src/pages/starter-prompts.tsx`.
58
+ - Configure app name, browser title, logos, colors, radius, and fonts in `src/theme.ts`.
59
+ - Keep the Guide page and its `ChatModule` as an example of page context and persistent chat; replace them when building your app.
55
60
  - Keep the default generated app name and browser title as `LYKOMI UI` unless the app is intentionally rebranded.
56
61
  - Use LYKOMI UI components from `@lykomi/web-ui` and primitives from `@lykomi/web-primitives`.
57
- - Keep `@source "../node_modules/@iloveagents/.../dist"` entries in `src/index.css`; Tailwind v4 needs them to scan package component classes.
62
+ - Keep `@source "../node_modules/@lykomi/.../dist"` entries in `src/index.css`; Tailwind v4 needs them to scan package component classes.
58
63
  - Keep `@import "@lykomi/web-ui/styles.css";`; it provides the LYKOMI UI theme tokens.
59
64
 
60
65
  # Attachments
@@ -1,12 +1,14 @@
1
1
  # __PROJECT_NAME__
2
2
 
3
- LYKOMI UI starter: a chat-first React app with a Microsoft Agent Framework service exposed through AG-UI.
3
+ LYKOMI UI starter: a React app with a Home composer, a contextual Guide page,
4
+ and a Microsoft Agent Framework service exposed through AG-UI.
4
5
 
5
6
  ## Quick Start
6
7
 
7
8
  ```bash
8
9
  pnpm install
9
10
  cp agent/.env.example agent/.env
11
+ # Set your Foundry project endpoint and model deployment in agent/.env.
10
12
  az login
11
13
  pnpm dev
12
14
  ```
@@ -25,14 +27,14 @@ Edit `agent/.env`:
25
27
 
26
28
  ```bash
27
29
  FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
28
- FOUNDRY_MODEL=gpt-5.4
30
+ FOUNDRY_MODEL=<model-deployment-name>
29
31
  ```
30
32
 
31
33
  `FoundryChatClient` reads the Microsoft Agent Framework environment variables directly. The starter passes `DefaultAzureCredential`, so local development works with `az login`. In Azure, use managed identity or the standard service principal environment variables.
32
34
 
33
35
  ## Try It
34
36
 
35
- The first screen is the chat. Try:
37
+ Home opens with the chat composer and three clickable prompts. The Guide route shows how a page sits beside the same persistent conversation, with real workbench tabs and resizable panels. Try:
36
38
 
37
39
  - `Say hello world using the hello_world tool`
38
40
  - `Check the weather in Seattle`
@@ -40,6 +42,8 @@ The first screen is the chat. Try:
40
42
 
41
43
  The bundled tools live in `agent/app/tools.py`.
42
44
 
45
+ The starter registers Guide navigation, page context, and an “About this starter” item in the lower interface menu through `src/starter-module.ts`. Open **Working draft** and **Architecture**, edit the draft, then select **Panels**. Pin, reorder and resize windows; switch back to **Tabs** or visit Home and return. The draft stays in memory until you close it or reload. Closing an edited draft asks before discarding it. `src/pages/guide-windows.ts` owns the app data; `WindowWorkbench` and `createWindowStore` from `@lykomi/web-ui` provide the shared window behaviour. The active window context, including the draft text, accompanies an AG-UI turn. Connect your own storage service before treating the draft as saved. Local demo auth has no user identity, so that control is an interface menu; after you wire real auth, the same menu shows the signed-in user and sign-out action.
46
+
43
47
  ## Coding With AI Agents
44
48
 
45
49
  This app ships `AGENTS.md` (+ a `CLAUDE.md` shim) for coding agents. Claude
@@ -65,33 +69,16 @@ Template changes such as new files or folder structure are not applied automatic
65
69
 
66
70
  ## Theme The App
67
71
 
68
- App name, browser title, logos, colors, radius, and fonts are configured through the `theme` layers in `src/main.tsx`.
72
+ The starter uses LYKOMI's neutral and deep-petrol palette, light and dark V2 favicons, and a locally bundled, OFL-licensed Manrope font. Change the app name, browser title, colors, radius, and fonts in `src/theme.ts`; add logo assets under `public/` and reference them in the theme's `branding` entry.
69
73
 
70
74
  ```ts
71
- bootstrapShell({
72
- pages: customerPages,
73
- authProvider: localDemoAuth,
74
- agentFetch: fetch,
75
- theme: [
76
- {
77
- id: "app-branding",
78
- definition: {
79
- branding: {
80
- appName: "LYKOMI UI",
81
- appTitle: "LYKOMI UI",
82
- logoUrl: "/logo.svg",
83
- darkLogoUrl: "/logo-dark.svg",
84
- },
85
- light: {
86
- primary: "oklch(0.54 0.21 282)",
87
- },
88
- radius: {
89
- radius: "0.5rem",
90
- },
91
- },
92
- },
93
- ],
94
- });
75
+ export const starterTheme: ThemeLayer = {
76
+ id: "my-app",
77
+ definition: {
78
+ light: { primary: "#3F666B" },
79
+ branding: { appName: "My app", appTitle: "My app" },
80
+ },
81
+ };
95
82
  ```
96
83
 
97
84
  Keep `@import "@lykomi/web-ui/styles.css";` in `src/index.css`; it exposes the package theme tokens to Tailwind.
@@ -103,7 +90,7 @@ variable and a microphone appears in the composer:
103
90
 
104
91
  ```bash
105
92
  # .env
106
- VITE_VOICE_PROXY_URL=ws://localhost:8080/ws?model=gpt-realtime
93
+ VITE_VOICE_PROXY_URL=ws://localhost:8080/ws?model=your-voice-deployment
107
94
  ```
108
95
 
109
96
  Leave it unset and nothing is registered — no microphone, no cost, the app is
@@ -116,12 +103,9 @@ docker run -p 8080:8080 \
116
103
  -e FOUNDRY_RESOURCE_NAME=your-foundry-resource \
117
104
  -e FOUNDRY_API_KEY="…" \
118
105
  -e ALLOWED_ORIGINS="http://localhost:8010" \
119
- ghcr.io/iloveagents/foundry-voice-live-proxy:latest
106
+ ghcr.io/iloveagents/foundry-voice-live-proxy:0.7.0
120
107
  ```
121
108
 
122
- > The default model (`gpt-realtime`) is available in **East US 2** and
123
- > **Sweden Central**.
124
-
125
109
  The default mode is **relay**: Voice Live transcribes what you say and speaks
126
110
  the answer, but *your* agent still answers it. A spoken turn is an ordinary
127
111
  turn — same tools, same retrieval, same history — so nothing about your agent
@@ -176,14 +160,21 @@ Agent Framework connects the MCP server when the agent runs. The AG-UI endpoint
176
160
  agent/ FastAPI + Microsoft Agent Framework AG-UI endpoint
177
161
  agent/app/tools.py Add agent tools here
178
162
  src/main.tsx LYKOMI UI shell bootstrap
179
- src/pages/chat-page.tsx Chat screen and quick starts
163
+ src/theme.ts App colors, typography, and branding
164
+ src/pages/starter-prompts.tsx Home quick starts
165
+ src/pages/guide-page.tsx A page beside the persistent chat
166
+ src/starter-module.ts Guide navigation and agent context
167
+ src/pages/chat-page.tsx Chat route
180
168
  src/index.css Tailwind v4 + LYKOMI UI theme resource and package dist scanning
169
+ public/fonts/ Manrope and its OFL license
181
170
  ```
182
171
 
183
172
  ## Auth
184
173
 
185
174
  This starter uses local pass-through auth so the demo runs without an Entra app registration.
186
175
 
176
+ The AG-UI endpoint declares `single_user=True` for this local demo. Before serving multiple users, replace that setting with an authorization scope resolver and protect the API with your chosen auth scheme. A browser-only MSAL setup does not protect the FastAPI endpoint by itself.
177
+
187
178
  For production MSAL auth, remove `authProvider` and `agentFetch` from `src/main.tsx`, then set:
188
179
 
189
180
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Copy to agent/.env and fill these values for Microsoft Agent Framework.
2
2
  FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com/api/projects/your-project
3
- FOUNDRY_MODEL=gpt-5.4
3
+ FOUNDRY_MODEL=your-model-deployment
4
4
 
5
5
  # DefaultAzureCredential uses `az login` locally, service principal env vars in CI,
6
6
  # and managed identity in Azure. Uncomment tenant/client values only when needed.
@@ -49,6 +49,13 @@ async def health() -> dict[str, str]:
49
49
  return {"status": "ok"}
50
50
 
51
51
 
52
+ @app.get("/api/chat/config")
53
+ async def chat_config() -> dict[str, object]:
54
+ # The scaffold does not set a reasoning level or context-window limit.
55
+ # Leave those values unknown until the app's backend can report them.
56
+ return {}
57
+
58
+
52
59
  try:
53
60
  from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
54
61
  except ImportError:
@@ -59,4 +66,7 @@ add_agent_framework_fastapi_endpoint(
59
66
  get_agent(),
60
67
  path="/api/agent",
61
68
  state_schema=STATE_SCHEMA,
69
+ # This local starter deliberately has no user sign-in. An authenticated
70
+ # deployment must replace this with an authorization scope resolver.
71
+ single_user=True,
62
72
  )
@@ -26,11 +26,12 @@
26
26
  "@assistant-ui/react": "^0.15.1",
27
27
  "@azure/msal-browser": "^5.6.3",
28
28
  "@azure/msal-react": "^5.2.1",
29
- "@lykomi/agent": "0.49.2",
30
- "@lykomi/web-primitives": "0.49.2",
31
- "@lykomi/web-shell": "0.49.2",
32
- "@lykomi/web-ui": "0.49.2",
33
- "@lykomi/web-voice": "0.1.33",
29
+ "@lykomi/agent": "0.50.0",
30
+ "@lykomi/web-primitives": "0.50.0",
31
+ "@lykomi/web-shell": "0.50.0",
32
+ "@lykomi/web-ui": "0.50.0",
33
+ "@lykomi/web-voice": "0.1.34",
34
+ "@radix-ui/react-tabs": "^1.1.22",
34
35
  "@tanstack/react-query": "^5.99.0",
35
36
  "lucide-react": "^0.553.0",
36
37
  "react": "^19.2.5",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="960" viewBox="0 0 960 960" role="img" aria-label="LYKOMI abstract icon black"><title>LYKOMI abstract icon black</title><g transform="translate(165 1)"><path fill="#FFFFFF" d="M163 273 C86 322 41 387 52 465 C62 541 126 589 176 644 C232 706 233 796 154 900 C247 841 298 768 295 688 C294 611 257 558 203 509 C141 453 102 396 163 273 Z"/><path fill="#FFFFFF" d="M394 58 C357 104 292 137 246 188 C191 249 182 315 203 380 C221 446 254 482 291 525 C359 611 368 704 303 798 C383 727 422 646 405 558 C398 488 352 446 304 401 C250 352 235 307 263 251 C288 202 342 185 367 134 C382 109 390 82 394 58 Z"/><path fill="#FFFFFF" d="M392 176 C371 211 341 242 330 266 C309 319 342 339 389 351 C467 370 539 398 553 480 C581 406 537 360 483 321 C431 281 396 235 392 176 Z"/></g></svg>
@@ -1,4 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
- <rect width="32" height="32" rx="7" fill="#0f172a"/>
3
- <path d="M9 17.4 14.2 8h8.9l-5.2 9.4h4.9L15.4 25v-7.6H9Z" fill="#34d399"/>
4
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="960" viewBox="0 0 960 960" role="img" aria-label="LYKOMI abstract icon black"><title>LYKOMI abstract icon black</title><g transform="translate(165 1)"><path fill="#0B0C0E" d="M163 273 C86 322 41 387 52 465 C62 541 126 589 176 644 C232 706 233 796 154 900 C247 841 298 768 295 688 C294 611 257 558 203 509 C141 453 102 396 163 273 Z"/><path fill="#0B0C0E" d="M394 58 C357 104 292 137 246 188 C191 249 182 315 203 380 C221 446 254 482 291 525 C359 611 368 704 303 798 C383 727 422 646 405 558 C398 488 352 446 304 401 C250 352 235 307 263 251 C288 202 342 185 367 134 C382 109 390 82 394 58 Z"/><path fill="#0B0C0E" d="M392 176 C371 211 341 242 330 266 C309 319 342 339 389 351 C467 370 539 398 553 480 C581 406 537 360 483 321 C431 281 396 235 392 176 Z"/></g></svg>
@@ -0,0 +1,93 @@
1
+ Copyright 2018 The Manrope Project Authors (https://github.com/googlefonts/manrope)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -1,6 +1,15 @@
1
1
  @import "tailwindcss";
2
2
  @import "@lykomi/web-ui/styles.css";
3
3
 
4
+ /* Manrope is bundled locally under the SIL Open Font License. */
5
+ @font-face {
6
+ font-family: "Manrope";
7
+ src: url("/fonts/Manrope-Variable.ttf") format("truetype");
8
+ font-style: normal;
9
+ font-weight: 200 800;
10
+ font-display: swap;
11
+ }
12
+
4
13
  /* Tailwind v4 needs explicit @source paths to scan package output
5
14
  * for utility classes. Without these, the LYKOMI UI components render
6
15
  * without spacing, borders, or theme tokens. */
@@ -2,7 +2,11 @@ import "./index.css";
2
2
  import { bootstrapShell } from "@lykomi/web-shell";
3
3
  import type { AuthAdapter } from "@lykomi/web-shell";
4
4
  import { createVoiceModule } from "@lykomi/web-voice";
5
+ import { registerChatSlots } from "@lykomi/web-ui";
5
6
  import { customerPages } from "@/pages/index.tsx";
7
+ import { starterTheme } from "./theme.ts";
8
+ import { StarterPrompts } from "./pages/starter-prompts.tsx";
9
+ import { starterModule } from "./starter-module.ts";
6
10
 
7
11
  const localDemoAuth: AuthAdapter = {
8
12
  Provider: ({ children }) => <>{children}</>,
@@ -26,20 +30,12 @@ const voiceModules = voiceProxyUrl
26
30
  ? [createVoiceModule({ connection: { proxyUrl: voiceProxyUrl } })]
27
31
  : [];
28
32
 
33
+ registerChatSlots({ homeStarters: StarterPrompts });
34
+
29
35
  bootstrapShell({
30
- modules: [...voiceModules], // Add your own ChatModules here.
36
+ modules: [starterModule, ...voiceModules], // Add your own ChatModules here.
31
37
  pages: customerPages,
32
38
  authProvider: localDemoAuth,
33
39
  agentFetch: fetch,
34
- theme: [
35
- {
36
- id: "app-branding",
37
- definition: {
38
- branding: {
39
- appName: "LYKOMI UI",
40
- appTitle: "LYKOMI UI",
41
- },
42
- },
43
- },
44
- ],
40
+ theme: [starterTheme],
45
41
  });
@@ -1,11 +1,5 @@
1
1
  import { ChatContent } from "@lykomi/web-ui";
2
2
 
3
- const starterSuggestions = [
4
- "Say hello world using the hello_world tool",
5
- "Check the weather in Seattle",
6
- "What tools can you use?",
7
- ];
8
-
9
3
  export function ChatPage() {
10
- return <ChatContent starterSuggestions={starterSuggestions} />;
4
+ return <ChatContent />;
11
5
  }
@@ -0,0 +1,134 @@
1
+ import { MessageSquare, Plus } from "lucide-react";
2
+ import { Button, Textarea } from "@lykomi/web-primitives";
3
+ import { WindowWorkbench } from "@lykomi/web-ui/windows";
4
+ import {
5
+ useChatBubbleStore,
6
+ DropdownMenu,
7
+ DropdownMenuTrigger,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ } from "@lykomi/web-ui";
11
+ import { guideWindows, openGuideWindow } from "./guide-windows.ts";
12
+
13
+ export function GuidePage() {
14
+ return (
15
+ <WindowWorkbench
16
+ controller={guideWindows}
17
+ toolbar={
18
+ <DropdownMenu>
19
+ <DropdownMenuTrigger asChild>
20
+ <Button variant="ghost" size="sm" aria-label="Open window">
21
+ <Plus />
22
+ Open
23
+ </Button>
24
+ </DropdownMenuTrigger>
25
+ <DropdownMenuContent align="end">
26
+ <DropdownMenuItem onSelect={() => openGuideWindow("overview")}>Guide</DropdownMenuItem>
27
+ <DropdownMenuItem onSelect={() => openGuideWindow("architecture")}>
28
+ Architecture
29
+ </DropdownMenuItem>
30
+ <DropdownMenuItem onSelect={() => openGuideWindow("draft")}>
31
+ Working draft
32
+ </DropdownMenuItem>
33
+ </DropdownMenuContent>
34
+ </DropdownMenu>
35
+ }
36
+ empty={
37
+ <div className="p-8">
38
+ <Button onClick={() => openGuideWindow("overview")}>Open guide</Button>
39
+ </div>
40
+ }
41
+ getWindowContext={(tab) => ({
42
+ title: tab.title,
43
+ kind: "document",
44
+ windowId: tab.id,
45
+ content:
46
+ tab.text ??
47
+ (tab.kind === "architecture"
48
+ ? "Browser shell → AG-UI → FastAPI agent. Each page supplies its own context."
49
+ : "The starter guide explains agent configuration, page context, tabs and panels."),
50
+ unsavedChanges: Boolean(tab.dirty),
51
+ })}
52
+ renderWindow={(tab) => (
53
+ <main className="mx-auto max-w-3xl px-6 py-8 sm:px-10" aria-label={String(tab.title)}>
54
+ <p className="text-sm font-medium text-primary">LYKOMI UI starter</p>
55
+ {tab.kind === "overview" && (
56
+ <>
57
+ <h1 className="mt-2 text-3xl font-semibold tracking-tight">
58
+ Build an agent app around real work.
59
+ </h1>
60
+ <p className="mt-4 text-base leading-relaxed text-muted-foreground">
61
+ Your pages, a persistent conversation, and a shared workbench. Open a draft and the
62
+ architecture, then switch between tabs and panels. Your work stays in place.
63
+ </p>
64
+ <div className="mt-6 flex flex-wrap gap-2">
65
+ <Button onClick={() => openGuideWindow("draft")}>Open working draft</Button>
66
+ <Button variant="outline" onClick={() => openGuideWindow("architecture")}>
67
+ Open architecture
68
+ </Button>
69
+ </div>
70
+ <section className="mt-8 rounded-xl border border-border bg-card p-6">
71
+ <h2 className="text-lg font-semibold">Connect your agent</h2>
72
+ <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
73
+ Set <code>FOUNDRY_PROJECT_ENDPOINT</code> and <code>FOUNDRY_MODEL</code> in{" "}
74
+ <code>agent/.env</code>, sign in with <code>az login</code>, then run{" "}
75
+ <code>pnpm dev</code>. Replace the sample tools in <code>agent/app/tools.py</code>
76
+ .
77
+ </p>
78
+ </section>
79
+ </>
80
+ )}
81
+ {tab.kind === "architecture" && (
82
+ <>
83
+ <h1 className="mt-2 text-2xl font-semibold">Starter architecture</h1>
84
+ <p className="mt-4 leading-relaxed text-muted-foreground">
85
+ <code>bootstrapShell</code> composes your routes, theme, modules and persistent
86
+ chat. The browser sends AG-UI runs to <code>/api/agent</code>, including the active
87
+ page and window context.
88
+ </p>
89
+ <h2 className="mt-6 text-lg font-semibold">Make the workbench yours</h2>
90
+ <p className="mt-2 leading-relaxed text-muted-foreground">
91
+ <code>createWindowStore</code> owns open windows. <code>WindowWorkbench</code>{" "}
92
+ renders the same content as tabs or panels. Your app supplies the content and
93
+ decides how to save drafts.
94
+ </p>
95
+ </>
96
+ )}
97
+ {tab.kind === "draft" && (
98
+ <>
99
+ <h1 className="mt-2 text-2xl font-semibold">Working draft</h1>
100
+ <p className="mt-3 text-sm text-muted-foreground">
101
+ Edit this draft, switch layouts, reorder its tab, or visit Home. It stays here until
102
+ you close it. This example keeps your work in memory for this browser session.
103
+ </p>
104
+ <label htmlFor="starter-draft" className="mt-6 block text-sm font-medium">
105
+ Your idea
106
+ </label>
107
+ <Textarea
108
+ id="starter-draft"
109
+ className="mt-2 min-h-48"
110
+ value={tab.text ?? ""}
111
+ onChange={(event) =>
112
+ guideWindows.update(tab.id, { text: event.target.value, dirty: true })
113
+ }
114
+ />
115
+ <p className="mt-2 text-xs text-muted-foreground">
116
+ {tab.dirty
117
+ ? "Unsaved changes · this demo has no storage service"
118
+ : "Try editing, then switch to panels"}
119
+ </p>
120
+ </>
121
+ )}
122
+ <Button
123
+ variant="outline"
124
+ className="mt-6 gap-2"
125
+ onClick={() => useChatBubbleStore.getState().open()}
126
+ >
127
+ <MessageSquare className="size-4" aria-hidden />
128
+ Ask the agent about this page
129
+ </Button>
130
+ </main>
131
+ )}
132
+ />
133
+ );
134
+ }
@@ -0,0 +1,32 @@
1
+ import { createWindowStore, type WorkbenchWindow } from "@lykomi/web-ui/windows";
2
+
3
+ export interface GuideWindow extends WorkbenchWindow {
4
+ kind: "overview" | "architecture" | "draft";
5
+ text?: string;
6
+ }
7
+
8
+ // App-owned data; the UI framework owns selection, pinning, ordering and layouts.
9
+ // Keeping this store outside the route also retains drafts when visiting Home.
10
+ export const guideWindows = createWindowStore<GuideWindow>({
11
+ storageKey: "starter.windows.layout",
12
+ });
13
+
14
+ export function openGuideWindow(kind: GuideWindow["kind"]) {
15
+ const title = { overview: "Guide", architecture: "Architecture", draft: "Working draft" }[kind];
16
+ guideWindows.open(
17
+ {
18
+ id: kind,
19
+ kind,
20
+ title,
21
+ independent: true,
22
+ text: kind === "draft" ? "My agent app will help people…" : undefined,
23
+ close: () => {
24
+ const tab = guideWindows.get().tabs.find((entry) => entry.id === kind);
25
+ if (tab?.dirty && !window.confirm("Close this draft and discard your changes?")) return;
26
+ guideWindows.remove(kind);
27
+ },
28
+ },
29
+ { mode: "new-tab" },
30
+ );
31
+ }
32
+ openGuideWindow("overview");
@@ -1,5 +1,6 @@
1
1
  import type { ShellPage } from "@lykomi/web-shell";
2
2
  import { ChatPage } from "./chat-page.tsx";
3
+ import { GuidePage } from "./guide-page.tsx";
3
4
 
4
5
  /**
5
6
  * Customer pages list. Add your own routes here.
@@ -10,12 +11,12 @@ import { ChatPage } from "./chat-page.tsx";
10
11
  */
11
12
  export const customerPages: ShellPage[] = [
12
13
  { path: "/", element: <ChatPage /> },
14
+ { path: "guide", element: <GuidePage /> },
13
15
  // /chat/:aguiThreadId — clicking a Recents chat in the sidebar (or
14
16
  // reloading the URL after sending a message) navigates here. Without
15
17
  // this route react-router has no match for the location and the SPA
16
18
  // renders a blank page on the chats-as-entities resume flow.
17
- // ChatPage itself is URL-agnostic; lykomi-ui's
18
- // ChatConversationAwareRuntime reads the thread id from the URL via
19
- // `CHAT_PATH_PATTERN` in spaces-web-ui's `chat-conversation-config`.
19
+ // ChatPage itself is URL-agnostic. A host that persists conversations can
20
+ // read this route parameter and restore the matching thread.
20
21
  { path: "chat/:id", element: <ChatPage />, chatOnly: true },
21
22
  ];
@@ -0,0 +1,30 @@
1
+ import { ThreadPrimitive } from "@assistant-ui/react";
2
+ import { Button } from "@lykomi/web-primitives";
3
+ import { useChatBubbleStore } from "@lykomi/web-ui";
4
+
5
+ const prompts = [
6
+ "Say hello world using the hello_world tool",
7
+ "Check the weather in Seattle",
8
+ "What tools can you use?",
9
+ ];
10
+
11
+ /** One click demonstrates a real AG-UI turn with the bundled tools. */
12
+ export function StarterPrompts() {
13
+ return (
14
+ <div className="flex flex-wrap justify-center gap-2" aria-label="Try a prompt">
15
+ {prompts.map((prompt) => (
16
+ <ThreadPrimitive.Suggestion
17
+ key={prompt}
18
+ prompt={prompt}
19
+ send
20
+ asChild
21
+ onClick={() => useChatBubbleStore.getState().open()}
22
+ >
23
+ <Button variant="outline" size="sm" className="h-auto min-h-9 text-wrap py-2 text-sm">
24
+ {prompt}
25
+ </Button>
26
+ </ThreadPrimitive.Suggestion>
27
+ ))}
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,51 @@
1
+ import { guideWindows } from "./pages/guide-windows.ts";
2
+ import { useEffect } from "react";
3
+ import { BookOpen } from "lucide-react";
4
+ import { useNavigate } from "react-router";
5
+ import { defineChatModule } from "@lykomi/web-shell";
6
+ import { useNavStore } from "@lykomi/web-ui";
7
+
8
+ function useStarterNavigation() {
9
+ const navigate = useNavigate();
10
+ useEffect(() => {
11
+ const protect = (event: BeforeUnloadEvent) => {
12
+ if (guideWindows.get().tabs.some((tab) => tab.dirty)) {
13
+ event.preventDefault();
14
+ event.returnValue = "";
15
+ }
16
+ };
17
+ window.addEventListener("beforeunload", protect);
18
+ useNavStore.getState().addGroup({
19
+ label: "Starter",
20
+ hideLabel: true,
21
+ items: [
22
+ {
23
+ to: "/guide",
24
+ label: "Guide",
25
+ icon: BookOpen,
26
+ description: "Starter guide for the agent, tools, and UI modules",
27
+ contextInstructions:
28
+ "The user is reading the starter guide. Explain how to replace the sample tools, " +
29
+ "add a page, or connect a service when asked. Do not claim a service is configured " +
30
+ "unless the user has supplied its settings.",
31
+ },
32
+ ],
33
+ });
34
+ useNavStore.getState().setUserMenuAction({
35
+ id: "starter-guide",
36
+ label: "About this starter",
37
+ onSelect: () => navigate("/guide"),
38
+ });
39
+ return () => {
40
+ window.removeEventListener("beforeunload", protect);
41
+ useNavStore.getState().removeGroup("Starter");
42
+ useNavStore.getState().removeUserMenuAction("starter-guide");
43
+ };
44
+ }, [navigate]);
45
+ }
46
+
47
+ /** A small module demonstrates how an app owns its routes and navigation. */
48
+ export const starterModule = defineChatModule({
49
+ name: "starter-guide",
50
+ useInit: useStarterNavigation,
51
+ });
@@ -0,0 +1,82 @@
1
+ import type { ThemeLayer } from "@lykomi/web-ui";
2
+
3
+ /** The LYKOMI starter palette. Replace or extend this layer for your app. */
4
+ export const starterTheme: ThemeLayer = {
5
+ id: "lykomi-starter",
6
+ name: "LYKOMI UI",
7
+ definition: {
8
+ light: {
9
+ background: "#FAFAF9",
10
+ foreground: "#0B0C0E",
11
+ card: "#FFFFFF",
12
+ cardForeground: "#0B0C0E",
13
+ primary: "#3F666B",
14
+ primaryForeground: "#FFFFFF",
15
+ secondary: "#E8E8E5",
16
+ secondaryForeground: "#0B0C0E",
17
+ muted: "#EDEDEB",
18
+ mutedForeground: "#626369",
19
+ accent: "#E5EEEF",
20
+ accentForeground: "#3F666B",
21
+ border: "#DCDDD9",
22
+ input: "#DCDDD9",
23
+ ring: "#3F666B",
24
+ },
25
+ dark: {
26
+ background: "#0D0D10",
27
+ foreground: "#EBEBF4",
28
+ card: "#151519",
29
+ cardForeground: "#EBEBF4",
30
+ primary: "#F4F4F4",
31
+ primaryForeground: "#101010",
32
+ secondary: "#1E1E22",
33
+ secondaryForeground: "#EBEBF4",
34
+ muted: "#1E1E22",
35
+ mutedForeground: "#B8B8C2",
36
+ accent: "#1E1D2C",
37
+ accentForeground: "#EBEBF4",
38
+ border: "#2A2A2E",
39
+ input: "#2A2A2E",
40
+ ring: "#8C8C8C",
41
+ },
42
+ sidebar: {
43
+ light: {
44
+ sidebar: "#F2F2F0",
45
+ sidebarForeground: "#0B0C0E",
46
+ sidebarPrimary: "#3F666B",
47
+ sidebarPrimaryForeground: "#FFFFFF",
48
+ sidebarAccent: "#E4E4E1",
49
+ sidebarAccentForeground: "#0B0C0E",
50
+ sidebarSelected: "#DCE9E7",
51
+ sidebarSelectedForeground: "#3F666B",
52
+ sidebarIcon: "#626369",
53
+ sidebarBorder: "#DCDDD9",
54
+ sidebarRing: "#3F666B",
55
+ },
56
+ dark: {
57
+ sidebar: "#111115",
58
+ sidebarForeground: "#E5E5E7",
59
+ sidebarPrimary: "#F4F4F4",
60
+ sidebarPrimaryForeground: "#101010",
61
+ sidebarAccent: "#1E1E22",
62
+ sidebarAccentForeground: "#F4F4F4",
63
+ sidebarSelected: "#1E1D2C",
64
+ sidebarSelectedForeground: "#F4F4F4",
65
+ sidebarIcon: "#C0C0C3",
66
+ sidebarBorder: "#2A2A2E",
67
+ sidebarRing: "#B4B4B4",
68
+ },
69
+ },
70
+ typography: {
71
+ bodyFont: '"Manrope", ui-sans-serif, system-ui, sans-serif',
72
+ headingFont: '"Manrope", ui-sans-serif, system-ui, sans-serif',
73
+ },
74
+ radius: { radius: "0.375rem" },
75
+ branding: {
76
+ appName: "LYKOMI UI",
77
+ appTitle: "LYKOMI UI",
78
+ faviconUrl: "/favicon.svg",
79
+ darkFaviconUrl: "/favicon-dark.svg",
80
+ },
81
+ },
82
+ };
@@ -61,11 +61,11 @@ export default defineConfig({
61
61
  )
62
62
  return "lykomi-workbench";
63
63
  if (pkg.startsWith("@lykomi/web-ui/")) return "vendor-lykomi-ui";
64
- if (pkg.startsWith("@lykomi/web-shell/")) return "vendor-foundry-shell";
64
+ if (pkg.startsWith("@lykomi/web-shell/")) return "vendor-lykomi-shell";
65
65
  if (pkg.startsWith("@lykomi/agent/")) return "vendor-lykomi-agent";
66
66
  // Voice is optional at runtime and heavy at build; its own chunk
67
67
  // keeps the main path light and the size gate honest.
68
- if (pkg.startsWith("@lykomi/web-voice/")) return "vendor-foundry-voice";
68
+ if (pkg.startsWith("@lykomi/web-voice/")) return "vendor-lykomi-voice";
69
69
  // Before the react rule: recharts drags react-smooth + the d3
70
70
  // sub-packages along, and "react-smooth" would land in
71
71
  // vendor-react and push it past the chunk-size gate.