ChristopherJKoen commited on
Commit
bdfdb65
·
1 Parent(s): 73b6f92

minimal server + web starter

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +12 -74
  2. agents/README.md +0 -13
  3. agents/__init__.py +0 -15
  4. agents/export/__init__.py +0 -3
  5. agents/export/agent.py +0 -112
  6. agents/extraction/__init__.py +0 -3
  7. agents/extraction/agent.py +0 -239
  8. agents/orchestrator/__init__.py +0 -3
  9. agents/orchestrator/agent.py +0 -18
  10. agents/rewrite/__init__.py +0 -3
  11. agents/rewrite/agent.py +0 -315
  12. agents/shared/base.py +0 -49
  13. agents/shared/client.py +0 -97
  14. agents/shared/embeddings.py +0 -19
  15. agents/standards_mapping/__init__.py +0 -3
  16. agents/standards_mapping/agent.py +0 -293
  17. agents/validation/__init__.py +0 -3
  18. agents/validation/agent.py +0 -96
  19. common/README.md +0 -10
  20. common/embedding_store.py +0 -98
  21. docker/Dockerfile +0 -20
  22. docs/README.md +0 -12
  23. docs/agents/orchestrator.md +0 -12
  24. docs/architecture/system-context.md +0 -11
  25. docs/pipelines/pypeflow-overview.md +0 -8
  26. docs/ui/review-workflow.md +0 -17
  27. frontend/README.md +0 -17
  28. frontend/index.html +1 -1
  29. frontend/package-lock.json +0 -2030
  30. frontend/package.json +1 -4
  31. frontend/public/index.html +0 -13
  32. frontend/src/App.tsx +1 -32
  33. frontend/src/components/PresetManager.tsx +0 -193
  34. frontend/src/components/SessionDetails.tsx +0 -578
  35. frontend/src/components/SessionList.tsx +0 -59
  36. frontend/src/components/UploadForm.tsx +0 -189
  37. frontend/src/main.tsx +1 -6
  38. frontend/src/pages/ObservatoryPage.tsx +0 -179
  39. frontend/src/pages/PresetEditPage.tsx +0 -202
  40. frontend/src/pages/PresetsPage.tsx +0 -9
  41. frontend/src/pages/SessionsPage.tsx +0 -19
  42. frontend/src/services/api.ts +0 -113
  43. frontend/src/styles.css +6 -634
  44. frontend/src/types/diagnostics.ts +0 -27
  45. frontend/src/types/session.ts +0 -59
  46. infra/README.md +0 -10
  47. notes/comments/Initial Start Up.docx +0 -0
  48. notes/comments/~$itial Start Up.docx +0 -0
  49. scripts/README.md +0 -9
  50. server/.env.example +0 -12
README.md CHANGED
@@ -1,86 +1,24 @@
1
- # RightCodes Architecture Scaffold
2
 
3
- This repository is the starting scaffold for the code and standards migration assistant. The layout mirrors the planned AI-driven workflow (ingestion -> extraction -> mapping -> rewrite -> validation -> export) and keeps agent orchestration, backend services, and UI concerns separated.
 
 
4
 
5
- ## Top-Level Directories
6
-
7
- - `frontend/` - React application for uploads, diff review, validation checklists, and export actions.
8
- - `server/` - FastAPI backend that manages sessions, orchestrates agents, and exposes REST/WebSocket APIs.
9
- - `agents/` - OpenAI agent wrappers plus prompt assets for each processing stage.
10
- - `workers/` - File ingestion, document parsing, and pipeline jobs executed off the main API thread.
11
- - `storage/` - Versioned document blobs, manifests, caches, and export artefacts.
12
- - `docs/` - Architecture notes, pipeline diagrams, agent specs, and UI flows.
13
- - `scripts/` - Developer utilities, operational scripts, and local tooling hooks.
14
- - `data/` - Sample inputs, canonical standards metadata, and fixture mappings.
15
- - `infra/` - DevOps assets (containers, CI pipelines, observability).
16
- - `common/` - Shared domain models, schemas, and event definitions.
17
-
18
- ## Getting Started
19
-
20
- ### Prerequisites
21
-
22
- - Python 3.8+ (3.11+ recommended)
23
- - Node.js 18+ and npm (ships with Node.js)
24
- - Internet connectivity on the first launch so pip/npm can download dependencies
25
-
26
- ### Launcher Quick Start
27
-
28
- The repository ships with `start-rightcodes.ps1` (PowerShell) and `start-rightcodes.bat` (Command Prompt) which check for Python and Node.js, create the virtual environment, install dependencies, and open the launcher UI.
29
 
 
30
  ```powershell
31
- .\start-rightcodes.ps1
32
- ```
33
-
34
- ```bat
35
- start-rightcodes.bat
36
- ```
37
-
38
- The scripts will guide you to the official Python and Node.js installers if either prerequisite is missing. After a successful first run, the cached `.venv/` and `frontend/node_modules/` folders allow the launcher to work offline.
39
-
40
- ### Backend (FastAPI)
41
-
42
- ```bash
43
- cd server
44
  python -m venv .venv
45
- .venv\Scripts\activate # Windows
46
- pip install -r requirements.txt
47
- # copy the sample env and add your OpenAI key
48
- copy .env.example .env # or use New-Item -Path .env -ItemType File
49
  ```
50
 
51
- Edit `.env` and set `RIGHTCODES_OPENAI_API_KEY=sk-your-key`.
52
-
53
- ```bash
54
- uvicorn app.main:app --reload --port 8000
55
- ```
56
-
57
- The API is available at `http://localhost:8000/api` and Swagger UI at `http://localhost:8000/api/docs`.
58
-
59
- ### Frontend (Vite + React)
60
-
61
- ```bash
62
  cd frontend
63
  npm install
64
  npm run dev
65
  ```
66
 
67
- Navigate to `http://localhost:5173` to access the UI. Configure a custom API base URL by setting `VITE_API_BASE_URL` in `frontend/.env`.
68
-
69
- ## Usage
70
-
71
- - Each conversion session requires the original report (`.docx`) plus one or more destination standards packs (`.pdf`). Upload all relevant standards PDFs so the AI pipeline can align existing references with the new context.
72
- - The web UI now shows live progress bars and an activity log so you can monitor each stage of the pipeline while it runs.
73
- - Backend agent calls expect an OpenAI API key in `RIGHTCODES_OPENAI_API_KEY`. On Windows PowerShell you can set it temporarily with `setx RIGHTCODES_OPENAI_API_KEY "sk-..."` (reopen your terminal afterward).
74
- - Optional: override the default OpenAI models by setting `RIGHTCODES_OPENAI_MODEL_EXTRACT`, `RIGHTCODES_OPENAI_MODEL_MAPPING`, `RIGHTCODES_OPENAI_MODEL_REWRITE`, `RIGHTCODES_OPENAI_MODEL_VALIDATE`, and `RIGHTCODES_OPENAI_MODEL_EMBED`. The pipeline stores standards embeddings under `storage/embeddings/`, enabling retrieval-augmented mapping, and the export stage now writes a converted DOCX to `storage/exports/` with a download button once the pipeline completes.
75
-
76
- ## Offline Distribution Options
77
-
78
- - **Zip bundle:** Run `python tools/build_offline_package.py` after a successful online launch. The script creates `dist/rightcodes-offline.zip` containing the repository, the prepared `.venv/`, and `frontend/node_modules/`. Pass `--python-runtime` and `--node-runtime` to embed portable runtimes if you have them (for example, a locally extracted Python embeddable zip and Node.js binary folder). Extract the archive on another machine (same OS/architecture) and use the launcher scripts without needing internet access.
79
- - **Docker image:** Build a pre-baked container with `docker build -t rightcodes-launcher -f docker/Dockerfile .`. Supply `RIGHTCODES_OPENAI_API_KEY` (and optionally `RIGHTCODES_OPENAI_API_KEY_SOURCE`) at runtime: `docker run --rm -p 8765:8765 -p 8000:8000 -p 5173:5173 -e RIGHTCODES_OPENAI_API_KEY=sk-xxx rightcodes-launcher`.
80
- - **First-run reminder:** Even with these assets, the *initial* bundle build still requires internet so dependencies can be fetched once before packaging.
81
-
82
- ## Next Steps
83
-
84
- 1. Flesh out agent logic in `agents/` and integrate with orchestration hooks.
85
- 2. Replace the in-memory session store with a durable persistence layer.
86
- 3. Wire the worker queue (`workers/queue/`) to execute long-running stages asynchronously.
 
1
+ # Minimal Server + Web Starter
2
 
3
+ This repository is a stripped-down template with:
4
+ - `server/` - FastAPI API with a single `/health` endpoint.
5
+ - `frontend/` - Vite + React app that renders a black full-screen page.
6
 
7
+ ## Quick Start
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ ### Server
10
  ```powershell
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  python -m venv .venv
12
+ .venv\Scripts\activate
13
+ pip install -r server/requirements.txt
14
+ uvicorn server.app.main:app --reload --port 8000
 
15
  ```
16
 
17
+ ### Web
18
+ ```powershell
 
 
 
 
 
 
 
 
 
19
  cd frontend
20
  npm install
21
  npm run dev
22
  ```
23
 
24
+ Open `http://localhost:5173`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/README.md DELETED
@@ -1,13 +0,0 @@
1
- # Agent Bundles
2
-
3
- Encapsulates prompts, tool definitions, and orchestration glue for each OpenAI Agent engaged in the pipeline.
4
-
5
- ## Structure
6
-
7
- - `orchestrator/` - Coordinator agent spec, high-level playbooks, and session controller logic.
8
- - `extraction/` - Document clause extraction prompts, tool configs for parsers, and output schemas.
9
- - `standards_mapping/` - Normalization/mapping prompts, ontology helpers, and reference datasets.
10
- - `rewrite/` - Minimal-change rewrite prompts, safety rules, and diff formatting helpers.
11
- - `validation/` - Post-rewrite review prompts, calculation sanity checks, and reporting templates.
12
- - `export/` - Finalization prompts, merge instructions, and docx regeneration aids.
13
- - `shared/` - Common prompt fragments, JSON schema definitions, and evaluation heuristics.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/__init__.py DELETED
@@ -1,15 +0,0 @@
1
- from .orchestrator.agent import OrchestratorAgent
2
- from .extraction.agent import ExtractionAgent
3
- from .standards_mapping.agent import StandardsMappingAgent
4
- from .rewrite.agent import RewriteAgent
5
- from .validation.agent import ValidationAgent
6
- from .export.agent import ExportAgent
7
-
8
- __all__ = [
9
- "OrchestratorAgent",
10
- "ExtractionAgent",
11
- "StandardsMappingAgent",
12
- "RewriteAgent",
13
- "ValidationAgent",
14
- "ExportAgent",
15
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/export/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import ExportAgent
2
-
3
- __all__ = ["ExportAgent"]
 
 
 
 
agents/export/agent.py DELETED
@@ -1,112 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from pathlib import Path
5
- from typing import Any, Dict, List
6
-
7
- from docx import Document
8
-
9
- from server.app.services.diagnostics_service import get_diagnostics_service
10
-
11
- from ..shared.base import AgentContext, BaseAgent
12
-
13
-
14
- def _apply_table_replacements(document: Document, replacements: List[Dict[str, Any]]) -> int:
15
- tables = document.tables
16
- applied = 0
17
- for item in replacements:
18
- index = item.get("table_index")
19
- updated_rows = item.get("updated_rows")
20
- if not isinstance(index, int) or index < 0 or index >= len(tables):
21
- continue
22
- if not isinstance(updated_rows, list):
23
- continue
24
- table = tables[index]
25
- for row_idx, row_values in enumerate(updated_rows):
26
- if row_idx < len(table.rows):
27
- row = table.rows[row_idx]
28
- else:
29
- row = table.add_row()
30
- for col_idx, value in enumerate(row_values):
31
- if col_idx < len(row.cells):
32
- row.cells[col_idx].text = str(value) if value is not None else ""
33
- else:
34
- break
35
- applied += 1
36
- return applied
37
-
38
-
39
- def _apply_replacements(document: Document, replacements: List[Dict[str, Any]]) -> int:
40
- applied = 0
41
- paragraphs = document.paragraphs
42
- for item in replacements:
43
- try:
44
- index = int(item.get("paragraph_index"))
45
- except (TypeError, ValueError):
46
- continue
47
- if index < 0 or index >= len(paragraphs):
48
- continue
49
- updated_text = item.get("updated_text")
50
- if not isinstance(updated_text, str):
51
- continue
52
- paragraphs[index].text = updated_text
53
- applied += 1
54
- return applied
55
-
56
-
57
- class ExportAgent(BaseAgent):
58
- name = "export-agent"
59
-
60
- async def run(self, context: AgentContext) -> Dict[str, Any]:
61
- await self.emit_debug("Exporting updated document to DOCX.")
62
-
63
- rewrite_plan = context.payload.get("rewrite_plan") or {}
64
- replacements = rewrite_plan.get("replacements") or []
65
- table_replacements = rewrite_plan.get("table_replacements") or []
66
- source_path = context.payload.get("original_path")
67
- if not source_path or not Path(source_path).exists():
68
- raise RuntimeError("Original document path not supplied to export agent.")
69
-
70
- document = Document(source_path)
71
- applied_paragraphs = _apply_replacements(document, replacements)
72
- applied_tables = _apply_table_replacements(document, table_replacements)
73
-
74
- storage_root = _resolve_storage_root()
75
- export_dir = Path(storage_root) / "exports"
76
- export_dir.mkdir(parents=True, exist_ok=True)
77
- export_path = export_dir / f"{context.session_id}-converted.docx"
78
- document.save(export_path)
79
- diagnostics = get_diagnostics_service()
80
- diagnostics.record_event(
81
- node_id="exports",
82
- event_type="export.generated",
83
- message=f"Generated export for session `{context.session_id}`",
84
- metadata={
85
- "session_id": context.session_id,
86
- "path": str(export_path),
87
- "paragraph_replacements": applied_paragraphs,
88
- "table_updates": applied_tables,
89
- },
90
- )
91
-
92
- if applied_paragraphs or applied_tables:
93
- note = "Converted document generated using rewrite plan."
94
- else:
95
- note = "Export completed, but no replacements were applied."
96
-
97
- return {
98
- "export_path": str(export_path),
99
- "notes": note,
100
- "replacement_count": applied_paragraphs,
101
- "table_replacement_count": applied_tables,
102
- "generated_at": datetime.utcnow().isoformat(),
103
- }
104
-
105
-
106
- def _resolve_storage_root() -> Path:
107
- try:
108
- from server.app.core.config import get_settings # local import avoids circular dependency
109
-
110
- return get_settings().storage_dir
111
- except Exception: # noqa: BLE001
112
- return (Path(__file__).resolve().parents[2] / "storage").resolve()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/extraction/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import ExtractionAgent
2
-
3
- __all__ = ["ExtractionAgent"]
 
 
 
 
agents/extraction/agent.py DELETED
@@ -1,239 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import asdict, is_dataclass
4
- import json
5
- from typing import Any, Dict, Iterable, List
6
-
7
- from ..shared.base import AgentContext, BaseAgent
8
-
9
-
10
- class ExtractionAgent(BaseAgent):
11
- name = "extraction-agent"
12
- paragraph_chunk_size = 40
13
- table_chunk_size = 15
14
- max_text_chars = 1500
15
-
16
- async def run(self, context: AgentContext) -> Dict[str, Any]:
17
- raw_paragraphs = context.payload.get("paragraphs", [])
18
- raw_tables = context.payload.get("tables", [])
19
- paragraphs = [_prepare_paragraph(item, self.max_text_chars) for item in _normalise_items(raw_paragraphs)]
20
- tables = [_prepare_table(item, self.max_text_chars) for item in _normalise_items(raw_tables)]
21
- metadata = context.payload.get("metadata", {})
22
-
23
- if not paragraphs and not tables:
24
- await self.emit_debug("No document content supplied to extraction agent.")
25
- return {
26
- "document_summary": "",
27
- "sections": [],
28
- "tables": [],
29
- "references": [],
30
- "notes": "Skipped: no document content provided.",
31
- }
32
-
33
- schema = {
34
- "name": "ExtractionResult",
35
- "schema": {
36
- "type": "object",
37
- "properties": {
38
- "document_summary": {"type": "string"},
39
- "sections": {
40
- "type": "array",
41
- "items": {
42
- "type": "object",
43
- "properties": {
44
- "paragraph_index": {"type": "integer"},
45
- "text": {"type": "string"},
46
- "references": {
47
- "type": "array",
48
- "items": {"type": "string"},
49
- "default": [],
50
- },
51
- },
52
- "required": ["paragraph_index", "text"],
53
- "additionalProperties": False,
54
- },
55
- "default": [],
56
- },
57
- "tables": {
58
- "type": "array",
59
- "items": {
60
- "type": "object",
61
- "properties": {
62
- "table_index": {"type": "integer"},
63
- "summary": {"type": "string"},
64
- "references": {
65
- "type": "array",
66
- "items": {"type": "string"},
67
- "default": [],
68
- },
69
- },
70
- "required": ["table_index", "summary"],
71
- "additionalProperties": False,
72
- },
73
- "default": [],
74
- },
75
- "references": {
76
- "type": "array",
77
- "items": {"type": "string"},
78
- "default": [],
79
- },
80
- "notes": {"type": "string"},
81
- },
82
- "required": ["document_summary", "sections", "tables", "references"],
83
- "additionalProperties": False,
84
- },
85
- }
86
-
87
- model = self.settings.openai_model_extract
88
- aggregated_sections: List[Dict[str, Any]] = []
89
- aggregated_tables: List[Dict[str, Any]] = []
90
- aggregated_references: set[str] = set()
91
- summaries: List[str] = []
92
- notes: List[str] = []
93
-
94
- for chunk in _chunk_list(paragraphs, self.paragraph_chunk_size):
95
- batch = await self._process_batch(
96
- context=context,
97
- model=model,
98
- schema=schema,
99
- paragraphs=chunk,
100
- tables=[],
101
- metadata=metadata,
102
- )
103
- if batch:
104
- summaries.append(batch.get("document_summary", ""))
105
- aggregated_sections.extend(batch.get("sections", []))
106
- aggregated_tables.extend(batch.get("tables", []))
107
- aggregated_references.update(batch.get("references", []))
108
- if batch.get("notes"):
109
- notes.append(batch["notes"])
110
-
111
- for chunk in _chunk_list(tables, self.table_chunk_size):
112
- batch = await self._process_batch(
113
- context=context,
114
- model=model,
115
- schema=schema,
116
- paragraphs=[],
117
- tables=chunk,
118
- metadata=metadata,
119
- )
120
- if batch:
121
- aggregated_tables.extend(batch.get("tables", []))
122
- aggregated_references.update(batch.get("references", []))
123
- if batch.get("notes"):
124
- notes.append(batch["notes"])
125
-
126
- summary = " ".join(filter(None, summaries)).strip()
127
-
128
- for item in paragraphs:
129
- aggregated_references.update(item.get("references", []))
130
- for item in tables:
131
- aggregated_references.update(item.get("references", []))
132
-
133
- return {
134
- "document_summary": summary,
135
- "sections": aggregated_sections,
136
- "tables": aggregated_tables,
137
- "references": sorted(aggregated_references),
138
- "notes": " ".join(notes).strip(),
139
- }
140
-
141
- async def _process_batch(
142
- self,
143
- *,
144
- context: AgentContext,
145
- model: str,
146
- schema: Dict[str, Any],
147
- paragraphs: List[Dict[str, Any]],
148
- tables: List[Dict[str, Any]],
149
- metadata: Dict[str, Any],
150
- ) -> Dict[str, Any]:
151
- if not paragraphs and not tables:
152
- return {}
153
-
154
- payload = {
155
- "paragraphs": paragraphs,
156
- "tables": tables,
157
- "metadata": metadata,
158
- }
159
-
160
- messages = [
161
- {
162
- "role": "system",
163
- "content": (
164
- "You are an engineering standards analyst. "
165
- "Analyse the supplied report content, identify normative references, "
166
- "and return structured data following the JSON schema."
167
- ),
168
- },
169
- {
170
- "role": "user",
171
- "content": (
172
- f"Session ID: {context.session_id}\n"
173
- f"Payload: {json.dumps(payload, ensure_ascii=False)}"
174
- ),
175
- },
176
- ]
177
-
178
- try:
179
- result = await self.call_openai_json(model=model, messages=messages, schema=schema)
180
- return result
181
- except Exception as exc: # noqa: BLE001
182
- await self.emit_debug(f"Extraction chunk failed: {exc}")
183
- return {
184
- "document_summary": "",
185
- "sections": [],
186
- "tables": [],
187
- "references": [],
188
- "notes": f"Chunk failed: {exc}",
189
- }
190
-
191
-
192
- def _normalise_items(items: List[Any]) -> List[Dict[str, Any]]:
193
- normalised: List[Dict[str, Any]] = []
194
- for item in items:
195
- if is_dataclass(item):
196
- normalised.append(asdict(item))
197
- elif isinstance(item, dict):
198
- normalised.append(item)
199
- else:
200
- normalised.append({"value": str(item)})
201
- return normalised
202
-
203
-
204
- def _prepare_paragraph(item: Dict[str, Any], max_chars: int) -> Dict[str, Any]:
205
- text = item.get("text", "")
206
- if len(text) > max_chars:
207
- text = text[:max_chars] + "...(trimmed)"
208
- return {
209
- "index": item.get("index"),
210
- "text": text,
211
- "style": item.get("style"),
212
- "heading_level": item.get("heading_level"),
213
- "references": item.get("references", []),
214
- }
215
-
216
-
217
- def _prepare_table(item: Dict[str, Any], max_chars: int) -> Dict[str, Any]:
218
- rows = item.get("rows", [])
219
- preview_rows = []
220
- for row in rows:
221
- preview_row = []
222
- for cell in row:
223
- cell_text = str(cell)
224
- if len(cell_text) > max_chars:
225
- cell_text = cell_text[:max_chars] + "...(trimmed)"
226
- preview_row.append(cell_text)
227
- preview_rows.append(preview_row)
228
- return {
229
- "index": item.get("index"),
230
- "rows": preview_rows,
231
- "references": item.get("references", []),
232
- }
233
-
234
-
235
- def _chunk_list(items: List[Dict[str, Any]], size: int) -> Iterable[List[Dict[str, Any]]]:
236
- if size <= 0:
237
- size = len(items) or 1
238
- for idx in range(0, len(items), size):
239
- yield items[idx : idx + size]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/orchestrator/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import OrchestratorAgent
2
-
3
- __all__ = ["OrchestratorAgent"]
 
 
 
 
agents/orchestrator/agent.py DELETED
@@ -1,18 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict
4
-
5
- from ..shared.base import AgentContext, BaseAgent
6
-
7
-
8
- class OrchestratorAgent(BaseAgent):
9
- name = "orchestrator-agent"
10
-
11
- async def run(self, context: AgentContext) -> Dict[str, Any]:
12
- await self.emit_debug(f"Received session {context.session_id}")
13
- # In a future iteration this agent will orchestrate sub-agent calls.
14
- return {
15
- "next_stage": "ingest",
16
- "notes": "Placeholder orchestrator response.",
17
- "input_payload": context.payload,
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/rewrite/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import RewriteAgent
2
-
3
- __all__ = ["RewriteAgent"]
 
 
 
 
agents/rewrite/agent.py DELETED
@@ -1,315 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from typing import Any, Dict, Iterable, List, Sequence
5
-
6
- from ..shared.base import AgentContext, BaseAgent
7
-
8
-
9
- class RewriteAgent(BaseAgent):
10
- name = "rewrite-agent"
11
- paragraph_chunk_size = 20
12
- max_paragraph_chars = 1600
13
- max_table_chars = 1200
14
-
15
- async def run(self, context: AgentContext) -> Dict[str, Any]:
16
- mapping_result = context.payload.get("mapping_result") or {}
17
- mappings: List[Dict[str, Any]] = mapping_result.get("mappings", [])
18
- if not mappings:
19
- await self.emit_debug("Rewrite skipped: no mappings provided.")
20
- return {
21
- "replacements": [],
22
- "table_replacements": [],
23
- "change_log": [],
24
- "notes": "Rewrite skipped: no mappings provided.",
25
- }
26
-
27
- mapping_by_reference = _index_mappings(mappings)
28
- doc_paragraphs = _normalise_paragraphs(
29
- context.payload.get("document_paragraphs", []), self.max_paragraph_chars
30
- )
31
- doc_tables = _normalise_tables(
32
- context.payload.get("document_tables", []), self.max_table_chars
33
- )
34
-
35
- paragraphs_to_rewrite = [
36
- paragraph
37
- for paragraph in doc_paragraphs
38
- if any(ref in mapping_by_reference for ref in paragraph["references"])
39
- ]
40
- tables_to_rewrite = [
41
- table
42
- for table in doc_tables
43
- if any(ref in mapping_by_reference for ref in table["references"])
44
- ]
45
-
46
- if not paragraphs_to_rewrite and not tables_to_rewrite:
47
- await self.emit_debug("Rewrite skipped: no paragraphs or tables matched mapped references.")
48
- return {
49
- "replacements": [],
50
- "table_replacements": [],
51
- "change_log": [],
52
- "notes": "Rewrite skipped: no references found in document.",
53
- }
54
-
55
- aggregated_replacements: List[Dict[str, Any]] = []
56
- aggregated_table_replacements: List[Dict[str, Any]] = []
57
- change_log_entries: List[Dict[str, Any]] = []
58
- change_log_seen: set[tuple] = set()
59
- notes: List[str] = []
60
- target_voice = context.payload.get("target_voice", "Professional engineering tone")
61
- constraints = context.payload.get("constraints", [])
62
-
63
- pending_tables = {table["index"]: table for table in tables_to_rewrite}
64
-
65
- for chunk in _chunk_list(paragraphs_to_rewrite, self.paragraph_chunk_size):
66
- relevant_refs = sorted(
67
- {
68
- reference
69
- for paragraph in chunk
70
- for reference in paragraph["references"]
71
- if reference in mapping_by_reference
72
- }
73
- )
74
- if not relevant_refs:
75
- continue
76
-
77
- mapping_subset = _collect_mapping_subset(mapping_by_reference, relevant_refs)
78
- associated_tables = _collect_tables_for_refs(pending_tables, relevant_refs)
79
-
80
- payload = {
81
- "instructions": {
82
- "target_voice": target_voice,
83
- "constraints": constraints,
84
- "guidance": [
85
- "Preserve numbering, bullet markers, and formatting cues.",
86
- "Do not alter calculations, quantities, or engineering values.",
87
- "Only update normative references and surrounding wording necessary for clarity.",
88
- "Maintain section titles and headings.",
89
- ],
90
- },
91
- "paragraphs": chunk,
92
- "tables": associated_tables,
93
- "mappings": mapping_subset,
94
- }
95
-
96
- schema = _rewrite_schema()
97
- messages = [
98
- {
99
- "role": "system",
100
- "content": (
101
- "You are an engineering editor updating a report so its references align with the target standards. "
102
- "Return JSON matching the schema. Maintain original structure and numbering while replacing each reference with the mapped target references."
103
- ),
104
- },
105
- {
106
- "role": "user",
107
- "content": (
108
- f"Session: {context.session_id}\n"
109
- f"Payload: {json.dumps(payload, ensure_ascii=False)}"
110
- ),
111
- },
112
- ]
113
-
114
- try:
115
- result = await self.call_openai_json(
116
- model=self.settings.openai_model_rewrite,
117
- messages=messages,
118
- schema=schema,
119
- )
120
- aggregated_replacements.extend(result.get("replacements", []))
121
- aggregated_table_replacements.extend(result.get("table_replacements", []))
122
- for entry in result.get("change_log", []):
123
- key = (
124
- entry.get("reference"),
125
- entry.get("target_reference"),
126
- tuple(entry.get("affected_paragraphs", [])),
127
- )
128
- if key not in change_log_seen:
129
- change_log_entries.append(entry)
130
- change_log_seen.add(key)
131
- if result.get("notes"):
132
- notes.append(result["notes"])
133
- except Exception as exc: # noqa: BLE001
134
- await self.emit_debug(f"Rewrite chunk failed: {exc}")
135
- notes.append(f"Rewrite chunk failed: {exc}")
136
-
137
- if not aggregated_replacements and not aggregated_table_replacements:
138
- return {
139
- "replacements": [],
140
- "table_replacements": [],
141
- "change_log": change_log_entries,
142
- "notes": "Rewrite completed but no updates were suggested.",
143
- }
144
-
145
- return {
146
- "replacements": aggregated_replacements,
147
- "table_replacements": aggregated_table_replacements,
148
- "change_log": change_log_entries,
149
- "notes": " ".join(notes).strip(),
150
- }
151
-
152
-
153
- def _index_mappings(mappings: Sequence[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
154
- index: Dict[str, List[Dict[str, Any]]] = {}
155
- for mapping in mappings:
156
- ref = mapping.get("source_reference")
157
- if not isinstance(ref, str):
158
- continue
159
- index.setdefault(ref, []).append(mapping)
160
- return index
161
-
162
-
163
- def _collect_mapping_subset(
164
- mapping_by_reference: Dict[str, List[Dict[str, Any]]],
165
- references: Sequence[str],
166
- ) -> List[Dict[str, Any]]:
167
- subset: List[Dict[str, Any]] = []
168
- for reference in references:
169
- subset.extend(mapping_by_reference.get(reference, []))
170
- return subset
171
-
172
-
173
- def _collect_tables_for_refs(
174
- pending_tables: Dict[int, Dict[str, Any]],
175
- references: Sequence[str],
176
- ) -> List[Dict[str, Any]]:
177
- matched: List[Dict[str, Any]] = []
178
- for index in list(pending_tables.keys()):
179
- table = pending_tables[index]
180
- if any(ref in references for ref in table["references"]):
181
- matched.append(table)
182
- pending_tables.pop(index, None)
183
- return matched
184
-
185
-
186
- def _normalise_paragraphs(items: Sequence[Dict[str, Any]], max_chars: int) -> List[Dict[str, Any]]:
187
- paragraphs: List[Dict[str, Any]] = []
188
- for item in items:
189
- index = item.get("index")
190
- if index is None:
191
- continue
192
- text = str(item.get("text", ""))
193
- if len(text) > max_chars:
194
- text = text[:max_chars] + "...(trimmed)"
195
- paragraphs.append(
196
- {
197
- "index": index,
198
- "text": text,
199
- "style": item.get("style"),
200
- "heading_level": item.get("heading_level"),
201
- "references": item.get("references", []),
202
- }
203
- )
204
- return paragraphs
205
-
206
-
207
- def _normalise_tables(items: Sequence[Dict[str, Any]], max_chars: int) -> List[Dict[str, Any]]:
208
- tables: List[Dict[str, Any]] = []
209
- for item in items:
210
- index = item.get("index")
211
- if index is None:
212
- continue
213
- rows = []
214
- for row in item.get("rows", []):
215
- preview_row = []
216
- for cell in row:
217
- cell_text = str(cell)
218
- if len(cell_text) > max_chars:
219
- cell_text = cell_text[:max_chars] + "...(trimmed)"
220
- preview_row.append(cell_text)
221
- rows.append(preview_row)
222
- tables.append(
223
- {
224
- "index": index,
225
- "rows": rows,
226
- "references": item.get("references", []),
227
- }
228
- )
229
- return tables
230
-
231
-
232
- def _chunk_list(items: Sequence[Dict[str, Any]], size: int) -> Iterable[List[Dict[str, Any]]]:
233
- if size <= 0:
234
- size = len(items) or 1
235
- for idx in range(0, len(items), size):
236
- yield list(items[idx : idx + size])
237
-
238
-
239
- def _rewrite_schema() -> Dict[str, Any]:
240
- return {
241
- "name": "RewritePlanChunk",
242
- "schema": {
243
- "type": "object",
244
- "properties": {
245
- "replacements": {
246
- "type": "array",
247
- "items": {
248
- "type": "object",
249
- "properties": {
250
- "paragraph_index": {"type": "integer"},
251
- "original_text": {"type": "string"},
252
- "updated_text": {"type": "string"},
253
- "applied_mappings": {
254
- "type": "array",
255
- "items": {"type": "string"},
256
- "default": [],
257
- },
258
- "change_reason": {"type": "string"},
259
- },
260
- "required": ["paragraph_index", "updated_text"],
261
- "additionalProperties": False,
262
- },
263
- "default": [],
264
- },
265
- "table_replacements": {
266
- "type": "array",
267
- "items": {
268
- "type": "object",
269
- "properties": {
270
- "table_index": {"type": "integer"},
271
- "updated_rows": {
272
- "type": "array",
273
- "items": {
274
- "type": "array",
275
- "items": {"type": "string"},
276
- },
277
- "default": [],
278
- },
279
- "applied_mappings": {
280
- "type": "array",
281
- "items": {"type": "string"},
282
- "default": [],
283
- },
284
- "change_reason": {"type": "string"},
285
- },
286
- "required": ["table_index"],
287
- "additionalProperties": False,
288
- },
289
- "default": [],
290
- },
291
- "change_log": {
292
- "type": "array",
293
- "items": {
294
- "type": "object",
295
- "properties": {
296
- "reference": {"type": "string"},
297
- "target_reference": {"type": "string"},
298
- "affected_paragraphs": {
299
- "type": "array",
300
- "items": {"type": "integer"},
301
- "default": [],
302
- },
303
- "note": {"type": "string"},
304
- },
305
- "required": ["reference", "target_reference"],
306
- "additionalProperties": False,
307
- },
308
- "default": [],
309
- },
310
- "notes": {"type": "string"},
311
- },
312
- "required": ["replacements", "table_replacements", "change_log"],
313
- "additionalProperties": False,
314
- },
315
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/shared/base.py DELETED
@@ -1,49 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from dataclasses import dataclass, field
5
- from typing import Any, Dict
6
-
7
- # We import config lazily to avoid circular imports during module initialisation.
8
- from typing import TYPE_CHECKING
9
-
10
- if TYPE_CHECKING:
11
- from server.app.core.config import Settings # pragma: no cover
12
-
13
-
14
- @dataclass
15
- class AgentContext:
16
- session_id: str
17
- payload: Dict[str, Any] = field(default_factory=dict)
18
-
19
-
20
- class BaseAgent(ABC):
21
- name: str
22
-
23
- @abstractmethod
24
- async def run(self, context: AgentContext) -> Dict[str, Any]:
25
- """Execute agent logic and return structured output."""
26
-
27
- async def emit_debug(self, message: str) -> None:
28
- # Placeholder until logging/event bus is wired in.
29
- print(f"[{self.name}] {message}")
30
-
31
- @property
32
- def settings(self):
33
- from server.app.core.config import get_settings # import here to avoid circular dependency
34
-
35
- return get_settings()
36
-
37
- async def call_openai_json(
38
- self,
39
- *,
40
- model: str,
41
- messages: list[Dict[str, Any]],
42
- schema: Dict[str, Any],
43
- ) -> Dict[str, Any]:
44
- from .client import create_json_response # import here to avoid circular dependency
45
-
46
- if not self.settings.openai_api_key:
47
- await self.emit_debug("OpenAI API key missing; returning empty response.")
48
- raise RuntimeError("OpenAI API key missing")
49
- return await create_json_response(model=model, messages=messages, schema=schema)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/shared/client.py DELETED
@@ -1,97 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import logging
5
- from functools import lru_cache
6
- from typing import Any, Iterable
7
-
8
- from openai import AsyncOpenAI, OpenAIError
9
-
10
- try:
11
- from server.app.core.config import get_settings
12
- from server.app.services.diagnostics_service import get_diagnostics_service
13
- except ModuleNotFoundError as exc: # pragma: no cover
14
- raise RuntimeError(
15
- "Failed to import server configuration. Ensure the project root is on PYTHONPATH."
16
- ) from exc
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- @lru_cache
22
- def get_openai_client() -> AsyncOpenAI:
23
- settings = get_settings()
24
- if not settings.openai_api_key:
25
- diagnostics = get_diagnostics_service()
26
- diagnostics.record_event(
27
- node_id="openai",
28
- event_type="openai.missing_key",
29
- message="OpenAI API key missing; requests will fail.",
30
- metadata={},
31
- )
32
- raise RuntimeError(
33
- "OpenAI API key is not configured. Set RIGHTCODES_OPENAI_API_KEY before invoking agents."
34
- )
35
- diagnostics = get_diagnostics_service()
36
- diagnostics.record_event(
37
- node_id="openai",
38
- event_type="openai.client_ready",
39
- message="OpenAI client initialised.",
40
- metadata={},
41
- )
42
- return AsyncOpenAI(api_key=settings.openai_api_key, base_url=settings.openai_api_base)
43
-
44
-
45
- async def create_json_response(
46
- *,
47
- model: str,
48
- messages: Iterable[dict[str, Any]],
49
- schema: dict[str, Any],
50
- ) -> dict[str, Any]:
51
- """Invoke OpenAI with a JSON schema response format."""
52
- client = get_openai_client()
53
- diagnostics = get_diagnostics_service()
54
- diagnostics.record_event(
55
- node_id="openai",
56
- event_type="openai.request",
57
- message=f"Requesting model `{model}`",
58
- metadata={"model": model},
59
- )
60
- try:
61
- response = await client.chat.completions.create(
62
- model=model,
63
- messages=list(messages),
64
- response_format={"type": "json_schema", "json_schema": schema},
65
- )
66
- except OpenAIError as exc:
67
- logger.exception("OpenAI call failed: %s", exc)
68
- diagnostics.record_event(
69
- node_id="openai",
70
- event_type="openai.error",
71
- message="OpenAI request failed.",
72
- metadata={"model": model, "error": str(exc)},
73
- )
74
- raise
75
-
76
- try:
77
- choice = response.choices[0]
78
- content = choice.message.content if choice and choice.message else None
79
- if not content:
80
- raise RuntimeError("OpenAI response did not include message content.")
81
- payload = json.loads(content)
82
- diagnostics.record_event(
83
- node_id="openai",
84
- event_type="openai.response",
85
- message="Received OpenAI response.",
86
- metadata={"model": model},
87
- )
88
- return payload
89
- except (AttributeError, json.JSONDecodeError) as exc:
90
- logger.exception("Failed to decode OpenAI response: %s", exc)
91
- diagnostics.record_event(
92
- node_id="openai",
93
- event_type="openai.error",
94
- message="Failed to decode OpenAI response.",
95
- metadata={"model": model, "error": str(exc)},
96
- )
97
- raise RuntimeError("OpenAI response was not valid JSON.") from exc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/shared/embeddings.py DELETED
@@ -1,19 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Iterable, List, Sequence
4
-
5
-
6
-
7
- async def embed_texts(texts: Iterable[str]) -> List[List[float]]:
8
- texts = [text if text else "" for text in texts]
9
- if not texts:
10
- return []
11
- from ..shared.client import get_openai_client
12
- client = get_openai_client()
13
- from server.app.core.config import get_settings
14
- settings = get_settings()
15
- response = await client.embeddings.create(
16
- model=settings.openai_model_embed,
17
- input=list(texts),
18
- )
19
- return [item.embedding for item in response.data]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/standards_mapping/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import StandardsMappingAgent
2
-
3
- __all__ = ["StandardsMappingAgent"]
 
 
 
 
agents/standards_mapping/agent.py DELETED
@@ -1,293 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import asdict, is_dataclass
4
- import json
5
- from pathlib import Path
6
- from typing import Any, Dict, Iterable, List
7
-
8
- from ..shared.base import AgentContext, BaseAgent
9
- from ..shared.embeddings import embed_texts
10
- from common.embedding_store import EmbeddingStore, get_session_embedding_path
11
-
12
-
13
- class StandardsMappingAgent(BaseAgent):
14
- name = "standards-mapping-agent"
15
- reference_chunk_size = 20
16
- max_excerpt_chars = 800
17
-
18
- async def run(self, context: AgentContext) -> Dict[str, Any]:
19
- extraction_result = context.payload.get("extraction_result") or {}
20
- references: List[str] = extraction_result.get("references") or []
21
- sections = extraction_result.get("sections") or []
22
- tables = extraction_result.get("tables") or []
23
- standards_chunks = _normalise_items(context.payload.get("standards_chunks", []))
24
- target_metadata = context.payload.get("target_metadata", {})
25
- store = EmbeddingStore(get_session_embedding_path(context.session_id))
26
-
27
- if not references or not standards_chunks:
28
- await self.emit_debug("Insufficient data for standards mapping.")
29
- return {
30
- "mappings": [],
31
- "unmapped_references": references,
32
- "notes": "Mapping skipped due to missing references or standards content.",
33
- }
34
-
35
- schema = {
36
- "name": "StandardsMapping",
37
- "schema": {
38
- "type": "object",
39
- "properties": {
40
- "mappings": {
41
- "type": "array",
42
- "items": {
43
- "type": "object",
44
- "properties": {
45
- "source_reference": {"type": "string"},
46
- "source_context": {"type": "string"},
47
- "target_reference": {"type": "string"},
48
- "target_clause": {"type": "string"},
49
- "target_summary": {"type": "string"},
50
- "confidence": {"type": "number"},
51
- "rationale": {"type": "string"},
52
- },
53
- "required": [
54
- "source_reference",
55
- "target_reference",
56
- "confidence",
57
- ],
58
- "additionalProperties": False,
59
- },
60
- "default": [],
61
- },
62
- "unmapped_references": {
63
- "type": "array",
64
- "items": {"type": "string"},
65
- "default": [],
66
- },
67
- "notes": {"type": "string"},
68
- },
69
- "required": ["mappings", "unmapped_references"],
70
- "additionalProperties": False,
71
- },
72
- }
73
-
74
- standards_overview = _build_standards_overview(standards_chunks, self.max_excerpt_chars)
75
-
76
- model = self.settings.openai_model_mapping
77
- aggregated_mappings: List[Dict[str, Any]] = []
78
- aggregated_unmapped: set[str] = set()
79
- notes: List[str] = []
80
-
81
- for chunk in _chunk_list(references, self.reference_chunk_size):
82
- reference_context = _build_reference_context(
83
- chunk, sections, tables, self.max_excerpt_chars
84
- )
85
- retrieved_candidates = await _retrieve_candidates(
86
- chunk, reference_context, store, target_metadata, self.max_excerpt_chars
87
- )
88
- payload = {
89
- "references": chunk,
90
- "reference_context": reference_context,
91
- "retrieved_candidates": [
92
- {"reference": ref, "candidates": retrieved_candidates.get(ref, [])}
93
- for ref in chunk
94
- ],
95
- "standards_overview": standards_overview,
96
- "target_metadata": target_metadata,
97
- }
98
-
99
- messages = [
100
- {
101
- "role": "system",
102
- "content": (
103
- "You are an engineering standards migration specialist. "
104
- "Map each legacy reference to the best matching clause in the target standards. "
105
- "Use the provided context and standards overview to justify your mapping. "
106
- "Return JSON that conforms to the supplied schema."
107
- ),
108
- },
109
- {
110
- "role": "user",
111
- "content": (
112
- f"Session: {context.session_id}\n"
113
- f"Payload: {json.dumps(payload, ensure_ascii=False)}"
114
- ),
115
- },
116
- ]
117
-
118
- try:
119
- result = await self.call_openai_json(model=model, messages=messages, schema=schema)
120
- aggregated_mappings.extend(result.get("mappings", []))
121
- aggregated_unmapped.update(result.get("unmapped_references", []))
122
- if result.get("notes"):
123
- notes.append(result["notes"])
124
- except Exception as exc: # noqa: BLE001
125
- await self.emit_debug(f"Standards mapping chunk failed: {exc}")
126
- aggregated_unmapped.update(chunk)
127
- notes.append(f"Chunk failed: {exc}")
128
-
129
- await self.emit_debug("Standards mapping completed via OpenAI.")
130
- return {
131
- "mappings": aggregated_mappings,
132
- "unmapped_references": sorted(aggregated_unmapped),
133
- "notes": " ".join(notes).strip(),
134
- }
135
-
136
-
137
- def _normalise_items(items: List[Any]) -> List[Dict[str, Any]]:
138
- normalised: List[Dict[str, Any]] = []
139
- for item in items:
140
- if is_dataclass(item):
141
- normalised.append(asdict(item))
142
- elif isinstance(item, dict):
143
- normalised.append(item)
144
- else:
145
- normalised.append({"text": str(item)})
146
- return normalised
147
-
148
-
149
- def _chunk_list(items: List[str], size: int) -> Iterable[List[str]]:
150
- if size <= 0:
151
- size = len(items) or 1
152
- for idx in range(0, len(items), size):
153
- yield items[idx : idx + size]
154
-
155
-
156
- def _build_reference_context(
157
- references: List[str],
158
- sections: List[Dict[str, Any]],
159
- tables: List[Dict[str, Any]],
160
- max_chars: int,
161
- ) -> List[Dict[str, Any]]:
162
- section_map: Dict[str, List[Dict[str, Any]]] = {}
163
- for section in sections:
164
- refs = section.get("references") or []
165
- for ref in refs:
166
- section_map.setdefault(ref, [])
167
- if len(section_map[ref]) < 3:
168
- text = section.get("text", "")
169
- if len(text) > max_chars:
170
- text = text[:max_chars] + "...(trimmed)"
171
- section_map[ref].append(
172
- {
173
- "paragraph_index": section.get("paragraph_index"),
174
- "text": text,
175
- }
176
- )
177
- table_map: Dict[str, List[Dict[str, Any]]] = {}
178
- for table in tables:
179
- refs = table.get("references") or []
180
- for ref in refs:
181
- table_map.setdefault(ref, [])
182
- if len(table_map[ref]) < 2:
183
- table_map[ref].append({"table_index": table.get("table_index"), "references": refs})
184
-
185
- context = []
186
- for ref in references:
187
- context.append(
188
- {
189
- "reference": ref,
190
- "paragraphs": section_map.get(ref, []),
191
- "tables": table_map.get(ref, []),
192
- }
193
- )
194
- return context
195
-
196
-
197
- def _build_standards_overview(
198
- standards_chunks: List[Dict[str, Any]],
199
- max_chars: int,
200
- ) -> List[Dict[str, Any]]:
201
- grouped: Dict[str, Dict[str, Any]] = {}
202
- for chunk in standards_chunks:
203
- path = chunk.get("path", "unknown")
204
- heading = chunk.get("heading")
205
- clauses = chunk.get("clause_numbers") or []
206
- text = chunk.get("text", "")
207
- if len(text) > max_chars:
208
- text = text[:max_chars] + "...(trimmed)"
209
-
210
- group = grouped.setdefault(
211
- path,
212
- {
213
- "document": Path(path).name,
214
- "headings": [],
215
- "clauses": [],
216
- "snippets": [],
217
- },
218
- )
219
- if heading and heading not in group["headings"] and len(group["headings"]) < 120:
220
- group["headings"].append(heading)
221
- for clause in clauses:
222
- if clause not in group["clauses"] and len(group["clauses"]) < 120:
223
- group["clauses"].append(clause)
224
- if text and len(group["snippets"]) < 30:
225
- group["snippets"].append(text)
226
-
227
- overview: List[Dict[str, Any]] = []
228
- for data in grouped.values():
229
- overview.append(
230
- {
231
- "document": data["document"],
232
- "headings": data["headings"][:50],
233
- "clauses": data["clauses"][:50],
234
- "snippets": data["snippets"],
235
- }
236
- )
237
- return overview[:30]
238
-
239
-
240
- async def _retrieve_candidates(
241
- references: List[str],
242
- reference_context: List[Dict[str, Any]],
243
- store: EmbeddingStore,
244
- target_metadata: Dict[str, Any],
245
- max_chars: int,
246
- ) -> Dict[str, List[Dict[str, Any]]]:
247
- if not references:
248
- return {}
249
- if store.is_empty:
250
- return {ref: [] for ref in references}
251
-
252
- context_lookup = {entry["reference"]: entry for entry in reference_context}
253
- embed_inputs = [
254
- _compose_reference_embedding_input(
255
- reference,
256
- context_lookup.get(reference, {}),
257
- target_metadata,
258
- max_chars,
259
- )
260
- for reference in references
261
- ]
262
- vectors = await embed_texts(embed_inputs)
263
- results: Dict[str, List[Dict[str, Any]]] = {}
264
- for reference, vector in zip(references, vectors):
265
- candidates = store.query(vector, top_k=8)
266
- results[reference] = candidates
267
- return results
268
-
269
-
270
- def _compose_reference_embedding_input(
271
- reference: str,
272
- context_entry: Dict[str, Any],
273
- target_metadata: Dict[str, Any],
274
- max_chars: int,
275
- ) -> str:
276
- lines = [reference]
277
- target_standard = target_metadata.get("target_standard")
278
- if target_standard:
279
- lines.append(f"Target standard family: {target_standard}")
280
- paragraphs = context_entry.get("paragraphs") or []
281
- for paragraph in paragraphs[:2]:
282
- text = paragraph.get("text")
283
- if text:
284
- lines.append(text)
285
- tables = context_entry.get("tables") or []
286
- if tables:
287
- refs = tables[0].get("references") or []
288
- if refs:
289
- lines.append("Table references: " + ", ".join(refs))
290
- text = "\n".join(filter(None, lines))
291
- if len(text) > max_chars:
292
- text = text[:max_chars] + "...(trimmed)"
293
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/validation/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .agent import ValidationAgent
2
-
3
- __all__ = ["ValidationAgent"]
 
 
 
 
agents/validation/agent.py DELETED
@@ -1,96 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from typing import Any, Dict, List
5
-
6
- from ..shared.base import AgentContext, BaseAgent
7
-
8
-
9
- class ValidationAgent(BaseAgent):
10
- name = "validation-agent"
11
-
12
- async def run(self, context: AgentContext) -> Dict[str, Any]:
13
- await self.emit_debug("Running compliance checks.")
14
-
15
- extraction = context.payload.get("extraction_result") or {}
16
- mapping = context.payload.get("mapping_result") or {}
17
- rewrite_plan = context.payload.get("rewrite_plan") or {}
18
-
19
- if not mapping or not rewrite_plan:
20
- return {
21
- "issues": [],
22
- "verdict": "pending",
23
- "notes": "Validation skipped because mapping or rewrite data was unavailable.",
24
- }
25
-
26
- schema = {
27
- "name": "ValidationReport",
28
- "schema": {
29
- "type": "object",
30
- "properties": {
31
- "verdict": {
32
- "type": "string",
33
- "enum": ["approved", "changes_requested", "pending"],
34
- },
35
- "issues": {
36
- "type": "array",
37
- "items": {
38
- "type": "object",
39
- "properties": {
40
- "description": {"type": "string"},
41
- "severity": {
42
- "type": "string",
43
- "enum": ["info", "low", "medium", "high"],
44
- },
45
- "related_reference": {"type": "string"},
46
- },
47
- "required": ["description", "severity"],
48
- "additionalProperties": False,
49
- },
50
- "default": [],
51
- },
52
- "notes": {"type": "string"},
53
- },
54
- "required": ["verdict", "issues"],
55
- "additionalProperties": False,
56
- },
57
- }
58
-
59
- mapping_snippet = json.dumps(mapping.get("mappings", [])[:20], ensure_ascii=False)
60
- rewrite_snippet = json.dumps(rewrite_plan.get("replacements", [])[:20], ensure_ascii=False)
61
- references = extraction.get("references", [])
62
-
63
- messages = [
64
- {
65
- "role": "system",
66
- "content": (
67
- "You are a senior structural engineer reviewing a standards migration. "
68
- "Evaluate whether the proposed replacements maintain compliance and highlight any risks."
69
- ),
70
- },
71
- {
72
- "role": "user",
73
- "content": (
74
- f"Session: {context.session_id}\n"
75
- f"Detected references: {references}\n"
76
- f"Mappings sample: {mapping_snippet}\n"
77
- f"Rewrite sample: {rewrite_snippet}"
78
- ),
79
- },
80
- ]
81
-
82
- model = self.settings.openai_model_mapping
83
-
84
- try:
85
- result = await self.call_openai_json(model=model, messages=messages, schema=schema)
86
- await self.emit_debug("Validation agent completed via OpenAI.")
87
- if not result.get("notes"):
88
- result["notes"] = "Review generated by automated validation agent."
89
- return result
90
- except Exception as exc: # noqa: BLE001
91
- await self.emit_debug(f"Validation agent failed: {exc}")
92
- return {
93
- "issues": [],
94
- "verdict": "pending",
95
- "notes": f"Validation failed: {exc}",
96
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
common/README.md DELETED
@@ -1,10 +0,0 @@
1
- # Common Domain Assets
2
-
3
- Cross-cutting models, schemas, and events shared between backend services, agents, and workers.
4
-
5
- ## Structure
6
-
7
- - `models/` - Core domain entities and DTOs reused across services.
8
- - `utils/` - Reusable helper functions (text normalization, ID generation).
9
- - `schemas/` - JSON schema definitions for agent input/output contracts.
10
- - `events/` - Event payload definitions for pipeline instrumentation.
 
 
 
 
 
 
 
 
 
 
 
common/embedding_store.py DELETED
@@ -1,98 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
- from typing import Any, List, Sequence, Tuple
7
-
8
- import numpy as np
9
-
10
-
11
- def _resolve_embedding_root() -> Path:
12
- try:
13
- from server.app.core.config import get_settings # local import to avoid hard dependency at import time
14
-
15
- storage_dir = get_settings().storage_dir
16
- except Exception: # noqa: BLE001
17
- storage_dir = Path(__file__).resolve().parents[1] / "storage"
18
- return Path(storage_dir) / "embeddings"
19
-
20
-
21
- def get_session_embedding_path(session_id: str) -> Path:
22
- return _resolve_embedding_root() / f"{session_id}.json"
23
-
24
-
25
- @dataclass
26
- class EmbeddingRecord:
27
- vector: List[float]
28
- metadata: dict[str, Any]
29
-
30
-
31
- class EmbeddingStore:
32
- def __init__(self, path: Path) -> None:
33
- self.path = path
34
- self._records: list[EmbeddingRecord] = []
35
- self._matrix: np.ndarray | None = None
36
- self._load()
37
-
38
- def _load(self) -> None:
39
- if not self.path.exists():
40
- return
41
- with self.path.open("r", encoding="utf-8") as fh:
42
- data = json.load(fh)
43
- self._records = [EmbeddingRecord(**item) for item in data]
44
-
45
- def save(self) -> None:
46
- self.path.parent.mkdir(parents=True, exist_ok=True)
47
- with self.path.open("w", encoding="utf-8") as fh:
48
- json.dump(
49
- [{"vector": rec.vector, "metadata": rec.metadata} for rec in self._records],
50
- fh,
51
- ensure_ascii=False,
52
- indent=2,
53
- )
54
-
55
- def clear(self) -> None:
56
- self._records.clear()
57
- self._matrix = None
58
-
59
- def extend(self, vectors: Sequence[Sequence[float]], metadatas: Sequence[dict[str, Any]]) -> None:
60
- for vector, metadata in zip(vectors, metadatas, strict=True):
61
- self._records.append(EmbeddingRecord(list(vector), dict(metadata)))
62
- self._matrix = None
63
-
64
- @property
65
- def is_empty(self) -> bool:
66
- return not self._records
67
-
68
- def _ensure_matrix(self) -> None:
69
- if self._matrix is None and self._records:
70
- self._matrix = np.array([rec.vector for rec in self._records], dtype=np.float32)
71
-
72
- def query(self, vector: Sequence[float], top_k: int = 5) -> List[Tuple[dict[str, Any], float]]:
73
- if self.is_empty:
74
- return []
75
- self._ensure_matrix()
76
- assert self._matrix is not None
77
- matrix = self._matrix
78
- vec = np.array(vector, dtype=np.float32)
79
- vec_norm = np.linalg.norm(vec)
80
- if not np.isfinite(vec_norm) or vec_norm == 0:
81
- return []
82
- matrix_norms = np.linalg.norm(matrix, axis=1)
83
- scores = matrix @ vec / (matrix_norms * vec_norm + 1e-12)
84
- top_k = min(top_k, len(scores))
85
- indices = np.argsort(scores)[::-1][:top_k]
86
- results: List[Tuple[dict[str, Any], float]] = []
87
- for idx in indices:
88
- score = float(scores[idx])
89
- metadata = self._records[int(idx)].metadata.copy()
90
- metadata["score"] = score
91
- results.append((metadata, score))
92
- return results
93
-
94
- def query_many(self, vectors: Sequence[Sequence[float]], top_k: int = 5) -> List[List[dict[str, Any]]]:
95
- return [
96
- [meta for meta, _ in self.query(vector, top_k=top_k)]
97
- for vector in vectors
98
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docker/Dockerfile DELETED
@@ -1,20 +0,0 @@
1
- FROM node:20-bookworm
2
-
3
- RUN apt-get update \
4
- && apt-get install -y --no-install-recommends python3 python3-venv python3-pip \
5
- && rm -rf /var/lib/apt/lists/*
6
-
7
- WORKDIR /app
8
-
9
- COPY . /app
10
-
11
- RUN python3 -m venv /app/.venv \
12
- && /app/.venv/bin/pip install --upgrade pip \
13
- && /app/.venv/bin/pip install -r server/requirements.txt \
14
- && npm install --prefix frontend
15
-
16
- ENV PATH="/app/.venv/bin:${PATH}"
17
-
18
- EXPOSE 8000 5173 8765
19
-
20
- CMD ["python3", "start-rightcodes.py", "--host", "0.0.0.0", "--port", "8765", "--no-browser"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/README.md DELETED
@@ -1,12 +0,0 @@
1
- # Documentation Hub
2
-
3
- Authoritative documentation for architecture, pipelines, agents, and UI workflows.
4
-
5
- ## Structure
6
-
7
- - `architecture/` - System diagrams, deployment topology, and context views.
8
- - `agents/` - Detailed specs of each agent, including prompt design and tool APIs.
9
- - `pipelines/` - Pypeflow diagrams, data contracts, and runbooks for each stage.
10
- - `standards/` - Reference material and taxonomy notes for supported codes and standards.
11
- - `ui/` - Wireframes, component inventories, and interaction design specs.
12
- - `specifications/` - Functional requirements, acceptance criteria, and use cases.
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/agents/orchestrator.md DELETED
@@ -1,12 +0,0 @@
1
- # Orchestrator Agent (Draft)
2
-
3
- - **Goal:** Coordinate pipeline stages, persist state transitions, and request human intervention when confidence drops below threshold.
4
- - **Inputs:** `document_manifest.json`, latest stage outputs, user session preferences.
5
- - **Outputs:** `progress_state.json`, downstream agent invocation plans, notifications/events.
6
- - **Tool Hooks:** Worker queue enqueuer, storage manifest writer, validation reporter.
7
-
8
- Action items:
9
-
10
- 1. Define structured prompt schema and guardrails.
11
- 2. Enumerate tool signatures for queueing, status updates, and failure escalation.
12
- 3. Align logging with `common/events/` payload definitions.
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/architecture/system-context.md DELETED
@@ -1,11 +0,0 @@
1
- # System Context (Draft)
2
-
3
- - **Primary Actors:** Engineering consultant (user), Orchestrator Agent, Validation Agent, Export Agent.
4
- - **External Systems:** OpenAI APIs, Object storage (S3-compatible), Auth provider (to be determined).
5
- - **Key Data Stores:** Session manifest store, document blob storage, telemetry pipeline.
6
-
7
- Pending tasks:
8
-
9
- 1. Complete C4 level 1 context diagram.
10
- 2. Document trust boundaries (uploaded documents vs generated artefacts).
11
- 3. Define audit/logging requirements tied to standards compliance.
 
 
 
 
 
 
 
 
 
 
 
 
docs/pipelines/pypeflow-overview.md DELETED
@@ -1,8 +0,0 @@
1
- # Pypeflow Overview
2
-
3
- > Placeholder: document the end-to-end job graph once the first pipeline prototype lands. Recommended sections:
4
- >
5
- > 1. High-level mermaid diagram showing ingestion -> extraction -> mapping -> rewrite -> validation -> export.
6
- > 2. Stage-by-stage JSON artefact expectations (input/output schemas).
7
- > 3. Failure handling and retry strategy per stage.
8
- > 4. Hooks for agent overrides and manual approvals.
 
 
 
 
 
 
 
 
 
docs/ui/review-workflow.md DELETED
@@ -1,17 +0,0 @@
1
- # Review Workflow (Draft)
2
-
3
- Stages:
4
-
5
- 1. Upload wizard captures Word report, one or more standards PDFs, and mapping intent.
6
- 2. Diff workspace highlights replaced clauses with inline confidence tags.
7
- 3. Validation dashboard lists outstanding checks, comments, and approval history.
8
-
9
- Open questions:
10
-
11
- - How should we present clause-level provenance (link back to PDF page)?
12
- - Do we surface agent rationales verbatim or summarised?
13
- - What accessibility requirements should inform colour coding and indicators?
14
-
15
- UI notes:
16
-
17
- - Show upload progress and pipeline activity log so users know when each processing stage completes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/README.md DELETED
@@ -1,17 +0,0 @@
1
- # Frontend Module
2
-
3
- React/TypeScript single-page app responsible for user interaction and review workflows.
4
-
5
- ## Structure
6
-
7
- - `public/` - Static assets and HTML shell.
8
- - `src/components/` - Shared UI components (uploaders, diff panels, status widgets).
9
- - `src/pages/` - Route-level containers for upload, review, and export views.
10
- - `src/hooks/` - Reusable logic for API access, session state, and polling.
11
- - `src/layouts/` - Shell layouts (wizard, review workspace).
12
- - `src/state/` - Store configuration (React Query, Zustand, or Redux).
13
- - `src/services/` - API clients, WebSocket connectors, and agent progress handlers.
14
- - `src/utils/` - Formatting helpers, doc diff utilities, and schema transformers.
15
- - `src/types/` - Shared TypeScript declarations.
16
- - `tests/` - Component and integration tests with fixtures and mocks.
17
- - `config/` - Build-time configuration (Vite/Webpack, env samples).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>RightCodes</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
 
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Starter</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
frontend/package-lock.json DELETED
@@ -1,2030 +0,0 @@
1
- {
2
- "name": "rightcodes-frontend",
3
- "version": "0.1.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "rightcodes-frontend",
9
- "version": "0.1.0",
10
- "dependencies": {
11
- "@tanstack/react-query": "^5.51.11",
12
- "axios": "^1.7.7",
13
- "react": "^18.3.1",
14
- "react-dom": "^18.3.1",
15
- "react-router-dom": "^6.26.0"
16
- },
17
- "devDependencies": {
18
- "@types/react": "^18.3.3",
19
- "@types/react-dom": "^18.3.3",
20
- "@vitejs/plugin-react": "^4.3.1",
21
- "typescript": "^5.4.5",
22
- "vite": "^5.4.8"
23
- }
24
- },
25
- "node_modules/@babel/code-frame": {
26
- "version": "7.27.1",
27
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
28
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
29
- "dev": true,
30
- "license": "MIT",
31
- "dependencies": {
32
- "@babel/helper-validator-identifier": "^7.27.1",
33
- "js-tokens": "^4.0.0",
34
- "picocolors": "^1.1.1"
35
- },
36
- "engines": {
37
- "node": ">=6.9.0"
38
- }
39
- },
40
- "node_modules/@babel/compat-data": {
41
- "version": "7.28.4",
42
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
43
- "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
44
- "dev": true,
45
- "license": "MIT",
46
- "engines": {
47
- "node": ">=6.9.0"
48
- }
49
- },
50
- "node_modules/@babel/core": {
51
- "version": "7.28.4",
52
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
53
- "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
54
- "dev": true,
55
- "license": "MIT",
56
- "dependencies": {
57
- "@babel/code-frame": "^7.27.1",
58
- "@babel/generator": "^7.28.3",
59
- "@babel/helper-compilation-targets": "^7.27.2",
60
- "@babel/helper-module-transforms": "^7.28.3",
61
- "@babel/helpers": "^7.28.4",
62
- "@babel/parser": "^7.28.4",
63
- "@babel/template": "^7.27.2",
64
- "@babel/traverse": "^7.28.4",
65
- "@babel/types": "^7.28.4",
66
- "@jridgewell/remapping": "^2.3.5",
67
- "convert-source-map": "^2.0.0",
68
- "debug": "^4.1.0",
69
- "gensync": "^1.0.0-beta.2",
70
- "json5": "^2.2.3",
71
- "semver": "^6.3.1"
72
- },
73
- "engines": {
74
- "node": ">=6.9.0"
75
- },
76
- "funding": {
77
- "type": "opencollective",
78
- "url": "https://opencollective.com/babel"
79
- }
80
- },
81
- "node_modules/@babel/generator": {
82
- "version": "7.28.3",
83
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
84
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
85
- "dev": true,
86
- "license": "MIT",
87
- "dependencies": {
88
- "@babel/parser": "^7.28.3",
89
- "@babel/types": "^7.28.2",
90
- "@jridgewell/gen-mapping": "^0.3.12",
91
- "@jridgewell/trace-mapping": "^0.3.28",
92
- "jsesc": "^3.0.2"
93
- },
94
- "engines": {
95
- "node": ">=6.9.0"
96
- }
97
- },
98
- "node_modules/@babel/helper-compilation-targets": {
99
- "version": "7.27.2",
100
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
101
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
102
- "dev": true,
103
- "license": "MIT",
104
- "dependencies": {
105
- "@babel/compat-data": "^7.27.2",
106
- "@babel/helper-validator-option": "^7.27.1",
107
- "browserslist": "^4.24.0",
108
- "lru-cache": "^5.1.1",
109
- "semver": "^6.3.1"
110
- },
111
- "engines": {
112
- "node": ">=6.9.0"
113
- }
114
- },
115
- "node_modules/@babel/helper-globals": {
116
- "version": "7.28.0",
117
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
118
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
119
- "dev": true,
120
- "license": "MIT",
121
- "engines": {
122
- "node": ">=6.9.0"
123
- }
124
- },
125
- "node_modules/@babel/helper-module-imports": {
126
- "version": "7.27.1",
127
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
128
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
129
- "dev": true,
130
- "license": "MIT",
131
- "dependencies": {
132
- "@babel/traverse": "^7.27.1",
133
- "@babel/types": "^7.27.1"
134
- },
135
- "engines": {
136
- "node": ">=6.9.0"
137
- }
138
- },
139
- "node_modules/@babel/helper-module-transforms": {
140
- "version": "7.28.3",
141
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
142
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
143
- "dev": true,
144
- "license": "MIT",
145
- "dependencies": {
146
- "@babel/helper-module-imports": "^7.27.1",
147
- "@babel/helper-validator-identifier": "^7.27.1",
148
- "@babel/traverse": "^7.28.3"
149
- },
150
- "engines": {
151
- "node": ">=6.9.0"
152
- },
153
- "peerDependencies": {
154
- "@babel/core": "^7.0.0"
155
- }
156
- },
157
- "node_modules/@babel/helper-plugin-utils": {
158
- "version": "7.27.1",
159
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
160
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
161
- "dev": true,
162
- "license": "MIT",
163
- "engines": {
164
- "node": ">=6.9.0"
165
- }
166
- },
167
- "node_modules/@babel/helper-string-parser": {
168
- "version": "7.27.1",
169
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
170
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
171
- "dev": true,
172
- "license": "MIT",
173
- "engines": {
174
- "node": ">=6.9.0"
175
- }
176
- },
177
- "node_modules/@babel/helper-validator-identifier": {
178
- "version": "7.27.1",
179
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
180
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
181
- "dev": true,
182
- "license": "MIT",
183
- "engines": {
184
- "node": ">=6.9.0"
185
- }
186
- },
187
- "node_modules/@babel/helper-validator-option": {
188
- "version": "7.27.1",
189
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
190
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
191
- "dev": true,
192
- "license": "MIT",
193
- "engines": {
194
- "node": ">=6.9.0"
195
- }
196
- },
197
- "node_modules/@babel/helpers": {
198
- "version": "7.28.4",
199
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
200
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
201
- "dev": true,
202
- "license": "MIT",
203
- "dependencies": {
204
- "@babel/template": "^7.27.2",
205
- "@babel/types": "^7.28.4"
206
- },
207
- "engines": {
208
- "node": ">=6.9.0"
209
- }
210
- },
211
- "node_modules/@babel/parser": {
212
- "version": "7.28.4",
213
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
214
- "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
215
- "dev": true,
216
- "license": "MIT",
217
- "dependencies": {
218
- "@babel/types": "^7.28.4"
219
- },
220
- "bin": {
221
- "parser": "bin/babel-parser.js"
222
- },
223
- "engines": {
224
- "node": ">=6.0.0"
225
- }
226
- },
227
- "node_modules/@babel/plugin-transform-react-jsx-self": {
228
- "version": "7.27.1",
229
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
230
- "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
231
- "dev": true,
232
- "license": "MIT",
233
- "dependencies": {
234
- "@babel/helper-plugin-utils": "^7.27.1"
235
- },
236
- "engines": {
237
- "node": ">=6.9.0"
238
- },
239
- "peerDependencies": {
240
- "@babel/core": "^7.0.0-0"
241
- }
242
- },
243
- "node_modules/@babel/plugin-transform-react-jsx-source": {
244
- "version": "7.27.1",
245
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
246
- "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
247
- "dev": true,
248
- "license": "MIT",
249
- "dependencies": {
250
- "@babel/helper-plugin-utils": "^7.27.1"
251
- },
252
- "engines": {
253
- "node": ">=6.9.0"
254
- },
255
- "peerDependencies": {
256
- "@babel/core": "^7.0.0-0"
257
- }
258
- },
259
- "node_modules/@babel/template": {
260
- "version": "7.27.2",
261
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
262
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
263
- "dev": true,
264
- "license": "MIT",
265
- "dependencies": {
266
- "@babel/code-frame": "^7.27.1",
267
- "@babel/parser": "^7.27.2",
268
- "@babel/types": "^7.27.1"
269
- },
270
- "engines": {
271
- "node": ">=6.9.0"
272
- }
273
- },
274
- "node_modules/@babel/traverse": {
275
- "version": "7.28.4",
276
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
277
- "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
278
- "dev": true,
279
- "license": "MIT",
280
- "dependencies": {
281
- "@babel/code-frame": "^7.27.1",
282
- "@babel/generator": "^7.28.3",
283
- "@babel/helper-globals": "^7.28.0",
284
- "@babel/parser": "^7.28.4",
285
- "@babel/template": "^7.27.2",
286
- "@babel/types": "^7.28.4",
287
- "debug": "^4.3.1"
288
- },
289
- "engines": {
290
- "node": ">=6.9.0"
291
- }
292
- },
293
- "node_modules/@babel/types": {
294
- "version": "7.28.4",
295
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
296
- "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
297
- "dev": true,
298
- "license": "MIT",
299
- "dependencies": {
300
- "@babel/helper-string-parser": "^7.27.1",
301
- "@babel/helper-validator-identifier": "^7.27.1"
302
- },
303
- "engines": {
304
- "node": ">=6.9.0"
305
- }
306
- },
307
- "node_modules/@esbuild/aix-ppc64": {
308
- "version": "0.21.5",
309
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
310
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
311
- "cpu": [
312
- "ppc64"
313
- ],
314
- "dev": true,
315
- "license": "MIT",
316
- "optional": true,
317
- "os": [
318
- "aix"
319
- ],
320
- "engines": {
321
- "node": ">=12"
322
- }
323
- },
324
- "node_modules/@esbuild/android-arm": {
325
- "version": "0.21.5",
326
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
327
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
328
- "cpu": [
329
- "arm"
330
- ],
331
- "dev": true,
332
- "license": "MIT",
333
- "optional": true,
334
- "os": [
335
- "android"
336
- ],
337
- "engines": {
338
- "node": ">=12"
339
- }
340
- },
341
- "node_modules/@esbuild/android-arm64": {
342
- "version": "0.21.5",
343
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
344
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
345
- "cpu": [
346
- "arm64"
347
- ],
348
- "dev": true,
349
- "license": "MIT",
350
- "optional": true,
351
- "os": [
352
- "android"
353
- ],
354
- "engines": {
355
- "node": ">=12"
356
- }
357
- },
358
- "node_modules/@esbuild/android-x64": {
359
- "version": "0.21.5",
360
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
361
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
362
- "cpu": [
363
- "x64"
364
- ],
365
- "dev": true,
366
- "license": "MIT",
367
- "optional": true,
368
- "os": [
369
- "android"
370
- ],
371
- "engines": {
372
- "node": ">=12"
373
- }
374
- },
375
- "node_modules/@esbuild/darwin-arm64": {
376
- "version": "0.21.5",
377
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
378
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
379
- "cpu": [
380
- "arm64"
381
- ],
382
- "dev": true,
383
- "license": "MIT",
384
- "optional": true,
385
- "os": [
386
- "darwin"
387
- ],
388
- "engines": {
389
- "node": ">=12"
390
- }
391
- },
392
- "node_modules/@esbuild/darwin-x64": {
393
- "version": "0.21.5",
394
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
395
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
396
- "cpu": [
397
- "x64"
398
- ],
399
- "dev": true,
400
- "license": "MIT",
401
- "optional": true,
402
- "os": [
403
- "darwin"
404
- ],
405
- "engines": {
406
- "node": ">=12"
407
- }
408
- },
409
- "node_modules/@esbuild/freebsd-arm64": {
410
- "version": "0.21.5",
411
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
412
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
413
- "cpu": [
414
- "arm64"
415
- ],
416
- "dev": true,
417
- "license": "MIT",
418
- "optional": true,
419
- "os": [
420
- "freebsd"
421
- ],
422
- "engines": {
423
- "node": ">=12"
424
- }
425
- },
426
- "node_modules/@esbuild/freebsd-x64": {
427
- "version": "0.21.5",
428
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
429
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
430
- "cpu": [
431
- "x64"
432
- ],
433
- "dev": true,
434
- "license": "MIT",
435
- "optional": true,
436
- "os": [
437
- "freebsd"
438
- ],
439
- "engines": {
440
- "node": ">=12"
441
- }
442
- },
443
- "node_modules/@esbuild/linux-arm": {
444
- "version": "0.21.5",
445
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
446
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
447
- "cpu": [
448
- "arm"
449
- ],
450
- "dev": true,
451
- "license": "MIT",
452
- "optional": true,
453
- "os": [
454
- "linux"
455
- ],
456
- "engines": {
457
- "node": ">=12"
458
- }
459
- },
460
- "node_modules/@esbuild/linux-arm64": {
461
- "version": "0.21.5",
462
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
463
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
464
- "cpu": [
465
- "arm64"
466
- ],
467
- "dev": true,
468
- "license": "MIT",
469
- "optional": true,
470
- "os": [
471
- "linux"
472
- ],
473
- "engines": {
474
- "node": ">=12"
475
- }
476
- },
477
- "node_modules/@esbuild/linux-ia32": {
478
- "version": "0.21.5",
479
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
480
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
481
- "cpu": [
482
- "ia32"
483
- ],
484
- "dev": true,
485
- "license": "MIT",
486
- "optional": true,
487
- "os": [
488
- "linux"
489
- ],
490
- "engines": {
491
- "node": ">=12"
492
- }
493
- },
494
- "node_modules/@esbuild/linux-loong64": {
495
- "version": "0.21.5",
496
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
497
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
498
- "cpu": [
499
- "loong64"
500
- ],
501
- "dev": true,
502
- "license": "MIT",
503
- "optional": true,
504
- "os": [
505
- "linux"
506
- ],
507
- "engines": {
508
- "node": ">=12"
509
- }
510
- },
511
- "node_modules/@esbuild/linux-mips64el": {
512
- "version": "0.21.5",
513
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
514
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
515
- "cpu": [
516
- "mips64el"
517
- ],
518
- "dev": true,
519
- "license": "MIT",
520
- "optional": true,
521
- "os": [
522
- "linux"
523
- ],
524
- "engines": {
525
- "node": ">=12"
526
- }
527
- },
528
- "node_modules/@esbuild/linux-ppc64": {
529
- "version": "0.21.5",
530
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
531
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
532
- "cpu": [
533
- "ppc64"
534
- ],
535
- "dev": true,
536
- "license": "MIT",
537
- "optional": true,
538
- "os": [
539
- "linux"
540
- ],
541
- "engines": {
542
- "node": ">=12"
543
- }
544
- },
545
- "node_modules/@esbuild/linux-riscv64": {
546
- "version": "0.21.5",
547
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
548
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
549
- "cpu": [
550
- "riscv64"
551
- ],
552
- "dev": true,
553
- "license": "MIT",
554
- "optional": true,
555
- "os": [
556
- "linux"
557
- ],
558
- "engines": {
559
- "node": ">=12"
560
- }
561
- },
562
- "node_modules/@esbuild/linux-s390x": {
563
- "version": "0.21.5",
564
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
565
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
566
- "cpu": [
567
- "s390x"
568
- ],
569
- "dev": true,
570
- "license": "MIT",
571
- "optional": true,
572
- "os": [
573
- "linux"
574
- ],
575
- "engines": {
576
- "node": ">=12"
577
- }
578
- },
579
- "node_modules/@esbuild/linux-x64": {
580
- "version": "0.21.5",
581
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
582
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
583
- "cpu": [
584
- "x64"
585
- ],
586
- "dev": true,
587
- "license": "MIT",
588
- "optional": true,
589
- "os": [
590
- "linux"
591
- ],
592
- "engines": {
593
- "node": ">=12"
594
- }
595
- },
596
- "node_modules/@esbuild/netbsd-x64": {
597
- "version": "0.21.5",
598
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
599
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
600
- "cpu": [
601
- "x64"
602
- ],
603
- "dev": true,
604
- "license": "MIT",
605
- "optional": true,
606
- "os": [
607
- "netbsd"
608
- ],
609
- "engines": {
610
- "node": ">=12"
611
- }
612
- },
613
- "node_modules/@esbuild/openbsd-x64": {
614
- "version": "0.21.5",
615
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
616
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
617
- "cpu": [
618
- "x64"
619
- ],
620
- "dev": true,
621
- "license": "MIT",
622
- "optional": true,
623
- "os": [
624
- "openbsd"
625
- ],
626
- "engines": {
627
- "node": ">=12"
628
- }
629
- },
630
- "node_modules/@esbuild/sunos-x64": {
631
- "version": "0.21.5",
632
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
633
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
634
- "cpu": [
635
- "x64"
636
- ],
637
- "dev": true,
638
- "license": "MIT",
639
- "optional": true,
640
- "os": [
641
- "sunos"
642
- ],
643
- "engines": {
644
- "node": ">=12"
645
- }
646
- },
647
- "node_modules/@esbuild/win32-arm64": {
648
- "version": "0.21.5",
649
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
650
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
651
- "cpu": [
652
- "arm64"
653
- ],
654
- "dev": true,
655
- "license": "MIT",
656
- "optional": true,
657
- "os": [
658
- "win32"
659
- ],
660
- "engines": {
661
- "node": ">=12"
662
- }
663
- },
664
- "node_modules/@esbuild/win32-ia32": {
665
- "version": "0.21.5",
666
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
667
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
668
- "cpu": [
669
- "ia32"
670
- ],
671
- "dev": true,
672
- "license": "MIT",
673
- "optional": true,
674
- "os": [
675
- "win32"
676
- ],
677
- "engines": {
678
- "node": ">=12"
679
- }
680
- },
681
- "node_modules/@esbuild/win32-x64": {
682
- "version": "0.21.5",
683
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
684
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
685
- "cpu": [
686
- "x64"
687
- ],
688
- "dev": true,
689
- "license": "MIT",
690
- "optional": true,
691
- "os": [
692
- "win32"
693
- ],
694
- "engines": {
695
- "node": ">=12"
696
- }
697
- },
698
- "node_modules/@jridgewell/gen-mapping": {
699
- "version": "0.3.13",
700
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
701
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
702
- "dev": true,
703
- "license": "MIT",
704
- "dependencies": {
705
- "@jridgewell/sourcemap-codec": "^1.5.0",
706
- "@jridgewell/trace-mapping": "^0.3.24"
707
- }
708
- },
709
- "node_modules/@jridgewell/remapping": {
710
- "version": "2.3.5",
711
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
712
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
713
- "dev": true,
714
- "license": "MIT",
715
- "dependencies": {
716
- "@jridgewell/gen-mapping": "^0.3.5",
717
- "@jridgewell/trace-mapping": "^0.3.24"
718
- }
719
- },
720
- "node_modules/@jridgewell/resolve-uri": {
721
- "version": "3.1.2",
722
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
723
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
724
- "dev": true,
725
- "license": "MIT",
726
- "engines": {
727
- "node": ">=6.0.0"
728
- }
729
- },
730
- "node_modules/@jridgewell/sourcemap-codec": {
731
- "version": "1.5.5",
732
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
733
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
734
- "dev": true,
735
- "license": "MIT"
736
- },
737
- "node_modules/@jridgewell/trace-mapping": {
738
- "version": "0.3.31",
739
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
740
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
741
- "dev": true,
742
- "license": "MIT",
743
- "dependencies": {
744
- "@jridgewell/resolve-uri": "^3.1.0",
745
- "@jridgewell/sourcemap-codec": "^1.4.14"
746
- }
747
- },
748
- "node_modules/@remix-run/router": {
749
- "version": "1.23.0",
750
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
751
- "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
752
- "license": "MIT",
753
- "engines": {
754
- "node": ">=14.0.0"
755
- }
756
- },
757
- "node_modules/@rolldown/pluginutils": {
758
- "version": "1.0.0-beta.27",
759
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
760
- "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
761
- "dev": true,
762
- "license": "MIT"
763
- },
764
- "node_modules/@rollup/rollup-android-arm-eabi": {
765
- "version": "4.52.4",
766
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
767
- "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
768
- "cpu": [
769
- "arm"
770
- ],
771
- "dev": true,
772
- "license": "MIT",
773
- "optional": true,
774
- "os": [
775
- "android"
776
- ]
777
- },
778
- "node_modules/@rollup/rollup-android-arm64": {
779
- "version": "4.52.4",
780
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
781
- "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
782
- "cpu": [
783
- "arm64"
784
- ],
785
- "dev": true,
786
- "license": "MIT",
787
- "optional": true,
788
- "os": [
789
- "android"
790
- ]
791
- },
792
- "node_modules/@rollup/rollup-darwin-arm64": {
793
- "version": "4.52.4",
794
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
795
- "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
796
- "cpu": [
797
- "arm64"
798
- ],
799
- "dev": true,
800
- "license": "MIT",
801
- "optional": true,
802
- "os": [
803
- "darwin"
804
- ]
805
- },
806
- "node_modules/@rollup/rollup-darwin-x64": {
807
- "version": "4.52.4",
808
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
809
- "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
810
- "cpu": [
811
- "x64"
812
- ],
813
- "dev": true,
814
- "license": "MIT",
815
- "optional": true,
816
- "os": [
817
- "darwin"
818
- ]
819
- },
820
- "node_modules/@rollup/rollup-freebsd-arm64": {
821
- "version": "4.52.4",
822
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
823
- "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
824
- "cpu": [
825
- "arm64"
826
- ],
827
- "dev": true,
828
- "license": "MIT",
829
- "optional": true,
830
- "os": [
831
- "freebsd"
832
- ]
833
- },
834
- "node_modules/@rollup/rollup-freebsd-x64": {
835
- "version": "4.52.4",
836
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
837
- "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
838
- "cpu": [
839
- "x64"
840
- ],
841
- "dev": true,
842
- "license": "MIT",
843
- "optional": true,
844
- "os": [
845
- "freebsd"
846
- ]
847
- },
848
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
849
- "version": "4.52.4",
850
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
851
- "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
852
- "cpu": [
853
- "arm"
854
- ],
855
- "dev": true,
856
- "license": "MIT",
857
- "optional": true,
858
- "os": [
859
- "linux"
860
- ]
861
- },
862
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
863
- "version": "4.52.4",
864
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
865
- "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
866
- "cpu": [
867
- "arm"
868
- ],
869
- "dev": true,
870
- "license": "MIT",
871
- "optional": true,
872
- "os": [
873
- "linux"
874
- ]
875
- },
876
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
877
- "version": "4.52.4",
878
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
879
- "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
880
- "cpu": [
881
- "arm64"
882
- ],
883
- "dev": true,
884
- "license": "MIT",
885
- "optional": true,
886
- "os": [
887
- "linux"
888
- ]
889
- },
890
- "node_modules/@rollup/rollup-linux-arm64-musl": {
891
- "version": "4.52.4",
892
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
893
- "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
894
- "cpu": [
895
- "arm64"
896
- ],
897
- "dev": true,
898
- "license": "MIT",
899
- "optional": true,
900
- "os": [
901
- "linux"
902
- ]
903
- },
904
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
905
- "version": "4.52.4",
906
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
907
- "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
908
- "cpu": [
909
- "loong64"
910
- ],
911
- "dev": true,
912
- "license": "MIT",
913
- "optional": true,
914
- "os": [
915
- "linux"
916
- ]
917
- },
918
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
919
- "version": "4.52.4",
920
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
921
- "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
922
- "cpu": [
923
- "ppc64"
924
- ],
925
- "dev": true,
926
- "license": "MIT",
927
- "optional": true,
928
- "os": [
929
- "linux"
930
- ]
931
- },
932
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
933
- "version": "4.52.4",
934
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
935
- "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
936
- "cpu": [
937
- "riscv64"
938
- ],
939
- "dev": true,
940
- "license": "MIT",
941
- "optional": true,
942
- "os": [
943
- "linux"
944
- ]
945
- },
946
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
947
- "version": "4.52.4",
948
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
949
- "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
950
- "cpu": [
951
- "riscv64"
952
- ],
953
- "dev": true,
954
- "license": "MIT",
955
- "optional": true,
956
- "os": [
957
- "linux"
958
- ]
959
- },
960
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
961
- "version": "4.52.4",
962
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
963
- "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
964
- "cpu": [
965
- "s390x"
966
- ],
967
- "dev": true,
968
- "license": "MIT",
969
- "optional": true,
970
- "os": [
971
- "linux"
972
- ]
973
- },
974
- "node_modules/@rollup/rollup-linux-x64-gnu": {
975
- "version": "4.52.4",
976
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
977
- "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
978
- "cpu": [
979
- "x64"
980
- ],
981
- "dev": true,
982
- "license": "MIT",
983
- "optional": true,
984
- "os": [
985
- "linux"
986
- ]
987
- },
988
- "node_modules/@rollup/rollup-linux-x64-musl": {
989
- "version": "4.52.4",
990
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
991
- "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
992
- "cpu": [
993
- "x64"
994
- ],
995
- "dev": true,
996
- "license": "MIT",
997
- "optional": true,
998
- "os": [
999
- "linux"
1000
- ]
1001
- },
1002
- "node_modules/@rollup/rollup-openharmony-arm64": {
1003
- "version": "4.52.4",
1004
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
1005
- "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
1006
- "cpu": [
1007
- "arm64"
1008
- ],
1009
- "dev": true,
1010
- "license": "MIT",
1011
- "optional": true,
1012
- "os": [
1013
- "openharmony"
1014
- ]
1015
- },
1016
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
1017
- "version": "4.52.4",
1018
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
1019
- "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
1020
- "cpu": [
1021
- "arm64"
1022
- ],
1023
- "dev": true,
1024
- "license": "MIT",
1025
- "optional": true,
1026
- "os": [
1027
- "win32"
1028
- ]
1029
- },
1030
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
1031
- "version": "4.52.4",
1032
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
1033
- "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
1034
- "cpu": [
1035
- "ia32"
1036
- ],
1037
- "dev": true,
1038
- "license": "MIT",
1039
- "optional": true,
1040
- "os": [
1041
- "win32"
1042
- ]
1043
- },
1044
- "node_modules/@rollup/rollup-win32-x64-gnu": {
1045
- "version": "4.52.4",
1046
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
1047
- "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
1048
- "cpu": [
1049
- "x64"
1050
- ],
1051
- "dev": true,
1052
- "license": "MIT",
1053
- "optional": true,
1054
- "os": [
1055
- "win32"
1056
- ]
1057
- },
1058
- "node_modules/@rollup/rollup-win32-x64-msvc": {
1059
- "version": "4.52.4",
1060
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
1061
- "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
1062
- "cpu": [
1063
- "x64"
1064
- ],
1065
- "dev": true,
1066
- "license": "MIT",
1067
- "optional": true,
1068
- "os": [
1069
- "win32"
1070
- ]
1071
- },
1072
- "node_modules/@tanstack/query-core": {
1073
- "version": "5.90.3",
1074
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.3.tgz",
1075
- "integrity": "sha512-HtPOnCwmx4dd35PfXU8jjkhwYrsHfuqgC8RCJIwWglmhIUIlzPP0ZcEkDAc+UtAWCiLm7T8rxeEfHZlz3hYMCA==",
1076
- "license": "MIT",
1077
- "funding": {
1078
- "type": "github",
1079
- "url": "https://github.com/sponsors/tannerlinsley"
1080
- }
1081
- },
1082
- "node_modules/@tanstack/react-query": {
1083
- "version": "5.90.3",
1084
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.3.tgz",
1085
- "integrity": "sha512-i/LRL6DtuhG6bjGzavIMIVuKKPWx2AnEBIsBfuMm3YoHne0a20nWmsatOCBcVSaT0/8/5YFjNkebHAPLVUSi0Q==",
1086
- "license": "MIT",
1087
- "dependencies": {
1088
- "@tanstack/query-core": "5.90.3"
1089
- },
1090
- "funding": {
1091
- "type": "github",
1092
- "url": "https://github.com/sponsors/tannerlinsley"
1093
- },
1094
- "peerDependencies": {
1095
- "react": "^18 || ^19"
1096
- }
1097
- },
1098
- "node_modules/@types/babel__core": {
1099
- "version": "7.20.5",
1100
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1101
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1102
- "dev": true,
1103
- "license": "MIT",
1104
- "dependencies": {
1105
- "@babel/parser": "^7.20.7",
1106
- "@babel/types": "^7.20.7",
1107
- "@types/babel__generator": "*",
1108
- "@types/babel__template": "*",
1109
- "@types/babel__traverse": "*"
1110
- }
1111
- },
1112
- "node_modules/@types/babel__generator": {
1113
- "version": "7.27.0",
1114
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1115
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1116
- "dev": true,
1117
- "license": "MIT",
1118
- "dependencies": {
1119
- "@babel/types": "^7.0.0"
1120
- }
1121
- },
1122
- "node_modules/@types/babel__template": {
1123
- "version": "7.4.4",
1124
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1125
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1126
- "dev": true,
1127
- "license": "MIT",
1128
- "dependencies": {
1129
- "@babel/parser": "^7.1.0",
1130
- "@babel/types": "^7.0.0"
1131
- }
1132
- },
1133
- "node_modules/@types/babel__traverse": {
1134
- "version": "7.28.0",
1135
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1136
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1137
- "dev": true,
1138
- "license": "MIT",
1139
- "dependencies": {
1140
- "@babel/types": "^7.28.2"
1141
- }
1142
- },
1143
- "node_modules/@types/estree": {
1144
- "version": "1.0.8",
1145
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1146
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1147
- "dev": true,
1148
- "license": "MIT"
1149
- },
1150
- "node_modules/@types/prop-types": {
1151
- "version": "15.7.15",
1152
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
1153
- "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
1154
- "dev": true,
1155
- "license": "MIT"
1156
- },
1157
- "node_modules/@types/react": {
1158
- "version": "18.3.26",
1159
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
1160
- "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
1161
- "dev": true,
1162
- "license": "MIT",
1163
- "dependencies": {
1164
- "@types/prop-types": "*",
1165
- "csstype": "^3.0.2"
1166
- }
1167
- },
1168
- "node_modules/@types/react-dom": {
1169
- "version": "18.3.7",
1170
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
1171
- "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
1172
- "dev": true,
1173
- "license": "MIT",
1174
- "peerDependencies": {
1175
- "@types/react": "^18.0.0"
1176
- }
1177
- },
1178
- "node_modules/@vitejs/plugin-react": {
1179
- "version": "4.7.0",
1180
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1181
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1182
- "dev": true,
1183
- "license": "MIT",
1184
- "dependencies": {
1185
- "@babel/core": "^7.28.0",
1186
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1187
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1188
- "@rolldown/pluginutils": "1.0.0-beta.27",
1189
- "@types/babel__core": "^7.20.5",
1190
- "react-refresh": "^0.17.0"
1191
- },
1192
- "engines": {
1193
- "node": "^14.18.0 || >=16.0.0"
1194
- },
1195
- "peerDependencies": {
1196
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1197
- }
1198
- },
1199
- "node_modules/asynckit": {
1200
- "version": "0.4.0",
1201
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
1202
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
1203
- "license": "MIT"
1204
- },
1205
- "node_modules/axios": {
1206
- "version": "1.12.2",
1207
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
1208
- "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
1209
- "license": "MIT",
1210
- "dependencies": {
1211
- "follow-redirects": "^1.15.6",
1212
- "form-data": "^4.0.4",
1213
- "proxy-from-env": "^1.1.0"
1214
- }
1215
- },
1216
- "node_modules/baseline-browser-mapping": {
1217
- "version": "2.8.16",
1218
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
1219
- "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
1220
- "dev": true,
1221
- "license": "Apache-2.0",
1222
- "bin": {
1223
- "baseline-browser-mapping": "dist/cli.js"
1224
- }
1225
- },
1226
- "node_modules/browserslist": {
1227
- "version": "4.26.3",
1228
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
1229
- "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
1230
- "dev": true,
1231
- "funding": [
1232
- {
1233
- "type": "opencollective",
1234
- "url": "https://opencollective.com/browserslist"
1235
- },
1236
- {
1237
- "type": "tidelift",
1238
- "url": "https://tidelift.com/funding/github/npm/browserslist"
1239
- },
1240
- {
1241
- "type": "github",
1242
- "url": "https://github.com/sponsors/ai"
1243
- }
1244
- ],
1245
- "license": "MIT",
1246
- "dependencies": {
1247
- "baseline-browser-mapping": "^2.8.9",
1248
- "caniuse-lite": "^1.0.30001746",
1249
- "electron-to-chromium": "^1.5.227",
1250
- "node-releases": "^2.0.21",
1251
- "update-browserslist-db": "^1.1.3"
1252
- },
1253
- "bin": {
1254
- "browserslist": "cli.js"
1255
- },
1256
- "engines": {
1257
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1258
- }
1259
- },
1260
- "node_modules/call-bind-apply-helpers": {
1261
- "version": "1.0.2",
1262
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
1263
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
1264
- "license": "MIT",
1265
- "dependencies": {
1266
- "es-errors": "^1.3.0",
1267
- "function-bind": "^1.1.2"
1268
- },
1269
- "engines": {
1270
- "node": ">= 0.4"
1271
- }
1272
- },
1273
- "node_modules/caniuse-lite": {
1274
- "version": "1.0.30001750",
1275
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
1276
- "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
1277
- "dev": true,
1278
- "funding": [
1279
- {
1280
- "type": "opencollective",
1281
- "url": "https://opencollective.com/browserslist"
1282
- },
1283
- {
1284
- "type": "tidelift",
1285
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1286
- },
1287
- {
1288
- "type": "github",
1289
- "url": "https://github.com/sponsors/ai"
1290
- }
1291
- ],
1292
- "license": "CC-BY-4.0"
1293
- },
1294
- "node_modules/combined-stream": {
1295
- "version": "1.0.8",
1296
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
1297
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
1298
- "license": "MIT",
1299
- "dependencies": {
1300
- "delayed-stream": "~1.0.0"
1301
- },
1302
- "engines": {
1303
- "node": ">= 0.8"
1304
- }
1305
- },
1306
- "node_modules/convert-source-map": {
1307
- "version": "2.0.0",
1308
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1309
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1310
- "dev": true,
1311
- "license": "MIT"
1312
- },
1313
- "node_modules/csstype": {
1314
- "version": "3.1.3",
1315
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
1316
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
1317
- "dev": true,
1318
- "license": "MIT"
1319
- },
1320
- "node_modules/debug": {
1321
- "version": "4.4.3",
1322
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1323
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1324
- "dev": true,
1325
- "license": "MIT",
1326
- "dependencies": {
1327
- "ms": "^2.1.3"
1328
- },
1329
- "engines": {
1330
- "node": ">=6.0"
1331
- },
1332
- "peerDependenciesMeta": {
1333
- "supports-color": {
1334
- "optional": true
1335
- }
1336
- }
1337
- },
1338
- "node_modules/delayed-stream": {
1339
- "version": "1.0.0",
1340
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
1341
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
1342
- "license": "MIT",
1343
- "engines": {
1344
- "node": ">=0.4.0"
1345
- }
1346
- },
1347
- "node_modules/dunder-proto": {
1348
- "version": "1.0.1",
1349
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
1350
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
1351
- "license": "MIT",
1352
- "dependencies": {
1353
- "call-bind-apply-helpers": "^1.0.1",
1354
- "es-errors": "^1.3.0",
1355
- "gopd": "^1.2.0"
1356
- },
1357
- "engines": {
1358
- "node": ">= 0.4"
1359
- }
1360
- },
1361
- "node_modules/electron-to-chromium": {
1362
- "version": "1.5.237",
1363
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
1364
- "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==",
1365
- "dev": true,
1366
- "license": "ISC"
1367
- },
1368
- "node_modules/es-define-property": {
1369
- "version": "1.0.1",
1370
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
1371
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
1372
- "license": "MIT",
1373
- "engines": {
1374
- "node": ">= 0.4"
1375
- }
1376
- },
1377
- "node_modules/es-errors": {
1378
- "version": "1.3.0",
1379
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
1380
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
1381
- "license": "MIT",
1382
- "engines": {
1383
- "node": ">= 0.4"
1384
- }
1385
- },
1386
- "node_modules/es-object-atoms": {
1387
- "version": "1.1.1",
1388
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
1389
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
1390
- "license": "MIT",
1391
- "dependencies": {
1392
- "es-errors": "^1.3.0"
1393
- },
1394
- "engines": {
1395
- "node": ">= 0.4"
1396
- }
1397
- },
1398
- "node_modules/es-set-tostringtag": {
1399
- "version": "2.1.0",
1400
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
1401
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
1402
- "license": "MIT",
1403
- "dependencies": {
1404
- "es-errors": "^1.3.0",
1405
- "get-intrinsic": "^1.2.6",
1406
- "has-tostringtag": "^1.0.2",
1407
- "hasown": "^2.0.2"
1408
- },
1409
- "engines": {
1410
- "node": ">= 0.4"
1411
- }
1412
- },
1413
- "node_modules/esbuild": {
1414
- "version": "0.21.5",
1415
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
1416
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
1417
- "dev": true,
1418
- "hasInstallScript": true,
1419
- "license": "MIT",
1420
- "bin": {
1421
- "esbuild": "bin/esbuild"
1422
- },
1423
- "engines": {
1424
- "node": ">=12"
1425
- },
1426
- "optionalDependencies": {
1427
- "@esbuild/aix-ppc64": "0.21.5",
1428
- "@esbuild/android-arm": "0.21.5",
1429
- "@esbuild/android-arm64": "0.21.5",
1430
- "@esbuild/android-x64": "0.21.5",
1431
- "@esbuild/darwin-arm64": "0.21.5",
1432
- "@esbuild/darwin-x64": "0.21.5",
1433
- "@esbuild/freebsd-arm64": "0.21.5",
1434
- "@esbuild/freebsd-x64": "0.21.5",
1435
- "@esbuild/linux-arm": "0.21.5",
1436
- "@esbuild/linux-arm64": "0.21.5",
1437
- "@esbuild/linux-ia32": "0.21.5",
1438
- "@esbuild/linux-loong64": "0.21.5",
1439
- "@esbuild/linux-mips64el": "0.21.5",
1440
- "@esbuild/linux-ppc64": "0.21.5",
1441
- "@esbuild/linux-riscv64": "0.21.5",
1442
- "@esbuild/linux-s390x": "0.21.5",
1443
- "@esbuild/linux-x64": "0.21.5",
1444
- "@esbuild/netbsd-x64": "0.21.5",
1445
- "@esbuild/openbsd-x64": "0.21.5",
1446
- "@esbuild/sunos-x64": "0.21.5",
1447
- "@esbuild/win32-arm64": "0.21.5",
1448
- "@esbuild/win32-ia32": "0.21.5",
1449
- "@esbuild/win32-x64": "0.21.5"
1450
- }
1451
- },
1452
- "node_modules/escalade": {
1453
- "version": "3.2.0",
1454
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1455
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1456
- "dev": true,
1457
- "license": "MIT",
1458
- "engines": {
1459
- "node": ">=6"
1460
- }
1461
- },
1462
- "node_modules/follow-redirects": {
1463
- "version": "1.15.11",
1464
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
1465
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
1466
- "funding": [
1467
- {
1468
- "type": "individual",
1469
- "url": "https://github.com/sponsors/RubenVerborgh"
1470
- }
1471
- ],
1472
- "license": "MIT",
1473
- "engines": {
1474
- "node": ">=4.0"
1475
- },
1476
- "peerDependenciesMeta": {
1477
- "debug": {
1478
- "optional": true
1479
- }
1480
- }
1481
- },
1482
- "node_modules/form-data": {
1483
- "version": "4.0.4",
1484
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
1485
- "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
1486
- "license": "MIT",
1487
- "dependencies": {
1488
- "asynckit": "^0.4.0",
1489
- "combined-stream": "^1.0.8",
1490
- "es-set-tostringtag": "^2.1.0",
1491
- "hasown": "^2.0.2",
1492
- "mime-types": "^2.1.12"
1493
- },
1494
- "engines": {
1495
- "node": ">= 6"
1496
- }
1497
- },
1498
- "node_modules/fsevents": {
1499
- "version": "2.3.3",
1500
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1501
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1502
- "dev": true,
1503
- "hasInstallScript": true,
1504
- "license": "MIT",
1505
- "optional": true,
1506
- "os": [
1507
- "darwin"
1508
- ],
1509
- "engines": {
1510
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1511
- }
1512
- },
1513
- "node_modules/function-bind": {
1514
- "version": "1.1.2",
1515
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1516
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1517
- "license": "MIT",
1518
- "funding": {
1519
- "url": "https://github.com/sponsors/ljharb"
1520
- }
1521
- },
1522
- "node_modules/gensync": {
1523
- "version": "1.0.0-beta.2",
1524
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1525
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1526
- "dev": true,
1527
- "license": "MIT",
1528
- "engines": {
1529
- "node": ">=6.9.0"
1530
- }
1531
- },
1532
- "node_modules/get-intrinsic": {
1533
- "version": "1.3.0",
1534
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1535
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1536
- "license": "MIT",
1537
- "dependencies": {
1538
- "call-bind-apply-helpers": "^1.0.2",
1539
- "es-define-property": "^1.0.1",
1540
- "es-errors": "^1.3.0",
1541
- "es-object-atoms": "^1.1.1",
1542
- "function-bind": "^1.1.2",
1543
- "get-proto": "^1.0.1",
1544
- "gopd": "^1.2.0",
1545
- "has-symbols": "^1.1.0",
1546
- "hasown": "^2.0.2",
1547
- "math-intrinsics": "^1.1.0"
1548
- },
1549
- "engines": {
1550
- "node": ">= 0.4"
1551
- },
1552
- "funding": {
1553
- "url": "https://github.com/sponsors/ljharb"
1554
- }
1555
- },
1556
- "node_modules/get-proto": {
1557
- "version": "1.0.1",
1558
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1559
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1560
- "license": "MIT",
1561
- "dependencies": {
1562
- "dunder-proto": "^1.0.1",
1563
- "es-object-atoms": "^1.0.0"
1564
- },
1565
- "engines": {
1566
- "node": ">= 0.4"
1567
- }
1568
- },
1569
- "node_modules/gopd": {
1570
- "version": "1.2.0",
1571
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1572
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1573
- "license": "MIT",
1574
- "engines": {
1575
- "node": ">= 0.4"
1576
- },
1577
- "funding": {
1578
- "url": "https://github.com/sponsors/ljharb"
1579
- }
1580
- },
1581
- "node_modules/has-symbols": {
1582
- "version": "1.1.0",
1583
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1584
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1585
- "license": "MIT",
1586
- "engines": {
1587
- "node": ">= 0.4"
1588
- },
1589
- "funding": {
1590
- "url": "https://github.com/sponsors/ljharb"
1591
- }
1592
- },
1593
- "node_modules/has-tostringtag": {
1594
- "version": "1.0.2",
1595
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1596
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1597
- "license": "MIT",
1598
- "dependencies": {
1599
- "has-symbols": "^1.0.3"
1600
- },
1601
- "engines": {
1602
- "node": ">= 0.4"
1603
- },
1604
- "funding": {
1605
- "url": "https://github.com/sponsors/ljharb"
1606
- }
1607
- },
1608
- "node_modules/hasown": {
1609
- "version": "2.0.2",
1610
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1611
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1612
- "license": "MIT",
1613
- "dependencies": {
1614
- "function-bind": "^1.1.2"
1615
- },
1616
- "engines": {
1617
- "node": ">= 0.4"
1618
- }
1619
- },
1620
- "node_modules/js-tokens": {
1621
- "version": "4.0.0",
1622
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1623
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1624
- "license": "MIT"
1625
- },
1626
- "node_modules/jsesc": {
1627
- "version": "3.1.0",
1628
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1629
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1630
- "dev": true,
1631
- "license": "MIT",
1632
- "bin": {
1633
- "jsesc": "bin/jsesc"
1634
- },
1635
- "engines": {
1636
- "node": ">=6"
1637
- }
1638
- },
1639
- "node_modules/json5": {
1640
- "version": "2.2.3",
1641
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1642
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1643
- "dev": true,
1644
- "license": "MIT",
1645
- "bin": {
1646
- "json5": "lib/cli.js"
1647
- },
1648
- "engines": {
1649
- "node": ">=6"
1650
- }
1651
- },
1652
- "node_modules/loose-envify": {
1653
- "version": "1.4.0",
1654
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1655
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1656
- "license": "MIT",
1657
- "dependencies": {
1658
- "js-tokens": "^3.0.0 || ^4.0.0"
1659
- },
1660
- "bin": {
1661
- "loose-envify": "cli.js"
1662
- }
1663
- },
1664
- "node_modules/lru-cache": {
1665
- "version": "5.1.1",
1666
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1667
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1668
- "dev": true,
1669
- "license": "ISC",
1670
- "dependencies": {
1671
- "yallist": "^3.0.2"
1672
- }
1673
- },
1674
- "node_modules/math-intrinsics": {
1675
- "version": "1.1.0",
1676
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1677
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1678
- "license": "MIT",
1679
- "engines": {
1680
- "node": ">= 0.4"
1681
- }
1682
- },
1683
- "node_modules/mime-db": {
1684
- "version": "1.52.0",
1685
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1686
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1687
- "license": "MIT",
1688
- "engines": {
1689
- "node": ">= 0.6"
1690
- }
1691
- },
1692
- "node_modules/mime-types": {
1693
- "version": "2.1.35",
1694
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1695
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1696
- "license": "MIT",
1697
- "dependencies": {
1698
- "mime-db": "1.52.0"
1699
- },
1700
- "engines": {
1701
- "node": ">= 0.6"
1702
- }
1703
- },
1704
- "node_modules/ms": {
1705
- "version": "2.1.3",
1706
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1707
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1708
- "dev": true,
1709
- "license": "MIT"
1710
- },
1711
- "node_modules/nanoid": {
1712
- "version": "3.3.11",
1713
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1714
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1715
- "dev": true,
1716
- "funding": [
1717
- {
1718
- "type": "github",
1719
- "url": "https://github.com/sponsors/ai"
1720
- }
1721
- ],
1722
- "license": "MIT",
1723
- "bin": {
1724
- "nanoid": "bin/nanoid.cjs"
1725
- },
1726
- "engines": {
1727
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1728
- }
1729
- },
1730
- "node_modules/node-releases": {
1731
- "version": "2.0.23",
1732
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
1733
- "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
1734
- "dev": true,
1735
- "license": "MIT"
1736
- },
1737
- "node_modules/picocolors": {
1738
- "version": "1.1.1",
1739
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1740
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1741
- "dev": true,
1742
- "license": "ISC"
1743
- },
1744
- "node_modules/postcss": {
1745
- "version": "8.5.6",
1746
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1747
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1748
- "dev": true,
1749
- "funding": [
1750
- {
1751
- "type": "opencollective",
1752
- "url": "https://opencollective.com/postcss/"
1753
- },
1754
- {
1755
- "type": "tidelift",
1756
- "url": "https://tidelift.com/funding/github/npm/postcss"
1757
- },
1758
- {
1759
- "type": "github",
1760
- "url": "https://github.com/sponsors/ai"
1761
- }
1762
- ],
1763
- "license": "MIT",
1764
- "dependencies": {
1765
- "nanoid": "^3.3.11",
1766
- "picocolors": "^1.1.1",
1767
- "source-map-js": "^1.2.1"
1768
- },
1769
- "engines": {
1770
- "node": "^10 || ^12 || >=14"
1771
- }
1772
- },
1773
- "node_modules/proxy-from-env": {
1774
- "version": "1.1.0",
1775
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1776
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1777
- "license": "MIT"
1778
- },
1779
- "node_modules/react": {
1780
- "version": "18.3.1",
1781
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1782
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1783
- "license": "MIT",
1784
- "dependencies": {
1785
- "loose-envify": "^1.1.0"
1786
- },
1787
- "engines": {
1788
- "node": ">=0.10.0"
1789
- }
1790
- },
1791
- "node_modules/react-dom": {
1792
- "version": "18.3.1",
1793
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1794
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1795
- "license": "MIT",
1796
- "dependencies": {
1797
- "loose-envify": "^1.1.0",
1798
- "scheduler": "^0.23.2"
1799
- },
1800
- "peerDependencies": {
1801
- "react": "^18.3.1"
1802
- }
1803
- },
1804
- "node_modules/react-refresh": {
1805
- "version": "0.17.0",
1806
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1807
- "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1808
- "dev": true,
1809
- "license": "MIT",
1810
- "engines": {
1811
- "node": ">=0.10.0"
1812
- }
1813
- },
1814
- "node_modules/react-router": {
1815
- "version": "6.30.1",
1816
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
1817
- "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
1818
- "license": "MIT",
1819
- "dependencies": {
1820
- "@remix-run/router": "1.23.0"
1821
- },
1822
- "engines": {
1823
- "node": ">=14.0.0"
1824
- },
1825
- "peerDependencies": {
1826
- "react": ">=16.8"
1827
- }
1828
- },
1829
- "node_modules/react-router-dom": {
1830
- "version": "6.30.1",
1831
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
1832
- "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
1833
- "license": "MIT",
1834
- "dependencies": {
1835
- "@remix-run/router": "1.23.0",
1836
- "react-router": "6.30.1"
1837
- },
1838
- "engines": {
1839
- "node": ">=14.0.0"
1840
- },
1841
- "peerDependencies": {
1842
- "react": ">=16.8",
1843
- "react-dom": ">=16.8"
1844
- }
1845
- },
1846
- "node_modules/rollup": {
1847
- "version": "4.52.4",
1848
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
1849
- "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
1850
- "dev": true,
1851
- "license": "MIT",
1852
- "dependencies": {
1853
- "@types/estree": "1.0.8"
1854
- },
1855
- "bin": {
1856
- "rollup": "dist/bin/rollup"
1857
- },
1858
- "engines": {
1859
- "node": ">=18.0.0",
1860
- "npm": ">=8.0.0"
1861
- },
1862
- "optionalDependencies": {
1863
- "@rollup/rollup-android-arm-eabi": "4.52.4",
1864
- "@rollup/rollup-android-arm64": "4.52.4",
1865
- "@rollup/rollup-darwin-arm64": "4.52.4",
1866
- "@rollup/rollup-darwin-x64": "4.52.4",
1867
- "@rollup/rollup-freebsd-arm64": "4.52.4",
1868
- "@rollup/rollup-freebsd-x64": "4.52.4",
1869
- "@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
1870
- "@rollup/rollup-linux-arm-musleabihf": "4.52.4",
1871
- "@rollup/rollup-linux-arm64-gnu": "4.52.4",
1872
- "@rollup/rollup-linux-arm64-musl": "4.52.4",
1873
- "@rollup/rollup-linux-loong64-gnu": "4.52.4",
1874
- "@rollup/rollup-linux-ppc64-gnu": "4.52.4",
1875
- "@rollup/rollup-linux-riscv64-gnu": "4.52.4",
1876
- "@rollup/rollup-linux-riscv64-musl": "4.52.4",
1877
- "@rollup/rollup-linux-s390x-gnu": "4.52.4",
1878
- "@rollup/rollup-linux-x64-gnu": "4.52.4",
1879
- "@rollup/rollup-linux-x64-musl": "4.52.4",
1880
- "@rollup/rollup-openharmony-arm64": "4.52.4",
1881
- "@rollup/rollup-win32-arm64-msvc": "4.52.4",
1882
- "@rollup/rollup-win32-ia32-msvc": "4.52.4",
1883
- "@rollup/rollup-win32-x64-gnu": "4.52.4",
1884
- "@rollup/rollup-win32-x64-msvc": "4.52.4",
1885
- "fsevents": "~2.3.2"
1886
- }
1887
- },
1888
- "node_modules/scheduler": {
1889
- "version": "0.23.2",
1890
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1891
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1892
- "license": "MIT",
1893
- "dependencies": {
1894
- "loose-envify": "^1.1.0"
1895
- }
1896
- },
1897
- "node_modules/semver": {
1898
- "version": "6.3.1",
1899
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1900
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1901
- "dev": true,
1902
- "license": "ISC",
1903
- "bin": {
1904
- "semver": "bin/semver.js"
1905
- }
1906
- },
1907
- "node_modules/source-map-js": {
1908
- "version": "1.2.1",
1909
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1910
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1911
- "dev": true,
1912
- "license": "BSD-3-Clause",
1913
- "engines": {
1914
- "node": ">=0.10.0"
1915
- }
1916
- },
1917
- "node_modules/typescript": {
1918
- "version": "5.9.3",
1919
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1920
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1921
- "dev": true,
1922
- "license": "Apache-2.0",
1923
- "bin": {
1924
- "tsc": "bin/tsc",
1925
- "tsserver": "bin/tsserver"
1926
- },
1927
- "engines": {
1928
- "node": ">=14.17"
1929
- }
1930
- },
1931
- "node_modules/update-browserslist-db": {
1932
- "version": "1.1.3",
1933
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
1934
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
1935
- "dev": true,
1936
- "funding": [
1937
- {
1938
- "type": "opencollective",
1939
- "url": "https://opencollective.com/browserslist"
1940
- },
1941
- {
1942
- "type": "tidelift",
1943
- "url": "https://tidelift.com/funding/github/npm/browserslist"
1944
- },
1945
- {
1946
- "type": "github",
1947
- "url": "https://github.com/sponsors/ai"
1948
- }
1949
- ],
1950
- "license": "MIT",
1951
- "dependencies": {
1952
- "escalade": "^3.2.0",
1953
- "picocolors": "^1.1.1"
1954
- },
1955
- "bin": {
1956
- "update-browserslist-db": "cli.js"
1957
- },
1958
- "peerDependencies": {
1959
- "browserslist": ">= 4.21.0"
1960
- }
1961
- },
1962
- "node_modules/vite": {
1963
- "version": "5.4.20",
1964
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
1965
- "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
1966
- "dev": true,
1967
- "license": "MIT",
1968
- "dependencies": {
1969
- "esbuild": "^0.21.3",
1970
- "postcss": "^8.4.43",
1971
- "rollup": "^4.20.0"
1972
- },
1973
- "bin": {
1974
- "vite": "bin/vite.js"
1975
- },
1976
- "engines": {
1977
- "node": "^18.0.0 || >=20.0.0"
1978
- },
1979
- "funding": {
1980
- "url": "https://github.com/vitejs/vite?sponsor=1"
1981
- },
1982
- "optionalDependencies": {
1983
- "fsevents": "~2.3.3"
1984
- },
1985
- "peerDependencies": {
1986
- "@types/node": "^18.0.0 || >=20.0.0",
1987
- "less": "*",
1988
- "lightningcss": "^1.21.0",
1989
- "sass": "*",
1990
- "sass-embedded": "*",
1991
- "stylus": "*",
1992
- "sugarss": "*",
1993
- "terser": "^5.4.0"
1994
- },
1995
- "peerDependenciesMeta": {
1996
- "@types/node": {
1997
- "optional": true
1998
- },
1999
- "less": {
2000
- "optional": true
2001
- },
2002
- "lightningcss": {
2003
- "optional": true
2004
- },
2005
- "sass": {
2006
- "optional": true
2007
- },
2008
- "sass-embedded": {
2009
- "optional": true
2010
- },
2011
- "stylus": {
2012
- "optional": true
2013
- },
2014
- "sugarss": {
2015
- "optional": true
2016
- },
2017
- "terser": {
2018
- "optional": true
2019
- }
2020
- }
2021
- },
2022
- "node_modules/yallist": {
2023
- "version": "3.1.1",
2024
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
2025
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
2026
- "dev": true,
2027
- "license": "ISC"
2028
- }
2029
- }
2030
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/package.json CHANGED
@@ -9,11 +9,8 @@
9
  "preview": "vite preview"
10
  },
11
  "dependencies": {
12
- "@tanstack/react-query": "^5.51.11",
13
- "axios": "^1.7.7",
14
  "react": "^18.3.1",
15
- "react-dom": "^18.3.1",
16
- "react-router-dom": "^6.26.0"
17
  },
18
  "devDependencies": {
19
  "@types/react": "^18.3.3",
 
9
  "preview": "vite preview"
10
  },
11
  "dependencies": {
 
 
12
  "react": "^18.3.1",
13
+ "react-dom": "^18.3.1"
 
14
  },
15
  "devDependencies": {
16
  "@types/react": "^18.3.3",
frontend/public/index.html DELETED
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>RightCodes</title>
8
- </head>
9
- <body>
10
- <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
12
- </body>
13
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/App.tsx CHANGED
@@ -1,34 +1,3 @@
1
- import { BrowserRouter, NavLink, Route, Routes } from "react-router-dom";
2
- import SessionsPage from "./pages/SessionsPage";
3
- import PresetsPage from "./pages/PresetsPage";
4
- import PresetEditPage from "./pages/PresetEditPage";
5
- import ObservatoryPage from "./pages/ObservatoryPage";
6
-
7
  export default function App() {
8
- return (
9
- <BrowserRouter>
10
- <div className="app-shell">
11
- <header>
12
- <h1>RightCodes Converter</h1>
13
- <p>Upload reports, review AI-assisted updates, and export revised documents.</p>
14
- <nav className="primary-nav">
15
- <NavLink to="/" end>
16
- Sessions
17
- </NavLink>
18
- <NavLink to="/presets">Presets</NavLink>
19
- <NavLink to="/observatory">Observatory</NavLink>
20
- </nav>
21
- </header>
22
- <main>
23
- <Routes>
24
- <Route path="/" element={<SessionsPage />} />
25
- <Route path="/presets" element={<PresetsPage />} />
26
- <Route path="/presets/:presetId/edit" element={<PresetEditPage />} />
27
- <Route path="/observatory" element={<ObservatoryPage />} />
28
- </Routes>
29
- </main>
30
- <footer>RightCodes {new Date().getFullYear()}</footer>
31
- </div>
32
- </BrowserRouter>
33
- );
34
  }
 
 
 
 
 
 
 
1
  export default function App() {
2
+ return <div className="black-screen" />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  }
frontend/src/components/PresetManager.tsx DELETED
@@ -1,193 +0,0 @@
1
- import { useState } from "react";
2
- import { Link } from "react-router-dom";
3
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
- import { createPreset, deletePreset, fetchPresets } from "../services/api";
5
- import type { StandardsPreset } from "../types/session";
6
-
7
- export default function PresetManager() {
8
- const queryClient = useQueryClient();
9
- const { data: presets = [], isLoading, isError, error } = useQuery({
10
- queryKey: ["presets"],
11
- queryFn: fetchPresets,
12
- refetchInterval: (currentPresets) =>
13
- Array.isArray(currentPresets) &&
14
- currentPresets.some((preset) => preset.status === "processing")
15
- ? 2000
16
- : false,
17
- });
18
-
19
- const [name, setName] = useState("");
20
- const [description, setDescription] = useState("");
21
- const [files, setFiles] = useState<File[]>([]);
22
-
23
- const createPresetMutation = useMutation({
24
- mutationFn: createPreset,
25
- onSuccess: () => {
26
- queryClient.invalidateQueries({ queryKey: ["presets"] });
27
- resetForm();
28
- },
29
- onError: (err: unknown) => {
30
- alert(`Preset creation failed: ${(err as Error).message}`);
31
- },
32
- });
33
-
34
- const deletePresetMutation = useMutation({
35
- mutationFn: (id: string) => deletePreset(id),
36
- onSuccess: () => {
37
- queryClient.invalidateQueries({ queryKey: ["presets"] });
38
- },
39
- onError: (err: unknown) => {
40
- alert(`Preset deletion failed: ${(err as Error).message}`);
41
- },
42
- });
43
-
44
- const resetForm = () => {
45
- setName("");
46
- setDescription("");
47
- setFiles([]);
48
- const fileInput = document.getElementById("preset-standards-input") as HTMLInputElement | null;
49
- if (fileInput) {
50
- fileInput.value = "";
51
- }
52
- };
53
-
54
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
55
- event.preventDefault();
56
- if (!files.length) {
57
- alert("Select at least one PDF to build a preset.");
58
- return;
59
- }
60
- createPresetMutation.mutate({
61
- name,
62
- description: description || undefined,
63
- files,
64
- });
65
- };
66
-
67
- const handleDelete = (preset: StandardsPreset) => {
68
- if (!window.confirm(`Delete preset "${preset.name}"? This cannot be undone.`)) {
69
- return;
70
- }
71
- deletePresetMutation.mutate(preset.id);
72
- };
73
-
74
- return (
75
- <section className="card">
76
- <h2>Manage Standards Presets</h2>
77
- <p className="muted">
78
- Reuse commonly referenced standards without re-uploading them for every session.
79
- </p>
80
- <form className="stacked" onSubmit={handleSubmit}>
81
- <label className="field">
82
- <span>Preset Name</span>
83
- <input value={name} onChange={(event) => setName(event.target.value)} required />
84
- </label>
85
- <label className="field">
86
- <span>Description (optional)</span>
87
- <input value={description} onChange={(event) => setDescription(event.target.value)} />
88
- </label>
89
- <label className="field">
90
- <span>Preset Standards PDFs</span>
91
- <input
92
- id="preset-standards-input"
93
- type="file"
94
- accept=".pdf"
95
- multiple
96
- onChange={(event) => setFiles(Array.from(event.target.files ?? []))}
97
- />
98
- <small>
99
- {files.length ? `${files.length} file(s) selected` : "Select one or more PDF references."}
100
- </small>
101
- </label>
102
- <button type="submit" disabled={createPresetMutation.isPending}>
103
- {createPresetMutation.isPending ? "Creating preset..." : "Save Preset"}
104
- </button>
105
- {createPresetMutation.isError && (
106
- <p className="error">Preset creation failed: {(createPresetMutation.error as Error).message}</p>
107
- )}
108
- {createPresetMutation.isSuccess && (
109
- <p className="success">
110
- Preset queued for processing. Progress will appear below shortly.
111
- </p>
112
- )}
113
- </form>
114
-
115
- <div className="preset-list">
116
- <h3>Saved Presets</h3>
117
- {isLoading && <p className="muted">Loading presets...</p>}
118
- {isError && <p className="error">Failed to load presets: {(error as Error).message}</p>}
119
- {!isLoading && !presets.length && <p className="muted">No presets created yet.</p>}
120
- {presets.length > 0 && (
121
- <ul>
122
- {presets.map((preset: StandardsPreset) => {
123
- const totalDocs = preset.total_count || preset.document_count || 0;
124
- const processed = Math.min(preset.processed_count || 0, totalDocs);
125
- const progressPercent = totalDocs
126
- ? Math.min(100, Math.round((processed / totalDocs) * 100))
127
- : preset.status === "ready"
128
- ? 100
129
- : 0;
130
- const nextDoc = Math.min(processed + 1, totalDocs);
131
- return (
132
- <li key={preset.id}>
133
- <div className="preset-header">
134
- <div>
135
- <strong>{preset.name}</strong>
136
- <p className="muted">
137
- {preset.document_count} file{preset.document_count === 1 ? "" : "s"} · Updated{" "}
138
- {new Date(preset.updated_at).toLocaleString()}
139
- </p>
140
- {preset.description && <p className="muted">{preset.description}</p>}
141
- </div>
142
- <div className="preset-status">
143
- {preset.status === "ready" && <span className="status-ready">Ready</span>}
144
- {preset.status === "processing" && (
145
- <span className="status-processing">
146
- Processing {processed}/{totalDocs}
147
- </span>
148
- )}
149
- {preset.status === "failed" && (
150
- <span className="status-failed">
151
- Failed{preset.last_error ? `: ${preset.last_error}` : ""}
152
- </span>
153
- )}
154
- </div>
155
- </div>
156
- {preset.status === "processing" && (
157
- <div className="progress-block">
158
- <div className="linear-progress">
159
- <div
160
- className="linear-progress-fill"
161
- style={{ width: `${progressPercent}%` }}
162
- />
163
- </div>
164
- <p className="muted">
165
- Currently processing document {nextDoc} of {totalDocs}.
166
- </p>
167
- </div>
168
- )}
169
- <div className="muted preset-docs">
170
- {preset.documents.map((doc) => doc.split(/[/\\]/).pop() ?? doc).join(", ")}
171
- </div>
172
- <div className="preset-actions">
173
- <Link className="ghost-button" to={`/presets/${preset.id}/edit`}>
174
- Edit
175
- </Link>
176
- <button
177
- type="button"
178
- className="ghost-button danger"
179
- onClick={() => handleDelete(preset)}
180
- disabled={deletePresetMutation.isPending || preset.status === "processing"}
181
- >
182
- Delete
183
- </button>
184
- </div>
185
- </li>
186
- );
187
- })}
188
- </ul>
189
- )}
190
- </div>
191
- </section>
192
- );
193
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/SessionDetails.tsx DELETED
@@ -1,578 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { fetchSessionById } from "../services/api";
4
- import type { Session, SessionSummary } from "../types/session";
5
-
6
- interface DocParse {
7
- path: string;
8
- paragraphs: Array<{ index: number; text: string; style?: string | null; heading_level?: number | null; references?: string[] }>;
9
- tables: Array<{ index: number; rows: string[][]; references?: string[] }>;
10
- summary?: Record<string, unknown>;
11
- }
12
-
13
- interface StandardsParseEntry {
14
- path: string;
15
- summary?: Record<string, unknown>;
16
- chunks?: Array<{
17
- page_number: number;
18
- chunk_index: number;
19
- text: string;
20
- heading?: string | null;
21
- clause_numbers?: string[];
22
- references?: string[];
23
- is_ocr?: boolean;
24
- }>;
25
- }
26
-
27
- interface ExtractionResult {
28
- document_summary?: string;
29
- sections?: Array<{ paragraph_index: number; text: string; references?: string[] }>;
30
- tables?: Array<{ table_index: number; summary: string; references?: string[] }>;
31
- references?: string[];
32
- notes?: string;
33
- }
34
-
35
- interface MappingResult {
36
- mappings?: Array<{
37
- source_reference: string;
38
- source_context?: string;
39
- target_reference: string;
40
- target_clause?: string;
41
- target_summary?: string;
42
- confidence?: number;
43
- rationale?: string;
44
- }>;
45
- unmapped_references?: string[];
46
- notes?: string;
47
- }
48
-
49
- interface RewritePlan {
50
- replacements?: Array<{
51
- paragraph_index: number;
52
- original_text: string;
53
- updated_text: string;
54
- applied_mapping: string;
55
- change_reason?: string;
56
- }>;
57
- notes?: string;
58
- }
59
-
60
- interface ValidationReport {
61
- issues?: Array<{ description?: string; severity?: string }>;
62
- verdict?: string;
63
- notes?: string;
64
- }
65
-
66
- interface ExportManifest {
67
- export_path?: string | null;
68
- notes?: string;
69
- replacement_count?: number;
70
- }
71
-
72
- interface SessionDetailsProps {
73
- session: SessionSummary | null;
74
- }
75
-
76
- export default function SessionDetails({ session }: SessionDetailsProps) {
77
- if (!session) {
78
- return <div className="card">Select a session to inspect its progress.</div>;
79
- }
80
-
81
- const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000/api";
82
- const partialSession = session as Partial<Session>;
83
- const summaryStandards = Array.isArray(partialSession.standards_docs)
84
- ? [...partialSession.standards_docs]
85
- : [];
86
- const summaryLogs = Array.isArray(partialSession.logs) ? [...partialSession.logs] : [];
87
- const summaryMetadata =
88
- partialSession.metadata && typeof partialSession.metadata === "object"
89
- ? { ...(partialSession.metadata as Record<string, unknown>) }
90
- : {};
91
- const normalizedSummary: Session = {
92
- ...session,
93
- standards_docs: summaryStandards,
94
- logs: summaryLogs,
95
- metadata: summaryMetadata,
96
- };
97
- const sessionQuery = useQuery({
98
- queryKey: ["session", session.id],
99
- queryFn: () => fetchSessionById(session.id),
100
- enabled: Boolean(session?.id),
101
- refetchInterval: (data) =>
102
- data && ["review", "completed", "failed"].includes(data.status) ? false : 2000,
103
- placeholderData: () => normalizedSummary,
104
- });
105
-
106
- const latest = sessionQuery.data ?? normalizedSummary;
107
- const standardsDocs = Array.isArray(latest.standards_docs) ? latest.standards_docs : [];
108
- const standardNames = standardsDocs.map((path) => path.split(/[/\\]/).pop() ?? path);
109
- const activityLogs = Array.isArray(latest.logs) ? latest.logs : [];
110
- const inFlight = !["review", "completed", "failed"].includes(latest.status);
111
-
112
- useEffect(() => {
113
- if (sessionQuery.data) {
114
- console.log(`[Session ${sessionQuery.data.id}] status: ${sessionQuery.data.status}`);
115
- }
116
- }, [sessionQuery.data?.id, sessionQuery.data?.status]);
117
-
118
- const metadata = (latest.metadata ?? {}) as Record<string, unknown>;
119
- const docParse = metadata.doc_parse as DocParse | undefined;
120
- const standardsParse = metadata.standards_parse as StandardsParseEntry[] | undefined;
121
- const extractionResult = metadata.extraction_result as ExtractionResult | undefined;
122
- const mappingResult = metadata.mapping_result as MappingResult | undefined;
123
- const rewritePlan = metadata.rewrite_plan as RewritePlan | undefined;
124
- const validationReport = metadata.validation_report as ValidationReport | undefined;
125
- const exportManifest = metadata.export_manifest as ExportManifest | undefined;
126
- const exportDownloadUrl =
127
- exportManifest?.export_path !== undefined
128
- ? `${apiBaseUrl}/sessions/${latest.id}/export`
129
- : null;
130
-
131
- const docSummary = docParse?.summary as
132
- | { paragraph_count?: number; table_count?: number; reference_count?: number }
133
- | undefined;
134
-
135
- const standardsProgress = metadata.standards_ingest_progress as
136
- | {
137
- total?: number;
138
- processed?: number;
139
- current_file?: string;
140
- cached_count?: number;
141
- parsed_count?: number;
142
- completed?: boolean;
143
- }
144
- | undefined;
145
- const standardsProgressFile =
146
- standardsProgress?.current_file?.split(/[/\\]/).pop() ?? undefined;
147
- const standardsProgressTotal =
148
- typeof standardsProgress?.total === "number" ? standardsProgress.total : undefined;
149
- const standardsProgressProcessed =
150
- standardsProgressTotal !== undefined
151
- ? Math.min(
152
- typeof standardsProgress?.processed === "number" ? standardsProgress.processed : 0,
153
- standardsProgressTotal
154
- )
155
- : undefined;
156
- const standardsCachedCount =
157
- typeof standardsProgress?.cached_count === "number" ? standardsProgress.cached_count : undefined;
158
- const standardsParsedCount =
159
- typeof standardsProgress?.parsed_count === "number" ? standardsProgress.parsed_count : undefined;
160
- const pipelineProgress = metadata.pipeline_progress as
161
- | {
162
- total?: number;
163
- current_index?: number;
164
- stage?: string | null;
165
- status?: string;
166
- }
167
- | undefined;
168
- const pipelineStageLabel = pipelineProgress?.stage
169
- ? pipelineProgress.stage
170
- .split("_")
171
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
172
- .join(" ")
173
- : undefined;
174
- const pipelineStageStatus = pipelineProgress?.status
175
- ? pipelineProgress.status.charAt(0).toUpperCase() + pipelineProgress.status.slice(1)
176
- : undefined;
177
- const presetMetadata = Array.isArray(metadata.presets)
178
- ? (metadata.presets as Array<{ id?: string; name?: string | null; documents?: string[] }>)
179
- : [];
180
- const presetNames = presetMetadata
181
- .map((item) => item?.name || item?.id || "")
182
- .filter((value) => Boolean(value)) as string[];
183
- const presetDocTotal = presetMetadata.reduce(
184
- (acc, item) => acc + (Array.isArray(item?.documents) ? item.documents.length : 0),
185
- 0
186
- );
187
-
188
- const [showAllMappings, setShowAllMappings] = useState(false);
189
- const [showAllReplacements, setShowAllReplacements] = useState(false);
190
-
191
- const sections = extractionResult?.sections ?? [];
192
- const sectionsPreview = sections.slice(0, 6);
193
-
194
- const mappingLimit = 6;
195
- const mappings = mappingResult?.mappings ?? [];
196
- const mappingsToDisplay = showAllMappings ? mappings : mappings.slice(0, mappingLimit);
197
-
198
- const replacementLimit = 6;
199
- const replacements = rewritePlan?.replacements ?? [];
200
- const replacementsToDisplay = showAllReplacements ? replacements : replacements.slice(0, replacementLimit);
201
-
202
- const tableReplacements = rewritePlan?.table_replacements ?? [];
203
- const changeLog = rewritePlan?.change_log ?? [];
204
-
205
- return (
206
- <div className="card">
207
- <div className="card-header">
208
- <h2>Session Overview</h2>
209
- <div className="header-actions">
210
- {sessionQuery.isFetching && <span className="muted text-small">Updating...</span>}
211
- <button
212
- type="button"
213
- className="ghost-button"
214
- onClick={() => sessionQuery.refetch()}
215
- disabled={sessionQuery.isFetching}
216
- >
217
- {sessionQuery.isFetching ? "Refreshing..." : "Refresh now"}
218
- </button>
219
- </div>
220
- </div>
221
- {inFlight && (
222
- <div className="progress-block">
223
- <div className="progress-bar">
224
- <div className="progress-bar-fill" />
225
- </div>
226
- <p className="muted">Processing pipeline... refreshing every 2s.</p>
227
- {pipelineProgress?.total ? (
228
- <p className="muted">
229
- Pipeline stages:{" "}
230
- {Math.min(pipelineProgress.current_index ?? 0, pipelineProgress.total)} /{" "}
231
- {pipelineProgress.total}
232
- {pipelineStageLabel ? ` — ${pipelineStageLabel}` : ""}
233
- {pipelineStageStatus ? ` (${pipelineStageStatus})` : ""}
234
- </p>
235
- ) : null}
236
- {standardsProgressTotal !== undefined ? (
237
- <p className="muted">
238
- Standards PDFs: {standardsProgressProcessed ?? 0} / {standardsProgressTotal}
239
- {standardsProgressFile ? ` — ${standardsProgressFile}` : ""}
240
- {standardsCachedCount !== undefined || standardsParsedCount !== undefined ? (
241
- <>
242
- {" "}(cached {standardsCachedCount ?? 0}, parsed {standardsParsedCount ?? 0})
243
- </>
244
- ) : null}
245
- </p>
246
- ) : null}
247
- </div>
248
- )}
249
- <dl className="details-grid">
250
- <div>
251
- <dt>Status</dt>
252
- <dd>{latest.status}</dd>
253
- </div>
254
- <div>
255
- <dt>Created</dt>
256
- <dd>{new Date(latest.created_at).toLocaleString()}</dd>
257
- </div>
258
- <div>
259
- <dt>Updated</dt>
260
- <dd>{new Date(latest.updated_at).toLocaleString()}</dd>
261
- </div>
262
- <div>
263
- <dt>Origin</dt>
264
- <dd>{latest.target_standard}</dd>
265
- </div>
266
- <div>
267
- <dt>Destination</dt>
268
- <dd>{latest.destination_standard}</dd>
269
- </div>
270
- {standardNames.length > 0 && (
271
- <div>
272
- <dt>Standards PDFs</dt>
273
- <dd>{standardNames.join(", ")}</dd>
274
- </div>
275
- )}
276
- {presetNames.length ? (
277
- <div>
278
- <dt>Preset</dt>
279
- <dd>
280
- {presetNames.join(", ")}
281
- {presetDocTotal ? ` (${presetDocTotal} file${presetDocTotal === 1 ? "" : "s"})` : ""}
282
- </dd>
283
- </div>
284
- ) : null}
285
- </dl>
286
- {latest.status === "review" && (
287
- <div className="notice success">
288
- Pipeline completed. Review the AI change set and validation notes next.
289
- </div>
290
- )}
291
- {latest.last_error && (
292
- <p className="error">
293
- <strong>Last error:</strong> {latest.last_error}
294
- </p>
295
- )}
296
- <div className="log-panel">
297
- <h3>Activity Log</h3>
298
- {activityLogs.length ? (
299
- <ul>
300
- {activityLogs.map((entry, index) => (
301
- <li key={`${entry}-${index}`}>{entry}</li>
302
- ))}
303
- </ul>
304
- ) : (
305
- <p className="muted">No activity recorded yet.</p>
306
- )}
307
- </div>
308
-
309
- {docParse && (
310
- <div className="section-card">
311
- <h3>Document Parsing</h3>
312
- <p className="muted">
313
- Analysed {docSummary?.paragraph_count ?? docParse.paragraphs.length} paragraphs and{" "}
314
- {docSummary?.table_count ?? docParse.tables.length} tables.
315
- </p>
316
- <ul className="section-list">
317
- {docParse.paragraphs.slice(0, 5).map((paragraph) => (
318
- <li key={paragraph.index}>
319
- <strong>Paragraph {paragraph.index}:</strong> {paragraph.text}
320
- {paragraph.references?.length ? (
321
- <span className="muted"> — refs: {paragraph.references.join(", ")}</span>
322
- ) : null}
323
- </li>
324
- ))}
325
- </ul>
326
- </div>
327
- )}
328
-
329
- {standardsParse && standardsParse.length > 0 && (
330
- <div className="section-card">
331
- <h3>Standards Corpus</h3>
332
- <ul className="section-list">
333
- {standardsParse.slice(0, 4).map((entry) => {
334
- const name = entry.path.split(/[/\\]/).pop() ?? entry.path;
335
- const summary = entry.summary as
336
- | { chunk_count?: number; reference_count?: number; ocr_chunk_count?: number }
337
- | undefined;
338
- const chunkCount = summary?.chunk_count ?? entry.chunks?.length ?? 0;
339
- const ocrChunkCount =
340
- summary?.ocr_chunk_count ?? entry.chunks?.filter((chunk) => chunk.is_ocr)?.length ?? 0;
341
- return (
342
- <li key={entry.path}>
343
- <strong>{name}</strong> - {chunkCount} chunks analysed
344
- {ocrChunkCount ? (
345
- <span className="muted">
346
- {" "}
347
- ({ocrChunkCount} OCR supplement{ocrChunkCount === 1 ? "" : "s"})
348
- </span>
349
- ) : null}
350
- </li>
351
- );
352
- })}
353
- </ul>
354
- </div>
355
- )}
356
-
357
- {extractionResult && (
358
- <div className="section-card">
359
- <h3>Extraction Summary</h3>
360
- {extractionResult.document_summary && (
361
- <p>{extractionResult.document_summary}</p>
362
- )}
363
- {extractionResult.references && extractionResult.references.length ? (
364
- <p className="muted">
365
- References detected: {extractionResult.references.slice(0, 20).join(", ")}
366
- {extractionResult.references.length > 20 ? "..." : ""}
367
- </p>
368
- ) : null}
369
- {sectionsPreview.length ? (
370
- <ul className="section-list">
371
- {sectionsPreview.map((section) => (
372
- <li key={section.paragraph_index}>
373
- <strong>Paragraph {section.paragraph_index}:</strong> {section.text}
374
- </li>
375
- ))}
376
- </ul>
377
- ) : null}
378
- {extractionResult.notes && <p className="muted">{extractionResult.notes}</p>}
379
- </div>
380
- )}
381
-
382
- {mappingResult && (
383
- <div className="section-card">
384
- <h3>Reference Mapping</h3>
385
- {mappings.length ? (
386
- <>
387
- <p className="muted">
388
- Showing {mappingsToDisplay.length} of {mappings.length} mappings.
389
- </p>
390
- <ul className="section-list">
391
- {mappingsToDisplay.map((mapping, idx) => {
392
- const cappedConfidence =
393
- typeof mapping.confidence === "number"
394
- ? Math.round(Math.min(Math.max(mapping.confidence, 0), 1) * 100)
395
- : null;
396
- return (
397
- <li key={`${mapping.source_reference}-${idx}`}>
398
- <strong>{mapping.source_reference}</strong>{" -> "}{mapping.target_reference}
399
- {cappedConfidence !== null && (
400
- <span className="muted"> (confidence {cappedConfidence}%)</span>
401
- )}
402
- {mapping.target_clause && (
403
- <div className="muted">Clause: {mapping.target_clause}</div>
404
- )}
405
- {mapping.rationale && (
406
- <div className="muted">Reason: {mapping.rationale}</div>
407
- )}
408
- </li>
409
- );
410
- })}
411
- </ul>
412
- {mappings.length > mappingLimit && (
413
- <button
414
- type="button"
415
- className="ghost-button"
416
- onClick={() => setShowAllMappings((prev) => !prev)}
417
- >
418
- {showAllMappings ? "Show fewer" : `Show all ${mappings.length}`}
419
- </button>
420
- )}
421
- </>
422
- ) : (
423
- <p className="muted">
424
- {mappingResult.notes ?? "No mapping actions recorded."}
425
- </p>
426
- )}
427
- </div>
428
- )}
429
-
430
- {rewritePlan && (
431
- <div className="section-card">
432
- <h3>Rewrite Plan</h3>
433
- {replacements.length ? (
434
- <>
435
- <p className="muted">
436
- Showing {replacementsToDisplay.length} of {replacements.length} replacements.
437
- </p>
438
- <ul className="section-list">
439
- {replacementsToDisplay.map((replacement, idx) => {
440
- const appliedMappings =
441
- Array.isArray(replacement.applied_mappings) && replacement.applied_mappings.length
442
- ? replacement.applied_mappings
443
- : replacement.applied_mapping
444
- ? [replacement.applied_mapping]
445
- : [];
446
- return (
447
- <li key={`${replacement.paragraph_index}-${idx}`}>
448
- <strong>Paragraph {replacement.paragraph_index}</strong>
449
- <div className="diff-block">
450
- <div>
451
- <span className="muted">Original:</span> {replacement.original_text}
452
- </div>
453
- <div>
454
- <span className="muted">Updated:</span> {replacement.updated_text}
455
- </div>
456
- {appliedMappings.length ? (
457
- <div className="muted">Mapping: {appliedMappings.join(", ")}</div>
458
- ) : null}
459
- {replacement.change_reason && (
460
- <div className="muted">Reason: {replacement.change_reason}</div>
461
- )}
462
- </div>
463
- </li>
464
- );
465
- })}
466
- </ul>
467
- {replacements.length > replacementLimit && (
468
- <button
469
- type="button"
470
- className="ghost-button"
471
- onClick={() => setShowAllReplacements((prev) => !prev)}
472
- >
473
- {showAllReplacements ? "Show fewer" : `Show all ${replacements.length}`}
474
- </button>
475
- )}
476
- </>
477
- ) : (
478
- <p className="muted">{rewritePlan.notes ?? "No rewrite actions required."}</p>
479
- )}
480
- {tableReplacements.length ? (
481
- <div className="subsection">
482
- <h4>Table Updates</h4>
483
- <ul className="section-list">
484
- {tableReplacements.map((table, idx) => {
485
- const appliedMappings =
486
- Array.isArray(table.applied_mappings) && table.applied_mappings.length
487
- ? table.applied_mappings
488
- : table.applied_mapping
489
- ? [table.applied_mapping]
490
- : [];
491
- return (
492
- <li key={`${table.table_index}-${idx}`}>
493
- <strong>Table {table.table_index}</strong>
494
- {appliedMappings.length ? (
495
- <div className="muted">Mapping: {appliedMappings.join(", ")}</div>
496
- ) : null}
497
- {table.change_reason && <div className="muted">{table.change_reason}</div>}
498
- {Array.isArray(table.updated_rows) && table.updated_rows.length ? (
499
- <div className="muted">Updated rows: {table.updated_rows.length}</div>
500
- ) : null}
501
- </li>
502
- );
503
- })}
504
- </ul>
505
- </div>
506
- ) : null}
507
- {changeLog.length ? (
508
- <div className="subsection">
509
- <h4>Change Log</h4>
510
- <ul className="section-list">
511
- {changeLog.map((entry, idx) => (
512
- <li key={`${entry.reference}-${idx}`}>
513
- <strong>{entry.reference}</strong>{" -> "}{entry.target_reference}
514
- {entry.affected_paragraphs && entry.affected_paragraphs.length ? (
515
- <span className="muted">{" "}(paragraphs {entry.affected_paragraphs.join(", ")})</span>
516
- ) : null}
517
- {entry.note && <div className="muted">{entry.note}</div>}
518
- </li>
519
- ))}
520
- </ul>
521
- </div>
522
- ) : null}
523
- {rewritePlan.notes && replacements.length ? (
524
- <p className="muted">{rewritePlan.notes}</p>
525
- ) : null}
526
- </div>
527
- )}
528
-
529
- {validationReport && (
530
- <div className="section-card">
531
- <h3>Validation</h3>
532
- {validationReport.verdict && (
533
- <p>
534
- Verdict: <strong>{validationReport.verdict}</strong>
535
- </p>
536
- )}
537
- {validationReport.issues?.length ? (
538
- <ul className="section-list">
539
- {validationReport.issues.map((issue, idx) => (
540
- <li key={idx}>
541
- {issue.description ?? "Issue reported"}
542
- {issue.severity && <span className="muted"> ({issue.severity})</span>}
543
- </li>
544
- ))}
545
- </ul>
546
- ) : (
547
- <p className="muted">No validation issues reported.</p>
548
- )}
549
- {validationReport.notes && <p className="muted">{validationReport.notes}</p>}
550
- </div>
551
- )}
552
-
553
- {exportManifest && (
554
- <div className="section-card">
555
- <h3>Export</h3>
556
- {exportDownloadUrl ? (
557
- <>
558
- <p className="muted">
559
- {exportManifest.notes ?? "Converted document generated using rewrite plan."}
560
- </p>
561
- {typeof exportManifest.replacement_count === "number" && (
562
- <p className="muted">
563
- {exportManifest.replacement_count} paragraph replacements applied.
564
- </p>
565
- )}
566
- <a className="ghost-button" href={exportDownloadUrl} download>
567
- Download DOCX
568
- </a>
569
- </>
570
- ) : (
571
- <p className="muted">{exportManifest.notes ?? "Export not yet generated."}</p>
572
- )}
573
- </div>
574
- )}
575
- </div>
576
- );
577
- }
578
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/SessionList.tsx DELETED
@@ -1,59 +0,0 @@
1
- import { useQuery } from "@tanstack/react-query";
2
- import { fetchSessions } from "../services/api";
3
- import type { SessionSummary } from "../types/session";
4
-
5
- interface SessionListProps {
6
- selectedId?: string | null;
7
- onSelect?: (session: SessionSummary) => void;
8
- }
9
-
10
- export default function SessionList({ selectedId, onSelect }: SessionListProps) {
11
- const { data, isLoading, isError, error } = useQuery({
12
- queryKey: ["sessions"],
13
- queryFn: fetchSessions,
14
- refetchInterval: 5000,
15
- });
16
- const dateFormatter = new Intl.DateTimeFormat(undefined, {
17
- dateStyle: "medium",
18
- timeStyle: "short",
19
- });
20
-
21
- if (isLoading) {
22
- return <div className="card">Loading sessions...</div>;
23
- }
24
- if (isError) {
25
- return <div className="card error">Failed to load sessions: {(error as Error).message}</div>;
26
- }
27
-
28
- if (!data?.length) {
29
- return <div className="card">No sessions yet. Upload a report to get started.</div>;
30
- }
31
-
32
- return (
33
- <div className="card">
34
- <h2>Recent Sessions</h2>
35
- <p className="muted">Auto-refreshing every 5s.</p>
36
- <ul className="session-list">
37
- {data.map((session) => (
38
- <li
39
- key={session.id}
40
- className={session.id === selectedId ? "selected" : ""}
41
- onClick={() => onSelect?.(session)}
42
- >
43
- <div>
44
- <strong>{session.name}</strong>
45
- <p>
46
- {session.target_standard} {"->"} {session.destination_standard}
47
- </p>
48
- <p className="muted" title={session.source_doc}>
49
- {dateFormatter.format(new Date(session.created_at))} {" · "}
50
- {session.source_doc.split(/[/\\]/).pop() ?? session.source_doc}
51
- </p>
52
- </div>
53
- <span className={`status status-${session.status}`}>{session.status}</span>
54
- </li>
55
- ))}
56
- </ul>
57
- </div>
58
- );
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/UploadForm.tsx DELETED
@@ -1,189 +0,0 @@
1
- import { useState } from "react";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { fetchPresets, uploadSession } from "../services/api";
4
- import type { Session, StandardsPreset } from "../types/session";
5
-
6
- interface UploadFormProps {
7
- onSuccess?: (session: Session) => void;
8
- }
9
-
10
- export default function UploadForm({ onSuccess }: UploadFormProps) {
11
- const queryClient = useQueryClient();
12
- const [name, setName] = useState("");
13
- const [targetStandard, setTargetStandard] = useState("");
14
- const [destinationStandard, setDestinationStandard] = useState("");
15
- const [file, setFile] = useState<File | null>(null);
16
- const [standardsFiles, setStandardsFiles] = useState<File[]>([]);
17
- const [selectedPresetId, setSelectedPresetId] = useState<string>("");
18
-
19
- const { data: presets = [], isLoading: presetsLoading, isError: presetsError } = useQuery({
20
- queryKey: ["presets"],
21
- queryFn: fetchPresets,
22
- refetchInterval: (currentPresets) =>
23
- Array.isArray(currentPresets) &&
24
- currentPresets.some((preset) => preset.status === "processing")
25
- ? 2000
26
- : false,
27
- });
28
- const selectedPreset = presets.find((preset) => preset.id === selectedPresetId);
29
-
30
- const mutation = useMutation({
31
- mutationFn: uploadSession,
32
- onSuccess: (session) => {
33
- console.log("Session created:", session);
34
- queryClient.invalidateQueries({ queryKey: ["sessions"] });
35
- onSuccess?.(session);
36
- resetForm();
37
- },
38
- onError: (error: unknown) => {
39
- console.error("Session creation failed:", error);
40
- },
41
- });
42
-
43
- function resetForm() {
44
- setName("");
45
- setTargetStandard("");
46
- setDestinationStandard("");
47
- setFile(null);
48
- setStandardsFiles([]);
49
- setSelectedPresetId("");
50
- const reportInput = document.getElementById("source-doc-input") as HTMLInputElement | null;
51
- if (reportInput) {
52
- reportInput.value = "";
53
- }
54
- const standardsInput = document.getElementById("standards-doc-input") as HTMLInputElement | null;
55
- if (standardsInput) {
56
- standardsInput.value = "";
57
- }
58
- }
59
-
60
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
61
- event.preventDefault();
62
- if (!file) {
63
- alert("Please select a report to upload.");
64
- return;
65
- }
66
- if (!standardsFiles.length && !selectedPresetId) {
67
- alert("Upload at least one standards PDF or choose a preset bundle.");
68
- return;
69
- }
70
- if (selectedPresetId && selectedPreset && selectedPreset.status !== "ready") {
71
- alert("The selected preset is still processing. Please wait until it is ready.");
72
- return;
73
- }
74
- mutation.mutate({
75
- name,
76
- target_standard: targetStandard,
77
- destination_standard: destinationStandard,
78
- metadata: {},
79
- sourceFile: file,
80
- standardsFiles,
81
- standardsPresetId: selectedPresetId || undefined,
82
- });
83
- };
84
-
85
- return (
86
- <form className="card" onSubmit={handleSubmit}>
87
- <h2>Create Conversion Session</h2>
88
- <label className="field">
89
- <span>Session Name</span>
90
- <input value={name} onChange={(event) => setName(event.target.value)} required />
91
- </label>
92
- <label className="field">
93
- <span>Current Standard</span>
94
- <input
95
- value={targetStandard}
96
- onChange={(event) => setTargetStandard(event.target.value)}
97
- required
98
- />
99
- </label>
100
- <label className="field">
101
- <span>Target Standard</span>
102
- <input
103
- value={destinationStandard}
104
- onChange={(event) => setDestinationStandard(event.target.value)}
105
- required
106
- />
107
- </label>
108
- <label className="field">
109
- <span>Word Report</span>
110
- <input
111
- id="source-doc-input"
112
- type="file"
113
- accept=".docx"
114
- onChange={(event) => setFile(event.target.files?.[0] ?? null)}
115
- required
116
- />
117
- </label>
118
- <label className="field">
119
- <span>New Standards PDFs</span>
120
- <input
121
- id="standards-doc-input"
122
- type="file"
123
- accept=".pdf"
124
- multiple
125
- onChange={(event) => setStandardsFiles(Array.from(event.target.files ?? []))}
126
- />
127
- <small>
128
- {standardsFiles.length
129
- ? `${standardsFiles.length} file(s) selected`
130
- : "Select one or more PDF references."}
131
- </small>
132
- </label>
133
- <label className="field">
134
- <span>Saved Standards Preset (optional)</span>
135
- <select
136
- value={selectedPresetId}
137
- onChange={(event) => setSelectedPresetId(event.target.value)}
138
- disabled={presetsLoading || !presets.length}
139
- >
140
- <option value="">Select a preset...</option>
141
- {presets.map((preset: StandardsPreset) => (
142
- <option
143
- key={preset.id}
144
- value={preset.id}
145
- disabled={preset.status !== "ready"}
146
- >
147
- {preset.name} ({preset.document_count} file{preset.document_count === 1 ? "" : "s"})
148
- {preset.status === "processing"
149
- ? ` — processing ${preset.processed_count}/${preset.total_count || preset.document_count}`
150
- : preset.status === "failed"
151
- ? " — failed"
152
- : ""}
153
- </option>
154
- ))}
155
- </select>
156
- <small>
157
- {presetsLoading
158
- ? "Loading saved presets..."
159
- : presetsError
160
- ? "Failed to load presets."
161
- : presets.length
162
- ? selectedPreset
163
- ? selectedPreset.status === "processing"
164
- ? `Processing preset ${selectedPreset.processed_count}/${selectedPreset.total_count || selectedPreset.document_count}.`
165
- : selectedPreset.status === "failed"
166
- ? `Preset failed${selectedPreset.last_error ? `: ${selectedPreset.last_error}` : ""}`
167
- : "Optionally reuse a previously parsed standards bundle."
168
- : "Optionally reuse a previously parsed standards bundle."
169
- : "No presets yet. Create one below."}
170
- </small>
171
- </label>
172
- <button type="submit" disabled={mutation.isPending}>
173
- {mutation.isPending ? "Uploading..." : "Start Conversion"}
174
- </button>
175
- {mutation.isPending && (
176
- <div className="progress-block">
177
- <div className="progress-bar">
178
- <div className="progress-bar-fill" />
179
- </div>
180
- <p className="muted">Uploading files and starting pipeline...</p>
181
- </div>
182
- )}
183
- {mutation.isError && (
184
- <p className="error">Upload failed: {(mutation.error as Error).message}</p>
185
- )}
186
- {mutation.isSuccess && <p className="success">Session created successfully.</p>}
187
- </form>
188
- );
189
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/main.tsx CHANGED
@@ -1,15 +1,10 @@
1
  import React from "react";
2
  import ReactDOM from "react-dom/client";
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
  import App from "./App";
5
  import "./styles.css";
6
 
7
- const queryClient = new QueryClient();
8
-
9
  ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
10
  <React.StrictMode>
11
- <QueryClientProvider client={queryClient}>
12
- <App />
13
- </QueryClientProvider>
14
  </React.StrictMode>,
15
  );
 
1
  import React from "react";
2
  import ReactDOM from "react-dom/client";
 
3
  import App from "./App";
4
  import "./styles.css";
5
 
 
 
6
  ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7
  <React.StrictMode>
8
+ <App />
 
 
9
  </React.StrictMode>,
10
  );
frontend/src/pages/ObservatoryPage.tsx DELETED
@@ -1,179 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { fetchDiagnosticsEvents, fetchDiagnosticsTopology } from "../services/api";
4
- import type { DiagnosticsEvent, DiagnosticsNode, DiagnosticsTopology } from "../types/diagnostics";
5
-
6
- const EVENT_PULSE_WINDOW_MS = 5000;
7
- const EVENTS_LIMIT = 60;
8
-
9
- function buildNodeMap(topology?: DiagnosticsTopology): Map<string, DiagnosticsNode> {
10
- if (!topology) {
11
- return new Map();
12
- }
13
- return new Map(topology.nodes.map((node) => [node.id, node]));
14
- }
15
-
16
- function useActiveNodes(events: DiagnosticsEvent[] | undefined, tick: number): Set<string> {
17
- return useMemo(() => {
18
- const active = new Set<string>();
19
- if (!events) {
20
- return active;
21
- }
22
- const now = Date.now();
23
- for (const event of events) {
24
- if (!event.node_id) {
25
- continue;
26
- }
27
- const timestamp = Date.parse(event.timestamp);
28
- if (Number.isNaN(timestamp)) {
29
- continue;
30
- }
31
- if (now - timestamp <= EVENT_PULSE_WINDOW_MS) {
32
- active.add(event.node_id);
33
- }
34
- }
35
- return active;
36
- }, [events, tick]);
37
- }
38
-
39
- const timeFormatter = new Intl.DateTimeFormat(undefined, {
40
- hour: "2-digit",
41
- minute: "2-digit",
42
- second: "2-digit",
43
- });
44
-
45
- export default function ObservatoryPage() {
46
- const [timeTick, setTimeTick] = useState(Date.now());
47
- useEffect(() => {
48
- const id = window.setInterval(() => setTimeTick(Date.now()), 1000);
49
- return () => window.clearInterval(id);
50
- }, []);
51
-
52
- const {
53
- data: topology,
54
- isLoading: topologyLoading,
55
- isError: topologyError,
56
- error: topologyErrorDetails,
57
- } = useQuery({
58
- queryKey: ["diagnostics", "topology"],
59
- queryFn: fetchDiagnosticsTopology,
60
- refetchInterval: 15000,
61
- });
62
-
63
- const {
64
- data: eventsData,
65
- isLoading: eventsLoading,
66
- isError: eventsError,
67
- error: eventsErrorDetails,
68
- } = useQuery({
69
- queryKey: ["diagnostics", "events"],
70
- queryFn: () => fetchDiagnosticsEvents(EVENTS_LIMIT),
71
- refetchInterval: 2000,
72
- });
73
-
74
- const nodeMap = buildNodeMap(topology);
75
- const activeNodes = useActiveNodes(eventsData, timeTick);
76
-
77
- const edges = useMemo(() => {
78
- if (!topology) {
79
- return [];
80
- }
81
- return topology.edges
82
- .map((edge) => {
83
- const source = nodeMap.get(edge.source);
84
- const target = nodeMap.get(edge.target);
85
- if (!source || !target) {
86
- return null;
87
- }
88
- return (
89
- <line
90
- key={edge.id}
91
- x1={source.position.x}
92
- y1={source.position.y}
93
- x2={target.position.x}
94
- y2={target.position.y}
95
- className="observatory-edge"
96
- />
97
- );
98
- })
99
- .filter(Boolean) as JSX.Element[];
100
- }, [nodeMap, topology]);
101
-
102
- const nodes = useMemo(() => {
103
- if (!topology) {
104
- return [];
105
- }
106
- return topology.nodes.map((node) => {
107
- const isActive = activeNodes.has(node.id);
108
- return (
109
- <g key={node.id} className={`observatory-node group-${node.group}`}>
110
- <circle
111
- cx={node.position.x}
112
- cy={node.position.y}
113
- r={isActive ? 6 : 4}
114
- className={isActive ? "pulse" : undefined}
115
- />
116
- <text x={node.position.x} y={node.position.y - 9} textAnchor="middle">
117
- {node.label}
118
- </text>
119
- </g>
120
- );
121
- });
122
- }, [activeNodes, topology]);
123
-
124
- return (
125
- <div className="observatory-page">
126
- <div className="observatory-header">
127
- <h2>System Observatory</h2>
128
- <p>
129
- Visual map of key storage locations, services, and external dependencies. Nodes pulse when recent activity
130
- is detected.
131
- </p>
132
- </div>
133
- <div className="observatory-content">
134
- <section className="observatory-graph-card">
135
- <h3>Topology</h3>
136
- {topologyLoading ? (
137
- <p className="muted">Loading topology...</p>
138
- ) : topologyError ? (
139
- <p className="error">Failed to load topology: {(topologyErrorDetails as Error).message}</p>
140
- ) : (
141
- <svg className="observatory-canvas" viewBox="0 0 100 60" preserveAspectRatio="xMidYMid meet">
142
- {edges}
143
- {nodes}
144
- </svg>
145
- )}
146
- </section>
147
- <aside className="observatory-feed-card">
148
- <h3>Activity Feed</h3>
149
- {eventsLoading ? (
150
- <p className="muted">Monitoring events...</p>
151
- ) : eventsError ? (
152
- <p className="error">Failed to load events: {(eventsErrorDetails as Error).message}</p>
153
- ) : eventsData && eventsData.length ? (
154
- <ul className="observatory-feed">
155
- {eventsData.map((event) => {
156
- const timestamp = Date.parse(event.timestamp);
157
- const timeString = Number.isNaN(timestamp)
158
- ? event.timestamp
159
- : timeFormatter.format(new Date(timestamp));
160
- const node = event.node_id ? nodeMap.get(event.node_id) : undefined;
161
- return (
162
- <li key={event.id}>
163
- <div className="feed-row">
164
- <strong>{node?.label ?? event.node_id ?? "System"}</strong>
165
- <span className="muted">{timeString}</span>
166
- </div>
167
- <div>{event.message}</div>
168
- </li>
169
- );
170
- })}
171
- </ul>
172
- ) : (
173
- <p className="muted">No activity detected yet.</p>
174
- )}
175
- </aside>
176
- </div>
177
- </div>
178
- );
179
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/PresetEditPage.tsx DELETED
@@ -1,202 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Link, Navigate, useParams } from "react-router-dom";
3
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
- import {
5
- addPresetDocuments,
6
- fetchPresetById,
7
- removePresetDocument,
8
- updatePreset,
9
- } from "../services/api";
10
- import type { StandardsPreset } from "../types/session";
11
-
12
- export default function PresetEditPage() {
13
- const { presetId } = useParams<{ presetId: string }>();
14
- const queryClient = useQueryClient();
15
-
16
- const {
17
- data: preset,
18
- isLoading,
19
- isError,
20
- error,
21
- } = useQuery({
22
- queryKey: ["preset", presetId],
23
- queryFn: () => fetchPresetById(presetId ?? ""),
24
- enabled: Boolean(presetId),
25
- refetchInterval: (currentPreset) =>
26
- currentPreset?.status === "processing" ? 2000 : false,
27
- });
28
-
29
- const [name, setName] = useState("");
30
- const [description, setDescription] = useState("");
31
-
32
- useEffect(() => {
33
- if (preset) {
34
- setName(preset.name);
35
- setDescription(preset.description ?? "");
36
- }
37
- }, [preset?.id, preset?.name, preset?.description]);
38
-
39
- const updateMutation = useMutation({
40
- mutationFn: (payload: { name?: string; description?: string | null }) =>
41
- updatePreset(presetId ?? "", payload),
42
- onSuccess: (updated) => {
43
- queryClient.setQueryData(["preset", presetId], updated);
44
- queryClient.invalidateQueries({ queryKey: ["presets"] });
45
- },
46
- onError: (err: unknown) => {
47
- alert(`Failed to update preset: ${(err as Error).message}`);
48
- },
49
- });
50
-
51
- const addDocumentsMutation = useMutation({
52
- mutationFn: (files: File[]) => addPresetDocuments(presetId ?? "", files),
53
- onSuccess: (updated) => {
54
- queryClient.setQueryData(["preset", presetId], updated);
55
- queryClient.invalidateQueries({ queryKey: ["presets"] });
56
- },
57
- onError: (err: unknown) => {
58
- alert(`Failed to add documents: ${(err as Error).message}`);
59
- },
60
- });
61
-
62
- const removeDocumentMutation = useMutation({
63
- mutationFn: (documentPath: string) => removePresetDocument(presetId ?? "", documentPath),
64
- onSuccess: (updated) => {
65
- queryClient.setQueryData(["preset", presetId], updated);
66
- queryClient.invalidateQueries({ queryKey: ["presets"] });
67
- },
68
- onError: (err: unknown) => {
69
- alert(`Failed to remove document: ${(err as Error).message}`);
70
- },
71
- });
72
-
73
- if (!presetId) {
74
- return <Navigate to="/presets" replace />;
75
- }
76
-
77
- const totalDocs = preset?.total_count ?? preset?.document_count ?? 0;
78
- const processedDocs = Math.min(preset?.processed_count ?? 0, totalDocs);
79
- const progressPercent = totalDocs
80
- ? Math.min(100, Math.round((processedDocs / totalDocs) * 100))
81
- : preset?.status === "ready"
82
- ? 100
83
- : 0;
84
- const nextDoc = Math.min(processedDocs + 1, totalDocs);
85
-
86
- const handleSave = (event: React.FormEvent<HTMLFormElement>) => {
87
- event.preventDefault();
88
- const trimmedName = name.trim();
89
- if (!trimmedName) {
90
- alert("Preset name cannot be empty.");
91
- return;
92
- }
93
- updateMutation.mutate({
94
- name: trimmedName,
95
- description: description.trim() || "",
96
- });
97
- };
98
-
99
- const handleAddFiles = (event: React.ChangeEvent<HTMLInputElement>) => {
100
- const files = Array.from(event.target.files ?? []);
101
- if (!files.length) {
102
- return;
103
- }
104
- addDocumentsMutation.mutate(files);
105
- event.target.value = "";
106
- };
107
-
108
- const handleRemoveDocument = (documentPath: string) => {
109
- if (!window.confirm("Remove this document from the preset?")) {
110
- return;
111
- }
112
- removeDocumentMutation.mutate(documentPath);
113
- };
114
-
115
- return (
116
- <div className="page-stack">
117
- <div className="card">
118
- <div className="edit-header">
119
- <div>
120
- <h2>Edit Preset</h2>
121
- <p className="muted">Update preset metadata and manage the associated PDF files.</p>
122
- </div>
123
- <Link className="ghost-button" to="/presets">
124
- ← Back to Presets
125
- </Link>
126
- </div>
127
-
128
- {isLoading && <p className="muted">Loading preset details...</p>}
129
- {isError && <p className="error">Failed to load preset: {(error as Error).message}</p>}
130
- {preset && (
131
- <>
132
- <form className="stacked" onSubmit={handleSave}>
133
- <label className="field">
134
- <span>Preset Name</span>
135
- <input value={name} onChange={(event) => setName(event.target.value)} required />
136
- </label>
137
- <label className="field">
138
- <span>Description</span>
139
- <input value={description} onChange={(event) => setDescription(event.target.value)} />
140
- </label>
141
- <button type="submit" disabled={updateMutation.isPending}>
142
- {updateMutation.isPending ? "Saving..." : "Save Changes"}
143
- </button>
144
- </form>
145
-
146
- <div className="preset-status-card">
147
- <p className="muted">
148
- Status: <strong>{preset.status}</strong>
149
- {preset.status === "processing" && (
150
- <>
151
- {" "}
152
- · {processedDocs}/{totalDocs} processed
153
- </>
154
- )}
155
- </p>
156
- {preset.last_error && <p className="error">Last error: {preset.last_error}</p>}
157
- </div>
158
- {preset.status === "processing" && totalDocs > 0 && (
159
- <div className="progress-block">
160
- <div className="linear-progress">
161
- <div className="linear-progress-fill" style={{ width: `${progressPercent}%` }} />
162
- </div>
163
- <p className="muted">
164
- Currently parsing document {nextDoc} of {totalDocs}.
165
- </p>
166
- </div>
167
- )}
168
-
169
- <div className="card subsection">
170
- <h3>Documents</h3>
171
- {preset.documents.length ? (
172
- <ul className="document-list">
173
- {preset.documents.map((doc) => (
174
- <li key={doc} className="document-item">
175
- <span>{doc.split(/[/\\]/).pop() ?? doc}</span>
176
- <button
177
- type="button"
178
- className="ghost-button danger"
179
- onClick={() => handleRemoveDocument(doc)}
180
- disabled={removeDocumentMutation.isPending || preset.status === "processing"}
181
- >
182
- Remove
183
- </button>
184
- </li>
185
- ))}
186
- </ul>
187
- ) : (
188
- <p className="muted">No documents attached yet.</p>
189
- )}
190
- <label className="field">
191
- <span>Add Additional PDFs</span>
192
- <input type="file" accept=".pdf" multiple onChange={handleAddFiles} />
193
- {addDocumentsMutation.isPending && <p className="muted">Uploading and parsing...</p>}
194
- </label>
195
- </div>
196
- </>
197
- )}
198
- </div>
199
- </div>
200
- );
201
- }
202
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/PresetsPage.tsx DELETED
@@ -1,9 +0,0 @@
1
- import PresetManager from "../components/PresetManager";
2
-
3
- export default function PresetsPage() {
4
- return (
5
- <div className="page-stack">
6
- <PresetManager />
7
- </div>
8
- );
9
- }
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/SessionsPage.tsx DELETED
@@ -1,19 +0,0 @@
1
- import { useState } from "react";
2
- import UploadForm from "../components/UploadForm";
3
- import SessionDetails from "../components/SessionDetails";
4
- import SessionList from "../components/SessionList";
5
- import type { Session, SessionSummary } from "../types/session";
6
-
7
- export default function SessionsPage() {
8
- const [selectedSession, setSelectedSession] = useState<Session | SessionSummary | null>(null);
9
-
10
- return (
11
- <div className="grid">
12
- <div>
13
- <UploadForm onSuccess={setSelectedSession} />
14
- <SessionDetails session={selectedSession} />
15
- </div>
16
- <SessionList selectedId={selectedSession?.id ?? null} onSelect={setSelectedSession} />
17
- </div>
18
- );
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/services/api.ts DELETED
@@ -1,113 +0,0 @@
1
- import axios from "axios";
2
- import type {
3
- CreatePresetPayload,
4
- CreateSessionPayload,
5
- Session,
6
- SessionStatusResponse,
7
- SessionSummary,
8
- StandardsPreset,
9
- UpdatePresetPayload,
10
- } from "../types/session";
11
- import type { DiagnosticsEvent, DiagnosticsTopology } from "../types/diagnostics";
12
-
13
- const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000/api";
14
-
15
- export async function uploadSession(payload: CreateSessionPayload): Promise<Session> {
16
- const formData = new FormData();
17
- formData.append("name", payload.name);
18
- formData.append("target_standard", payload.target_standard);
19
- formData.append("destination_standard", payload.destination_standard);
20
- formData.append("source_doc", payload.sourceFile);
21
- (payload.standardsFiles ?? []).forEach((file) => {
22
- formData.append("standards_pdfs", file);
23
- });
24
- if (payload.standardsPresetId) {
25
- formData.append("standards_preset_id", payload.standardsPresetId);
26
- }
27
- if (payload.metadata) {
28
- formData.append("metadata", JSON.stringify(payload.metadata));
29
- }
30
-
31
- const response = await axios.post<Session>(`${API_BASE_URL}/sessions`, formData, {
32
- headers: { "Content-Type": "multipart/form-data" },
33
- });
34
- return response.data;
35
- }
36
-
37
- export async function fetchSessions(): Promise<SessionSummary[]> {
38
- const response = await axios.get<SessionSummary[]>(`${API_BASE_URL}/sessions`);
39
- return response.data;
40
- }
41
-
42
- export async function fetchSessionById(id: string): Promise<Session> {
43
- const response = await axios.get<Session>(`${API_BASE_URL}/sessions/${id}`);
44
- return response.data;
45
- }
46
-
47
- export async function fetchSessionStatus(id: string): Promise<SessionStatusResponse> {
48
- const response = await axios.get<SessionStatusResponse>(`${API_BASE_URL}/sessions/${id}/status`);
49
- return response.data;
50
- }
51
-
52
- export async function fetchPresets(): Promise<StandardsPreset[]> {
53
- const response = await axios.get<StandardsPreset[]>(`${API_BASE_URL}/presets`);
54
- return response.data;
55
- }
56
-
57
- export async function fetchPresetById(id: string): Promise<StandardsPreset> {
58
- const response = await axios.get<StandardsPreset>(`${API_BASE_URL}/presets/${id}`);
59
- return response.data;
60
- }
61
-
62
- export async function createPreset(payload: CreatePresetPayload): Promise<StandardsPreset> {
63
- const formData = new FormData();
64
- formData.append("name", payload.name);
65
- if (payload.description) {
66
- formData.append("description", payload.description);
67
- }
68
- payload.files.forEach((file) => {
69
- formData.append("standards_pdfs", file);
70
- });
71
-
72
- const response = await axios.post<StandardsPreset>(`${API_BASE_URL}/presets`, formData, {
73
- headers: { "Content-Type": "multipart/form-data" },
74
- });
75
- return response.data;
76
- }
77
-
78
- export async function addPresetDocuments(id: string, files: File[]): Promise<StandardsPreset> {
79
- const formData = new FormData();
80
- files.forEach((file) => formData.append("standards_pdfs", file));
81
- const response = await axios.post<StandardsPreset>(`${API_BASE_URL}/presets/${id}/documents`, formData, {
82
- headers: { "Content-Type": "multipart/form-data" },
83
- });
84
- return response.data;
85
- }
86
-
87
- export async function updatePreset(id: string, payload: UpdatePresetPayload): Promise<StandardsPreset> {
88
- const response = await axios.patch<StandardsPreset>(`${API_BASE_URL}/presets/${id}`, payload);
89
- return response.data;
90
- }
91
-
92
- export async function deletePreset(id: string): Promise<void> {
93
- await axios.delete(`${API_BASE_URL}/presets/${id}`);
94
- }
95
-
96
- export async function removePresetDocument(id: string, documentPath: string): Promise<StandardsPreset> {
97
- const response = await axios.delete<StandardsPreset>(`${API_BASE_URL}/presets/${id}/documents`, {
98
- params: { document: documentPath },
99
- });
100
- return response.data;
101
- }
102
-
103
- export async function fetchDiagnosticsTopology(): Promise<DiagnosticsTopology> {
104
- const response = await axios.get<DiagnosticsTopology>(`${API_BASE_URL}/diagnostics/topology`);
105
- return response.data;
106
- }
107
-
108
- export async function fetchDiagnosticsEvents(limit = 50): Promise<DiagnosticsEvent[]> {
109
- const response = await axios.get<{ events: DiagnosticsEvent[] }>(`${API_BASE_URL}/diagnostics/events`, {
110
- params: { limit },
111
- });
112
- return response.data.events;
113
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/styles.css CHANGED
@@ -1,643 +1,15 @@
1
- :root {
2
- color-scheme: light;
3
- font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
4
- line-height: 1.5;
5
- font-weight: 400;
6
- color: #0f172a;
7
- background-color: #f8fafc;
8
- }
9
-
10
  * {
11
  box-sizing: border-box;
12
  }
13
 
14
  body {
15
  margin: 0;
 
16
  }
17
 
18
- a {
19
- color: inherit;
20
- }
21
-
22
- button {
23
- font: inherit;
24
- }
25
-
26
- .app-shell {
27
- min-height: 100vh;
28
- display: flex;
29
- flex-direction: column;
30
- padding: 1.5rem;
31
- gap: 1.5rem;
32
- }
33
-
34
- header {
35
- display: flex;
36
- flex-direction: column;
37
- gap: 0.5rem;
38
- }
39
-
40
- .primary-nav {
41
- display: flex;
42
- gap: 0.75rem;
43
- margin-top: 0.5rem;
44
- }
45
-
46
- .primary-nav a {
47
- color: #1d4ed8;
48
- padding: 0.4rem 0.75rem;
49
- border-radius: 999px;
50
- border: 1px solid transparent;
51
- text-decoration: none;
52
- transition: background 0.2s, color 0.2s, border-color 0.2s;
53
- font-weight: 600;
54
- font-size: 0.9rem;
55
- }
56
-
57
- .primary-nav a:hover {
58
- border-color: #bfdbfe;
59
- background: #e0f2fe;
60
- }
61
-
62
- .primary-nav a.active {
63
- background: #1d4ed8;
64
- color: #ffffff;
65
- }
66
-
67
- main {
68
- flex: 1;
69
- }
70
-
71
- .grid {
72
- display: grid;
73
- grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
74
- gap: 1.5rem;
75
- }
76
-
77
- .card {
78
- background: #ffffff;
79
- padding: 1.25rem;
80
- border-radius: 12px;
81
- box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
82
- display: flex;
83
- flex-direction: column;
84
- gap: 0.75rem;
85
- }
86
-
87
- .field {
88
- display: flex;
89
- flex-direction: column;
90
- gap: 0.25rem;
91
- font-weight: 600;
92
- color: #0f172a;
93
- }
94
-
95
- .field input {
96
- padding: 0.5rem 0.75rem;
97
- border-radius: 8px;
98
- border: 1px solid #cbd5f5;
99
- font: inherit;
100
- }
101
-
102
- .field select {
103
- padding: 0.5rem 0.75rem;
104
- border-radius: 8px;
105
- border: 1px solid #cbd5f5;
106
- font: inherit;
107
- background: #ffffff;
108
- }
109
-
110
- button[type="submit"] {
111
- background: #1d4ed8;
112
- color: #ffffff;
113
- border: none;
114
- padding: 0.6rem 1rem;
115
- border-radius: 8px;
116
- cursor: pointer;
117
- transition: background 0.2s;
118
- }
119
-
120
- button[type="submit"]:disabled {
121
- background: #93c5fd;
122
- cursor: not-allowed;
123
- }
124
-
125
- .error {
126
- color: #b91c1c;
127
- }
128
-
129
- .success {
130
- color: #047857;
131
- }
132
-
133
- .muted {
134
- color: #64748b;
135
- font-size: 0.875rem;
136
- }
137
-
138
- .progress-block {
139
- display: flex;
140
- flex-direction: column;
141
- gap: 0.5rem;
142
- }
143
-
144
- .progress-bar {
145
- position: relative;
146
- width: 100%;
147
- height: 6px;
148
- background: #dbeafe;
149
- border-radius: 999px;
150
- overflow: hidden;
151
- }
152
-
153
- .progress-bar-fill {
154
- position: absolute;
155
- inset: 0;
156
- background: linear-gradient(90deg, #1d4ed8, #38bdf8);
157
- animation: progress-pulse 1.2s linear infinite;
158
- }
159
-
160
- @keyframes progress-pulse {
161
- 0% {
162
- transform: translateX(-50%);
163
- }
164
- 50% {
165
- transform: translateX(0%);
166
- }
167
- 100% {
168
- transform: translateX(100%);
169
- }
170
- }
171
-
172
- .notice {
173
- padding: 0.75rem;
174
- border-radius: 8px;
175
- border: 1px solid transparent;
176
- }
177
-
178
- .notice.success {
179
- background: #ecfdf5;
180
- border-color: #bbf7d0;
181
- color: #036949;
182
- }
183
-
184
- .log-panel {
185
- display: flex;
186
- flex-direction: column;
187
- gap: 0.5rem;
188
  }
189
-
190
- .log-panel ul {
191
- margin: 0;
192
- padding-left: 1rem;
193
- display: flex;
194
- flex-direction: column;
195
- gap: 0.35rem;
196
- }
197
-
198
- .log-panel li {
199
- font-size: 0.9rem;
200
- color: #0f172a;
201
- }
202
-
203
- .section-card {
204
- margin-top: 1.25rem;
205
- padding-top: 1rem;
206
- border-top: 1px solid #e2e8f0;
207
- display: flex;
208
- flex-direction: column;
209
- gap: 0.6rem;
210
- }
211
-
212
- .section-card h3 {
213
- margin: 0;
214
- font-size: 1rem;
215
- color: #1e293b;
216
- }
217
-
218
- .section-list {
219
- list-style: disc;
220
- margin: 0;
221
- padding-left: 1.25rem;
222
- display: flex;
223
- flex-direction: column;
224
- gap: 0.5rem;
225
- }
226
-
227
- .section-list li {
228
- color: #0f172a;
229
- }
230
-
231
- .diff-block {
232
- display: flex;
233
- flex-direction: column;
234
- gap: 0.35rem;
235
- margin-top: 0.35rem;
236
- padding-left: 0.5rem;
237
- border-left: 2px solid #cbd5f5;
238
- }
239
-
240
- .field small {
241
- font-weight: 400;
242
- color: #64748b;
243
- }
244
-
245
- .session-list {
246
- list-style: none;
247
- margin: 0;
248
- padding: 0;
249
- display: flex;
250
- flex-direction: column;
251
- gap: 0.75rem;
252
- }
253
-
254
- .session-list li {
255
- display: flex;
256
- justify-content: space-between;
257
- align-items: center;
258
- padding: 0.75rem;
259
- border-radius: 10px;
260
- border: 1px solid transparent;
261
- cursor: pointer;
262
- transition: border-color 0.2s, background 0.2s;
263
- }
264
-
265
- .session-list li:hover {
266
- border-color: #bfdbfe;
267
- }
268
-
269
- .session-list li.selected {
270
- border-color: #1d4ed8;
271
- background: #eff6ff;
272
- }
273
-
274
- .status {
275
- text-transform: uppercase;
276
- font-weight: 600;
277
- font-size: 0.75rem;
278
- }
279
-
280
- .status-created,
281
- .status-processing {
282
- color: #1d4ed8;
283
- }
284
-
285
- .status-review {
286
- color: #0f766e;
287
- }
288
-
289
- .status-completed {
290
- color: #16a34a;
291
- }
292
-
293
- .status-failed {
294
- color: #b91c1c;
295
- }
296
-
297
- .details-grid {
298
- display: grid;
299
- grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
300
- gap: 0.75rem;
301
- }
302
-
303
- .details-grid dt {
304
- font-size: 0.75rem;
305
- text-transform: uppercase;
306
- color: #475569;
307
- }
308
-
309
- .details-grid dd {
310
- margin: 0;
311
- font-weight: 600;
312
- }
313
-
314
- footer {
315
- text-align: center;
316
- color: #64748b;
317
- font-size: 0.875rem;
318
- }
319
-
320
- @media (max-width: 960px) {
321
- .grid {
322
- grid-template-columns: 1fr;
323
- }
324
- }
325
- .actions-row {
326
- display: flex;
327
- justify-content: flex-end;
328
- }
329
-
330
- .card-header {
331
- display: flex;
332
- align-items: center;
333
- justify-content: space-between;
334
- gap: 1rem;
335
- margin-bottom: 1rem;
336
- }
337
-
338
- .card-header h2 {
339
- margin: 0;
340
- }
341
-
342
- .header-actions {
343
- display: flex;
344
- align-items: center;
345
- gap: 0.75rem;
346
- }
347
-
348
- .text-small {
349
- font-size: 0.875rem;
350
- }
351
-
352
- .ghost-button {
353
- background: transparent;
354
- border: 1px solid #cbd5f5;
355
- color: #1d4ed8;
356
- padding: 0.4rem 0.8rem;
357
- border-radius: 8px;
358
- cursor: pointer;
359
- transition: background 0.2s, color 0.2s;
360
- }
361
-
362
- .ghost-button:hover:not(:disabled) {
363
- background: #e0f2fe;
364
- }
365
-
366
- .ghost-button:disabled {
367
- color: #94a3b8;
368
- cursor: not-allowed;
369
- }
370
-
371
- .subsection {
372
- margin-top: 0.75rem;
373
- display: flex;
374
- flex-direction: column;
375
- gap: 0.5rem;
376
- }
377
-
378
- .stacked {
379
- display: flex;
380
- flex-direction: column;
381
- gap: 0.75rem;
382
- }
383
-
384
- .preset-list ul {
385
- list-style: none;
386
- padding: 0;
387
- margin: 0;
388
- display: flex;
389
- flex-direction: column;
390
- gap: 0.75rem;
391
- }
392
-
393
- .preset-list li {
394
- border: 1px solid #e2e8f0;
395
- border-radius: 10px;
396
- padding: 0.75rem;
397
- display: flex;
398
- flex-direction: column;
399
- gap: 0.35rem;
400
- }
401
-
402
- .preset-docs {
403
- font-size: 0.8rem;
404
- }
405
-
406
- .preset-actions {
407
- display: flex;
408
- gap: 0.5rem;
409
- margin-top: 0.5rem;
410
- }
411
-
412
- .ghost-button.danger {
413
- border-color: #fca5a5;
414
- color: #b91c1c;
415
- }
416
-
417
- .ghost-button.danger:hover:not(:disabled) {
418
- background: #fee2e2;
419
- }
420
-
421
- .ghost-button.danger:disabled {
422
- color: #fca5a5;
423
- border-color: #fca5a5;
424
- }
425
-
426
- .edit-header {
427
- display: flex;
428
- align-items: center;
429
- justify-content: space-between;
430
- gap: 1rem;
431
- flex-wrap: wrap;
432
- }
433
-
434
- .document-list {
435
- list-style: none;
436
- padding: 0;
437
- margin: 0;
438
- display: flex;
439
- flex-direction: column;
440
- gap: 0.5rem;
441
- }
442
-
443
- .document-item {
444
- display: flex;
445
- align-items: center;
446
- justify-content: space-between;
447
- gap: 0.75rem;
448
- padding: 0.6rem 0.75rem;
449
- border: 1px solid #e2e8f0;
450
- border-radius: 8px;
451
- }
452
-
453
- .preset-status-card {
454
- margin-top: 1rem;
455
- padding: 0.75rem;
456
- border: 1px solid #e2e8f0;
457
- border-radius: 10px;
458
- background: #f8fafc;
459
- }
460
-
461
-
462
- .page-stack {
463
- display: flex;
464
- flex-direction: column;
465
- gap: 1.5rem;
466
- }
467
-
468
- .preset-header {
469
- display: flex;
470
- justify-content: space-between;
471
- gap: 1rem;
472
- }
473
-
474
- .preset-status {
475
- display: flex;
476
- align-items: center;
477
- gap: 0.5rem;
478
- font-size: 0.85rem;
479
- }
480
-
481
- .status-ready {
482
- color: #16a34a;
483
- font-weight: 600;
484
- }
485
-
486
- .status-processing {
487
- color: #1d4ed8;
488
- font-weight: 600;
489
- }
490
-
491
- .status-failed {
492
- color: #b91c1c;
493
- font-weight: 600;
494
- }
495
-
496
- .linear-progress {
497
- width: 100%;
498
- height: 6px;
499
- border-radius: 999px;
500
- background: #dbeafe;
501
- overflow: hidden;
502
- }
503
-
504
- .linear-progress-fill {
505
- height: 100%;
506
- background: linear-gradient(90deg, #1d4ed8, #38bdf8);
507
- transition: width 0.3s ease;
508
- }
509
-
510
-
511
- /* Observatory */
512
- .observatory-page {
513
- display: flex;
514
- flex-direction: column;
515
- gap: 1.5rem;
516
- }
517
-
518
- .observatory-header h2 {
519
- margin: 0;
520
- }
521
-
522
- .observatory-header p {
523
- margin: 0.25rem 0 0;
524
- color: #475569;
525
- }
526
-
527
- .observatory-content {
528
- display: flex;
529
- flex-wrap: wrap;
530
- gap: 1.5rem;
531
- }
532
-
533
- .observatory-graph-card,
534
- .observatory-feed-card {
535
- background: #ffffff;
536
- border-radius: 12px;
537
- padding: 1.25rem;
538
- box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
539
- flex: 1 1 320px;
540
- min-height: 320px;
541
- }
542
-
543
- .observatory-graph-card {
544
- flex: 2 1 520px;
545
- display: flex;
546
- flex-direction: column;
547
- }
548
-
549
- .observatory-canvas {
550
- margin-top: 1rem;
551
- width: 100%;
552
- height: 100%;
553
- min-height: 320px;
554
- border-radius: 12px;
555
- background: radial-gradient(circle at center, #dbeafe, #e2e8f0 70%);
556
- padding: 1rem;
557
- }
558
-
559
- .observatory-edge {
560
- stroke: rgba(30, 64, 175, 0.35);
561
- stroke-width: 0.8;
562
- stroke-linecap: round;
563
- }
564
-
565
- .observatory-node circle {
566
- transform-origin: center;
567
- transform-box: fill-box;
568
- fill: #1d4ed8;
569
- opacity: 0.7;
570
- transition: r 0.2s ease, opacity 0.2s ease;
571
- }
572
-
573
- .observatory-node text {
574
- font-size: 3px;
575
- fill: #0f172a;
576
- pointer-events: none;
577
- }
578
-
579
- .observatory-node.group-storage circle {
580
- fill: #0ea5e9;
581
- }
582
-
583
- .observatory-node.group-service circle {
584
- fill: #16a34a;
585
- }
586
-
587
- .observatory-node.group-external circle {
588
- fill: #f97316;
589
- }
590
-
591
- .observatory-node circle.pulse {
592
- animation: observatory-pulse 1.2s ease-in-out infinite;
593
- opacity: 0.95;
594
- }
595
-
596
- @keyframes observatory-pulse {
597
- 0%, 100% {
598
- transform: scale(1);
599
- opacity: 0.95;
600
- }
601
- 50% {
602
- transform: scale(1.3);
603
- opacity: 0.6;
604
- }
605
- }
606
-
607
- .observatory-feed-card {
608
- display: flex;
609
- flex-direction: column;
610
- }
611
-
612
- .observatory-feed {
613
- list-style: none;
614
- padding: 0;
615
- margin: 1rem 0 0;
616
- display: flex;
617
- flex-direction: column;
618
- gap: 0.75rem;
619
- max-height: 360px;
620
- overflow-y: auto;
621
- }
622
-
623
- .observatory-feed li {
624
- padding: 0.75rem;
625
- border-radius: 10px;
626
- background: #f8fafc;
627
- border: 1px solid #e2e8f0;
628
- }
629
-
630
- .observatory-feed .feed-row {
631
- display: flex;
632
- align-items: center;
633
- justify-content: space-between;
634
- margin-bottom: 0.35rem;
635
- }
636
-
637
- .observatory-feed .feed-row strong {
638
- color: #0f172a;
639
- }
640
-
641
- .observatory-feed .feed-row .muted {
642
- font-size: 0.85rem;
643
- }
 
 
 
 
 
 
 
 
 
 
1
  * {
2
  box-sizing: border-box;
3
  }
4
 
5
  body {
6
  margin: 0;
7
+ background: #000000;
8
  }
9
 
10
+ #root,
11
+ .black-screen {
12
+ width: 100vw;
13
+ height: 100vh;
14
+ background: #000000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/types/diagnostics.ts DELETED
@@ -1,27 +0,0 @@
1
- export interface DiagnosticsNode {
2
- id: string;
3
- label: string;
4
- group: "storage" | "service" | "external" | string;
5
- position: { x: number; y: number };
6
- last_event_at?: string | null;
7
- }
8
-
9
- export interface DiagnosticsEdge {
10
- id: string;
11
- source: string;
12
- target: string;
13
- }
14
-
15
- export interface DiagnosticsTopology {
16
- nodes: DiagnosticsNode[];
17
- edges: DiagnosticsEdge[];
18
- }
19
-
20
- export interface DiagnosticsEvent {
21
- id: string;
22
- timestamp: string;
23
- event_type: string;
24
- message: string;
25
- node_id?: string | null;
26
- metadata?: Record<string, unknown>;
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/types/session.ts DELETED
@@ -1,59 +0,0 @@
1
- export interface SessionSummary {
2
- id: string;
3
- name: string;
4
- status: string;
5
- created_at: string;
6
- updated_at: string;
7
- source_doc: string;
8
- target_standard: string;
9
- destination_standard: string;
10
- standards_count: number;
11
- last_error?: string | null;
12
- }
13
-
14
- export interface Session extends SessionSummary {
15
- standards_docs: string[];
16
- logs: string[];
17
- metadata: Record<string, unknown>;
18
- }
19
-
20
- export interface CreateSessionPayload {
21
- name: string;
22
- target_standard: string;
23
- destination_standard: string;
24
- metadata?: Record<string, unknown>;
25
- sourceFile: File;
26
- standardsFiles?: File[];
27
- standardsPresetId?: string | null;
28
- }
29
-
30
- export interface SessionStatusResponse {
31
- id: string;
32
- status: string;
33
- updated_at: string;
34
- }
35
-
36
- export interface StandardsPreset {
37
- id: string;
38
- name: string;
39
- description?: string | null;
40
- documents: string[];
41
- document_count: number;
42
- status: string;
43
- processed_count: number;
44
- total_count: number;
45
- last_error?: string | null;
46
- created_at: string;
47
- updated_at: string;
48
- }
49
-
50
- export interface CreatePresetPayload {
51
- name: string;
52
- description?: string;
53
- files: File[];
54
- }
55
-
56
- export interface UpdatePresetPayload {
57
- name?: string;
58
- description?: string | null;
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
infra/README.md DELETED
@@ -1,10 +0,0 @@
1
- # Infrastructure Assets
2
-
3
- Deployment and operational tooling for local, staging, and production environments.
4
-
5
- ## Structure
6
-
7
- - `docker/` - Container definitions, compose files, and local runtime configs.
8
- - `configs/` - Shared configuration overlays (env templates, secrets examples).
9
- - `observability/` - Logging, metrics, and tracing configuration.
10
- - `ci/` - Continuous integration workflows and automation manifests.
 
 
 
 
 
 
 
 
 
 
 
notes/comments/Initial Start Up.docx DELETED
Binary file (23.9 kB)
 
notes/comments/~$itial Start Up.docx DELETED
Binary file (162 Bytes)
 
scripts/README.md DELETED
@@ -1,9 +0,0 @@
1
- # Scripts
2
-
3
- Automation helpers for development, QA, and operations.
4
-
5
- ## Structure
6
-
7
- - `dev/` - Local environment bootstrap, data loaders, and convenience tasks.
8
- - `tools/` - Linting, formatting, and code generation utilities.
9
- - `ops/` - Deployment, monitoring, and maintenance scripts.
 
 
 
 
 
 
 
 
 
 
server/.env.example DELETED
@@ -1,12 +0,0 @@
1
- RIGHTCODES_APP_NAME=RightCodes Server
2
- RIGHTCODES_DEBUG=true
3
- RIGHTCODES_API_PREFIX=/api
4
- RIGHTCODES_CORS_ORIGINS=["http://localhost:5173"]
5
- RIGHTCODES_STORAGE_DIR=./storage
6
- RIGHTCODES_OPENAI_API_KEY=sk-your-openai-key
7
- RIGHTCODES_OPENAI_API_BASE=https://api.openai.com/v1
8
- RIGHTCODES_OPENAI_MODEL_EXTRACT=gpt-4.1-mini
9
- RIGHTCODES_OPENAI_MODEL_MAPPING=gpt-4.1-mini
10
- RIGHTCODES_OPENAI_MODEL_REWRITE=gpt-4o-mini
11
- RIGHTCODES_OPENAI_MODEL_VALIDATE=gpt-4.1-mini
12
- RIGHTCODES_OPENAI_MODEL_EMBED=text-embedding-3-large