ltcai 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +33 -24
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +84 -0
  4. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  5. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  6. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  7. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
  8. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
  9. package/docs/V4_2_VALIDATION_REPORT.md +89 -0
  10. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
  11. package/frontend/index.html +24 -0
  12. package/frontend/openapi.json +14436 -0
  13. package/frontend/src/App.tsx +184 -0
  14. package/frontend/src/api/client.ts +320 -0
  15. package/frontend/src/api/openapi.ts +16921 -0
  16. package/frontend/src/components/primitives.tsx +204 -0
  17. package/frontend/src/components/ui/badge.tsx +27 -0
  18. package/frontend/src/components/ui/button.tsx +37 -0
  19. package/frontend/src/components/ui/card.tsx +22 -0
  20. package/frontend/src/components/ui/input.tsx +16 -0
  21. package/frontend/src/components/ui/textarea.tsx +16 -0
  22. package/frontend/src/lib/utils.ts +33 -0
  23. package/frontend/src/main.tsx +23 -0
  24. package/frontend/src/pages/Act.tsx +245 -0
  25. package/frontend/src/pages/Ask.tsx +200 -0
  26. package/frontend/src/pages/Brain.tsx +267 -0
  27. package/frontend/src/pages/Capture.tsx +158 -0
  28. package/frontend/src/pages/Library.tsx +187 -0
  29. package/frontend/src/pages/System.tsx +378 -0
  30. package/frontend/src/routes.ts +85 -0
  31. package/frontend/src/store/appStore.ts +54 -0
  32. package/frontend/src/styles.css +107 -0
  33. package/kg_schema.py +1 -1
  34. package/knowledge_graph.py +4 -4
  35. package/lattice_brain/__init__.py +70 -0
  36. package/lattice_brain/_kg_common.py +1 -0
  37. package/lattice_brain/archive.py +133 -0
  38. package/lattice_brain/context.py +3 -0
  39. package/lattice_brain/conversations.py +3 -0
  40. package/lattice_brain/core.py +82 -0
  41. package/lattice_brain/discovery.py +1 -0
  42. package/lattice_brain/documents.py +1 -0
  43. package/lattice_brain/embeddings.py +82 -0
  44. package/lattice_brain/identity.py +13 -0
  45. package/lattice_brain/ingest.py +1 -0
  46. package/lattice_brain/memory.py +3 -0
  47. package/lattice_brain/network.py +1 -0
  48. package/lattice_brain/projection.py +1 -0
  49. package/lattice_brain/provenance.py +1 -0
  50. package/lattice_brain/retrieval.py +1 -0
  51. package/lattice_brain/schema.py +1 -0
  52. package/lattice_brain/storage/__init__.py +22 -0
  53. package/lattice_brain/storage/base.py +72 -0
  54. package/lattice_brain/storage/docker.py +105 -0
  55. package/lattice_brain/storage/factory.py +31 -0
  56. package/lattice_brain/storage/migration.py +190 -0
  57. package/lattice_brain/storage/postgres.py +123 -0
  58. package/lattice_brain/storage/sqlite.py +128 -0
  59. package/lattice_brain/store.py +3 -0
  60. package/lattice_brain/write_master.py +1 -0
  61. package/latticeai/__init__.py +1 -1
  62. package/latticeai/api/portability.py +69 -0
  63. package/latticeai/api/setup.py +5 -4
  64. package/latticeai/api/static_routes.py +4 -4
  65. package/latticeai/app_factory.py +17 -10
  66. package/latticeai/brain/__init__.py +6 -6
  67. package/latticeai/brain/_kg_common.py +1 -1
  68. package/latticeai/brain/network.py +1 -1
  69. package/latticeai/brain/retrieval.py +15 -0
  70. package/latticeai/brain/store.py +22 -6
  71. package/latticeai/core/config.py +8 -0
  72. package/latticeai/core/marketplace.py +1 -1
  73. package/latticeai/core/multi_agent.py +1 -1
  74. package/latticeai/core/workspace_os.py +1 -1
  75. package/latticeai/services/kg_portability.py +82 -1
  76. package/package.json +55 -15
  77. package/scripts/build_frontend_assets.mjs +38 -0
  78. package/scripts/bump_version.py +4 -1
  79. package/scripts/export_openapi.py +31 -0
  80. package/scripts/lint_frontend.mjs +91 -0
  81. package/scripts/migrate_brain_storage.py +53 -0
  82. package/scripts/run_python.mjs +47 -0
  83. package/scripts/wheel_smoke.py +3 -0
  84. package/src-tauri/Cargo.lock +4833 -0
  85. package/src-tauri/Cargo.toml +19 -0
  86. package/src-tauri/build.rs +3 -0
  87. package/src-tauri/capabilities/default.json +7 -0
  88. package/src-tauri/src/main.rs +78 -0
  89. package/src-tauri/tauri.conf.json +39 -0
  90. package/static/app/asset-manifest.json +32 -0
  91. package/static/app/assets/core-CwxXejkd.js +2 -0
  92. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  93. package/static/app/assets/index-CDjiH_se.css +2 -0
  94. package/static/app/assets/index-C_HAkbAg.js +333 -0
  95. package/static/app/assets/index-C_HAkbAg.js.map +1 -0
  96. package/static/app/index.html +25 -0
  97. package/static/manifest.json +2 -2
  98. package/static/sw.js +4 -4
  99. package/scripts/build_v3_assets.mjs +0 -170
  100. package/scripts/lint_v3.mjs +0 -120
  101. package/static/v3/asset-manifest.json +0 -63
  102. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  103. package/static/v3/css/lattice.base.css +0 -128
  104. package/static/v3/css/lattice.components.cde18231.css +0 -472
  105. package/static/v3/css/lattice.components.css +0 -472
  106. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  107. package/static/v3/css/lattice.shell.css +0 -452
  108. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  109. package/static/v3/css/lattice.tokens.css +0 -135
  110. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  111. package/static/v3/css/lattice.views.css +0 -360
  112. package/static/v3/index.html +0 -68
  113. package/static/v3/js/app.c5c80c46.js +0 -26
  114. package/static/v3/js/app.js +0 -26
  115. package/static/v3/js/core/api.ba0fbf14.js +0 -625
  116. package/static/v3/js/core/api.js +0 -625
  117. package/static/v3/js/core/components.f25b3b93.js +0 -230
  118. package/static/v3/js/core/components.js +0 -230
  119. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  120. package/static/v3/js/core/dom.js +0 -148
  121. package/static/v3/js/core/i18n.880e1fec.js +0 -575
  122. package/static/v3/js/core/i18n.js +0 -575
  123. package/static/v3/js/core/router.584570f2.js +0 -37
  124. package/static/v3/js/core/router.js +0 -37
  125. package/static/v3/js/core/routes.37522821.js +0 -101
  126. package/static/v3/js/core/routes.js +0 -101
  127. package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
  128. package/static/v3/js/core/shell.js +0 -420
  129. package/static/v3/js/core/store.7b2aa044.js +0 -123
  130. package/static/v3/js/core/store.js +0 -123
  131. package/static/v3/js/views/account.eff40715.js +0 -143
  132. package/static/v3/js/views/account.js +0 -143
  133. package/static/v3/js/views/activity.0d271ef9.js +0 -67
  134. package/static/v3/js/views/activity.js +0 -67
  135. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  136. package/static/v3/js/views/admin-audit.js +0 -185
  137. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  138. package/static/v3/js/views/admin-permissions.js +0 -177
  139. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  140. package/static/v3/js/views/admin-policies.js +0 -102
  141. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  142. package/static/v3/js/views/admin-private-vpc.js +0 -135
  143. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  144. package/static/v3/js/views/admin-security.js +0 -180
  145. package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
  146. package/static/v3/js/views/admin-users.js +0 -166
  147. package/static/v3/js/views/agents.17c5288d.js +0 -564
  148. package/static/v3/js/views/agents.js +0 -564
  149. package/static/v3/js/views/chat.e250e2cc.js +0 -624
  150. package/static/v3/js/views/chat.js +0 -624
  151. package/static/v3/js/views/files.adad14c1.js +0 -365
  152. package/static/v3/js/views/files.js +0 -365
  153. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  154. package/static/v3/js/views/graph-canvas.js +0 -509
  155. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  156. package/static/v3/js/views/home.js +0 -200
  157. package/static/v3/js/views/hooks.37895880.js +0 -220
  158. package/static/v3/js/views/hooks.js +0 -220
  159. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  160. package/static/v3/js/views/hybrid-search.js +0 -194
  161. package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
  162. package/static/v3/js/views/knowledge-graph.js +0 -529
  163. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  164. package/static/v3/js/views/marketplace.js +0 -141
  165. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  166. package/static/v3/js/views/mcp.js +0 -114
  167. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  168. package/static/v3/js/views/memory.js +0 -147
  169. package/static/v3/js/views/models.a1ffa147.js +0 -256
  170. package/static/v3/js/views/models.js +0 -256
  171. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  172. package/static/v3/js/views/my-computer.js +0 -463
  173. package/static/v3/js/views/network.52a4f181.js +0 -97
  174. package/static/v3/js/views/network.js +0 -97
  175. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  176. package/static/v3/js/views/pipeline.js +0 -157
  177. package/static/v3/js/views/planning.4876fd77.js +0 -174
  178. package/static/v3/js/views/planning.js +0 -174
  179. package/static/v3/js/views/runs.b63b2afa.js +0 -144
  180. package/static/v3/js/views/runs.js +0 -144
  181. package/static/v3/js/views/settings.b7140634.js +0 -317
  182. package/static/v3/js/views/settings.js +0 -317
  183. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  184. package/static/v3/js/views/skills.js +0 -109
  185. package/static/v3/js/views/snapshots.6f5db095.js +0 -135
  186. package/static/v3/js/views/snapshots.js +0 -135
  187. package/static/v3/js/views/tools.e4f11276.js +0 -108
  188. package/static/v3/js/views/tools.js +0 -108
  189. package/static/v3/js/views/workflows.7752225a.js +0 -213
  190. package/static/v3/js/views/workflows.js +0 -213
  191. package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
  192. package/static/v3/js/views/workspace-admin.js +0 -156
@@ -0,0 +1,89 @@
1
+ # Lattice AI v4.2.0 — Validation Report
2
+
3
+ Date: 2026-06-12
4
+ Commit under validation: v4.2.0 release commit on `main` after `v4.1.0`
5
+
6
+ ## Result
7
+
8
+ v4.2.0 validation passed for the implemented Brain Core package, storage
9
+ abstraction, SQLite default runtime, encrypted archives, generated OpenAPI
10
+ client, frontend/system controls, Docker-backed Postgres/pgvector migration,
11
+ desktop check, and release artifacts.
12
+
13
+ ## Commands
14
+
15
+ | Check | Command | Result |
16
+ | --- | --- | --- |
17
+ | OpenAPI generation | `npm run frontend:openapi` | PASS — 313 paths |
18
+ | Python compile | `npm run check:python` | PASS — 235 modules |
19
+ | Ruff | `node scripts/run_python.mjs -m ruff check .` | PASS |
20
+ | Unit tests | `npm run test:unit -- --tb=short` | PASS — 593 passed, 2 warnings |
21
+ | Live Postgres migration | `LTCAI_LIVE_POSTGRES_DOCKER_CONSENT=1 node scripts/run_python.mjs -m pytest tests/integration/test_v42_postgres_migration_live.py -v --tb=short` | PASS |
22
+ | Live integration | `LTCAI_TEST_BASE_URL=http://127.0.0.1:8899 npm run test:integration -- --tb=short` | PASS — 9 passed, 1 skipped |
23
+ | Frontend lint | `npm run lint` | PASS |
24
+ | TypeScript + VS Code extension build | `npm run typecheck` | PASS |
25
+ | Vite app build | `npm run build:assets` | PASS |
26
+ | Playwright visual/offline suite | `npx playwright test tests/visual/v3.spec.js` | PASS — 12 passed |
27
+ | Tauri desktop check | `npm run desktop:tauri:check` | PASS |
28
+ | Release artifacts | `npm run release:artifacts` | PASS |
29
+ | Artifact validation | `npm run release:validate` | PASS |
30
+ | Wheel smoke | `node scripts/run_python.mjs scripts/wheel_smoke.py --wheel dist/ltcai-4.2.0-py3-none-any.whl` | PASS |
31
+ | npm dry-run | `npm pack --dry-run` | PASS |
32
+
33
+ ## Storage-Specific Coverage
34
+
35
+ - `test_v42_brain_storage.py` validates:
36
+ - `lattice_brain` package exports a working Knowledge Graph store.
37
+ - `BrainCore` constructs SQLite graph and durable conversation stores.
38
+ - default storage is SQLite.
39
+ - explicit Postgres without DSN fails honestly.
40
+ - SQLite-to-Postgres migration planning preserves all user tables and
41
+ idempotence keys, including rowid-less FTS5 shadow tables with primary
42
+ keys.
43
+ - Docker setup does not start without explicit consent.
44
+ - encrypted `.latticebrain` archives round-trip DB + blobs.
45
+ - portability service exposes storage status, dry-run migration, and archives.
46
+ - `test_v42_postgres_migration_live.py` validates the explicit-consent Docker
47
+ path with `pgvector/pgvector:pg16`:
48
+ - live pgvector Postgres starts through `DockerPostgresWizard`.
49
+ - v4 SQLite brain data copies without source mutation.
50
+ - table row counts match after migration.
51
+ - rerunning migration is idempotent.
52
+ - pgvector extension and vector distance ordering work.
53
+ - explicit Postgres runtime paths fail closed instead of falling back to
54
+ SQLite.
55
+ - the test Compose stack is torn down with volumes after validation.
56
+ - Existing vector tests continue to validate real local vector search.
57
+ - Existing portability tests continue to validate JSON export/import and ZIP
58
+ backup/restore.
59
+
60
+ ## Generated Artifacts
61
+
62
+ - `dist/ltcai-4.2.0-py3-none-any.whl`
63
+ - `dist/ltcai-4.2.0.tar.gz`
64
+ - `dist/ltcai-4.2.0.vsix`
65
+ - `ltcai-4.2.0.tgz`
66
+
67
+ ## Warnings
68
+
69
+ - Vite reports the main app chunk is larger than 500 kB; build succeeds.
70
+ - Tauri/Rust reports transitive `block v0.1.6` future-incompatibility warning;
71
+ cargo check succeeds.
72
+ - Release validation warns that older artifacts remain in `dist/`; exact
73
+ v4.2.0 artifact validation passes and publish docs require exact filenames.
74
+ - Wheel smoke in a clean venv reports MLX unavailable; expected when optional
75
+ local MLX runtime is not installed. `/health` still reports version `4.2.0`.
76
+
77
+ ## Docker/Postgres Validation
78
+
79
+ Owner granted explicit Docker consent for the v4.2.0 Postgres/pgvector
80
+ migration test. Docker was used only for this validation path. The live test
81
+ started a local `pgvector/pgvector:pg16` Postgres service, migrated a seeded v4
82
+ SQLite brain database, verified row counts, idempotence, pgvector distance
83
+ search, fail-closed Postgres behavior, and then removed the test Compose stack
84
+ and volumes. SQLite remains the default and fully validated runtime.
85
+
86
+ ## External Registries
87
+
88
+ No PyPI, npm Registry, VS Code Marketplace, Open VSX, or other external
89
+ registry publish command was run.
@@ -5,24 +5,27 @@
5
5
  > completed analysis. **Update this file before ending any phase and before any
6
6
  > likely session/context/usage limit.**
7
7
  >
8
- > Last updated: 2026-06-12 — v4.0.1 maintenance release prep; T9 remainder remains closed
8
+ > Last updated: 2026-06-12 — v4.2.0 Brain Core/storage release; Remaining Gaps remain empty
9
9
 
10
10
  ---
11
11
 
12
- ## 0. RC STATUS (final)
13
-
14
- **v4.0.1 packages the `main` commits after tag `v4.0.0`; implementation gaps are empty.**
15
- Latest verified implementation milestone: T9 remainder closed with full unit coverage
16
- (`585 passed`), ruff, Python compile, `npm run lint`, `npm run build:assets`,
17
- Playwright v3 visual coverage, Python sdist/wheel build, installed-wheel smoke,
18
- and `npm pack --dry-run`.
19
- The v4.0.1 release process builds and validates artifacts for GitHub Release
20
- attachment only. It does not publish to PyPI, npm Registry, VS Code Marketplace,
21
- Open VSX, or production deployment targets.
22
- v4.0.1 validation completed: Python compile, ruff, 585 unit tests, 9 live
23
- integration tests, frontend lint, VS Code typecheck, 16 Playwright visual tests,
24
- Python wheel/sdist build, npm tgz build, VSIX build, release artifact
25
- validation, wheel smoke, and npm pack dry-run all passed.
12
+ ## 0. RELEASE STATUS (v4.2.0)
13
+
14
+ **v4.2.0 extracts Brain Core into `lattice_brain` and adds the pluggable
15
+ storage layer on top of `main` after v4.1.0; implementation gaps remain empty.**
16
+ Latest implementation milestone: FastAPI now constructs the Knowledge Graph and
17
+ durable conversation runtime through `lattice_brain.BrainCore`. The new
18
+ `lattice_brain.storage` package provides `StorageEngine`, `SQLiteEngine`,
19
+ `PostgresEngine`, explicit-consent Docker Postgres setup, SQLite-to-Postgres
20
+ migration tooling, sqlite-vec capability reporting with honest cosine fallback,
21
+ and encrypted `.latticebrain` archive create/restore. Owner-granted Docker
22
+ validation covered live `pgvector/pgvector:pg16` migration integrity,
23
+ rowid-less FTS5 shadow tables, idempotent reruns, pgvector distance search,
24
+ and fail-closed Postgres behavior.
25
+ The v4.2.0 release process builds validated artifacts, tags `v4.2.0`, and
26
+ creates a GitHub Release only. It does not publish to PyPI, npm Registry,
27
+ VS Code Marketplace, Open VSX, or deploy to production targets.
28
+ v4.2.0 validation report: `docs/V4_2_VALIDATION_REPORT.md`.
26
29
  Remaining implementation gaps: **none**.
27
30
  Owner-only blockers: pptx history rewrite (requires force-push/owner decision)
28
31
  and consent-gated production embedder provisioning (silent default download is
@@ -30,11 +33,13 @@ not permitted).
30
33
 
31
34
  ## Remaining Gaps
32
35
 
33
- None. The T9 remainder was closed on main with legacy page deletion, `/app`
34
- parity views, token-native account UI, en/ko i18n, approval/run inbox,
35
- workflow-trigger controls, Brain Network UI, chat context-trace panel, and
36
- Knowledge Graph provenance coverage surfaced in the SPA. Owner-only blockers
37
- above are intentionally not implementation gaps.
36
+ None. v4.2.0 closes the backend Brain Core/storage rebuild on top of the
37
+ already-empty v4.1.0 gap list: `lattice_brain` is the independent package
38
+ boundary, SQLite remains the default engine, Postgres/pgvector is opt-in and
39
+ honestly unavailable when dependencies or DSN are missing, Docker setup is
40
+ consent-gated, migration planning is non-destructive, and encrypted
41
+ `.latticebrain` archives round-trip the SQLite brain DB plus blobs. Owner-only
42
+ blockers above are intentionally not implementation gaps.
38
43
 
39
44
  ## 1. Program Charter (from the user's v4.0.0 directive)
40
45
 
@@ -220,9 +225,9 @@ Track log (update at every track boundary):
220
225
  and KG provenance coverage. en/ko i18n runtime backs routes, shell, and new
221
226
  parity views; `scripts/lint_v3.mjs` gates it. Visual coverage moved to the
222
227
  v3 surface and legacy-page specs were retired.
223
- - T9-canvas agent left static/v3/js/views/graph-canvas.js (509 lines,
224
- node --check passes) but NEVER rewired knowledge-graph.js file kept
225
- uncommitted in tree; integration outstanding.
228
+ - Superseded note: an early T9-canvas handoff left a static v3 graph-canvas file
229
+ unrewired, but this is no longer active work. v4.1.0 removed `static/v3` and
230
+ replaced the graph surface with the React/Vite/Cytoscape Brain view.
226
231
  - NOTE: The old T3d queue is closed. T9 parity surfaces remain active with
227
232
  full contracts in this file + the plan.
228
233
  - **T3e**: docs/kg-schema.md regenerated from enums.
@@ -278,9 +283,9 @@ the canonical Phase A record.**
278
283
  services, 27 API routers + `server_app.py` at 1,554 lines). Legacy root
279
284
  modules ~6,720 lines incl. `knowledge_graph.py` **4,633 lines**,
280
285
  `kg_schema.py` 521, `llm_router.py` 775, `mcp_registry.py` 791.
281
- - Frontend: `/app` v3 SPA (`static/v3/`, token-native) is primary; legacy
282
- static HTML pages were later removed and compatibility routes redirect
283
- into `/app`.
286
+ - Historical frontend baseline: `/app` static v3 SPA (`static/v3/`,
287
+ token-native) was primary at v3.6.0. v4.1.0 later replaced it with the
288
+ React/Vite SPA in `frontend/` and `static/app/`.
284
289
  - Repo root clutter: ~30 `ltcai-*.tgz` tarballs, `ltcai-0.3.1/` extracted copy,
285
290
  logs, `chat_history.json`, 15MB pptx — most likely untracked; verify with
286
291
  `git ls-files` before cleaning.
@@ -0,0 +1,24 @@
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, viewport-fit=cover" />
6
+ <meta name="color-scheme" content="dark light" />
7
+ <title>Lattice AI · Digital Brain</title>
8
+ <link rel="manifest" href="/manifest.json" />
9
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png" />
10
+ <script>
11
+ (function () {
12
+ try {
13
+ var theme = localStorage.getItem("lattice.theme");
14
+ if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
15
+ } catch (e) {}
16
+ })();
17
+ </script>
18
+ </head>
19
+ <body>
20
+ <div id="root"></div>
21
+ <noscript>Lattice AI requires JavaScript for the local Digital Brain desktop shell.</noscript>
22
+ <script type="module" src="/src/main.tsx"></script>
23
+ </body>
24
+ </html>