Compare commits

...
Author SHA1 Message Date
MervinPraison 7fe03534de Release v0.3.142 2026-08-26 01:00:01 +00:00
MervinPraisonandCursor 23a193eb22 fix(frontend): complete docs UI P1 — routing, nav, TOC, mermaid
Wire route-manifest template resolution, mobile nav drawer, live TOC from
page headings, Mermaid async rendering via aiui:content-loaded, and
FlexibleLayout footer deduplication.

Co-authored-by: Cursor <[email protected]>
2026-08-25 23:58:43 +01:00
MervinPraison b6dc50180b Release v0.3.141 2026-08-25 22:48:37 +00:00
MervinPraisonandCursor d91c3baf21 fix(frontend): improve docs UI contrast and MkDocs rendering
Fix unreadable light-mode code blocks, transform MkDocs Material syntax
(tabs, icons, grid cards) in both React and plugin paths, and auto-select
nav items from the URL including top-level entries like /docs/index.

Co-authored-by: Cursor <[email protected]>
2026-08-25 23:45:46 +01:00
MervinPraisonandCursor 5c9e8b2356 docs(adp): harden protocol spec after multi-agent validation
Split component catalog schema from instance data, fix theme/slots
validation, clarify navigation model, add implementation status and
CI tests for ADP JSON Schema conformance.

Co-authored-by: Cursor <[email protected]>
2026-08-25 22:47:42 +01:00
MervinPraison a48f3ae4b2 fix(ci): enhance merge gate recovery and streamline auto-merge process 2026-08-25 22:40:35 +01:00
MervinPraison b4d705ce12 fix(ci): recognise Claude replies and unblock stale-FINAL after rebase
Port merge-gate recovery fixes from PraisonAI-Tools so auto-merge resumes after
conflict rebase pushes and MervinPraison Claude finished comments count.
2026-08-15 13:33:36 +01:00
MervinPraison 88c84d0c35 Release v0.3.140 2026-08-01 02:22:54 +00:00
MervinPraison 6d71fa4197 fix: port full scope-creep gates from PraisonAI with UI wording
Align merge gate, conflict/CI fix prompts, FINAL phases, and claude.yml
with PraisonAI lightweight-and-powerful checks — product value, layering
(praisonaiagents / PraisonAI-Tools), and server.py bloat — not Agent-class.
2026-07-31 14:04:16 +01:00
praisonai-triage-agent[bot] 576164960f Merge pull request #250 from MervinPraison/claude/issue-240-20260727-0912
feat: surface degraded knowledge search when embedding model unavailable
2026-07-31 12:08:20 +00:00
praisonai-triage-agent[bot] 9ec612b362 Merge pull request #249 from MervinPraison/claude/issue-235-20260727-0910
fix: prevent cp1252 UnicodeEncodeError in dashboard examples
2026-07-31 12:04:29 +00:00
praisonai-triage-agent[bot] 56760f843c Merge pull request #248 from MervinPraison/claude/issue-238-20260727-0912
fix: require explicit opt-in for GATEWAY_AUTH_TOKEN url auth
2026-07-31 12:00:49 +00:00
praisonai-triage-agent[bot] 39123500d5 Merge pull request #214 from MervinPraison/claude/issue-209-20260714-fix
fix: dedupe aiui:prefill-composer listener across chat re-navigations
2026-07-31 11:57:08 +00:00
praisonai-triage-agent[bot] 4098e5da4b Merge pull request #213 from MervinPraison/claude/issue-207-20260714-fix
fix: overview approval polling stacks duplicate click handlers (#207)
2026-07-31 11:53:16 +00:00
MervinPraison b3daa2fcfa Release v0.3.139 2026-07-31 11:52:38 +00:00
praisonai-triage-agent[bot] 7c7c121940 Merge pull request #215 from MervinPraison/claude/issue-211-20260714-work-hub-chat
feat: Work Hub Chat tab sends messages to agent via existing transport
2026-07-31 11:49:54 +00:00
praisonai-triage-agent[bot] c1da3fe0ae Merge pull request #212 from MervinPraison/claude/issue-210-20260714-1010
fix: honest gateway reachability probe on onboarding Screen 1
2026-07-31 11:48:48 +00:00
praisonai-triage-agent[bot]andMervin Praison a44776b18e fix: surface AC-4 opt-in on cross-origin CORS block in gateway probe
The reachability probe used default cors mode, so a cross-origin CORS
block rejected the fetch (landing in the 'network' branch) instead of
producing an opaque response. That made the 'opaque' branch and its
"Continue anyway" checkbox (AC-4) dead code. Now a rejected fetch on a
cross-origin URL maps to 'opaque' (ambiguous: CORS-blocked or down) so
the opt-in checkbox renders, while same-origin rejections remain a
definitive 'network' error.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-31 10:56:40 +00:00
MervinPraison 0e095d73fe fix: unblock FINAL @claude when review bots stay silent
Port PraisonAI review-chain parity: 30-minute required-reviewer timeout,
skip Copilot in scheduled FINAL sweep, and selftests for timeout path.
2026-07-31 11:53:09 +01:00
praisonai-triage-agent[bot]andMervin Praison d9836a955e fix: drop superseded #238 token-auth changes (already landed via #251)
Main's #251/#253/#258 implements the #238 fix with a stricter, more secure
design: AIUI_URL_TOKEN / GATEWAY_AUTH_TOKEN alone are inert; URL-token auth
activates only on explicit opt-in (AIUI_REQUIRE_TOKEN, YAML auth.requireToken,
require_auth). This branch's earlier variant treated AIUI_URL_TOKEN presence
(and YAML auth.urlToken) as an opt-in signal, which forced 401 and broke
test_token_auth_optin.py::test_aiui_url_token_alone_does_not_force_401.

Revert server.py to main's canonical logic and drop the now-conflicting
tests/unit/test_gateway_token_optin.py (its cases are covered by main's
tests/unit/test_token_auth_optin.py).

Co-authored-by: Mervin Praison <[email protected]>
2026-07-30 06:34:03 +00:00
praisonai-triage-agent[bot]andMervin Praison 0f986461f9 fix: require explicit opt-in for GATEWAY_AUTH_TOKEN url auth (fixes #238)
GATEWAY_AUTH_TOKEN leaked into an interactive shell no longer silently
enforces 401 on every /api/* route in standalone create_app(). Enforcement
now requires explicit intent via AIUI_URL_TOKEN, AIUI_REQUIRE_TOKEN=1, or
YAML auth.urlToken. Secured gateway deployments keep working.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-30 06:32:39 +00:00
MervinPraisonandClaude 6ffc84e9ba fix: scope overview approval re-bind to re-rendered subtrees (fixes #207)
refreshApprovals re-rendered only #ov-metrics and #ov-attention but then
called bindEvents on the whole overview root, stacking click/keydown
listeners on unchanged shell nodes (agent rows, omnibar) on every 10s poll.
Scope the re-bind to the two re-rendered subtrees so stale nodes keep a
single listener. paint() still does the full-tree bind on initial render.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-30 06:32:21 +00:00
MervinPraisonandClaude d4aa8c2ec4 feat: wire Work Hub Chat tab to agent via shared mini-chat transport (fixes #211)
Add mini-chat.js reusing the existing /api/chat/ws protocol (run_content/
run_completed/run_error) and wire work-hub.js renderChat() to stream real
agent replies instead of only appending a local DOM bubble. Sessions/agents
rebind per card and the WebSocket is torn down on card/tab switch.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-30 06:32:14 +00:00
MervinPraisonandClaude c4be709f8d fix: dedupe aiui:prefill-composer listener across chat re-navigations (fixes #209)
Register the Code Studio prefill listener as a module-scope singleton via
ensurePrefillListener() and remove it in cleanup(), preventing duplicate
window handlers from stacking on repeated SPA navigation between /chat and
/code-studio.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-30 06:32:06 +00:00
praisonai-triage-agent[bot]andMervin Praison 92adfc6f6e fix: honest gateway reachability probe on onboarding Screen 1 (fixes #210)
Co-authored-by: Mervin Praison <[email protected]>
2026-07-30 06:32:04 +00:00
MervinPraison 90315326f7 Release v0.3.138 2026-07-30 01:56:27 +00:00
MervinPraison 0719c41399 fix: auto-trigger conflict and CI fixes without FINAL review gate
Remove the FINAL @claude prerequisite from merge-conflict and CI-failure
workflows, clear pipeline/blocked:conflict after rebase completion, add
bot-pr-recovery cron, and update gate selftests.
2026-07-29 18:15:42 +01:00
praisonai-triage-agent[bot] d008b6a250 Merge pull request #216 from MervinPraison/claude/issue-208-20260714-i18n-memory
feat: wire i18n strings and per-turn memory search into context ring UI
2026-07-29 14:38:03 +00:00
praisonai-triage-agent[bot]andMervin Praison 05fb59e890 feat: surface degraded knowledge search when embedding model unavailable (fixes #240)
Rebased onto latest main. Reconciled with the overlapping degraded-mode
work already merged from #234:
- Adopt main's canonical _is_embedding_error / _embedding_error naming,
  embedding health dict, and richer /v1/embeddings fallback (503 vs 500).
- Keep _is_embedding_unavailable helper (used by main's test suite).
- Align SimpleKnowledgeManager fallback-test to main's "local" mode.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <[email protected]>
2026-07-29 13:11:17 +00:00
praisonai-triage-agent[bot]andMervin Praison 8067429e83 fix: prevent cp1252 UnicodeEncodeError in dashboard examples (fixes #235)
Add examples/python/_shared/console.py with safe_print/icon helpers
mirroring the CLI _supports_unicode/_icon pattern, and wire safe_print
into the gateway and channel examples so emoji output degrades to ASCII
on legacy Windows consoles instead of crashing before the server starts.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-29 13:07:27 +00:00
MervinPraisonandClaude 631ae74a29 feat: wire i18n strings and per-turn memory search into context ring UI (fixes #208)
Gap A: chat.js now loads locale strings via /api/i18n and renders ring
tooltip, compaction banner, compacted flash, and memory chip through a
t() helper with English fallback.

Gap B: refreshMemoryChip() posts the last user message to
/api/memory/search and shows per-turn hit count instead of the total
stored memory count from GET /api/memory.

Gap C: role="progressbar" and aria-value* moved to the ring wrapper div;
the send button is now type="button" with a send-only label.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-29 13:05:17 +00:00
praisonai-triage-agent[bot] e85f6778dd Merge pull request #258 from MervinPraison/claude/issue-256-20260729-120000
fix: stop full-suite pollution flaking auth timing + health cache tests
2026-07-29 11:39:19 +00:00
praisonai-triage-agent[bot] d6354fade0 Merge pull request #257 from MervinPraison/claude/issue-255-20260729-000000
fix: Windows-safe test_default_sqlite_url (stub HOME/USERPROFILE)
2026-07-29 11:35:43 +00:00
MervinPraisonandClaude b16a5f6056 fix: stop full-suite pollution flaking auth timing + health cache tests (fixes #256)
Reset the global _features registry in the test_server client fixture so a
feature leaked by an earlier crashing test cannot slow /health and flake the
deep-health cache/timing assertions. Make test_timing_attack_resistance
noise-resistant by comparing the median of repeated samples with a warm-up
instead of a single sample, so CPU contention under the full suite no longer
trips the variance threshold.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-29 09:28:32 +00:00
MervinPraisonandClaude 6521beb7b2 fix: stub HOME/USERPROFILE in test_default_sqlite_url for Windows (fixes #255)
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-29 09:21:35 +00:00
MervinPraison 9e3384c40a Release v0.3.137 2026-07-29 02:06:56 +00:00
praisonai-triage-agent[bot] e12416e15b Merge pull request #254 from MervinPraison/claude/issue-252-20260728-000000
fix: make five unit tests pass on Windows
2026-07-28 11:38:05 +00:00
MervinPraison 5568b10077 Release v0.3.136 2026-07-28 11:33:51 +00:00
praisonai-triage-agent[bot] 557852b287 Merge pull request #253 from MervinPraison/claude/issue-251-20260728-000000
fix: make URL-token auth opt-in so env token no longer forces 401
2026-07-28 11:30:52 +00:00
MervinPraisonandClaude 652d612d35 fix: make five unit tests pass on Windows (fixes #252)
Resolve cross-platform failures caused by Windows' coarse time.time()
resolution, backslash path separators, and reliance on the grep binary.

- lifecycle: measure hook durations with time.perf_counter() so
  startup_time/shutdown_time are non-zero on Windows' low-res wall clock
- datastore: build the default sqlite URL with Path.as_posix() for a
  consistent forward-slash URL across platforms
- test_audio_hooks: allow last_chunk_at == started_at (clock granularity)
- test_sqlalchemy_datastore: compare against as_posix() paths
- test_unified_naming: replace grep subprocess with a portable Python scan

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-28 10:17:52 +00:00
MervinPraisonandClaude 1f578a4827 fix: make URL-token auth opt-in so env token no longer forces 401 (fixes #251)
TokenQueryMiddleware previously activated whenever GATEWAY_AUTH_TOKEN or
AIUI_URL_TOKEN was present in the process environment, forcing 401 on all
standalone /api/* routes. A token leaking into a developer shell after a
gateway run broke ad-hoc TestClient scripts (#238, #251).

Activation now requires explicit opt-in: AIUI_REQUIRE_TOKEN, YAML
auth.requireToken, or require_auth=True. Secured deployments keep full
Bearer / query-param / cookie token auth.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-28 10:16:03 +00:00
praisonai-triage-agent[bot] 84ebdbc695 Merge pull request #247 from MervinPraison/claude/issue-236-20260727-0911
fix: add UTF-8 encoding to frontend source test reads
2026-07-27 12:23:34 +00:00
praisonai-triage-agent[bot] 182d065524 Merge pull request #246 from MervinPraison/claude/issue-239-20260727-120000
fix: correct tracing integration test paths to /api/traces
2026-07-27 12:18:36 +00:00
praisonai-triage-agent[bot] 132018d123 Merge pull request #245 from MervinPraison/claude/issue-237-20260727-0911
fix: use explicit UTF-8 encoding in CLI integration test fixture
2026-07-27 12:13:41 +00:00
MervinPraison bb85860a33 Release v0.3.135 2026-07-27 12:11:31 +00:00
praisonai-triage-agent[bot] c074d0782c Merge pull request #244 from MervinPraison/claude/issue-234-20260727-fix
feat: configurable embedding model + degraded knowledge search reporting
2026-07-27 12:08:27 +00:00
praisonai-triage-agent[bot] f7e19b9a13 Merge pull request #243 from MervinPraison/claude/issue-233-20260727-0904
fix: correct tracing API test paths (/api/tracing -> /api/traces)
2026-07-27 12:02:58 +00:00
praisonai-triage-agent[bot] 1a91f574b9 Merge pull request #242 from MervinPraison/claude/issue-231-20260727-0857
feat: safe_print for examples to avoid cp1252 UnicodeEncodeError
2026-07-27 11:58:32 +00:00
praisonai-triage-agent[bot] 0c982f4959 Merge pull request #241 from MervinPraison/claude/issue-232-20260727-0900
fix: specify UTF-8 encoding for frontend source reads in tests
2026-07-27 11:53:17 +00:00
MervinPraisonandClaude 0cc7f70bbb fix: correct tracing integration test paths to /api/traces (fixes #239)
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-27 09:14:24 +00:00
praisonai-triage-agent[bot]andpraisonai-triage-agent[bot] 87c9ed09b4 fix: add UTF-8 encoding to frontend source test reads (fixes #236)
Standardize Path.read_text() calls across unit/integration tests to
explicitly use encoding="utf-8". On Windows the default locale is cp1252,
which raises UnicodeDecodeError when reading committed UTF-8 frontend
bundles (em dash, emoji, multi-byte chars), producing false negatives.

Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
2026-07-27 09:14:23 +00:00
praisonai-triage-agent[bot]andMervin Praison 30c34e7266 fix: use explicit UTF-8 encoding in CLI integration test fixture (fixes #237)
Test fixture wrote YAML/markdown and read JSON without encoding, defaulting
to cp1252 on Windows and breaking UTF-8 reads for the © character. Specify
encoding="utf-8" on all write_text/read_text calls for deterministic
cross-platform behaviour.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-27 09:13:28 +00:00
MervinPraisonandClaude 43b9fe7253 feat: configurable embedding model + degraded-mode reporting for knowledge search (fixes #234)
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-27 09:10:15 +00:00
praisonai-triage-agent[bot]andMervin Praison 5b61ece40d fix: correct tracing API test paths /api/tracing to /api/traces (fixes #233)
Co-authored-by: Mervin Praison <[email protected]>
2026-07-27 09:05:29 +00:00
praisonai-triage-agent[bot]andMervin Praison 6fd27d0ed7 feat: safe_print for examples to avoid cp1252 UnicodeEncodeError (fixes #231)
Add examples/python/_shared/console.py with safe_print/icon helpers that
degrade emoji to ASCII on legacy Windows code pages (cp1252) instead of
crashing, mirroring the CLI _supports_unicode/_icon pattern. Wire the helper
into the high-severity gateway (16, 17, 18) and channel (20-23) examples so
they start on default Windows PowerShell without PYTHONUTF8.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-27 09:04:48 +00:00
praisonai-triage-agent[bot]andMervin Praison 78a8a499c1 fix: specify UTF-8 encoding for frontend source reads in tests (fixes #232)
Standardize all Path.read_text() calls in the test suite to pass
encoding="utf-8" so committed UTF-8 frontend JS/HTML bundles decode
correctly on Windows (cp1252 default locale) instead of raising
UnicodeDecodeError.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-27 09:03:59 +00:00
MervinPraison 01a31f7c8b Release v0.3.134 2026-07-16 02:07:47 +00:00
praisonai-triage-agent[bot] fc5ce8a30c Merge pull request #230 from MervinPraison/claude/issue-223-20260715-finops
feat: Cost FinOps Strip on Overview and Chat (STITCH-013)
2026-07-15 16:45:49 +00:00
MervinPraisonandClaude c9985de128 feat: Cost FinOps Strip on Overview and Chat (fixes #223)
Add STITCH-013 ambient spend awareness reusing existing usage, config
and context-stats APIs. New _helpers.js FinOps utilities (MiniDonutSVG,
budget math, CostStrip/BudgetBanner markup); Overview CostStrip +
BudgetBanner with graceful degradation; Chat SessionCostChip + budget
banner updating on run_completed; FinOps config schema section with
cross-field validation; finops.* i18n keys (en/es/fr); unit tests.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-15 15:25:36 +00:00
praisonai-triage-agent[bot] c3e330454c Merge pull request #227 from MervinPraison/claude/issue-218-20260715-000000
feat: Skills Studio — pending/diff/history for self_improve skill writes
2026-07-15 15:23:05 +00:00
praisonai-triage-agent[bot] 203b885d6b Merge pull request #226 from MervinPraison/claude/issue-220-20260715-1317
feat: Channel Inbox — unified multi-channel message hub (STITCH-010)
2026-07-15 15:19:10 +00:00
praisonai-triage-agent[bot] 9a543164aa Merge pull request #225 from MervinPraison/claude/issue-219-20260715-1317
feat: Eval Quality Cockpit — regression dashboard (STITCH-009)
2026-07-15 15:13:56 +00:00
praisonai-triage-agent[bot] 8eb9d0a75c Merge pull request #224 from MervinPraison/claude/issue-221-20260715-000000
feat: session fork endpoint for Agent Playground (STITCH-011)
2026-07-15 15:08:36 +00:00
praisonai-triage-agent[bot]andMervin Praison f0a6b16626 fix: persist fork lineage metadata on default datastores
The session fork endpoint recorded parent_session_id/agent_id/source via
ds.update_session(metadata=...), but the shipped default stores dropped it:
SDKFileDataStore.update_session only handled title/platform/icon and
JSONFileDataStore had no override at all (base no-op). Fork lineage only
survived on the volatile MemoryDataStore used in tests, silently failing in
production.

- SDKFileDataStore: persist custom session metadata in the sentinel meta
  message and surface it via get_session().
- JSONFileDataStore: add update_session override persisting arbitrary kwargs
  (also fixes title updates being lost).
- Add regression test asserting fork metadata persists on the default stores.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <[email protected]>
2026-07-15 13:43:50 +00:00
MervinPraisonandClaude 6608e17303 feat: add Skills Studio pending/diff/history workflow (fixes #218)
Refactor skills.js into a tabbed Skills Studio (Pending, Installed,
History) that surfaces self_improve skill-write approvals, adds a
side-by-side line diff with raw/preview toggle, and wires approve/deny
to the existing /api/approvals endpoints. Adds diffLines,
renderMarkdownPreview and isSkillApproval helpers, a cross-link from
approvals.js for skill rows, and skills.* i18n keys.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-15 13:24:41 +00:00
praisonai-triage-agent[bot]andMervin Praison 91ff46dcb1 feat: add Channel Inbox unified multi-channel message hub (fixes #220)
Track B session-filter implementation: new /inbox three-pane view that
derives threads from channel-backed sessions + chat history, persists
status/assignee via session state, replies through the chat transport,
and deep-links to /chat and /work. Surfaces platform/channel_id in the
sessions list API.

Co-authored-by: Mervin Praison <[email protected]>
2026-07-15 13:24:25 +00:00
MervinPraisonandClaude 71931db0c6 feat: add session fork endpoint for Agent Playground (fixes #221)
POST /api/sessions/{id}/fork branches a chat session into a new child,
copying messages up to an optional up_to_index and recording parent
metadata. Backend primitive for the Agent Playground (STITCH-011).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-07-15 13:22:59 +00:00
118 changed files with 7787 additions and 725 deletions

No files matched your search

@@ -32,4 +32,13 @@ const comment = ciFix.buildCiFixComment({
});
assert('comment mentions product guardrails', comment.includes('Product guardrails'));
const skip = ciFix.shouldSkipCiFix({
comments: [],
headSha: 'abc1234567890abcdef1234567890abcdef12',
labels: [],
claudeInProgress: false,
failedChecks: [{ name: 'python', workflow: 'CI' }],
});
assert('ci fix without FINAL review allowed', !skip.skip);
process.exit(failed ? 1 : 0);
+4 -5
View File
@@ -46,10 +46,9 @@ function hasRecentCiFixComment(comments, headSha) {
});
}
function shouldSkipCiFix({ comments, headSha, labels, hasFinal, claudeInProgress, failedChecks }) {
function shouldSkipCiFix({ comments, headSha, labels, claudeInProgress, failedChecks }) {
if (!failedChecks.length) return { skip: true, reason: 'no failed checks' };
if (labels.includes(CI_FIX_LABEL)) return { skip: true, reason: 'ci fix pending' };
if (!hasFinal) return { skip: true, reason: 'awaiting final claude review trigger' };
if (claudeInProgress) return { skip: true, reason: 'claude in progress' };
if (hasCiFixCommentForSha(comments, headSha)) {
return { skip: true, reason: 'already commented for this sha' };
@@ -159,12 +158,14 @@ function buildCiFixComment({ headSha, failedChecks, failureSummaries }) {
'',
'**Product guardrails (AGENTS.md):**',
'- Do not disturb PraisonAIUI contracts or frontend/backend layering to accommodate scope creep.',
'- Core agent runtime belongs in praisonaiagents; agent-callable tools belong in PraisonAI-Tools — not here.',
'- Prefer existing YAML config, plugins, and feature modules over new server.py/create_app() params or knobs with no live consumer.',
'- Tests must continue to guard backward compatibility and UX behaviour — passing CI by lowering test standards is not acceptable.',
'- If the feature does not genuinely add product value, recommend reverting or narrowing scope instead of patching around failures.',
'',
'## What to do',
'1. State your verdict: **legitimate fix**, **regression fix**, or **needs human review** — and why (1–3 sentences).',
'2. Fix root cause with **minimal changes**; never bloat the Agent class with extra params.',
'2. Fix root cause with **minimal changes**; never bloat server.py or create_app() with extra params.',
exampleTest
? `3. Run failing tests locally, e.g. \`pytest ${exampleTest} -q\`, plus any related tests touched by the PR.`
: '3. Run failing tests locally with targeted pytest, plus any related tests touched by the PR.',
@@ -221,7 +222,6 @@ async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts
return { skipped: true, reason: 'no failed checks yet (may be pending)' };
}
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(ctx.comments);
const claudeInProgress = await mergeGate.hasInProgressClaudeAssistant(
github, owner, repo, prNumber
);
@@ -229,7 +229,6 @@ async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts
comments: ctx.comments,
headSha,
labels,
hasFinal,
claudeInProgress,
failedChecks: failedRuns,
});
+21 -2
View File
@@ -21,8 +21,27 @@ module.exports = {
'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). '
+ 'Do NOT expand into praisonaiagents or the monorepo unless the PR explicitly requires it.',
finalClaudeProductValue:
'4. Product value: review whether the change genuinely adds PraisonAIUI UX value and correct '
+ 'layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep.',
'4. Product value: review in depth whether the change genuinely adds PraisonAIUI UX value — '
+ 'never add features for the sake of adding them. It must strengthen the UI (simpler, '
+ 'more user-friendly, robust). If it does not clearly add value, request changes or recommend '
+ 'rejecting/closing rather than merging scope creep.\n'
+ '5. Layering: UI/frontend/server features here; core agent runtime belongs in praisonaiagents '
+ '(PraisonAI monorepo). Agent-callable tools → PraisonAI-Tools. Do not pull monorepo or SDK '
+ 'scope into this repo unless the PR explicitly requires it.\n'
+ '6. Do not bloat server.py or create_app() with new params — prefer existing YAML config, '
+ 'plugins, and feature modules; only add params if absolutely required. Reject knobs/exports '
+ 'with no live consumer.',
mergeGateProductValue:
'Confirm product value gate: change strengthens PraisonAIUI UX (no scope creep). The aim is a '
+ 'LIGHTWEIGHT AND POWERFUL package — BLOCK changes that add params/modules/exports duplicating '
+ 'existing UI capabilities (YAML config, plugins, feature modules, existing server hooks), or '
+ 'add knobs/fields with no live consumer, i.e. scope creep for the sake of it.',
mergeGateLayering:
'Layering routing: BLOCK if core agent logic was added here instead of praisonaiagents; BLOCK if '
+ 'agent-callable tools were added here instead of PraisonAI-Tools.',
mergeGateServerBloat:
'Do not bloat server.py or create_app() with additional params — only if absolutely required; '
+ 'prefer existing config/plugin patterns.',
agentPyChecks: false,
reviewBotLogins: [
'coderabbitai[bot]',
+1 -1
View File
@@ -205,7 +205,7 @@ const headAfterRebase = conflictIso(-21 * 60 * 1000);
const rebaseComments = [conflictTrigger, rebaseDone, finalAfterRebase];
assert('conflict blocks before rebase done', mg.hasRecentConflictComment([conflictTrigger], headAfterRebase));
assert('conflict clears after rebase + FINAL on HEAD', !mg.hasRecentConflictComment(rebaseComments, headAfterRebase));
assert('conflict still blocks without FINAL on HEAD', mg.hasRecentConflictComment(
assert('conflict clears after rebase without FINAL on HEAD', !mg.hasRecentConflictComment(
[conflictTrigger, rebaseDone],
headAfterRebase
));
+43 -3
View File
@@ -67,7 +67,11 @@ function isClaudeTriggerNoise(c) {
function isClaudeFinalReplyComment(c) {
const login = (c.user?.login || '').toLowerCase();
if (!login.includes('praisonai-triage')) return false;
const fromAutomation =
login.includes('praisonai-triage') ||
login.includes('github-actions') ||
AUTO_ACTORS.some((a) => a.toLowerCase() === login);
if (!fromAutomation) return false;
return (c.body || '').includes('Claude finished');
}
@@ -89,15 +93,47 @@ function isConflictRebaseTriggerComment(c) {
function isConflictRebaseCompletionComment(c) {
const login = (c.user?.login || '').toLowerCase();
if (!login.includes('praisonai-triage') && !login.includes('github-actions')) return false;
const fromAutomation =
login.includes('praisonai-triage') ||
login.includes('github-actions') ||
AUTO_ACTORS.some((a) => a.toLowerCase() === login);
if (!fromAutomation) return false;
const body = (c.body || '').toLowerCase();
return (
body.includes('rebase complete') ||
body.includes('rebase onto') ||
body.includes('force-pushed') ||
(body.includes('conflict') && body.includes('resolved'))
);
}
/** HEAD landed after conflict rebase but no FINAL @claude since that push. */
function needsFinalReReviewAfterConflictRebase(comments, headPushedAt) {
if (!headPushedAt) return false;
const headTime = new Date(headPushedAt).getTime();
const conflictTriggers = comments.filter(isConflictRebaseTriggerComment);
if (conflictTriggers.length === 0) return false;
const latestConflict = conflictTriggers.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const conflictTime = new Date(latestConflict.created_at).getTime();
if (headTime <= conflictTime) return false;
const rebaseDone = comments.some(
(c) =>
new Date(c.created_at).getTime() > conflictTime && isConflictRebaseCompletionComment(c)
);
if (!rebaseDone) return false;
const finalsAfterHead = comments.filter(
(c) =>
isFinalClaudeTriggerComment(c) &&
new Date(c.created_at).getTime() >= headTime - 60000
);
return finalsAfterHead.length === 0;
}
function conflictRebaseQuiescent(comments, headPushedAt) {
const conflictTriggers = comments.filter(isConflictRebaseTriggerComment);
if (conflictTriggers.length === 0) return true;
@@ -113,7 +149,7 @@ function conflictRebaseQuiescent(comments, headPushedAt) {
);
if (!rebaseDone) return false;
return finalClaudeCompletedOnSha(comments, headPushedAt);
return true;
}
function hasRecentConflictComment(comments, headPushedAt = null) {
@@ -238,6 +274,9 @@ function shouldSkipStaleFinalRecovery(comments, headPushedAt, headPusherLogin =
if (headPusherLogin && isClaudeAutomationLogin(headPusherLogin)) {
return { skip: true, reason: 'head pushed by Claude automation' };
}
if (needsFinalReReviewAfterConflictRebase(comments, headPushedAt)) {
return { skip: false, reason: '' };
}
if (isPushSoonAfterLatestFinal(comments, headPushedAt)) {
return {
skip: true,
@@ -846,6 +885,7 @@ module.exports = {
hasRecentConflictComment,
isConflictRebaseTriggerComment,
isConflictRebaseCompletionComment,
needsFinalReReviewAfterConflictRebase,
conflictRebaseQuiescent,
hasHumanChangesRequested,
hasAnyChangesRequested,
@@ -0,0 +1,90 @@
#!/usr/bin/env node
const chain = require('./pr-review-chain.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
const coderabbit = {
user: { login: 'coderabbitai[bot]' },
body: '<!-- summarize by coderabbit.ai --> summary',
created_at: '2026-07-02T07:16:00Z',
};
const greptile = {
user: { login: 'greptile-apps[bot]' },
body: '<h3>Greptile Summary</h3><p>Looks good</p>',
created_at: '2026-07-02T07:19:00Z',
};
const copilotKick = {
user: { login: 'MervinPraison' },
body: '@copilot Do a thorough review',
created_at: '2026-07-02T07:20:00Z',
};
const copilotReply = {
user: { login: 'copilot-swe-agent' },
body: 'Copilot review here',
created_at: '2026-07-02T07:22:00Z',
};
assert('prior not ready without greptile', !chain.priorReviewersReady([coderabbit]).ready);
assert('prior ready with coderabbit + greptile', chain.priorReviewersReady([coderabbit, greptile]).ready);
assert('copilot not ready before trigger', !chain.copilotReviewReady([coderabbit, greptile]).ready);
assert(
'copilot ready after reply',
chain.copilotReviewReady([coderabbit, greptile, copilotKick, copilotReply]).ready
);
assert(
'claude not ready before copilot',
!chain.claudeFinalReady([coderabbit, greptile]).ready
);
assert(
'claude ready after full chain',
chain.claudeFinalReady([coderabbit, greptile, copilotKick, copilotReply]).ready
);
assert(
'claude timeout fallback when copilot triggered but silent',
chain.claudeFinalReady([coderabbit, greptile, copilotKick], [], { allowCopilotTimeout: true }).ready
);
assert(
'no timeout fallback without copilot trigger',
!chain.claudeFinalReady([coderabbit, greptile], [], { allowCopilotTimeout: true }).ready
);
assert(
'skipCopilot proceeds after prior reviewers',
chain.claudeFinalReady([coderabbit, greptile], [], { skipCopilot: true }).ready
);
const oldPr = new Date(Date.now() - 31 * 60 * 1000).toISOString();
assert(
'prior not ready without coderabbit before timeout',
!chain.priorReviewersReady([greptile], [], { prCreatedAt: new Date().toISOString(), requiredWaitMs: 30 * 60 * 1000 }).ready
);
assert(
'prior timeout when coderabbit silent after 30m',
chain.priorReviewersReady([greptile], [], { prCreatedAt: oldPr, requiredWaitMs: 30 * 60 * 1000 }).ready
);
assert(
'claude FINAL timeout when required reviewer silent and no prior FINAL',
chain.claudeFinalReady([greptile], [], { prCreatedAt: oldPr, requiredWaitMs: 30 * 60 * 1000 }).ready
);
assert(
'claude FINAL timeout auto-skips copilot without explicit skipCopilot',
chain.claudeFinalReady([greptile], [], { prCreatedAt: oldPr, requiredWaitMs: 30 * 60 * 1000 }).copilotSkipped
);
const finalClaude = {
user: { login: 'github-actions[bot]' },
body: '@claude You are the FINAL architecture reviewer for this PR.',
created_at: '2026-07-02T07:25:00Z',
};
assert(
'claude FINAL not re-posted when already triggered',
!chain.claudeFinalReady([greptile, finalClaude], [], { prCreatedAt: oldPr, requiredWaitMs: 0 }).ready
);
process.exit(failed ? 1 : 0);
+21 -2
View File
@@ -16,6 +16,7 @@ const CLAUDE_TRIGGER_LOGINS = new Set(['MervinPraison', 'github-actions[bot]']);
const REQUIRED_PRIOR = ['coderabbit', 'greptile'];
const OPTIONAL_PRIOR = ['qodo', 'gemini'];
const OPTIONAL_PRIOR_WAIT_MS = 20 * 60 * 1000;
const REQUIRED_PRIOR_WAIT_MS = 30 * 60 * 1000;
function loginOf(item) {
return (item?.user?.login || '').toLowerCase();
@@ -75,11 +76,23 @@ function priorReviewerStatus(comments, reviews = []) {
function priorReviewersReady(comments, reviews = [], options = {}) {
const waitMs = options.optionalWaitMs ?? OPTIONAL_PRIOR_WAIT_MS;
const requiredWaitMs = options.requiredWaitMs ?? REQUIRED_PRIOR_WAIT_MS;
const prCreatedAt = options.prCreatedAt || null;
const status = priorReviewerStatus(comments, reviews);
const missingRequired = REQUIRED_PRIOR.filter((id) => !status[id]);
if (missingRequired.length) {
if (prCreatedAt) {
const age = Date.now() - new Date(prCreatedAt).getTime();
if (age >= requiredWaitMs) {
return {
ready: true,
reason: `required reviewer timeout (${missingRequired.join(', ')} silent after ${Math.round(requiredWaitMs / 60000)}m)`,
status,
requiredTimeout: true,
};
}
}
return { ready: false, reason: `waiting for ${missingRequired.join(', ')}`, status };
}
@@ -157,8 +170,13 @@ function claudeFinalReady(comments, reviews = [], options = {}) {
if (!prior.ready) {
return { ready: false, reason: prior.reason };
}
if (isSkipCopilot(options)) {
return { ready: true, reason: 'copilot skipped', copilotSkipped: true };
if (isSkipCopilot(options) || prior.requiredTimeout) {
return {
ready: true,
reason: prior.requiredTimeout ? prior.reason : 'copilot skipped',
copilotSkipped: true,
requiredTimeout: prior.requiredTimeout,
};
}
const copilot = copilotReviewReady(comments, reviews);
if (copilot.ready) {
@@ -283,6 +301,7 @@ module.exports = {
isSkipCopilot,
REQUIRED_PRIOR,
OPTIONAL_PRIOR,
REQUIRED_PRIOR_WAIT_MS,
COPILOT_REVIEW_BODY,
priorReviewerStatus,
priorReviewersReady,
+3 -3
View File
@@ -40,7 +40,7 @@ env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
CLAUDE_UI_SCOPE: 'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Do not expand into praisonaiagents or the monorepo unless the issue explicitly requires it.'
CLAUDE_FINAL_PHASES_COMPACT: "**Phase 1: Review per AGENTS.md** (lazy imports, backward compat, import-time <200ms; SDK/product value gate — genuine UI value, correct UI vs praisonaiagents layering, no scope creep)\n**Phase 2: FIX valid issues** found by prior reviewers — push to THIS branch\n**Phase 3: Final verdict** — approve PR if ready, else request changes"
CLAUDE_FINAL_PHASES_DETAILED: "**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n4. SDK/product value: review whether the change genuinely adds value — stronger PraisonAIUI UX and correct layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep; request changes or recommend closing if value is unclear\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items"
CLAUDE_FINAL_PHASES_DETAILED: "**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n4. Product value: review in depth whether the change genuinely adds PraisonAIUI UX value — never add features for the sake of adding them. It must strengthen the UI (simpler, more user-friendly, robust). Reject scope creep; request changes or recommend closing if value is unclear\n5. Layering: UI/frontend/server features here; core agent runtime belongs in praisonaiagents. Agent-callable tools → PraisonAI-Tools\n6. Do not bloat server.py or create_app() with new params — prefer existing YAML config, plugins, and feature modules; reject knobs/exports with no live consumer\n\n**Phase 2: FIX Valid Issues**\n7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n8. Push all code fixes directly to THIS branch (do NOT create a new PR)\n9. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n10. If all issues are resolved, approve the PR / close the Issue\n11. If blocking issues remain, request changes / leave clear action items"
jobs:
# ================================================================
@@ -565,7 +565,7 @@ jobs:
const finalBody = mergeGate.FINAL_CLAUDE_REVIEW_BODY;
const staleOpts = { allowStaleRepost: true, skipCopilot: true, optionalWaitMs: 0 };
const missingOpts = { allowCopilotTimeout: true, optionalWaitMs: 0 };
const missingOpts = { skipCopilot: true, optionalWaitMs: 0 };
if (hasFinal && isStale) {
const gate = mergeGate.shouldSkipStaleFinalRecovery(comments, headPushedAt, headPusher);
@@ -654,7 +654,7 @@ jobs:
const opts = hasFinal && isStale
? { allowStaleRepost: true, skipCopilot: true, optionalWaitMs: 0 }
: { allowCopilotTimeout: true, optionalWaitMs: 0 };
: { skipCopilot: true, optionalWaitMs: 0 };
const result = await chain.maybeTriggerClaudeFinal(
github, owner, repo, pr.number,
mergeGate.FINAL_CLAUDE_REVIEW_BODY, core, opts
+43
View File
@@ -0,0 +1,43 @@
name: Bot PR Recovery
# Belt-and-braces: kick CodeRabbit/Qodo when pull_request:opened never fired for bot PRs.
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch:
inputs:
pr_number:
description: 'Specific PR number to kick (optional)'
required: false
type: string
permissions:
pull-requests: write
issues: write
jobs:
recover:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Find and kick stalled bot PRs
uses: actions/github-script@v7
env:
PR_NUMBER_INPUT: ${{ github.event.inputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const manual = (process.env.PR_NUMBER_INPUT || '').trim();
const prNumber = manual ? parseInt(manual, 10) : null;
if (manual && Number.isNaN(prNumber)) {
throw new Error(`Invalid pr_number: ${manual}`);
}
await chain.recoverStalledBotPrs(github, owner, repo, { prNumber }, core);
+8 -7
View File
@@ -280,7 +280,7 @@ jobs:
model: claude-opus-4-8
trigger_phrase: '@claude-merge-gate-internal'
direct_prompt: |
You are the MERGE GATE reviewer for PraisonAI PR #${{ matrix.pr_number }}.
You are the MERGE GATE reviewer for PraisonAIUI PR #${{ matrix.pr_number }}.
READ-ONLY: do not modify code, commit, push, rebase, or create branches.
CRITICAL OUTPUT RULE:
@@ -295,12 +295,13 @@ jobs:
Review checklist:
1. Read AGENTS.md and the PR diff on the current branch.
2. Read all PR comments and reviews (FINAL architecture reviewer / Lead Engineer must have run).
3. Confirm SDK value gate: change strengthens praisonaiagents (no scope creep).
4. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
5. If agent.py adds >100 lines or new Agent __init__ params, respond MERGE_GATE_VERDICT: BLOCK (manual review required).
6. BLOCK if labels security/breaking-change/needs-manual-review/release, sensitive paths (.github/workflows, auth, pyproject.toml), secrets in diff, PR >800 lines or >30 files, or SDK changes without tests. Label `merge-gate-ci-only` exempts CI-only changes under `.github/workflows/`, `.github/actions/`, or merge-gate scripts.
7. Confirm no CHANGES_REQUESTED reviews and CI green on HEAD ${{ matrix.head_sha }}.
8. If ready to merge: MERGE_GATE_VERDICT: APPROVE. Otherwise: MERGE_GATE_VERDICT: BLOCK with reasons.
3. Confirm product value gate: change strengthens PraisonAIUI UX (no scope creep). The aim is a LIGHTWEIGHT AND POWERFUL package — BLOCK changes that add params/modules/exports duplicating existing UI capabilities (YAML config, plugins, feature modules, existing server hooks), or add knobs/fields with no live consumer, i.e. scope creep for the sake of it.
4. Layering routing: BLOCK if core agent logic was added here instead of praisonaiagents; BLOCK if agent-callable tools were added here instead of PraisonAI-Tools.
5. Do not bloat server.py or create_app() with additional params — only if absolutely required; prefer existing config/plugin patterns.
6. If server.py or create_app() adds >100 lines or new top-level params without a clear live consumer, respond MERGE_GATE_VERDICT: BLOCK (manual review required).
7. BLOCK if labels security/breaking-change/needs-manual-review/release, sensitive paths (.github/workflows, auth, pyproject.toml), secrets in diff, PR >800 lines or >30 files, or product code changes without tests. Label `merge-gate-ci-only` exempts CI-only changes under `.github/workflows/`, `.github/actions/`, or merge-gate scripts.
8. Confirm no CHANGES_REQUESTED reviews and CI green on HEAD ${{ matrix.head_sha }}.
9. If ready to merge: MERGE_GATE_VERDICT: APPROVE. Otherwise: MERGE_GATE_VERDICT: BLOCK with reasons.
Do not approve or merge via GitHub UI/API — only post the verdict comment.
disallowed_tools: |
+14
View File
@@ -349,6 +349,9 @@ jobs:
SCOPE: src/praisonaiui, src/frontend, tests, docs only.
**Phase 1 — Review** architecture, security, tests, backward compat, import-time budget.
Product value gate: change must strengthen PraisonAIUI UX (no scope creep). BLOCK params/modules/exports
duplicating existing UI capabilities or knobs with no live consumer. Core agent logic belongs in
praisonaiagents; agent-callable tools in PraisonAI-Tools — not here.
**Phase 2 — FIX (mandatory)** implement every VALID issue on THIS branch. Push commits before Phase 3.
Do NOT ask the author to fix. Do NOT open a new PR. Do NOT claim fork restrictions unless is_fork is true.
**Phase 3 — Verdict** approve only after fixes are pushed and tests pass; else request changes with specifics.
@@ -403,6 +406,11 @@ jobs:
STEP 1 — READ GUIDELINES
═══════════════════════════════════════════════════════════════
Read `AGENTS.md` and any `docs/principles.md`. Key invariants:
• AIM: This package must stay LIGHTWEIGHT AND POWERFUL. Do a CRITICAL REVIEW at each stage
(issue triage, fix planning, PR review/implementation) and reject scope creep for the sake
of adding features. Prefer existing YAML config, plugins, feature modules, and server hooks
over new server.py/create_app() params or exports. Even when fixing a raised issue, keep the
fix minimal — do NOT expand surface area or add knobs with no live consumer.
• Cold import of `praisonaiui` must stay under 200 ms; module count must not grow.
• Heavy / optional deps (`mcp`, `langchain`, `llama_index`, `semantic_kernel`,
`slack_sdk`, `discord.py`, `botbuilder.core`, `openai`, `anthropic`, `mistralai`,
@@ -438,6 +446,12 @@ jobs:
CONCEPTUAL CHECKS (if ANY fails — add a comment, do NOT open a PR):
• Does it duplicate an existing public symbol in `src/praisonaiui/__init__.py`?
• Does it duplicate existing UI capability via YAML config, plugins, or feature modules
instead of adding new server params? → Reject as scope creep; prefer existing patterns.
• Does it add core agent runtime logic here instead of praisonaiagents, or agent-callable
tools here instead of PraisonAI-Tools? → Route externally; do not implement here.
• Does it bloat server.py or create_app() with new params without a clear live consumer?
→ Prefer existing config/plugin hooks; only add params if absolutely required.
• Does it add new class-first message types (`class Ask*Message`, `class *Message`
with a verb in the name)? → Use a verb-first helper instead.
• Does it add a new hard dependency to `pyproject.toml` `[project] dependencies`?
+4 -16
View File
@@ -12,7 +12,7 @@ on:
- cron: '0 */6 * * *'
concurrency:
group: merge-conflict-scan-${{ github.repository }}
group: merge-conflict-scan-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -42,7 +42,9 @@ jobs:
'@claude this PR has merge conflicts with `main`.',
'Please rebase onto latest `main`, resolve conflicts (keep this PR\'s intent, merge in newer main logic),',
'run targeted tests, and force-push with `--force-with-lease`.',
'Comment which files you resolved.'
'Comment which files you resolved.',
'Do not bloat server.py or create_app() with new params — prefer existing YAML config, plugins, and feature modules unless absolutely required.',
'Keep the resolution minimal — the aim is a lightweight and powerful UI package; do not introduce scope creep while resolving conflicts.',
].join(' ');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -83,16 +85,6 @@ jobs:
};
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some((c) => {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
}
function hasRecentAutoComment(comments) {
const cutoff = Date.now() - COOLDOWN_MS;
return comments.some((c) => {
@@ -143,10 +135,6 @@ jobs:
return { skipped: true, reason: 'recent auto comment' };
}
if (!hasFinalClaudeReviewTrigger(comments)) {
return { skipped: true, reason: 'awaiting final claude review trigger' };
}
await github.rest.issues.addLabels({
owner,
repo,
+69
View File
@@ -0,0 +1,69 @@
# Knowledge Embedding Model Configuration
Knowledge / RAG search uses vector **embeddings**. The default embedding model is
`text-embedding-3-small`. Many OpenAI keys or projects do not have access to that
model (organization tier, model allowlist, or regional restrictions), which caused
knowledge search to silently degrade to local substring matching.
PraisonAIUI now makes this failure mode configurable and observable.
## Configure the embedding model
Set an accessible model via environment variable:
```bash
export AIUI_EMBEDDING_MODEL=text-embedding-ada-002
```
`OPENAI_EMBEDDING_MODEL` is also honoured as a fallback if `AIUI_EMBEDDING_MODEL`
is not set.
## Fallback chain
The OpenAI-compatible `/v1/embeddings` endpoint tries the requested (or default)
model first, then each model in the fallback chain if the primary returns a
`model_not_found` / 403 access error:
```bash
export AIUI_EMBEDDING_FALLBACK_MODELS="text-embedding-ada-002,text-embedding-3-small"
```
If every candidate is inaccessible, the endpoint returns `503` with an actionable
`model_not_found` error instead of a generic `500`.
## Observing degraded search
`GET /api/knowledge/status` reports embedding availability:
```json
{
"total": 12,
"backend": "SDKKnowledgeManager",
"status": "degraded",
"embedding": {"available": false, "error": "Project does not have access to model `text-embedding-3-small`"},
"warnings": ["Embedding model unavailable; knowledge search degraded to local text matching. Set AIUI_EMBEDDING_MODEL to an accessible model."]
}
```
`POST /api/knowledge/search` indicates whether results came from vector search or a
degraded local fallback:
```json
{
"results": [],
"count": 0,
"search_mode": "fallback",
"warning": "Vector search unavailable: Project does not have access to model text-embedding-3-small. Set AIUI_EMBEDDING_MODEL=text-embedding-ada-002 or enable embedding models in your provider project."
}
```
A dashboard can show a warning banner whenever `search_mode == "fallback"` or
`embedding.available == false`.
## Model reference
| Model | Notes |
|-------|-------|
| `text-embedding-3-small` | Default — best cost/quality when available |
| `text-embedding-3-large` | Higher quality — may also be restricted |
| `text-embedding-ada-002` | Legacy — widely available on older projects |
+69
View File
@@ -0,0 +1,69 @@
# Application Design Protocol (ADP)
Declarative UI design standard for describing application layout, navigation, components, and theming from YAML or JSON manifests.
**Status:** v1alpha1 research draft — see [implementation-status.md](./implementation-status.md) for what is implemented today.
## Documents
| Document | Description |
|----------|-------------|
| [Research report](../application-design-protocol-report.md) | 46 standards compared; YAML vs JSON debate; design principles |
| [spec.md](./spec.md) | Normative specification (draft) |
| [validation-rules.md](./validation-rules.md) | Cross-reference rules R1–R30 |
| [aiui-mapping.md](./aiui-mapping.md) | PraisonAIUI field mapping |
| [implementation-status.md](./implementation-status.md) | Feature implementation tracker |
## Schemas
| File | Purpose |
|------|---------|
| [schema/application.schema.json](./schema/application.schema.json) | Root Application kind |
| [schema/component-catalog.schema.json](./schema/component-catalog.schema.json) | Catalog meta-schema |
| [schema/theme.schema.json](./schema/theme.schema.json) | Theme preset or DTCG tokens |
| [catalogs/default.catalog.json](./catalogs/default.catalog.json) | Default component and layout catalog |
## Examples
| File | Description |
|------|-------------|
| [examples/minimal.application.yaml](./examples/minimal.application.yaml) | Smallest valid app |
| [examples/docs-site.application.yaml](./examples/docs-site.application.yaml) | Full docs site |
| [examples/landing-page.application.yaml](./examples/landing-page.application.yaml) | Zone-based layout |
## Validate locally
```bash
pip install jsonschema pyyaml
pytest tests/unit/test_adp_schemas.py -v
```
## Quick start
```yaml
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: my-app
version: "1.0.0"
spec:
site:
title: "My App"
theme:
preset: zinc
darkMode: true
templates:
docs:
layout: TwoColumnLayout
slots:
main: { type: DocContent }
routes:
- match: "/docs/**"
template: docs
content:
docs:
dir: "./docs"
```
+138
View File
@@ -0,0 +1,138 @@
# PraisonAIUI → ADP Field Mapping
Field-by-field mapping from current `aiui.template.yaml` (`Config` model in `src/praisonaiui/schema/models.py`) to ADP `Application` spec.
**Compatibility:** Flat `aiui.template.yaml` files work with the PraisonAIUI compiler today. ADP envelope format is **planned** — see [implementation-status.md](./implementation-status.md).
---
## Envelope (new in ADP)
| ADP field | PraisonAIUI today | Status | Change |
|-----------|-------------------|--------|--------|
| `schemaVersion: 1` | `schemaVersion: 1` | Implemented | Compatible |
| `apiVersion: adp.io/v1alpha1` | Not present | Planned | Add when envelope parser lands |
| `kind: Application` | Not present | Planned | Add when envelope parser lands |
| `metadata.name` | Not present | Planned | Derive from `site.title` slug |
| `metadata.version` | Not present | Planned | Recommend SemVer |
## Top-level → spec wrapper
| PraisonAIUI (flat) | ADP (envelope) | Status | Change |
|--------------------|----------------|--------|--------|
| `site:` | `spec.site:` | Planned | Wrap under `spec` |
| `site.theme` | `spec.theme:` | Planned | Promote theme to spec level |
| `navigation:` | `spec.navigation:` | Partial | Tabs compiled; not rendered |
| `navbar:` | `spec.navbar:` | Partial | Compiled; not rendered |
| `footer:` | `spec.footer:` | Partial | Compiled; not rendered |
| `search:` | `spec.search:` | Partial | Compiled; not rendered |
| `content:` | `spec.content:` | Implemented | Same |
| `components:` | `spec.components:` | Implemented | Same |
| `templates:` | `spec.templates:` | Implemented | Same |
| `routes:` | `spec.routes:` | Partial | Compiled; routing not honoured |
| `dependencies:` | `spec.dependencies:` | Implemented | Same |
| `logo:` (top-level) | Future ADP field | Implemented | Top-level on `Config`, not under `site` |
| `style:`, `chat:`, `auth:`, `dashboard:` | Not in ADP v1alpha1 | N/A | Separate kinds in future |
## Navigation fields
| Field | PraisonAIUI | ADP | Status | Notes |
|-------|-------------|-----|--------|-------|
| `navigation.tabs[]` | Mintlify-style tabs | `spec.navigation.tabs` | Partial | Compiled, not rendered |
| `content.docs.nav.mode` | `NavConfig.mode` | Same | Gap | Defined but unused by compiler |
| Top-level `navigation.mode` | Not present | Removed from ADP | N/A | Use `content.docs.nav.mode` instead |
## Theme fields
| Field | PraisonAIUI | ADP | Status |
|-------|-------------|-----|--------|
| `site.theme.preset` | Implemented | `spec.theme.preset` | Planned (envelope) |
| `site.theme.radius` | Implemented | Same | Same |
| `site.theme.darkMode` | Implemented | Same | Same |
| `theme.tokens` | Not present | DTCG file ref | Planned |
## Component fields
| Field | PraisonAIUI | ADP | Status |
|-------|-------------|-----|--------|
| `components.{id}.type` | String literal | Catalog allowlist | Planned (R6) |
| `components.{id}.props` | `dict[str, Any]` | Catalog schema | Planned (R7) |
## Template fields
| Field | PraisonAIUI | ADP | Status |
|-------|-------------|-----|--------|
| `templates.{id}.layout` | String | Catalog enum | Same values |
| `templates.{id}.slots` | `{ ref \| type }` | Same | Same |
| `templates.{id}.zones` | Widget arrays | Same | Same |
| Slots + zones on same template | Allowed | R13: one or the other | Stricter in ADP |
## Route fields
| Field | PraisonAIUI | ADP | Status |
|-------|-------------|-----|--------|
| `routes[].match` | Glob string | Same | Same |
| `routes[].template` | Template key | Same | Same |
| Runtime route resolution | Not implemented | R28 required | Gap |
## Migration example
### Today (PraisonAIUI)
```yaml
schemaVersion: 1
site:
title: "My Docs"
theme:
preset: zinc
templates:
docs:
layout: TwoColumnLayout
slots:
main: { type: DocContent }
routes:
- match: "/docs/**"
template: docs
```
### Target (ADP)
```yaml
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: my-docs
version: "1.0.0"
spec:
site:
title: "My Docs"
theme:
preset: zinc
templates:
docs:
layout: TwoColumnLayout
slots:
main: { type: DocContent }
routes:
- match: "/docs/**"
template: docs
content:
docs:
dir: "./docs"
```
## Breaking vs compatible changes
| Change | Breaking? | Mitigation |
|--------|-----------|------------|
| Add envelope fields | No | Envelope parser normalises to flat `Config` |
| Catalog validation | Soft break | Warn first, error in strict mode |
| Route runtime resolution | No (fix) | Frontend change |
| R13 slots/zones exclusivity | Soft break | Warn on violation |
| Remove chat/dashboard from ADP | No | Separate kinds in future |
@@ -0,0 +1,128 @@
{
"name": "adp-default-catalog",
"version": "1.0.0",
"description": "Default ADP component and layout catalog for PraisonAIUI",
"components": {
"Header": {
"description": "Site header with logo and navigation",
"props": {
"type": "object",
"properties": {
"logoText": { "type": "string" },
"logoHref": { "type": "string", "default": "/" }
},
"required": ["logoText"],
"additionalProperties": false
},
"aria": { "role": "banner" },
"slots": ["header"]
},
"Footer": {
"description": "Site footer",
"props": {
"type": "object",
"properties": {
"text": { "type": "string" }
},
"additionalProperties": false
},
"aria": { "role": "contentinfo" },
"slots": ["footer"]
},
"DocsSidebar": {
"description": "Documentation sidebar navigation",
"props": {
"type": "object",
"properties": {
"source": { "type": "string", "enum": ["docs-nav", "manual"], "default": "docs-nav" }
},
"additionalProperties": false
},
"aria": { "role": "navigation" },
"slots": ["left", "leftSidebar"]
},
"DocContent": {
"description": "Main documentation content area",
"props": { "type": "object", "additionalProperties": false },
"aria": { "role": "main" },
"slots": ["main"]
},
"Toc": {
"description": "Table of contents for current page",
"props": { "type": "object", "additionalProperties": false },
"aria": { "role": "navigation" },
"slots": ["right", "rightSidebar"]
},
"HeroBanner": {
"description": "Hero banner with title and CTA",
"props": {
"type": "object",
"properties": {
"title": { "type": "string" },
"subtitle": { "type": "string" },
"ctaLabel": { "type": "string" },
"ctaHref": { "type": "string" }
},
"required": ["title"],
"additionalProperties": false
},
"slots": ["hero"]
},
"SocialLinks": {
"description": "Social media link row",
"props": {
"type": "object",
"properties": {
"links": {
"type": "array",
"items": {
"type": "object",
"properties": {
"platform": { "type": "string" },
"href": { "type": "string" }
},
"required": ["platform", "href"],
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"slots": ["footer"]
},
"Copyright": {
"description": "Copyright notice",
"props": {
"type": "object",
"properties": {
"text": { "type": "string" }
},
"required": ["text"],
"additionalProperties": false
},
"slots": ["footer"]
}
},
"layouts": {
"ThreeColumnLayout": {
"description": "Header + left sidebar + main + right TOC + footer",
"slots": ["header", "left", "main", "right", "footer"]
},
"TwoColumnLayout": {
"description": "Sidebar + main content",
"slots": ["left", "main"]
},
"CenteredLayout": {
"description": "Centred single column",
"slots": ["main"]
},
"FullWidthLayout": {
"description": "Full-width content",
"slots": ["header", "main", "footer"]
},
"FlexibleLayout": {
"description": "WordPress-style zone grid",
"zones": ["header", "topNav", "hero", "leftSidebar", "main", "rightSidebar", "bottomNav", "footer"]
}
}
}
@@ -0,0 +1,104 @@
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: praisonaiui-docs
version: "1.0.0"
labels:
generator: praisonaiui
spec:
site:
title: "PraisonAIUI Docs"
description: "Documentation for PraisonAIUI - YAML-driven website generator"
ui: shadcn
theme:
preset: zinc
radius: md
darkMode: true
navigation:
tabs:
- tab: "Documentation"
groups:
- group: "Getting Started"
icon: rocket
prefix: getting-started
pages:
- index
- getting-started/installation
- getting-started/quickstart
- group: "Core Concepts"
icon: lightbulb
prefix: concepts
pages:
- concepts/architecture
- concepts/configuration
- concepts/templates
- tab: "API Reference"
groups:
- group: "API Reference"
icon: code
prefix: api
pages:
- api/cli
- api/python
navbar:
primary:
type: github
href: "https://github.com/MervinPraison/PraisonAIUI"
links:
- label: "Submit Issues"
href: "https://github.com/MervinPraison/PraisonAIUI/issues"
footer:
socials:
github: "https://github.com/MervinPraison/PraisonAI"
links:
- header: "Resources"
items:
- label: "GitHub"
href: "https://github.com/MervinPraison/PraisonAI"
search:
enabled: true
provider: fusejs
content:
docs:
dir: "./docs"
include:
- "**/*.md"
- "**/*.mdx"
nav:
mode: manual
components:
header_main:
type: Header
props:
logoText: "PraisonAIUI"
footer_main:
type: Footer
props:
text: "© 2026 PraisonAI"
sidebar_docs:
type: DocsSidebar
props:
source: docs-nav
templates:
docs:
layout: ThreeColumnLayout
slots:
header: { ref: header_main }
left: { ref: sidebar_docs }
main: { type: DocContent }
footer: { ref: footer_main }
routes:
- match: "/docs/**"
template: docs
@@ -0,0 +1,58 @@
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: colorful-landing
version: "1.0.0"
spec:
site:
title: "Colorful Docs"
description: "Zone-based landing page with hero and footer widgets"
ui: shadcn
theme:
preset: rose
radius: xl
darkMode: true
content:
docs:
dir: "./docs"
nav:
mode: auto
collapsible: true
templates:
docs:
layout: FlexibleLayout
zones:
hero:
- type: HeroBanner
props:
title: "Colorful Documentation"
subtitle: "Vibrant, playful, and beautifully designed"
ctaLabel: "Get Started"
ctaHref: "/docs/getting-started/quickstart"
rightSidebar:
- type: Toc
footer:
- type: SocialLinks
props:
links:
- platform: Twitter
href: "https://twitter.com"
- platform: GitHub
href: "https://github.com"
- type: Copyright
props:
text: "Made with love by Colorful Team"
routes:
- match: "/docs/**"
template: docs
a11y:
skipToContent: true
reduceMotion: false
@@ -0,0 +1,32 @@
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: minimal-docs
version: "1.0.0"
spec:
site:
title: "Minimal Docs"
description: "Smallest valid ADP Application manifest"
ui: shadcn
theme:
preset: zinc
radius: md
darkMode: true
content:
docs:
dir: "./docs"
templates:
docs:
layout: TwoColumnLayout
slots:
main: { type: DocContent }
routes:
- match: "/docs/**"
template: docs
@@ -0,0 +1,36 @@
# ADP Implementation Status
Honest status of ADP v1alpha1 features against the PraisonAIUI reference implementation.
**Last updated:** August 2026
| Feature | Spec reference | Status | Notes |
|---------|----------------|--------|-------|
| JSON Schema validation in CLI | spec §6 | Planned | Schemas published; no `aiui validate --adp` yet |
| ADP envelope parsing (`apiVersion/kind/spec`) | spec §3 | Planned | Flat `aiui.template.yaml` works today |
| Component catalog validation (R6–R7) | spec §5 | Planned | Catalog split to `catalogs/default.catalog.json` |
| `$yaml:` / `$json:` file includes | spec §7 | Planned | Not implemented in compiler |
| Strict mode unknown-field rejection (R4) | validation-rules R4 | Planned | Pydantic allows extra fields on some models |
| Route-driven template resolution (R28) | validation-rules R28 | Gap | Frontend hardcodes `templates.docs` |
| Sidebar nav mode (R29) | validation-rules R29 | Gap | Filesystem-only sidebar today |
| Catalog ARIA roles at render (R30) | validation-rules R30 | Gap | No catalog-driven a11y in renderer |
| `navigation.tabs` rendering | spec §4.3 | Gap | Compiled to manifest but not consumed by React |
| DTCG token file validation (R26) | validation-rules R26 | Planned | Preset shorthand works via Tailwind |
| Published `$schema` URLs | README | Partial | Local schemas only; `https://adp.io/` is placeholder |
## What works today
- Flat `aiui.template.yaml` → Pydantic `Config` → compiler → JSON manifests → React SPA
- Components, templates, routes, theme presets, content scanning
- ADP example manifests validate against `application.schema.json` (standalone)
- Unit tests in `tests/unit/test_adp_schemas.py`
## Migration path
1. Add envelope normalisation layer before `Config.model_validate`
2. Wire `route-manifest.json` into frontend routing (R28)
3. Honour `navigation.tabs` and `content.docs.nav.mode` (R29)
4. Add `aiui validate --schema adp` using published JSON Schema
5. Apply catalog ARIA hints at render time (R30)
See [aiui-mapping.md](./aiui-mapping.md) for field-level compatibility.
@@ -0,0 +1,449 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "application.schema.json",
"title": "ADP Application",
"description": "Application Design Protocol — Application kind manifest",
"type": "object",
"required": ["schemaVersion", "apiVersion", "kind", "metadata", "spec"],
"additionalProperties": false,
"patternProperties": {
"^x-adp-": {}
},
"properties": {
"schemaVersion": {
"type": "integer",
"const": 1,
"description": "ADP envelope version"
},
"apiVersion": {
"type": "string",
"const": "adp.io/v1alpha1"
},
"kind": {
"type": "string",
"const": "Application"
},
"metadata": {
"type": "object",
"required": ["name"],
"additionalProperties": false,
"patternProperties": {
"^x-adp-": {}
},
"properties": {
"name": { "type": "string", "minLength": 1 },
"version": { "type": "string" },
"labels": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"annotations": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
},
"spec": {
"type": "object",
"required": ["site", "templates", "routes"],
"additionalProperties": false,
"patternProperties": {
"^x-adp-": {}
},
"properties": {
"site": { "$ref": "#/$defs/site" },
"theme": { "$ref": "#/$defs/theme" },
"navigation": { "$ref": "#/$defs/navigation" },
"navbar": { "$ref": "#/$defs/navbar" },
"footer": { "$ref": "#/$defs/footer" },
"search": { "$ref": "#/$defs/search" },
"components": { "$ref": "#/$defs/components" },
"templates": { "$ref": "#/$defs/templates" },
"routes": { "$ref": "#/$defs/routes" },
"content": { "$ref": "#/$defs/content" },
"dependencies": { "$ref": "#/$defs/dependencies" },
"seo": { "$ref": "#/$defs/seo" },
"i18n": { "$ref": "#/$defs/i18n" },
"a11y": { "$ref": "#/$defs/a11y" }
}
}
},
"$defs": {
"site": {
"type": "object",
"required": ["title"],
"additionalProperties": false,
"patternProperties": {
"^x-adp-": {}
},
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"ui": {
"type": "string",
"enum": ["shadcn", "mui", "chakra"],
"default": "shadcn"
},
"routeBaseDocs": { "type": "string", "default": "/docs" },
"customCss": { "type": "string" },
"plugins": {
"type": "array",
"items": { "type": "string" }
}
}
},
"theme": {
"oneOf": [
{
"title": "Preset theme",
"type": "object",
"additionalProperties": false,
"properties": {
"preset": {
"type": "string",
"enum": [
"zinc", "slate", "stone", "gray", "neutral",
"red", "orange", "amber", "yellow", "lime", "green",
"emerald", "teal", "cyan", "sky", "blue", "indigo",
"violet", "purple", "fuchsia", "pink", "rose"
]
},
"radius": {
"type": "string",
"enum": ["none", "sm", "md", "lg", "xl"]
},
"darkMode": { "type": "boolean" }
},
"required": ["preset"]
},
{
"title": "DTCG token reference",
"type": "object",
"additionalProperties": false,
"properties": {
"tokens": {
"type": "string",
"description": "Path or URL to DTCG 2025.10 compliant tokens.json"
},
"darkMode": { "type": "boolean" }
},
"required": ["tokens"]
}
]
},
"slotRef": {
"oneOf": [
{ "type": "null" },
{
"type": "object",
"required": ["ref"],
"additionalProperties": false,
"properties": {
"ref": { "type": "string" }
}
},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": { "type": "string" },
"props": {
"type": "object",
"additionalProperties": true
}
}
}
]
},
"widget": {
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": { "type": "string" },
"props": {
"type": "object",
"additionalProperties": true
}
}
},
"components": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": { "type": "string" },
"props": {
"type": "object",
"additionalProperties": true
}
}
}
},
"template": {
"type": "object",
"required": ["layout"],
"allOf": [
{
"oneOf": [
{ "required": ["slots"] },
{ "required": ["zones"] }
]
}
],
"additionalProperties": false,
"properties": {
"layout": {
"type": "string",
"enum": [
"ThreeColumnLayout",
"TwoColumnLayout",
"CenteredLayout",
"FullWidthLayout",
"FlexibleLayout"
]
},
"slots": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/slotRef" }
},
"zones": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "$ref": "#/$defs/widget" }
}
}
}
},
"templates": {
"type": "object",
"minProperties": 1,
"additionalProperties": { "$ref": "#/$defs/template" }
},
"route": {
"type": "object",
"required": ["match", "template"],
"additionalProperties": false,
"properties": {
"match": { "type": "string" },
"template": { "type": "string" },
"slots": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/slotRef" }
}
}
},
"routes": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/route" }
},
"navGroup": {
"type": "object",
"required": ["group"],
"additionalProperties": false,
"properties": {
"group": { "type": "string" },
"icon": { "type": "string" },
"prefix": { "type": "string" },
"pages": {
"type": "array",
"items": { "type": "string" }
}
}
},
"navTab": {
"type": "object",
"required": ["tab"],
"additionalProperties": false,
"properties": {
"tab": { "type": "string" },
"url": { "type": "string" },
"groups": {
"type": "array",
"items": { "$ref": "#/$defs/navGroup" }
}
}
},
"navigation": {
"type": "object",
"additionalProperties": false,
"properties": {
"tabs": {
"type": "array",
"items": { "$ref": "#/$defs/navTab" }
}
}
},
"navbar": {
"type": "object",
"additionalProperties": false,
"properties": {
"primary": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": { "type": "string", "enum": ["button", "github"] },
"label": { "type": "string" },
"href": { "type": "string" }
}
},
"links": {
"type": "array",
"items": {
"type": "object",
"required": ["label", "href"],
"additionalProperties": false,
"properties": {
"label": { "type": "string" },
"href": { "type": "string" }
}
}
}
}
},
"footer": {
"type": "object",
"additionalProperties": false,
"properties": {
"socials": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"links": {
"type": "array",
"items": {
"type": "object",
"required": ["header"],
"additionalProperties": false,
"properties": {
"header": { "type": "string" },
"items": {
"type": "array",
"items": {
"type": "object",
"required": ["label", "href"],
"additionalProperties": false,
"properties": {
"label": { "type": "string" },
"href": { "type": "string" }
}
}
}
}
}
}
}
},
"search": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"provider": {
"type": "string",
"enum": ["fusejs", "lunrjs", "pagefind"]
}
}
},
"dependencies": {
"type": "object",
"additionalProperties": false,
"properties": {
"shadcn": {
"type": "array",
"items": { "type": "string" }
}
}
},
"contentSource": {
"type": "object",
"required": ["dir"],
"additionalProperties": false,
"properties": {
"dir": { "type": "string" },
"include": {
"type": "array",
"items": { "type": "string" }
},
"exclude": {
"type": "array",
"items": { "type": "string" }
},
"indexFiles": {
"type": "array",
"items": { "type": "string" }
},
"nav": {
"type": "object",
"additionalProperties": false,
"properties": {
"mode": { "type": "string", "enum": ["auto", "manual"] },
"sort": { "type": "string", "enum": ["filesystem", "alpha", "date"] },
"collapsible": { "type": "boolean" },
"maxDepth": { "type": "integer", "minimum": 1, "maximum": 10 }
},
"allOf": [
{
"if": {
"properties": { "mode": { "const": "manual" } },
"required": ["mode"]
},
"then": {
"description": "Manual sidebar mode requires navigation.tabs at spec level"
}
}
]
}
}
},
"content": {
"type": "object",
"additionalProperties": false,
"properties": {
"docs": { "$ref": "#/$defs/contentSource" },
"blog": { "$ref": "#/$defs/contentSource" }
}
},
"seo": {
"type": "object",
"additionalProperties": false,
"properties": {
"titleTemplate": { "type": "string" },
"defaultImage": { "type": "string" },
"twitter": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
},
"i18n": {
"type": "object",
"additionalProperties": false,
"properties": {
"defaultLocale": { "type": "string" },
"locales": { "type": "array", "items": { "type": "string" } },
"rtlLocales": { "type": "array", "items": { "type": "string" } },
"fallbackLocale": { "type": "string" },
"translationsDir": { "type": "string" }
}
},
"a11y": {
"type": "object",
"additionalProperties": false,
"properties": {
"skipToContent": { "type": "boolean" },
"focusVisible": { "type": "boolean" },
"reduceMotion": { "type": "boolean" },
"ariaLabels": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
}
}
}
@@ -0,0 +1,82 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "component-catalog.schema.json",
"title": "ADP Component Catalog",
"description": "Allowlisted component types for ADP Application manifests",
"type": "object",
"required": ["name", "version", "components"],
"additionalProperties": false,
"patternProperties": {
"^x-adp-": {}
},
"properties": {
"name": { "type": "string" },
"version": { "type": "string" },
"description": { "type": "string" },
"components": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/componentType" }
},
"layouts": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/layoutType" }
}
},
"$defs": {
"componentType": {
"type": "object",
"required": ["description", "props"],
"additionalProperties": false,
"properties": {
"description": { "type": "string" },
"props": { "type": "object" },
"aria": {
"type": "object",
"additionalProperties": false,
"properties": {
"role": { "type": "string" },
"label": { "type": "string" }
}
},
"slots": {
"type": "array",
"items": { "type": "string" }
},
"deprecated": { "type": "boolean" },
"x-sunset": { "type": "string", "format": "date" }
}
},
"layoutType": {
"oneOf": [
{
"title": "Slot-based layout",
"type": "object",
"required": ["description", "slots"],
"additionalProperties": false,
"properties": {
"description": { "type": "string" },
"slots": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
}
},
{
"title": "Zone-based layout",
"type": "object",
"required": ["description", "zones"],
"additionalProperties": false,
"properties": {
"description": { "type": "string" },
"zones": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
}
}
]
}
}
}
@@ -0,0 +1,47 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "theme.schema.json",
"title": "ADP Theme",
"description": "Theme configuration — preset shorthand or DTCG 2025.10 token reference",
"oneOf": [
{
"title": "Preset theme",
"type": "object",
"additionalProperties": false,
"properties": {
"preset": {
"type": "string",
"enum": [
"zinc", "slate", "stone", "gray", "neutral",
"red", "orange", "amber", "yellow", "lime", "green",
"emerald", "teal", "cyan", "sky", "blue", "indigo",
"violet", "purple", "fuchsia", "pink", "rose"
]
},
"radius": {
"type": "string",
"enum": ["none", "sm", "md", "lg", "xl"],
"default": "md"
},
"darkMode": {
"type": "boolean",
"default": true
}
},
"required": ["preset"]
},
{
"title": "DTCG token reference",
"type": "object",
"additionalProperties": false,
"properties": {
"tokens": {
"type": "string",
"description": "Path or URL to DTCG 2025.10 compliant tokens.json"
},
"darkMode": { "type": "boolean" }
},
"required": ["tokens"]
}
]
}
+314
View File
@@ -0,0 +1,314 @@
# Application Design Protocol (ADP) — Normative Specification
**Version:** v1alpha1 (draft)
**Status:** Draft — not yet normative
**Date:** August 2026
This document uses [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119) keywords: MUST, MUST NOT, SHOULD, MAY.
---
## 1. Scope
ADP defines a declarative format for describing **application UI design**: site metadata, theming, navigation, reusable components, page templates, and URL routing. It does NOT define:
- Server-side APIs or data models
- Agent orchestration or workflow graphs
- Infrastructure or deployment configuration
- Executable code or templating logic in manifests
Implementations MUST treat ADP manifests as **data**, not code.
---
## 2. Terminology
| Term | Definition |
|------|------------|
| **Manifest** | A YAML or JSON document conforming to this spec |
| **Application** | The root `kind: Application` manifest describing a complete UI |
| **Component** | A named, reusable UI block with a `type` and `props` |
| **Template** | A layout definition assigning components to slots or zones |
| **Slot** | A named region within a layout (e.g. `header`, `main`) |
| **Zone** | An ordered list of widgets in a slot (WordPress-style) |
| **Route** | A URL pattern mapping to a template |
| **Catalog** | Published JSON Schema defining allowed component types |
| **Compiler** | Tool that validates and emits runtime JSON manifests |
| **Renderer** | Runtime that consumes compiled manifests and renders UI |
---
## 3. Document envelope
Every ADP manifest MUST include:
```yaml
schemaVersion: 1 # Integer; ADP envelope version
apiVersion: adp.io/v1alpha1
kind: Application # Application | Theme | ComponentCatalog (future)
metadata:
name: string # Required; unique identifier
version: string # SHOULD use SemVer
labels: object # MAY contain key-value pairs
annotations: object # MAY contain tooling metadata
spec: object # Required; kind-specific payload
```
### 3.1 Versioning policy
- `schemaVersion` — integer bumped on breaking envelope changes
- `apiVersion` — string group/version (e.g. `adp.io/v1alpha1`, `adp.io/v1`)
- `metadata.version` — application SemVer (independent of protocol version)
- Breaking changes MUST increment `apiVersion` (e.g. v1alpha1 → v1)
- Deprecated fields MUST include `deprecated: true` and SHOULD include `x-sunset` date
### 3.2 Authoring formats
Manifests MAY be authored as YAML (`.application.yaml`) or JSON (`.application.json`). Semantics MUST be identical. Runtimes MUST NOT consume YAML directly; compilers MUST normalise to JSON before validation.
---
## 4. Application spec
An `Application` spec MUST contain at minimum:
| Field | Required | Description |
|-------|----------|-------------|
| `site.title` | YES | Application display name |
| `templates` | YES | At least one template |
| `routes` | YES | At least one route |
All other fields are OPTIONAL.
### 4.1 Site (`spec.site`)
```yaml
site:
title: string # Required
description: string # Optional
ui: shadcn # UI framework hint; default shadcn
routeBaseDocs: /docs # Base path for documentation routes
```
### 4.2 Theme (`spec.theme`)
Theme MAY use preset shorthand or reference a DTCG token file:
```yaml
theme:
preset: zinc # Tailwind-style preset name
radius: md # none | sm | md | lg | xl
darkMode: true
# OR
tokens: $yaml: ./theme.tokens.json # DTCG 2025.10 file
```
### 4.3 Navigation
ADP separates **top tab bar** from **sidebar source**:
```yaml
navigation:
tabs: # Top tab bar (Mintlify-style)
- tab: Documentation
groups:
- group: Getting Started
pages: [index, getting-started/installation]
content:
docs:
dir: ./docs
nav:
mode: auto # auto | manual — sidebar source
sort: filesystem
collapsible: true
maxDepth: 4
```
- **`navigation.tabs`** — top-level tab bar with grouped pages
- **`content.docs.nav.mode: auto`** — sidebar generated from filesystem scan
- **`content.docs.nav.mode: manual`** — sidebar driven by `navigation.tabs` groups
- Renderers MUST honour `content.docs.nav.mode` when building sidebar (R29)
### 4.4 Components (`spec.components`)
```yaml
components:
header_main:
type: Header # MUST exist in component catalog
props:
logoText: PraisonAIUI
```
Rules:
- Component IDs MUST be unique within the manifest
- `type` MUST match an entry in the published component catalog
- `props` MUST validate against the catalog JSON Schema for that type
### 4.5 Templates (`spec.templates`)
```yaml
templates:
docs:
layout: ThreeColumnLayout
slots:
header: { ref: header_main }
left: { ref: sidebar_docs }
main: { type: DocContent }
right: null # Explicitly hide slot
landing:
layout: FlexibleLayout
zones:
hero:
- type: HeroBanner
props: { title: "Welcome" }
```
Rules:
- `layout` MUST be a known layout type in the catalog
- Each slot value MUST be `{ ref: id }`, `{ type: ComponentType }`, or `null`
- Zones MUST use zone names valid for `FlexibleLayout`
- A template MUST declare either `slots` or `zones`, not both (R13)
### 4.6 Routes (`spec.routes`)
```yaml
routes:
- match: /docs/changelog
template: changelog
slots: # Optional per-route slot overrides
right: null
- match: /docs/**
template: docs
```
Rules:
- Routes MUST be ordered most-specific-first
- `match` MUST be a glob pattern
- `template` MUST reference a key in `spec.templates`
- Renderers MUST resolve routes at runtime using compiled `route-manifest.json`
### 4.7 Content (`spec.content`)
```yaml
content:
docs:
dir: ./docs
include: ["**/*.md", "**/*.mdx"]
exclude: []
nav:
mode: auto
sort: filesystem
collapsible: true
maxDepth: 4
```
---
## 5. Component catalog
Implementations MUST publish a `component-catalog.schema.json` (meta-schema) and a catalog instance such as [`catalogs/default.catalog.json`](./catalogs/default.catalog.json):
```json
{
"name": "adp-default-catalog",
"version": "1.0.0",
"components": {
"Header": {
"description": "Site header with logo and navigation",
"props": {
"type": "object",
"properties": { "logoText": { "type": "string" } },
"required": ["logoText"],
"additionalProperties": false
},
"aria": { "role": "banner" },
"slots": ["header"]
}
},
"layouts": {
"ThreeColumnLayout": {
"description": "Header + sidebar + main + TOC + footer",
"slots": ["header", "left", "main", "right", "footer"]
}
}
}
```
Custom component types MUST be registered via plugin schema layers. Unknown types MUST cause validation failure in strict mode.
---
## 6. Compilation pipeline
A conforming compiler MUST:
1. Parse YAML or JSON manifest
2. Normalise to JSON
3. Validate against `application.schema.json` (JSON Schema 2020-12)
4. Validate cross-reference rules (see [validation-rules.md](./validation-rules.md))
5. Validate component props against catalog schema
6. Emit runtime manifests:
- `ui-config.json` — site, components, templates, theme, navigation
- `route-manifest.json` — ordered routes with template refs
- `docs-nav.json` — filesystem-derived navigation (if mode auto)
---
## 7. File composition
Manifests MAY include external files using Backstage-style placeholders:
```yaml
theme:
$yaml: ./theme.yaml
navigation:
$yaml: ./navigation.yaml
```
Rules:
- Paths MUST be relative to the containing manifest
- Path traversal (`../../`) MUST be rejected
- Supported placeholders: `$yaml:`, `$json:`, `$text:`
---
## 8. Extensions
Vendor extensions MUST use the `x-adp-` prefix:
```yaml
spec:
site:
x-adp-customAnalytics: true
```
Implementations MUST preserve unknown `x-adp-*` fields. Non-prefixed unknown fields MUST be rejected in strict mode.
---
## 9. Conformance
An implementation is **ADP-conformant** if and only if it:
1. Accepts manifests with `apiVersion: adp.io/v1alpha1` and `kind: Application`
2. Validates all MUST-level requirements in this spec and validation-rules.md
3. Rejects manifests with unknown fields in strict mode
4. Emits runtime JSON manifests from valid Application specs
5. Resolves routes to templates at render time
---
## 10. Related documents
- [Research report](../application-design-protocol-report.md)
- [Implementation status](./implementation-status.md)
- [PraisonAIUI mapping](./aiui-mapping.md)
- [JSON Schema: application](./schema/application.schema.json)
- [JSON Schema: component catalog](./schema/component-catalog.schema.json)
- [JSON Schema: theme](./schema/theme.schema.json)
- [Examples](./examples/)
+115
View File
@@ -0,0 +1,115 @@
# ADP Validation Rules
Cross-reference validation rules for Application Design Protocol manifests. These constraints cannot be expressed in JSON Schema alone and MUST be enforced by conforming compilers.
Rules use RFC 2119 keywords: MUST, MUST NOT, SHOULD, MAY.
Inspired by AWP validation rules R1–R32 pattern.
---
## Envelope rules
| Rule | Level | Description |
|------|-------|-------------|
| **R1** | MUST | `schemaVersion` MUST equal `1` for `apiVersion: adp.io/v1alpha1` |
| **R2** | MUST | `kind` MUST be `Application` for root manifests |
| **R3** | MUST | `metadata.name` MUST be unique within a deployment scope |
| **R4** | MUST NOT | Unknown top-level fields outside `x-adp-*` namespace in strict mode |
## Component rules
| Rule | Level | Description |
|------|-------|-------------|
| **R5** | MUST | Every `spec.components` entry MUST have a unique ID |
| **R6** | MUST | Every component `type` MUST exist in the published component catalog |
| **R7** | MUST | Component `props` MUST validate against catalog JSON Schema for that type |
| **R8** | MUST NOT | A component ID be referenced by `ref:` unless it exists in `spec.components` |
| **R9** | SHOULD | Unused components (defined but never referenced) SHOULD emit a warning |
## Template rules
| Rule | Level | Description |
|------|-------|-------------|
| **R10** | MUST | Every `spec.templates` entry MUST declare a valid `layout` from the catalog |
| **R11** | MUST | Slot names in `templates.*.slots` MUST be valid for the declared layout |
| **R12** | MUST | Zone names in `templates.*.zones` MUST be valid for `FlexibleLayout` |
| **R13** | MUST | Each template MUST declare either `slots` or `zones`, not both |
| **R14** | MUST | Inline slot `{ type: X }` types MUST exist in the component catalog |
| **R15** | MUST | Slot `{ ref: id }` MUST resolve to a component in `spec.components` |
## Route rules
| Rule | Level | Description |
|------|-------|-------------|
| **R16** | MUST | Every route `template` MUST reference a key in `spec.templates` |
| **R17** | MUST | Routes MUST be ordered most-specific-first (longer/more precise patterns before wildcards) |
| **R18** | MUST | Route slot overrides MUST use valid slot names for the target template's layout |
| **R19** | SHOULD | At least one route SHOULD match the site root or docs base path |
## Content and navigation rules
| Rule | Level | Description |
|------|-------|-------------|
| **R20** | MUST | If `content.docs.dir` is declared, the directory MUST exist at compile time |
| **R21** | MUST | If `content.docs.nav.mode` is `manual`, `navigation.tabs` MUST be non-empty |
| **R22** | SHOULD | If `content.docs.nav.mode` is `auto`, a content source MUST be declared |
Navigation model:
- **`navigation.tabs`** — top tab bar (Mintlify-style)
- **`content.docs.nav.mode`** — sidebar source: `auto` (filesystem scan) or `manual` (use `navigation.tabs` groups)
## File include rules
| Rule | Level | Description |
|------|-------|-------------|
| **R23** | MUST NOT | `$yaml:`, `$json:`, or `$text:` paths traverse outside the project root |
| **R24** | MUST | Included files MUST parse as valid YAML/JSON/text respectively |
| **R25** | MUST NOT | Circular file includes (A includes B includes A) |
## Theme rules
| Rule | Level | Description |
|------|-------|-------------|
| **R26** | MUST | If `theme.tokens` is declared, the referenced file MUST exist and validate against DTCG 2025.10 |
| **R27** | MUST NOT | Both `theme.preset` and `theme.tokens` be declared simultaneously |
## Renderer conformance (target — not yet implemented in PraisonAIUI)
These rules define the target behaviour for ADP-conformant renderers. See [implementation-status.md](./implementation-status.md).
| Rule | Level | Description |
|------|-------|-------------|
| **R28** | MUST | Renderers MUST resolve the active template from `route-manifest.json` at runtime |
| **R29** | MUST | Renderers MUST honour `content.docs.nav.mode` when building sidebar |
| **R30** | MUST | Renderers MUST apply ARIA roles from catalog for each rendered component |
---
## Validation pipeline order
```
1. Parse YAML/JSON
2. Resolve $yaml/$json/$text includes (R23–R25)
3. JSON Schema validate (application.schema.json)
4. Cross-ref rules R1–R27
5. Component props validate against catalog (R6–R7)
6. Emit runtime manifests
7. Runtime conformance R28–R30 (renderer responsibility)
```
## Error format
Validation failures MUST report:
```json
{
"rule": "R8",
"path": "spec.templates.docs.slots.header.ref",
"message": "Component ref 'missing_header' not found in spec.components",
"severity": "error"
}
```
Warnings (R9) use `"severity": "warning"`.
@@ -0,0 +1,427 @@
# Application Design Protocol (ADP) — Research Report
**Version:** 1.0 (draft)
**Date:** August 2026
**Scope:** UI, layout, navigation, theming — declarative application design from text manifests
**Status:** Research and recommendation; not yet a published standard
---
## 1. Executive summary
There is no single existing standard for **designing full application UI from a declarative text file**. Instead, the problem is addressed in fragments:
- **Docs-site generators** (Mintlify, PraisonAIUI, Hugo) declare navigation and layout in config files.
- **Form libraries** (JSON Forms, RJSF) declare layout trees from JSON Schema.
- **Agent UI protocols** (A2UI, AG-UI) declare streaming component surfaces for LLM-generated UI.
- **Catalog standards** (OpenUI, Backstage) declare component metadata without runtime layout.
- **Design token standards** (DTCG 2025.10) declare theming without structure.
**Recommendation:** Define an **Application Design Protocol (ADP)** that synthesises proven patterns from 46 surveyed standards into a UI-only manifest format.
PraisonAIUI already implements approximately 60% of this model. ADP formalises, extends, and publishes what exists today.
---
## 2. Problem statement
### 2.1 What we need
A protocol that lets a developer (or LLM) describe an application's visual design — pages, navigation, components, themes, routes — in **one or more text files**, without writing application code.
### 2.2 What exists today (fragmented)
| Need | Who solves it | Gap |
|------|---------------|-----|
| Docs site from config | Mintlify, PraisonAIUI, Docusaurus | No shared spec; Mintlify uses JSON, others use YAML or JS |
| Component catalog | OpenUI, A2UI | No layout or routing |
| Layout trees | JSON Forms uiSchema | Forms only, not app shells |
| Theming | DTCG, Material 3 | Tokens only, no pages |
| Agent-generated UI | A2UI | Runtime streaming, not human-authored static sites |
| Software metadata | Backstage | Catalog entities, not UI composition |
### 2.3 Design goals for ADP
1. **Human-readable** — editable without IDE plugins
2. **LLM-safe** — structural schema rejects unknown fields
3. **Portable** — same manifest renders on any ADP-compliant runtime
4. **Validatable** — fail fast at build time, not in the browser
5. **Extensible** — plugins add catalog entries, not inline code
6. **Accessible** — ARIA hints required in component catalog
---
## 3. Standards landscape — 46 protocols in 10 categories
Each entry describes **how UI/layout/navigation is declared** — the primary comparison dimension.
### Category A: Web platform baseline (5)
| Standard | Format | How UI/layout/navigation is declared | Schema | Versioning |
|----------|--------|--------------------------------------|--------|------------|
| **HTML + CSS** | `.html` + `.css` | Semantic elements; CSS Flexbox/Grid; `<nav>` + links | Living spec grammar | Browser Baseline |
| **Web Components + DSD** | HTML + JS | Custom element tree; slots; Declarative Shadow DOM | DOM IDL | Platform flags |
| **WAI-ARIA + APG** | HTML attributes | Landmarks, widget roles, keyboard patterns | WAI-ARIA 1.2 | Rec track |
| **DTCG Design Tokens** | JSON | Token groups: `$value`, `$type`, `{alias}` refs — **no layout** | DTCG 2025.10 | Date editions |
| **OpenUI** | YAML/JSON | Component catalog: `components{id: props}` — **no layout tree** | Prop definitions | Per-library version |
### Category B: Schema-driven forms and layout (3)
| Standard | Format | How UI/layout/navigation is declared | Schema | Versioning |
|----------|--------|--------------------------------------|--------|------------|
| **JSON Forms** | JSON Schema + UI Schema | `VerticalLayout`, `Control`, `Categorization` with `scope` pointers | JSON Schema + uiSchema | Package SemVer |
| **RJSF** | JSON Schema + uiSchema | Schema-tree mirroring; `ui:widget`, `ui:options` | React convention | Package SemVer |
| **Google A2UI** | JSONL stream | Flat adjacency-list; `createSurface`; catalog-constrained types | JSON Schema catalog | v0.9.1 / v1.0 RC |
### Category C: Docs-site generators (6)
| Standard | Format | How UI/layout/navigation is declared | Schema | Versioning |
|----------|--------|--------------------------------------|--------|------------|
| **Mintlify** | JSON (`docs.json`) | `navigation.groups[].pages[]`; `$schema` URL; `$ref` splitting | Published JSON Schema URL | Platform |
| **PraisonAIUI** | YAML | `components` → `templates{layout, slots, zones}` → `routes` | Pydantic (internal) | `schemaVersion: 1` |
| **Docusaurus** | JS config | `themeConfig.navbar`, plugin sidebar config | TypeScript types | Package SemVer |
| **Hugo** | TOML/YAML | `menu.main[]`, theme selection | Hugo schema | Release train |
| **Jekyll** | YAML | Frontmatter per page; `_data/` nav | Conventions | SemVer |
| **Gatsby** | JS/TS | `siteMetadata` + `plugins[]` | JS object | Major versions |
### Category D: Native declarative UI — reference only (5)
| Standard | Format | Serialised wire format? | Navigation model |
|----------|--------|-------------------------|------------------|
| **Jetpack Compose** | Kotlin | No | `NavHost` + routes |
| **SwiftUI** | Swift | No | `NavigationStack` |
| **Flutter** | Dart | No | `Router` / `Navigator` |
| **Qt QML** | `.qml` | Yes | `StackView`, bindings |
| **XAML** | XML | Yes (dialect-fragmented) | `Frame`, `NavigationView` |
### Category E: Design systems (2)
| Standard | Format | Theming model |
|----------|--------|---------------|
| **Material Design 3** | DTCG JSON | reference → system → component token tiers |
| **Bootstrap 5.3** | SCSS → CSS vars | Sass maps → `--bs-*` custom properties |
### Category F: Design-to-code bridges — not source-of-truth (3)
| Standard | Output | Why not ADP source |
|----------|--------|-------------------|
| **Figma MCP** | React+Tailwind context | Proprietary; bridge only |
| **Webflow export** | HTML/CSS/JS ZIP | No schema; paid export |
| **Framer export** | React bundles | Platform lock-in |
### Category G: Component documentation (1)
| Standard | Format | Navigation |
|----------|--------|------------|
| **Storybook CSF 3** | TS modules | Sidebar via `title` path hierarchy |
### Category H: Configuration and packaging (12)
| Standard | Format | Composition mechanism | ADP borrow |
|----------|--------|----------------------|------------|
| **Kubernetes CRD** | YAML | `apiVersion/kind/spec`; structural OpenAPI | Structural validation |
| **Helm** | YAML + templates | Subcharts; values merge; `values.schema.json` | Defaults + overrides |
| **Backstage catalog** | YAML | `$yaml:`/`$json:` includes; relation graph | Envelope + includes |
| **OpenAPI 3.1** | YAML/JSON | `$ref`, `components` registry | JSON Schema foundation |
| **AsyncAPI 3.x** | YAML/JSON | Channels → messages | Future event UI |
| **JSON Schema 2020-12** | JSON | `$ref`, `$defs`, conditionals | Validation dialect |
| **CloudEvents** | JSON envelope | Fixed envelope + opaque payload | Future event wrapper |
| **Docker Compose** | YAML | Multi-file merge; profiles | Override stacking |
| **CUE** | CUE | Unify schema + data | Power-user compile layer |
| **Jsonnet** | Jsonnet → JSON | `import` + merge | Theme variants |
| **Pkl** | `.pkl` | `amends`, mixins | Typed config model |
| **OCI Artifact** | JSON manifest | Multi-layer blobs | Bundle packaging |
### Category I: Agent and workflow protocols (7)
| Standard | Format | UI/state declaration | ADP borrow |
|----------|--------|---------------------|------------|
| **MCP** | JSON-RPC | Tool schemas — no UI layout | Future widget actions |
| **AG-UI** | Streaming JSON | EventType enum; snapshots + deltas | Streaming model |
| **A2A** | Proto → JSON-RPC | Agent Cards | Transport |
| **AWP** | Multi-file YAML | 7 layers; rules R1–R32 | Layer + validation model |
| **LangGraph** | workflow.yaml | Graph nodes + edges | Layout graph |
| **AutoGen** | ComponentModel JSON | `{provider, type, version, config}` | Pluggable components |
| **CrewAI** | JSONC/YAML | Agent arrays — weak schema | Anti-pattern |
### Category J: Application packaging (2)
| Standard | Format | Application declaration |
|----------|--------|------------------------|
| **OAM** | YAML | `Application` with `components[]`, `traits[]` |
| **CNAB** | bundle.json | Parameters, credentials, actions |
---
## 4. How they do it — mechanism comparison matrix
| Mechanism | Standards | How it works | ADP |
|-----------|-----------|--------------|-----|
| **Envelope document** | K8s, Backstage, OAM, AWP | `apiVersion` + `kind` + `metadata` + `spec` | Adopt |
| **Component registry + ref** | PraisonAIUI, OpenUI | Named map; templates use `ref:` | Adopt |
| **Layout + slots** | PraisonAIUI, JSON Forms | Layout type + named regions | Adopt (unified) |
| **Route → template** | PraisonAIUI, Flutter | URL glob → template | Adopt |
| **Navigation tree** | Mintlify, Storybook | Hierarchical groups → pages | Adopt |
| **Design token file** | DTCG, Material 3 | Token JSON with aliases | Adopt |
| **Catalog allowlist** | A2UI, OpenUI | Closed component vocabulary | Adopt |
| **Schema + uiSchema split** | JSON Forms, RJSF | Data vs presentation | Partial |
| **Streaming surfaces** | A2UI, AG-UI | Incremental JSON updates | Future |
| **Layered spec** | AWP, Helm | Separate files merged at compile | Adopt |
| **File includes** | Backstage, Mintlify | `$yaml:` / `$ref` external files | Adopt |
| **Structural pruning** | K8s CRD | Reject unknown fields | Adopt |
| **Validation rules R1–Rn** | AWP | Cross-ref beyond JSON Schema | Adopt |
| **JS executable config** | Docusaurus, Gatsby | Code config | Reject |
| **Visual canvas** | Figma, Webflow | Proprietary designer | Reject |
| **Code-only UI** | Compose, SwiftUI | No wire format | Out of scope |
---
## 5. Side-by-side: four closest analogues
| Dimension | PraisonAIUI | Mintlify | A2UI | Backstage |
|-----------|-------------|----------|------|-----------|
| **Authoring format** | YAML | JSON | JSON (stream) | YAML |
| **Envelope** | `schemaVersion` only | Flat JSON | Message `version` | `apiVersion/kind/metadata/spec` |
| **Navigation** | `navigation.tabs[]` + filesystem | `navigation.groups[]` | N/A | N/A (metadata catalog) |
| **Components** | `components{id: type, props}` | N/A | Catalog + adjacency list | Entity kinds |
| **Layout** | `templates{layout, slots, zones}` | Theme only | Surfaces | N/A |
| **Routes** | `routes{match, template}` | Page paths in nav | N/A | N/A |
| **Schema published** | Pydantic only | `$schema` URL | JSON Schema catalog | JSON Schema per kind |
| **Runtime** | React SPA | Hosted platform | Any renderer | Catalog API |
| **Primary use** | Docs sites | Docs sites | Agent-generated UI | Software inventory |
**Synthesis:** ADP combines PraisonAIUI's composition model, Mintlify's schema publishing, A2UI's catalog allowlist, and Backstage's envelope and file includes.
---
## 6. YAML vs alternatives — structured debate
### 6.1 The assumption
YAML is the best authoring format for application design manifests.
### 6.2 Counter-evidence
**Mintlify** — the closest commercial docs-site analogue — uses **JSON** (`docs.json`) with a published `$schema` URL for IDE autocomplete. This works well for flat-to-moderately-nested config but becomes verbose for deep layout trees.
**A2UI** uses **JSON** as both authoring and wire format because LLMs emit structured JSON reliably and ambiguity is unacceptable at runtime.
**Docusaurus and Gatsby** use **JavaScript/TypeScript** config for programmatic power — but this sacrifices portability and LLM generation safety.
### 6.3 Format comparison
| Format | Strengths | Weaknesses for ADP |
|--------|-----------|-------------------|
| **YAML** | Comments; readable nesting; LLM-friendly; PraisonAIUI continuity | Ambiguous typing; merge key footguns; no native DRY |
| **JSON** | Unambiguous; `$schema` IDE support; Mintlify-proven | No comments; verbose for layout trees |
| **TOML** | Clear flat tables (Hugo) | Poor for nested templates/zones |
| **CUE / Jsonnet / Pkl** | Typed composition without templating | Learning curve; not LLM-default |
| **HCL** | Modules, expressions | Ecosystem-bound; overkill for UI-only |
| **JS/TS** | Full programmatic power | Not portable; not LLM-safe |
### 6.4 Recommendation
```
Author (human/LLM) → YAML (default) or JSON (alternate)
↓
ADP compiler/validator (JSON Schema 2020-12)
↓
Normalised JSON manifest (runtime)
↓
UI renderer (catalog-constrained)
```
- **Primary authoring:** YAML — best for nested `templates` / `zones` trees
- **Alternate authoring:** JSON — for teams preferring Mintlify-style `$schema` tooling
- **Never at runtime:** YAML — always compile to JSON before serve
- **Composition:** `$yaml:` file includes (Backstage), not Helm Go templates
- **Power users:** Optional CUE/Jsonnet compiles to YAML before validation
**Verdict:** YAML remains the recommended default, but ADP MUST accept JSON as an equivalent authoring format with identical semantics.
---
## 7. ADP design principles
1. **Declarative, not executable** — manifests describe intent; runtimes render; no inline code or templating logic in core spec.
2. **Schema-first** — JSON Schema 2020-12 is canonical; YAML is authoring convenience.
3. **Structural strictness** — unknown fields rejected in strict mode (K8s CRD pattern).
4. **Catalog allowlist** — component `type` must exist in published catalog (A2UI pattern).
5. **Envelope consistency** — every document uses `apiVersion`, `kind`, `metadata`, `spec`.
6. **Separation of concerns** — theme, navigation, components, templates, routes are distinct spec sections.
7. **Compile, don't interpret** — YAML → validated JSON manifest → static assets at build time.
8. **Accessibility by default** — catalog entries include ARIA role hints (WAI-ARIA pattern).
9. **Versioned protocol** — `schemaVersion` integer + `apiVersion` string; breaking changes bump version.
10. **Extension namespace** — `x-adp-*` prefix reserved; plugins register catalog layers.
11. **File composition** — `$yaml:` includes for modular configs (Backstage pattern).
12. **Route-driven rendering** — URL resolution selects template; no hardcoded layout in renderer.
---
## 8. Proposed ADP document model
```yaml
schemaVersion: 1
apiVersion: adp.io/v1alpha1
kind: Application
metadata:
name: my-docs-site
version: "1.0.0"
labels: {}
spec:
site:
title: "My Site"
description: "..."
theme:
preset: zinc # or DTCG token ref
radius: md
darkMode: true
navigation:
mode: auto # auto | manual
tabs: [...]
components:
header_main:
type: Header
props: { logoText: "..." }
templates:
docs:
layout: ThreeColumnLayout
slots:
header: { ref: header_main }
main: { type: DocContent }
routes:
- match: "/docs/**"
template: docs
content:
docs:
dir: "./docs"
```
### Data flow
```
application.yaml → Parse → JSON Schema validate → Cross-ref rules (R1–R30)
→ Compile → ui-config.json + route-manifest.json + docs-nav.json
→ React / static renderer
```
---
## 9. PraisonAIUI alignment and gaps
### Already aligned
| PraisonAIUI today | ADP equivalent |
|-------------------|----------------|
| `schemaVersion: 1` | `schemaVersion` + `apiVersion` |
| `components` + `ref:` | `spec.components` + slot refs |
| `templates.layout` + `slots` | `spec.templates` |
| `routes` | `spec.routes` |
| `site.theme` | `spec.theme` |
| Compiler → JSON manifests | ADP compile pipeline |
| Pydantic validation | JSON Schema + cross-ref rules |
### Gaps to close
| Gap | ADP fix |
|-----|---------|
| No published JSON Schema | Publish `application.schema.json` |
| `navigation.tabs` compiled but not rendered | Mandate runtime consumption in spec |
| `route-manifest.json` not used for routing | Mandate route-driven template resolution |
| Dual slots + zones models | Unified: zones = ordered slot arrays |
| Closed hardcoded component types | Published `component-catalog.schema.json` |
| No envelope (`kind`, `metadata`) | Add Backstage-style envelope |
---
## 10. Migration path
Existing `aiui.template.yaml` files remain valid. Migration steps:
1. Add `apiVersion: adp.io/v1alpha1` and `kind: Application` (optional during transition).
2. Wrap top-level keys under `spec:` (compiler accepts both shapes during v1alpha1).
3. Rename `site` fields unchanged; map `navigation`, `templates`, `routes` as-is.
4. Publish catalog; register custom component types via plugin schema layers.
See [adp/aiui-mapping.md](./adp/aiui-mapping.md) for field-by-field mapping.
---
## 11. Open questions
1. **Should ADP standardise layout type names** (`ThreeColumnLayout`) or allow runtime-defined layouts?
2. **Should navigation support both filesystem-auto and manual tabs** in one spec, or require a mode switch?
3. **Should agent-driven surfaces (A2UI) be a separate `kind: Surface`** or embedded in Application?
4. **Should theme reference DTCG token files directly** or use simplified preset shorthand?
---
## 12. Appendix A — Standards index
| # | Standard | URL |
|---|----------|-----|
| 1 | A2UI | https://a2ui.org/ |
| 2 | AG-UI | https://docs.ag-ui.com/ |
| 3 | AWP | https://github.com/veegee82/agent-workflow-protocol |
| 4 | AsyncAPI | https://www.asyncapi.com/ |
| 5 | Backstage catalog | https://backstage.io/docs/features/software-catalog/descriptor-format |
| 6 | Bootstrap | https://getbootstrap.com/ |
| 7 | CloudEvents | https://cloudevents.io/ |
| 8 | CNAB | https://cnab.io/ |
| 9 | CrewAI | https://docs.crewai.com/ |
| 10 | CUE | https://cuelang.org/ |
| 11 | Docusaurus | https://docusaurus.io/ |
| 12 | DTCG Design Tokens | https://www.designtokens.org/ |
| 13 | Figma Code Connect | https://developers.figma.com/docs/code-connect/ |
| 14 | Framer | https://www.framer.com/ |
| 15 | Gatsby | https://www.gatsbyjs.com/ |
| 16 | Helm | https://helm.sh/ |
| 17 | Hugo | https://gohugo.io/ |
| 18 | Jekyll | https://jekyllrb.com/ |
| 19 | JSON Forms | https://jsonforms.io/ |
| 20 | JSON Schema | https://json-schema.org/ |
| 21 | Kubernetes CRD | https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/ |
| 22 | LangGraph | https://langchain-ai.github.io/langgraph/ |
| 23 | Material Design 3 | https://m3.material.io/ |
| 24 | MCP | https://modelcontextprotocol.io/ |
| 25 | Mintlify | https://mintlify.com/docs |
| 26 | OAM | https://oam.dev/ |
| 27 | OCI Artifact | https://opencontainers.org/ |
| 28 | OpenAPI | https://www.openapis.org/ |
| 29 | OpenUI | https://openuispec.org/ |
| 30 | Pkl | https://pkl-lang.org/ |
| 31 | PraisonAIUI | https://github.com/MervinPraison/PraisonAIUI |
| 32 | RJSF | https://rjsf-team.github.io/react-jsonschema-form/ |
| 33 | Storybook CSF | https://storybook.js.org/docs/api/csf |
| 34 | W3C ARIA APG | https://www.w3.org/WAI/ARIA/apg/ |
| 35 | Web Components | https://web.dev/articles/declarative-shadow-dom |
| 36 | Webflow | https://webflow.com/ |
| 37 | AutoGen | https://microsoft.github.io/autogen/ |
| 38 | A2A | https://google.github.io/A2A/ |
| 39 | Jsonnet | https://jsonnet.org/ |
| 40 | Docker Compose | https://docs.docker.com/compose/ |
| 41 | Qt QML | https://doc.qt.io/qt-6/qmlapplications.html |
| 42 | XAML | https://learn.microsoft.com/en-us/windows/apps/develop/platform/xaml/xaml-overview |
| 43 | Jetpack Compose | https://developer.android.com/develop/ui/compose |
| 44 | SwiftUI | https://developer.apple.com/xcode/swiftui/ |
| 45 | HTML + CSS | https://html.spec.whatwg.org/ |
| 46 | Flutter | https://flutter.dev/ |
---
## 13. Conclusion
ADP is feasible and largely prefigured by PraisonAIUI's existing architecture. The research across 46 standards yields a clear synthesis:
- **Envelope** from Backstage/K8s
- **Composition** from PraisonAIUI (components → templates → routes)
- **Catalog** from A2UI/OpenUI
- **Theming** from DTCG
- **Validation** from AWP (structural schema + R-rules)
- **Authoring** in YAML (default) with JSON alternate (Mintlify pattern)
**Status:** Research draft — see [adp/implementation-status.md](./adp/implementation-status.md) for implementation gaps.
The draft spec, JSON Schema files, and example manifests are published alongside this report in [`docs/protocols/adp/`](./adp/).
@@ -25,6 +25,7 @@ from praisonaiui.server import create_app
# Use shared seed data helper
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import safe_print as print
from _shared.seed_data import seed_demo_data
seed_demo_data()
+1
View File
@@ -20,6 +20,7 @@ import sys
# Use shared seed data helper
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import safe_print as print
from _shared.seed_data import seed_demo_data
+1
View File
@@ -26,6 +26,7 @@ import time
import praisonaiui as aiui
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '_shared'))
from console import safe_print as print
from stream_bridge import StreamBridge
try:
+6
View File
@@ -4,9 +4,15 @@ Demonstrates ALL 16 PraisonAIUI features with real API calls.
Run: aiui run app.py
"""
import os
import sys
from starlette.testclient import TestClient
from praisonaiui.server import create_app
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import safe_print as print
def test_all_features():
"""Test all 16 feature APIs with real data."""
+16 -13
View File
@@ -44,15 +44,18 @@ import sys
# ── Import praisonaiui ──────────────────────────────────────
import praisonaiui as aiui
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
# ── Check gateway availability ──────────────────────────────
try:
from praisonaiui.integration import AIUIGateway
from praisonaiagents import Agent
GATEWAY_OK = True
except ImportError as e:
print(f"⚠️ Gateway dependencies missing: {e}")
print(" Install with: pip install praisonai praisonaiagents")
print(" Falling back to standalone mode.")
safe_print(f"{icon('⚠️', '[WARN]')} Gateway dependencies missing: {e}")
safe_print(" Install with: pip install praisonai praisonaiagents")
safe_print(" Falling back to standalone mode.")
GATEWAY_OK = False
# ── Set dashboard style ─────────────────────────────────────
@@ -149,15 +152,15 @@ def _register_agents_in_dashboard():
"model": agent_def["model"],
"icon": agent_def["icon"],
})
print(f" ✓ Dashboard: {agent_def['icon']} {agent_def['name']}")
safe_print(f" {icon('✓', '[OK]')} Dashboard: {agent_def['name']}")
# ── Main ────────────────────────────────────────────────────
async def main():
host = os.getenv("HOST", "127.0.0.1")
if GATEWAY_OK:
print("🚀 Starting with Gateway mode (full execution + streaming)")
print()
safe_print(f"{icon('🚀', '[START]')} Starting with Gateway mode (full execution + streaming)")
safe_print()
# Create the gateway
gateway = AIUIGateway(
@@ -174,22 +177,22 @@ async def main():
self_reflect=False,
)
gateway.register_agent(agent, agent_id=agent_def["agent_id"])
print(f" ✓ Gateway: {agent_def['icon']} {agent_def['name']} ({agent_def['model']})")
safe_print(f" {icon('✓', '[OK]')} Gateway: {agent_def['name']} ({agent_def['model']})")
# ── Register agents in dashboard CRUD storage ───────
_register_agents_in_dashboard()
print()
print(f" Dashboard: http://localhost:8083")
print(f" WebSocket: ws://localhost:8083/ws")
print(f" Inspector: http://localhost:8083 → 🔍 Feature Inspector")
print()
safe_print()
safe_print(" Dashboard: http://localhost:8083")
safe_print(" WebSocket: ws://localhost:8083/ws")
safe_print(f" Inspector: http://localhost:8083 {icon('→', '->')} {icon('🔍', '')} Feature Inspector")
safe_print()
# Start the gateway — this runs the full server
await gateway.start()
else:
# Fallback to standalone mode (same as example 15)
print("📦 Starting in standalone mode (CRUD only, no agent execution)")
safe_print(f"{icon('📦', '[INFO]')} Starting in standalone mode (CRUD only, no agent execution)")
from praisonaiui.server import create_app
import uvicorn
+21 -18
View File
@@ -26,15 +26,18 @@ import sys
import praisonaiui as aiui
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
# ── Check gateway availability ──────────────────────────────
try:
from praisonaiui.integration import AIUIGateway
from praisonaiagents import Agent
GATEWAY_OK = True
except ImportError as e:
print(f"⚠️ Gateway dependencies not found: {e}")
print(" Install with: pip install praisonai praisonaiagents")
print(" Falling back to standalone mode (REST APIs only).\n")
safe_print(f"{icon('⚠️', '[WARN]')} Gateway dependencies not found: {e}")
safe_print(" Install with: pip install praisonai praisonaiagents")
safe_print(" Falling back to standalone mode (REST APIs only).\n")
GATEWAY_OK = False
aiui.set_style("dashboard")
@@ -115,7 +118,7 @@ def _register_agents_in_dashboard():
"model": agent_def["model"],
"icon": agent_def["icon"],
})
print(f" ✓ Agent: {agent_def['icon']} {agent_def['name']}")
safe_print(f" {icon('✓', '[OK]')} Agent: {agent_def['name']}")
# ── Main ────────────────────────────────────────────────────
@@ -125,8 +128,8 @@ async def main():
host = os.getenv("HOST", "127.0.0.1")
if GATEWAY_OK:
print("🚀 Starting with Gateway mode (full execution + streaming)")
print()
safe_print(f"{icon('🚀', '[START]')} Starting with Gateway mode (full execution + streaming)")
safe_print()
gateway = AIUIGateway(host=host, port=port)
@@ -139,32 +142,32 @@ async def main():
reflection=False,
)
gateway.register_agent(agent, agent_id=agent_def["agent_id"])
print(f" ✓ Gateway: {agent_def['icon']} {agent_def['name']} ({agent_def['model']})")
safe_print(f" {icon('✓', '[OK]')} Gateway: {agent_def['name']} ({agent_def['model']})")
# Also register in dashboard CRUD
_register_agents_in_dashboard()
print()
print(f" Dashboard: http://localhost:{port}")
print(f" Explorer: http://localhost:{port} → 🔬 Feature Explorer")
print(f" WebSocket: ws://localhost:{port}/ws")
print()
safe_print()
safe_print(f" Dashboard: http://localhost:{port}")
safe_print(f" Explorer: http://localhost:{port} {icon('→', '->')} {icon('🔬', '')} Feature Explorer")
safe_print(f" WebSocket: ws://localhost:{port}/ws")
safe_print()
await gateway.start()
else:
# Standalone: REST APIs only, no agent execution
print("📦 Starting in standalone mode (REST APIs only, no agent execution)")
safe_print(f"{icon('📦', '[INFO]')} Starting in standalone mode (REST APIs only, no agent execution)")
from praisonaiui.server import create_app
import uvicorn
_register_agents_in_dashboard()
app = create_app()
print()
print(f" Dashboard: http://localhost:{port}")
print(f" Explorer: http://localhost:{port} → 🔬 Feature Explorer")
print(f" Note: POST endpoints may fail without gateway")
print()
safe_print()
safe_print(f" Dashboard: http://localhost:{port}")
safe_print(f" Explorer: http://localhost:{port} {icon('→', '->')} {icon('🔬', '')} Feature Explorer")
safe_print(" Note: POST endpoints may fail without gateway")
safe_print()
config = uvicorn.Config(app, host=host, port=port, log_level="info")
server = uvicorn.Server(config)
+22 -18
View File
@@ -25,9 +25,13 @@ Usage:
import asyncio
import os
import sys
import praisonaiui as aiui
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
# ── Check gateway availability ──────────────────────────────
try:
from praisonaiagents import Agent
@@ -35,9 +39,9 @@ try:
from praisonaiui.integration import AIUIGateway
GATEWAY_OK = True
except ImportError as e:
print(f"⚠️ Gateway dependencies not found: {e}")
print(" Install with: pip install praisonai praisonaiagents")
print(" Falling back to standalone mode (REST APIs only).\n")
safe_print(f"{icon('⚠️', '[WARN]')} Gateway dependencies not found: {e}")
safe_print(" Install with: pip install praisonai praisonaiagents")
safe_print(" Falling back to standalone mode (REST APIs only).\n")
GATEWAY_OK = False
aiui.set_style("dashboard")
@@ -127,7 +131,7 @@ def _register_agents_in_dashboard():
"model": agent_def["model"],
"icon": agent_def["icon"],
})
print(f" ✓ Agent: {agent_def['icon']} {agent_def['name']}")
safe_print(f" {icon('✓', '[OK]')} Agent: {agent_def['name']}")
# ── Main ────────────────────────────────────────────────────
@@ -137,8 +141,8 @@ async def main():
host = os.getenv("HOST", "127.0.0.1")
if GATEWAY_OK:
print("🚀 Starting with Gateway mode (full execution + streaming)")
print()
safe_print(f"{icon('🚀', '[START]')} Starting with Gateway mode (full execution + streaming)")
safe_print()
gateway = AIUIGateway(host=host, port=port)
@@ -151,21 +155,21 @@ async def main():
reflection=False,
)
gateway.register_agent(agent, agent_id=agent_def["agent_id"])
print(f" ✓ Gateway: {agent_def['icon']} {agent_def['name']} ({agent_def['model']})")
safe_print(f" {icon('✓', '[OK]')} Gateway: {agent_def['name']} ({agent_def['model']})")
# Also register in dashboard CRUD
_register_agents_in_dashboard()
print()
print(f" Dashboard: http://localhost:{port}")
print(f" Explorer: http://localhost:{port} → 🔬 Feature Explorer")
print(f" WebSocket: ws://localhost:{port}/ws")
print()
safe_print()
safe_print(f" Dashboard: http://localhost:{port}")
safe_print(f" Explorer: http://localhost:{port} {icon('→', '->')} {icon('🔬', '')} Feature Explorer")
safe_print(f" WebSocket: ws://localhost:{port}/ws")
safe_print()
await gateway.start()
else:
# Standalone: REST APIs only, no agent execution
print("📦 Starting in standalone mode (REST APIs only, no agent execution)")
safe_print(f"{icon('📦', '[INFO]')} Starting in standalone mode (REST APIs only, no agent execution)")
import uvicorn
from praisonaiui.server import create_app
@@ -173,11 +177,11 @@ async def main():
_register_agents_in_dashboard()
app = create_app()
print()
print(f" Dashboard: http://localhost:{port}")
print(f" Explorer: http://localhost:{port} → 🔬 Feature Explorer")
print(" Note: POST endpoints may fail without gateway")
print()
safe_print()
safe_print(f" Dashboard: http://localhost:{port}")
safe_print(f" Explorer: http://localhost:{port} {icon('→', '->')} {icon('🔬', '')} Feature Explorer")
safe_print(" Note: POST endpoints may fail without gateway")
safe_print()
config = uvicorn.Config(app, host=host, port=port, log_level="info")
server = uvicorn.Server(config)
+21 -18
View File
@@ -32,6 +32,9 @@ import asyncio
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
# ── Check prerequisites ─────────────────────────────────────────────
try:
@@ -40,8 +43,8 @@ try:
SDK_OK = True
except ImportError:
SDK_OK = False
print("⚠️ Missing dependencies. Install with:")
print(" pip install praisonai[email] praisonaiagents")
safe_print(f"{icon('⚠️', '[WARN]')} Missing dependencies. Install with:")
safe_print(" pip install praisonai[email] praisonaiagents")
sys.exit(1)
@@ -68,8 +71,8 @@ agent = Agent(
async def run_simple():
"""Start email bot with automatic env var resolution."""
bot = Bot("email", agent=agent)
print(f"📧 Starting email bot for: {os.getenv('EMAIL_ADDRESS', '(not set)')}")
print(" Listening for new emails... (Ctrl+C to stop)")
safe_print(f"{icon('📧', '[EMAIL]')} Starting email bot for: {os.getenv('EMAIL_ADDRESS', '(not set)')}")
safe_print(" Listening for new emails... (Ctrl+C to stop)")
await bot.start()
@@ -96,9 +99,9 @@ async def run_with_handlers():
@bot.on_message
async def log_email(message):
subject = message.metadata.get("subject", "(no subject)")
print(f"📨 From: {message.sender.username}")
print(f" Subject: {subject}")
print(f" Preview: {message.text[:100]}...")
safe_print(f"{icon('📨', '[MSG]')} From: {message.sender.username}")
safe_print(f" Subject: {subject}")
safe_print(f" Preview: {message.text[:100]}...")
# Custom command: emails with subject starting with "/status"
@bot.on_command("status")
@@ -118,11 +121,11 @@ async def run_with_handlers():
reply_to=message.message_id,
)
print(f"📧 Starting email bot with custom handlers")
print(f" Email: {bot._email_address}")
print(f" Poll interval: {config.polling_interval}s")
print(f" Allowed senders: {'all' if not config.allowed_users else config.allowed_users}")
print(" Listening... (Ctrl+C to stop)")
safe_print(f"{icon('📧', '[EMAIL]')} Starting email bot with custom handlers")
safe_print(f" Email: {bot._email_address}")
safe_print(f" Poll interval: {config.polling_interval}s")
safe_print(f" Allowed senders: {'all' if not config.allowed_users else config.allowed_users}")
safe_print(" Listening... (Ctrl+C to stop)")
await bot.start()
@@ -131,17 +134,17 @@ async def run_with_handlers():
async def run_probe():
"""Test email server connectivity."""
bot = Bot("email", agent=agent)
print("🔍 Testing email server connectivity...")
safe_print(f"{icon('🔍', '[PROBE]')} Testing email server connectivity...")
result = await bot.probe()
print(f"\n Connection: {'✅ OK' if result.ok else '❌ FAILED'}")
print(f" Platform: {result.platform}")
safe_print(f"\n Connection: {icon('✅', '[OK]') + ' OK' if result.ok else icon('❌', '[FAIL]') + ' FAILED'}")
safe_print(f" Platform: {result.platform}")
if result.bot_username:
print(f" Email: {result.bot_username}")
safe_print(f" Email: {result.bot_username}")
if result.elapsed_ms:
print(f" Latency: {result.elapsed_ms:.0f}ms")
safe_print(f" Latency: {result.elapsed_ms:.0f}ms")
if result.error:
print(f" Error: {result.error}")
safe_print(f" Error: {result.error}")
# ── Main ────────────────────────────────────────────────────────────
+26 -24
View File
@@ -42,7 +42,9 @@ import sys
# ── Imports ─────────────────────────────────────────────────────────
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "src"))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
from praisonaiui.server import create_app
import uvicorn
@@ -91,11 +93,11 @@ def seed_email_channel():
})
if r.status_code in (200, 201):
channel_id = r.json().get("id", "unknown")
print(f" ✅ Email channel registered: {channel_id}")
safe_print(f" {icon('✅', '[OK]')} Email channel registered: {channel_id}")
else:
print(f" ⚠️ Channel registration: {r.status_code} — {r.json()}")
safe_print(f" {icon('⚠️', '[WARN]')} Channel registration: {r.status_code} - {r.json()}")
else:
print(" ✓ Email channel already exists")
safe_print(f" {icon('✓', '[OK]')} Email channel already exists")
# Also seed other channels for comparison in dashboard
for name, platform in [("Discord #general", "discord"), ("Telegram Support", "telegram")]:
@@ -106,7 +108,7 @@ def seed_email_channel():
"platform": platform,
"config": {},
})
print(f" ✓ Seeded {platform} channel")
safe_print(f" {icon('✓', '[OK]')} Seeded {platform} channel")
# ── Verify email platform support ──────────────────────────────────
@@ -122,8 +124,8 @@ def verify_email_support():
if r.status_code == 200:
platforms = r.json().get("platforms", [])
email_supported = "email" in platforms
print(f" Supported platforms: {platforms}")
print(f" Email supported: {'✅ Yes' if email_supported else '❌ No — add email to SUPPORTED_PLATFORMS'}")
safe_print(f" Supported platforms: {platforms}")
safe_print(f" Email supported: {icon('✅', '[OK]') + ' Yes' if email_supported else icon('❌', '[X]') + ' No - add email to SUPPORTED_PLATFORMS'}")
return email_supported
return False
@@ -137,51 +139,51 @@ def test_channel_api():
app = create_app()
client = TestClient(app)
print("\n📋 Channel API Test")
print("=" * 40)
safe_print(f"\n{icon('📋', '[TEST]')} Channel API Test")
safe_print("=" * 40)
# List channels
r = client.get("/api/channels")
channels = r.json().get("channels", [])
print(f" GET /api/channels: {len(channels)} channels")
safe_print(f" GET /api/channels: {len(channels)} channels")
for ch in channels:
icon = "📧" if ch.get("platform") == "email" else "💬"
print(f" {icon} {ch.get('name', '?')} ({ch.get('platform', '?')})")
ch_icon = icon("📧", "[email]") if ch.get("platform") == "email" else icon("💬", "[chat]")
safe_print(f" {ch_icon} {ch.get('name', '?')} ({ch.get('platform', '?')})")
# Get email channel status
email_ch = next((ch for ch in channels if ch.get("platform") == "email"), None)
if email_ch:
ch_id = email_ch.get("id")
r = client.get(f"/api/channels/{ch_id}/status")
print(f" GET /api/channels/{ch_id}/status: {r.json()}")
safe_print(f" GET /api/channels/{ch_id}/status: {r.json()}")
print()
safe_print()
# ── Main ────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("📧 PraisonAIUI — Email Channel Integration")
print("=" * 50)
print()
safe_print(f"{icon('📧', '[EMAIL]')} PraisonAIUI - Email Channel Integration")
safe_print("=" * 50)
safe_print()
if "--test" in sys.argv:
# Test mode: verify APIs + seed data
print("🔍 Verifying email platform support...")
safe_print(f"{icon('🔍', '[VERIFY]')} Verifying email platform support...")
verify_email_support()
print()
print("🌱 Seeding email channel...")
safe_print()
safe_print(f"{icon('🌱', '[SEED]')} Seeding email channel...")
seed_email_channel()
test_channel_api()
else:
# Run the dashboard
print("🌱 Seeding channels...")
safe_print(f"{icon('🌱', '[SEED]')} Seeding channels...")
seed_email_channel()
print()
safe_print()
app = create_app()
print(f"✅ Dashboard at http://localhost:8084")
print(f" Channels: http://localhost:8084/api/channels")
print(f" Platforms: http://localhost:8084/api/channels/platforms")
safe_print(f"{icon('✅', '[OK]')} Dashboard at http://localhost:8084")
safe_print(" Channels: http://localhost:8084/api/channels")
safe_print(" Platforms: http://localhost:8084/api/channels/platforms")
host = os.getenv("HOST", "127.0.0.1")
uvicorn.run(app, host=host, port=8084, log_level="info")
+32 -29
View File
@@ -33,6 +33,9 @@ import asyncio
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
# ── Check prerequisites ─────────────────────────────────────────────
try:
@@ -41,8 +44,8 @@ try:
SDK_OK = True
except ImportError:
SDK_OK = False
print("⚠️ Missing dependencies. Install with:")
print(" pip install praisonai agentmail praisonaiagents")
safe_print(f"{icon('⚠️', '[WARN]')} Missing dependencies. Install with:")
safe_print(" pip install praisonai agentmail praisonaiagents")
sys.exit(1)
@@ -69,9 +72,9 @@ agent = Agent(
async def run_simple():
"""Start agentmail bot with automatic env var resolution."""
bot = Bot("agentmail", agent=agent)
print(f"📩 Starting AgentMail bot")
print(f" API key: {'✅ set' if os.getenv('AGENTMAIL_API_KEY') else '❌ not set'}")
print(" Listening for new emails... (Ctrl+C to stop)")
safe_print(f"{icon('📩', '[MAIL]')} Starting AgentMail bot")
safe_print(f" API key: {icon('✅', '[OK]') + ' set' if os.getenv('AGENTMAIL_API_KEY') else icon('❌', '[X]') + ' not set'}")
safe_print(" Listening for new emails... (Ctrl+C to stop)")
await bot.start()
@@ -99,9 +102,9 @@ async def run_with_handlers():
@bot.on_message
async def log_email(message):
subject = message.metadata.get("subject", "(no subject)")
print(f"📨 From: {message.sender.username}")
print(f" Subject: {subject}")
print(f" Preview: {message.text[:100]}...")
safe_print(f"{icon('📨', '[MSG]')} From: {message.sender.username}")
safe_print(f" Subject: {subject}")
safe_print(f" Preview: {message.text[:100]}...")
# Custom command: emails with subject starting with "/status"
@bot.on_command("status")
@@ -121,10 +124,10 @@ async def run_with_handlers():
reply_to=message.message_id,
)
print(f"📩 Starting AgentMail bot with custom handlers")
print(f" Email: {bot.email_address or '(will be assigned on start)'}")
print(f" Poll interval: {config.polling_interval}s")
print(" Listening... (Ctrl+C to stop)")
safe_print(f"{icon('📩', '[MAIL]')} Starting AgentMail bot with custom handlers")
safe_print(f" Email: {bot.email_address or '(will be assigned on start)'}")
safe_print(f" Poll interval: {config.polling_interval}s")
safe_print(" Listening... (Ctrl+C to stop)")
await bot.start()
@@ -136,30 +139,30 @@ async def run_inbox_demo():
token=os.environ.get("AGENTMAIL_API_KEY", ""),
)
print("📩 AgentMail Inbox Lifecycle Demo")
print("=" * 50)
safe_print(f"{icon('📩', '[MAIL]')} AgentMail Inbox Lifecycle Demo")
safe_print("=" * 50)
# Create inbox
print("\n1. Creating inbox...")
safe_print("\n1. Creating inbox...")
inbox = await bot.create_inbox(domain=os.getenv("AGENTMAIL_DOMAIN"))
print(f" ✅ Created: {inbox.get('email_address', 'unknown')}")
print(f" ID: {inbox.get('id', 'unknown')}")
safe_print(f" {icon('✅', '[OK]')} Created: {inbox.get('email_address', 'unknown')}")
safe_print(f" ID: {inbox.get('id', 'unknown')}")
# List inboxes
print("\n2. Listing inboxes...")
safe_print("\n2. Listing inboxes...")
inboxes = await bot.list_inboxes()
print(f" Found: {len(inboxes)} inbox(es)")
safe_print(f" Found: {len(inboxes)} inbox(es)")
for ib in inboxes:
print(f" 📧 {ib.get('email_address', '?')} (id: {ib.get('id', '?')})")
safe_print(f" {icon('📧', '[EMAIL]')} {ib.get('email_address', '?')} (id: {ib.get('id', '?')})")
# Delete inbox
inbox_id = inbox.get("id", "")
if inbox_id:
print(f"\n3. Deleting inbox {inbox_id}...")
safe_print(f"\n3. Deleting inbox {inbox_id}...")
deleted = await bot.delete_inbox(inbox_id)
print(f" {'✅ Deleted' if deleted else '❌ Failed to delete'}")
safe_print(f" {icon('✅', '[OK]') + ' Deleted' if deleted else icon('❌', '[X]') + ' Failed to delete'}")
print("\n✅ Inbox lifecycle complete")
safe_print(f"\n{icon('✅', '[OK]')} Inbox lifecycle complete")
# ── Probe: Test connectivity ────────────────────────────────────────
@@ -167,17 +170,17 @@ async def run_inbox_demo():
async def run_probe():
"""Test AgentMail API connectivity."""
bot = Bot("agentmail", agent=agent)
print("🔍 Testing AgentMail API connectivity...")
safe_print(f"{icon('🔍', '[PROBE]')} Testing AgentMail API connectivity...")
result = await bot.probe()
print(f"\n Connection: {'✅ OK' if result.ok else '❌ FAILED'}")
print(f" Platform: {result.platform}")
safe_print(f"\n Connection: {icon('✅', '[OK]') + ' OK' if result.ok else icon('❌', '[FAIL]') + ' FAILED'}")
safe_print(f" Platform: {result.platform}")
if result.bot_username:
print(f" Email: {result.bot_username}")
safe_print(f" Email: {result.bot_username}")
if result.elapsed_ms:
print(f" Latency: {result.elapsed_ms:.0f}ms")
safe_print(f" Latency: {result.elapsed_ms:.0f}ms")
if result.error:
print(f" Error: {result.error}")
safe_print(f" Error: {result.error}")
# ── Main ────────────────────────────────────────────────────────────
+27 -25
View File
@@ -40,7 +40,9 @@ import sys
# ── Imports ─────────────────────────────────────────────────────────
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "src"))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.console import icon, safe_print
from praisonaiui.server import create_app
import uvicorn
@@ -88,11 +90,11 @@ def seed_agentmail_channel():
})
if r.status_code in (200, 201):
channel_id = r.json().get("id", "unknown")
print(f" ✅ AgentMail channel registered: {channel_id}")
safe_print(f" {icon('✅', '[OK]')} AgentMail channel registered: {channel_id}")
else:
print(f" ⚠️ Channel registration: {r.status_code} — {r.json()}")
safe_print(f" {icon('⚠️', '[WARN]')} Channel registration: {r.status_code} - {r.json()}")
else:
print(" ✓ AgentMail channel already exists")
safe_print(f" {icon('✓', '[OK]')} AgentMail channel already exists")
# Also seed email + telegram for comparison in dashboard
for name, platform in [("Support Email", "email"), ("Telegram Support", "telegram")]:
@@ -103,7 +105,7 @@ def seed_agentmail_channel():
"platform": platform,
"config": {},
})
print(f" ✓ Seeded {platform} channel")
safe_print(f" {icon('✓', '[OK]')} Seeded {platform} channel")
# ── Verify agentmail platform support ──────────────────────────────
@@ -120,9 +122,9 @@ def verify_agentmail_support():
platforms = r.json().get("platforms", [])
am_supported = "agentmail" in platforms
email_supported = "email" in platforms
print(f" Supported platforms: {platforms}")
print(f" email supported: {'✅ Yes' if email_supported else '❌ No'}")
print(f" agentmail supported: {'✅ Yes' if am_supported else '❌ No'}")
safe_print(f" Supported platforms: {platforms}")
safe_print(f" email supported: {icon('✅', '[OK]') + ' Yes' if email_supported else icon('❌', '[X]') + ' No'}")
safe_print(f" agentmail supported: {icon('✅', '[OK]') + ' Yes' if am_supported else icon('❌', '[X]') + ' No'}")
return am_supported
return False
@@ -136,51 +138,51 @@ def test_channel_api():
app = create_app()
client = TestClient(app)
print("\n📋 Channel API Test")
print("=" * 40)
safe_print(f"\n{icon('📋', '[TEST]')} Channel API Test")
safe_print("=" * 40)
# List channels
r = client.get("/api/channels")
channels = r.json().get("channels", [])
print(f" GET /api/channels: {len(channels)} channels")
safe_print(f" GET /api/channels: {len(channels)} channels")
for ch in channels:
icon = {"email": "📧", "agentmail": "📩"}.get(ch.get("platform", ""), "💬")
print(f" {icon} {ch.get('name', '?')} ({ch.get('platform', '?')})")
ch_icon = {"email": icon("📧", "[email]"), "agentmail": icon("📩", "[mail]")}.get(ch.get("platform", ""), icon("💬", "[chat]"))
safe_print(f" {ch_icon} {ch.get('name', '?')} ({ch.get('platform', '?')})")
# Get agentmail channel status
am_ch = next((ch for ch in channels if ch.get("platform") == "agentmail"), None)
if am_ch:
ch_id = am_ch.get("id")
r = client.get(f"/api/channels/{ch_id}/status")
print(f" GET /api/channels/{ch_id}/status: {r.json()}")
safe_print(f" GET /api/channels/{ch_id}/status: {r.json()}")
print()
safe_print()
# ── Main ────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("📩 PraisonAIUI — AgentMail Channel Integration")
print("=" * 50)
print()
safe_print(f"{icon('📩', '[MAIL]')} PraisonAIUI - AgentMail Channel Integration")
safe_print("=" * 50)
safe_print()
if "--test" in sys.argv:
# Test mode: verify APIs + seed data
print("🔍 Verifying agentmail platform support...")
safe_print(f"{icon('🔍', '[VERIFY]')} Verifying agentmail platform support...")
verify_agentmail_support()
print()
print("🌱 Seeding agentmail channel...")
safe_print()
safe_print(f"{icon('🌱', '[SEED]')} Seeding agentmail channel...")
seed_agentmail_channel()
test_channel_api()
else:
# Run the dashboard
print("🌱 Seeding channels...")
safe_print(f"{icon('🌱', '[SEED]')} Seeding channels...")
seed_agentmail_channel()
print()
safe_print()
app = create_app()
print(f"✅ Dashboard at http://localhost:8084")
print(f" Channels: http://localhost:8084/api/channels")
print(f" Platforms: http://localhost:8084/api/channels/platforms")
safe_print(f"{icon('✅', '[OK]')} Dashboard at http://localhost:8084")
safe_print(" Channels: http://localhost:8084/api/channels")
safe_print(" Platforms: http://localhost:8084/api/channels/platforms")
host = os.getenv("HOST", "127.0.0.1")
uvicorn.run(app, host=host, port=8084, log_level="info")
+40
View File
@@ -0,0 +1,40 @@
"""Cross-platform console output for examples.
Legacy Windows consoles default stdout to a non-UTF code page (cp1252/cp437)
that cannot encode emoji such as U+1F680. Calling ``print`` with those
characters raises ``UnicodeEncodeError`` and crashes the example before the
server starts. These helpers mirror the ``_supports_unicode``/``_icon`` pattern
in ``praisonaiui.cli`` so examples degrade to ASCII instead of crashing while
preserving emoji on UTF-aware terminals.
Usage:
from _shared.console import safe_print, icon
safe_print(f"{icon('🚀', '[START]')} Starting gateway...")
"""
from __future__ import annotations
import sys
from typing import Any
def supports_unicode() -> bool:
try:
encoding = sys.stdout.encoding or ""
except Exception:
return False
return "utf" in encoding.lower()
def icon(symbol: str, fallback: str) -> str:
return symbol if supports_unicode() else fallback
def safe_print(*args: Any, **kwargs: Any) -> None:
sep = kwargs.pop("sep", " ")
text = sep.join(str(a) for a in args)
try:
print(text, **kwargs)
except UnicodeEncodeError:
print(text.encode("ascii", errors="replace").decode("ascii"), **kwargs)
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "aiui"
version = "0.3.133"
version = "0.3.142"
description = "YAML-driven website generator - CLI and compiler"
readme = "README.md"
license = {text = "MIT"}
+89 -58
View File
@@ -3,13 +3,14 @@ import { applyTheme } from './themes'
import './index.css'
import type { UIConfig, DocsNav, RouteManifest, NavItem } from './types'
import { Header } from './Header'
import { Sidebar } from './Sidebar'
import { Sidebar, MobileNavSheet } from './Sidebar'
import { Content } from './Content'
import { ZoneWidgets } from './Widgets'
import { Toc } from './Toc'
import { Footer } from './Footer'
import { ChatLayout, AgentUILayout, CopilotWidget, PlaygroundLayout } from './layouts'
import { LocaleProvider } from './i18n'
import { resolveTemplate, shouldShowToc } from './resolver'
function SkipLink({ enabled }: { enabled?: boolean }) {
if (!enabled) return null
@@ -31,12 +32,17 @@ export default function App() {
const [error, setError] = useState<string | null>(null)
const [selectedItem, setSelectedItem] = useState<NavItem | null>(null)
const [activeItemPath, setActiveItemPath] = useState<string>('')
const [currentPath, setCurrentPath] = useState(() => window.location.pathname.replace(/\/$/, '') || '/')
const [mobileNavOpen, setMobileNavOpen] = useState(false)
// Helper function to find nav item by path
// Helper function to find nav item by path (searches top-level items and nested children)
const findNavItemByPath = (navData: DocsNav, path: string): NavItem | null => {
const normalizedPath = path.replace(/\/$/, '') || '/'
const findItem = (items: NavItem[]): NavItem | null => {
for (const item of items) {
if (item.path === path || item.path === path.replace(/^\//, '')) {
const itemPath = (item.path ?? '').replace(/\/$/, '')
if (itemPath === normalizedPath || itemPath === normalizedPath.replace(/^\//, '')) {
return item
}
if (item.children) {
@@ -47,23 +53,14 @@ export default function App() {
return null
}
// Search all nav groups
if (navData.items) {
for (const group of navData.items) {
if (group.children) {
const found = findItem(group.children)
if (found) return found
}
}
}
return null
return navData.items ? findItem(navData.items) : null
}
// Update SEO meta tags dynamically
const updateSEO = (title: string, path: string, description?: string) => {
const updateSEO = (title: string, path: string, description?: string, configOverride?: UIConfig) => {
const cfg = configOverride ?? config
// Update title using SEO titleTemplate if available
const titleTemplate = config.seo?.titleTemplate || '%s | %s'
const siteName = config.site?.title || 'Documentation'
const titleTemplate = cfg.seo?.titleTemplate || '%s | %s'
const siteName = cfg.site?.title || 'Documentation'
if (titleTemplate.includes('%s')) {
// Replace first %s with page title, second %s (if exists) with site name
let formattedTitle = titleTemplate.replace('%s', title)
@@ -91,7 +88,7 @@ export default function App() {
metaDesc.name = 'description'
document.head.appendChild(metaDesc)
}
metaDesc.content = description || `${title} - ${config.site?.description || ''}`
metaDesc.content = description || `${title} - ${cfg.site?.description || ''}`
// Update Open Graph tags
let ogTitle = document.querySelector('meta[property="og:title"]') as HTMLMetaElement
@@ -111,8 +108,8 @@ export default function App() {
ogUrl.content = window.location.origin + path
// Update Twitter meta tags if configured
if (config.seo?.twitter) {
Object.entries(config.seo.twitter).forEach(([key, value]) => {
if (cfg.seo?.twitter) {
Object.entries(cfg.seo.twitter).forEach(([key, value]) => {
let twitterMeta = document.querySelector(`meta[name="twitter:${key}"]`) as HTMLMetaElement
if (!twitterMeta) {
twitterMeta = document.createElement('meta')
@@ -124,17 +121,23 @@ export default function App() {
}
// Set default image if available
if (config.seo?.defaultImage) {
if (cfg.seo?.defaultImage) {
let ogImage = document.querySelector('meta[property="og:image"]') as HTMLMetaElement
if (!ogImage) {
ogImage = document.createElement('meta')
ogImage.setAttribute('property', 'og:image')
document.head.appendChild(ogImage)
}
ogImage.content = config.seo.defaultImage
ogImage.content = cfg.seo.defaultImage
}
}
const selectNavItem = (item: NavItem) => {
setSelectedItem(item)
setActiveItemPath(item.path || item.title)
updateSEO(item.title, item.path || '/')
}
useEffect(() => {
async function loadManifests() {
try {
@@ -163,12 +166,21 @@ export default function App() {
)
// Handle initial URL path for SPA routing
const currentPath = window.location.pathname
if (currentPath && currentPath !== '/') {
const currentPath = window.location.pathname.replace(/\/$/, '') || '/'
setCurrentPath(currentPath)
if (currentPath !== '/') {
const found = findNavItemByPath(navData, currentPath)
if (found) {
setSelectedItem(found)
setActiveItemPath(found.path || found.title)
updateSEO(found.title, found.path || currentPath, undefined, configData)
}
} else {
const docsHome = findNavItemByPath(navData, '/docs/index')
if (docsHome) {
setSelectedItem(docsHome)
setActiveItemPath(docsHome.path || docsHome.title)
updateSEO(docsHome.title, docsHome.path || '/docs/index', undefined, configData)
}
}
} catch (err) {
@@ -184,19 +196,21 @@ export default function App() {
// Handle browser back/forward navigation in a separate effect
useEffect(() => {
const handlePopState = () => {
const path = window.location.pathname
const path = window.location.pathname.replace(/\/$/, '') || '/'
setCurrentPath(path)
const found = findNavItemByPath(nav, path)
if (found) {
setSelectedItem(found)
setActiveItemPath(found.path || found.title)
// Use updateSEO function for consistency
updateSEO(found.title, path)
selectNavItem(found)
} else if (path === '/') {
setSelectedItem(null)
setActiveItemPath('')
// Reset title to site title
document.title = config.site?.title || 'Documentation'
const docsHome = findNavItemByPath(nav, '/docs/index')
if (docsHome) {
selectNavItem(docsHome)
} else {
setSelectedItem(null)
setActiveItemPath('')
document.title = config.site?.title || 'Documentation'
}
}
}
@@ -207,13 +221,42 @@ export default function App() {
const handleItemClick = (item: NavItem) => {
setSelectedItem(item)
setActiveItemPath(item.path || item.title)
// Use History API for SEO-friendly URLs
const path = item.path || `/${item.title.toLowerCase().replace(/\s+/g, '-')}`
setCurrentPath(path.replace(/\/$/, '') || '/')
window.history.pushState({ path }, item.title, path)
// Update SEO meta tags
updateSEO(item.title, path)
}
const templateMatch = resolveTemplate(
currentPath.replace(/^\//, ''),
routes,
config.templates ?? {},
)
const activeTemplateKey = templateMatch?.template ?? 'docs'
const activeTemplate = config.templates?.[activeTemplateKey] ?? config.templates?.docs
const layout = templateMatch?.layout ?? activeTemplate?.layout ?? 'ThreeColumnLayout'
const zones = activeTemplate?.zones
const showToc = shouldShowToc(templateMatch)
const showGlobalFooter = !(layout === 'FlexibleLayout' && (zones?.footer?.length ?? 0) > 0)
const docsChrome = (
<>
<MobileNavSheet
open={mobileNavOpen}
onOpenChange={setMobileNavOpen}
nav={nav}
activeItem={activeItemPath}
onItemClick={handleItemClick}
title={config.site?.title || 'Navigation'}
/>
<Header
config={config}
templateKey={activeTemplateKey}
onMenuClick={() => setMobileNavOpen(true)}
/>
</>
)
if (loading) {
return (
<div className="dark flex items-center justify-center min-h-screen bg-background">
@@ -231,15 +274,10 @@ export default function App() {
)
}
// Determine layout from templates config
const layout = config.templates?.docs?.layout || 'ThreeColumnLayout'
const zones = config.templates?.docs?.zones
// Render based on layout type
const renderLayout = () => {
switch (layout) {
case 'TwoColumnLayout':
// Sidebar + Content (no TOC)
return (
<div className="flex">
<Sidebar nav={nav} activeItem={activeItemPath} onItemClick={handleItemClick} />
@@ -247,7 +285,6 @@ export default function App() {
</div>
)
case 'CenteredLayout':
// Centered content, no sidebar
return (
<div className="flex justify-center">
<div className="w-full max-w-4xl px-6">
@@ -257,7 +294,6 @@ export default function App() {
</div>
)
case 'FullWidthLayout':
// Full width content
return (
<div className="px-6">
{zones?.hero && zones.hero.length > 0 && <ZoneWidgets widgets={zones.hero} />}
@@ -265,12 +301,10 @@ export default function App() {
</div>
)
case 'FlexibleLayout': {
// WordPress-style zones layout with navigation invariant
// When docs nav exists but no leftSidebar zone configured, render default nav
const hasExplicitLeftSidebar = (zones?.leftSidebar?.length ?? 0) > 0
const hasNavigation = (nav?.items?.length ?? 0) > 0
const shouldRenderNavigation = hasExplicitLeftSidebar || hasNavigation
return (
<div className="flex flex-col">
{zones?.hero && zones.hero.length > 0 && <ZoneWidgets widgets={zones.hero} />}
@@ -287,7 +321,7 @@ export default function App() {
<div className="flex-1">
<Content config={config} routes={routes} selectedItem={selectedItem} />
</div>
<Toc selectedItem={selectedItem} zones={zones} />
<Toc selectedItem={selectedItem} zones={zones} showToc={showToc} />
</div>
{zones?.bottomNav && zones.bottomNav.length > 0 && (
<div className="border-t p-4 bg-muted/30">
@@ -304,12 +338,11 @@ export default function App() {
}
case 'ThreeColumnLayout':
default:
// Classic: Sidebar + Content + TOC with zones
return (
<div className="flex">
<Sidebar nav={nav} activeItem={activeItemPath} onItemClick={handleItemClick} />
<Content config={config} routes={routes} selectedItem={selectedItem} />
<Toc selectedItem={selectedItem} zones={zones} />
{showToc && <Toc selectedItem={selectedItem} zones={zones} showToc={showToc} />}
</div>
)
}
@@ -347,14 +380,13 @@ export default function App() {
// Dashboard is handled entirely by plugin dashboard.js — React yields
return null
case 'custom':
// For custom, show docs with copilot widget if chat enabled
if (config.chat?.enabled) {
return (
<div className="min-h-screen bg-background text-foreground">
<SkipLink enabled={config.a11y?.skipToContent} />
<Header config={config} />
{docsChrome}
{renderLayout()}
<Footer config={config} />
{showGlobalFooter && <Footer config={config} templateKey={activeTemplateKey} />}
<CopilotWidget config={config.chat} layout={config.layout} />
</div>
)
@@ -362,23 +394,22 @@ export default function App() {
return (
<div className="min-h-screen bg-background text-foreground">
<SkipLink enabled={config.a11y?.skipToContent} />
<Header config={config} />
{docsChrome}
{renderLayout()}
<Footer config={config} />
{showGlobalFooter && <Footer config={config} templateKey={activeTemplateKey} />}
</div>
)
case 'docs':
default:
// Docs mode - optionally with copilot widget
if (config.chat?.enabled) {
const layoutMode = config.layout?.mode
if (layoutMode && ['bottom-right', 'bottom-left', 'top-right', 'top-left'].includes(layoutMode)) {
return (
<div className="min-h-screen bg-background text-foreground">
<SkipLink enabled={config.a11y?.skipToContent} />
<Header config={config} />
{docsChrome}
{renderLayout()}
<Footer config={config} />
{showGlobalFooter && <Footer config={config} templateKey={activeTemplateKey} />}
<CopilotWidget config={config.chat} layout={config.layout} />
</div>
)
@@ -387,9 +418,9 @@ export default function App() {
return (
<div className="min-h-screen bg-background text-foreground">
<SkipLink enabled={config.a11y?.skipToContent} />
<Header config={config} />
{docsChrome}
{renderLayout()}
<Footer config={config} />
{showGlobalFooter && <Footer config={config} templateKey={activeTemplateKey} />}
</div>
)
}
+28 -15
View File
@@ -1,9 +1,13 @@
// Content component — markdown rendering and landing page
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Separator } from '@/components/ui/separator'
import { SHADCN_THEMES } from './themes'
import { enhanceMkdocsDom } from './markdown/mkdocsEnhance'
import { preprocessMkdocsMarkdown } from './markdown/mkdocsPreprocess'
import { slugify } from './markdown/slug'
import { MobileToc } from './Toc'
import type { UIConfig, NavItem, RouteManifest } from './types'
interface ContentProps {
@@ -15,6 +19,7 @@ interface ContentProps {
export function Content({ config, routes, selectedItem }: ContentProps) {
const [markdown, setMarkdown] = useState<string>('')
const [loadingContent, setLoadingContent] = useState(false)
const articleRef = useRef<HTMLElement>(null)
const theme = config.site?.theme
// Load markdown content when selected item changes
@@ -27,12 +32,11 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
const loadContent = async () => {
setLoadingContent(true)
try {
// Convert path like /docs/getting-started/installation to docs/getting-started/installation.md
const docPath = (selectedItem.path ?? '').replace(/^\//, '') + '.md'
const response = await fetch(`/${docPath}`)
if (response.ok) {
const content = await response.text()
setMarkdown(content)
setMarkdown(preprocessMkdocsMarkdown(content))
} else {
setMarkdown(`*Content for **${selectedItem.title}** not found.*`)
}
@@ -46,12 +50,19 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
loadContent()
}, [selectedItem])
// Custom components for react-markdown with Tailwind styling
useEffect(() => {
if (!markdown || !articleRef.current) return
enhanceMkdocsDom(articleRef.current)
window.dispatchEvent(new CustomEvent('aiui:content-loaded', { detail: { root: articleRef.current } }))
}, [markdown])
const headingId = (children?: React.ReactNode) => slugify(String(children ?? ''))
const markdownComponents = {
h1: ({ children }: { children?: React.ReactNode }) => <h1 className="text-3xl font-bold mt-8 mb-4">{children}</h1>,
h2: ({ children }: { children?: React.ReactNode }) => <h2 className="text-2xl font-semibold mt-8 mb-4 text-primary">{children}</h2>,
h3: ({ children }: { children?: React.ReactNode }) => <h3 className="text-xl font-semibold mt-6 mb-3">{children}</h3>,
h4: ({ children }: { children?: React.ReactNode }) => <h4 className="text-lg font-semibold mt-4 mb-2">{children}</h4>,
h1: ({ children }: { children?: React.ReactNode }) => <h1 id={headingId(children)} className="text-3xl font-bold mt-8 mb-4 scroll-mt-20">{children}</h1>,
h2: ({ children }: { children?: React.ReactNode }) => <h2 id={headingId(children)} className="text-2xl font-semibold mt-8 mb-4 text-primary scroll-mt-20">{children}</h2>,
h3: ({ children }: { children?: React.ReactNode }) => <h3 id={headingId(children)} className="text-xl font-semibold mt-6 mb-3 scroll-mt-20">{children}</h3>,
h4: ({ children }: { children?: React.ReactNode }) => <h4 id={headingId(children)} className="text-lg font-semibold mt-4 mb-2 scroll-mt-20">{children}</h4>,
p: ({ children }: { children?: React.ReactNode }) => <p className="my-3 text-muted-foreground leading-relaxed">{children}</p>,
a: ({ href, children }: { href?: string; children?: React.ReactNode }) => <a href={href} className="text-primary hover:underline">{children}</a>,
ul: ({ children }: { children?: React.ReactNode }) => <ul className="list-disc pl-6 my-4 space-y-1">{children}</ul>,
@@ -59,16 +70,14 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
li: ({ children }: { children?: React.ReactNode }) => <li className="text-muted-foreground">{children}</li>,
blockquote: ({ children }: { children?: React.ReactNode }) => <blockquote className="border-l-4 border-primary pl-4 my-4 italic text-muted-foreground">{children}</blockquote>,
code: ({ className, children }: { className?: string; children?: React.ReactNode }) => {
// Check if this is inside a pre block (fenced code block)
const isInline = !className && String(children).indexOf('\n') === -1
if (isInline) {
return <code className="bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono">{children}</code>
}
// Block code - rendered by pre wrapper
return <code className="block font-mono">{children}</code>
return <code className={`block font-mono text-foreground ${className ?? ''}`}>{children}</code>
},
pre: ({ children }: { children?: React.ReactNode }) => (
<pre className="bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre">{children}</pre>
<pre className="bg-muted text-foreground border border-border p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre">{children}</pre>
),
table: ({ children }: { children?: React.ReactNode }) => <div className="overflow-auto my-4"><table className="w-full border-collapse text-sm">{children}</table></div>,
thead: ({ children }: { children?: React.ReactNode }) => <thead className="bg-muted/50">{children}</thead>,
@@ -83,10 +92,14 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
if (selectedItem) {
return (
<main id="main-content" className="flex-1 p-8 max-w-3xl">
<MobileToc selectedItem={selectedItem} />
{loadingContent ? (
<div className="text-muted-foreground">Loading content...</div>
) : markdown ? (
<article className="prose max-w-none">
<article
ref={articleRef}
className="prose prose-neutral dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:text-foreground"
>
<Markdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{markdown}
</Markdown>
@@ -118,7 +131,7 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
<p className="text-sm text-muted-foreground mb-2">
<strong>Current theme from YAML:</strong>
</p>
<pre className="text-primary text-sm">
<pre className="bg-muted text-foreground border border-border p-3 rounded-lg text-sm overflow-auto">
{`site:
theme:
preset: "${theme?.preset || 'zinc'}"
@@ -150,7 +163,7 @@ export function Content({ config, routes, selectedItem }: ContentProps) {
<p className="text-muted-foreground mb-4">
<span className="font-medium text-primary">{routes.routes?.length || 0}</span> routes configured.
</p>
<pre className="bg-muted p-4 rounded-lg text-sm overflow-auto">
<pre className="bg-muted text-foreground border border-border p-4 rounded-lg text-sm overflow-auto">
{JSON.stringify(routes.routes?.slice(0, 3), null, 2)}
</pre>
</main>
+2 -3
View File
@@ -1,9 +1,8 @@
// Footer component
import type { UIConfig } from './types'
export function Footer({ config }: { config: UIConfig }) {
// Resolve footer component via template slot ref (e.g., footer_main)
const footerSlot = config.templates?.docs?.slots?.footer
export function Footer({ config, templateKey = 'docs' }: { config: UIConfig; templateKey?: string }) {
const footerSlot = config.templates?.[templateKey]?.slots?.footer
const footerRef = footerSlot?.ref
const footerComponent = footerRef
? config.components?.[footerRef]
+32 -11
View File
@@ -1,10 +1,19 @@
// Header component
import { Menu } from 'lucide-react'
import type { UIConfig } from './types'
import { LocaleSwitcher } from './i18n'
import { Button } from '@/components/ui/button'
export function Header({ config }: { config: UIConfig }) {
// Resolve header component via template slot ref (e.g., header_main)
const headerSlot = config.templates?.docs?.slots?.header
export function Header({
config,
templateKey = 'docs',
onMenuClick,
}: {
config: UIConfig
templateKey?: string
onMenuClick?: () => void
}) {
const headerSlot = config.templates?.[templateKey]?.slots?.header
const headerRef = headerSlot?.ref
const headerComponent = headerRef
? config.components?.[headerRef]
@@ -16,7 +25,6 @@ export function Header({ config }: { config: UIConfig }) {
cta?: { label: string; href: string }
} | undefined
// Get logo configuration - support both header.logoImage and config.logo
const logoConfig = config.logo
const isDarkMode = document.documentElement.classList.contains('dark')
const logoSrc = header?.logoImage || (logoConfig ? (isDarkMode ? (logoConfig.dark || logoConfig.light) : (logoConfig.light || logoConfig.dark)) : null)
@@ -24,16 +32,29 @@ export function Header({ config }: { config: UIConfig }) {
return (
<header className="sticky top-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-xl supports-[backdrop-filter]:bg-background/60">
<div className="max-w-screen-2xl mx-auto flex h-16 items-center px-6 gap-8">
<a href={logoHref} className="flex items-center gap-3 group cursor-pointer" onClick={(e) => {
<div className="max-w-screen-2xl mx-auto flex h-16 items-center px-6 gap-4">
{onMenuClick && (
<Button
type="button"
variant="ghost"
size="icon"
className="md:hidden shrink-0"
onClick={onMenuClick}
aria-label="Open navigation menu"
>
<Menu className="h-5 w-5" />
</Button>
)}
<a href={logoHref} className="flex items-center gap-3 group cursor-pointer min-w-0" onClick={(e) => {
e.preventDefault()
window.history.pushState({}, '', logoHref)
window.location.reload()
}}>
{logoSrc ? (
<img
src={logoSrc}
alt="Logo"
<img
src={logoSrc}
alt="Logo"
className="w-8 h-8 rounded-lg shadow-lg shadow-primary/20 group-hover:shadow-primary/40 transition-shadow object-contain"
/>
) : (
@@ -41,7 +62,7 @@ export function Header({ config }: { config: UIConfig }) {
<span className="text-primary-foreground text-xs font-bold">AI</span>
</div>
)}
<span className="font-semibold text-lg tracking-tight">{header?.logoText || config.site?.title || 'PraisonAIUI'}</span>
<span className="font-semibold text-lg tracking-tight truncate">{header?.logoText || config.site?.title || 'PraisonAIUI'}</span>
</a>
<nav className="hidden md:flex items-center gap-1 flex-1">
@@ -56,7 +77,7 @@ export function Header({ config }: { config: UIConfig }) {
))}
</nav>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 ml-auto">
<LocaleSwitcher />
{header?.cta && (
<a
+54 -9
View File
@@ -1,15 +1,21 @@
// Sidebar component — navigation tree
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import type { NavItem } from './types'
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import type { DocsNav, NavItem } from './types'
interface SidebarProps {
nav: { items?: NavItem[] }
interface NavTreeProps {
nav: DocsNav
activeItem: string
onItemClick: (item: NavItem) => void
}
export function Sidebar({ nav, activeItem, onItemClick }: SidebarProps) {
export function NavTree({ nav, activeItem, onItemClick }: NavTreeProps) {
const renderItem = (item: NavItem, depth = 0) => {
const isActive = (item.path || item.title) === activeItem
const hasChildren = item.children && item.children.length > 0
@@ -30,10 +36,11 @@ export function Sidebar({ nav, activeItem, onItemClick }: SidebarProps) {
return (
<button
key={item.title + (item.path || '')}
type="button"
onClick={() => onItemClick(item)}
className={`w-full text-left px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${isActive
? 'bg-primary/10 text-primary font-medium border-l-2 border-primary'
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
? 'bg-primary/10 text-primary font-medium border-l-2 border-primary'
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
}`}
style={{ paddingLeft: `${12 + depth * 12}px` }}
>
@@ -42,14 +49,52 @@ export function Sidebar({ nav, activeItem, onItemClick }: SidebarProps) {
)
}
return (
<nav className="space-y-0.5">
{nav.items?.map((group) => renderItem(group))}
</nav>
)
}
interface SidebarProps extends NavTreeProps {
nav: DocsNav
}
export function Sidebar({ nav, activeItem, onItemClick }: SidebarProps) {
return (
<aside className="w-64 min-w-[16rem] border-r border-border/50 hidden md:block">
<ScrollArea className="h-[calc(100vh-4rem)] py-4 px-2">
<nav className="space-y-0.5">
{nav.items?.map((group) => renderItem(group))}
</nav>
<NavTree nav={nav} activeItem={activeItem} onItemClick={onItemClick} />
<Separator className="my-4" />
</ScrollArea>
</aside>
)
}
interface MobileNavSheetProps extends NavTreeProps {
open: boolean
onOpenChange: (open: boolean) => void
title?: string
}
export function MobileNavSheet({ open, onOpenChange, nav, activeItem, onItemClick, title = 'Navigation' }: MobileNavSheetProps) {
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="left" className="w-72 p-0">
<SheetHeader className="border-b px-4 py-3">
<SheetTitle>{title}</SheetTitle>
</SheetHeader>
<ScrollArea className="h-[calc(100vh-4rem)] px-2 py-4">
<NavTree
nav={nav}
activeItem={activeItem}
onItemClick={(item) => {
onItemClick(item)
onOpenChange(false)
}}
/>
</ScrollArea>
</SheetContent>
</Sheet>
)
}
+101 -37
View File
@@ -1,51 +1,115 @@
// Table of Contents & right sidebar component
import { useCallback, useEffect, useState } from 'react'
import type { NavItem, ZonesConfig } from './types'
import { ZoneWidgets } from './Widgets'
export function Toc({ selectedItem, zones }: { selectedItem: NavItem | null; zones?: ZonesConfig }) {
interface TocHeading {
id: string
text: string
level: number
}
function collectHeadings(): TocHeading[] {
const article = document.querySelector('#main-content article') ?? document.querySelector('article.prose')
if (!article) return []
return Array.from(article.querySelectorAll('h2[id], h3[id], h4[id]')).map((el) => ({
id: el.id,
text: el.textContent?.trim() ?? '',
level: Number(el.tagName.slice(1)),
}))
}
function usePageHeadings(selectedItem: NavItem | null) {
const [headings, setHeadings] = useState<TocHeading[]>([])
const refreshHeadings = useCallback(() => {
setHeadings(collectHeadings())
}, [])
useEffect(() => {
refreshHeadings()
const timer = window.setTimeout(refreshHeadings, 100)
return () => window.clearTimeout(timer)
}, [selectedItem, refreshHeadings])
useEffect(() => {
const onContentLoaded = () => refreshHeadings()
window.addEventListener('aiui:content-loaded', onContentLoaded)
return () => window.removeEventListener('aiui:content-loaded', onContentLoaded)
}, [refreshHeadings])
return headings
}
function TocLinks({ headings }: { headings: TocHeading[] }) {
if (headings.length === 0) {
return <p className="text-sm text-muted-foreground">No sections on this page.</p>
}
return (
<div className="space-y-2">
{headings.map((heading) => (
<a
key={heading.id}
href={`#${heading.id}`}
className={`flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors ${heading.level === 3 ? 'pl-3 text-sm' : heading.level === 4 ? 'pl-6 text-sm' : 'font-medium'}`}
>
<span className="w-1 h-1 rounded-full bg-muted-foreground/30 shrink-0" />
{heading.text}
</a>
))}
</div>
)
}
export function MobileToc({ selectedItem }: { selectedItem: NavItem | null }) {
const headings = usePageHeadings(selectedItem)
if (!selectedItem || headings.length === 0) return null
return (
<details className="lg:hidden border border-border rounded-lg mb-6 px-4 py-3 bg-muted/20">
<summary className="text-sm font-medium cursor-pointer">On this page</summary>
<nav className="mt-3">
<TocLinks headings={headings} />
</nav>
</details>
)
}
export function Toc({
selectedItem,
zones,
showToc = true,
}: {
selectedItem: NavItem | null
zones?: ZonesConfig
showToc?: boolean
}) {
const headings = usePageHeadings(selectedItem)
const rightSidebarWidgets = zones?.rightSidebar || []
if (!showToc && rightSidebarWidgets.filter((w) => w.type !== 'Toc').length === 0) {
return null
}
return (
<aside className="w-64 hidden lg:block border-l border-border/50">
<div className="sticky top-20 px-4 py-6 space-y-4">
{/* Table of Contents */}
<div>
<h4 className="text-xs font-semibold text-muted-foreground/70 uppercase tracking-widest mb-4">
On this page
</h4>
<nav className="space-y-2 text-sm">
{selectedItem ? (
<div className="space-y-2">
<a href="#" className="flex items-center gap-2 text-primary font-medium">
<span className="w-1 h-1 rounded-full bg-primary" />
{selectedItem.title}
</a>
<a href="#overview" className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors pl-3">
Overview
</a>
<a href="#usage" className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors pl-3">
Usage
</a>
</div>
) : (
<div className="space-y-2">
<a href="#theme" className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors">
<span className="w-1 h-1 rounded-full bg-muted-foreground/30" />
Theme Configuration
</a>
<a href="#presets" className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors">
<span className="w-1 h-1 rounded-full bg-muted-foreground/30" />
Available Presets
</a>
</div>
)}
</nav>
</div>
{showToc && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground/70 uppercase tracking-widest mb-4">
On this page
</h4>
<nav className="text-sm">
<TocLinks headings={headings} />
</nav>
</div>
)}
{/* Zone Widgets (excluding Toc) */}
{rightSidebarWidgets.filter(w => w.type !== 'Toc').length > 0 && (
{rightSidebarWidgets.filter((w) => w.type !== 'Toc').length > 0 && (
<div className="pt-4 border-t border-border/50">
<ZoneWidgets widgets={rightSidebarWidgets.filter(w => w.type !== 'Toc')} />
<ZoneWidgets widgets={rightSidebarWidgets.filter((w) => w.type !== 'Toc')} />
</div>
)}
</div>
+24
View File
@@ -56,3 +56,27 @@
@apply bg-background text-foreground;
}
}
@layer components {
/* Readable code blocks in light and dark prose */
.prose pre {
@apply bg-muted text-foreground border border-border;
}
.prose pre code {
@apply text-foreground bg-transparent;
color: inherit;
}
.prose :not(pre) > code {
@apply bg-primary/10 text-primary;
}
.prose .aiui-feature-grid > li {
@apply list-none;
}
.prose .aiui-tabs pre {
@apply my-2;
}
}
+149
View File
@@ -0,0 +1,149 @@
import { isMkdocsTabHeading, mkdocsTabTitle } from './mkdocsPreprocess'
/** Post-render DOM fixes for MkDocs-style content (tabs, feature cards). */
export function enhanceMkdocsDom(root: HTMLElement): void {
const articles = new Set<HTMLElement>()
if (root.matches('article.prose, main .prose, .prose, main.flex-1, #main-content')) {
articles.add(root)
}
root.querySelectorAll<HTMLElement>('article.prose, main .prose, .prose').forEach((el) => articles.add(el))
for (const article of articles) {
removeRawDivParagraphs(article)
enhanceFeatureGrid(article)
enhanceTabs(article)
}
}
function removeRawDivParagraphs(article: HTMLElement): void {
for (const p of article.querySelectorAll('p')) {
const text = p.textContent?.trim() ?? ''
if (/^<\/?div[\s>]/.test(text) || text === '</div>') {
p.remove()
}
}
}
function enhanceFeatureGrid(article: HTMLElement): void {
for (const ul of article.querySelectorAll('ul')) {
if (ul.classList.contains('aiui-feature-grid')) continue
const firstLi = ul.querySelector('li')
const looksLikeCards = Boolean(
firstLi?.textContent?.includes('YAML-Driven')
|| firstLi?.textContent?.includes('Component Slots')
|| firstLi?.querySelector('strong'),
)
if (!looksLikeCards) continue
ul.classList.add(
'aiui-feature-grid',
'grid',
'grid-cols-1',
'md:grid-cols-2',
'gap-4',
'my-6',
'list-none',
'pl-0',
)
for (const li of ul.querySelectorAll('li')) {
li.classList.add('border', 'border-border', 'rounded-lg', 'p-4', 'bg-card')
}
}
}
interface TabSection {
heading: HTMLHeadingElement
title: string
nodes: Node[]
}
function enhanceTabs(article: HTMLElement): void {
if (article.querySelector('[data-aiui-tabs]')) return
const tabHeadings = Array.from(article.querySelectorAll<HTMLHeadingElement>('h3')).filter((h3) =>
isMkdocsTabHeading(h3.textContent?.trim() ?? ''),
)
if (tabHeadings.length < 2) return
const sections: TabSection[] = tabHeadings.map((heading) => {
const title = mkdocsTabTitle(heading.textContent?.trim() ?? '')
const nodes: Node[] = []
let sibling = heading.nextSibling
while (sibling) {
if (sibling.nodeType === Node.ELEMENT_NODE) {
const el = sibling as HTMLElement
if (el.tagName === 'H3' && isMkdocsTabHeading(el.textContent?.trim() ?? '')) break
if (['H1', 'H2'].includes(el.tagName)) break
}
const next = sibling.nextSibling
nodes.push(sibling)
sibling = next
}
return { heading, title, nodes }
})
const wrapper = document.createElement('div')
wrapper.className = 'aiui-tabs my-6 border border-border rounded-lg overflow-hidden'
wrapper.dataset.aiuiTabs = 'true'
const buttons = document.createElement('div')
buttons.className = 'aiui-tab-buttons flex flex-wrap gap-0 border-b border-border bg-muted/30'
wrapper.appendChild(buttons)
const panels = document.createElement('div')
panels.className = 'aiui-tab-panels'
wrapper.appendChild(panels)
sections.forEach((section, index) => {
const btn = document.createElement('button')
btn.type = 'button'
btn.className = [
'aiui-tab-button px-4 py-2 text-sm font-medium transition-colors',
index === 0
? 'bg-background text-foreground border-b-2 border-primary'
: 'text-muted-foreground hover:text-foreground',
].join(' ')
btn.textContent = section.title
btn.dataset.tabIndex = String(index)
buttons.appendChild(btn)
const panel = document.createElement('div')
panel.className = index === 0 ? 'aiui-tab-panel p-4 block' : 'aiui-tab-panel p-4 hidden'
panel.dataset.tabPanel = String(index)
for (const node of section.nodes) {
panel.appendChild(node)
}
panels.appendChild(panel)
})
const insertPoint = sections[0].heading
article.insertBefore(wrapper, insertPoint)
for (const section of sections) {
section.heading.remove()
}
buttons.addEventListener('click', (event) => {
const target = (event.target as HTMLElement).closest<HTMLButtonElement>('.aiui-tab-button')
if (!target) return
const index = target.dataset.tabIndex ?? '0'
for (const btn of buttons.querySelectorAll<HTMLButtonElement>('.aiui-tab-button')) {
const active = btn.dataset.tabIndex === index
btn.classList.toggle('bg-background', active)
btn.classList.toggle('text-foreground', active)
btn.classList.toggle('border-b-2', active)
btn.classList.toggle('border-primary', active)
btn.classList.toggle('text-muted-foreground', !active)
}
for (const panel of panels.querySelectorAll<HTMLElement>('.aiui-tab-panel')) {
panel.classList.toggle('hidden', panel.dataset.tabPanel !== index)
panel.classList.toggle('block', panel.dataset.tabPanel === index)
}
})
}
@@ -0,0 +1,100 @@
/** MkDocs Material syntax → standard markdown for react-markdown. */
export const MKDOCS_ICON_MAP: Record<string, string> = {
':material-file-document:': '📄',
':material-puzzle:': '🧩',
':material-palette:': '🎨',
':material-rocket-launch:': '🚀',
':material-code-tags:': '💻',
':material-cog:': '⚙️',
':material-lightning-bolt:': '⚡',
':material-shield:': '🛡️',
':material-database:': '🗄️',
':material-web:': '🌐',
':material-book:': '📖',
':material-star:': '⭐',
':material-check:': '✅',
':material-close:': '❌',
':material-alert:': '⚠️',
':material-information:': 'ℹ️',
}
const TAB_HEADING_PREFIX = '\u200Btab:'
export function replaceMkdocsIcons(text: string): string {
let result = text
for (const [shortcode, emoji] of Object.entries(MKDOCS_ICON_MAP)) {
result = result.replaceAll(shortcode, emoji)
}
return result
.replace(/:material-[\w-]+:/g, '•')
.replace(/:octicons-[\w-]+(?:-\d+)?:/g, '•')
}
export function stripMkdocsGridWrappers(text: string): string {
return text
.replace(/<div[^>]*markdown[^>]*>\s*/gi, '')
.replace(/<\/div>\s*/gi, '')
}
/** Convert `=== "Title"` tab groups to headings the DOM enhancer recognises. */
export function convertMkdocsTabs(md: string): string {
const lines = md.split('\n')
const out: string[] = []
let i = 0
while (i < lines.length) {
const tabMatch = lines[i].match(/^===\s+"([^"]+)"\s*$/)
if (!tabMatch) {
out.push(lines[i])
i++
continue
}
const title = tabMatch[1]
i++
while (i < lines.length && lines[i].trim() === '') i++
const block: string[] = []
while (i < lines.length) {
if (/^===\s+"[^"]+"\s*$/.test(lines[i])) break
if (lines[i].trim() === '' && i + 1 < lines.length && /^===\s+"[^"]+"\s*$/.test(lines[i + 1])) break
if (lines[i].startsWith(' ') || lines[i].trimStart().startsWith('```')) {
block.push(lines[i].replace(/^ /, ''))
i++
continue
}
if (block.length > 0 && lines[i].trim() === '') {
block.push('')
i++
continue
}
break
}
out.push(`### ${TAB_HEADING_PREFIX}${title}`)
out.push('')
out.push(...block)
out.push('')
}
return out.join('\n')
}
export function preprocessMkdocsMarkdown(md: string): string {
let result = stripMkdocsGridWrappers(md)
result = replaceMkdocsIcons(result)
result = convertMkdocsTabs(result)
return result
}
export function isMkdocsTabHeading(text: string): boolean {
return text.startsWith(TAB_HEADING_PREFIX) || text.startsWith('tab:')
}
export function mkdocsTabTitle(text: string): string {
return text.replace(/^\u200Btab:/, '').replace(/^tab:/, '')
}
+8
View File
@@ -0,0 +1,8 @@
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
}
+64
View File
@@ -0,0 +1,64 @@
import type { RouteManifest, SlotConfig, TemplateConfig } from './types'
export interface TemplateMatch {
template: string
layout: string
slots: Record<string, SlotConfig | null | undefined>
slotOverrides?: Record<string, SlotConfig | null>
}
export function matchPattern(path: string, pattern: string): boolean {
const normalizedPath = path.replace(/^\/+|\/+$/g, '')
const normalizedPattern = pattern.replace(/^\/+|\/+$/g, '')
const regexPattern = normalizedPattern
.replace(/\*\*/g, '{{GLOB}}')
.replace(/\*/g, '[^/]+')
.replace(/\{\{GLOB\}\}/g, '.*')
return new RegExp(`^${regexPattern}$`).test(normalizedPath)
}
export function resolveTemplate(
path: string,
manifest: RouteManifest,
templates: Record<string, TemplateConfig>,
): TemplateMatch | null {
const routes = manifest.routes ?? []
const sortedRoutes = [...routes].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
for (const route of sortedRoutes) {
if (!matchPattern(path, route.pattern)) continue
const template = templates[route.template]
if (!template) {
console.warn(`Template "${route.template}" not found`)
return null
}
return {
template: route.template,
layout: template.layout ?? 'ThreeColumnLayout',
slots: template.slots ?? {},
slotOverrides: route.slotOverrides,
}
}
return null
}
export function mergedSlot(
slotName: string,
match: TemplateMatch | null,
): SlotConfig | null | undefined {
if (!match) return undefined
if (match.slotOverrides && slotName in match.slotOverrides) {
return match.slotOverrides[slotName]
}
return match.slots[slotName]
}
export function shouldShowToc(match: TemplateMatch | null): boolean {
const right = mergedSlot('right', match)
return right !== null
}
+6 -1
View File
@@ -96,7 +96,12 @@ export interface DocsNav {
}
export interface RouteManifest {
routes?: { pattern: string; template: string }[]
routes?: {
pattern: string
template: string
priority?: number
slotOverrides?: Record<string, SlotConfig | null>
}[]
}
// Message element types (matching Python schema)
+1 -1
View File
@@ -1,3 +1,3 @@
"""Version information."""
__version__ = "0.3.133"
__version__ = "0.3.142"
+13 -1
View File
@@ -476,6 +476,18 @@ class JSONFileDataStore(BaseDataStore):
return await asyncio.to_thread(_get)
async def update_session(self, session_id: str, **kwargs: Any) -> None:
def _update() -> None:
path = self._session_path(session_id)
data = self._read_session(path)
if data is None:
return
data.update(kwargs)
data["updated_at"] = datetime.now(timezone.utc).isoformat()
self._write_session(path, data)
await asyncio.to_thread(_update)
def _read_feedback(self) -> list[dict[str, Any]]:
"""Read feedback from JSON file."""
try:
@@ -579,7 +591,7 @@ class SQLAlchemyDataStore(BaseDataStore):
base = Path(os.environ.get("AIUI_DATA_DIR", str(Path.home() / ".praisonaiui")))
base.mkdir(parents=True, exist_ok=True)
db_path = base / "aiui.db"
return f"sqlite+aiosqlite:///{db_path}"
return f"sqlite+aiosqlite:///{db_path.as_posix()}"
async def _ensure_initialized(self) -> None:
"""Lazy initialization of SQLAlchemy engine and tables."""
+8
View File
@@ -33,6 +33,8 @@ logger = logging.getLogger(__name__)
# Sentinel key used to identify the AIUI metadata message inside SDK sessions
_META_KEY = "_aiui_meta"
_TITLE_KEY = "_aiui_title"
# Bucket holding arbitrary session-level metadata (e.g. fork lineage)
_CUSTOM_META_KEY = "_aiui_custom"
class SDKFileDataStore(BaseDataStore):
@@ -116,6 +118,10 @@ class SDKFileDataStore(BaseDataStore):
result["platform"] = meta["platform"]
if meta.get("icon"):
result["icon"] = meta["icon"]
# Surface any custom session-level metadata (e.g. fork lineage)
custom = meta.get(_CUSTOM_META_KEY)
if isinstance(custom, dict) and custom:
result["metadata"] = custom
return result
async def create_session(self, session_id: Optional[str] = None) -> dict[str, Any]:
@@ -183,6 +189,8 @@ class SDKFileDataStore(BaseDataStore):
await self._persist_meta(session_id, "platform", kwargs["platform"])
if "icon" in kwargs:
await self._persist_meta(session_id, "icon", kwargs["icon"])
if "metadata" in kwargs and isinstance(kwargs["metadata"], dict):
await self._persist_meta(session_id, _CUSTOM_META_KEY, kwargs["metadata"])
async def close(self) -> None:
pass
@@ -176,6 +176,49 @@ CONFIG_SCHEMA = {
},
},
},
"finops": {
"type": "object",
"title": "FinOps & Budgets",
"description": (
"Alerts appear on Overview and Chat. Does not block agent runs."
),
"properties": {
"enabled": {
"type": "boolean",
"title": "Enable FinOps",
"default": True,
},
"daily_token_budget": {
"type": "integer",
"title": "Daily Token Budget",
"minimum": 0,
},
"daily_cost_budget_usd": {
"type": "number",
"title": "Daily Cost Budget (USD)",
"minimum": 0,
},
"warn_pct": {
"type": "integer",
"title": "Warn Threshold (%)",
"minimum": 1,
"maximum": 99,
"default": 80,
},
"critical_pct": {
"type": "integer",
"title": "Critical Threshold (%)",
"minimum": 1,
"maximum": 100,
"default": 95,
},
"show_session_chip": {
"type": "boolean",
"title": "Show Session Cost Chip",
"default": True,
},
},
},
},
}
@@ -216,6 +259,24 @@ def validate_config(config: Dict[str, Any], schema: Dict[str, Any] = None) -> Li
if "maximum" in prop_schema and value > prop_schema["maximum"]:
errors.append(f"{key}: must be <= {prop_schema['maximum']}")
errors.extend(_validate_finops(config.get("finops")))
return errors
def _validate_finops(finops: Any) -> List[str]:
"""Cross-field validation for the FinOps section (E4/E21)."""
if not isinstance(finops, dict):
return []
errors: List[str] = []
warn = finops.get("warn_pct", 80)
critical = finops.get("critical_pct", 95)
if isinstance(warn, (int, float)) and isinstance(critical, (int, float)):
if critical <= warn:
errors.append("finops.critical_pct: must be greater than warn_pct")
for field in ("daily_token_budget", "daily_cost_budget_usd"):
val = finops.get(field)
if isinstance(val, (int, float)) and val < 0:
errors.append(f"finops.{field}: must be >= 0")
return errors
+51
View File
@@ -81,6 +81,23 @@ class JSONLocaleManager(I18nProtocol):
"chat.compaction.compacted": "Context compacted",
"chat.memory.chip": "{count} memories",
"chat.memory.empty_turn": "No memories for this turn",
"skills.title": "Skills Studio",
"skills.subtitle": "Review self_improve proposals",
"skills.tab.pending": "Pending",
"skills.tab.installed": "Installed",
"skills.tab.history": "History",
"skills.diff.current": "Current",
"skills.diff.proposed": "Proposed",
"skills.diff.no_existing": "No existing skill",
"skills.action.approve": "Approve",
"skills.action.reject": "Reject",
"skills.pending.empty": "No pending skill writes",
"finops.strip.title": "Cost FinOps",
"finops.strip.today": "Today: {tokens} tokens",
"finops.strip.view_usage": "View usage",
"finops.banner.warn": "Daily token budget at {pct}% \u2014 consider lighter models",
"finops.banner.critical": "Daily token budget at {pct}% \u2014 review usage",
"finops.chip.label": "{tokens} \u00b7 {model}",
"nav.dashboard": "Dashboard",
"nav.agents": "Agents",
"nav.settings": "Settings",
@@ -102,6 +119,23 @@ class JSONLocaleManager(I18nProtocol):
"chat.compaction.compacted": "Contexto compactado",
"chat.memory.chip": "{count} recuerdos",
"chat.memory.empty_turn": "Sin recuerdos para este turno",
"skills.title": "Estudio de habilidades",
"skills.subtitle": "Revisar propuestas de self_improve",
"skills.tab.pending": "Pendientes",
"skills.tab.installed": "Instaladas",
"skills.tab.history": "Historial",
"skills.diff.current": "Actual",
"skills.diff.proposed": "Propuesto",
"skills.diff.no_existing": "No existe la habilidad",
"skills.action.approve": "Aprobar",
"skills.action.reject": "Rechazar",
"skills.pending.empty": "No hay escrituras de habilidades pendientes",
"finops.strip.title": "FinOps de costos",
"finops.strip.today": "Hoy: {tokens} tokens",
"finops.strip.view_usage": "Ver uso",
"finops.banner.warn": "Presupuesto diario de tokens al {pct}% \u2014 considere modelos más ligeros",
"finops.banner.critical": "Presupuesto diario de tokens al {pct}% \u2014 revise el uso",
"finops.chip.label": "{tokens} \u00b7 {model}",
"nav.dashboard": "Panel",
"nav.agents": "Agentes",
"nav.settings": "Configuración",
@@ -123,6 +157,23 @@ class JSONLocaleManager(I18nProtocol):
"chat.compaction.compacted": "Contexte compacté",
"chat.memory.chip": "{count} souvenirs",
"chat.memory.empty_turn": "Aucun souvenir pour ce tour",
"skills.title": "Studio de comp\u00e9tences",
"skills.subtitle": "Examiner les propositions de self_improve",
"skills.tab.pending": "En attente",
"skills.tab.installed": "Install\u00e9es",
"skills.tab.history": "Historique",
"skills.diff.current": "Actuel",
"skills.diff.proposed": "Propos\u00e9",
"skills.diff.no_existing": "Aucune comp\u00e9tence existante",
"skills.action.approve": "Approuver",
"skills.action.reject": "Rejeter",
"skills.pending.empty": "Aucune \u00e9criture de comp\u00e9tence en attente",
"finops.strip.title": "FinOps des coûts",
"finops.strip.today": "Aujourd'hui : {tokens} tokens",
"finops.strip.view_usage": "Voir l'utilisation",
"finops.banner.warn": "Budget quotidien de tokens à {pct}% \u2014 envisagez des modèles plus légers",
"finops.banner.critical": "Budget quotidien de tokens à {pct}% \u2014 vérifiez l'utilisation",
"finops.chip.label": "{tokens} \u00b7 {model}",
"nav.dashboard": "Tableau de bord",
"nav.agents": "Agents",
"nav.settings": "Paramètres",
+83 -2
View File
@@ -26,6 +26,24 @@ from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
_EMBEDDING_ERROR_MARKERS = (
"does not have access",
"model_not_found",
"does not exist or you do not have access",
"notfounderror",
)
def _is_embedding_error(err_msg: str) -> bool:
"""True when an error string indicates an unavailable embedding model."""
lowered = err_msg.lower()
return any(marker in lowered for marker in _EMBEDDING_ERROR_MARKERS)
def _is_embedding_unavailable(err: BaseException) -> bool:
"""Detect an embedding model access / not-found failure from an exception."""
return _is_embedding_error(str(err))
# ── Knowledge Protocol ───────────────────────────────────────────────
@@ -91,6 +109,10 @@ class KnowledgeProtocol(ABC):
"""Get corpus statistics."""
return {"total": 0}
def last_search_meta(self) -> Dict[str, Any]:
"""Metadata for the most recent search: {"mode": ..., "error": ...}."""
return {"mode": "vector", "error": None}
def health(self) -> Dict[str, Any]:
"""Health check for this backend."""
return {"status": "ok", "provider": self.__class__.__name__}
@@ -171,8 +193,12 @@ class SimpleKnowledgeManager(KnowledgeProtocol):
"status": "ok",
"provider": "SimpleKnowledgeManager",
"total_entries": len(self._entries),
"embedding": {"available": False, "reason": "no_vector_backend"},
}
def last_search_meta(self) -> Dict[str, Any]:
return {"mode": "local", "error": None}
# ── SDK Knowledge Manager (wraps praisonaiagents.knowledge) ──────────
@@ -191,6 +217,9 @@ class SDKKnowledgeManager(KnowledgeProtocol):
self._sdk_knowledge = None
self._sdk_probed = False # True once we've verified SDK actually works
self._local_index: Dict[str, Dict[str, Any]] = {}
self._embedding_unavailable = False
self._embedding_error: Optional[str] = None
self._last_search_meta: Dict[str, Any] = {"mode": "vector", "error": None}
def _get_sdk_knowledge(self) -> Any:
"""Lazy-init SDK Knowledge with health probe.
@@ -242,6 +271,10 @@ class SDKKnowledgeManager(KnowledgeProtocol):
"using local in-memory index"
)
self._sdk_knowledge = None
elif _is_embedding_error(err_msg):
self._embedding_unavailable = True
self._embedding_error = err_msg
logger.warning("Embedding model unavailable: %s", e)
else:
# Other errors (e.g. empty DB) are fine — SDK works
pass
@@ -249,6 +282,10 @@ class SDKKnowledgeManager(KnowledgeProtocol):
self._sdk_probed = True
return self._sdk_knowledge
def last_search_meta(self) -> Dict[str, Any]:
"""Metadata for the most recent search: {"mode": ..., "error": ...}."""
return dict(self._last_search_meta)
def store(
self,
text: str,
@@ -294,6 +331,7 @@ class SDKKnowledgeManager(KnowledgeProtocol):
try:
results = sdk.search(query, limit=limit, user_id="praisonaiui")
if isinstance(results, list) and results:
self._last_search_meta = {"mode": "vector", "error": None}
return [
{
"id": str(r.get("id", i)),
@@ -304,7 +342,11 @@ class SDKKnowledgeManager(KnowledgeProtocol):
for i, r in enumerate(results[:limit])
]
except BaseException as e:
err_msg = str(e)
logger.warning("SDK knowledge search failed: %s; falling back to local", e)
if _is_embedding_error(err_msg):
self._embedding_unavailable = True
self._embedding_error = err_msg
# Fallback to local text search
query_lower = query.lower()
@@ -314,6 +356,21 @@ class SDKKnowledgeManager(KnowledgeProtocol):
results.append(e)
if len(results) >= limit:
break
if self._embedding_unavailable:
self._last_search_meta = {
"mode": "fallback",
"error": (
"Vector search unavailable: "
f"{self._embedding_error or 'embedding model not accessible'}. "
"Set AIUI_EMBEDDING_MODEL=text-embedding-ada-002 or enable embedding "
"models in your provider project."
),
}
elif sdk is not None:
self._last_search_meta = {"mode": "vector", "error": None}
else:
self._last_search_meta = {"mode": "local", "error": None}
return results
def add_file(
@@ -453,11 +510,25 @@ class SDKKnowledgeManager(KnowledgeProtocol):
def health(self) -> Dict[str, Any]:
sdk = self._get_sdk_knowledge()
embedding_available = sdk is not None and not self._embedding_unavailable
warnings: List[str] = []
embedding: Dict[str, Any] = {"available": embedding_available}
if self._embedding_unavailable:
embedding["error"] = self._embedding_error
warnings.append(
"Embedding model unavailable; knowledge search degraded to local text "
"matching. Set AIUI_EMBEDDING_MODEL to an accessible model."
)
status = "ok"
if sdk is None or self._embedding_unavailable:
status = "degraded"
return {
"status": "ok" if sdk is not None else "degraded",
"status": status,
"provider": "SDKKnowledgeManager",
"sdk_available": sdk is not None,
"total_indexed": len(self._local_index),
"embedding": embedding,
"warnings": warnings,
}
@@ -626,7 +697,15 @@ class KnowledgeFeature(BaseFeatureProtocol):
query=query,
limit=body.get("limit", 10),
)
return JSONResponse({"results": results, "count": len(results)})
meta = mgr.last_search_meta()
return JSONResponse(
{
"results": results,
"count": len(results),
"search_mode": meta.get("mode", "unknown"),
"warning": meta.get("error"),
}
)
async def _add_file(self, request: Request) -> JSONResponse:
"""POST /api/knowledge/add-file — ingest a file."""
@@ -675,6 +754,8 @@ class KnowledgeFeature(BaseFeatureProtocol):
"files": stats.get("files", 0),
"backend": h.get("provider", "unknown"),
"status": h.get("status", "ok"),
"embedding": h.get("embedding", {"available": True}),
"warnings": h.get("warnings", []),
}
)
+4 -4
View File
@@ -175,7 +175,7 @@ async def run_startup_hooks() -> None:
return
_log.info(f"Running {len(_startup_hooks)} startup hooks")
start_time = time.time()
start_time = time.perf_counter()
for hook in _startup_hooks:
try:
@@ -187,7 +187,7 @@ async def run_startup_hooks() -> None:
_log.error(f"Startup hook failed: {getattr(hook, '__name__', str(hook))}: {e}")
# Continue with other hooks - individual failures shouldn't break startup
execution_time = time.time() - start_time
execution_time = time.perf_counter() - start_time
_lifecycle_state.update(
{
"startup_completed": True,
@@ -209,7 +209,7 @@ async def run_shutdown_hooks() -> None:
_lifecycle_state["shutdown_initiated"] = True
_log.info(f"Running {len(_shutdown_hooks)} shutdown hooks")
start_time = time.time()
start_time = time.perf_counter()
# Get shutdown timeout from environment
timeout = float(os.environ.get("AIUI_SHUTDOWN_TIMEOUT", "30.0"))
@@ -227,7 +227,7 @@ async def run_shutdown_hooks() -> None:
try:
await asyncio.wait_for(_execute_hooks(), timeout=timeout)
execution_time = time.time() - start_time
execution_time = time.perf_counter() - start_time
_lifecycle_state["shutdown_time"] = execution_time
_log.info(f"Shutdown hooks completed in {execution_time:.3f}s")
except asyncio.TimeoutError:
+76 -18
View File
@@ -6,6 +6,7 @@ Wraps praisonai.capabilities functions as HTTP endpoints.
from __future__ import annotations
import os
import time
import uuid
from dataclasses import asdict
@@ -17,6 +18,35 @@ from starlette.routing import Route
from ._base import BaseFeatureProtocol
def _default_embedding_model() -> str:
"""Resolve the default embedding model from environment."""
return os.environ.get(
"AIUI_EMBEDDING_MODEL",
os.environ.get("OPENAI_EMBEDDING_MODEL", "text-embedding-3-small"),
)
def _embedding_fallback_models() -> list[str]:
"""Resolve the embedding fallback chain from environment."""
raw = os.environ.get(
"AIUI_EMBEDDING_FALLBACK_MODELS",
"text-embedding-ada-002,text-embedding-3-small",
)
return [m.strip() for m in raw.split(",") if m.strip()]
def _is_model_not_found(err: BaseException) -> bool:
"""Heuristically detect embedding model access / not-found errors."""
msg = str(err).lower()
return (
"does not have access" in msg
or "model_not_found" in msg
or "does not exist" in msg
or "notfounderror" in msg
)
# Lazy import capabilities
_capabilities = None
@@ -280,29 +310,57 @@ class OpenAIAPIFeature(BaseFeatureProtocol):
body = await request.json()
input_text = body.get("input", "")
model = body.get("model", "text-embedding-3-small")
model = body.get("model") or _default_embedding_model()
try:
result = await caps.aembed(
input=input_text,
model=model,
)
candidates = [model]
for fallback in _embedding_fallback_models():
if fallback not in candidates:
candidates.append(fallback)
data = _result_to_dict(result)
last_err: BaseException | None = None
for candidate in candidates:
try:
result = await caps.aembed(
input=input_text,
model=candidate,
)
data = _result_to_dict(result)
return JSONResponse(
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": data.get("embedding", []),
}
],
"model": candidate,
"usage": data.get("usage", {"prompt_tokens": 0, "total_tokens": 0}),
}
)
except Exception as e:
last_err = e
if not _is_model_not_found(e):
break
if last_err is not None and _is_model_not_found(last_err):
return JSONResponse(
{
"object": "list",
"data": [
{"object": "embedding", "index": 0, "embedding": data.get("embedding", [])}
],
"model": model,
"usage": data.get("usage", {"prompt_tokens": 0, "total_tokens": 0}),
}
)
except Exception as e:
return JSONResponse(
{"error": {"message": str(e), "type": "api_error"}}, status_code=500
"error": {
"message": (
f"Embedding model unavailable: {last_err}. "
"Set AIUI_EMBEDDING_MODEL to a model your project can access "
"(e.g. text-embedding-ada-002)."
),
"type": "model_not_found",
}
},
status_code=503,
)
return JSONResponse(
{"error": {"message": str(last_err), "type": "api_error"}}, status_code=500
)
async def _image_generate(self, request: Request) -> JSONResponse:
"""POST /v1/images/generations — Generate images."""
+102
View File
@@ -132,6 +132,14 @@ class _InMemorySessionStore(SessionProtocol):
_session_metadata: Dict[str, Dict[str, Any]] = {}
_MESSAGE_COPY_FIELDS = ("role", "content", "text", "tool_calls", "name", "metadata")
def _sanitize_message(message: Dict[str, Any]) -> Dict[str, Any]:
"""Copy a message for a forked session, stripping ephemeral identifiers."""
return {k: message[k] for k in _MESSAGE_COPY_FIELDS if k in message}
def _get_metadata(session_id: str) -> Dict[str, Any]:
"""Get session metadata (labels, usage, etc)."""
if session_id not in _session_metadata:
@@ -166,6 +174,7 @@ class SessionsFeature(BaseFeatureProtocol):
Route("/api/sessions/{session_id}/context", self._build_context, methods=["POST"]),
Route("/api/sessions/{session_id}/compact", self._compact, methods=["POST"]),
Route("/api/sessions/{session_id}/reset", self._reset, methods=["POST"]),
Route("/api/sessions/{session_id}/fork", self._fork, methods=["POST"]),
Route("/api/sessions/{session_id}/preview", self._preview, methods=["GET"]),
Route("/api/sessions/{session_id}/labels", self._labels, methods=["GET"]),
Route("/api/sessions/{session_id}/labels", self._set_labels, methods=["POST"]),
@@ -307,6 +316,16 @@ class SessionsFeature(BaseFeatureProtocol):
or s.get("agent")
or s.get("metadata", {}).get("agent_id")
),
"platform": (
s.get("platform")
or s.get("metadata", {}).get("platform")
or meta.get("_platform")
),
"channel_id": (
s.get("channel_id")
or s.get("metadata", {}).get("channel_id")
or (sid[len("channel-"):] if sid.startswith("channel-") else None)
),
"source": s.get("source") or s.get("metadata", {}).get("source") or "datastore",
}
)
@@ -519,6 +538,89 @@ class SessionsFeature(BaseFeatureProtocol):
}
)
async def _fork(self, request: Request) -> JSONResponse:
"""POST /api/sessions/{id}/fork — Branch a session into a new child.
Body (all optional):
up_to_index: int — keep messages[0..up_to_index] inclusive
title_suffix: str — appended to the parent title (default " (fork)")
agent_id: str — override recorded on the child metadata
labels: list[str] — labels applied to the child
"""
source_id = request.path_params["session_id"]
if source_id.startswith("agent:"):
return JSONResponse(
{"error": "Cannot fork a virtual agent session"}, status_code=400
)
content_type = request.headers.get("content-type") or ""
body = await request.json() if content_type.startswith("application/json") else {}
try:
from praisonaiui.server import get_datastore
ds = get_datastore()
except Exception as e:
logger.warning(f"fork: datastore unavailable: {e}")
return JSONResponse({"error": "Datastore unavailable"}, status_code=503)
source = await ds.get_session(source_id)
if source is None:
return JSONResponse({"error": "Source session not found"}, status_code=404)
messages = await ds.get_messages(source_id)
total = len(messages)
up_to_index = body.get("up_to_index")
if up_to_index is not None:
if not isinstance(up_to_index, int) or up_to_index < 0 or up_to_index >= max(total, 1):
return JSONResponse(
{"error": "up_to_index out of range", "total_messages": total},
status_code=400,
)
messages = messages[: up_to_index + 1]
child = await ds.create_session()
child_id = child.get("id") or child.get("session_id")
for msg in messages:
await ds.add_message(child_id, _sanitize_message(msg))
suffix = body.get("title_suffix", " (fork)")
parent_title = source.get("title") or ""
child_meta = {
"parent_session_id": source_id,
"forked_at": time.time(),
"fork_point_index": up_to_index,
"source": "playground-fork",
}
if body.get("agent_id"):
child_meta["agent_id"] = body["agent_id"]
try:
await ds.update_session(
child_id,
title=f"{parent_title}{suffix}",
metadata=child_meta,
)
except Exception as e:
logger.warning(f"fork: update_session metadata failed: {e}")
labels = body.get("labels")
if isinstance(labels, list) and labels:
_get_metadata(child_id)["_labels"] = [str(x) for x in labels]
return JSONResponse(
{
"session_id": child_id,
"parent_id": source_id,
"forked_at_index": up_to_index,
"message_count": len(messages),
"created_at": time.time(),
}
)
async def _labels(self, request: Request) -> JSONResponse:
sid = request.path_params["session_id"]
meta = _get_metadata(sid)
+32 -4
View File
@@ -2860,7 +2860,14 @@ def create_app(
require_auth: bool = False,
config_path: Optional[Path] = None,
) -> Starlette:
"""Create the Starlette application."""
"""Create the Starlette application.
URL-token auth (``TokenQueryMiddleware``) is opt-in: it activates only
when ``AIUI_REQUIRE_TOKEN`` is truthy, YAML ``auth.requireToken`` is set,
or ``require_auth=True``. A bare ``GATEWAY_AUTH_TOKEN`` /
``AIUI_URL_TOKEN`` env var (e.g. leaked into a developer shell after a
gateway run) no longer forces 401 on standalone ``/api/*`` (#238, #251).
"""
from praisonaiui.auth import (
AuthMiddleware,
login_handler,
@@ -2874,10 +2881,13 @@ def create_app(
config = load_config_from_yaml(config_path)
# Extract auth settings from config
_config_requires_token = False
if config:
auth_config = config.get("auth", {})
if auth_config.get("requireAuth") or auth_config.get("require_auth"):
require_auth = True
if auth_config.get("requireToken") or auth_config.get("require_token"):
_config_requires_token = True
middleware = [
Middleware(
@@ -2891,10 +2901,20 @@ def create_app(
# Add optional AUTH_ENFORCE middleware (env var driven)
middleware.append(Middleware(AuthEnforcementMiddleware))
# URL-token auth — opt-in via env. Enables query-param / cookie auth so
# dashboards can be shared by URL (Jupyter model).
# URL-token auth — opt-in. Enables query-param / cookie auth so dashboards
# can be shared by URL (Jupyter model). The mere presence of a token env
# var (e.g. GATEWAY_AUTH_TOKEN leaking into a developer shell after a
# gateway run) must NOT force auth on standalone create_app() — that broke
# ad-hoc TestClient scripts (#238, #251). Activation now requires an
# explicit opt-in: AIUI_REQUIRE_TOKEN=1, YAML auth.requireToken, or the
# require_auth flag.
_tok = os.environ.get("AIUI_URL_TOKEN") or os.environ.get("GATEWAY_AUTH_TOKEN")
if _tok:
_token_auth_opt_in = (
os.environ.get("AIUI_REQUIRE_TOKEN", "").lower() in ("true", "1", "yes")
or _config_requires_token
or require_auth
)
if _tok and _token_auth_opt_in:
from praisonaiui.auth import TokenQueryMiddleware
middleware.append(
Middleware(TokenQueryMiddleware, expected_token=_tok)
@@ -3284,6 +3304,14 @@ def create_app(
"description": "Messaging platform connections",
"order": 5,
},
{
"id": "inbox",
"title": "Channel Inbox",
"icon": "📥",
"group": "Agent",
"description": "Unified multi-channel conversation triage",
"order": 6,
},
{
"id": "sessions",
"title": "Sessions",
@@ -1 +1 @@
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-500:oklch(60.6% .25 292.717);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--contLine truncated
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-500:oklch(60.6% .25 292.717);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--contLine truncated
@@ -1,65 +1,18 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index2.js","assets/common-types.js","assets/index3.js"])))=>i.map(i=>d[i]);
function j1(e,t){for(var r=0;r<t.length;r++){const a=t[r];if(typeof a!="string"&&!Array.isArray(a)){for(const o in a)if(o!=="default"&&!(o in e)){const s=Object.getOwnPropertyDescriptor(a,o);s&&Object.defineProperty(e,o,s.get?s:{enumerable:!0,get:()=>a[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const u of s.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&a(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(o){if(o.ep)return;o.ep=!0;const s=r(o);fetch(o.href,s)}})();function Qu(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var of={exports:{}},al={};var LT;function U1(){if(LT)return al;LT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,o,s){var u=null;if(s!==void 0&&(u=""+s),o.key!==void 0&&(u=""+o.key),"key"in o){s={};for(var d in o)d!=="key"&&(s[d]=o[d])}else s=o;return o=s.ref,{$$typeof:e,type:a,key:u,ref:o!==void 0?o:null,props:s}}return al.Fragment=t,al.jsx=r,al.jsxs=r,al}var DT;function F1(){return DT||(DT=1,of.exports=U1()),of.exports}var g=F1(),lf={exports:{}},Ne={};var MT;function B1(){if(MT)return Ne;MT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function E(U){return U===null||typeof U!="object"?null:(U=v&&U[v]||U["@@iterator"],typeof U=="function"?U:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},T=Object.assign,k={};function A(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}A.prototype.isReactComponent={},A.prototype.setState=function(U,J){if(typeof U!="object"&&typeof U!="function"&&U!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,U,J,"setState")},A.prototype.forceUpdate=function(U){this.updater.enqueueForceUpdate(this,U,"forceUpdate")};function _(){}_.prototype=A.prototype;function R(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}var L=R.prototype=new _;L.constructor=R,T(L,A.prototype),L.isPureReactComponent=!0;var j=Array.isArray;function D(){}var z={H:null,A:null,T:null,S:null},F=Object.prototype.hasOwnProperty;function V(U,J,I){var ue=I.ref;return{$$typeof:e,type:U,key:J,ref:ue!==void 0?ue:null,props:I}}function M(U,J){return V(U.type,J,U.props)}function H(U){return typeof U=="object"&&U!==null&&U.$$typeof===e}function K(U){var J={"=":"=0",":":"=2"};return"$"+U.replace(/[=:]/g,function(I){return J[I]})}var Z=/\/+/g;function te(U,J){return typeof U=="object"&&U!==null&&U.key!=null?K(""+U.key):J.toString(36)}function Q(U){switch(U.status){case"fulfilled":return U.value;case"rejected":throw U.reason;default:switch(typeof U.status=="string"?U.then(D,D):(U.status="pending",U.then(function(J){U.status==="pending"&&(U.status="fulfilled",U.value=J)},function(J){U.status==="pending"&&(U.status="rejected",U.reason=J)})),U.status){case"fulfilled":return U.value;case"rejected":throw U.reason}}throw U}function P(U,J,I,ue,fe){var de=typeof U;(de==="undefined"||de==="boolean")&&(U=null);var le=!1;if(U===null)le=!0;else switch(de){case"bigint":case"string":case"number":le=!0;break;case"object":switch(U.$$typeof){case e:case t:le=!0;break;case h:return le=U._init,P(le(U._payload),J,I,ue,fe)}}if(le)return fe=fe(U),le=ue===""?"."+te(U,0):ue,j(fe)?(I="",le!=null&&(I=le.replace(Z,"$&/")+"/"),P(fe,J,I,"",function(Re){return Re})):fe!=null&&(H(fe)&&(fe=M(fe,I+(fe.key==null||U&&U.key===fe.key?"":(""+fe.key).replace(Z,"$&/")+"/")+le)),J.push(fe)),1;le=0;var we=ue===""?".":ue+":";if(j(U))for(var me=0;me<U.length;me++)ue=U[me],de=we+te(ue,me),le+=P(ue,J,I,de,fe);else if(me=E(U),typeof me=="function")for(U=me.call(U),me=0;!(ue=U.next()).done;)ue=ue.value,de=we+te(ue,me++),le+=P(ue,J,I,de,fe);else if(de==="object"){if(typeof U.then=="function")return P(Q(U),J,I,ue,fe);throw J=String(U),Error("Objects are not valid as a React child (found: "+(J==="[object Object]"?"object with keys {"+Object.keys(U).join(", ")+"}":J)+"). If you meant to render a collection of children, use an arLine truncated
function NL(e,t){for(var r=0;r<t.length;r++){const a=t[r];if(typeof a!="string"&&!Array.isArray(a)){for(const o in a)if(o!=="default"&&!(o in e)){const s=Object.getOwnPropertyDescriptor(a,o);s&&Object.defineProperty(e,o,s.get?s:{enumerable:!0,get:()=>a[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const u of s.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&a(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(o){if(o.ep)return;o.ep=!0;const s=r(o);fetch(o.href,s)}})();function ac(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var ff={exports:{}},ul={};var XT;function RL(){if(XT)return ul;XT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,o,s){var u=null;if(s!==void 0&&(u=""+s),o.key!==void 0&&(u=""+o.key),"key"in o){s={};for(var c in o)c!=="key"&&(s[c]=o[c])}else s=o;return o=s.ref,{$$typeof:e,type:a,key:u,ref:o!==void 0?o:null,props:s}}return ul.Fragment=t,ul.jsx=r,ul.jsxs=r,ul}var ZT;function CL(){return ZT||(ZT=1,ff.exports=RL()),ff.exports}var g=CL(),gf={exports:{}},Re={};var QT;function IL(){if(QT)return Re;QT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),u=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),m=Symbol.for("react.activity"),v=Symbol.iterator;function E(U){return U===null||typeof U!="object"?null:(U=v&&U[v]||U["@@iterator"],typeof U=="function"?U:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},w=Object.assign,k={};function A(U,B,C){this.props=U,this.context=B,this.refs=k,this.updater=C||x}A.prototype.isReactComponent={},A.prototype.setState=function(U,B){if(typeof U!="object"&&typeof U!="function"&&U!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,U,B,"setState")},A.prototype.forceUpdate=function(U){this.updater.enqueueForceUpdate(this,U,"forceUpdate")};function _(){}_.prototype=A.prototype;function N(U,B,C){this.props=U,this.context=B,this.refs=k,this.updater=C||x}var D=N.prototype=new _;D.constructor=N,w(D,A.prototype),D.isPureReactComponent=!0;var F=Array.isArray;function L(){}var z={H:null,A:null,T:null,S:null},$=Object.prototype.hasOwnProperty;function K(U,B,C){var J=C.ref;return{$$typeof:e,type:U,key:B,ref:J!==void 0?J:null,props:C}}function j(U,B){return K(U.type,B,U.props)}function q(U){return typeof U=="object"&&U!==null&&U.$$typeof===e}function X(U){var B={"=":"=0",":":"=2"};return"$"+U.replace(/[=:]/g,function(C){return B[C]})}var ne=/\/+/g;function re(U,B){return typeof U=="object"&&U!==null&&U.key!=null?X(""+U.key):B.toString(36)}function ee(U){switch(U.status){case"fulfilled":return U.value;case"rejected":throw U.reason;default:switch(typeof U.status=="string"?U.then(L,L):(U.status="pending",U.then(function(B){U.status==="pending"&&(U.status="fulfilled",U.value=B)},function(B){U.status==="pending"&&(U.status="rejected",U.reason=B)})),U.status){case"fulfilled":return U.value;case"rejected":throw U.reason}}throw U}function M(U,B,C,J,ae){var ue=typeof U;(ue==="undefined"||ue==="boolean")&&(U=null);var ie=!1;if(U===null)ie=!0;else switch(ue){case"bigint":case"string":case"number":ie=!0;break;case"object":switch(U.$$typeof){case e:case t:ie=!0;break;case h:return ie=U._init,M(ie(U._payload),B,C,J,ae)}}if(ie)return ae=ae(U),ie=J===""?"."+re(U,0):J,F(ae)?(C="",ie!=null&&(C=ie.replace(ne,"$&/")+"/"),M(ae,B,C,"",function(ke){return ke})):ae!=null&&(q(ae)&&(ae=j(ae,C+(ae.key==null||U&&U.key===ae.key?"":(""+ae.key).replace(ne,"$&/")+"/")+ie)),B.push(ae)),1;ie=0;var he=J===""?".":J+":";if(F(U))for(var fe=0;fe<U.length;fe++)J=U[fe],ue=he+re(J,fe),ie+=M(J,B,C,ue,ae);else if(fe=E(U),typeof fe=="function")for(U=fe.call(U),fe=0;!(J=U.next()).done;)J=J.value,ue=he+re(J,fe++),ie+=M(J,B,C,ue,ae);else if(ue==="object"){if(typeof U.then=="function")return M(ee(U),B,C,J,ae);throw B=String(U),Error("Objects are not valid as a React child (found: "+(B==="[object Object]"?"object with keys {"+Object.keys(U).join(", ")+"}":B)+"). If you meant to render a collection of children, use an array instead."Line truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+qe+n+dt}var qn=!1;function Yn(n,i){if(!n||qn)return"";qn=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var c={DetermineComponentFrameRoot:function(){try{if(i){var oe=function(){throw Error()};if(Object.defineProperty(oe.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(oe,[])}catch(ee){var X=ee}Reflect.construct(n,[],oe)}else{try{oe.call()}catch(ee){X=ee}n.call(oe.prototype)}}else{try{throw Error()}catch(ee){X=ee}(oe=n())&&typeof oe.catch=="function"&&oe.catch(function(){})}}catch(ee){if(ee&&X&&typeof ee.stack=="string")return[ee.stack,X.stack]}return[null,null]}};c.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var m=Object.getOwnPropertyDescriptor(c.DetermineComponentFrameRoot,"name");m&&m.configurable&&Object.defineProperty(c.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var y=c.DetermineComponentFrameRoot(),x=y[0],N=y[1];if(x&&N){var B=x.split(`
`),Y=N.split(`
`);for(m=c=0;c<B.length&&!B[c].includes("DetermineComponentFrameRoot");)c++;for(;m<Y.length&&!Y[m].includes("DetermineComponentFrameRoot");)m++;if(c===B.length||m===Y.length)for(c=B.length-1,m=Y.length-1;1<=c&&0<=m&&B[c]!==Y[m];)m--;for(;1<=c&&0<=m;c--,m--)if(B[c]!==Y[m]){if(c!==1||m!==1)do if(c--,m--,0>m||B[c]!==Y[m]){var re=`
`+B[c].replace(" at new "," at ");return n.displayName&&re.includes("<anonymous>")&&(re=re.replace("<anonymous>",n.displayName)),re}while(1<=c&&0<=m);break}}}finally{qn=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?st(l):""}function Cr(n,i){switch(n.tag){case 26:case 27:case 5:return st(n.type);case 16:return st("Lazy");case 13:return n.child!==i&&i!==null?st("Suspense Fallback"):st("Suspense");case 19:return st("SuspenseList");case 0:case 15:return Yn(n.type,!1);case 11:return Yn(n.type.render,!1);case 1:return Yn(n.type,!0);case 31:return st("Activity");default:return""}}function ql(n){try{var i="",l=null;do i+=Cr(n,l),l=n,n=n.return;while(n);return i}catch(c){return`
Error generating stack: `+c.message+`
`+c.stack}}var Wa=Object.prototype.hasOwnProperty,Ka=e.unstable_scheduleCallback,po=e.unstable_cancelCallback,Hc=e.unstable_shouldYield,Vc=e.unstable_requestPaint,zt=e.unstable_now,qc=e.unstable_getCurrentPriorityLevel,ae=e.unstable_ImmediatePriority,pe=e.unstable_UserBlockingPriority,ke=e.unstable_NormalPriority,Me=e.unstable_LowPriority,We=e.unstable_IdlePriority,nn=e.log,or=e.unstable_setDisableYieldValue,Pt=null,vt=null;function Vt(n){if(typeof nn=="function"&&or(n),vt&&typeof vt.setStrictMode=="function")try{vt.setStrictMode(Pt,n)}catch{}}var tt=Math.clz32?Math.clz32:xO,Or=Math.log,In=Math.LN2;function xO(n){return n>>>=0,n===0?32:31-(Or(n)/In|0)|0}var Yl=256,Wl=262144,Kl=4194304;function ma(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function Xl(n,i,l){var c=n.pendingLanes;if(c===0)return 0;var m=0,y=n.suspendedLanes,x=n.pingedLanes;n=n.warmLanes;var N=c&134217727;return N!==0?(c=N&~y,c!==0?m=ma(c):(x&=N,x!==0?m=ma(x):l||(l=N&~n,l!==0&&(m=ma(l))))):(N=c&~y,N!==0?m=ma(N):x!==0?m=ma(x):l||(l=c&~n,l!==0&&(m=ma(l)))),m===0?0:i!==0&&i!==m&&(i&y)===0&&(y=m&-m,l=i&-i,y>=l||y===32&&(l&4194048)!==0)?i:m}function fo(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function wO(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uv(){var n=Kl;return Kl<<=1,(Kl&62914560)===0&&(Kl=4194304),n}function Yc(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function go(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function TO(n,i,l,c,m,y){var x=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var N=n.entanglements,B=n.expirationTimes,Y=n.hiddenUpdates;for(l=x&~l;0<l;){var re=31-tt(l),oe=1<<re;N[re]=0,B[re]=-1;var X=Y[re];if(X!==null)for(Y[re]=null,re=0;re<X.length;re++){var ee=X[re];ee!==null&&(ee.lane&=-536870913)}l&=~oe}c!==0&&Fv(n,c,0),y!==0&&m===0&&n.tag!==0&&(n.suspendedLanes|=y&~(x&~i))}function Fv(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var c=31-tt(i);n.entangledLanes|=i,n.entanglements[c]=n.entanglements[c]|1073741824|l&261930}function Bv(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var c=31-tt(l),m=1<<c;m&i|n[c]&i&&(n[c]|=i),l&=~m}}function zv(n,i){var l=i&-i;return l=(l&42)!==0?1:Wc(l),(l&(n.suspendedLanes|i))!==0?0:l}function Wc(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function Kc(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Pv(){var n=W.p;return n!==0?n:(n=window.event,n===void 0?32:kT(n.type))}function $v(n,i){var l=W.p;try{return W.p=n,i()}finally{W.p=l}}var Ir=Math.random().toString(36).slice(2),kt="__reactFiber$"+Ir,qt="__reactProps$"+Ir,Xa="__reactContainer$"+Ir,Xc="__reactEvents$"+Ir,AO="__reactListeners$"+Ir,kO="__reactHandles$"+Ir,Gv="__reactResources$"+Ir,mo="__reactMarker$"+Ir;function Zc(n){delete n[kt],delete n[qt],delete n[Xc],delete n[AO],delete n[kO]}function Za(n){var i=n[kt];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Xa]||l[kt]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=cT(n);n!==null;){if(l=n[kt])return l;n=cT(n)}return i}n=l,l=n.parentNode}return null}function Qa(n){if(n=n[kt]||n[Xa]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function ho(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Ja(n){var i=n[Gv];return i||(i=n[Gv]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[mo]=!0}var Hv=new Set,Vv={};function ha(n,i){ei(n,i),ei(n+"Capture",i)}function ei(n,i){for(Vv[n]=i,n=0;n<i.length;n++)Hv.add(i[n])}var _O=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u20 Line truncated
`).replace(a1,"")}function eT(n,i){return i=Jw(i),Jw(n)===i}function Je(n,i,l,c,m,y){switch(l){case"children":typeof c=="string"?i==="body"||i==="textarea"&&c===""||ni(n,c):(typeof c=="number"||typeof c=="bigint")&&i!=="body"&&ni(n,""+c);break;case"className":Ql(n,"class",c);break;case"tabIndex":Ql(n,"tabindex",c);break;case"dir":case"role":case"viewBox":case"width":case"height":Ql(n,l,c);break;case"style":eS(n,c,y);break;case"data":if(i!=="object"){Ql(n,"data",c);break}case"src":case"href":if(c===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(c==null||typeof c=="function"||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"action":case"formAction":if(typeof c=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof y=="function"&&(l==="formAction"?(i!=="input"&&Je(n,i,"name",m.name,m,null),Je(n,i,"formEncType",m.formEncType,m,null),Je(n,i,"formMethod",m.formMethod,m,null),Je(n,i,"formTarget",m.formTarget,m,null)):(Je(n,i,"encType",m.encType,m,null),Je(n,i,"method",m.method,m,null),Je(n,i,"target",m.target,m,null)));if(c==null||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"onClick":c!=null&&(n.onclick=sr);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=c&&typeof c!="function"&&typeof c!="symbol";break;case"muted":n.muted=c&&typeof c!="function"&&typeof c!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(c==null||typeof c=="function"||typeof c=="boolean"||typeof c=="symbol"){n.removeAttribute("xlink:href");break}l=es(""+c),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""+c):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":c&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":c===!0?n.setAttribute(l,""):c!==!1&&c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,c):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":c!=null&&typeof c!="function"&&typeof c!="symbol"&&!isNaN(c)&&1<=c?n.setAttribute(l,c):n.removeAttribute(l);break;case"rowSpan":case"start":c==null||typeof c=="function"||typeof c=="symbol"||isNaN(c)?n.removeAttribute(l):n.setAttribute(l,c);break;case"popover":Ue("beforetoggle",n),Ue("toggle",n),Zl(n,"popover",c);break;case"xlinkActuate":lr(n,"http://www.w3.org/1999/xlink","xlink:actuate",c);break;case"xlinkArcrole":lr(n,"http://www.w3.org/1999/xlink","xlink:arcrole",c);break;case"xlinkRole":lr(n,"http://www.w3.org/1999/xlink","xlink:role",c);break;case"xlinkShow":lr(n,"http://www.w3.org/1999/xlink","xlink:show",c);break;case"xlinkTitle":lr(n,"http://www.w3.org/1999/xlink","xlink:title",c);break;case"xlinkType":lr(n,"http://www.w3.org/1999/xlink","xlink:type",c);break;case"xmlBase":lr(n,"http://www.w3.org/XML/1998/namespace","xml:base",c);break;case"xmlLang":lr(n,"http://www.w3.org/XML/1998/namespace","xml:lang",c);break;case"xmlSpace":lr(n,"http://www.w3.org/XML/1998/namespace","xml:space",c);break;case"is":Zl(n,"is",c);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=IO.get(l)||l,Zl(n,l,c))}}function Pp(n,i,l,c,m,y){switch(l){case"style":eS(n,c,y);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof c=="string"?ni(n,c):(typeof c=="number"||typeof c=="bigint")&&ni(n,""+c);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"onClick":c!=null&&(n.onclick=sr);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
`,f="/",h="*",b="",v="comment",E="declaration";function w(k,A){if(typeof k!="string")throw new TypeError("First argument must be a string");if(!k)return[];A=A||{};var _=1,R=1;function L(te){var Q=te.match(t);Q&&(_+=Q.length);var P=te.lastIndexOf(p);R=~P?te.length-P:R+te.length}function j(){var te={line:_,column:R};return function(Q){return Q.position=new D(te),V(),Q}}function D(te){this.start=te,this.end={line:_,column:R},this.source=A.source}D.prototype.content=k;function z(te){var Q=new Error(A.source+":"+_+":"+R+": "+te);if(Q.reason=te,Q.filename=A.source,Q.line=_,Q.column=R,Q.source=k,!A.silent)throw Q}function F(te){var Q=te.exec(k);if(Q){var P=Q[0];return L(P),k=k.slice(P.length),Q}}function V(){F(r)}function M(te){var Q;for(te=te||[];Q=H();)Q!==!1&&te.push(Q);return te}function H(){var te=j();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var Q=2;b!=k.charAt(Q)&&(h!=k.charAt(Q)||f!=k.charAt(Q+1));)++Q;if(Q+=2,b===k.charAt(Q-1))return z("End of comment missing");var P=k.slice(2,Q-2);return R+=2,L(P),k=k.slice(Q),R+=2,te({type:v,comment:P})}}function K(){var te=j(),Q=F(a);if(Q){if(H(),!F(o))return z("property missing ':'");var P=F(s),W=te({type:E,property:T(Q[0].replace(e,b)),value:P?T(P[0].replace(e,b)):b});return F(u),W}}function Z(){var te=[];M(te);for(var Q;Q=K();)Q!==!1&&(te.push(Q),M(te));return te}return V(),Z()}function T(k){return k?k.replace(d,b):b}return mf=w,mf}var iA;function VD(){if(iA)return Di;iA=1;var e=Di&&Di.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Di,"__esModule",{value:!0}),Di.default=r;const t=e(HD());function r(a,o){let s=null;if(!a||typeof a!="string")return s;const u=(0,t.default)(a),d=typeof o=="function";return u.forEach(p=>{if(p.type!=="declaration")return;const{property:f,value:h}=p;d?o(f,h,p):h&&(s=s||{},s[f]=h)}),s}return Di}var ll={},oA;function qD(){if(oA)return ll;oA=1,Object.defineProperty(ll,"__esModule",{value:!0}),ll.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,o=/^-(ms)-/,s=function(f){return!f||r.test(f)||e.test(f)},u=function(f,h){return h.toUpperCase()},d=function(f,h){return"".concat(h,"-")},p=function(f,h){return h===void 0&&(h={}),s(f)?f:(f=f.toLowerCase(),h.reactCompat?f=f.replace(o,d):f=f.replace(a,d),f.replace(t,u))};return ll.camelCase=p,ll}var sl,lA;function YD(){if(lA)return sl;lA=1;var e=sl&&sl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e(VD()),r=qD();function a(o,s){var u={};return!o||typeof o!="string"||(0,t.default)(o,function(d,p){d&&p&&(u[(0,r.camelCase)(d,s)]=p)}),u}return a.default=a,sl=a,sl}var WD=YD();const KD=Qu(WD),M_=j_("end"),jg=j_("start");function j_(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function XD(e){const t=jg(e),r=M_(e);if(t&&r)return{start:t,end:r}}function gl(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?sA(e.position):"start"in e||"end"in e?sA(e):"line"in e||"column"in e?Kf(e):""}function Kf(e){return uA(e&&e.line)+":"+uA(e&&e.column)}function sA(e){return Kf(e&&e.start)+"-"+Kf(e&&e.end)}function uA(e){return e&&typeof e=="number"?e:1}class Ut extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let o="",s={},u=!1;if(r&&("line"in r&&"column"in r?s={place:r}:"start"in r&&"end"in r?s={place:r}:"type"in r?s={ancestors:[r],place:r.position}:s={...r}),typeof t=="string"?o=t:!s.cause&&t&&(u=!0,o=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof a=="string"){const p=a.indexOf(":");p===-1?s.ruleId=a:(s.source=a.slice(0,p),s.ruleId=a.slice(p+1))}if(!s.place&&s.ancestors&&s.ancestors){const p=s.ancestors[s.ancestors.length-1];p&&(s.place=p.position)}const d=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=o,this.line=d?d.line:void 0,this.name=gl(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=u&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Ut.prototype.file="";Ut.prototype.name="";Ut.prototype.reason="";Ut.prototype.message="";Ut.prototype.stack="";Ut.prototype.column=void 0;Ut.prototype.line=void 0;Ut.prototype.ancestors=void 0;Ut.prototype.cause=void 0;Ut.prototype.fatal=void 0;Ut.prototype.place=void 0;Ut.prototype.ruleId=void 0;Ut.prototype.source=void 0;const Ug={}.hasOwnProperty,ZD=new Map,QD=/[A-Z]/g,JD=new Set(["table","tbody","thead","tfoot","tr"]),eM=new Set(["td","th"]),U_="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function tM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if( Line truncated
`;break}case-3:{u=`\r
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function uj(e){const a={constructs:$_([ij,...(e||{}).extensions||[]]),content:o(TM),defined:[],document:o(kM),flow:o(H2),lazy:{},string:o(Y2),text:o(W2)};return a;function o(s){return u;function u(d){return oj(a,s,d)}}}function cj(e){for(;!Y_(e););return e}const EA=/[\0\t\n\r]/g;function dj(){let e=1,t="",r=!0,a;return o;function o(s,u,d){const p=[];let f,h,b,v,E;for(s=t+(typeof s=="string"?s.toString():new TextDecoder(u||void 0).decode(s)),b=0,t="",r&&(s.charCodeAt(0)===65279&&b++,r=void 0);b<s.length;){if(EA.lastIndex=b,f=EA.exec(s),v=f&&f.index!==void 0?f.index:s.length,E=s.charCodeAt(v),!f){t=s.slice(b);break}if(E===10&&b===v&&a)p.push(-3),a=void 0;else switch(a&&(p.push(-5),a=void 0),b<v&&(p.push(s.slice(b,v)),e+=v-b),E){case 0:{p.push(65533),e++;break}case 9:{for(h=Math.ceil(e/4)*4,p.push(-2);e++<h;)p.push(-1);break}case 10:{p.push(-4),e=1;break}default:a=!0,e=1}b=v+1}return d&&(a&&p.push(-5),t&&p.push(t),p.push(null)),p}}const pj=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function fj(e){return e.replace(pj,gj)}function gj(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return G_(r.slice(s?2:1),s?16:10)}return xl(r)||e}const J_={}.hasOwnProperty;function mj(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),hj(r)(cj(uj(r).document().write(dj()(e,t,!0))))}function hj(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Wa),autolinkProtocol:V,autolinkEmail:V,atxHeading:s(qn),blockQuote:s(Re),characterEscape:V,characterReference:V,codeFenced:s(Oe),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Oe,u),codeText:s(qe,u),codeTextData:V,data:V,codeFlowValue:V,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Yn),hardBreakTrailing:s(Yn),htmlFlow:s(Cr,u),htmlFlowData:V,htmlText:s(Cr,u),htmlTextData:V,image:s(ql),label:u,link:s(Wa),listItem:s(po),listItemValue:v,listOrdered:s(Ka,b),listUnordered:s(Ka),paragraph:s(Hc),reference:I,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(qn),strong:s(Vc),thematicBreak:s(qc)},exit:{atxHeading:p(),atxHeadingSequence:j,autolink:p(),autolinkEmail:me,autolinkProtocol:we,blockQuote:p(),characterEscapeValue:M,characterReferenceMarkerHexadecimal:fe,characterReferenceMarkerNumeric:fe,characterReferenceValue:de,characterReference:le,codeFenced:p(k),codeFencedFence:T,codeFencedFenceInfo:E,codeFencedFenceMeta:w,codeFlowValue:M,codeIndented:p(A),codeText:p(Q),codeTextData:M,data:M,definition:p(),definitionDestinationString:L,definitionLabelString:_,definitionTitleString:R,emphasis:p(),hardBreakEscape:p(K),hardBreakTrailing:p(K),htmlFlow:p(Z),htmlFlowData:M,htmlText:p(te),htmlTextData:M,image:p(W),label:ce,labelText:ne,lineEnding:H,link:p(P),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:ue,resourceDestinationString:C,resourceTitleString:U,resource:J,setextHeading:p(F),setextHeadingLineSequence:z,setextHeadingText:D,strong:p(),thematicBreak:p()}};eN(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ae){let pe={type:"root",children:[]};const ke={stack:[pe],tokenStack:[],config:t,enter:d,exit:f,buffer:u,resume:h,data:r},Me=[];let We=-1;for(;++We<ae.length;)if(ae[We][1].type==="listOrdered"||ae[We][1].type==="listUnordered")if(ae[We][0]==="enter")Me.push(We);else{const nn=Me.pop();We=o(ae,nn,We)}for(We=-1;++We<ae.length;){const nn=t[ae[We][0]];J_.call(nn,ae[We][1].type)&&nn[ae[We][1].type].call(Object.assign({sliceSerialize:ae[We][2].sliceSerialize},ke),ae[We][1])}if(ke.tokenStack.length>0){const nn=ke.tokenStack[ke.tokenStack.length-1];(nn[1]||vA).call(ke,void 0,nn[0])}for(pe.position={start:aa(ae.length>0?ae[0][1].start:{line:1,column:1,offset:0}),end:aa(ae.length>0?ae[ae.length-2][1].end:{line:1,column:1,offset:0})},We=-1;++We<t.transforms.length;)pe=t.transforms[We](pe)||pe;return pe}function o(ae,pe,ke){let Me=pe-1,We=-1,nn=!1,or,Pt,vt,Vt;for(;++Me<=ke;){const tt=ae[Me];switch(tt[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{tt[0]==="enter"?We++:We--,Vt=void 0;break}case"lineEndingBlank":{tt[0]==="enter"&&(or&&!Vt&&!We&&!vt&&(vt=Me),Vt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Vt=void 0}if(!We&&tt[0]==="enter"&&tt[1].type==="listItemPrefix"||We===-1&&tt[0]==="exit"&&(tt[1].type==="listUnordered"||tt[1].type==="listOrdered")){if(or){let Or=Me;for(Pt=void 0;Or--;){const In=ae[Or];if(In[1].type==="lineEnding"||In[1].type==="lineEndingBlank"){if(In[0]==="exit")continue;Pt&&(ae[Pt][1].type="lineEndingBlank",nn=!0),In[1].type="lineEnding",Pt=Or}else if(!(In[1].type==="linePrefix"||In[1].type==="blockQuotePrefix"||In[1].type==="blockQuotePrefixWhitespLine truncated
`}]}function Sj(e,t){const r=t.value?t.value+`
`:"",a={},o=t.lang?t.lang.split(/\s+/):[];o.length>0&&(a.className=["language-"+o[0]]);let s={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(s.data={meta:t.meta}),e.patch(t,s),s=e.applyData(t,s),s={type:"element",tagName:"pre",properties:{},children:[s]},e.patch(t,s),s}function xj(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wj(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Tj(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),o=Ji(a.toLowerCase()),s=e.footnoteOrder.indexOf(a);let u,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),u=e.footnoteOrder.length):u=s+1,d+=1,e.footnoteCounts.set(a,d);const p={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+o,id:r+"fnref-"+o+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(u)}]};e.patch(t,p);const f={type:"element",tagName:"sup",properties:{},children:[p]};return e.patch(t,f),e.applyData(t,f)}function Aj(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function kj(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function tN(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const o=e.all(t),s=o[0];s&&s.type==="text"?s.value="["+s.value:o.unshift({type:"text",value:"["});const u=o[o.length-1];return u&&u.type==="text"?u.value+=a:o.push({type:"text",value:a}),o}function _j(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={src:Ji(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"img",properties:o,children:[]};return e.patch(t,s),e.applyData(t,s)}function Nj(e,t){const r={src:Ji(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function Rj(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function Cj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={href:Ji(a.url||"")};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"a",properties:o,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function Oj(e,t){const r={href:Ji(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Ij(e,t,r){const a=e.all(t),o=r?Lj(r):nN(t),s={},u=[];if(typeof t.checked=="boolean"){const h=a[0];let b;h&&h.type==="element"&&h.tagName==="p"?b=h:(b={type:"element",tagName:"p",properties:{},children:[]},a.unshift(b)),b.children.length>0&&b.children.unshift({type:"text",value:" "}),b.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const h=a[d];(o||d!==0||h.type!=="element"||h.tagName!=="p")&&u.push({type:"text",value:`
`}),h.type==="element"&&h.tagName==="p"&&!o?u.push(...h.children):u.push(h)}const p=a[a.length-1];p&&(o||p.type!=="element"||p.tagName!=="p")&&u.push({type:"text",value:`
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function Lj(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=nN(r[a])}return t}function nN(e){const t=e.spread;return t??e.children.length>1}function Dj(e,t){const r={},a=e.all(t);let o=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++o<a.length;){const u=a[o];if(u.type==="element"&&u.tagName==="li"&&u.properties&&Array.isArray(u.properties.className)&&u.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const s={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,s),e.applyData(t,s)}function Mj(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function jj(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function Uj(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Fj(e,t){const r=e.all(t),a=r.shift(),o=[];if(a){const u={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],u),o.push(u)}if(r.length>0){const u={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=jg(t.children[1]),p=M_(t.children[t.children.length-1]);d&&p&&(u.position={start:d,end:p}),o.push(u)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(o,!0)};return e.patch(t,s),e.applyData(t,s)}function Bj(e,t,r){const a=r?r.children:void 0,s=(a?a.indexOf(t):1)===0?"th":"td",u=r&&r.type==="table"?r.align:void 0,d=u?u.length:t.children.length;let p=-1;const f=[];for(;++p<d;){const b=t.children[p],v={},E=u?u[p]:void 0;E&&(v.align=E);let w={type:"element",tagName:s,properties:v,children:[]};b&&(w.children=e.all(b),e.patch(b,w),w=e.applyData(b,w)),f.push(w)}const h={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,h),e.applyData(t,h)}function zj(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const SA=9,xA=32;function Pj(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(wA(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(wA(t.slice(o),o>0,!1)),s.join("")}function wA(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===SA||s===xA;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===SA||s===xA;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function $j(e,t){const r={type:"text",value:Pj(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function Gj(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const Hj={blockquote:Ej,break:vj,code:Sj,delete:xj,emphasis:wj,footnoteReference:Tj,heading:Aj,html:kj,imageReference:_j,image:Nj,inlineCode:Rj,linkReference:Cj,link:Oj,listItem:Ij,list:Dj,paragraph:Mj,root:jj,strong:Uj,table:Fj,tableCell:zj,tableRow:Bj,text:$j,thematicBreak:Gj,toml:fu,yaml:fu,definition:fu,footnoteDefinition:fu};function fu(){}const rN=-1,ic=0,hl=1,zu=2,$g=3,Gg=4,Hg=5,Vg=6,aN=7,iN=8,TA=typeof self=="object"?self:globalThis,Vj=(e,t)=>{const r=(o,s)=>(e.set(s,o),o),a=o=>{if(e.has(o))return e.get(o);const[s,u]=t[o];switch(s){case ic:case rN:return r(u,o);case hl:{const d=r([],o);for(const p of u)d.push(a(p));return d}case zu:{const d=r({},o);for(const[p,f]of u)d[a(p)]=a(f);return d}case $g:return r(new Date(u),o);case Gg:{const{source:d,flags:p}=u;return r(new RegExp(d,p),o)}case Hg:{const d=r(new Map,o);for(const[p,f]of u)d.set(a(p),a(f));return d}case Vg:{const d=r(new Set,o);for(const p of u)d.add(a(p));return d}case aN:{const{name:d,message:p}=u;return r(new TA[d](p),o)}case iN:return r(BigInt(u),o);case"BigInt":return r(Object(BigInt(u)),o);case"ArrayBuffer":return r(new Uint8Array(u).buffer,u);case"DataView":{const{buffer:d}=new Uint8Array(u);return r(new DataView(d),u)}}return r(new TA[s](u),o)};return a},AA=e=>Vj(new Map,e)(0),Mi="",{toString:qj}={},{keys:Yj}=Object,cl=e=>{const t=typeof e;if(t!=="object"||!e)return[ic,t];const r=qj.call(e).slice(8,-1);switch(r){case"Array":return[hl,Mi];case"Object":return[zu,Mi];case"Date":return[$g,Mi];case"RegExp":return[Gg,Mi];case"Map":return[Hg,Mi];case"Set":return[Vg,Mi];case"DataView":return[hl,r]}return r.includes("Array")?[hl,r]:r.includes("Error")?[aN,r]:[zu,r]},gu=([e,t])=>e===ic&&(t==="function"||t==="symbol"),Wj=(e,t,r,a)=>{const o=(u,d)=>{const p=a.push(u)-1;return r.set(d,p),p},s=u=>{if(r.has(u))return r.get(u);let[d,p]=cl(u);switch(d){case ic:{let h=u;switch(p){case"bigint":d=iN,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([rN],u)}return o([d,h],u)}case hl:{if(p){let v=u;return p==="DataView"?v=new Uint8Array(u.buffer):p==="ArrayBuffer"&&(v=new Uint8Array( Line truncated
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
`}]}}const oc=(function(e){if(e==null)return tU;if(typeof e=="function")return lc(e);if(typeof e=="object")return Array.isArray(e)?Qj(e):Jj(e);if(typeof e=="string")return eU(e);throw new Error("Expected function, string, or object as test")});function Qj(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=oc(e[r]);return lc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function Jj(e){const t=e;return lc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function eU(e){return lc(t);function t(r){return r&&r.type===e}}function lc(e){return t;function t(r,a,o){return!!(nU(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function tU(){return!0}function nU(e){return e!==null&&typeof e=="object"&&"type"in e}const oN=[],rU=!0,Qf=!1,aU="skip";function lN(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=oc(o),u=a?-1:1;d(e,void 0,[])();function d(p,f,h){const b=p&&typeof p=="object"?p:{};if(typeof b.type=="string"){const E=typeof b.tagName=="string"?b.tagName:typeof b.name=="string"?b.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(p.type+(E?"<"+E+">":""))+")"})}return v;function v(){let E=oN,w,T,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=iU(r(p,h)),E[0]===Qf))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==aU)for(T=(a?A.children.length:-1)+u,k=h.concat(A);T>-1&&T<A.children.length;){const _=A.children[T];if(w=d(_,T,k)(),w[0]===Qf)return w;T=typeof w[1]=="number"?w[1]:T+u}}return E}}}function iU(e){return Array.isArray(e)?e:typeof e=="number"?[rU,e]:e==null?oN:[e]}function qg(e,t,r,a){let o,s,u;typeof t=="function"&&typeof r!="function"?(s=void 0,u=t,o=r):(s=t,u=r,o=a),lN(e,s,d,o);function d(p,f){const h=f[f.length-1],b=h?h.children.indexOf(p):void 0;return u(p,b,h)}}const Jf={}.hasOwnProperty,oU={};function lU(e,t){const r=t||oU,a=new Map,o=new Map,s=new Map,u={...Hj,...r.handlers},d={all:f,applyData:uU,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:sU,wrap:dU};return qg(e,function(h){if(h.type==="definition"||h.type==="footnoteDefinition"){const b=h.type==="definition"?a:o,v=String(h.identifier).toUpperCase();b.has(v)||b.set(v,h)}}),d;function p(h,b){const v=h.type,E=d.handlers[v];if(Jf.call(d.handlers,v)&&E)return E(d,h,b);if(d.options.passThrough&&d.options.passThrough.includes(v)){if("children"in h){const{children:T,...k}=h,A=Pu(k);return A.children=d.all(h),A}return Pu(h)}return(d.options.unknownHandler||cU)(d,h,b)}function f(h){const b=[];if("children"in h){const v=h.children;let E=-1;for(;++E<v.length;){const w=d.one(v[E],h);if(w){if(E&&v[E-1].type==="break"&&(!Array.isArray(w)&&w.type==="text"&&(w.value=_A(w.value)),!Array.isArray(w)&&w.type==="element")){const T=w.children[0];T&&T.type==="text"&&(T.value=_A(T.value))}Array.isArray(w)?b.push(...w):b.push(w)}}}return b}}function sU(e,t){e.position&&(t.position=XD(e))}function uU(e,t){let r=t;if(e&&e.data){const a=e.data.hName,o=e.data.hChildren,s=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const u="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:u}}r.type==="element"&&s&&Object.assign(r.properties,Pu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function cU(e,t){const r=t.data||{},a="value"in t&&!(Jf.call(r,"hProperties")||Jf.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function dU(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
`});++a<e.length;)a&&r.push({type:"text",value:`
`}),r.push(e[a]);return t&&e.length>0&&r.push({type:"text",value:`
`}),r}function _A(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function NA(e,t){const r=lU(e,t),a=r.one(e,void 0),o=Zj(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
`},o),s}function pU(e,t){return e&&"run"in e?async function(r,a){const o=NA(r,{file:a,...t});await e.run(o,a)}:function(r,a){return NA(r,{file:a,...e||t})}}function RA(e){if(e)throw e}var Ef,CA;function fU(){if(CA)return Ef;CA=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},s=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var h=e.call(f,"constructor"),b=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!h&&!b)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},u=function(f,h){r&&h.name==="__proto__"?r(f,h.name,{enumerable:!0,configurable:!0,value:h.newValue,writable:!0}):f[h.name]=h.newValue},d=function(f,h){if(h==="__proto__")if(e.call(f,h)){if(a)return a(f,h).value}else return;return f[h]};return Ef=function p(){var f,h,b,v,E,w,T=arguments[0],k=1,A=arguments.length,_=!1;for(typeof T=="boolean"&&(_=T,T=arguments[1]||{},k=2),(T==null||typeof T!="object"&&typeof T!="function")&&(T={});k<A;++k)if(f=arguments[k],f!=null)for(h in f)b=d(T,h),v=d(f,h),T!==v&&(_&&v&&(s(v)||(E=o(v)))?(E?(E=!1,w=b&&o(b)?b:[]):w=b&&s(b)?b:{},u(T,{name:h,newValue:p(_,w,v)})):typeof v<"u"&&u(T,{name:h,newValue:v}));return T},Ef}var gU=fU();const vf=Qu(gU);function eg(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function mU(){const e=[],t={run:r,use:a};return t;function r(...o){let s=-1;const u=o.pop();if(typeof u!="function")throw new TypeError("Expected function as last argument, not "+u);d(null,...o);function d(p,...f){const h=e[++s];let b=-1;if(p){u(p);return}for(;++b<o.length;)(f[b]===null||f[b]===void 0)&&(f[b]=o[b]);o=f,h?hU(h,d)(...f):u(null,...f)}}function a(o){if(typeof o!="function")throw new TypeError("Expected `middelware` to be a function, not "+o);return e.push(o),t}}function hU(e,t){let r;return a;function a(...u){const d=e.length>u.length;let p;d&&u.push(o);try{p=e.apply(this,u)}catch(f){const h=f;if(d&&r)throw h;return o(h)}d||(p&&p.then&&typeof p.then=="function"?p.then(s,o):p instanceof Error?o(p):s(p))}function o(u,...d){r||(r=!0,t(u,...d))}function s(u){o(null,u)}}const Qn={basename:bU,dirname:yU,extname:EU,join:vU,sep:"/"};function bU(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Ol(e);let r=0,a=-1,o=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else a<0&&(s=!0,a=o+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let u=-1,d=t.length-1;for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else u<0&&(s=!0,u=o+1),d>-1&&(e.codePointAt(o)===t.codePointAt(d--)?d<0&&(a=o):(d=-1,a=u));return r===a?a=u:a<0&&(a=e.length),e.slice(r,a)}function yU(e){if(Ol(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function EU(e){Ol(e);let t=e.length,r=-1,a=0,o=-1,s=0,u;for(;t--;){const d=e.codePointAt(t);if(d===47){if(u){a=t+1;break}continue}r<0&&(u=!0,r=t+1),d===46?o<0?o=t:s!==1&&(s=1):o>-1&&(s=-1)}return o<0||r<0||s===0||s===1&&o===r-1&&o===a+1?"":e.slice(o,r)}function vU(...e){let t=-1,r;for(;++t<e.length;)Ol(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":SU(r)}function SU(e){Ol(e);const t=e.codePointAt(0)===47;let r=xU(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function xU(e,t){let r="",a=0,o=-1,s=0,u=-1,d,p;for(;++u<=e.length;){if(u<e.length)d=e.codePointAt(u);else{if(d===47)break;d=47}if(d===47){if(!(o===u-1||s===1))if(o!==u-1&&s===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(p=r.lastIndexOf("/"),p!==r.length-1){p<0?(r="",a=0):(r=r.slice(0,p),a=r.length-1-r.lastIndexOf("/")),o=u,s=0;continue}}else if(r.length>0){r="",a=0,o=u,s=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(o+1,u):r=e.slice(o+1,u),a=u-o-1;o=u,s=0}else d===46&&s>-1?s++:s=-1}return r}function Ol(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const wU={cwd:TU};function TU(){return"/"}function tg(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function AU(e){if(typeof e=="string")e=new URL(e);else if(!tg(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return kU(e)}function kU(e){if(e.hostname!==""){consLine truncated
`:" ")+s.indentLines(s.containerFlow(a,d.current()),t?pN:mF))),f(),p}}function mF(e,t,r){return t===0?e:pN(e,t,r)}function pN(e,t,r){return(r?"":" ")+e}const hF=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];fN.peek=SF;function bF(){return{canContainEols:["delete"],enter:{strikethrough:EF},exit:{strikethrough:vF}}}function yF(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:hF}],handlers:{delete:fN}}}function EF(e){this.enter({type:"delete",children:[]},e)}function vF(e){this.exit(e)}function fN(e,t,r,a){const o=r.createTracker(a),s=r.enter("strikethrough");let u=o.move("~~");return u+=r.containerPhrasing(e,{...o.current(),before:u,after:"~"}),u+=o.move("~~"),s(),u}function SF(){return"~"}function xF(e){return e.length}function wF(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||xF,s=[],u=[],d=[],p=[];let f=0,h=-1;for(;++h<e.length;){const T=[],k=[];let A=-1;for(e[h].length>f&&(f=e[h].length);++A<e[h].length;){const _=TF(e[h][A]);if(r.alignDelimiters!==!1){const R=o(_);k[A]=R,(p[A]===void 0||R>p[A])&&(p[A]=R)}T.push(_)}u[h]=T,d[h]=k}let b=-1;if(typeof a=="object"&&"length"in a)for(;++b<f;)s[b]=UA(a[b]);else{const T=UA(a);for(;++b<f;)s[b]=T}b=-1;const v=[],E=[];for(;++b<f;){const T=s[b];let k="",A="";T===99?(k=":",A=":"):T===108?k=":":T===114&&(A=":");let _=r.alignDelimiters===!1?1:Math.max(1,p[b]-k.length-A.length);const R=k+"-".repeat(_)+A;r.alignDelimiters!==!1&&(_=k.length+_+A.length,_>p[b]&&(p[b]=_),E[b]=_),v[b]=R}u.splice(1,0,v),d.splice(1,0,E),h=-1;const w=[];for(;++h<u.length;){const T=u[h],k=d[h];b=-1;const A=[];for(;++b<f;){const _=T[b]||"";let R="",L="";if(r.alignDelimiters!==!1){const j=p[b]-(k[b]||0),D=s[b];D===114?R=" ".repeat(j):D===99?j%2?(R=" ".repeat(j/2+.5),L=" ".repeat(j/2-.5)):(R=" ".repeat(j/2),L=R):L=" ".repeat(j)}r.delimiterStart!==!1&&!b&&A.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&_==="")&&(r.delimiterStart!==!1||b)&&A.push(" "),r.alignDelimiters!==!1&&A.push(R),A.push(_),r.alignDelimiters!==!1&&A.push(L),r.padding!==!1&&A.push(" "),(r.delimiterEnd!==!1||b!==f-1)&&A.push("|")}w.push(r.delimiterEnd===!1?A.join("").replace(/ +$/,""):A.join(""))}return w.join(`
`)}function TF(e){return e==null?"":String(e)}function UA(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function AF(e,t,r,a){const o=r.enter("blockquote"),s=r.createTracker(a);s.move("> "),s.shift(2);const u=r.indentLines(r.containerFlow(e,s.current()),kF);return o(),u}function kF(e,t,r){return">"+(r?"":" ")+e}function _F(e,t){return FA(e,t.inConstruct,!0)&&!FA(e,t.notInConstruct,!1)}function FA(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function BA(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
`&&_F(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
`}function NF(e,t){const r=String(e);let a=r.indexOf(t),o=a,s=0,u=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===o?++s>u&&(u=s):s=1,o=a+t.length,a=r.indexOf(t,o);return u}function RF(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function CF(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function OF(e,t,r,a){const o=CF(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(RF(e,r)){const b=r.enter("codeIndented"),v=r.indentLines(s,IF);return b(),v}const d=r.createTracker(a),p=o.repeat(Math.max(NF(s,o)+1,3)),f=r.enter("codeFenced");let h=d.move(p);if(e.lang){const b=r.enter(`codeFencedLang${u}`);h+=d.move(r.safe(e.lang,{before:h,after:" ",encode:["`"],...d.current()})),b()}if(e.lang&&e.meta){const b=r.enter(`codeFencedMeta${u}`);h+=d.move(" "),h+=d.move(r.safe(e.meta,{before:h,after:`
`,encode:["`"],...d.current()})),b()}return h+=d.move(`
`),s&&(h+=d.move(s+`
`)),h+=d.move(p),f(),h}function IF(e,t,r){return(r?"":" ")+e}function Wg(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function LF(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("definition");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("[");return f+=p.move(r.safe(r.associationId(e),{before:f,after:"]",...p.current()})),f+=p.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":`
`,...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),u(),f}function DF(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function wl(e){return"&#x"+e.toString(16).toUpperCase()+";"}function $u(e,t,r){const a=Wi(e),o=Wi(t);return a===void 0?o===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}gN.peek=MF;function gN(e,t,r,a){const o=DF(r),s=r.enter("emphasis"),u=r.createTracker(a),d=u.move(o);let p=u.move(r.containerPhrasing(e,{after:o,before:d,...u.current()}));const f=p.charCodeAt(0),h=$u(a.before.charCodeAt(a.before.length-1),f,o);h.inside&&(p=wl(f)+p.slice(1));const b=p.charCodeAt(p.length-1),v=$u(a.after.charCodeAt(0),b,o);v.inside&&(p=p.slice(0,-1)+wl(b));const E=u.move(o);return s(),r.attentionEncodeSurroundingInfo={after:v.outside,before:h.outside},d+p+E}function MF(e,t,r){return r.options.emphasis||"*"}function jF(e,t){let r=!1;return qg(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Qf}),!!((!e.depth||e.depth<3)&&zg(e)&&(t.options.setext||r))}function UF(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(jF(e,r)){const h=r.enter("headingSetext"),b=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
`,after:`
`});return b(),h(),v+`
`+(o===1?"=":"-").repeat(v.length-(Math.max(v.lastIndexOf("\r"),v.lastIndexOf(`
`))+1))}const u="#".repeat(o),d=r.enter("headingAtx"),p=r.enter("phrasing");s.move(u+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
`,...s.current()});return/^[\t ]/.test(f)&&(f=wl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),d(),f}mN.peek=FF;function mN(e){return e.value||""}function FF(){return"<"}hN.peek=BF;function hN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("image");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("![");return f+=p.move(r.safe(e.alt,{before:f,after:"]",...p.current()})),f+=p.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":")",...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),f+=p.move(")"),u(),f}function BF(){return"!"}bN.peek=zF;function bN(e,t,r,a){const o=e.referenceType,s=r.enter("imageReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("![");const f=r.safe(e.alt,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function zF(){return"!"}yN.peek=PF;function yN(e,t,r){let a=e.value||"",o="`",s=-1;for(;new RegExp("(^|[^`])"+o+"([^`]|$)").test(a);)o+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++s<r.unsafe.length;){const u=r.unsafe[s],d=r.compilePattern(u);let p;if(u.atBreak)for(;p=d.exec(a);){let f=p.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(p.index+1)}}return o+a+o}function PF(){return"`"}function EN(e,t){const r=zg(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}vN.peek=$F;function vN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let d,p;if(EN(e,r)){const h=r.stack;r.stack=[],d=r.enter("autolink");let b=u.move("<");return b+=u.move(r.containerPhrasing(e,{before:b,after:">",...u.current()})),b+=u.move(">"),d(),r.stack=h,b}d=r.enter("link"),p=r.enter("label");let f=u.move("[");return f+=u.move(r.containerPhrasing(e,{before:f,after:"](",...u.current()})),f+=u.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=u.move("<"),f+=u.move(r.safe(e.url,{before:f,after:">",...u.current()})),f+=u.move(">")):(p=r.enter("destinationRaw"),f+=u.move(r.safe(e.url,{before:f,after:e.title?" ":")",...u.current()}))),p(),e.title&&(p=r.enter(`title${s}`),f+=u.move(" "+o),f+=u.move(r.safe(e.title,{before:f,after:o,...u.current()})),f+=u.move(o),p()),f+=u.move(")"),d(),f}function $F(e,t,r){return EN(e,r)?"<":"["}SN.peek=GF;function SN(e,t,r,a){const o=e.referenceType,s=r.enter("linkReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("[");const f=r.containerPhrasing(e,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function GF(){return"["}function Kg(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function HF(e){const t=Kg(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function VF(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function xN(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function qF(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?VF(r):Kg(r);const d=e.ordered?u==="."?")":".":HF(r);let p=t&&r.bulletLastUsed?u===r.bulletLastUsed:!1;if(!e.ordered){const h=e.children?e.children[0]:void 0;if((u==="*"||u==="-")&&h&&(!h.children||!h.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(p=!0),xN(r)===u&&h){let b=-1;for(;++b<e.chilLine truncated
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:v,table:u,tableCell:p,tableRow:d}};function u(E,w,T,k){return f(h(E,T,k),E.align)}function d(E,w,T,k){const A=b(E,T,k),_=f([A]);return _.slice(0,_.indexOf(`
`))}function p(E,w,T,k){const A=T.enter("tableCell"),_=T.enter("phrasing"),R=T.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),R}function f(E,w){return wF(E,{align:w,alignDelimiters:a,padding:r,stringLength:o})}function h(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("table");for(;++A<k.length;)_[A]=b(k[A],w,T);return R(),_}function b(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("tableRow");for(;++A<k.length;)_[A]=p(k[A],E,w,T);return R(),_}function v(E,w,T){let k=TN.inlineCode(E,w,T);return T.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function cB(){return{exit:{taskListCheckValueChecked:PA,taskListCheckValueUnchecked:PA,paragraph:pB}}}function dB(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:fB}}}function PA(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function pB(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const o=t.children;let s=-1,u;for(;++s<o.length;){const d=o[s];if(d.type==="paragraph"){u=d;break}}u===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function fB(e,t,r,a){const o=e.children[0],s=typeof e.checked=="boolean"&&o&&o.type==="paragraph",u="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);s&&d.move(u);let p=TN.listItem(e,t,r,{...a,...d.current()});return s&&(p=p.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),p;function f(h){return h+u}}function gB(){return[qU(),fF(),bF(),rB(),cB()]}function mB(e){return{extensions:[YU(),gF(e),yF(),uB(e),dB()]}}const hB={tokenize:xB,partial:!0},AN={tokenize:wB,partial:!0},kN={tokenize:TB,partial:!0},_N={tokenize:AB,partial:!0},bB={tokenize:kB,partial:!0},NN={name:"wwwAutolink",tokenize:vB,previous:CN},RN={name:"protocolAutolink",tokenize:SB,previous:ON},Nr={name:"emailAutolink",tokenize:EB,previous:IN},rr={};function yB(){return{text:rr}}let Da=48;for(;Da<123;)rr[Da]=Nr,Da++,Da===58?Da=65:Da===91&&(Da=97);rr[43]=Nr;rr[45]=Nr;rr[46]=Nr;rr[95]=Nr;rr[72]=[Nr,RN];rr[104]=[Nr,RN];rr[87]=[Nr,NN];rr[119]=[Nr,NN];function EB(e,t,r){const a=this;let o,s;return u;function u(b){return!ng(b)||!IN.call(a,a.previous)||Xg(a.events)?r(b):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(b))}function d(b){return ng(b)?(e.consume(b),d):b===64?(e.consume(b),p):r(b)}function p(b){return b===46?e.check(bB,h,f)(b):b===45||b===95||Dt(b)?(s=!0,e.consume(b),p):h(b)}function f(b){return e.consume(b),o=!0,p}function h(b){return s&&o&&Ht(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(b)):r(b)}}function vB(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!CN.call(a,a.previous)||Xg(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(hB,e.attempt(AN,e.attempt(kN,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function SB(e,t,r){const a=this;let o="",s=!1;return u;function u(b){return(b===72||b===104)&&ON.call(a,a.previous)&&!Xg(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),o+=String.fromCodePoint(b),e.consume(b),d):r(b)}function d(b){if(Ht(b)&&o.length<5)return o+=String.fromCodePoint(b),e.consume(b),d;if(b===58){const v=o.toLowerCase();if(v==="http"||v==="https")return e.consume(b),p}return r(b)}function p(b){return b===47?(e.consume(b),s?f:(s=!0,p)):r(b)}function f(b){return b===null||Bu(b)||rt(b)||Fa(b)||rc(b)?r(b):e.attempt(AN,e.attempt(kN,h),r)(b)}function h(b){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(b)}}function xB(e,t,r){let a=0;return o;function o(u){return(u===87||u===119)&&a<3?(a++,e.consume(u),o):u===46&&a===3?(e.consume(u),s):r(u)}function s(u){return u===null?r(u):t(u)}}function wB(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(_N,p,d)(f):f===null||rt(f)||Fa(f)||f!==45&&rc(f)?p(f):(s=!0,e.consume(f),u)}function d(f){return f===95?a=!0:(o=a,a=void 0),e.consume(f),u}function p(f){return o||a||!s?r(f):t(f)}}function TB(e,t){let r=0,a=0;return o;function o(u){return u===40?(r++,e.consume(u),o):u===41&&a<r?s(u):u===33||u===34||u===38||u===39||u===41||u===42||u===44||u===46||u===58||u===59||u===60||u===63||u===93||u===95||u===126?e.check(_N,t,s)(u):u===null||rt(u)||Fa(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function AB(e,t,r){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),s):d===93?(e.consume(d),o):d===60||d===null||rt(d)||Fa(d)?t(d):r(d)}function o(d){return d===null||d===40||d===91||rt(d)||Fa(d)?t(d):a(d)}function s(d){return Ht(d)?u(d):r(d)}function u(d){re Line truncated
`)===-1?g.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:h}):g.jsx("code",{className:"block font-mono",children:h}),pre:({children:f})=>g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:f}),table:({children:f})=>g.jsx("div",{className:"overflow-auto my-4",children:g.jsx("table",{className:"w-full border-collapse text-sm",children:f})}),thead:({children:f})=>g.jsx("thead",{className:"bg-muted/50",children:f}),tr:({children:f})=>g.jsx("tr",{className:"border-b",children:f}),th:({children:f})=>g.jsx("th",{className:"px-4 py-2 text-left font-medium",children:f}),td:({children:f})=>g.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:f}),hr:()=>g.jsx("hr",{className:"my-6 border-border"}),strong:({children:f})=>g.jsx("strong",{className:"font-semibold text-foreground",children:f}),em:({children:f})=>g.jsx("em",{children:f})};return r?g.jsx("main",{id:"main-content",className:"flex-1 p-8 max-w-3xl",children:s?g.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):a?g.jsx("article",{className:"prose max-w-none",children:g.jsx(uN,{remarkPlugins:[LN],components:p,children:a})}):g.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:g.jsxs("p",{className:"text-muted-foreground",children:["Content for ",g.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})}):g.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[g.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),g.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),g.jsx(Pi,{className:"my-8"}),g.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),g.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[g.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:g.jsx("strong",{children:"Current theme from YAML:"})}),g.jsx("pre",{className:"text-primary text-sm",children:`site:
theme:
preset: "${d?.preset||"zinc"}"
radius: "${d?.radius||"md"}"
darkMode: ${d?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(Y1).map(f=>g.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${f===(d?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:f},f))}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),g.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("span",{className:"font-medium text-primary",children:t.routes?.length||0})," routes configured."]}),g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(t.routes?.slice(0,3),null,2)})]})}const GA=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,HA=u_,Zg=(e,t)=>r=>{var a;if(t?.variants==null)return HA(e,r?.class,r?.className);const{variants:o,defaultVariants:s}=t,u=Object.keys(o).map(f=>{const h=r?.[f],b=s?.[f];if(h===null)return null;const v=GA(h)||GA(b);return o[f][v]}),d=r&&Object.entries(r).reduce((f,h)=>{let[b,v]=h;return v===void 0||(f[b]=v),f},{}),p=t==null||(a=t.compoundVariants)===null||a===void 0?void 0:a.reduce((f,h)=>{let{class:b,className:v,...E}=h;return Object.entries(E).every(w=>{let[T,k]=w;return Array.isArray(k)?k.includes({...s,...d}[T]):{...s,...d}[T]===k})?[...f,b,v]:f},[]);return HA(e,u,p,r?.class,r?.className)},DN=Zg("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-xs":"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3","icon-sm":"size-8","icon-lg":"size-10"}},defaultVariants:{variant:"default",size:"default"}});function $i({className:e,variant:t="default",size:r="default",asChild:a=!1,...o}){const s=a?Dg:"button";return g.jsx(s,{"data-slot":"button","data-variant":t,"data-size":r,className:be(DN({variant:t,size:r,className:e})),...o})}function _n({className:e,...t}){return g.jsx("div",{"data-slot":"card",className:be("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...t})}function Bn({className:e,...t}){return g.jsx("div",{"data-slot":"card-header",className:be("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",e),...t})}function zn({className:e,...t}){return g.jsx("div",{"data-slot":"card-title",className:be("leading-none font-semibold",e),...t})}function Qg({className:e,...t}){return g.jsx("div",{"data-slot":"card-description",className:be("text-muted-foreground text-sm",e),...t})}function Nn({className:e,...t}){return g.jsx("div",{"data-slot":"card-content",className:be("px-6",e),...t})}function KB({className:e,...t}){return g.jsx("div",{"data-slot":"card-footer",className:be("flex items-center px-6 [.border-t]:pt-6",e),...t})}function XB({props:e}){const t=e?.title||"Statistics",r=e?.value||"0",a=e?.change||"",o=e?.changeType||"positive";return g.jsxs(_n,{className:"mb-4",children:[g.jsxs(Bn,{className:"pb-2",children:[g.jsx(Qg,{children:t}),g.jsx(zn,{className:"text-2xl",children:r})]}),a&&g.jsx(Nn,{className:"pt-0",children:g.jsx("span",{className:o==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:a})})]})}function ZB({props:e}){const t=e?.title||"Quick Links",r=e?.links||[];return g.jsxs(_n,{className:"mb-4",children:[g.jsx(Bn,{className:"pb-2",children:g.jsx(zn,{className:"text-sm font-medium",children:t})}),g.jsx(Nn,{className:"space-y-1",children:r.map((a,o)=>g.jsx("a",{href:a.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:a.label},o))})]})}function QB({ Line truncated
`);p=b.pop()||"";for(const v of b)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));Ez(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=Ua.get(e);u&&An(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=Ua.get(e);s&&(s.abortController=null),An(e,{isStreaming:!1}),o?.onEnd?.()}}function Ez(e,t,r){const a=Ua.get(e);if(!a)return;const o=a.toolCallMap;switch(t.type){case"session":t.session_id&&typeof t.session_id=="string"&&r?.onSessionId?.(t.session_id);break;case"token":case"run_content":case"team_run_content":{const u=t.token??t.content??"";u&&An(e,{currentResponse:a.state.currentResponse+u});break}case"message":case"run_completed":case"team_run_completed":{const u=t.content;u&&An(e,{currentResponse:u});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const u=t.step;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,u]});break}case"reasoning_started":case"team_reasoning_started":An(e,{thinkingSteps:[...a.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const u={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"running",tool_call_id:t.tool_call_id,args:t.args};u.tool_call_id&&o.set(u.tool_call_id,u);const d=qA(a.state.toolCalls,u);An(e,{toolCalls:d});break}case"tool_call_completed":case"team_tool_call_completed":{const u=t.extra_data,d=t.a2ui??u?.a2ui,p=t.elements??u?.elements,f={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"done",tool_call_id:t.tool_call_id,args:t.args,formatted_result:t.formatted_result,result:t.result,surface_id:t.surface_id??u?.surface_id,a2ui:d?.length?{messages:d,surface_id:t.surface_id??u?.surface_id}:void 0,elements:p};f.tool_call_id&&o.set(f.tool_call_id,f);const h=qA(a.state.toolCalls,f),b=p?.length?[...a.state.pendingElements,...p]:a.state.pendingElements;An(e,{toolCalls:h,pendingElements:b});break}case"message_element":{const u=t.extra_data,d=t.element??u?.element;d&&An(e,{pendingElements:[...a.state.pendingElements,d]});break}case"error":case"run_error":case"team_run_error":{const u=t.error;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,`Error: ${u}`]});break}case"end":case"done":case"run_cancelled":case"team_run_cancelled":break;default:gz(e,t);break}}function qA(e,t){if(t.tool_call_id){const r=e.findIndex(a=>a.tool_call_id===t.tool_call_id);if(r>=0){const a=[...e];return a[r]={...a[r],...t},a}}return[...e,t]}function UN(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>yu(t));S.useEffect(()=>{if(!t){a(yu(null));return}return a(yu(t)),mz(t,()=>{a(yu(t))})},[t]);const o=S.useCallback((u,d)=>{const p=t??`new-${Date.now()}`;bz(p,u,d,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&hz(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function vz(e,t){if(e==null)return{};var r={};for(var a in e)if({}.hasOwnProperty.call(e,a)){if(t.indexOf(a)!==-1)continue;r[a]=e[a]}return r}function Sz(e,t){if(e==null)return{};var r,a,o=vz(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)r=s[a],t.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function ig(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,a=Array(t);r<t;r++)a[r]=e[r];return a}function xz(e){if(Array.isArray(e))return ig(e)}function wz(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function Tz(e,t){if(e){if(typeof e=="string")return ig(e,t);var r={}.toString.call(e).slice(8,-1);return r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set"?Array.from(e):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?ig(e,t):void 0}}function Az(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function og(e){return xz(e)||wz(e)||Tz(e)||Az()}function Tl(e){"@babel/helpers - typeof";return Tl=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Tl(e)}function kz(e,t){if(Tl(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var a=r.call(e,t);if(Tl(a)!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function _z(e){var t=kz(e,"string");return Tl(t)=="symbol"?t:t+""}function FN(e,t,r){return(t=_z(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function lg(){return lg=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},lg.apply(null,arguments)}function YA(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),r.push.apply(r,a)}return r}function Gi(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?arguments[t]:{};t%2?YA(Object(r),!0).forEach(function(a){FN(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):YA(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function Nz(e){var t=e.length;if(t===0||t===1)return e;if(t===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(t===3)return[e[0],e[1],e[2],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0])];if(t>=4)return[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[ Line truncated
`))})}function Uz(e){var t=e.codeString,r=e.codeStyle,a=e.containerStyle,o=a===void 0?{float:"left",paddingRight:"10px"}:a,s=e.numberStyle,u=s===void 0?{}:s,d=e.startingLineNumber;return Ke.createElement("code",{style:Object.assign({},r,o)},jz({lines:t.replace(/\n$/,"").split(`
`),style:u,startingLineNumber:d}))}function Fz(e){return"".concat(e.toString().length,".25em")}function zN(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function PN(e,t,r){var a={display:"inline-block",minWidth:Fz(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=oa(oa({},a),o);return s}function Ru(e){var t=e.children,r=e.lineNumber,a=e.lineNumberStyle,o=e.largestLineNumber,s=e.showInlineLineNumbers,u=e.lineProps,d=u===void 0?{}:u,p=e.className,f=p===void 0?[]:p,h=e.showLineNumbers,b=e.wrapLongLines,v=e.wrapLines,E=v===void 0?!1:v,w=E?oa({},typeof d=="function"?d(r):d):{};if(w.className=w.className?[].concat(og(w.className.trim().split(/\s+/)),og(f)):f,r&&s){var T=PN(a,r,o);t.unshift(zN(r,T))}return b&h&&(w.style=oa({display:"flex"},w.style)),{type:"element",tagName:"span",properties:w,children:t}}function $N(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];e.length===void 0&&(e=[e]);for(var a=0;a<e.length;a++){var o=e[a];if(o.type==="text")r.push(Ru({children:[o],className:og(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);$N(o.children,u).forEach(function(d){return r.push(d)})}}return r}function Bz(e,t,r,a,o,s,u,d,p){var f,h=$N(e.value),b=[],v=-1,E=0;function w(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Ru({children:j,lineNumber:D,lineNumberStyle:d,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:z,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function T(j,D){if(a&&D&&o){var z=PN(d,D,u);j.unshift(zN(D,z))}return j}function k(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||z.length>0?w(j,D,z):T(j,D)}for(var A=function(){var D=h[E],z=D.children[0].value,F=Mz(z);if(F){var V=z.split(`
`);V.forEach(function(M,H){var K=a&&b.length+s,Z={type:"text",value:"".concat(M,`
`)};if(H===0){var te=h.slice(v+1,E).concat(Ru({children:[Z],className:D.properties.className})),Q=k(te,K);b.push(Q)}else if(H===V.length-1){var P=h[E+1]&&h[E+1].children&&h[E+1].children[0],W={type:"text",value:"".concat(M)};if(P){var ne=Ru({children:[W],className:D.properties.className});h.splice(E+1,0,ne)}else{var ce=[W],C=k(ce,K,D.properties.className);b.push(C)}}else{var U=[Z],J=k(U,K,D.properties.className);b.push(J)}}),v=E}E++};E<h.length;)A();if(v!==h.length-1){var _=h.slice(v+1,h.length);if(_&&_.length){var R=a&&b.length+s,L=k(_,R);b.push(L)}}return t?b:(f=[]).concat.apply(f,b)}function zz(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return BN({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function GN(e){return e&&typeof e.highlightAuto<"u"}function Pz(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if(GN(t)){var s=Iz(t,r);return r==="text"?{value:o,language:"text"}:s?t.highlight(r,a):t.highlightAuto(a)}try{return r&&r!=="text"?{value:t.highlight(a,r)}:{value:o}}catch{return{value:o}}}function $z(e,t){return function(a){var o,s,u=a.language,d=a.children,p=a.style,f=p===void 0?t:p,h=a.customStyle,b=h===void 0?{}:h,v=a.codeTagProps,E=v===void 0?{className:u?"language-".concat(u):void 0,style:oa(oa({},f['code[class*="language-"]']),f['code[class*="language-'.concat(u,'"]')])}:v,w=a.useInlineStyles,T=w===void 0?!0:w,k=a.showLineNumbers,A=k===void 0?!1:k,_=a.showInlineLineNumbers,R=_===void 0?!0:_,L=a.startingLineNumber,j=L===void 0?1:L,D=a.lineNumberContainerStyle,z=a.lineNumberStyle,F=z===void 0?{}:z,V=a.wrapLines,M=a.wrapLongLines,H=M===void 0?!1:M,K=a.lineProps,Z=K===void 0?{}:K,te=a.renderer,Q=a.PreTag,P=Q===void 0?"pre":Q,W=a.CodeTag,ne=W===void 0?"code":W,ce=a.code,C=ce===void 0?(Array.isArray(d)?d[0]:d)||"":ce,U=a.astGenerator,J=Sz(a,Lz);U=U||e;var I=A?Ke.createElement(Uz,{containerStyle:D,codeStyle:E.style||{},numberStyle:F,startingLineNumber:j,codeString:C}):null,ue=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},fe=GN(U)?"hljs":"prismjs",de=T?Object.assign({},J,{style:Object.assign({},ue,b)}):Object.assign({},J,{className:J.className?"".concat(fe," ").concat(J.className):fe,style:Object.assign({},b)});if(H?E.style=oa({whiteSpace:"pre-wrap"},E.style):E.style=oa({whiteSpace:"pre"},E.style),!U)return Ke.createElement(P,de,I,Ke.createElement(ne,E,C));(V===void 0&&te||H)&&(V=!0),te=te||zz;var le=[{type:"text",value:C}],we=Pz({astGenerator:U,language:u,code:C,defaultCodeValue:le});we.language===null&&(we.value=le);var me=(o=(s=C.match(/\n/g))===null||s===void 0?void 0:s.length)!==null&&o!==void 0?o:0,Re=j+me,Oe=Bz(we,V,Z,A,R,j,Re,F,H);return Ke.createElement(P,de,Ke.createElement(ne,E,!R&&I,te({rows:Oe,stylesheet:f,useInlineStyles:T})))}}const Gz="modulepreload",Hz=function(e){return"/"+e},XA={},ZA=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let p=function(f){return Promise.all(f.map(h=>Promise.resolve(h).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const u=document.querySelector("meta[property=csp-nonce]"),d=u?.nonce||u?.getAttribute("nonce");o=p(r.map(f=>{if(f=Hz(f),f in XA)return;XA[f]=!0;const h=f.endsWith(".css"),b=h?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${f}"]${b}`))return;const v=document.createElement("link");if(v.rel=h?"stylesheet":Gz,h||(v.as="script"),v.crossOrigin="",v.href=f,d&&v.setAttribute("nonce",d),document.head.appendChild(v),h)return new Promise((E,w)=>{v.addEventListener("load",E),v.addEventListener("error",()=>w(new Error(`Unable to preload CSS for ${f}`)))})}))}function s(u){const d=new Event("vite:preloadError",{cancelable:!0});if(d.payload=u,window.dispatchEvent(d),!d.defaultPrevented)throw u}return o.then(u=>{for(const d of u||[])d.status==="rejected"&&s(d.reason);return t().catch(s)})},Vz=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apex","apl","applescript","aql","arduino","arff","armasm","arturo","asciidoc","asm6502","asmatmel","aspnet","autohotkey","autoit","avisynth","avro-idl","awk","bash","basic","batch","bbcode","bbj","bicep","birb","bison","bnf","bqn","brainfuck","brightscript","bro","bsl","c","cfscript","chaiscript","cil","cilkc","cilkcpp","clike","clojure","cmake","cobol","coffeescript","concurnas","cooklang","coq","cpp","crystal","csharp","cshtml","csp","css-extras","css","csv","cue","cypher","d","dart","dataweave","dax","dhall","diff","django","dns-zone-file","docker","dot","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","false","firestore-security-rules","flow","fortran","fsharp","ftl","gap","gcode","gdscript","gedcom","gettext","gherkin","git","glsl","gml","gn","go-module","go","gradle","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hoon","hpkp","hsts","http","ichigojam","icon","icu-message-format","idris","iecst","ignore","inform7","ini","io","j","java","javado Line truncated
?|
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}Bt.displayName="markup-templating";Bt.aliases=[];function Bt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var d=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=d.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return d[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function d(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var b=u[s],v=a.tokenStack[b],E=typeof h=="string"?h:h.content,w=r(o,b),T=E.indexOf(w);if(T>-1){++s;var k=E.substring(0,T),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(T+w.length),R=[];k&&R.push.apply(R,d([k])),R.push(A),_&&R.push.apply(R,d([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(R)):h.content=R}}else h.content&&d(h.content)}return p}d(a.tokens)}}})})(e)}uh.displayName="django";uh.aliases=["jinja2"];function uh(e){e.register(Bt),(function(t){t.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var r=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,a=t.languages["markup-templating"];t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"django",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"jinja2",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"jinja2")})})(e)}ch.displayName="dns-zone-file";ch.aliases=["dns-zone"];function ch(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}dh.displayName="docker";dh.aliases=["dockerfile"];function dh(e){(function(t){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,a=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,s=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return o}),u={pattern:RegExp(o),greedy:!0},d={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function p(f,h){return f=f.replace(/<OPT>/g,function(){return s}).replace(/<SP>/g,function(){return a}),RegExp(f,h)}t.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:p(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[u,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:p(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:d,string:u,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper Line truncated
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+a+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+a),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Ul.displayName="t4-templating";Ul.aliases=[];function Ul(e){(function(t){function r(o,s,u){return{pattern:RegExp("<#"+o+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+o+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:s,alias:u}}}}function a(o){var s=t.languages[o],u="language-"+o;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:r("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:r("=",s,u),"class-feature":r("\\+",s,u),standard:r("",s,u)}}}}t.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:a})})(e)}TE.displayName="t4-cs";TE.aliases=["t4"];function TE(e){e.register(to),e.register(Ul),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}bc.displayName="vbnet";bc.aliases=[];function bc(e){e.register(dc),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}AE.displayName="t4-vb";AE.aliases=[];function AE(e){e.register(Ul),e.register(bc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}kE.displayName="tap";kE.aliases=[];function kE(e){e.register(cc),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}_E.displayName="tcl";_E.aliases=[];function _E(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\b/m,lookbehind:!0},/\b(?:else|elseif)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait Line truncated
- a positive number
- less than the value passed to \`max\` (or ${dv} if no \`max\` prop is set)
- \`null\` or \`undefined\` if the progress is indeterminate.
Defaulting to \`null\`.`}var b3=PR,y3=GR;function E3({className:e,value:t,...r}){return g.jsx(b3,{"data-slot":"progress",className:be("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(y3,{"data-slot":"progress-indicator",className:"bg-primary h-full w-full flex-1 transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})})}function Bl(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function zl(e){const[t,r]=S.useState(void 0);return gt(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});const a=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const s=o[0];let u,d;if("borderBoxSize"in s){const p=s.borderBoxSize,f=Array.isArray(p)?p[0]:p;u=f.inlineSize,d=f.blockSize}else u=e.offsetWidth,d=e.offsetHeight;r({width:u,height:d})});return a.observe(e,{box:"border-box"}),()=>a.unobserve(e)}else r(void 0)},[e]),t}var Ac="Switch",[v3]=jt(Ac),[S3,x3]=v3(Ac),VR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,name:a,checked:o,defaultChecked:s,required:u,disabled:d,value:p="on",onCheckedChange:f,form:h,...b}=e,[v,E]=S.useState(null),w=_e(t,R=>E(R)),T=S.useRef(!1),k=v?h||!!v.closest("form"):!0,[A,_]=en({prop:o,defaultProp:s??!1,onChange:f,caller:Ac});return g.jsxs(S3,{scope:r,checked:A,disabled:d,children:[g.jsx(Se.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":KR(A),"data-disabled":d?"":void 0,disabled:d,value:p,...b,ref:w,onClick:ge(e.onClick,R=>{_(L=>!L),k&&(T.current=R.isPropagationStopped(),T.current||R.stopPropagation())})}),k&&g.jsx(WR,{control:v,bubbles:!T.current,name:a,value:p,checked:A,required:u,disabled:d,form:h,style:{transform:"translateX(-100%)"}})]})});VR.displayName=Ac;var qR="SwitchThumb",YR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=x3(qR,r);return g.jsx(Se.span,{"data-state":KR(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});YR.displayName=qR;var w3="SwitchBubbleInput",WR=S.forwardRef(({__scopeSwitch:e,control:t,checked:r,bubbles:a=!0,...o},s)=>{const u=S.useRef(null),d=_e(u,s),p=Bl(r),f=zl(t);return S.useEffect(()=>{const h=u.current;if(!h)return;const b=window.HTMLInputElement.prototype,E=Object.getOwnPropertyDescriptor(b,"checked").set;if(p!==r&&E){const w=new Event("click",{bubbles:a});E.call(h,r),h.dispatchEvent(w)}},[p,r,a]),g.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:r,...o,tabIndex:-1,ref:d,style:{...o.style,...f,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});WR.displayName=w3;function KR(e){return e?"checked":"unchecked"}var T3=VR,A3=YR;function k3({className:e,size:t="default",...r}){return g.jsx(T3,{"data-slot":"switch","data-size":t,className:be("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",e),...r,children:g.jsx(A3,{"data-slot":"switch-thumb",className:be("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}var kc="Checkbox",[_3]=jt(kc),[N3,pv]=_3(kc);function R3(e){const{__scopeCheckbox:t,checked:r,children:a,defaultChecked:o,disabled:s,form:u,name:d,onCheckedChange:p,required:f,value:h="on",internal_do_not_use_render:b}=e,[v,E]=en({prop:r,defaultProp:o??!1,onChange:p,caller:kc}),[w,T]=S.useState(null),[k,A]=S.useState(null),_=S.useRef(!1),R=w?!!u||!!w.closest("form"):!0,L={checked:v,disabled:s,setChecked:E,control:w,setControl:T,name:d,form:u,value:h,hasConsumerStoppedPropagationRef:_,required:f,defaultChecked:la(o)?!1:o,isFormControl:R,bubbleInput:k,setBubbleInput:A};return g.jsx(N3,{scope:t,...L,children:C3(b)?b(L):a})}var XR="CheckboxTrigger",ZR=S.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:r,...a},o)=>{const{control:s,value:u,disabled:d,checked:p,required:f,setControl:h,setChecked:b,hasConsumerStoppedPropagationRef:v,isFormControl:E,bubbleInput:w}=pv(XR,e),T=_e(o,h),k=S.useRef(p);return S.useEffect(()=>{const A=s?.form;if(A){const _=()=>b(k.current);return A.addEventListener("reset",_),()=>A.removeEventListener("reset",_)}},[s,b]),g.jsx(Se.button,{type:"button",role:"checkbox","aria-checked":la(p)?"mixed":p,"aria-required":f,"data-state":rC(p),"data-disabled":d?"":void 0,disabled:d,value:u,...a,ref:T,onKeyDown:ge(t,A=>{A.key==="Enter"&&A.preventDefault()}),onClick:ge(r,A=>{b(_=>la(_)?!0:!_),w&&E&&(v.current=A.isPropagatiLine truncated
.`.concat(OG,` {
`+qe+n+dt}var Yn=!1;function Wn(n,i){if(!n||Yn)return"";Yn=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var d={DetermineComponentFrameRoot:function(){try{if(i){var ce=function(){throw Error()};if(Object.defineProperty(ce.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ce,[])}catch(te){var Z=te}Reflect.construct(n,[],ce)}else{try{ce.call()}catch(te){Z=te}n.call(ce.prototype)}}else{try{throw Error()}catch(te){Z=te}(ce=n())&&typeof ce.catch=="function"&&ce.catch(function(){})}}catch(te){if(te&&Z&&typeof te.stack=="string")return[te.stack,Z.stack]}return[null,null]}};d.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var b=Object.getOwnPropertyDescriptor(d.DetermineComponentFrameRoot,"name");b&&b.configurable&&Object.defineProperty(d.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var y=d.DetermineComponentFrameRoot(),T=y[0],R=y[1];if(T&&R){var P=T.split(`
`),W=R.split(`
`);for(b=d=0;d<P.length&&!P[d].includes("DetermineComponentFrameRoot");)d++;for(;b<W.length&&!W[b].includes("DetermineComponentFrameRoot");)b++;if(d===P.length||b===W.length)for(d=P.length-1,b=W.length-1;1<=d&&0<=b&&P[d]!==W[b];)b--;for(;1<=d&&0<=b;d--,b--)if(P[d]!==W[b]){if(d!==1||b!==1)do if(d--,b--,0>b||P[d]!==W[b]){var oe=`
`+P[d].replace(" at new "," at ");return n.displayName&&oe.includes("<anonymous>")&&(oe=oe.replace("<anonymous>",n.displayName)),oe}while(1<=d&&0<=b);break}}}finally{Yn=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?st(l):""}function Ir(n,i){switch(n.tag){case 26:case 27:case 5:return st(n.type);case 16:return st("Lazy");case 13:return n.child!==i&&i!==null?st("Suspense Fallback"):st("Suspense");case 19:return st("SuspenseList");case 0:case 15:return Wn(n.type,!1);case 11:return Wn(n.type.render,!1);case 1:return Wn(n.type,!0);case 31:return st("Activity");default:return""}}function Jl(n){try{var i="",l=null;do i+=Ir(n,l),l=n,n=n.return;while(n);return i}catch(d){return`
Error generating stack: `+d.message+`
`+d.stack}}var Za=Object.prototype.hasOwnProperty,Qa=e.unstable_scheduleCallback,bo=e.unstable_cancelCallback,Zc=e.unstable_shouldYield,Qc=e.unstable_requestPaint,Pt=e.unstable_now,Jc=e.unstable_getCurrentPriorityLevel,le=e.unstable_ImmediatePriority,ge=e.unstable_UserBlockingPriority,_e=e.unstable_NormalPriority,Me=e.unstable_LowPriority,We=e.unstable_IdlePriority,an=e.log,lr=e.unstable_setDisableYieldValue,$t=null,Et=null;function qt(n){if(typeof an=="function"&&lr(n),Et&&typeof Et.setStrictMode=="function")try{Et.setStrictMode($t,n)}catch{}}var tt=Math.clz32?Math.clz32:gO,Or=Math.log,Ln=Math.LN2;function gO(n){return n>>>=0,n===0?32:31-(Or(n)/Ln|0)|0}var es=256,ts=262144,ns=4194304;function ba(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function rs(n,i,l){var d=n.pendingLanes;if(d===0)return 0;var b=0,y=n.suspendedLanes,T=n.pingedLanes;n=n.warmLanes;var R=d&134217727;return R!==0?(d=R&~y,d!==0?b=ba(d):(T&=R,T!==0?b=ba(T):l||(l=R&~n,l!==0&&(b=ba(l))))):(R=d&~y,R!==0?b=ba(R):T!==0?b=ba(T):l||(l=d&~n,l!==0&&(b=ba(l)))),b===0?0:i!==0&&i!==b&&(i&y)===0&&(y=b&-b,l=i&-i,y>=l||y===32&&(l&4194048)!==0)?i:b}function yo(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function mO(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function eS(){var n=ns;return ns<<=1,(ns&62914560)===0&&(ns=4194304),n}function ed(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function vo(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function hO(n,i,l,d,b,y){var T=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var R=n.entanglements,P=n.expirationTimes,W=n.hiddenUpdates;for(l=T&~l;0<l;){var oe=31-tt(l),ce=1<<oe;R[oe]=0,P[oe]=-1;var Z=W[oe];if(Z!==null)for(W[oe]=null,oe=0;oe<Z.length;oe++){var te=Z[oe];te!==null&&(te.lane&=-536870913)}l&=~ce}d!==0&&tS(n,d,0),y!==0&&b===0&&n.tag!==0&&(n.suspendedLanes|=y&~(T&~i))}function tS(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var d=31-tt(i);n.entangledLanes|=i,n.entanglements[d]=n.entanglements[d]|1073741824|l&261930}function nS(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var d=31-tt(l),b=1<<d;b&i|n[d]&i&&(n[d]|=i),l&=~b}}function rS(n,i){var l=i&-i;return l=(l&42)!==0?1:td(l),(l&(n.suspendedLanes|i))!==0?0:l}function td(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function nd(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function aS(){var n=H.p;return n!==0?n:(n=window.event,n===void 0?32:GT(n.type))}function iS(n,i){var l=H.p;try{return H.p=n,i()}finally{H.p=l}}var Lr=Math.random().toString(36).slice(2),_t="__reactFiber$"+Lr,Yt="__reactProps$"+Lr,Ja="__reactContainer$"+Lr,rd="__reactEvents$"+Lr,bO="__reactListeners$"+Lr,yO="__reactHandles$"+Lr,oS="__reactResources$"+Lr,Eo="__reactMarker$"+Lr;function ad(n){delete n[_t],delete n[Yt],delete n[rd],delete n[bO],delete n[yO]}function ei(n){var i=n[_t];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Ja]||l[_t]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=_T(n);n!==null;){if(l=n[_t])return l;n=_T(n)}return i}n=l,l=n.parentNode}return null}function ti(n){if(n=n[_t]||n[Ja]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function So(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function ni(n){var i=n[oS];return i||(i=n[oS]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[Eo]=!0}var lS=new Set,sS={};function ya(n,i){ri(n,i),ri(n+"Capture",i)}function ri(n,i){for(sS[n]=i,n=0;n<i.length;n++)lS.add(i[n])}var vO=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u20 Line truncated
`).replace(X1,"")}function bT(n,i){return i=hT(i),hT(n)===i}function Je(n,i,l,d,b,y){switch(l){case"children":typeof d=="string"?i==="body"||i==="textarea"&&d===""||ii(n,d):(typeof d=="number"||typeof d=="bigint")&&i!=="body"&&ii(n,""+d);break;case"className":is(n,"class",d);break;case"tabIndex":is(n,"tabindex",d);break;case"dir":case"role":case"viewBox":case"width":case"height":is(n,l,d);break;case"style":bS(n,d,y);break;case"data":if(i!=="object"){is(n,"data",d);break}case"src":case"href":if(d===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(d==null||typeof d=="function"||typeof d=="symbol"||typeof d=="boolean"){n.removeAttribute(l);break}d=ls(""+d),n.setAttribute(l,d);break;case"action":case"formAction":if(typeof d=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof y=="function"&&(l==="formAction"?(i!=="input"&&Je(n,i,"name",b.name,b,null),Je(n,i,"formEncType",b.formEncType,b,null),Je(n,i,"formMethod",b.formMethod,b,null),Je(n,i,"formTarget",b.formTarget,b,null)):(Je(n,i,"encType",b.encType,b,null),Je(n,i,"method",b.method,b,null),Je(n,i,"target",b.target,b,null)));if(d==null||typeof d=="symbol"||typeof d=="boolean"){n.removeAttribute(l);break}d=ls(""+d),n.setAttribute(l,d);break;case"onClick":d!=null&&(n.onclick=ur);break;case"onScroll":d!=null&&Ue("scroll",n);break;case"onScrollEnd":d!=null&&Ue("scrollend",n);break;case"dangerouslySetInnerHTML":if(d!=null){if(typeof d!="object"||!("__html"in d))throw Error(a(61));if(l=d.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=d&&typeof d!="function"&&typeof d!="symbol";break;case"muted":n.muted=d&&typeof d!="function"&&typeof d!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(d==null||typeof d=="function"||typeof d=="boolean"||typeof d=="symbol"){n.removeAttribute("xlink:href");break}l=ls(""+d),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":d!=null&&typeof d!="function"&&typeof d!="symbol"?n.setAttribute(l,""+d):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":d&&typeof d!="function"&&typeof d!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":d===!0?n.setAttribute(l,""):d!==!1&&d!=null&&typeof d!="function"&&typeof d!="symbol"?n.setAttribute(l,d):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":d!=null&&typeof d!="function"&&typeof d!="symbol"&&!isNaN(d)&&1<=d?n.setAttribute(l,d):n.removeAttribute(l);break;case"rowSpan":case"start":d==null||typeof d=="function"||typeof d=="symbol"||isNaN(d)?n.removeAttribute(l):n.setAttribute(l,d);break;case"popover":Ue("beforetoggle",n),Ue("toggle",n),as(n,"popover",d);break;case"xlinkActuate":sr(n,"http://www.w3.org/1999/xlink","xlink:actuate",d);break;case"xlinkArcrole":sr(n,"http://www.w3.org/1999/xlink","xlink:arcrole",d);break;case"xlinkRole":sr(n,"http://www.w3.org/1999/xlink","xlink:role",d);break;case"xlinkShow":sr(n,"http://www.w3.org/1999/xlink","xlink:show",d);break;case"xlinkTitle":sr(n,"http://www.w3.org/1999/xlink","xlink:title",d);break;case"xlinkType":sr(n,"http://www.w3.org/1999/xlink","xlink:type",d);break;case"xmlBase":sr(n,"http://www.w3.org/XML/1998/namespace","xml:base",d);break;case"xmlLang":sr(n,"http://www.w3.org/XML/1998/namespace","xml:lang",d);break;case"xmlSpace":sr(n,"http://www.w3.org/XML/1998/namespace","xml:space",d);break;case"is":as(n,"is",d);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=TO.get(l)||l,as(n,l,d))}}function Wp(n,i,l,d,b,y){switch(l){case"style":bS(n,d,y);break;case"dangerouslySetInnerHTML":if(d!=null){if(typeof d!="object"||!("__html"in d))throw Error(a(61));if(l=d.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof d=="string"?ii(n,d):(typeof d=="number"||typeof d=="bigint")&&ii(n,""+d);break;case"onScroll":d!=null&&Ue("scroll",n);break;case"onScrollEnd":d!=null&&Ue("scrollend",n);break;case"onClick":d!=null&&(n.onclick=ur);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
.`.concat(y2,` {
overflow: hidden `).concat(a,`;
padding-right: `).concat(d,"px ").concat(a,`;
padding-right: `).concat(c,"px ").concat(a,`;
}
body[`).concat(qi,`] {
body[`).concat(Wi,`] {
overflow: hidden `).concat(a,`;
overscroll-behavior: contain;
`).concat([t&&"position: relative ".concat(a,";"),r==="margin"&&`
@@ -68,41 +21,90 @@ Defaulting to \`null\`.`}var b3=PR,y3=GR;function E3({className:e,value:t,...r})
padding-right: `).concat(u,`px;
margin-left:0;
margin-top:0;
margin-right: `).concat(d,"px ").concat(a,`;
`),r==="padding"&&"padding-right: ".concat(d,"px ").concat(a,";")].filter(Boolean).join(""),`
margin-right: `).concat(c,"px ").concat(a,`;
`),r==="padding"&&"padding-right: ".concat(c,"px ").concat(a,";")].filter(Boolean).join(""),`
}
.`).concat(Iu,` {
right: `).concat(d,"px ").concat(a,`;
.`).concat(Du,` {
right: `).concat(c,"px ").concat(a,`;
}
.`).concat(Lu,` {
margin-right: `).concat(d,"px ").concat(a,`;
.`).concat(Mu,` {
margin-right: `).concat(c,"px ").concat(a,`;
}
.`).concat(Iu," .").concat(Iu,` {
.`).concat(Du," .").concat(Du,` {
right: 0 `).concat(a,`;
}
.`).concat(Lu," .").concat(Lu,` {
.`).concat(Mu," .").concat(Mu,` {
margin-right: 0 `).concat(a,`;
}
body[`).concat(qi,`] {
`).concat(IG,": ").concat(d,`px;
body[`).concat(Wi,`] {
`).concat(v2,": ").concat(c,`px;
}
`)},Dk=function(){var e=parseInt(document.body.getAttribute(qi)||"0",10);return isFinite(e)?e:0},ZG=function(){S.useEffect(function(){return document.body.setAttribute(qi,(Dk()+1).toString()),function(){var e=Dk()-1;e<=0?document.body.removeAttribute(qi):document.body.setAttribute(qi,e.toString())}},[])},QG=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;ZG();var s=S.useMemo(function(){return WG(o)},[o]);return S.createElement(KG,{styles:XG(s,!t,o,r?"":"!important")})},bg=!1;if(typeof window<"u")try{var Tu=Object.defineProperty({},"passive",{get:function(){return bg=!0,!0}});window.addEventListener("test",Tu,Tu),window.removeEventListener("test",Tu,Tu)}catch{bg=!1}var Ui=bg?{passive:!1}:!1,JG=function(e){return e.tagName==="TEXTAREA"},YC=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!JG(e)&&r[t]==="visible")},eH=function(e){return YC(e,"overflowY")},tH=function(e){return YC(e,"overflowX")},Mk=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=WC(e,a);if(o){var s=KC(e,a),u=s[1],d=s[2];if(u>d)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},nH=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},rH=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},WC=function(e,t){return e==="v"?eH(t):tH(t)},KC=function(e,t){return e==="v"?nH(t):rH(t)},aH=function(e,t){return e==="h"&&t==="rtl"?-1:1},iH=function(e,t,r,a,o){var s=aH(e,window.getComputedStyle(t).direction),u=s*a,d=r.target,p=t.contains(d),f=!1,h=u>0,b=0,v=0;do{if(!d)break;var E=KC(e,d),w=E[0],T=E[1],k=E[2],A=T-k-s*w;(w||A)&&WC(e,d)&&(b+=A,v+=w);var _=d.parentNode;d=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!p&&d!==document.body||p&&(t.contains(d)||t===d));return(h&&Math.abs(b)<1||!h&&Math.abs(v)<1)&&(f=!0),f},Au=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},jk=function(e){return[e.deltaX,e.deltaY]},Uk=function(e){return e&&"current"in e?e.current:e},oH=function(e,t){return e[0]===t[0]&&e[1]===t[1]},lH=function(e){return`
`)},kA=function(){var e=parseInt(document.body.getAttribute(Wi)||"0",10);return isFinite(e)?e:0},F2=function(){S.useEffect(function(){return document.body.setAttribute(Wi,(kA()+1).toString()),function(){var e=kA()-1;e<=0?document.body.removeAttribute(Wi):document.body.setAttribute(Wi,e.toString())}},[])},B2=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;F2();var s=S.useMemo(function(){return M2(o)},[o]);return S.createElement(j2,{styles:U2(s,!t,o,r?"":"!important")})},ig=!1;if(typeof window<"u")try{var vu=Object.defineProperty({},"passive",{get:function(){return ig=!0,!0}});window.addEventListener("test",vu,vu),window.removeEventListener("test",vu,vu)}catch{ig=!1}var Ui=ig?{passive:!1}:!1,z2=function(e){return e.tagName==="TEXTAREA"},uN=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!z2(e)&&r[t]==="visible")},P2=function(e){return uN(e,"overflowY")},$2=function(e){return uN(e,"overflowX")},_A=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=cN(e,a);if(o){var s=dN(e,a),u=s[1],c=s[2];if(u>c)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},G2=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},H2=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},cN=function(e,t){return e==="v"?P2(t):$2(t)},dN=function(e,t){return e==="v"?G2(t):H2(t)},V2=function(e,t){return e==="h"&&t==="rtl"?-1:1},q2=function(e,t,r,a,o){var s=V2(e,window.getComputedStyle(t).direction),u=s*a,c=r.target,p=t.contains(c),f=!1,h=u>0,m=0,v=0;do{if(!c)break;var E=dN(e,c),x=E[0],w=E[1],k=E[2],A=w-k-s*x;(x||A)&&cN(e,c)&&(m+=A,v+=x);var _=c.parentNode;c=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!p&&c!==document.body||p&&(t.contains(c)||t===c));return(h&&Math.abs(m)<1||!h&&Math.abs(v)<1)&&(f=!0),f},Eu=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},NA=function(e){return[e.deltaX,e.deltaY]},RA=function(e){return e&&"current"in e?e.current:e},Y2=function(e,t){return e[0]===t[0]&&e[1]===t[1]},W2=function(e){return`
.block-interactivity-`.concat(e,` {pointer-events: none;}
.allow-interactivity-`).concat(e,` {pointer-events: all;}
`)},sH=0,Fi=[];function uH(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(sH++)[0],s=S.useState(qC)[0],u=S.useRef(e);S.useEffect(function(){u.current=e},[e]),S.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var T=CG([e.lockRef.current],(e.shards||[]).map(Uk),!0).filter(Boolean);return T.forEach(function(k){return k.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),T.forEach(function(k){return k.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var d=S.useCallback(function(T,k){if("touches"in T&&T.touches.length===2||T.type==="wheel"&&T.ctrlKey)return!u.current.allowPinchZoom;var A=Au(T),_=r.current,R="deltaX"in T?T.deltaX:_[0]-A[0],L="deltaY"in T?T.deltaY:_[1]-A[1],j,D=T.target,z=Math.abs(R)>Math.abs(L)?"h":"v";if("touches"in T&&z==="h"&&D.type==="range")return!1;var F=window.getSelection(),V=F&&F.anchorNode,M=V?V===D||V.contains(D):!1;if(M)return!1;var H=Mk(z,D);if(!H)return!0;if(H?j=z:(j=z==="v"?"h":"v",H=Mk(z,D)),!H)return!1;if(!a.current&&"changedTouches"in T&&(R||L)&&(a.current=j),!j)return!0;var K=a.current||j;return iH(K,k,T,K==="h"?R:L)},[]),p=S.useCallback(function(T){var k=T;if(!(!Fi.length||Fi[Fi.length-1]!==s)){var A="deltaY"in k?jk(k):Au(k),_=t.current.filter(function(j){return j.name===k.type&&(j.target===k.target||k.target===j.shadowParent)&&oH(j.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var R=(u.current.shards||[]).map(Uk).filter(Boolean).filter(function(j){return j.contains(k.target)}),L=R.length>0?d(k,R[0]):!u.current.noIsolation;L&&k.cancelable&&k.preventDefault()}}},[]),f=S.useCallback(function(T,k,A,_){var R={name:T,delta:k,target:A,should:_,shadowParent:cH(A)};t.current.push(R),setTimeout(function(){t.current=t.current.filter(function(L){return L!==R})},1)},[]),h=S.useCallback(function(T){r.current=Au(T),a.current=void 0},[]),b=S.useCallback(function(T){f(T.type,jk(T),T.target,d(T,e.lockRef.current))},[]),v=S.useCallback(function(T){f(T.type,Au(T),T.target,d(T,e.lockRef.current))},[]);S.useEffect(function(){return Fi.push(s),e.setCallbacks({onScrollCapture:b,onWheelCapture:b,onTouchMoveCapture:v}),document.addEventListener("wheel",p,Ui),document.addEventListener("touchmove",p,Ui),document.addEventListener("touchstart",h,Ui),function(){Fi=Fi.filter(function(T){return T!==s}),document.removeEventListener("wheel",p,Ui),document.removeEventListener("touchmove",p,Ui),document.removeEventListener("touchstart",h,Ui)}},[]);var E=e.removeScrollBar,w=e.inert;return S.createElement(S.Fragment,null,w?S.createElement(s,{styles:lH(o)}):null,E?S.createElement(QG,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function cH(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const dH=BG(VC,uH);var Mc=S.forwardRef(function(e,t){return S.createElement(Dc,Jn({},e,{ref:t,sideCar:dH}))});Mc.classNames=Dc.classNames;var pH=[" ","Enter","ArrowUp","ArrowDown"],fH=[" ","Enter"],za="Select",[jc,Uc,gH]=yc(za),[uo]=jt(za,[gH,so]),Fc=so(),[mH,pa]=uo(za),[hH,bH]=uo(za),XC=e=>{const{__scopeSelect:t,children:r,open:a,defaultOpen:o,onOpenChange:s,value:u,defaultValue:d,onValueChange:p,dir:f,name:h,autoComplete:b,disabled:v,required:E,form:w}=e,T=Fc(t),[k,A]=S.useState(null),[_,R]=S.useState(null),[L,j]=S.useState(!1),D=Ha(f),[z,F]=en({prop:a,defaultProp:o??!1,onChange:s,caller:za}),[V,M]=en({prop:u,defaultProp:d,onChange:p,caller:za}),H=S.useRef(null),K=k?w||!!k.closest("form"):!0,[Z,te]=S.useState(new Set),Q=Array.from(Z).map(P=>P.props.value).join(";");return g.jsx(wv,{...T,children:g.jsxs(mH,{required:E,scope:t,trigger:k,onTriggerChange:A,valueNode:_,onValueNodeChange:R,valueNodeHasChildren:L,onValueNodeHasChildrenChange:j,contentId:Rn(),value:V,onValueChange:M,open:z,onOpenChange:F,dir:D,triggerPointerDownPosRef:H,disabled:v,children:[g.jsx(jc.Provider,{scope:t,children:g.jsx(hH,{scope:e.__scopeSelect,onNativeOptionAdd:S.useCallback(P=>{te(W=>new Set(W).add(P))},[]),onNativeOptionRemove:S.useCallback(P=>{te(W=>{const ne=new Set(W);return ne.delete(P),ne})},[]),children:r})}),K?g.jsxs(y0,{"aria-hidden":!0,required:E,tabIndex:-1,name:h,autoComplete:b,value:V,onChange:P=>M(P.target.value),disabled:v,form:w,children:[V===void 0?g.jsx("option",{value:""}):null,Array.from(Z)]},Q):null]})})};XC.displayName=za;var ZC="SelectTrigger",QC=S.forwardRef((e,t)=>{const{__scopeSelect:r,disabled:a=!1,...o}=e,s=Fc(r),u=pa(ZC,r),d=u.disabled||a,p=_e(t,u.onTriggerChange),f=Uc(r),h=S.useRef("touch"),[b,v,E]=v0(T=>{const k=f().filter(R=>!R.disabled),A=k.find(R=>R.value===u.value),_=S0(k,T,A);_!==void 0&&u.onValueChange(_.value)}),w=T=>{d||(u.onOpenChange(!0),E()),T&&(u.triggerPointerDownPosRef.current={x:Math.round(T.pageX),y:Math.round(T.pageY)})};return g.jsx(Lc,{asChild:!0,...s,children:g.jsx(Se.button,{type:"button",role:"combobox","aria-controls":u.contentId,"aria-ex Line truncated
`)},K2=0,Fi=[];function X2(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(K2++)[0],s=S.useState(sN)[0],u=S.useRef(e);S.useEffect(function(){u.current=e},[e]),S.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var w=b2([e.lockRef.current],(e.shards||[]).map(RA),!0).filter(Boolean);return w.forEach(function(k){return k.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),w.forEach(function(k){return k.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var c=S.useCallback(function(w,k){if("touches"in w&&w.touches.length===2||w.type==="wheel"&&w.ctrlKey)return!u.current.allowPinchZoom;var A=Eu(w),_=r.current,N="deltaX"in w?w.deltaX:_[0]-A[0],D="deltaY"in w?w.deltaY:_[1]-A[1],F,L=w.target,z=Math.abs(N)>Math.abs(D)?"h":"v";if("touches"in w&&z==="h"&&L.type==="range")return!1;var $=window.getSelection(),K=$&&$.anchorNode,j=K?K===L||K.contains(L):!1;if(j)return!1;var q=_A(z,L);if(!q)return!0;if(q?F=z:(F=z==="v"?"h":"v",q=_A(z,L)),!q)return!1;if(!a.current&&"changedTouches"in w&&(N||D)&&(a.current=F),!F)return!0;var X=a.current||F;return q2(X,k,w,X==="h"?N:D)},[]),p=S.useCallback(function(w){var k=w;if(!(!Fi.length||Fi[Fi.length-1]!==s)){var A="deltaY"in k?NA(k):Eu(k),_=t.current.filter(function(F){return F.name===k.type&&(F.target===k.target||k.target===F.shadowParent)&&Y2(F.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var N=(u.current.shards||[]).map(RA).filter(Boolean).filter(function(F){return F.contains(k.target)}),D=N.length>0?c(k,N[0]):!u.current.noIsolation;D&&k.cancelable&&k.preventDefault()}}},[]),f=S.useCallback(function(w,k,A,_){var N={name:w,delta:k,target:A,should:_,shadowParent:Z2(A)};t.current.push(N),setTimeout(function(){t.current=t.current.filter(function(D){return D!==N})},1)},[]),h=S.useCallback(function(w){r.current=Eu(w),a.current=void 0},[]),m=S.useCallback(function(w){f(w.type,NA(w),w.target,c(w,e.lockRef.current))},[]),v=S.useCallback(function(w){f(w.type,Eu(w),w.target,c(w,e.lockRef.current))},[]);S.useEffect(function(){return Fi.push(s),e.setCallbacks({onScrollCapture:m,onWheelCapture:m,onTouchMoveCapture:v}),document.addEventListener("wheel",p,Ui),document.addEventListener("touchmove",p,Ui),document.addEventListener("touchstart",h,Ui),function(){Fi=Fi.filter(function(w){return w!==s}),document.removeEventListener("wheel",p,Ui),document.removeEventListener("touchmove",p,Ui),document.removeEventListener("touchstart",h,Ui)}},[]);var E=e.removeScrollBar,x=e.inert;return S.createElement(S.Fragment,null,x?S.createElement(s,{styles:W2(o)}):null,E?S.createElement(B2,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function Z2(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const Q2=k2(lN,X2);var dc=S.forwardRef(function(e,t){return S.createElement(cc,er({},e,{ref:t,sideCar:Q2}))});dc.classNames=cc.classNames;var J2=function(e){if(typeof document>"u")return null;var t=Array.isArray(e)?e[0]:e;return t.ownerDocument.body},Bi=new WeakMap,Su=new WeakMap,xu={},kf=0,pN=function(e){return e&&(e.host||pN(e.parentNode))},ej=function(e,t){return t.map(function(r){if(e.contains(r))return r;var a=pN(r);return a&&e.contains(a)?a:(console.error("aria-hidden",r,"in not contained inside",e,". Doing nothing"),null)}).filter(function(r){return!!r})},tj=function(e,t,r,a){var o=ej(t,Array.isArray(e)?e:[e]);xu[r]||(xu[r]=new WeakMap);var s=xu[r],u=[],c=new Set,p=new Set(o),f=function(m){!m||c.has(m)||(c.add(m),f(m.parentNode))};o.forEach(f);var h=function(m){!m||p.has(m)||Array.prototype.forEach.call(m.children,function(v){if(c.has(v))h(v);else try{var E=v.getAttribute(a),x=E!==null&&E!=="false",w=(Bi.get(v)||0)+1,k=(s.get(v)||0)+1;Bi.set(v,w),s.set(v,k),u.push(v),w===1&&x&&Su.set(v,!0),k===1&&v.setAttribute(r,"true"),x||v.setAttribute(a,"true")}catch(A){console.error("aria-hidden: cannot operate on ",v,A)}})};return h(t),c.clear(),kf++,function(){u.forEach(function(m){var v=Bi.get(m)-1,E=s.get(m)-1;Bi.set(m,v),s.set(m,E),v||(Su.has(m)||m.removeAttribute(a),Su.delete(m)),E||m.removeAttribute(r)}),kf--,kf||(Bi=new WeakMap,Bi=new WeakMap,Su=new WeakMap,xu={})}},Xg=function(e,t,r){r===void 0&&(r="data-aria-hidden");var a=Array.from(Array.isArray(e)?e:[e]),o=J2(e);return o?(a.push.apply(a,Array.from(o.querySelectorAll("[aria-live], script"))),tj(a,o,r,"aria-hidden")):function(){return null}};function nj(e){const t=rj(e),r=S.forwardRef((a,o)=>{const{children:s,...u}=a,c=S.Children.toArray(s),p=c.find(ij);if(p){const f=p.props.children,h=c.map(m=>m===p?S.Children.count(f)>1?S.Children.only(null):S.isValidElement(f)?f.props.children:null:m);return g.jsx(t,{...u,ref:o,children:S.isValidElement(f)?S.cloneElement(f,void 0,h):null})}return g.jsx(t,{...u,ref:o,children:s})});return r.displayName=`${e}.Slot`,r}function rj(e){const t=S.forwardRef((r,a)=>{const{children:o Line truncated
If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return S.useEffect(()=>{e&&(document.getElementById(e)||console.error(r))},[r,e]),null},L5="DialogDescriptionWarning",D5=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${Z0(L5).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},M5=U0,j5=B0,U5=P0,F5=$0,B5=G0,z5=V0,P5=Y0,$5=K0;function G5({...e}){return g.jsx(M5,{"data-slot":"dialog",...e})}function H5({...e}){return g.jsx(j5,{"data-slot":"dialog-trigger",...e})}function V5({...e}){return g.jsx(U5,{"data-slot":"dialog-portal",...e})}function q5({className:e,...t}){return g.jsx(F5,{"data-slot":"dialog-overlay",className:be("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",e),...t})}function Y5({className:e,children:t,showCloseButton:r=!0,...a}){return g.jsxs(V5,{"data-slot":"dialog-portal",children:[g.jsx(q5,{}),g.jsxs(B5,{"data-slot":"dialog-content",className:be("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",e),...a,children:[t,r&&g.jsxs($5,{"data-slot":"dialog-close",className:"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",children:[g.jsx(z$,{}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function W5({className:e,...t}){return g.jsx("div",{"data-slot":"dialog-header",className:be("flex flex-col gap-2 text-center sm:text-left",e),...t})}function K5({className:e,...t}){return g.jsx(z5,{"data-slot":"dialog-title",className:be("text-lg leading-none font-semibold",e),...t})}function X5({className:e,...t}){return g.jsx(P5,{"data-slot":"dialog-description",className:be("text-muted-foreground text-sm",e),...t})}var Z5=Symbol("radix.slottable");function Q5(e){const t=({children:r})=>g.jsx(g.Fragment,{children:r});return t.displayName=`${e}.Slottable`,t.__radixId=Z5,t}var[Pc]=jt("Tooltip",[so]),$c=so(),Q0="TooltipProvider",J5=700,kg="tooltip.open",[e6,Iv]=Pc(Q0),J0=e=>{const{__scopeTooltip:t,delayDuration:r=J5,skipDelayDuration:a=300,disableHoverableContent:o=!1,children:s}=e,u=S.useRef(!0),d=S.useRef(!1),p=S.useRef(0);return S.useEffect(()=>{const f=p.current;return()=>window.clearTimeout(f)},[]),g.jsx(e6,{scope:t,isOpenDelayedRef:u,delayDuration:r,onOpen:S.useCallback(()=>{window.clearTimeout(p.current),u.current=!1},[]),onClose:S.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>u.current=!0,a)},[a]),isPointerInTransitRef:d,onPointerInTransitChange:S.useCallback(f=>{d.current=f},[]),disableHoverableContent:o,children:s})};J0.displayName=Q0;var kl="Tooltip",[t6,Hl]=Pc(kl),eO=e=>{const{__scopeTooltip:t,children:r,open:a,defaultOpen:o,onOpenChange:s,disableHoverableContent:u,delayDuration:d}=e,p=Iv(kl,e.__scopeTooltip),f=$c(t),[h,b]=S.useState(null),v=Rn(),E=S.useRef(0),w=u??p.disableHoverableContent,T=d??p.delayDuration,k=S.useRef(!1),[A,_]=en({prop:a,defaultProp:o??!1,onChange:z=>{z?(p.onOpen(),document.dispatchEvent(new CustomEvent(kg))):p.onClose(),s?.(z)},caller:kl}),R=S.useMemo(()=>A?k.current?"delayed-open":"instant-open":"closed",[A]),L=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,k.current=!1,_(!0)},[_]),j=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,_(!1)},[_]),D=S.useCallback(()=>{window.clearTimeout(E.current),E.current=window.setTimeout(()=>{k.current=!0,_(!0),E.current=0},T)},[T,_]);return S.useEffect(()=>()=>{E.current&&(window.clearTimeout(E.current),E.current=0)},[]),g.jsx(wv,{...f,children:g.jsx(t6,{scope:t,contentId:v,open:A,stateAttribute:R,trigger:h,onTriggerChange:b,onTriggerEnter:S.useCallback(()=>{p.isOpenDelayedRef.current?D():L()},[p.isOpenDelayedRef,D,L]),onTriggerLeave:S.useCallback(()=>{w?j():(window.clearTimeout(E.current),E.current=0)},[j,w]),onOpen:L,onClose:j,disableHoverableContent:w,children:r})})};eO.displayName=kl;var _g="TooltipTrigger",tO=S.forwardRef((e,t)=>{const{__scopeTooltip:r,...a}=e,o=Hl(_g,r),s=Iv(_g,r),u=$c(r),d=S.useRef(null),p=_e(t,d,o.onTriggerChange),f=S.useRef(!1),h=S.useRef(!1),b=S.useCallback(()=>f.current=!1,[]);return S.useEffect(()=>()=>document.removeEventListener("pointerup",b),[b]),g.jsx(Lc,{asChild:!0,...u,children:g.jsx(Se.button, Line truncated
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return S.useEffect(()=>{e&&(document.getElementById(e)||console.error(r))},[r,e]),null},mj="DialogDescriptionWarning",hj=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${NN(mj).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},em=gN,bj=hN,tm=yN,nm=vN,rm=EN,RN=xN,yj=TN,CN=kN;function vj({...e}){return g.jsx(em,{"data-slot":"sheet",...e})}function Ej({...e}){return g.jsx(tm,{"data-slot":"sheet-portal",...e})}function Sj({className:e,...t}){return g.jsx(nm,{"data-slot":"sheet-overlay",className:me("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",e),...t})}function xj({className:e,children:t,side:r="right",showCloseButton:a=!0,...o}){return g.jsxs(Ej,{children:[g.jsx(Sj,{}),g.jsxs(rm,{"data-slot":"sheet-content",className:me("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",r==="right"&&"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",r==="left"&&"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",r==="top"&&"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",r==="bottom"&&"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",e),...o,children:[t,a&&g.jsxs(CN,{className:"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none",children:[g.jsx(S_,{className:"size-4"}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function wj({className:e,...t}){return g.jsx("div",{"data-slot":"sheet-header",className:me("flex flex-col gap-1.5 p-4",e),...t})}function Tj({className:e,...t}){return g.jsx(RN,{"data-slot":"sheet-title",className:me("text-foreground font-semibold",e),...t})}function IN({nav:e,activeItem:t,onItemClick:r}){const a=(o,s=0)=>{const u=(o.path||o.title)===t;return o.children&&o.children.length>0?g.jsxs("div",{children:[g.jsx("div",{className:"flex items-center gap-2 px-3 py-1.5 text-xs font-semibold text-muted-foreground/70 uppercase tracking-widest mt-4 first:mt-0",children:o.title}),g.jsx("div",{className:"space-y-0.5",children:o.children.map(p=>a(p,s+1))})]},o.title+(o.path||"")):g.jsx("button",{type:"button",onClick:()=>r(o),className:`w-full text-left px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${u?"bg-primary/10 text-primary font-medium border-l-2 border-primary":"text-muted-foreground hover:text-foreground hover:bg-muted/50"}`,style:{paddingLeft:`${12+s*12}px`},children:o.title},o.title+(o.path||""))};return g.jsx("nav",{className:"space-y-0.5",children:e.items?.map(o=>a(o))})}function _f({nav:e,activeItem:t,onItemClick:r}){return g.jsx("aside",{className:"w-64 min-w-[16rem] border-r border-border/50 hidden md:block",children:g.jsxs(eN,{className:"h-[calc(100vh-4rem)] py-4 px-2",children:[g.jsx(IN,{nav:e,activeItem:t,onItemClick:r}),g.jsx(qi,{className:"my-4"})]})})}function Aj({open:e,onOpenChange:t,nav:r,activeItem:a,onItemClick:o,title:s="Navigation"}){return g.jsx(vj,{open:e,onOpenChange:t,children:g.jsxs(xj,{side:"left",className:"w-72 p-0",children:[g.jsx(wj,{className:"border-b px-4 py-3",children:g.jsx(Tj,{children:s})}),g.jsx(eN,{className:"h-[calc(100vh-4rem)] px-2 py-4",children:g.jsx(IN,{nav:r,activeItem:a,onItemClick:u=>{o(u),t(!1)}})})]})})}function CA(e){const t=[],r=String(e||"");let a=r.indexOf(","),o=0,s=!1;for(;!s;){a===-1&&(a=r.length,s=!0);const u=r.slice(o,a).trim();(u||!s)&&t.push(u),o=a+1,a=r.indexOf(",",o)}return t}function kj(e,t){const r={};return(e[e.length-1]===""?[...e,""]:e).join((r.padRight?" ":"")+","+(r.padLeft===!1?"":" ")).trim()}const _j=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Nj=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Rj={};function IA(e,t){return(Rj.jsx?Nj:_j).test(e)}const Cj=/[ \t\n\f\r]/g;function Ij(e){return typeof e=="object"?e.type==="text"?OA(e.value):!1:OA(e)}function OA(e){return e.replace(Cj,"")===""}class Ul{constructor(t,r,a){this.normal=r,this.property=t,a&&(this.space=a)}}Ul.prototype.normal={};Ul.prototype.property={};Ul.prototype.space=void 0;function ON(e,t){const r={},a={};for(const o of e)Object.assign(r,o.property),Object.assign(a,o.normal);return new Ul(r,a,t)}function kl(e){return e.toLowerCase()}class rn{con Line truncated
`,f="/",h="*",m="",v="comment",E="declaration";function x(k,A){if(typeof k!="string")throw new TypeError("First argument must be a string");if(!k)return[];A=A||{};var _=1,N=1;function D(re){var ee=re.match(t);ee&&(_+=ee.length);var M=re.lastIndexOf(p);N=~M?re.length-M:N+re.length}function F(){var re={line:_,column:N};return function(ee){return ee.position=new L(re),K(),ee}}function L(re){this.start=re,this.end={line:_,column:N},this.source=A.source}L.prototype.content=k;function z(re){var ee=new Error(A.source+":"+_+":"+N+": "+re);if(ee.reason=re,ee.filename=A.source,ee.line=_,ee.column=N,ee.source=k,!A.silent)throw ee}function $(re){var ee=re.exec(k);if(ee){var M=ee[0];return D(M),k=k.slice(M.length),ee}}function K(){$(r)}function j(re){var ee;for(re=re||[];ee=q();)ee!==!1&&re.push(ee);return re}function q(){var re=F();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var ee=2;m!=k.charAt(ee)&&(h!=k.charAt(ee)||f!=k.charAt(ee+1));)++ee;if(ee+=2,m===k.charAt(ee-1))return z("End of comment missing");var M=k.slice(2,ee-2);return N+=2,D(M),k=k.slice(ee),N+=2,re({type:v,comment:M})}}function X(){var re=F(),ee=$(a);if(ee){if(q(),!$(o))return z("property missing ':'");var M=$(s),H=re({type:E,property:w(ee[0].replace(e,m)),value:M?w(M[0].replace(e,m)):m});return $(u),H}}function ne(){var re=[];j(re);for(var ee;ee=X();)ee!==!1&&(re.push(ee),j(re));return re}return K(),ne()}function w(k){return k?k.replace(c,m):m}return Rf=x,Rf}var UA;function $j(){if(UA)return zi;UA=1;var e=zi&&zi.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(zi,"__esModule",{value:!0}),zi.default=r;const t=e(Pj());function r(a,o){let s=null;if(!a||typeof a!="string")return s;const u=(0,t.default)(a),c=typeof o=="function";return u.forEach(p=>{if(p.type!=="declaration")return;const{property:f,value:h}=p;c?o(f,h,p):h&&(s=s||{},s[f]=h)}),s}return zi}var pl={},FA;function Gj(){if(FA)return pl;FA=1,Object.defineProperty(pl,"__esModule",{value:!0}),pl.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,o=/^-(ms)-/,s=function(f){return!f||r.test(f)||e.test(f)},u=function(f,h){return h.toUpperCase()},c=function(f,h){return"".concat(h,"-")},p=function(f,h){return h===void 0&&(h={}),s(f)?f:(f=f.toLowerCase(),h.reactCompat?f=f.replace(o,c):f=f.replace(a,c),f.replace(t,u))};return pl.camelCase=p,pl}var fl,BA;function Hj(){if(BA)return fl;BA=1;var e=fl&&fl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e($j()),r=Gj();function a(o,s){var u={};return!o||typeof o!="string"||(0,t.default)(o,function(c,p){c&&p&&(u[(0,r.camelCase)(c,s)]=p)}),u}return a.default=a,fl=a,fl}var Vj=Hj();const qj=ac(Vj),PN=$N("end"),im=$N("start");function $N(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function Yj(e){const t=im(e),r=PN(e);if(t&&r)return{start:t,end:r}}function El(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?zA(e.position):"start"in e||"end"in e?zA(e):"line"in e||"column"in e?sg(e):""}function sg(e){return PA(e&&e.line)+":"+PA(e&&e.column)}function zA(e){return sg(e&&e.start)+"-"+sg(e&&e.end)}function PA(e){return e&&typeof e=="number"?e:1}class Ft extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let o="",s={},u=!1;if(r&&("line"in r&&"column"in r?s={place:r}:"start"in r&&"end"in r?s={place:r}:"type"in r?s={ancestors:[r],place:r.position}:s={...r}),typeof t=="string"?o=t:!s.cause&&t&&(u=!0,o=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof a=="string"){const p=a.indexOf(":");p===-1?s.ruleId=a:(s.source=a.slice(0,p),s.ruleId=a.slice(p+1))}if(!s.place&&s.ancestors&&s.ancestors){const p=s.ancestors[s.ancestors.length-1];p&&(s.place=p.position)}const c=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=c?c.column:void 0,this.fatal=void 0,this.file="",this.message=o,this.line=c?c.line:void 0,this.name=El(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=u&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Ft.prototype.file="";Ft.prototype.name="";Ft.prototype.reason="";Ft.prototype.message="";Ft.prototype.stack="";Ft.prototype.column=void 0;Ft.prototype.line=void 0;Ft.prototype.ancestors=void 0;Ft.prototype.cause=void 0;Ft.prototype.fatal=void 0;Ft.prototype.place=void 0;Ft.prototype.ruleId=void 0;Ft.prototype.source=void 0;const om={}.hasOwnProperty,Wj=new Map,Kj=/[A-Z]/g,Xj=new Set(["table","tbody","thead","tfoot","tr"]),Zj=new Set(["td","th"]),GN="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function Qj(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options Line truncated
`;break}case-3:{u=`\r
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function oB(e){const a={constructs:WN([nB,...(e||{}).extensions||[]]),content:o(SU),defined:[],document:o(wU),flow:o(PF),lazy:{},string:o(HF),text:o(VF)};return a;function o(s){return u;function u(c){return rB(a,s,c)}}}function lB(e){for(;!JN(e););return e}const ZA=/[\0\t\n\r]/g;function sB(){let e=1,t="",r=!0,a;return o;function o(s,u,c){const p=[];let f,h,m,v,E;for(s=t+(typeof s=="string"?s.toString():new TextDecoder(u||void 0).decode(s)),m=0,t="",r&&(s.charCodeAt(0)===65279&&m++,r=void 0);m<s.length;){if(ZA.lastIndex=m,f=ZA.exec(s),v=f&&f.index!==void 0?f.index:s.length,E=s.charCodeAt(v),!f){t=s.slice(m);break}if(E===10&&m===v&&a)p.push(-3),a=void 0;else switch(a&&(p.push(-5),a=void 0),m<v&&(p.push(s.slice(m,v)),e+=v-m),E){case 0:{p.push(65533),e++;break}case 9:{for(h=Math.ceil(e/4)*4,p.push(-2);e++<h;)p.push(-1);break}case 10:{p.push(-4),e=1;break}default:a=!0,e=1}m=v+1}return c&&(a&&p.push(-5),t&&p.push(t),p.push(null)),p}}const uB=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function cB(e){return e.replace(uB,dB)}function dB(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return KN(r.slice(s?2:1),s?16:10)}return Nl(r)||e}const iR={}.hasOwnProperty;function pB(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),fB(r)(lB(oB(r).document().write(sB()(e,t,!0))))}function fB(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Za),autolinkProtocol:K,autolinkEmail:K,atxHeading:s(Yn),blockQuote:s(ke),characterEscape:K,characterReference:K,codeFenced:s(Ie),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Ie,u),codeText:s(qe,u),codeTextData:K,data:K,codeFlowValue:K,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Wn),hardBreakTrailing:s(Wn),htmlFlow:s(Ir,u),htmlFlowData:K,htmlText:s(Ir,u),htmlTextData:K,image:s(Jl),label:u,link:s(Za),listItem:s(bo),listItemValue:v,listOrdered:s(Qa,m),listUnordered:s(Qa),paragraph:s(Zc),reference:C,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(Yn),strong:s(Qc),thematicBreak:s(Jc)},exit:{atxHeading:p(),atxHeadingSequence:F,autolink:p(),autolinkEmail:fe,autolinkProtocol:he,blockQuote:p(),characterEscapeValue:j,characterReferenceMarkerHexadecimal:ae,characterReferenceMarkerNumeric:ae,characterReferenceValue:ue,characterReference:ie,codeFenced:p(k),codeFencedFence:w,codeFencedFenceInfo:E,codeFencedFenceMeta:x,codeFlowValue:j,codeIndented:p(A),codeText:p(ee),codeTextData:j,data:j,definition:p(),definitionDestinationString:D,definitionLabelString:_,definitionTitleString:N,emphasis:p(),hardBreakEscape:p(X),hardBreakTrailing:p(X),htmlFlow:p(ne),htmlFlowData:j,htmlText:p(re),htmlTextData:j,image:p(H),label:de,labelText:Q,lineEnding:q,link:p(M),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:J,resourceDestinationString:I,resourceTitleString:U,resource:B,setextHeading:p($),setextHeadingLineSequence:z,setextHeadingText:L,strong:p(),thematicBreak:p()}};oR(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(le){let ge={type:"root",children:[]};const _e={stack:[ge],tokenStack:[],config:t,enter:c,exit:f,buffer:u,resume:h,data:r},Me=[];let We=-1;for(;++We<le.length;)if(le[We][1].type==="listOrdered"||le[We][1].type==="listUnordered")if(le[We][0]==="enter")Me.push(We);else{const an=Me.pop();We=o(le,an,We)}for(We=-1;++We<le.length;){const an=t[le[We][0]];iR.call(an,le[We][1].type)&&an[le[We][1].type].call(Object.assign({sliceSerialize:le[We][2].sliceSerialize},_e),le[We][1])}if(_e.tokenStack.length>0){const an=_e.tokenStack[_e.tokenStack.length-1];(an[1]||QA).call(_e,void 0,an[0])}for(ge.position={start:ia(le.length>0?le[0][1].start:{line:1,column:1,offset:0}),end:ia(le.length>0?le[le.length-2][1].end:{line:1,column:1,offset:0})},We=-1;++We<t.transforms.length;)ge=t.transforms[We](ge)||ge;return ge}function o(le,ge,_e){let Me=ge-1,We=-1,an=!1,lr,$t,Et,qt;for(;++Me<=_e;){const tt=le[Me];switch(tt[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{tt[0]==="enter"?We++:We--,qt=void 0;break}case"lineEndingBlank":{tt[0]==="enter"&&(lr&&!qt&&!We&&!Et&&(Et=Me),qt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:qt=void 0}if(!We&&tt[0]==="enter"&&tt[1].type==="listItemPrefix"||We===-1&&tt[0]==="exit"&&(tt[1].type==="listUnordered"||tt[1].type==="listOrdered")){if(lr){let Or=Me;for($t=void 0;Or--;){const Ln=le[Or];if(Ln[1].type==="lineEnding"||Ln[1].type==="lineEndingBlank"){if(Ln[0]==="exit")continue;$t&&(le[$t][1].type="lineEndingBlank",an=!0),Ln[1].type="lineEnding",$t=Or}else if(!(Ln[1].type==="linePrefix"||Ln[1].type==="blockQuotePrefix"||Ln[1].type==="blockQuotePrefixWhitespLine truncated
`}]}function yB(e,t){const r=t.value?t.value+`
`:"",a={},o=t.lang?t.lang.split(/\s+/):[];o.length>0&&(a.className=["language-"+o[0]]);let s={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(s.data={meta:t.meta}),e.patch(t,s),s=e.applyData(t,s),s={type:"element",tagName:"pre",properties:{},children:[s]},e.patch(t,s),s}function vB(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function EB(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function SB(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),o=ro(a.toLowerCase()),s=e.footnoteOrder.indexOf(a);let u,c=e.footnoteCounts.get(a);c===void 0?(c=0,e.footnoteOrder.push(a),u=e.footnoteOrder.length):u=s+1,c+=1,e.footnoteCounts.set(a,c);const p={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+o,id:r+"fnref-"+o+(c>1?"-"+c:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(u)}]};e.patch(t,p);const f={type:"element",tagName:"sup",properties:{},children:[p]};return e.patch(t,f),e.applyData(t,f)}function xB(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wB(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function lR(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const o=e.all(t),s=o[0];s&&s.type==="text"?s.value="["+s.value:o.unshift({type:"text",value:"["});const u=o[o.length-1];return u&&u.type==="text"?u.value+=a:o.push({type:"text",value:a}),o}function TB(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return lR(e,t);const o={src:ro(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"img",properties:o,children:[]};return e.patch(t,s),e.applyData(t,s)}function AB(e,t){const r={src:ro(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function kB(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function _B(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return lR(e,t);const o={href:ro(a.url||"")};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"a",properties:o,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function NB(e,t){const r={href:ro(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function RB(e,t,r){const a=e.all(t),o=r?CB(r):sR(t),s={},u=[];if(typeof t.checked=="boolean"){const h=a[0];let m;h&&h.type==="element"&&h.tagName==="p"?m=h:(m={type:"element",tagName:"p",properties:{},children:[]},a.unshift(m)),m.children.length>0&&m.children.unshift({type:"text",value:" "}),m.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let c=-1;for(;++c<a.length;){const h=a[c];(o||c!==0||h.type!=="element"||h.tagName!=="p")&&u.push({type:"text",value:`
`}),h.type==="element"&&h.tagName==="p"&&!o?u.push(...h.children):u.push(h)}const p=a[a.length-1];p&&(o||p.type!=="element"||p.tagName!=="p")&&u.push({type:"text",value:`
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function CB(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=sR(r[a])}return t}function sR(e){const t=e.spread;return t??e.children.length>1}function IB(e,t){const r={},a=e.all(t);let o=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++o<a.length;){const u=a[o];if(u.type==="element"&&u.tagName==="li"&&u.properties&&Array.isArray(u.properties.className)&&u.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const s={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,s),e.applyData(t,s)}function OB(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function LB(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function DB(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function MB(e,t){const r=e.all(t),a=r.shift(),o=[];if(a){const u={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],u),o.push(u)}if(r.length>0){const u={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},c=im(t.children[1]),p=PN(t.children[t.children.length-1]);c&&p&&(u.position={start:c,end:p}),o.push(u)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(o,!0)};return e.patch(t,s),e.applyData(t,s)}function jB(e,t,r){const a=r?r.children:void 0,s=(a?a.indexOf(t):1)===0?"th":"td",u=r&&r.type==="table"?r.align:void 0,c=u?u.length:t.children.length;let p=-1;const f=[];for(;++p<c;){const m=t.children[p],v={},E=u?u[p]:void 0;E&&(v.align=E);let x={type:"element",tagName:s,properties:v,children:[]};m&&(x.children=e.all(m),e.patch(m,x),x=e.applyData(m,x)),f.push(x)}const h={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,h),e.applyData(t,h)}function UB(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const JA=9,ek=32;function FB(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(tk(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(tk(t.slice(o),o>0,!1)),s.join("")}function tk(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===JA||s===ek;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===JA||s===ek;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function BB(e,t){const r={type:"text",value:FB(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function zB(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const PB={blockquote:hB,break:bB,code:yB,delete:vB,emphasis:EB,footnoteReference:SB,heading:xB,html:wB,imageReference:TB,image:AB,inlineCode:kB,linkReference:_B,link:NB,listItem:RB,list:IB,paragraph:OB,root:LB,strong:DB,table:MB,tableCell:UB,tableRow:jB,text:BB,thematicBreak:zB,toml:wu,yaml:wu,definition:wu,footnoteDefinition:wu};function wu(){}const uR=-1,hc=0,xl=1,Yu=2,dm=3,pm=4,fm=5,gm=6,cR=7,dR=8,nk=typeof self=="object"?self:globalThis,$B=(e,t)=>{const r=(o,s)=>(e.set(s,o),o),a=o=>{if(e.has(o))return e.get(o);const[s,u]=t[o];switch(s){case hc:case uR:return r(u,o);case xl:{const c=r([],o);for(const p of u)c.push(a(p));return c}case Yu:{const c=r({},o);for(const[p,f]of u)c[a(p)]=a(f);return c}case dm:return r(new Date(u),o);case pm:{const{source:c,flags:p}=u;return r(new RegExp(c,p),o)}case fm:{const c=r(new Map,o);for(const[p,f]of u)c.set(a(p),a(f));return c}case gm:{const c=r(new Set,o);for(const p of u)c.add(a(p));return c}case cR:{const{name:c,message:p}=u;return r(new nk[c](p),o)}case dR:return r(BigInt(u),o);case"BigInt":return r(Object(BigInt(u)),o);case"ArrayBuffer":return r(new Uint8Array(u).buffer,u);case"DataView":{const{buffer:c}=new Uint8Array(u);return r(new DataView(c),u)}}return r(new nk[s](u),o)};return a},rk=e=>$B(new Map,e)(0),Pi="",{toString:GB}={},{keys:HB}=Object,ml=e=>{const t=typeof e;if(t!=="object"||!e)return[hc,t];const r=GB.call(e).slice(8,-1);switch(r){case"Array":return[xl,Pi];case"Object":return[Yu,Pi];case"Date":return[dm,Pi];case"RegExp":return[pm,Pi];case"Map":return[fm,Pi];case"Set":return[gm,Pi];case"DataView":return[xl,r]}return r.includes("Array")?[xl,r]:r.includes("Error")?[cR,r]:[Yu,r]},Tu=([e,t])=>e===hc&&(t==="function"||t==="symbol"),VB=(e,t,r,a)=>{const o=(u,c)=>{const p=a.push(u)-1;return r.set(c,p),p},s=u=>{if(r.has(u))return r.get(u);let[c,p]=ml(u);switch(c){case hc:{let h=u;switch(p){case"bigint":c=dR,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([uR],u)}return o([c,h],u)}case xl:{if(p){let v=u;return p==="DataView"?v=new Uint8Array(u.buffer):p==="ArrayBuffer"&&(v=new Uint8Array( Line truncated
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(c,!0)},{type:"text",value:`
`}]}}const bc=(function(e){if(e==null)return QB;if(typeof e=="function")return yc(e);if(typeof e=="object")return Array.isArray(e)?KB(e):XB(e);if(typeof e=="string")return ZB(e);throw new Error("Expected function, string, or object as test")});function KB(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=bc(e[r]);return yc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function XB(e){const t=e;return yc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function ZB(e){return yc(t);function t(r){return r&&r.type===e}}function yc(e){return t;function t(r,a,o){return!!(JB(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function QB(){return!0}function JB(e){return e!==null&&typeof e=="object"&&"type"in e}const pR=[],ez=!0,dg=!1,tz="skip";function fR(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=bc(o),u=a?-1:1;c(e,void 0,[])();function c(p,f,h){const m=p&&typeof p=="object"?p:{};if(typeof m.type=="string"){const E=typeof m.tagName=="string"?m.tagName:typeof m.name=="string"?m.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(p.type+(E?"<"+E+">":""))+")"})}return v;function v(){let E=pR,x,w,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=nz(r(p,h)),E[0]===dg))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==tz)for(w=(a?A.children.length:-1)+u,k=h.concat(A);w>-1&&w<A.children.length;){const _=A.children[w];if(x=c(_,w,k)(),x[0]===dg)return x;w=typeof x[1]=="number"?x[1]:w+u}}return E}}}function nz(e){return Array.isArray(e)?e:typeof e=="number"?[ez,e]:e==null?pR:[e]}function mm(e,t,r,a){let o,s,u;typeof t=="function"&&typeof r!="function"?(s=void 0,u=t,o=r):(s=t,u=r,o=a),fR(e,s,c,o);function c(p,f){const h=f[f.length-1],m=h?h.children.indexOf(p):void 0;return u(p,m,h)}}const pg={}.hasOwnProperty,rz={};function az(e,t){const r=t||rz,a=new Map,o=new Map,s=new Map,u={...PB,...r.handlers},c={all:f,applyData:oz,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:iz,wrap:sz};return mm(e,function(h){if(h.type==="definition"||h.type==="footnoteDefinition"){const m=h.type==="definition"?a:o,v=String(h.identifier).toUpperCase();m.has(v)||m.set(v,h)}}),c;function p(h,m){const v=h.type,E=c.handlers[v];if(pg.call(c.handlers,v)&&E)return E(c,h,m);if(c.options.passThrough&&c.options.passThrough.includes(v)){if("children"in h){const{children:w,...k}=h,A=Wu(k);return A.children=c.all(h),A}return Wu(h)}return(c.options.unknownHandler||lz)(c,h,m)}function f(h){const m=[];if("children"in h){const v=h.children;let E=-1;for(;++E<v.length;){const x=c.one(v[E],h);if(x){if(E&&v[E-1].type==="break"&&(!Array.isArray(x)&&x.type==="text"&&(x.value=ik(x.value)),!Array.isArray(x)&&x.type==="element")){const w=x.children[0];w&&w.type==="text"&&(w.value=ik(w.value))}Array.isArray(x)?m.push(...x):m.push(x)}}}return m}}function iz(e,t){e.position&&(t.position=Yj(e))}function oz(e,t){let r=t;if(e&&e.data){const a=e.data.hName,o=e.data.hChildren,s=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const u="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:u}}r.type==="element"&&s&&Object.assign(r.properties,Wu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function lz(e,t){const r=t.data||{},a="value"in t&&!(pg.call(r,"hProperties")||pg.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function sz(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
`});++a<e.length;)a&&r.push({type:"text",value:`
`}),r.push(e[a]);return t&&e.length>0&&r.push({type:"text",value:`
`}),r}function ik(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function ok(e,t){const r=az(e,t),a=r.one(e,void 0),o=WB(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
`},o),s}function uz(e,t){return e&&"run"in e?async function(r,a){const o=ok(r,{file:a,...t});await e.run(o,a)}:function(r,a){return ok(r,{file:a,...e||t})}}function lk(e){if(e)throw e}var Lf,sk;function cz(){if(sk)return Lf;sk=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},s=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var h=e.call(f,"constructor"),m=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!h&&!m)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},u=function(f,h){r&&h.name==="__proto__"?r(f,h.name,{enumerable:!0,configurable:!0,value:h.newValue,writable:!0}):f[h.name]=h.newValue},c=function(f,h){if(h==="__proto__")if(e.call(f,h)){if(a)return a(f,h).value}else return;return f[h]};return Lf=function p(){var f,h,m,v,E,x,w=arguments[0],k=1,A=arguments.length,_=!1;for(typeof w=="boolean"&&(_=w,w=arguments[1]||{},k=2),(w==null||typeof w!="object"&&typeof w!="function")&&(w={});k<A;++k)if(f=arguments[k],f!=null)for(h in f)m=c(w,h),v=c(f,h),w!==v&&(_&&v&&(s(v)||(E=o(v)))?(E?(E=!1,x=m&&o(m)?m:[]):x=m&&s(m)?m:{},u(w,{name:h,newValue:p(_,x,v)})):typeof v<"u"&&u(w,{name:h,newValue:v}));return w},Lf}var dz=cz();const Df=ac(dz);function fg(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function pz(){const e=[],t={run:r,use:a};return t;function r(...o){let s=-1;const u=o.pop();if(typeof u!="function")throw new TypeError("Expected function as last argument, not "+u);c(null,...o);function c(p,...f){const h=e[++s];let m=-1;if(p){u(p);return}for(;++m<o.length;)(f[m]===null||f[m]===void 0)&&(f[m]=o[m]);o=f,h?fz(h,c)(...f):u(null,...f)}}function a(o){if(typeof o!="function")throw new TypeError("Expected `middelware` to be a function, not "+o);return e.push(o),t}}function fz(e,t){let r;return a;function a(...u){const c=e.length>u.length;let p;c&&u.push(o);try{p=e.apply(this,u)}catch(f){const h=f;if(c&&r)throw h;return o(h)}c||(p&&p.then&&typeof p.then=="function"?p.then(s,o):p instanceof Error?o(p):s(p))}function o(u,...c){r||(r=!0,t(u,...c))}function s(u){o(null,u)}}const Jn={basename:gz,dirname:mz,extname:hz,join:bz,sep:"/"};function gz(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Bl(e);let r=0,a=-1,o=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else a<0&&(s=!0,a=o+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let u=-1,c=t.length-1;for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else u<0&&(s=!0,u=o+1),c>-1&&(e.codePointAt(o)===t.codePointAt(c--)?c<0&&(a=o):(c=-1,a=u));return r===a?a=u:a<0&&(a=e.length),e.slice(r,a)}function mz(e){if(Bl(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function hz(e){Bl(e);let t=e.length,r=-1,a=0,o=-1,s=0,u;for(;t--;){const c=e.codePointAt(t);if(c===47){if(u){a=t+1;break}continue}r<0&&(u=!0,r=t+1),c===46?o<0?o=t:s!==1&&(s=1):o>-1&&(s=-1)}return o<0||r<0||s===0||s===1&&o===r-1&&o===a+1?"":e.slice(o,r)}function bz(...e){let t=-1,r;for(;++t<e.length;)Bl(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":yz(r)}function yz(e){Bl(e);const t=e.codePointAt(0)===47;let r=vz(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function vz(e,t){let r="",a=0,o=-1,s=0,u=-1,c,p;for(;++u<=e.length;){if(u<e.length)c=e.codePointAt(u);else{if(c===47)break;c=47}if(c===47){if(!(o===u-1||s===1))if(o!==u-1&&s===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(p=r.lastIndexOf("/"),p!==r.length-1){p<0?(r="",a=0):(r=r.slice(0,p),a=r.length-1-r.lastIndexOf("/")),o=u,s=0;continue}}else if(r.length>0){r="",a=0,o=u,s=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(o+1,u):r=e.slice(o+1,u),a=u-o-1;o=u,s=0}else c===46&&s>-1?s++:s=-1}return r}function Bl(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const Ez={cwd:Sz};function Sz(){return"/"}function gg(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function xz(e){if(typeof e=="string")e=new URL(e);else if(!gg(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return wz(e)}function wz(e){if(e.hostname!==""){consLine truncated
`:" ")+s.indentLines(s.containerFlow(a,c.current()),t?yR:pP))),f(),p}}function pP(e,t,r){return t===0?e:yR(e,t,r)}function yR(e,t,r){return(r?"":" ")+e}const fP=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];vR.peek=yP;function gP(){return{canContainEols:["delete"],enter:{strikethrough:hP},exit:{strikethrough:bP}}}function mP(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:fP}],handlers:{delete:vR}}}function hP(e){this.enter({type:"delete",children:[]},e)}function bP(e){this.exit(e)}function vR(e,t,r,a){const o=r.createTracker(a),s=r.enter("strikethrough");let u=o.move("~~");return u+=r.containerPhrasing(e,{...o.current(),before:u,after:"~"}),u+=o.move("~~"),s(),u}function yP(){return"~"}function vP(e){return e.length}function EP(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||vP,s=[],u=[],c=[],p=[];let f=0,h=-1;for(;++h<e.length;){const w=[],k=[];let A=-1;for(e[h].length>f&&(f=e[h].length);++A<e[h].length;){const _=SP(e[h][A]);if(r.alignDelimiters!==!1){const N=o(_);k[A]=N,(p[A]===void 0||N>p[A])&&(p[A]=N)}w.push(_)}u[h]=w,c[h]=k}let m=-1;if(typeof a=="object"&&"length"in a)for(;++m<f;)s[m]=mk(a[m]);else{const w=mk(a);for(;++m<f;)s[m]=w}m=-1;const v=[],E=[];for(;++m<f;){const w=s[m];let k="",A="";w===99?(k=":",A=":"):w===108?k=":":w===114&&(A=":");let _=r.alignDelimiters===!1?1:Math.max(1,p[m]-k.length-A.length);const N=k+"-".repeat(_)+A;r.alignDelimiters!==!1&&(_=k.length+_+A.length,_>p[m]&&(p[m]=_),E[m]=_),v[m]=N}u.splice(1,0,v),c.splice(1,0,E),h=-1;const x=[];for(;++h<u.length;){const w=u[h],k=c[h];m=-1;const A=[];for(;++m<f;){const _=w[m]||"";let N="",D="";if(r.alignDelimiters!==!1){const F=p[m]-(k[m]||0),L=s[m];L===114?N=" ".repeat(F):L===99?F%2?(N=" ".repeat(F/2+.5),D=" ".repeat(F/2-.5)):(N=" ".repeat(F/2),D=N):D=" ".repeat(F)}r.delimiterStart!==!1&&!m&&A.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&_==="")&&(r.delimiterStart!==!1||m)&&A.push(" "),r.alignDelimiters!==!1&&A.push(N),A.push(_),r.alignDelimiters!==!1&&A.push(D),r.padding!==!1&&A.push(" "),(r.delimiterEnd!==!1||m!==f-1)&&A.push("|")}x.push(r.delimiterEnd===!1?A.join("").replace(/ +$/,""):A.join(""))}return x.join(`
`)}function SP(e){return e==null?"":String(e)}function mk(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function xP(e,t,r,a){const o=r.enter("blockquote"),s=r.createTracker(a);s.move("> "),s.shift(2);const u=r.indentLines(r.containerFlow(e,s.current()),wP);return o(),u}function wP(e,t,r){return">"+(r?"":" ")+e}function TP(e,t){return hk(e,t.inConstruct,!0)&&!hk(e,t.notInConstruct,!1)}function hk(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function bk(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
`&&TP(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
`}function AP(e,t){const r=String(e);let a=r.indexOf(t),o=a,s=0,u=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===o?++s>u&&(u=s):s=1,o=a+t.length,a=r.indexOf(t,o);return u}function kP(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function _P(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function NP(e,t,r,a){const o=_P(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(kP(e,r)){const m=r.enter("codeIndented"),v=r.indentLines(s,RP);return m(),v}const c=r.createTracker(a),p=o.repeat(Math.max(AP(s,o)+1,3)),f=r.enter("codeFenced");let h=c.move(p);if(e.lang){const m=r.enter(`codeFencedLang${u}`);h+=c.move(r.safe(e.lang,{before:h,after:" ",encode:["`"],...c.current()})),m()}if(e.lang&&e.meta){const m=r.enter(`codeFencedMeta${u}`);h+=c.move(" "),h+=c.move(r.safe(e.meta,{before:h,after:`
`,encode:["`"],...c.current()})),m()}return h+=c.move(`
`),s&&(h+=c.move(s+`
`)),h+=c.move(p),f(),h}function RP(e,t,r){return(r?"":" ")+e}function bm(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function CP(e,t,r,a){const o=bm(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("definition");let c=r.enter("label");const p=r.createTracker(a);let f=p.move("[");return f+=p.move(r.safe(r.associationId(e),{before:f,after:"]",...p.current()})),f+=p.move("]: "),c(),!e.url||/[\0- \u007F]/.test(e.url)?(c=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(c=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":`
`,...p.current()}))),c(),e.title&&(c=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),c()),u(),f}function IP(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function Rl(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Ku(e,t,r){const a=Qi(e),o=Qi(t);return a===void 0?o===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}ER.peek=OP;function ER(e,t,r,a){const o=IP(r),s=r.enter("emphasis"),u=r.createTracker(a),c=u.move(o);let p=u.move(r.containerPhrasing(e,{after:o,before:c,...u.current()}));const f=p.charCodeAt(0),h=Ku(a.before.charCodeAt(a.before.length-1),f,o);h.inside&&(p=Rl(f)+p.slice(1));const m=p.charCodeAt(p.length-1),v=Ku(a.after.charCodeAt(0),m,o);v.inside&&(p=p.slice(0,-1)+Rl(m));const E=u.move(o);return s(),r.attentionEncodeSurroundingInfo={after:v.outside,before:h.outside},c+p+E}function OP(e,t,r){return r.options.emphasis||"*"}function LP(e,t){let r=!1;return mm(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,dg}),!!((!e.depth||e.depth<3)&&um(e)&&(t.options.setext||r))}function DP(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(LP(e,r)){const h=r.enter("headingSetext"),m=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
`,after:`
`});return m(),h(),v+`
`+(o===1?"=":"-").repeat(v.length-(Math.max(v.lastIndexOf("\r"),v.lastIndexOf(`
`))+1))}const u="#".repeat(o),c=r.enter("headingAtx"),p=r.enter("phrasing");s.move(u+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
`,...s.current()});return/^[\t ]/.test(f)&&(f=Rl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),c(),f}SR.peek=MP;function SR(e){return e.value||""}function MP(){return"<"}xR.peek=jP;function xR(e,t,r,a){const o=bm(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("image");let c=r.enter("label");const p=r.createTracker(a);let f=p.move("![");return f+=p.move(r.safe(e.alt,{before:f,after:"]",...p.current()})),f+=p.move("]("),c(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(c=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(c=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":")",...p.current()}))),c(),e.title&&(c=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),c()),f+=p.move(")"),u(),f}function jP(){return"!"}wR.peek=UP;function wR(e,t,r,a){const o=e.referenceType,s=r.enter("imageReference");let u=r.enter("label");const c=r.createTracker(a);let p=c.move("![");const f=r.safe(e.alt,{before:p,after:"]",...c.current()});p+=c.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const m=r.safe(r.associationId(e),{before:p,after:"]",...c.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==m?p+=c.move(m+"]"):o==="shortcut"?p=p.slice(0,-1):p+=c.move("]"),p}function UP(){return"!"}TR.peek=FP;function TR(e,t,r){let a=e.value||"",o="`",s=-1;for(;new RegExp("(^|[^`])"+o+"([^`]|$)").test(a);)o+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++s<r.unsafe.length;){const u=r.unsafe[s],c=r.compilePattern(u);let p;if(u.atBreak)for(;p=c.exec(a);){let f=p.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(p.index+1)}}return o+a+o}function FP(){return"`"}function AR(e,t){const r=um(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}kR.peek=BP;function kR(e,t,r,a){const o=bm(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let c,p;if(AR(e,r)){const h=r.stack;r.stack=[],c=r.enter("autolink");let m=u.move("<");return m+=u.move(r.containerPhrasing(e,{before:m,after:">",...u.current()})),m+=u.move(">"),c(),r.stack=h,m}c=r.enter("link"),p=r.enter("label");let f=u.move("[");return f+=u.move(r.containerPhrasing(e,{before:f,after:"](",...u.current()})),f+=u.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=u.move("<"),f+=u.move(r.safe(e.url,{before:f,after:">",...u.current()})),f+=u.move(">")):(p=r.enter("destinationRaw"),f+=u.move(r.safe(e.url,{before:f,after:e.title?" ":")",...u.current()}))),p(),e.title&&(p=r.enter(`title${s}`),f+=u.move(" "+o),f+=u.move(r.safe(e.title,{before:f,after:o,...u.current()})),f+=u.move(o),p()),f+=u.move(")"),c(),f}function BP(e,t,r){return AR(e,r)?"<":"["}_R.peek=zP;function _R(e,t,r,a){const o=e.referenceType,s=r.enter("linkReference");let u=r.enter("label");const c=r.createTracker(a);let p=c.move("[");const f=r.containerPhrasing(e,{before:p,after:"]",...c.current()});p+=c.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const m=r.safe(r.associationId(e),{before:p,after:"]",...c.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==m?p+=c.move(m+"]"):o==="shortcut"?p=p.slice(0,-1):p+=c.move("]"),p}function zP(){return"["}function ym(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function PP(e){const t=ym(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function $P(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function NR(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function GP(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?$P(r):ym(r);const c=e.ordered?u==="."?")":".":PP(r);let p=t&&r.bulletLastUsed?u===r.bulletLastUsed:!1;if(!e.ordered){const h=e.children?e.children[0]:void 0;if((u==="*"||u==="-")&&h&&(!h.children||!h.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(p=!0),NR(r)===u&&h){let m=-1;for(;++m<e.chilLine truncated
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:v,table:u,tableCell:p,tableRow:c}};function u(E,x,w,k){return f(h(E,w,k),E.align)}function c(E,x,w,k){const A=m(E,w,k),_=f([A]);return _.slice(0,_.indexOf(`
`))}function p(E,x,w,k){const A=w.enter("tableCell"),_=w.enter("phrasing"),N=w.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),N}function f(E,x){return EP(E,{align:x,alignDelimiters:a,padding:r,stringLength:o})}function h(E,x,w){const k=E.children;let A=-1;const _=[],N=x.enter("table");for(;++A<k.length;)_[A]=m(k[A],x,w);return N(),_}function m(E,x,w){const k=E.children;let A=-1;const _=[],N=x.enter("tableRow");for(;++A<k.length;)_[A]=p(k[A],E,x,w);return N(),_}function v(E,x,w){let k=CR.inlineCode(E,x,w);return w.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function l$(){return{exit:{taskListCheckValueChecked:vk,taskListCheckValueUnchecked:vk,paragraph:u$}}}function s$(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:c$}}}function vk(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function u$(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const o=t.children;let s=-1,u;for(;++s<o.length;){const c=o[s];if(c.type==="paragraph"){u=c;break}}u===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function c$(e,t,r,a){const o=e.children[0],s=typeof e.checked=="boolean"&&o&&o.type==="paragraph",u="["+(e.checked?"x":" ")+"] ",c=r.createTracker(a);s&&c.move(u);let p=CR.listItem(e,t,r,{...a,...c.current()});return s&&(p=p.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),p;function f(h){return h+u}}function d$(){return[Gz(),cP(),gP(),e$(),l$()]}function p$(e){return{extensions:[Hz(),dP(e),mP(),o$(e),s$()]}}const f$={tokenize:v$,partial:!0},IR={tokenize:E$,partial:!0},OR={tokenize:S$,partial:!0},LR={tokenize:x$,partial:!0},g$={tokenize:w$,partial:!0},DR={name:"wwwAutolink",tokenize:b$,previous:jR},MR={name:"protocolAutolink",tokenize:y$,previous:UR},Rr={name:"emailAutolink",tokenize:h$,previous:FR},ar={};function m$(){return{text:ar}}let ja=48;for(;ja<123;)ar[ja]=Rr,ja++,ja===58?ja=65:ja===91&&(ja=97);ar[43]=Rr;ar[45]=Rr;ar[46]=Rr;ar[95]=Rr;ar[72]=[Rr,MR];ar[104]=[Rr,MR];ar[87]=[Rr,DR];ar[119]=[Rr,DR];function h$(e,t,r){const a=this;let o,s;return u;function u(m){return!mg(m)||!FR.call(a,a.previous)||vm(a.events)?r(m):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),c(m))}function c(m){return mg(m)?(e.consume(m),c):m===64?(e.consume(m),p):r(m)}function p(m){return m===46?e.check(g$,h,f)(m):m===45||m===95||Mt(m)?(s=!0,e.consume(m),p):h(m)}function f(m){return e.consume(m),o=!0,p}function h(m){return s&&o&&Vt(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(m)):r(m)}}function b$(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!jR.call(a,a.previous)||vm(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(f$,e.attempt(IR,e.attempt(OR,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function y$(e,t,r){const a=this;let o="",s=!1;return u;function u(m){return(m===72||m===104)&&UR.call(a,a.previous)&&!vm(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),o+=String.fromCodePoint(m),e.consume(m),c):r(m)}function c(m){if(Vt(m)&&o.length<5)return o+=String.fromCodePoint(m),e.consume(m),c;if(m===58){const v=o.toLowerCase();if(v==="http"||v==="https")return e.consume(m),p}return r(m)}function p(m){return m===47?(e.consume(m),s?f:(s=!0,p)):r(m)}function f(m){return m===null||qu(m)||rt(m)||$a(m)||gc(m)?r(m):e.attempt(IR,e.attempt(OR,h),r)(m)}function h(m){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(m)}}function v$(e,t,r){let a=0;return o;function o(u){return(u===87||u===119)&&a<3?(a++,e.consume(u),o):u===46&&a===3?(e.consume(u),s):r(u)}function s(u){return u===null?r(u):t(u)}}function E$(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(LR,p,c)(f):f===null||rt(f)||$a(f)||f!==45&&gc(f)?p(f):(s=!0,e.consume(f),u)}function c(f){return f===95?a=!0:(o=a,a=void 0),e.consume(f),u}function p(f){return o||a||!s?r(f):t(f)}}function S$(e,t){let r=0,a=0;return o;function o(u){return u===40?(r++,e.consume(u),o):u===41&&a<r?s(u):u===33||u===34||u===38||u===39||u===41||u===42||u===44||u===46||u===58||u===59||u===60||u===63||u===93||u===95||u===126?e.check(LR,t,s)(u):u===null||rt(u)||$a(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function x$(e,t,r){return a;function a(c){return c===33||c===34||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===63||c===95||c===126?(e.consume(c),a):c===38?(e.consume(c),s):c===93?(e.consume(c),o):c===60||c===null||rt(c)||$a(c)?t(c):r(c)}function o(c){return c===null||c===40||c===91||rt(c)||$a(c)?t(c):a(c)}function s(c){return Vt(c)?u(c):r(c)}function u(c){re Line truncated
`),r=[];let a=0;for(;a<t.length;){const o=t[a].match(/^===\s+"([^"]+)"\s*$/);if(!o){r.push(t[a]),a++;continue}const s=o[1];for(a++;a<t.length&&t[a].trim()==="";)a++;const u=[];for(;a<t.length&&!(/^===\s+"[^"]+"\s*$/.test(t[a])||t[a].trim()===""&&a+1<t.length&&/^===\s+"[^"]+"\s*$/.test(t[a+1]));){if(t[a].startsWith(" ")||t[a].trimStart().startsWith("```")){u.push(t[a].replace(/^ /,"")),a++;continue}if(u.length>0&&t[a].trim()===""){u.push(""),a++;continue}break}r.push(`### ${zR}${s}`),r.push(""),r.push(...u),r.push("")}return r.join(`
`)}function X$(e){let t=W$(e);return t=Y$(t),t=K$(t),t}function Sk(e){return e.startsWith(zR)||e.startsWith("tab:")}function Z$(e){return e.replace(/^\u200Btab:/,"").replace(/^tab:/,"")}function Q$(e){const t=new Set;e.matches("article.prose, main .prose, .prose, main.flex-1, #main-content")&&t.add(e),e.querySelectorAll("article.prose, main .prose, .prose").forEach(r=>t.add(r));for(const r of t)J$(r),e3(r),t3(r)}function J$(e){for(const t of e.querySelectorAll("p")){const r=t.textContent?.trim()??"";(/^<\/?div[\s>]/.test(r)||r==="</div>")&&t.remove()}}function e3(e){for(const t of e.querySelectorAll("ul")){if(t.classList.contains("aiui-feature-grid"))continue;const r=t.querySelector("li");if(r?.textContent?.includes("YAML-Driven")||r?.textContent?.includes("Component Slots")||r?.querySelector("strong")){t.classList.add("aiui-feature-grid","grid","grid-cols-1","md:grid-cols-2","gap-4","my-6","list-none","pl-0");for(const o of t.querySelectorAll("li"))o.classList.add("border","border-border","rounded-lg","p-4","bg-card")}}}function t3(e){if(e.querySelector("[data-aiui-tabs]"))return;const t=Array.from(e.querySelectorAll("h3")).filter(c=>Sk(c.textContent?.trim()??""));if(t.length<2)return;const r=t.map(c=>{const p=Z$(c.textContent?.trim()??""),f=[];let h=c.nextSibling;for(;h;){if(h.nodeType===Node.ELEMENT_NODE){const v=h;if(v.tagName==="H3"&&Sk(v.textContent?.trim()??"")||["H1","H2"].includes(v.tagName))break}const m=h.nextSibling;f.push(h),h=m}return{heading:c,title:p,nodes:f}}),a=document.createElement("div");a.className="aiui-tabs my-6 border border-border rounded-lg overflow-hidden",a.dataset.aiuiTabs="true";const o=document.createElement("div");o.className="aiui-tab-buttons flex flex-wrap gap-0 border-b border-border bg-muted/30",a.appendChild(o);const s=document.createElement("div");s.className="aiui-tab-panels",a.appendChild(s),r.forEach((c,p)=>{const f=document.createElement("button");f.type="button",f.className=["aiui-tab-button px-4 py-2 text-sm font-medium transition-colors",p===0?"bg-background text-foreground border-b-2 border-primary":"text-muted-foreground hover:text-foreground"].join(" "),f.textContent=c.title,f.dataset.tabIndex=String(p),o.appendChild(f);const h=document.createElement("div");h.className=p===0?"aiui-tab-panel p-4 block":"aiui-tab-panel p-4 hidden",h.dataset.tabPanel=String(p);for(const m of c.nodes)h.appendChild(m);s.appendChild(h)});const u=r[0].heading;e.insertBefore(a,u);for(const c of r)c.heading.remove();o.addEventListener("click",c=>{const p=c.target.closest(".aiui-tab-button");if(!p)return;const f=p.dataset.tabIndex??"0";for(const h of o.querySelectorAll(".aiui-tab-button")){const m=h.dataset.tabIndex===f;h.classList.toggle("bg-background",m),h.classList.toggle("text-foreground",m),h.classList.toggle("border-b-2",m),h.classList.toggle("border-primary",m),h.classList.toggle("text-muted-foreground",!m)}for(const h of s.querySelectorAll(".aiui-tab-panel"))h.classList.toggle("hidden",h.dataset.tabPanel!==f),h.classList.toggle("block",h.dataset.tabPanel===f)})}function n3(e){return e.toLowerCase().replace(/[^\w\s-]/g,"").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}function Rn({className:e,...t}){return g.jsx("div",{"data-slot":"card",className:me("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...t})}function zn({className:e,...t}){return g.jsx("div",{"data-slot":"card-header",className:me("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",e),...t})}function Pn({className:e,...t}){return g.jsx("div",{"data-slot":"card-title",className:me("leading-none font-semibold",e),...t})}function Em({className:e,...t}){return g.jsx("div",{"data-slot":"card-description",className:me("text-muted-foreground text-sm",e),...t})}function Cn({className:e,...t}){return g.jsx("div",{"data-slot":"card-content",className:me("px-6",e),...t})}function r3({className:e,...t}){return g.jsx("div",{"data-slot":"card-footer",className:me("flex items-center px-6 [.border-t]:pt-6",e),...t})}function a3({props:e}){const t=e?.title||"Statistics",r=e?.value||"0",a=e?.change||"",o=e?.changeType||"positive";return g.jsxs(Rn,{className:"mb-4",children:[g.jsxs(zn,{className:"pb-2",children:[g.jsx(Em,{children:t}),g.jsx(Pn,{className:"text-2xl",children:r})]}),a&&g.jsx(Cn,{className:"pt-0",children:g.jsx("span",{className:o==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:a})})]})}function i3({props:e}){const t=e?.title||"Quick Links",r=e?.links||[];return g.jsxs(Rn,{className:"mb-4",children:[g.jsx(zn,{className:"pb-2",children:g.jsx(Pn,{className:"text-sm font-medium",children:t})}),g.jsx(Cn,{className:"space-y-1",children:r.map((a,o)=>g.jsx("a",{href:a.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:a.label},o))})]})}function o3({props:e}){const t=e?.title||"Newsletter",r=e?.buttonT Line truncated
`)===-1?g.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:v}):g.jsx("code",{className:`block font-mono text-foreground ${m??""}`,children:v}),pre:({children:m})=>g.jsx("pre",{className:"bg-muted text-foreground border border-border p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:m}),table:({children:m})=>g.jsx("div",{className:"overflow-auto my-4",children:g.jsx("table",{className:"w-full border-collapse text-sm",children:m})}),thead:({children:m})=>g.jsx("thead",{className:"bg-muted/50",children:m}),tr:({children:m})=>g.jsx("tr",{className:"border-b",children:m}),th:({children:m})=>g.jsx("th",{className:"px-4 py-2 text-left font-medium",children:m}),td:({children:m})=>g.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:m}),hr:()=>g.jsx("hr",{className:"my-6 border-border"}),strong:({children:m})=>g.jsx("strong",{className:"font-semibold text-foreground",children:m}),em:({children:m})=>g.jsx("em",{children:m})};return r?g.jsxs("main",{id:"main-content",className:"flex-1 p-8 max-w-3xl",children:[g.jsx(h3,{selectedItem:r}),s?g.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):a?g.jsx("article",{ref:c,className:"prose prose-neutral dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:text-foreground",children:g.jsx(mR,{remarkPlugins:[BR],components:h,children:a})}):g.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:g.jsxs("p",{className:"text-muted-foreground",children:["Content for ",g.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})]}):g.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[g.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),g.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),g.jsx(qi,{className:"my-8"}),g.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),g.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[g.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:g.jsx("strong",{children:"Current theme from YAML:"})}),g.jsx("pre",{className:"bg-muted text-foreground border border-border p-3 rounded-lg text-sm overflow-auto",children:`site:
theme:
preset: "${p?.preset||"zinc"}"
radius: "${p?.radius||"md"}"
darkMode: ${p?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(BL).map(m=>g.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${m===(p?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:m},m))}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),g.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("span",{className:"font-medium text-primary",children:t.routes?.length||0})," routes configured."]}),g.jsx("pre",{className:"bg-muted text-foreground border border-border p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(t.routes?.slice(0,3),null,2)})]})}function _u({config:e,templateKey:t="docs"}){const r=e.templates?.[t]?.slots?.footer,a=r?.ref,s=(a?e.components?.[a]:e.components?.footer)?.props||r?.props;return g.jsx("footer",{className:"border-t border-border/50 py-8 px-6 bg-muted/20",children:g.jsxs("div",{className:"max-w-screen-2xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-4 text-sm text-muted-foreground",children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("div",{className:"w-5 h-5 rounded bg-gradient-to-br from-primary/60 to-primary flex items-center justify-center",children:g.jsx("span",{className:"text-primary-foreground text-[8px] font-bold",children:"AI"})}),g.jsx("span",{children:s?.text||`© ${new Date().getFullYear()} PraisonAIUI`})]}),g.jsx("nav",{className:"flex items-center gap-6",children:s?.links?.map(u=>g.jsx("a",{href:u.href,className:"hover:text-foreground transition-colors",children:u.label},u.href))})]})})}const b3=()=>({taskLists:[],collapsed:!1}),Bn=new Map,hg=100,y3=1440*60*1e3;function v3(){const e=Date.now(),t=Array.from(Bn.entries());for(const[r,a]of t)e-a.lastAccessed>y3&&Bn.delete(r);if(Bn.size>hg){const r=Array.from(Bn.entries()).sort(([,o],[,s])=>o.lastAccessed-s.lastAccessed),a=Bn.size-hg;for(let o=0;o<a;o++)Bn.delete(r[o][0])}}function bg(e){if(!Bn.has(e))Bn.size>=hg&&v3(),Bn.set(e,{state:b3(),listeners:new Set,lastAccessed:Date.now()});else{const t=Bn.get(e);t.lastAccessed=Date.now()}return Bn.get(e)}function Sm(e,t){const r=bg(e),a=t(r.state);r.state=a,r.listeners.forEach(o=>o())}function E3(e,t){Sm(e,r=>{const a=r.taskLists.filter(s=>s.id!==t.task_list_id),o={id:t.task_list_id,name:t.name,tasks:t.tasks,stats:GR(t.tasks),sequence:t.sequence,created_at:t.timestamp,updated_at:t.timestamp};return{...r,taskLists:[...a,o],collapsed:r.taskLists.length===0?t.tasks.length<=1:r.collapsed}})}function S3(e,t){Sm(e,r=>{const a=r.taskLists.map(o=>o.id===t.task_list_id?{...o,tasks:t.tasks,stats:GR(t.tasks),sequence:t.sequence,updated_at:t.timestamp}:o);return{...r,taskLists:a}})}function GR(e){return e.reduce((t,r)=>(t.total+=1,t[r.status.toLowerCase()]+=1,t),{total:0,ready:0,running:0,done:0,failed:0})}function x3(e,t){Sm(e,r=>({...r,collapsed:t}))}function w3(e){const[,t]=S.useState({});return S.useEffect(()=>{const r=bg(e),a=()=>t({});return r.listeners.add(a),()=>{r.listeners.delete(a)}},[e]),bg(e).state}function T3(e,t){return t.type==="task_list.init"?(E3(e,t),!0):t.type==="task_list.update"?(S3(e,t),!0):!1}const HR=()=>({isStreaming:!1,currentResponse:"",toolCalls:[],thinkingSteps:[],pendingElements:[]}),za=new Map;function vc(e){return za.has(e)||za.set(e,{state:HR(),listeners:new Set,abortController:null,toolCallMap:new Map}),za.get(e)}function Nu(e){return e?vc(e).state:HR()}function A3(e,t){const r=vc(e);return r.listeners.add(t),()=>r.listeners.delete(t)}function _n(e,t){const r=vc(e);r.state={...r.state,...t},r.listeners.forEach(a=>a())}async function k3(e){const t=za.get(e);if(t){t.abortController?.abort(),t.abortController=null,_n(e,{isStreaming:!1});try{await fetch("/cancel",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:e})})}catch{}}}function _3(e,t,r,a){const o=vc(e);if(o.state.isStreaming)return;_n(e,{isStreaming:!0,currentResponse:"",toolCalls:[],thinkingSteps:[],pendingElements:[]}),o.toolCallMap=new Map;const s=new AbortController;o.abortController=s,N3(e,t,r,s,a)}async function N3(e,t,r,a,o){try{const s=await fetch("/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:t,session_id:e,agent:r}),signal:a.signal});if(!s.ok)throw new Error(`HTTP ${s.status}`);const u=s.body?.getReader();if(!u)throw new Error("No response body");const c=new TextDecoder;let p="";for(;;){const{done:f,value:h}=await u.read();if(f)break;p+=c.decode(h,{stream:!0});const m=p.split(`
`);p=m.pop()||"";for(const v of m)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));R3(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=za.get(e);u&&_n(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=za.get(e);s&&(s.abortController=null),_n(e,{isStreaming:!1}),o?.onEnd?.()}}function R3(e,t,r){const a=za.get(e);if(!a)return;const o=a.toolCallMap;switch(t.type){case"session":t.session_id&&typeof t.session_id=="string"&&r?.onSessionId?.(t.session_id);break;case"token":case"run_content":case"team_run_content":{const u=t.token??t.content??"";u&&_n(e,{currentResponse:a.state.currentResponse+u});break}case"message":case"run_completed":case"team_run_completed":{const u=t.content;u&&_n(e,{currentResponse:u});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const u=t.step;u&&_n(e,{thinkingSteps:[...a.state.thinkingSteps,u]});break}case"reasoning_started":case"team_reasoning_started":_n(e,{thinkingSteps:[...a.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const u={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"running",tool_call_id:t.tool_call_id,args:t.args};u.tool_call_id&&o.set(u.tool_call_id,u);const c=wk(a.state.toolCalls,u);_n(e,{toolCalls:c});break}case"tool_call_completed":case"team_tool_call_completed":{const u=t.extra_data,c=t.a2ui??u?.a2ui,p=t.elements??u?.elements,f={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"done",tool_call_id:t.tool_call_id,args:t.args,formatted_result:t.formatted_result,result:t.result,surface_id:t.surface_id??u?.surface_id,a2ui:c?.length?{messages:c,surface_id:t.surface_id??u?.surface_id}:void 0,elements:p};f.tool_call_id&&o.set(f.tool_call_id,f);const h=wk(a.state.toolCalls,f),m=p?.length?[...a.state.pendingElements,...p]:a.state.pendingElements;_n(e,{toolCalls:h,pendingElements:m});break}case"message_element":{const u=t.extra_data,c=t.element??u?.element;c&&_n(e,{pendingElements:[...a.state.pendingElements,c]});break}case"error":case"run_error":case"team_run_error":{const u=t.error;u&&_n(e,{thinkingSteps:[...a.state.thinkingSteps,`Error: ${u}`]});break}case"approval_required":case"approval_pending":case"run_paused":{const u=t.extra_data;if(u?.type==="approval_required"){const c=u;window.dispatchEvent(new CustomEvent("aiui:approval-required",{detail:c}))}break}case"end":case"done":case"run_cancelled":case"team_run_cancelled":break;default:T3(e,t);break}}function wk(e,t){if(t.tool_call_id){const r=e.findIndex(a=>a.tool_call_id===t.tool_call_id);if(r>=0){const a=[...e];return a[r]={...a[r],...t},a}}return[...e,t]}function VR(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>Nu(t));S.useEffect(()=>{if(!t){a(Nu(null));return}return a(Nu(t)),A3(t,()=>{a(Nu(t))})},[t]);const o=S.useCallback((u,c)=>{const p=t??`new-${Date.now()}`;_3(p,u,c,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&k3(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function C3(e,t){if(e==null)return{};var r={};for(var a in e)if({}.hasOwnProperty.call(e,a)){if(t.indexOf(a)!==-1)continue;r[a]=e[a]}return r}function I3(e,t){if(e==null)return{};var r,a,o=C3(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)r=s[a],t.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function yg(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,a=Array(t);r<t;r++)a[r]=e[r];return a}function O3(e){if(Array.isArray(e))return yg(e)}function L3(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function D3(e,t){if(e){if(typeof e=="string")return yg(e,t);var r={}.toString.call(e).slice(8,-1);return r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set"?Array.from(e):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?yg(e,t):void 0}}function M3(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function vg(e){return O3(e)||L3(e)||D3(e)||M3()}function Cl(e){"@babel/helpers - typeof";return Cl=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Cl(e)}function j3(e,t){if(Cl(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var a=r.call(e,t);if(Cl(a)!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function U3(e){var t=j3(e,"string");return Cl(t)=="symbol"?t:t+""}function qR(e,t,r){return(t=U3(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function Eg(){return Eg=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},Eg.apply(null,arguments)}function Tk(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),r.push.apply(r,a)}return r}function Yi(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?arguments[t]:{};t%2?Tk(Object(r),!0).forEach(function(a){qR(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):Tk(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function F3(e){var t=e.length;if(t===0||t===1)return e;if(t===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(t===3)return[e[0],e[1],e[2],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0])];if(t>=4)return[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[ Line truncated
`))})}function Y3(e){var t=e.codeString,r=e.codeStyle,a=e.containerStyle,o=a===void 0?{float:"left",paddingRight:"10px"}:a,s=e.numberStyle,u=s===void 0?{}:s,c=e.startingLineNumber;return Ke.createElement("code",{style:Object.assign({},r,o)},q3({lines:t.replace(/\n$/,"").split(`
`),style:u,startingLineNumber:c}))}function W3(e){return"".concat(e.toString().length,".25em")}function WR(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function KR(e,t,r){var a={display:"inline-block",minWidth:W3(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=la(la({},a),o);return s}function Uu(e){var t=e.children,r=e.lineNumber,a=e.lineNumberStyle,o=e.largestLineNumber,s=e.showInlineLineNumbers,u=e.lineProps,c=u===void 0?{}:u,p=e.className,f=p===void 0?[]:p,h=e.showLineNumbers,m=e.wrapLongLines,v=e.wrapLines,E=v===void 0?!1:v,x=E?la({},typeof c=="function"?c(r):c):{};if(x.className=x.className?[].concat(vg(x.className.trim().split(/\s+/)),vg(f)):f,r&&s){var w=KR(a,r,o);t.unshift(WR(r,w))}return m&h&&(x.style=la({display:"flex"},x.style)),{type:"element",tagName:"span",properties:x,children:t}}function XR(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];e.length===void 0&&(e=[e]);for(var a=0;a<e.length;a++){var o=e[a];if(o.type==="text")r.push(Uu({children:[o],className:vg(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);XR(o.children,u).forEach(function(c){return r.push(c)})}}return r}function K3(e,t,r,a,o,s,u,c,p){var f,h=XR(e.value),m=[],v=-1,E=0;function x(F,L){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Uu({children:F,lineNumber:L,lineNumberStyle:c,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:z,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function w(F,L){if(a&&L&&o){var z=KR(c,L,u);F.unshift(WR(L,z))}return F}function k(F,L){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||z.length>0?x(F,L,z):w(F,L)}for(var A=function(){var L=h[E],z=L.children[0].value,$=V3(z);if($){var K=z.split(`
`);K.forEach(function(j,q){var X=a&&m.length+s,ne={type:"text",value:"".concat(j,`
`)};if(q===0){var re=h.slice(v+1,E).concat(Uu({children:[ne],className:L.properties.className})),ee=k(re,X);m.push(ee)}else if(q===K.length-1){var M=h[E+1]&&h[E+1].children&&h[E+1].children[0],H={type:"text",value:"".concat(j)};if(M){var Q=Uu({children:[H],className:L.properties.className});h.splice(E+1,0,Q)}else{var de=[H],I=k(de,X,L.properties.className);m.push(I)}}else{var U=[ne],B=k(U,X,L.properties.className);m.push(B)}}),v=E}E++};E<h.length;)A();if(v!==h.length-1){var _=h.slice(v+1,h.length);if(_&&_.length){var N=a&&m.length+s,D=k(_,N);m.push(D)}}return t?m:(f=[]).concat.apply(f,m)}function X3(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return YR({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function ZR(e){return e&&typeof e.highlightAuto<"u"}function Z3(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if(ZR(t)){var s=$3(t,r);return r==="text"?{value:o,language:"text"}:s?t.highlight(r,a):t.highlightAuto(a)}try{return r&&r!=="text"?{value:t.highlight(a,r)}:{value:o}}catch{return{value:o}}}function Q3(e,t){return function(a){var o,s,u=a.language,c=a.children,p=a.style,f=p===void 0?t:p,h=a.customStyle,m=h===void 0?{}:h,v=a.codeTagProps,E=v===void 0?{className:u?"language-".concat(u):void 0,style:la(la({},f['code[class*="language-"]']),f['code[class*="language-'.concat(u,'"]')])}:v,x=a.useInlineStyles,w=x===void 0?!0:x,k=a.showLineNumbers,A=k===void 0?!1:k,_=a.showInlineLineNumbers,N=_===void 0?!0:_,D=a.startingLineNumber,F=D===void 0?1:D,L=a.lineNumberContainerStyle,z=a.lineNumberStyle,$=z===void 0?{}:z,K=a.wrapLines,j=a.wrapLongLines,q=j===void 0?!1:j,X=a.lineProps,ne=X===void 0?{}:X,re=a.renderer,ee=a.PreTag,M=ee===void 0?"pre":ee,H=a.CodeTag,Q=H===void 0?"code":H,de=a.code,I=de===void 0?(Array.isArray(c)?c[0]:c)||"":de,U=a.astGenerator,B=I3(a,G3);U=U||e;var C=A?Ke.createElement(Y3,{containerStyle:L,codeStyle:E.style||{},numberStyle:$,startingLineNumber:F,codeString:I}):null,J=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},ae=ZR(U)?"hljs":"prismjs",ue=w?Object.assign({},B,{style:Object.assign({},J,m)}):Object.assign({},B,{className:B.className?"".concat(ae," ").concat(B.className):ae,style:Object.assign({},m)});if(q?E.style=la({whiteSpace:"pre-wrap"},E.style):E.style=la({whiteSpace:"pre"},E.style),!U)return Ke.createElement(M,ue,C,Ke.createElement(Q,E,I));(K===void 0&&re||q)&&(K=!0),re=re||X3;var ie=[{type:"text",value:I}],he=Z3({astGenerator:U,language:u,code:I,defaultCodeValue:ie});he.language===null&&(he.value=ie);var fe=(o=(s=I.match(/\n/g))===null||s===void 0?void 0:s.length)!==null&&o!==void 0?o:0,ke=F+fe,Ie=K3(he,K,ne,A,N,F,ke,$,q);return Ke.createElement(M,ue,Ke.createElement(Q,E,!N&&C,re({rows:Ie,stylesheet:f,useInlineStyles:w})))}}const J3="modulepreload",e4=function(e){return"/"+e},_k={},Nk=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let p=function(f){return Promise.all(f.map(h=>Promise.resolve(h).then(m=>({status:"fulfilled",value:m}),m=>({status:"rejected",reason:m}))))};document.getElementsByTagName("link");const u=document.querySelector("meta[property=csp-nonce]"),c=u?.nonce||u?.getAttribute("nonce");o=p(r.map(f=>{if(f=e4(f),f in _k)return;_k[f]=!0;const h=f.endsWith(".css"),m=h?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${f}"]${m}`))return;const v=document.createElement("link");if(v.rel=h?"stylesheet":J3,h||(v.as="script"),v.crossOrigin="",v.href=f,c&&v.setAttribute("nonce",c),document.head.appendChild(v),h)return new Promise((E,x)=>{v.addEventListener("load",E),v.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${f}`)))})}))}function s(u){const c=new Event("vite:preloadError",{cancelable:!0});if(c.payload=u,window.dispatchEvent(c),!c.defaultPrevented)throw u}return o.then(u=>{for(const c of u||[])c.status==="rejected"&&s(c.reason);return t().catch(s)})},t4=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apex","apl","applescript","aql","arduino","arff","armasm","arturo","asciidoc","asm6502","asmatmel","aspnet","autohotkey","autoit","avisynth","avro-idl","awk","bash","basic","batch","bbcode","bbj","bicep","birb","bison","bnf","bqn","brainfuck","brightscript","bro","bsl","c","cfscript","chaiscript","cil","cilkc","cilkcpp","clike","clojure","cmake","cobol","coffeescript","concurnas","cooklang","coq","cpp","crystal","csharp","cshtml","csp","css-extras","css","csv","cue","cypher","d","dart","dataweave","dax","dhall","diff","django","dns-zone-file","docker","dot","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","false","firestore-security-rules","flow","fortran","fsharp","ftl","gap","gcode","gdscript","gedcom","gettext","gherkin","git","glsl","gml","gn","go-module","go","gradle","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hoon","hpkp","hsts","http","ichigojam","icon","icu-message-format","idris","iecst","ignore","inform7","ini","io","j","java","java Line truncated
?|
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}zt.displayName="markup-templating";zt.aliases=[];function zt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var c=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=c.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return c[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function c(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var m=u[s],v=a.tokenStack[m],E=typeof h=="string"?h:h.content,x=r(o,m),w=E.indexOf(x);if(w>-1){++s;var k=E.substring(0,w),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(w+x.length),N=[];k&&N.push.apply(N,c([k])),N.push(A),_&&N.push.apply(N,c([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(N)):h.content=N}}else h.content&&c(h.content)}return p}c(a.tokens)}}})})(e)}Ih.displayName="django";Ih.aliases=["jinja2"];function Ih(e){e.register(zt),(function(t){t.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var r=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,a=t.languages["markup-templating"];t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"django",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"jinja2",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"jinja2")})})(e)}Oh.displayName="dns-zone-file";Oh.aliases=["dns-zone"];function Oh(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}Lh.displayName="docker";Lh.aliases=["dockerfile"];function Lh(e){(function(t){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,a=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,s=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return o}),u={pattern:RegExp(o),greedy:!0},c={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function p(f,h){return f=f.replace(/<OPT>/g,function(){return s}).replace(/<SP>/g,function(){return a}),RegExp(f,h)}t.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:p(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[u,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:p(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:c,string:u,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper Line truncated
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+a+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+a),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Vl.displayName="t4-templating";Vl.aliases=[];function Vl(e){(function(t){function r(o,s,u){return{pattern:RegExp("<#"+o+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+o+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:s,alias:u}}}}function a(o){var s=t.languages[o],u="language-"+o;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:r("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:r("=",s,u),"class-feature":r("\\+",s,u),standard:r("",s,u)}}}}t.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:a})})(e)}qv.displayName="t4-cs";qv.aliases=["t4"];function qv(e){e.register(io),e.register(Vl),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}Nc.displayName="vbnet";Nc.aliases=[];function Nc(e){e.register(xc),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}Yv.displayName="t4-vb";Yv.aliases=[];function Yv(e){e.register(Vl),e.register(Nc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}Wv.displayName="tap";Wv.aliases=[];function Wv(e){e.register(Sc),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}Kv.displayName="tcl";Kv.aliases=[];function Kv(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\b/m,lookbehind:!0},/\b(?:else|elseif)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait Line truncated
- a positive number
- less than the value passed to \`max\` (or ${OE} if no \`max\` prop is set)
- \`null\` or \`undefined\` if the progress is indeterminate.
Defaulting to \`null\`.`}var JG=VC,eH=YC;function tH({className:e,value:t,...r}){return g.jsx(JG,{"data-slot":"progress",className:me("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(eH,{"data-slot":"progress-indicator",className:"bg-primary h-full w-full flex-1 transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})})}function Yl(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function Wl(e){const[t,r]=S.useState(void 0);return gt(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});const a=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const s=o[0];let u,c;if("borderBoxSize"in s){const p=s.borderBoxSize,f=Array.isArray(p)?p[0]:p;u=f.inlineSize,c=f.blockSize}else u=e.offsetWidth,c=e.offsetHeight;r({width:u,height:c})});return a.observe(e,{box:"border-box"}),()=>a.unobserve(e)}else r(void 0)},[e]),t}var Mc="Switch",[nH]=Ut(Mc),[rH,aH]=nH(Mc),KC=S.forwardRef((e,t)=>{const{__scopeSwitch:r,name:a,checked:o,defaultChecked:s,required:u,disabled:c,value:p="on",onCheckedChange:f,form:h,...m}=e,[v,E]=S.useState(null),x=Ne(t,N=>E(N)),w=S.useRef(!1),k=v?h||!!v.closest("form"):!0,[A,_]=tn({prop:o,defaultProp:s??!1,onChange:f,caller:Mc});return g.jsxs(rH,{scope:r,checked:A,disabled:c,children:[g.jsx(be.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":JC(A),"data-disabled":c?"":void 0,disabled:c,value:p,...m,ref:x,onClick:ye(e.onClick,N=>{_(D=>!D),k&&(w.current=N.isPropagationStopped(),w.current||N.stopPropagation())})}),k&&g.jsx(QC,{control:v,bubbles:!w.current,name:a,value:p,checked:A,required:u,disabled:c,form:h,style:{transform:"translateX(-100%)"}})]})});KC.displayName=Mc;var XC="SwitchThumb",ZC=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=aH(XC,r);return g.jsx(be.span,{"data-state":JC(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});ZC.displayName=XC;var iH="SwitchBubbleInput",QC=S.forwardRef(({__scopeSwitch:e,control:t,checked:r,bubbles:a=!0,...o},s)=>{const u=S.useRef(null),c=Ne(u,s),p=Yl(r),f=Wl(t);return S.useEffect(()=>{const h=u.current;if(!h)return;const m=window.HTMLInputElement.prototype,E=Object.getOwnPropertyDescriptor(m,"checked").set;if(p!==r&&E){const x=new Event("click",{bubbles:a});E.call(h,r),h.dispatchEvent(x)}},[p,r,a]),g.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:r,...o,tabIndex:-1,ref:c,style:{...o.style,...f,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});QC.displayName=iH;function JC(e){return e?"checked":"unchecked"}var oH=KC,lH=ZC;function sH({className:e,size:t="default",...r}){return g.jsx(oH,{"data-slot":"switch","data-size":t,className:me("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",e),...r,children:g.jsx(lH,{"data-slot":"switch-thumb",className:me("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}var jc="Checkbox",[uH]=Ut(jc),[cH,LE]=uH(jc);function dH(e){const{__scopeCheckbox:t,checked:r,children:a,defaultChecked:o,disabled:s,form:u,name:c,onCheckedChange:p,required:f,value:h="on",internal_do_not_use_render:m}=e,[v,E]=tn({prop:r,defaultProp:o??!1,onChange:p,caller:jc}),[x,w]=S.useState(null),[k,A]=S.useState(null),_=S.useRef(!1),N=x?!!u||!!x.closest("form"):!0,D={checked:v,disabled:s,setChecked:E,control:x,setControl:w,name:c,form:u,value:h,hasConsumerStoppedPropagationRef:_,required:f,defaultChecked:sa(o)?!1:o,isFormControl:N,bubbleInput:k,setBubbleInput:A};return g.jsx(cH,{scope:t,...D,children:pH(m)?m(D):a})}var e0="CheckboxTrigger",t0=S.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:r,...a},o)=>{const{control:s,value:u,disabled:c,checked:p,required:f,setControl:h,setChecked:m,hasConsumerStoppedPropagationRef:v,isFormControl:E,bubbleInput:x}=LE(e0,e),w=Ne(o,h),k=S.useRef(p);return S.useEffect(()=>{const A=s?.form;if(A){const _=()=>m(k.current);return A.addEventListener("reset",_),()=>A.removeEventListener("reset",_)}},[s,m]),g.jsx(be.button,{type:"button",role:"checkbox","aria-checked":sa(p)?"mixed":p,"aria-required":f,"data-state":l0(p),"data-disabled":c?"":void 0,disabled:c,value:u,...a,ref:w,onKeyDown:ye(t,A=>{A.key==="Enter"&&A.preventDefault()}),onClick:ye(r,A=>{m(_=>sa(_)?!0:!_),x&&E&&(v.current=A.isPropagatiLine truncated
`);for(;t.length&&!t[0].trim();)t.shift();for(;t.length&&!t[t.length-1].trim();)t.pop();if(!t.length)return"";const r=Math.min(...t.filter(a=>a.trim().length>0).map(a=>a.match(/^ */)?.[0].length??0));return r===0?t.join(`
`):t.map(a=>a.slice(r)).join(`
`)}function wV({language:e,children:t}){const[r,a]=S.useState(!1),o=S.useCallback(async()=>{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)},[t]);return g.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[g.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[g.jsx("span",{children:e||"code"}),g.jsx("button",{onClick:o,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),g.jsx(KN,{style:yP,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function TV(e){const t=e.split(`
`);let r=!1,a=!1,o="",s=0;for(let u=0;u<t.length;u++){const d=t[u];if(/^[ \t]*```/.test(d)){r=!r;continue}if(r)continue;const p=d.match(/^([ \t]*)(\d+)\.[ \t]/);if(p){const f=p[1];!a||f!==o?(a=!0,o=f,s=1):s++,t[u]=d.replace(/^([ \t]*)\d+\./,`${f}${s}.`)}else d.trim()===""&&a||a&&/^[ \t]+\S/.test(d)||a&&(a=!1,s=0)}return t.join(`
`)}function vO({content:e}){const t=TV(e);return g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:p-0 prose-pre:bg-transparent",children:g.jsx(uN,{remarkPlugins:[LN],components:{ul({children:r,ordered:a,start:o}){if(a){const s=o!=null?Number(o):1;return g.jsx("ol",{start:s,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return g.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return g.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:a,...o}){const s=/language-(\w+)/.exec(r||""),u=xV(String(a));return!s&&!u.includes(`
`)?g.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...o,children:a}):g.jsx(wV,{language:s?.[1],children:u})},a({href:r,children:a}){return g.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:a})},table({children:r}){return g.jsx("div",{className:"overflow-x-auto my-3",children:g.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:t})})}function AV({message:e,sessionId:t}){return e.role==="user"?g.jsx("div",{className:"flex justify-end",children:g.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[g.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:g.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),g.jsx(Zu,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(Zu,{role:"assistant"}),g.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&g.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((a,o)=>g.jsx(EO,{toolCall:a},o))}),g.jsx(vO,{content:e.content}),e.elements&&e.elements.length>0&&g.jsx("div",{className:"mt-3 space-y-3",children:e.elements.map((a,o)=>g.jsx(jv,{element:a,className:"w-full"},o))}),e.images&&e.images.length>0&&g.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((a,o)=>g.jsx("img",{src:a,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&g.jsx(mV,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(vV,{text:e.content}),g.jsx(SV,{messageId:e.id})]})]})]})}function kV({onSend:e,onCancel:t,isStreaming:r=!1,placeholder:a="Type a message...",enableFileUpload:o=!1}){const[s,u]=S.useState(""),[d,p]=S.useState([]),f=S.useRef(null),h=S.useRef(null),b=S.useCallback(()=>{const A=s.trim();(A||d.length>0)&&!r&&(e(A,d.length>0?d:void 0),u(""),p([]),f.current&&(f.current.style.height="auto"))},[s,d,r,e]),v=S.useCallback(A=>{const _=Array.from(A.target.files||[]);p(R=>[...R,..._]),h.current&&(h.current.value="")},[]),E=S.useCallback(()=>{h.current?.click()},[]),w=S.useCallback(A=>{p(_=>_.filter((R,L)=>L!==A))},[]),T=S.useCallback(A=>{A.key==="Enter"&&!A.shiftKey&&(A.preventDefault(),b())},[b]),k=S.useCallback(()=>{const A=f.current;A&&(A.style.height="auto",A.style.height=`${Math.min(A.scrollHeight,200)}px`)},[]);return g.jsx("div",{className:"p-4 pb-6",children:g.jsxs("div",{className:"max-w-3xl mx-auto",children:[g.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[d.length>0&&g.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:d.map((A,_)=>g.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[g.jsx("span",{className:"truncate max-w-[100px]",children:A.name}),g.jsx("button",{type:"button",onClick:()=>w(_),className:"hover:text-destructive",children:"×"})]},`${A.name}-${_}`))}),g.jsxs("div",{className:"flex items-end gap-2 p-2",children:[o&&g.jsxs(g.Fragment,{children:[g.jsx("input",{ref:h,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),g.jsx("button",{type:"button",onClick:E,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})})})]}),g.jsx("textarea",{ref:f,value:s,onChange:A=>u(A.target.value),onKeyDown:T,onInput:k,placeholder:a,disabled:r,rows:1,className:"flex-1 resize-none bg-transparent px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 placeholder:text-muted-foreground/60",style:{maxHeight:"200px"}}),r?g.jsx("button",{type:"button",onClick:t,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:g.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):g.jsx("button",{type:"button",onClick:b,disabled:!s.trim(),className:"p-2 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",title:"Send",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),g.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line" Line truncated
`),b=S.useCallback(async()=>{!r.trim()||s||p(r)},[r,s,p]),v=S.useCallback(()=>{a("")},[]);return g.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[g.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[g.jsx("h1",{className:"text-xl font-semibold",children:t||"Playground"}),g.jsx("div",{className:"flex items-center gap-2",children:g.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),g.jsxs("div",{className:"flex-1 flex",children:[g.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[g.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),g.jsx("div",{className:"flex-1 p-4",children:g.jsx("textarea",{value:r,onChange:E=>a(E.target.value),placeholder:"Enter your prompt here...",className:"w-full h-full resize-none bg-transparent border rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-primary"})}),g.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:s?g.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):g.jsx("button",{onClick:b,disabled:!r.trim(),className:"px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",children:"Submit"})})]}),g.jsxs("div",{className:"flex-1 flex flex-col",children:[g.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),s&&g.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),g.jsx("div",{className:"flex-1 p-4 overflow-auto",children:h?g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:h}):g.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&g.jsxs("div",{className:"border-t px-6 py-3 bg-muted/20 flex items-center gap-4 text-sm text-muted-foreground",children:[e.features.streaming&&g.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&g.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&g.jsx("span",{children:"✓ Tools"})]})]})}function _u({enabled:e}){return e?g.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-primary text-primary-foreground px-4 py-2 rounded-md text-sm font-medium",children:"Skip to main content"}):null}function jV(){const[e,t]=S.useState({}),[r,a]=S.useState({}),[o,s]=S.useState({}),[u,d]=S.useState(!0),[p,f]=S.useState(null),[h,b]=S.useState(null),[v,E]=S.useState("");S.useEffect(()=>{async function L(){try{const[j,D,z]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!j.ok||!D.ok||!z.ok)throw new Error("Failed to load manifests");const F=await j.json(),V=await D.json();t(F),a(V),s(await z.json());const M=F.site?.theme;K1(M?.preset||"zinc",M?.darkMode!==!1,M?.radius||"md");const H=window.location.pathname;if(H&&H!=="/"){const K=Z=>{for(const te of Z){if(te.path===H||te.path===H.replace(/^\//,""))return te;if(te.children){const Q=K(te.children);if(Q)return Q}}return null};if(V.items){for(const Z of V.items)if(Z.children){const te=K(Z.children);if(te){b(te),E(te.path||te.title);break}}}}}catch(j){f(j instanceof Error?j.message:"Unknown error")}finally{d(!1)}}L()},[]),S.useEffect(()=>{const L=()=>{const j=window.location.pathname,D=F=>{for(const V of F){if(V.path===j||V.path===j.replace(/^\//,""))return V;if(V.children){const M=D(V.children);if(M)return M}}return null};let z=null;if(r.items){for(const F of r.items)if(F.children&&(z=D(F.children),z))break}if(z){b(z),E(z.path||z.title);const F=e.site?.title||"Documentation",V=e.seo?.titleTemplate||"%s | %s";if(V.includes("%s")){let M=V.replace("%s",z.title);M.includes("%s")&&(M=M.replace("%s",F)),document.title=M}else document.title=`${z.title} | ${F}`}else j==="/"&&(b(null),E(""),document.title=e.site?.title||"Documentation")};return window.addEventListener("popstate",L),()=>window.removeEventListener("popstate",L)},[r,e]);const w=(L,j,D)=>{const z=e.seo?.titleTemplate||"%s | %s",F=e.site?.title||"Documentation";if(z.includes("%s")){let Z=z.replace("%s",L);Z.includes("%s")&&(Z=Z.replace("%s",F)),document.title=Z}else document.title=`${L} | ${F}`;let V=document.querySelector('link[rel="canonical"]');V||(V=document.createElement("link"),V.rel="canonical",document.head.appendChild(V)),V.href=window.location.origin+j;let M=document.querySelector('meta[name="description"]');M||(M=document.createElement("meta"),M.name="description",document.head.appendChild(M)),M.content=`${L} - ${e.site?.description||""}`;let H=document.querySele Line truncated
`)}function Cq({language:e,children:t}){const[r,a]=S.useState(!1),o=S.useCallback(async()=>{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)},[t]);return g.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[g.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[g.jsx("span",{children:e||"code"}),g.jsx("button",{onClick:o,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),g.jsx(rC,{style:N4,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function Iq(e){const t=e.split(`
`);let r=!1,a=!1,o="",s=0;for(let u=0;u<t.length;u++){const c=t[u];if(/^[ \t]*```/.test(c)){r=!r;continue}if(r)continue;const p=c.match(/^([ \t]*)(\d+)\.[ \t]/);if(p){const f=p[1];!a||f!==o?(a=!0,o=f,s=1):s++,t[u]=c.replace(/^([ \t]*)\d+\./,`${f}${s}.`)}else c.trim()===""&&a||a&&/^[ \t]+\S/.test(c)||a&&(a=!1,s=0)}return t.join(`
`)}function nO({content:e}){const t=Iq(e);return g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:p-0 prose-pre:bg-transparent",children:g.jsx(mR,{remarkPlugins:[BR],components:{ul({children:r,ordered:a,start:o}){if(a){const s=o!=null?Number(o):1;return g.jsx("ol",{start:s,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return g.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return g.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:a,...o}){const s=/language-(\w+)/.exec(r||""),u=Rq(String(a));return!s&&!u.includes(`
`)?g.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...o,children:a}):g.jsx(Cq,{language:s?.[1],children:u})},a({href:r,children:a}){return g.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:a})},table({children:r}){return g.jsx("div",{className:"overflow-x-auto my-3",children:g.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:t})})}function Oq({message:e,sessionId:t}){return e.role==="user"?g.jsx("div",{className:"flex justify-end",children:g.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[g.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:g.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),g.jsx(rc,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(rc,{role:"assistant"}),g.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&g.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((a,o)=>g.jsx(tO,{toolCall:a},o))}),g.jsx(nO,{content:e.content}),e.elements&&e.elements.length>0&&g.jsx("div",{className:"mt-3 space-y-3",children:e.elements.map((a,o)=>g.jsx(QE,{element:a,className:"w-full"},o))}),e.images&&e.images.length>0&&g.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((a,o)=>g.jsx("img",{src:a,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&g.jsx(xq,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(_q,{text:e.content}),g.jsx(Nq,{messageId:e.id})]})]})]})}function Lq({onSend:e,onCancel:t,isStreaming:r=!1,placeholder:a="Type a message...",enableFileUpload:o=!1}){const[s,u]=S.useState(""),[c,p]=S.useState([]),f=S.useRef(null),h=S.useRef(null),m=S.useCallback(()=>{const A=s.trim();(A||c.length>0)&&!r&&(e(A,c.length>0?c:void 0),u(""),p([]),f.current&&(f.current.style.height="auto"))},[s,c,r,e]),v=S.useCallback(A=>{const _=Array.from(A.target.files||[]);p(N=>[...N,..._]),h.current&&(h.current.value="")},[]),E=S.useCallback(()=>{h.current?.click()},[]),x=S.useCallback(A=>{p(_=>_.filter((N,D)=>D!==A))},[]),w=S.useCallback(A=>{A.key==="Enter"&&!A.shiftKey&&(A.preventDefault(),m())},[m]),k=S.useCallback(()=>{const A=f.current;A&&(A.style.height="auto",A.style.height=`${Math.min(A.scrollHeight,200)}px`)},[]);return g.jsx("div",{className:"p-4 pb-6",children:g.jsxs("div",{className:"max-w-3xl mx-auto",children:[g.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[c.length>0&&g.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:c.map((A,_)=>g.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[g.jsx("span",{className:"truncate max-w-[100px]",children:A.name}),g.jsx("button",{type:"button",onClick:()=>x(_),className:"hover:text-destructive",children:"×"})]},`${A.name}-${_}`))}),g.jsxs("div",{className:"flex items-end gap-2 p-2",children:[o&&g.jsxs(g.Fragment,{children:[g.jsx("input",{ref:h,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),g.jsx("button",{type:"button",onClick:E,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})})})]}),g.jsx("textarea",{ref:f,value:s,onChange:A=>u(A.target.value),onKeyDown:w,onInput:k,placeholder:a,disabled:r,rows:1,className:"flex-1 resize-none bg-transparent px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 placeholder:text-muted-foreground/60",style:{maxHeight:"200px"}}),r?g.jsx("button",{type:"button",onClick:t,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:g.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):g.jsx("button",{type:"button",onClick:m,disabled:!s.trim(),className:"p-2 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",title:"Send",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),g.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line" Line truncated
`),m=S.useCallback(async()=>{!r.trim()||s||p(r)},[r,s,p]),v=S.useCallback(()=>{a("")},[]);return g.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[g.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[g.jsx("h1",{className:"text-xl font-semibold",children:t||"Playground"}),g.jsx("div",{className:"flex items-center gap-2",children:g.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),g.jsxs("div",{className:"flex-1 flex",children:[g.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[g.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),g.jsx("div",{className:"flex-1 p-4",children:g.jsx("textarea",{value:r,onChange:E=>a(E.target.value),placeholder:"Enter your prompt here...",className:"w-full h-full resize-none bg-transparent border rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-primary"})}),g.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:s?g.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):g.jsx("button",{onClick:m,disabled:!r.trim(),className:"px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",children:"Submit"})})]}),g.jsxs("div",{className:"flex-1 flex flex-col",children:[g.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),s&&g.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),g.jsx("div",{className:"flex-1 p-4 overflow-auto",children:h?g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:h}):g.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&g.jsxs("div",{className:"border-t px-6 py-3 bg-muted/20 flex items-center gap-4 text-sm text-muted-foreground",children:[e.features.streaming&&g.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&g.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&g.jsx("span",{children:"✓ Tools"})]})]})}function v9(e,t){const r=e.replace(/^\/+|\/+$/g,""),o=t.replace(/^\/+|\/+$/g,"").replace(/\*\*/g,"{{GLOB}}").replace(/\*/g,"[^/]+").replace(/\{\{GLOB\}\}/g,".*");return new RegExp(`^${o}$`).test(r)}function E9(e,t,r){const o=[...t.routes??[]].sort((s,u)=>(u.priority??0)-(s.priority??0));for(const s of o){if(!v9(e,s.pattern))continue;const u=r[s.template];return u?{template:s.template,layout:u.layout??"ThreeColumnLayout",slots:u.slots??{},slotOverrides:s.slotOverrides}:(console.warn(`Template "${s.template}" not found`),null)}return null}function S9(e,t){if(t)return t.slotOverrides&&e in t.slotOverrides?t.slotOverrides[e]:t.slots[e]}function x9(e){return S9("right",e)!==null}function Lu({enabled:e}){return e?g.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-primary text-primary-foreground px-4 py-2 rounded-md text-sm font-medium",children:"Skip to main content"}):null}function w9(){const[e,t]=S.useState({}),[r,a]=S.useState({}),[o,s]=S.useState({}),[u,c]=S.useState(!0),[p,f]=S.useState(null),[h,m]=S.useState(null),[v,E]=S.useState(""),[x,w]=S.useState(()=>window.location.pathname.replace(/\/$/,"")||"/"),[k,A]=S.useState(!1),_=(M,H)=>{const Q=H.replace(/\/$/,"")||"/",de=I=>{for(const U of I){const B=(U.path??"").replace(/\/$/,"");if(B===Q||B===Q.replace(/^\//,""))return U;if(U.children){const C=de(U.children);if(C)return C}}return null};return M.items?de(M.items):null},N=(M,H,Q,de)=>{const I=de??e,U=I.seo?.titleTemplate||"%s | %s",B=I.site?.title||"Documentation";if(U.includes("%s")){let ie=U.replace("%s",M);ie.includes("%s")&&(ie=ie.replace("%s",B)),document.title=ie}else document.title=`${M} | ${B}`;let C=document.querySelector('link[rel="canonical"]');C||(C=document.createElement("link"),C.rel="canonical",document.head.appendChild(C)),C.href=window.location.origin+H;let J=document.querySelector('meta[name="description"]');J||(J=document.createElement("meta"),J.name="description",document.head.appendChild(J)),J.content=`${M} - ${I.site?.description||""}`;let ae=document.querySelector('meta[property="og:title"]');ae||(ae=document.createElement("meta"),ae.setAttribute("property","og:title"),document.head.appendChild(ae)),ae.content=M;let ue=document.querySelector('meta[property="og:url"]');if(ue||(ue=document.createElement("meta"),ue.setAttribute("property","og:url"),document.head.appendChild(ue)),ue.content=window.location.origin+H,I.seo?.twitter&&Object.entries(I.seo.twitter).forEach(([ie,he])=>{let fe=document.querySel Line truncated
@@ -475,6 +475,7 @@
}
/* ── Light Theme (Gap 19) ─────────────────────────────────────────*/
:root:not(.dark) .chat-container,
[data-theme="light"] .chat-container,
.chat-container.light {
--chat-bg: #fff;
@@ -82,6 +82,7 @@ function addCopyButtons(root) {
if (pre.dataset.copyProcessed) return;
if (pre.closest('.mermaid-diagram')) return;
if (pre.querySelector('.mermaid')) return;
if (pre.querySelector('code.language-mermaid')) return;
pre.dataset.copyProcessed = 'true';
@@ -122,6 +122,8 @@ async function loadContent(root) {
// Update the "On This Page" ToC if toc plugin is active
updateTocSidebar(article);
window.dispatchEvent(new CustomEvent('aiui:content-loaded', { detail: { root: article } }));
console.debug('[AIUI:content-loader] Loaded content from', mdPath);
} catch (err) {
@@ -163,15 +165,73 @@ function updateTocSidebar(article) {
/* ───────── Markdown → HTML Converter ───────── */
function convertMkdocsTabs(md) {
const lines = md.split('\n');
const out = [];
let i = 0;
while (i < lines.length) {
const tabMatch = lines[i].match(/^===\s+"([^"]+)"\s*$/);
if (!tabMatch) {
out.push(lines[i]);
i++;
continue;
}
const title = tabMatch[1];
i++;
while (i < lines.length && lines[i].trim() === '') i++;
const block = [];
while (i < lines.length) {
if (/^===\s+"[^"]+"\s*$/.test(lines[i])) break;
if (lines[i].trim() === '' && i + 1 < lines.length && /^===\s+"[^"]+"\s*$/.test(lines[i + 1])) break;
if (lines[i].startsWith(' ') || lines[i].trimStart().startsWith('```')) {
block.push(lines[i].replace(/^ /, ''));
i++;
continue;
}
if (block.length > 0 && lines[i].trim() === '') {
block.push('');
i++;
continue;
}
break;
}
out.push(`### \u200Btab:${title}`);
out.push('');
out.push(...block);
out.push('');
}
return out.join('\n');
}
function markdownToHtml(md) {
// Clean up MkDocs-specific syntax
// Clean up MkDocs-specific syntax (mirrors src/frontend/src/markdown/mkdocsPreprocess.ts)
let html = md
.replace(/<div[^>]*markdown[^>]*>/gi, '')
.replace(/<\/div>/gi, '')
.replace(/===\s+"([^"]+)"/g, '**$1**')
.replace(/<div[^>]*markdown[^>]*>\s*/gi, '')
.replace(/<\/div>\s*/gi, '');
const iconMap = {
':material-file-document:': '📄',
':material-puzzle:': '🧩',
':material-palette:': '🎨',
':material-rocket-launch:': '🚀',
};
for (const [shortcode, emoji] of Object.entries(iconMap)) {
html = html.replaceAll(shortcode, emoji);
}
html = html
.replace(/:material-[\w-]+:/g, '•')
.replace(/:octicons-[\w-]+(?:-\d+)?:/g, '•');
html = convertMkdocsTabs(html);
const lines = html.split('\n');
const result = [];
let inCodeBlock = false, codeContent = '', codeLang = '';
@@ -207,7 +267,7 @@ function markdownToHtml(md) {
// Code blocks
if (line.trimStart().startsWith('```')) {
if (inCodeBlock) {
result.push(`<pre class="bg-gray-900 rounded-lg p-4 my-4 overflow-x-auto"><code class="language-${codeLang}">${escapeHtml(codeContent.trim())}</code></pre>`);
result.push(`<pre class="bg-muted text-foreground border border-border rounded-lg p-4 my-4 overflow-x-auto"><code class="language-${codeLang}">${escapeHtml(codeContent.trim())}</code></pre>`);
inCodeBlock = false; codeContent = '';
} else {
flushList(); flushTable(); inCodeBlock = true;
@@ -292,8 +352,8 @@ function inlineMarkdown(text) {
return text
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`([^`]+)`/g, '<code class="bg-gray-800 px-1.5 py-0.5 rounded text-sm">$1</code>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-blue-400 hover:text-blue-300 underline">$1</a>');
.replace(/`([^`]+)`/g, '<code class="bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono">$1</code>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-primary hover:underline">$1</a>');
}
function escapeHtml(text) {
@@ -376,6 +436,8 @@ async function navigateToContent(targetPath) {
// Update the "On This Page" ToC
updateTocSidebar(article);
window.dispatchEvent(new CustomEvent('aiui:content-loaded', { detail: { root: article } }));
// Remove anti-flicker CSS (critical: ensures content is visible)
const af = document.getElementById('aiui-anti-flicker');
if (af) af.remove();
@@ -156,6 +156,7 @@ const BUILTIN_VIEWS = {
approvals: '/plugins/views/approvals.js',
usage: '/plugins/views/usage.js',
channels: '/plugins/views/channels.js',
inbox: '/plugins/views/inbox.js',
skills: '/plugins/views/skills.js',
tools: '/plugins/views/skills.js',
nodes: '/plugins/views/nodes.js',
@@ -1363,6 +1364,46 @@ async function consumeToken(token) {
}
}
async function probeGatewayReachable(urlString) {
let base;
try {
base = new URL(urlString);
} catch (e) {
return { ok: false, reason: 'invalid_url' };
}
if (base.protocol !== 'http:' && base.protocol !== 'https:') {
return { ok: false, reason: 'invalid_url' };
}
const probe = new URL('/api/auth/status', base);
const sameOrigin = base.origin === window.location.origin;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(probe.toString(), {
method: 'GET',
credentials: 'omit',
signal: controller.signal,
});
clearTimeout(timeout);
if (res.type === 'opaque' || res.status === 0) {
return { ok: false, reason: 'opaque' };
}
return { ok: true, status: res.status };
} catch (e) {
clearTimeout(timeout);
if (e.name === 'AbortError') {
return { ok: false, reason: 'timeout' };
}
// A rejected fetch on a cross-origin URL is ambiguous: the gateway may be
// up but blocking CORS, or genuinely unreachable. The browser cannot tell
// these apart, so surface the "Continue anyway" opt-in rather than a
// definitive unreachable error. Same-origin rejections are genuine.
return { ok: false, reason: sameOrigin ? 'network' : 'opaque' };
}
}
async function maybeShowOnboarding() {
let params;
try {
@@ -1448,18 +1489,36 @@ function renderOnboarding(status) {
<button id="ob-continue" style="width:100%;padding:11px;border:none;border-radius:8px;background:var(--db-accent,#4f8cff);color:#fff;font-size:14px;font-weight:600;cursor:pointer">Continue</button>
<button id="ob-standalone" style="width:100%;padding:8px;margin-top:10px;border:none;background:none;color:var(--db-text-muted,#9aa4b2);font-size:13px;cursor:pointer;text-decoration:underline">Use standalone mode</button>
`);
overlay.querySelector('#ob-continue').addEventListener('click', async () => {
const continueBtn = overlay.querySelector('#ob-continue');
continueBtn.addEventListener('click', async () => {
const url = overlay.querySelector('#ob-url').value.trim();
const errEl = overlay.querySelector('#ob-url-err');
const override = overlay.querySelector('#ob-url-override');
errEl.textContent = '';
try {
await fetch(url, { method: 'HEAD', mode: 'no-cors' });
} catch (e) {
errEl.textContent = `Cannot reach gateway at ${url}`;
if (override && override.checked) {
state.step = 2;
render();
return;
}
state.step = 2;
render();
continueBtn.disabled = true;
continueBtn.textContent = 'Checking…';
const result = await probeGatewayReachable(url);
continueBtn.disabled = false;
continueBtn.textContent = 'Continue';
if (result.ok) {
state.step = 2;
render();
return;
}
if (result.reason === 'invalid_url') {
errEl.textContent = `Enter a valid http(s) URL`;
} else if (result.reason === 'opaque') {
errEl.innerHTML = `Cannot verify gateway from browser. <label style="cursor:pointer"><input id="ob-url-override" type="checkbox" style="vertical-align:middle"> Continue anyway</label>`;
} else if (result.reason === 'timeout') {
errEl.textContent = `Gateway did not respond at ${url}`;
} else {
errEl.textContent = `Cannot reach gateway at ${url}`;
}
});
overlay.querySelector('#ob-standalone').addEventListener('click', () => {
try { window.localStorage.setItem(ONBOARD_SKIP_KEY, '1'); } catch (e) { /* ignore */ }
@@ -300,7 +300,13 @@ let lastUrl = '';
export default {
name: 'mermaid',
async init() { await loadMermaidLib(); },
async init() {
await loadMermaidLib();
window.addEventListener('aiui:content-loaded', (event) => {
const root = event.detail?.root || document;
renderMermaidBlocks(root);
});
},
onContentChange(root) {
const currentUrl = location.pathname + location.hash;
@@ -1,74 +1,247 @@
/**
* AIUI MkDocs Compatibility Plugin
*
*
* Strips or transforms MkDocs Material-specific syntax that renders
* as raw text in standard markdown renderers:
*
* - :material-*: icon shortcodes → emoji or removes them
* - <div class="grid cards" markdown> → removes wrapper
* - === "Tab Title" → transforms to heading
* - !!! note/warning → transforms to blockquote
* - </div> visible tags → removes them
* as raw text in standard markdown renderers.
*/
const ICON_MAP = {
':material-file-document:': '📄',
':material-puzzle:': '🧩',
':material-palette:': '🎨',
':material-rocket-launch:': '🚀',
':material-code-tags:': '💻',
':material-cog:': '⚙️',
':material-lightning-bolt:': '⚡',
':material-shield:': '🛡️',
':material-database:': '🗄️',
':material-web:': '🌐',
':material-book:': '📖',
':material-star:': '⭐',
':material-check:': '✅',
':material-close:': '❌',
':material-alert:': '⚠️',
':material-information:': 'ℹ️',
};
const TAB_HEADING_PREFIX = '\u200Btab:';
function replaceIcons(text) {
let result = text;
for (const [shortcode, emoji] of Object.entries(ICON_MAP)) {
result = result.replaceAll(shortcode, emoji);
}
return result
.replace(/:material-[\w-]+:/g, '•')
.replace(/:octicons-[\w-]+(?:-\d+)?:/g, '•');
}
function convertMkdocsTabs(md) {
const lines = md.split('\n');
const out = [];
let i = 0;
while (i < lines.length) {
const tabMatch = lines[i].match(/^===\s+"([^"]+)"\s*$/);
if (!tabMatch) {
out.push(lines[i]);
i++;
continue;
}
const title = tabMatch[1];
i++;
while (i < lines.length && lines[i].trim() === '') i++;
const block = [];
while (i < lines.length) {
if (/^===\s+"[^"]+"\s*$/.test(lines[i])) break;
if (lines[i].trim() === '' && i + 1 < lines.length && /^===\s+"[^"]+"\s*$/.test(lines[i + 1])) break;
if (lines[i].startsWith(' ') || lines[i].trimStart().startsWith('```')) {
block.push(lines[i].replace(/^ /, ''));
i++;
continue;
}
if (block.length > 0 && lines[i].trim() === '') {
block.push('');
i++;
continue;
}
break;
}
out.push(`### ${TAB_HEADING_PREFIX}${title}`);
out.push('');
out.push(...block);
out.push('');
}
return out.join('\n');
}
function isTabHeading(text) {
return text.startsWith(TAB_HEADING_PREFIX) || text.startsWith('tab:');
}
function tabTitle(text) {
return text.replace(/^\u200Btab:/, '').replace(/^tab:/, '');
}
function enhanceFeatureGrid(article) {
for (const ul of article.querySelectorAll('ul')) {
if (ul.classList.contains('aiui-feature-grid')) continue;
const firstLi = ul.querySelector('li');
const looksLikeCards = Boolean(
firstLi?.textContent?.includes('YAML-Driven')
|| firstLi?.textContent?.includes('Component Slots')
|| firstLi?.querySelector('strong'),
);
if (!looksLikeCards) continue;
ul.classList.add(
'aiui-feature-grid',
'grid',
'grid-cols-1',
'md:grid-cols-2',
'gap-4',
'my-6',
'list-none',
'pl-0',
);
for (const li of ul.querySelectorAll('li')) {
li.classList.add('border', 'border-border', 'rounded-lg', 'p-4', 'bg-card');
}
}
}
function enhanceTabs(article) {
if (article.querySelector('[data-aiui-tabs]')) return;
const tabHeadings = Array.from(article.querySelectorAll('h3')).filter((h3) =>
isTabHeading(h3.textContent?.trim() ?? ''),
);
if (tabHeadings.length < 2) return;
const sections = tabHeadings.map((heading) => {
const title = tabTitle(heading.textContent?.trim() ?? '');
const nodes = [];
let sibling = heading.nextSibling;
while (sibling) {
if (sibling.nodeType === Node.ELEMENT_NODE) {
const el = sibling;
if (el.tagName === 'H3' && isTabHeading(el.textContent?.trim() ?? '')) break;
if (['H1', 'H2'].includes(el.tagName)) break;
}
const next = sibling.nextSibling;
nodes.push(sibling);
sibling = next;
}
return { heading, title, nodes };
});
const wrapper = document.createElement('div');
wrapper.className = 'aiui-tabs my-6 border border-border rounded-lg overflow-hidden';
wrapper.dataset.aiuiTabs = 'true';
const buttons = document.createElement('div');
buttons.className = 'aiui-tab-buttons flex flex-wrap gap-0 border-b border-border bg-muted/30';
wrapper.appendChild(buttons);
const panels = document.createElement('div');
panels.className = 'aiui-tab-panels';
wrapper.appendChild(panels);
sections.forEach((section, index) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = [
'aiui-tab-button px-4 py-2 text-sm font-medium transition-colors',
index === 0
? 'bg-background text-foreground border-b-2 border-primary'
: 'text-muted-foreground hover:text-foreground',
].join(' ');
btn.textContent = section.title;
btn.dataset.tabIndex = String(index);
buttons.appendChild(btn);
const panel = document.createElement('div');
panel.className = index === 0 ? 'aiui-tab-panel p-4 block' : 'aiui-tab-panel p-4 hidden';
panel.dataset.tabPanel = String(index);
for (const node of section.nodes) {
panel.appendChild(node);
}
panels.appendChild(panel);
});
article.insertBefore(wrapper, sections[0].heading);
for (const section of sections) {
section.heading.remove();
}
buttons.addEventListener('click', (event) => {
const target = event.target.closest('.aiui-tab-button');
if (!target) return;
const index = target.dataset.tabIndex ?? '0';
for (const btn of buttons.querySelectorAll('.aiui-tab-button')) {
const active = btn.dataset.tabIndex === index;
btn.classList.toggle('bg-background', active);
btn.classList.toggle('text-foreground', active);
btn.classList.toggle('border-b-2', active);
btn.classList.toggle('border-primary', active);
btn.classList.toggle('text-muted-foreground', !active);
}
for (const panel of panels.querySelectorAll('.aiui-tab-panel')) {
panel.classList.toggle('hidden', panel.dataset.tabPanel !== index);
panel.classList.toggle('block', panel.dataset.tabPanel === index);
}
});
}
/**
* Clean MkDocs-specific patterns from rendered HTML.
*/
function cleanMkDocsContent(root) {
// Find all text nodes and elements in the content area
const articles = root.querySelectorAll('article.prose, main .prose');
if (articles.length === 0) return;
const articles = new Set();
if (root.matches?.('article.prose, main .prose, .prose, main.flex-1, #main-content')) {
articles.add(root);
}
root.querySelectorAll('article.prose, main .prose, .prose, main.flex-1, #main-content').forEach((el) => articles.add(el));
if (articles.size === 0) return;
for (const article of articles) {
if (article.dataset.mkdocsClean) continue;
delete article.dataset.mkdocsClean;
let html = article.innerHTML;
let changed = false;
// 1. Remove :material-*: icon shortcodes (replace with relevant emoji or empty)
const iconMap = {
':material-file-document:': '📄',
':material-puzzle:': '🧩',
':material-palette:': '🎨',
':material-rocket-launch:': '🚀',
':material-code-tags:': '💻',
':material-cog:': '⚙️',
':material-lightning-bolt:': '⚡',
':material-shield:': '🛡️',
':material-database:': '🗄️',
':material-web:': '🌐',
':material-book:': '📖',
':material-star:': '⭐',
':material-check:': '✅',
':material-close:': '❌',
':material-alert:': '⚠️',
':material-information:': 'ℹ️',
};
// Replace known icons
for (const [shortcode, emoji] of Object.entries(iconMap)) {
for (const [shortcode, emoji] of Object.entries(ICON_MAP)) {
if (html.includes(shortcode)) {
html = html.replaceAll(shortcode, emoji);
changed = true;
}
}
// Replace any remaining :material-*: with generic icon
const materialPattern = /:material-[\w-]+:/g;
if (materialPattern.test(html)) {
html = html.replace(materialPattern, '•');
changed = true;
}
// Replace :octicons-*: shortcodes
const octiconsPattern = /:octicons-[\w-]+(?:-\d+)?:/g;
if (octiconsPattern.test(html)) {
html = html.replace(octiconsPattern, '•');
changed = true;
}
// 2. Remove <div class="grid cards" markdown> and </div> tags
// These render as visible text in react-markdown
const gridCardPattern = /&lt;div\s+class="[^"]*"\s*(?:markdown)?&gt;/gi;
if (gridCardPattern.test(html)) {
html = html.replace(gridCardPattern, '');
@@ -81,29 +254,20 @@ function cleanMkDocsContent(root) {
changed = true;
}
// Also handle cases where react-markdown renders them as actual elements
const rawDivNodes = article.querySelectorAll('p');
for (const p of rawDivNodes) {
if (changed) {
article.innerHTML = html;
}
for (const p of article.querySelectorAll('p')) {
const text = p.textContent.trim();
if (/^<\/?div[\s>]/.test(text) || text === '</div>') {
// Clear text instead of hiding — safer for React reconciliation
p.textContent = '';
changed = true;
p.remove();
}
}
// 3. Transform === "Tab Title" content tabs to headings
// These appear as literal text like: === "Python"
const tabPattern = /===\s+"([^"]+)"/g;
if (tabPattern.test(html)) {
html = html.replace(tabPattern, '<strong>$1</strong>');
changed = true;
}
if (changed) {
article.innerHTML = html;
article.dataset.mkdocsClean = 'true';
}
enhanceFeatureGrid(article);
enhanceTabs(article);
article.dataset.mkdocsClean = 'true';
}
}
@@ -118,3 +282,5 @@ export default {
cleanMkDocsContent(root);
},
};
export { replaceIcons, convertMkdocsTabs, ICON_MAP };
@@ -1,54 +1,73 @@
/**
* AIUI Syntax Highlighting Plugin
*
* Adds colorful syntax highlighting to code blocks using highlight.js.
* Adds colourful syntax highlighting to code blocks using highlight.js.
* Loads from CDN – no build step required.
*/
let hljsReady = false;
let pendingHighlight = false;
let currentTheme = null;
const HLJS_VERSION = '11.9.0';
const HLJS_THEME = 'github-dark';
const HLJS_CDN = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${HLJS_VERSION}`;
function isDarkMode() {
return document.documentElement.classList.contains('dark');
}
function getHljsTheme() {
return isDarkMode() ? 'github-dark' : 'github';
}
function applyPreStyles() {
const dark = isDarkMode();
let override = document.getElementById('aiui-hljs-overrides');
if (!override) {
override = document.createElement('style');
override.id = 'aiui-hljs-overrides';
document.head.appendChild(override);
}
override.textContent = `
pre code.hljs {
background: transparent !important;
padding: 0 !important;
color: inherit !important;
}
pre {
background: ${dark ? 'rgba(15, 23, 42, 0.6)' : 'var(--muted, #f4f4f5)'} !important;
color: ${dark ? 'inherit' : 'var(--foreground, #18181b)'} !important;
border: 1px solid ${dark ? 'rgba(148, 163, 184, 0.1)' : 'var(--border, #e4e4e7)'};
border-radius: 0.5rem;
padding: 1rem !important;
overflow-x: auto;
}
`;
}
/**
* Load highlight.js CSS theme and core script from CDN.
*/
function loadHljs() {
return new Promise((resolve) => {
// Load theme CSS
const theme = getHljsTheme();
currentTheme = theme;
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `${HLJS_CDN}/styles/${HLJS_THEME}.min.css`;
link.id = 'aiui-hljs-theme';
link.href = `${HLJS_CDN}/styles/${theme}.min.css`;
document.head.appendChild(link);
// Override styles for better integration
const override = document.createElement('style');
override.id = 'aiui-hljs-overrides';
override.textContent = `
pre code.hljs {
background: transparent !important;
padding: 0 !important;
}
pre {
background: rgba(15, 23, 42, 0.6) !important;
border: 1px solid rgba(148, 163, 184, 0.1);
border-radius: 0.5rem;
padding: 1rem !important;
overflow-x: auto;
}
`;
document.head.appendChild(override);
applyPreStyles();
// Load highlight.js core
const script = document.createElement('script');
script.src = `${HLJS_CDN}/highlight.min.js`;
script.onload = () => {
hljsReady = true;
console.debug('[AIUI:syntax] highlight.js core loaded');
// Load additional language packs
const langs = ['python', 'yaml', 'bash', 'javascript', 'typescript', 'json', 'css', 'html', 'xml'];
let loaded = 0;
for (const lang of langs) {
@@ -58,7 +77,6 @@ function loadHljs() {
loaded++;
if (loaded >= langs.length) {
console.debug('[AIUI:syntax] All languages loaded');
// Highlight any pending content
if (pendingHighlight) {
pendingHighlight = false;
highlightCodeBlocks();
@@ -68,7 +86,6 @@ function loadHljs() {
document.head.appendChild(ls);
}
// Also highlight immediately with core languages
setTimeout(() => highlightCodeBlocks(), 200);
resolve();
};
@@ -80,6 +97,28 @@ function loadHljs() {
});
}
function swapThemeIfNeeded() {
const theme = getHljsTheme();
if (theme === currentTheme) {
applyPreStyles();
return;
}
currentTheme = theme;
const link = document.getElementById('aiui-hljs-theme');
if (link) {
link.href = `${HLJS_CDN}/styles/${theme}.min.css`;
}
applyPreStyles();
for (const block of document.querySelectorAll('pre code[data-hljs-highlighted]')) {
delete block.dataset.hljsHighlighted;
block.removeAttribute('data-highlighted');
block.classList.remove('hljs');
}
highlightCodeBlocks();
}
/**
* Highlight all unprocessed code blocks.
*/
@@ -89,9 +128,10 @@ function highlightCodeBlocks() {
return;
}
swapThemeIfNeeded();
const blocks = document.querySelectorAll('pre code:not([data-hljs-highlighted])');
for (const block of blocks) {
// Skip mermaid blocks
if (block.classList.contains('language-mermaid') ||
block.closest('.mermaid') ||
block.closest('[data-aiui-plugin="mermaid"]')) {
@@ -111,10 +151,13 @@ export default {
name: 'syntax-highlight',
async init() {
await loadHljs();
const observer = new MutationObserver(() => swapThemeIfNeeded());
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
console.debug('[AIUI:syntax] Plugin loaded.');
},
onContentChange() {
// Try immediately and with delay (for dynamically inserted content)
highlightCodeBlocks();
setTimeout(highlightCodeBlocks, 300);
setTimeout(highlightCodeBlocks, 800);
@@ -237,9 +237,309 @@ export function timeAgo(input) {
return `${Math.floor(h / 24)}d ago`;
}
/** Format a token count into a compact string (mirrors usage.js). */
export function formatTokens(tokens) {
const n = Number(tokens) || 0;
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
return String(Math.round(n));
}
/** Format a USD cost into a string (mirrors usage.js). */
export function formatCost(cost) {
const n = Number(cost) || 0;
if (n < 0.01) return `$${n.toFixed(4)}`;
if (n < 1) return `$${n.toFixed(3)}`;
return `$${n.toFixed(2)}`;
}
/** Strip a provider prefix from a model id (anthropic/claude-3 → claude-3). */
export function shortModelName(model) {
const s = model == null ? '' : String(model);
const idx = s.lastIndexOf('/');
return idx >= 0 ? s.slice(idx + 1) : s;
}
/**
* Sum tokens from timeseries buckets belonging to the current local day.
* Falls back to summing all buckets when no bucket carries a hour_key.
*/
export function sumTodayTokens(timeseries) {
const arr = Array.isArray(timeseries) ? timeseries : [];
const todayKey = new Date().toISOString().slice(0, 10);
const dated = arr.filter((p) => typeof p.hour_key === 'string');
const scope = dated.length ? dated.filter((p) => p.hour_key.startsWith(todayKey)) : arr;
return scope.reduce((sum, p) => sum + (Number(p.tokens) || 0), 0);
}
/**
* Percent of daily budget consumed, capped at 100.
* Returns null when the budget is unset or non-positive (E3/E4).
*/
export function computeBudgetPct(today, budget) {
const b = Number(budget);
if (!b || b <= 0) return null;
const pct = (Number(today) || 0) / b * 100;
if (!isFinite(pct) || pct < 0) return 0;
return Math.min(pct, 100);
}
/** Banner level from a budget percentage and thresholds. */
export function budgetLevelFor(pct, warnPct = 80, criticalPct = 95) {
if (pct == null || isNaN(pct)) return 'none';
if (pct >= criticalPct) return 'critical';
if (pct >= warnPct) return 'warn';
return 'none';
}
/** Gauge fill colour by budget percentage. */
export function budgetGaugeColor(pct) {
if (pct == null) return 'var(--db-accent,#6366f1)';
if (pct >= 95) return '#ef4444';
if (pct >= 80) return '#f97316';
if (pct >= 60) return '#eab308';
return '#22c55e';
}
const DONUT_PALETTE = ['#3b82f6', '#8b5cf6', '#22c55e', '#64748b'];
/**
* Pure SVG donut (no chart library).
*
* @param {Array<{label: string, value: number, color?: string}>} slices
* @param {number} [size=40]
* @returns {string} SVG markup
*/
export function MiniDonutSVG(slices, size = 40) {
const data = (Array.isArray(slices) ? slices : [])
.filter((s) => s && (Number(s.value) || 0) > 0);
const total = data.reduce((sum, s) => sum + (Number(s.value) || 0), 0);
if (!data.length || total <= 0) {
const r = size / 2 - 3;
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" role="img" aria-label="No model data"><circle cx="${size / 2}" cy="${size / 2}" r="${r}" fill="none" stroke="var(--db-border,#3f3f46)" stroke-width="4" stroke-dasharray="3 3" /></svg>`;
}
const cx = size / 2;
const cy = size / 2;
const r = size / 2 - 3;
const inner = r * 0.55;
let angle = -Math.PI / 2;
const parts = [];
const labels = [];
data.forEach((s, i) => {
const frac = (Number(s.value) || 0) / total;
const end = angle + frac * Math.PI * 2;
const color = s.color || DONUT_PALETTE[i % DONUT_PALETTE.length];
const large = end - angle > Math.PI ? 1 : 0;
const x1 = cx + r * Math.cos(angle);
const y1 = cy + r * Math.sin(angle);
const x2 = cx + r * Math.cos(end);
const y2 = cy + r * Math.sin(end);
const ix2 = cx + inner * Math.cos(end);
const iy2 = cy + inner * Math.sin(end);
const ix1 = cx + inner * Math.cos(angle);
const iy1 = cy + inner * Math.sin(angle);
parts.push(`<path d="M${x1.toFixed(2)},${y1.toFixed(2)} A${r},${r} 0 ${large} 1 ${x2.toFixed(2)},${y2.toFixed(2)} L${ix2.toFixed(2)},${iy2.toFixed(2)} A${inner},${inner} 0 ${large} 0 ${ix1.toFixed(2)},${iy1.toFixed(2)} Z" fill="${color}" />`);
labels.push(`${esc(s.label)} ${Math.round(frac * 100)}%`);
angle = end;
});
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" role="img" aria-label="${esc(labels.join(', '))}">${parts.join('')}</svg>`;
}
/**
* Reduce per-model usage rows to donut slices: top 3 by cost + aggregated Other.
*
* @param {Array<{model?: string, cost_usd?: number, total_tokens?: number}>} models
* @returns {Array<{label: string, value: number, color: string}>}
*/
export function modelDonutSlices(models) {
const rows = (Array.isArray(models) ? models : [])
.map((m) => ({ label: shortModelName(m.model), value: Number(m.cost_usd) || Number(m.total_tokens) || 0 }))
.filter((m) => m.value > 0)
.sort((a, b) => b.value - a.value);
const top = rows.slice(0, 3).map((m, i) => ({ ...m, color: DONUT_PALETTE[i] }));
const rest = rows.slice(3).reduce((sum, m) => sum + m.value, 0);
if (rest > 0) top.push({ label: 'Other', value: rest, color: DONUT_PALETTE[3] });
return top;
}
/**
* Build the Overview Cost FinOps strip markup.
*
* @param {Object} opts
* @param {number} opts.todayTokens
* @param {number} [opts.todayCost]
* @param {number|null} [opts.budget]
* @param {number} [opts.warnPct]
* @param {number} [opts.criticalPct]
* @param {Array} [opts.models]
* @param {number} [opts.delta] - signed pct vs yesterday
* @returns {string} HTML string
*/
export function costStripHTML({ todayTokens, todayCost, budget = null, warnPct = 80, criticalPct = 95, models = [], delta } = {}) {
const pct = computeBudgetPct(todayTokens, budget);
const level = budgetLevelFor(pct, warnPct, criticalPct);
const gaugeColor = budgetGaugeColor(pct);
const slices = modelDonutSlices(models);
const topModel = slices.length ? slices[0].label : '';
const budgetText = pct == null
? `${formatTokens(todayTokens)} tokens`
: `${formatTokens(todayTokens)} / ${formatTokens(budget)} tokens (${Math.round(pct)}%)`;
const gauge = pct == null ? '' : `<div class="db-finops-gauge" role="progressbar" aria-valuenow="${Math.round(pct)}" aria-valuemin="0" aria-valuemax="100" style="height:8px;border-radius:6px;background:var(--db-border,#3f3f46);overflow:hidden;margin-top:6px">
<div class="db-finops-gauge-fill" style="width:${Math.round(pct)}%;height:100%;background:${gaugeColor};transition:width 400ms ease"></div>
</div>`;
const pill = level === 'none' ? '' : `<span class="db-finops-pill" style="font-size:11px;padding:2px 8px;border-radius:10px;margin-left:8px;background:${level === 'critical' ? 'rgba(239,68,68,.15)' : 'rgba(234,179,8,.15)'};color:${level === 'critical' ? '#ef4444' : '#eab308'}">${Math.round(pct)}%</span>`;
const costText = todayCost != null ? `Est. ${formatCost(todayCost)} today` : '';
let deltaHtml = '';
if (typeof delta === 'number' && !isNaN(delta) && delta !== 0) {
const up = delta > 0;
deltaHtml = `<span style="font-size:11px;color:${up ? '#ef4444' : '#22c55e'};margin-left:8px">${up ? '↗' : '↘'} ${Math.abs(delta).toFixed(0)}% vs yesterday</span>`;
}
return `<div id="ov-finops-strip" class="db-card db-finops-strip" role="region" aria-label="Cost and usage summary" tabindex="0" data-nav="usage"
style="cursor:pointer;display:flex;align-items:center;gap:20px;padding:14px 18px;margin-top:16px;margin-bottom:8px;flex-wrap:wrap">
<div style="display:flex;align-items:center;gap:8px;min-width:130px">
<span aria-hidden="true">💰</span>
<span style="font-size:13px;font-weight:600;color:var(--db-text)">Cost FinOps</span>${pill}
</div>
<div style="flex:1;min-width:200px">
<div style="font-size:20px;font-weight:700;color:var(--db-text)">${esc(budgetText)}${deltaHtml}</div>
<div style="font-size:12px;color:var(--db-text-dim,#a1a1aa)">${esc(costText)}</div>
${gauge}
</div>
<div style="display:flex;align-items:center;gap:10px">
${MiniDonutSVG(slices, 40)}
<span style="font-size:12px;color:var(--db-text-dim,#a1a1aa)">${esc(topModel)}</span>
</div>
<span style="font-size:12px;color:var(--db-accent,#6366f1)">View usage →</span>
</div>`;
}
/**
* Build a FinOps budget banner (Overview or Chat).
*
* @param {string} level - 'none' | 'warn' | 'critical'
* @param {number} pct
* @returns {string} HTML string ('' when level is none)
*/
export function budgetBannerHTML(level, pct) {
if (level === 'none') return '';
const critical = level === 'critical';
const color = critical ? '#ef4444' : '#eab308';
const bg = critical ? 'rgba(239,68,68,.1)' : 'rgba(234,179,8,.1)';
const copy = critical
? `Daily token budget at ${Math.round(pct)}% — review usage`
: `Daily token budget at ${Math.round(pct)}% — consider lighter models`;
return `<div class="db-finops-banner" role="${critical ? 'alert' : 'status'}"
style="display:flex;align-items:center;gap:8px;padding:8px 14px;border:1px solid ${color};border-radius:10px;background:${bg};color:${color};font-size:12px;font-weight:500;margin-bottom:8px">
<span aria-hidden="true">💰</span>
<span style="flex:1">${esc(copy)}</span>
<button type="button" class="db-finops-banner-usage" style="background:none;border:none;color:inherit;cursor:pointer;font-size:12px;text-decoration:underline">Open usage</button>
<button type="button" class="db-finops-banner-dismiss" title="Dismiss" aria-label="Dismiss" style="background:none;border:none;color:inherit;cursor:pointer;font-size:13px;padding:0 4px">✕</button>
</div>`;
}
/** Modal shell for view-local dialogs. */
export function modalShell(id, innerHtml) {
return `<div id="${id}" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100;align-items:center;justify-content:center;padding:20px">
<div style="background:var(--db-sidebar-bg);border:1px solid var(--db-border);border-radius:12px;max-width:520px;width:100%;max-height:90vh;overflow:auto;padding:20px">${innerHtml}</div>
</div>`;
}
/**
* Line-level diff between two text blocks using an LCS table.
* Normalises CRLF/CR to LF before comparing. Bounded for large inputs.
*
* @param {string} before
* @param {string} after
* @param {number} [maxLines=2000] - Truncate each side to this many lines.
* @returns {{type:('add'|'del'|'ctx'), text:string}[]}
*/
export function diffLines(before, after, maxLines = 2000) {
const norm = (s) => String(s == null ? '' : s).replace(/\r\n?/g, '\n');
const a = norm(before).split('\n').slice(0, maxLines);
const b = norm(after).split('\n').slice(0, maxLines);
const n = a.length;
const m = b.length;
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
for (let i = n - 1; i >= 0; i--) {
for (let j = m - 1; j >= 0; j--) {
lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
}
}
const out = [];
let i = 0;
let j = 0;
while (i < n && j < m) {
if (a[i] === b[j]) {
out.push({ type: 'ctx', text: a[i] });
i++;
j++;
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
out.push({ type: 'del', text: a[i] });
i++;
} else {
out.push({ type: 'add', text: b[j] });
j++;
}
}
while (i < n) { out.push({ type: 'del', text: a[i] }); i++; }
while (j < m) { out.push({ type: 'add', text: b[j] }); j++; }
return out;
}
/**
* Minimal, XSS-safe markdown preview. All content is escaped first; only a
* small, fixed set of inline/block constructs are re-enabled. No raw HTML,
* scripts, or attributes from source ever reach the DOM.
*
* @param {string} md
* @returns {string} sanitised HTML string
*/
export function renderMarkdownPreview(md) {
const lines = String(md == null ? '' : md).replace(/\r\n?/g, '\n').split('\n');
const inline = (s) => esc(s)
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
const html = [];
let inList = false;
const closeList = () => { if (inList) { html.push('</ul>'); inList = false; } };
for (const raw of lines) {
const line = raw.replace(/\t/g, ' ');
const h = line.match(/^(#{1,6})\s+(.*)$/);
const li = line.match(/^\s*[-*]\s+(.*)$/);
if (h) {
closeList();
const lvl = h[1].length;
html.push(`<h${lvl}>${inline(h[2])}</h${lvl}>`);
} else if (li) {
if (!inList) { html.push('<ul>'); inList = true; }
html.push(`<li>${inline(li[1])}</li>`);
} else if (line.trim() === '') {
closeList();
} else {
closeList();
html.push(`<p>${inline(line)}</p>`);
}
}
closeList();
return html.join('');
}
/**
* True when a pending approval represents a skill write (rules R1-R5).
*
* @param {Object} a - approval record
* @returns {boolean}
*/
export function isSkillApproval(a) {
if (!a || typeof a !== 'object') return false;
const NAMES = ['write_skill', 'skill_write', 'save_skill', 'create_skill'];
const tool = String(a.tool_name || '').toLowerCase();
const action = String(a.action || '').toLowerCase();
if (NAMES.includes(tool) || NAMES.includes(action)) return true;
if (String(a.type || '').toLowerCase() === 'skill_write') return true;
if (a.metadata && String(a.metadata.kind || '').toLowerCase() === 'skill') return true;
const path = String((a.arguments && a.arguments.path) || '');
if (path.endsWith('SKILL.md') || path.includes('.praisonai/skills/')) return true;
return false;
}
@@ -2,6 +2,8 @@
* Approvals View — execution approval queue
* API: /api/approvals
*/
import { isSkillApproval } from '/plugins/views/_helpers.js';
export async function render(container) {
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
@@ -33,11 +35,13 @@ export async function render(container) {
const card = document.createElement('div');
card.className = 'db-card';
card.style.cssText = 'margin-bottom:10px;padding:16px 20px;display:flex;align-items:center;justify-content:space-between';
const isSkill = isSkillApproval(a);
card.innerHTML = `
<div style="flex:1">
<div style="font-weight:500;font-size:14px">${riskIcons[a.risk_level] || '❓'} ${a.tool_name || a.action || 'Unknown'}</div>
<div style="font-size:12px;color:var(--db-text-dim);margin-top:4px">Agent: ${a.agent_name || '—'} · ${a.reason || ''}</div>
<div style="font-size:11px;color:var(--db-text-dim)">${a.created_at ? new Date(a.created_at * 1000).toLocaleString() : ''}</div>
${isSkill ? `<a href="/skills?tab=pending&approval_id=${encodeURIComponent(a.id || a.approval_id || '')}" data-skill-studio="1" style="font-size:11px;color:var(--db-accent);text-decoration:none">Skill write? Open Skills Studio →</a>` : ''}
</div>
<div style="display:flex;gap:8px">
<button class="approv-yes" data-id="${a.id || a.approval_id}" style="padding:6px 16px;background:rgba(34,197,94,0.15);color:#22c55e;border:1px solid rgba(34,197,94,0.3);border-radius:6px;cursor:pointer;font-size:12px">✓ Approve</button>
@@ -90,7 +90,10 @@ export async function render(container) {
<h2 style="margin:0;font-size:1.5rem">Messaging Channels</h2>
<div style="margin-top:4px;font-size:13px;color:var(--db-text-dim)">${online} Online · ${channels.length} Total</div>
</div>
<button id="ch-add" style="padding:8px 18px;background:var(--db-accent,#6366f1);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600;font-size:.85rem">+ Add Channel</button>
<div style="display:flex;align-items:center;gap:12px">
<a href="/inbox" style="font-size:.85rem;color:var(--db-accent,#6366f1);text-decoration:none">Open Inbox →</a>
<button id="ch-add" style="padding:8px 18px;background:var(--db-accent,#6366f1);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600;font-size:.85rem">+ Add Channel</button>
</div>
</div>
<div id="ch-grid" class="db-columns" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px"></div>
<div id="ch-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100;align-items:center;justify-content:center"></div>
@@ -22,6 +22,7 @@ let agents = [];
let messageQueue = [];
let focusMode = false;
let sessionSelectHandler = null;
let prefillHandler = null;
let containerRef = null;
let currentDeltaEl = null;
let currentDeltaBodyEl = null;
@@ -34,6 +35,52 @@ const RING_CIRCUMFERENCE = 2 * Math.PI * 20; // r=20 → ~125.66
let contextPollTimer = null;
let contextRefreshDebounce = null;
let lastContextPct = 0;
let finopsConfig = null;
let sessionCostState = { tokens: 0, model: '', estimate: false };
let lastUserMessage = ''; // last sent user text, drives per-turn memory search
// ── i18n State ───────────────────────────────────────────────────
let i18nStrings = {};
const I18N_FALLBACK = {
'chat.context.healthy': 'Context healthy',
'chat.context.warning': 'Approaching context limit',
'chat.context.critical': 'Context nearly full',
'chat.context.estimate': 'Estimate',
'chat.compaction.banner': 'Context nearly full — older messages may be summarized on next send',
'chat.compaction.compacted': 'Context compacted',
'chat.memory.chip': '{count} memories',
'chat.memory.empty_turn': 'No memories for this turn',
};
function t(key, params) {
let text = i18nStrings[key] != null ? i18nStrings[key] : (I18N_FALLBACK[key] != null ? I18N_FALLBACK[key] : key);
if (params) {
for (const k in params) {
text = text.split('{' + k + '}').join(String(params[k]));
}
}
return text;
}
async function loadI18nStrings() {
let locale = 'en';
try {
const stored = (typeof localStorage !== 'undefined') ? localStorage.getItem('aiui-locale') : null;
if (stored) {
locale = stored;
} else {
const lr = await fetch('/api/i18n/locale');
if (lr.ok) { const ld = await lr.json(); if (ld && ld.locale) locale = ld.locale; }
}
} catch (e) { /* fall back to English */ }
try {
const resp = await fetch('/api/i18n/strings/' + encodeURIComponent(locale));
if (resp.ok) {
const data = await resp.json();
i18nStrings = (data && data.strings) || {};
}
} catch (e) { /* keep English fallback */ }
}
// ── Floating Chat State ──────────────────────────────────────────
let chatModeConfig = { mode: 'fullpage' };
@@ -322,6 +369,16 @@ function injectStyles() {
.chat-memory-chip:hover { background:rgba(147,51,234,.16); }
.chat-memory-chip.loading { opacity:.6; }
/* ── Session Cost Chip (FinOps) ─────────────────────────────── */
.chat-session-cost-chip { display:none; align-items:center; gap:4px; font-size:11px; font-weight:500; padding:4px 10px; border-radius:20px; color:var(--db-text-dim); background:rgba(99,102,241,.08); border:1px solid rgba(99,102,241,.18); white-space:nowrap; cursor:default; }
.chat-session-cost-chip.visible { display:inline-flex; }
.chat-session-cost-chip.estimate { font-style:italic; }
#chat-finops-banner:empty { display:none; }
@media (max-width:480px) {
.chat-session-cost-chip .cost-chip-model { display:none; }
}
.chat-floating-container .chat-session-cost-chip { display:none !important; }
/* ── Enhanced memory cards ──────────────────────────────────── */
.chat-mem-item .mem-head { display:flex; align-items:center; gap:6px; margin-bottom:4px; }
.chat-mem-item .mem-badge { font-size:10px; padding:1px 6px; border-radius:6px; background:rgba(147,51,234,.12); color:rgba(147,51,234,.85); font-weight:600; text-transform:uppercase; }
@@ -683,11 +740,34 @@ function setupResizeHandles(wrapper) {
});
}
// ── Prefill Composer Listener (singleton) ───────────────────────
// Code Studio's "Send to Agent" dispatches a window-level
// 'aiui:prefill-composer' event. This SPA calls render() on every
// visit to /chat, so the listener is installed once at module scope
// and guarded to avoid stacking duplicate handlers across navigations.
function ensurePrefillListener() {
if (prefillHandler) return;
prefillHandler = (e) => {
const text = e.detail && e.detail.text;
if (!text) return;
const el = document.getElementById('chat-input');
if (!el) return;
el.value = text;
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
el.focus();
};
window.addEventListener('aiui:prefill-composer', prefillHandler);
}
// ── Main render (ES module entry point) ─────────────────────────
export async function render(container) {
injectStyles();
containerRef = container;
// Load translations for ring/banner/chip (English fallback on failure)
await loadI18nStrings();
// Fetch chat mode config
try {
const configRes = await fetch('/ui-config.json');
@@ -770,21 +850,24 @@ export async function render(container) {
</div>
</div>
<div class="chat-compose">
<div id="chat-finops-banner"></div>
<div class="chat-attach-strip" id="chat-attach-strip"></div>
<div class="chat-compose-row">
<button class="chat-attach-btn" id="chat-attach-btn" title="Attach file (PDF, image)">📎</button>
<input type="file" id="chat-file-input" accept=".pdf,.png,.jpg,.jpeg,.gif,.webp,image/*,application/pdf" multiple style="display:none" />
<textarea id="chat-input" placeholder="Type a message…" rows="1"></textarea>
<div class="chat-compose-actions">
<div class="chat-ring-wrap chat-ring-healthy" id="chat-ring-wrap">
<div class="chat-session-cost-chip" id="chat-session-cost-chip" tabindex="0"></div>
<div class="chat-ring-wrap chat-ring-healthy" id="chat-ring-wrap"
role="progressbar" aria-label="Context usage"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<svg viewBox="0 0 48 48" aria-hidden="true">
<circle class="chat-ring-track" cx="24" cy="24" r="20"></circle>
<circle class="chat-ring-arc" id="chat-ring-arc" cx="24" cy="24" r="20"
stroke-dasharray="125.66" stroke-dashoffset="125.66"></circle>
</svg>
<button class="chat-ring-send" id="chat-send-btn" title="Send"
role="progressbar" aria-label="Send message"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">➤</button>
type="button" aria-label="Send message">➤</button>
<div class="chat-ring-tooltip" id="chat-ring-tooltip"></div>
</div>
</div>
@@ -819,16 +902,7 @@ export async function render(container) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
window.addEventListener('aiui:prefill-composer', (e) => {
const text = e.detail && e.detail.text;
if (!text) return;
const el = document.getElementById('chat-input');
if (!el) return;
el.value = text;
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
el.focus();
});
ensurePrefillListener();
input.addEventListener('input', () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 150) + 'px';
@@ -865,6 +939,7 @@ export async function render(container) {
// Initial context ring render (estimate for empty/new session)
refreshContextStats();
startContextPoll();
loadFinopsConfig();
await loadAgents();
@@ -904,6 +979,10 @@ export function cleanup() {
window.removeEventListener('aiui:session-select', sessionSelectHandler);
sessionSelectHandler = null;
}
if (prefillHandler) {
window.removeEventListener('aiui:prefill-composer', prefillHandler);
prefillHandler = null;
}
currentSessionId = null;
currentAgentName = null;
isStreaming = false;
@@ -1195,6 +1274,7 @@ function handleWsMessage(data) {
// Refresh context ring + memory chip after turn completes
scheduleContextRefresh();
refreshMemoryChip();
updateFinopsBanner();
break;
case 'context_update':
@@ -1848,7 +1928,6 @@ function ringStateFor(pct) {
function updateContextRing(stats) {
const wrap = document.getElementById('chat-ring-wrap');
const arc = document.getElementById('chat-ring-arc');
const sendBtn = document.getElementById('chat-send-btn');
if (!wrap || !arc) return;
const used = Number(stats.used) || 0;
@@ -1872,13 +1951,12 @@ function updateContextRing(stats) {
arc.setAttribute('stroke-dasharray', RING_CIRCUMFERENCE.toFixed(2));
arc.setAttribute('stroke-dashoffset', (RING_CIRCUMFERENCE * (1 - clamped / 100)).toFixed(2));
if (sendBtn) {
sendBtn.setAttribute('aria-valuenow', String(Math.round(pct)));
sendBtn.setAttribute('aria-valuemax', '100');
}
wrap.setAttribute('aria-valuenow', String(Math.round(pct)));
wrap.setAttribute('aria-valuemax', '100');
updateRingTooltip(stats, pct);
updateCompactionBanner(pct, estimate);
updateSessionCostChip(stats);
}
function updateRingTooltip(stats, pct) {
@@ -1895,9 +1973,11 @@ function updateRingTooltip(stats, pct) {
['Memory', b.memory || 0],
];
const maxVal = Math.max(1, ...rows.map((r) => r[1]));
const title = stats.estimate ? 'Context breakdown (estimate)' : 'Context breakdown';
const statusKey = pct >= 85 ? 'chat.context.critical' : (pct >= 60 ? 'chat.context.warning' : 'chat.context.healthy');
const title = stats.estimate ? escapeHtml(t('chat.context.estimate')) : escapeHtml(t(statusKey));
tip.innerHTML =
'<h5>' + title + '</h5>' +
'<div class="chat-ring-tt-status">' + escapeHtml(t(statusKey)) + '</div>' +
rows.map(([lbl, val]) =>
'<div class="chat-ring-tt-row">' +
'<span class="lbl">' + lbl + '</span>' +
@@ -1925,9 +2005,7 @@ function updateCompactionBanner(pct, estimate) {
banner.classList.add('open');
banner.classList.toggle('critical', pct >= 95);
banner.setAttribute('role', pct >= 95 ? 'alert' : 'status');
textEl.textContent = pct >= 95
? 'Context ' + Math.round(pct) + '% — older messages will be summarized on next send'
: 'Context ' + Math.round(pct) + '% — older messages may be summarized on next send';
textEl.textContent = t('chat.compaction.banner');
}
function dismissCompactionBanner() {
@@ -1945,7 +2023,7 @@ function flashCompactionCompacted(data) {
banner.classList.remove('critical');
banner.setAttribute('role', 'status');
const removed = data && data.removed_tokens ? ' (' + data.removed_tokens + ' tokens freed)' : '';
textEl.textContent = 'Context compacted' + removed;
textEl.textContent = t('chat.compaction.compacted') + removed;
sessionStorage.removeItem('aiui-compaction-dismissed');
setTimeout(() => {
if (lastContextPct < 85) banner.classList.remove('open');
@@ -1992,32 +2070,46 @@ function updateMemoryChip(count, loading) {
chip.classList.remove('loading');
const n = Number(count) || 0;
if (n <= 0) {
// E5: hide chip when no memories retrieved this turn
chip.classList.remove('visible');
chip.innerHTML = '';
// E5: no memories retrieved this turn — show muted empty-turn label
chip.classList.add('visible');
chip.title = t('chat.memory.empty_turn');
chip.innerHTML = '🧠 0';
return;
}
chip.classList.add('visible');
chip.innerHTML = '🧠 ' + n + ' ' + (n === 1 ? 'memory' : 'memories');
chip.title = '';
chip.innerHTML = '🧠 ' + escapeHtml(t('chat.memory.chip', { count: n }));
}
// Per-turn memory search: count hits for the last user message, not total store.
async function refreshMemoryChip() {
const chip = document.getElementById('chat-memory-chip');
if (!chip) return;
const query = (lastUserMessage || '').trim();
if (!query) {
updateMemoryChip(0, false);
return;
}
updateMemoryChip(0, true);
try {
const endpoint = currentSessionId
? '/api/memory/session/' + encodeURIComponent(currentSessionId)
: '/api/memory';
let resp = await fetch(endpoint);
if (!resp.ok) resp = await fetch('/api/memory');
if (!resp.ok) { chip.classList.remove('loading'); chip.innerHTML = '🧠 —'; chip.classList.add('visible'); return; }
const resp = await fetch('/api/memory/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
limit: 10,
session_id: currentSessionId || undefined,
}),
});
if (!resp.ok) { chip.classList.remove('loading'); chip.classList.add('visible'); chip.title = ''; chip.innerHTML = '🧠 —'; return; }
const data = await resp.json();
const items = data.memories || [];
updateMemoryChip(items.length, false);
const results = data.results || [];
updateMemoryChip(results.length, false);
} catch (e) {
// E6: memory search failed — muted dash
chip.classList.remove('loading');
chip.classList.add('visible');
chip.title = '';
chip.innerHTML = '🧠 —';
}
}
@@ -2039,6 +2131,124 @@ function showStreamMetrics(metrics) {
metricsEl.innerHTML = parts.join('') || '';
}
// ── Session Cost Chip (FinOps / STITCH-013) ─────────────────────
function finopsChipEnabled() {
if (!finopsConfig) return true; // default on until config loads
if (finopsConfig.enabled === false) return false;
return finopsConfig.show_session_chip !== false;
}
function fmtChipTokens(n) {
const v = Number(n) || 0;
if (v >= 1000000) return (v / 1000000).toFixed(1) + 'M';
if (v >= 1000) return (v / 1000).toFixed(1) + 'k';
return String(Math.round(v));
}
function shortModel(model) {
const s = model == null ? '' : String(model);
const idx = s.lastIndexOf('/');
return idx >= 0 ? s.slice(idx + 1) : s;
}
function updateSessionCostChip(stats) {
const chip = document.getElementById('chat-session-cost-chip');
if (!chip) return;
// E9/E2: hide when no session, feature disabled, or chip toggled off
if (!currentSessionId || !finopsChipEnabled()) {
chip.classList.remove('visible');
chip.innerHTML = '';
return;
}
const tokens = Number(stats && stats.used) || sessionCostState.tokens || 0;
const model = shortModel((stats && stats.model) || sessionCostState.model || currentAgentName || '');
const estimate = !!(stats && stats.estimate);
sessionCostState = { tokens, model, estimate };
if (tokens <= 0) {
chip.classList.remove('visible');
chip.innerHTML = '';
return;
}
chip.classList.add('visible');
chip.classList.toggle('estimate', estimate);
const prefix = estimate ? '~' : '';
const modelHtml = model
? ` · <span class="cost-chip-model">${escapeChip(model)}</span>`
: '';
chip.innerHTML = prefix + fmtChipTokens(tokens) + modelHtml;
chip.setAttribute('aria-label', 'Session token usage ' + Math.round(tokens) + ' tokens, model ' + (model || 'unknown'));
chip.title = 'Session spend\nTokens: ' + Math.round(tokens).toLocaleString() + '\nModel: ' + (model || 'unknown');
}
function escapeChip(s) {
const d = document.createElement('div');
d.textContent = s == null ? '' : String(s);
return d.innerHTML;
}
async function loadFinopsConfig() {
try {
const res = await fetch('/api/config/runtime');
if (!res.ok) return;
const data = await res.json();
finopsConfig = (data && data.config && data.config.finops) || finopsConfig;
} catch (e) {
// graceful: keep default-on behaviour
}
updateFinopsBanner();
}
function finopsBannerDismissed() {
return sessionStorage.getItem('finops-banner-dismissed') === '1';
}
async function updateFinopsBanner() {
const host = document.getElementById('chat-finops-banner');
if (!host) return;
if (!finopsConfig || finopsConfig.enabled === false) { host.innerHTML = ''; return; }
const budget = finopsConfig.daily_token_budget != null ? Number(finopsConfig.daily_token_budget) : null;
if (!budget || budget <= 0) { host.innerHTML = ''; return; }
if (finopsBannerDismissed()) { host.innerHTML = ''; return; }
let today = 0;
try {
const res = await fetch('/api/usage/timeseries?hours=24');
if (!res.ok) { host.innerHTML = ''; return; } // E1: hide, no toast
const data = await res.json();
const ts = Array.isArray(data.timeseries) ? data.timeseries : [];
const todayKey = new Date().toISOString().slice(0, 10);
const dated = ts.filter((p) => typeof p.hour_key === 'string');
const scope = dated.length ? dated.filter((p) => p.hour_key.startsWith(todayKey)) : ts;
today = scope.reduce((sum, p) => sum + (Number(p.tokens) || 0), 0);
} catch (e) {
host.innerHTML = '';
return;
}
const pct = Math.min((today / budget) * 100, 100);
const warnPct = Number(finopsConfig.warn_pct) || 80;
const criticalPct = Number(finopsConfig.critical_pct) || 95;
if (pct < warnPct) { host.innerHTML = ''; return; }
const critical = pct >= criticalPct;
const color = critical ? '#ef4444' : '#eab308';
const bg = critical ? 'rgba(239,68,68,.1)' : 'rgba(234,179,8,.1)';
const copy = critical
? 'Daily token budget at ' + Math.round(pct) + '% — review usage'
: 'Daily token budget at ' + Math.round(pct) + '% — consider lighter models';
host.innerHTML =
'<div class="chat-finops-banner-inner" role="' + (critical ? 'alert' : 'status') + '"' +
' style="display:flex;align-items:center;gap:8px;padding:8px 14px;margin-bottom:8px;border:1px solid ' + color +
';border-radius:10px;background:' + bg + ';color:' + color + ';font-size:12px;font-weight:500">' +
'<span aria-hidden="true">💰</span>' +
'<span style="flex:1">' + escapeChip(copy) + '</span>' +
'<button type="button" class="finops-banner-dismiss" title="Dismiss" aria-label="Dismiss"' +
' style="background:none;border:none;color:inherit;cursor:pointer;font-size:13px;padding:0 4px">✕</button>' +
'</div>';
const dismiss = host.querySelector('.finops-banner-dismiss');
if (dismiss) dismiss.addEventListener('click', () => {
sessionStorage.setItem('finops-banner-dismissed', '1');
host.innerHTML = '';
});
}
// ── Memory Panel ────────────────────────────────────────────────
function toggleMemoryPanel() {
const panel = document.getElementById('chat-memory-panel');
@@ -2200,6 +2410,9 @@ function sendMessage() {
const content = input.value.trim();
if (!content && pendingAttachments.length === 0) return;
// Track last user text so the memory chip searches this turn's query
if (content) lastUserMessage = content;
// Generate session ID if needed
if (!currentSessionId) {
currentSessionId = crypto.randomUUID ? crypto.randomUUID() :
@@ -0,0 +1,498 @@
/**
* Channel Inbox (/inbox) — unified multi-channel conversation triage (STITCH-010).
*
* Track B (session-filter fallback): derives threads from channel-backed
* sessions + chat history, groups messages by sender, and persists status +
* assignee via /api/sessions/{id}/state. Replies use the same chat transport
* as chat.js (/api/chat/send). Deep-links to /chat and /work.
*
* Three panes: Queue (list + filters) · Conversation (history + composer) ·
* Context rail (customer meta + assign agent + status + actions).
*/
import { showToast } from '../toast.js';
const STYLE_ID = 'channel-inbox-styles';
const PLATFORM_ICONS = {
discord: '🎮', slack: '💬', telegram: '✈️', whatsapp: '📱',
imessage: '🍎', signal: '🔒', googlechat: '💼', nostr: '🟣',
};
const STATUS_META = {
open: { label: 'Open', color: '#6366f1', priority: 2 },
waiting_on_agent: { label: 'Waiting on agent', color: '#f59e0b', priority: 1 },
waiting_on_customer: { label: 'Waiting on customer', color: '#a1a1aa', priority: 3 },
resolved: { label: 'Resolved', color: '#22c55e', priority: 4 },
};
const STATUS_ORDER = ['open', 'waiting_on_agent', 'waiting_on_customer', 'resolved'];
function esc(s) {
const d = document.createElement('div');
d.textContent = s == null ? '' : String(s);
return d.innerHTML;
}
function platformIcon(p) {
return PLATFORM_ICONS[String(p || '').toLowerCase()] || '📡';
}
function relativeTime(ts) {
if (!ts) return '';
const t = typeof ts === 'number' ? ts * (ts < 1e12 ? 1000 : 1) : Date.parse(ts);
if (!t || isNaN(t)) return '';
const diff = Math.floor((Date.now() - t) / 1000);
if (diff < 60) return 'now';
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
return `${Math.floor(diff / 86400)}d`;
}
function statusMeta(status) {
return STATUS_META[status] || STATUS_META.open;
}
// ── Pure thread-building helpers (unit-tested via source assertions) ──
export function senderKey(channelId, sender) {
const s = (sender == null || sender === '') ? 'general' : String(sender);
return `${channelId || 'unknown'}:${s}`;
}
export function groupMessagesBySender(sessionId, channelId, messages) {
const groups = new Map();
(messages || []).forEach((m) => {
const sender = m.sender || m.sender_id || m.user || '';
const key = senderKey(channelId, sender);
if (!groups.has(key)) {
groups.set(key, { key, sessionId, channelId, sender, messages: [] });
}
groups.get(key).messages.push(m);
});
return Array.from(groups.values());
}
export function buildThreadRow(group, platform, stateThreads) {
const msgs = group.messages || [];
const last = msgs[msgs.length - 1] || {};
const persisted = (stateThreads && stateThreads[group.key]) || {};
const preview = String(last.content || last.text || '').slice(0, 120);
const senderDisplay = last.sender_display || group.sender || 'General';
const updatedAt = last.timestamp || last.created_at || last.ts || 0;
return {
threadId: group.key,
sessionId: group.sessionId,
channelId: group.channelId,
platform: platform || last.platform || '',
sender: group.sender || '',
senderDisplay,
preview,
status: persisted.status || 'open',
assignedAgent: persisted.assigned_agent || '',
updatedAt,
messageCount: msgs.length,
messages: msgs,
};
}
export function sortThreads(rows) {
return rows.slice().sort((a, b) => {
const pa = statusMeta(a.status).priority;
const pb = statusMeta(b.status).priority;
if (pa !== pb) return pa - pb;
return (b.updatedAt || 0) - (a.updatedAt || 0);
});
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.inbox { display:flex; height:100%; min-height:0; gap:1px; background:var(--db-border,#3f3f46); }
.inbox-pane { background:var(--db-bg,#18181b); overflow:auto; min-height:0; display:flex; flex-direction:column; }
.inbox-queue { width:300px; min-width:240px; flex:0 0 auto; }
.inbox-conv { flex:1 1 auto; min-width:0; }
.inbox-rail { width:300px; min-width:260px; flex:0 0 auto; }
.inbox-head { padding:12px 14px; border-bottom:1px solid var(--db-border,#3f3f46);
font-size:14px; font-weight:600; position:sticky; top:0; background:var(--db-bg,#18181b); z-index:2; }
.inbox-filters { padding:10px 12px; border-bottom:1px solid var(--db-border,#3f3f46);
display:flex; flex-direction:column; gap:8px; }
.inbox-search { width:100%; box-sizing:border-box; padding:6px 10px; border-radius:8px;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); color:var(--db-text,#e4e4e7); font-size:13px; }
.inbox-chips { display:flex; flex-wrap:wrap; gap:6px; }
.inbox-chip { padding:4px 10px; font-size:12px; border-radius:999px; cursor:pointer;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); color:var(--db-text-dim,#a1a1aa); }
.inbox-chip.active { color:#fff; background:var(--db-accent,#6366f1); border-color:var(--db-accent,#6366f1); }
.inbox-list { flex:1 1 auto; overflow:auto; }
.inbox-row { display:flex; gap:10px; padding:10px 12px; cursor:pointer; border-bottom:1px solid var(--db-border,#3f3f46); }
.inbox-row:hover { background:var(--db-card-bg,#27272a); }
.inbox-row.active { background:var(--db-card-bg,#27272a); border-left:3px solid var(--db-accent,#6366f1); }
.inbox-row-icon { font-size:18px; flex:0 0 auto; }
.inbox-row-body { flex:1 1 auto; min-width:0; }
.inbox-row-top { display:flex; justify-content:space-between; gap:6px; align-items:center; }
.inbox-row-sender { font-weight:600; font-size:13px; color:var(--db-text,#e4e4e7); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.inbox-row-time { font-size:11px; color:var(--db-text-dim,#a1a1aa); flex:0 0 auto; }
.inbox-row-preview { font-size:12px; color:var(--db-text-dim,#a1a1aa); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.inbox-dot { width:8px; height:8px; border-radius:50%; flex:0 0 auto; margin-top:5px; }
.inbox-empty { padding:32px 20px; text-align:center; color:var(--db-text-dim,#a1a1aa); font-size:13px; }
.inbox-empty a { color:var(--db-accent,#6366f1); }
.inbox-msgs { flex:1 1 auto; overflow:auto; padding:14px; display:flex; flex-direction:column; gap:8px; }
.inbox-msg { max-width:80%; padding:8px 12px; border-radius:10px; font-size:13px; line-height:1.5; }
.inbox-msg-in { align-self:flex-start; background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); }
.inbox-msg-out { align-self:flex-end; background:var(--db-accent,#6366f1); color:#fff; }
.inbox-composer { display:flex; gap:8px; padding:12px; border-top:1px solid var(--db-border,#3f3f46); }
.inbox-composer textarea { flex:1; resize:none; min-height:40px; max-height:96px; padding:8px 10px;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); border-radius:8px; color:var(--db-text,#e4e4e7); font-size:13px; box-sizing:border-box; }
.inbox-btn { padding:8px 16px; background:var(--db-accent,#6366f1); color:#fff; border:none; border-radius:8px; cursor:pointer; font-weight:600; font-size:13px; }
.inbox-btn:disabled { opacity:.5; cursor:not-allowed; }
.inbox-rail-sec { padding:14px; border-bottom:1px solid var(--db-border,#3f3f46); }
.inbox-rail-label { font-size:11px; text-transform:uppercase; letter-spacing:.5px; color:var(--db-text-dim,#a1a1aa); margin-bottom:6px; }
.inbox-select { width:100%; box-sizing:border-box; padding:6px 10px; border-radius:8px;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); color:var(--db-text,#e4e4e7); font-size:13px; }
.inbox-pill-group { display:flex; flex-wrap:wrap; gap:6px; }
.inbox-pill { padding:4px 10px; font-size:12px; border-radius:999px; cursor:pointer;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); color:var(--db-text-dim,#a1a1aa); }
.inbox-pill.active { color:#fff; }
.inbox-action { display:block; width:100%; box-sizing:border-box; text-align:center; margin-top:8px; padding:8px;
border-radius:8px; font-size:13px; cursor:pointer; text-decoration:none;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); color:var(--db-text,#e4e4e7); }
.inbox-banner { padding:8px 12px; background:rgba(245,158,11,.15); border:1px solid rgba(245,158,11,.35);
color:#f59e0b; font-size:12px; border-radius:8px; margin:12px; }
.inbox-strip { font-size:12px; color:var(--db-text-dim,#a1a1aa); font-weight:400; }
@media (max-width:768px) {
.inbox { flex-direction:column; }
.inbox-queue, .inbox-conv, .inbox-rail { width:auto; }
}
`;
document.head.appendChild(style);
}
async function fetchJSON(url, opts) {
try {
const r = await fetch(url, opts);
if (!r.ok) return null;
return await r.json();
} catch (e) {
return null;
}
}
export async function render(container) {
container.setAttribute('data-page', 'inbox');
injectStyles();
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
const [sessRes, chRes, agentsRes, defsRes] = await Promise.allSettled([
fetchJSON('/api/sessions'),
fetchJSON('/api/channels'),
fetchJSON('/agents'),
fetchJSON('/api/agents/definitions'),
]);
const val = (r) => (r.status === 'fulfilled' ? r.value : null) || {};
const sessions = (val(sessRes).sessions || val(sessRes) || []);
let channels = val(chRes).channels || val(chRes) || [];
if (!Array.isArray(channels)) {
channels = Object.entries(channels).map(([id, c]) => ({ id, ...(typeof c === 'object' ? c : { platform: c }) }));
}
const agentList = new Set();
const av = val(agentsRes);
(av.agents || av || []).forEach((a) => agentList.add(typeof a === 'string' ? a : (a.name || a.id)));
const dv = val(defsRes);
(dv.definitions || dv.agents || dv || []).forEach((a) => agentList.add(typeof a === 'string' ? a : (a.name || a.id)));
const agents = Array.from(agentList).filter(Boolean);
const channelById = {};
channels.forEach((c) => { channelById[c.id || c.channel_id] = c; });
const channelSessions = sessions.filter((s) => {
const sid = s.id || s.session_id || '';
return String(sid).startsWith('channel-') || !!s.platform;
});
const runningChannels = channels.filter((c) => c.running === true).length;
const totalChannels = channels.length;
// Load state + history for each channel session, build threads.
let threads = [];
await Promise.allSettled(channelSessions.map(async (s) => {
const sid = s.id || s.session_id;
const channelId = s.channel_id || (String(sid).startsWith('channel-') ? String(sid).slice('channel-'.length) : sid);
const [hist, state] = await Promise.all([
fetchJSON('/api/chat/history/' + encodeURIComponent(sid)),
fetchJSON('/api/sessions/' + encodeURIComponent(sid) + '/state'),
]);
const messages = (hist && (hist.messages || hist.history || hist)) || [];
const stateThreads = (state && state.state && state.state.inbox_threads) || {};
const groups = groupMessagesBySender(sid, channelId, Array.isArray(messages) ? messages : []);
if (groups.length === 0) {
threads.push(buildThreadRow(
{ key: senderKey(channelId, ''), sessionId: sid, channelId, sender: '', messages: [] },
s.platform, stateThreads));
} else {
groups.forEach((g) => threads.push(buildThreadRow(g, s.platform, stateThreads)));
}
}));
const state = {
platform: 'all',
status: 'open',
search: '',
selected: null,
threads,
};
const params = new URLSearchParams(location.search);
if (params.get('status')) state.status = params.get('status');
container.innerHTML = `
<div class="inbox">
<div class="inbox-pane inbox-queue">
<div class="inbox-head">Channel Inbox
<div class="inbox-strip">${runningChannels}/${totalChannels} channels online · ${threads.length} threads</div>
</div>
<div class="inbox-filters">
<input class="inbox-search" id="inbox-search" placeholder="Search conversations" />
<div class="inbox-chips" id="inbox-platform-chips"></div>
<div class="inbox-chips" id="inbox-status-chips"></div>
</div>
<div class="inbox-list" id="inbox-list"></div>
</div>
<div class="inbox-pane inbox-conv" id="inbox-conv"></div>
<div class="inbox-pane inbox-rail" id="inbox-rail"></div>
</div>`;
const listEl = container.querySelector('#inbox-list');
const convEl = container.querySelector('#inbox-conv');
const railEl = container.querySelector('#inbox-rail');
const platEl = container.querySelector('#inbox-platform-chips');
const statEl = container.querySelector('#inbox-status-chips');
const searchEl = container.querySelector('#inbox-search');
const platformsPresent = Array.from(new Set(threads.map((t) => t.platform).filter(Boolean)));
const platformChips = ['all', ...platformsPresent];
platEl.innerHTML = platformChips.map((p) =>
`<span class="inbox-chip ${p === state.platform ? 'active' : ''}" data-platform="${esc(p)}">${p === 'all' ? 'All' : platformIcon(p) + ' ' + esc(p)}</span>`
).join('');
const statusTabs = ['open', 'waiting_on_agent', 'all', 'resolved'];
statEl.innerHTML = statusTabs.map((s) =>
`<span class="inbox-chip ${s === state.status ? 'active' : ''}" data-status="${esc(s)}">${s === 'all' ? 'All' : statusMeta(s).label}</span>`
).join('');
function filtered() {
let rows = state.threads.filter((t) => {
if (state.platform !== 'all' && t.platform !== state.platform) return false;
if (state.status === 'all') { /* show all */ }
else if (state.status === 'open') { if (t.status === 'resolved') return false; }
else if (t.status !== state.status) return false;
if (state.search) {
const q = state.search.toLowerCase();
if (!(t.senderDisplay + ' ' + t.preview).toLowerCase().includes(q)) return false;
}
return true;
});
return sortThreads(rows);
}
function renderList() {
const rows = filtered();
if (rows.length === 0) {
if (channels.length === 0) {
listEl.innerHTML = `<div class="inbox-empty">No channels configured<br><a href="/channels">Configure channels →</a></div>`;
} else {
listEl.innerHTML = `<div class="inbox-empty">No conversations in this view</div>`;
}
return;
}
listEl.innerHTML = rows.map((t) => {
const active = state.selected === t.threadId ? 'active' : '';
const sm = statusMeta(t.status);
return `<div class="inbox-row ${active}" data-thread="${esc(t.threadId)}">
<div class="inbox-row-icon">${platformIcon(t.platform)}</div>
<div class="inbox-row-body">
<div class="inbox-row-top">
<span class="inbox-row-sender">${esc(t.senderDisplay)}</span>
<span class="inbox-row-time">${relativeTime(t.updatedAt)}</span>
</div>
<div class="inbox-row-preview">${esc(t.preview) || '<em>No messages yet</em>'}</div>
</div>
<div class="inbox-dot" style="background:${sm.color}" title="${sm.label}"></div>
</div>`;
}).join('');
listEl.querySelectorAll('.inbox-row').forEach((row) => {
row.addEventListener('click', () => selectThread(row.getAttribute('data-thread')));
});
}
function findThread(id) {
return state.threads.find((t) => t.threadId === id) || null;
}
async function persistThread(t) {
const body = { state: { inbox_threads: { [t.threadId]: { status: t.status, assigned_agent: t.assignedAgent } } } };
const r = await fetchJSON('/api/sessions/' + encodeURIComponent(t.sessionId) + '/state', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
});
return r != null;
}
function renderConversation(t) {
if (!t) {
convEl.innerHTML = `<div class="inbox-empty">Select a conversation to view messages</div>`;
return;
}
const ch = channelById[t.channelId] || {};
const offline = ch.running === false;
const sm = statusMeta(t.status);
convEl.innerHTML = `
<div class="inbox-head">${platformIcon(t.platform)} ${esc(t.senderDisplay)}
<span class="inbox-strip">${esc(t.platform || 'channel')} · <span style="color:${sm.color}">${sm.label}</span></span>
</div>
${offline ? `<div class="inbox-banner">This channel is offline. Replies are disabled until it reconnects.</div>` : ''}
<div class="inbox-msgs" id="inbox-msgs"></div>
<div class="inbox-composer">
<textarea id="inbox-reply" placeholder="Reply${t.assignedAgent ? ' as ' + esc(t.assignedAgent) : ''}…" ${offline ? 'disabled' : ''}></textarea>
<button class="inbox-btn" id="inbox-send" ${offline ? 'disabled' : ''}>Send</button>
</div>`;
const msgsEl = convEl.querySelector('#inbox-msgs');
if (!t.messages || t.messages.length === 0) {
msgsEl.innerHTML = `<div class="inbox-empty">No messages yet</div>`;
} else {
msgsEl.innerHTML = t.messages.map((m) => {
const role = String(m.role || '').toLowerCase();
const out = role === 'assistant' || role === 'agent' || m.direction === 'outbound';
return `<div class="inbox-msg ${out ? 'inbox-msg-out' : 'inbox-msg-in'}">${esc(m.content || m.text || '')}</div>`;
}).join('');
msgsEl.scrollTop = msgsEl.scrollHeight;
}
const sendBtn = convEl.querySelector('#inbox-send');
const replyEl = convEl.querySelector('#inbox-reply');
if (sendBtn) sendBtn.addEventListener('click', () => sendReply(t, replyEl, msgsEl, sendBtn));
}
async function sendReply(t, replyEl, msgsEl, sendBtn) {
const content = (replyEl.value || '').trim();
if (!content) return;
sendBtn.disabled = true;
const data = await fetchJSON('/api/chat/send', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, session_id: t.sessionId, agent_name: t.assignedAgent || undefined }),
});
sendBtn.disabled = false;
if (data == null || data.error) {
showToast(data && data.error ? data.error : 'Failed to send reply', 'error');
return;
}
const msg = { role: 'agent', content, direction: 'outbound' };
t.messages.push(msg);
t.preview = content.slice(0, 120);
t.updatedAt = Date.now() / 1000;
t.status = 'waiting_on_customer';
replyEl.value = '';
persistThread(t);
renderConversation(t);
renderRail(t);
renderList();
}
function renderRail(t) {
if (!t) {
railEl.innerHTML = `<div class="inbox-empty">Details</div>`;
return;
}
const ch = channelById[t.channelId] || {};
const chStatus = ch.running === true ? 'Connected' : ch.start_error ? 'Error' : 'Stopped';
const opts = ['', ...agents].map((a) =>
`<option value="${esc(a)}" ${a === t.assignedAgent ? 'selected' : ''}>${a ? esc(a) : 'Unassigned'}</option>`
).join('');
const pills = STATUS_ORDER.map((s) => {
const sm = STATUS_META[s];
const active = s === t.status ? 'active' : '';
const style = s === t.status ? `background:${sm.color};border-color:${sm.color}` : '';
return `<span class="inbox-pill ${active}" data-status="${s}" style="${style}">${sm.label}</span>`;
}).join('');
railEl.innerHTML = `
<div class="inbox-head">Details</div>
<div class="inbox-rail-sec">
<div class="inbox-rail-label">Customer</div>
<div style="font-size:13px">${esc(t.senderDisplay)}</div>
<div style="font-size:12px;color:var(--db-text-dim,#a1a1aa)">${esc(t.platform || 'channel')} · ${esc(t.sender || 'general')}</div>
</div>
<div class="inbox-rail-sec">
<div class="inbox-rail-label">Assigned agent</div>
<select class="inbox-select" id="inbox-assign">${opts}</select>
</div>
<div class="inbox-rail-sec">
<div class="inbox-rail-label">Status</div>
<div class="inbox-pill-group" id="inbox-status-pills">${pills}</div>
</div>
<div class="inbox-rail-sec">
<div class="inbox-rail-label">Actions</div>
<a class="inbox-action" href="/chat?session=${encodeURIComponent(t.sessionId)}">Open in Chat</a>
<a class="inbox-action" href="/work?card=${encodeURIComponent(t.sessionId)}">Escalate to Work Hub</a>
<button class="inbox-action" id="inbox-resolve">Mark resolved</button>
</div>
<div class="inbox-rail-sec">
<div class="inbox-rail-label">Channel</div>
<div style="font-size:12px">${platformIcon(t.platform)} ${esc(t.channelId)} · ${esc(chStatus)}</div>
</div>`;
const assignEl = railEl.querySelector('#inbox-assign');
assignEl.addEventListener('change', async () => {
t.assignedAgent = assignEl.value;
const ok = await persistThread(t);
if (ok) showToast(t.assignedAgent ? `Assigned to ${t.assignedAgent}` : 'Unassigned', 'success');
renderList();
renderConversation(t);
});
railEl.querySelectorAll('#inbox-status-pills .inbox-pill').forEach((pill) => {
pill.addEventListener('click', async () => {
t.status = pill.getAttribute('data-status');
await persistThread(t);
renderRail(t);
renderConversation(t);
renderList();
});
});
const resolveBtn = railEl.querySelector('#inbox-resolve');
resolveBtn.addEventListener('click', async () => {
t.status = 'resolved';
await persistThread(t);
renderRail(t);
renderList();
showToast('Thread resolved', 'success');
});
}
function selectThread(id) {
const t = findThread(id);
state.selected = id;
renderList();
renderConversation(t);
renderRail(t);
}
platEl.querySelectorAll('.inbox-chip').forEach((chip) => {
chip.addEventListener('click', () => {
state.platform = chip.getAttribute('data-platform');
platEl.querySelectorAll('.inbox-chip').forEach((c) => c.classList.toggle('active', c === chip));
renderList();
});
});
statEl.querySelectorAll('.inbox-chip').forEach((chip) => {
chip.addEventListener('click', () => {
state.status = chip.getAttribute('data-status');
statEl.querySelectorAll('.inbox-chip').forEach((c) => c.classList.toggle('active', c === chip));
renderList();
});
});
searchEl.addEventListener('input', () => { state.search = searchEl.value; renderList(); });
renderList();
renderConversation(null);
renderRail(null);
const first = filtered()[0];
if (first) selectThread(first.threadId);
}
@@ -0,0 +1,156 @@
/**
* Mini-chat transport — shared agent WebSocket bridge for embedded chat panes.
*
* Reuses the same wire protocol as the full Chat view (chat.js): it connects
* to /api/chat/ws, sends { type:'chat', content, session_id, agent_name },
* and streams run_content / run_completed / run_error events back to the caller.
*
* The full Chat view keeps its own rich rendering; this module only owns the
* transport so embedded surfaces (Work Hub Chat tab, etc.) can send to and
* stream from the same agent without rebuilding chat from scratch.
*
* Usage:
* import { createMiniChat } from '/plugins/views/mini-chat.js';
* const chat = createMiniChat({ agentId, sessionId });
* chat.send({ text, onDelta, onComplete, onError });
* chat.disconnect(); // on card change / teardown
*/
function wsUrl() {
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
return protocol + '//' + location.host + '/api/chat/ws';
}
function genSessionId() {
if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
}
/**
* Create a mini-chat transport bound to one agent + session.
*
* @param {Object} opts
* @param {string} [opts.agentId] - Agent name to route the message to.
* @param {string} [opts.sessionId] - Session id (generated if omitted).
* @returns {{ send: Function, disconnect: Function, sessionId: string }}
*/
export function createMiniChat({ agentId = '', sessionId = '' } = {}) {
let ws = null;
let closed = false;
const session = sessionId || genSessionId();
let active = null; // { onDelta, onComplete, onError, done }
function finish(handler, arg) {
if (!active || active.done) return;
active.done = true;
const cb = active[handler];
active = null;
if (typeof cb === 'function') {
try { cb(arg); } catch (_) { /* caller handler error */ }
}
}
function handle(data) {
const type = data && data.type;
switch (type) {
case 'run_content':
case 'team_run_content':
if (data.token && active && typeof active.onDelta === 'function') {
try { active.onDelta(data.token); } catch (_) { /* handler error */ }
}
break;
case 'run_completed':
case 'team_run_completed':
finish('onComplete', data.content || '');
break;
case 'run_error':
case 'team_run_error':
finish('onError', new Error(data.error || 'Run error'));
break;
default:
break;
}
}
function ensureSocket() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return ws;
ws = new WebSocket(wsUrl());
ws.onmessage = (event) => {
let data;
try { data = JSON.parse(event.data); } catch (_) { return; }
if (data.session_id && data.session_id !== session) return;
handle(data);
};
ws.onerror = () => {
finish('onError', new Error('Connection error'));
};
ws.onclose = () => {
if (!closed) finish('onError', new Error('Connection closed'));
};
return ws;
}
function transmit(text) {
const sock = ensureSocket();
const payload = JSON.stringify({
type: 'chat',
content: text,
session_id: session,
agent_name: agentId || null,
});
if (sock.readyState === WebSocket.OPEN) {
sock.send(payload);
} else if (sock.readyState === WebSocket.CONNECTING) {
sock.addEventListener('open', () => {
if (!closed) {
try { sock.send(payload); } catch (_) { finish('onError', new Error('Connection error')); }
}
}, { once: true });
} else {
finish('onError', new Error('Connection unavailable'));
}
}
/**
* Send a user message and stream the agent reply.
*
* @param {Object} req
* @param {string} req.text - Message text.
* @param {Function} [req.onDelta] - Called with each streamed token.
* @param {Function} [req.onComplete] - Called with the final content string.
* @param {Function} [req.onError] - Called with an Error on failure.
*/
function send({ text, onDelta, onComplete, onError }) {
if (closed) {
if (typeof onError === 'function') onError(new Error('Disconnected'));
return;
}
if (active && !active.done) {
finish('onError', new Error('Superseded'));
}
active = { onDelta, onComplete, onError, done: false };
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
finish('onError', new Error('You are offline.'));
return;
}
try {
transmit(String(text == null ? '' : text));
} catch (_) {
finish('onError', new Error('Connection error'));
}
}
function disconnect() {
closed = true;
active = null;
if (ws) {
try { ws.onclose = null; ws.onerror = null; ws.onmessage = null; ws.close(); } catch (_) { /* noop */ }
ws = null;
}
}
return { send, disconnect, sessionId: session };
}
@@ -11,7 +11,11 @@
*
* Renders into [data-page="overview"].
*/
import { metricCard, esc, timeAgo } from '/plugins/views/_helpers.js';
import {
metricCard, esc, timeAgo,
sumTodayTokens, computeBudgetPct, budgetLevelFor,
costStripHTML, budgetBannerHTML,
} from '/plugins/views/_helpers.js';
const METRICS_POLL_MS = 30000;
const APPROVALS_POLL_MS = 10000;
@@ -76,6 +80,8 @@ async function loadAll() {
fetchJson('/api/gateway/status'),
fetchJson('/api/features'),
fetchJson('/api/traces?limit=10'),
fetchJson('/api/usage/models'),
fetchJson('/api/config/runtime'),
]);
const agents = toArray(settled(results, 0, {}), 'agents');
@@ -87,13 +93,17 @@ async function loadAll() {
const gateway = settled(results, 6, null);
const features = toArray(settled(results, 7, {}), 'features');
const traces = toArray(settled(results, 8, {}), 'traces');
const models = toArray(settled(results, 9, {}), 'models');
const runtime = settled(results, 10, null);
return {
agents, sessions, pending, usage, timeseries, channels, gateway, features, traces,
agents, sessions, pending, usage, timeseries, channels, gateway, features, traces, models,
finops: (runtime && runtime.config && runtime.config.finops) || null,
errors: {
agents: results[0].status === 'rejected',
sessions: results[1].status === 'rejected',
usage: results[3].status === 'rejected',
timeseries: results[4].status === 'rejected',
},
};
}
@@ -152,6 +162,35 @@ function renderMetrics(data) {
return `<div class="db-metric-grid" style="display:grid;grid-template-columns:repeat(6,1fr);gap:12px">${cards.join('')}</div>`;
}
function finopsState(data) {
const finops = data.finops || {};
// E1: usage timeseries unreachable → hide all FinOps UI (no error toast)
if (data.errors.usage || data.errors.timeseries) return null;
// E2: master toggle off → hide
if (finops.enabled === false) return null;
const ts = data.timeseries && Array.isArray(data.timeseries.timeseries) ? data.timeseries.timeseries : [];
const today = sumTodayTokens(ts);
const todayCost = ts.reduce((sum, p) => sum + (Number(p.cost) || 0), 0);
const budget = finops.daily_token_budget != null ? Number(finops.daily_token_budget) : null;
const warnPct = Number(finops.warn_pct) || 80;
const criticalPct = Number(finops.critical_pct) || 95;
const pct = computeBudgetPct(today, budget);
const level = budgetLevelFor(pct, warnPct, criticalPct);
return { today, todayCost, budget, warnPct, criticalPct, pct, level };
}
function renderFinOps(data) {
const st = finopsState(data);
if (!st) return '';
const bannerDismissed = sessionStorage.getItem('finops-banner-dismissed') === '1';
const banner = (st.level !== 'none' && !bannerDismissed) ? budgetBannerHTML(st.level, st.pct) : '';
const strip = costStripHTML({
todayTokens: st.today, todayCost: st.todayCost, budget: st.budget,
warnPct: st.warnPct, criticalPct: st.criticalPct, models: data.models,
});
return `<div id="ov-finops-banner">${banner}</div>${strip}`;
}
function renderAgentTable(agents) {
if (agents.length === 0) {
return `<div class="db-card" style="text-align:center;padding:2.5rem 1.5rem;color:var(--db-text-dim)">
@@ -265,6 +304,8 @@ function shell(data) {
<div id="ov-metrics">${renderMetrics(data)}</div>
<div id="ov-finops">${renderFinOps(data)}</div>
<div class="db-ov-columns" style="display:grid;grid-template-columns:3fr 2fr;gap:16px;margin-top:20px">
<div>
<h3 style="margin:0 0 12px;font-size:15px;font-weight:600">Active Agents</h3>
@@ -351,6 +392,17 @@ function bindEvents(container, data) {
container.querySelector('[data-omnibar]')?.addEventListener('click', openPalette);
container.querySelector('.db-finops-banner-usage')?.addEventListener('click', (e) => {
e.stopPropagation();
navigate('usage');
});
container.querySelector('.db-finops-banner-dismiss')?.addEventListener('click', (e) => {
e.stopPropagation();
sessionStorage.setItem('finops-banner-dismissed', '1');
const host = container.querySelector('#ov-finops-banner');
if (host) host.innerHTML = '';
});
container.querySelectorAll('.db-agent-row').forEach((row) => {
row.addEventListener('click', (e) => {
if (e.target.closest('.db-agent-chat')) return;
@@ -397,9 +449,14 @@ async function refreshApprovals() {
data.pending = pending;
const metricsEl = _container.querySelector('#ov-metrics');
const attnEl = _container.querySelector('#ov-attention');
if (metricsEl) metricsEl.innerHTML = renderMetrics(data);
if (attnEl) attnEl.innerHTML = renderAttention(data);
bindEvents(_container, data);
if (metricsEl) {
metricsEl.innerHTML = renderMetrics(data);
bindEvents(metricsEl, data);
}
if (attnEl) {
attnEl.innerHTML = renderAttention(data);
bindEvents(attnEl, data);
}
}
function startPolling() {
@@ -1,72 +1,354 @@
/**
* Skills View — tool/skill catalog with enable/disable toggles
* API: /api/skills
* Skills Studio (STITCH-008) — unified Pending → Diff → Approve → Test →
* Installed lifecycle for self_improve skill writes.
*
* Tabs: Pending (skill-filtered approvals) · Installed (toggle catalog) · History.
* URL state: /skills?tab=pending|installed|history&approval_id={id}
*
* Reuses existing /api/approvals and /api/skills endpoints; no new backend.
*/
export async function render(container) {
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
import { esc, timeAgo, diffLines, renderMarkdownPreview, isSkillApproval } from '/plugins/views/_helpers.js';
let tools = [], categories = [];
try { const r = await fetch('/api/skills'); const d = await r.json(); tools = d.tools || d.skills || d || []; if (!Array.isArray(tools)) tools = Object.entries(tools).map(([id,t]) => ({id,...(typeof t==='object'?t:{name:t})})); } catch(e) {}
try { const r = await fetch('/api/skills/categories'); const d = await r.json(); categories = d.categories || d || []; } catch(e) {}
const RISK_ICONS = { low: '✅', medium: '⚠️', high: '🟠', critical: '🔴' };
// Group by category
const grouped = {};
tools.forEach(t => { const cat = t.category || 'other'; if (!grouped[cat]) grouped[cat] = []; grouped[cat].push(t); });
let _container = null;
const enabledCount = tools.filter(t => t.enabled !== false).length;
function currentTab() {
const p = new URLSearchParams(location.search);
return p.get('tab') || '';
}
container.innerHTML = `
<div class="db-columns" style="grid-template-columns:repeat(3,1fr);margin-bottom:24px">
<div class="db-card"><div class="db-card-title">Total Skills</div><div class="db-card-value">${tools.length}</div></div>
<div class="db-card"><div class="db-card-title">Enabled</div><div class="db-card-value" style="color:#22c55e">${enabledCount}</div></div>
<div class="db-card"><div class="db-card-title">Categories</div><div class="db-card-value">${Object.keys(grouped).length}</div></div>
function currentApprovalId() {
return new URLSearchParams(location.search).get('approval_id') || '';
}
function setUrlState(tab, approvalId) {
const p = new URLSearchParams(location.search);
if (tab) p.set('tab', tab); else p.delete('tab');
if (approvalId) p.set('approval_id', approvalId); else p.delete('approval_id');
history.replaceState(history.state, '', `${location.pathname}?${p.toString()}`);
}
async function fetchJson(url, opts) {
const r = await fetch(url, opts);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
function approvalId(a) {
return a.id || a.approval_id || '';
}
function skillName(a) {
const args = a.arguments || {};
if (args.skill_name) return args.skill_name;
if (args.name) return args.name;
const path = String(args.path || '');
const parts = path.split('/').filter(Boolean);
if (path.endsWith('SKILL.md') && parts.length >= 2) return parts[parts.length - 2];
return a.tool_name || a.action || 'skill';
}
function proposedContent(a) {
const args = a.arguments || {};
return a.proposed_content || args.content || args.skill_md || '';
}
function changeType(a) {
if (a.current_content) return 'Update';
const args = a.arguments || {};
if (String(args.op || a.type || '').toLowerCase().includes('delete')) return 'Delete';
return 'New';
}
// ── Diff overlay ────────────────────────────────────────────────────
function renderDiffRows(before, after) {
const rows = diffLines(before, after);
const colorFor = { add: 'rgba(34,197,94,.12)', del: 'rgba(239,68,68,.12)', ctx: 'transparent' };
const signFor = { add: '+', del: '-', ctx: ' ' };
let adds = 0;
let dels = 0;
const body = rows.map((r) => {
if (r.type === 'add') adds++;
if (r.type === 'del') dels++;
return `<div style="display:flex;font-family:monospace;font-size:12px;background:${colorFor[r.type]}">
<span style="width:16px;text-align:center;color:var(--db-text-dim);flex-shrink:0">${signFor[r.type]}</span>
<span style="white-space:pre-wrap;word-break:break-word">${esc(r.text)}</span>
</div>`;
}).join('');
return { html: body, adds, dels };
}
function diffOverlayHtml(a, skills) {
const before = a.current_content || '';
const after = proposedContent(a);
const hasProposed = after !== '';
const { html: rawRows, adds, dels } = renderDiffRows(before, after);
const name = skillName(a);
const dupe = changeType(a) === 'New' && skills.some((s) => (s.name || s.id) === name);
const leftEmpty = before === '';
return `
<div class="ss-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:200;display:flex;justify-content:flex-end">
<div class="ss-panel" style="width:60%;max-width:920px;height:100%;background:var(--db-sidebar-bg);border-left:1px solid var(--db-border);display:flex;flex-direction:column;box-sizing:border-box">
<div style="padding:16px 20px;border-bottom:1px solid var(--db-border);display:flex;align-items:center;justify-content:space-between;gap:12px">
<div>
<div style="font-size:16px;font-weight:600">${esc(name)}
<span style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--db-card-bg);border:1px solid var(--db-border);margin-left:6px">${changeType(a)}</span>
${dupe ? '<span style="font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(234,179,8,.15);color:#eab308;margin-left:4px">Name collision</span>' : ''}
</div>
<div style="font-size:12px;color:var(--db-text-dim);margin-top:4px">${RISK_ICONS[a.risk_level] || ''} ${esc(a.agent_name || '')} · +${adds} −${dels}</div>
</div>
<div style="display:flex;gap:8px;align-items:center">
<button class="ss-mode" data-mode="raw" style="padding:5px 12px;font-size:12px;border:1px solid var(--db-border);background:var(--db-accent);color:#fff;border-radius:6px;cursor:pointer">Raw</button>
<button class="ss-mode" data-mode="preview" style="padding:5px 12px;font-size:12px;border:1px solid var(--db-border);background:transparent;color:var(--db-text);border-radius:6px;cursor:pointer">Preview</button>
<button class="ss-close" style="background:none;border:none;color:var(--db-text-dim);font-size:22px;cursor:pointer">×</button>
</div>
</div>
${hasProposed ? '' : '<div style="padding:12px 20px;background:rgba(239,68,68,.1);color:#ef4444;font-size:13px">Content unavailable — approval disabled</div>'}
<div class="ss-diff-body" style="flex:1;overflow:auto;padding:16px 20px">
<div class="ss-columns" style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
<div>
<div style="font-size:12px;color:var(--db-text-dim);text-transform:uppercase;margin-bottom:8px">Current</div>
<div class="ss-left">${leftEmpty ? '<div style="color:var(--db-text-dim);font-style:italic">No existing skill</div>' : `<pre style="white-space:pre-wrap;font-size:12px;margin:0">${esc(before)}</pre>`}</div>
</div>
<div>
<div style="font-size:12px;color:var(--db-text-dim);text-transform:uppercase;margin-bottom:8px">Proposed</div>
<div class="ss-right">${rawRows}</div>
</div>
</div>
</div>
<div style="padding:14px 20px;border-top:1px solid var(--db-border);display:flex;gap:10px;justify-content:flex-end">
<button class="ss-deny" data-id="${esc(approvalId(a))}" style="padding:8px 18px;background:rgba(239,68,68,.15);color:#ef4444;border:1px solid rgba(239,68,68,.3);border-radius:6px;cursor:pointer;font-size:13px">Reject</button>
<button class="ss-approve" data-id="${esc(approvalId(a))}" ${hasProposed ? '' : 'disabled'} style="padding:8px 18px;background:${hasProposed ? 'rgba(34,197,94,.15)' : 'var(--db-card-bg)'};color:${hasProposed ? '#22c55e' : 'var(--db-text-dim)'};border:1px solid rgba(34,197,94,.3);border-radius:6px;cursor:${hasProposed ? 'pointer' : 'not-allowed'};font-size:13px">Approve</button>
</div>
</div>
</div>`;
}
function openDiff(a, skills) {
const host = _container.querySelector('#ss-overlay-host');
if (!host) return;
host.innerHTML = diffOverlayHtml(a, skills);
setUrlState('pending', approvalId(a));
const before = a.current_content || '';
const after = proposedContent(a);
const close = () => { host.innerHTML = ''; setUrlState('pending', ''); };
host.querySelector('.ss-overlay').addEventListener('click', (e) => { if (e.target === e.currentTarget) close(); });
host.querySelector('.ss-close').addEventListener('click', close);
host.querySelectorAll('.ss-mode').forEach((btn) => {
btn.addEventListener('click', () => {
const mode = btn.dataset.mode;
host.querySelectorAll('.ss-mode').forEach((b) => {
const on = b === btn;
b.style.background = on ? 'var(--db-accent)' : 'transparent';
b.style.color = on ? '#fff' : 'var(--db-text)';
});
const left = host.querySelector('.ss-left');
const right = host.querySelector('.ss-right');
if (mode === 'preview') {
left.innerHTML = before === '' ? '<div style="color:var(--db-text-dim);font-style:italic">No existing skill</div>' : renderMarkdownPreview(before);
right.innerHTML = renderMarkdownPreview(after);
} else {
left.innerHTML = before === '' ? '<div style="color:var(--db-text-dim);font-style:italic">No existing skill</div>' : `<pre style="white-space:pre-wrap;font-size:12px;margin:0">${esc(before)}</pre>`;
right.innerHTML = renderDiffRows(before, after).html;
}
});
});
const resolve = async (verb, id) => {
try { await fetch(`/api/approvals/${id}/${verb}`, { method: 'POST' }); } catch (e) { /* ignore */ }
close();
paint();
};
host.querySelector('.ss-approve')?.addEventListener('click', (e) => {
if (e.currentTarget.disabled) return;
resolve('approve', e.currentTarget.dataset.id);
});
host.querySelector('.ss-deny')?.addEventListener('click', (e) => resolve('deny', e.currentTarget.dataset.id));
}
// ── Tabs ────────────────────────────────────────────────────────────
function pendingCard(a, skills) {
const name = skillName(a);
const dupe = changeType(a) === 'New' && skills.some((s) => (s.name || s.id) === name);
const preview = proposedContent(a).slice(0, 200);
return `<div class="ss-pending-card db-card" data-id="${esc(approvalId(a))}" style="margin-bottom:10px;padding:16px 20px;cursor:pointer">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
<div style="flex:1;min-width:0">
<div style="font-weight:600;font-size:14px">${esc(name.slice(0, 40))}
<span style="font-size:11px;padding:2px 8px;border-radius:10px;background:var(--db-card-bg);border:1px solid var(--db-border);margin-left:6px">${changeType(a)}</span>
${dupe ? '<span style="font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(234,179,8,.15);color:#eab308;margin-left:4px">Name collision</span>' : ''}
</div>
<div style="font-size:12px;color:var(--db-text-dim);margin-top:4px">🤖 ${esc(a.agent_name || '—')} · ${esc((a.metadata && a.metadata.source) || 'self_improve')} · ${esc(timeAgo(a.created_at))}</div>
</div>
<div style="font-size:12px">${RISK_ICONS[a.risk_level] || ''}</div>
</div>
<div id="skills-list"></div>
`;
${preview ? `<pre style="margin:10px 0 0;font-size:11px;color:var(--db-text-dim);white-space:pre-wrap;max-height:44px;overflow:hidden">${esc(preview)}</pre>` : ''}
</div>`;
}
const list = container.querySelector('#skills-list');
for (const [category, catTools] of Object.entries(grouped)) {
const section = document.createElement('div');
section.style.marginBottom = '20px';
section.innerHTML = `<h3 style="font-size:14px;font-weight:600;margin:0 0 10px;text-transform:capitalize">${category} (${catTools.length})</h3>`;
function pendingTab(pending, skills) {
if (pending.length === 0) {
return `<div class="db-card" style="text-align:center;padding:2.5rem 1.5rem;color:var(--db-text-dim)">
<div style="font-size:2.5rem;opacity:.4;margin-bottom:.75rem">📖</div>
<div style="color:var(--db-text);font-weight:600;margin-bottom:.35rem">No pending skill writes</div>
<div style="font-size:13px">Set <code>SKILL_WRITE_APPROVAL=1</code> and run an agent with <code>self_improve</code> to stage proposals here.</div>
</div>`;
}
return pending.map((a) => pendingCard(a, skills)).join('');
}
const grid = document.createElement('div');
grid.className = 'db-columns';
grid.style.gridTemplateColumns = 'repeat(auto-fill,minmax(280px,1fr))';
catTools.forEach(t => {
const card = document.createElement('div');
card.className = 'db-card';
card.style.cssText = 'padding:14px 18px;display:flex;align-items:center;justify-content:space-between';
function installedTab(tools, pendingNames) {
const grouped = {};
tools.forEach((t) => { const c = t.category || 'other'; (grouped[c] = grouped[c] || []).push(t); });
if (tools.length === 0) return '<div class="db-viewer"><pre>No skills/tools available</pre></div>';
return Object.entries(grouped).map(([category, catTools]) => {
const cards = catTools.map((t) => {
const enabled = t.enabled !== false;
const icon = t.icon || '🔧';
const keyStatus = t.api_key_set ? '🔑' : '';
card.innerHTML = `
const hasPending = pendingNames.has(t.name || t.id);
return `<div class="db-card" style="padding:14px 18px;display:flex;align-items:center;justify-content:space-between">
<div style="display:flex;align-items:center;gap:10px;flex:1">
<span style="font-size:20px">${icon}</span>
<div>
<div style="font-size:13px;font-weight:500">${t.name || t.id} ${keyStatus}</div>
<div style="font-size:11px;color:var(--db-text-dim)">${t.description || ''}</div>
<div style="font-size:13px;font-weight:500">${esc(t.name || t.id)} ${keyStatus}${hasPending ? ' <span title="Pending change" style="color:#eab308">●</span>' : ''}</div>
<div style="font-size:11px;color:var(--db-text-dim)">${esc(t.description || '')}</div>
</div>
</div>
<label style="position:relative;display:inline-block;width:40px;height:22px;cursor:pointer">
<input type="checkbox" class="skill-toggle" data-id="${t.id || t.name}" ${enabled ? 'checked' : ''} style="opacity:0;width:0;height:0">
<input type="checkbox" class="skill-toggle" data-id="${esc(t.id || t.name)}" ${enabled ? 'checked' : ''} style="opacity:0;width:0;height:0">
<span style="position:absolute;inset:0;background:${enabled ? 'var(--db-accent)' : 'var(--db-border)'};border-radius:22px;transition:0.3s"></span>
<span style="position:absolute;top:2px;left:${enabled ? '20px' : '2px'};width:18px;height:18px;background:#fff;border-radius:50%;transition:0.3s"></span>
</label>
`;
grid.appendChild(card);
});
</div>`;
}).join('');
return `<div style="margin-bottom:20px">
<h3 style="font-size:14px;font-weight:600;margin:0 0 10px;text-transform:capitalize">${esc(category)} (${catTools.length})</h3>
<div class="db-columns" style="grid-template-columns:repeat(auto-fill,minmax(280px,1fr))">${cards}</div>
</div>`;
}).join('');
}
section.appendChild(grid);
list.appendChild(section);
function historyTab(history) {
const skillHist = (Array.isArray(history) ? history : []).filter(isSkillApproval);
if (skillHist.length === 0) return '<div style="font-size:13px;color:var(--db-text-dim)">No skill approval history</div>';
return skillHist.slice(0, 25).map((h) => {
const approved = h.status === 'approved' || h.approved;
return `<div style="padding:8px 0;border-bottom:1px solid var(--db-border);font-size:13px;display:flex;justify-content:space-between;gap:10px">
<span>${esc(skillName(h))} — ${esc(h.agent_name || '')}</span>
<span style="color:${approved ? '#22c55e' : '#ef4444'}">${approved ? '✓ approved' : '✗ denied'} · ${esc(timeAgo(h.resolved_at || h.created_at))}</span>
</div>`;
}).join('');
}
const STUDIO_STYLE = `<style id="ss-style">
@media (max-width: 768px) {
.ss-panel { width: 100% !important; }
.ss-columns { grid-template-columns: 1fr !important; }
}
</style>`;
if (tools.length === 0) list.innerHTML = '<div class="db-viewer"><pre>No skills/tools available</pre></div>';
function shell(data) {
const { pending, tools } = data;
const tab = data.activeTab;
const enabledCount = tools.filter((t) => t.enabled !== false).length;
const catCount = new Set(tools.map((t) => t.category || 'other')).size;
const tabBtn = (id, label, badge) => {
const on = tab === id;
return `<button class="ss-tab" data-tab="${id}" style="padding:8px 16px;border:none;border-bottom:2px solid ${on ? 'var(--db-accent)' : 'transparent'};background:none;color:${on ? 'var(--db-text)' : 'var(--db-text-dim)'};font-size:13px;font-weight:${on ? '600' : '400'};cursor:pointer">${label}${badge ? ` <span style="background:rgba(234,179,8,.2);color:#eab308;padding:1px 7px;border-radius:10px;font-size:11px;margin-left:4px">${badge}</span>` : ''}</button>`;
};
let body = '';
if (tab === 'installed') body = installedTab(tools, data.pendingNames);
else if (tab === 'history') body = historyTab(data.history);
else body = pendingTab(pending, tools);
container.querySelectorAll('.skill-toggle').forEach(cb => {
return `${STUDIO_STYLE}
<div style="margin-bottom:8px">
<h2 style="margin:0;font-size:1.1rem;font-weight:600">Skills Studio</h2>
<div style="font-size:13px;color:var(--db-text-dim)">Review self_improve proposals</div>
</div>
<div class="db-columns" style="grid-template-columns:repeat(3,1fr);margin:16px 0 20px">
<div class="db-card"><div class="db-card-title">Pending</div><div class="db-card-value" style="color:#eab308">${pending.length}</div></div>
<div class="db-card"><div class="db-card-title">Enabled</div><div class="db-card-value" style="color:#22c55e">${enabledCount}</div></div>
<div class="db-card"><div class="db-card-title">Categories</div><div class="db-card-value">${catCount}</div></div>
</div>
<div style="display:flex;gap:4px;border-bottom:1px solid var(--db-border);margin-bottom:16px">
${tabBtn('pending', 'Pending', pending.length || '')}
${tabBtn('installed', 'Installed', '')}
${tabBtn('history', 'History', '')}
</div>
<div id="ss-body">${body}</div>
<div id="ss-overlay-host"></div>`;
}
async function loadAll() {
const results = await Promise.allSettled([
fetchJson('/api/skills'),
fetchJson('/api/approvals/pending'),
fetchJson('/api/approvals/history'),
]);
const val = (i, f) => (results[i].status === 'fulfilled' ? results[i].value : f);
const sk = val(0, {});
let tools = sk.tools || sk.skills || sk || [];
if (!Array.isArray(tools)) tools = Object.entries(tools).map(([id, t]) => ({ id, ...(typeof t === 'object' ? t : { name: t }) }));
const rawPending = (val(1, {}).approvals) || [];
const history = (val(2, {}).approvals) || (val(2, {}).history) || [];
const pending = rawPending.filter(isSkillApproval);
const pendingNames = new Set(pending.map((a) => skillName(a)));
return { tools, pending, history, pendingNames };
}
function bindEvents(data) {
_container.querySelectorAll('.ss-tab').forEach((btn) => {
btn.addEventListener('click', () => {
data.activeTab = btn.dataset.tab;
setUrlState(data.activeTab, '');
renderShell(data);
});
});
_container.querySelectorAll('.ss-pending-card').forEach((card) => {
card.addEventListener('click', () => {
const a = data.pending.find((x) => approvalId(x) === card.dataset.id);
if (a) openDiff(a, data.tools);
});
});
_container.querySelectorAll('.skill-toggle').forEach((cb) => {
cb.addEventListener('change', async () => {
try { await fetch(`/api/skills/${cb.dataset.id}/toggle`, {method:'POST'}); render(container); } catch(e) {}
try { await fetch(`/api/skills/${cb.dataset.id}/toggle`, { method: 'POST' }); } catch (e) { /* ignore */ }
paint();
});
});
}
function renderShell(data) {
_container.innerHTML = shell(data);
bindEvents(data);
}
async function paint() {
if (!_container) return;
const data = await loadAll();
const tab = currentTab() || (data.pending.length > 0 ? 'pending' : 'installed');
data.activeTab = tab;
renderShell(data);
const deepId = currentApprovalId();
if (deepId && tab === 'pending') {
const a = data.pending.find((x) => approvalId(x) === deepId);
if (a) openDiff(a, data.tools);
}
}
export async function render(container) {
_container = container;
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
await paint();
}
export function cleanup() {
_container = null;
}
@@ -9,6 +9,7 @@
* (?board=&card=&tab=) is kept in sync for deep links.
*/
import { createInteractiveBoard, relativeTime, escapeHtml } from '../board.js';
import { createMiniChat } from './mini-chat.js';
const STYLE_ID = 'work-hub-styles';
@@ -35,6 +36,11 @@ function injectStyles() {
.work-hub-chat-msg { padding:8px 10px; border-radius:8px; font-size:13px; line-height:1.5;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); }
.work-hub-chat-msg .role { font-size:11px; font-weight:600; color:var(--db-accent,#6366f1); }
.work-hub-chat-msg .work-hub-chat-body { white-space:pre-wrap; }
.work-hub-chat-streaming .work-hub-chat-body::after { content:'▌'; animation:work-hub-blink 1s infinite; }
.work-hub-chat-error { border-color:#ef4444; }
.work-hub-chat-error .work-hub-chat-body { color:#ef4444; }
@keyframes work-hub-blink { 50% { opacity:0; } }
.work-hub-composer { display:flex; gap:8px; align-items:flex-end; }
.work-hub-composer textarea { flex:1; resize:none; min-height:40px; max-height:64px; padding:8px 10px;
background:var(--db-card-bg,#27272a); border:1px solid var(--db-border,#3f3f46); border-radius:8px;
@@ -71,6 +77,14 @@ export async function render(container) {
let selectedCard = null;
let activeTab = params.get('tab') || 'description';
const pendingCardId = params.get('card');
let miniChat = null;
function teardownChat() {
if (miniChat) {
try { miniChat.disconnect(); } catch (_) { /* noop */ }
miniChat = null;
}
}
const hub = document.createElement('div');
hub.className = 'work-hub';
@@ -99,6 +113,7 @@ export async function render(container) {
}
function renderDetail() {
teardownChat();
if (!selectedCard) {
p2.innerHTML = '<div class="work-hub-empty">Select a card to see its details.</div>';
return;
@@ -153,7 +168,9 @@ export async function render(container) {
}
function renderChat(body, card) {
const agentId = card.assignee || (card.meta || {}).agent_id || '';
const meta = card.meta || {};
const agentId = card.assignee || meta.agent_id || '';
const sessionId = meta.session_id || '';
body.innerHTML = `
<div class="work-hub-chat-log" id="work-hub-chat-log">
<div class="work-hub-empty">Ask the assigned agent (${escapeHtml(agentId)}) about this card.</div>
@@ -162,21 +179,74 @@ export async function render(container) {
<textarea id="work-hub-composer" rows="2" placeholder="Message ${escapeHtml(agentId)}…"></textarea>
<button class="db-btn db-btn-sm" id="work-hub-send">Send</button>
</div>
<div style="margin-top:8px;font-size:12px"><a href="/chat" style="color:var(--db-accent,#6366f1)">Open full chat →</a></div>`;
<div style="margin-top:8px;font-size:12px"><a href="${sessionId ? '/chat?session_id=' + encodeURIComponent(sessionId) : '/chat'}" style="color:var(--db-accent,#6366f1)">Open full chat →</a></div>`;
const log = body.querySelector('#work-hub-chat-log');
const ta = body.querySelector('#work-hub-composer');
const send = body.querySelector('#work-hub-send');
send.addEventListener('click', () => {
const text = ta.value.trim();
if (!text) return;
const msg = document.createElement('div');
msg.className = 'work-hub-chat-msg';
msg.innerHTML = `<div class="role">You</div>${escapeHtml(text)}`;
teardownChat();
miniChat = createMiniChat({ agentId, sessionId });
function appendMsg(role, text) {
if (log.querySelector('.work-hub-empty')) log.innerHTML = '';
log.appendChild(msg);
ta.value = '';
const el = document.createElement('div');
el.className = 'work-hub-chat-msg';
el.innerHTML = `<div class="role">${escapeHtml(role)}</div>${escapeHtml(text)}`;
log.appendChild(el);
log.scrollTop = log.scrollHeight;
return el;
}
function submit() {
const text = ta.value.trim();
if (!text || !miniChat) return;
appendMsg('You', text);
ta.value = '';
if (log.querySelector('.work-hub-empty')) log.innerHTML = '';
const reply = document.createElement('div');
reply.className = 'work-hub-chat-msg work-hub-chat-streaming';
const roleEl = document.createElement('div');
roleEl.className = 'role';
roleEl.textContent = agentId || 'Agent';
const textEl = document.createElement('div');
textEl.className = 'work-hub-chat-body';
textEl.textContent = '…';
reply.appendChild(roleEl);
reply.appendChild(textEl);
log.appendChild(reply);
log.scrollTop = log.scrollHeight;
let acc = '';
send.disabled = true;
miniChat.send({
text,
onDelta: (token) => {
acc += token;
textEl.textContent = acc;
log.scrollTop = log.scrollHeight;
},
onComplete: (content) => {
reply.classList.remove('work-hub-chat-streaming');
if (content && content.trim()) textEl.textContent = content;
else if (!acc) textEl.textContent = '(no response)';
send.disabled = false;
log.scrollTop = log.scrollHeight;
},
onError: (err) => {
reply.classList.remove('work-hub-chat-streaming');
reply.classList.add('work-hub-chat-error');
textEl.textContent = '⚠ ' + ((err && err.message) || 'Could not reach agent.');
send.disabled = false;
log.scrollTop = log.scrollHeight;
},
});
}
send.addEventListener('click', submit);
ta.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
});
}
@@ -265,5 +335,5 @@ export async function render(container) {
renderDetail();
renderActivity();
return () => board.destroy();
return () => { teardownChat(); board.destroy(); };
}
+13 -9
View File
@@ -49,13 +49,16 @@ templates:
routes:
- match: "/docs/**"
template: docs
"""
""",
encoding="utf-8",
)
docs_dir = tmp_path / "docs"
docs_dir.mkdir()
(docs_dir / "index.md").write_text("# Welcome\n\nHello from integration test.")
(docs_dir / "guide.md").write_text("# Guide\n\nA test guide.")
(docs_dir / "index.md").write_text(
"# Welcome\n\nHello from integration test.", encoding="utf-8"
)
(docs_dir / "guide.md").write_text("# Guide\n\nA test guide.", encoding="utf-8")
return tmp_path
@@ -94,7 +97,7 @@ class TestBuildCommand:
["build", "--config", str(project_dir / "aiui.template.yaml"), "--output", str(output)],
)
ui_config = json.loads((output / "ui-config.json").read_text())
ui_config = json.loads((output / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["site"]["title"] == "Integration Test Site"
assert "header_main" in ui_config["components"]
assert "footer_main" in ui_config["components"]
@@ -111,12 +114,12 @@ class TestBuildCommand:
],
)
content = (output / "ui-config.json").read_text()
content = (output / "ui-config.json").read_text(encoding="utf-8")
assert "\n" not in content # Minified = no newlines
def test_build_invalid_config(self, tmp_path: Path):
config = tmp_path / "bad.yaml"
config.write_text("not: valid: yaml: config")
config.write_text("not: valid: yaml: config", encoding="utf-8")
result = runner.invoke(
app,
["build", "--config", str(config), "--output", str(tmp_path / "out")],
@@ -141,7 +144,8 @@ templates:
routes:
- match: "/docs/**"
template: docs
"""
""",
encoding="utf-8",
)
result = runner.invoke(
app,
@@ -215,8 +219,8 @@ class TestFullPipeline:
assert (output / "index.html").exists()
# Verify content
ui_config = json.loads((output / "ui-config.json").read_text())
ui_config = json.loads((output / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["site"]["title"] == "Integration Test Site"
routes = json.loads((output / "route-manifest.json").read_text())
routes = json.loads((output / "route-manifest.json").read_text(encoding="utf-8"))
assert len(routes["routes"]) >= 1
+6 -2
View File
@@ -488,12 +488,16 @@ class TestTelemetryAPI:
class TestTracingAPI:
def test_list_traces(self, client):
r = client.get("/api/tracing")
r = client.get("/api/traces")
assert r.status_code == 200
data = r.json()
assert isinstance(data, (list, dict))
def test_tracing_status(self, client):
r = client.get("/api/tracing/status")
r = client.get("/api/traces/status")
assert r.status_code == 200
data = r.json()
assert "status" in data or "feature" in data
# ===========================================================================
+87
View File
@@ -0,0 +1,87 @@
"""Unit tests for ADP JSON Schema validation."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
import yaml
from jsonschema import Draft202012Validator, ValidationError
ADP_ROOT = Path(__file__).resolve().parents[2] / "docs" / "protocols" / "adp"
SCHEMA_DIR = ADP_ROOT / "schema"
EXAMPLES_DIR = ADP_ROOT / "examples"
CATALOGS_DIR = ADP_ROOT / "catalogs"
def _load_json(path: Path) -> dict:
return json.loads(path.read_text())
def _load_yaml(path: Path) -> dict:
return yaml.safe_load(path.read_text())
def _application_validator() -> Draft202012Validator:
schema = _load_json(SCHEMA_DIR / "application.schema.json")
return Draft202012Validator(schema)
def _catalog_validator() -> Draft202012Validator:
schema = _load_json(SCHEMA_DIR / "component-catalog.schema.json")
return Draft202012Validator(schema)
def _theme_validator() -> Draft202012Validator:
schema = _load_json(SCHEMA_DIR / "theme.schema.json")
return Draft202012Validator(schema)
@pytest.mark.parametrize(
"schema_path",
[
SCHEMA_DIR / "application.schema.json",
SCHEMA_DIR / "component-catalog.schema.json",
SCHEMA_DIR / "theme.schema.json",
],
)
def test_schema_files_are_valid_meta_schema(schema_path: Path) -> None:
schema = _load_json(schema_path)
Draft202012Validator.check_schema(schema)
@pytest.mark.parametrize(
"example_name",
["minimal.application.yaml", "docs-site.application.yaml", "landing-page.application.yaml"],
)
def test_examples_validate_against_application_schema(example_name: str) -> None:
doc = _load_yaml(EXAMPLES_DIR / example_name)
_application_validator().validate(doc)
def test_default_catalog_validates_against_catalog_schema() -> None:
catalog = _load_json(CATALOGS_DIR / "default.catalog.json")
_catalog_validator().validate(catalog)
def test_theme_mutual_exclusion_r27() -> None:
with pytest.raises(ValidationError):
_theme_validator().validate({"preset": "zinc", "tokens": "./tokens.json"})
def test_theme_preset_only_valid() -> None:
_theme_validator().validate({"preset": "zinc", "radius": "md", "darkMode": True})
def test_x_adp_extension_accepted_at_root() -> None:
doc = _load_yaml(EXAMPLES_DIR / "minimal.application.yaml")
doc["x-adp-custom"] = True
_application_validator().validate(doc)
def test_template_requires_slots_or_zones() -> None:
doc = _load_yaml(EXAMPLES_DIR / "minimal.application.yaml")
doc["spec"]["templates"]["docs"] = {"layout": "TwoColumnLayout"}
with pytest.raises(ValidationError):
_application_validator().validate(doc)
+1 -1
View File
@@ -305,7 +305,7 @@ class TestAudioSessionManagement:
session = _audio_sessions[session_id]
assert session["chunks_received"] == 1
assert session["bytes_received"] == len(test_data)
assert session["last_chunk_at"] > session["started_at"]
assert session["last_chunk_at"] >= session["started_at"]
# Check global stats were updated
assert _audio_stats["total_chunks"] == 1
+24 -12
View File
@@ -224,25 +224,37 @@ def test_constant_time_comparison():
def test_timing_attack_resistance():
"""Test timing attack resistance - all incorrect tokens take similar time."""
"""Test timing attack resistance - all incorrect tokens take similar time.
Timing is inherently noisy: a single sample per token trips on scheduler
spikes when the full suite runs under CPU contention (issue #256). We take
several samples per token and compare the *median* time, which is resistant
to transient spikes, so the test measures the constant-time property rather
than system load.
"""
correct = "secret123456789012345678901234567890"
wrong_same_length = "wrongg123456789012345678901234567890"
wrong_diff_length = "wrong"
tokens = [wrong_same_length, wrong_diff_length, ""]
# Measure times for different wrong tokens
times = []
for wrong_token in [wrong_same_length, wrong_diff_length, ""]:
start = time.perf_counter()
for _ in range(1000): # Multiple iterations for better timing measurement
_constant_time_eq(correct, wrong_token)
end = time.perf_counter()
times.append(end - start)
def _median_time(wrong_token: str) -> float:
samples = []
for _ in range(7):
start = time.perf_counter()
for _ in range(1000):
_constant_time_eq(correct, wrong_token)
samples.append(time.perf_counter() - start)
samples.sort()
return samples[len(samples) // 2]
# Times should be similar (within reasonable variance)
# This is a best-effort test - timing can be affected by system load
# Warm up so JIT/cache effects do not skew the first token's samples.
_median_time(correct)
times = [_median_time(token) for token in tokens]
# Median times should be similar (all wrong tokens take comparable time).
avg_time = sum(times) / len(times)
for t in times:
# Allow up to 50% variance due to system noise
assert abs(t - avg_time) / avg_time < 0.5, f"Timing variance too high: {times}"
+2 -2
View File
@@ -22,7 +22,7 @@ EXPLORER_VIEW = _FRONTEND / "views" / "explorer.js"
def _view() -> str:
return CHANNELS_VIEW.read_text()
return CHANNELS_VIEW.read_text(encoding="utf-8")
class TestAddChannelControls:
@@ -91,6 +91,6 @@ class TestEmptyState:
class TestExplorerPreset:
def test_explorer_has_add_channel_post(self):
src = EXPLORER_VIEW.read_text()
src = EXPLORER_VIEW.read_text(encoding="utf-8")
assert "Add Channel" in src
assert "env:TELEGRAM_BOT_TOKEN" in src
+7 -7
View File
@@ -25,13 +25,13 @@ CHAT_JS = FRONTEND / "views" / "chat.js"
class TestSelectPageShellError:
def test_shell_error_helper_defined(self):
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
assert "function showDashboardInitError(" in src
assert "'alert'" in src
assert "db-error" in src
def test_select_page_calls_shell_error_on_missing_main(self):
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
idx = src.find("const main = document.getElementById('db-main-content')")
assert idx != -1
region = src[idx:idx + 400]
@@ -41,16 +41,16 @@ class TestSelectPageShellError:
class TestChatOfflineMessaging:
def test_toast_imported(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert "import { showToast } from '../toast.js';" in src
def test_offline_hint_in_fallback(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert "navigator.onLine === false" in src
assert "You are offline. " in src
def test_fallback_catch_shows_toast(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
idx = src.find("fetch('/api/chat/send'")
assert idx != -1
region = src[idx:idx + 900]
@@ -62,7 +62,7 @@ class TestChatOfflineMessaging:
class TestSystemErrorRoleAlert:
def test_append_message_sets_role_alert(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
idx = src.find("function appendMessage(")
assert idx != -1
region = src[idx:idx + 700]
@@ -72,7 +72,7 @@ class TestSystemErrorRoleAlert:
class TestWebSocketSendGuard:
def test_ws_send_wrapped_in_try_catch(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
idx = src.find("ws.send(JSON.stringify(payload))")
assert idx != -1
before = src[max(0, idx - 120):idx]
+32
View File
@@ -0,0 +1,32 @@
"""Tests for the chat prefill-composer listener lifecycle (issue #209).
The 'aiui:prefill-composer' listener must be registered as a module-scope
singleton and torn down in cleanup(), so repeated SPA navigations between
/code-studio and /chat do not stack duplicate handlers on window.
"""
from pathlib import Path
VIEWS = Path("src/praisonaiui/templates/frontend/plugins/views")
class TestChatPrefillListenerLifecycle:
def test_module_scope_handler_declared(self):
src = (VIEWS / "chat.js").read_text()
assert "let prefillHandler = null;" in src
def test_ensure_prefill_listener_guard(self):
src = (VIEWS / "chat.js").read_text()
assert "function ensurePrefillListener()" in src
assert "if (prefillHandler) return;" in src
def test_render_uses_singleton_not_inline_addlistener(self):
src = (VIEWS / "chat.js").read_text()
assert "ensurePrefillListener();" in src
# The listener is added exactly once, inside the guard helper.
assert src.count("window.addEventListener('aiui:prefill-composer'") == 1
def test_cleanup_removes_listener(self):
src = (VIEWS / "chat.js").read_text()
assert "window.removeEventListener('aiui:prefill-composer', prefillHandler)" in src
assert src.count("prefillHandler = null;") >= 2
+6 -6
View File
@@ -50,28 +50,28 @@ class TestCodeStudioFrontend:
assert (VIEWS / "code-studio.js").is_file()
def test_view_exports_render(self):
src = (VIEWS / "code-studio.js").read_text()
src = (VIEWS / "code-studio.js").read_text(encoding="utf-8")
assert "export async function render" in src
assert "export function cleanup" in src
def test_view_calls_execute_api(self):
src = (VIEWS / "code-studio.js").read_text()
src = (VIEWS / "code-studio.js").read_text(encoding="utf-8")
assert "/api/code/execute" in src
assert "/api/code/languages" in src
def test_no_browser_eval(self):
src = (VIEWS / "code-studio.js").read_text()
src = (VIEWS / "code-studio.js").read_text(encoding="utf-8")
assert "eval(" not in src
def test_registered_in_dashboard(self):
src = (PLUGINS / "dashboard.js").read_text()
src = (PLUGINS / "dashboard.js").read_text(encoding="utf-8")
assert "'code-studio'" in src
assert "code-studio.js" in src
def test_send_to_agent_event(self):
src = (VIEWS / "code-studio.js").read_text()
src = (VIEWS / "code-studio.js").read_text(encoding="utf-8")
assert "aiui:prefill-composer" in src
def test_chat_has_prefill_listener(self):
src = (VIEWS / "chat.js").read_text()
src = (VIEWS / "chat.js").read_text(encoding="utf-8")
assert "aiui:prefill-composer" in src
+1 -1
View File
@@ -22,7 +22,7 @@ DASHBOARD_JS = FRONTEND / "dashboard.js"
class TestCommandPalette:
def _src(self) -> str:
return DASHBOARD_JS.read_text()
return DASHBOARD_JS.read_text(encoding="utf-8")
def test_open_command_palette_exposed_globally(self):
src = self._src()
+19 -19
View File
@@ -86,7 +86,7 @@ class TestCompilerUiConfig:
result = compiler.compile(tmp_path / "output")
assert result.success is True
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["site"]["title"] == "Test Site"
assert "templates" in ui_config
assert "docs" in ui_config["templates"]
@@ -96,7 +96,7 @@ class TestCompilerUiConfig:
result = compiler.compile(tmp_path / "output")
assert result.success is True
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8"))
assert "header_main" in ui_config["components"]
assert ui_config["components"]["header_main"]["props"]["logoText"] == "MySite"
assert "footer_main" in ui_config["components"]
@@ -107,7 +107,7 @@ class TestCompilerUiConfig:
result = compiler.compile(tmp_path / "output")
assert result.success is True
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8"))
docs_template = ui_config["templates"]["docs"]
assert docs_template["slots"]["header"]["ref"] == "header_main"
assert docs_template["slots"]["footer"]["ref"] == "footer_main"
@@ -123,7 +123,7 @@ class TestCompilerRouteManifest:
assert "route-manifest.json" in result.files
manifest = json.loads(
(tmp_path / "output" / "route-manifest.json").read_text()
(tmp_path / "output" / "route-manifest.json").read_text(encoding="utf-8")
)
assert len(manifest["routes"]) == 1
assert manifest["routes"][0]["pattern"] == "/docs/**"
@@ -147,7 +147,7 @@ class TestCompilerRouteManifest:
assert result.success is True
manifest = json.loads(
(tmp_path / "output" / "route-manifest.json").read_text()
(tmp_path / "output" / "route-manifest.json").read_text(encoding="utf-8")
)
# First route in list gets highest priority
assert manifest["routes"][0]["priority"] > manifest["routes"][1]["priority"]
@@ -200,7 +200,7 @@ class TestCompilerOutputFiles:
compiler = Compiler(minimal_config, base_path=tmp_path)
compiler.compile(tmp_path / "output", minify=True)
content = (tmp_path / "output" / "ui-config.json").read_text()
content = (tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8")
# Minified JSON should not have newlines
assert "\n" not in content
@@ -208,7 +208,7 @@ class TestCompilerOutputFiles:
compiler = Compiler(minimal_config, base_path=tmp_path)
compiler.compile(tmp_path / "output", minify=False)
content = (tmp_path / "output" / "ui-config.json").read_text()
content = (tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8")
# Non-minified JSON should have newlines
assert "\n" in content
@@ -228,7 +228,7 @@ class TestCompilerDocsContent:
result = compiler.compile(tmp_path / "output")
assert "docs-nav.json" in result.files
nav = json.loads((tmp_path / "output" / "docs-nav.json").read_text())
nav = json.loads((tmp_path / "output" / "docs-nav.json").read_text(encoding="utf-8"))
assert "items" in nav
def test_no_docs_nav_without_content(self, minimal_config: Config, tmp_path: Path):
@@ -291,7 +291,7 @@ class TestCompilerThemeCss:
assert result.success is True
theme_css = tmp_path / "output" / "assets" / "theme.css"
assert theme_css.exists(), "assets/theme.css must be generated"
content = theme_css.read_text()
content = theme_css.read_text(encoding="utf-8")
assert ":root {" in content
assert "--radius:" in content
@@ -340,7 +340,7 @@ class TestInjectThemeCss:
inject_theme_css(tmp_path, preset="zinc", dark_mode=True, radius="0.5rem")
theme_file = tmp_path / "assets" / "theme.css"
assert theme_file.exists()
content = theme_file.read_text()
content = theme_file.read_text(encoding="utf-8")
assert ":root {" in content
def test_creates_assets_dir(self, tmp_path: Path):
@@ -367,7 +367,7 @@ class TestThemePipelineIntegration:
assert result.success is True
assert "assets/theme.css" in result.files
theme_css = (tmp_path / "output" / "assets" / "theme.css").read_text()
theme_css = (tmp_path / "output" / "assets" / "theme.css").read_text(encoding="utf-8")
assert ":root {" in theme_css
assert ".dark {" in theme_css
assert "--radius: 1rem" in theme_css # xl = 1rem
@@ -386,7 +386,7 @@ class TestThemePipelineIntegration:
result = compiler.compile(tmp_path / "output")
assert result.success is True
theme_css = (tmp_path / "output" / "assets" / "theme.css").read_text()
theme_css = (tmp_path / "output" / "assets" / "theme.css").read_text(encoding="utf-8")
assert ":root {" in theme_css
assert ".dark" not in theme_css
@@ -405,7 +405,7 @@ class TestThemePipelineIntegration:
assert result.success is True
ui_config = json.loads(
(tmp_path / "output" / "ui-config.json").read_text()
(tmp_path / "output" / "ui-config.json").read_text(encoding="utf-8")
)
theme = ui_config["site"]["theme"]
assert theme["preset"] == "blue"
@@ -426,7 +426,7 @@ class TestThemePipelineIntegration:
theme_css_path = tmp_path / "output" / "assets" / "theme.css"
assert theme_css_path.exists(), "theme.css must always be generated"
content = theme_css_path.read_text()
content = theme_css_path.read_text(encoding="utf-8")
assert ":root {" in content
assert "--radius:" in content
@@ -444,7 +444,7 @@ class TestThemePipelineIntegration:
result = compiler.compile(tmp_path / "output")
assert result.success is True
index_html = (tmp_path / "output" / "index.html").read_text()
index_html = (tmp_path / "output" / "index.html").read_text(encoding="utf-8")
assert "classList.add('dark')" not in index_html
def test_dark_mode_antiflicker_has_dark_class(self, tmp_path: Path):
@@ -461,7 +461,7 @@ class TestThemePipelineIntegration:
result = compiler.compile(tmp_path / "output")
assert result.success is True
index_html = (tmp_path / "output" / "index.html").read_text()
index_html = (tmp_path / "output" / "index.html").read_text(encoding="utf-8")
assert "classList.add('dark')" in index_html
@@ -514,7 +514,7 @@ class TestCompositionResolver:
# Load ui-config.json and verify auto-bridging
ui_config_path = tmp_path / "output" / "ui-config.json"
ui_config = json.loads(ui_config_path.read_text())
ui_config = json.loads(ui_config_path.read_text(encoding="utf-8"))
docs_template = ui_config["templates"]["docs"]
assert "zones" in docs_template
@@ -556,7 +556,7 @@ class TestCompositionResolver:
# Load ui-config.json and verify no auto-bridging occurred
ui_config_path = tmp_path / "output" / "ui-config.json"
ui_config = json.loads(ui_config_path.read_text())
ui_config = json.loads(ui_config_path.read_text(encoding="utf-8"))
docs_template = ui_config["templates"]["docs"]
# Should not have zones for non-FlexibleLayout
@@ -589,7 +589,7 @@ class TestCompositionResolver:
# Load ui-config.json and verify no duplicate components
ui_config_path = tmp_path / "output" / "ui-config.json"
ui_config = json.loads(ui_config_path.read_text())
ui_config = json.loads(ui_config_path.read_text(encoding="utf-8"))
docs_template = ui_config["templates"]["docs"]
zones = docs_template["zones"]
+5 -5
View File
@@ -30,7 +30,7 @@ class TestCompilerChatOutput:
result = compiler.compile(tmp_path)
assert result.success
ui_config = json.loads((tmp_path / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["style"] == "chat"
def test_layout_in_ui_config(self, tmp_path: Path):
@@ -43,7 +43,7 @@ class TestCompilerChatOutput:
result = compiler.compile(tmp_path)
assert result.success
ui_config = json.loads((tmp_path / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["layout"]["mode"] == "bottom-right"
assert ui_config["layout"]["width"] == "400px"
assert ui_config["layout"]["height"] == "600px"
@@ -73,7 +73,7 @@ class TestCompilerChatOutput:
result = compiler.compile(tmp_path)
assert result.success
ui_config = json.loads((tmp_path / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["chat"]["enabled"] is True
assert ui_config["chat"]["name"] == "AI Assistant"
assert len(ui_config["chat"]["starters"]) == 1
@@ -96,7 +96,7 @@ class TestCompilerChatOutput:
result = compiler.compile(tmp_path)
assert result.success
ui_config = json.loads((tmp_path / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "ui-config.json").read_text(encoding="utf-8"))
assert ui_config["auth"]["enabled"] is True
assert "password" in ui_config["auth"]["providers"]
assert "google" in ui_config["auth"]["providers"]
@@ -128,7 +128,7 @@ class TestCompilerChatOutput:
result = compiler.compile(tmp_path)
assert result.success
ui_config = json.loads((tmp_path / "ui-config.json").read_text())
ui_config = json.loads((tmp_path / "ui-config.json").read_text(encoding="utf-8"))
assert len(ui_config["widgets"]) == 2
assert ui_config["widgets"][0]["type"] == "slider"
assert ui_config["widgets"][0]["name"] == "temperature"
+2 -2
View File
@@ -24,7 +24,7 @@ DASHBOARD_JS = (
@pytest.fixture
def dashboard_source():
"""Read dashboard.js source."""
return DASHBOARD_JS.read_text()
return DASHBOARD_JS.read_text(encoding="utf-8")
class TestComponentRegistry:
@@ -68,7 +68,7 @@ class TestPythonJSComponentParity:
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "ui.py"
)
source = ui_py.read_text()
source = ui_py.read_text(encoding="utf-8")
# Extract all "type": "xxx" patterns from ui.py
py_types = set(re.findall(r'"type":\s*"(\w+)"', source))
+1 -1
View File
@@ -111,7 +111,7 @@ class TestBuiltinSchemaCoverage:
ui_src = (
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "ui.py"
).read_text()
).read_text(encoding="utf-8")
py_types = set(re.findall(r'"type":\s*"(\w+)"', ui_src))
schemas = srv.get_component_schemas()
missing = py_types - set(schemas)
+8 -8
View File
@@ -136,47 +136,47 @@ class TestRingThresholds:
class TestChatJsComponents:
def test_ring_dom_present(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-ring-wrap"' in src
assert 'id="chat-ring-arc"' in src
assert 'id="chat-ring-tooltip"' in src
def test_send_button_inside_ring_preserved_id(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert 'class="chat-ring-send" id="chat-send-btn"' in src
assert 'aria-valuenow' in src
assert 'aria-valuemax' in src
def test_compaction_banner_present(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-compaction-banner"' in src
assert "function updateCompactionBanner(" in src
assert "function dismissCompactionBanner(" in src
def test_memory_chip_present(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-memory-chip"' in src
assert "function refreshMemoryChip(" in src
def test_ring_state_machine_thresholds(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert "function ringStateFor(" in src
assert "pct >= 95" in src
assert "pct >= 85" in src
assert "pct >= 60" in src
def test_ws_context_events_handled(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert "case 'context_update':" in src
assert "case 'context_compacted':" in src
def test_poll_fallback_and_debounce(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
assert "function startContextPoll(" in src
assert "function scheduleContextRefresh(" in src
def test_existing_memory_panel_and_metrics_preserved(self):
src = CHAT_JS.read_text()
src = CHAT_JS.read_text(encoding="utf-8")
# Regression: do not remove existing shipped elements
assert 'id="chat-memory-panel"' in src
assert 'id="chat-memory-btn"' in src
+126
View File
@@ -0,0 +1,126 @@
"""Unit tests for issue #208 — i18n wiring, per-turn memory search, and a11y.
Covers post-merge gaps left by PR #206 on the chat context ring UI:
* Gap A — ring/banner/chip strings consume i18n via a JS ``t()`` helper.
* Gap B — memory chip uses ``POST /api/memory/search`` (per-turn hits),
not ``GET /api/memory`` (total store).
* Gap C — ``role="progressbar"`` lives on the ring wrapper, not the send button.
"""
from __future__ import annotations
from pathlib import Path
from praisonaiui.features.i18n import JSONLocaleManager
FRONTEND = (
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
)
CHAT_JS = FRONTEND / "views" / "chat.js"
def _src() -> str:
return CHAT_JS.read_text(encoding="utf-8")
class TestGapAI18nWiring:
def test_i18n_loader_present(self):
src = _src()
assert "async function loadI18nStrings(" in src
assert "/api/i18n/strings/" in src
assert "await loadI18nStrings()" in src
def test_t_helper_with_fallback(self):
src = _src()
assert "function t(key, params)" in src
assert "I18N_FALLBACK" in src
def test_i18n_used_at_least_four_times(self):
# E2E regression bar from the issue: i18n key lookups >= 4
src = _src()
assert src.count("t('chat.") >= 4
def test_banner_uses_i18n_key(self):
src = _src()
assert "t('chat.compaction.banner')" in src
def test_compacted_flash_uses_i18n_key(self):
src = _src()
assert "t('chat.compaction.compacted')" in src
def test_memory_chip_uses_i18n_key(self):
src = _src()
assert "t('chat.memory.chip'" in src
assert "t('chat.memory.empty_turn')" in src
def test_tooltip_uses_context_status_keys(self):
src = _src()
assert "chat.context.critical" in src
assert "chat.context.warning" in src
assert "chat.context.healthy" in src
assert "t('chat.context.estimate')" in src
class TestGapBMemorySearch:
def test_search_endpoint_used(self):
# E2E regression bar: memory/search present >= 1
src = _src()
assert "/api/memory/search" in src
def test_search_is_post_with_query(self):
src = _src()
assert "method: 'POST'" in src
assert "lastUserMessage" in src
assert "query," in src
def test_chip_counts_results_not_total(self):
src = _src()
assert "data.results || []" in src
assert "results.length" in src
def test_last_user_message_tracked_on_send(self):
src = _src()
assert "lastUserMessage = content" in src
class TestGapCAccessibility:
def test_progressbar_role_on_wrapper(self):
src = _src()
# Wrapper div carries the progressbar role + aria values
assert 'id="chat-ring-wrap"' in src
wrap_idx = src.index('id="chat-ring-wrap"')
wrap_slice = src[wrap_idx:wrap_idx + 260]
assert 'role="progressbar"' in wrap_slice
assert "aria-valuenow" in wrap_slice
assert "aria-valuemax" in wrap_slice
def test_send_button_is_button_not_progressbar(self):
src = _src()
btn_idx = src.index('id="chat-send-btn"')
btn_slice = src[btn_idx:btn_idx + 160]
assert 'type="button"' in btn_slice
assert 'role="progressbar"' not in btn_slice
assert 'aria-label="Send message"' in btn_slice
def test_aria_updates_target_wrapper(self):
src = _src()
assert "wrap.setAttribute('aria-valuenow'" in src
class TestI18nMemoryChipLocales:
def test_chip_translation_interpolates_count_all_locales(self):
mgr = JSONLocaleManager()
for locale, word in (("en", "memories"), ("es", "recuerdos"), ("fr", "souvenirs")):
out = mgr.t("chat.memory.chip", locale=locale, count=2)
assert "2" in out
assert word in out
def test_banner_localised(self):
mgr = JSONLocaleManager()
assert mgr.t("chat.compaction.banner", locale="es") != mgr.t(
"chat.compaction.banner", locale="en"
)
assert mgr.t("chat.compaction.banner", locale="fr") != mgr.t(
"chat.compaction.banner", locale="en"
)
@@ -27,7 +27,7 @@ DASHBOARD_JS = (
def _style() -> str:
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
m = re.search(r"const DASHBOARD_STYLE = `(.*?)`;", src, re.DOTALL)
assert m, "DASHBOARD_STYLE template literal not found"
return m.group(1)
@@ -21,7 +21,7 @@ DASHBOARD_JS = FRONTEND / "dashboard.js"
class TestSelectPageGuard:
def _select_page_body(self) -> str:
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
m = re.search(
r"async function selectPage\(pageId\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
@@ -56,7 +56,7 @@ class TestSelectPageGuard:
assert "console.error" in m.group(1)
def test_show_dashboard_init_error_helper_present(self):
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
m = re.search(
r"function showDashboardInitError\(message\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
+2 -2
View File
@@ -23,7 +23,7 @@ GENERATOR = ROOT / "scripts" / "patch_dashboard_modular.py"
class TestDashboardShellOrder:
def _build_body(self) -> str:
src = DASHBOARD_JS.read_text()
src = DASHBOARD_JS.read_text(encoding="utf-8")
m = re.search(
r"function buildDashboard\([^)]*\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
@@ -51,7 +51,7 @@ class TestDashboardShellOrder:
assert m, "shellHeader insert must be guarded by children.length"
def test_generator_emits_main_before_insert_before(self):
src = GENERATOR.read_text()
src = GENERATOR.read_text(encoding="utf-8")
append_idx = src.find("root.appendChild(main);\n const shellHeader")
insert_idx = src.find("root.insertBefore(shellHeader, main)")
assert append_idx != -1, (
+70
View File
@@ -0,0 +1,70 @@
"""Tests for the shared examples console helper.
These verify that examples degrade emoji to ASCII on legacy Windows code pages
(cp1252) instead of raising ``UnicodeEncodeError`` while preserving emoji on
UTF-aware terminals, mirroring the CLI ``_icon``/``_supports_unicode`` pattern.
"""
import io
import os
import sys
from unittest import mock
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "examples", "python", "_shared"))
from console import icon, safe_print, supports_unicode
class TestSupportsUnicode:
def test_utf8_encoding_is_supported(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "utf-8"
with mock.patch("sys.stdout", mock_stdout):
assert supports_unicode() is True
def test_cp1252_encoding_is_not_supported(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "cp1252"
with mock.patch("sys.stdout", mock_stdout):
assert supports_unicode() is False
def test_missing_encoding_falls_back_to_ascii(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = None
with mock.patch("sys.stdout", mock_stdout):
assert supports_unicode() is False
class TestIcon:
def test_returns_symbol_on_utf8(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "utf-8"
with mock.patch("sys.stdout", mock_stdout):
assert icon("🚀", "[START]") == "🚀"
def test_returns_fallback_on_cp1252(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "cp1252"
with mock.patch("sys.stdout", mock_stdout):
assert icon("🚀", "[START]") == "[START]"
class TestSafePrint:
def test_never_raises_on_cp1252(self):
buffer = io.BytesIO()
stream = io.TextIOWrapper(buffer, encoding="cp1252", errors="strict")
with mock.patch("sys.stdout", stream):
safe_print("🚀 Starting")
stream.flush()
assert buffer.getvalue()
def test_preserves_unicode_on_utf8(self):
buffer = io.StringIO()
with mock.patch("sys.stdout", buffer):
safe_print("🚀 Starting")
assert "🚀" in buffer.getvalue()
def test_respects_sep(self):
buffer = io.StringIO()
with mock.patch("sys.stdout", buffer):
safe_print("a", "b", sep="-")
assert buffer.getvalue().strip() == "a-b"
+249
View File
@@ -0,0 +1,249 @@
"""Unit tests for the Cost FinOps Strip (STITCH-013, #223).
Covers:
* Budget threshold + today-sum logic (mirrors the JS helpers).
* FinOps config schema section + cross-field validation.
* Frontend component presence in overview.js / chat.js / _helpers.js.
* i18n keys for strip/banner/chip.
"""
from __future__ import annotations
from pathlib import Path
from praisonaiui.features.config_runtime import CONFIG_SCHEMA, validate_config
from praisonaiui.features.i18n import JSONLocaleManager
FRONTEND = (
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
)
HELPERS_JS = FRONTEND / "views" / "_helpers.js"
OVERVIEW_JS = FRONTEND / "views" / "overview.js"
CHAT_JS = FRONTEND / "views" / "chat.js"
def _compute_budget_pct(today: float, budget) -> float | None:
"""Python mirror of computeBudgetPct."""
if not budget or budget <= 0:
return None
pct = today / budget * 100
if pct < 0:
return 0
return min(pct, 100)
def _budget_level(pct, warn_pct: int = 80, critical_pct: int = 95) -> str:
"""Python mirror of budgetLevelFor."""
if pct is None:
return "none"
if pct >= critical_pct:
return "critical"
if pct >= warn_pct:
return "warn"
return "none"
def _sum_today_tokens(timeseries: list[dict]) -> int:
"""Python mirror of sumTodayTokens (uses fixed today key for determinism)."""
today_key = "2026-07-15"
dated = [p for p in timeseries if isinstance(p.get("hour_key"), str)]
scope = [p for p in dated if p["hour_key"].startswith(today_key)] if dated else timeseries
return sum(int(p.get("tokens", 0) or 0) for p in scope)
def _short_model(model: str) -> str:
idx = model.rfind("/")
return model[idx + 1:] if idx >= 0 else model
class TestBudgetPct:
def test_80_percent(self):
assert _compute_budget_pct(1_600_000, 2_000_000) == 80
def test_null_budget(self):
assert _compute_budget_pct(1_000_000, None) is None
def test_zero_budget_treated_as_unset(self):
assert _compute_budget_pct(1_000_000, 0) is None
def test_over_budget_capped_at_100(self):
assert _compute_budget_pct(3_000_000, 2_000_000) == 100
class TestBudgetLevel:
def test_below_warn_is_none(self):
assert _budget_level(79) == "none"
def test_at_warn_is_warn(self):
assert _budget_level(80) == "warn"
assert _budget_level(82) == "warn"
def test_at_critical_is_critical(self):
assert _budget_level(95) == "critical"
assert _budget_level(96) == "critical"
def test_null_pct_is_none(self):
assert _budget_level(None) == "none"
class TestSumTodayTokens:
def test_only_today_buckets_summed(self):
ts = [
{"hour_key": "2026-07-15-08", "tokens": 100},
{"hour_key": "2026-07-15-09", "tokens": 200},
{"hour_key": "2026-07-14-23", "tokens": 999},
]
assert _sum_today_tokens(ts) == 300
def test_falls_back_to_all_when_undated(self):
ts = [{"tokens": 10}, {"tokens": 20}]
assert _sum_today_tokens(ts) == 30
class TestShortModel:
def test_strips_provider_prefix(self):
assert _short_model("anthropic/claude-3-5-sonnet") == "claude-3-5-sonnet"
def test_no_prefix_unchanged(self):
assert _short_model("gpt-4o-mini") == "gpt-4o-mini"
class TestFinOpsConfigSchema:
def test_finops_section_present(self):
assert "finops" in CONFIG_SCHEMA["properties"]
def test_finops_fields_present(self):
props = CONFIG_SCHEMA["properties"]["finops"]["properties"]
for field in (
"enabled",
"daily_token_budget",
"daily_cost_budget_usd",
"warn_pct",
"critical_pct",
"show_session_chip",
):
assert field in props, f"missing finops field: {field}"
def test_threshold_defaults(self):
props = CONFIG_SCHEMA["properties"]["finops"]["properties"]
assert props["warn_pct"]["default"] == 80
assert props["critical_pct"]["default"] == 95
class TestFinOpsValidation:
def test_critical_must_exceed_warn(self):
errors = validate_config({"finops": {"warn_pct": 90, "critical_pct": 80}})
assert any("critical_pct" in e for e in errors)
def test_equal_thresholds_rejected(self):
errors = validate_config({"finops": {"warn_pct": 80, "critical_pct": 80}})
assert any("critical_pct" in e for e in errors)
def test_valid_thresholds_pass(self):
errors = validate_config({"finops": {"warn_pct": 80, "critical_pct": 95}})
assert not any("critical_pct" in e for e in errors)
def test_negative_budget_rejected(self):
errors = validate_config({"finops": {"daily_token_budget": -5}})
assert any("daily_token_budget" in e for e in errors)
def test_no_finops_section_ok(self):
assert validate_config({"model": {"name": "gpt-4o-mini"}}) == []
class TestHelpersJs:
def test_donut_and_strip_helpers_present(self):
src = HELPERS_JS.read_text(encoding="utf-8")
assert "export function MiniDonutSVG(" in src
assert "export function costStripHTML(" in src
assert "export function budgetBannerHTML(" in src
def test_math_helpers_present(self):
src = HELPERS_JS.read_text(encoding="utf-8")
assert "export function computeBudgetPct(" in src
assert "export function budgetLevelFor(" in src
assert "export function sumTodayTokens(" in src
assert "export function formatTokens(" in src
assert "export function formatCost(" in src
def test_donut_empty_state_accessible(self):
src = HELPERS_JS.read_text(encoding="utf-8")
assert 'aria-label="No model data"' in src
def test_strip_navigates_and_is_region(self):
src = HELPERS_JS.read_text(encoding="utf-8")
assert 'data-nav="usage"' in src
assert 'role="region"' in src
class TestOverviewJs:
def test_finops_imports_and_render(self):
src = OVERVIEW_JS.read_text(encoding="utf-8")
assert "costStripHTML" in src
assert "budgetBannerHTML" in src
assert "function renderFinOps(" in src
assert 'id="ov-finops"' in src
def test_fetches_models_and_config(self):
src = OVERVIEW_JS.read_text(encoding="utf-8")
assert "/api/usage/models" in src
assert "/api/config/runtime" in src
def test_graceful_degrade_state_machine(self):
src = OVERVIEW_JS.read_text(encoding="utf-8")
assert "function finopsState(" in src
assert "finops.enabled === false" in src
class TestChatJs:
def test_session_cost_chip_present(self):
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-session-cost-chip"' in src
assert "function updateSessionCostChip(" in src
def test_finops_banner_present(self):
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-finops-banner"' in src
assert "function updateFinopsBanner(" in src
assert "function loadFinopsConfig(" in src
def test_chip_updates_on_run_completed(self):
src = CHAT_JS.read_text(encoding="utf-8")
assert "updateFinopsBanner()" in src
assert "updateSessionCostChip(stats)" in src
def test_context_ring_regression_preserved(self):
src = CHAT_JS.read_text(encoding="utf-8")
assert 'id="chat-ring-wrap"' in src
assert "function ringStateFor(" in src
assert 'class="chat-ring-send" id="chat-send-btn"' in src
class TestI18nKeys:
KEYS = [
"finops.strip.title",
"finops.strip.today",
"finops.strip.view_usage",
"finops.banner.warn",
"finops.banner.critical",
"finops.chip.label",
]
def test_all_keys_present_all_locales(self):
mgr = JSONLocaleManager()
for locale in ("en", "es", "fr"):
strings = mgr.get_strings(locale)
for k in self.KEYS:
assert k in strings, f"missing {locale} key: {k}"
def test_banner_interpolates_pct(self):
mgr = JSONLocaleManager()
out = mgr.t("finops.banner.warn", locale="en", pct=82)
assert "82" in out
def test_chip_interpolates_tokens_and_model(self):
mgr = JSONLocaleManager()
out = mgr.t("finops.chip.label", locale="en", tokens="12.4k", model="gpt-4o-mini")
assert "12.4k" in out
assert "gpt-4o-mini" in out
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more