cleanup - removing ai agent for deterministic code and better formatting
This commit is contained in:
@@ -5,14 +5,6 @@
|
|||||||
# When running via docker-compose, this is set for you in docker-compose.yml.
|
# When running via docker-compose, this is set for you in docker-compose.yml.
|
||||||
DATABASE_URL=postgres://sde:sde_password@localhost:5432/sde_toolkit
|
DATABASE_URL=postgres://sde:sde_password@localhost:5432/sde_toolkit
|
||||||
|
|
||||||
# --- Anthropic API (for the "AI draft" feature) ---
|
|
||||||
# Get a key from https://console.anthropic.com/ . Without it, the AI button
|
|
||||||
# fails politely and everything else keeps working.
|
|
||||||
ANTHROPIC_API_KEY=
|
|
||||||
|
|
||||||
# Model used for AI drafting.
|
|
||||||
ANTHROPIC_MODEL=claude-sonnet-5
|
|
||||||
|
|
||||||
# --- Server ---
|
# --- Server ---
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A shared, multi-user web app with two meeting tools:
|
A shared, multi-user web app with two meeting tools:
|
||||||
|
|
||||||
- **Field Problem Workshop** — capture field problems, cluster them into root problems, dot-vote, build "breadcrumb" trails from problem to measurable outcome, and assign actions. Includes an optional AI draft feature.
|
- **Field Problem Workshop** — capture field problems, cluster them into root problems, dot-vote, build "breadcrumb" trails from problem to measurable outcome, and assign actions. A "Suggest fields" button fills each breadcrumb from built-in construction-execution methodology (no external service).
|
||||||
- **Scope Lock Meeting Suite** — Scope Boundary Board, MVP Priority Ranker, Decision Registry, and Micron Pilot Readiness.
|
- **Scope Lock Meeting Suite** — Scope Boundary Board, MVP Priority Ranker, Decision Registry, and Micron Pilot Readiness.
|
||||||
|
|
||||||
Everyone who opens the same link works from the **same data**, stored on a shared server. Edits autosave, and a version check stops two people from silently overwriting each other. The original single-file HTML versions are kept in [`legacy/`](./legacy) for reference.
|
Everyone who opens the same link works from the **same data**, stored on a shared server. Edits autosave, and a version check stops two people from silently overwriting each other. The original single-file HTML versions are kept in [`legacy/`](./legacy) for reference.
|
||||||
@@ -32,14 +32,14 @@ docker compose logs -f app # watch app logs
|
|||||||
|
|
||||||
Data persists in a Docker volume (`db_data`), so it survives restarts.
|
Data persists in a Docker volume (`db_data`), so it survives restarts.
|
||||||
|
|
||||||
To enable the **AI draft** feature, provide an Anthropic API key before starting — either export `ANTHROPIC_API_KEY` in your shell or put it in a `.env` file next to `docker-compose.yml`. Without a key the AI button simply fails politely; everything else works.
|
There are no API keys or external accounts to set up. The "Suggest fields" button runs entirely in-app from a built-in methodology table.
|
||||||
|
|
||||||
## Local development (without Docker)
|
## Local development (without Docker)
|
||||||
|
|
||||||
Needs Node 20+ and a PostgreSQL you can reach.
|
Needs Node 20+ and a PostgreSQL you can reach.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env # edit DATABASE_URL (and ANTHROPIC_API_KEY if wanted)
|
cp .env.example .env # edit DATABASE_URL
|
||||||
npm install
|
npm install
|
||||||
psql "$DATABASE_URL" -f db/init.sql # create the schema once
|
psql "$DATABASE_URL" -f db/init.sql # create the schema once
|
||||||
npm run dev # http://localhost:5173
|
npm run dev # http://localhost:5173
|
||||||
@@ -54,8 +54,6 @@ All config is via environment variables — see [`.env.example`](./.env.example)
|
|||||||
| Variable | Purpose |
|
| Variable | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `DATABASE_URL` | Postgres connection string. Set automatically by docker-compose. |
|
| `DATABASE_URL` | Postgres connection string. Set automatically by docker-compose. |
|
||||||
| `ANTHROPIC_API_KEY` | Enables the "AI draft" button. Blank = feature disabled. |
|
|
||||||
| `ANTHROPIC_MODEL` | Model for AI drafting (default `claude-sonnet-5`). |
|
|
||||||
| `PORT` | Server port (default 3000). |
|
| `PORT` | Server port (default 3000). |
|
||||||
| `ORIGIN` | Public URL the app is served from; used to validate POST origins. |
|
| `ORIGIN` | Public URL the app is served from; used to validate POST origins. |
|
||||||
|
|
||||||
@@ -67,6 +65,7 @@ src/
|
|||||||
app.html page shell
|
app.html page shell
|
||||||
lib/
|
lib/
|
||||||
types.ts data shapes for both tools
|
types.ts data shapes for both tools
|
||||||
|
breadcrumbs.ts methodology table + keyword classifier ("Suggest fields")
|
||||||
workspace.svelte.ts client sync: load / autosave / poll / conflict handling
|
workspace.svelte.ts client sync: load / autosave / poll / conflict handling
|
||||||
toast.svelte.ts shared toast controller
|
toast.svelte.ts shared toast controller
|
||||||
components/ Toast.svelte, SyncBadge.svelte
|
components/ Toast.svelte, SyncBadge.svelte
|
||||||
@@ -74,7 +73,6 @@ src/
|
|||||||
server/
|
server/
|
||||||
db/index.ts Postgres connection pool (lazy)
|
db/index.ts Postgres connection pool (lazy)
|
||||||
db/workspaces.ts load / save / reset with optimistic concurrency
|
db/workspaces.ts load / save / reset with optimistic concurrency
|
||||||
ai.ts Anthropic proxy (holds the API key server-side)
|
|
||||||
routes/
|
routes/
|
||||||
+layout.svelte imports global CSS
|
+layout.svelte imports global CSS
|
||||||
+page.svelte home / tool picker
|
+page.svelte home / tool picker
|
||||||
@@ -83,7 +81,6 @@ src/
|
|||||||
api/
|
api/
|
||||||
workspaces/[tool]/[name]/+server.ts GET (load) + PUT (save)
|
workspaces/[tool]/[name]/+server.ts GET (load) + PUT (save)
|
||||||
workspaces/[tool]/[name]/reset/+server.ts POST (reset to baseline)
|
workspaces/[tool]/[name]/reset/+server.ts POST (reset to baseline)
|
||||||
ai/draft/+server.ts POST (AI breadcrumb draft)
|
|
||||||
db/init.sql database schema (runs once on first DB startup)
|
db/init.sql database schema (runs once on first DB startup)
|
||||||
static/favicon.svg
|
static/favicon.svg
|
||||||
Dockerfile, docker-compose.yml
|
Dockerfile, docker-compose.yml
|
||||||
@@ -108,7 +105,7 @@ It requires, on your Gitea instance: Actions enabled with a registered `act_runn
|
|||||||
|
|
||||||
## The `.skill` file
|
## The `.skill` file
|
||||||
|
|
||||||
`legacy/construction-breadcrumbs.skill` is a **Claude skill** (a zip of instructions + a methodology knowledge base). It is not part of the website — it teaches Claude how to draft breadcrumb fields using construction methodology (EVM, CII AWP, Last Planner, first-time quality). The same knowledge is embedded server-side in `src/lib/server/ai.ts`, which powers the app's AI draft button. Install the skill in Claude (via "Save skill") to use the methodology in any chat.
|
`legacy/construction-breadcrumbs.skill` is a **Claude skill** (a zip of instructions + a methodology knowledge base). It is not part of the website — it teaches Claude how to draft breadcrumb fields using construction methodology (EVM, CII AWP, Last Planner, first-time quality). The same methodology is encoded, deterministically and offline, in [`src/lib/breadcrumbs.ts`](./src/lib/breadcrumbs.ts), which powers the app's "Suggest fields" button. Install the skill in Claude (via "Save skill") to use the methodology in any chat.
|
||||||
|
|
||||||
## Possible next steps
|
## Possible next steps
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgres://sde:sde_password@db:5432/sde_toolkit
|
DATABASE_URL: postgres://sde:sde_password@db:5432/sde_toolkit
|
||||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
||||||
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-5}
|
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
ORIGIN: ${ORIGIN:-http://localhost:3000}
|
ORIGIN: ${ORIGIN:-http://localhost:3000}
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -224,6 +224,7 @@
|
|||||||
<div class="brand"><strong>Project SDE</strong> <span>| Scope Lock Meeting Suite</span></div>
|
<div class="brand"><strong>Project SDE</strong> <span>| Scope Lock Meeting Suite</span></div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="meta">Micron Pilot | Innovation Team</div>
|
<div class="meta">Micron Pilot | Innovation Team</div>
|
||||||
|
<button class="hdr-btn" id="btnSaveJson">Save JSON</button>
|
||||||
<button class="hdr-btn" id="btnReset">Reset all</button>
|
<button class="hdr-btn" id="btnReset">Reset all</button>
|
||||||
<button class="hdr-btn primary" id="btnExport">Export meeting summary</button>
|
<button class="hdr-btn primary" id="btnExport">Export meeting summary</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -658,6 +659,18 @@ document.getElementById('btnAddReady').addEventListener('click', ()=>{
|
|||||||
});
|
});
|
||||||
document.getElementById('newReadyItem').addEventListener('keydown', e=>{ if(e.key==='Enter') document.getElementById('btnAddReady').click(); });
|
document.getElementById('newReadyItem').addEventListener('keydown', e=>{ if(e.key==='Enter') document.getElementById('btnAddReady').click(); });
|
||||||
|
|
||||||
|
/* ================= SAVE JSON (raw data dump for migration/import) ================= */
|
||||||
|
document.getElementById('btnSaveJson').addEventListener('click', ()=>{
|
||||||
|
const stamp = new Date().toISOString().slice(0,10);
|
||||||
|
const blob = new Blob([JSON.stringify(S, null, 2)], {type:'application/json'});
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = `SDE_Scope_Lock_state_${stamp}.json`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
toast('State saved to JSON');
|
||||||
|
});
|
||||||
|
|
||||||
/* ================= EXPORT ================= */
|
/* ================= EXPORT ================= */
|
||||||
document.getElementById('btnExport').addEventListener('click', ()=>{
|
document.getElementById('btnExport').addEventListener('click', ()=>{
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
|
|||||||
376
package-lock.json
generated
376
package-lock.json
generated
@@ -8,7 +8,6 @@
|
|||||||
"name": "sde-meeting-toolkit",
|
"name": "sde-meeting-toolkit",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.32.0",
|
|
||||||
"pg": "^8.13.0"
|
"pg": "^8.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -22,21 +21,6 @@
|
|||||||
"vite": "^5.4.0"
|
"vite": "^5.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@anthropic-ai/sdk": {
|
|
||||||
"version": "0.32.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz",
|
|
||||||
"integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "^18.11.18",
|
|
||||||
"@types/node-fetch": "^2.6.4",
|
|
||||||
"abort-controller": "^3.0.0",
|
|
||||||
"agentkeepalive": "^4.2.1",
|
|
||||||
"form-data-encoder": "1.7.2",
|
|
||||||
"formdata-node": "^4.3.2",
|
|
||||||
"node-fetch": "^2.6.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
@@ -1136,21 +1120,12 @@
|
|||||||
"version": "18.19.130",
|
"version": "18.19.130",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node-fetch": {
|
|
||||||
"version": "2.6.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
|
|
||||||
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*",
|
|
||||||
"form-data": "^4.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg": {
|
"node_modules/@types/pg": {
|
||||||
"version": "8.20.0",
|
"version": "8.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
|
||||||
@@ -1177,18 +1152,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/abort-controller": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"event-target-shim": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.17.0",
|
"version": "8.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
||||||
@@ -1202,18 +1165,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/agentkeepalive": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
|
|
||||||
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"humanize-ms": "^1.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/aria-query": {
|
"node_modules/aria-query": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
||||||
@@ -1224,12 +1175,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -1240,19 +1185,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -1279,18 +1211,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/combined-stream": {
|
|
||||||
"version": "1.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/commondir": {
|
"node_modules/commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
@@ -1336,15 +1256,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/devalue": {
|
"node_modules/devalue": {
|
||||||
"version": "5.8.2",
|
"version": "5.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.2.tgz",
|
||||||
@@ -1352,65 +1263,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dunder-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"gopd": "^1.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-define-property": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-errors": {
|
"node_modules/es-errors": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-set-tostringtag": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"get-intrinsic": "^1.2.6",
|
|
||||||
"has-tostringtag": "^1.0.2",
|
|
||||||
"hasown": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
@@ -1482,15 +1344,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/event-target-shim": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -1509,41 +1362,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"es-set-tostringtag": "^2.1.0",
|
|
||||||
"hasown": "^2.0.4",
|
|
||||||
"mime-types": "^2.1.35"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/form-data-encoder": {
|
|
||||||
"version": "1.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
|
|
||||||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/formdata-node": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"node-domexception": "1.0.0",
|
|
||||||
"web-streams-polyfill": "4.0.0-beta.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -1563,91 +1381,17 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
|
||||||
"es-define-property": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"es-object-atoms": "^1.1.1",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-proto": "^1.0.1",
|
|
||||||
"gopd": "^1.2.0",
|
|
||||||
"has-symbols": "^1.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"math-intrinsics": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/get-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"dunder-proto": "^1.0.1",
|
|
||||||
"es-object-atoms": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gopd": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-symbols": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-tostringtag": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"has-symbols": "^1.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -1656,15 +1400,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/humanize-ms": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.16.2",
|
"version": "2.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
|
||||||
@@ -1725,36 +1460,6 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/math-intrinsics": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mri": {
|
"node_modules/mri": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||||
@@ -1779,6 +1484,7 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -1800,46 +1506,6 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-domexception": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
|
||||||
"deprecated": "Use your platform's native DOMException instead",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/jimmywarting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://paypal.me/jimmywarting"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-fetch": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"whatwg-url": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "4.x || >=6.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"encoding": "^0.1.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"encoding": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
@@ -2245,12 +1911,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tr46": {
|
|
||||||
"version": "0.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
@@ -2269,6 +1929,7 @@
|
|||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
@@ -2351,31 +2012,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/web-streams-polyfill": {
|
|
||||||
"version": "4.0.0-beta.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
|
||||||
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webidl-conversions": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
|
||||||
"license": "BSD-2-Clause"
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-url": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tr46": "~0.0.3",
|
|
||||||
"webidl-conversions": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
"vite": "^5.4.0"
|
"vite": "^5.4.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.32.0",
|
|
||||||
"pg": "^8.13.0"
|
"pg": "^8.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,19 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
min-height: 100vh;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|||||||
292
src/lib/breadcrumbs.ts
Normal file
292
src/lib/breadcrumbs.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// Deterministic breadcrumb suggestions — no external AI.
|
||||||
|
//
|
||||||
|
// This replaces the old Anthropic-backed "AI draft" feature. The methodology it
|
||||||
|
// encodes is exactly what mattered: seven construction-execution domains, each
|
||||||
|
// with a canonical value driver, capability, metric, method, and baseline drawn
|
||||||
|
// from established practice (Earned Value Management, CII Advanced Work
|
||||||
|
// Packaging, Last Planner System, PMBOK change control, first-time quality,
|
||||||
|
// CII lessons-learned). A problem statement is classified into a domain by
|
||||||
|
// keyword, then that domain's template fills the breadcrumb fields. The team
|
||||||
|
// then reviews and edits — same as before, minus the API key, tokens, and
|
||||||
|
// network round-trip.
|
||||||
|
//
|
||||||
|
// The metric text is always written as an outcome, never adoption language, so
|
||||||
|
// it passes the adoption gate the UI already enforces.
|
||||||
|
|
||||||
|
export const BREADCRUMB_FIELDS = ['driver', 'cap', 'metric', 'method', 'baseline'] as const;
|
||||||
|
export type BreadcrumbField = (typeof BREADCRUMB_FIELDS)[number];
|
||||||
|
|
||||||
|
export interface Domain {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
/** Lowercased keywords/phrases that steer classification toward this domain. */
|
||||||
|
keywords: string[];
|
||||||
|
driver: string;
|
||||||
|
cap: string;
|
||||||
|
metric: string;
|
||||||
|
method: string;
|
||||||
|
baseline: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Suggestion {
|
||||||
|
domain: string;
|
||||||
|
label: string;
|
||||||
|
driver: string;
|
||||||
|
cap: string;
|
||||||
|
metric: string;
|
||||||
|
method: string;
|
||||||
|
baseline: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The catch-all used when nothing matches or the user picks "not sure". It fills
|
||||||
|
// the fields with the gates themselves, so the card is still useful guidance.
|
||||||
|
const GENERAL: Domain = {
|
||||||
|
id: 'general',
|
||||||
|
label: 'General / not sure',
|
||||||
|
keywords: [],
|
||||||
|
driver:
|
||||||
|
'The business end this should improve — state it in cost, schedule, quality, safety, or predictability terms, not as a tool.',
|
||||||
|
cap: 'The capability needed to move that outcome — a function, not a product or vendor name.',
|
||||||
|
metric: 'An outcome ratio or rate that moves — never "implemented", "adopted", "rolled out", or "active users".',
|
||||||
|
method: 'Data source + comparison + cadence for measuring the metric.',
|
||||||
|
baseline:
|
||||||
|
'A number, or the cheapest defensible pre-pilot study that produces one (poll < record trace < log audit < 2-week coding < sampling study). Set targets after the baseline, not before.'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DOMAINS: Domain[] = [
|
||||||
|
{
|
||||||
|
id: 'progress-visibility',
|
||||||
|
label: 'Progress visibility',
|
||||||
|
keywords: [
|
||||||
|
'progress',
|
||||||
|
'percent complete',
|
||||||
|
'% complete',
|
||||||
|
'percent-complete',
|
||||||
|
'claiming',
|
||||||
|
'reported',
|
||||||
|
'rules of credit',
|
||||||
|
'forecast',
|
||||||
|
'earned value',
|
||||||
|
's-curve',
|
||||||
|
'roll-up',
|
||||||
|
'rollup',
|
||||||
|
'status'
|
||||||
|
],
|
||||||
|
driver:
|
||||||
|
'Trustworthy progress information so cost and schedule forecasts hold up and decisions are not made on inflated percent-complete.',
|
||||||
|
cap: 'Objective progress measurement with rules of credit tied to verifiable field milestones.',
|
||||||
|
metric: 'Claiming accuracy — reported % versus field-verified % within tolerance, by phase.',
|
||||||
|
method: 'Monthly spot audit of 30+ sampled assets: compare reported % against a field walkdown.',
|
||||||
|
baseline: 'The first audit establishes the baseline claiming gap.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'data-freshness',
|
||||||
|
label: 'Data freshness / entry burden',
|
||||||
|
keywords: [
|
||||||
|
'data entry',
|
||||||
|
'stale',
|
||||||
|
'out of date',
|
||||||
|
'duplicate entry',
|
||||||
|
're-enter',
|
||||||
|
'reenter',
|
||||||
|
'double entry',
|
||||||
|
'paper',
|
||||||
|
'spreadsheet',
|
||||||
|
'manual entry',
|
||||||
|
'latency',
|
||||||
|
'single source',
|
||||||
|
'transcribe',
|
||||||
|
'rekey'
|
||||||
|
],
|
||||||
|
driver:
|
||||||
|
'Decisions made on current field reality, with data captured once instead of burdening crews with duplicate entry.',
|
||||||
|
cap: 'A single source of truth with data entered once at the point of work.',
|
||||||
|
metric: 'Entry latency — % of updates recorded within 24 hours of the work completing.',
|
||||||
|
method: 'Compare system timestamp against actual completion date, sampled weekly.',
|
||||||
|
baseline: 'Audit the current paper-to-digital lag on this project before the pilot.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'constraint-readiness',
|
||||||
|
label: 'Constraint readiness / work release',
|
||||||
|
keywords: [
|
||||||
|
'constraint',
|
||||||
|
'ready to work',
|
||||||
|
'work package',
|
||||||
|
'iwp',
|
||||||
|
'material',
|
||||||
|
'tools',
|
||||||
|
'access',
|
||||||
|
'permit',
|
||||||
|
'scaffolding',
|
||||||
|
'release',
|
||||||
|
'prerequisite',
|
||||||
|
'work front',
|
||||||
|
'stand-down',
|
||||||
|
'stand down'
|
||||||
|
],
|
||||||
|
driver:
|
||||||
|
'Crews start only constraint-free work, so time is not lost to missing material, tools, access, or prerequisites.',
|
||||||
|
cap: 'A release gate that verifies every constraint is cleared before a work package is issued.',
|
||||||
|
metric: '% of work packages released constraint-free (no later constraint-driven stop).',
|
||||||
|
method: 'Weekly release-gate checklist audit; a later constraint stop counts as a miss.',
|
||||||
|
baseline: 'Audit the last month of work starts from the daily logs.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'planning-resources',
|
||||||
|
label: 'Planning / resources / idle time',
|
||||||
|
keywords: [
|
||||||
|
'plan',
|
||||||
|
'planning',
|
||||||
|
'commitment',
|
||||||
|
'schedule reliability',
|
||||||
|
'idle',
|
||||||
|
'waiting',
|
||||||
|
'wait',
|
||||||
|
'crew',
|
||||||
|
'resource',
|
||||||
|
'look ahead',
|
||||||
|
'lookahead',
|
||||||
|
'promise',
|
||||||
|
'weekly plan',
|
||||||
|
'ppc',
|
||||||
|
'churn'
|
||||||
|
],
|
||||||
|
driver: 'Predictable weekly output, so crews are not idle and downstream work is not disrupted.',
|
||||||
|
cap: 'Collaborative short-interval planning with commitments tracked and variances learned from.',
|
||||||
|
metric: 'PPC — % of weekly commitments completed as promised.',
|
||||||
|
method: 'Log commitments at weekly planning, score the following week, publish the trend.',
|
||||||
|
baseline: 'The first 4 weeks of logging establish the baseline PPC.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'scope-change',
|
||||||
|
label: 'Scope control / change',
|
||||||
|
keywords: [
|
||||||
|
'scope',
|
||||||
|
'change order',
|
||||||
|
'unauthorized',
|
||||||
|
'extra work',
|
||||||
|
't&m',
|
||||||
|
'backcharge',
|
||||||
|
'out of scope',
|
||||||
|
'variation',
|
||||||
|
'change log',
|
||||||
|
'co log'
|
||||||
|
],
|
||||||
|
driver: 'Changes are authorized and captured before work proceeds, protecting margin and schedule.',
|
||||||
|
cap: 'A change-control loop that logs and prices scope changes at the time they occur.',
|
||||||
|
metric: 'Unauthorized-work hours or dollars performed before a change was authorized.',
|
||||||
|
method: 'Monthly reconciliation of field records against the change-order log.',
|
||||||
|
baseline: 'Run the reconciliation on the current project to establish the baseline.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quality-rework',
|
||||||
|
label: 'Quality / rework',
|
||||||
|
keywords: [
|
||||||
|
'quality',
|
||||||
|
'rework',
|
||||||
|
'defect',
|
||||||
|
'punch',
|
||||||
|
'punchlist',
|
||||||
|
'punch list',
|
||||||
|
'redo',
|
||||||
|
'first pass',
|
||||||
|
'first-time',
|
||||||
|
'inspection',
|
||||||
|
'qc',
|
||||||
|
'qa',
|
||||||
|
'out of rev',
|
||||||
|
'out-of-rev',
|
||||||
|
'revision',
|
||||||
|
'nonconformance',
|
||||||
|
'ncr',
|
||||||
|
'out of spec',
|
||||||
|
'wrong'
|
||||||
|
],
|
||||||
|
driver: 'Work done right the first time, cutting the craft hours lost to rework.',
|
||||||
|
cap: 'First-time-quality controls with rework captured and its causes coded.',
|
||||||
|
metric: 'Rework as a % of craft hours.',
|
||||||
|
method: 'A dedicated rework cost code with reason coding, reviewed monthly.',
|
||||||
|
baseline: 'Create the cost code; the first month of coding is the baseline.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'knowledge-feedback',
|
||||||
|
label: 'Knowledge / feedback',
|
||||||
|
keywords: [
|
||||||
|
'lessons learned',
|
||||||
|
'lesson learned',
|
||||||
|
'closeout',
|
||||||
|
'close-out',
|
||||||
|
'handover',
|
||||||
|
'handoff',
|
||||||
|
'knowledge',
|
||||||
|
'feedback',
|
||||||
|
'estimate',
|
||||||
|
'historical',
|
||||||
|
'punch closure',
|
||||||
|
'as-built',
|
||||||
|
'as built',
|
||||||
|
'documentation',
|
||||||
|
'chain of custody',
|
||||||
|
'design intent'
|
||||||
|
],
|
||||||
|
driver: 'Lessons and actuals feed the next project, so estimates and plans improve over time.',
|
||||||
|
cap: 'A closeout and feedback loop that captures actuals and routes them to estimating.',
|
||||||
|
metric: 'Closeout compliance — % of closeouts completed within 60 days.',
|
||||||
|
method: 'Closeout checklist audit against project completion dates.',
|
||||||
|
baseline: 'Audit the last 4–6 projects to establish the baseline.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Domain options for a UI picker: the seven domains plus the catch-all. */
|
||||||
|
export const DOMAIN_OPTIONS: { id: string; label: string }[] = [
|
||||||
|
...DOMAINS.map((d) => ({ id: d.id, label: d.label })),
|
||||||
|
{ id: GENERAL.id, label: GENERAL.label }
|
||||||
|
];
|
||||||
|
|
||||||
|
function domainById(id: string): Domain | undefined {
|
||||||
|
if (id === GENERAL.id) return GENERAL;
|
||||||
|
return DOMAINS.find((d) => d.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classify a problem into a domain by counting keyword hits. Returns the domain
|
||||||
|
* id with the most matches, or 'general' if nothing matches. `related` issues
|
||||||
|
* are weighted lightly so they can break ties but not overrule the statement.
|
||||||
|
*/
|
||||||
|
export function classifyDomain(prob: string, related: string[] = []): string {
|
||||||
|
const main = String(prob).toLowerCase();
|
||||||
|
const rel = related.map((r) => String(r).toLowerCase()).join(' \n ');
|
||||||
|
|
||||||
|
let best = GENERAL.id;
|
||||||
|
let bestScore = 0;
|
||||||
|
for (const d of DOMAINS) {
|
||||||
|
let score = 0;
|
||||||
|
for (const kw of d.keywords) {
|
||||||
|
if (main.includes(kw)) score += 2;
|
||||||
|
if (rel.includes(kw)) score += 1;
|
||||||
|
}
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
best = d.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the five breadcrumb fields from methodology. If `forceDomain` is a real
|
||||||
|
* domain id, that domain's template is used; otherwise the problem is classified
|
||||||
|
* automatically. The result is a starting draft for the team to edit.
|
||||||
|
*/
|
||||||
|
export function suggestBreadcrumb(prob: string, related: string[] = [], forceDomain = ''): Suggestion {
|
||||||
|
const domain = domainById(forceDomain) ?? domainById(classifyDomain(prob, related)) ?? GENERAL;
|
||||||
|
return {
|
||||||
|
domain: domain.id,
|
||||||
|
label: domain.label,
|
||||||
|
driver: domain.driver,
|
||||||
|
cap: domain.cap,
|
||||||
|
metric: domain.metric,
|
||||||
|
method: domain.method,
|
||||||
|
baseline: domain.baseline
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
// Server-side "AI draft" for breadcrumb fields. The Anthropic API key lives here
|
|
||||||
// (from an env var) and never reaches the browser.
|
|
||||||
|
|
||||||
import Anthropic from '@anthropic-ai/sdk';
|
|
||||||
import { env } from '$env/dynamic/private';
|
|
||||||
|
|
||||||
export const REQUIRED_FIELDS = ['driver', 'cap', 'metric', 'method', 'baseline'] as const;
|
|
||||||
export const ADOPTION_WORDS =
|
|
||||||
/implement|adopt|roll ?out|have a tool|tool exists|usage|active users?|users? active/i;
|
|
||||||
|
|
||||||
export interface DraftResult {
|
|
||||||
domain: string;
|
|
||||||
driver: string;
|
|
||||||
cap: string;
|
|
||||||
metric: string;
|
|
||||||
method: string;
|
|
||||||
baseline: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHOD_KB = `Domain playbook (anchor methodology -> standard metrics/methods/baselines):
|
|
||||||
1 PROGRESS VISIBILITY - Earned Value Mgmt, rules of credit. Metrics: claiming accuracy (reported % vs field-verified % within tolerance, by phase; method: monthly spot audit of 30+ sampled assets; baseline: first audit); forecast reliability (forecast vs actual completion; baseline: last project's reports). Never use "% of assets with a status" (adoption in disguise).
|
|
||||||
2 DATA FRESHNESS / ENTRY BURDEN - lean data flow, single source of truth. Metrics: entry latency (% of updates within 24h of completion; method: system timestamp vs completion date, weekly; baseline: audit paper-to-digital lag on current project); touch count (systems the same record is entered into; method: trace 10 records end to end; baseline: pre-pilot trace); non-productive data minutes/person/day (method+baseline: structured field poll pre-pilot, repeat midpoint).
|
|
||||||
3 CONSTRAINT READINESS / WORK RELEASE - CII AWP IR 272-2, constraint-free IWP release (drawings, material, tools, labor, access, predecessors, permits, quality docs, scaffolding). Metrics: % releases issued constraint-free (method: release-gate checklist audit weekly, later constraint stop = miss; baseline: audit last month of work starts from daily logs); stand-down count/week (method: foreman log with fixed reason list; baseline: 2-week logging exercise); tool time % (method: activity sampling study; baseline: pre-pilot study; propose only when stakes justify cost).
|
|
||||||
4 PLANNING / RESOURCES / IDLE - Last Planner System. Metrics: PPC (% weekly commitments completed as promised; method: log at weekly planning, score next week, publish trend; baseline: first 4 weeks of logging); reasons-for-variance distribution (fixed-list coding of misses); idle hours/crew-week (timecard or foreman reason codes; baseline: 2-week coding).
|
|
||||||
5 SCOPE CONTROL / CHANGE - PMBOK change control, CII change research. Metrics: unauthorized-work hours/dollars at time of performance (method: monthly reconciliation of field records vs CO log; baseline: run reconciliation on current project); change capture latency days (field record date vs log date); change cycle time (log timestamps; baseline: last project's log).
|
|
||||||
6 QUALITY / REWORK - first-time quality, CII rework research (2-6% direct cost where measured). Metrics: rework % of craft hours (method: dedicated rework cost code + reason coding, monthly; baseline: create the code, first month is baseline); first-pass yield at QC gates; out-of-rev incidents (QC walkdown coding).
|
|
||||||
7 KNOWLEDGE / FEEDBACK - CII lessons-learned, estimate feedback. Metrics: closeout compliance % within 60 days (method: closeout checklist audit; baseline: audit last 4-6 projects); % estimates referencing prior actuals (estimating file audit); punch closure median days (issue log timestamps; if untimestamped, that gap is the finding).
|
|
||||||
GATES: driver = business end in cost/schedule/quality/safety/predictability terms, never a tool. capability = function not product, no vendor names. metric = outcome ratio/rate, NEVER "implemented/adopted/rolled out/active users/have a tool". method = data source + comparison + cadence. baseline = a number or the named pre-pilot study that produces one (cheapest defensible: poll < record trace < log audit < 2-week coding < sampling study). Set targets after baseline, not before. Each field under ~30 words except method/baseline.`;
|
|
||||||
|
|
||||||
function parseJson(text: string): Record<string, unknown> {
|
|
||||||
const clean = String(text).replace(/```json|```/g, '').trim();
|
|
||||||
const candidates = ['[', '{'].map((ch) => {
|
|
||||||
const i = clean.indexOf(ch);
|
|
||||||
return i === -1 ? Infinity : i;
|
|
||||||
});
|
|
||||||
const start = Math.min(...candidates);
|
|
||||||
return JSON.parse(start === Infinity ? clean : clean.slice(start));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** True when the AI feature is configured. Used to disable the button gracefully. */
|
|
||||||
export function aiConfigured(): boolean {
|
|
||||||
return Boolean(env.ANTHROPIC_API_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function draftBreadcrumb(prob: string, related: string[]): Promise<DraftResult> {
|
|
||||||
const apiKey = env.ANTHROPIC_API_KEY;
|
|
||||||
if (!apiKey) throw new Error('AI is not configured on this server (missing ANTHROPIC_API_KEY).');
|
|
||||||
if (!prob.trim()) throw new Error('Write the problem statement first.');
|
|
||||||
|
|
||||||
const client = new Anthropic({ apiKey });
|
|
||||||
const rel = related.length
|
|
||||||
? `\nRelated issues in this cluster:\n${related.map((r) => '- ' + r).join('\n')}`
|
|
||||||
: '';
|
|
||||||
const prompt = `You populate breadcrumb trails for construction field problems at an industrial I&C contractor, using best-in-class execution methodology.\n\n${METHOD_KB}\n\nProblem statement:\n${prob}${rel}\n\nClassify the problem into one domain above, then draft the five fields per that domain's standard metrics, methods, and baselines, obeying every GATE. Respond ONLY with a JSON object, no prose, no markdown fences:\n{"domain":"...","driver":"...","cap":"...","metric":"...","method":"...","baseline":"..."}`;
|
|
||||||
|
|
||||||
const message = await client.messages.create({
|
|
||||||
model: env.ANTHROPIC_MODEL || 'claude-sonnet-5',
|
|
||||||
max_tokens: 1000,
|
|
||||||
messages: [{ role: 'user', content: prompt }]
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = message.content
|
|
||||||
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
||||||
.map((block) => block.text)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const obj = parseJson(text);
|
|
||||||
for (const f of REQUIRED_FIELDS) {
|
|
||||||
if (!obj[f]) throw new Error('AI returned an incomplete draft; try again.');
|
|
||||||
}
|
|
||||||
if (ADOPTION_WORDS.test(String(obj.metric))) {
|
|
||||||
throw new Error('Draft failed the adoption gate; try again.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
domain: String(obj.domain ?? 'methodology'),
|
|
||||||
driver: String(obj.driver),
|
|
||||||
cap: String(obj.cap),
|
|
||||||
metric: String(obj.metric),
|
|
||||||
method: String(obj.method),
|
|
||||||
baseline: String(obj.baseline)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,9 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
background: var(--text);
|
background: var(--text);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
@@ -86,10 +89,13 @@
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
transition: box-shadow 0.15s;
|
transition:
|
||||||
|
box-shadow 0.15s,
|
||||||
|
transform 0.15s;
|
||||||
}
|
}
|
||||||
.card:hover {
|
.card:hover {
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
.card h2 {
|
.card h2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -105,4 +111,16 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
padding: 28px 16px;
|
||||||
|
}
|
||||||
|
.intro h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { error, json } from '@sveltejs/kit';
|
|
||||||
import { draftBreadcrumb } from '$lib/server/ai';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/ai/draft — draft breadcrumb fields from a problem statement.
|
|
||||||
* Body: { prob: string, related?: string[] }
|
|
||||||
*/
|
|
||||||
export const POST: RequestHandler = async ({ request }) => {
|
|
||||||
let body: { prob?: string; related?: string[] };
|
|
||||||
try {
|
|
||||||
body = await request.json();
|
|
||||||
} catch {
|
|
||||||
throw error(400, 'Body must be JSON');
|
|
||||||
}
|
|
||||||
|
|
||||||
const prob = String(body.prob ?? '').trim();
|
|
||||||
const related = Array.isArray(body.related) ? body.related.map(String) : [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const draft = await draftBreadcrumb(prob, related);
|
|
||||||
return json(draft);
|
|
||||||
} catch (e) {
|
|
||||||
const message = e instanceof Error ? e.message : 'AI draft failed';
|
|
||||||
// 422: the request was well-formed but the AI step could not complete.
|
|
||||||
throw error(422, message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -404,6 +404,9 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
background: var(--text);
|
background: var(--text);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
@@ -447,12 +450,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 56px;
|
||||||
|
z-index: 20;
|
||||||
background: var(--layer);
|
background: var(--layer);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
nav button {
|
nav button {
|
||||||
|
white-space: nowrap;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 3px solid transparent;
|
border-bottom: 3px solid transparent;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import Toast from '$lib/components/Toast.svelte';
|
import Toast from '$lib/components/Toast.svelte';
|
||||||
import SyncBadge from '$lib/components/SyncBadge.svelte';
|
import SyncBadge from '$lib/components/SyncBadge.svelte';
|
||||||
import { PEOPLE, SUGGESTIONS } from '$lib/defaults/workshop';
|
import { PEOPLE, SUGGESTIONS } from '$lib/defaults/workshop';
|
||||||
|
import { suggestBreadcrumb, DOMAIN_OPTIONS } from '$lib/breadcrumbs';
|
||||||
import type { Breadcrumb, Cluster, Problem, WorkshopState } from '$lib/types';
|
import type { Breadcrumb, Cluster, Problem, WorkshopState } from '$lib/types';
|
||||||
|
|
||||||
const ws = new Workspace<WorkshopState>('workshop');
|
const ws = new Workspace<WorkshopState>('workshop');
|
||||||
@@ -169,7 +170,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Breadcrumbs ----------
|
// ---------- Breadcrumbs ----------
|
||||||
let drafting = $state<Record<string, boolean>>({});
|
// Per-card domain choice for the "Suggest fields" button. '' = auto-detect.
|
||||||
|
let pickDomain = $state<Record<string, string>>({});
|
||||||
let nDone = $derived(s ? s.breadcrumbs.filter(bcComplete).length : 0);
|
let nDone = $derived(s ? s.breadcrumbs.filter(bcComplete).length : 0);
|
||||||
let nOut = $derived(s ? s.breadcrumbs.filter((b) => b.type === 'Outcome').length : 0);
|
let nOut = $derived(s ? s.breadcrumbs.filter((b) => b.type === 'Outcome').length : 0);
|
||||||
|
|
||||||
@@ -187,30 +189,22 @@
|
|||||||
s.breadcrumbs.push(newBreadcrumb({}));
|
s.breadcrumbs.push(newBreadcrumb({}));
|
||||||
ws.touch();
|
ws.touch();
|
||||||
}
|
}
|
||||||
async function aiDraft(b: Breadcrumb) {
|
// Fill the five fields from construction-execution methodology (no external
|
||||||
|
// AI): classify the problem into a domain by keyword, or use the domain the
|
||||||
|
// user picked, then drop in that domain's canonical driver/metric/method/etc.
|
||||||
|
function suggestFields(b: Breadcrumb) {
|
||||||
if (!String(b.prob).trim()) {
|
if (!String(b.prob).trim()) {
|
||||||
toast.show('Write the problem statement first');
|
toast.show('Write the problem statement first');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasContent = REQ.some((f) => String(b[f]).trim());
|
const hasContent = REQ.some((f) => String(b[f]).trim());
|
||||||
if (hasContent && !confirm('Replace the current field contents with an AI draft grounded in construction execution methodology?')) return;
|
if (hasContent && !confirm('Replace the current field contents with methodology-based suggestions?')) return;
|
||||||
drafting[b.id] = true;
|
const res = suggestBreadcrumb(b.prob, b.related, pickDomain[b.id] ?? '');
|
||||||
try {
|
for (const f of REQ) b[f] = res[f];
|
||||||
const res = await fetch('/api/ai/draft', {
|
// Reflect the detected domain back into the picker so it's visible.
|
||||||
method: 'POST',
|
pickDomain[b.id] = res.domain;
|
||||||
headers: { 'Content-Type': 'application/json' },
|
ws.touch();
|
||||||
body: JSON.stringify({ prob: b.prob, related: b.related })
|
toast.show('Filled from the ' + res.label + ' playbook. Review and edit as a team.');
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (!res.ok) throw new Error(data?.message || 'AI draft failed');
|
|
||||||
for (const f of REQ) b[f] = String(data[f]);
|
|
||||||
ws.touch();
|
|
||||||
toast.show('Drafted from ' + (data.domain || 'methodology') + '. Review as a team.');
|
|
||||||
} catch (err) {
|
|
||||||
toast.show('AI draft unavailable: ' + (err instanceof Error ? err.message : 'error'));
|
|
||||||
} finally {
|
|
||||||
drafting[b.id] = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Actions ----------
|
// ---------- Actions ----------
|
||||||
@@ -504,7 +498,7 @@
|
|||||||
{#if tab === 'bc'}
|
{#if tab === 'bc'}
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h1>Breadcrumbs</h1>
|
<h1>Breadcrumbs</h1>
|
||||||
<p>Root problem to value driver to capability to measurable outcome. A completed breadcrumb IS a pilot success criterion; the export builds the criteria table from every complete card. AI draft fills the fields from construction execution methodology (EVM, CII AWP, Last Planner, first-time quality); the room reviews and edits.</p>
|
<p>Root problem to value driver to capability to measurable outcome. A completed breadcrumb IS a pilot success criterion; the export builds the criteria table from every complete card. Suggest fields fills the card from construction execution methodology (EVM, CII AWP, Last Planner, first-time quality) based on the problem's domain; the room reviews and edits.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-bar">
|
<div class="summary-bar">
|
||||||
<div><b>{nDone}</b>Complete (= criteria)</div>
|
<div><b>{nDone}</b>Complete (= criteria)</div>
|
||||||
@@ -558,7 +552,13 @@
|
|||||||
{#if b.type === 'Outcome' && ADOPTION_WORDS.test(b.metric)}
|
{#if b.type === 'Outcome' && ADOPTION_WORDS.test(b.metric)}
|
||||||
<span class="gate-hint">Metric reads as adoption. Name the outcome the tool should move, or tag this card Adoption.</span>
|
<span class="gate-hint">Metric reads as adoption. Name the outcome the tool should move, or tag this card Adoption.</span>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="btn small ai" disabled={drafting[b.id]} onclick={() => aiDraft(b)}>{drafting[b.id] ? 'Thinking…' : '✨ AI draft fields'}</button>
|
<div class="suggest">
|
||||||
|
<select class="domain-pick" bind:value={pickDomain[b.id]} title="Methodology domain">
|
||||||
|
<option value="">Auto-detect domain</option>
|
||||||
|
{#each DOMAIN_OPTIONS as d}<option value={d.id}>{d.label}</option>{/each}
|
||||||
|
</select>
|
||||||
|
<button class="btn small ai" onclick={() => suggestFields(b)}>Suggest fields</button>
|
||||||
|
</div>
|
||||||
<button class="row-del" title="Remove" style="margin-left:auto;" onclick={() => delBc(b.id)}>✕</button>
|
<button class="row-del" title="Remove" style="margin-left:auto;" onclick={() => delBc(b.id)}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -602,6 +602,9 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
background: var(--text);
|
background: var(--text);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
@@ -644,10 +647,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 56px;
|
||||||
|
z-index: 20;
|
||||||
background: var(--layer);
|
background: var(--layer);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
nav button {
|
nav button {
|
||||||
background: none;
|
background: none;
|
||||||
@@ -656,6 +665,7 @@
|
|||||||
padding: 14px 18px;
|
padding: 14px 18px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
nav button:hover {
|
nav button:hover {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -739,10 +749,19 @@
|
|||||||
.btn.ai:hover {
|
.btn.ai:hover {
|
||||||
background: #f6f2ff;
|
background: #f6f2ff;
|
||||||
}
|
}
|
||||||
.btn.ai:disabled {
|
.suggest {
|
||||||
color: var(--text-helper);
|
display: flex;
|
||||||
border-color: var(--border-strong);
|
align-items: center;
|
||||||
background: transparent;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.domain-pick {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid var(--border-strong);
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-family: inherit;
|
||||||
|
max-width: 220px;
|
||||||
}
|
}
|
||||||
.add-row {
|
.add-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user