@askable-ui/create-app 0.6.3

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 ADDED
@@ -0,0 +1,25 @@
1
+ # @askable-ui/create-app
2
+
3
+ Scaffold a runnable **React + Vite + CopilotKit + askable-ui** starter app.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm create @askable-ui/app my-app
9
+ cd my-app
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ The generated app includes:
15
+
16
+ - a small analytics dashboard annotated with `data-askable`
17
+ - `useAskable()` pre-wired for hover + click focus tracking
18
+ - `useCopilotReadable()` pushing current and recent Askable context into CopilotKit
19
+ - a local Express runtime wired to `@copilotkit/runtime`
20
+ - a starter README and `.env.example`
21
+
22
+ ## Notes
23
+
24
+ - The scaffold writes files only. It does **not** auto-run `npm install`.
25
+ - If `OPENAI_API_KEY` is missing, the generated runtime still starts so the app boots locally, but Copilot responses stay disabled until you add a real key.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../src/scaffold.js';
3
+
4
+ runCli(process.argv.slice(2)).catch((error) => {
5
+ console.error(`\n✖ ${error instanceof Error ? error.message : String(error)}`);
6
+ process.exitCode = 1;
7
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@askable-ui/create-app",
3
+ "version": "0.6.3",
4
+ "description": "Scaffold a React + Vite + CopilotKit + askable-ui starter app",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-askable-app": "bin/create-askable-app.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "template",
13
+ "README.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/askable-ui/askable.git",
18
+ "directory": "packages/create-askable-app"
19
+ },
20
+ "keywords": [
21
+ "askable",
22
+ "copilotkit",
23
+ "vite",
24
+ "react",
25
+ "scaffold"
26
+ ],
27
+ "homepage": "https://askable-ui.com",
28
+ "license": "MIT",
29
+ "scripts": {
30
+ "test": "node --test"
31
+ }
32
+ }
@@ -0,0 +1,80 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const ASKABLE_VERSION = '0.6.3';
6
+ const COPILOTKIT_VERSION = '1.56.2';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');
11
+
12
+ export function toPackageName(rawName) {
13
+ return rawName
14
+ .trim()
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9-_]+/g, '-')
17
+ .replace(/^-+|-+$/g, '')
18
+ .replace(/-{2,}/g, '-');
19
+ }
20
+
21
+ export function isDirectoryEmpty(targetDir) {
22
+ if (!fs.existsSync(targetDir)) {
23
+ return true;
24
+ }
25
+
26
+ const entries = fs.readdirSync(targetDir).filter((entry) => entry !== '.DS_Store');
27
+ return entries.length === 0;
28
+ }
29
+
30
+ function render(content, projectName) {
31
+ return content
32
+ .replaceAll('__APP_NAME__', projectName)
33
+ .replaceAll('__PACKAGE_NAME__', toPackageName(projectName) || 'askable-app')
34
+ .replaceAll('__ASKABLE_VERSION__', ASKABLE_VERSION)
35
+ .replaceAll('__COPILOTKIT_VERSION__', COPILOTKIT_VERSION);
36
+ }
37
+
38
+ function copyTemplate(templateDir, targetDir, projectName) {
39
+ for (const entry of fs.readdirSync(templateDir, { withFileTypes: true })) {
40
+ const sourcePath = path.join(templateDir, entry.name);
41
+ const outputName = entry.name === '_gitignore' ? '.gitignore' : entry.name;
42
+ const targetPath = path.join(targetDir, outputName);
43
+
44
+ if (entry.isDirectory()) {
45
+ fs.mkdirSync(targetPath, { recursive: true });
46
+ copyTemplate(sourcePath, targetPath, projectName);
47
+ continue;
48
+ }
49
+
50
+ const raw = fs.readFileSync(sourcePath, 'utf8');
51
+ fs.writeFileSync(targetPath, render(raw, projectName));
52
+ }
53
+ }
54
+
55
+ export async function runCli(args) {
56
+ const [projectArg] = args;
57
+
58
+ if (!projectArg || projectArg === '--help' || projectArg === '-h') {
59
+ console.log(`create-askable-app\n\nUsage:\n npm create @askable-ui/app my-app\n`);
60
+ return;
61
+ }
62
+
63
+ const projectName = projectArg.trim();
64
+ const targetDir = path.resolve(process.cwd(), projectName);
65
+
66
+ if (!isDirectoryEmpty(targetDir)) {
67
+ throw new Error(`Target directory is not empty: ${targetDir}`);
68
+ }
69
+
70
+ fs.mkdirSync(targetDir, { recursive: true });
71
+ copyTemplate(TEMPLATE_DIR, targetDir, projectName);
72
+
73
+ console.log(`\n✔ askable starter created at ${targetDir}\n`);
74
+ console.log('Next steps:');
75
+ console.log(` cd ${projectName}`);
76
+ console.log(' npm install');
77
+ console.log(' cp .env.example .env');
78
+ console.log(' npm run dev');
79
+ console.log('\nTip: add OPENAI_API_KEY to .env when you want the CopilotKit runtime to answer.');
80
+ }
@@ -0,0 +1,5 @@
1
+ VITE_COPILOT_RUNTIME_URL=http://localhost:3001/api/copilotkit
2
+ PORT=3001
3
+ CORS_ORIGIN=http://localhost:5173
4
+ OPENAI_API_KEY=
5
+ OPENAI_MODEL=gpt-4o-mini
@@ -0,0 +1,57 @@
1
+ # __APP_NAME__
2
+
3
+ A starter project generated by `create-askable-app`.
4
+
5
+ This template wires together:
6
+
7
+ - **askable-ui** for UI focus tracking with `useAskable()`
8
+ - **CopilotKit** for the chat sidebar and runtime
9
+ - **React + Vite** for the frontend
10
+ - **Express + @copilotkit/runtime** for the local backend runtime
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ npm install
16
+ cp .env.example .env
17
+ npm run dev
18
+ ```
19
+
20
+ The app opens at `http://localhost:5173` and the Copilot runtime listens on `http://localhost:3001/api/copilotkit`.
21
+
22
+ ## Environment
23
+
24
+ Update `.env` with your own provider key:
25
+
26
+ ```bash
27
+ OPENAI_API_KEY=<your key>
28
+ OPENAI_MODEL=gpt-4o-mini
29
+ ```
30
+
31
+ If `OPENAI_API_KEY` is blank, the runtime still starts so the dashboard loads, but Copilot responses stay disabled until you add a real key.
32
+
33
+ ## How Askable and CopilotKit fit together
34
+
35
+ 1. Dashboard panels are annotated with `data-askable` metadata and curated `data-askable-text` summaries.
36
+ 2. `useAskable()` observes the page and produces prompt-ready context like:
37
+ - current focus
38
+ - recent interaction history
39
+ 3. `useCopilotReadable()` forwards those Askable strings into CopilotKit.
40
+ 4. CopilotKit sends the chat request to the local runtime at `/api/copilotkit`.
41
+ 5. The runtime calls the LLM and answers with the current dashboard context already attached.
42
+
43
+ ## Starter structure
44
+
45
+ - `src/App.tsx` — annotated dashboard + `useCopilotReadable()` wiring
46
+ - `src/styles.css` — dashboard styling
47
+ - `server/index.ts` — local CopilotKit runtime
48
+ - `.env.example` — runtime URL, OpenAI key, model, and CORS settings
49
+
50
+ ## Useful scripts
51
+
52
+ ```bash
53
+ npm run dev # Vite frontend + local Copilot runtime
54
+ npm run build # Build the frontend and compile the runtime
55
+ npm run preview # Preview the built frontend
56
+ npm run start # Run the compiled runtime from dist-server/
57
+ ```
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ dist-server
4
+ .env
@@ -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" />
6
+ <title>__APP_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,40 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
8
+ "dev:client": "vite",
9
+ "dev:server": "tsx server/index.ts",
10
+ "build": "npm run build:client && npm run build:server",
11
+ "build:client": "vite build",
12
+ "build:server": "tsc -p tsconfig.server.json",
13
+ "preview": "vite preview",
14
+ "start": "node dist-server/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@askable-ui/react": "^__ASKABLE_VERSION__",
18
+ "@copilotkit/react-core": "__COPILOTKIT_VERSION__",
19
+ "@copilotkit/react-ui": "__COPILOTKIT_VERSION__",
20
+ "react": "19.2.5",
21
+ "react-dom": "19.2.5"
22
+ },
23
+ "devDependencies": {
24
+ "@copilotkit/runtime": "__COPILOTKIT_VERSION__",
25
+ "@types/cors": "2.8.19",
26
+ "@types/express": "5.0.6",
27
+ "@types/node": "25.6.0",
28
+ "@types/react": "19.2.14",
29
+ "@types/react-dom": "19.2.3",
30
+ "@vitejs/plugin-react": "6.0.1",
31
+ "concurrently": "9.2.1",
32
+ "cors": "2.8.6",
33
+ "dotenv": "17.4.2",
34
+ "express": "5.2.1",
35
+ "openai": "6.34.0",
36
+ "tsx": "4.21.0",
37
+ "typescript": "6.0.3",
38
+ "vite": "8.0.9"
39
+ }
40
+ }
@@ -0,0 +1,55 @@
1
+ import 'dotenv/config';
2
+ import cors from 'cors';
3
+ import express from 'express';
4
+ import type { Request, Response } from 'express';
5
+ import { BuiltInAgent, CopilotRuntime, createCopilotExpressHandler } from '@copilotkit/runtime/v2';
6
+
7
+ const port = Number(process.env.PORT ?? 3001);
8
+ const endpoint = '/api/copilotkit';
9
+ const frontendOrigin = process.env.CORS_ORIGIN ?? 'http://localhost:5173';
10
+ const openAiApiKey = process.env.OPENAI_API_KEY;
11
+ const openAiModel = process.env.OPENAI_MODEL ?? 'gpt-4o-mini';
12
+
13
+ const runtime = new CopilotRuntime({
14
+ agents: {
15
+ default: new BuiltInAgent({
16
+ model: `openai/${openAiModel}`,
17
+ apiKey: openAiApiKey,
18
+ prompt:
19
+ 'You are a helpful revenue operations copilot. Use the Askable current-focus context and recent focus history to answer precisely about the currently selected KPI, chart, or deal row. If the user has not selected anything yet, ask them what they want to inspect.',
20
+ }),
21
+ },
22
+ });
23
+
24
+ const app = express();
25
+ app.use(express.json({ limit: '2mb' }));
26
+ app.use(cors({ origin: frontendOrigin, credentials: true }));
27
+ app.use(
28
+ createCopilotExpressHandler({
29
+ runtime,
30
+ basePath: endpoint,
31
+ mode: 'single-route',
32
+ cors: {
33
+ origin: frontendOrigin,
34
+ credentials: true,
35
+ },
36
+ }),
37
+ );
38
+
39
+ app.get('/health', (_req: Request, res: Response) => {
40
+ res.json({
41
+ ok: true,
42
+ endpoint,
43
+ llmReady: Boolean(openAiApiKey),
44
+ model: `openai/${openAiModel}`,
45
+ });
46
+ });
47
+
48
+ app.listen(port, () => {
49
+ const status = openAiApiKey
50
+ ? `BuiltInAgent using openai/${openAiModel}`
51
+ : `BuiltInAgent using openai/${openAiModel} (set OPENAI_API_KEY to enable live responses)`;
52
+ console.log(`Copilot runtime listening on http://localhost:${port}${endpoint}`);
53
+ console.log(`Frontend origin: ${frontendOrigin}`);
54
+ console.log(`Agent: ${status}`);
55
+ });
@@ -0,0 +1,254 @@
1
+ import { useMemo, useRef, useState } from 'react';
2
+ import { CopilotSidebar } from '@copilotkit/react-ui';
3
+ import { useCopilotReadable } from '@copilotkit/react-core';
4
+ import { useAskable } from '@askable-ui/react';
5
+
6
+ type MetricCard = {
7
+ id: string;
8
+ label: string;
9
+ value: string;
10
+ delta: string;
11
+ narrative: string;
12
+ detail: string;
13
+ };
14
+
15
+ type Deal = {
16
+ id: string;
17
+ company: string;
18
+ stage: string;
19
+ value: string;
20
+ owner: string;
21
+ };
22
+
23
+ const metrics: MetricCard[] = [
24
+ {
25
+ id: 'pipeline-coverage',
26
+ label: 'Pipeline coverage',
27
+ value: '3.9x',
28
+ delta: '+0.4x week over week',
29
+ narrative: 'Coverage exceeds the 3.5x target after enterprise expansion in financial services.',
30
+ detail: 'AI focus summary: pipeline coverage is 3.9x against a 3.5x target, driven by enterprise pipeline growth.',
31
+ },
32
+ {
33
+ id: 'net-revenue-retention',
34
+ label: 'Net revenue retention',
35
+ value: '118%',
36
+ delta: '+6 pts quarter over quarter',
37
+ narrative: 'Existing accounts expanded after onboarding automation shipped to the top five teams.',
38
+ detail: 'AI focus summary: NRR is 118%, up 6 points quarter over quarter due to expansion in installed accounts.',
39
+ },
40
+ {
41
+ id: 'support-backlog',
42
+ label: 'Support backlog',
43
+ value: '24 tickets',
44
+ delta: '-31% since last Friday',
45
+ narrative: 'Backlog is falling after the self-serve billing flow and routing updates reduced triage load.',
46
+ detail: 'AI focus summary: support backlog fell to 24 tickets, down 31%, after self-serve billing and routing improvements.',
47
+ },
48
+ ];
49
+
50
+ const deals: Deal[] = [
51
+ { id: 'acme', company: 'Acme Foods', stage: 'Security review', value: '$84k', owner: 'Nina' },
52
+ { id: 'northstar', company: 'Northstar Health', stage: 'Champion identified', value: '$122k', owner: 'Sam' },
53
+ { id: 'lattice', company: 'Lattice Cloud', stage: 'Commercials', value: '$61k', owner: 'Aria' },
54
+ { id: 'meridian', company: 'Meridian Retail', stage: 'Pilot live', value: '$48k', owner: 'Jon' },
55
+ ];
56
+
57
+ function askableMeta(meta: Record<string, string>) {
58
+ return JSON.stringify(meta);
59
+ }
60
+
61
+ export default function App() {
62
+ const { ctx, promptContext } = useAskable();
63
+ const [selectedPanel, setSelectedPanel] = useState<string | null>(null);
64
+ const panelRefs = useRef<Record<string, HTMLElement | null>>({});
65
+
66
+ const currentContext = promptContext || 'No panel selected yet. Hover or click a KPI card or deal row to feed Askable context into CopilotKit.';
67
+ const recentContext = useMemo(() => {
68
+ const history = ctx.toHistoryContext(4);
69
+ return history || 'Recent UI history will appear here after a few interactions.';
70
+ }, [ctx, promptContext]);
71
+
72
+ useCopilotReadable(
73
+ {
74
+ description: 'The Askable UI panel the user is currently focused on.',
75
+ value: currentContext,
76
+ },
77
+ [currentContext],
78
+ );
79
+
80
+ useCopilotReadable(
81
+ {
82
+ description: 'Recent Askable UI focus history from newest to oldest.',
83
+ value: recentContext,
84
+ },
85
+ [recentContext],
86
+ );
87
+
88
+ function focusPanel(id: string) {
89
+ const panel = panelRefs.current[id];
90
+ if (!panel) return;
91
+ ctx.select(panel);
92
+ setSelectedPanel(id);
93
+ }
94
+
95
+ return (
96
+ <div className="app-shell">
97
+ <main className="workspace">
98
+ <section className="hero card">
99
+ <div>
100
+ <p className="eyebrow">askable-ui + CopilotKit starter</p>
101
+ <h1>Ship a UI-aware copilot in one scaffold.</h1>
102
+ <p className="hero-copy">
103
+ Hover or click any panel to update Askable context. CopilotKit receives the current focus and recent history via
104
+ <code> useCopilotReadable() </code>
105
+ so the assistant can answer about exactly what the user sees.
106
+ </p>
107
+ </div>
108
+ <div className="hero-actions">
109
+ <button type="button" onClick={() => focusPanel('pipeline-coverage')}>
110
+ Ask about pipeline
111
+ </button>
112
+ <button type="button" className="secondary" onClick={() => focusPanel('northstar')}>
113
+ Ask about Northstar
114
+ </button>
115
+ </div>
116
+ </section>
117
+
118
+ <section className="metrics-grid">
119
+ {metrics.map((metric) => (
120
+ <article
121
+ key={metric.id}
122
+ ref={(node) => {
123
+ panelRefs.current[metric.id] = node;
124
+ }}
125
+ className={`card metric-card ${selectedPanel === metric.id ? 'is-selected' : ''}`}
126
+ data-askable={askableMeta({
127
+ area: 'executive dashboard',
128
+ panel: metric.label,
129
+ value: metric.value,
130
+ delta: metric.delta,
131
+ })}
132
+ data-askable-text={metric.detail}
133
+ >
134
+ <div className="metric-head">
135
+ <span>{metric.label}</span>
136
+ <span className="delta">{metric.delta}</span>
137
+ </div>
138
+ <strong>{metric.value}</strong>
139
+ <p>{metric.narrative}</p>
140
+ <button type="button" className="link-button" onClick={() => focusPanel(metric.id)}>
141
+ Ask AI about this card
142
+ </button>
143
+ </article>
144
+ ))}
145
+ </section>
146
+
147
+ <section className="grid-two">
148
+ <article
149
+ className={`card chart-card ${selectedPanel === 'forecast' ? 'is-selected' : ''}`}
150
+ ref={(node) => {
151
+ panelRefs.current.forecast = node;
152
+ }}
153
+ data-askable={askableMeta({
154
+ area: 'forecast',
155
+ chart: 'quarterly revenue',
156
+ trend: 'up and to the right',
157
+ confidence: 'medium-high',
158
+ })}
159
+ data-askable-text="AI focus summary: quarterly revenue is rising steadily from $1.2M in Q1 to a projected $1.9M in Q4, with strongest momentum in enterprise expansions."
160
+ >
161
+ <div className="section-head">
162
+ <div>
163
+ <p className="eyebrow">Forecast</p>
164
+ <h2>Quarterly revenue outlook</h2>
165
+ </div>
166
+ <button type="button" className="link-button" onClick={() => focusPanel('forecast')}>
167
+ Ask AI about this chart
168
+ </button>
169
+ </div>
170
+ <div className="bars" aria-hidden="true">
171
+ <span style={{ height: '46%' }} />
172
+ <span style={{ height: '59%' }} />
173
+ <span style={{ height: '74%' }} />
174
+ <span style={{ height: '92%' }} />
175
+ </div>
176
+ <p className="chart-caption">Q4 upside comes from larger multi-product expansions rather than raw logo growth.</p>
177
+ </article>
178
+
179
+ <article className="card context-card">
180
+ <div className="section-head compact">
181
+ <div>
182
+ <p className="eyebrow">Live Askable context</p>
183
+ <h2>What CopilotKit can currently read</h2>
184
+ </div>
185
+ </div>
186
+ <pre>{currentContext}</pre>
187
+ <h3>Recent focus history</h3>
188
+ <pre>{recentContext}</pre>
189
+ </article>
190
+ </section>
191
+
192
+ <section className="card deals-card">
193
+ <div className="section-head">
194
+ <div>
195
+ <p className="eyebrow">Pipeline drilldown</p>
196
+ <h2>Priority deals</h2>
197
+ </div>
198
+ </div>
199
+ <div className="table-head">
200
+ <span>Account</span>
201
+ <span>Stage</span>
202
+ <span>Value</span>
203
+ <span>Owner</span>
204
+ </div>
205
+ <div className="deal-list">
206
+ {deals.map((deal) => (
207
+ <button
208
+ key={deal.id}
209
+ type="button"
210
+ ref={(node) => {
211
+ panelRefs.current[deal.id] = node;
212
+ }}
213
+ className={`deal-row ${selectedPanel === deal.id ? 'is-selected' : ''}`}
214
+ data-askable={askableMeta({
215
+ area: 'pipeline drilldown',
216
+ account: deal.company,
217
+ stage: deal.stage,
218
+ value: deal.value,
219
+ owner: deal.owner,
220
+ })}
221
+ data-askable-text={`AI focus summary: ${deal.company} is in ${deal.stage}, worth ${deal.value}, owned by ${deal.owner}.`}
222
+ onClick={() => focusPanel(deal.id)}
223
+ >
224
+ <span>{deal.company}</span>
225
+ <span>{deal.stage}</span>
226
+ <span>{deal.value}</span>
227
+ <span>{deal.owner}</span>
228
+ </button>
229
+ ))}
230
+ </div>
231
+ </section>
232
+ </main>
233
+
234
+ <aside className="copilot-panel">
235
+ <div className="copilot-panel-header">
236
+ <p className="eyebrow">CopilotKit sidebar</p>
237
+ <h2>Ask about whatever is selected</h2>
238
+ <p>
239
+ Add <code>OPENAI_API_KEY</code> to <code>.env</code> to enable responses from the local runtime.
240
+ </p>
241
+ </div>
242
+ <CopilotSidebar
243
+ instructions="You are a helpful revenue operations copilot. Use the Askable context and recent focus history to answer precisely about the currently selected panel. If no panel is selected, ask the user what they want to inspect."
244
+ defaultOpen
245
+ clickOutsideToClose={false}
246
+ labels={{
247
+ title: 'Askable Copilot',
248
+ initial: 'Ask about the highlighted KPI, chart, or deal.',
249
+ }}
250
+ />
251
+ </aside>
252
+ </div>
253
+ );
254
+ }
@@ -0,0 +1,16 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { CopilotKit } from '@copilotkit/react-core';
4
+ import '@copilotkit/react-ui/styles.css';
5
+ import './styles.css';
6
+ import App from './App';
7
+
8
+ const runtimeUrl = import.meta.env.VITE_COPILOT_RUNTIME_URL ?? 'http://localhost:3001/api/copilotkit';
9
+
10
+ createRoot(document.getElementById('root')!).render(
11
+ <StrictMode>
12
+ <CopilotKit runtimeUrl={runtimeUrl}>
13
+ <App />
14
+ </CopilotKit>
15
+ </StrictMode>,
16
+ );
@@ -0,0 +1,304 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4
+ background: #07111f;
5
+ color: #e5eefb;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ min-height: 100vh;
15
+ background:
16
+ radial-gradient(circle at top left, rgba(56, 189, 248, 0.18), transparent 24%),
17
+ radial-gradient(circle at top right, rgba(14, 165, 233, 0.15), transparent 18%),
18
+ linear-gradient(180deg, #07111f 0%, #0b1729 100%);
19
+ }
20
+
21
+ a {
22
+ color: inherit;
23
+ }
24
+
25
+ button,
26
+ input,
27
+ textarea {
28
+ font: inherit;
29
+ }
30
+
31
+ code,
32
+ pre {
33
+ font-family: 'SFMono-Regular', ui-monospace, SFMono-Regular, Menlo, monospace;
34
+ }
35
+
36
+ #root {
37
+ min-height: 100vh;
38
+ }
39
+
40
+ .app-shell {
41
+ display: grid;
42
+ grid-template-columns: minmax(0, 1fr) 420px;
43
+ min-height: 100vh;
44
+ }
45
+
46
+ .workspace {
47
+ padding: 32px;
48
+ display: grid;
49
+ gap: 24px;
50
+ }
51
+
52
+ .card {
53
+ border: 1px solid rgba(148, 163, 184, 0.22);
54
+ background: rgba(10, 17, 31, 0.78);
55
+ backdrop-filter: blur(14px);
56
+ border-radius: 24px;
57
+ padding: 24px;
58
+ box-shadow: 0 18px 48px rgba(2, 8, 23, 0.28);
59
+ }
60
+
61
+ .eyebrow {
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.14em;
64
+ font-size: 0.72rem;
65
+ color: #7dd3fc;
66
+ margin: 0 0 12px;
67
+ }
68
+
69
+ .hero {
70
+ display: flex;
71
+ justify-content: space-between;
72
+ gap: 24px;
73
+ align-items: flex-end;
74
+ }
75
+
76
+ .hero h1,
77
+ .section-head h2,
78
+ .context-card h2,
79
+ .copilot-panel-header h2 {
80
+ margin: 0;
81
+ font-size: clamp(1.6rem, 3vw, 2.8rem);
82
+ line-height: 1.05;
83
+ }
84
+
85
+ .hero-copy,
86
+ .copilot-panel-header p,
87
+ .metric-card p,
88
+ .chart-caption {
89
+ color: #b7c6dc;
90
+ line-height: 1.65;
91
+ }
92
+
93
+ .hero-actions {
94
+ display: flex;
95
+ gap: 12px;
96
+ flex-wrap: wrap;
97
+ }
98
+
99
+ button {
100
+ border: 0;
101
+ border-radius: 999px;
102
+ padding: 0.82rem 1.1rem;
103
+ background: linear-gradient(135deg, #38bdf8, #2563eb);
104
+ color: #eff6ff;
105
+ cursor: pointer;
106
+ transition: transform 140ms ease, box-shadow 140ms ease, opacity 140ms ease;
107
+ box-shadow: 0 12px 32px rgba(37, 99, 235, 0.28);
108
+ }
109
+
110
+ button:hover {
111
+ transform: translateY(-1px);
112
+ }
113
+
114
+ button.secondary,
115
+ .link-button {
116
+ background: rgba(30, 41, 59, 0.85);
117
+ color: #d8e7fb;
118
+ box-shadow: none;
119
+ }
120
+
121
+ .link-button {
122
+ padding-left: 0;
123
+ padding-right: 0;
124
+ background: transparent;
125
+ color: #7dd3fc;
126
+ }
127
+
128
+ .metrics-grid {
129
+ display: grid;
130
+ grid-template-columns: repeat(3, minmax(0, 1fr));
131
+ gap: 20px;
132
+ }
133
+
134
+ .metric-card strong {
135
+ display: block;
136
+ font-size: 2rem;
137
+ margin-bottom: 12px;
138
+ }
139
+
140
+ .metric-head,
141
+ .section-head,
142
+ .table-head,
143
+ .deal-row {
144
+ display: grid;
145
+ align-items: center;
146
+ }
147
+
148
+ .metric-head {
149
+ grid-template-columns: 1fr auto;
150
+ gap: 12px;
151
+ color: #d8e7fb;
152
+ margin-bottom: 18px;
153
+ }
154
+
155
+ .delta {
156
+ color: #7dd3fc;
157
+ font-size: 0.9rem;
158
+ }
159
+
160
+ .grid-two {
161
+ display: grid;
162
+ grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
163
+ gap: 20px;
164
+ }
165
+
166
+ .section-head {
167
+ grid-template-columns: 1fr auto;
168
+ gap: 16px;
169
+ margin-bottom: 18px;
170
+ }
171
+
172
+ .section-head.compact {
173
+ grid-template-columns: 1fr;
174
+ }
175
+
176
+ .bars {
177
+ height: 220px;
178
+ display: grid;
179
+ grid-template-columns: repeat(4, 1fr);
180
+ align-items: end;
181
+ gap: 18px;
182
+ padding: 12px 0 0;
183
+ }
184
+
185
+ .bars span {
186
+ border-radius: 20px 20px 10px 10px;
187
+ background: linear-gradient(180deg, #38bdf8 0%, #1d4ed8 100%);
188
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
189
+ }
190
+
191
+ .context-card pre {
192
+ margin: 0;
193
+ white-space: pre-wrap;
194
+ overflow-wrap: anywhere;
195
+ background: rgba(15, 23, 42, 0.9);
196
+ border: 1px solid rgba(148, 163, 184, 0.16);
197
+ border-radius: 18px;
198
+ padding: 14px;
199
+ color: #d9e7f8;
200
+ line-height: 1.55;
201
+ }
202
+
203
+ .context-card h3 {
204
+ margin: 18px 0 10px;
205
+ font-size: 0.95rem;
206
+ color: #8acdf3;
207
+ }
208
+
209
+ .table-head,
210
+ .deal-row {
211
+ grid-template-columns: 1.2fr 1fr 0.7fr 0.7fr;
212
+ gap: 12px;
213
+ }
214
+
215
+ .table-head {
216
+ color: #8da2c0;
217
+ font-size: 0.84rem;
218
+ text-transform: uppercase;
219
+ letter-spacing: 0.12em;
220
+ padding: 0 0 10px;
221
+ }
222
+
223
+ .deal-list {
224
+ display: grid;
225
+ gap: 12px;
226
+ }
227
+
228
+ .deal-row {
229
+ width: 100%;
230
+ text-align: left;
231
+ padding: 16px 18px;
232
+ border-radius: 18px;
233
+ background: rgba(15, 23, 42, 0.92);
234
+ border: 1px solid rgba(148, 163, 184, 0.18);
235
+ color: #e2ebf8;
236
+ }
237
+
238
+ .metric-card.is-selected,
239
+ .chart-card.is-selected,
240
+ .deal-row.is-selected {
241
+ border-color: rgba(56, 189, 248, 0.9);
242
+ box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.25), 0 18px 48px rgba(2, 8, 23, 0.28);
243
+ }
244
+
245
+ .copilot-panel {
246
+ border-left: 1px solid rgba(148, 163, 184, 0.18);
247
+ background: rgba(2, 6, 23, 0.75);
248
+ padding: 24px;
249
+ display: grid;
250
+ grid-template-rows: auto minmax(0, 1fr);
251
+ gap: 18px;
252
+ min-height: 100vh;
253
+ position: sticky;
254
+ top: 0;
255
+ }
256
+
257
+ .copilot-panel-header {
258
+ padding: 12px 4px 0;
259
+ }
260
+
261
+ .copilot-panel :where(.copilotKitWindow, .copilotKitSidebar, .copilotKitChat) {
262
+ height: 100%;
263
+ max-height: calc(100vh - 160px);
264
+ }
265
+
266
+ @media (max-width: 1180px) {
267
+ .app-shell {
268
+ grid-template-columns: 1fr;
269
+ }
270
+
271
+ .copilot-panel {
272
+ position: static;
273
+ min-height: auto;
274
+ border-left: 0;
275
+ border-top: 1px solid rgba(148, 163, 184, 0.18);
276
+ }
277
+ }
278
+
279
+ @media (max-width: 920px) {
280
+ .metrics-grid,
281
+ .grid-two {
282
+ grid-template-columns: 1fr;
283
+ }
284
+
285
+ .hero {
286
+ flex-direction: column;
287
+ align-items: flex-start;
288
+ }
289
+ }
290
+
291
+ @media (max-width: 720px) {
292
+ .workspace,
293
+ .copilot-panel {
294
+ padding: 18px;
295
+ }
296
+
297
+ .table-head {
298
+ display: none;
299
+ }
300
+
301
+ .deal-row {
302
+ grid-template-columns: 1fr 1fr;
303
+ }
304
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src"],
20
+ "references": [{ "path": "./tsconfig.server.json" }]
21
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "rootDir": "server",
8
+ "outDir": "dist-server",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["server/**/*.ts"]
16
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ });