@even-toolkit/create-even-app 1.1.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.
Files changed (126) hide show
  1. package/index.js +159 -0
  2. package/package.json +28 -0
  3. package/templates/chat/README.md +27 -0
  4. package/templates/chat/index.html +12 -0
  5. package/templates/chat/package.json +34 -0
  6. package/templates/chat/src/App.tsx +61 -0
  7. package/templates/chat/src/app.css +54 -0
  8. package/templates/chat/src/contexts/ChatContext.tsx +99 -0
  9. package/templates/chat/src/glass/AppGlasses.tsx +70 -0
  10. package/templates/chat/src/glass/screens/home.ts +24 -0
  11. package/templates/chat/src/glass/selectors.ts +9 -0
  12. package/templates/chat/src/glass/shared.ts +8 -0
  13. package/templates/chat/src/glass/splash.ts +25 -0
  14. package/templates/chat/src/main.tsx +13 -0
  15. package/templates/chat/src/screens/ChatScreen.tsx +69 -0
  16. package/templates/chat/src/screens/Settings.tsx +88 -0
  17. package/templates/chat/src/types.ts +13 -0
  18. package/templates/chat/src/vite-env.d.ts +1 -0
  19. package/templates/chat/template.json +7 -0
  20. package/templates/chat/tsconfig.json +20 -0
  21. package/templates/chat/tsconfig.node.json +13 -0
  22. package/templates/chat/vite.config.ts +12 -0
  23. package/templates/dashboard/README.md +17 -0
  24. package/templates/dashboard/index.html +12 -0
  25. package/templates/dashboard/package.json +34 -0
  26. package/templates/dashboard/src/App.tsx +27 -0
  27. package/templates/dashboard/src/app.css +54 -0
  28. package/templates/dashboard/src/glass/AppGlasses.tsx +53 -0
  29. package/templates/dashboard/src/glass/screens/home.ts +23 -0
  30. package/templates/dashboard/src/glass/selectors.ts +9 -0
  31. package/templates/dashboard/src/glass/shared.ts +8 -0
  32. package/templates/dashboard/src/glass/splash.ts +22 -0
  33. package/templates/dashboard/src/main.tsx +13 -0
  34. package/templates/dashboard/src/screens/ChartsScreen.tsx +99 -0
  35. package/templates/dashboard/src/screens/OverviewScreen.tsx +102 -0
  36. package/templates/dashboard/src/screens/SettingsScreen.tsx +60 -0
  37. package/templates/dashboard/src/vite-env.d.ts +1 -0
  38. package/templates/dashboard/template.json +7 -0
  39. package/templates/dashboard/tsconfig.json +20 -0
  40. package/templates/dashboard/tsconfig.node.json +13 -0
  41. package/templates/dashboard/vite.config.ts +12 -0
  42. package/templates/media/README.md +27 -0
  43. package/templates/media/index.html +12 -0
  44. package/templates/media/package.json +34 -0
  45. package/templates/media/src/App.tsx +24 -0
  46. package/templates/media/src/app.css +54 -0
  47. package/templates/media/src/contexts/MediaContext.tsx +108 -0
  48. package/templates/media/src/glass/AppGlasses.tsx +59 -0
  49. package/templates/media/src/glass/screens/home.ts +24 -0
  50. package/templates/media/src/glass/selectors.ts +9 -0
  51. package/templates/media/src/glass/shared.ts +8 -0
  52. package/templates/media/src/glass/splash.ts +25 -0
  53. package/templates/media/src/layouts/shell.tsx +39 -0
  54. package/templates/media/src/main.tsx +13 -0
  55. package/templates/media/src/screens/AudioScreen.tsx +78 -0
  56. package/templates/media/src/screens/GalleryScreen.tsx +98 -0
  57. package/templates/media/src/screens/Settings.tsx +86 -0
  58. package/templates/media/src/screens/UploadScreen.tsx +95 -0
  59. package/templates/media/src/types.ts +29 -0
  60. package/templates/media/src/vite-env.d.ts +1 -0
  61. package/templates/media/template.json +7 -0
  62. package/templates/media/tsconfig.json +20 -0
  63. package/templates/media/tsconfig.node.json +13 -0
  64. package/templates/media/vite.config.ts +12 -0
  65. package/templates/minimal/README.md +27 -0
  66. package/templates/minimal/index.html +12 -0
  67. package/templates/minimal/package.json +34 -0
  68. package/templates/minimal/src/App.tsx +50 -0
  69. package/templates/minimal/src/app.css +54 -0
  70. package/templates/minimal/src/glass/AppGlasses.tsx +54 -0
  71. package/templates/minimal/src/glass/screens/home.ts +24 -0
  72. package/templates/minimal/src/glass/selectors.ts +9 -0
  73. package/templates/minimal/src/glass/shared.ts +8 -0
  74. package/templates/minimal/src/glass/splash.ts +25 -0
  75. package/templates/minimal/src/main.tsx +13 -0
  76. package/templates/minimal/src/vite-env.d.ts +1 -0
  77. package/templates/minimal/template.json +7 -0
  78. package/templates/minimal/tsconfig.json +20 -0
  79. package/templates/minimal/tsconfig.node.json +13 -0
  80. package/templates/minimal/vite.config.ts +12 -0
  81. package/templates/notes/README.md +27 -0
  82. package/templates/notes/index.html +12 -0
  83. package/templates/notes/package.json +34 -0
  84. package/templates/notes/src/App.tsx +25 -0
  85. package/templates/notes/src/app.css +54 -0
  86. package/templates/notes/src/contexts/NotesContext.tsx +140 -0
  87. package/templates/notes/src/glass/AppGlasses.tsx +58 -0
  88. package/templates/notes/src/glass/screens/home.ts +24 -0
  89. package/templates/notes/src/glass/selectors.ts +9 -0
  90. package/templates/notes/src/glass/shared.ts +8 -0
  91. package/templates/notes/src/glass/splash.ts +24 -0
  92. package/templates/notes/src/layouts/shell.tsx +36 -0
  93. package/templates/notes/src/main.tsx +13 -0
  94. package/templates/notes/src/screens/NoteDetail.tsx +104 -0
  95. package/templates/notes/src/screens/NoteForm.tsx +84 -0
  96. package/templates/notes/src/screens/NoteList.tsx +108 -0
  97. package/templates/notes/src/screens/Settings.tsx +88 -0
  98. package/templates/notes/src/types.ts +14 -0
  99. package/templates/notes/src/vite-env.d.ts +1 -0
  100. package/templates/notes/template.json +7 -0
  101. package/templates/notes/tsconfig.json +20 -0
  102. package/templates/notes/tsconfig.node.json +13 -0
  103. package/templates/notes/vite.config.ts +12 -0
  104. package/templates/tracker/README.md +27 -0
  105. package/templates/tracker/index.html +12 -0
  106. package/templates/tracker/package.json +34 -0
  107. package/templates/tracker/src/App.tsx +24 -0
  108. package/templates/tracker/src/app.css +54 -0
  109. package/templates/tracker/src/contexts/TrackerContext.tsx +193 -0
  110. package/templates/tracker/src/glass/AppGlasses.tsx +64 -0
  111. package/templates/tracker/src/glass/screens/home.ts +24 -0
  112. package/templates/tracker/src/glass/selectors.ts +9 -0
  113. package/templates/tracker/src/glass/shared.ts +8 -0
  114. package/templates/tracker/src/glass/splash.ts +24 -0
  115. package/templates/tracker/src/layouts/shell.tsx +37 -0
  116. package/templates/tracker/src/main.tsx +13 -0
  117. package/templates/tracker/src/screens/HistoryScreen.tsx +106 -0
  118. package/templates/tracker/src/screens/NewEntryScreen.tsx +135 -0
  119. package/templates/tracker/src/screens/Settings.tsx +135 -0
  120. package/templates/tracker/src/screens/TodayScreen.tsx +147 -0
  121. package/templates/tracker/src/types.ts +34 -0
  122. package/templates/tracker/src/vite-env.d.ts +1 -0
  123. package/templates/tracker/template.json +7 -0
  124. package/templates/tracker/tsconfig.json +20 -0
  125. package/templates/tracker/tsconfig.node.json +13 -0
  126. package/templates/tracker/vite.config.ts +12 -0
package/index.js ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const TEMPLATES_DIR = path.join(__dirname, 'templates');
10
+
11
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
12
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
13
+
14
+ // ── Load available templates ──
15
+
16
+ function loadTemplates() {
17
+ const dirs = fs.readdirSync(TEMPLATES_DIR).filter((d) =>
18
+ fs.statSync(path.join(TEMPLATES_DIR, d)).isDirectory() &&
19
+ fs.existsSync(path.join(TEMPLATES_DIR, d, 'template.json'))
20
+ );
21
+ return dirs.map((d) => {
22
+ const manifest = JSON.parse(fs.readFileSync(path.join(TEMPLATES_DIR, d, 'template.json'), 'utf8'));
23
+ return { dir: d, ...manifest };
24
+ });
25
+ }
26
+
27
+ // ── Interactive template picker ──
28
+
29
+ async function pickTemplate(templates, preselected) {
30
+ if (preselected) {
31
+ const match = templates.find((t) => t.name === preselected || t.dir === preselected);
32
+ if (match) return match;
33
+ console.error(`Template "${preselected}" not found. Available: ${templates.map((t) => t.name).join(', ')}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ console.log('\nAvailable templates:\n');
38
+ templates.forEach((t, i) => {
39
+ console.log(` ${i + 1}. ${t.name.padEnd(14)} — ${t.description}`);
40
+ });
41
+ console.log();
42
+
43
+ const choice = await ask(`Choose a template [1-${templates.length}] (default 1): `);
44
+ const idx = (parseInt(choice, 10) || 1) - 1;
45
+ if (idx < 0 || idx >= templates.length) {
46
+ console.error('Invalid choice.');
47
+ process.exit(1);
48
+ }
49
+ return templates[idx];
50
+ }
51
+
52
+ // ── Copy directory recursively, replacing tokens ──
53
+
54
+ const TOKEN_FILES = new Set([
55
+ 'package.json', 'app.json', 'index.html', 'README.md',
56
+ 'App.tsx', 'main.tsx', 'shell.tsx', 'splash.ts', 'AppGlasses.tsx', 'selectors.ts',
57
+ ]);
58
+
59
+ function copyDir(src, dest, tokens) {
60
+ fs.mkdirSync(dest, { recursive: true });
61
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
62
+ if (entry.name === 'template.json' || entry.name === 'node_modules') continue;
63
+ const srcPath = path.join(src, entry.name);
64
+ const destPath = path.join(dest, entry.name);
65
+ if (entry.isDirectory()) {
66
+ copyDir(srcPath, destPath, tokens);
67
+ } else {
68
+ let content = fs.readFileSync(srcPath, 'utf8');
69
+ if (TOKEN_FILES.has(entry.name) || entry.name.endsWith('.tsx') || entry.name.endsWith('.ts') || entry.name.endsWith('.json') || entry.name.endsWith('.html') || entry.name.endsWith('.md')) {
70
+ for (const [key, value] of Object.entries(tokens)) {
71
+ content = content.replaceAll(key, value);
72
+ }
73
+ }
74
+ fs.writeFileSync(destPath, content);
75
+ }
76
+ }
77
+ }
78
+
79
+ // ── Main ──
80
+
81
+ async function main() {
82
+ const args = process.argv.slice(2);
83
+ let appName = null;
84
+ let templateName = null;
85
+
86
+ // Parse args
87
+ for (let i = 0; i < args.length; i++) {
88
+ if (args[i] === '--template' || args[i] === '-t') {
89
+ templateName = args[++i];
90
+ } else if (!args[i].startsWith('-')) {
91
+ appName = args[i];
92
+ }
93
+ }
94
+
95
+ console.log(`
96
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
97
+ ░ @even-toolkit/create-even-app ░
98
+ ░ G2 Smart Glasses App Scaffold ░
99
+ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
100
+ `);
101
+
102
+ // App name
103
+ if (!appName) {
104
+ appName = await ask('App name (e.g. my-app): ');
105
+ }
106
+ if (!appName) {
107
+ console.error('App name is required.');
108
+ process.exit(1);
109
+ }
110
+
111
+ const dir = path.resolve(process.cwd(), appName);
112
+ if (fs.existsSync(dir)) {
113
+ console.error(`Directory "${appName}" already exists.`);
114
+ process.exit(1);
115
+ }
116
+
117
+ // Template selection
118
+ const templates = loadTemplates();
119
+ if (templates.length === 0) {
120
+ console.error('No templates found in templates/ directory.');
121
+ process.exit(1);
122
+ }
123
+ const template = await pickTemplate(templates, templateName);
124
+
125
+ // Package ID
126
+ const defaultId = `com.${appName.replace(/[^a-z0-9]/g, '')}.g2`;
127
+ const packageId = await ask(`Package ID (${defaultId}): `) || defaultId;
128
+
129
+ const displayName = appName
130
+ .split(/[-_]/)
131
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
132
+ .join(' ');
133
+
134
+ rl.close();
135
+
136
+ console.log(`\nScaffolding "${displayName}" with "${template.name}" template...\n`);
137
+
138
+ // Copy template with token replacement
139
+ const tokens = {
140
+ '{{APP_NAME}}': appName,
141
+ '{{DISPLAY_NAME}}': displayName,
142
+ '{{DISPLAY_NAME_UPPER}}': displayName.toUpperCase(),
143
+ '{{PACKAGE_ID}}': packageId,
144
+ };
145
+ copyDir(path.join(TEMPLATES_DIR, template.dir), dir, tokens);
146
+
147
+ console.log(`Done! Next steps:\n`);
148
+ console.log(` cd ${appName}`);
149
+ console.log(` npm install`);
150
+ console.log(` npm run dev\n`);
151
+ console.log(` # Build for Even Hub:`);
152
+ console.log(` npm run build`);
153
+ console.log(` npx @evenrealities/evenhub-cli pack app.json dist\n`);
154
+ }
155
+
156
+ main().catch((err) => {
157
+ console.error(err);
158
+ process.exit(1);
159
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@even-toolkit/create-even-app",
3
+ "version": "1.1.0",
4
+ "description": "Scaffold a new Even Realities G2 glasses app from a template gallery",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-even-app": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "templates/"
12
+ ],
13
+ "keywords": [
14
+ "even-realities",
15
+ "even-toolkit",
16
+ "g2-glasses",
17
+ "smart-glasses",
18
+ "scaffold",
19
+ "create-app",
20
+ "template"
21
+ ],
22
+ "author": "fabioglimb",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/fabioglimb/create-even-app"
27
+ }
28
+ }
@@ -0,0 +1,27 @@
1
+ # {{DISPLAY_NAME}}
2
+
3
+ A G2 smart glasses app built with [even-toolkit](https://www.npmjs.com/package/even-toolkit).
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Open [http://localhost:5173](http://localhost:5173) in your browser.
13
+
14
+ ## Test with Simulator
15
+
16
+ ```bash
17
+ npx @evenrealities/evenhub-simulator@latest http://localhost:5173
18
+ ```
19
+
20
+ ## Build for Even Hub
21
+
22
+ ```bash
23
+ npm run build
24
+ npx @evenrealities/evenhub-cli pack app.json dist
25
+ ```
26
+
27
+ Upload the generated `.ehpk` file to the Even Hub.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
+ <title>{{DISPLAY_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "{{APP_NAME}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 5173 --host",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@evenrealities/even_hub_sdk": "^0.0.9",
13
+ "@jappyjan/even-better-sdk": "^0.0.11",
14
+ "class-variance-authority": "^0.7.1",
15
+ "clsx": "^2.1.0",
16
+ "even-toolkit": "^1.5.0",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "react-is": "^19.2.4",
20
+ "react-router": "^7.0.0",
21
+ "tailwind-merge": "^3.0.0",
22
+ "upng-js": "^2.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@tailwindcss/vite": "^4.0.0",
26
+ "@types/react": "^19.0.0",
27
+ "@types/react-dom": "^19.0.0",
28
+ "@types/node": "^22.0.0",
29
+ "@vitejs/plugin-react": "^4.3.0",
30
+ "tailwindcss": "^4.0.0",
31
+ "typescript": "~5.4.0",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
@@ -0,0 +1,61 @@
1
+ import { Routes, Route, useNavigate, useLocation } from 'react-router'
2
+ import { AppShell, NavHeader, Button } from 'even-toolkit/web'
3
+ import { IcSettings, IcChevronBack } from 'even-toolkit/web/icons/svg-icons'
4
+ import { ChatProvider } from './contexts/ChatContext'
5
+ import { ChatScreen } from './screens/ChatScreen'
6
+ import { Settings } from './screens/Settings'
7
+ import { AppGlasses } from './glass/AppGlasses'
8
+
9
+ function ChatLayout() {
10
+ const navigate = useNavigate()
11
+
12
+ return (
13
+ <AppShell
14
+ header={
15
+ <NavHeader
16
+ title="{{DISPLAY_NAME}}"
17
+ right={
18
+ <Button variant="ghost" size="icon" onClick={() => navigate('/settings')}>
19
+ <IcSettings className="w-5 h-5" />
20
+ </Button>
21
+ }
22
+ />
23
+ }
24
+ >
25
+ <ChatScreen />
26
+ <AppGlasses />
27
+ </AppShell>
28
+ )
29
+ }
30
+
31
+ function SettingsLayout() {
32
+ const navigate = useNavigate()
33
+
34
+ return (
35
+ <AppShell
36
+ header={
37
+ <NavHeader
38
+ title="Settings"
39
+ left={
40
+ <Button variant="ghost" size="icon" onClick={() => navigate('/')}>
41
+ <IcChevronBack className="w-5 h-5" />
42
+ </Button>
43
+ }
44
+ />
45
+ }
46
+ >
47
+ <Settings />
48
+ </AppShell>
49
+ )
50
+ }
51
+
52
+ export function App() {
53
+ return (
54
+ <ChatProvider>
55
+ <Routes>
56
+ <Route path="/" element={<ChatLayout />} />
57
+ <Route path="/settings" element={<SettingsLayout />} />
58
+ </Routes>
59
+ </ChatProvider>
60
+ )
61
+ }
@@ -0,0 +1,54 @@
1
+ @import "tailwindcss";
2
+ @import "even-toolkit/web/theme-light.css";
3
+ @import "even-toolkit/web/typography.css";
4
+ @import "even-toolkit/web/utilities.css";
5
+ @source "../src";
6
+ @source "../node_modules/even-toolkit/web";
7
+ @source "../node_modules/even-toolkit/dist";
8
+
9
+ @theme {
10
+ --color-bg: var(--color-bg);
11
+ --color-surface: var(--color-surface);
12
+ --color-surface-light: var(--color-surface-light);
13
+ --color-surface-lighter: var(--color-surface-lighter);
14
+ --color-border: var(--color-border);
15
+ --color-border-light: var(--color-border-light);
16
+ --color-text: var(--color-text);
17
+ --color-text-dim: var(--color-text-dim);
18
+ --color-text-muted: var(--color-text-muted);
19
+ --color-text-highlight: var(--color-text-highlight);
20
+ --color-accent: var(--color-accent);
21
+ --color-accent-alpha: var(--color-accent-alpha);
22
+ --color-accent-warning: var(--color-accent-warning);
23
+ --color-positive: var(--color-positive);
24
+ --color-positive-alpha: var(--color-positive-alpha);
25
+ --color-negative: var(--color-negative);
26
+ --color-negative-alpha: var(--color-negative-alpha);
27
+ --color-overlay: var(--color-overlay);
28
+ --color-input-bg: var(--color-input-bg);
29
+ --radius-default: var(--radius-default);
30
+ --font-display: var(--font-display);
31
+ --font-body: var(--font-body);
32
+ --font-mono: var(--font-mono);
33
+ }
34
+
35
+ html, body {
36
+ background: var(--color-bg);
37
+ color: var(--color-text);
38
+ min-height: 100dvh;
39
+ font-family: var(--font-display);
40
+ -webkit-font-smoothing: antialiased;
41
+ }
42
+
43
+ #root {
44
+ max-width: 430px;
45
+ margin: 0 auto;
46
+ }
47
+
48
+ .scrollbar-hide {
49
+ -ms-overflow-style: none;
50
+ scrollbar-width: none;
51
+ }
52
+ .scrollbar-hide::-webkit-scrollbar {
53
+ display: none;
54
+ }
@@ -0,0 +1,99 @@
1
+ import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'
2
+ import type { Message, ChatSettings } from '../types'
3
+
4
+ interface ChatContextValue {
5
+ messages: Message[]
6
+ isLoading: boolean
7
+ settings: ChatSettings
8
+ setSettings: (settings: ChatSettings) => void
9
+ sendMessage: (content: string) => void
10
+ clearMessages: () => void
11
+ }
12
+
13
+ const ChatContext = createContext<ChatContextValue | null>(null)
14
+
15
+ const SAMPLE_MESSAGES: Message[] = [
16
+ {
17
+ id: '1',
18
+ role: 'user',
19
+ content: 'What can you help me with?',
20
+ timestamp: Date.now() - 120000,
21
+ },
22
+ {
23
+ id: '2',
24
+ role: 'assistant',
25
+ content: 'I can help you with a wide range of tasks — answering questions, writing, analysis, coding, brainstorming, and more. I can also display key information on your G2 glasses for quick, hands-free reference. What would you like to work on?',
26
+ timestamp: Date.now() - 110000,
27
+ },
28
+ {
29
+ id: '3',
30
+ role: 'user',
31
+ content: 'Show me the weather forecast for today.',
32
+ timestamp: Date.now() - 60000,
33
+ },
34
+ {
35
+ id: '4',
36
+ role: 'assistant',
37
+ content: 'Here is today\'s forecast:\n\nMostly sunny, high of 22C / 72F with light winds from the northwest. UV index is moderate — sunglasses recommended (you\'re already wearing them). No rain expected until Thursday.',
38
+ timestamp: Date.now() - 50000,
39
+ },
40
+ ]
41
+
42
+ const SIMULATED_RESPONSES = [
43
+ 'That\'s a great question. Let me think about it for a moment.\n\nBased on what I know, the answer involves a few key considerations. Would you like me to go deeper into any particular aspect?',
44
+ 'I\'ve processed your request. Here is what I found:\n\nThe information you\'re looking for can be broken down into three main areas. I\'ll summarize the key points and push them to your glasses display for quick reference.',
45
+ 'Interesting thought. Here\'s my perspective on it:\n\nThere are multiple angles to consider here. The most important factor is understanding the context and constraints involved. Let me know if you\'d like more detail on any specific part.',
46
+ ]
47
+
48
+ export function ChatProvider({ children }: { children: ReactNode }) {
49
+ const [messages, setMessages] = useState<Message[]>(SAMPLE_MESSAGES)
50
+ const [isLoading, setIsLoading] = useState(false)
51
+ const [settings, setSettings] = useState<ChatSettings>({
52
+ provider: 'openai',
53
+ apiKey: '',
54
+ voiceEnabled: true,
55
+ darkMode: false,
56
+ })
57
+
58
+ const sendMessage = useCallback((content: string) => {
59
+ const userMessage: Message = {
60
+ id: crypto.randomUUID(),
61
+ role: 'user',
62
+ content,
63
+ timestamp: Date.now(),
64
+ }
65
+
66
+ setMessages((prev) => [...prev, userMessage])
67
+ setIsLoading(true)
68
+
69
+ // Simulate AI response
70
+ const delay = 1200 + Math.random() * 1800
71
+ setTimeout(() => {
72
+ const response = SIMULATED_RESPONSES[Math.floor(Math.random() * SIMULATED_RESPONSES.length)]
73
+ const assistantMessage: Message = {
74
+ id: crypto.randomUUID(),
75
+ role: 'assistant',
76
+ content: response,
77
+ timestamp: Date.now(),
78
+ }
79
+ setMessages((prev) => [...prev, assistantMessage])
80
+ setIsLoading(false)
81
+ }, delay)
82
+ }, [])
83
+
84
+ const clearMessages = useCallback(() => {
85
+ setMessages([])
86
+ }, [])
87
+
88
+ return (
89
+ <ChatContext.Provider value={{ messages, isLoading, settings, setSettings, sendMessage, clearMessages }}>
90
+ {children}
91
+ </ChatContext.Provider>
92
+ )
93
+ }
94
+
95
+ export function useChat() {
96
+ const ctx = useContext(ChatContext)
97
+ if (!ctx) throw new Error('useChat must be used within ChatProvider')
98
+ return ctx
99
+ }
@@ -0,0 +1,70 @@
1
+ import { useCallback, useMemo, useRef } from 'react'
2
+ import { useNavigate, useLocation } from 'react-router'
3
+ import { useGlasses } from 'even-toolkit/useGlasses'
4
+ import { useFlashPhase } from 'even-toolkit/useFlashPhase'
5
+ import { createScreenMapper, getHomeTiles } from 'even-toolkit/glass-router'
6
+ import { appSplash } from './splash'
7
+ import { toDisplayData, onGlassAction, type AppSnapshot } from './selectors'
8
+ import type { AppActions } from './shared'
9
+ import { useChat } from '../contexts/ChatContext'
10
+
11
+ const deriveScreen = createScreenMapper([
12
+ { pattern: '/', screen: 'home' },
13
+ { pattern: '/settings', screen: 'home' },
14
+ ], 'home')
15
+
16
+ const homeTiles = getHomeTiles(appSplash)
17
+
18
+ export function AppGlasses() {
19
+ const navigate = useNavigate()
20
+ const location = useLocation()
21
+ const { messages } = useChat()
22
+ const flashPhase = useFlashPhase(deriveScreen(location.pathname) === 'home')
23
+
24
+ const snapshotRef = useMemo(() => ({
25
+ current: null as AppSnapshot | null,
26
+ }), [])
27
+
28
+ // Show recent messages on the glasses display
29
+ const recentItems = messages
30
+ .slice(-6)
31
+ .map((msg) => {
32
+ const prefix = msg.role === 'user' ? 'You' : 'AI'
33
+ const text = msg.content.length > 60 ? msg.content.slice(0, 57) + '...' : msg.content
34
+ return `${prefix}: ${text}`
35
+ })
36
+
37
+ if (recentItems.length === 0) {
38
+ recentItems.push('{{DISPLAY_NAME}} ready', 'Send a message to begin')
39
+ }
40
+
41
+ const snapshot: AppSnapshot = {
42
+ items: recentItems,
43
+ flashPhase,
44
+ }
45
+ snapshotRef.current = snapshot
46
+
47
+ const getSnapshot = useCallback(() => snapshotRef.current!, [snapshotRef])
48
+
49
+ const ctxRef = useRef<AppActions>({ navigate })
50
+ ctxRef.current = { navigate }
51
+
52
+ const handleGlassAction = useCallback(
53
+ (action: Parameters<typeof onGlassAction>[0], nav: Parameters<typeof onGlassAction>[1], snap: AppSnapshot) =>
54
+ onGlassAction(action, nav, snap, ctxRef.current),
55
+ [],
56
+ )
57
+
58
+ useGlasses({
59
+ getSnapshot,
60
+ toDisplayData,
61
+ onGlassAction: handleGlassAction,
62
+ deriveScreen,
63
+ appName: '{{DISPLAY_NAME_UPPER}}',
64
+ splash: appSplash,
65
+ getPageMode: (screen) => screen === 'home' ? 'home' : 'text',
66
+ homeImageTiles: homeTiles,
67
+ })
68
+
69
+ return null
70
+ }
@@ -0,0 +1,24 @@
1
+ import type { GlassScreen } from 'even-toolkit/glass-screen-router'
2
+ import { buildScrollableList } from 'even-toolkit/glass-display-builders'
3
+ import { moveHighlight } from 'even-toolkit/glass-nav'
4
+ import type { AppSnapshot, AppActions } from '../shared'
5
+
6
+ export const homeScreen: GlassScreen<AppSnapshot, AppActions> = {
7
+ display(snapshot, nav) {
8
+ return {
9
+ lines: buildScrollableList({
10
+ items: snapshot.items,
11
+ highlightedIndex: nav.highlightedIndex,
12
+ maxVisible: 5,
13
+ formatter: (item) => item,
14
+ }),
15
+ }
16
+ },
17
+
18
+ action(action, nav, snapshot) {
19
+ if (action.type === 'HIGHLIGHT_MOVE') {
20
+ return { ...nav, highlightedIndex: moveHighlight(nav.highlightedIndex, action.direction, snapshot.items.length - 1) }
21
+ }
22
+ return nav
23
+ },
24
+ }
@@ -0,0 +1,9 @@
1
+ import { createGlassScreenRouter } from 'even-toolkit/glass-screen-router'
2
+ import type { AppSnapshot, AppActions } from './shared'
3
+ import { homeScreen } from './screens/home'
4
+
5
+ export type { AppSnapshot, AppActions }
6
+
7
+ export const { toDisplayData, onGlassAction } = createGlassScreenRouter<AppSnapshot, AppActions>({
8
+ 'home': homeScreen,
9
+ }, 'home')
@@ -0,0 +1,8 @@
1
+ export interface AppSnapshot {
2
+ items: string[]
3
+ flashPhase: boolean
4
+ }
5
+
6
+ export interface AppActions {
7
+ navigate: (path: string) => void
8
+ }
@@ -0,0 +1,25 @@
1
+ import { createSplash, TILE_PRESETS } from 'even-toolkit/splash'
2
+
3
+ export function renderSplash(ctx: CanvasRenderingContext2D, w: number, h: number) {
4
+ const fg = '#e0e0e0'
5
+ const cx = w / 2
6
+ const s = Math.min(w / 200, h / 200)
7
+
8
+ // Simple text-based splash — replace with pixel-art icon for your app
9
+ ctx.fillStyle = fg
10
+ ctx.font = `bold ${14 * s}px "Courier New", monospace`
11
+ ctx.textAlign = 'center'
12
+ ctx.fillText('{{DISPLAY_NAME_UPPER}}', cx, 50 * s)
13
+ ctx.textAlign = 'left'
14
+ }
15
+
16
+ export const appSplash = createSplash({
17
+ tiles: 1,
18
+ tileLayout: 'vertical',
19
+ tilePositions: TILE_PRESETS.topCenter1,
20
+ canvasSize: { w: 200, h: 200 },
21
+ minTimeMs: 0,
22
+ maxTimeMs: 0,
23
+ menuText: '',
24
+ render: renderSplash,
25
+ })
@@ -0,0 +1,13 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router'
4
+ import { App } from './App'
5
+ import './app.css'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>,
13
+ )