Compare commits

...
Author SHA1 Message Date
MervinPraisonandCursor 9403f6ba58 fix(ci): dedupe Claude workflow when label and @claude comment both fire
Skip issue_comment trigger if claude label was added within 3 minutes.
Add per-issue concurrency to cancel overlapping runs.

Co-authored-by: Cursor <[email protected]>
2026-06-15 13:43:54 +01:00
66acd35496 chore(ci): auto-trigger Claude on owner issue open (parity with PraisonAI) (#81)
* chore(ci): auto-trigger Claude on owner issue open (parity with PraisonAI)

Mirror praisonai-package claude.yml: triage labels on open, auto-add claude
label for owner/collaborator issues, GitHub App token, fork PR handling.

Co-authored-by: Cursor <[email protected]>

* chore(ci): add trigger workflow to sync secrets from PraisonAI

Co-authored-by: Cursor <[email protected]>

* fix(ci): fallback to GITHUB_TOKEN when Claude app secrets missing

Auto-triage works without cross-repo secret copy. Sync workflow documents
manual fallback when SECRETS_ADMIN_PAT is unavailable.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: MervinPraison <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-06-15 13:34:39 +01:00
MervinPraison 8a483a62dd chore: update dependencies and enhance CI workflow
- Updated `praisonaiagents` and `praisonai` versions in `pyproject.toml`.
- Added integration smoke tests to the CI workflow for better backend validation.
- Improved documentation on platform integration and SDK gaps.
- Enhanced the UI for issues and sessions with new toolbar components.
- Refactored backend integration checks and tool resolution logic for better maintainability.
2026-06-15 13:22:27 +01:00
MervinPraisonandCursor d418dcf443 refactor: harden input validation and access controls
Co-authored-by: Cursor <[email protected]>
2026-06-13 13:44:05 +01:00
Mervin Praison c467643c15 Merge pull request #53 from MervinPraison/feat/l1-integration-backends
feat: L1 backend integration for PraisonAI package
2026-05-21 11:59:15 +01:00
MervinPraison 8caa8d51d0 test: align unit tests with surface decorators, dashboard modules, MCP session context.
Update regression allowlist and assertions after integration branch API changes.
2026-05-21 11:58:00 +01:00
MervinPraison 541ada0399 chore: fix ruff lint errors for CI.
Remove unused imports and sort import blocks flagged by ruff.
2026-05-21 11:56:23 +01:00
MervinPraison fb28f2a2ee test(jobs): read jobs config from module after set_jobs_backend.
Imported dict binding was stale after global reassignment in set_jobs_api.
2026-05-21 11:55:10 +01:00
MervinPraison ac0a4f32a8 test(jobs): reset jobs API config before backend default assertion.
Avoid cross-test pollution when other suites call set_jobs_api first.
2026-05-21 11:52:29 +01:00
MervinPraison 3a0fa72c3d Merge origin/main into feat/l1-integration-backends and fix review issues.
Resolve pyproject/uv.lock version conflicts, merge package integration docs,
fix a2ui-mapper JS syntax, run workflows in thread pool, and use AsyncContext
for video agent tools when called from a running event loop.
2026-05-21 11:51:16 +01:00
MervinPraison f38aa27345 feat(jobs): add configurable jobs API and platform board examples.
Expose set_jobs_api/set_jobs_backend for dashboard jobs view routing and add platform-board examples with tests.
2026-05-21 11:45:45 +01:00
MervinPraison 39615089ba refactor(dashboard): slim view modules and add package alignment tests.
Delegate heavy dashboard views to shared modules, register shell header branding slot, and add tests for auth/api views and jobs UI config.
2026-05-21 11:45:36 +01:00
MervinPraison 77b67271ec feat(docs): add PraisonAI package integration documentation
This commit introduces detailed documentation on how to integrate
the PraisonAI package with the Agent runtime, gateway, and CLI.
It outlines deployment options, wiring checklists, API surfaces,
and plugin systems to facilitate user understanding and usage.

Additionally, updates the version in `pyproject.toml` and
`.gitignore` to include database files and modifies the lock file.
2026-05-21 11:44:05 +01:00
MervinPraison 7099e9f2ce u 2026-05-21 11:43:26 +01:00
MervinPraison 3f02c32740 feat(docs): add image preview feature for chat
Introduced documentation for a new feature allowing inline media rendering
of agent-generated images during chat. This update includes architecture
details, data flow paths, and examples for seamless integration with
existing backends. Updated related documentation to link to new feature.
2026-05-20 20:38:41 +01:00
MervinPraison a9dfc97e8c feat(chat): support inline media in chat messages
- Added support for inline media (images, videos, files) in chat messages
- Updated `ChatArea`, `ChatMessages`, and `ToolCallDisplay` components to render media
- Introduced `ImagePreviewProvider` example demonstrating dynamic image rendering
- Enhanced documentation on provider media capabilities
- Updated integration and unit tests to cover new media functionality
2026-05-20 20:03:01 +01:00
MervinPraison 5429a287e7 feat(frontend, docs): add A2UI surfaces and chat-canvas integration
- Implemented A2UI surfaces for agent-driven UI in chat/canvas.
- Enhanced documentation for architecture and agent UI features.
- Introduced new example demonstrating A2UI canvas integration.
- Updated various frontend components to support new features.
- Improved tests for A2UI and related functionalities.
2026-05-20 15:55:45 +01:00
MervinPraison a4167b1477 feat(video): add Video Studio feature with integration and examples
Introduced Video Studio integration with PraisonAI Video engine.
Added documentation, example app, and multiple backend routes for project management, scene processing, and rendering.
Updated version in pyproject.toml for release.
2026-05-20 12:11:11 +01:00
MervinPraison 0923aac53a feat(docs): add documentation for modular dashboard shell feature
This commit introduces comprehensive documentation for the Agent UI host, detailing the architecture, quick start guide, extension API, layout components, optional dashboard modules, and third-party plugin development. Additionally, it includes a minimal example of an external agent hosted on the PraisonAIUI dashboard shell.
2026-05-19 21:27:40 +01:00
Mervin PraisonandMervinPraison b589c6fce2 refactor(api): PraisonAI-native names for discovery, citations, reasoning types (#52)
Rename the six public dataclasses shipped in v0.3.113 so none of them are
exact-name lifts from an external framework.  Names are now PraisonAI-unique,
noun-first, non-developer-friendly, and pair with the existing ``*Prompt`` /
``ThinkingSteps`` PraisonAI style.

## Rename table

| v0.3.113 (lifted) | v0.3.114 (PraisonAI-native) |
|-------------------|-----------------------------|
| ModelInfo         | **ModelCard**               |
| AgentDetails      | **AgentCard**               |
| TeamDetails       | **TeamCard**                |
| Reference         | **SourceChunk**             |
| ReferenceData     | **SourceBundle**            |
| ReasoningStep     | **ThoughtStep**             |
| `references_event()`     | **`source_bundle_event()`** |
| `reasoning_step_event()` | **`thought_step_event()`**  |

## What stays the same

- **Wire format is unchanged.**  The SSE event ``type`` string is still
  ``"references"``, and the JSON payload still uses the keys ``query``,
  ``references``, ``time_ms`` so third-party chat frontends see the exact
  shape they expect.
- ``RunEvent`` and ``RunEventType`` keep their existing names — these
  pre-existed in PraisonAIUI before the competitor audit and are not lifts.
- ``RunEventType.REFERENCES = "references"`` enum variant is unchanged.
- All REST routes added in v0.3.113 are unchanged (``/teams``, etc.).

## Why a breaking rename (no aliases)

Keeping the lifted names even as silent aliases would defeat the stated
goal ("names unique for PraisonAI").  v0.3.113 was released <1 h before
this rename, so no external consumer can have taken a dependency yet.

## Tests

- Every existing test in ``tests/unit/test_protocol_parity.py`` updated
  to the new names — **all 15 pass**.
- **New regression guard** ``test_no_competitor_lifted_names_exported``
  fails loudly if any of the six lifted names ever reappear on the
  public module, preventing accidental reintroduction.

Full suite: **955 pass**, 4 skipped, 8 xfailed, 1 xpassed.  Ruff clean.

## Version
Bumps to 0.3.114.

Co-authored-by: MervinPraison <[email protected]>
2026-04-23 00:41:32 +01:00
159 changed files with 12067 additions and 2301 deletions

No files matched your search

+4 -2
View File
@@ -28,10 +28,12 @@ jobs:
run: ruff check src/praisonaiui
- name: Test with pytest
# Integration tests are environment-sensitive (LLM API keys,
# long-running flows). Unit tests are the contract CI guards.
# Unit tests are the primary CI contract; integration smoke validates backends.
run: pytest tests/unit -v --no-cov --timeout=60
- name: Integration smoke tests
run: pytest tests/integration/test_agentic_roundtrip.py tests/test_feature_sdk_backends.py -q --no-cov --timeout=120
docs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
+229 -78
View File
@@ -1,13 +1,16 @@
name: Claude Assistant
# Triggers:
# - issues:opened → lightweight triage (welcome comment + label)
# - issues:opened → lightweight triage (labels + auto-claude for owner)
# - issues:labeled → full implementation when `claude` label applied
# - issues:assigned → full implementation when issue assigned to Claude
# - issue_comment → full implementation when @claude mentioned (issues + PRs)
# - pull_request_review → full implementation when @claude mentioned in review
# - pull_request_review_comment → full implementation when @claude mentioned inline
#
# Escalate an issue with the `claude` label OR an @claude comment — not both.
# If both fire within 3 minutes, the comment trigger is skipped (dedupe job).
#
# Focus: CLAUDE only. Copilot and CodeRabbit are GitHub Apps that react to PRs
# automatically — no workflow file needed. auto-pr-comment.yml orchestrates the
# CodeRabbit → Copilot → Claude review chain for PRs.
@@ -22,19 +25,10 @@ on:
pull_request_review:
types: [submitted]
permissions:
contents: write
pull-requests: write
issues: write
actions: read
id-token: write
jobs:
# ================================================================
# Job 1 — Issue triage (runs on ANY new issue, including from external users)
# Posts a welcome comment. Does NOT execute Claude Code.
# To escalate to full implementation, a maintainer adds the `claude` label
# or mentions @claude in a comment — which triggers Job 2.
# Job 1 — Issue triage (runs on ANY new issue, including external users)
# Posts welcome comment + triage labels. Auto-adds `claude` for owner issues.
# ================================================================
issue-triage:
if: |
@@ -46,87 +40,262 @@ jobs:
issues: write
contents: read
steps:
- name: Post welcome comment
- name: Generate GitHub App Token
id: app-token
continue-on-error: true
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Resolve GitHub token
id: github-token
run: |
if [ -n "${{ steps.app-token.outputs.token }}" ]; then
echo "token=${{ steps.app-token.outputs.token }}" >> "$GITHUB_OUTPUT"
else
echo "token=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_OUTPUT"
fi
- name: Triage and acknowledge issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ steps.github-token.outputs.token }}
script: |
const body = [
'👋 Thanks for opening an issue in **PraisonAIUI**.',
'',
'A maintainer will triage this shortly. If you would like Claude to investigate and draft a fix, a maintainer can:',
'',
'- Apply the `claude` label to this issue, **or**',
'- Mention `@claude` in a comment with any additional instructions.',
'',
'Please make sure your issue includes:',
'',
'1. A clear description of the behaviour you expect vs what you see.',
'2. Minimal reproduction steps (code snippet or `aiui.template.yaml`).',
'3. Version: `python -c "import praisonaiui; print(praisonaiui.__version__)"`.',
'4. Any relevant log output.',
'',
'See [AGENTS.md](https://github.com/MervinPraison/PraisonAIUI/blob/main/AGENTS.md) for project conventions.'
].join('\n');
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const content = title + ' ' + body;
const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(
issue.author_association
);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
const labels = [];
if (content.includes('feature request') || content.includes('[feature')) {
labels.push('enhancement');
} else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) {
labels.push('bug');
} else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) {
labels.push('question');
}
if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) {
labels.push('security');
}
if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) {
labels.push('performance');
}
if (content.includes('documentation') || content.includes('docs')) {
labels.push('documentation');
}
if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) {
labels.push('javascript');
}
if (content.includes('frontend') || content.includes('react') || content.includes('vite')) {
labels.push('frontend');
}
if (content.includes('example') || content.includes('yaml')) {
labels.push('examples');
}
if (labels.length > 0) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels
});
}
if (!isOwner) {
await github.rest.issues.createComment({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: [
`👋 Thanks for opening this issue, @${issue.user.login}!`,
'',
'A maintainer will review this shortly. In the meantime:',
'- Make sure you\'ve included steps to reproduce (for bugs)',
'- Check [existing issues](https://github.com/MervinPraison/PraisonAIUI/issues) for duplicates',
'- Review [AGENTS.md](https://github.com/MervinPraison/PraisonAIUI/blob/main/AGENTS.md) for project conventions',
'',
'_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._'
].join('\n')
});
}
if (isOwner) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['claude']
});
}
# ================================================================
# Job 2 — Full Claude Code implementation
# Runs when:
# - A comment containing `@claude` is posted on an issue or PR
# - The `claude` label is added to an issue
# - The issue is assigned to a Claude bot
# - A PR review or review-comment contains `@claude`
# Triggers on: labeled `claude`, @claude comments, assignments
#
# Dedup: label + @claude comment on the same issue fires two events.
# Skip the comment trigger when `claude` was labelled within 3 minutes.
# ================================================================
claude-response:
env:
ISSUE_NUMBER_RESOLVED: ${{ github.event.issue.number || github.event.pull_request.number }}
claude-dedupe:
if: |
github.event.action != 'opened' &&
(github.event.action != 'labeled' || github.event.label.name == 'claude') &&
(github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) &&
(
!contains(github.actor, '[bot]') ||
github.actor == 'github-actions[bot]' ||
github.actor == 'praisonai-triage-agent[bot]'
) &&
github.actor != 'dependabot[bot]' &&
github.actor != 'cursor[bot]' &&
github.actor != 'renovate[bot]' &&
(
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && github.event.action == 'labeled' && contains(toJSON(github.event.label), 'claude')) ||
(github.event_name == 'issues' && github.event.action == 'assigned')
)
github.actor != 'renovate[bot]'
runs-on: ubuntu-latest
outputs:
proceed: ${{ steps.check.outputs.proceed }}
permissions:
issues: read
pull-requests: read
steps:
- name: Skip duplicate comment after fresh claude label
id: check
uses: actions/github-script@v7
with:
script: |
if (context.eventName !== 'issue_comment' || context.payload.issue?.pull_request) {
core.setOutput('proceed', 'true');
return;
}
const issue = context.payload.issue;
const hasClaude = (issue.labels || []).some(l => l.name === 'claude');
if (!hasClaude) {
core.setOutput('proceed', 'true');
return;
}
const events = await github.rest.issues.listEvents({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 30,
});
const windowMs = 3 * 60 * 1000;
const recentLabel = events.data.find(e =>
e.event === 'labeled' &&
e.label?.name === 'claude' &&
(Date.now() - new Date(e.created_at).getTime()) < windowMs
);
if (recentLabel) {
core.info('Skipping: claude label was added within 3 minutes — label trigger already handles this issue.');
core.setOutput('proceed', 'false');
return;
}
core.setOutput('proceed', 'true');
claude-response:
needs: claude-dedupe
env:
ISSUE_NUMBER_RESOLVED: ${{ github.event.issue.number || github.event.pull_request.number }}
if: needs.claude-dedupe.outputs.proceed == 'true'
concurrency:
group: claude-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: read
id-token: write
steps:
- name: Generate GitHub App Token
id: app-token
continue-on-error: true
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Resolve GitHub token
id: github-token
run: |
if [ -n "${{ steps.app-token.outputs.token }}" ]; then
echo "token=${{ steps.app-token.outputs.token }}" >> "$GITHUB_OUTPUT"
else
echo "token=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_OUTPUT"
fi
- name: Check for Fork PR
id: check_fork
uses: actions/github-script@v7
with:
github-token: ${{ steps.github-token.outputs.token }}
script: |
const isPR = context.eventName === 'pull_request' || (context.payload.issue && context.payload.issue.pull_request);
if (isPR) {
const prNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
core.setOutput('pr_branch', pr.data.head.ref);
if (pr.data.head.repo && pr.data.head.repo.full_name !== context.repo.full_name) {
core.setOutput('is_fork', 'true');
core.setOutput('clone_url', pr.data.head.repo.clone_url);
core.setOutput('branch', pr.data.head.ref);
return;
}
}
core.setOutput('is_fork', 'false');
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.event.issue.pull_request && format('refs/pull/{0}/head', github.event.issue.number) || github.event.repository.default_branch }}
token: ${{ steps.github-token.outputs.token }}
- name: Fetch PR branch and Setup Remote
if: github.event.issue.pull_request
env:
PR_BRANCH: ${{ steps.check_fork.outputs.pr_branch }}
IS_FORK: ${{ steps.check_fork.outputs.is_fork }}
run: |
git fetch origin pull/${{ github.event.issue.number }}/head:"${PR_BRANCH}"
if [ "${IS_FORK}" = "true" ]; then
git remote set-url origin file://$(pwd)
fi
- name: Run Claude Code Action
uses: anthropics/claude-code-action@beta
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.github-token.outputs.token }}
with:
allowed_bots: 'praisonai-triage-agent[bot]'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ steps.github-token.outputs.token }}
trigger_phrase: "@claude"
label_trigger: "claude"
allowed_bots: "praisonai-triage-agent[bot]"
timeout_minutes: "30"
direct_prompt: |
${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK. YOU DO NOT HAVE PUSH PERMISSIONS TO THIS REPOSITORY. ONLY READ FILES, VALIDATE CODE, AND PROVIDE YOUR COMMENTS/FEEDBACK DIRECTLY IN THIS PR VIA COMMENTS. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || 'NOTE: The branch is under MervinPraison/PraisonAIUI (not a fork). You are able to make modifications and push directly to this branch.\n\n' }}
You are implementing changes in **MervinPraison/PraisonAIUI** (YAML-driven server-driven UI with agentic chat).
Follow `AGENTS.md` at the repository root. If a conflict arises between this prompt and `AGENTS.md`, `AGENTS.md` wins.
═══════════════════════════════════════════════════════════════
STEP 0 — GIT & GITHUB CLI
═══════════════════════════════════════════════════════════════
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "MervinPraison"
git config --global user.email "454862+MervinPraison@users.noreply.github.com"
gh auth setup-git
═══════════════════════════════════════════════════════════════
@@ -204,14 +373,6 @@ jobs:
# 3. Import-time invariant (MUST stay under 200 ms cold):
python -c "import time, sys; t=time.time(); import praisonaiui; dt=(time.time()-t)*1000; heavy=[m for m in sys.modules if any(h in m for h in ['langchain','llama_index','mcp','slack','discord','botbuilder','openai.','anthropic.','mistralai','google.generativeai'])]; print(f'{dt:.1f}ms modules={len(sys.modules)} leaked={heavy}'); assert heavy == [], f'Heavy deps leaked: {heavy}'"
# 4. Real agentic end-to-end test (write this into tests/integration/
# if your feature changes the agent-facing API):
# from praisonaiagents import Agent
# import praisonaiui as aiui
# agent = Agent(name="demo", instructions="You are a helpful assistant")
# result = agent.start("Say hello in one sentence")
# print(result)
If anything fails, fix the root cause (never skip, xfail, or delete the test).
═══════════════════════════════════════════════════════════════
@@ -233,10 +394,6 @@ jobs:
<what changed in one paragraph>
## Before / After
<copy-pasteable snippet per public API change>
## Acceptance criteria
<copy from the issue; tick boxes only with commit SHA + file path evidence>
@@ -246,12 +403,6 @@ jobs:
\`\`\`
<paste pytest output>
\`\`\`
## Import-time proof
\`\`\`
<paste the python -c import-time output>
\`\`\`
"
fi
@@ -0,0 +1,32 @@
name: Sync Claude Secrets from PraisonAI
# GitHub does not allow reading secret values back via API.
# This workflow triggers the sync job in MervinPraison/PraisonAI, which copies:
# CLAUDE_APP_ID, CLAUDE_APP_PRIVATE_KEY, CLAUDE_CODE_OAUTH_TOKEN, GH_TOKEN
#
# Requires SECRETS_ADMIN_PAT in PraisonAI (repo Settings → Secrets).
# If sync fails, copy secrets manually:
# PraisonAI → Settings → Secrets → Actions
# PraisonAIUI → Settings → Secrets → Actions (same names)
on:
workflow_dispatch:
permissions:
contents: read
jobs:
trigger-sync:
runs-on: ubuntu-latest
steps:
- name: Trigger PraisonAI secret sync
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run sync-secrets-to-aiui.yml \
--repo MervinPraison/PraisonAI \
--ref main
echo "Triggered: https://github.com/MervinPraison/PraisonAI/actions/workflows/sync-secrets-to-aiui.yml"
echo ""
echo "If sync fails (401/403), refresh SECRETS_ADMIN_PAT in PraisonAI,"
echo "or copy CLAUDE_APP_ID + CLAUDE_APP_PRIVATE_KEY + GH_TOKEN manually."
+2 -1
View File
@@ -55,4 +55,5 @@ aiui-output/
_site
.praisonai
tmp/
tmp/
*.db
+6 -3
View File
@@ -111,9 +111,12 @@ YAML config → Pydantic models → Compiler → JSON manifests → React SPA
### Frontend Build
1. Source in `src/frontend/src/` (React 19 + TypeScript + Vite)
2. Build: `cd src/frontend && npm run build`
3. Output: `src/frontend/dist/`
4. Sync to templates: copy `dist/{index.html,assets/,icon.svg}` → `src/praisonaiui/templates/frontend/`
5. Compiler copies templates to output dir during `aiui build`
3. Optional A2UI renderer (full agent-driven UI catalog): `npm install @a2ui/react @a2ui/web_core`
4. Output: `src/frontend/dist/`
5. Sync to templates: copy `dist/{index.html,assets/,icon.svg}` → `src/praisonaiui/templates/frontend/`
6. Compiler copies templates to output dir during `aiui build`
Chat inline media (agent-generated images): `RunEventType.MESSAGE_ELEMENT` — see `docs/features/image-preview-chat.md`
## Gotchas
+9 -6
View File
@@ -16,7 +16,8 @@ graph TD
GW["WebSocketGateway<br/>REST + WebSocket<br/>Bot management"]
end
CHAT -->|"AG-UI protocol<br/>(SSE events)"| AGUI
CHAT -->|"WebSocket / SSE<br/>(RunEvent protocol)"| GW
CHAT -.->|"AG-UI (optional)<br/>POST /agui"| AGUI
DASH -->|"REST API<br/>(/api/channels, /api/nodes)"| GW
```
@@ -110,7 +111,7 @@ sequenceDiagram
## Using Both Together
`AIUIGateway.start()` in `integration.py` mounts both layers on the same server:
`AIUIGateway.start()` in `integration.py` mounts the gateway and dashboard REST API on one server. Chat uses the built-in WebSocket/SSE `RunEvent` protocol; AG-UI is mounted separately via `AguiFeature` when enabled:
```python
from praisonaiui.integration import AIUIGateway
@@ -121,10 +122,12 @@ agent = Agent(name="assistant", instructions="You are helpful.")
gateway.register_agent(agent)
await gateway.start()
# Chat: POST /agui → AG-UI streaming (via AGUI class)
# Admin: GET /api/channels → Gateway REST API
# Admin: GET /api/nodes → Gateway REST API
# WS: /ws → WebSocket for real-time updates
# Chat: POST /run, WS /api/chat/ws → RunEvent streaming (built-in)
# AG-UI: POST /agui, GET /agui/status → CopilotKit-compatible SSE (AguiFeature)
# Admin: GET /api/channels → Gateway REST API
# Admin: GET /api/nodes → Gateway REST API
# WS: /ws → Legacy gateway WebSocket
# A2UI: GET/POST /api/surfaces/* → Agent-driven surfaces (SurfaceFeature)
```
## Summary
+30 -1
View File
@@ -108,6 +108,34 @@ class EchoProvider(BaseProvider):
yield RunEvent(type=RunEventType.RUN_COMPLETED, content=f"Echo: {message}")
```
### Inline media (images, video, files)
Any provider can attach rich media to chat messages using ``MESSAGE_ELEMENT``
events. The UI renders them inline and persists them in session history.
```python
class ImageProvider(BaseProvider):
async def run(self, message, *, session_id=None, agent_name=None, **kw):
yield RunEvent(type=RunEventType.RUN_STARTED)
# ... call your image API (OpenAI, Stability, custom, etc.) ...
image_url = "https://example.com/generated.png"
yield BaseProvider.message_element_event({
"type": "image",
"url": image_url,
"alt": "Generated image",
})
yield RunEvent(type=RunEventType.RUN_COMPLETED, content="Here is your image.")
```
Tool results from PraisonAI agents are also detected automatically when they
match OpenAI image shapes (``{"data": [{"url": "..."}]}``) or explicit element
dicts — no provider code required.
Callback handlers can use ``Message.add_image()`` or ``await aiui.image(url)``
and the default ``PraisonAIProvider`` maps those to ``MESSAGE_ELEMENT`` events.
Full architecture, data flows, and tests: [Image Preview in Chat](../features/image-preview-chat.md).
### With Tool Calls and Reasoning
```python
@@ -155,7 +183,7 @@ Every event has a `type` field and optional payload fields:
| `step` | `str` | `REASONING_STEP` |
| `error` | `str` | `RUN_ERROR` |
| `agent_name` | `str` | Multi-agent events |
| `extra_data` | `dict` | Custom payload |
| `extra_data` | `dict` | Custom payload (includes `element` for `message_element`) |
### Event Types (27 total)
@@ -178,6 +206,7 @@ Every event has a `type` field and optional payload fields:
| `updating_memory` | Memory being updated |
| `run_paused` | Run paused (e.g. waiting for user) |
| `run_continued` | Run resumed |
| `message_element` | Inline media element (image, video, file, etc.) — see [Image Preview in Chat](../features/image-preview-chat.md) |
=== "Team Events"
+242
View File
@@ -0,0 +1,242 @@
# Agent UI host — modular dashboard shell
PraisonAIUI can host any AI agent backend behind a **modular dashboard shell**: pages, views, layout components, optional plugins, and branding — without forking the frontend.
## Architecture
```text
YAML / Python config → server (pages API, plugins.json) → dashboard.js shell
↓
registerView / BUILTIN_VIEWS / layout JSON / registerComponent
↓
renderComponent() + theme (--db-*)
```
**Extension priority** (first match wins):
1. `window.aiui.registerView(pageId, …)`
2. Dashboard manifest plugins (`dashboard-plugins/*/manifest.json`)
3. Built-in view modules (`plugins/views/*.js` via `BUILTIN_VIEWS`)
4. Server `aiui.layout()` JSON from `@aiui.page()`
5. `window.aiui.registerComponent(type, …)` inside `renderComponent`
6. Generic JSON fallback viewer
## Video Studio
For YAML scene authoring with an external PraisonAI Video engine, see [Video Studio](video-studio.md).
## Quick start (external agent)
```python
import praisonaiui as aiui
from praisonaiui.server import create_app
aiui.set_style("dashboard")
aiui.set_branding(title="Acme Agents", logo="A")
aiui.set_pages(["chat", "tasks", "sessions", "config"])
aiui.set_dashboard(modules=["jobs"], sidebar=True, page_header=True)
@aiui.page("tasks", title="Tasks", icon="📋", group="Work")
async def tasks_page():
return aiui.layout([
aiui.board(columns=[
{"id": "todo", "title": "Todo", "cards": [aiui.card("Review PR", footer="agent-a")]},
{"id": "done", "title": "Done", "cards": [aiui.card("Ship feature")]},
]),
])
app = create_app()
```
See [`examples/python/external-agent-dashboard/app.py`](../../examples/python/external-agent-dashboard/app.py).
## `window.aiui` extension API
| API | Purpose |
|-----|---------|
| `version` | Shell protocol version (`'1'`) |
| `registerView(pageId, renderFn, cleanup?)` | Replace or add a full page renderer |
| `registerComponent(type, renderFn)` | Override a layout component type |
| `registerSurfaceRenderer(surfaceId, renderFn)` | Custom A2UI surface renderer |
| `selectPage(pageId)` | Navigate programmatically (e.g. open full canvas) |
| `registerSlot(name, renderFn)` | Inject DOM into named slots |
| `sdk.fetchJSON(url, opts?)` | Authenticated fetch helper |
| `sdk.el(tag, attrs?, children?)` | Small DOM builder |
| `sdk.themeVar('--db-*', fallback?)` | Read theme CSS variables |
| `sdk.createBoard(root, { fetch, pollMs? })` | Live board with optional polling |
### Slots
| Slot id | Where it renders |
|---------|------------------|
| `shell:sidebar:footer` | Bottom of sidebar (default: health hint) |
| `page:{pageId}:toolbar` | Right side of page header |
Register only what you need; omit slots you do not use.
## Layout components (Python → JS)
Define pages with `@aiui.page()` returning `aiui.layout([...])`. Types are implemented once in [`ui.py`](../../src/praisonaiui/ui.py) and [`dashboard.js`](../../src/praisonaiui/templates/frontend/plugins/dashboard.js).
**Kanban / board:** use `aiui.board(columns=[...])`. Columns contain `id`, `title`, and `cards` (from `aiui.card()` or dicts). The shell renders via `renderBoard`, composing existing card renderers (DRY).
## A2UI surfaces (agent-driven UI)
Three tiers work together:
| Tier | Mechanism | Use case |
|------|-----------|----------|
| Dashboard | `aiui.layout()` → `_components` | Python-authored pages |
| A2UI | `send_a2ui_messages` → `SurfaceHost` | Agent-generated UI in chat/canvas |
| AG-UI | `POST /agui` SSE | CopilotKit / external clients |
Register an empty canvas page:
```python
@aiui.page("agent-canvas", title="Agent Canvas", icon="🖼️")
async def agent_canvas():
return {"_surface": {"id": "main", "messages": []}}
```
Or use `@aiui.surface_action("main")` for button callbacks. See [`examples/python/29-a2ui-canvas/`](../../examples/python/29-a2ui-canvas/app.py).
### Chat + Canvas same-window preview
Three dashboard modes:
| Page | Route | Purpose |
|------|-------|---------|
| `chat-canvas` | `/chat-canvas` | **Split view** — chat left, live A2UI preview right |
| `chat` | `/chat` | Vanilla chat only (unchanged) |
| `canvas` | `/canvas` | Full-page surface workspace |
Enable preview settings and list the combined page:
```python
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas"])
```
`/ui-config.json` exposes `chat.preview: { enabled, surfaceId, width }` for the preview panel.
Shared modules: `surface-utils.js` (render, load, WebSocket, actions), `canvas-preview.js` (preview panel), `a2ui-mapper.js` (default DOM mapper). Override rendering with `registerSurfaceRenderer("main", fn)`.
## Optional dashboard modules
Load heavy built-ins only when listed:
```python
aiui.set_dashboard(modules=["jobs", "auth", "api"])
```
```yaml
dashboard:
modules: [jobs]
```
This adds `jobs.js`, `auth.js`, or `api.js` to the plugin chain and maps them in `BUILTIN_VIEWS`. `feedback` is always available as a built-in view when the page is enabled.
## Third-party dashboard plugins
Place folders under:
- `~/.praisonai/dashboard-plugins/{name}/`
- Or `src/praisonaiui/templates/frontend/dashboard-plugins/{name}/` (shipped examples)
Each plugin needs `manifest.json` + `index.js` (optional `styles.css`):
```json
{
"name": "my-board",
"label": "My board",
"icon": "📋",
"tab": { "path": "/my-board" },
"entry": "index.js"
}
```
The server exposes:
- `GET /api/dashboard/plugins` — manifest list
- `GET /dashboard-plugins/{name}/{path}` — static assets
Plugins register views in `index.js`:
```javascript
window.aiui.registerView('my-board', async (container) => {
window.aiui.sdk.createBoard(container, {
fetch: async () => ({ columns: [...] }),
});
});
```
Sample: [`sample-board`](../../src/praisonaiui/templates/frontend/dashboard-plugins/sample-board/).
## Agent plugins vs dashboard plugins
| System | Location | Purpose |
|--------|----------|---------|
| Agent plugins | `~/.praisonai/plugins/` | Python agent extensions |
| Dashboard plugins | `~/.praisonai/dashboard-plugins/` | JS UI tabs |
See [PraisonAI package integration](praisonai-package-integration.md) for backend wiring, jobs API, and deployment.
## Granular control — opt-in / opt-out
| What you control | How | Example |
|------------------|-----|---------|
| UI mode | `aiui.set_style(...)` | `"dashboard"`, `"chat"`, `"docs"` |
| Sidebar pages | `aiui.set_pages([ids])` whitelist | `["chat", "sessions"]` |
| Hide built-ins | YAML `pages.disabled: [...]` | Blacklist in server |
| Sidebar on/off | `set_dashboard(sidebar=False)` | Chat-only full width |
| Page title bar | `set_dashboard(page_header=False)` | Cleaner surface |
| Optional modules | `set_dashboard(modules=[...])` | `["jobs"]` only |
| Third-party tabs | Install chosen `dashboard-plugins/` folders | Manifest per plugin |
| Shell chrome | `registerSlot` | Header/footer widgets |
| Look only | `set_theme`, `set_branding`, `set_custom_css` | White-label |
| Replace one page | `registerView(pageId, ...)` | Override built-in |
| Board UI | `aiui.board()` or plugin | Not in default nav |
**Note:** `@aiui.page()` custom pages are **not** filtered by `set_pages()` — only built-in dashboard pages are. Remove custom pages from code to hide them.
### Example profiles
```python
# Chat-only
aiui.set_style("dashboard")
aiui.set_pages(["chat"])
aiui.set_dashboard(sidebar=False, page_header=False)
# Ops console — no chat
aiui.set_pages(["sessions", "agents", "logs", "config", "usage", "approvals"])
```
```yaml
style: dashboard
pages:
enabled: [chat, agents, memory, sessions]
dashboard:
sidebar: true
pageHeader: true
modules: [jobs]
plugin_dirs: [/opt/my-agent/plugins]
```
CLI: `aiui pages ids` lists built-in page ids for whitelists.
## DRY rules for contributors
1. Add new layout types in **`ui.py`** and one `case` in **`dashboard.js`**.
2. Page-specific UI belongs in **`plugins/views/*.js`**, delegating to shared modules (`jobs.js`, etc.) via thin wrappers.
3. Reuse **`_helpers.js`** (`pageToolbar`, `filterChips`, `searchInput`, `modalShell`, `toast`).
4. Boards compose **`card`** / **`columns`** — do not duplicate card markup in plugins.
## Related files
| File | Role |
|------|------|
| [`praisonai-package-integration.md`](praisonai-package-integration.md) | Praison backend wiring |
| [`server.py`](../../src/praisonaiui/server.py) | Pages API, `set_dashboard`, plugin chain |
| [`dashboard_plugins.py`](../../src/praisonaiui/dashboard_plugins.py) | Manifest discovery and static routes |
| [`dashboard.js`](../../src/praisonaiui/templates/frontend/plugins/dashboard.js) | Shell, registries, renderers |
| [`schema/models.py`](../../src/praisonaiui/schema/models.py) | `DashboardConfig.modules`, `plugin_dirs` |
+25
View File
@@ -0,0 +1,25 @@
# Backend Integration Contract
PraisonAIUI supports a four-layer integration model so standalone mode and SDK-integrated mode can share the same feature APIs.
## Four Layers
1. **Feature API layer**
Endpoints live in feature modules and expose stable HTTP/CLI behaviour.
2. **Feature manager layer**
Each feature manager keeps local fallback logic for standalone operation.
3. **Backend factory layer**
`praisonaiui.backends` provides optional injected factories (`get_workflow_runner()`, `get_hooks_lister()`, `get_usage_query()`, etc.).
4. **SDK/runtime layer**
PraisonAI or praisonaiagents implementations are used when injected or importable.
## Injection Contract
- Injected backends are optional and must be callable.
- Features must keep local fallbacks when injected backends are absent or fail.
- Backend call signatures should remain minimal and dict/list based.
- Integration should not introduce hard imports in feature modules when a fallback already exists.
## Operational Rule
When integrated, wrappers should inject backend callables at startup; when standalone, features should run without any backend injection.
+2 -2
View File
@@ -129,7 +129,7 @@
| Android app | 🔴 | 🟢 | |
| Voice Wake (wake word) | 🔴 | 🟢 | macOS + iOS |
| Talk Mode (voice chat) | 🔴 | 🟢 | ElevenLabs + system TTS |
| Canvas (visual workspace) | 🔴 | 🟢 A2UI | Agent-driven UI |
| Canvas (visual workspace) | 🟡 A2UI surfaces | 🟢 A2UI | Agent-driven UI via `send_a2ui_messages` |
| Device pairing | 🔴 | 🟢 Bonjour | iOS/Android → Gateway |
| PWA support | 🔴 | 🔴 | Neither has PWA |
| i18n / localization | 🔴 | 🔴 | Neither has i18n |
@@ -187,7 +187,7 @@
| **Skills marketplace** | 13,000+ vs 0 | 🔴 Critical — massive ecosystem moat |
| **Native apps** | macOS/iOS/Android vs web-only | 🟡 High — device presence |
| **Voice** | Wake word + Talk Mode vs none | 🟡 High — hands-free interaction |
| **Canvas (A2UI)** | Agent-driven visual workspace vs none | 🟡 Medium |
| **Canvas (A2UI)** | Agent-driven visual workspace vs partial parity | 🟡 Medium |
| **Agent-to-agent** | Sessions-based messaging vs none | 🟡 Medium |
### Strategic Positioning
+171
View File
@@ -0,0 +1,171 @@
# Image Preview in Chat
Protocol-driven inline media for agent-generated images (and other element types) during chat streaming. Any backend that implements `BaseProvider` can emit images without PraisonAI-specific code in chat or the frontend.
## Problem This Solves
Before this feature, PraisonAIUI could **render** images (`Message.add_image`, `ElementRenderer`) but the **wire protocol dropped media** during agent runs. ImageAgent / tool results appeared as JSON in the tool-call panel instead of inline `<img>` previews.
## Architecture
Modular layers — same pattern as [A2UI surfaces](./agent-ui-host.md):
```mermaid
sequenceDiagram
participant Backend as Provider_or_Tool
participant Detect as media_utils.py
participant Protocol as RunEvent_MESSAGE_ELEMENT
participant Chat as chat.py
participant UI as chat.js_React
Backend->>Detect: tool result or callback payload
Detect->>Protocol: normalised element dict
Protocol->>Chat: broadcast message_element
Chat->>UI: WebSocket / SSE frame
UI->>UI: appendMediaElement / ElementRenderer
Chat->>Chat: persist elements in session history
```
### Layer responsibilities
| Layer | Module | Backend-agnostic? |
|-------|--------|-------------------|
| **Detection** | [`media_utils.py`](../../src/praisonaiui/media_utils.py) | Yes — normalises OpenAI, URL, b64, element dicts |
| **Protocol** | [`provider.py`](../../src/praisonaiui/provider.py) — `RunEventType.MESSAGE_ELEMENT` | Yes |
| **Adapter** | [`providers/__init__.py`](../../src/praisonaiui/providers/__init__.py) — PraisonAI bridge only | No — maps SDK/callback queue → protocol |
| **Transport** | [`chat.py`](../../src/praisonaiui/features/chat.py) — broadcast + persist | Yes |
| **Storage** | [`attachments.py`](../../src/praisonaiui/features/attachments.py) — `GET /api/chat/media/{id}` | Yes — reuses attachment store for b64 |
| **UI** | [`chat.js`](../../src/praisonaiui/templates/frontend/plugins/views/chat.js), [`ElementRenderer`](../../src/frontend/src/chat/MultimediaElements.tsx) | Yes — renders `{type, url, alt}` |
Detection and UI do **not** import PraisonAI. Only the provider adapter knows ImageAgent / OpenAI `{data: [{url}]}` shapes.
## Wire protocol
### Event type
```
type: "message_element"
element: { "type": "image", "url": "...", "alt": "..." }
session_id: "..."
run_id: "..."
```
Defined in Python as `RunEventType.MESSAGE_ELEMENT` and mirrored in [`types.ts`](../../src/frontend/src/types.ts).
### Helper (any provider)
```python
from praisonaiui.provider import BaseProvider, RunEvent, RunEventType
yield BaseProvider.message_element_event({
"type": "image",
"url": "https://example.com/out.png",
"alt": "Generated image",
})
```
Element shape reuses the existing [message element schema](./message-elements.md) (`image`, `pdf`, `video`, `audio`, `file`, `code`).
## Data flow paths
### 1. Custom provider (swappable backend)
```python
import praisonaiui as aiui
from praisonaiui.provider import BaseProvider, RunEvent, RunEventType
class MyImageProvider(BaseProvider):
async def run(self, message, *, session_id=None, agent_name=None, **kw):
yield RunEvent(type=RunEventType.RUN_STARTED)
# Call any image API here (OpenAI, Stability, local, etc.)
yield BaseProvider.message_element_event({
"type": "image",
"url": image_url,
"alt": "Generated image",
})
yield RunEvent(type=RunEventType.RUN_COMPLETED, content="Here is your image.")
aiui.set_provider(MyImageProvider())
```
Example app: [`examples/python/30-image-preview/app.py`](../../examples/python/30-image-preview/app.py).
### 2. Callback handler (`@aiui.reply`)
```python
import praisonaiui as aiui
from praisonaiui import Message
@aiui.reply
async def on_message(msg):
m = Message(content="Here is your image:")
m.add_image("https://example.com/chart.png", alt="Chart")
await m.send()
```
`PraisonAIProvider` maps queue events (`image`, `message` + `elements`) → `MESSAGE_ELEMENT`.
### 3. Tool results (automatic detection)
When a tool completes, `tool_completed_extra()` in [`a2ui_utils.py`](../../src/praisonaiui/a2ui_utils.py) calls `build_media_extra()` from `media_utils`. Supported shapes:
| Input shape | Example |
|-------------|---------|
| OpenAI / ImageAgent | `{"data": [{"url": "...", "b64_json": "...", "revised_prompt": "..."}]}` |
| Explicit element | `{"type": "image", "url": "..."}` |
| Plain URL string | `"https://cdn.example.com/out.png"` |
| Data URL | `"data:image/png;base64,..."` |
| Nested elements | `{"elements": [{"type": "video", "url": "..."}]}` |
`chat.py` broadcasts `message_element` for each detected element and attaches `elements` to the persisted assistant message.
### 4. ImageAgent direct mode
If the registered agent's `chat()` returns an OpenAI-shaped dict, `PraisonAIProvider._run_direct_mode` runs `extract_media_elements()` and emits `MESSAGE_ELEMENT` events before `RUN_COMPLETED`.
## Base64 storage
Large `b64_json` payloads are **not** stored inline in message history. They are decoded and saved via `AttachmentManager`, then served at:
```
GET /api/chat/media/{attachment_id}
```
If storage fails, the system falls back to a `data:image/png;base64,...` URL.
## Frontend rendering
| Surface | Handler |
|---------|---------|
| Dashboard chat (template) | `chat.js` — `case 'message_element': appendMediaElement()` |
| React chat | `streamingStore.ts` — `pendingElements` + `ElementRenderer` |
| Tool call panel | Thumbnail from `toolCall.elements` when expanded |
| Session reload | `elements` on messages from `/api/chat/history` and `/sessions/{id}/runs` |
See also [Message Elements](./message-elements.md) for element properties and display modes.
## Deduplication
`_collect_element()` in `chat.py` dedupes by URL so the same image is not stored twice when both a `MESSAGE_ELEMENT` event and a tool completion carry the same URL.
## Out of scope
- **REST image generation** (`POST /v1/images/generations`) — separate OpenAI-compat endpoint; not wired into chat streaming
- **Canvas / A2UI surfaces** — separate feature for agent-driven UI catalogs
- **Auto-registering ImageAgent** — agents are configured explicitly by the user
## Tests
```bash
pytest tests/unit/test_media_utils.py \
tests/integration/test_chat_image_preview.py -v -o addopts=
```
Coverage includes: detection normalisation, b64 fallback, provider callback bridge, `_run_and_broadcast` persistence, media serve route, ImageAgent dict responses, URL dedup.
## Related docs
- [Providers](../concepts/providers.md) — provider protocol and inline media snippet
- [Message Elements](./message-elements.md) — element types and Python API
- [Attachments](./attachments.md) — user file uploads (separate from generated media serve path)
- [Gateway Chat](./gateway-chat.md) — WebSocket event flow
+7 -1
View File
@@ -172,4 +172,10 @@ The new element system maintains backward compatibility with existing message pr
- `message.files` (still supported)
- New `message.elements` takes precedence when both are present
This ensures existing code continues to work while providing a migration path to the new standardized element system.
This ensures existing code continues to work while providing a migration path to the new standardized element system.
## Agent-generated images (streaming)
When an agent or tool **creates** an image during a chat run, the UI receives `message_element` events over WebSocket/SSE and renders them inline. This uses the same element dict shape as above but is driven by the [RunEvent protocol](../concepts/providers.md), not only `Message.send()`.
See **[Image Preview in Chat](./image-preview-chat.md)** for the full architecture: detection (`media_utils`), provider bridge, persistence, and swappable backends.
@@ -0,0 +1,192 @@
# PraisonAI package integration
How **PraisonAIUI** (`aiui`) connects to **`praisonai-package`** — the agent runtime, gateway, and CLI. For generic UI hosting see [Agent UI host](agent-ui-host.md).
## Division of labour
| Layer | Repo | Role |
|-------|------|------|
| Agent runtime | `praisonai-agents` | `Agent`, tools, memory, streaming |
| Gateway, jobs, CLI | `praisonai` | WebSocket `/ws`, `/api/v1/runs`, `praisonai claw` |
| Dashboard UI | **PraisonAIUI** | `dashboard.js`, `@aiui.page`, layout protocol |
| Work tracking | `praisonai-platform` | Issues API (optional board data source) |
Canonical app: `praisonai-package` → `src/praisonai/praisonai/claw/default_app.py`.
## Deployment shapes
### 1. Single process — `praisonai claw` (recommended)
```bash
praisonai claw # → aiui run claw/default_app.py, default :8082
```
```text
Browser → create_app() (same process)
→ PraisonAISessionDataStore (sessions)
→ PraisonAIProvider (@aiui.reply / agents)
→ Built-in dashboard views
```
Use when: local dev, full dashboard, agents registered in Python.
### 2. Unified — `AIUIGateway`
```python
from praisonaiui.integration import AIUIGateway
gateway = AIUIGateway(port=8080)
gateway.register_agent(agent)
await gateway.start() # create_app() + WebSocket /ws
```
Use when: you need **WebSocket gateway protocol** and dashboard on **one port**.
### 3. Split — gateway + UI
| Service | Default port | Endpoints |
|---------|--------------|-----------|
| `praisonai gateway start` | 8765 | `ws://…/ws`, `/health`, `/info` |
| `praisonai claw` or `aiui run` | 8082 | Dashboard, `/run` SSE, `/api/*` |
Use when: gateway runs headless; UI is a separate process. Point chat at gateway via provider config or run `AIUIGateway` instead.
## Wiring checklist
```python
import praisonaiui as aiui
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
aiui.set_datastore(PraisonAISessionDataStore())
aiui.set_style("dashboard")
aiui.set_pages(["chat", "agents", "sessions", "config"])
aiui.set_dashboard(modules=["jobs"], sidebar=True, page_header=True)
# Optional: point jobs UI at package jobs server paths
aiui.set_jobs_api(api_base="/api/v1/runs", backend="praisonai")
```
Or via host bootstrap:
```python
from praisonai.integration.host_app import configure_host, create_host_app
configure_host(pages=[...], modules=["jobs"], agents=[...])
app = create_host_app()
```
## API surface
### Chat and agents (dashboard)
| Endpoint | Purpose |
|----------|---------|
| `POST /run` | Stream agent run (SSE) |
| `GET /sessions`, `POST /sessions` | Session CRUD |
| `GET /agents`, `/api/agents/definitions` | Agent list / CRUD |
| `GET /api/health` | Sidebar health slot |
### Async jobs — two paths
| Backend | Base path | When |
|---------|-----------|------|
| **aiui** `JobsFeature` | `/api/jobs` | Default; in-process job store |
| **praisonai** jobs server | `/api/v1/runs` | Standalone `praisonai jobs` / FastAPI router |
Configure the dashboard jobs view:
```python
aiui.set_jobs_api(api_base="/api/jobs", backend="aiui") # default
aiui.set_jobs_api(api_base="/api/v1/runs", backend="praisonai")
```
`/ui-config.json` exposes `jobs: { apiBase, backend }` for `jobs.js`.
**Response shapes:** both return `{ jobs, total }`. Praison uses `job_id`; aiui uses `id`. The jobs view normalises either field.
| Action | aiui | praisonai |
|--------|------|-----------|
| List | `GET /api/jobs` | `GET /api/v1/runs` |
| Detail | `GET /api/jobs/{id}` | `GET /api/v1/runs/{id}` |
| Cancel | `POST /api/jobs/{id}/cancel` | `POST /api/v1/runs/{id}/cancel` |
| Stream | `GET /api/jobs/{id}/stream` | `GET /api/v1/runs/{id}/stream` |
| Delete | `DELETE /api/jobs/{id}` | Not on package router |
### REST invoke (automation)
| Path | Use |
|------|-----|
| `POST /api/v1/agents/{id}/invoke` | Sync invoke (n8n, scripts) — **package** `agent_invoke.py` |
| `POST /run` | Streaming chat — **dashboard** chat page |
Same agents if registered on both surfaces; different protocols.
### Discovery (package serve)
| Endpoint | Purpose |
|----------|---------|
| `GET /__praisonai__/discovery` | Serve-mode capability discovery |
| `GET /health`, `GET /info` | Gateway liveness and metadata |
| `GET /api/gateway/status` | aiui gateway feature status |
See **Feature Explorer** page in dashboard for live probes.
## Parallel UI protocols (not dashboard replacements)
Under `praisonaiagents/ui/`:
| Protocol | Purpose |
|----------|---------|
| **A2A** | Agent cards, `/.well-known/agent.json`, external agent hosts |
| **AG-UI** | CopilotKit-style event stream (`POST /agui`) |
| **A2UI** | Declarative surfaces in chat/canvas |
Dashboard shell remains the ops console for `praisonai claw`. Use A2A/AG-UI/A2UI when embedding in other products.
## Two plugin systems
| System | Location | Purpose |
|--------|----------|---------|
| **Agent plugins** | `~/.praisonai/plugins/` | Python agent extensions (`praisonaiagents.plugins`) |
| **Dashboard plugins** | `~/.praisonai/dashboard-plugins/` | JS tabs + `manifest.json` for aiui shell |
Do not mix folders. Agent plugins extend runtime; dashboard plugins extend UI tabs only.
## Optional modules
```python
aiui.set_dashboard(modules=["jobs", "auth", "api"])
```
Loads `jobs.js`, `auth.js`, `api.js` into the plugin chain and maps them in `BUILTIN_VIEWS`. Enable only what your deployment exposes.
## Board / work tracking
Neither repo ships Hermes-style Kanban. Options:
1. **`aiui.board()`** — columns from `@aiui.page` handler (jobs by status, custom data).
2. **`praisonai-platform` issues** — map issue `status` → board columns (see `examples/python/platform-board/`). Set `PRAISONAI_PLATFORM_URL` and `PRAISONAI_PLATFORM_WORKSPACE_ID` (default workspace `default`); issues are fetched from `GET /api/v1/workspaces/{workspace_id}/issues`.
3. **`dashboard-plugins`** — rich JS via `registerView` + `sdk.createBoard`.
Advanced board UX (drag-drop, WebSocket sync) is **deferred** — use polling via `sdk.createBoard({ pollMs })` or a custom plugin.
## Smoke checklist
After `praisonai claw` or `aiui run` with package bridges:
- [ ] `GET /api/health` → sidebar footer shows healthy
- [ ] Chat page sends message, streams reply
- [ ] Sessions page lists `~/.praisonai/sessions/` data
- [ ] Agents page CRUD (if agents registered)
- [ ] Jobs page (if `modules` includes `jobs` and jobs API configured)
- [ ] Explorer probes `/health`, `/ui-config.json`, `/api/dashboard/plugins`
- [ ] Auth/API pages (if `modules` includes `auth` / `api`)
## Related
- [Agent UI host](agent-ui-host.md) — extension API, opt-in matrix
- [Backend integration](backend-integration.md) — L1 backend injection
- [Standalone vs integrated](standalone-vs-integrated.md) — SDK gaps and fallbacks
- [PraisonAIUI repository](https://github.com/MervinPraison/PraisonAIUI)
- [`integration.py`](../../src/praisonaiui/integration.py) — `AIUIGateway`
- [`examples/python/praisonai-claw-board/`](../../examples/python/praisonai-claw-board/app.py) — board from jobs
- [`examples/python/platform-board/`](../../examples/python/platform-board/app.py) — board from platform issues
+53
View File
@@ -0,0 +1,53 @@
# Standalone vs Integrated
This page summarises the expected install and runtime shape for PraisonAIUI in both modes.
## Install Matrix
| Mode | Package install | Runtime dependency | Behaviour |
| --- | --- | --- | --- |
| Standalone UI | `pip install praisonaiui` | Optional SDK packages absent | Uses built-in local managers and fallbacks |
| Integrated (PraisonAI) | `pip install praisonaiui praisonai praisonaiagents` | SDK and wrapper available | Uses injected backends and SDK-aware managers |
## Backend injection (integrated mode)
When hosted via `praisonai.integration.host_app.configure_host()`, the wrapper calls `praisonaiui.backends.set_backend()` for:
| Backend key | Feature area |
| --- | --- |
| `hooks` | Hooks dashboard |
| `workflows` | Workflow runs |
| `usage_query` / `usage_sink` | Usage analytics |
| `approvals_pending` / `approvals_policies` | HITL approvals |
| `jobs_store` / `jobs_executor` | Async jobs (optional) |
| `channel_bot` | Channel bots (optional) |
| `tool_resolver` | Agent tool name resolution (optional) |
Detect integrated mode in code: `praisonaiui.backends.is_integrated_mode()`.
## Documented SDK gaps (local fallback)
These features work standalone with in-memory stores. Health responses include `sdk_gap: true`:
| Feature | Gap | Standalone behaviour |
| --- | --- | --- |
| **nodes** | No cluster API in praisonaiagents | In-memory node registry |
| **auth** | No SDK auth module | In-memory API keys / sessions |
| **channels** | Bot runtime via praisonai wrapper | In-memory channel registry; bots need `praisonai[bot]` |
| **jobs** | Mock success only when standalone and agent missing | Integrated mode fails job instead of fake success |
| **realtime** | WebRTC needs `OPENAI_API_KEY` | Mock stream standalone; degraded error when integrated without key |
| **media / code** | SDK paths optional | `status: simulated` standalone; `status: degraded` integrated on failure |
View gaps in the dashboard sidebar footer (`GET /api/health` → `sdk_gaps` array).
## Practical Differences
- **Session/data handling**: standalone uses UI datastore fallback; integrated aligns with SDK-backed stores.
- **Feature backends**: standalone keeps in-memory/mock behaviour; integrated routes through injected factories.
- **Lifecycle hooks**: integrated mode can subscribe to SDK hooks (handoff, MCP lifecycle, tracing/eval stores).
## Recommendation
Use standalone mode for local UI-only workflows. Use integrated mode when you need shared runtime state with PraisonAI agents, tools, tracing, approvals, and schedules.
See also [backend-integration.md](backend-integration.md) and [praisonai-package-integration.md](praisonai-package-integration.md).
+114
View File
@@ -0,0 +1,114 @@
# Video Studio
The Video Studio integrates [PraisonAI Video](https://github.com/MervinPraison/praisonai-video) as an external Node render engine. PraisonAIUI provides the dashboard shell, project files, YAML editor, job UX, and HTTP proxying. The video engine handles parse, compile, lint, and MP4 export. **Agents live in PraisonAI (Chat + tools), not inside the renderer.**
## Prerequisites
1. Install PraisonAI Video CLI (`praisonai-video`) or run from the monorepo.
2. Start the engine sidecar (recommended):
```bash
praisonai-video serve --port 3921
```
3. Run the example app:
```bash
aiui run examples/python/video-studio/app.py
```
Open the **Video** tab, create a project, edit `scene.yaml`, and use Lint / Render.
For **Chat** to edit scenes, install `praisonaiagents` and set `OPENAI_API_KEY`. Optional: `VIDEO_STUDIO_PROJECT_ID` to target a specific project.
## Configuration
| Variable | Default | Purpose |
|----------|---------|---------|
| `VIDEO_ENGINE_URL` | `http://127.0.0.1:3921` | HTTP API base URL |
| `VIDEO_ENGINE_TOKEN` | (none) | Optional Bearer token forwarded to the engine |
| `PRAISONAI_PROJECTS_DIR` | `~/.praisonai/projects` | On-disk project roots |
| `PRAISONAI_VIDEO_CLI` | (auto-detect) | Path to `praisonai-video` or `dist/cli.js` for subprocess fallback |
| `VIDEO_STUDIO_PROJECT_ID` | (auto) | Active project for agent tools when not passed explicitly |
| `PRAISONAI_MODEL` | `gpt-4o-mini` | LLM for Video Editor agent in the example app |
Python API:
```python
from praisonaiui.video_config import set_video_engine
set_video_engine(url="http://127.0.0.1:3921", projects_dir="~/my-videos")
```
## Projects
Each project is a directory containing:
- `scene.yaml` — scene definition
- `scene.visual-test.yaml` — optional visual regression spec
- `.praisonai/project.json` — metadata
- `exports/` — rendered MP4 artefacts
## API routes
All routes are under `/api/video/`:
- `GET /health` — sidecar status
- `POST /lint`, `POST /compile` — scene validation
- `GET /preview-url`, `POST /preview/start` — preview iframe URL (CLI; dashboard preview hidden)
- `POST /render` — async render job (`type: video_render`); optional `backend` (`playwright` | `remotion`) overrides `scene.yaml` `render.backend`
- `GET /jobs/{id}` — job status and download URL
- `GET|POST /projects` — list and create projects
- `GET /projects/{id}/studio-refresh` — poll after agent tool updates (`refresh: true` once)
When the HTTP sidecar is unavailable, lint and render fall back to subprocess calls to `praisonai-video`.
## Dashboard plugin
The UI lives in `dashboard-plugins/video-studio/` (built-in) or `~/.praisonai/dashboard-plugins/video-studio/` for overrides. It uses `window.aiui.sdk.fetchJSON`.
**Frame-by-frame composition preview is hidden** in the dashboard (`SHOW_COMPOSITION_PREVIEW = false`) until serve preview + seek are reliable. Use **Render MP4** and the **Your video** player for output. Set the flag to `true` in `index.js` to re-enable the optional iframe scrubber.
After agent tool writes, the editor reloads via `video-studio:refresh` (custom event) or polling `studio-refresh`.
## Agent workflow (PraisonAI-first)
1. Open **Chat** with the Video Editor agent (example app registers tools when `praisonaiagents` is installed).
2. Agent calls `video_get_scene` / `video_update_scene` / `video_lint_scene` / `video_render_project` (same outcomes as UI buttons).
3. Video Studio tab picks up changes automatically.
Tools are defined in `praisonaiui/video_agent_tools.py`. For Cursor/Codex outside the UI, use the skill at `skills/praisonai-video/SKILL.md` in the PraisonAI Video repo.
### Optional render backends
Default is **Playwright** (no `render:` block in `scene.yaml`). Use Remotion only when required:
```yaml
render:
backend: remotion # needs Remotion licence + remotion-root export; lint will error on default YAML export
```
`video_render_project(backend="playwright")` overrides YAML for one agent run. Hyperframes is deferred.
## CLI parity
The text box edits the same `scene.yaml` the CLI uses. Equivalent commands (from the video repo):
```bash
praisonai-video project create "My video"
praisonai-video project lint <project-id>
praisonai-video project preview <project-id>
praisonai-video project test <project-id>
praisonai-video project render <project-id>
praisonai-video project reset <project-id>
```
Lint editor text without saving: `praisonai-video lint --stdin < scene.yaml`
See `docs/integrations/praisonaiui.md` in the PraisonAI Video repo for the full mapping table.
## Related
- [Agent UI host](agent-ui-host.md) — extension patterns (`registerView`, plugins, `@aiui.page`)
- PraisonAI Video integration doc: `docs/integrations/praisonaiui.md` in the video repo
- PraisonAI Video skill: `skills/praisonai-video/SKILL.md` — YAML-first invariants for agents and CI
+88
View File
@@ -0,0 +1,88 @@
"""Example 29 — A2UI canvas and send_a2ui_messages integration.
Run:
python app.py
# Optional full Google A2UI renderer in React shell:
cd ../../../frontend && npm install @a2ui/react @a2ui/web_core && npm run build
"""
from __future__ import annotations
import praisonaiui as aiui
aiui.set_style("dashboard")
aiui.set_branding(title="A2UI Canvas Demo", logo="🖼️")
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas", "agent-canvas"])
@aiui.page("agent-canvas", title="Agent Canvas", icon="🖼️", group="A2UI")
async def agent_canvas_page():
return {"_surface": {"id": "main", "messages": []}}
@aiui.surface_action("main")
async def on_surface_action(action: dict):
return {"received": action}
def _build_assistant_agent():
from praisonaiagents import Agent
from praisonaiagents.tools.decorator import tool
from praisonaiagents.tools.a2ui_tools import send_a2ui_messages as sdk_send_a2ui
from praisonaiui.a2ui_utils import coerce_a2ui_tool_messages
@tool
def send_a2ui_messages(messages):
"""
Push A2UI v0.9 UI to the canvas. Always call this for buttons, layouts, forms.
Example:
send_a2ui_messages(messages=[
{"createSurface": {"surfaceId": "main", "catalogId": "basic"}},
{"updateComponents": {"components": [
{"component": "Text", "text": {"literal": "Mobile App Header"}},
{"component": "Button", "text": {"literal": "Submit"},
"action": {"name": "handleSubmit"}},
]}},
])
"""
coerced = coerce_a2ui_tool_messages(messages, surface_id="main")
return sdk_send_a2ui(messages=coerced)
role = (
"You are a UI builder. For ANY UI request you MUST call send_a2ui_messages "
"before replying — never claim UI was created without a successful tool call. "
"Use surface id 'main'. Prefer Column layouts for mobile app UIs."
)
try:
from praisonaiagents.ui.a2ui.adapter import generate_a2ui_system_prompt
instructions = generate_a2ui_system_prompt(
role_description=role,
workflow_description="Call send_a2ui_messages with valid A2UI JSON, then briefly confirm.",
ui_description="Dashboard canvas on surface main.",
)
except ImportError:
instructions = role
return Agent(name="assistant", instructions=instructions, tools=[send_a2ui_messages])
def create_app():
try:
agent = _build_assistant_agent()
aiui.register_agent("assistant", agent)
except ImportError:
pass
from praisonaiui.server import create_app as _create_app
return _create_app()
if __name__ == "__main__":
import uvicorn
uvicorn.run(create_app(), host="127.0.0.1", port=8099)
+51
View File
@@ -0,0 +1,51 @@
"""Image preview in chat — ImageAgent example.
Uses praisonaiagents ``ImageAgent`` with ``PraisonAIProvider`` so generated
images appear inline in chat via ``MESSAGE_ELEMENT`` events.
Requires:
pip install praisonaiagents litellm
export OPENAI_API_KEY=your-key
Optional:
export IMAGE_MODEL=gpt-image-2 # default; override if your project uses another image model
Run:
aiui run app.py
"""
import os
import praisonaiui as aiui
from praisonaiui.providers import PraisonAIProvider
try:
from praisonaiagents import ImageAgent
except ImportError as exc:
raise ImportError("Install praisonaiagents: pip install praisonaiagents") from exc
# Match other chat examples (25-clean-chat, 26-custom-theme-chat): dashboard shell
aiui.set_style("dashboard")
aiui.set_dashboard(sidebar=False, page_header=False)
aiui.set_branding(title="Image Studio", logo="🖼️")
aiui.set_theme(preset="blue", dark_mode=True, radius="lg")
aiui.set_pages(["chat"])
image_agent = ImageAgent(
name="Image Bot",
llm=os.environ.get("IMAGE_MODEL", "gpt-image-2"),
verbose=False,
)
# Legacy DALL-E params break newer OpenAI image endpoints
image_agent.image_config.response_format = None
image_agent.image_config.style = None
aiui.set_provider(PraisonAIProvider(agent=image_agent))
@aiui.starters
async def get_starters():
return [
{"label": "Sunset over mountains", "message": "A sunset over mountains", "icon": "🌄"},
{"label": "Robot portrait", "message": "A friendly robot portrait", "icon": "🤖"},
]
Binary file not shown.
@@ -0,0 +1,37 @@
"""Minimal external agent hosted on PraisonAIUI dashboard shell."""
import praisonaiui as aiui
from praisonaiui.server import create_app
aiui.set_style("dashboard")
aiui.set_branding(title="Acme Agents", logo="A")
aiui.set_pages(["chat", "tasks", "sessions", "config"])
aiui.set_dashboard(modules=["jobs"], sidebar=True, page_header=True)
@aiui.page("tasks", title="Tasks", icon="📋", group="Work", order=5)
async def tasks_page():
return aiui.layout(
[
aiui.board(
columns=[
{
"id": "todo",
"title": "Todo",
"cards": [
aiui.card("Review PR", footer="agent-a"),
aiui.card("Write docs", footer="agent-b"),
],
},
{
"id": "done",
"title": "Done",
"cards": [aiui.card("Modular shell", footer="shipped")],
},
]
),
]
)
app = create_app()
+43
View File
@@ -0,0 +1,43 @@
"""Board from praisonai-platform issues (optional separate service)."""
import os
import httpx
import praisonaiui as aiui
from praisonaiui.server import create_app
PLATFORM = os.getenv("PRAISONAI_PLATFORM_URL", "http://127.0.0.1:8000")
WORKSPACE_ID = os.getenv("PRAISONAI_PLATFORM_WORKSPACE_ID", "default")
aiui.set_style("dashboard")
aiui.set_pages(["issues-board"])
aiui.set_dashboard(sidebar=True)
@aiui.page("issues-board", title="Issues", icon="📌", group="Work")
async def issues_board():
issues = []
url = f"{PLATFORM.rstrip('/')}/api/v1/workspaces/{WORKSPACE_ID}/issues"
try:
async with httpx.AsyncClient(timeout=10.0) as c:
r = await c.get(url)
if r.status_code == 200:
d = r.json()
issues = d if isinstance(d, list) else d.get("issues") or d.get("items") or []
except Exception:
pass
by: dict[str, list] = {}
for i in issues:
s = (i.get("status") or "backlog").lower().replace(" ", "_")
by.setdefault(s, []).append(i)
columns = []
for sid in ("backlog", "in_progress", "done"):
cards = [aiui.card(i.get("title", "Issue"), footer=sid) for i in by.get(sid, [])]
columns.append({"id": sid, "title": sid.replace("_", " ").title(), "cards": cards or [aiui.card("(empty)")]})
return aiui.layout([
aiui.text(f"Platform: {PLATFORM} · workspace: {WORKSPACE_ID}"),
aiui.board(columns=columns),
])
app = create_app()
@@ -0,0 +1,36 @@
"""Board page from aiui jobs — status columns."""
import os
import praisonaiui as aiui
from praisonaiui.server import create_app
aiui.set_style("dashboard")
aiui.set_pages(["jobs-board"])
aiui.set_dashboard(modules=["jobs"], sidebar=True)
def _card(job: dict) -> dict:
title = (job.get("prompt") or job.get("id") or "Job")[:80]
return aiui.card(title, footer=job.get("status", ""))
@aiui.page("jobs-board", title="Jobs Board", icon="📋", group="Work")
async def jobs_board():
from praisonaiui.features.jobs import get_job_store
jobs = get_job_store().list_all()
cols = ["queued", "running", "succeeded", "failed"]
columns = []
for c in cols:
cards = [_card(j) for j in jobs if j.get("status") == c]
columns.append({"id": c, "title": c.title(), "cards": cards or [aiui.card("(empty)")]})
return aiui.layout([aiui.board(columns=columns)])
app = create_app()
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=int(os.getenv("PORT", "8082")))
+55
View File
@@ -0,0 +1,55 @@
"""Video Studio example — YAML scenes via PraisonAI Video engine sidecar."""
import os
import praisonaiui as aiui
from praisonaiui.server import create_app
from praisonaiui.video_config import set_video_engine
aiui.set_style("dashboard")
aiui.set_branding(title="Video Studio", logo="🎬")
aiui.set_pages(["chat", "video-studio", "jobs", "config"])
aiui.set_dashboard(modules=["jobs", "video"], sidebar=True, page_header=True)
# Optional: override engine URL (default http://127.0.0.1:3921)
# set_video_engine(url="http://127.0.0.1:3921")
_VIDEO_AGENT_INSTRUCTIONS = """You edit PraisonAI Video projects via tools.
Rules:
- scene.yaml must use schemaVersion: 1 with composition and scene blocks.
- Always video_lint_scene after video_update_scene; fix errors before render.
- Use video_render_project only when lint is clean.
- Omit render: in scene.yaml unless the user needs remotion (licensed) or hyperframes (deferred).
- Default render path is Playwright; optional render.backend in scene.yaml.
- Default project: VIDEO_STUDIO_PROJECT_ID env, or the only listed project.
- Skill reference: praisonai-video repo skills/praisonai-video/SKILL.md (YAML-first invariants).
"""
try:
from praisonaiagents import Agent
from praisonaiui.providers import PraisonAIProvider
from praisonaiui.video_agent_tools import get_video_agent_tools
_video_agent = Agent(
name="Video Editor",
instructions=_VIDEO_AGENT_INSTRUCTIONS,
llm=os.environ.get("PRAISONAI_MODEL", "gpt-4o-mini"),
tools=get_video_agent_tools(),
reflection=False,
)
aiui.set_provider(PraisonAIProvider(agent=_video_agent))
except ImportError:
pass # Chat works without praisonaiagents; Video tab still uses REST APIs
@aiui.page("video-studio", title="Video", icon="🎬", group="Create", order=20)
async def video_studio_page():
return aiui.layout(
[
aiui.html_embed('<div id="video-studio-root"></div>'),
]
)
app = create_app()
+8 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "aiui"
version = "0.3.113"
version = "0.3.121"
description = "YAML-driven website generator - CLI and compiler"
readme = "README.md"
license = {text = "MIT"}
@@ -61,9 +61,9 @@ postgres = [
praisonai = [
# Core agents SDK with knowledge support
"praisonaiagents[knowledge,search,crawl]>=1.5.56",
"praisonaiagents[knowledge,search,crawl]>=1.6.46",
# PraisonAI wrapper (includes CLI, bots, gateway support)
"praisonai[bot,api,os]>=2.0.0",
"praisonai[bot,api,os]>=4.6.0",
# Gateway dependencies
"fastapi>=0.115.0",
"sse-starlette>=1.6.0",
@@ -114,6 +114,10 @@ teams = [
"botbuilder-schema>=4.0.0",
]
a2ui = [
"praisonaiagents[a2ui]>=1.6.38",
]
all = [
"aiui[memory]",
"aiui[knowledge]",
@@ -129,6 +133,7 @@ all = [
"aiui[slack]",
"aiui[discord]",
"aiui[teams]",
"aiui[a2ui]",
]
dev = [
+339
View File
@@ -0,0 +1,339 @@
#!/usr/bin/env python3
"""Apply modular shell patches to dashboard.js (one-shot)."""
from pathlib import Path
p = Path(__file__).resolve().parents[1] / "src/praisonaiui/templates/frontend/plugins/dashboard.js"
text = p.read_text()
# 1) Extend BUILTIN_VIEWS and registries after theme-picker line
old_views = " 'theme-picker': '/plugins/views/theme-picker.js',\n};"
new_views = """ 'theme-picker': '/plugins/views/theme-picker.js',
feedback: '/plugins/views/feedback.js',
jobs: '/plugins/views/jobs.js',
auth: '/plugins/views/auth.js',
api: '/plugins/views/api.js',
};
const SLOT_REGISTRY = {};
let _dashboardPluginManifests = [];
const aiuiSdk = {
version: '1',
async fetchJSON(url, options) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
},
el(tag, attrs, children) {
const node = document.createElement(tag);
if (attrs) {
Object.entries(attrs).forEach(([k, v]) => {
if (k === 'className') node.className = v;
else if (k === 'text') node.textContent = v;
else if (k.startsWith('on') && typeof v === 'function') node.addEventListener(k.slice(2).toLowerCase(), v);
else node.setAttribute(k, v);
});
}
(children || []).forEach((c) => { if (c != null) node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); });
return node;
},
themeVar(name, fallback) {
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return v || fallback || '';
},
createBoard(root, opts) {
let destroyed = false;
const wrap = document.createElement('motion.div');
wrap.className = 'aiui-board';
root.appendChild(wrap);
async function refresh() {
if (destroyed || !opts?.fetch) return;
try {
const data = await opts.fetch();
wrap.innerHTML = '';
if (data?._components) data._components.forEach((c) => wrap.appendChild(renderComponent(c)));
else if (data?.columns) wrap.appendChild(renderBoard({ type: 'board', columns: data.columns }));
} catch (e) { wrap.innerHTML = `<div class="db-alert db-alert-error">${e.message}</div>`; }
}
refresh();
const interval = opts?.pollMs ? setInterval(refresh, opts.pollMs) : null;
return { refresh, destroy() { destroyed = true; if (interval) clearInterval(interval); wrap.remove(); } };
},
};"""
if old_views not in text:
raise SystemExit("BUILTIN_VIEWS anchor not found")
text = text.replace(old_views, new_views.replace('motion.div', 'div'))
old_api = """window.aiui.components = COMPONENT_REGISTRY;
// ── Sidebar State"""
new_api = """window.aiui.version = '1';
window.aiui.components = COMPONENT_REGISTRY;
window.aiui.registerSlot = function (name, renderFn) { SLOT_REGISTRY[name] = renderFn; };
window.aiui.slots = SLOT_REGISTRY;
window.aiui.sdk = aiuiSdk;
// ── Sidebar State"""
text = text.replace(old_api, new_api)
# 2) CSS after .db-columns
css_anchor = " .db-columns { display: grid; gap: 16px; }\n .db-card {"
css_insert = """ .db-columns { display: grid; gap: 16px; }
.aiui-board-columns { display: flex; gap: 12px; align-items: start; overflow-x: auto; padding-bottom: 8px; }
.aiui-board-column { flex: 0 0 280px; display: flex; flex-direction: column; gap: 8px;
background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); padding: 10px; min-height: 120px; }
.aiui-board-column-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin: 0 0 4px; }
.aiui-board-column-cards { display: flex; flex-direction: column; gap: 8px; }
.aiui-board-card .db-card { margin: 0; }
.db-slot-host { min-height: 0; }
.db-sidebar-footer { margin-top: auto; padding: 10px 14px; border-top: 1px solid var(--db-border); font-size: 11px; color: var(--db-text-dim); }
.db-mobile-header { display: none; position: fixed; top: 0; left: 0; right: 0; z-index: 40; min-height: 48px; align-items: center; gap: 8px; padding: 8px 12px; background: var(--db-sidebar-bg); border-bottom: 1px solid var(--db-border); }
.db-mobile-overlay { display: none; position: fixed; inset: 0; z-index: 35; background: rgba(0,0,0,0.5); }
@media (max-width: 1023px) {
.db-mobile-header { display: flex; }
.db-sidebar { position: fixed; z-index: 45; transform: translateX(-100%); transition: transform 0.2s ease; }
.db-sidebar.db-sidebar-open { transform: translateX(0); }
.db-mobile-overlay.db-visible { display: block; }
.db-main { padding-top: 48px; }
}
.db-card {"""
text = text.replace(css_anchor, css_insert)
# 3) Helpers before init
init_anchor = "async function init() {"
helpers = '''
function renderSlot(name, parent) {
const fn = SLOT_REGISTRY[name];
if (!fn || !parent) return;
const host = document.createElement('div');
host.className = 'db-slot-host';
host.dataset.slot = name;
try { const node = fn(); if (node) host.appendChild(node); } catch (e) { console.warn(`[AIUI] Slot '${name}':`, e); }
parent.appendChild(host);
}
async function loadDashboardPlugins() {
_dashboardPluginManifests = [];
try {
const res = await fetch('/api/dashboard/plugins');
if (!res.ok) return;
_dashboardPluginManifests = (await res.json()).plugins || [];
} catch (e) { console.warn('[AIUI] Dashboard plugins:', e); return; }
for (const manifest of _dashboardPluginManifests) {
if (manifest.css) {
const href = `/dashboard-plugins/${manifest.name}/${manifest.css}`;
if (!document.querySelector(`link[data-aiui-plugin-css="${manifest.name}"]`)) {
const link = document.createElement('link');
link.rel = 'stylesheet'; link.href = href; link.dataset.aiuiPluginCss = manifest.name;
document.head.appendChild(link);
}
}
if (manifest.entry && !document.querySelector(`script[data-aiui-plugin="${manifest.name}"]`)) {
await new Promise((resolve) => {
const script = document.createElement('script');
script.src = `/dashboard-plugins/${manifest.name}/${manifest.entry}`;
script.async = true; script.dataset.aiuiPlugin = manifest.name;
script.onload = script.onerror = () => resolve();
document.head.appendChild(script);
});
}
if (manifest.page && !pagesData.find((pg) => pg.id === manifest.page.id)) pagesData.push(manifest.page);
}
}
function setupMobileNav(root, sidebar) {
if (!sidebar) return;
let overlay = document.getElementById('db-mobile-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'db-mobile-overlay'; overlay.className = 'db-mobile-overlay';
overlay.addEventListener('click', () => { sidebar.classList.remove('db-sidebar-open'); overlay.classList.remove('db-visible'); });
root.insertBefore(overlay, root.firstChild);
}
if (!document.getElementById('db-mobile-header')) {
const header = document.createElement('motion.div');
header.id = 'db-mobile-header'; header.className = 'db-mobile-header';
const menuBtn = document.createElement('button');
menuBtn.type = 'button'; menuBtn.textContent = '☰';
menuBtn.style.cssText = 'background:transparent;border:1px solid var(--db-border);color:var(--db-text);border-radius:6px;padding:6px 10px;cursor:pointer';
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('db-sidebar-open');
overlay.classList.toggle('db-visible', sidebar.classList.contains('db-sidebar-open'));
});
const title = document.createElement('span'); title.id = 'db-mobile-title'; title.style.fontWeight = '600';
header.appendChild(menuBtn); header.appendChild(title);
root.insertBefore(header, overlay.nextSibling);
}
}
'''
helpers = helpers.replace('motion.div', 'motion.div').replace("createElement('motion.div')", "createElement('motion.div')")
helpers = helpers.replace('motion.div', 'div')
text = text.replace(init_anchor, helpers + init_anchor)
# 4) init awaits buildDashboard
text = text.replace(
" // Build the dashboard (after theme is applied)\n buildDashboard();\n}",
" await buildDashboard();\n}",
)
# 5) buildDashboard plugins + mobile
text = text.replace(
" dashboardConfig = uiConfig.dashboard || {};\n } catch (e) {",
" dashboardConfig = uiConfig.dashboard || {};\n window.__aiuiDashboardModules = dashboardConfig.modules || [];\n await loadDashboardPlugins();\n } catch (e) {",
)
text = text.replace(
""" if (showSidebar) {
const sidebar = buildSidebar(pagesData);
root.appendChild(sidebar);
}
root.appendChild(main);""",
""" let sidebar = null;
if (showSidebar) {
sidebar = buildSidebar(pagesData);
const slotFooter = document.createElement('div');
slotFooter.className = 'db-sidebar-footer';
renderSlot('shell:sidebar:footer', slotFooter);
if (slotFooter.children.length) sidebar.appendChild(slotFooter);
root.appendChild(sidebar);
setupMobileNav(root, sidebar);
}
const shellHeader = document.createElement('div');
shellHeader.id = 'db-shell-header-slot';
renderSlot('shell:header', shellHeader);
if (shellHeader.children.length) root.insertBefore(shellHeader, main);
root.appendChild(main);""",
)
# 6) selectPage toolbar
old_sel = """ const headerHtml = showPageHeader ? `
<motion.div class="db-page-header">
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
</motion.div>
` : '';
main.innerHTML = `
${headerHtml}
<motion.div data-page="${pageId}" id="db-page-content"></motion.div>
`;
const container = document.querySelector(`[data-page="${pageId}"]`);"""
old_sel = old_sel.replace('motion.div', 'div')
new_sel = """ const toolbarSlotId = `page:${pageId}:toolbar`;
const headerHtml = showPageHeader ? `
<div class="db-page-header">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap">
<div>
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
</div>
<div id="db-page-toolbar-slot" class="db-slot-host" data-slot="${toolbarSlotId}"></div>
</div>
</div>
` : '';
main.innerHTML = `${headerHtml}<motion.div data-page="${pageId}" id="db-page-content"></motion.div>`;
if (showPageHeader) {
const toolbarHost = document.getElementById('db-page-toolbar-slot');
if (toolbarHost && SLOT_REGISTRY[toolbarSlotId]) {
try { const node = SLOT_REGISTRY[toolbarSlotId](); if (node) toolbarHost.appendChild(node); } catch (e) {}
}
}
const mobileTitle = document.getElementById('db-mobile-title');
if (mobileTitle) mobileTitle.textContent = page.title || '';
const container = document.querySelector(`[data-page="${pageId}"]`);"""
new_sel = new_sel.replace('motion.div', 'div')
if old_sel not in text:
old_sel2 = """ const headerHtml = showPageHeader ? `
<div class="db-page-header">
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
</motion.div>
` : '';
main.innerHTML = `
${headerHtml}
<motion.div data-page="${pageId}" id="db-page-content"></motion.div>
`;
const container = document.querySelector(`[data-page="${pageId}"]`);""".replace('motion.div','motion.div')
# use correct version
old_sel2 = """ const headerHtml = showPageHeader ? `
<div class="db-page-header">
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
</div>
` : '';
main.innerHTML = `
${headerHtml}
<div data-page="${pageId}" id="db-page-content"></div>
`;
const container = document.querySelector(`[data-page="${pageId}"]`);"""
text = text.replace(old_sel2, new_sel)
else:
text = text.replace(old_sel, new_sel)
# 7) board case + renderBoard
text = text.replace(
" case 'form_action': return renderFormAction(comp);\n default: {",
" case 'form_action': return renderFormAction(comp);\n case 'board': return renderBoard(comp);\n default: {",
)
if "function renderBoard" not in text:
text = text.replace(
"function renderCard(comp) {",
'''function renderBoard(comp) {
const wrap = document.createElement('div');
wrap.className = 'aiui-board';
const row = document.createElement('div');
row.className = 'aiui-board-columns';
(comp.columns || []).forEach((col) => {
const colEl = document.createElement('div');
colEl.className = 'aiui-board-column';
const title = document.createElement('div');
title.className = 'aiui-board-column-title';
title.textContent = col.title || col.id || 'Column';
colEl.appendChild(title);
const cardsWrap = document.createElement('div');
cardsWrap.className = 'aiui-board-column-cards';
(col.cards || []).forEach((cardComp) => {
const cardWrap = document.createElement('div');
cardWrap.className = 'aiui-board-card';
const normalized = cardComp?.type ? cardComp : {
type: 'card', title: cardComp.title || cardComp.name || 'Card',
value: cardComp.value, footer: cardComp.footer || cardComp.assignee || cardComp.status,
};
cardWrap.appendChild(renderComponent(normalized));
cardsWrap.appendChild(cardWrap);
});
colEl.appendChild(cardsWrap);
row.appendChild(colEl);
});
wrap.appendChild(row);
return wrap;
}
function renderCard(comp) {''',
)
# 8) default slot before export
if "registerSlot('shell:sidebar:footer'" not in text:
text = text.replace(
"export { init, onContentChange };",
"""window.aiui.registerSlot('shell:sidebar:footer', () => {
const el = document.createElement('div');
el.innerHTML = '<span class="db-health-dot" style="color:var(--db-text-dim)">●</span> <span class="db-health-label">Checking…</span>';
fetch('/api/health').then((r) => r.json()).then((d) => {
const ok = d && (d.status === 'ok' || d.status === 'healthy');
const dot = el.querySelector('.db-health-dot');
const label = el.querySelector('.db-health-label');
if (dot) dot.style.color = ok ? '#22c55e' : '#ef4444';
if (label) label.textContent = ok ? 'Connected' : 'Degraded';
}).catch(() => { const label = el.querySelector('.db-health-label'); if (label) label.textContent = 'Offline'; });
return el;
});
export { init, onContentChange };""",
)
p.write_text(text)
print("patched ok", len(text))
+185
View File
@@ -45,6 +45,8 @@
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@a2ui/react": "^0.10.0",
"@a2ui/web_core": "^0.10.0",
"@eslint/js": "^9.39.1",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^24.10.1",
@@ -65,8 +67,87 @@
"vite-plugin-dts": "^4.5.4"
},
"peerDependencies": {
"@a2ui/react": ">=0.10.0",
"@a2ui/web_core": ">=0.10.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"peerDependenciesMeta": {
"@a2ui/react": {
"optional": true
},
"@a2ui/web_core": {
"optional": true
}
}
},
"node_modules/@a2ui/markdown-it": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@a2ui/markdown-it/-/markdown-it-0.0.4.tgz",
"integrity": "sha512-uaYNywdiHDH4i//pHpyckFnApvOshiUNT2Xl8awceNjL1XAX/gqtki9GRUJQ26RE7id2+aPNgOy1W6mXNBxriQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dompurify": "^3.3.1",
"markdown-it": "^14.1.0"
},
"peerDependencies": {
"@a2ui/web_core": "^0.9.2"
}
},
"node_modules/@a2ui/react": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@a2ui/react/-/react-0.10.0.tgz",
"integrity": "sha512-LeyrHUv7lNkLqrxr+qM2XybCB5ok/8gm5fVcs2foPiPV0KN6XtZGDKk23Kt3oitRHJp7vFOeyDD9y+UYWh4Y3A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@a2ui/markdown-it": "^0.0.4",
"@a2ui/web_core": "^0.10.0",
"clsx": "^2.1.0",
"markdown-it": "^14.0.0",
"zod": "^3.23.8"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"zod": "^3.23.8"
}
},
"node_modules/@a2ui/react/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@a2ui/web_core": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@a2ui/web_core/-/web_core-0.10.0.tgz",
"integrity": "sha512-pSieKxaB6aZWBL2LjZfKo4/1v4xMWylczpzte1ehuGH0kfNGf/iTd73PKsIKYx3b0FNWTbD8gAloOM4JCVqUig==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@preact/signals-core": "^1.13.0",
"date-fns": "^4.1.0",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.25.1"
}
},
"node_modules/@a2ui/web_core/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@babel/code-frame": {
@@ -1276,6 +1357,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@preact/signals-core": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.2.tgz",
"integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -3751,6 +3843,14 @@
"@types/react": "*"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -4872,6 +4972,16 @@
"csstype": "^3.0.2"
}
},
"node_modules/dompurify": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.5.tgz",
"integrity": "sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==",
"dev": true,
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.307",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
@@ -6145,6 +6255,16 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
@@ -6257,6 +6377,37 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/markdown-table": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
@@ -6549,6 +6700,13 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true,
"license": "MIT"
},
"node_modules/micromark": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
@@ -7471,6 +7629,16 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
@@ -8214,6 +8382,13 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true,
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
@@ -8639,6 +8814,16 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.25.28 || ^4"
}
},
"node_modules/zod-validation-error": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+12
View File
@@ -48,9 +48,19 @@
"directory": "src/frontend"
},
"peerDependencies": {
"@a2ui/react": ">=0.10.0",
"@a2ui/web_core": ">=0.10.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"peerDependenciesMeta": {
"@a2ui/react": {
"optional": true
},
"@a2ui/web_core": {
"optional": true
}
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-avatar": "^1.1.11",
@@ -88,6 +98,8 @@
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@a2ui/react": "^0.10.0",
"@a2ui/web_core": "^0.10.0",
"@eslint/js": "^9.39.1",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^24.10.1",
+3
View File
@@ -55,6 +55,7 @@ export function ChatArea({ config, className = '', sessionId: externalSessionId,
content: run.content || run.message || '',
timestamp: run.timestamp || new Date().toISOString(),
...(run.toolCalls ? { toolCalls: run.toolCalls } : {}),
...(run.elements ? { elements: run.elements } : {}),
}))
setMessages(loaded)
}
@@ -71,6 +72,7 @@ export function ChatArea({ config, className = '', sessionId: externalSessionId,
currentResponse,
toolCalls,
thinkingSteps,
pendingElements,
sendMessage,
cancel,
} = useSSE({
@@ -152,6 +154,7 @@ export function ChatArea({ config, className = '', sessionId: externalSessionId,
currentResponse={currentResponse}
thinkingSteps={thinkingSteps}
toolCalls={toolCalls}
pendingElements={pendingElements}
isStreaming={isStreaming}
/>
)}
+36 -3
View File
@@ -13,6 +13,7 @@ interface ChatMessagesProps {
currentResponse: string
thinkingSteps: string[]
toolCalls: ToolCall[]
pendingElements?: (import('../types').MessageElementUnion | Record<string, unknown>)[]
isStreaming: boolean
sessionId?: string
}
@@ -22,6 +23,7 @@ export function ChatMessages({
currentResponse,
thinkingSteps,
toolCalls,
pendingElements = [],
isStreaming,
sessionId,
}: ChatMessagesProps) {
@@ -45,14 +47,27 @@ export function ChatMessages({
))}
</div>
)}
{currentResponse && (
{(currentResponse || pendingElements.length > 0) ? (
<div className="flex gap-3">
<Avatar role="assistant" />
<div className="flex-1 min-w-0">
<MarkdownContent content={currentResponse} />
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-0.5 rounded-sm" />
{currentResponse && (
<>
<MarkdownContent content={currentResponse} />
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-0.5 rounded-sm" />
</>
)}
{pendingElements.length > 0 && (
<div className={`space-y-3 ${currentResponse ? 'mt-3' : ''}`}>
{pendingElements.map((element, i) => (
<ElementRenderer key={i} element={element} className="w-full" />
))}
</div>
)}
</div>
</div>
) : (
<StreamLoadingBubble />
)}
</div>
)}
@@ -61,6 +76,24 @@ export function ChatMessages({
}
// -- Avatar ---
function StreamLoadingBubble() {
return (
<div className="flex gap-3" aria-live="polite" aria-label="Assistant is responding">
<Avatar role="assistant" />
<div className="flex-1 min-w-0">
<div className="inline-flex items-center gap-2 rounded-2xl bg-muted/60 px-4 py-3 text-sm text-muted-foreground">
<span className="inline-flex items-center gap-1" aria-hidden="true">
<span className="w-2 h-2 rounded-full bg-primary/70 animate-bounce [animation-delay:0ms]" />
<span className="w-2 h-2 rounded-full bg-primary/70 animate-bounce [animation-delay:150ms]" />
<span className="w-2 h-2 rounded-full bg-primary/70 animate-bounce [animation-delay:300ms]" />
</span>
<span>Generating response…</span>
</div>
</div>
</div>
)
}
function Avatar({ role }: { role: string }) {
if (role === 'user') {
return (
+13 -1
View File
@@ -285,7 +285,7 @@ export function CodeBlock({ code, language, className = '' }: CodeBlockProps) {
)
}
// Unified element renderer supporting all element types and display modes
import { SurfaceHost } from '../surfaces/SurfaceHost'
interface ElementRendererProps {
element: MessageElementUnion | Record<string, unknown>
className?: string
@@ -361,6 +361,18 @@ export function ElementRenderer({ element, className = '' }: ElementRendererProp
/>
)
}
case 'a2ui_surface': {
const el = element as Record<string, unknown>
const messages = (el.messages ?? []) as Record<string, unknown>[]
return (
<SurfaceHost
mode="auto"
messages={messages}
surfaceId={el.surface_id as string | undefined}
className={className}
/>
)
}
default:
// Fallback for legacy/unknown element types
if ('url' in element && typeof element.url === 'string') {
+24
View File
@@ -1,5 +1,7 @@
import { useState } from 'react'
import type { ToolCall } from '../types'
import { ElementRenderer } from './MultimediaElements'
import { SurfaceHost } from '../surfaces/SurfaceHost'
interface ToolCallDisplayProps {
toolCall: ToolCall
@@ -57,6 +59,28 @@ export function ToolCallDisplay({ toolCall }: ToolCallDisplayProps) {
</button>
{expanded && (
<div className="p-2 border-t space-y-2">
{toolCall.elements?.length ? (
<div>
<span className="text-muted-foreground">Preview:</span>
<div className="mt-1 space-y-2">
{toolCall.elements.map((element, i) => (
<ElementRenderer key={i} element={element} className="max-w-sm" />
))}
</div>
</div>
) : null}
{toolCall.a2ui?.messages?.length ? (
<div>
<span className="text-muted-foreground">UI:</span>
<div className="mt-1">
<SurfaceHost
mode="auto"
messages={toolCall.a2ui.messages}
surfaceId={toolCall.a2ui.surface_id ?? toolCall.surface_id}
/>
</div>
</div>
) : null}
{toolCall.args && Object.keys(toolCall.args).length > 0 && (
<div>
<span className="text-muted-foreground">Arguments:</span>
+28 -1
View File
@@ -18,6 +18,7 @@ export interface SessionStreamState {
currentResponse: string
toolCalls: ToolCall[]
thinkingSteps: string[]
pendingElements: (import('../types').MessageElementUnion | Record<string, unknown>)[]
}
const defaultState = (): SessionStreamState => ({
@@ -25,6 +26,7 @@ const defaultState = (): SessionStreamState => ({
currentResponse: '',
toolCalls: [],
thinkingSteps: [],
pendingElements: [],
})
// ---------------------------------------------------------------------------
@@ -119,6 +121,7 @@ export function startStream(
currentResponse: '',
toolCalls: [],
thinkingSteps: [],
pendingElements: [],
})
// Reset tool call dedup map for this new stream
entry.toolCallMap = new Map()
@@ -264,6 +267,11 @@ function handleStoreEvent(
case 'tool_call_completed':
case 'team_tool_call_completed': {
const extra = event.extra_data as Record<string, unknown> | undefined
const a2uiRaw = (event.a2ui ?? extra?.a2ui) as Record<string, unknown>[] | undefined
const mediaEls = (event.elements ?? extra?.elements) as
| (import('../types').MessageElementUnion | Record<string, unknown>)[]
| undefined
const tc: ToolCall = {
name: event.name as string,
description: event.description as string | undefined,
@@ -274,10 +282,29 @@ function handleStoreEvent(
args: event.args as Record<string, unknown> | undefined,
formatted_result: event.formatted_result as string | undefined,
result: event.result,
surface_id: (event.surface_id ?? extra?.surface_id) as string | undefined,
a2ui: a2uiRaw?.length
? { messages: a2uiRaw, surface_id: (event.surface_id ?? extra?.surface_id) as string | undefined }
: undefined,
elements: mediaEls,
}
if (tc.tool_call_id) toolCallMap.set(tc.tool_call_id, tc)
const all = buildToolCallList(entry.state.toolCalls, tc)
update(sessionId, { toolCalls: all })
const pendingElements = mediaEls?.length
? [...entry.state.pendingElements, ...mediaEls]
: entry.state.pendingElements
update(sessionId, { toolCalls: all, pendingElements })
break
}
case 'message_element': {
const extra = event.extra_data as Record<string, unknown> | undefined
const el = (event.element ?? extra?.element) as Record<string, unknown> | undefined
if (el) {
update(sessionId, {
pendingElements: [...entry.state.pendingElements, el],
})
}
break
}
+2
View File
@@ -27,6 +27,7 @@ interface UseSSEReturn {
currentResponse: string
toolCalls: import('../types').ToolCall[]
thinkingSteps: string[]
pendingElements: (import('../types').MessageElementUnion | Record<string, unknown>)[]
}
export function useSSE(options: UseSSEOptions = {}): UseSSEReturn {
@@ -75,6 +76,7 @@ export function useSSE(options: UseSSEOptions = {}): UseSSEReturn {
currentResponse: snap.currentResponse,
toolCalls: snap.toolCalls,
thinkingSteps: snap.thinkingSteps,
pendingElements: snap.pendingElements,
sendMessage,
cancel,
}
+168
View File
@@ -0,0 +1,168 @@
import { useEffect, useMemo, useState, type ComponentType } from 'react'
import { ComponentRenderer } from '../components/ComponentRenderer'
import { a2uiToComponents } from './a2uiToComponents'
import type { SurfaceHostProps } from './types'
interface A2uiRendererProps {
messages: Record<string, unknown>[]
surfaceId?: string
sessionId?: string
className?: string
}
type SurfaceEntry = {
id: string
surface: unknown
processor: unknown
A2uiSurface: ComponentType<{
surface: unknown
processor: unknown
onAction?: (action: Record<string, unknown>) => void
}>
}
export function A2uiRenderer({ messages, surfaceId, sessionId, className = '' }: A2uiRendererProps) {
const [surfaces, setSurfaces] = useState<SurfaceEntry[]>([])
const [error, setError] = useState<string | null>(null)
const [ready, setReady] = useState(false)
const messageKey = useMemo(() => JSON.stringify(messages), [messages])
useEffect(() => {
let cancelled = false
async function load() {
setError(null)
try {
const webCore = await import(/* @vite-ignore */ '@a2ui/web_core/v0_9')
const reactMod = await import(/* @vite-ignore */ '@a2ui/react/v0_9')
const { MessageProcessor } = webCore
const { A2uiSurface, basicCatalog } = reactMod
const processor = new MessageProcessor([basicCatalog])
processor.processMessages(messages)
const rawSurfaces = (processor as { getSurfaces?: () => unknown[] }).getSurfaces?.()
?? (processor as { surfaces?: unknown[] }).surfaces
?? []
const list: SurfaceEntry[] = []
if (Array.isArray(rawSurfaces) && rawSurfaces.length) {
for (const s of rawSurfaces) {
const id = typeof s === 'string' ? s : (s as { id?: string })?.id ?? 'main'
list.push({ id, surface: s, A2uiSurface, processor })
}
} else {
list.push({
id: surfaceId ?? 'main',
surface: surfaceId ?? 'main',
A2uiSurface,
processor,
})
}
if (!cancelled) {
setSurfaces(list)
setReady(true)
}
} catch (e) {
if (!cancelled) {
setError(
e instanceof Error
? e.message
: 'A2UI renderer unavailable — install @a2ui/react and @a2ui/web_core',
)
setReady(false)
}
}
}
if (messages?.length) {
load()
}
return () => {
cancelled = true
}
}, [messageKey, surfaceId, messages])
if (error) {
return (
<div className={`rounded-md border border-dashed p-3 text-xs text-muted-foreground ${className}`}>
<p className="font-medium mb-1">A2UI surface</p>
<p>{error}</p>
<pre className="mt-2 max-h-40 overflow-auto text-[10px] opacity-70">
{JSON.stringify(messages, null, 2)}
</pre>
</div>
)
}
if (!ready || !surfaces.length) {
return (
<div className={`rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground ${className}`}>
Loading A2UI surface…
</div>
)
}
return (
<div className={`space-y-2 ${className}`}>
{surfaces.map((entry) => (
<entry.A2uiSurface
key={entry.id}
surface={entry.surface}
processor={entry.processor}
onAction={async (action) => {
const sid = surfaceId ?? entry.id
await fetch(`/api/surfaces/${sid}/action`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...action, session_id: sessionId }),
})
}}
/>
))}
</div>
)
}
export function SurfaceHost({
mode = 'auto',
messages = [],
components,
surfaceId,
sessionId,
className = '',
}: SurfaceHostProps) {
if (components?.length) {
return (
<div className={className}>
<ComponentRenderer components={components} />
</div>
)
}
if (mode === 'components' || !messages.length) {
return null
}
if (mode === 'auto') {
const mapped = a2uiToComponents(messages)
if (mapped?.length) {
return (
<div className={className}>
<ComponentRenderer components={mapped} />
</div>
)
}
}
return (
<A2uiRenderer
messages={messages}
surfaceId={surfaceId}
sessionId={sessionId}
className={className}
/>
)
}
+19
View File
@@ -0,0 +1,19 @@
/** Optional A2UI packages — installed via npm when using agent-driven surfaces. */
declare module '@a2ui/web_core/v0_9' {
export class MessageProcessor {
constructor(catalogs: unknown[])
processMessages(messages: unknown[]): void
getSurfaces?(): unknown[]
surfaces?: unknown[]
}
}
declare module '@a2ui/react/v0_9' {
import type { ComponentType } from 'react'
export const basicCatalog: unknown
export const A2uiSurface: ComponentType<{
surface: unknown
processor: unknown
onAction?: (action: Record<string, unknown>) => void
}>
}
@@ -0,0 +1,63 @@
import type { ComponentDict } from '../components/ComponentRenderer'
/** Partial A2UI → aiui component mapper (DRY fallback before Google renderer). */
function textFromNode(node: Record<string, unknown>): string {
const text = node.text
if (typeof text === 'string') return text
if (typeof text === 'object' && text !== null) {
const t = text as Record<string, unknown>
return String(t.literal ?? t.path ?? '')
}
return String(node.content ?? node.label ?? '')
}
function mapComponent(comp: Record<string, unknown>): ComponentDict | null {
const type = comp.component as string | undefined
if (!type) return null
switch (type) {
case 'Text':
return { type: 'text', content: textFromNode(comp) }
case 'Markdown':
return { type: 'markdown', content: textFromNode(comp) }
case 'Divider':
return { type: 'separator' }
case 'Image': {
const url = (comp.url as Record<string, unknown>)?.literal ?? comp.url
return { type: 'image', src: String(url ?? ''), alt: textFromNode(comp) }
}
case 'Button':
return {
type: 'button',
label: textFromNode(comp),
variant: 'default',
}
default:
return null
}
}
export function a2uiToComponents(messages: Record<string, unknown>[]): ComponentDict[] | null {
const out: ComponentDict[] = []
let sawUnmapped = false
for (const msg of messages) {
const update = msg.updateComponents as Record<string, unknown> | undefined
const components = (update?.components ?? update?.componentList) as Record<string, unknown>[] | undefined
if (!Array.isArray(components)) continue
for (const comp of components) {
const mapped = mapComponent(comp)
if (mapped) {
out.push(mapped)
} else {
sawUnmapped = true
}
}
}
if (!out.length) return null
if (sawUnmapped) return null
return out
}
+15
View File
@@ -0,0 +1,15 @@
export type SurfaceRenderMode = 'auto' | 'a2ui' | 'components'
export interface A2uiPayload {
messages: Record<string, unknown>[]
surface_id?: string
}
export interface SurfaceHostProps {
mode?: SurfaceRenderMode
messages?: Record<string, unknown>[]
components?: import('../components/ComponentRenderer').ComponentDict[]
surfaceId?: string
sessionId?: string
className?: string
}
+9
View File
@@ -160,6 +160,9 @@ export interface ToolCall {
args?: Record<string, unknown>
result?: unknown
error?: string
a2ui?: { messages: Record<string, unknown>[]; surface_id?: string }
surface_id?: string
elements?: (MessageElementUnion | Record<string, unknown>)[]
}
export interface FileAttachment {
@@ -260,6 +263,7 @@ export type RunEventType =
| 'memory_update_started'
| 'memory_update_completed'
| 'updating_memory'
| 'message_element'
// Team events (11 types)
| 'team_run_started'
| 'team_run_content'
@@ -335,10 +339,15 @@ export interface SSEEvent {
team_id?: string
// Memory
memory_type?: 'short_term' | 'long_term'
// Message elements (protocol-driven media)
element?: MessageElementUnion | Record<string, unknown>
elements?: (MessageElementUnion | Record<string, unknown>)[]
// Extra data (Agent-UI pattern)
extra_data?: {
references?: unknown[]
reasoning_steps?: string[]
element?: MessageElementUnion | Record<string, unknown>
elements?: (MessageElementUnion | Record<string, unknown>)[]
[key: string]: unknown
}
}
+83
View File
@@ -0,0 +1,83 @@
import { useCallback, useEffect, useState } from 'react'
import type { DashboardPageProps } from '../types/dashboard'
import { SurfaceHost } from '../surfaces/SurfaceHost'
/**
* Full-page A2UI canvas — live surface backed by /api/surfaces/{id}.
*/
export function CanvasView({ page }: DashboardPageProps) {
const params = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '')
const surfaceId = params.get('surface') ?? (page as { surface_id?: string }).surface_id ?? 'main'
const [messages, setMessages] = useState<Record<string, unknown>[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const loadSurface = useCallback(async () => {
try {
setLoading(true)
const res = await fetch(`/api/surfaces/${encodeURIComponent(surfaceId)}`)
if (res.status === 404) {
setMessages([])
setError(null)
return
}
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
setMessages((data.messages ?? []) as Record<string, unknown>[])
setError(null)
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to load surface'
if (msg.includes('404')) {
setMessages([])
setError(null)
return
}
setError(msg)
} finally {
setLoading(false)
}
}, [surfaceId])
useEffect(() => {
loadSurface()
}, [loadSurface])
useEffect(() => {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
const ws = new WebSocket(`${proto}://${window.location.host}/api/chat/ws`)
ws.onmessage = (ev) => {
try {
const data = JSON.parse(ev.data)
if (data.type === 'a2ui_surface' && data.surface_id === surfaceId && data.messages) {
setMessages(data.messages as Record<string, unknown>[])
}
} catch {
/* ignore */
}
}
return () => ws.close()
}, [surfaceId])
if (loading) {
return <div className="p-6 text-muted-foreground">Loading canvas…</div>
}
return (
<div className="p-6 max-w-5xl mx-auto">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold">{page.title}</h2>
<button
type="button"
onClick={loadSurface}
className="px-3 py-1.5 text-sm rounded-md border hover:bg-accent"
>
↻ Refresh
</button>
</div>
{error ? (
<div className="text-destructive text-sm mb-4">{error}</div>
) : null}
<SurfaceHost mode="auto" messages={messages} surfaceId={surfaceId} />
</div>
)
}
+18
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import type { DashboardPageProps } from '../types/dashboard'
import { ComponentRenderer, type ComponentDict } from '../components/ComponentRenderer'
import { SurfaceHost } from '../surfaces/SurfaceHost'
interface KeyValue {
label: string
@@ -45,6 +46,23 @@ export function CustomPageView({ page }: DashboardPageProps) {
if (error) return <div className="p-6 text-destructive">Error: {error}</div>
if (!data) return null
const surfacePayload = data._surface as Record<string, unknown> | undefined
if (surfacePayload && (surfacePayload.messages || surfacePayload.id)) {
const surfaceId = String(surfacePayload.id ?? 'main')
const messages = (surfacePayload.messages ?? []) as Record<string, unknown>[]
return (
<div className="p-6 max-w-5xl">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold">{page.title}</h2>
<button onClick={fetchData} className="px-3 py-1.5 text-sm rounded-md border hover:bg-accent">
↻ Refresh
</button>
</div>
<SurfaceHost mode="auto" messages={messages} surfaceId={surfaceId} />
</div>
)
}
if (data._components && Array.isArray(data._components)) {
return (
<div className="p-6 max-w-5xl">
+2
View File
@@ -19,6 +19,7 @@ import { UsageView } from './UsageView'
import { DebugView } from './DebugView'
import { RealtimeView } from './RealtimeView'
import { CustomPageView } from './CustomPageView'
import { CanvasView } from './CanvasView'
/** Registry of built-in page components, keyed by page ID */
export const BUILTIN_VIEWS: Record<string, ComponentType<DashboardPageProps>> = {
@@ -30,6 +31,7 @@ export const BUILTIN_VIEWS: Record<string, ComponentType<DashboardPageProps>> =
usage: UsageView as unknown as ComponentType<DashboardPageProps>,
debug: DebugView as unknown as ComponentType<DashboardPageProps>,
realtime: RealtimeView as unknown as ComponentType<DashboardPageProps>,
canvas: CanvasView as unknown as ComponentType<DashboardPageProps>,
}
/**
+28 -15
View File
@@ -31,6 +31,8 @@ def __getattr__(name: str):
"on",
"resume",
"page",
"surface_page",
"surface_action",
"say",
"stream",
"stream_token",
@@ -182,8 +184,12 @@ def __getattr__(name: str):
"set_custom_js",
"register_theme",
"set_chat_features",
"set_settings",
"set_dashboard",
"set_jobs_api",
"set_jobs_backend",
"set_chat_mode",
"set_chat_preview",
"set_brand_color",
"set_sidebar_config",
"set_feedback_enabled",
@@ -201,15 +207,15 @@ def __getattr__(name: str):
"BaseProvider",
"RunEvent",
"RunEventType",
# Discovery schema (Issue #48)
"ModelInfo",
"AgentDetails",
"TeamDetails",
# RAG citations (Issue #49)
"Reference",
"ReferenceData",
# Rich reasoning (Issue #50)
"ReasoningStep",
# Discovery cards (Issue #48) — PraisonAI-native names
"ModelCard",
"AgentCard",
"TeamCard",
# RAG source chunks (Issue #49) — PraisonAI-native names
"SourceChunk",
"SourceBundle",
# Rich reasoning (Issue #50) — PraisonAI-native name
"ThoughtStep",
}
_providers_attrs = {"PraisonAIProvider"}
_config_attrs = {"configure"}
@@ -434,6 +440,8 @@ __all__ = [
"on",
"resume",
"page",
"surface_page",
"surface_action",
# Lifecycle hooks
"on_app_startup",
"on_app_shutdown",
@@ -468,8 +476,12 @@ __all__ = [
"set_theme",
"set_custom_css",
"set_chat_features",
"set_settings",
"set_dashboard",
"set_jobs_api",
"set_jobs_backend",
"set_chat_mode",
"set_chat_preview",
"set_brand_color",
"set_sidebar_config",
"set_feedback_enabled",
@@ -488,12 +500,13 @@ __all__ = [
"RunEventType",
"PraisonAIProvider",
# Discovery, citations, reasoning schema (Issues #48, #49, #50)
"ModelInfo",
"AgentDetails",
"TeamDetails",
"Reference",
"ReferenceData",
"ReasoningStep",
# PraisonAI-native, noun-first names (not lifted from any external framework)
"ModelCard",
"AgentCard",
"TeamCard",
"SourceChunk",
"SourceBundle",
"ThoughtStep",
# Configuration
"configure",
# Action classes and decorators
+1 -1
View File
@@ -1,3 +1,3 @@
"""Version information."""
__version__ = "0.3.113"
__version__ = "0.3.114"
+20
View File
@@ -0,0 +1,20 @@
"""Default SDK hooks lister — used when no backend injected."""
from __future__ import annotations
from typing import Any, Dict, List
def list_sdk_hooks() -> List[Dict[str, Any]]:
"""Flat hook list from praisonaiagents HookRegistry."""
try:
from praisonaiagents.hooks import get_default_registry
registry = get_default_registry()
flat: List[Dict[str, Any]] = []
for event, hooks in registry.list_hooks().items():
for h in hooks:
flat.append({"event": event, "source": "sdk", **h})
return flat
except ImportError:
return []
+73
View File
@@ -0,0 +1,73 @@
"""Default workflow runner — AgentFlow from registered workflow steps."""
from __future__ import annotations
import time
import uuid
from typing import Any, Dict
def run_workflow(
workflow_id: str,
*,
workflow: Dict[str, Any],
input_data: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
"""Run workflow via praisonaiagents AgentFlow when steps are defined."""
run_id = uuid.uuid4().hex[:12]
started = time.time()
input_data = input_data or {}
text = input_data.get("text") or input_data.get("message") or str(input_data)
try:
from praisonaiagents import Agent, AgentFlow
raw_steps = workflow.get("steps") or []
if not raw_steps:
return {
"id": run_id,
"workflow_id": workflow_id,
"workflow_name": workflow.get("name", ""),
"status": "failed",
"input": input_data,
"error": "Workflow has no steps",
"started_at": started,
"completed_at": time.time(),
}
steps = []
for i, step in enumerate(raw_steps):
if isinstance(step, str):
steps.append(
Agent(
name=f"step_{i + 1}",
instructions=step,
llm=workflow.get("model", "gpt-4o-mini"),
)
)
else:
steps.append(step)
flow = AgentFlow(steps=steps, name=workflow.get("name", "Workflow"))
result = flow.run(text)
return {
"id": run_id,
"workflow_id": workflow_id,
"workflow_name": workflow.get("name", ""),
"status": "completed",
"input": input_data,
"output": result if isinstance(result, dict) else {"result": str(result)},
"started_at": started,
"completed_at": time.time(),
}
except Exception as exc:
return {
"id": run_id,
"workflow_id": workflow_id,
"workflow_name": workflow.get("name", ""),
"status": "failed",
"input": input_data,
"error": str(exc),
"started_at": started,
"completed_at": time.time(),
}
+145
View File
@@ -0,0 +1,145 @@
"""A2UI payload detection and normalisation for PraisonAIUI."""
from __future__ import annotations
import json
from typing import Any, Dict, List, Optional
from praisonaiagents.ui.protocols import A2UI_MIME_TYPE
DEFAULT_SURFACE_ID = "main"
A2UI_VERSION = "v0.9"
def is_a2ui_tool_result(result: Any) -> bool:
"""Return True if *result* looks like an A2UI tool or A2A part payload."""
if result is None:
return False
if isinstance(result, dict):
mime = result.get("mime_type") or result.get("mimeType")
if mime == A2UI_MIME_TYPE:
return True
if isinstance(result.get("messages"), list):
return True
if isinstance(result.get("a2ui_part"), (dict, list)):
return True
if isinstance(result, list) and result:
first = result[0]
if isinstance(first, dict) and any(
k in first for k in ("createSurface", "updateComponents", "updateDataModel", "deleteSurface")
):
return True
return False
def _extract_raw_messages(result: Any) -> List[Dict[str, Any]]:
"""Pull the message list from various A2UI result shapes."""
if isinstance(result, list):
return [m for m in result if isinstance(m, dict)]
if not isinstance(result, dict):
return []
for key in ("messages", "a2ui_part"):
val = result.get(key)
if isinstance(val, list):
return [m for m in val if isinstance(m, dict)]
if isinstance(val, dict) and isinstance(val.get("messages"), list):
return [m for m in val["messages"] if isinstance(m, dict)]
if any(k in result for k in ("createSurface", "updateComponents", "updateDataModel", "deleteSurface")):
return [result]
return []
def coerce_a2ui_tool_messages(raw: Any, *, surface_id: str = DEFAULT_SURFACE_ID) -> List[Dict[str, Any]]:
"""Coerce common LLM tool-arg shapes into a valid A2UI message list."""
if raw is None:
raise ValueError("messages must be a list of A2UI message dicts or a JSON string")
if isinstance(raw, str):
try:
raw = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError(f"messages JSON string is not valid JSON: {exc}") from exc
if isinstance(raw, dict):
if isinstance(raw.get("messages"), list):
raw = raw["messages"]
elif "components" in raw:
raw = [{"updateComponents": {"components": raw["components"]}}]
elif any(k in raw for k in ("createSurface", "updateComponents", "updateDataModel", "deleteSurface")):
raw = [raw]
elif raw.get("component"):
raw = [{"updateComponents": {"components": [raw]}}]
else:
raise ValueError("messages must be a list of A2UI message dicts or a JSON string")
if not isinstance(raw, list):
raise ValueError("messages must be a list of A2UI message dicts or a JSON string")
items = [dict(m) for m in raw if isinstance(m, dict)]
if not items:
raise ValueError("messages must contain at least one A2UI message dict")
if not any(m.get("createSurface") for m in items):
items.insert(0, {"createSurface": {"surfaceId": surface_id, "catalogId": "basic"}})
return normalise_a2ui_messages({"messages": items})
def normalise_a2ui_messages(result: Any) -> List[Dict[str, Any]]:
"""Normalise A2UI messages — ensure version field and list shape."""
raw = _extract_raw_messages(result)
out: List[Dict[str, Any]] = []
for msg in raw:
item = dict(msg)
if "version" not in item:
item["version"] = A2UI_VERSION
out.append(item)
return out
def infer_surface_id(result: Any, messages: Optional[List[Dict[str, Any]]] = None) -> str:
"""Infer surface id from createSurface message or result dict."""
msgs = messages if messages is not None else normalise_a2ui_messages(result)
for msg in msgs:
create = msg.get("createSurface")
if isinstance(create, dict):
sid = create.get("surfaceId") or create.get("id")
if sid:
return str(sid)
if isinstance(result, dict):
sid = result.get("surface_id") or result.get("surfaceId")
if sid:
return str(sid)
return DEFAULT_SURFACE_ID
def build_a2ui_extra(result: Any) -> Optional[Dict[str, Any]]:
"""Build extra_data dict for RunEvent when result is A2UI."""
if not is_a2ui_tool_result(result):
return None
messages = normalise_a2ui_messages(result)
if not messages:
return None
surface_id = infer_surface_id(result, messages)
return {"a2ui": messages, "surface_id": surface_id}
def tool_completed_extra(result: Any, *, has_complete_args: bool = True) -> Dict[str, Any]:
"""Merge standard tool-completion flags with optional A2UI and media payloads."""
extra: Dict[str, Any] = {}
if has_complete_args:
extra["has_complete_args"] = True
try:
from praisonaiui.media_utils import build_media_extra
media = build_media_extra(result)
if media:
extra.update(media)
except ImportError:
pass
a2ui_extra = build_a2ui_extra(result)
if a2ui_extra:
extra.update(a2ui_extra)
return extra
+126
View File
@@ -0,0 +1,126 @@
"""Optional L1 backend injection for PraisonAIUI features.
Standalone aiui uses built-in defaults. When integrated with PraisonAI,
the wrapper calls ``set_backend()`` to supply SDK bridges without coupling
aiui to praisonai imports.
"""
from __future__ import annotations
import os
from typing import Any, Callable, Dict, List, Optional
_backends: Dict[str, Any] = {}
def set_backend(name: str, impl: Any) -> None:
"""Register a backend implementation for a feature area."""
_backends[name] = impl
def get_backend(name: str, default: Any = None) -> Any:
"""Return a registered backend or *default*."""
return _backends.get(name, default)
def clear_backends() -> None:
"""Clear all injected backends (tests)."""
_backends.clear()
def list_backends() -> List[str]:
"""Return names of registered backends."""
return list(_backends.keys())
def is_integrated_mode() -> bool:
"""True when PraisonAI host has injected SDK bridges."""
if os.environ.get("PRAISONAI_INTEGRATED", "").strip().lower() in ("1", "true", "yes"):
return True
return any(k in _backends for k in ("hooks", "workflows", "usage_query", "usage_sink"))
def get_workflow_runner() -> Optional[Callable[..., Dict[str, Any]]]:
"""Return workflow runner if injected."""
runner = get_backend("workflows")
return runner if callable(runner) else None
def get_hooks_lister() -> Optional[Callable[[], list]]:
"""Return SDK hooks lister if injected."""
lister = get_backend("hooks")
return lister if callable(lister) else None
def get_usage_sink() -> Any:
"""Return TokenUsageSinkProtocol implementation if injected."""
return get_backend("usage_sink")
def get_usage_query() -> Optional[Callable[..., Any]]:
"""Return SDK usage query callable if injected."""
query = get_backend("usage_query")
return query if callable(query) else None
def get_approvals_lister() -> Optional[Callable[..., List[Dict[str, Any]]]]:
"""Return SDK approvals lister callable if injected."""
lister = get_backend("approvals")
return lister if callable(lister) else None
def get_jobs_store_factory() -> Optional[Callable[[], Any]]:
"""Return jobs store factory if injected by praisonai host."""
factory = get_backend("jobs_store")
return factory if callable(factory) else None
def get_jobs_executor_factory() -> Optional[Callable[[], Any]]:
"""Return jobs executor factory if injected by praisonai host."""
factory = get_backend("jobs_executor")
return factory if callable(factory) else None
def get_channel_bot_factory() -> Optional[Callable[..., Any]]:
"""Return channel bot factory(platform, token, agent, config) if injected."""
factory = get_backend("channel_bot")
return factory if callable(factory) else None
def get_tool_resolver() -> Any:
"""Return ToolResolver-like object from backend or praisonai wrapper."""
resolver = get_backend("tool_resolver")
if resolver is not None:
return resolver() if callable(resolver) and not hasattr(resolver, "resolve") else resolver
try:
from praisonai.tool_resolver import ToolResolver
return ToolResolver()
except ImportError:
return None
def get_kanban_store_factory() -> Optional[Callable[[], Any]]:
"""Return kanban store factory if injected by praisonai host."""
factory = get_backend("kanban_store")
return factory if callable(factory) else None
def get_kanban_api_base() -> Optional[str]:
"""Optional external kanban API base (wrapper mounts its own router)."""
base = get_backend("kanban_api_base")
return str(base).rstrip("/") if base else None
def resolve_tools(tool_names: List[Any]) -> List[Any]:
"""Resolve tool name strings via injected or default ToolResolver."""
resolver = get_tool_resolver()
if resolver is None:
return []
resolved: List[Any] = []
for name in tool_names:
if isinstance(name, str) and name.strip():
tool = resolver.resolve(name.strip())
if tool is not None:
resolved.append(tool)
return resolved
+49
View File
@@ -326,6 +326,55 @@ def page(
return decorator
def surface_page(
id: str,
*,
surface_id: str = "main",
title: str = "Canvas",
icon: str = "🖼️",
group: str = "Custom",
description: str = "",
order: int = 100,
) -> Callable[[F], F]:
"""Register a dashboard page backed by an A2UI surface."""
def decorator(func: F) -> F:
from praisonaiui.server import register_page
async def handler():
data = func()
if asyncio.iscoroutine(data):
data = await data
if isinstance(data, dict) and "_surface" in data:
return data
return {"_surface": {"id": surface_id, "messages": data or []}}
register_page(
id,
title=title,
icon=icon,
group=group,
description=description,
handler=handler,
order=order,
)
return func
return decorator
def surface_action(surface_id: str = "main") -> Callable[[F], F]:
"""Decorator to handle user actions from an A2UI surface."""
def decorator(func: F) -> F:
from praisonaiui.features.surfaces import register_surface_action
register_surface_action(surface_id, func)
return func
return decorator
def resume(func: F) -> F:
"""Decorator for session resume handler.
+110
View File
@@ -2797,6 +2797,116 @@ pages_app = typer.Typer(name="pages", help="Manage sidebar pages (list, ids)", a
app.add_typer(pages_app, name="pages")
# ── Surfaces (A2UI canvas) ───────────────────────────────────────────
surface_app = typer.Typer(
name="surface", help="Manage A2UI surfaces (agent canvas)", add_completion=False
)
app.add_typer(surface_app, name="surface")
@surface_app.command("list")
def surface_list(server: str = _SERVER_OPT) -> None:
"""List all surfaces."""
try:
data = _api_get(server, "/api/surfaces")
surfaces = data.get("surfaces", [])
if not surfaces:
console.print("[dim]No surfaces yet (empty canvas is normal).[/dim]")
return
for s in surfaces:
console.print(
f" {s.get('id', '?')} — {s.get('message_count', 0)} message(s)"
)
console.print(f"Total: {len(surfaces)}")
except Exception as e:
console.print(f"[red]✗[/red] {e}")
raise typer.Exit(code=1)
@surface_app.command("get")
def surface_get(
surface_id: str = typer.Argument("main", help="Surface id"),
server: str = _SERVER_OPT,
as_json: bool = typer.Option(False, "--json", help="Print raw JSON"),
) -> None:
"""Get surface messages (empty list if not created yet)."""
import json as _json
try:
data = _api_get(server, f"/api/surfaces/{surface_id}")
if as_json:
console.print(_json.dumps(data, indent=2))
return
messages = data.get("messages") or []
console.print(f"Surface: {data.get('id', surface_id)}")
console.print(f"Messages: {len(messages)}")
if messages:
console.print(_json.dumps(messages, indent=2))
except Exception as e:
console.print(f"[red]✗[/red] {e}")
raise typer.Exit(code=1)
@surface_app.command("push")
def surface_push(
surface_id: str = typer.Argument("main", help="Surface id"),
file: Path = typer.Option(..., "--file", "-f", help="JSON file: {messages: [...]}"),
replace: bool = typer.Option(False, "--replace", help="Replace instead of append"),
server: str = _SERVER_OPT,
) -> None:
"""Push A2UI messages to a surface (same as POST /api/surfaces/{id}/messages)."""
import json as _json
try:
body = _json.loads(file.read_text(encoding="utf-8"))
if "messages" not in body:
raise typer.BadParameter("JSON file must contain a 'messages' array")
if replace:
body["replace"] = True
data = _api_post(server, f"/api/surfaces/{surface_id}/messages", body)
console.print(
f"[green]✓[/green] Pushed to {data.get('id', surface_id)} "
f"({data.get('message_count', '?')} message(s))"
)
except typer.BadParameter:
raise
except Exception as e:
console.print(f"[red]✗[/red] {e}")
raise typer.Exit(code=1)
@surface_app.command("clear")
def surface_clear(
surface_id: str = typer.Argument("main", help="Surface id"),
server: str = _SERVER_OPT,
) -> None:
"""Delete a surface (same as DELETE /api/surfaces/{id})."""
try:
data = _api_delete(server, f"/api/surfaces/{surface_id}")
console.print(f"[green]✓[/green] Cleared surface: {data.get('id', surface_id)}")
except Exception as e:
err = str(e)
if "404" in err:
console.print(f"[yellow]![/yellow] Surface '{surface_id}' not found (already empty)")
return
console.print(f"[red]✗[/red] {e}")
raise typer.Exit(code=1)
@surface_app.command("status")
def surface_status(server: str = _SERVER_OPT) -> None:
"""Show surface feature status."""
try:
data = _api_get(server, "/api/surfaces")
surfaces = data.get("surfaces", [])
total_msgs = sum(s.get("message_count", 0) for s in surfaces)
console.print(f"Surfaces: {len(surfaces)}")
console.print(f"Total messages: {total_msgs}")
except Exception as e:
console.print(f"[red]✗[/red] {e}")
raise typer.Exit(code=1)
@pages_app.command("list")
def pages_list(server: str = _SERVER_OPT) -> None:
"""List all registered sidebar pages."""
+2 -1
View File
@@ -7,6 +7,7 @@ can call host-page JavaScript functions (e.g., navigate_to("/settings")).
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from functools import wraps
from typing import Any, Awaitable, Callable, Dict, List, Optional
@@ -229,7 +230,7 @@ async def call_copilot_function(name: str, arguments: Dict[str, Any]) -> Any:
@copilot_function("get_page_info", "Get information about the current page", [])
async def get_page_info() -> Dict[str, Any]:
"""Get basic page information."""
return {"title": "PraisonAIUI Chat", "url": "/", "timestamp": "TODO: implement in frontend"}
return {"title": "PraisonAIUI Chat", "url": "/", "timestamp": datetime.utcnow().isoformat()}
@copilot_function(
+126
View File
@@ -0,0 +1,126 @@
"""Discover and serve third-party dashboard UI plugins (manifest + static assets)."""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
from starlette.requests import Request
from starlette.responses import FileResponse, JSONResponse, Response
from starlette.routing import Route
logger = logging.getLogger(__name__)
_BUILTIN_PLUGIN_ROOT = (
Path(__file__).resolve().parent / "templates" / "frontend" / "dashboard-plugins"
)
def _plugin_search_dirs(extra_dirs: list[str] | None = None) -> list[Path]:
dirs: list[Path] = []
home = Path.home() / ".praisonai" / "dashboard-plugins"
if home.is_dir():
dirs.append(home)
if _BUILTIN_PLUGIN_ROOT.is_dir():
dirs.append(_BUILTIN_PLUGIN_ROOT)
for raw in extra_dirs or []:
p = Path(raw).expanduser()
if p.is_dir():
dirs.append(p)
return dirs
def discover_dashboard_plugins(extra_dirs: list[str] | None = None) -> list[dict[str, Any]]:
"""Scan dashboard-plugins folders for manifest.json files."""
manifests: list[dict[str, Any]] = []
seen: set[str] = set()
for root in _plugin_search_dirs(extra_dirs):
for child in sorted(root.iterdir()):
if not child.is_dir():
continue
manifest_path = child / "manifest.json"
if not manifest_path.is_file():
continue
try:
data = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
logger.warning("Invalid dashboard plugin manifest %s: %s", manifest_path, exc)
continue
name = data.get("name") or child.name
if name in seen:
continue
seen.add(name)
tab = data.get("tab") or {}
path = tab.get("path") or f"/{name}"
page_id = path.strip("/").split("/")[0] or name
manifests.append(
{
"name": name,
"label": data.get("label", name),
"description": data.get("description", ""),
"icon": data.get("icon", "📦"),
"version": data.get("version", "0.0.0"),
"entry": data.get("entry", "index.js"),
"css": data.get("css"),
"has_api": bool(data.get("api")),
"tab": tab,
"root": str(child.resolve()),
"page": {
"id": page_id,
"title": data.get("label", name),
"icon": data.get("icon", "📦"),
"group": data.get("group", "Plugins"),
"description": data.get("description", ""),
},
}
)
return manifests
def _resolve_plugin_file(name: str, subpath: str, extra_dirs: list[str] | None) -> Path | None:
for manifest in discover_dashboard_plugins(extra_dirs):
if manifest["name"] != name:
continue
root = Path(manifest["root"])
target = (root / subpath).resolve()
if not str(target).startswith(str(root.resolve())):
return None
if target.is_file():
return target
return None
def make_dashboard_plugin_routes(
get_extra_dirs,
) -> list[Route]:
"""Factory: ``get_extra_dirs`` returns plugin dir list from server config."""
async def list_plugins(request: Request) -> JSONResponse:
extra = get_extra_dirs()
return JSONResponse({"plugins": discover_dashboard_plugins(extra)})
async def serve_plugin_asset(request: Request) -> Response:
name = request.path_params["name"]
subpath = request.path_params["path"]
extra = get_extra_dirs()
file_path = _resolve_plugin_file(name, subpath, extra)
if not file_path:
return Response(status_code=404)
media = "text/javascript"
if file_path.suffix == ".css":
media = "text/css"
elif file_path.suffix == ".json":
media = "application/json"
return FileResponse(file_path, media_type=media)
return [
Route("/api/dashboard/plugins", list_plugins, methods=["GET"]),
Route(
"/dashboard-plugins/{name}/{path:path}",
serve_plugin_asset,
methods=["GET"],
),
]
+8
View File
@@ -12,6 +12,10 @@ a sentinel system message (content ``""``). This message is filtered out
when returning messages to the frontend.
If the SDK is not installed, AIUI falls back to ``JSONFileDataStore``.
When AIUI is integrated into PraisonAI, ``PraisonAISessionDataStore`` (the
wrapper used by the integration layer) is the canonical datastore entrypoint.
``SDKFileDataStore`` remains the standalone AIUI adapter.
"""
from __future__ import annotations
@@ -149,6 +153,8 @@ class SDKFileDataStore(BaseDataStore):
metadata: dict[str, Any] = {}
if "toolCalls" in message:
metadata["toolCalls"] = message["toolCalls"]
if "elements" in message:
metadata["elements"] = message["elements"]
await asyncio.to_thread(
self._store.add_message,
@@ -279,5 +285,7 @@ class SDKFileDataStore(BaseDataStore):
# Restore UI-specific fields from metadata
if "toolCalls" in meta:
d["toolCalls"] = meta["toolCalls"]
if "elements" in meta:
d["elements"] = meta["elements"]
messages.append(d)
return messages
+5
View File
@@ -47,6 +47,7 @@ def auto_register_defaults() -> None:
_BUILTIN_FEATURES = [
(".agents", "AgentsCrudFeature"),
(".approvals", "ApprovalsFeature"),
(".agui", "AguiFeature"),
(".attachments", "AttachmentsFeature"),
(".auth", "AuthFeature"),
(".browser_automation", "BrowserAutomationFeature"),
@@ -59,6 +60,7 @@ def auto_register_defaults() -> None:
(".hooks", "HooksFeature"),
(".i18n", "I18nFeature"),
(".jobs", "JobsFeature"),
(".kanban", "KanbanFeature"),
(".logs", "LogsFeature"),
(".marketplace", "MarketplaceFeature"),
(".media_analysis", "MediaAnalysisFeature"),
@@ -73,15 +75,18 @@ def auto_register_defaults() -> None:
(".sessions_ext", "SessionsFeature"),
(".skills", "SkillsFeature"),
(".subagents", "SubagentsFeature"),
(".surfaces", "SurfacesFeature"),
(".theme", "ThemeFeature"),
(".tts", "TTSFeature"),
(".usage", "UsageFeature"),
(".realtime", "RealtimeFeature"),
(".workflows", "WorkflowsFeature"),
(".guardrails", "GuardrailsFeature"),
(".eval", "EvalFeature"),
(".telemetry", "TelemetryFeature"),
(".tracing", "TracingFeature"),
(".security", "SecurityFeature"),
(".video", "VideoFeature"),
]
for mod_path, cls_name in _BUILTIN_FEATURES:
+30 -29
View File
@@ -222,8 +222,9 @@ class SDKAgentRegistry(AgentRegistryProtocol):
"""Wraps praisonaiagents.Agent for production use."""
def __init__(self) -> None:
from praisonaiagents import Agent # noqa: F401
from praisonaiagents import Agent
self._Agent = Agent
self._simple = SimpleAgentRegistry()
logger.info("SDKAgentRegistry initialized (praisonaiagents available)")
@@ -231,7 +232,22 @@ class SDKAgentRegistry(AgentRegistryProtocol):
self._simple.set_data_file(path)
def create(self, agent_def: Dict[str, Any]) -> Dict[str, Any]:
return self._simple.create(agent_def)
agent = self._simple.create(agent_def)
try:
# Instantiate SDK Agent to validate integrated configuration.
self._Agent(
name=agent.get("name", "assistant"),
instructions=(
agent.get("instructions")
or agent.get("system_prompt")
or "You are a helpful assistant."
),
llm=agent.get("model", "gpt-4o-mini"),
)
_sync_to_gateway(agent)
except Exception as e:
logger.debug("SDK agent instantiation skipped: %s", e)
return agent
def get(self, agent_id: str) -> Optional[Dict[str, Any]]:
return self._simple.get(agent_id)
@@ -294,27 +310,20 @@ def _sync_to_gateway(agent_def: Dict[str, Any]) -> None:
"reflection": agent_def.get("reflection", False),
}
# G8: Resolve tool name strings to callables via ToolResolver
# G8: Resolve tool name strings to callables via backend ToolResolver
tool_names = agent_def.get("tools", [])
if tool_names:
agent_tools = []
try:
from praisonai.tool_resolver import ToolResolver
from praisonaiui.backends import resolve_tools
resolver = ToolResolver()
for tn in tool_names:
if isinstance(tn, str) and tn.strip():
resolved = resolver.resolve(tn.strip())
if resolved:
agent_tools.append(resolved)
else:
logger.warning(
f"Tool '{tn}' not found for agent '{agent_def.get('id')}'"
)
except ImportError:
logger.debug("ToolResolver not available, skipping tool resolution")
agent_tools = resolve_tools(tool_names)
if agent_tools:
agent_kwargs["tools"] = agent_tools
else:
for tn in tool_names:
if isinstance(tn, str) and tn.strip():
logger.warning(
f"Tool '{tn}' not found for agent '{agent_def.get('id')}'"
)
agent = Agent(**agent_kwargs)
gw.register_agent(agent, agent_id=agent_def["id"])
@@ -652,20 +661,12 @@ class AgentsCrudFeature(BaseFeatureProtocol):
from praisonaiagents import Agent
# Resolve tools from CRUD definition
agent_tools = []
tool_names = agent_def.get("tools", [])
agent_tools = []
if tool_names:
try:
from praisonai.tool_resolver import ToolResolver
from praisonaiui.backends import resolve_tools
resolver = ToolResolver()
for tn in tool_names:
if isinstance(tn, str) and tn.strip():
resolved = resolver.resolve(tn.strip())
if resolved:
agent_tools.append(resolved)
except ImportError:
pass
agent_tools = resolve_tools(tool_names)
agent = Agent(
name=agent_def.get("name", "assistant"),
+126
View File
@@ -0,0 +1,126 @@
"""AG-UI protocol feature — POST /agui SSE streaming for CopilotKit clients."""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional, Union
from starlette.requests import Request
from starlette.responses import JSONResponse, Response, StreamingResponse
from starlette.routing import Route
from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
def _resolve_agent(agent_name: Optional[str] = None):
"""Resolve a registered PraisonAI agent instance."""
from praisonaiui.server import _agents
if agent_name and agent_name in _agents:
return _agents[agent_name]["agent"]
if len(_agents) == 1:
return next(iter(_agents.values()))["agent"]
if _agents:
first = next(iter(_agents.keys()))
return _agents[first]["agent"]
try:
from praisonaiui.server import get_provider
provider = get_provider()
if hasattr(provider, "agents") and provider.agents:
return provider.agents[0]
except Exception:
pass
return None
async def _post_agui(request: Request) -> Union[StreamingResponse, JSONResponse]:
try:
from praisonaiagents.ui.agui import AGUI
from praisonaiagents.ui.agui.encoder import EventEncoder
from praisonaiagents.ui.agui.types import RunAgentInput
except ImportError:
return JSONResponse(
{"error": "AG-UI requires praisonaiagents. Install: pip install aiui[praisonai]"},
status_code=503,
)
body = await request.json()
agent_name = body.get("agent_name") or request.query_params.get("agent")
agent = _resolve_agent(agent_name)
if agent is None:
return JSONResponse({"error": "No agent registered"}, status_code=503)
try:
run_input = RunAgentInput.model_validate(body)
except Exception as exc:
return JSONResponse({"error": f"Invalid AG-UI input: {exc}"}, status_code=400)
agui = AGUI(agent=agent)
encoder = EventEncoder()
async def event_stream():
async for event in agui._run_agent(run_input):
yield encoder.encode(event)
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
},
)
async def _agui_status(_request: Request) -> JSONResponse:
agent = _resolve_agent()
return JSONResponse(
{
"status": "available" if agent else "unavailable",
"agent_registered": agent is not None,
}
)
async def _agui_options(_request: Request) -> Response:
return JSONResponse({}, headers={"Access-Control-Allow-Origin": "*"})
class AguiFeature(BaseFeatureProtocol):
"""AG-UI SSE endpoint at POST /agui."""
feature_name = "agui"
feature_description = "AG-UI protocol (CopilotKit-compatible SSE)"
@property
def name(self) -> str:
return self.feature_name
@property
def description(self) -> str:
return self.feature_description
def routes(self) -> List[Route]:
return [
Route("/agui", _post_agui, methods=["POST"]),
Route("/agui", _agui_options, methods=["OPTIONS"]),
Route("/agui/status", _agui_status, methods=["GET"]),
]
async def health(self) -> Dict[str, Any]:
agent = _resolve_agent()
return {
"status": "ok" if agent else "degraded",
"feature": self.name,
"agent_available": agent is not None,
}
+69 -5
View File
@@ -32,6 +32,20 @@ RISK_LEVELS = ["low", "medium", "high", "critical"]
RISK_ICONS = {"low": "✅", "medium": "⚠️", "high": "🟠", "critical": "🔴"}
def _list_backend_approvals() -> List[Dict[str, Any]] | None:
"""List approvals from injected backend when available."""
from praisonaiui.backends import get_approvals_lister
lister = get_approvals_lister()
if lister is None:
return None
try:
items = lister()
except Exception:
return None
return items if isinstance(items, list) else None
# ── Approval Protocol ────────────────────────────────────────────────
@@ -200,10 +214,14 @@ class SDKApprovalManager(ApprovalProtocol):
def __init__(self) -> None:
from praisonaiagents.approval import get_approval_registry
from praisonaiagents.approval.protocols import ApprovalDecision, ApprovalRequest
self._registry = get_approval_registry()
self._simple = SimpleApprovalManager()
logger.info("SDKApprovalManager initialized (ApprovalRegistry available)")
self._ApprovalDecision = ApprovalDecision
self._ApprovalRequest = ApprovalRequest
self._registry.set_backend(_UIStoreBackend(self._simple))
logger.info("SDKApprovalManager initialized (ApprovalRegistry wired to UI store)")
def list_pending(self) -> List[Dict[str, Any]]:
return self._simple.list_pending()
@@ -212,12 +230,19 @@ class SDKApprovalManager(ApprovalProtocol):
return self._simple.list_history(limit=limit)
def request_approval(self, entry: Dict[str, Any]) -> Dict[str, Any]:
return self._simple.request_approval(entry)
result = self._simple.request_approval(entry)
tool = entry.get("tool_name")
if tool and entry.get("status") == "pending":
self._registry.add_requirement(tool, entry.get("risk_level", "high"))
return result
def approve(
self, approval_id: str, reason: str = "", approver: str = "user", always: bool = False
) -> Optional[Dict[str, Any]]:
return self._simple.approve(approval_id, reason, approver, always)
entry = self._simple.approve(approval_id, reason, approver, always)
if entry and always and entry.get("tool_name"):
self._registry.add_requirement(entry["tool_name"], entry.get("risk_level", "high"))
return entry
def deny(
self, approval_id: str, reason: str = "", approver: str = "user", always: bool = False
@@ -228,10 +253,17 @@ class SDKApprovalManager(ApprovalProtocol):
return self._simple.get(approval_id)
def get_policies(self) -> Dict[str, Any]:
return self._simple.get_policies()
policies = self._simple.get_policies()
policies["sdk_auto_approve_env"] = self._registry.is_env_auto_approve()
return policies
def update_policies(self, updates: Dict[str, Any]) -> Dict[str, Any]:
return self._simple.update_policies(updates)
result = self._simple.update_policies(updates)
for tool in updates.get("always_deny_tools") or []:
self._registry.add_requirement(tool, "high")
for tool in updates.get("auto_approve_tools") or []:
self._registry.remove_requirement(tool)
return result
def health(self) -> Dict[str, Any]:
h = self._simple.health()
@@ -240,6 +272,34 @@ class SDKApprovalManager(ApprovalProtocol):
return h
class _UIStoreBackend:
"""Routes agent approval requests into the UI SimpleApprovalManager."""
def __init__(self, store: SimpleApprovalManager) -> None:
self._store = store
def request_approval_sync(self, request) -> Any:
from praisonaiagents.approval.protocols import ApprovalDecision
entry = self._store.request_approval(
{
"tool_name": request.tool_name,
"agent_name": getattr(request, "agent_name", "") or "",
"risk_level": request.risk_level or "medium",
"arguments": request.arguments or {},
}
)
status = entry.get("status", "pending")
if status == "approved":
return ApprovalDecision(approved=True, reason=entry.get("reason", ""))
if status == "denied":
return ApprovalDecision(approved=False, reason=entry.get("reason", "denied"))
return ApprovalDecision(approved=False, reason="Awaiting UI approval")
async def request_approval(self, request) -> Any:
return self.request_approval_sync(request)
# ── Manager singleton ────────────────────────────────────────────────
_approval_manager: Optional[ApprovalProtocol] = None
@@ -322,6 +382,10 @@ class ApprovalsFeature(BaseFeatureProtocol):
async def _list(self, request: Request) -> JSONResponse:
mgr = get_approval_manager()
backend_items = _list_backend_approvals()
if backend_items is not None:
return JSONResponse({"approvals": backend_items, "count": len(backend_items)})
status_filter = request.query_params.get("status", "all")
if status_filter == "pending":
items = mgr.list_pending()
+16 -1
View File
@@ -13,7 +13,7 @@ import uuid
from typing import Any, Dict, List, Optional
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.responses import FileResponse, JSONResponse
from starlette.routing import Route
from ._base import BaseFeatureProtocol
@@ -256,6 +256,20 @@ async def _delete_attachment(request: Request) -> JSONResponse:
return JSONResponse({"error": "Not found"}, status_code=404)
async def _serve_media(request: Request) -> FileResponse | JSONResponse:
"""GET /api/chat/media/{attachment_id} — serve a stored generated-media file."""
attachment_id = request.path_params["attachment_id"]
mgr = get_attachment_manager()
meta = mgr.get(attachment_id)
if not meta or not meta.get("path"):
return JSONResponse({"error": "Not found"}, status_code=404)
return FileResponse(
meta["path"],
media_type=meta.get("content_type", "application/octet-stream"),
filename=meta.get("filename"),
)
# ── Feature ──────────────────────────────────────────────────────
@@ -278,6 +292,7 @@ class AttachmentsFeature(BaseFeatureProtocol):
Route("/api/chat/attachments", _upload_attachment, methods=["POST"]),
Route("/api/chat/attachments/{session_id}", _list_attachments, methods=["GET"]),
Route("/api/chat/attachments/{attachment_id}", _delete_attachment, methods=["DELETE"]),
Route("/api/chat/media/{attachment_id}", _serve_media, methods=["GET"]),
]
+2
View File
@@ -190,6 +190,8 @@ class AuthFeature(BaseFeatureProtocol):
"mode": _auth_config.get("mode", "none"),
"api_keys": len(_auth_config.get("api_keys", {})),
"active_sessions": len(_active_sessions),
"sdk_gap": True,
"sdk_gap_message": "Auth uses in-memory store; no praisonaiagents auth API.",
**gateway_health(),
}
@@ -81,9 +81,11 @@ class BrowserAutomationManager(BrowserAutomationProtocol):
agent = BrowserAgent()
result = await agent.run(task=task, url=url)
if isinstance(result, dict) and result.get("error"):
return {"status": "failed", "error": str(result.get("error"))}
return {"status": "completed", "result": str(result)}
except Exception as e:
return {"status": "error", "error": str(e)}
return {"status": "failed", "error": str(e)}
_browser_manager = None
+27 -11
View File
@@ -231,6 +231,7 @@ class ChannelsFeature(BaseFeatureProtocol):
"""
platform = entry["platform"]
config = entry.get("config", {})
fallback_errors: List[str] = []
# Platform-aware token resolution:
# email/agentmail use api_key or dedicated env vars, not bot_token
if platform == "agentmail":
@@ -248,7 +249,10 @@ class ChannelsFeature(BaseFeatureProtocol):
else:
token = config.get("bot_token", "")
if not token:
return f"No token in channel config or environment for {platform}"
return (
f"No token configured for {platform}. "
"Set config.bot_token (or platform-specific token) or the matching env var."
)
# Slack Socket Mode requires an app_token (xapp-...)
if platform == "slack":
if not self._resolve_slack_app_token(config):
@@ -262,15 +266,10 @@ class ChannelsFeature(BaseFeatureProtocol):
try:
from praisonaiagents import Agent
# G3: Resolve tools via praisonai wrapper
agent_tools = []
try:
from praisonai.tool_resolver import ToolResolver
# G3: Resolve tools via backend or praisonai wrapper
from praisonaiui.backends import resolve_tools
resolver = ToolResolver()
agent_tools = resolver.resolve_many(["internet_search"])
except ImportError:
pass # praisonai not installed — no tools
agent_tools = resolve_tools(["internet_search"])
agent = Agent(
name="assistant",
@@ -321,13 +320,18 @@ class ChannelsFeature(BaseFeatureProtocol):
bot = gw._create_bot(platform, token, gw_agent, bot_config, ch_cfg)
except Exception as e:
logger.debug(f"Gateway _create_bot failed: {e}")
fallback_errors.append(f"gateway: {e}")
# ── Strategy 2: direct import from praisonai.bots ───────────────
if bot is None:
bot = self._create_bot_direct(platform, token, agent, config)
if bot is None:
return f"Could not create {platform} bot — required packages may not be installed"
details = f" ({'; '.join(fallback_errors)})" if fallback_errors else ""
return (
f"Could not create {platform} bot. "
f"Check platform dependencies and channel config{details}"
)
# ── Start the bot as an asyncio task ─────────────────────────────
async def _run_safe(name: str, b: Any) -> None:
@@ -364,7 +368,19 @@ class ChannelsFeature(BaseFeatureProtocol):
@staticmethod
def _create_bot_direct(platform: str, token: str, agent: Any, config: Dict[str, Any]) -> Any:
"""Try to directly instantiate a bot class by platform name."""
"""Try injected backend, then direct import by platform name."""
from praisonaiui.backends import get_channel_bot_factory
factory = get_channel_bot_factory()
if factory is not None:
try:
bot = factory(platform, token, agent, config)
if bot is not None:
logger.info("Created %s bot via injected channel_bot backend", platform)
return bot
except Exception as exc:
logger.debug("channel_bot backend failed for %s: %s", platform, exc)
bot_classes: Dict[str, List[str]] = {
"discord": [
"praisonai.bots.discord.DiscordBot",
+66 -11
View File
@@ -32,6 +32,16 @@ from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
def _collect_element(collected: List[Dict[str, Any]], element: Dict[str, Any]) -> None:
"""Append *element* unless the same URL is already collected."""
if not element:
return
url = element.get("url")
if url and any(e.get("url") == url for e in collected):
return
collected.append(element)
# ── Tool display enrichment (reuses SDK's TOOL_LABELS — DRY) ────────
@@ -111,6 +121,7 @@ class ChatMessage:
agent_name: Optional[str] = None
timestamp: float = field(default_factory=time.time)
metadata: Optional[Dict[str, Any]] = None
elements: Optional[List[Dict[str, Any]]] = None
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {
@@ -124,6 +135,8 @@ class ChatMessage:
d["agent_name"] = self.agent_name
if self.metadata:
d["metadata"] = self.metadata
if self.elements:
d["elements"] = self.elements
return d
@@ -447,6 +460,7 @@ async def _run_and_broadcast(
_seen_tool_started: set = set() # tool_call_id or name we've sent STARTED for
_seen_tool_completed: set = set() # tool_call_id or name we've sent COMPLETED for
collected_tool_calls: Dict[str, Dict] = {} # Merge STARTED+COMPLETED per tool_call_id
collected_elements: List[Dict[str, Any]] = []
try:
# Pass image attachments to provider for SDK native multimodal handling
@@ -467,15 +481,17 @@ async def _run_and_broadcast(
}
if event.type == RunEventType.RUN_CONTENT:
payload["token"] = event.token or ""
if event.token:
full_response += event.token
token = event.token or ""
if token and token != "None":
payload["token"] = token
full_response += token
elif event.type == RunEventType.RUN_COMPLETED:
payload["content"] = event.content or full_response
# Prefer SDKs final response over accumulated tokens —
# this matches what finalizeDelta() shows in the live view.
if event.content:
full_response = event.content
content = event.content or full_response
if content == "None":
content = ""
payload["content"] = content
if content:
full_response = content
elif event.type == RunEventType.RUN_ERROR:
payload["error"] = event.error or "Unknown error"
elif event.type == RunEventType.TOOL_CALL_STARTED:
@@ -535,6 +551,30 @@ async def _run_and_broadcast(
payload["result"] = event.result
payload["tool_call_id"] = event.tool_call_id
_enrich_tool_payload(payload, tool_step_counter, is_completed=True)
extra = getattr(event, "extra_data", None) or {}
if extra.get("a2ui"):
payload["a2ui"] = extra["a2ui"]
payload["surface_id"] = extra.get("surface_id", "main")
try:
from praisonaiui.features.surfaces import ingest_a2ui_extra
await ingest_a2ui_extra(extra, session_id=session_id)
except ImportError:
pass
if extra.get("elements"):
payload["elements"] = extra["elements"]
for el in extra["elements"]:
if isinstance(el, dict):
_collect_element(collected_elements, el)
await mgr.broadcast(
session_id,
{
"type": "message_element",
"session_id": session_id,
"run_id": run_id,
"element": el,
},
)
# Merge COMPLETED into STARTED entry (richer args + result)
tc_id = payload.get("tool_call_id", "")
if tc_id in collected_tool_calls:
@@ -543,6 +583,11 @@ async def _run_and_broadcast(
entry["result"] = payload.get("result")
entry["formatted_result"] = payload.get("formatted_result", "✓ Done")
entry["status"] = "done"
if payload.get("elements"):
entry["elements"] = payload["elements"]
if payload.get("a2ui"):
entry["a2ui"] = payload["a2ui"]
entry["surface_id"] = payload.get("surface_id")
# Re-enrich description with full args (now has keywords)
_enrich_tool_payload(entry, entry.get("step_number", 0), is_completed=False)
else:
@@ -567,6 +612,13 @@ async def _run_and_broadcast(
elif event.type == RunEventType.LLM_CONTENT:
# Intermediate narrative text between tool calls
payload["content"] = event.content or ""
elif event.type == RunEventType.MESSAGE_ELEMENT:
extra = getattr(event, "extra_data", None) or {}
element = extra.get("element")
if not element or not isinstance(element, dict):
continue
payload["element"] = element
_collect_element(collected_elements, element)
if event.agent_name:
payload["agent_name"] = event.agent_name
@@ -594,12 +646,13 @@ async def _run_and_broadcast(
)
# Save assistant response
if full_response:
if full_response or collected_elements:
assistant_msg = ChatMessage(
role="assistant",
content=full_response,
content=full_response or "",
session_id=session_id,
agent_name=agent_name,
elements=collected_elements or None,
)
mgr._history.setdefault(session_id, []).append(assistant_msg)
@@ -607,10 +660,12 @@ async def _run_and_broadcast(
try:
msg_data: dict = {
"role": "assistant",
"content": full_response,
"content": full_response or "",
}
if collected_tool_calls:
msg_data["toolCalls"] = list(collected_tool_calls.values())
if collected_elements:
msg_data["elements"] = collected_elements
await _datastore.add_message(session_id, msg_data)
except Exception:
pass
+13 -1
View File
@@ -88,7 +88,19 @@ class SandboxExecutionManager(CodeExecutionProtocol):
except Exception as e:
logger.warning("SDK execute_code failed: %s", e)
# Fallback: simulate execution for safety
from praisonaiui.backends import is_integrated_mode
if is_integrated_mode():
entry = {
"language": language,
"status": "degraded",
"error": "Code execution unavailable (praisonaiagents.tools.code missing or failed)",
"sandbox": self._sandbox,
}
self._history.append(entry)
return entry
# Standalone fallback: simulate execution for safety
entry = {
"language": language,
"status": "simulated",
+28
View File
@@ -147,6 +147,9 @@ class SDKEvalManager(EvalProtocol):
def list_results(
self, *, limit: int = 50, agent_id: Optional[str] = None
) -> List[Dict[str, Any]]:
sdk_results = self._list_results_from_sdk(limit=limit, agent_id=agent_id)
if sdk_results is not None:
return sdk_results
return self._simple.list_results(limit=limit, agent_id=agent_id)
def run_evaluation(self, eval_data: Dict[str, Any]) -> Dict[str, Any]:
@@ -185,6 +188,31 @@ class SDKEvalManager(EvalProtocol):
def get_scores(self) -> List[Dict[str, Any]]:
return self._simple.get_scores()
def _list_results_from_sdk(
self, *, limit: int, agent_id: Optional[str]
) -> Optional[List[Dict[str, Any]]]:
try:
from praisonaiagents import eval as eval_module
get_store = getattr(eval_module, "get_eval_store", None)
if not callable(get_store):
return None
store = get_store()
if store is None:
return None
if hasattr(store, "list_results"):
items = store.list_results(limit=limit, agent_id=agent_id)
elif hasattr(store, "list"):
items = store.list(limit=limit, agent_id=agent_id)
else:
return None
if isinstance(items, list):
return items
return None
except Exception:
return None
def health(self) -> Dict[str, Any]:
h = self._simple.health()
h["provider"] = "SDKEvalManager"
+28 -7
View File
@@ -16,11 +16,28 @@ from starlette.routing import Route
from ._base import BaseFeatureProtocol
# In-memory hooks registry
# UI-registered hooks (API-managed) + SDK hooks merged at list time
_hooks: Dict[str, Dict[str, Any]] = {}
_hook_log: List[Dict[str, Any]] = []
def _all_hooks() -> List[Dict[str, Any]]:
"""Merge SDK hooks with UI-registered hooks."""
from praisonaiui.backends import get_hooks_lister
lister = get_hooks_lister()
sdk_hooks = lister() if lister else None
if sdk_hooks is None:
from praisonaiui._sdk_hooks import list_sdk_hooks
sdk_hooks = list_sdk_hooks()
merged = {h["id"]: h for h in sdk_hooks if h.get("id")}
for hook_id, entry in _hooks.items():
merged[hook_id] = {**entry, "source": entry.get("source", "ui")}
return list(merged.values())
class HooksFeature(BaseFeatureProtocol):
"""Hooks management wired to praisonaiagents.hooks."""
@@ -59,17 +76,17 @@ class HooksFeature(BaseFeatureProtocol):
]
async def health(self) -> Dict[str, Any]:
hooks = _all_hooks()
return {
"status": "ok",
"feature": self.name,
"total_hooks": len(_hooks),
"total_hooks": len(hooks),
"log_entries": len(_hook_log),
}
# ── API handlers ─────────────────────────────────────────────────
async def _list(self, request: Request) -> JSONResponse:
return JSONResponse({"hooks": list(_hooks.values()), "count": len(_hooks)})
hooks = _all_hooks()
return JSONResponse({"hooks": hooks, "count": len(hooks)})
async def _register(self, request: Request) -> JSONResponse:
body = await request.json()
@@ -124,9 +141,13 @@ class HooksFeature(BaseFeatureProtocol):
# ── CLI handlers ─────────────────────────────────────────────────
def _cli_list(self) -> str:
if not _hooks:
hooks = _all_hooks()
if not hooks:
return "No hooks registered"
lines = [f" {h['id']} — {h['name']} ({h['event']}, {h['type']})" for h in _hooks.values()]
lines = [
f" {h['id']} — {h.get('name', h['id'])} ({h.get('event', '?')}, {h.get('source', 'ui')})"
for h in hooks
]
return "\n".join(lines)
def _cli_trigger(self, hook_id: str) -> str:
+311 -46
View File
@@ -16,7 +16,7 @@ import logging
import time
import uuid
from abc import ABC, abstractmethod
from datetime import datetime
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional
@@ -105,33 +105,168 @@ class SimpleJobStore(JobStoreProtocol):
class SDKJobStore(JobStoreProtocol):
"""Wraps praisonai.jobs for production use."""
"""Uses praisonai.jobs store when the package is installed."""
def __init__(self) -> None:
from praisonai.jobs import JobExecutor # noqa: F401
from praisonaiui.backends import get_jobs_store_factory
self._simple = SimpleJobStore()
logger.info("SDKJobStore initialized (praisonai.jobs available)")
factory = get_jobs_store_factory()
if factory is not None:
self._sdk = factory()
logger.info("SDKJobStore initialized (injected jobs_store backend)")
return
from praisonai.jobs.server import get_store
self._sdk = get_store()
logger.info("SDKJobStore initialized (praisonai.jobs store)")
@staticmethod
def _parse_sdk_status(status: Optional[str]) -> Any:
if not status:
return None
from praisonai.jobs.models import JobStatus as SdkStatus
try:
return SdkStatus(status)
except ValueError:
return None
@staticmethod
def _ts_to_float(value: Any) -> Any:
if value is None:
return None
if hasattr(value, "timestamp"):
return value.timestamp()
return value
@staticmethod
def _float_to_dt(value: Any) -> Any:
if value is None:
return None
if isinstance(value, (int, float)):
return datetime.fromtimestamp(value, tz=timezone.utc)
return value
@classmethod
def _job_to_dict(cls, job: Any) -> Dict[str, Any]:
return {
"id": job.id,
"job_id": job.id,
"status": job.status.value if hasattr(job.status, "value") else job.status,
"prompt": job.prompt,
"agent_file": job.agent_file,
"agent_yaml": job.agent_yaml,
"recipe_name": job.recipe_name,
"framework": job.framework,
"config": job.config or {},
"webhook_url": job.webhook_url,
"timeout": job.timeout,
"session_id": job.session_id,
"idempotency_key": job.idempotency_key,
"progress_percentage": getattr(job, "progress_percentage", 0.0),
"progress_step": getattr(job, "progress_step", None),
"created_at": cls._ts_to_float(job.created_at),
"started_at": cls._ts_to_float(job.started_at),
"completed_at": cls._ts_to_float(job.completed_at),
"result": job.result,
"error": job.error,
"metrics": job.metrics,
}
@classmethod
def _dict_to_job(cls, job: Dict[str, Any]) -> Any:
from praisonai.jobs.models import Job as SdkJob
from praisonai.jobs.models import JobStatus as SdkStatus
status = job.get("status", JobStatus.QUEUED.value)
if isinstance(status, str):
status = SdkStatus(status)
return SdkJob(
id=job.get("id") or job.get("job_id") or f"run_{uuid.uuid4().hex[:12]}",
status=status,
prompt=job.get("prompt", ""),
agent_file=job.get("agent_file"),
agent_yaml=job.get("agent_yaml"),
recipe_name=job.get("recipe_name"),
framework=job.get("framework", "praisonai"),
config=job.get("config") or {},
webhook_url=job.get("webhook_url"),
timeout=job.get("timeout", 3600),
session_id=job.get("session_id"),
idempotency_key=job.get("idempotency_key"),
progress_percentage=job.get("progress_percentage", 0.0),
progress_step=job.get("progress_step"),
created_at=cls._float_to_dt(job.get("created_at")) or datetime.now(timezone.utc),
started_at=cls._float_to_dt(job.get("started_at")),
completed_at=cls._float_to_dt(job.get("completed_at")),
result=job.get("result"),
error=job.get("error"),
metrics=job.get("metrics"),
)
async def get_job_async(self, job_id: str) -> Optional[Dict[str, Any]]:
job = await self._sdk.get(job_id)
return self._job_to_dict(job) if job else None
async def get_by_idempotency_key_async(self, key: str) -> Optional[Dict[str, Any]]:
job = await self._sdk.get_by_idempotency_key(key)
return self._job_to_dict(job) if job else None
async def save_job_async(self, job: Dict[str, Any]) -> None:
await self._sdk.save(self._dict_to_job(job))
async def delete_job_async(self, job_id: str) -> bool:
return await self._sdk.delete(job_id)
async def list_jobs_async(
self,
*,
status: Optional[str] = None,
session_id: Optional[str] = None,
limit: int = 100,
offset: int = 0,
) -> List[Dict[str, Any]]:
st = self._parse_sdk_status(status)
jobs = await self._sdk.list_jobs(status=st, session_id=session_id, limit=limit, offset=offset)
return [self._job_to_dict(j) for j in jobs]
async def count_jobs_async(
self,
*,
status: Optional[str] = None,
session_id: Optional[str] = None,
) -> int:
st = self._parse_sdk_status(status)
return await self._sdk.count(status=st, session_id=session_id)
async def stats_async(self) -> Dict[str, Any]:
if hasattr(self._sdk, "get_stats"):
raw = await self._sdk.get_stats()
raw["provider"] = "SDKJobStore"
raw["sdk_available"] = True
return raw
total = await self.count_jobs_async()
return {"provider": "SDKJobStore", "sdk_available": True, "total_jobs": total}
def get(self, job_id: str) -> Optional[Dict[str, Any]]:
return self._simple.get(job_id)
return _run_store_coro(self.get_job_async(job_id))
def list_all(self) -> List[Dict[str, Any]]:
return self._simple.list_all()
return _run_store_coro(self.list_jobs_async(limit=10000))
def save(self, job: Dict[str, Any]) -> None:
self._simple.save(job)
_run_store_coro(self.save_job_async(job))
def delete(self, job_id: str) -> bool:
return self._simple.delete(job_id)
return _run_store_coro(self.delete_job_async(job_id))
def stats(self) -> Dict[str, Any]:
return self._simple.stats()
return _run_store_coro(self.stats_async())
def health(self) -> Dict[str, Any]:
h = self._simple.health()
h["provider"] = "SDKJobStore"
h["sdk_available"] = True
h = self.stats()
h["status"] = "ok"
return h
@@ -154,6 +289,56 @@ def get_job_store() -> JobStoreProtocol:
return _job_store
def _run_store_coro(coro: Any) -> Any:
"""Run an async store call from sync contexts (CLI)."""
try:
asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(coro)
raise RuntimeError("Sync job store access from async context; use store_* helpers")
async def _store_get(store: JobStoreProtocol, job_id: str) -> Optional[Dict[str, Any]]:
if hasattr(store, "get_job_async"):
return await store.get_job_async(job_id)
return store.get(job_id)
async def _store_save(store: JobStoreProtocol, job: Dict[str, Any]) -> None:
if hasattr(store, "save_job_async"):
await store.save_job_async(job)
else:
store.save(job)
async def _store_delete(store: JobStoreProtocol, job_id: str) -> bool:
if hasattr(store, "delete_job_async"):
return await store.delete_job_async(job_id)
return store.delete(job_id)
async def _store_count(
store: JobStoreProtocol,
*,
status: Optional[str] = None,
session_id: Optional[str] = None,
) -> int:
if hasattr(store, "count_jobs_async"):
return await store.count_jobs_async(status=status, session_id=session_id)
jobs = store.list_all()
if status:
jobs = [j for j in jobs if j.get("status") == status]
if session_id:
jobs = [j for j in jobs if j.get("session_id") == session_id]
return len(jobs)
async def _store_stats(store: JobStoreProtocol) -> Dict[str, Any]:
if hasattr(store, "stats_async"):
return await store.stats_async()
return store.stats()
class JobsFeature(BaseFeatureProtocol):
"""Async job management for agent execution."""
@@ -173,6 +358,7 @@ class JobsFeature(BaseFeatureProtocol):
Route("/api/jobs", self._list, methods=["GET"]),
Route("/api/jobs", self._submit, methods=["POST"]),
Route("/api/jobs/stats", self._stats, methods=["GET"]),
Route("/api/jobs/board", self._board, methods=["GET"]),
Route("/api/jobs/{job_id}", self._get, methods=["GET"]),
Route("/api/jobs/{job_id}", self._delete, methods=["DELETE"]),
Route("/api/jobs/{job_id}/status", self._status, methods=["GET"]),
@@ -198,10 +384,11 @@ class JobsFeature(BaseFeatureProtocol):
from ._gateway_helpers import gateway_health
store = get_job_store()
store_health = await _store_stats(store)
return {
"status": "ok",
"feature": self.name,
**store.health(),
**store_health,
**gateway_health(),
}
@@ -215,17 +402,31 @@ class JobsFeature(BaseFeatureProtocol):
page = int(request.query_params.get("page", 1))
page_size = int(request.query_params.get("page_size", 20))
jobs = store.list_all()
if hasattr(store, "list_jobs_async"):
offset = (page - 1) * page_size
jobs = await store.list_jobs_async(
status=status_filter,
session_id=session_id,
limit=page_size,
offset=offset,
)
total = await _store_count(
store,
status=status_filter,
session_id=session_id,
)
else:
jobs = store.list_all()
if status_filter:
jobs = [j for j in jobs if j.get("status") == status_filter]
if session_id:
jobs = [j for j in jobs if j.get("session_id") == session_id]
if status_filter:
jobs = [j for j in jobs if j.get("status") == status_filter]
if session_id:
jobs = [j for j in jobs if j.get("session_id") == session_id]
jobs.sort(key=lambda j: j.get("created_at", 0), reverse=True)
total = len(jobs)
offset = (page - 1) * page_size
jobs = jobs[offset : offset + page_size]
jobs.sort(key=lambda j: j.get("created_at", 0), reverse=True)
total = len(jobs)
offset = (page - 1) * page_size
jobs = jobs[offset : offset + page_size]
return JSONResponse(
{
@@ -236,6 +437,32 @@ class JobsFeature(BaseFeatureProtocol):
}
)
async def _board(self, request: Request) -> JSONResponse:
"""Kanban-style column layout from job statuses."""
store = get_job_store()
if hasattr(store, "list_jobs_async"):
jobs = await store.list_jobs_async(limit=10000)
else:
jobs = store.list_all()
cols = ["queued", "running", "succeeded", "failed", "cancelled"]
columns = []
for col_id in cols:
cards = []
for job in jobs:
if job.get("status") != col_id:
continue
title = (job.get("prompt") or job.get("id") or "Job")[:80]
cards.append(
{
"id": job.get("id"),
"title": title,
"footer": job.get("status", col_id),
"status": col_id,
}
)
columns.append({"id": col_id, "title": col_id.replace("_", " ").title(), "cards": cards})
return JSONResponse({"board": "jobs", "columns": columns, "tasks_total": len(jobs)})
async def _submit(self, request: Request) -> JSONResponse:
"""Submit a new job for execution."""
body = await request.json()
@@ -270,11 +497,16 @@ class JobsFeature(BaseFeatureProtocol):
# Check idempotency
idem_key = body.get("idempotency_key")
if idem_key:
for existing in store.list_all():
if existing.get("idempotency_key") == idem_key:
if hasattr(store, "get_by_idempotency_key_async"):
existing = await store.get_by_idempotency_key_async(idem_key)
if existing:
return JSONResponse(existing, status_code=200)
else:
for existing in store.list_all():
if existing.get("idempotency_key") == idem_key:
return JSONResponse(existing, status_code=200)
store.save(job)
await _store_save(store, job)
# Start execution
asyncio.create_task(self._execute_job(job_id))
@@ -298,13 +530,14 @@ class JobsFeature(BaseFeatureProtocol):
async def _execute_job(self, job_id: str) -> None:
"""Execute a job using gateway-registered agents when available."""
store = get_job_store()
job = store.get(job_id)
job = await _store_get(store, job_id)
if not job:
return
# Mark as running
job["status"] = JobStatus.RUNNING.value
job["started_at"] = time.time()
await _store_save(store, job)
await self._notify_progress(job_id)
try:
@@ -329,6 +562,7 @@ class JobsFeature(BaseFeatureProtocol):
await asyncio.sleep(1)
job["progress_percentage"] = praison_job.progress_percentage
job["progress_step"] = praison_job.progress_step
await _store_save(store, job)
await self._notify_progress(job_id)
if praison_job.status.value == "succeeded":
@@ -364,9 +598,11 @@ class JobsFeature(BaseFeatureProtocol):
model = job.get("config", {}).get("model", "gpt-4o-mini")
# Resolve tools from job config
agent_tools = []
tool_names = job.get("config", {}).get("tools", [])
if tool_names:
from praisonaiui.backends import resolve_tools
agent_tools = resolve_tools(tool_names) if tool_names else []
if tool_names and not agent_tools:
try:
from praisonai.tool_resolver import ToolResolver
@@ -392,10 +628,12 @@ class JobsFeature(BaseFeatureProtocol):
if agent is not None:
job["progress_percentage"] = 10
job["progress_step"] = "Creating agent..."
await _store_save(store, job)
await self._notify_progress(job_id)
job["progress_percentage"] = 30
job["progress_step"] = "Executing agent..."
await _store_save(store, job)
await self._notify_progress(job_id)
# Execute in thread pool
@@ -409,17 +647,32 @@ class JobsFeature(BaseFeatureProtocol):
job["result"] = result
job["metrics"] = {"model": model}
else:
# Mock fallback
from praisonaiui.backends import is_integrated_mode, resolve_tools
if is_integrated_mode():
job["status"] = JobStatus.FAILED.value
job["error"] = (
"Agent execution unavailable in integrated mode. "
"Install praisonaiagents and ensure the agent can be created."
)
job["completed_at"] = time.time()
await _store_save(store, job)
await self._notify_progress(job_id)
return
# Standalone mock fallback
logger.warning("No agent available, using mock execution")
for i in range(5):
if job.get("_cancel_requested"):
job["status"] = JobStatus.CANCELLED.value
job["completed_at"] = time.time()
await _store_save(store, job)
await self._notify_progress(job_id)
return
job["progress_percentage"] = (i + 1) * 20
job["progress_step"] = f"Step {i + 1}/5"
await _store_save(store, job)
await self._notify_progress(job_id)
await asyncio.sleep(0.5)
@@ -433,24 +686,34 @@ class JobsFeature(BaseFeatureProtocol):
job["completed_at"] = time.time()
job["progress_percentage"] = 100.0
await _store_save(store, job)
await self._notify_progress(job_id)
def _get_executor(self):
"""Try to get JobExecutor from praisonai.jobs."""
"""Try to get JobExecutor from injected backend or praisonai.jobs."""
try:
import importlib.util
if hasattr(self, "_sdk_executor"):
return self._sdk_executor
if importlib.util.find_spec("praisonai.jobs"):
# Would need to be initialized properly with store
return None # For now, use mock execution
return None
from praisonaiui.backends import get_jobs_executor_factory
factory = get_jobs_executor_factory()
if factory is not None:
self._sdk_executor = factory()
return self._sdk_executor
from praisonai.jobs import JobExecutor
from praisonai.jobs.store import InMemoryJobStore
self._sdk_executor = JobExecutor(store=InMemoryJobStore())
return self._sdk_executor
except Exception:
return None
async def _notify_progress(self, job_id: str) -> None:
"""Notify all progress listeners."""
if job_id in _progress_callbacks:
job = get_job_store().get(job_id)
job = await _store_get(get_job_store(), job_id)
if job:
for queue in _progress_callbacks[job_id]:
try:
@@ -461,7 +724,7 @@ class JobsFeature(BaseFeatureProtocol):
async def _get(self, request: Request) -> JSONResponse:
"""Get a job by ID."""
job_id = request.path_params["job_id"]
job = get_job_store().get(job_id)
job = await _store_get(get_job_store(), job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
return JSONResponse(job)
@@ -469,7 +732,7 @@ class JobsFeature(BaseFeatureProtocol):
async def _status(self, request: Request) -> JSONResponse:
"""Get job status."""
job_id = request.path_params["job_id"]
job = get_job_store().get(job_id)
job = await _store_get(get_job_store(), job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
@@ -498,7 +761,7 @@ class JobsFeature(BaseFeatureProtocol):
async def _result(self, request: Request) -> JSONResponse:
"""Get job result."""
job_id = request.path_params["job_id"]
job = get_job_store().get(job_id)
job = await _store_get(get_job_store(), job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
@@ -533,7 +796,8 @@ class JobsFeature(BaseFeatureProtocol):
async def _cancel(self, request: Request) -> JSONResponse:
"""Cancel a running job."""
job_id = request.path_params["job_id"]
job = get_job_store().get(job_id)
store = get_job_store()
job = await _store_get(store, job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
@@ -550,6 +814,7 @@ class JobsFeature(BaseFeatureProtocol):
job["_cancel_requested"] = True
job["status"] = JobStatus.CANCELLED.value
job["completed_at"] = time.time()
await _store_save(store, job)
return JSONResponse(
{
@@ -563,7 +828,7 @@ class JobsFeature(BaseFeatureProtocol):
"""Delete a completed job."""
store = get_job_store()
job_id = request.path_params["job_id"]
job = store.get(job_id)
job = await _store_get(store, job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
@@ -577,13 +842,13 @@ class JobsFeature(BaseFeatureProtocol):
{"error": "Cannot delete running job. Cancel first."}, status_code=409
)
store.delete(job_id)
await _store_delete(store, job_id)
return JSONResponse({"deleted": job_id})
async def _stream(self, request: Request) -> StreamingResponse:
"""Stream job progress via SSE."""
job_id = request.path_params["job_id"]
job = get_job_store().get(job_id)
job = await _store_get(get_job_store(), job_id)
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
@@ -600,7 +865,7 @@ class JobsFeature(BaseFeatureProtocol):
last_progress = -1
while True:
current_job = get_job_store().get(job_id)
current_job = await _store_get(get_job_store(), job_id)
if not current_job:
yield 'event: error\ndata: {"error": "Job not found"}\n\n'
break
@@ -659,7 +924,7 @@ class JobsFeature(BaseFeatureProtocol):
async def _stats(self, request: Request) -> JSONResponse:
"""Get executor statistics."""
store = get_job_store()
s = store.stats()
s = await _store_stats(store)
s["max_concurrent"] = 10
return JSONResponse(s)
+424
View File
@@ -0,0 +1,424 @@
"""Kanban feature — protocol-driven task board for PraisonAIUI.
Architecture:
KanbanStoreProtocol (ABC) <- any backend implements this
├── SimpleKanbanStore <- default in-memory (no deps)
└── InjectedKanbanStore <- wrapper-injected via backends.py
KanbanFeature (BaseFeatureProtocol)
└── REST + SSE for board UI and dashboard plugins
"""
from __future__ import annotations
import asyncio
import json
import logging
import time
import uuid
from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime, timezone
from typing import Any, Deque, Dict, List, Optional, Set
from starlette.requests import Request
from starlette.responses import JSONResponse, StreamingResponse
from starlette.routing import Route
from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
BOARD_COLUMNS = [
{"id": "triage", "title": "Triage"},
{"id": "todo", "title": "Todo"},
{"id": "ready", "title": "Ready"},
{"id": "running", "title": "Running"},
{"id": "blocked", "title": "Blocked"},
{"id": "review", "title": "Review"},
{"id": "done", "title": "Done"},
]
VALID_STATUSES: Set[str] = {c["id"] for c in BOARD_COLUMNS} | {"archived"}
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def _task_card(task: Dict[str, Any]) -> Dict[str, Any]:
"""Normalise task dict to board card component."""
title = (task.get("title") or task.get("id") or "Task")[:120]
footer_parts = [p for p in (task.get("assignee"), task.get("priority")) if p]
return {
"id": task.get("id"),
"title": title,
"footer": " · ".join(str(p) for p in footer_parts) if footer_parts else task.get("status", ""),
"status": task.get("status"),
"assignee": task.get("assignee"),
"priority": task.get("priority"),
"body": task.get("body"),
"comments": task.get("comments") or [],
"meta": task.get("meta") or {},
}
# ── Store protocol ───────────────────────────────────────────────────
class KanbanStoreProtocol(ABC):
"""Protocol interface for kanban task storage."""
@abstractmethod
def get_board(
self,
*,
board: str = "default",
tenant: Optional[str] = None,
include_archived: bool = False,
) -> Dict[str, Any]: ...
@abstractmethod
def get_task(self, task_id: str) -> Optional[Dict[str, Any]]: ...
@abstractmethod
def create_task(self, data: Dict[str, Any]) -> Dict[str, Any]: ...
@abstractmethod
def update_task(self, task_id: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]: ...
@abstractmethod
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]: ...
@abstractmethod
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]: ...
@abstractmethod
def delete_task(self, task_id: str) -> bool: ...
@abstractmethod
def list_events(self, since: float = 0.0, board: str = "default") -> List[Dict[str, Any]]: ...
def health(self) -> Dict[str, Any]:
return {"status": "ok", "provider": self.__class__.__name__}
class SimpleKanbanStore(KanbanStoreProtocol):
"""In-memory kanban store — zero dependencies, volatile."""
def __init__(self) -> None:
self._tasks: Dict[str, Dict[str, Any]] = {}
self._events: Deque[Dict[str, Any]] = deque(maxlen=500)
self._boards: Set[str] = {"default"}
def _emit(self, event_type: str, task: Dict[str, Any], extra: Optional[Dict[str, Any]] = None) -> None:
self._events.append(
{
"type": event_type,
"task_id": task.get("id"),
"status": task.get("status"),
"board": task.get("board", "default"),
"ts": time.time(),
**(extra or {}),
}
)
def get_board(
self,
*,
board: str = "default",
tenant: Optional[str] = None,
include_archived: bool = False,
) -> Dict[str, Any]:
self._boards.add(board)
tasks = [
t
for t in self._tasks.values()
if t.get("board", "default") == board
and (include_archived or t.get("status") != "archived")
and (not tenant or t.get("tenant") == tenant)
]
by_status: Dict[str, List[Dict[str, Any]]] = {c["id"]: [] for c in BOARD_COLUMNS}
for task in tasks:
status = task.get("status", "todo")
if status not in by_status and status != "archived":
by_status.setdefault(status, [])
if status in by_status:
by_status[status].append(_task_card(task))
columns = []
for col in BOARD_COLUMNS:
cards = by_status.get(col["id"], [])
columns.append({"id": col["id"], "title": col["title"], "cards": cards})
return {
"board": board,
"columns": columns,
"tasks_total": len(tasks),
"provider": "SimpleKanbanStore",
}
def get_task(self, task_id: str) -> Optional[Dict[str, Any]]:
task = self._tasks.get(task_id)
return dict(task) if task else None
def create_task(self, data: Dict[str, Any]) -> Dict[str, Any]:
task_id = data.get("id") or f"task_{uuid.uuid4().hex[:10]}"
status = data.get("status", "todo")
if status not in VALID_STATUSES:
status = "todo"
task = {
"id": task_id,
"title": data.get("title") or "Untitled",
"body": data.get("body") or "",
"status": status,
"assignee": data.get("assignee"),
"priority": data.get("priority"),
"tenant": data.get("tenant"),
"board": data.get("board", "default"),
"comments": [],
"meta": data.get("meta") or {},
"created_at": _now_iso(),
"updated_at": _now_iso(),
}
self._tasks[task_id] = task
self._boards.add(task["board"])
self._emit("task_created", task)
return dict(task)
def update_task(self, task_id: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
task = self._tasks.get(task_id)
if not task:
return None
for key in ("title", "body", "assignee", "priority", "tenant", "meta"):
if key in data:
task[key] = data[key]
if "status" in data and data["status"] in VALID_STATUSES:
task["status"] = data["status"]
task["updated_at"] = _now_iso()
self._emit("task_updated", task)
return dict(task)
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]:
if status not in VALID_STATUSES:
return None
task = self._tasks.get(task_id)
if not task:
return None
old = task.get("status")
task["status"] = status
task["updated_at"] = _now_iso()
self._emit("task_moved", task, {"from_status": old, "to_status": status})
return dict(task)
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]:
if status not in VALID_STATUSES:
return {"updated": 0, "errors": ["invalid status"]}
updated = 0
for tid in task_ids:
if self.move_task(tid, status):
updated += 1
return {"updated": updated, "status": status}
def delete_task(self, task_id: str) -> bool:
task = self._tasks.pop(task_id, None)
if task:
self._emit("task_deleted", task)
return True
return False
def list_events(self, since: float = 0.0, board: str = "default") -> List[Dict[str, Any]]:
return [e for e in list(self._events) if e.get("ts", 0) >= since and e.get("board", "default") == board]
def health(self) -> Dict[str, Any]:
return {
"status": "ok",
"provider": "SimpleKanbanStore",
"tasks": len(self._tasks),
"boards": sorted(self._boards),
}
class InjectedKanbanStore(KanbanStoreProtocol):
"""Delegates to wrapper-injected kanban store factory."""
def __init__(self, impl: Any) -> None:
self._impl = impl
def _call(self, name: str, *args: Any, **kwargs: Any) -> Any:
fn = getattr(self._impl, name, None)
if not callable(fn):
raise RuntimeError(f"Injected kanban store missing {name}")
return fn(*args, **kwargs)
def get_board(self, **kwargs: Any) -> Dict[str, Any]:
return self._call("get_board", **kwargs)
def get_task(self, task_id: str) -> Optional[Dict[str, Any]]:
return self._call("get_task", task_id)
def create_task(self, data: Dict[str, Any]) -> Dict[str, Any]:
return self._call("create_task", data)
def update_task(self, task_id: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self._call("update_task", task_id, data)
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]:
return self._call("move_task", task_id, status)
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]:
return self._call("bulk_update", task_ids, status)
def delete_task(self, task_id: str) -> bool:
return self._call("delete_task", task_id)
def list_events(self, since: float = 0.0, board: str = "default") -> List[Dict[str, Any]]:
return self._call("list_events", since, board)
def health(self) -> Dict[str, Any]:
if hasattr(self._impl, "health") and callable(self._impl.health):
return self._impl.health()
return {"status": "ok", "provider": "InjectedKanbanStore"}
_kanban_store: Optional[KanbanStoreProtocol] = None
def get_kanban_store() -> KanbanStoreProtocol:
"""Active kanban store — injected backend first, else SimpleKanbanStore."""
global _kanban_store
if _kanban_store is not None:
return _kanban_store
from praisonaiui.backends import get_kanban_store_factory
factory = get_kanban_store_factory()
if factory is not None:
try:
impl = factory()
_kanban_store = InjectedKanbanStore(impl)
logger.info("Using InjectedKanbanStore")
return _kanban_store
except Exception as exc:
logger.warning("Injected kanban store failed (%s), using SimpleKanbanStore", exc)
_kanban_store = SimpleKanbanStore()
return _kanban_store
def reset_kanban_store() -> None:
"""Reset store singleton (tests)."""
global _kanban_store
_kanban_store = None
class KanbanFeature(BaseFeatureProtocol):
"""Kanban task board — columns, drag-drop API, SSE events."""
feature_name = "kanban"
feature_description = "Multi-agent kanban task board"
@property
def name(self) -> str:
return self.feature_name
@property
def description(self) -> str:
return self.feature_description
def routes(self) -> List[Route]:
return [
Route("/api/kanban/board", self._board, methods=["GET"]),
Route("/api/kanban/tasks", self._create, methods=["POST"]),
Route("/api/kanban/tasks/bulk", self._bulk, methods=["POST"]),
Route("/api/kanban/tasks/{task_id}", self._get, methods=["GET"]),
Route("/api/kanban/tasks/{task_id}", self._patch, methods=["PATCH"]),
Route("/api/kanban/tasks/{task_id}", self._delete, methods=["DELETE"]),
Route("/api/kanban/tasks/{task_id}/move", self._move, methods=["POST"]),
Route("/api/kanban/events", self._events, methods=["GET"]),
Route("/api/kanban/health", self._health, methods=["GET"]),
Route("/api/kanban/boards", self._boards, methods=["GET"]),
]
async def health(self) -> Dict[str, Any]:
store = get_kanban_store()
return {"status": "ok", "feature": self.name, **store.health()}
async def _board(self, request: Request) -> JSONResponse:
store = get_kanban_store()
board = request.query_params.get("board", "default")
tenant = request.query_params.get("tenant")
archived = request.query_params.get("archived", "").lower() in ("1", "true", "yes")
data = store.get_board(board=board, tenant=tenant, include_archived=archived)
return JSONResponse(data)
async def _get(self, request: Request) -> JSONResponse:
store = get_kanban_store()
task = store.get_task(request.path_params["task_id"])
if not task:
return JSONResponse({"error": "not found"}, status_code=404)
return JSONResponse(task)
async def _create(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
task = store.create_task(body if isinstance(body, dict) else {})
return JSONResponse(task, status_code=201)
async def _patch(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
task = store.update_task(request.path_params["task_id"], body if isinstance(body, dict) else {})
if not task:
return JSONResponse({"error": "not found"}, status_code=404)
return JSONResponse(task)
async def _move(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
status = (body or {}).get("status")
if not status:
return JSONResponse({"error": "status required"}, status_code=400)
task = store.move_task(request.path_params["task_id"], status)
if not task:
return JSONResponse({"error": "not found or invalid status"}, status_code=404)
return JSONResponse(task)
async def _bulk(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
task_ids = (body or {}).get("task_ids") or []
status = (body or {}).get("status")
if not isinstance(task_ids, list) or not status:
return JSONResponse({"error": "task_ids and status required"}, status_code=400)
return JSONResponse(store.bulk_update(task_ids, status))
async def _delete(self, request: Request) -> JSONResponse:
store = get_kanban_store()
ok = store.delete_task(request.path_params["task_id"])
if not ok:
return JSONResponse({"error": "not found"}, status_code=404)
return JSONResponse({"deleted": True})
async def _boards(self, request: Request) -> JSONResponse:
store = get_kanban_store()
h = store.health()
boards = h.get("boards") or ["default"]
return JSONResponse({"boards": [{"id": b, "title": b.title()} for b in boards]})
async def _health(self, request: Request) -> JSONResponse:
return JSONResponse(await self.health())
async def _events(self, request: Request) -> StreamingResponse:
"""SSE stream of kanban task events (board live sync)."""
board = request.query_params.get("board", "default")
since = float(request.query_params.get("since") or 0)
async def stream():
store = get_kanban_store()
cursor = since
while True:
events = store.list_events(since=cursor, board=board)
for ev in events:
cursor = max(cursor, ev.get("ts", cursor))
yield f"data: {json.dumps(ev)}\n\n"
yield ": keepalive\n\n"
await asyncio.sleep(1.5)
return StreamingResponse(stream(), media_type="text/event-stream")
+30 -11
View File
@@ -9,10 +9,12 @@ Provides first-class MCP client functionality with:
from __future__ import annotations
import json
import logging
import subprocess
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Protocol
try:
@@ -322,7 +324,9 @@ class MCPClientManager:
def __init__(self):
self._clients: Dict[str, MCPClientProtocol] = {}
async def connect_server(self, server_config: Dict[str, Any]) -> MCPServer:
async def connect_server(
self, server_config: Dict[str, Any], session_context: Optional[Dict[str, Any]] = None
) -> MCPServer:
"""Connect to an MCP server based on configuration."""
name = server_config["name"]
@@ -366,7 +370,7 @@ class MCPClientManager:
server._client = client
# Fire connect hooks
await self._fire_connect_hooks(server, None) # TODO: pass actual session context
await self._fire_connect_hooks(server, session_context)
else:
server.status = MCPStatus.ERROR
server.last_error = "Connection failed"
@@ -380,7 +384,9 @@ class MCPClientManager:
await self._notify_status_change(server)
return server
async def disconnect_server(self, name: str) -> bool:
async def disconnect_server(
self, name: str, session_context: Optional[Dict[str, Any]] = None
) -> bool:
"""Disconnect an MCP server by name."""
if name not in _mcp_servers:
return False
@@ -393,7 +399,7 @@ class MCPClientManager:
await client.disconnect()
# Fire disconnect hooks
await self._fire_disconnect_hooks(server, None) # TODO: pass actual session context
await self._fire_disconnect_hooks(server, session_context)
except Exception as e:
logger.exception(f"Error during MCP server {name} disconnect")
@@ -481,14 +487,18 @@ def add_status_change_callback(callback: Callable) -> None:
_status_change_callbacks.append(callback)
async def connect_mcp_server(config: Dict[str, Any]) -> MCPServer:
async def connect_mcp_server(
config: Dict[str, Any], session_context: Optional[Dict[str, Any]] = None
) -> MCPServer:
"""Connect to an MCP server."""
return await _manager.connect_server(config)
return await _manager.connect_server(config, session_context=session_context)
async def disconnect_mcp_server(name: str) -> bool:
async def disconnect_mcp_server(
name: str, session_context: Optional[Dict[str, Any]] = None
) -> bool:
"""Disconnect an MCP server."""
return await _manager.disconnect_server(name)
return await _manager.disconnect_server(name, session_context=session_context)
async def list_mcp_servers() -> List[MCPServer]:
@@ -505,8 +515,17 @@ async def get_mcp_server(name: str) -> Optional[MCPServer]:
async def auto_discover_mcp_servers() -> None:
"""Auto-discover and connect MCP servers from configuration."""
try:
# TODO: Load from actual config file
# For now, this is a placeholder
logger.info("MCP auto-discovery not yet implemented")
config_path = Path.home() / ".praisonai" / "mcp.json"
if not config_path.exists():
return
config_data = json.loads(config_path.read_text(encoding="utf-8"))
servers = config_data.get("servers", [])
if not isinstance(servers, list):
return
for server_config in servers:
if isinstance(server_config, dict) and server_config.get("name"):
await connect_mcp_server(server_config)
except Exception as e:
logger.exception(f"Failed to auto-discover MCP servers: {e}")
+37 -1
View File
@@ -111,7 +111,16 @@ class VisionAnalysisManager(MediaAnalysisProtocol):
except Exception as e:
logger.warning("SDK VisionAgent failed: %s", e)
# Fallback: return metadata-only analysis
from praisonaiui.backends import is_integrated_mode
if is_integrated_mode():
return {
"error": "Vision analysis unavailable (SDK agent failed)",
"status": "degraded",
"provider": "none",
}
# Standalone fallback: metadata-only analysis
return {
"analysis": f"[Simulated] Image analysis for: {url or 'base64 data'}",
"status": "simulated",
@@ -151,6 +160,33 @@ class VisionAnalysisManager(MediaAnalysisProtocol):
except Exception as e:
logger.warning("SDK OCR failed: %s", e)
try:
from markitdown import MarkItDown
source = url
if not source:
return {
"text": "[markitdown] base64 OCR is not supported; provide a URL or file path",
"status": "error",
"provider": "markitdown",
}
md = MarkItDown()
result = md.convert(source)
text = getattr(result, "text_content", None) or str(result)
return {"text": text, "status": "success", "provider": "markitdown"}
except Exception:
pass
from praisonaiui.backends import is_integrated_mode
if is_integrated_mode():
return {
"text": "",
"status": "degraded",
"provider": "none",
"error": "OCR unavailable (SDK and markitdown failed)",
}
return {
"text": "[Simulated] OCR text extraction placeholder",
"status": "simulated",
+2
View File
@@ -140,6 +140,8 @@ class NodesFeature(BaseFeatureProtocol):
"total_nodes": len(_nodes),
"online_nodes": online,
"connected_instances": len(_instances),
"sdk_gap": True,
"sdk_gap_message": "Node orchestration uses local fallback; no praisonaiagents nodes API detected.",
**gateway_health(),
}
+47 -2
View File
@@ -8,6 +8,7 @@ Architecture:
from __future__ import annotations
import logging
import os
from abc import ABC, abstractmethod
from typing import Any, AsyncIterator, Dict, List, Optional
@@ -123,7 +124,30 @@ class OpenAIRealtimeManager(RealtimeProtocol):
yield {"type": "error", "error": "Session not found"}
return
# Mock event stream - in real implementation this would connect to OpenAI
# Prefer OpenAI-backed event when API key is present.
if os.environ.get("OPENAI_API_KEY"):
yield {
"type": "session.ready",
"session_id": session_id,
"provider": "openai",
"transport": "webrtc",
"status": session.get("status", "created"),
"client_secret": session.get("client_secret"),
}
return
from praisonaiui.backends import is_integrated_mode
if is_integrated_mode():
yield {
"type": "error",
"status": "degraded",
"error": "OPENAI_API_KEY required for realtime WebRTC in integrated mode",
"session_id": session_id,
}
return
# Standalone mock event stream fallback
yield {
"type": "conversation.item.created",
"item": {
@@ -138,10 +162,31 @@ class OpenAIRealtimeManager(RealtimeProtocol):
self, session_id: str, tool_name: str, args: Dict[str, Any]
) -> Dict[str, Any]:
"""Handle tool calls from realtime API."""
from praisonaiui.backends import get_tool_resolver
resolver = get_tool_resolver()
if resolver is not None and hasattr(resolver, "resolve"):
tool = resolver.resolve(tool_name)
if tool is not None:
try:
result = tool(**args) if callable(tool) else str(tool)
return {
"type": "function_call_output",
"call_id": args.get("call_id", "unknown"),
"output": str(result),
}
except Exception as exc:
return {
"type": "error",
"error": str(exc),
"call_id": args.get("call_id", "unknown"),
}
return {
"type": "function_call_output",
"call_id": args.get("call_id", "unknown"),
"output": f"Tool {tool_name} called with args: {args}",
"output": f"Tool {tool_name} not available",
"status": "degraded",
}
async def close_session(self, session_id: str) -> None:
+12
View File
@@ -417,6 +417,18 @@ def _ensure_scheduler_started() -> None:
global _scheduler_task
if _scheduler_task is not None and not _scheduler_task.done():
return
try:
from praisonaiui.backends import get_backend
start_schedule_loop = get_backend("schedule_loop")
if callable(start_schedule_loop):
started = start_schedule_loop()
if started:
return
except Exception:
pass
import asyncio
try:
+79 -11
View File
@@ -72,6 +72,17 @@ def _get_session_store() -> SessionProtocol:
return _session_store
async def _get_datastore_session(session_id: str) -> Optional[Dict[str, Any]]:
"""Read session from server datastore when available."""
try:
from praisonaiui.server import get_datastore
ds = get_datastore()
return await ds.get_session(session_id)
except Exception:
return None
class _InMemorySessionStore(SessionProtocol):
"""Fallback in-memory store if praisonaiagents not available."""
@@ -149,6 +160,7 @@ class SessionsFeature(BaseFeatureProtocol):
def routes(self) -> List[Route]:
return [
Route("/api/sessions", self._list_sessions, methods=["GET"]),
Route("/api/sessions/search", self._search_sessions, methods=["GET"]),
Route("/api/sessions/{session_id}/state", self._get_state, methods=["GET"]),
Route("/api/sessions/{session_id}/state", self._save_state, methods=["POST"]),
Route("/api/sessions/{session_id}/context", self._build_context, methods=["POST"]),
@@ -277,6 +289,25 @@ class SessionsFeature(BaseFeatureProtocol):
"created_at": s.get("created_at", meta.get("_created_at")),
"updated_at": s.get("updated_at", meta.get("_updated_at")),
"title": s.get("title", ""),
"model": s.get("model") or s.get("metadata", {}).get("model"),
"tokens": (
s.get("tokens")
or s.get("usage", {}).get("tokens")
or s.get("metadata", {}).get("tokens")
or meta.get("_usage", {}).get("tokens", 0)
),
"cost": (
s.get("cost")
or s.get("usage", {}).get("cost")
or s.get("metadata", {}).get("cost")
or 0
),
"agent_id": (
s.get("agent_id")
or s.get("agent")
or s.get("metadata", {}).get("agent_id")
),
"source": s.get("source") or s.get("metadata", {}).get("source") or "datastore",
}
)
except Exception as e:
@@ -312,6 +343,26 @@ class SessionsFeature(BaseFeatureProtocol):
return JSONResponse({"sessions": sessions, "count": len(sessions)})
async def _search_sessions(self, request: Request) -> JSONResponse:
"""GET /api/sessions/search?q= — filter sessions by title or id."""
query = (request.query_params.get("q") or "").strip().lower()
if not query:
return JSONResponse({"sessions": [], "count": 0})
list_resp = await self._list_sessions(request)
import json
body = json.loads(list_resp.body)
sessions = body.get("sessions") or []
matched = [
s
for s in sessions
if query in str(s.get("id", "")).lower()
or query in str(s.get("title", "")).lower()
or query in str(s.get("session_id", "")).lower()
]
return JSONResponse({"sessions": matched, "count": len(matched), "query": query})
async def _get_state(self, request: Request) -> JSONResponse:
sid = request.path_params["session_id"]
meta = _get_metadata(sid)
@@ -335,10 +386,15 @@ class SessionsFeature(BaseFeatureProtocol):
meta = _get_metadata(sid)
if meta:
context_parts.append(f"Session state: {len(meta)} keys")
store = _get_session_store()
messages = (
store.get_chat_history(sid, max_items) if hasattr(store, "get_chat_history") else []
)
ds_session = await _get_datastore_session(sid)
messages = (ds_session or {}).get("messages", [])
if max_items:
messages = messages[-max_items:]
if not messages:
store = _get_session_store()
messages = (
store.get_chat_history(sid, max_items) if hasattr(store, "get_chat_history") else []
)
for m in messages:
content = m.get("content", "") or m.get("text", "")
if content:
@@ -371,8 +427,11 @@ class SessionsFeature(BaseFeatureProtocol):
async def _compact(self, request: Request) -> JSONResponse:
"""POST /api/sessions/{id}/compact — Summarize old messages to reduce context."""
sid = request.path_params["session_id"]
store = _get_session_store()
messages = store.get_chat_history(sid) if hasattr(store, "get_chat_history") else []
ds_session = await _get_datastore_session(sid)
messages = (ds_session or {}).get("messages", [])
if not messages:
store = _get_session_store()
messages = store.get_chat_history(sid) if hasattr(store, "get_chat_history") else []
before_count = len(messages)
before_tokens = sum(len(str(m.get("content", "")).split()) * 1.3 for m in messages)
@@ -396,8 +455,11 @@ class SessionsFeature(BaseFeatureProtocol):
"""GET /api/sessions/{id}/preview — Return formatted preview without full history."""
sid = request.path_params["session_id"]
meta = _get_metadata(sid)
store = _get_session_store()
messages = store.get_chat_history(sid) if hasattr(store, "get_chat_history") else []
ds_session = await _get_datastore_session(sid)
messages = (ds_session or {}).get("messages", [])
if not messages:
store = _get_session_store()
messages = store.get_chat_history(sid) if hasattr(store, "get_chat_history") else []
# Get first and last messages
first_message = messages[0] if messages else None
@@ -440,9 +502,15 @@ class SessionsFeature(BaseFeatureProtocol):
mode = body.get("mode", "clear") # "clear" or "new"
if mode == "clear":
_session_metadata.pop(sid, None)
store = _get_session_store()
if hasattr(store, "clear_session"):
store.clear_session(sid)
try:
from praisonaiui.server import get_datastore
ds = get_datastore()
await ds.update_session(sid, {"messages": []})
except Exception:
store = _get_session_store()
if hasattr(store, "clear_session"):
store.clear_session(sid)
return JSONResponse(
{
"session_id": sid,
+23
View File
@@ -96,12 +96,35 @@ class SubagentManager(SubagentProtocol):
_subagent_manager: Optional[SubagentManager] = None
_handoff_subscription_attempted = False
def _subscribe_sdk_handoff_hooks(mgr: SubagentManager) -> None:
"""Subscribe to SDK handoff hooks when available."""
global _handoff_subscription_attempted
if _handoff_subscription_attempted:
return
_handoff_subscription_attempted = True
try:
from praisonaiagents.hooks import add_hook
def _on_handoff(event: Any) -> None:
payload = event if isinstance(event, dict) else {}
parent = payload.get("parent_agent") or payload.get("from_agent") or "parent"
child = payload.get("child_agent") or payload.get("to_agent") or "child"
session_id = payload.get("session_id") or "default"
mgr.register_spawn(str(parent), str(child), str(session_id))
add_hook("handoff", _on_handoff)
except Exception:
logger.debug("SDK handoff hooks unavailable for subagent tracking")
def get_subagent_manager() -> SubagentManager:
global _subagent_manager
if _subagent_manager is None:
_subagent_manager = SubagentManager()
_subscribe_sdk_handoff_hooks(_subagent_manager)
return _subagent_manager
+303
View File
@@ -0,0 +1,303 @@
"""A2UI surface state — agent-driven UI surfaces via REST + WebSocket."""
from __future__ import annotations
import asyncio
import logging
import time
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from praisonaiui.a2ui_utils import normalise_a2ui_messages
from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
_surface_action_handlers: Dict[str, Callable] = {}
def register_surface_action(surface_id: str, handler: Callable) -> None:
"""Register a handler for user actions on a surface."""
_surface_action_handlers[surface_id] = handler
def get_surface_action_handler(surface_id: str) -> Optional[Callable]:
return _surface_action_handlers.get(surface_id)
@dataclass
class SurfaceState:
"""In-memory surface with A2UI message log."""
id: str
messages: List[Dict[str, Any]] = field(default_factory=list)
updated_at: float = field(default_factory=time.time)
metadata: Dict[str, Any] = field(default_factory=dict)
class SurfaceStore:
"""Thread-safe in-memory surface store."""
def __init__(self) -> None:
self._surfaces: Dict[str, SurfaceState] = {}
self._lock = asyncio.Lock()
self._action_queue: List[Dict[str, Any]] = []
async def list_surfaces(self) -> List[Dict[str, Any]]:
async with self._lock:
return [
{
"id": s.id,
"message_count": len(s.messages),
"updated_at": s.updated_at,
"metadata": s.metadata,
}
for s in self._surfaces.values()
]
async def get(self, surface_id: str) -> Optional[SurfaceState]:
async with self._lock:
return self._surfaces.get(surface_id)
async def get_or_create(self, surface_id: str) -> SurfaceState:
async with self._lock:
if surface_id not in self._surfaces:
self._surfaces[surface_id] = SurfaceState(id=surface_id)
return self._surfaces[surface_id]
async def apply_messages(
self, surface_id: str, messages: List[Dict[str, Any]]
) -> SurfaceState:
normalised = normalise_a2ui_messages({"messages": messages})
async with self._lock:
state = self._surfaces.setdefault(surface_id, SurfaceState(id=surface_id))
if any(m.get("createSurface") for m in normalised):
state.messages = list(normalised)
else:
state.messages.extend(normalised)
state.updated_at = time.time()
return state
async def set_messages(
self, surface_id: str, messages: List[Dict[str, Any]]
) -> SurfaceState:
normalised = normalise_a2ui_messages({"messages": messages})
async with self._lock:
state = self._surfaces.setdefault(surface_id, SurfaceState(id=surface_id))
state.messages = normalised
state.updated_at = time.time()
return state
async def clear(self, surface_id: str) -> bool:
async with self._lock:
return self._surfaces.pop(surface_id, None) is not None
async def queue_action(self, action: Dict[str, Any]) -> None:
async with self._lock:
self._action_queue.append(action)
async def pop_actions(self, limit: int = 50) -> List[Dict[str, Any]]:
async with self._lock:
items = self._action_queue[:limit]
self._action_queue = self._action_queue[limit:]
return items
_store: Optional[SurfaceStore] = None
def get_surface_store() -> SurfaceStore:
global _store
if _store is None:
_store = SurfaceStore()
return _store
def set_surface_store(store: SurfaceStore) -> None:
global _store
_store = store
async def broadcast_a2ui_surface(
surface_id: str,
messages: List[Dict[str, Any]],
session_id: Optional[str] = None,
) -> None:
"""Push A2UI update to WebSocket clients."""
payload: Dict[str, Any] = {
"type": "a2ui_surface",
"surface_id": surface_id,
"messages": messages,
}
if session_id:
payload["session_id"] = session_id
try:
from praisonaiui.features.chat import get_chat_manager
mgr = get_chat_manager()
await mgr.broadcast(session_id or "", payload)
except Exception as exc:
logger.debug("Surface broadcast skipped: %s", exc)
async def ingest_a2ui_extra(
extra_data: Optional[Dict[str, Any]],
session_id: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""Apply A2UI from RunEvent extra_data and broadcast."""
if not extra_data:
return None
a2ui = extra_data.get("a2ui")
if not a2ui:
return None
surface_id = str(extra_data.get("surface_id", "main"))
store = get_surface_store()
# Tool results are a full snapshot — replace, do not stack old components.
state = await store.set_messages(surface_id, a2ui)
await broadcast_a2ui_surface(surface_id, state.messages, session_id=session_id)
return {"surface_id": surface_id, "messages": state.messages}
def attach_a2ui_to_payload(payload: Dict[str, Any], extra_data: Optional[Dict[str, Any]]) -> None:
"""Copy A2UI fields from RunEvent extra_data onto a wire payload."""
if not extra_data:
return
if extra_data.get("a2ui"):
payload["a2ui"] = extra_data["a2ui"]
payload["surface_id"] = extra_data.get("surface_id", "main")
if extra_data.get("extra_data"):
nested = extra_data["extra_data"]
if isinstance(nested, dict) and nested.get("a2ui"):
payload["a2ui"] = nested["a2ui"]
payload["surface_id"] = nested.get("surface_id", "main")
async def _list_surfaces(_request: Request) -> JSONResponse:
store = get_surface_store()
return JSONResponse({"surfaces": await store.list_surfaces()})
async def _get_surface(request: Request) -> JSONResponse:
surface_id = request.path_params["surface_id"]
store = get_surface_store()
state = await store.get(surface_id)
if not state:
# Empty canvas is normal before the first A2UI push — not an error.
return JSONResponse(
{
"id": surface_id,
"messages": [],
"updated_at": None,
"metadata": {},
}
)
return JSONResponse(
{
"id": state.id,
"messages": state.messages,
"updated_at": state.updated_at,
"metadata": state.metadata,
}
)
async def _post_messages(request: Request) -> JSONResponse:
surface_id = request.path_params["surface_id"]
body = await request.json()
messages = body.get("messages", [])
if not isinstance(messages, list):
return JSONResponse({"error": "messages must be a list"}, status_code=400)
store = get_surface_store()
if body.get("replace"):
state = await store.set_messages(surface_id, messages)
else:
state = await store.apply_messages(surface_id, messages)
await broadcast_a2ui_surface(surface_id, state.messages)
return JSONResponse({"id": state.id, "message_count": len(state.messages)})
async def _post_action(request: Request) -> JSONResponse:
surface_id = request.path_params["surface_id"]
body = await request.json()
action = {
"type": "a2ui_action",
"surface_id": surface_id,
"component_id": body.get("component_id"),
"action": body.get("action"),
"data": body.get("data", {}),
"session_id": body.get("session_id"),
}
store = get_surface_store()
await store.queue_action(action)
handler = get_surface_action_handler(surface_id)
if handler:
try:
result = handler(action)
if asyncio.iscoroutine(result):
result = await result
return JSONResponse({"status": "handled", "result": result})
except Exception as exc:
logger.error("Surface action handler error: %s", exc)
return JSONResponse({"status": "error", "error": str(exc)}, status_code=500)
# Inject as chat message when session_id provided
session_id = body.get("session_id")
if session_id:
try:
from praisonaiui.features.chat import ChatMessage, get_chat_manager
mgr = get_chat_manager()
msg = ChatMessage(
role="user",
content=f"[A2UI action on {surface_id}] {body.get('action', '')}: {body.get('data', {})}",
session_id=session_id,
)
mgr._history.setdefault(session_id, []).append(msg)
except Exception:
pass
return JSONResponse({"status": "queued", "action": action})
async def _delete_surface(request: Request) -> JSONResponse:
surface_id = request.path_params["surface_id"]
store = get_surface_store()
removed = await store.clear(surface_id)
if not removed:
return JSONResponse({"error": "Surface not found"}, status_code=404)
return JSONResponse({"status": "deleted", "id": surface_id})
class SurfacesFeature(BaseFeatureProtocol):
"""A2UI surface CRUD and user action endpoints."""
feature_name = "surfaces"
feature_description = "Agent-driven A2UI surfaces"
@property
def name(self) -> str:
return self.feature_name
@property
def description(self) -> str:
return self.feature_description
def routes(self) -> List[Route]:
return [
Route("/api/surfaces", _list_surfaces, methods=["GET"]),
Route("/api/surfaces/{surface_id}", _get_surface, methods=["GET"]),
Route("/api/surfaces/{surface_id}/messages", _post_messages, methods=["POST"]),
Route("/api/surfaces/{surface_id}/action", _post_action, methods=["POST"]),
Route("/api/surfaces/{surface_id}", _delete_surface, methods=["DELETE"]),
]
async def health(self) -> Dict[str, Any]:
store = get_surface_store()
surfaces = await store.list_surfaces()
return {"status": "ok", "feature": self.name, "surface_count": len(surfaces)}
+49
View File
@@ -143,14 +143,23 @@ class SDKTracingManager(TracingProtocol):
def list_traces(
self, *, limit: int = 50, agent_id: Optional[str] = None
) -> List[Dict[str, Any]]:
sdk_traces = self._list_traces_from_sdk(limit=limit, agent_id=agent_id)
if sdk_traces is not None:
return sdk_traces
return self._simple.list_traces(limit=limit, agent_id=agent_id)
def list_spans(
self, *, limit: int = 100, trace_id: Optional[str] = None
) -> List[Dict[str, Any]]:
sdk_spans = self._list_spans_from_sdk(limit=limit, trace_id=trace_id)
if sdk_spans is not None:
return sdk_spans
return self._simple.list_spans(limit=limit, trace_id=trace_id)
def get_trace(self, trace_id: str) -> Optional[Dict[str, Any]]:
sdk_trace = self._get_trace_from_sdk(trace_id)
if sdk_trace is not None:
return sdk_trace
return self._simple.get_trace(trace_id)
def record_trace(self, trace: Dict[str, Any]) -> str:
@@ -159,6 +168,46 @@ class SDKTracingManager(TracingProtocol):
def record_span(self, span: Dict[str, Any]) -> str:
return self._simple.record_span(span)
def _list_traces_from_sdk(
self, *, limit: int, agent_id: Optional[str]
) -> Optional[List[Dict[str, Any]]]:
try:
from praisonaiagents import trace as trace_module
getter = getattr(trace_module, "list_traces", None)
if not callable(getter):
return None
items = getter(limit=limit, agent_id=agent_id)
return items if isinstance(items, list) else None
except Exception:
return None
def _list_spans_from_sdk(
self, *, limit: int, trace_id: Optional[str]
) -> Optional[List[Dict[str, Any]]]:
try:
from praisonaiagents import trace as trace_module
getter = getattr(trace_module, "list_spans", None)
if not callable(getter):
return None
items = getter(limit=limit, trace_id=trace_id)
return items if isinstance(items, list) else None
except Exception:
return None
def _get_trace_from_sdk(self, trace_id: str) -> Optional[Dict[str, Any]]:
try:
from praisonaiagents import trace as trace_module
getter = getattr(trace_module, "get_trace", None)
if not callable(getter):
return None
trace = getter(trace_id)
return trace if isinstance(trace, dict) else None
except Exception:
return None
def health(self) -> Dict[str, Any]:
h = self._simple.health()
h["provider"] = "SDKTracingManager"
+55 -1
View File
@@ -4,7 +4,7 @@ Architecture:
UsageProtocol (ABC) <- any backend implements this
└── SimpleUsageManager <- default in-memory with disk persistence
SDK gap: no token/cost tracking API in praisonaiagents.
SDK gap: bridged via ``backends.get_usage_sink()`` or praisonaiagents token collector.
PraisonAIUsage (BaseFeatureProtocol)
└── delegates to module-level functions (SimpleUsageManager pattern)
@@ -128,6 +128,49 @@ def _get_hour_key(timestamp: float) -> str:
return dt.strftime("%Y-%m-%d-%H")
def _ingest_from_usage_sink() -> None:
"""Pull records from injected or global token usage sink into aggregates."""
from praisonaiui.backends import get_usage_sink
sink = get_usage_sink()
if sink is None:
try:
from praisonaiagents.telemetry.token_collector import _token_collector
sink = getattr(_token_collector, "_sink", None)
except ImportError:
return
if sink is None or not hasattr(sink, "records"):
return
seen = {r.get("timestamp") for r in _usage_records if isinstance(r, dict)}
for rec in sink.records:
ts = rec.get("timestamp")
if ts in seen:
continue
track_usage(
model=rec.get("model", "unknown"),
input_tokens=int(rec.get("input_tokens", 0)),
output_tokens=int(rec.get("output_tokens", 0)),
session_id=(rec.get("metadata") or {}).get("session_id", "unknown"),
agent_name=rec.get("agent_name", "unknown"),
)
def _query_usage_backend() -> Dict[str, Any] | None:
"""Query injected usage backend when available."""
from praisonaiui.backends import get_usage_query
query = get_usage_query()
if query is None:
return None
try:
data = query()
except Exception:
return None
return data if isinstance(data, dict) else None
def track_usage(
model: str = "unknown",
input_tokens: int = 0,
@@ -328,6 +371,11 @@ class UsageFeature(BaseFeatureProtocol):
Response format matches the dashboard frontend expectations:
{"usage": {..., "by_model": {...}, "by_session": {...}}, "sessions": {...}}
"""
backend_data = _query_usage_backend()
if backend_data:
return JSONResponse(backend_data)
_ingest_from_usage_sink()
total_reqs = _aggregates["total_requests"]
total_cost = _aggregates["total_cost"]
total_tokens = _aggregates["total_input_tokens"] + _aggregates["total_output_tokens"]
@@ -557,3 +605,9 @@ def get_token_usage(session_id: str) -> Dict[str, Any]:
"total_cost": round(stats["cost"], 4),
"requests": stats["requests"],
}
def get_usage_aggregates() -> Dict[str, Any]:
"""Return current in-memory usage aggregates."""
_ingest_from_usage_sink()
return _aggregates
+358
View File
@@ -0,0 +1,358 @@
"""Video Studio — proxy to PraisonAI Video engine + project CRUD."""
from __future__ import annotations
import asyncio
import logging
import time
import uuid
from pathlib import Path
from typing import Any, Dict, List
from starlette.requests import Request
from starlette.responses import FileResponse, JSONResponse
from starlette.routing import Route
from praisonaiui.features.jobs import JobStatus, get_job_store
from praisonaiui.video_client import (
VideoEngineError,
compile_scene,
health,
lint,
preview_start,
registry,
test_scene,
)
from praisonaiui.video_client import (
render as engine_render,
)
from praisonaiui.video_projects import (
create_project,
export_artifact_path,
list_projects,
read_scene_yaml,
reset_scene_yaml,
resolve_project,
save_scene_yaml,
)
from praisonaiui.video_studio_sync import consume_project_refresh
from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
_video_jobs: Dict[str, Dict[str, Any]] = {}
class VideoFeature(BaseFeatureProtocol):
"""PraisonAI Video Studio API (projects, lint, render jobs)."""
feature_name = "video"
feature_description = "Video Studio — YAML scenes via external render engine"
@property
def name(self) -> str:
return self.feature_name
@property
def description(self) -> str:
return self.feature_description
def routes(self) -> List[Route]:
return [
Route("/api/video/health", self._health, methods=["GET"]),
Route("/api/video/lint", self._lint, methods=["POST"]),
Route("/api/video/compile", self._compile, methods=["POST"]),
Route("/api/video/preview-url", self._preview_url, methods=["GET"]),
Route("/api/video/preview/start", self._preview_start, methods=["POST"]),
Route("/api/video/render", self._render, methods=["POST"]),
Route("/api/video/test", self._test, methods=["POST"]),
Route("/api/video/jobs/{job_id}", self._job_status, methods=["GET"]),
Route("/api/video/registry", self._registry, methods=["GET"]),
Route("/api/video/projects", self._projects_list, methods=["GET"]),
Route("/api/video/projects", self._projects_create, methods=["POST"]),
Route("/api/video/projects/{project_id}", self._project_get, methods=["GET"]),
Route("/api/video/projects/{project_id}/scene", self._project_scene_put, methods=["PUT"]),
Route("/api/video/projects/{project_id}/reset", self._project_reset, methods=["POST"]),
Route(
"/api/video/projects/{project_id}/artifacts/{filename}",
self._artifact_download,
methods=["GET"],
),
Route(
"/api/video/projects/{project_id}/studio-refresh",
self._studio_refresh,
methods=["GET"],
),
]
async def health(self) -> Dict[str, Any]:
engine = await health()
return {"status": "ok", "feature": self.name, "engine": engine}
async def _health(self, request: Request) -> JSONResponse:
return JSONResponse(await self.health())
async def _lint(self, request: Request) -> JSONResponse:
body = await request.json()
try:
if body.get("yaml") is not None:
issues = await lint(yaml_text=body.get("yaml", ""), variables=body.get("variables"))
elif body.get("projectId"):
path = str(resolve_project(body["projectId"]) / "scene.yaml")
issues = await lint(path=path, variables=body.get("variables"))
elif body.get("projectPath"):
issues = await lint(path=str(Path(body["projectPath"]) / "scene.yaml"), variables=body.get("variables"))
else:
issues = await lint(yaml_text=body.get("yaml", ""), variables=body.get("variables"))
return JSONResponse({"issues": issues})
except VideoEngineError as exc:
return JSONResponse({"error": str(exc), "issues": []}, status_code=exc.status_code)
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
async def _compile(self, request: Request) -> JSONResponse:
body = await request.json()
try:
if body.get("projectId"):
path = str(resolve_project(body["projectId"]) / "scene.yaml")
data = await compile_scene(path=path, variables=body.get("variables"))
elif body.get("projectPath"):
data = await compile_scene(
path=str(Path(body["projectPath"]) / "scene.yaml"),
variables=body.get("variables"),
)
else:
data = await compile_scene(yaml_text=body.get("yaml", ""), variables=body.get("variables"))
if data.get("error"):
return JSONResponse(data, status_code=422)
return JSONResponse(data)
except VideoEngineError as exc:
if exc.detail:
return JSONResponse(exc.detail, status_code=exc.status_code)
return JSONResponse({"error": str(exc)}, status_code=exc.status_code)
async def _preview_url(self, request: Request) -> JSONResponse:
project_id = request.query_params.get("projectId")
if not project_id:
return JSONResponse({"error": "projectId required"}, status_code=400)
try:
project_path = str(resolve_project(project_id))
data = await preview_start(project_path)
url = data.get("url")
if url:
return JSONResponse({"url": url, "port": data.get("port")})
return JSONResponse(
{
"url": None,
"message": data.get("message")
or "Video engine not running. Start with: praisonai-video serve",
}
)
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
async def _preview_start(self, request: Request) -> JSONResponse:
body = await request.json()
project_path = body.get("projectPath")
if body.get("projectId"):
project_path = str(resolve_project(body["projectId"]))
if not project_path:
return JSONResponse({"error": "projectPath or projectId required"}, status_code=400)
data = await preview_start(project_path)
return JSONResponse(data)
async def _render(self, request: Request) -> JSONResponse:
body = await request.json()
project_id = body.get("projectId")
if not project_id:
return JSONResponse({"error": "projectId required"}, status_code=400)
try:
project_dir = resolve_project(project_id)
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
out_path = export_artifact_path(project_id, body.get("out", "out.mp4"))
job_id = f"vid_{uuid.uuid4().hex[:12]}"
now = time.time()
job = {
"id": job_id,
"type": "video_render",
"status": JobStatus.QUEUED.value,
"projectId": project_id,
"projectPath": str(project_dir),
"out": str(out_path),
"test": bool(body.get("test")),
"progress": None,
"created_at": now,
"started_at": None,
"completed_at": None,
"result": None,
"error": None,
}
_video_jobs[job_id] = job
store = get_job_store()
store.save({**job, "prompt": f"Render {project_id}"})
explicit_backend = body.get("backend")
if isinstance(explicit_backend, str):
explicit_backend = explicit_backend.strip() or None
else:
explicit_backend = None
asyncio.create_task(
self._run_render(
job_id, str(project_dir), str(out_path), job["test"], explicit_backend
)
)
return JSONResponse({"jobId": job_id})
async def _run_render(
self,
job_id: str,
project_path: str,
out: str,
test: bool,
explicit_backend: str | None = None,
) -> None:
job = _video_jobs.get(job_id)
if not job:
return
store = get_job_store()
job["status"] = JobStatus.RUNNING.value
job["started_at"] = time.time()
store.save({**job, "prompt": job.get("prompt", f"Render {job.get('projectId')}")})
try:
from praisonaiui.video_projects import read_render_backend_from_scene
backend = explicit_backend
if not backend and job.get("projectId"):
backend = read_render_backend_from_scene(job["projectId"])
result = await engine_render(
project_path, out=out, test=test, backend=backend
)
status = result.get("status", "succeeded")
if status == "succeeded" or result.get("artifactPath"):
artifact = result.get("artifactPath", out)
download_url = f"/api/video/projects/{job['projectId']}/artifacts/{Path(artifact).name}"
job["status"] = JobStatus.SUCCEEDED.value
job["result"] = {"artifactPath": artifact, "downloadUrl": download_url}
elif result.get("jobId"):
job["engineJobId"] = result["jobId"]
job["status"] = JobStatus.RUNNING.value
else:
job["status"] = JobStatus.SUCCEEDED.value
job["result"] = result
except VideoEngineError as exc:
job["status"] = JobStatus.FAILED.value
job["error"] = str(exc)
except Exception as exc:
logger.exception("Video render failed for %s", job_id)
job["status"] = JobStatus.FAILED.value
job["error"] = str(exc)
finally:
job["completed_at"] = time.time()
store.save({**job, "prompt": job.get("prompt", "")})
async def _test(self, request: Request) -> JSONResponse:
body = await request.json()
project_id = body.get("projectId")
if not project_id:
return JSONResponse({"error": "projectId required"}, status_code=400)
try:
project_path = str(resolve_project(project_id))
result = await test_scene(project_path)
return JSONResponse(result)
except VideoEngineError as exc:
return JSONResponse({"error": str(exc), "passed": False}, status_code=exc.status_code)
async def _job_status(self, request: Request) -> JSONResponse:
job_id = request.path_params["job_id"]
job = _video_jobs.get(job_id)
if not job:
store_job = get_job_store().get(job_id)
if store_job and store_job.get("type") == "video_render":
job = store_job
if not job:
return JSONResponse({"error": "Job not found"}, status_code=404)
engine_id = job.get("engineJobId")
if engine_id and job.get("status") == JobStatus.RUNNING.value:
try:
from praisonaiui.video_client import _request
remote = await _request("GET", f"/v1/jobs/{engine_id}")
job["status"] = remote.get("status", job["status"])
job["progress"] = remote.get("progress")
if remote.get("artifactUrl"):
job.setdefault("result", {})["downloadUrl"] = remote["artifactUrl"]
except VideoEngineError:
pass
return JSONResponse(
{
"id": job_id,
"status": job.get("status"),
"progress": job.get("progress"),
"artifactPath": (job.get("result") or {}).get("artifactPath"),
"downloadUrl": (job.get("result") or {}).get("downloadUrl"),
"error": job.get("error"),
}
)
async def _registry(self, request: Request) -> JSONResponse:
return JSONResponse(await registry())
async def _projects_list(self, request: Request) -> JSONResponse:
return JSONResponse({"projects": list_projects()})
async def _projects_create(self, request: Request) -> JSONResponse:
body = await request.json()
name = body.get("name", "Untitled video")
project = create_project(name)
return JSONResponse(project, status_code=201)
async def _project_get(self, request: Request) -> JSONResponse:
project_id = request.path_params["project_id"]
try:
path = resolve_project(project_id)
yaml_text = read_scene_yaml(project_id)
return JSONResponse(
{"id": project_id, "path": str(path), "sceneYaml": yaml_text}
)
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
async def _project_reset(self, request: Request) -> JSONResponse:
project_id = request.path_params["project_id"]
try:
yaml_text = reset_scene_yaml(project_id)
return JSONResponse({"ok": True, "sceneYaml": yaml_text})
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
async def _project_scene_put(self, request: Request) -> JSONResponse:
project_id = request.path_params["project_id"]
body = await request.json()
yaml_text = body.get("yaml", "")
try:
save_scene_yaml(project_id, yaml_text)
return JSONResponse({"ok": True})
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
async def _studio_refresh(self, request: Request) -> JSONResponse:
project_id = request.path_params["project_id"]
return JSONResponse({"refresh": consume_project_refresh(project_id)})
async def _artifact_download(self, request: Request) -> FileResponse | JSONResponse:
project_id = request.path_params["project_id"]
filename = request.path_params["filename"]
try:
path = resolve_project(project_id) / "exports" / filename
if not path.is_file():
return JSONResponse({"error": "Artifact not found"}, status_code=404)
return FileResponse(path, media_type="video/mp4", filename=filename)
except FileNotFoundError as exc:
return JSONResponse({"error": str(exc)}, status_code=404)
__all__ = ["VideoFeature"]
+21 -27
View File
@@ -7,6 +7,7 @@ listing, running, and checking status of multi-step workflows
from __future__ import annotations
import asyncio
import time
import uuid
from typing import Any, Dict, List
@@ -17,11 +18,23 @@ from starlette.routing import Route
from ._base import BaseFeatureProtocol
# In-memory workflow registry + run history
_workflows: Dict[str, Dict[str, Any]] = {}
_runs: Dict[str, Dict[str, Any]] = {}
def _execute_workflow(wf_id: str, wf: Dict[str, Any], input_data: Dict[str, Any]) -> Dict[str, Any]:
"""Run via injected backend or built-in AgentFlow runner."""
from praisonaiui.backends import get_workflow_runner
runner = get_workflow_runner()
if runner is not None:
return runner(wf_id, workflow=wf, input_data=input_data)
from praisonaiui._workflow_runner import run_workflow
return run_workflow(wf_id, workflow=wf, input_data=input_data)
class WorkflowsFeature(BaseFeatureProtocol):
"""Workflow management wired to praisonaiagents.workflows."""
@@ -70,8 +83,6 @@ class WorkflowsFeature(BaseFeatureProtocol):
"total_runs": len(_runs),
}
# ── API handlers ─────────────────────────────────────────────────
async def _list(self, request: Request) -> JSONResponse:
return JSONResponse({"workflows": list(_workflows.values()), "count": len(_workflows)})
@@ -108,20 +119,12 @@ class WorkflowsFeature(BaseFeatureProtocol):
wf = _workflows.get(wf_id)
if not wf:
return JSONResponse({"error": "Workflow not found"}, status_code=404)
run_id = uuid.uuid4().hex[:12]
content_type = request.headers.get("content-type")
body = await request.json() if content_type == "application/json" else {}
run_entry = {
"id": run_id,
"workflow_id": wf_id,
"workflow_name": wf["name"],
"status": "completed",
"input": body.get("input", {}),
"output": {"message": f"Workflow '{wf['name']}' executed successfully"},
"started_at": time.time(),
"completed_at": time.time(),
}
_runs[run_id] = run_entry
run_entry = await asyncio.to_thread(
_execute_workflow, wf_id, wf, body.get("input", {})
)
_runs[run_entry["id"]] = run_entry
return JSONResponse(run_entry)
async def _status(self, request: Request) -> JSONResponse:
@@ -149,8 +152,6 @@ class WorkflowsFeature(BaseFeatureProtocol):
return JSONResponse({"error": "Run not found"}, status_code=404)
return JSONResponse(run)
# ── CLI handlers ─────────────────────────────────────────────────
def _cli_list(self) -> str:
if not _workflows:
return "No workflows registered"
@@ -161,15 +162,9 @@ class WorkflowsFeature(BaseFeatureProtocol):
wf = _workflows.get(workflow_id)
if not wf:
return f"Workflow {workflow_id} not found"
run_id = uuid.uuid4().hex[:12]
_runs[run_id] = {
"id": run_id,
"workflow_id": workflow_id,
"status": "completed",
"started_at": time.time(),
"completed_at": time.time(),
}
return f"Ran workflow {workflow_id} → run {run_id}"
run_entry = _execute_workflow(workflow_id, wf, {})
_runs[run_entry["id"]] = run_entry
return f"Ran workflow {workflow_id} → run {run_entry['id']} ({run_entry['status']})"
def _cli_status(self) -> str:
return f"Workflows: {len(_workflows)}, Runs: {len(_runs)}"
@@ -184,5 +179,4 @@ class WorkflowsFeature(BaseFeatureProtocol):
return "\n".join(lines)
# Backward-compat alias
PraisonAIWorkflows = WorkflowsFeature
+17
View File
@@ -141,6 +141,23 @@ class AIUIGateway:
"Install with: pip install starlette uvicorn"
)
# ── Bootstrap datastore + SDK bridges (Pattern C parity) ───────
try:
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
from praisonaiui.server import set_datastore
set_datastore(PraisonAISessionDataStore())
except ImportError:
pass
try:
from praisonai.integration.host_app import setup_bridges
setup_bridges()
except ImportError:
pass
# ── Register gateway agents with the server's provider system ──
from praisonaiui.server import create_app, register_agent, set_provider
+232
View File
@@ -0,0 +1,232 @@
"""Media payload detection and normalisation for PraisonAIUI chat."""
from __future__ import annotations
import base64
import re
from typing import Any, Dict, List, Optional
MEDIA_ELEMENT_TYPES = frozenset({"image", "pdf", "video", "audio", "file"})
_IMAGE_URL_RE = re.compile(
r"^https?://[^\s]+\.(png|jpg|jpeg|gif|webp|svg|bmp)(\?[^\s]*)?$",
re.IGNORECASE,
)
_DATA_IMAGE_RE = re.compile(r"^data:image/[^;]+;base64,", re.IGNORECASE)
def media_serve_url(attachment_id: str) -> str:
"""Public URL for a stored generated-media attachment."""
return f"/api/chat/media/{attachment_id}"
def _store_b64_image(b64_json: str, session_id: str = "") -> str:
"""Persist base64 image bytes and return a stable serve URL."""
from praisonaiui.features.attachments import get_attachment_manager
raw = base64.b64decode(b64_json, validate=True)
mgr = get_attachment_manager()
meta = mgr.upload(
data=raw,
filename="generated.png",
content_type="image/png",
session_id=session_id,
)
return media_serve_url(meta["id"])
def _image_element(
*,
url: Optional[str] = None,
b64_json: Optional[str] = None,
alt: str = "",
persist_b64: bool = True,
session_id: str = "",
) -> Optional[Dict[str, Any]]:
if url:
return {"type": "image", "url": url, "alt": alt or "Generated image"}
if b64_json:
if persist_b64:
try:
url = _store_b64_image(b64_json, session_id=session_id)
return {"type": "image", "url": url, "alt": alt or "Generated image"}
except Exception:
pass
return {
"type": "image",
"url": f"data:image/png;base64,{b64_json}",
"alt": alt or "Generated image",
}
return None
def _item_to_element(
item: Any,
*,
persist_b64: bool = True,
session_id: str = "",
) -> Optional[Dict[str, Any]]:
if not isinstance(item, dict):
return None
el_type = item.get("type")
if el_type in MEDIA_ELEMENT_TYPES and item.get("url"):
return dict(item)
url = item.get("url")
b64 = item.get("b64_json")
alt = item.get("alt") or item.get("revised_prompt") or item.get("name") or ""
if url or b64:
return _image_element(
url=url,
b64_json=b64,
alt=str(alt) if alt else "",
persist_b64=persist_b64,
session_id=session_id,
)
return None
def _openai_data_list(result: dict) -> List[Any]:
data = result.get("data")
if isinstance(data, list):
return data
return []
def extract_media_elements(
result: Any,
*,
persist_b64: bool = True,
session_id: str = "",
) -> List[Dict[str, Any]]:
"""Normalise backend-specific media payloads into message element dicts."""
if result is None:
return []
if isinstance(result, str):
text = result.strip()
if _DATA_IMAGE_RE.match(text) or _IMAGE_URL_RE.match(text):
return [{"type": "image", "url": text, "alt": "Image"}]
return []
if isinstance(result, dict):
if result.get("type") in MEDIA_ELEMENT_TYPES and result.get("url"):
return [dict(result)]
elements: List[Dict[str, Any]] = []
for item in _openai_data_list(result):
el = _item_to_element(
item, persist_b64=persist_b64, session_id=session_id
)
if el:
elements.append(el)
if elements:
return elements
el = _item_to_element(result, persist_b64=persist_b64, session_id=session_id)
if el:
return [el]
nested = result.get("elements")
if isinstance(nested, list):
out: List[Dict[str, Any]] = []
for item in nested:
if isinstance(item, dict) and item.get("type") in MEDIA_ELEMENT_TYPES:
out.append(dict(item))
if out:
return out
return []
if isinstance(result, list):
out: List[Dict[str, Any]] = []
for item in result:
if isinstance(item, dict) and item.get("type") in MEDIA_ELEMENT_TYPES:
out.append(dict(item))
continue
el = _item_to_element(item, persist_b64=persist_b64, session_id=session_id)
if el:
out.append(el)
return out
# litellm ImageResponse / ImageAgent.chat() return value
data = getattr(result, "data", None)
if isinstance(data, list):
elements: List[Dict[str, Any]] = []
for item in data:
if isinstance(item, dict):
el = _item_to_element(
item, persist_b64=persist_b64, session_id=session_id
)
else:
url = getattr(item, "url", None)
b64 = getattr(item, "b64_json", None)
alt = (
getattr(item, "revised_prompt", None)
or getattr(item, "alt", None)
or getattr(item, "name", None)
or ""
)
el = _image_element(
url=url,
b64_json=b64,
alt=str(alt) if alt else "",
persist_b64=persist_b64,
session_id=session_id,
)
if el:
elements.append(el)
if elements:
return elements
# ImageResult-like objects from praisonai capabilities
url = getattr(result, "url", None)
b64 = getattr(result, "b64_json", None)
if url or b64:
el = _image_element(
url=url,
b64_json=b64,
alt=getattr(result, "revised_prompt", "") or "",
persist_b64=persist_b64,
session_id=session_id,
)
return [el] if el else []
return []
def is_media_tool_result(result: Any) -> bool:
"""Return True if *result* contains displayable media elements."""
return bool(extract_media_elements(result, persist_b64=False))
def build_media_extra(
result: Any,
*,
persist_b64: bool = True,
session_id: str = "",
) -> Optional[Dict[str, Any]]:
"""Build extra_data dict for RunEvent when result carries media."""
elements = extract_media_elements(
result, persist_b64=persist_b64, session_id=session_id
)
if not elements:
return None
return {"elements": elements}
def queue_event_to_element(event: dict) -> Optional[Dict[str, Any]]:
"""Map legacy callback queue events (image/video/…) to element dicts."""
evt_type = event.get("type")
url = event.get("url", "")
if not url and evt_type != "code":
return None
if evt_type == "image":
return {"type": "image", "url": url, "alt": event.get("alt", "")}
if evt_type == "video":
return {"type": "video", "url": url, "name": event.get("name", "")}
if evt_type == "audio":
return {"type": "audio", "url": url, "name": event.get("name", "")}
if evt_type == "file":
return {"type": "file", "url": url, "name": event.get("name", "")}
return None
+62 -28
View File
@@ -72,6 +72,9 @@ class RunEventType(str, Enum):
# --- RAG / Citations (Issue #49) ---
REFERENCES = "references"
# --- Rich message elements (images, video, etc.) ---
MESSAGE_ELEMENT = "message_element"
# --- Team variants ---
TEAM_RUN_STARTED = "team_run_started"
TEAM_RUN_CONTENT = "team_run_content"
@@ -93,8 +96,12 @@ class RunEventType(str, Enum):
@dataclass
class ModelInfo:
"""Model / provider info advertised by an agent or team (Issue #48)."""
class ModelCard:
"""Model / provider info advertised by an agent or team (Issue #48).
PraisonAI-native, non-developer-friendly name: a "card" is the small
info block a UI shows next to an agent.
"""
name: str
model: str
@@ -105,13 +112,13 @@ class ModelInfo:
@dataclass
class AgentDetails:
"""Standard agent-discovery schema for third-party chat frontends (Issue #48)."""
class AgentCard:
"""Agent discovery card — what a chat frontend shows in its picker (Issue #48)."""
agent_id: str
name: str
description: str = ""
model: Optional[ModelInfo] = None
model: Optional[ModelCard] = None
storage: bool = False
def to_dict(self) -> Dict[str, Any]:
@@ -127,13 +134,13 @@ class AgentDetails:
@dataclass
class TeamDetails:
"""Standard team-discovery schema (Issue #48)."""
class TeamCard:
"""Team discovery card — what a chat frontend shows for multi-agent teams (Issue #48)."""
team_id: str
name: str
description: str = ""
model: Optional[ModelInfo] = None
model: Optional[ModelCard] = None
storage: bool = False
def to_dict(self) -> Dict[str, Any]:
@@ -149,8 +156,12 @@ class TeamDetails:
@dataclass
class Reference:
"""A single RAG retrieval chunk cited by an answer (Issue #49)."""
class SourceChunk:
"""A single RAG retrieval chunk cited by an answer (Issue #49).
PraisonAI-native name — "source chunk" is the plain-English term a
non-developer would use for "a piece of a source document that was used".
"""
name: str
content: str
@@ -167,17 +178,22 @@ class Reference:
@dataclass
class ReferenceData:
"""A retrieval batch: a query and the chunks it returned (Issue #49)."""
class SourceBundle:
"""A bundle of source chunks retrieved for one query (Issue #49).
Wire format keeps the industry-standard ``references`` JSON key so
third-party frontends see the expected shape; only the Python class
name is PraisonAI-native.
"""
query: str
references: List[Reference] = field(default_factory=list)
chunks: List[SourceChunk] = field(default_factory=list)
time_ms: Optional[float] = None
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {
"query": self.query,
"references": [r.to_dict() for r in self.references],
"references": [c.to_dict() for c in self.chunks],
}
if self.time_ms is not None:
d["time_ms"] = self.time_ms
@@ -185,11 +201,13 @@ class ReferenceData:
@dataclass
class ReasoningStep:
class ThoughtStep:
"""Structured reasoning-step payload (Issue #50).
All fields beyond ``title`` are optional so providers can adopt the richer
schema incrementally without breaking existing behaviour.
PraisonAI-native name — pairs with the existing ``ThinkingSteps`` UI
component. All fields beyond ``title`` are optional so providers can
adopt the richer schema incrementally without breaking existing
behaviour.
"""
title: str
@@ -306,6 +324,14 @@ class RunEvent:
val = getattr(self, key)
if val is not None:
d[key] = val
if self.extra_data:
if self.type == RunEventType.MESSAGE_ELEMENT:
element = self.extra_data.get("element")
if element is not None:
d["element"] = element
elements = self.extra_data.get("elements")
if elements is not None and "elements" not in d:
d["elements"] = elements
return d
@@ -400,22 +426,22 @@ class BaseProvider(ABC):
# -----------------------------------------------------------------
@staticmethod
def references_event(
query: str,
references: List["Reference"],
time_ms: Optional[float] = None,
) -> RunEvent:
"""Build a ``REFERENCES`` event from a ``Reference`` list (Issue #49)."""
def source_bundle_event(bundle: "SourceBundle") -> RunEvent:
"""Build a ``REFERENCES`` event from a ``SourceBundle`` (Issue #49).
The wire ``type`` stays ``references`` for third-party frontend
compatibility; only the Python-side helper name is PraisonAI-native.
"""
return RunEvent(
type=RunEventType.REFERENCES,
query=query,
references=[r.to_dict() for r in references],
time_ms=time_ms,
query=bundle.query,
references=[c.to_dict() for c in bundle.chunks],
time_ms=bundle.time_ms,
)
@staticmethod
def reasoning_step_event(step: "ReasoningStep") -> RunEvent:
"""Build a ``REASONING_STEP`` event from a structured step (Issue #50)."""
def thought_step_event(step: "ThoughtStep") -> RunEvent:
"""Build a ``REASONING_STEP`` event from a ``ThoughtStep`` (Issue #50)."""
return RunEvent(
type=RunEventType.REASONING_STEP,
step=step.title,
@@ -424,3 +450,11 @@ class BaseProvider(ABC):
next_action=step.next_action,
extra_data=step.to_dict(),
)
@staticmethod
def message_element_event(element: Dict[str, Any]) -> RunEvent:
"""Build a ``MESSAGE_ELEMENT`` event for inline media in chat."""
return RunEvent(
type=RunEventType.MESSAGE_ELEMENT,
extra_data={"element": element},
)
+162 -29
View File
@@ -31,6 +31,20 @@ _STREAM_EVENT_MAPPING = None
_MAPPING_WARNED_TYPES: set = set() # Track already-warned unknown types
def _tool_completed_extra_from_result(
result: Any,
*,
has_complete_args: bool = True,
) -> Optional[Dict[str, Any]]:
try:
from praisonaiui.a2ui_utils import tool_completed_extra
extra = tool_completed_extra(result, has_complete_args=has_complete_args)
return extra or None
except ImportError:
return {"has_complete_args": True} if has_complete_args else None
def _build_stream_event_mapping():
"""Build the StreamEvent → RunEvent mapping dict.
@@ -65,6 +79,10 @@ def _build_stream_event_mapping():
name=e.tool_call.get("name") if e.tool_call else None,
result=e.tool_call.get("result") if e.tool_call else None,
tool_call_id=e.tool_call.get("id") if e.tool_call else None,
extra_data=_tool_completed_extra_from_result(
e.tool_call.get("result") if e.tool_call else None,
has_complete_args=False,
),
),
SET.FIRST_TOKEN: lambda e: RunEvent(
type=RunEventType.RUN_CONTENT,
@@ -92,14 +110,18 @@ def _build_stream_event_mapping():
extra_data={"has_complete_args": True},
)
if hasattr(SET, "TOOL_CALL_RESULT"):
mapping[SET.TOOL_CALL_RESULT] = lambda e: RunEvent(
type=RunEventType.TOOL_CALL_COMPLETED,
name=e.tool_call.get("name") if e.tool_call else None,
args=e.tool_call.get("arguments") if e.tool_call else None,
result=e.tool_call.get("result") if e.tool_call else None,
tool_call_id=e.tool_call.get("id") if e.tool_call else None,
extra_data={"has_complete_args": True},
)
def _tool_call_result(e):
result = e.tool_call.get("result") if e.tool_call else None
return RunEvent(
type=RunEventType.TOOL_CALL_COMPLETED,
name=e.tool_call.get("name") if e.tool_call else None,
args=e.tool_call.get("arguments") if e.tool_call else None,
result=result,
tool_call_id=e.tool_call.get("id") if e.tool_call else None,
extra_data=_tool_completed_extra_from_result(result),
)
mapping[SET.TOOL_CALL_RESULT] = _tool_call_result
return mapping
@@ -140,12 +162,14 @@ def _hook_event_to_run_events(hook_event_name: str, event_data) -> List[RunEvent
)
)
elif hook_event_name == "after_tool":
result = getattr(event_data, "result", None)
events.append(
RunEvent(
type=RunEventType.TOOL_CALL_COMPLETED,
name=getattr(event_data, "tool_name", None),
result=getattr(event_data, "result", None),
result=result,
error=getattr(event_data, "error", None),
extra_data=_tool_completed_extra_from_result(result),
)
)
elif hook_event_name == "before_agent":
@@ -255,6 +279,19 @@ class PraisonAIProvider(BaseProvider):
except (ImportError, Exception):
pass
# 2b. Agents registered via praisonaiui.register_agent()
try:
from praisonaiui.server import _agents
if agent_name and agent_name in _agents:
return _agents[agent_name]["agent"]
if len(_agents) == 1:
return next(iter(_agents.values()))["agent"]
if _agents and not agent_name:
return next(iter(_agents.values()))["agent"]
except ImportError:
pass
# 3. Per-session agent: return cached if available
# Key includes agent_name so switching agents creates a new instance
cache_key = f"{session_id}:{agent_name or ''}"
@@ -449,6 +486,21 @@ class PraisonAIProvider(BaseProvider):
full_response += "\n"
full_response += content
yield RunEvent(type=RunEventType.RUN_CONTENT, content=content)
for el in event.get("elements") or []:
if isinstance(el, dict):
yield RunEvent(
type=RunEventType.MESSAGE_ELEMENT,
extra_data={"element": el},
)
elif evt_type in ("image", "video", "audio", "file"):
from praisonaiui.media_utils import queue_event_to_element
element = queue_event_to_element(event)
if element:
yield RunEvent(
type=RunEventType.MESSAGE_ELEMENT,
extra_data={"element": element},
)
elif evt_type == "thinking":
yield RunEvent(type=RunEventType.REASONING_STEP, step=event.get("step", ""))
elif evt_type == "tool_call":
@@ -459,11 +511,13 @@ class PraisonAIProvider(BaseProvider):
result=event.get("result"),
)
elif evt_type == "tool_result":
result = event.get("result")
yield RunEvent(
type=RunEventType.TOOL_CALL_COMPLETED,
name=event.get("name"),
result=event.get("result"),
result=result,
error=event.get("error"),
extra_data=_tool_completed_extra_from_result(result),
)
elif evt_type == "ask":
yield RunEvent(
@@ -610,29 +664,54 @@ class PraisonAIProvider(BaseProvider):
except ImportError:
pass
# Run agent.chat in a background thread while draining the
# Run agent.achat (async unified dispatch) while draining the
# event queue concurrently so tokens stream in real-time.
full_response = ""
_chat_error = None
_raw_chat_response: Any = None
_streamed_tokens = 0
_streamed_text = "" # Track actual text content from stream
# Track tool calls that were started but not yet completed,
# so we can synthesize COMPLETED events if the SDK omits them.
_pending_tool_calls: Dict[str, RunEvent] = {}
def _normalise_chat_response(response: Any, streamed: str) -> str:
"""Avoid showing literal 'None' when SDK returns no text."""
if response is not None:
text = response if isinstance(response, str) else str(response)
if text and text != "None":
return text
streamed = (streamed or "").strip()
if streamed and streamed != "None":
return streamed
return ""
async def _run_chat():
nonlocal full_response, _chat_error
nonlocal full_response, _chat_error, _raw_chat_response
try:
chat_kwargs = {"stream": True}
# Forward attachment file paths for SDK native multimodal handling
chat_kwargs: Dict[str, Any] = {}
if "attachments" in kwargs and kwargs["attachments"]:
chat_kwargs["attachments"] = kwargs["attachments"]
response = await asyncio.to_thread(agent.chat, message, **chat_kwargs)
full_response = str(response)
# Media agents return one payload — streaming breaks litellm image API.
_MEDIA_AGENT_TYPES = frozenset({"ImageAgent", "VideoAgent", "AudioAgent"})
is_media_agent = type(agent).__name__ in _MEDIA_AGENT_TYPES
if hasattr(agent, "achat") and not is_media_agent:
response = await agent.achat(
message, stream=True, **chat_kwargs
)
elif hasattr(agent, "achat"):
response = await agent.achat(
message, stream=False, **chat_kwargs
)
else:
response = await asyncio.to_thread(
agent.chat, message, stream=False, **chat_kwargs
)
_raw_chat_response = response
full_response = _normalise_chat_response(response, _streamed_text)
except Exception as exc:
_chat_error = exc
finally:
# Sentinel to tell the drain loop the chat is done
await event_queue.put(None)
chat_task = asyncio.create_task(_run_chat())
@@ -646,9 +725,11 @@ class PraisonAIProvider(BaseProvider):
if run_evt is None: # Sentinel — chat finished
break
_streamed_tokens += 1
# Track actual streamed text content
# Track actual streamed text content (ignore bogus None tokens from SDK)
if run_evt.type == RunEventType.RUN_CONTENT:
_streamed_text += run_evt.token or run_evt.content or ""
tok = run_evt.token or run_evt.content or ""
if tok and tok != "None":
_streamed_text += tok
# Track pending tool calls
if run_evt.type == RunEventType.TOOL_CALL_STARTED and run_evt.name:
key = run_evt.tool_call_id or run_evt.name
@@ -666,9 +747,10 @@ class PraisonAIProvider(BaseProvider):
try:
run_evt = event_queue.get_nowait()
if run_evt is not None:
# Track actual streamed text content
if run_evt.type == RunEventType.RUN_CONTENT:
_streamed_text += run_evt.token or run_evt.content or ""
tok = run_evt.token or run_evt.content or ""
if tok and tok != "None":
_streamed_text += tok
# Track pending tool calls in drain phase too
if run_evt.type == RunEventType.TOOL_CALL_STARTED and run_evt.name:
key = run_evt.tool_call_id or run_evt.name
@@ -688,11 +770,51 @@ class PraisonAIProvider(BaseProvider):
yield RunEvent(type=RunEventType.RUN_ERROR, error=str(_chat_error))
return
# Emit the full response if streaming didn't capture it.
# The SDK's stream_emitter may fire events with empty content
# even though agent.chat() returns the full response text.
# Compare what was streamed vs the actual response and fill the gap.
if full_response and len(_streamed_text.strip()) < len(full_response.strip()) * 0.8:
if _raw_chat_response is not None:
from praisonaiui.media_utils import extract_media_elements
if isinstance(_raw_chat_response, dict) and _raw_chat_response.get("error"):
yield RunEvent(
type=RunEventType.RUN_ERROR,
error=str(_raw_chat_response["error"]),
)
return
media_els = extract_media_elements(
_raw_chat_response,
session_id=session_id or "",
)
for el in media_els:
yield RunEvent(
type=RunEventType.MESSAGE_ELEMENT,
extra_data={"element": el},
)
if media_els and not _streamed_text.strip():
full_response = "Here is your generated image."
full_response = _normalise_chat_response(full_response or None, _streamed_text)
if not full_response:
try:
fallback_kwargs: Dict[str, Any] = {"stream": False}
if "attachments" in kwargs and kwargs["attachments"]:
fallback_kwargs["attachments"] = kwargs["attachments"]
if hasattr(agent, "achat"):
fallback = await agent.achat(message, **fallback_kwargs)
else:
fallback = await asyncio.to_thread(
agent.chat, message, **fallback_kwargs
)
full_response = _normalise_chat_response(fallback, _streamed_text)
except Exception as exc:
yield RunEvent(type=RunEventType.RUN_ERROR, error=str(exc))
return
if full_response and not _streamed_text.strip():
yield RunEvent(type=RunEventType.RUN_CONTENT, content=full_response)
elif (
full_response
and len(_streamed_text.strip()) < len(full_response.strip()) * 0.8
):
yield RunEvent(type=RunEventType.RUN_CONTENT, content=full_response)
# Synthesize TOOL_CALL_COMPLETED for any tool calls that were started
@@ -760,9 +882,20 @@ class PraisonAIProvider(BaseProvider):
from praisonaiui.features.usage import track_usage
agent_model = getattr(agent, "llm", "unknown")
# Estimate tokens from response length (rough heuristic)
input_tokens = max(1, len(message) // 4)
output_tokens = max(1, len(full_response) // 4)
input_tokens = output_tokens = 0
try:
from praisonaiagents.telemetry.token_collector import _token_collector
recent = _token_collector.get_recent_interactions(1)
if recent:
metrics = recent[-1].get("metrics") or {}
input_tokens = int(metrics.get("input_tokens", 0))
output_tokens = int(metrics.get("output_tokens", 0))
except ImportError:
pass
if not input_tokens and not output_tokens:
input_tokens = max(1, len(message) // 4)
output_tokens = max(1, len(full_response) // 4)
track_usage(
model=str(agent_model),
input_tokens=input_tokens,
+9
View File
@@ -351,6 +351,15 @@ class DashboardConfig(BaseModel):
page_header: bool = Field(
default=True, alias="pageHeader", description="Show page title/description header"
)
modules: list[str] = Field(
default_factory=list,
description="Optional dashboard JS modules to load (jobs, auth, api)",
)
plugin_dirs: list[str] = Field(
default_factory=list,
alias="pluginDirs",
description="Extra directories to scan for dashboard-plugins manifests",
)
model_config = ConfigDict(populate_by_name=True)
+295 -32
View File
@@ -12,7 +12,7 @@ from collections import deque
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, AsyncGenerator, Callable, Optional
from typing import Any, AsyncGenerator, Callable, Dict, Optional
from starlette.applications import Starlette
from starlette.middleware import Middleware
@@ -105,6 +105,8 @@ _custom_css: Optional[str] = None
_custom_js: Optional[str] = None
# Chat features override set via aiui.set_chat_features()
_chat_features: Optional[dict[str, Any]] = None
# External agent settings panel entries set via aiui.set_settings()
_agent_settings: Optional[list[dict[str, Any]]] = None
# Dashboard config set via aiui.set_dashboard()
_dashboard_config: Optional[dict[str, Any]] = None
# Feedback configuration
@@ -118,8 +120,8 @@ def reset_state() -> None:
state leaking between tests. Not for production use.
"""
global _style, _branding, _theme, _custom_css, _custom_js, _chat_features
global _dashboard_config, _provider, _config_path, _config_cache
global _chat_mode, _brand_color, _effective_style, _selected_profile, _feedback_enabled
global _dashboard_config, _agent_settings, _provider, _config_path, _config_cache
global _chat_mode, _chat_preview, _brand_color, _effective_style, _selected_profile, _feedback_enabled
_callbacks.clear()
_agents.clear()
_pages.clear()
@@ -143,11 +145,13 @@ def reset_state() -> None:
_custom_css = None
_custom_js = None
_chat_features = None
_agent_settings = None
_dashboard_config = None
_provider = None
_config_path = None
_config_cache = None
_chat_mode = None
_chat_preview = None
_brand_color = None
_effective_style = "chat"
_selected_profile = {"id": None}
@@ -340,10 +344,27 @@ def set_chat_features(
}
def set_settings(entries: list[dict[str, Any]]) -> None:
"""Expose external agent settings for the dashboard settings panel.
Args:
entries: List of setting descriptors (id, label, type, value, etc.).
Example::
import praisonaiui as aiui
aiui.set_settings([{"id": "model", "label": "Model", "value": "gpt-4o-mini"}])
"""
global _agent_settings
_agent_settings = list(entries)
def set_dashboard(
*,
sidebar: bool = True,
page_header: bool = True,
modules: list[str] | None = None,
plugin_dirs: list[str] | None = None,
) -> None:
"""Configure dashboard layout options.
@@ -353,23 +374,81 @@ def set_dashboard(
Args:
sidebar: Show the left sidebar navigation (default True).
page_header: Show the page title/description header (default True).
modules: Optional JS modules to load (``jobs``, ``auth``, ``api``).
plugin_dirs: Extra paths to scan for ``dashboard-plugins`` manifests.
Example::
import praisonaiui as aiui
aiui.set_style("dashboard")
aiui.set_dashboard(sidebar=False) # Chat-only dashboard, no sidebar
aiui.set_dashboard(modules=["jobs", "auth"])
"""
global _dashboard_config
_dashboard_config = {
"sidebar": sidebar,
"pageHeader": page_header,
"modules": list(modules) if modules else [],
"pluginDirs": list(plugin_dirs) if plugin_dirs else [],
}
_jobs_api_config: dict[str, str] = {"apiBase": "/api/jobs", "backend": "aiui"}
def set_jobs_api(*, api_base: str = "/api/jobs", backend: str = "aiui") -> None:
"""Configure dashboard jobs view API paths."""
global _jobs_api_config
_jobs_api_config = {
"apiBase": api_base.rstrip("/") or "/api/jobs",
"backend": backend,
}
def set_jobs_backend(name: str) -> None:
"""Shortcut: ``praisonai`` → ``/api/v1/runs``, ``aiui`` → ``/api/jobs``."""
if name == "praisonai":
set_jobs_api(api_base="/api/v1/runs", backend="praisonai")
else:
set_jobs_api(api_base="/api/jobs", backend="aiui")
# Chat mode config set via aiui.set_chat_mode()
_chat_mode: Optional[dict[str, Any]] = None
# Chat + canvas split preview (opt-in via chat-canvas page)
_chat_preview: Optional[dict[str, Any]] = None
def set_chat_preview(
enabled: bool = True,
*,
surface_id: str = "main",
width: str = "40%",
) -> None:
"""Configure same-window chat + canvas split preview.
When enabled, use the ``chat-canvas`` dashboard page (chat left, live
A2UI surface right). The vanilla ``chat`` page is unchanged.
Args:
enabled: Expose preview settings in ``/ui-config.json``.
surface_id: Default A2UI surface id for the preview panel.
width: CSS width of the preview column (e.g. ``"40%"``, ``"420px"``).
Example::
import praisonaiui as aiui
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas"])
"""
global _chat_preview
_chat_preview = {
"enabled": enabled,
"surfaceId": surface_id,
"width": width,
}
def set_chat_mode(
mode: str = "fullpage",
@@ -702,9 +781,11 @@ async def _plugins_config(request: Request) -> JSONResponse:
# Base plugins (always loaded)
plugins: list[str] = ["fetch-retry"]
if style == "dashboard":
plugins += [
"dashboard",
]
plugins.append("dashboard")
dash_cfg = _dashboard_config or {}
for mod in dash_cfg.get("modules") or []:
if mod in ("jobs", "auth", "api") and mod not in plugins:
plugins.append(mod)
elif style == "docs":
plugins += [
"topnav",
@@ -845,13 +926,39 @@ async def health(request: Request) -> JSONResponse:
provider_info.update(provider_health)
except Exception:
pass
return JSONResponse(
{
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
"provider": provider_info,
}
)
sdk_gaps: list = []
integrated_flag: Optional[bool] = None
try:
from praisonaiui.backends import is_integrated_mode
from praisonaiui.features import get_features
integrated_flag = is_integrated_mode()
for name, feat in get_features().items():
try:
fh = await feat.health()
if fh.get("sdk_gap"):
sdk_gaps.append(
{
"feature": name,
"message": fh.get("sdk_gap_message", "SDK gap"),
}
)
except Exception:
continue
except Exception:
pass
payload: Dict[str, Any] = {
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
"provider": provider_info,
}
if sdk_gaps:
payload["sdk_gaps"] = sdk_gaps
if integrated_flag is not None:
payload["integrated"] = integrated_flag
return JSONResponse(payload)
async def list_agents(request: Request) -> JSONResponse:
@@ -1135,6 +1242,14 @@ async def api_overview(request: Request) -> JSONResponse:
pass
break
try:
from praisonaiui.features.usage import get_usage_aggregates
usage_stats = get_usage_aggregates()
total_requests = int(usage_stats.get("total_requests", 0))
except Exception:
total_requests = _usage_stats["total_requests"]
return JSONResponse(
{
"status": "ok",
@@ -1151,7 +1266,7 @@ async def api_overview(request: Request) -> JSONResponse:
"active_tasks": len(_active_tasks),
"registered_agents": len(_agents),
"registered_profiles": len(profiles),
"total_requests": _usage_stats["total_requests"],
"total_requests": total_requests,
},
"agents": list(_agents.keys()),
"config": {
@@ -1208,9 +1323,17 @@ async def api_logs(request: Request) -> JSONResponse:
async def api_usage(request: Request) -> JSONResponse:
"""Return usage statistics."""
usage_data: dict[str, Any]
try:
from praisonaiui.features.usage import get_usage_aggregates
usage_data = get_usage_aggregates()
except Exception:
usage_data = _usage_stats
return JSONResponse(
{
"usage": _usage_stats,
"usage": usage_data,
"sessions": {
"total": len(await _datastore.list_sessions()),
"active": len(_active_tasks),
@@ -1358,8 +1481,17 @@ async def api_action_click(request: Request) -> JSONResponse:
async def api_pages(request: Request) -> JSONResponse:
"""List registered dashboard pages (protocol-driven)."""
# Sort by order within each group
pages = sorted(_pages.values(), key=lambda p: (p.get("order", 100), p["id"]))
from praisonaiui.dashboard_plugins import discover_dashboard_plugins
pages = list(_pages.values())
extra_dirs = (_dashboard_config or {}).get("pluginDirs") or []
known_ids = {p["id"] for p in pages}
for manifest in discover_dashboard_plugins(extra_dirs):
page = manifest.get("page")
if page and page["id"] not in known_ids:
pages.append(page)
known_ids.add(page["id"])
pages = sorted(pages, key=lambda p: (p.get("order", 100), p["id"]))
return JSONResponse({"pages": pages})
@@ -1391,6 +1523,33 @@ async def api_features(request: Request) -> JSONResponse:
return JSONResponse({"features": list(infos), "count": len(infos)})
def _mcp_stdio_connect_allowed(request: Request) -> bool:
"""Allow stdio MCP only from localhost, with auth, or explicit dev override."""
if os.environ.get("PRAISONAIUI_ALLOW_REMOTE_MCP_STDIO", "").lower() in ("true", "1", "yes"):
return True
client = request.client
if client and client.host in ("127.0.0.1", "::1", "localhost"):
return True
auth = request.headers.get("Authorization", "")
if auth.startswith("Bearer "):
token = auth[7:]
try:
from praisonaiui.features.auth import verify_api_key
if verify_api_key(token):
return True
except ImportError:
pass
try:
from praisonaiui.auth import validate_token
if validate_token(token):
return True
except ImportError:
pass
return False
async def api_mcp_servers(request: Request) -> JSONResponse:
"""GET /api/mcp/servers — List all MCP servers with their status."""
try:
@@ -1441,7 +1600,16 @@ async def api_mcp_connect(request: Request) -> JSONResponse:
{"error": "Either 'command' (for stdio) or 'url' (for SSE/HTTP) is required"},
status_code=400,
)
server = await connect_mcp_server(body)
if "command" in body and not _mcp_stdio_connect_allowed(request):
return JSONResponse(
{
"error": "Remote stdio MCP connections are not permitted",
"hint": "Use localhost, provide Authorization, or set PRAISONAIUI_ALLOW_REMOTE_MCP_STDIO=true",
},
status_code=403,
)
session_id = body.get("session_id") or request.query_params.get("session_id")
server = await connect_mcp_server(body, session_context={"session_id": session_id})
server_data = {
"name": server.name,
"transport": server.transport.value,
@@ -1472,7 +1640,8 @@ async def api_mcp_disconnect(request: Request) -> JSONResponse:
from praisonaiui.features.mcp import disconnect_mcp_server
server_id = request.path_params["server_id"]
success = await disconnect_mcp_server(server_id)
session_id = request.query_params.get("session_id")
success = await disconnect_mcp_server(server_id, session_context={"session_id": session_id})
if success:
return JSONResponse({"success": True, "message": f"Disconnected from {server_id}"})
return JSONResponse({"error": f"Server '{server_id}' not found"}, status_code=404)
@@ -1809,16 +1978,16 @@ def _get_version() -> str:
def track_usage(session_id: str = "unknown", model: str = "unknown", tokens: int = 0):
"""Track token usage stats. Called from callbacks."""
_usage_stats["total_requests"] += 1
_usage_stats["total_tokens"] += tokens
if model not in _usage_stats["by_model"]:
_usage_stats["by_model"][model] = {"requests": 0, "tokens": 0}
_usage_stats["by_model"][model]["requests"] += 1
_usage_stats["by_model"][model]["tokens"] += tokens
if session_id not in _usage_stats["by_session"]:
_usage_stats["by_session"][session_id] = {"requests": 0, "tokens": 0}
_usage_stats["by_session"][session_id]["requests"] += 1
_usage_stats["by_session"][session_id]["tokens"] += tokens
from praisonaiui.features.usage import track_usage as track_feature_usage
input_tokens = int(tokens) if int(tokens) > 0 else 0
track_feature_usage(
model=model or "unknown",
input_tokens=input_tokens,
output_tokens=0,
session_id=session_id or "unknown",
agent_name="unknown",
)
class LogBufferHandler(logging.Handler):
@@ -2000,6 +2169,7 @@ async def run_agent(request: Request, body: dict = None) -> StreamingResponse:
tool_step_counter = 0
seen_tool_ids: set = set()
collected_tool_calls: list = [] # Collect for persistence
collected_elements: list = []
# Inject knowledge context if available (non-blocking)
augmented_message = message
@@ -2040,6 +2210,16 @@ async def run_agent(request: Request, body: dict = None) -> StreamingResponse:
# this matches what finalizeDelta() shows in the live view.
if run_event.content:
full_response = run_event.content
elif run_event.type == RunEventType.MESSAGE_ELEMENT:
extra = run_event.extra_data or {}
element = extra.get("element")
if isinstance(element, dict):
try:
from praisonaiui.features.chat import _collect_element
_collect_element(collected_elements, element)
except ImportError:
collected_elements.append(element)
# Enrich tool call events with description/icon/step_number
if run_event.type in (
@@ -2070,6 +2250,29 @@ async def run_agent(request: Request, body: dict = None) -> StreamingResponse:
payload.setdefault("icon", "🔧")
payload.setdefault("description", f"🔧 Using {name}")
payload.setdefault("step_number", tool_step_counter)
extra = run_event.extra_data or {}
if extra.get("a2ui"):
payload["a2ui"] = extra["a2ui"]
payload["surface_id"] = extra.get("surface_id", "main")
if is_completed:
try:
from praisonaiui.features.surfaces import ingest_a2ui_extra
await ingest_a2ui_extra(extra, session_id=session_id)
except ImportError:
pass
if extra.get("elements"):
payload["elements"] = extra["elements"]
try:
from praisonaiui.features.chat import _collect_element
for el in extra["elements"]:
if isinstance(el, dict):
_collect_element(collected_elements, el)
except ImportError:
for el in extra["elements"]:
if isinstance(el, dict):
collected_elements.append(el)
collected_tool_calls.append(payload)
yield f"data: {json.dumps(payload)}\n\n"
else:
@@ -2092,14 +2295,16 @@ async def run_agent(request: Request, body: dict = None) -> StreamingResponse:
yield f"data: {json.dumps({'type': 'error', 'error': str(e)})}\n\n"
# Save assistant response to session (include tool calls for persistence)
if full_response:
if full_response or collected_elements:
msg_data: dict = {
"role": "assistant",
"content": full_response,
"content": full_response or "",
"timestamp": datetime.now(timezone.utc).isoformat(),
}
if collected_tool_calls:
msg_data["toolCalls"] = collected_tool_calls
if collected_elements:
msg_data["elements"] = collected_elements
await _datastore.add_message(session_id, msg_data)
# Auto-store conversation turn in memory (non-blocking)
@@ -2555,16 +2760,25 @@ def create_app(
_dash_section = _cs.get_section("dashboard") if _cs else None
if _dash_section and isinstance(_dash_section, dict):
_dash_cfg = {
"sidebar": _dash_section.get("sidebar", _dash_section.get("sidebar", True)),
"sidebar": _dash_section.get("sidebar", True),
"pageHeader": _dash_section.get(
"pageHeader", _dash_section.get("page_header", True)
),
"modules": _dash_section.get("modules") or [],
"pluginDirs": _dash_section.get("pluginDirs")
or _dash_section.get("plugin_dirs")
or [],
}
except Exception:
pass
# Chat mode config
_chat_mode_cfg = _chat_mode or {"mode": "fullpage"}
_chat_preview_cfg = _chat_preview or {
"enabled": False,
"surfaceId": "main",
"width": "40%",
}
# Brand color
_brand = _brand_color
@@ -2588,6 +2802,7 @@ def create_app(
"chat": {
"enabled": effective_style in ("chat", "agents", "playground", "dashboard"),
"mode": _chat_mode_cfg,
"preview": _chat_preview_cfg,
**(
{
"features": _chat_features,
@@ -2603,6 +2818,14 @@ def create_app(
if _dash_cfg
else {}
),
**(
{
"settings": _agent_settings,
}
if _agent_settings
else {}
),
"jobs": dict(_jobs_api_config),
"debug": _debug,
}
)
@@ -2696,6 +2919,14 @@ def create_app(
Route("/route-manifest.json", _route_manifest_json, methods=["GET"]),
]
from praisonaiui.dashboard_plugins import make_dashboard_plugin_routes
def _dashboard_plugin_dirs() -> list[str]:
cfg = _dashboard_config or {}
return list(cfg.get("pluginDirs") or cfg.get("plugin_dirs") or [])
routes.extend(make_dashboard_plugin_routes(_dashboard_plugin_dirs))
# ── Gap S1: Auto-initialize gateway in standalone mode ──────────
# When running standalone (not via AIUIGateway.start()), we need to
# initialize a gateway so that gateway-dependent features work.
@@ -2797,6 +3028,22 @@ def create_app(
"description": "AI agent chat",
"order": 1,
},
{
"id": "chat-canvas",
"title": "Chat + Canvas",
"icon": "💬🖼️",
"group": "Agent",
"description": "Chat with live A2UI preview panel",
"order": 2,
},
{
"id": "canvas",
"title": "Canvas",
"icon": "🖼️",
"group": "Agent",
"description": "Live A2UI surface workspace",
"order": 6,
},
{
"id": "channels",
"title": "Channels",
@@ -2845,6 +3092,22 @@ def create_app(
"description": "Async agent jobs",
"order": 40,
},
{
"id": "jobs-board",
"title": "Jobs Board",
"icon": "📊",
"group": "Work",
"description": "Jobs kanban by status",
"order": 11,
},
{
"id": "kanban",
"title": "Kanban",
"icon": "📌",
"group": "Work",
"description": "Multi-agent task board",
"order": 10,
},
{
"id": "approvals",
"title": "Approvals",
@@ -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-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-orange-400:oklch(75% .183 55.934);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-500:oklch(79.5% .184 86.047);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--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-400:oklch(70.7% .022 261.325);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-xs:.125rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--blur-xl:24px;--aspect-video:16 / 9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-border:var(--border)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif,Line 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-400:oklch(75% .183 55.934);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--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;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;-Line truncated
@@ -1,58 +1,107 @@
function t_(e,a){for(var r=0;r<a.length;r++){const o=a[r];if(typeof o!="string"&&!Array.isArray(o)){for(const s in o)if(s!=="default"&&!(s in e)){const u=Object.getOwnPropertyDescriptor(o,s);u&&Object.defineProperty(e,s,u.get?u:{enumerable:!0,get:()=>o[s]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const a=document.createElement("link").relList;if(a&&a.supports&&a.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const u of s)if(u.type==="childList")for(const c of u.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&o(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const u={};return s.integrity&&(u.integrity=s.integrity),s.referrerPolicy&&(u.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?u.credentials="include":s.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function o(s){if(s.ep)return;s.ep=!0;const u=r(s);fetch(s.href,u)}})();function Gd(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Qc={exports:{}},no={};var sv;function n_(){if(sv)return no;sv=1;var e=Symbol.for("react.transitional.element"),a=Symbol.for("react.fragment");function r(o,s,u){var c=null;if(u!==void 0&&(c=""+u),s.key!==void 0&&(c=""+s.key),"key"in s){u={};for(var p in s)p!=="key"&&(u[p]=s[p])}else u=s;return s=u.ref,{$$typeof:e,type:o,key:c,ref:s!==void 0?s:null,props:u}}return no.Fragment=a,no.jsx=r,no.jsxs=r,no}var uv;function a_(){return uv||(uv=1,Qc.exports=n_()),Qc.exports}var y=a_(),Jc={exports:{}},Ae={};var cv;function r_(){if(cv)return Ae;cv=1;var e=Symbol.for("react.transitional.element"),a=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),u=Symbol.for("react.consumer"),c=Symbol.for("react.context"),p=Symbol.for("react.forward_ref"),g=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),b=Symbol.for("react.lazy"),h=Symbol.for("react.activity"),v=Symbol.iterator;function S(O){return O===null||typeof O!="object"?null:(O=v&&O[v]||O["@@iterator"],typeof O=="function"?O:null)}var A={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,C={};function _(O,V,k){this.props=O,this.context=V,this.refs=C,this.updater=k||A}_.prototype.isReactComponent={},_.prototype.setState=function(O,V){if(typeof O!="object"&&typeof O!="function"&&O!=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,O,V,"setState")},_.prototype.forceUpdate=function(O){this.updater.enqueueForceUpdate(this,O,"forceUpdate")};function U(){}U.prototype=_.prototype;function M(O,V,k){this.props=O,this.context=V,this.refs=C,this.updater=k||A}var $=M.prototype=new U;$.constructor=M,N($,_.prototype),$.isPureReactComponent=!0;var W=Array.isArray;function I(){}var Y={H:null,A:null,T:null,S:null},P=Object.prototype.hasOwnProperty;function te(O,V,k){var se=k.ref;return{$$typeof:e,type:O,key:V,ref:se!==void 0?se:null,props:k}}function D(O,V){return te(O.type,V,O.props)}function J(O){return typeof O=="object"&&O!==null&&O.$$typeof===e}function ae(O){var V={"=":"=0",":":"=2"};return"$"+O.replace(/[=:]/g,function(k){return V[k]})}var oe=/\/+/g;function re(O,V){return typeof O=="object"&&O!==null&&O.key!=null?ae(""+O.key):V.toString(36)}function ne(O){switch(O.status){case"fulfilled":return O.value;case"rejected":throw O.reason;default:switch(typeof O.status=="string"?O.then(I,I):(O.status="pending",O.then(function(V){O.status==="pending"&&(O.status="fulfilled",O.value=V)},function(V){O.status==="pending"&&(O.status="rejected",O.reason=V)})),O.status){case"fulfilled":return O.value;case"rejected":throw O.reason}}throw O}function F(O,V,k,se,de){var ce=typeof O;(ce==="undefined"||ce==="boolean")&&(O=null);var Te=!1;if(O===null)Te=!0;else switch(ce){case"bigint":case"string":case"number":Te=!0;break;case"object":switch(O.$$typeof){case e:case a:Te=!0;break;case b:return Te=O._init,F(Te(O._payload),V,k,se,de)}}if(Te)return de=de(O),Te=se===""?"."+re(O,0):se,W(de)?(k="",Te!=null&&(k=Te.replace(oe,"$&/")+"/"),F(de,V,k,"",function(mt){return mt})):de!=null&&(J(de)&&(de=D(de,k+(de.key==null||O&&O.key===de.key?"":(""+de.key).replace(oe,"$&/")+"/")+Te)),V.push(de)),1;Te=0;var ze=se===""?".":se+":";if(W(O))for(var Se=0;Se<O.length;Se++)se=O[Se],ce=ze+re(se,Se),Te+=F(se,V,k,ce,de);else if(Se=S(O),typeof Se=="function")for(O=Se.call(O),Se=0;!(se=O.next()).done;)se=se.value,ce=ze+re(se,Se++),Te+=F(se,V,k,ce,de);else if(ce==="object"){if(typeof O.then=="function")return F(ne(O),V,k,se,de);throw V=String(O),Error("Objects are not valid as a React child (found: "+(V==="[object Object]"?"object with keys {"+Object.keys(O).join(", ")+"}":V)+"). If you meant to render a collection of children, Line truncated
at`)?" (<anonymous>)":-1<i.stack.indexOf("@")?"@unknown:0:0":""}return`
`+yn+t+Zn}var rr=!1;function ir(t,n){if(!t||rr)return"";rr=!0;var i=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var l={DetermineComponentFrameRoot:function(){try{if(n){var ee=function(){throw Error()};if(Object.defineProperty(ee.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ee,[])}catch(q){var H=q}Reflect.construct(t,[],ee)}else{try{ee.call()}catch(q){H=q}t.call(ee.prototype)}}else{try{throw Error()}catch(q){H=q}(ee=t())&&typeof ee.catch=="function"&&ee.catch(function(){})}}catch(q){if(q&&H&&typeof q.stack=="string")return[q.stack,H.stack]}return[null,null]}};l.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var d=Object.getOwnPropertyDescriptor(l.DetermineComponentFrameRoot,"name");d&&d.configurable&&Object.defineProperty(l.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var m=l.DetermineComponentFrameRoot(),E=m[0],T=m[1];if(E&&T){var R=E.split(`
`),G=T.split(`
`);for(d=l=0;l<R.length&&!R[l].includes("DetermineComponentFrameRoot");)l++;for(;d<G.length&&!G[d].includes("DetermineComponentFrameRoot");)d++;if(l===R.length||d===G.length)for(l=R.length-1,d=G.length-1;1<=l&&0<=d&&R[l]!==G[d];)d--;for(;1<=l&&0<=d;l--,d--)if(R[l]!==G[d]){if(l!==1||d!==1)do if(l--,d--,0>d||R[l]!==G[d]){var X=`
`+R[l].replace(" at new "," at ");return t.displayName&&X.includes("<anonymous>")&&(X=X.replace("<anonymous>",t.displayName)),X}while(1<=l&&0<=d);break}}}finally{rr=!1,Error.prepareStackTrace=i}return(i=t?t.displayName||t.name:"")?It(i):""}function Io(t,n){switch(t.tag){case 26:case 27:case 5:return It(t.type);case 16:return It("Lazy");case 13:return t.child!==n&&n!==null?It("Suspense Fallback"):It("Suspense");case 19:return It("SuspenseList");case 0:case 15:return ir(t.type,!1);case 11:return ir(t.type.render,!1);case 1:return ir(t.type,!0);case 31:return It("Activity");default:return""}}function Lo(t){try{var n="",i=null;do n+=Io(t,i),i=t,t=t.return;while(t);return n}catch(l){return`
Error generating stack: `+l.message+`
`+l.stack}}var or=Object.prototype.hasOwnProperty,lr=e.unstable_scheduleCallback,ci=e.unstable_cancelCallback,Bs=e.unstable_shouldYield,Fs=e.unstable_requestPaint,Nt=e.unstable_now,zs=e.unstable_getCurrentPriorityLevel,Z=e.unstable_ImmediatePriority,le=e.unstable_UserBlockingPriority,ve=e.unstable_NormalPriority,Ne=e.unstable_LowPriority,Ge=e.unstable_IdlePriority,Pt=e.log,_n=e.unstable_setDisableYieldValue,Rt=null,ut=null;function Lt(t){if(typeof Pt=="function"&&_n(t),ut&&typeof ut.setStrictMode=="function")try{ut.setStrictMode(Rt,t)}catch{}}var Ye=Math.clz32?Math.clz32:Gw,Kn=Math.log,pn=Math.LN2;function Gw(t){return t>>>=0,t===0?32:31-(Kn(t)/pn|0)|0}var Do=256,Mo=262144,Uo=4194304;function Na(t){var n=t&42;if(n!==0)return n;switch(t&-t){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 t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Bo(t,n,i){var l=t.pendingLanes;if(l===0)return 0;var d=0,m=t.suspendedLanes,E=t.pingedLanes;t=t.warmLanes;var T=l&134217727;return T!==0?(l=T&~m,l!==0?d=Na(l):(E&=T,E!==0?d=Na(E):i||(i=T&~t,i!==0&&(d=Na(i))))):(T=l&~m,T!==0?d=Na(T):E!==0?d=Na(E):i||(i=l&~t,i!==0&&(d=Na(i)))),d===0?0:n!==0&&n!==d&&(n&m)===0&&(m=d&-d,i=n&-n,m>=i||m===32&&(i&4194048)!==0)?n:d}function di(t,n){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&n)===0}function Hw(t,n){switch(t){case 1:case 2:case 4:case 8:case 64:return n+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 n+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 ph(){var t=Uo;return Uo<<=1,(Uo&62914560)===0&&(Uo=4194304),t}function js(t){for(var n=[],i=0;31>i;i++)n.push(t);return n}function pi(t,n){t.pendingLanes|=n,n!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Pw(t,n,i,l,d,m){var E=t.pendingLanes;t.pendingLanes=i,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=i,t.entangledLanes&=i,t.errorRecoveryDisabledLanes&=i,t.shellSuspendCounter=0;var T=t.entanglements,R=t.expirationTimes,G=t.hiddenUpdates;for(i=E&~i;0<i;){var X=31-Ye(i),ee=1<<X;T[X]=0,R[X]=-1;var H=G[X];if(H!==null)for(G[X]=null,X=0;X<H.length;X++){var q=H[X];q!==null&&(q.lane&=-536870913)}i&=~ee}l!==0&&fh(t,l,0),m!==0&&d===0&&t.tag!==0&&(t.suspendedLanes|=m&~(E&~n))}function fh(t,n,i){t.pendingLanes|=n,t.suspendedLanes&=~n;var l=31-Ye(n);t.entangledLanes|=n,t.entanglements[l]=t.entanglements[l]|1073741824|i&261930}function gh(t,n){var i=t.entangledLanes|=n;for(t=t.entanglements;i;){var l=31-Ye(i),d=1<<l;d&n|t[l]&n&&(t[l]|=n),i&=~d}}function mh(t,n){var i=n&-n;return i=(i&42)!==0?1:Gs(i),(i&(t.suspendedLanes|n))!==0?0:i}function Gs(t){switch(t){case 2:t=1;break;case 8:t=4;break;case 32:t=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:t=128;break;case 268435456:t=134217728;break;default:t=0}return t}function Hs(t){return t&=-t,2<t?8<t?(t&134217727)!==0?32:268435456:8:2}function bh(){var t=K.p;return t!==0?t:(t=window.event,t===void 0?32:tv(t.type))}function hh(t,n){var i=K.p;try{return K.p=t,n()}finally{K.p=i}}var Qn=Math.random().toString(36).slice(2),bt="__reactFiber$"+Qn,Dt="__reactProps$"+Qn,sr="__reactContainer$"+Qn,Ps="__reactEvents$"+Qn,$w="__reactListeners$"+Qn,qw="__reactHandles$"+Qn,yh="__reactResources$"+Qn,fi="__reactMarker$"+Qn;function $s(t){delete t[bt],delete t[Dt],delete t[Ps],delete t[$w],delete t[qw]}function ur(t){var n=t[bt];if(n)return n;for(var i=t.parentNode;i;){if(n=i[sr]||i[bt]){if(i=n.alternate,n.child!==null||i!==null&&i.child!==null)for(t=zS(t);t!==null;){if(i=t[bt])return i;t=zS(t)}return n}t=i,i=t.parentNode}return null}function cr(t){if(t=t[bt]||t[sr]){var n=t.tag;if(n===5||n===6||n===13||n===31||n===26||n===27||n===3)return t}return null}function gi(t){var n=t.tag;if(n===5||n===26||n===27||n===6)return t.stateNode;throw Error(o(33))}function dr(t){var n=t[yh];return n||(n=t[yh]={hoistableStyles:new Map,hoistableScripts:new Map}),n}function pt(t){t[fi]=!0}var Eh=new Set,Sh={};function Ra(t,n){pr(t,n),pr(t+"Capture",n)}function pr(t,n){for(Sh[t]=n,t=0;t<n.length;t++)Eh.add(n[t])}var Vw=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u2 Line truncated
`).replace(Ak,"")}function RS(t,n){return n=NS(n),NS(t)===n}function qe(t,n,i,l,d,m){switch(i){case"children":typeof l=="string"?n==="body"||n==="textarea"&&l===""||gr(t,l):(typeof l=="number"||typeof l=="bigint")&&n!=="body"&&gr(t,""+l);break;case"className":zo(t,"class",l);break;case"tabIndex":zo(t,"tabindex",l);break;case"dir":case"role":case"viewBox":case"width":case"height":zo(t,i,l);break;case"style":Rh(t,l,m);break;case"data":if(n!=="object"){zo(t,"data",l);break}case"src":case"href":if(l===""&&(n!=="a"||i!=="href")){t.removeAttribute(i);break}if(l==null||typeof l=="function"||typeof l=="symbol"||typeof l=="boolean"){t.removeAttribute(i);break}l=Go(""+l),t.setAttribute(i,l);break;case"action":case"formAction":if(typeof l=="function"){t.setAttribute(i,"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 m=="function"&&(i==="formAction"?(n!=="input"&&qe(t,n,"name",d.name,d,null),qe(t,n,"formEncType",d.formEncType,d,null),qe(t,n,"formMethod",d.formMethod,d,null),qe(t,n,"formTarget",d.formTarget,d,null)):(qe(t,n,"encType",d.encType,d,null),qe(t,n,"method",d.method,d,null),qe(t,n,"target",d.target,d,null)));if(l==null||typeof l=="symbol"||typeof l=="boolean"){t.removeAttribute(i);break}l=Go(""+l),t.setAttribute(i,l);break;case"onClick":l!=null&&(t.onclick=Rn);break;case"onScroll":l!=null&&Ce("scroll",t);break;case"onScrollEnd":l!=null&&Ce("scrollend",t);break;case"dangerouslySetInnerHTML":if(l!=null){if(typeof l!="object"||!("__html"in l))throw Error(o(61));if(i=l.__html,i!=null){if(d.children!=null)throw Error(o(60));t.innerHTML=i}}break;case"multiple":t.multiple=l&&typeof l!="function"&&typeof l!="symbol";break;case"muted":t.muted=l&&typeof l!="function"&&typeof l!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(l==null||typeof l=="function"||typeof l=="boolean"||typeof l=="symbol"){t.removeAttribute("xlink:href");break}i=Go(""+l),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",i);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":l!=null&&typeof l!="function"&&typeof l!="symbol"?t.setAttribute(i,""+l):t.removeAttribute(i);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":l&&typeof l!="function"&&typeof l!="symbol"?t.setAttribute(i,""):t.removeAttribute(i);break;case"capture":case"download":l===!0?t.setAttribute(i,""):l!==!1&&l!=null&&typeof l!="function"&&typeof l!="symbol"?t.setAttribute(i,l):t.removeAttribute(i);break;case"cols":case"rows":case"size":case"span":l!=null&&typeof l!="function"&&typeof l!="symbol"&&!isNaN(l)&&1<=l?t.setAttribute(i,l):t.removeAttribute(i);break;case"rowSpan":case"start":l==null||typeof l=="function"||typeof l=="symbol"||isNaN(l)?t.removeAttribute(i):t.setAttribute(i,l);break;case"popover":Ce("beforetoggle",t),Ce("toggle",t),Fo(t,"popover",l);break;case"xlinkActuate":Nn(t,"http://www.w3.org/1999/xlink","xlink:actuate",l);break;case"xlinkArcrole":Nn(t,"http://www.w3.org/1999/xlink","xlink:arcrole",l);break;case"xlinkRole":Nn(t,"http://www.w3.org/1999/xlink","xlink:role",l);break;case"xlinkShow":Nn(t,"http://www.w3.org/1999/xlink","xlink:show",l);break;case"xlinkTitle":Nn(t,"http://www.w3.org/1999/xlink","xlink:title",l);break;case"xlinkType":Nn(t,"http://www.w3.org/1999/xlink","xlink:type",l);break;case"xmlBase":Nn(t,"http://www.w3.org/XML/1998/namespace","xml:base",l);break;case"xmlLang":Nn(t,"http://www.w3.org/XML/1998/namespace","xml:lang",l);break;case"xmlSpace":Nn(t,"http://www.w3.org/XML/1998/namespace","xml:space",l);break;case"is":Fo(t,"is",l);break;case"innerText":case"textContent":break;default:(!(2<i.length)||i[0]!=="o"&&i[0]!=="O"||i[1]!=="n"&&i[1]!=="N")&&(i=Kw.get(i)||i,Fo(t,i,l))}}function Dc(t,n,i,l,d,m){switch(i){case"style":Rh(t,l,m);break;case"dangerouslySetInnerHTML":if(l!=null){if(typeof l!="object"||!("__html"in l))throw Error(o(61));if(i=l.__html,i!=null){if(d.children!=null)throw Error(o(60));t.innerHTML=i}}break;case"children":typeof l=="string"?gr(t,l):(typeof l=="number"||typeof l=="bigint")&&gr(t,""+l);break;case"onScroll":l!=null&&Ce("scroll",t);break;case"onScrollEnd":l!=null&&Ce("scrollend",t);break;case"onClick":l!=null&&(t.onclick=Rn);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
`,f="/",b="*",h="",v="comment",S="declaration";function A(C,_){if(typeof C!="string")throw new TypeError("First argument must be a string");if(!C)return[];_=_||{};var U=1,M=1;function $(re){var ne=re.match(a);ne&&(U+=ne.length);var F=re.lastIndexOf(g);M=~F?re.length-F:M+re.length}function W(){var re={line:U,column:M};return function(ne){return ne.position=new I(re),te(),ne}}function I(re){this.start=re,this.end={line:U,column:M},this.source=_.source}I.prototype.content=C;function Y(re){var ne=new Error(_.source+":"+U+":"+M+": "+re);if(ne.reason=re,ne.filename=_.source,ne.line=U,ne.column=M,ne.source=C,!_.silent)throw ne}function P(re){var ne=re.exec(C);if(ne){var F=ne[0];return $(F),C=C.slice(F.length),ne}}function te(){P(r)}function D(re){var ne;for(re=re||[];ne=J();)ne!==!1&&re.push(ne);return re}function J(){var re=W();if(!(f!=C.charAt(0)||b!=C.charAt(1))){for(var ne=2;h!=C.charAt(ne)&&(b!=C.charAt(ne)||f!=C.charAt(ne+1));)++ne;if(ne+=2,h===C.charAt(ne-1))return Y("End of comment missing");var F=C.slice(2,ne-2);return M+=2,$(F),C=C.slice(ne),M+=2,re({type:v,comment:F})}}function ae(){var re=W(),ne=P(o);if(ne){if(J(),!P(s))return Y("property missing ':'");var F=P(u),K=re({type:S,property:N(ne[0].replace(e,h)),value:F?N(F[0].replace(e,h)):h});return P(c),K}}function oe(){var re=[];D(re);for(var ne;ne=ae();)ne!==!1&&(re.push(ne),D(re));return re}return te(),oe()}function N(C){return C?C.replace(p,h):h}return od=A,od}var Mv;function pR(){if(Mv)return Vr;Mv=1;var e=Vr&&Vr.__importDefault||function(o){return o&&o.__esModule?o:{default:o}};Object.defineProperty(Vr,"__esModule",{value:!0}),Vr.default=r;const a=e(dR());function r(o,s){let u=null;if(!o||typeof o!="string")return u;const c=(0,a.default)(o),p=typeof s=="function";return c.forEach(g=>{if(g.type!=="declaration")return;const{property:f,value:b}=g;p?s(f,b,g):b&&(u=u||{},u[f]=b)}),u}return Vr}var io={},Uv;function fR(){if(Uv)return io;Uv=1,Object.defineProperty(io,"__esModule",{value:!0}),io.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,a=/-([a-z])/g,r=/^[^-]+$/,o=/^-(webkit|moz|ms|o|khtml)-/,s=/^-(ms)-/,u=function(f){return!f||r.test(f)||e.test(f)},c=function(f,b){return b.toUpperCase()},p=function(f,b){return"".concat(b,"-")},g=function(f,b){return b===void 0&&(b={}),u(f)?f:(f=f.toLowerCase(),b.reactCompat?f=f.replace(s,p):f=f.replace(o,p),f.replace(a,c))};return io.camelCase=g,io}var oo,Bv;function gR(){if(Bv)return oo;Bv=1;var e=oo&&oo.__importDefault||function(s){return s&&s.__esModule?s:{default:s}},a=e(pR()),r=fR();function o(s,u){var c={};return!s||typeof s!="string"||(0,a.default)(s,function(p,g){p&&g&&(c[(0,r.camelCase)(p,u)]=g)}),c}return o.default=o,oo=o,oo}var mR=gR();const bR=Gd(mR),AA=wA("end"),Zd=wA("start");function wA(e){return a;function a(r){const o=r&&r.position&&r.position[e]||{};if(typeof o.line=="number"&&o.line>0&&typeof o.column=="number"&&o.column>0)return{line:o.line,column:o.column,offset:typeof o.offset=="number"&&o.offset>-1?o.offset:void 0}}}function hR(e){const a=Zd(e),r=AA(e);if(a&&r)return{start:a,end:r}}function co(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?Fv(e.position):"start"in e||"end"in e?Fv(e):"line"in e||"column"in e?_d(e):""}function _d(e){return zv(e&&e.line)+":"+zv(e&&e.column)}function Fv(e){return _d(e&&e.start)+"-"+_d(e&&e.end)}function zv(e){return e&&typeof e=="number"?e:1}class xt extends Error{constructor(a,r,o){super(),typeof r=="string"&&(o=r,r=void 0);let s="",u={},c=!1;if(r&&("line"in r&&"column"in r?u={place:r}:"start"in r&&"end"in r?u={place:r}:"type"in r?u={ancestors:[r],place:r.position}:u={...r}),typeof a=="string"?s=a:!u.cause&&a&&(c=!0,s=a.message,u.cause=a),!u.ruleId&&!u.source&&typeof o=="string"){const g=o.indexOf(":");g===-1?u.ruleId=o:(u.source=o.slice(0,g),u.ruleId=o.slice(g+1))}if(!u.place&&u.ancestors&&u.ancestors){const g=u.ancestors[u.ancestors.length-1];g&&(u.place=g.position)}const p=u.place&&"start"in u.place?u.place.start:u.place;this.ancestors=u.ancestors||void 0,this.cause=u.cause||void 0,this.column=p?p.column:void 0,this.fatal=void 0,this.file="",this.message=s,this.line=p?p.line:void 0,this.name=co(u.place)||"1:1",this.place=u.place||void 0,this.reason=this.message,this.ruleId=u.ruleId||void 0,this.source=u.source||void 0,this.stack=c&&u.cause&&typeof u.cause.stack=="string"?u.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}xt.prototype.file="";xt.prototype.name="";xt.prototype.reason="";xt.prototype.message="";xt.prototype.stack="";xt.prototype.column=void 0;xt.prototype.line=void 0;xt.prototype.ancestors=void 0;xt.prototype.cause=void 0;xt.prototype.fatal=void 0;xt.prototype.place=void 0;xt.prototype.ruleId=void 0;xt.prototype.source=void 0;const Kd={}.hasOwnProperty,yR=new Map,ER=/[A-Z]/g,SR=new Set(["table","tbody","thead","tfoot","tr"]),vR=new Set(["td","th"]),xA="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function TR(e,a){if(!a||a.Fragment===void 0)throw new TypeError("Expected `Fragment` in op Line truncated
`;break}case-3:{c=`\r
`;break}case-2:{c=a?" ":" ";break}case-1:{if(!a&&s)continue;c=" ";break}default:c=String.fromCharCode(u)}s=u===-2,o.push(c)}return o.join("")}function CO(e){const o={constructs:CA([kO,...(e||{}).extensions||[]]),content:s(qR),defined:[],document:s(YR),flow:s(dO),lazy:{},string:s(gO),text:s(mO)};return o;function s(u){return c;function c(p){return _O(o,u,p)}}}function OO(e){for(;!MA(e););return e}const Xv=/[\0\t\n\r]/g;function IO(){let e=1,a="",r=!0,o;return s;function s(u,c,p){const g=[];let f,b,h,v,S;for(u=a+(typeof u=="string"?u.toString():new TextDecoder(c||void 0).decode(u)),h=0,a="",r&&(u.charCodeAt(0)===65279&&h++,r=void 0);h<u.length;){if(Xv.lastIndex=h,f=Xv.exec(u),v=f&&f.index!==void 0?f.index:u.length,S=u.charCodeAt(v),!f){a=u.slice(h);break}if(S===10&&h===v&&o)g.push(-3),o=void 0;else switch(o&&(g.push(-5),o=void 0),h<v&&(g.push(u.slice(h,v)),e+=v-h),S){case 0:{g.push(65533),e++;break}case 9:{for(b=Math.ceil(e/4)*4,g.push(-2);e++<b;)g.push(-1);break}case 10:{g.push(-4),e=1;break}default:o=!0,e=1}h=v+1}return p&&(o&&g.push(-5),a&&g.push(a),g.push(null)),g}}const LO=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function DO(e){return e.replace(LO,MO)}function MO(e,a,r){if(a)return a;if(r.charCodeAt(0)===35){const s=r.charCodeAt(1),u=s===120||s===88;return OA(r.slice(u?2:1),u?16:10)}return yo(r)||e}const GA={}.hasOwnProperty;function UO(e,a,r){return a&&typeof a=="object"&&(r=a,a=void 0),BO(r)(OO(CO(r).document().write(IO()(e,a,!0))))}function BO(e){const a={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:u(or),autolinkProtocol:te,autolinkEmail:te,atxHeading:u(rr),blockQuote:u(mt),characterEscape:te,characterReference:te,codeFenced:u(Tt),codeFencedFenceInfo:c,codeFencedFenceMeta:c,codeIndented:u(Tt,c),codeText:u(yn,c),codeTextData:te,data:te,codeFlowValue:te,definition:u(Zn),definitionDestinationString:c,definitionLabelString:c,definitionTitleString:c,emphasis:u(It),hardBreakEscape:u(ir),hardBreakTrailing:u(ir),htmlFlow:u(Io,c),htmlFlowData:te,htmlText:u(Io,c),htmlTextData:te,image:u(Lo),label:c,link:u(or),listItem:u(ci),listItemValue:v,listOrdered:u(lr,h),listUnordered:u(lr),paragraph:u(Bs),reference:k,referenceString:c,resourceDestinationString:c,resourceTitleString:c,setextHeading:u(rr),strong:u(Fs),thematicBreak:u(zs)},exit:{atxHeading:g(),atxHeadingSequence:W,autolink:g(),autolinkEmail:Se,autolinkProtocol:ze,blockQuote:g(),characterEscapeValue:D,characterReferenceMarkerHexadecimal:de,characterReferenceMarkerNumeric:de,characterReferenceValue:ce,characterReference:Te,codeFenced:g(C),codeFencedFence:N,codeFencedFenceInfo:S,codeFencedFenceMeta:A,codeFlowValue:D,codeIndented:g(_),codeText:g(ne),codeTextData:D,data:D,definition:g(),definitionDestinationString:$,definitionLabelString:U,definitionTitleString:M,emphasis:g(),hardBreakEscape:g(ae),hardBreakTrailing:g(ae),htmlFlow:g(oe),htmlFlowData:D,htmlText:g(re),htmlTextData:D,image:g(K),label:he,labelText:ue,lineEnding:J,link:g(F),listItem:g(),listOrdered:g(),listUnordered:g(),paragraph:g(),referenceString:se,resourceDestinationString:x,resourceTitleString:O,resource:V,setextHeading:g(P),setextHeadingLineSequence:Y,setextHeadingText:I,strong:g(),thematicBreak:g()}};HA(a,(e||{}).mdastExtensions||[]);const r={};return o;function o(Z){let le={type:"root",children:[]};const ve={stack:[le],tokenStack:[],config:a,enter:p,exit:f,buffer:c,resume:b,data:r},Ne=[];let Ge=-1;for(;++Ge<Z.length;)if(Z[Ge][1].type==="listOrdered"||Z[Ge][1].type==="listUnordered")if(Z[Ge][0]==="enter")Ne.push(Ge);else{const Pt=Ne.pop();Ge=s(Z,Pt,Ge)}for(Ge=-1;++Ge<Z.length;){const Pt=a[Z[Ge][0]];GA.call(Pt,Z[Ge][1].type)&&Pt[Z[Ge][1].type].call(Object.assign({sliceSerialize:Z[Ge][2].sliceSerialize},ve),Z[Ge][1])}if(ve.tokenStack.length>0){const Pt=ve.tokenStack[ve.tokenStack.length-1];(Pt[1]||Zv).call(ve,void 0,Pt[0])}for(le.position={start:wa(Z.length>0?Z[0][1].start:{line:1,column:1,offset:0}),end:wa(Z.length>0?Z[Z.length-2][1].end:{line:1,column:1,offset:0})},Ge=-1;++Ge<a.transforms.length;)le=a.transforms[Ge](le)||le;return le}function s(Z,le,ve){let Ne=le-1,Ge=-1,Pt=!1,_n,Rt,ut,Lt;for(;++Ne<=ve;){const Ye=Z[Ne];switch(Ye[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{Ye[0]==="enter"?Ge++:Ge--,Lt=void 0;break}case"lineEndingBlank":{Ye[0]==="enter"&&(_n&&!Lt&&!Ge&&!ut&&(ut=Ne),Lt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Lt=void 0}if(!Ge&&Ye[0]==="enter"&&Ye[1].type==="listItemPrefix"||Ge===-1&&Ye[0]==="exit"&&(Ye[1].type==="listUnordered"||Ye[1].type==="listOrdered")){if(_n){let Kn=Ne;for(Rt=void 0;Kn--;){const pn=Z[Kn];if(pn[1].type==="lineEnding"||pn[1].type==="lineEndingBlank"){if(pn[0]==="exit")continue;Rt&&(Z[Rt][1].type="lineEndingBlank",Pt=!0),pn[1].type="lineEnding",Rt=Kn}else if(!(pn[1].type==="linePrefix"||pn[1].type==="blockQuotePrefix"||pn[1].type==="blockQuotePrefixWhitespace"||pnLine truncated
`}]}function HO(e,a){const r=a.value?a.value+`
`:"",o={},s=a.lang?a.lang.split(/\s+/):[];s.length>0&&(o.className=["language-"+s[0]]);let u={type:"element",tagName:"code",properties:o,children:[{type:"text",value:r}]};return a.meta&&(u.data={meta:a.meta}),e.patch(a,u),u=e.applyData(a,u),u={type:"element",tagName:"pre",properties:{},children:[u]},e.patch(a,u),u}function PO(e,a){const r={type:"element",tagName:"del",properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}function $O(e,a){const r={type:"element",tagName:"em",properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}function qO(e,a){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",o=String(a.identifier).toUpperCase(),s=ti(o.toLowerCase()),u=e.footnoteOrder.indexOf(o);let c,p=e.footnoteCounts.get(o);p===void 0?(p=0,e.footnoteOrder.push(o),c=e.footnoteOrder.length):c=u+1,p+=1,e.footnoteCounts.set(o,p);const g={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+s,id:r+"fnref-"+s+(p>1?"-"+p:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(c)}]};e.patch(a,g);const f={type:"element",tagName:"sup",properties:{},children:[g]};return e.patch(a,f),e.applyData(a,f)}function VO(e,a){const r={type:"element",tagName:"h"+a.depth,properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}function YO(e,a){if(e.options.allowDangerousHtml){const r={type:"raw",value:a.value};return e.patch(a,r),e.applyData(a,r)}}function PA(e,a){const r=a.referenceType;let o="]";if(r==="collapsed"?o+="[]":r==="full"&&(o+="["+(a.label||a.identifier)+"]"),a.type==="imageReference")return[{type:"text",value:"!["+a.alt+o}];const s=e.all(a),u=s[0];u&&u.type==="text"?u.value="["+u.value:s.unshift({type:"text",value:"["});const c=s[s.length-1];return c&&c.type==="text"?c.value+=o:s.push({type:"text",value:o}),s}function WO(e,a){const r=String(a.identifier).toUpperCase(),o=e.definitionById.get(r);if(!o)return PA(e,a);const s={src:ti(o.url||""),alt:a.alt};o.title!==null&&o.title!==void 0&&(s.title=o.title);const u={type:"element",tagName:"img",properties:s,children:[]};return e.patch(a,u),e.applyData(a,u)}function XO(e,a){const r={src:ti(a.url)};a.alt!==null&&a.alt!==void 0&&(r.alt=a.alt),a.title!==null&&a.title!==void 0&&(r.title=a.title);const o={type:"element",tagName:"img",properties:r,children:[]};return e.patch(a,o),e.applyData(a,o)}function ZO(e,a){const r={type:"text",value:a.value.replace(/\r?\n|\r/g," ")};e.patch(a,r);const o={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(a,o),e.applyData(a,o)}function KO(e,a){const r=String(a.identifier).toUpperCase(),o=e.definitionById.get(r);if(!o)return PA(e,a);const s={href:ti(o.url||"")};o.title!==null&&o.title!==void 0&&(s.title=o.title);const u={type:"element",tagName:"a",properties:s,children:e.all(a)};return e.patch(a,u),e.applyData(a,u)}function QO(e,a){const r={href:ti(a.url)};a.title!==null&&a.title!==void 0&&(r.title=a.title);const o={type:"element",tagName:"a",properties:r,children:e.all(a)};return e.patch(a,o),e.applyData(a,o)}function JO(e,a,r){const o=e.all(a),s=r?eI(r):$A(a),u={},c=[];if(typeof a.checked=="boolean"){const b=o[0];let h;b&&b.type==="element"&&b.tagName==="p"?h=b:(h={type:"element",tagName:"p",properties:{},children:[]},o.unshift(h)),h.children.length>0&&h.children.unshift({type:"text",value:" "}),h.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:a.checked,disabled:!0},children:[]}),u.className=["task-list-item"]}let p=-1;for(;++p<o.length;){const b=o[p];(s||p!==0||b.type!=="element"||b.tagName!=="p")&&c.push({type:"text",value:`
`}),b.type==="element"&&b.tagName==="p"&&!s?c.push(...b.children):c.push(b)}const g=o[o.length-1];g&&(s||g.type!=="element"||g.tagName!=="p")&&c.push({type:"text",value:`
`});const f={type:"element",tagName:"li",properties:u,children:c};return e.patch(a,f),e.applyData(a,f)}function eI(e){let a=!1;if(e.type==="list"){a=e.spread||!1;const r=e.children;let o=-1;for(;!a&&++o<r.length;)a=$A(r[o])}return a}function $A(e){const a=e.spread;return a??e.children.length>1}function tI(e,a){const r={},o=e.all(a);let s=-1;for(typeof a.start=="number"&&a.start!==1&&(r.start=a.start);++s<o.length;){const c=o[s];if(c.type==="element"&&c.tagName==="li"&&c.properties&&Array.isArray(c.properties.className)&&c.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const u={type:"element",tagName:a.ordered?"ol":"ul",properties:r,children:e.wrap(o,!0)};return e.patch(a,u),e.applyData(a,u)}function nI(e,a){const r={type:"element",tagName:"p",properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}function aI(e,a){const r={type:"root",children:e.wrap(e.all(a))};return e.patch(a,r),e.applyData(a,r)}function rI(e,a){const r={type:"element",tagName:"strong",properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}function iI(e,a){const r=e.all(a),o=r.shift(),s=[];if(o){const c={type:"element",tagName:"thead",properties:{},children:e.wrap([o],!0)};e.patch(a.children[0],c),s.push(c)}if(r.length>0){const c={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},p=Zd(a.children[1]),g=AA(a.children[a.children.length-1]);p&&g&&(c.position={start:p,end:g}),s.push(c)}const u={type:"element",tagName:"table",properties:{},children:e.wrap(s,!0)};return e.patch(a,u),e.applyData(a,u)}function oI(e,a,r){const o=r?r.children:void 0,u=(o?o.indexOf(a):1)===0?"th":"td",c=r&&r.type==="table"?r.align:void 0,p=c?c.length:a.children.length;let g=-1;const f=[];for(;++g<p;){const h=a.children[g],v={},S=c?c[g]:void 0;S&&(v.align=S);let A={type:"element",tagName:u,properties:v,children:[]};h&&(A.children=e.all(h),e.patch(h,A),A=e.applyData(h,A)),f.push(A)}const b={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(a,b),e.applyData(a,b)}function lI(e,a){const r={type:"element",tagName:"td",properties:{},children:e.all(a)};return e.patch(a,r),e.applyData(a,r)}const Kv=9,Qv=32;function sI(e){const a=String(e),r=/\r?\n|\r/g;let o=r.exec(a),s=0;const u=[];for(;o;)u.push(Jv(a.slice(s,o.index),s>0,!0),o[0]),s=o.index+o[0].length,o=r.exec(a);return u.push(Jv(a.slice(s),s>0,!1)),u.join("")}function Jv(e,a,r){let o=0,s=e.length;if(a){let u=e.codePointAt(o);for(;u===Kv||u===Qv;)o++,u=e.codePointAt(o)}if(r){let u=e.codePointAt(s-1);for(;u===Kv||u===Qv;)s--,u=e.codePointAt(s-1)}return s>o?e.slice(o,s):""}function uI(e,a){const r={type:"text",value:sI(String(a.value))};return e.patch(a,r),e.applyData(a,r)}function cI(e,a){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(a,r),e.applyData(a,r)}const dI={blockquote:jO,break:GO,code:HO,delete:PO,emphasis:$O,footnoteReference:qO,heading:VO,html:YO,imageReference:WO,image:XO,inlineCode:ZO,linkReference:KO,link:QO,listItem:JO,list:tI,paragraph:nI,root:aI,strong:rI,table:iI,tableCell:lI,tableRow:oI,text:uI,thematicBreak:cI,toml:es,yaml:es,definition:es,footnoteDefinition:es};function es(){}const qA=-1,ws=0,fo=1,bs=2,np=3,ap=4,rp=5,ip=6,VA=7,YA=8,eT=typeof self=="object"?self:globalThis,pI=(e,a)=>{const r=(s,u)=>(e.set(u,s),s),o=s=>{if(e.has(s))return e.get(s);const[u,c]=a[s];switch(u){case ws:case qA:return r(c,s);case fo:{const p=r([],s);for(const g of c)p.push(o(g));return p}case bs:{const p=r({},s);for(const[g,f]of c)p[o(g)]=o(f);return p}case np:return r(new Date(c),s);case ap:{const{source:p,flags:g}=c;return r(new RegExp(p,g),s)}case rp:{const p=r(new Map,s);for(const[g,f]of c)p.set(o(g),o(f));return p}case ip:{const p=r(new Set,s);for(const g of c)p.add(o(g));return p}case VA:{const{name:p,message:g}=c;return r(new eT[p](g),s)}case YA:return r(BigInt(c),s);case"BigInt":return r(Object(BigInt(c)),s);case"ArrayBuffer":return r(new Uint8Array(c).buffer,c);case"DataView":{const{buffer:p}=new Uint8Array(c);return r(new DataView(p),c)}}return r(new eT[u](c),s)};return o},tT=e=>pI(new Map,e)(0),Yr="",{toString:fI}={},{keys:gI}=Object,so=e=>{const a=typeof e;if(a!=="object"||!e)return[ws,a];const r=fI.call(e).slice(8,-1);switch(r){case"Array":return[fo,Yr];case"Object":return[bs,Yr];case"Date":return[np,Yr];case"RegExp":return[ap,Yr];case"Map":return[rp,Yr];case"Set":return[ip,Yr];case"DataView":return[fo,r]}return r.includes("Array")?[fo,r]:r.includes("Error")?[VA,r]:[bs,r]},ts=([e,a])=>e===ws&&(a==="function"||a==="symbol"),mI=(e,a,r,o)=>{const s=(c,p)=>{const g=o.push(c)-1;return r.set(p,g),g},u=c=>{if(r.has(c))return r.get(c);let[p,g]=so(c);switch(p){case ws:{let b=c;switch(g){case"bigint":p=YA,b=c.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+g);b=null;break;case"undefined":return s([qA],c)}return s([p,b],c)}case fo:{if(g){let v=c;return g==="DataView"?v=new Uint8Array(c.buffer):g==="ArrayBuffer"&&(v=new Uint8Array( Line truncated
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(p,!0)},{type:"text",value:`
`}]}}const xs=(function(e){if(e==null)return TI;if(typeof e=="function")return ks(e);if(typeof e=="object")return Array.isArray(e)?EI(e):SI(e);if(typeof e=="string")return vI(e);throw new Error("Expected function, string, or object as test")});function EI(e){const a=[];let r=-1;for(;++r<e.length;)a[r]=xs(e[r]);return ks(o);function o(...s){let u=-1;for(;++u<a.length;)if(a[u].apply(this,s))return!0;return!1}}function SI(e){const a=e;return ks(r);function r(o){const s=o;let u;for(u in e)if(s[u]!==a[u])return!1;return!0}}function vI(e){return ks(a);function a(r){return r&&r.type===e}}function ks(e){return a;function a(r,o,s){return!!(AI(r)&&e.call(this,r,typeof o=="number"?o:void 0,s||void 0))}}function TI(){return!0}function AI(e){return e!==null&&typeof e=="object"&&"type"in e}const WA=[],wI=!0,Cd=!1,xI="skip";function XA(e,a,r,o){let s;typeof a=="function"&&typeof r!="function"?(o=r,r=a):s=a;const u=xs(s),c=o?-1:1;p(e,void 0,[])();function p(g,f,b){const h=g&&typeof g=="object"?g:{};if(typeof h.type=="string"){const S=typeof h.tagName=="string"?h.tagName:typeof h.name=="string"?h.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(g.type+(S?"<"+S+">":""))+")"})}return v;function v(){let S=WA,A,N,C;if((!a||u(g,f,b[b.length-1]||void 0))&&(S=kI(r(g,b)),S[0]===Cd))return S;if("children"in g&&g.children){const _=g;if(_.children&&S[0]!==xI)for(N=(o?_.children.length:-1)+c,C=b.concat(_);N>-1&&N<_.children.length;){const U=_.children[N];if(A=p(U,N,C)(),A[0]===Cd)return A;N=typeof A[1]=="number"?A[1]:N+c}}return S}}}function kI(e){return Array.isArray(e)?e:typeof e=="number"?[wI,e]:e==null?WA:[e]}function op(e,a,r,o){let s,u,c;typeof a=="function"&&typeof r!="function"?(u=void 0,c=a,s=r):(u=a,c=r,s=o),XA(e,u,p,s);function p(g,f){const b=f[f.length-1],h=b?b.children.indexOf(g):void 0;return c(g,h,b)}}const Od={}.hasOwnProperty,_I={};function NI(e,a){const r=a||_I,o=new Map,s=new Map,u=new Map,c={...dI,...r.handlers},p={all:f,applyData:CI,definitionById:o,footnoteById:s,footnoteCounts:u,footnoteOrder:[],handlers:c,one:g,options:r,patch:RI,wrap:II};return op(e,function(b){if(b.type==="definition"||b.type==="footnoteDefinition"){const h=b.type==="definition"?o:s,v=String(b.identifier).toUpperCase();h.has(v)||h.set(v,b)}}),p;function g(b,h){const v=b.type,S=p.handlers[v];if(Od.call(p.handlers,v)&&S)return S(p,b,h);if(p.options.passThrough&&p.options.passThrough.includes(v)){if("children"in b){const{children:N,...C}=b,_=hs(C);return _.children=p.all(b),_}return hs(b)}return(p.options.unknownHandler||OI)(p,b,h)}function f(b){const h=[];if("children"in b){const v=b.children;let S=-1;for(;++S<v.length;){const A=p.one(v[S],b);if(A){if(S&&v[S-1].type==="break"&&(!Array.isArray(A)&&A.type==="text"&&(A.value=aT(A.value)),!Array.isArray(A)&&A.type==="element")){const N=A.children[0];N&&N.type==="text"&&(N.value=aT(N.value))}Array.isArray(A)?h.push(...A):h.push(A)}}}return h}}function RI(e,a){e.position&&(a.position=hR(e))}function CI(e,a){let r=a;if(e&&e.data){const o=e.data.hName,s=e.data.hChildren,u=e.data.hProperties;if(typeof o=="string")if(r.type==="element")r.tagName=o;else{const c="children"in r?r.children:[r];r={type:"element",tagName:o,properties:{},children:c}}r.type==="element"&&u&&Object.assign(r.properties,hs(u)),"children"in r&&r.children&&s!==null&&s!==void 0&&(r.children=s)}return r}function OI(e,a){const r=a.data||{},o="value"in a&&!(Od.call(r,"hProperties")||Od.call(r,"hChildren"))?{type:"text",value:a.value}:{type:"element",tagName:"div",properties:{},children:e.all(a)};return e.patch(a,o),e.applyData(a,o)}function II(e,a){const r=[];let o=-1;for(a&&r.push({type:"text",value:`
`});++o<e.length;)o&&r.push({type:"text",value:`
`}),r.push(e[o]);return a&&e.length>0&&r.push({type:"text",value:`
`}),r}function aT(e){let a=0,r=e.charCodeAt(a);for(;r===9||r===32;)a++,r=e.charCodeAt(a);return e.slice(a)}function rT(e,a){const r=NI(e,a),o=r.one(e,void 0),s=yI(r),u=Array.isArray(o)?{type:"root",children:o}:o||{type:"root",children:[]};return s&&u.children.push({type:"text",value:`
`},s),u}function LI(e,a){return e&&"run"in e?async function(r,o){const s=rT(r,{file:o,...a});await e.run(s,o)}:function(r,o){return rT(r,{file:o,...e||a})}}function iT(e){if(e)throw e}var cd,oT;function DI(){if(oT)return cd;oT=1;var e=Object.prototype.hasOwnProperty,a=Object.prototype.toString,r=Object.defineProperty,o=Object.getOwnPropertyDescriptor,s=function(f){return typeof Array.isArray=="function"?Array.isArray(f):a.call(f)==="[object Array]"},u=function(f){if(!f||a.call(f)!=="[object Object]")return!1;var b=e.call(f,"constructor"),h=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!b&&!h)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},c=function(f,b){r&&b.name==="__proto__"?r(f,b.name,{enumerable:!0,configurable:!0,value:b.newValue,writable:!0}):f[b.name]=b.newValue},p=function(f,b){if(b==="__proto__")if(e.call(f,b)){if(o)return o(f,b).value}else return;return f[b]};return cd=function g(){var f,b,h,v,S,A,N=arguments[0],C=1,_=arguments.length,U=!1;for(typeof N=="boolean"&&(U=N,N=arguments[1]||{},C=2),(N==null||typeof N!="object"&&typeof N!="function")&&(N={});C<_;++C)if(f=arguments[C],f!=null)for(b in f)h=p(N,b),v=p(f,b),N!==v&&(U&&v&&(u(v)||(S=s(v)))?(S?(S=!1,A=h&&s(h)?h:[]):A=h&&u(h)?h:{},c(N,{name:b,newValue:g(U,A,v)})):typeof v<"u"&&c(N,{name:b,newValue:v}));return N},cd}var MI=DI();const dd=Gd(MI);function Id(e){if(typeof e!="object"||e===null)return!1;const a=Object.getPrototypeOf(e);return(a===null||a===Object.prototype||Object.getPrototypeOf(a)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function UI(){const e=[],a={run:r,use:o};return a;function r(...s){let u=-1;const c=s.pop();if(typeof c!="function")throw new TypeError("Expected function as last argument, not "+c);p(null,...s);function p(g,...f){const b=e[++u];let h=-1;if(g){c(g);return}for(;++h<s.length;)(f[h]===null||f[h]===void 0)&&(f[h]=s[h]);s=f,b?BI(b,p)(...f):c(null,...f)}}function o(s){if(typeof s!="function")throw new TypeError("Expected `middelware` to be a function, not "+s);return e.push(s),a}}function BI(e,a){let r;return o;function o(...c){const p=e.length>c.length;let g;p&&c.push(s);try{g=e.apply(this,c)}catch(f){const b=f;if(p&&r)throw b;return s(b)}p||(g&&g.then&&typeof g.then=="function"?g.then(u,s):g instanceof Error?s(g):u(g))}function s(c,...p){r||(r=!0,a(c,...p))}function u(c){s(null,c)}}const An={basename:FI,dirname:zI,extname:jI,join:GI,sep:"/"};function FI(e,a){if(a!==void 0&&typeof a!="string")throw new TypeError('"ext" argument must be a string');xo(e);let r=0,o=-1,s=e.length,u;if(a===void 0||a.length===0||a.length>e.length){for(;s--;)if(e.codePointAt(s)===47){if(u){r=s+1;break}}else o<0&&(u=!0,o=s+1);return o<0?"":e.slice(r,o)}if(a===e)return"";let c=-1,p=a.length-1;for(;s--;)if(e.codePointAt(s)===47){if(u){r=s+1;break}}else c<0&&(u=!0,c=s+1),p>-1&&(e.codePointAt(s)===a.codePointAt(p--)?p<0&&(o=s):(p=-1,o=c));return r===o?o=c:o<0&&(o=e.length),e.slice(r,o)}function zI(e){if(xo(e),e.length===0)return".";let a=-1,r=e.length,o;for(;--r;)if(e.codePointAt(r)===47){if(o){a=r;break}}else o||(o=!0);return a<0?e.codePointAt(0)===47?"/":".":a===1&&e.codePointAt(0)===47?"//":e.slice(0,a)}function jI(e){xo(e);let a=e.length,r=-1,o=0,s=-1,u=0,c;for(;a--;){const p=e.codePointAt(a);if(p===47){if(c){o=a+1;break}continue}r<0&&(c=!0,r=a+1),p===46?s<0?s=a:u!==1&&(u=1):s>-1&&(u=-1)}return s<0||r<0||u===0||u===1&&s===r-1&&s===o+1?"":e.slice(s,r)}function GI(...e){let a=-1,r;for(;++a<e.length;)xo(e[a]),e[a]&&(r=r===void 0?e[a]:r+"/"+e[a]);return r===void 0?".":HI(r)}function HI(e){xo(e);const a=e.codePointAt(0)===47;let r=PI(e,!a);return r.length===0&&!a&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),a?"/"+r:r}function PI(e,a){let r="",o=0,s=-1,u=0,c=-1,p,g;for(;++c<=e.length;){if(c<e.length)p=e.codePointAt(c);else{if(p===47)break;p=47}if(p===47){if(!(s===c-1||u===1))if(s!==c-1&&u===2){if(r.length<2||o!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(g=r.lastIndexOf("/"),g!==r.length-1){g<0?(r="",o=0):(r=r.slice(0,g),o=r.length-1-r.lastIndexOf("/")),s=c,u=0;continue}}else if(r.length>0){r="",o=0,s=c,u=0;continue}}a&&(r=r.length>0?r+"/..":"..",o=2)}else r.length>0?r+="/"+e.slice(s+1,c):r=e.slice(s+1,c),o=c-s-1;s=c,u=0}else p===46&&u>-1?u++:u=-1}return r}function xo(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const $I={cwd:qI};function qI(){return"/"}function Ld(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function VI(e){if(typeof e=="string")e=new URL(e);else if(!Ld(e)){const a=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw a.code="ERR_INVALID_ARG_TYPE",a}if(e.protocol!=="file:"){const a=new TypeError("The URL must be of scheme file");throw a.code="ERR_INVALID_URL_SCHEME",a}return YI(e)}function YI(e){if(e.hostname!==""){consLine truncated
`:" ")+u.indentLines(u.containerFlow(o,p.current()),a?ew:U0))),f(),g}}function U0(e,a,r){return a===0?e:ew(e,a,r)}function ew(e,a,r){return(r?"":" ")+e}const B0=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];tw.peek=H0;function F0(){return{canContainEols:["delete"],enter:{strikethrough:j0},exit:{strikethrough:G0}}}function z0(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:B0}],handlers:{delete:tw}}}function j0(e){this.enter({type:"delete",children:[]},e)}function G0(e){this.exit(e)}function tw(e,a,r,o){const s=r.createTracker(o),u=r.enter("strikethrough");let c=s.move("~~");return c+=r.containerPhrasing(e,{...s.current(),before:c,after:"~"}),c+=s.move("~~"),u(),c}function H0(){return"~"}function P0(e){return e.length}function $0(e,a){const r=a||{},o=(r.align||[]).concat(),s=r.stringLength||P0,u=[],c=[],p=[],g=[];let f=0,b=-1;for(;++b<e.length;){const N=[],C=[];let _=-1;for(e[b].length>f&&(f=e[b].length);++_<e[b].length;){const U=q0(e[b][_]);if(r.alignDelimiters!==!1){const M=s(U);C[_]=M,(g[_]===void 0||M>g[_])&&(g[_]=M)}N.push(U)}c[b]=N,p[b]=C}let h=-1;if(typeof o=="object"&&"length"in o)for(;++h<f;)u[h]=fT(o[h]);else{const N=fT(o);for(;++h<f;)u[h]=N}h=-1;const v=[],S=[];for(;++h<f;){const N=u[h];let C="",_="";N===99?(C=":",_=":"):N===108?C=":":N===114&&(_=":");let U=r.alignDelimiters===!1?1:Math.max(1,g[h]-C.length-_.length);const M=C+"-".repeat(U)+_;r.alignDelimiters!==!1&&(U=C.length+U+_.length,U>g[h]&&(g[h]=U),S[h]=U),v[h]=M}c.splice(1,0,v),p.splice(1,0,S),b=-1;const A=[];for(;++b<c.length;){const N=c[b],C=p[b];h=-1;const _=[];for(;++h<f;){const U=N[h]||"";let M="",$="";if(r.alignDelimiters!==!1){const W=g[h]-(C[h]||0),I=u[h];I===114?M=" ".repeat(W):I===99?W%2?(M=" ".repeat(W/2+.5),$=" ".repeat(W/2-.5)):(M=" ".repeat(W/2),$=M):$=" ".repeat(W)}r.delimiterStart!==!1&&!h&&_.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&U==="")&&(r.delimiterStart!==!1||h)&&_.push(" "),r.alignDelimiters!==!1&&_.push(M),_.push(U),r.alignDelimiters!==!1&&_.push($),r.padding!==!1&&_.push(" "),(r.delimiterEnd!==!1||h!==f-1)&&_.push("|")}A.push(r.delimiterEnd===!1?_.join("").replace(/ +$/,""):_.join(""))}return A.join(`
`)}function q0(e){return e==null?"":String(e)}function fT(e){const a=typeof e=="string"?e.codePointAt(0):0;return a===67||a===99?99:a===76||a===108?108:a===82||a===114?114:0}function V0(e,a,r,o){const s=r.enter("blockquote"),u=r.createTracker(o);u.move("> "),u.shift(2);const c=r.indentLines(r.containerFlow(e,u.current()),Y0);return s(),c}function Y0(e,a,r){return">"+(r?"":" ")+e}function W0(e,a){return gT(e,a.inConstruct,!0)&&!gT(e,a.notInConstruct,!1)}function gT(e,a,r){if(typeof a=="string"&&(a=[a]),!a||a.length===0)return r;let o=-1;for(;++o<a.length;)if(e.includes(a[o]))return!0;return!1}function mT(e,a,r,o){let s=-1;for(;++s<r.unsafe.length;)if(r.unsafe[s].character===`
`&&W0(r.stack,r.unsafe[s]))return/[ \t]/.test(o.before)?"":" ";return`\\
`}function X0(e,a){const r=String(e);let o=r.indexOf(a),s=o,u=0,c=0;if(typeof a!="string")throw new TypeError("Expected substring");for(;o!==-1;)o===s?++u>c&&(c=u):u=1,s=o+a.length,o=r.indexOf(a,s);return c}function Z0(e,a){return!!(a.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function K0(e){const a=e.options.fence||"`";if(a!=="`"&&a!=="~")throw new Error("Cannot serialize code with `"+a+"` for `options.fence`, expected `` ` `` or `~`");return a}function Q0(e,a,r,o){const s=K0(r),u=e.value||"",c=s==="`"?"GraveAccent":"Tilde";if(Z0(e,r)){const h=r.enter("codeIndented"),v=r.indentLines(u,J0);return h(),v}const p=r.createTracker(o),g=s.repeat(Math.max(X0(u,s)+1,3)),f=r.enter("codeFenced");let b=p.move(g);if(e.lang){const h=r.enter(`codeFencedLang${c}`);b+=p.move(r.safe(e.lang,{before:b,after:" ",encode:["`"],...p.current()})),h()}if(e.lang&&e.meta){const h=r.enter(`codeFencedMeta${c}`);b+=p.move(" "),b+=p.move(r.safe(e.meta,{before:b,after:`
`,encode:["`"],...p.current()})),h()}return b+=p.move(`
`),u&&(b+=p.move(u+`
`)),b+=p.move(g),f(),b}function J0(e,a,r){return(r?"":" ")+e}function sp(e){const a=e.options.quote||'"';if(a!=='"'&&a!=="'")throw new Error("Cannot serialize title with `"+a+"` for `options.quote`, expected `\"`, or `'`");return a}function eL(e,a,r,o){const s=sp(r),u=s==='"'?"Quote":"Apostrophe",c=r.enter("definition");let p=r.enter("label");const g=r.createTracker(o);let f=g.move("[");return f+=g.move(r.safe(r.associationId(e),{before:f,after:"]",...g.current()})),f+=g.move("]: "),p(),!e.url||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=g.move("<"),f+=g.move(r.safe(e.url,{before:f,after:">",...g.current()})),f+=g.move(">")):(p=r.enter("destinationRaw"),f+=g.move(r.safe(e.url,{before:f,after:e.title?" ":`
`,...g.current()}))),p(),e.title&&(p=r.enter(`title${u}`),f+=g.move(" "+s),f+=g.move(r.safe(e.title,{before:f,after:s,...g.current()})),f+=g.move(s),p()),c(),f}function tL(e){const a=e.options.emphasis||"*";if(a!=="*"&&a!=="_")throw new Error("Cannot serialize emphasis with `"+a+"` for `options.emphasis`, expected `*`, or `_`");return a}function Eo(e){return"&#x"+e.toString(16).toUpperCase()+";"}function ys(e,a,r){const o=Jr(e),s=Jr(a);return o===void 0?s===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:o===1?s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}nw.peek=nL;function nw(e,a,r,o){const s=tL(r),u=r.enter("emphasis"),c=r.createTracker(o),p=c.move(s);let g=c.move(r.containerPhrasing(e,{after:s,before:p,...c.current()}));const f=g.charCodeAt(0),b=ys(o.before.charCodeAt(o.before.length-1),f,s);b.inside&&(g=Eo(f)+g.slice(1));const h=g.charCodeAt(g.length-1),v=ys(o.after.charCodeAt(0),h,s);v.inside&&(g=g.slice(0,-1)+Eo(h));const S=c.move(s);return u(),r.attentionEncodeSurroundingInfo={after:v.outside,before:b.outside},p+g+S}function nL(e,a,r){return r.options.emphasis||"*"}function aL(e,a){let r=!1;return op(e,function(o){if("value"in o&&/\r?\n|\r/.test(o.value)||o.type==="break")return r=!0,Cd}),!!((!e.depth||e.depth<3)&&ep(e)&&(a.options.setext||r))}function rL(e,a,r,o){const s=Math.max(Math.min(6,e.depth||1),1),u=r.createTracker(o);if(aL(e,r)){const b=r.enter("headingSetext"),h=r.enter("phrasing"),v=r.containerPhrasing(e,{...u.current(),before:`
function M1(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 Zu(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var af={exports:{}},al={};var OT;function j1(){if(OT)return al;OT=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 IT;function U1(){return IT||(IT=1,af.exports=j1()),af.exports}var g=U1(),of={exports:{}},Ne={};var LT;function F1(){if(LT)return Ne;LT=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(j){return j===null||typeof j!="object"?null:(j=v&&j[v]||j["@@iterator"],typeof j=="function"?j:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},T=Object.assign,k={};function A(j,Q,I){this.props=j,this.context=Q,this.refs=k,this.updater=I||w}A.prototype.isReactComponent={},A.prototype.setState=function(j,Q){if(typeof j!="object"&&typeof j!="function"&&j!=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,j,Q,"setState")},A.prototype.forceUpdate=function(j){this.updater.enqueueForceUpdate(this,j,"forceUpdate")};function _(){}_.prototype=A.prototype;function R(j,Q,I){this.props=j,this.context=Q,this.refs=k,this.updater=I||w}var L=R.prototype=new _;L.constructor=R,T(L,A.prototype),L.isPureReactComponent=!0;var F=Array.isArray;function D(){}var P={H:null,A:null,T:null,S:null},z=Object.prototype.hasOwnProperty;function W(j,Q,I){var ue=I.ref;return{$$typeof:e,type:j,key:Q,ref:ue!==void 0?ue:null,props:I}}function M(j,Q){return W(j.type,Q,j.props)}function H(j){return typeof j=="object"&&j!==null&&j.$$typeof===e}function X(j){var Q={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(I){return Q[I]})}var ee=/\/+/g;function te(j,Q){return typeof j=="object"&&j!==null&&j.key!=null?X(""+j.key):Q.toString(36)}function Z(j){switch(j.status){case"fulfilled":return j.value;case"rejected":throw j.reason;default:switch(typeof j.status=="string"?j.then(D,D):(j.status="pending",j.then(function(Q){j.status==="pending"&&(j.status="fulfilled",j.value=Q)},function(Q){j.status==="pending"&&(j.status="rejected",j.reason=Q)})),j.status){case"fulfilled":return j.value;case"rejected":throw j.reason}}throw j}function B(j,Q,I,ue,fe){var de=typeof j;(de==="undefined"||de==="boolean")&&(j=null);var le=!1;if(j===null)le=!0;else switch(de){case"bigint":case"string":case"number":le=!0;break;case"object":switch(j.$$typeof){case e:case t:le=!0;break;case h:return le=j._init,B(le(j._payload),Q,I,ue,fe)}}if(le)return fe=fe(j),le=ue===""?"."+te(j,0):ue,F(fe)?(I="",le!=null&&(I=le.replace(ee,"$&/")+"/"),B(fe,Q,I,"",function(Re){return Re})):fe!=null&&(H(fe)&&(fe=M(fe,I+(fe.key==null||j&&j.key===fe.key?"":(""+fe.key).replace(ee,"$&/")+"/")+le)),Q.push(fe)),1;le=0;var we=ue===""?".":ue+":";if(F(j))for(var me=0;me<j.length;me++)ue=j[me],de=we+te(ue,me),le+=B(ue,Q,I,de,fe);else if(me=E(j),typeof me=="function")for(j=me.call(j),me=0;!(ue=j.next()).done;)ue=ue.value,de=we+te(ue,me++),le+=B(ue,Q,I,de,fe);else if(de==="object"){if(typeof j.then=="function")return B(Z(j),Q,I,ue,fe);throw Q=String(j),Error("Objects are not valid as a React child (found: "+(Q==="[object Object]"?"object with keys {"+Object.keys(j).join(", ")+"}":Q)+"). If you meant to render a collection of children, use anLine 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(J){var K=J}Reflect.construct(n,[],oe)}else{try{oe.call()}catch(J){K=J}n.call(oe.prototype)}}else{try{throw Error()}catch(J){K=J}(oe=n())&&typeof oe.catch=="function"&&oe.catch(function(){})}}catch(J){if(J&&K&&typeof J.stack=="string")return[J.stack,K.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 U=x.split(`
`),q=N.split(`
`);for(m=c=0;c<U.length&&!U[c].includes("DetermineComponentFrameRoot");)c++;for(;m<q.length&&!q[m].includes("DetermineComponentFrameRoot");)m++;if(c===U.length||m===q.length)for(c=U.length-1,m=q.length-1;1<=c&&0<=m&&U[c]!==q[m];)m--;for(;1<=c&&0<=m;c--,m--)if(U[c]!==q[m]){if(c!==1||m!==1)do if(c--,m--,0>m||U[c]!==q[m]){var re=`
`+U[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 Ya=Object.prototype.hasOwnProperty,Wa=e.unstable_scheduleCallback,po=e.unstable_cancelCallback,Gc=e.unstable_shouldYield,Hc=e.unstable_requestPaint,zt=e.unstable_now,Vc=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:SO,Or=Math.log,In=Math.LN2;function SO(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 xO(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 Mv(){var n=Kl;return Kl<<=1,(Kl&62914560)===0&&(Kl=4194304),n}function qc(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 wO(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,U=n.expirationTimes,q=n.hiddenUpdates;for(l=x&~l;0<l;){var re=31-tt(l),oe=1<<re;N[re]=0,U[re]=-1;var K=q[re];if(K!==null)for(q[re]=null,re=0;re<K.length;re++){var J=K[re];J!==null&&(J.lane&=-536870913)}l&=~oe}c!==0&&jv(n,c,0),y!==0&&m===0&&n.tag!==0&&(n.suspendedLanes|=y&~(x&~i))}function jv(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 Uv(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 Fv(n,i){var l=i&-i;return l=(l&42)!==0?1:Yc(l),(l&(n.suspendedLanes|i))!==0?0:l}function Yc(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 Wc(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Bv(){var n=Y.p;return n!==0?n:(n=window.event,n===void 0?32:TT(n.type))}function zv(n,i){var l=Y.p;try{return Y.p=n,i()}finally{Y.p=l}}var Ir=Math.random().toString(36).slice(2),kt="__reactFiber$"+Ir,qt="__reactProps$"+Ir,Ka="__reactContainer$"+Ir,Kc="__reactEvents$"+Ir,TO="__reactListeners$"+Ir,AO="__reactHandles$"+Ir,Pv="__reactResources$"+Ir,mo="__reactMarker$"+Ir;function Xc(n){delete n[kt],delete n[qt],delete n[Kc],delete n[TO],delete n[AO]}function Xa(n){var i=n[kt];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Ka]||l[kt]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=sT(n);n!==null;){if(l=n[kt])return l;n=sT(n)}return i}n=l,l=n.parentNode}return null}function Za(n){if(n=n[kt]||n[Ka]){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 Qa(n){var i=n[Pv];return i||(i=n[Pv]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[mo]=!0}var $v=new Set,Gv={};function ha(n,i){Ja(n,i),Ja(n+"Capture",i)}function Ja(n,i){for(Gv[n]=i,n=0;n<i.length;n++)$v.add(i[n])}var kO=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\ Line truncated
`).replace(r1,"")}function Qw(n,i){return i=Zw(i),Zw(n)===i}function Je(n,i,l,c,m,y){switch(l){case"children":typeof c=="string"?i==="body"||i==="textarea"&&c===""||ti(n,c):(typeof c=="number"||typeof c=="bigint")&&i!=="body"&&ti(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":Qv(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=OO.get(l)||l,Zl(n,l,c))}}function zp(n,i,l,c,m,y){switch(l){case"style":Qv(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"?ti(n,c):(typeof c=="number"||typeof c=="bigint")&&ti(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 Z=te.match(t);Z&&(_+=Z.length);var B=te.lastIndexOf(p);R=~B?te.length-B:R+te.length}function F(){var te={line:_,column:R};return function(Z){return Z.position=new D(te),W(),Z}}function D(te){this.start=te,this.end={line:_,column:R},this.source=A.source}D.prototype.content=k;function P(te){var Z=new Error(A.source+":"+_+":"+R+": "+te);if(Z.reason=te,Z.filename=A.source,Z.line=_,Z.column=R,Z.source=k,!A.silent)throw Z}function z(te){var Z=te.exec(k);if(Z){var B=Z[0];return L(B),k=k.slice(B.length),Z}}function W(){z(r)}function M(te){var Z;for(te=te||[];Z=H();)Z!==!1&&te.push(Z);return te}function H(){var te=F();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var Z=2;b!=k.charAt(Z)&&(h!=k.charAt(Z)||f!=k.charAt(Z+1));)++Z;if(Z+=2,b===k.charAt(Z-1))return P("End of comment missing");var B=k.slice(2,Z-2);return R+=2,L(B),k=k.slice(Z),R+=2,te({type:v,comment:B})}}function X(){var te=F(),Z=z(a);if(Z){if(H(),!z(o))return P("property missing ':'");var B=z(s),Y=te({type:E,property:T(Z[0].replace(e,b)),value:B?T(B[0].replace(e,b)):b});return z(u),Y}}function ee(){var te=[];M(te);for(var Z;Z=X();)Z!==!1&&(te.push(Z),M(te));return te}return W(),ee()}function T(k){return k?k.replace(d,b):b}return ff=w,ff}var aA;function HD(){if(aA)return Li;aA=1;var e=Li&&Li.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Li,"__esModule",{value:!0}),Li.default=r;const t=e(GD());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 Li}var ll={},iA;function VD(){if(iA)return ll;iA=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,oA;function qD(){if(oA)return sl;oA=1;var e=sl&&sl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e(HD()),r=VD();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 YD=qD();const WD=Zu(YD),D_=M_("end"),Dg=M_("start");function M_(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 KD(e){const t=Dg(e),r=D_(e);if(t&&r)return{start:t,end:r}}function gl(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?lA(e.position):"start"in e||"end"in e?lA(e):"line"in e||"column"in e?Yf(e):""}function Yf(e){return sA(e&&e.line)+":"+sA(e&&e.column)}function lA(e){return Yf(e&&e.start)+"-"+Yf(e&&e.end)}function sA(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 Mg={}.hasOwnProperty,XD=new Map,ZD=/[A-Z]/g,QD=new Set(["table","tbody","thead","tfoot","tr"]),JD=new Set(["td","th"]),j_="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function eM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;i 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 sj(e){const a={constructs:P_([aj,...(e||{}).extensions||[]]),content:o(wM),defined:[],document:o(AM),flow:o(G2),lazy:{},string:o(q2),text:o(Y2)};return a;function o(s){return u;function u(d){return ij(a,s,d)}}}function uj(e){for(;!q_(e););return e}const yA=/[\0\t\n\r]/g;function cj(){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(yA.lastIndex=b,f=yA.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 dj=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function pj(e){return e.replace(dj,fj)}function fj(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return $_(r.slice(s?2:1),s?16:10)}return xl(r)||e}const Q_={}.hasOwnProperty;function gj(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),mj(r)(uj(sj(r).document().write(cj()(e,t,!0))))}function mj(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Ya),autolinkProtocol:W,autolinkEmail:W,atxHeading:s(qn),blockQuote:s(Re),characterEscape:W,characterReference:W,codeFenced:s(Oe),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Oe,u),codeText:s(qe,u),codeTextData:W,data:W,codeFlowValue:W,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Yn),hardBreakTrailing:s(Yn),htmlFlow:s(Cr,u),htmlFlowData:W,htmlText:s(Cr,u),htmlTextData:W,image:s(ql),label:u,link:s(Ya),listItem:s(po),listItemValue:v,listOrdered:s(Wa,b),listUnordered:s(Wa),paragraph:s(Gc),reference:I,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(qn),strong:s(Hc),thematicBreak:s(Vc)},exit:{atxHeading:p(),atxHeadingSequence:F,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(Z),codeTextData:M,data:M,definition:p(),definitionDestinationString:L,definitionLabelString:_,definitionTitleString:R,emphasis:p(),hardBreakEscape:p(X),hardBreakTrailing:p(X),htmlFlow:p(ee),htmlFlowData:M,htmlText:p(te),htmlTextData:M,image:p(Y),label:ce,labelText:ne,lineEnding:H,link:p(B),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:ue,resourceDestinationString:C,resourceTitleString:j,resource:Q,setextHeading:p(z),setextHeadingLineSequence:P,setextHeadingText:D,strong:p(),thematicBreak:p()}};J_(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]];Q_.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]||EA).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==="blockQuotePrefixWhitesLine truncated
`}]}function vj(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 Sj(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function xj(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wj(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 Tj(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 Aj(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function eN(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 kj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return eN(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 _j(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 Nj(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 Rj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return eN(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 Cj(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 Oj(e,t,r){const a=e.all(t),o=r?Ij(r):tN(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 Ij(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=tN(r[a])}return t}function tN(e){const t=e.spread;return t??e.children.length>1}function Lj(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 Dj(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Mj(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function jj(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Uj(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=Dg(t.children[1]),p=D_(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 Fj(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 Bj(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const vA=9,SA=32;function zj(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(xA(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(xA(t.slice(o),o>0,!1)),s.join("")}function xA(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===vA||s===SA;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===vA||s===SA;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function Pj(e,t){const r={type:"text",value:zj(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function $j(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const Gj={blockquote:yj,break:Ej,code:vj,delete:Sj,emphasis:xj,footnoteReference:wj,heading:Tj,html:Aj,imageReference:kj,image:_j,inlineCode:Nj,linkReference:Rj,link:Cj,listItem:Oj,list:Lj,paragraph:Dj,root:Mj,strong:jj,table:Uj,tableCell:Bj,tableRow:Fj,text:Pj,thematicBreak:$j,toml:fu,yaml:fu,definition:fu,footnoteDefinition:fu};function fu(){}const nN=-1,ac=0,hl=1,Bu=2,zg=3,Pg=4,$g=5,Gg=6,rN=7,aN=8,wA=typeof self=="object"?self:globalThis,Hj=(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 ac:case nN:return r(u,o);case hl:{const d=r([],o);for(const p of u)d.push(a(p));return d}case Bu:{const d=r({},o);for(const[p,f]of u)d[a(p)]=a(f);return d}case zg:return r(new Date(u),o);case Pg:{const{source:d,flags:p}=u;return r(new RegExp(d,p),o)}case $g:{const d=r(new Map,o);for(const[p,f]of u)d.set(a(p),a(f));return d}case Gg:{const d=r(new Set,o);for(const p of u)d.add(a(p));return d}case rN:{const{name:d,message:p}=u;return r(new wA[d](p),o)}case aN: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 wA[s](u),o)};return a},TA=e=>Hj(new Map,e)(0),Di="",{toString:Vj}={},{keys:qj}=Object,cl=e=>{const t=typeof e;if(t!=="object"||!e)return[ac,t];const r=Vj.call(e).slice(8,-1);switch(r){case"Array":return[hl,Di];case"Object":return[Bu,Di];case"Date":return[zg,Di];case"RegExp":return[Pg,Di];case"Map":return[$g,Di];case"Set":return[Gg,Di];case"DataView":return[hl,r]}return r.includes("Array")?[hl,r]:r.includes("Error")?[rN,r]:[Bu,r]},gu=([e,t])=>e===ac&&(t==="function"||t==="symbol"),Yj=(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 ac:{let h=u;switch(p){case"bigint":d=aN,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([nN],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 ic=(function(e){if(e==null)return eU;if(typeof e=="function")return oc(e);if(typeof e=="object")return Array.isArray(e)?Zj(e):Qj(e);if(typeof e=="string")return Jj(e);throw new Error("Expected function, string, or object as test")});function Zj(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=ic(e[r]);return oc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function Qj(e){const t=e;return oc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function Jj(e){return oc(t);function t(r){return r&&r.type===e}}function oc(e){return t;function t(r,a,o){return!!(tU(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function eU(){return!0}function tU(e){return e!==null&&typeof e=="object"&&"type"in e}const iN=[],nU=!0,Xf=!1,rU="skip";function oN(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=ic(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=iN,w,T,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=aU(r(p,h)),E[0]===Xf))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==rU)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]===Xf)return w;T=typeof w[1]=="number"?w[1]:T+u}}return E}}}function aU(e){return Array.isArray(e)?e:typeof e=="number"?[nU,e]:e==null?iN:[e]}function Hg(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),oN(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 Zf={}.hasOwnProperty,iU={};function oU(e,t){const r=t||iU,a=new Map,o=new Map,s=new Map,u={...Gj,...r.handlers},d={all:f,applyData:sU,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:lU,wrap:cU};return Hg(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(Zf.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=zu(k);return A.children=d.all(h),A}return zu(h)}return(d.options.unknownHandler||uU)(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=kA(w.value)),!Array.isArray(w)&&w.type==="element")){const T=w.children[0];T&&T.type==="text"&&(T.value=kA(T.value))}Array.isArray(w)?b.push(...w):b.push(w)}}}return b}}function lU(e,t){e.position&&(t.position=KD(e))}function sU(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,zu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function uU(e,t){const r=t.data||{},a="value"in t&&!(Zf.call(r,"hProperties")||Zf.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 cU(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 kA(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function _A(e,t){const r=oU(e,t),a=r.one(e,void 0),o=Xj(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
`},o),s}function dU(e,t){return e&&"run"in e?async function(r,a){const o=_A(r,{file:a,...t});await e.run(o,a)}:function(r,a){return _A(r,{file:a,...e||t})}}function NA(e){if(e)throw e}var bf,RA;function pU(){if(RA)return bf;RA=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 bf=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},bf}var fU=pU();const yf=Zu(fU);function Qf(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 gU(){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?mU(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 mU(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:hU,dirname:bU,extname:yU,join:EU,sep:"/"};function hU(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 bU(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 yU(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 EU(...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?".":vU(r)}function vU(e){Ol(e);const t=e.codePointAt(0)===47;let r=SU(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function SU(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 xU={cwd:wU};function wU(){return"/"}function Jf(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function TU(e){if(typeof e=="string")e=new URL(e);else if(!Jf(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 AU(e)}function AU(e){if(e.hostname!==""){consLine truncated
`:" ")+s.indentLines(s.containerFlow(a,d.current()),t?dN:gF))),f(),p}}function gF(e,t,r){return t===0?e:dN(e,t,r)}function dN(e,t,r){return(r?"":" ")+e}const mF=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];pN.peek=vF;function hF(){return{canContainEols:["delete"],enter:{strikethrough:yF},exit:{strikethrough:EF}}}function bF(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:mF}],handlers:{delete:pN}}}function yF(e){this.enter({type:"delete",children:[]},e)}function EF(e){this.exit(e)}function pN(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 vF(){return"~"}function SF(e){return e.length}function xF(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||SF,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 _=wF(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]=jA(a[b]);else{const T=jA(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 F=p[b]-(k[b]||0),D=s[b];D===114?R=" ".repeat(F):D===99?F%2?(R=" ".repeat(F/2+.5),L=" ".repeat(F/2-.5)):(R=" ".repeat(F/2),L=R):L=" ".repeat(F)}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 wF(e){return e==null?"":String(e)}function jA(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 TF(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()),AF);return o(),u}function AF(e,t,r){return">"+(r?"":" ")+e}function kF(e,t){return UA(e,t.inConstruct,!0)&&!UA(e,t.notInConstruct,!1)}function UA(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 FA(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
`&&kF(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
`}function _F(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 NF(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 RF(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 CF(e,t,r,a){const o=RF(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(NF(e,r)){const b=r.enter("codeIndented"),v=r.indentLines(s,OF);return b(),v}const d=r.createTracker(a),p=o.repeat(Math.max(_F(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 OF(e,t,r){return(r?"":" ")+e}function qg(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 IF(e,t,r,a){const o=qg(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 LF(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 Pu(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}}fN.peek=DF;function fN(e,t,r,a){const o=LF(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=Pu(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=Pu(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 DF(e,t,r){return r.options.emphasis||"*"}function MF(e,t){let r=!1;return Hg(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Xf}),!!((!e.depth||e.depth<3)&&Fg(e)&&(t.options.setext||r))}function jF(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(MF(e,r)){const h=r.enter("headingSetext"),b=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
`,after:`
`});return h(),b(),v+`
`+(s===1?"=":"-").repeat(v.length-(Math.max(v.lastIndexOf("\r"),v.lastIndexOf(`
`))+1))}const c="#".repeat(s),p=r.enter("headingAtx"),g=r.enter("phrasing");u.move(c+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
`,...u.current()});return/^[\t ]/.test(f)&&(f=Eo(f.charCodeAt(0))+f.slice(1)),f=f?c+" "+f:c,r.options.closeAtx&&(f+=" "+c),g(),p(),f}aw.peek=iL;function aw(e){return e.value||""}function iL(){return"<"}rw.peek=oL;function rw(e,a,r,o){const s=sp(r),u=s==='"'?"Quote":"Apostrophe",c=r.enter("image");let p=r.enter("label");const g=r.createTracker(o);let f=g.move("![");return f+=g.move(r.safe(e.alt,{before:f,after:"]",...g.current()})),f+=g.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=g.move("<"),f+=g.move(r.safe(e.url,{before:f,after:">",...g.current()})),f+=g.move(">")):(p=r.enter("destinationRaw"),f+=g.move(r.safe(e.url,{before:f,after:e.title?" ":")",...g.current()}))),p(),e.title&&(p=r.enter(`title${u}`),f+=g.move(" "+s),f+=g.move(r.safe(e.title,{before:f,after:s,...g.current()})),f+=g.move(s),p()),f+=g.move(")"),c(),f}function oL(){return"!"}iw.peek=lL;function iw(e,a,r,o){const s=e.referenceType,u=r.enter("imageReference");let c=r.enter("label");const p=r.createTracker(o);let g=p.move("![");const f=r.safe(e.alt,{before:g,after:"]",...p.current()});g+=p.move(f+"]["),c();const b=r.stack;r.stack=[],c=r.enter("reference");const h=r.safe(r.associationId(e),{before:g,after:"]",...p.current()});return c(),r.stack=b,u(),s==="full"||!f||f!==h?g+=p.move(h+"]"):s==="shortcut"?g=g.slice(0,-1):g+=p.move("]"),g}function lL(){return"!"}ow.peek=sL;function ow(e,a,r){let o=e.value||"",s="`",u=-1;for(;new RegExp("(^|[^`])"+s+"([^`]|$)").test(o);)s+="`";for(/[^ \r\n]/.test(o)&&(/^[ \r\n]/.test(o)&&/[ \r\n]$/.test(o)||/^`|`$/.test(o))&&(o=" "+o+" ");++u<r.unsafe.length;){const c=r.unsafe[u],p=r.compilePattern(c);let g;if(c.atBreak)for(;g=p.exec(o);){let f=g.index;o.charCodeAt(f)===10&&o.charCodeAt(f-1)===13&&f--,o=o.slice(0,f)+" "+o.slice(g.index+1)}}return s+o+s}function sL(){return"`"}function lw(e,a){const r=ep(e);return!!(!a.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))}sw.peek=uL;function sw(e,a,r,o){const s=sp(r),u=s==='"'?"Quote":"Apostrophe",c=r.createTracker(o);let p,g;if(lw(e,r)){const b=r.stack;r.stack=[],p=r.enter("autolink");let h=c.move("<");return h+=c.move(r.containerPhrasing(e,{before:h,after:">",...c.current()})),h+=c.move(">"),p(),r.stack=b,h}p=r.enter("link"),g=r.enter("label");let f=c.move("[");return f+=c.move(r.containerPhrasing(e,{before:f,after:"](",...c.current()})),f+=c.move("]("),g(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(g=r.enter("destinationLiteral"),f+=c.move("<"),f+=c.move(r.safe(e.url,{before:f,after:">",...c.current()})),f+=c.move(">")):(g=r.enter("destinationRaw"),f+=c.move(r.safe(e.url,{before:f,after:e.title?" ":")",...c.current()}))),g(),e.title&&(g=r.enter(`title${u}`),f+=c.move(" "+s),f+=c.move(r.safe(e.title,{before:f,after:s,...c.current()})),f+=c.move(s),g()),f+=c.move(")"),p(),f}function uL(e,a,r){return lw(e,r)?"<":"["}uw.peek=cL;function uw(e,a,r,o){const s=e.referenceType,u=r.enter("linkReference");let c=r.enter("label");const p=r.createTracker(o);let g=p.move("[");const f=r.containerPhrasing(e,{before:g,after:"]",...p.current()});g+=p.move(f+"]["),c();const b=r.stack;r.stack=[],c=r.enter("reference");const h=r.safe(r.associationId(e),{before:g,after:"]",...p.current()});return c(),r.stack=b,u(),s==="full"||!f||f!==h?g+=p.move(h+"]"):s==="shortcut"?g=g.slice(0,-1):g+=p.move("]"),g}function cL(){return"["}function up(e){const a=e.options.bullet||"*";if(a!=="*"&&a!=="+"&&a!=="-")throw new Error("Cannot serialize items with `"+a+"` for `options.bullet`, expected `*`, `+`, or `-`");return a}function dL(e){const a=up(e),r=e.options.bulletOther;if(!r)return a==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===a)throw new Error("Expected `bullet` (`"+a+"`) and `bulletOther` (`"+r+"`) to be different");return r}function pL(e){const a=e.options.bulletOrdered||".";if(a!=="."&&a!==")")throw new Error("Cannot serialize items with `"+a+"` for `options.bulletOrdered`, expected `.` or `)`");return a}function cw(e){const a=e.options.rule||"*";if(a!=="*"&&a!=="-"&&a!=="_")throw new Error("Cannot serialize rules with `"+a+"` for `options.rule`, expected `*`, `-`, or `_`");return a}function fL(e,a,r,o){const s=r.enter("list"),u=r.bulletCurrent;let c=e.ordered?pL(r):up(r);const p=e.ordered?c==="."?")":".":dL(r);let g=a&&r.bulletLastUsed?c===r.bulletLastUsed:!1;if(!e.ordered){const b=e.children?e.children[0]:void 0;if((c==="*"||c==="-")&&b&&(!b.children||!b.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&&(g=!0),cw(r)===c&&b){let h=-1;for(;++h<e.chilLine truncated
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:v,table:c,tableCell:g,tableRow:p}};function c(S,A,N,C){return f(b(S,N,C),S.align)}function p(S,A,N,C){const _=h(S,N,C),U=f([_]);return U.slice(0,U.indexOf(`
`))}function g(S,A,N,C){const _=N.enter("tableCell"),U=N.enter("phrasing"),M=N.containerPhrasing(S,{...C,before:u,after:u});return U(),_(),M}function f(S,A){return $0(S,{align:A,alignDelimiters:o,padding:r,stringLength:s})}function b(S,A,N){const C=S.children;let _=-1;const U=[],M=A.enter("table");for(;++_<C.length;)U[_]=h(C[_],A,N);return M(),U}function h(S,A,N){const C=S.children;let _=-1;const U=[],M=A.enter("tableRow");for(;++_<C.length;)U[_]=g(C[_],S,A,N);return M(),U}function v(S,A,N){let C=pw.inlineCode(S,A,N);return N.stack.includes("tableCell")&&(C=C.replace(/\|/g,"\\$&")),C}}function OL(){return{exit:{taskListCheckValueChecked:hT,taskListCheckValueUnchecked:hT,paragraph:LL}}}function IL(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:DL}}}function hT(e){const a=this.stack[this.stack.length-2];a.type,a.checked=e.type==="taskListCheckValueChecked"}function LL(e){const a=this.stack[this.stack.length-2];if(a&&a.type==="listItem"&&typeof a.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const o=r.children[0];if(o&&o.type==="text"){const s=a.children;let u=-1,c;for(;++u<s.length;){const p=s[u];if(p.type==="paragraph"){c=p;break}}c===r&&(o.value=o.value.slice(1),o.value.length===0?r.children.shift():r.position&&o.position&&typeof o.position.start.offset=="number"&&(o.position.start.column++,o.position.start.offset++,r.position.start=Object.assign({},o.position.start)))}}this.exit(e)}function DL(e,a,r,o){const s=e.children[0],u=typeof e.checked=="boolean"&&s&&s.type==="paragraph",c="["+(e.checked?"x":" ")+"] ",p=r.createTracker(o);u&&p.move(c);let g=pw.listItem(e,a,r,{...o,...p.current()});return u&&(g=g.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),g;function f(b){return b+c}}function ML(){return[f0(),D0(),F0(),wL(),OL()]}function UL(e){return{extensions:[g0(),M0(e),z0(),CL(e),IL()]}}const BL={tokenize:PL,partial:!0},fw={tokenize:$L,partial:!0},gw={tokenize:qL,partial:!0},mw={tokenize:VL,partial:!0},FL={tokenize:YL,partial:!0},bw={name:"wwwAutolink",tokenize:GL,previous:yw},hw={name:"protocolAutolink",tokenize:HL,previous:Ew},Xn={name:"emailAutolink",tokenize:jL,previous:Sw},xn={};function zL(){return{text:xn}}let Ya=48;for(;Ya<123;)xn[Ya]=Xn,Ya++,Ya===58?Ya=65:Ya===91&&(Ya=97);xn[43]=Xn;xn[45]=Xn;xn[46]=Xn;xn[95]=Xn;xn[72]=[Xn,hw];xn[104]=[Xn,hw];xn[87]=[Xn,bw];xn[119]=[Xn,bw];function jL(e,a,r){const o=this;let s,u;return c;function c(h){return!Dd(h)||!Sw.call(o,o.previous)||cp(o.events)?r(h):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),p(h))}function p(h){return Dd(h)?(e.consume(h),p):h===64?(e.consume(h),g):r(h)}function g(h){return h===46?e.check(FL,b,f)(h):h===45||h===95||wt(h)?(u=!0,e.consume(h),g):b(h)}function f(h){return e.consume(h),s=!0,g}function b(h){return u&&s&&Ot(o.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),a(h)):r(h)}}function GL(e,a,r){const o=this;return s;function s(c){return c!==87&&c!==119||!yw.call(o,o.previous)||cp(o.events)?r(c):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(BL,e.attempt(fw,e.attempt(gw,u),r),r)(c))}function u(c){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),a(c)}}function HL(e,a,r){const o=this;let s="",u=!1;return c;function c(h){return(h===72||h===104)&&Ew.call(o,o.previous)&&!cp(o.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),s+=String.fromCodePoint(h),e.consume(h),p):r(h)}function p(h){if(Ot(h)&&s.length<5)return s+=String.fromCodePoint(h),e.consume(h),p;if(h===58){const v=s.toLowerCase();if(v==="http"||v==="https")return e.consume(h),g}return r(h)}function g(h){return h===47?(e.consume(h),u?f:(u=!0,g)):r(h)}function f(h){return h===null||ms(h)||Xe(h)||Ka(h)||Ts(h)?r(h):e.attempt(fw,e.attempt(gw,b),r)(h)}function b(h){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),a(h)}}function PL(e,a,r){let o=0;return s;function s(c){return(c===87||c===119)&&o<3?(o++,e.consume(c),s):c===46&&o===3?(e.consume(c),u):r(c)}function u(c){return c===null?r(c):a(c)}}function $L(e,a,r){let o,s,u;return c;function c(f){return f===46||f===95?e.check(mw,g,p)(f):f===null||Xe(f)||Ka(f)||f!==45&&Ts(f)?g(f):(u=!0,e.consume(f),c)}function p(f){return f===95?o=!0:(s=o,o=void 0),e.consume(f),c}function g(f){return s||o||!u?r(f):a(f)}}function qL(e,a){let r=0,o=0;return s;function s(c){return c===40?(r++,e.consume(c),s):c===41&&o<r?u(c):c===33||c===34||c===38||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===60||c===63||c===93||c===95||c===126?e.check(mw,a,u)(c):c===null||Xe(c)||Ka(c)?a(c):(e.consume(c),s)}function u(c){return c===41&&o++,e.consume(c),s}}function VL(e,a,r){return o;function o(p){return p===33||p===34||p===39||p===41||p===42||p===44||p===46||p===58||p===59||p===63||p===95||p===126?(e.consume(p),o):p===38?(e.consume(p),u):p===93?(e.consume(p),s):p===60||p===null||Xe(p)||Ka(p)?a(p):r(p)}function s(p){return p===null||p===40||p===91||Xe(p)||Ka(p)?a(p):o(p)}function u(p){return Ot(p)?c(p):r(p)}function c(p){re Line truncated
`)===-1?y.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:b}):y.jsx("code",{className:"block font-mono",children:b}),pre:({children:f})=>y.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:f}),table:({children:f})=>y.jsx("div",{className:"overflow-auto my-4",children:y.jsx("table",{className:"w-full border-collapse text-sm",children:f})}),thead:({children:f})=>y.jsx("thead",{className:"bg-muted/50",children:f}),tr:({children:f})=>y.jsx("tr",{className:"border-b",children:f}),th:({children:f})=>y.jsx("th",{className:"px-4 py-2 text-left font-medium",children:f}),td:({children:f})=>y.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:f}),hr:()=>y.jsx("hr",{className:"my-6 border-border"}),strong:({children:f})=>y.jsx("strong",{className:"font-semibold text-foreground",children:f}),em:({children:f})=>y.jsx("em",{children:f})};return r?y.jsx("main",{className:"flex-1 p-8 max-w-3xl",children:u?y.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):o?y.jsx("article",{className:"prose max-w-none",children:y.jsx(KA,{remarkPlugins:[vw],components:g,children:o})}):y.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:y.jsxs("p",{className:"text-muted-foreground",children:["Content for ",y.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})}):y.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[y.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),y.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),y.jsx(fA,{className:"my-8"}),y.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),y.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[y.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:y.jsx("strong",{children:"Current theme from YAML:"})}),y.jsx("pre",{className:"text-primary text-sm",children:`site:
`});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}gN.peek=UF;function gN(e){return e.value||""}function UF(){return"<"}mN.peek=FF;function mN(e,t,r,a){const o=qg(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 FF(){return"!"}hN.peek=BF;function hN(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 BF(){return"!"}bN.peek=zF;function bN(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 zF(){return"`"}function yN(e,t){const r=Fg(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))}EN.peek=PF;function EN(e,t,r,a){const o=qg(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let d,p;if(yN(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 PF(e,t,r){return yN(e,r)?"<":"["}vN.peek=$F;function vN(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 $F(){return"["}function Yg(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 GF(e){const t=Yg(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 HF(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 SN(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 VF(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?HF(r):Yg(r);const d=e.ordered?u==="."?")":".":GF(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),SN(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 xF(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=wN.inlineCode(E,w,T);return T.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function uB(){return{exit:{taskListCheckValueChecked:zA,taskListCheckValueUnchecked:zA,paragraph:dB}}}function cB(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:pB}}}function zA(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function dB(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 pB(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=wN.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 fB(){return[VU(),pF(),hF(),nB(),uB()]}function gB(e){return{extensions:[qU(),fF(e),bF(),sB(e),cB()]}}const mB={tokenize:SB,partial:!0},TN={tokenize:xB,partial:!0},AN={tokenize:wB,partial:!0},kN={tokenize:TB,partial:!0},hB={tokenize:AB,partial:!0},_N={name:"wwwAutolink",tokenize:EB,previous:RN},NN={name:"protocolAutolink",tokenize:vB,previous:CN},Nr={name:"emailAutolink",tokenize:yB,previous:ON},rr={};function bB(){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,NN];rr[104]=[Nr,NN];rr[87]=[Nr,_N];rr[119]=[Nr,_N];function yB(e,t,r){const a=this;let o,s;return u;function u(b){return!eg(b)||!ON.call(a,a.previous)||Wg(a.events)?r(b):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(b))}function d(b){return eg(b)?(e.consume(b),d):b===64?(e.consume(b),p):r(b)}function p(b){return b===46?e.check(hB,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 EB(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!RN.call(a,a.previous)||Wg(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(mB,e.attempt(TN,e.attempt(AN,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function vB(e,t,r){const a=this;let o="",s=!1;return u;function u(b){return(b===72||b===104)&&CN.call(a,a.previous)&&!Wg(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||Fu(b)||rt(b)||Ua(b)||nc(b)?r(b):e.attempt(TN,e.attempt(AN,h),r)(b)}function h(b){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(b)}}function SB(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 xB(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(kN,p,d)(f):f===null||rt(f)||Ua(f)||f!==45&&nc(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 wB(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(kN,t,s)(u):u===null||rt(u)||Ua(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function TB(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)||Ua(d)?t(d):r(d)}function o(d){return d===null||d===40||d===91||rt(d)||Ua(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",{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(sN,{remarkPlugins:[IN],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: "${p?.preset||"zinc"}"
radius: "${p?.radius||"md"}"
darkMode: ${p?.darkMode!==!1}`})]}),y.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),y.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(f_).map(f=>y.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${f===(p?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:f},f))}),y.jsxs("p",{className:"text-muted-foreground mb-4",children:[y.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),y.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),y.jsxs("p",{className:"text-muted-foreground mb-4",children:[y.jsx("span",{className:"font-medium text-primary",children:a.routes?.length||0})," routes configured."]}),y.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(a.routes?.slice(0,3),null,2)})]})}const ET=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,ST=XT,bD=(e,a)=>r=>{var o;if(a?.variants==null)return ST(e,r?.class,r?.className);const{variants:s,defaultVariants:u}=a,c=Object.keys(s).map(f=>{const b=r?.[f],h=u?.[f];if(b===null)return null;const v=ET(b)||ET(h);return s[f][v]}),p=r&&Object.entries(r).reduce((f,b)=>{let[h,v]=b;return v===void 0||(f[h]=v),f},{}),g=a==null||(o=a.compoundVariants)===null||o===void 0?void 0:o.reduce((f,b)=>{let{class:h,className:v,...S}=b;return Object.entries(S).every(A=>{let[N,C]=A;return Array.isArray(C)?C.includes({...u,...p}[N]):{...u,...p}[N]===C})?[...f,h,v]:f},[]);return ST(e,c,g,r?.class,r?.className)},hD=bD("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 Tw({className:e,variant:a="default",size:r="default",asChild:o=!1,...s}){const u=o?FN:"button";return y.jsx(u,{"data-slot":"button","data-variant":a,"data-size":r,className:Wn(hD({variant:a,size:r,className:e})),...s})}function tr({className:e,...a}){return y.jsx("div",{"data-slot":"card",className:Wn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...a})}function ni({className:e,...a}){return y.jsx("div",{"data-slot":"card-header",className:Wn("@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),...a})}function ai({className:e,...a}){return y.jsx("div",{"data-slot":"card-title",className:Wn("leading-none font-semibold",e),...a})}function Aw({className:e,...a}){return y.jsx("div",{"data-slot":"card-description",className:Wn("text-muted-foreground text-sm",e),...a})}function nr({className:e,...a}){return y.jsx("div",{"data-slot":"card-content",className:Wn("px-6",e),...a})}function yD({props:e}){const a=e?.title||"Statistics",r=e?.value||"0",o=e?.change||"",s=e?.changeType||"positive";return y.jsxs(tr,{className:"mb-4",children:[y.jsxs(ni,{className:"pb-2",children:[y.jsx(Aw,{children:a}),y.jsx(ai,{className:"text-2xl",children:r})]}),o&&y.jsx(nr,{className:"pt-0",children:y.jsx("span",{className:s==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:o})})]})}function ED({props:e}){const a=e?.title||"Quick Links",r=e?.links||[];return y.jsxs(tr,{className:"mb-4",children:[y.jsx(ni,{className:"pb-2",children:y.jsx(ai,{className:"text-sm font-medium",children:a})}),y.jsx(nr,{className:"space-y-1",children:r.map((o,s)=>y.jsx("a",{href:o.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:o.label},s))})]})}function SD({props:e}){const a=e?.title||"Newsletter",r=e?.buttonText||"Subscribe";return y.jsxs(tr,{className:"mb-4",children:[y.jsxs(ni,{className:"pb-2", Line truncated
`);g=h.pop()||"";for(const v of h)if(v.startsWith("data: "))try{const S=JSON.parse(v.slice(6));LD(e,S,s)}catch{}}}catch(u){if(u.name!=="AbortError"){const c=Za.get(e);c&&bn(e,{thinkingSteps:[...c.state.thinkingSteps,`Error: ${u.message}`]})}}finally{const u=Za.get(e);u&&(u.abortController=null),bn(e,{isStreaming:!1}),s?.onEnd?.()}}function LD(e,a,r){const o=Za.get(e);if(!o)return;const s=o.toolCallMap;switch(a.type){case"session":a.session_id&&typeof a.session_id=="string"&&r?.onSessionId?.(a.session_id);break;case"token":case"run_content":case"team_run_content":{const c=a.token??a.content??"";c&&bn(e,{currentResponse:o.state.currentResponse+c});break}case"message":case"run_completed":case"team_run_completed":{const c=a.content;c&&bn(e,{currentResponse:c});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const c=a.step;c&&bn(e,{thinkingSteps:[...o.state.thinkingSteps,c]});break}case"reasoning_started":case"team_reasoning_started":bn(e,{thinkingSteps:[...o.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const c={name:a.name,description:a.description,icon:a.icon,step_number:a.step_number,status:"running",tool_call_id:a.tool_call_id,args:a.args};c.tool_call_id&&s.set(c.tool_call_id,c);const p=TT(o.state.toolCalls,c);bn(e,{toolCalls:p});break}case"tool_call_completed":case"team_tool_call_completed":{const c={name:a.name,description:a.description,icon:a.icon,step_number:a.step_number,status:"done",tool_call_id:a.tool_call_id,args:a.args,formatted_result:a.formatted_result,result:a.result};c.tool_call_id&&s.set(c.tool_call_id,c);const p=TT(o.state.toolCalls,c);bn(e,{toolCalls:p});break}case"error":case"run_error":case"team_run_error":{const c=a.error;c&&bn(e,{thinkingSteps:[...o.state.thinkingSteps,`Error: ${c}`]});break}}}function TT(e,a){if(a.tool_call_id){const r=e.findIndex(o=>o.tool_call_id===a.tool_call_id);if(r>=0){const o=[...e];return o[r]={...o[r],...a},o}}return[...e,a]}function xw(e={}){const{externalSessionId:a}=e,[r,o]=z.useState(()=>is(a));z.useEffect(()=>{if(!a){o(is(null));return}return o(is(a)),RD(a,()=>{o(is(a))})},[a]);const s=z.useCallback((c,p)=>{const g=a??`new-${Date.now()}`;OD(g,c,p,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[a,e.onSessionId,e.onEnd]),u=z.useCallback(()=>{a&&CD(a)},[a]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,sendMessage:s,cancel:u}}function DD(e,a){if(e==null)return{};var r={};for(var o in e)if({}.hasOwnProperty.call(e,o)){if(a.indexOf(o)!==-1)continue;r[o]=e[o]}return r}function MD(e,a){if(e==null)return{};var r,o,s=DD(e,a);if(Object.getOwnPropertySymbols){var u=Object.getOwnPropertySymbols(e);for(o=0;o<u.length;o++)r=u[o],a.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(s[r]=e[r])}return s}function Md(e,a){(a==null||a>e.length)&&(a=e.length);for(var r=0,o=Array(a);r<a;r++)o[r]=e[r];return o}function UD(e){if(Array.isArray(e))return Md(e)}function BD(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function FD(e,a){if(e){if(typeof e=="string")return Md(e,a);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)?Md(e,a):void 0}}function zD(){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 Ud(e){return UD(e)||BD(e)||FD(e)||zD()}function So(e){"@babel/helpers - typeof";return So=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(a){return typeof a}:function(a){return a&&typeof Symbol=="function"&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},So(e)}function jD(e,a){if(So(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var o=r.call(e,a);if(So(o)!="object")return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return(a==="string"?String:Number)(e)}function GD(e){var a=jD(e,"string");return So(a)=="symbol"?a:a+""}function kw(e,a,r){return(a=GD(a))in e?Object.defineProperty(e,a,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[a]=r,e}function Bd(){return Bd=Object.assign?Object.assign.bind():function(e){for(var a=1;a<arguments.length;a++){var r=arguments[a];for(var o in r)({}).hasOwnProperty.call(r,o)&&(e[o]=r[o])}return e},Bd.apply(null,arguments)}function AT(e,a){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);a&&(o=o.filter(function(s){return Object.getOwnPropertyDescriptor(e,s).enumerable})),r.push.apply(r,o)}return r}function Zr(e){for(var a=1;a<arguments.length;a++){var r=arguments[a]!=null?arguments[a]:{};a%2?AT(Object(r),!0).forEach(function(o){kw(e,o,r[o])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):AT(Object(r)).forEach(function(o){Object.defineProperty(e,o,Object.getOwnPropertyDescriptor(r,o))})}return e}function HD(e){var a=e.length;if(a===0||a===1)return e;if(a===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(a===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(a>=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 KD(e){var a=e.codeString,r=e.codeStyle,o=e.containerStyle,s=o===void 0?{float:"left",paddingRight:"10px"}:o,u=e.numberStyle,c=u===void 0?{}:u,p=e.startingLineNumber;return Yn.createElement("code",{style:Object.assign({},r,s)},ZD({lines:a.replace(/\n$/,"").split(`
`),style:c,startingLineNumber:p}))}function QD(e){return"".concat(e.toString().length,".25em")}function Nw(e,a){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:a},children:[{type:"text",value:e}]}}function Rw(e,a,r){var o={display:"inline-block",minWidth:QD(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},s=typeof e=="function"?e(a):e,u=xa(xa({},o),s);return u}function ss(e){var a=e.children,r=e.lineNumber,o=e.lineNumberStyle,s=e.largestLineNumber,u=e.showInlineLineNumbers,c=e.lineProps,p=c===void 0?{}:c,g=e.className,f=g===void 0?[]:g,b=e.showLineNumbers,h=e.wrapLongLines,v=e.wrapLines,S=v===void 0?!1:v,A=S?xa({},typeof p=="function"?p(r):p):{};if(A.className=A.className?[].concat(Ud(A.className.trim().split(/\s+/)),Ud(f)):f,r&&u){var N=Rw(o,r,s);a.unshift(Nw(r,N))}return h&b&&(A.style=xa({display:"flex"},A.style)),{type:"element",tagName:"span",properties:A,children:a}}function Cw(e){var a=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 o=0;o<e.length;o++){var s=e[o];if(s.type==="text")r.push(ss({children:[s],className:Ud(new Set(a))}));else if(s.children){var u,c=a.concat(((u=s.properties)===null||u===void 0?void 0:u.className)||[]);Cw(s.children,c).forEach(function(p){return r.push(p)})}}return r}function JD(e,a,r,o,s,u,c,p,g){var f,b=Cw(e.value),h=[],v=-1,S=0;function A(W,I){var Y=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return ss({children:W,lineNumber:I,lineNumberStyle:p,largestLineNumber:c,showInlineLineNumbers:s,lineProps:r,className:Y,showLineNumbers:o,wrapLongLines:g,wrapLines:a})}function N(W,I){if(o&&I&&s){var Y=Rw(p,I,c);W.unshift(Nw(I,Y))}return W}function C(W,I){var Y=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return a||Y.length>0?A(W,I,Y):N(W,I)}for(var _=function(){var I=b[S],Y=I.children[0].value,P=XD(Y);if(P){var te=Y.split(`
`);te.forEach(function(D,J){var ae=o&&h.length+u,oe={type:"text",value:"".concat(D,`
`)};if(J===0){var re=b.slice(v+1,S).concat(ss({children:[oe],className:I.properties.className})),ne=C(re,ae);h.push(ne)}else if(J===te.length-1){var F=b[S+1]&&b[S+1].children&&b[S+1].children[0],K={type:"text",value:"".concat(D)};if(F){var ue=ss({children:[K],className:I.properties.className});b.splice(S+1,0,ue)}else{var he=[K],x=C(he,ae,I.properties.className);h.push(x)}}else{var O=[oe],V=C(O,ae,I.properties.className);h.push(V)}}),v=S}S++};S<b.length;)_();if(v!==b.length-1){var U=b.slice(v+1,b.length);if(U&&U.length){var M=o&&h.length+u,$=C(U,M);h.push($)}}return a?h:(f=[]).concat.apply(f,h)}function e1(e){var a=e.rows,r=e.stylesheet,o=e.useInlineStyles;return a.map(function(s,u){return _w({node:s,stylesheet:r,useInlineStyles:o,key:"code-segment-".concat(u)})})}function Ow(e){return e&&typeof e.highlightAuto<"u"}function t1(e){var a=e.astGenerator,r=e.language,o=e.code,s=e.defaultCodeValue;if(Ow(a)){var u=VD(a,r);return r==="text"?{value:s,language:"text"}:u?a.highlight(r,o):a.highlightAuto(o)}try{return r&&r!=="text"?{value:a.highlight(o,r)}:{value:s}}catch{return{value:s}}}function n1(e,a){return function(o){var s,u,c=o.language,p=o.children,g=o.style,f=g===void 0?a:g,b=o.customStyle,h=b===void 0?{}:b,v=o.codeTagProps,S=v===void 0?{className:c?"language-".concat(c):void 0,style:xa(xa({},f['code[class*="language-"]']),f['code[class*="language-'.concat(c,'"]')])}:v,A=o.useInlineStyles,N=A===void 0?!0:A,C=o.showLineNumbers,_=C===void 0?!1:C,U=o.showInlineLineNumbers,M=U===void 0?!0:U,$=o.startingLineNumber,W=$===void 0?1:$,I=o.lineNumberContainerStyle,Y=o.lineNumberStyle,P=Y===void 0?{}:Y,te=o.wrapLines,D=o.wrapLongLines,J=D===void 0?!1:D,ae=o.lineProps,oe=ae===void 0?{}:ae,re=o.renderer,ne=o.PreTag,F=ne===void 0?"pre":ne,K=o.CodeTag,ue=K===void 0?"code":K,he=o.code,x=he===void 0?(Array.isArray(p)?p[0]:p)||"":he,O=o.astGenerator,V=MD(o,YD);O=O||e;var k=_?Yn.createElement(KD,{containerStyle:I,codeStyle:S.style||{},numberStyle:P,startingLineNumber:W,codeString:x}):null,se=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},de=Ow(O)?"hljs":"prismjs",ce=N?Object.assign({},V,{style:Object.assign({},se,h)}):Object.assign({},V,{className:V.className?"".concat(de," ").concat(V.className):de,style:Object.assign({},h)});if(J?S.style=xa({whiteSpace:"pre-wrap"},S.style):S.style=xa({whiteSpace:"pre"},S.style),!O)return Yn.createElement(F,ce,k,Yn.createElement(ue,S,x));(te===void 0&&re||J)&&(te=!0),re=re||e1;var Te=[{type:"text",value:x}],ze=t1({astGenerator:O,language:c,code:x,defaultCodeValue:Te});ze.language===null&&(ze.value=Te);var Se=(s=(u=x.match(/\n/g))===null||u===void 0?void 0:u.length)!==null&&s!==void 0?s:0,mt=W+Se,Tt=JD(ze,te,oe,_,M,W,mt,P,J);return Yn.createElement(F,ce,Yn.createElement(ue,S,!M&&k,re({rows:Tt,stylesheet:f,useInlineStyles:N})))}}const a1=["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","javadoc","javadoclike","javascript","javastacktrace","jexl","jolie","jq","js-extras","js-templates","jsdoc","json","json5","jsonp","jsstacktrace","jsx","julia","keepalived","keyman","kotlin","kumir","kusto","latex","latte","less","lilypond","linker-script","liquid","lisp","livescript","llvm","log","lolcode","lua","magma","makefile","markdown","markup-templating","markup","mata","matlab","maxscript","mel","mermaid","metafont","mizar","mongodb","monkey","moonscript","n1ql","n4js","nand2tetris-hdl","naniscript","nasm","neon","nevod","nginx","nim","nix","nsis","objectivec","ocaml","odin","opencl","openqasm","oz","parigp","parser","pascal","pascaligo","pcaxis","peoplecode","perl","php-extras","php","phpdoc","plant-uml","plsql","powerquery","powershell","processing","prolog","promql","properties","protobuf","psl","pug","puppet","pure","purebasic","purescript","python","q","qml","qore","qsharp","r","racket","reason","regex","rego","renpy","rescript","rest","rip","roboconf","robotframework","ruby","rust","sas","sass","scala","scheme","scss","shell-session","smali","smalltal Line truncated
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(q1).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 $A=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,GA=s_,Kg=(e,t)=>r=>{var a;if(t?.variants==null)return GA(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=$A(h)||$A(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 GA(e,u,p,r?.class,r?.className)},LN=Kg("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?Ig:"button";return g.jsx(s,{"data-slot":"button","data-variant":t,"data-size":r,className:be(LN({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 Xg({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 WB({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 KB({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(Xg,{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 XB({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 ZB({ Line truncated
`);p=b.pop()||"";for(const v of b)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));yz(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=ja.get(e);u&&An(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=ja.get(e);s&&(s.abortController=null),An(e,{isStreaming:!1}),o?.onEnd?.()}}function yz(e,t,r){const a=ja.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=VA(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=VA(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:fz(e,t);break}}function VA(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 jN(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>yu(t));S.useEffect(()=>{if(!t){a(yu(null));return}return a(yu(t)),gz(t,()=>{a(yu(t))})},[t]);const o=S.useCallback((u,d)=>{const p=t??`new-${Date.now()}`;hz(p,u,d,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&mz(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function Ez(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 vz(e,t){if(e==null)return{};var r,a,o=Ez(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 rg(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 Sz(e){if(Array.isArray(e))return rg(e)}function xz(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function wz(e,t){if(e){if(typeof e=="string")return rg(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)?rg(e,t):void 0}}function Tz(){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 ag(e){return Sz(e)||xz(e)||wz(e)||Tz()}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 Az(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 kz(e){var t=Az(e,"string");return Tl(t)=="symbol"?t:t+""}function UN(e,t,r){return(t=kz(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function ig(){return ig=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},ig.apply(null,arguments)}function qA(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?qA(Object(r),!0).forEach(function(a){UN(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):qA(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function _z(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 jz(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)},Mz({lines:t.replace(/\n$/,"").split(`
`),style:u,startingLineNumber:d}))}function Uz(e){return"".concat(e.toString().length,".25em")}function BN(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 zN(e,t,r){var a={display:"inline-block",minWidth:Uz(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=oa(oa({},a),o);return s}function Nu(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(ag(w.className.trim().split(/\s+/)),ag(f)):f,r&&s){var T=zN(a,r,o);t.unshift(BN(r,T))}return b&h&&(w.style=oa({display:"flex"},w.style)),{type:"element",tagName:"span",properties:w,children:t}}function PN(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(Nu({children:[o],className:ag(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);PN(o.children,u).forEach(function(d){return r.push(d)})}}return r}function Fz(e,t,r,a,o,s,u,d,p){var f,h=PN(e.value),b=[],v=-1,E=0;function w(F,D){var P=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Nu({children:F,lineNumber:D,lineNumberStyle:d,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:P,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function T(F,D){if(a&&D&&o){var P=zN(d,D,u);F.unshift(BN(D,P))}return F}function k(F,D){var P=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||P.length>0?w(F,D,P):T(F,D)}for(var A=function(){var D=h[E],P=D.children[0].value,z=Dz(P);if(z){var W=P.split(`
`);W.forEach(function(M,H){var X=a&&b.length+s,ee={type:"text",value:"".concat(M,`
`)};if(H===0){var te=h.slice(v+1,E).concat(Nu({children:[ee],className:D.properties.className})),Z=k(te,X);b.push(Z)}else if(H===W.length-1){var B=h[E+1]&&h[E+1].children&&h[E+1].children[0],Y={type:"text",value:"".concat(M)};if(B){var ne=Nu({children:[Y],className:D.properties.className});h.splice(E+1,0,ne)}else{var ce=[Y],C=k(ce,X,D.properties.className);b.push(C)}}else{var j=[ee],Q=k(j,X,D.properties.className);b.push(Q)}}),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 Bz(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return FN({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function $N(e){return e&&typeof e.highlightAuto<"u"}function zz(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if($N(t)){var s=Oz(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 Pz(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,F=L===void 0?1:L,D=a.lineNumberContainerStyle,P=a.lineNumberStyle,z=P===void 0?{}:P,W=a.wrapLines,M=a.wrapLongLines,H=M===void 0?!1:M,X=a.lineProps,ee=X===void 0?{}:X,te=a.renderer,Z=a.PreTag,B=Z===void 0?"pre":Z,Y=a.CodeTag,ne=Y===void 0?"code":Y,ce=a.code,C=ce===void 0?(Array.isArray(d)?d[0]:d)||"":ce,j=a.astGenerator,Q=vz(a,Iz);j=j||e;var I=A?Ke.createElement(jz,{containerStyle:D,codeStyle:E.style||{},numberStyle:z,startingLineNumber:F,codeString:C}):null,ue=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},fe=$N(j)?"hljs":"prismjs",de=T?Object.assign({},Q,{style:Object.assign({},ue,b)}):Object.assign({},Q,{className:Q.className?"".concat(fe," ").concat(Q.className):fe,style:Object.assign({},b)});if(H?E.style=oa({whiteSpace:"pre-wrap"},E.style):E.style=oa({whiteSpace:"pre"},E.style),!j)return Ke.createElement(B,de,I,Ke.createElement(ne,E,C));(W===void 0&&te||H)&&(W=!0),te=te||Bz;var le=[{type:"text",value:C}],we=zz({astGenerator:j,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=F+me,Oe=Fz(we,W,ee,A,R,F,Re,z,H);return Ke.createElement(B,de,Ke.createElement(ne,E,!R&&I,te({rows:Oe,stylesheet:f,useInlineStyles:T})))}}const $z="modulepreload",Gz=function(e){return"/"+e},KA={},XA=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let f=function(h){return Promise.all(h.map(b=>Promise.resolve(b).then(v=>({status:"fulfilled",value:v}),v=>({status:"rejected",reason:v}))))};var u=f;document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),p=d?.nonce||d?.getAttribute("nonce");o=f(r.map(h=>{if(h=Gz(h),h in KA)return;KA[h]=!0;const b=h.endsWith(".css"),v=b?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${h}"]${v}`))return;const E=document.createElement("link");if(E.rel=b?"stylesheet":$z,b||(E.as="script"),E.crossOrigin="",E.href=h,p&&E.setAttribute("nonce",p),document.head.appendChild(E),b)return new Promise((w,T)=>{E.addEventListener("load",w),E.addEventListener("error",()=>T(new Error(`Unable to preload CSS for ${h}`)))})}))}function s(d){const p=new Event("vite:preloadError",{cancelable:!0});if(p.payload=d,window.dispatchEvent(p),!p.defaultPrevented)throw d}return o.then(d=>{for(const p of d||[])p.status==="rejected"&&s(p.reason);return t().catch(s)})},Hz=["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","j Line truncated
?|
|(?![\\s\\S])))+`,"m"),alias:u,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(o)[0]}}}}),Object.defineProperty(a.languages.diff,"PREFIXES",{value:r})})(e)}_t.displayName="markup-templating";_t.aliases=[];function _t(e){e.register(vt),(function(a){function r(o,s){return"___"+o.toUpperCase()+s+"___"}Object.defineProperties(a.languages["markup-templating"]={},{buildPlaceholders:{value:function(o,s,u,c){if(o.language===s){var p=o.tokenStack=[];o.code=o.code.replace(u,function(g){if(typeof c=="function"&&!c(g))return g;for(var f=p.length,b;o.code.indexOf(b=r(s,f))!==-1;)++f;return p[f]=g,b}),o.grammar=a.languages.markup}}},tokenizePlaceholders:{value:function(o,s){if(o.language!==s||!o.tokenStack)return;o.grammar=a.languages[s];var u=0,c=Object.keys(o.tokenStack);function p(g){for(var f=0;f<g.length&&!(u>=c.length);f++){var b=g[f];if(typeof b=="string"||b.content&&typeof b.content=="string"){var h=c[u],v=o.tokenStack[h],S=typeof b=="string"?b:b.content,A=r(s,h),N=S.indexOf(A);if(N>-1){++u;var C=S.substring(0,N),_=new a.Token(s,a.tokenize(v,o.grammar),"language-"+s,v),U=S.substring(N+A.length),M=[];C&&M.push.apply(M,p([C])),M.push(_),U&&M.push.apply(M,p([U])),typeof b=="string"?g.splice.apply(g,[f,1].concat(M)):b.content=M}}else b.content&&p(b.content)}return g}p(o.tokens)}}})})(e)}vf.displayName="django";vf.aliases=["jinja2"];function vf(e){e.register(_t),(function(a){a.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,o=a.languages["markup-templating"];a.hooks.add("before-tokenize",function(s){o.buildPlaceholders(s,"django",r)}),a.hooks.add("after-tokenize",function(s){o.tokenizePlaceholders(s,"django")}),a.languages.jinja2=a.languages.django,a.hooks.add("before-tokenize",function(s){o.buildPlaceholders(s,"jinja2",r)}),a.hooks.add("after-tokenize",function(s){o.tokenizePlaceholders(s,"jinja2")})})(e)}Tf.displayName="dns-zone-file";Tf.aliases=["dns-zone"];function Tf(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"]}Af.displayName="docker";Af.aliases=["dockerfile"];function Af(e){(function(a){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,o=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),s=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,u=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return s}),c={pattern:RegExp(s),greedy:!0},p={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function g(f,b){return f=f.replace(/<OPT>/g,function(){return u}).replace(/<SP>/g,function(){return o}),RegExp(f,b)}a.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:g(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[c,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:g(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:g(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:g(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:p,string:c,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper Line truncated
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+o+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+o),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Oo.displayName="t4-templating";Oo.aliases=[];function Oo(e){(function(a){function r(s,u,c){return{pattern:RegExp("<#"+s+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+s+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:u,alias:c}}}}function o(s){var u=a.languages[s],c="language-"+s;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("=",u,c),"class-feature":r("\\+",u,c),standard:r("",u,c)}}}}a.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:o})})(e)}Ub.displayName="t4-cs";Ub.aliases=["t4"];function Ub(e){e.register(ii),e.register(Oo),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}Us.displayName="vbnet";Us.aliases=[];function Us(e){e.register(Cs),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:/[,;:(){}]/})}Bb.displayName="t4-vb";Bb.aliases=[];function Bb(e){e.register(Oo),e.register(Us),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}Fb.displayName="tap";Fb.aliases=[];function Fb(e){e.register(Rs),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"}}}zb.displayName="tcl";zb.aliases=[];function zb(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
`);for(;a.length&&!a[0].trim();)a.shift();for(;a.length&&!a[a.length-1].trim();)a.pop();if(!a.length)return"";const r=Math.min(...a.filter(o=>o.trim().length>0).map(o=>o.match(/^ */)?.[0].length??0));return r===0?a.join(`
`):a.map(o=>o.slice(r)).join(`
`)}function D1({language:e,children:a}){const[r,o]=z.useState(!1),s=z.useCallback(async()=>{await navigator.clipboard.writeText(a),o(!0),setTimeout(()=>o(!1),2e3)},[a]);return y.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[y.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[y.jsx("span",{children:e||"code"}),y.jsx("button",{onClick:s,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),y.jsx(Bw,{style:C1,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:a})]})}function M1(e){const a=e.split(`
`);let r=!1,o=!1,s="",u=0;for(let c=0;c<a.length;c++){const p=a[c];if(/^[ \t]*```/.test(p)){r=!r;continue}if(r)continue;const g=p.match(/^([ \t]*)(\d+)\.[ \t]/);if(g){const f=g[1];!o||f!==s?(o=!0,s=f,u=1):u++,a[c]=p.replace(/^([ \t]*)\d+\./,`${f}${u}.`)}else p.trim()===""&&o||o&&/^[ \t]+\S/.test(p)||o&&(o=!1,u=0)}return a.join(`
`)}function zw({content:e}){const a=M1(e);return y.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:y.jsx(KA,{remarkPlugins:[vw],components:{ul({children:r,ordered:o,start:s}){if(o){const u=s!=null?Number(s):1;return y.jsx("ol",{start:u,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return y.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return y.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:o,...s}){const u=/language-(\w+)/.exec(r||""),c=L1(String(o));return!u&&!c.includes(`
`)?y.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...s,children:o}):y.jsx(D1,{language:u?.[1],children:c})},a({href:r,children:o}){return y.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:o})},table({children:r}){return y.jsx("div",{className:"overflow-x-auto my-3",children:y.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:a})})}function U1({message:e}){return e.role==="user"?y.jsx("div",{className:"flex justify-end",children:y.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[y.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:y.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),y.jsx(jd,{role:"user"})]})}):y.jsxs("div",{className:"flex gap-3 group",children:[y.jsx(jd,{role:"assistant"}),y.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&y.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((r,o)=>y.jsx(Fw,{toolCall:r},o))}),y.jsx(zw,{content:e.content}),e.images&&e.images.length>0&&y.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((r,o)=>y.jsx("img",{src:r,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&y.jsx("div",{className:"mt-3 flex flex-wrap gap-2",children:e.actions.map(r=>y.jsx("button",{className:"px-3 py-1.5 text-xs rounded-lg bg-background border hover:bg-accent transition-colors",children:r.label},r.name))}),y.jsx("div",{className:"mt-1 flex items-center gap-1",children:y.jsx(I1,{text:e.content})})]})]})}function B1({onSend:e,onCancel:a,isStreaming:r=!1,placeholder:o="Type a message...",enableFileUpload:s=!1}){const[u,c]=z.useState(""),[p,g]=z.useState([]),f=z.useRef(null),b=z.useRef(null),h=z.useCallback(()=>{const _=u.trim();(_||p.length>0)&&!r&&(e(_,p.length>0?p:void 0),c(""),g([]),f.current&&(f.current.style.height="auto"))},[u,p,r,e]),v=z.useCallback(_=>{const U=Array.from(_.target.files||[]);g(M=>[...M,...U]),b.current&&(b.current.value="")},[]),S=z.useCallback(()=>{b.current?.click()},[]),A=z.useCallback(_=>{g(U=>U.filter((M,$)=>$!==_))},[]),N=z.useCallback(_=>{_.key==="Enter"&&!_.shiftKey&&(_.preventDefault(),h())},[h]),C=z.useCallback(()=>{const _=f.current;_&&(_.style.height="auto",_.style.height=`${Math.min(_.scrollHeight,200)}px`)},[]);return y.jsx("div",{className:"p-4 pb-6",children:y.jsxs("div",{className:"max-w-3xl mx-auto",children:[y.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[p.length>0&&y.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:p.map((_,U)=>y.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[y.jsx("span",{className:"truncate max-w-[100px]",children:_.name}),y.jsx("button",{type:"button",onClick:()=>A(U),className:"hover:text-destructive",children:"×"})]},`${_.name}-${U}`))}),y.jsxs("div",{className:"flex items-end gap-2 p-2",children:[s&&y.jsxs(y.Fragment,{children:[y.jsx("input",{ref:b,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),y.jsx("button",{type:"button",onClick:S,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:y.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:y.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"})})})]}),y.jsx("textarea",{ref:f,value:u,onChange:_=>c(_.target.value),onKeyDown:N,onInput:C,placeholder:o,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?y.jsx("button",{type:"button",onClick:a,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:y.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:y.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):y.jsx("button",{type:"button",onClick:h,disabled:!u.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:y.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:y.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),y.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line"})]})})}function F1({starters:e,onStar Line truncated
`),h=z.useCallback(async()=>{!r.trim()||u||g(r)},[r,u,g]),v=z.useCallback(()=>{o("")},[]);return y.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[y.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[y.jsx("h1",{className:"text-xl font-semibold",children:a||"Playground"}),y.jsx("div",{className:"flex items-center gap-2",children:y.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),y.jsxs("div",{className:"flex-1 flex",children:[y.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[y.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:y.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),y.jsx("div",{className:"flex-1 p-4",children:y.jsx("textarea",{value:r,onChange:S=>o(S.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"})}),y.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:u?y.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):y.jsx("button",{onClick:h,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"})})]}),y.jsxs("div",{className:"flex-1 flex flex-col",children:[y.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[y.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),u&&y.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),y.jsx("div",{className:"flex-1 p-4 overflow-auto",children:b?y.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:b}):y.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&y.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&&y.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&y.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&y.jsx("span",{children:"✓ Tools"})]})]})}function P1(){const[e,a]=z.useState({}),[r,o]=z.useState({}),[s,u]=z.useState({}),[c,p]=z.useState(!0),[g,f]=z.useState(null),[b,h]=z.useState(null),[v,S]=z.useState("");z.useEffect(()=>{async function $(){try{const[I,Y,P]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!I.ok||!Y.ok||!P.ok)throw new Error("Failed to load manifests");const te=await I.json(),D=await Y.json();a(te),o(D),u(await P.json());const J=te.site?.theme;m_(J?.preset||"zinc",J?.darkMode!==!1,J?.radius||"md");const ae=window.location.pathname;if(ae&&ae!=="/"){const oe=re=>{for(const ne of re){if(ne.path===ae||ne.path===ae.replace(/^\//,""))return ne;if(ne.children){const F=oe(ne.children);if(F)return F}}return null};if(D.items){for(const re of D.items)if(re.children){const ne=oe(re.children);if(ne){h(ne),S(ne.path||ne.title);break}}}}}catch(I){f(I instanceof Error?I.message:"Unknown error")}finally{p(!1)}}$();const W=()=>{window.location.pathname==="/"&&(h(null),S(""))};return window.addEventListener("popstate",W),()=>window.removeEventListener("popstate",W)},[]);const A=($,W,I)=>{document.title=`${$} | ${e.site?.title||"Documentation"}`;let Y=document.querySelector('link[rel="canonical"]');Y||(Y=document.createElement("link"),Y.rel="canonical",document.head.appendChild(Y)),Y.href=window.location.origin+W;let P=document.querySelector('meta[name="description"]');P||(P=document.createElement("meta"),P.name="description",document.head.appendChild(P)),P.content=`${$} - ${e.site?.description||""}`;let te=document.querySelector('meta[property="og:title"]');te||(te=document.createElement("meta"),te.setAttribute("property","og:title"),document.head.appendChild(te)),te.content=$;let D=document.querySelector('meta[property="og:url"]');D||(D=document.createElement("meta"),D.setAttribute("property","og:url"),document.head.appendChild(D)),D.content=window.location.origin+W},N=$=>{h($),S($.path||$.title);const W=$.path||`/${$.title.toLowerCase().replace(/\s+/g,"-")}`;window.history.pushState({path:W},$.title,W),A($.title,W)};if(c)return y.jsx("div",{className:"dark flex items-center justify-center min-h-screen bg-background",children:y.jsx("span",{className:"text-muted-foreground",children:"Loading..."})});if(g)return y.jsxs("div",{className:"dark flex flex-col items-center justify-center min-h-screen gap-4 bg-background",children:[y.jsx("h2",{className:"text-xl font-semibold text-destructive",children:"Failed to load"}),y.jsx("p",{className:"text-muted-foreground",children:g} 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)}lh.displayName="django";lh.aliases=["jinja2"];function lh(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)}sh.displayName="dns-zone-file";sh.aliases=["dns-zone"];function sh(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"]}uh.displayName="docker";uh.aliases=["dockerfile"];function uh(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)}xE.displayName="t4-cs";xE.aliases=["t4"];function xE(e){e.register(to),e.register(Ul),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}hc.displayName="vbnet";hc.aliases=[];function hc(e){e.register(cc),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:/[,;:(){}]/})}wE.displayName="t4-vb";wE.aliases=[];function wE(e){e.register(Ul),e.register(hc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}TE.displayName="tap";TE.aliases=[];function TE(e){e.register(uc),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"}}}AE.displayName="tcl";AE.aliases=[];function AE(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 ${uv} if no \`max\` prop is set)
- \`null\` or \`undefined\` if the progress is indeterminate.
Defaulting to \`null\`.`}var h3=zR,b3=$R;function y3({className:e,value:t,...r}){return g.jsx(h3,{"data-slot":"progress",className:be("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(b3,{"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 Tc="Switch",[E3]=jt(Tc),[v3,S3]=E3(Tc),HR=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:Tc});return g.jsxs(v3,{scope:r,checked:A,disabled:d,children:[g.jsx(Se.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":WR(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(YR,{control:v,bubbles:!T.current,name:a,value:p,checked:A,required:u,disabled:d,form:h,style:{transform:"translateX(-100%)"}})]})});HR.displayName=Tc;var VR="SwitchThumb",qR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=S3(VR,r);return g.jsx(Se.span,{"data-state":WR(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});qR.displayName=VR;var x3="SwitchBubbleInput",YR=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}})});YR.displayName=x3;function WR(e){return e?"checked":"unchecked"}var w3=HR,T3=qR;function A3({className:e,size:t="default",...r}){return g.jsx(w3,{"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(T3,{"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 Ac="Checkbox",[k3]=jt(Ac),[_3,cv]=k3(Ac);function N3(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:Ac}),[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(_3,{scope:t,...L,children:R3(b)?b(L):a})}var KR="CheckboxTrigger",XR=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}=cv(KR,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":nC(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(CG,` {
overflow: hidden `).concat(a,`;
padding-right: `).concat(d,"px ").concat(a,`;
}
body[`).concat(qi,`] {
overflow: hidden `).concat(a,`;
overscroll-behavior: contain;
`).concat([t&&"position: relative ".concat(a,";"),r==="margin"&&`
padding-left: `.concat(o,`px;
padding-top: `).concat(s,`px;
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(""),`
}
.`).concat(Ou,` {
right: `).concat(d,"px ").concat(a,`;
}
.`).concat(Iu,` {
margin-right: `).concat(d,"px ").concat(a,`;
}
.`).concat(Ou," .").concat(Ou,` {
right: 0 `).concat(a,`;
}
.`).concat(Iu," .").concat(Iu,` {
margin-right: 0 `).concat(a,`;
}
body[`).concat(qi,`] {
`).concat(OG,": ").concat(d,`px;
}
`)},Lk=function(){var e=parseInt(document.body.getAttribute(qi)||"0",10);return isFinite(e)?e:0},XG=function(){S.useEffect(function(){return document.body.setAttribute(qi,(Lk()+1).toString()),function(){var e=Lk()-1;e<=0?document.body.removeAttribute(qi):document.body.setAttribute(qi,e.toString())}},[])},ZG=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;XG();var s=S.useMemo(function(){return YG(o)},[o]);return S.createElement(WG,{styles:KG(s,!t,o,r?"":"!important")})},mg=!1;if(typeof window<"u")try{var Tu=Object.defineProperty({},"passive",{get:function(){return mg=!0,!0}});window.addEventListener("test",Tu,Tu),window.removeEventListener("test",Tu,Tu)}catch{mg=!1}var ji=mg?{passive:!1}:!1,QG=function(e){return e.tagName==="TEXTAREA"},qC=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!QG(e)&&r[t]==="visible")},JG=function(e){return qC(e,"overflowY")},eH=function(e){return qC(e,"overflowX")},Dk=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=YC(e,a);if(o){var s=WC(e,a),u=s[1],d=s[2];if(u>d)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},tH=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},nH=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},YC=function(e,t){return e==="v"?JG(t):eH(t)},WC=function(e,t){return e==="v"?tH(t):nH(t)},rH=function(e,t){return e==="h"&&t==="rtl"?-1:1},aH=function(e,t,r,a,o){var s=rH(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=WC(e,d),w=E[0],T=E[1],k=E[2],A=T-k-s*w;(w||A)&&YC(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]},Mk=function(e){return[e.deltaX,e.deltaY]},jk=function(e){return e&&"current"in e?e.current:e},iH=function(e,t){return e[0]===t[0]&&e[1]===t[1]},oH=function(e){return`
.block-interactivity-`.concat(e,` {pointer-events: none;}
.allow-interactivity-`).concat(e,` {pointer-events: all;}
`)},lH=0,Ui=[];function sH(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(lH++)[0],s=S.useState(VC)[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=RG([e.lockRef.current],(e.shards||[]).map(jk),!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],F,D=T.target,P=Math.abs(R)>Math.abs(L)?"h":"v";if("touches"in T&&P==="h"&&D.type==="range")return!1;var z=window.getSelection(),W=z&&z.anchorNode,M=W?W===D||W.contains(D):!1;if(M)return!1;var H=Dk(P,D);if(!H)return!0;if(H?F=P:(F=P==="v"?"h":"v",H=Dk(P,D)),!H)return!1;if(!a.current&&"changedTouches"in T&&(R||L)&&(a.current=F),!F)return!0;var X=a.current||F;return aH(X,k,T,X==="h"?R:L)},[]),p=S.useCallback(function(T){var k=T;if(!(!Ui.length||Ui[Ui.length-1]!==s)){var A="deltaY"in k?Mk(k):Au(k),_=t.current.filter(function(F){return F.name===k.type&&(F.target===k.target||k.target===F.shadowParent)&&iH(F.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var R=(u.current.shards||[]).map(jk).filter(Boolean).filter(function(F){return F.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:uH(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,Mk(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 Ui.push(s),e.setCallbacks({onScrollCapture:b,onWheelCapture:b,onTouchMoveCapture:v}),document.addEventListener("wheel",p,ji),document.addEventListener("touchmove",p,ji),document.addEventListener("touchstart",h,ji),function(){Ui=Ui.filter(function(T){return T!==s}),document.removeEventListener("wheel",p,ji),document.removeEventListener("touchmove",p,ji),document.removeEventListener("touchstart",h,ji)}},[]);var E=e.removeScrollBar,w=e.inert;return S.createElement(S.Fragment,null,w?S.createElement(s,{styles:oH(o)}):null,E?S.createElement(ZG,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function uH(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const cH=FG(HC,sH);var Dc=S.forwardRef(function(e,t){return S.createElement(Lc,Jn({},e,{ref:t,sideCar:cH}))});Dc.classNames=Lc.classNames;var dH=[" ","Enter","ArrowUp","ArrowDown"],pH=[" ","Enter"],Ba="Select",[Mc,jc,fH]=bc(Ba),[uo]=jt(Ba,[fH,so]),Uc=so(),[gH,pa]=uo(Ba),[mH,hH]=uo(Ba),KC=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=Uc(t),[k,A]=S.useState(null),[_,R]=S.useState(null),[L,F]=S.useState(!1),D=Ga(f),[P,z]=en({prop:a,defaultProp:o??!1,onChange:s,caller:Ba}),[W,M]=en({prop:u,defaultProp:d,onChange:p,caller:Ba}),H=S.useRef(null),X=k?w||!!k.closest("form"):!0,[ee,te]=S.useState(new Set),Z=Array.from(ee).map(B=>B.props.value).join(";");return g.jsx(Sv,{...T,children:g.jsxs(gH,{required:E,scope:t,trigger:k,onTriggerChange:A,valueNode:_,onValueNodeChange:R,valueNodeHasChildren:L,onValueNodeHasChildrenChange:F,contentId:Rn(),value:W,onValueChange:M,open:P,onOpenChange:z,dir:D,triggerPointerDownPosRef:H,disabled:v,children:[g.jsx(Mc.Provider,{scope:t,children:g.jsx(mH,{scope:e.__scopeSelect,onNativeOptionAdd:S.useCallback(B=>{te(Y=>new Set(Y).add(B))},[]),onNativeOptionRemove:S.useCallback(B=>{te(Y=>{const ne=new Set(Y);return ne.delete(B),ne})},[]),children:r})}),X?g.jsxs(b0,{"aria-hidden":!0,required:E,tabIndex:-1,name:h,autoComplete:b,value:W,onChange:B=>M(B.target.value),disabled:v,form:w,children:[W===void 0?g.jsx("option",{value:""}):null,Array.from(ee)]},Z):null]})})};KC.displayName=Ba;var XC="SelectTrigger",ZC=S.forwardRef((e,t)=>{const{__scopeSelect:r,disabled:a=!1,...o}=e,s=Uc(r),u=pa(XC,r),d=u.disabled||a,p=_e(t,u.onTriggerChange),f=jc(r),h=S.useRef("touch"),[b,v,E]=E0(T=>{const k=f().filter(R=>!R.disabled),A=k.find(R=>R.value===u.value),_=v0(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(Ic,{asChild:!0,...s,children:g.jsx(Se.button,{type:"button",role:"combobox","aria-controls":u.contentId,"aria 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},I5="DialogDescriptionWarning",L5=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${X0(I5).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},D5=j0,M5=F0,j5=z0,U5=P0,F5=$0,B5=H0,z5=q0,P5=W0;function $5({...e}){return g.jsx(D5,{"data-slot":"dialog",...e})}function G5({...e}){return g.jsx(M5,{"data-slot":"dialog-trigger",...e})}function H5({...e}){return g.jsx(j5,{"data-slot":"dialog-portal",...e})}function V5({className:e,...t}){return g.jsx(U5,{"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 q5({className:e,children:t,showCloseButton:r=!0,...a}){return g.jsxs(H5,{"data-slot":"dialog-portal",children:[g.jsx(V5,{}),g.jsxs(F5,{"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(P5,{"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(B$,{}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function Y5({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 W5({className:e,...t}){return g.jsx(B5,{"data-slot":"dialog-title",className:be("text-lg leading-none font-semibold",e),...t})}function K5({className:e,...t}){return g.jsx(z5,{"data-slot":"dialog-description",className:be("text-muted-foreground text-sm",e),...t})}var X5=Symbol("radix.slottable");function Z5(e){const t=({children:r})=>g.jsx(g.Fragment,{children:r});return t.displayName=`${e}.Slottable`,t.__radixId=X5,t}var[zc]=jt("Tooltip",[so]),Pc=so(),Z0="TooltipProvider",Q5=700,Tg="tooltip.open",[J5,Cv]=zc(Z0),Q0=e=>{const{__scopeTooltip:t,delayDuration:r=Q5,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(J5,{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})};Q0.displayName=Z0;var kl="Tooltip",[e6,Hl]=zc(kl),J0=e=>{const{__scopeTooltip:t,children:r,open:a,defaultOpen:o,onOpenChange:s,disableHoverableContent:u,delayDuration:d}=e,p=Cv(kl,e.__scopeTooltip),f=Pc(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:P=>{P?(p.onOpen(),document.dispatchEvent(new CustomEvent(Tg))):p.onClose(),s?.(P)},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)},[_]),F=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(Sv,{...f,children:g.jsx(e6,{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?F():(window.clearTimeout(E.current),E.current=0)},[F,w]),onOpen:L,onClose:F,disableHoverableContent:w,children:r})})};J0.displayName=kl;var Ag="TooltipTrigger",eO=S.forwardRef((e,t)=>{const{__scopeTooltip:r,...a}=e,o=Hl(Ag,r),s=Cv(Ag,r),u=Pc(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(Ic,{asChild:!0,...u,children:g.jsx(Se.button, Line 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 xV({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(WN,{style:bP,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function wV(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 EO({content:e}){const t=wV(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(sN,{remarkPlugins:[IN],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=SV(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(xV,{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 TV({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(Xu,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(Xu,{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(yO,{toolCall:a},o))}),g.jsx(EO,{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(Dv,{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(gV,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(EV,{text:e.content}),g.jsx(vV,{messageId:e.id})]})]})]})}function AV({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 MV(){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[D,P,z]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!D.ok||!P.ok||!z.ok)throw new Error("Failed to load manifests");const W=await D.json(),M=await P.json();t(W),a(M),s(await z.json());const H=W.site?.theme;W1(H?.preset||"zinc",H?.darkMode!==!1,H?.radius||"md");const X=window.location.pathname;if(X&&X!=="/"){const ee=te=>{for(const Z of te){if(Z.path===X||Z.path===X.replace(/^\//,""))return Z;if(Z.children){const B=ee(Z.children);if(B)return B}}return null};if(M.items){for(const te of M.items)if(te.children){const Z=ee(te.children);if(Z){b(Z),E(Z.path||Z.title);break}}}}}catch(D){f(D instanceof Error?D.message:"Unknown error")}finally{d(!1)}}L();const F=()=>{window.location.pathname==="/"&&(b(null),E(""))};return window.addEventListener("popstate",F),()=>window.removeEventListener("popstate",F)},[]);const w=(L,F,D)=>{document.title=`${L} | ${e.site?.title||"Documentation"}`;let P=document.querySelector('link[rel="canonical"]');P||(P=document.createElement("link"),P.rel="canonical",document.head.appendChild(P)),P.href=window.location.origin+F;let z=document.querySelector('meta[name="description"]');z||(z=document.createElement("meta"),z.name="description",document.head.appendChild(z)),z.content=`${L} - ${e.site?.description||""}`;let W=document.querySelector('meta[property="og:title"]');W||(W=document.createElement("meta"),W.setAttribute("property","og:title"),document.head.appendChild(W)),W.content=L;let M=document.querySelector('meta[property="og:url"]');M||(M=document.createElement("meta"),M.setAttribute("property","og:url"),document.head.appendChild(M)),M.content=window.location.origin+F},T=L=>{b(L),E(L.path||L.title);const F=L.path||`/${L.title.toLowerCase().replace(/\s+/g,"-")}`;window.history.pushState({path:F},L.title,F),w(L.title,F)};if(u)return g.jsx("div",{className:"dark flex items-center justify-center min-h-screen bg-background",children:g.jsx("span",{className:"text-muted-foreground",children:"Loading..."})});if(p)return g.jsxs("div",{className:"dark flex flex-col items-center justify-center min-h-screen gap-4 bg-background",children:[g.jsx("h2",{className:"text-xl font-semibold text-destructive",children:"Failed to load"}),g.jsx("p",{className:"text-muted-foreground",children:p})]});const k=e.templates? Line truncated
@@ -0,0 +1 @@
const e={};throw new Error('Could not resolve "@a2ui/web_core/v0_9" imported by "@praisonaiui/react".');export{e as default};
@@ -0,0 +1 @@
const r={};throw new Error('Could not resolve "@a2ui/react/v0_9" imported by "@praisonaiui/react".');export{r as default};
@@ -0,0 +1,12 @@
/**
* Kanban dashboard plugin — registers view using shared board.js engine.
*/
(function () {
'use strict';
window.aiui.registerView('kanban', async function (container) {
const mod = await import('/plugins/views/kanban.js');
const cleanup = await mod.render(container);
return cleanup;
});
})();
@@ -0,0 +1,14 @@
{
"name": "kanban",
"label": "Kanban",
"description": "Multi-agent task board with drag-drop columns",
"icon": "📌",
"version": "0.1.0",
"group": "Work",
"tab": {
"path": "/kanban",
"position": "after:jobs-board"
},
"entry": "index.js",
"css": "styles.css"
}
@@ -0,0 +1,2 @@
/* Kanban plugin — extends built-in board styles */
.aiui-board-page-root { min-height: 400px; }
@@ -0,0 +1,32 @@
/**
* Sample dashboard plugin — registers a page view with a static board.
*/
(function () {
'use strict';
const pageId = 'sample-board';
window.aiui.registerView(pageId, async (container) => {
container.innerHTML = '';
const board = window.aiui.sdk.createBoard(container, {
fetch: async () => ({
columns: [
{
id: 'todo',
title: 'Todo',
cards: [
{ title: 'Demo task', footer: 'agent-a', badge: 'new' },
],
},
{
id: 'done',
title: 'Done',
cards: [
{ title: 'Ship modular shell', footer: 'you' },
],
},
],
}),
});
return () => board.destroy();
});
})();
@@ -0,0 +1,10 @@
{
"name": "sample-board",
"label": "Sample board",
"description": "Example dashboard plugin using aiui.board layout",
"icon": "📋",
"version": "1.0.0",
"group": "Examples",
"tab": { "path": "/sample-board", "position": "end" },
"entry": "index.js"
}
@@ -0,0 +1,433 @@
/**
* Video Studio dashboard plugin — YAML editor, lint, render jobs, MP4 player.
*/
(function () {
'use strict';
const pageId = 'video-studio';
const LINT_DEBOUNCE_MS = 300;
const SHOW_COMPOSITION_PREVIEW = false;
const REFRESH_POLL_MS = 3000;
function el(tag, attrs, children) {
const node = document.createElement(tag);
if (attrs) {
Object.entries(attrs).forEach(([k, v]) => {
if (k === 'className') node.className = v;
else if (k === 'text') node.textContent = v;
else node.setAttribute(k, v);
});
}
(children || []).forEach((c) => {
if (typeof c === 'string') node.appendChild(document.createTextNode(c));
else if (c) node.appendChild(c);
});
return node;
}
async function api(path, opts) {
return window.aiui.sdk.fetchJSON(path, opts || {});
}
window.aiui.registerView(pageId, async (container) => {
container.innerHTML = '';
const root = el('div', { id: 'video-studio-root' });
container.appendChild(root);
let projects = [];
let currentId = null;
let lintTimer = null;
let saveTimer = null;
let activeJobId = null;
let jobPollTimer = null;
let refreshPollTimer = null;
const bar = el('div', { className: 'vs-bar' });
const projectSelect = el('select', { id: 'vs-project-select' });
const btnNew = el('button', { text: 'New project' });
const btnLint = el('button', { text: 'Lint' });
const btnTest = el('button', { text: 'Visual test' });
const btnRender = el('button', { text: 'Render MP4' });
const btnReset = el('button', { text: 'Reset scene' });
const pathLabel = el('span', { className: 'vs-hint', text: '' });
const barChildren = [projectSelect, btnNew, btnLint, btnTest, btnRender, btnReset, pathLabel];
let btnPreview = null;
let compositionDetails = null;
let iframe = null;
let frameSlider = null;
let frameLabel = null;
let previewHint = null;
let durationFrames = 90;
let previewUrl = null;
if (SHOW_COMPOSITION_PREVIEW) {
btnPreview = el('button', { text: 'Start preview' });
barChildren.splice(4, 0, btnPreview);
}
barChildren.forEach(function (c) {
bar.append(c);
});
const main = el('div', { className: 'vs-main' });
const editorCol = el('div', { className: 'vs-editor' });
const textarea = el('textarea', { spellcheck: 'false' });
const issuesList = el('ul', { className: 'vs-issues' });
editorCol.append(textarea, issuesList);
const previewCol = el('div', { className: 'vs-preview' });
previewCol.append(el('p', { className: 'vs-section-label', text: 'Your video' }));
const outputHint = el('p', {
className: 'vs-hint',
text: 'Render MP4 to generate a video. It will appear here with playback controls.',
});
const outputWrap = el('div', { className: 'vs-output-wrap' });
const videoEl = el('video', {
className: 'vs-output-video',
controls: 'controls',
playsinline: 'playsinline',
preload: 'metadata',
});
videoEl.setAttribute('controls', '');
const outputActions = el('div', { className: 'vs-output-actions' });
const btnPlay = el('button', { type: 'button', text: 'Play' });
const btnDownload = el('a', { className: 'vs-download-link', text: 'Download' });
btnDownload.style.display = 'none';
outputActions.append(btnPlay, btnDownload);
outputWrap.append(videoEl, outputActions);
previewCol.append(outputHint, outputWrap);
if (SHOW_COMPOSITION_PREVIEW) {
compositionDetails = el('details', { className: 'vs-composition-details' });
const compositionSummary = el('summary', {
text: 'Optional: frame-by-frame preview (while editing YAML)',
});
previewHint = el('p', {
className: 'vs-hint',
text: 'Not the final MP4. Click Start preview in the toolbar (video engine must be running). First load may take ~30s.',
});
iframe = el('iframe', { title: 'Scene preview', sandbox: 'allow-scripts allow-same-origin' });
const transport = el('div', { className: 'vs-transport' });
frameLabel = el('span', { text: 'Frame 45 / 90' });
frameSlider = el('input', { type: 'range', min: '0', max: '89', value: '45' });
transport.append(frameLabel, frameSlider);
compositionDetails.append(compositionSummary, previewHint, iframe, transport);
previewCol.append(compositionDetails);
}
main.append(editorCol, previewCol);
const jobsStrip = el('div', { className: 'vs-jobs', text: 'No render job yet.' });
root.append(bar, main, jobsStrip);
async function loadProjects() {
const data = await api('/api/video/projects');
projects = data.projects || [];
projectSelect.innerHTML = '';
const placeholder = el('option', { value: '', text: '— Select project —' });
projectSelect.append(placeholder);
projects.forEach((p) => {
projectSelect.append(el('option', { value: p.id, text: p.name || p.id }));
});
}
async function openProject(id, opts) {
if (!id) return;
const reloadYaml = !opts || opts.reloadYaml !== false;
currentId = id;
const data = await api('/api/video/projects/' + encodeURIComponent(id));
if (reloadYaml) textarea.value = data.sceneYaml || '';
pathLabel.textContent = data.path || '';
await runLint();
await loadRenderedMp4();
if (SHOW_COMPOSITION_PREVIEW) {
try {
const compiled = await api('/api/video/compile', {
method: 'POST',
body: JSON.stringify({ projectId: id }),
});
if (compiled.ir && compiled.ir.videoConfig) {
durationFrames = compiled.ir.videoConfig.durationInFrames || 90;
frameSlider.max = String(Math.max(0, durationFrames - 1));
const start = defaultPreviewFrame();
frameSlider.value = String(start);
frameLabel.textContent = 'Frame ' + start + ' / ' + durationFrames;
}
} catch (_) {
/* compile may fail if engine down */
}
}
}
async function refreshFromAgent() {
if (!currentId) return;
await openProject(currentId, { reloadYaml: true });
}
function onStudioRefresh() {
refreshFromAgent().catch(function () {});
}
window.addEventListener('video-studio:refresh', onStudioRefresh);
async function pollStudioRefresh() {
if (!currentId || document.hidden) return;
try {
const data = await api(
'/api/video/projects/' + encodeURIComponent(currentId) + '/studio-refresh'
);
if (data.refresh) await refreshFromAgent();
} catch (_) {
/* ignore */
}
}
refreshPollTimer = setInterval(pollStudioRefresh, REFRESH_POLL_MS);
async function runLint() {
if (!currentId) return;
try {
const data = await api('/api/video/lint', {
method: 'POST',
body: JSON.stringify({ projectId: currentId, yaml: textarea.value }),
});
issuesList.innerHTML = '';
(data.issues || []).forEach((issue) => {
issuesList.append(
el('li', {
text: (issue.severity || 'error') + ': ' + (issue.path || '') + ' — ' + (issue.message || ''),
})
);
});
if (!(data.issues || []).length) {
issuesList.append(el('li', { text: 'No issues.', style: 'color:#86efac;list-style:none' }));
}
} catch (err) {
issuesList.innerHTML = '';
issuesList.append(el('li', { text: String(err.message || err) }));
}
}
function scheduleLint() {
clearTimeout(lintTimer);
lintTimer = setTimeout(runLint, LINT_DEBOUNCE_MS);
}
function scheduleSave() {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => saveScene().catch(() => {}), 800);
}
async function saveScene() {
if (!currentId) return;
await api('/api/video/projects/' + encodeURIComponent(currentId) + '/scene', {
method: 'PUT',
body: JSON.stringify({ yaml: textarea.value }),
});
}
function artifactUrl(filename) {
if (!currentId) return '';
return (
'/api/video/projects/' +
encodeURIComponent(currentId) +
'/artifacts/' +
encodeURIComponent(filename || 'out.mp4')
);
}
function showRenderedMp4(url) {
if (!url) return;
const src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 't=' + Date.now();
videoEl.src = src;
btnDownload.href = url;
btnDownload.style.display = 'inline-block';
outputHint.textContent = 'Rendered video — use Play or the player controls below.';
}
function clearRenderedMp4() {
videoEl.removeAttribute('src');
videoEl.load();
btnDownload.style.display = 'none';
btnDownload.removeAttribute('href');
outputHint.textContent =
'Render MP4 to generate a video. It will appear here with playback controls.';
}
async function loadRenderedMp4() {
if (!currentId) {
clearRenderedMp4();
return;
}
const url = artifactUrl('out.mp4');
videoEl.onloadeddata = function () {
outputHint.textContent = 'Rendered video — use Play or the player controls below.';
};
videoEl.onerror = function () {
clearRenderedMp4();
};
videoEl.src = url + '?t=' + Date.now();
}
btnPlay.addEventListener('click', () => {
if (!videoEl.src) {
alert('No rendered MP4 yet. Click Render MP4 first.');
return;
}
videoEl.play().catch(function (err) {
alert(err.message || 'Could not play video');
});
});
if (SHOW_COMPOSITION_PREVIEW) {
async function startCompositionPreview() {
if (!currentId) return alert('Select a project first.');
compositionDetails.open = true;
previewHint.textContent = 'Building preview… (may take up to a minute the first time).';
btnPreview.disabled = true;
try {
const data = await api('/api/video/preview/start', {
method: 'POST',
body: JSON.stringify({ projectId: currentId }),
});
if (data.url) {
previewUrl = data.url;
attachPreviewIframe(data.url);
previewHint.textContent = 'Frame preview ready — scrub below (hello text is clearest around frame 45).';
} else {
iframe.removeAttribute('src');
previewHint.textContent =
data.message || 'Preview failed. Start: praisonai-video serve --port 3921';
}
} catch (err) {
previewHint.textContent = 'Preview error: ' + (err.message || err);
} finally {
btnPreview.disabled = false;
}
}
function defaultPreviewFrame() {
return Math.min(45, Math.max(0, durationFrames - 1));
}
function seekFrame(n) {
frameLabel.textContent = 'Frame ' + n + ' / ' + durationFrames;
if (!iframe.contentWindow) return;
iframe.contentWindow.postMessage({ type: 'pav-seek', frame: n }, '*');
}
function attachPreviewIframe(url) {
const src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 't=' + Date.now();
iframe.onload = function () {
const start = defaultPreviewFrame();
frameSlider.value = String(start);
seekFrame(start);
};
iframe.src = src;
}
frameSlider.addEventListener('input', () => {
seekFrame(Number(frameSlider.value));
});
btnPreview.addEventListener('click', () => startCompositionPreview());
}
textarea.addEventListener('input', () => {
scheduleLint();
scheduleSave();
});
btnReset.addEventListener('click', async () => {
if (!currentId) return alert('Select a project first.');
if (!confirm('Restore the default hello scene? Unsaved edits will be lost.')) return;
const data = await api(
'/api/video/projects/' + encodeURIComponent(currentId) + '/reset',
{ method: 'POST', body: '{}' }
);
textarea.value = data.sceneYaml || '';
await runLint();
});
projectSelect.addEventListener('change', () => {
openProject(projectSelect.value).catch((e) => alert(e.message || e));
});
btnNew.addEventListener('click', async () => {
const name = prompt('Project name', 'My video') || 'My video';
const created = await api('/api/video/projects', {
method: 'POST',
body: JSON.stringify({ name }),
});
await loadProjects();
projectSelect.value = created.id;
await openProject(created.id);
});
btnLint.addEventListener('click', () => runLint());
btnTest.addEventListener('click', async () => {
if (!currentId) return alert('Select a project first.');
btnTest.disabled = true;
try {
const result = await api('/api/video/test', {
method: 'POST',
body: JSON.stringify({ projectId: currentId }),
});
alert(result.passed ? 'Visual test passed.' : 'Visual test failed.\n' + (result.output || ''));
} finally {
btnTest.disabled = false;
}
});
async function pollJob(jobId) {
const data = await api('/api/video/jobs/' + encodeURIComponent(jobId));
const prog = data.progress;
const progText = prog ? ' (' + prog.frame + '/' + prog.totalFrames + ')' : '';
jobsStrip.textContent = 'Job ' + jobId + ': ' + data.status + progText;
if (data.status === 'running' || data.status === 'queued') {
btnRender.disabled = true;
return;
}
clearInterval(jobPollTimer);
jobPollTimer = null;
btnRender.disabled = false;
if (data.status === 'succeeded' && data.downloadUrl) {
showRenderedMp4(data.downloadUrl);
jobsStrip.innerHTML = '';
jobsStrip.append('Render complete — play the video on the right.');
videoEl.play().catch(function () {});
} else if (data.error) {
jobsStrip.textContent = 'Render failed: ' + data.error;
}
}
btnRender.addEventListener('click', async () => {
if (!currentId) return alert('Select a project first.');
await saveScene();
btnRender.disabled = true;
const data = await api('/api/video/render', {
method: 'POST',
body: JSON.stringify({ projectId: currentId }),
});
activeJobId = data.jobId;
jobsStrip.textContent = 'Render queued…';
jobPollTimer = setInterval(() => pollJob(activeJobId), 1500);
pollJob(activeJobId);
});
await loadProjects();
const health = await api('/api/video/health').catch(() => ({}));
if (health.engine && health.engine.status === 'unavailable') {
pathLabel.textContent =
'Video engine not running. Start with: praisonai-video serve';
}
return () => {
clearTimeout(lintTimer);
clearTimeout(saveTimer);
clearInterval(jobPollTimer);
clearInterval(refreshPollTimer);
window.removeEventListener('video-studio:refresh', onStudioRefresh);
};
});
})();
@@ -0,0 +1,11 @@
{
"name": "video-studio",
"label": "Video",
"description": "YAML scene editor with lint, frame preview, and render jobs",
"icon": "🎬",
"version": "1.0.0",
"group": "Create",
"tab": { "path": "/video-studio", "position": "end" },
"entry": "index.js",
"css": "styles.css"
}
@@ -0,0 +1,165 @@
#video-studio-root {
display: flex;
flex-direction: column;
gap: 12px;
height: 100%;
min-height: 480px;
font-family: var(--db-font, system-ui, sans-serif);
color: var(--db-text, #e2e8f0);
}
.vs-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--db-border, #334155);
}
.vs-bar select,
.vs-bar button {
font-size: 13px;
padding: 6px 10px;
border-radius: 6px;
border: 1px solid var(--db-border, #475569);
background: var(--db-surface, #1e293b);
color: inherit;
cursor: pointer;
}
.vs-bar button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.vs-main {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
flex: 1;
min-height: 0;
}
@media (max-width: 900px) {
.vs-main {
grid-template-columns: 1fr;
}
}
.vs-editor textarea {
width: 100%;
min-height: 220px;
font-family: ui-monospace, monospace;
font-size: 12px;
padding: 10px;
border-radius: 8px;
border: 1px solid var(--db-border, #475569);
background: var(--db-bg, #0f172a);
color: inherit;
resize: vertical;
box-sizing: border-box;
}
.vs-issues {
margin-top: 8px;
font-size: 12px;
max-height: 120px;
overflow: auto;
}
.vs-issues li {
color: #f87171;
margin: 4px 0;
}
.vs-preview iframe {
width: 100%;
height: 240px;
border: 1px solid var(--db-border, #475569);
border-radius: 8px;
background: #000;
}
.vs-transport {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
.vs-transport input[type="range"] {
flex: 1;
}
.vs-jobs {
font-size: 12px;
padding: 8px;
border-radius: 8px;
background: var(--db-surface, #1e293b);
}
.vs-hint {
font-size: 12px;
opacity: 0.75;
}
.vs-section-label {
font-size: 12px;
font-weight: 600;
margin: 12px 0 4px;
opacity: 0.9;
}
.vs-output-wrap {
display: flex;
flex-direction: column;
gap: 8px;
}
.vs-output-video {
width: 100%;
max-height: 240px;
border-radius: 8px;
border: 1px solid var(--db-border, #475569);
background: #000;
}
.vs-output-actions {
display: flex;
gap: 8px;
align-items: center;
}
.vs-output-actions button,
.vs-download-link {
font-size: 13px;
padding: 6px 12px;
border-radius: 6px;
border: 1px solid var(--db-border, #475569);
background: var(--db-surface, #1e293b);
color: inherit;
cursor: pointer;
text-decoration: none;
}
.vs-download-link:hover {
text-decoration: underline;
}
.vs-composition-details {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--db-border, #334155);
}
.vs-composition-details summary {
cursor: pointer;
font-size: 12px;
font-weight: 600;
margin-bottom: 8px;
}
.vs-composition-details[open] .vs-hint {
margin-bottom: 8px;
}
@@ -0,0 +1,136 @@
/**
* Plain-JS A2UI v0.9 → DOM mapper (dashboard fallback before JSON pre).
*/
function textFromNode(node) {
if (!node || typeof node !== 'object') return '';
const text = node.text;
if (typeof text === 'string') return text;
if (text && typeof text === 'object') {
return String(text.literal ?? text.path ?? '');
}
return String(node.content ?? node.label ?? '');
}
function actionFromNode(node) {
if (!node || typeof node !== 'object') return null;
const action = node.action ?? node.onAction ?? node.event;
if (!action) return null;
if (typeof action === 'string') return { name: action };
if (typeof action === 'object') return action;
return null;
}
function renderMappedComponent(comp, surfaceId, onAction) {
const type = comp.component;
if (!type) return null;
if (type === 'Column' || type === 'Row') {
const el = document.createElement('div');
el.className = 'db-a2ui-' + type.toLowerCase();
el.style.display = 'flex';
el.style.flexDirection = type === 'Column' ? 'column' : 'row';
el.style.gap = '8px';
const children = comp.children ?? comp.childComponents ?? [];
let hasChild = false;
for (const child of children) {
const childEl = renderMappedComponent(child, surfaceId, onAction);
if (childEl) {
el.appendChild(childEl);
hasChild = true;
}
}
return hasChild ? el : null;
}
if (type === 'Card') {
const el = document.createElement('div');
el.className = 'db-a2ui-card db-card';
el.style.padding = '12px';
const title = textFromNode(comp);
if (title) {
const h = document.createElement('div');
h.className = 'db-a2ui-text';
h.style.fontWeight = '600';
h.textContent = title;
el.appendChild(h);
}
const children = comp.children ?? comp.childComponents ?? [];
for (const child of children) {
const childEl = renderMappedComponent(child, surfaceId, onAction);
if (childEl) el.appendChild(childEl);
}
return el;
}
if (type === 'Text') {
const el = document.createElement('p');
el.className = 'db-a2ui-text';
el.textContent = textFromNode(comp);
return el;
}
if (type === 'Markdown') {
const el = document.createElement('div');
el.className = 'db-a2ui-markdown';
el.textContent = textFromNode(comp);
return el;
}
if (type === 'Divider') {
const el = document.createElement('hr');
el.className = 'db-a2ui-divider';
return el;
}
if (type === 'Image') {
const el = document.createElement('img');
el.className = 'db-a2ui-image';
const url = comp.url?.literal ?? comp.url;
el.src = String(url ?? '');
el.alt = textFromNode(comp);
return el;
}
if (type === 'Button') {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'db-btn db-a2ui-button';
btn.textContent = textFromNode(comp);
const action = actionFromNode(comp);
const componentId = comp.id ?? comp.componentId ?? null;
if (action || componentId) {
btn.addEventListener('click', () => {
if (typeof onAction === 'function') {
onAction({
component_id: componentId,
action: action?.name ?? action?.type ?? 'click',
data: action?.payload ?? action ?? {},
});
}
});
}
return btn;
}
return null;
}
/**
* Map A2UI messages to a DOM fragment. Returns null if unmapped types present.
*/
export function a2uiMessagesToFragment(messages, surfaceId, onAction) {
const wrap = document.createDocumentFragment();
let sawMapped = false;
for (const msg of messages || []) {
const update = msg.updateComponents;
const components = update?.components ?? update?.componentList;
if (!Array.isArray(components)) continue;
for (const comp of components) {
const el = renderMappedComponent(comp, surfaceId, onAction);
if (el) {
wrap.appendChild(el);
sawMapped = true;
}
}
}
if (!sawMapped) return null;
return wrap;
}
@@ -4,6 +4,7 @@
* Provides full CRUD for agent management with an editor interface.
*/
import { showConfirm } from './toast.js';
import { pageToolbar } from './views/_helpers.js';
let agentsData = null;
let modelsData = null;
@@ -281,12 +282,7 @@ function renderAgentsUI() {
if (!agentsData || agentsData.agents.length === 0) {
container.innerHTML = `
<div class="aiui-agents-header">
<h2>Agents</h2>
<button class="aiui-btn aiui-btn-primary" onclick="window.aiuiCreateAgent()">
+ New Agent
</button>
</div>
${pageToolbar('Agents', '<button class="aiui-btn aiui-btn-primary" onclick="window.aiuiCreateAgent()">+ New Agent</button>')}
<div class="aiui-agents-empty">
<div class="aiui-empty-icon">🤖</div>
<h3>No Agents</h3>
@@ -302,15 +298,7 @@ function renderAgentsUI() {
const cards = agentsData.agents.map(renderAgentCard).join('');
container.innerHTML = `
<div class="aiui-agents-header">
<div class="aiui-agents-title">
<h2>Agents</h2>
<span class="aiui-agents-count">${agentsData.count} agents</span>
</div>
<button class="aiui-btn aiui-btn-primary" onclick="window.aiuiCreateAgent()">
+ New Agent
</button>
</div>
${pageToolbar(`Agents <span class="aiui-agents-count">${agentsData.count} agents</span>`, '<button class="aiui-btn aiui-btn-primary" onclick="window.aiuiCreateAgent()">+ New Agent</button>')}
<div class="aiui-agents-grid">
${cards}
</div>
@@ -0,0 +1,351 @@
/**
* Interactive kanban board engine — DRY composable layer for PraisonAIUI.
*
* Used by built-in views (kanban, jobs-board) and dashboard-plugins/kanban.
* Reuses existing card/column CSS classes from dashboard.js (.aiui-board-*).
*/
const STATUS_COLOURS = {
triage: '#71717a',
todo: '#3b82f6',
ready: '#8b5cf6',
running: '#f59e0b',
blocked: '#ef4444',
review: '#06b6d4',
done: '#22c55e',
queued: '#3b82f6',
succeeded: '#22c55e',
failed: '#ef4444',
cancelled: '#71717a',
};
function el(tag, attrs, children) {
const node = document.createElement(tag);
if (attrs) {
Object.entries(attrs).forEach(([k, v]) => {
if (k === 'className') node.className = v;
else if (k === 'text') node.textContent = v;
else if (k.startsWith('on') && typeof v === 'function') node.addEventListener(k.slice(2).toLowerCase(), v);
else if (v != null) node.setAttribute(k, v);
});
}
(children || []).forEach((c) => {
if (c != null) node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
});
return node;
}
function cardElement(card, { selectable, selected, onSelect, onOpen }) {
const wrap = el('div', {
className: `aiui-board-card${selected ? ' aiui-board-card-selected' : ''}`,
draggable: selectable ? 'true' : 'false',
'data-task-id': card.id || '',
'data-status': card.status || '',
});
const inner = el('div', { className: 'db-card' });
if (card.title) inner.appendChild(el('div', { className: 'db-card-title', text: card.title }));
if (card.value != null) inner.appendChild(el('div', { className: 'db-card-value', text: String(card.value) }));
const footer = card.footer || card.assignee || '';
if (footer) inner.appendChild(el('div', { className: 'db-card-footer', text: footer }));
if (card.priority) {
inner.appendChild(el('span', {
className: 'aiui-board-badge',
text: String(card.priority),
style: 'font-size:10px;margin-left:6px;opacity:0.8',
}));
}
wrap.appendChild(inner);
wrap.addEventListener('click', (e) => {
if (e.shiftKey && onSelect) onSelect(card.id, true);
else if (onOpen) onOpen(card);
});
if (selectable) {
wrap.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', card.id || '');
e.dataTransfer.effectAllowed = 'move';
wrap.classList.add('aiui-board-dragging');
});
wrap.addEventListener('dragend', () => wrap.classList.remove('aiui-board-dragging'));
}
return wrap;
}
function columnElement(col, cards, opts) {
const colEl = el('div', {
className: 'aiui-board-column',
'data-column-id': col.id,
});
const dot = STATUS_COLOURS[col.id] ? `color:${STATUS_COLOURS[col.id]}` : '';
const title = el('div', { className: 'aiui-board-column-title' });
title.innerHTML = `<span style="${dot}">●</span> ${col.title || col.id} (${cards.length})`;
colEl.appendChild(title);
const cardsWrap = el('div', { className: 'aiui-board-column-cards' });
if (opts.laneByProfile && col.id === 'running') {
const lanes = {};
cards.forEach((c) => {
const lane = c.assignee || c.meta?.assignee || 'unassigned';
(lanes[lane] ||= []).push(c);
});
Object.entries(lanes).forEach(([lane, laneCards]) => {
const laneEl = el('div', { className: 'aiui-board-lane' });
laneEl.appendChild(el('div', { className: 'aiui-board-lane-title', text: lane }));
const laneCardsWrap = el('div', { className: 'aiui-board-column-cards' });
laneCards.forEach((c) => laneCardsWrap.appendChild(
cardElement(c, {
selectable: opts.interactive,
selected: opts.selected?.has(c.id),
onSelect: opts.onSelect,
onOpen: opts.onOpen,
}),
));
laneEl.appendChild(laneCardsWrap);
cardsWrap.appendChild(laneEl);
});
} else {
cards.forEach((c) => cardsWrap.appendChild(
cardElement(c, {
selectable: opts.interactive,
selected: opts.selected?.has(c.id),
onSelect: opts.onSelect,
onOpen: opts.onOpen,
}),
));
}
if (opts.interactive) {
cardsWrap.addEventListener('dragover', (e) => { e.preventDefault(); cardsWrap.classList.add('aiui-board-drop-target'); });
cardsWrap.addEventListener('dragleave', () => cardsWrap.classList.remove('aiui-board-drop-target'));
cardsWrap.addEventListener('drop', async (e) => {
e.preventDefault();
cardsWrap.classList.remove('aiui-board-drop-target');
const taskId = e.dataTransfer.getData('text/plain');
if (taskId && opts.onMove) await opts.onMove(taskId, col.id);
});
}
colEl.appendChild(cardsWrap);
return colEl;
}
function renderBoardDOM(container, data, opts) {
container.innerHTML = '';
const wrap = el('div', { className: 'aiui-board' });
const row = el('div', { className: 'aiui-board-columns' });
(data.columns || []).forEach((col) => {
row.appendChild(columnElement(col, col.cards || [], opts));
});
wrap.appendChild(row);
if (opts.showTrash && opts.interactive) {
const trash = el('div', {
className: 'aiui-board-trash',
text: '🗑 Drop to delete',
});
trash.addEventListener('dragover', (e) => { e.preventDefault(); trash.classList.add('aiui-board-drop-target'); });
trash.addEventListener('dragleave', () => trash.classList.remove('aiui-board-drop-target'));
trash.addEventListener('drop', async (e) => {
e.preventDefault();
trash.classList.remove('aiui-board-drop-target');
const taskId = e.dataTransfer.getData('text/plain');
if (taskId && opts.onDelete) await opts.onDelete(taskId);
});
wrap.appendChild(trash);
}
container.appendChild(wrap);
return wrap;
}
function openTaskDrawer(card, { apiBase, onClose, onRefresh }) {
const overlay = el('div', { className: 'aiui-board-drawer-overlay' });
const drawer = el('div', { className: 'aiui-board-drawer' });
const close = () => overlay.remove();
drawer.appendChild(el('button', {
className: 'aiui-board-drawer-close',
text: '×',
onclick: close,
}));
drawer.appendChild(el('h3', { text: card.title || 'Task' }));
if (card.body) drawer.appendChild(el('pre', { className: 'aiui-board-drawer-body', text: card.body }));
else drawer.appendChild(el('p', { className: 'aiui-board-drawer-meta', text: `ID: ${card.id || '—'}` }));
const meta = el('div', { className: 'aiui-board-drawer-meta' });
meta.textContent = [card.status, card.assignee, card.priority].filter(Boolean).join(' · ');
drawer.appendChild(meta);
if (apiBase && card.id) {
const actions = el('div', { className: 'aiui-board-drawer-actions' });
['blocked', 'ready', 'done'].forEach((st) => {
actions.appendChild(el('button', {
className: 'db-btn db-btn-sm',
text: st,
onclick: async () => {
await fetch(`${apiBase}/tasks/${card.id}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: st }),
});
if (onRefresh) await onRefresh();
close();
if (onClose) onClose();
},
}));
});
drawer.appendChild(actions);
}
overlay.appendChild(drawer);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
}
/**
* Interactive board with DnD, bulk toolbar, drawer, optional SSE refresh.
*/
export function createInteractiveBoard(root, opts = {}) {
let destroyed = false;
let pollTimer = null;
let eventSource = null;
const selected = new Set();
let lastData = null;
const toolbar = el('div', { className: 'aiui-board-toolbar' });
root.appendChild(toolbar);
const boardHost = el('div', { className: 'aiui-board-host' });
root.appendChild(boardHost);
const apiBase = (opts.apiBase || '/api/kanban').replace(/\/$/, '');
async function defaultMove(taskId, toStatus) {
const res = await fetch(`${apiBase}/tasks/${taskId}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: toStatus }),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
async function defaultDelete(taskId) {
const res = await fetch(`${apiBase}/tasks/${taskId}`, { method: 'DELETE' });
if (!res.ok) throw new Error(await res.text());
}
async function defaultBulk(status) {
if (!selected.size) return;
const res = await fetch(`${apiBase}/tasks/bulk`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_ids: [...selected], status }),
});
if (!res.ok) throw new Error(await res.text());
selected.clear();
updateToolbar();
}
function updateToolbar() {
toolbar.innerHTML = '';
if (!opts.interactive) return;
if (selected.size) {
toolbar.appendChild(el('span', { text: `${selected.size} selected`, className: 'aiui-board-toolbar-label' }));
['ready', 'done', 'archived'].forEach((st) => {
toolbar.appendChild(el('button', {
className: 'db-btn db-btn-sm',
text: `Move → ${st}`,
onclick: async () => { try { await defaultBulk(st); await refresh(); } catch (e) { alert(e.message); } },
}));
});
toolbar.appendChild(el('button', {
className: 'db-btn db-btn-sm',
text: 'Clear',
onclick: () => { selected.clear(); updateToolbar(); refresh(); },
}));
}
if (opts.boardSwitcher && lastData?.boards) {
const sel = el('select', { className: 'aiui-board-board-select' });
const activeBoard = opts.boardState?.id || opts.currentBoard || 'default';
(lastData.boards || []).forEach((b) => {
const opt = el('option', { value: b.id, text: b.title || b.id });
if (b.id === activeBoard) opt.selected = true;
sel.appendChild(opt);
});
sel.addEventListener('change', () => {
if (opts.boardState) opts.boardState.id = sel.value;
else opts.currentBoard = sel.value;
refresh();
});
toolbar.appendChild(sel);
}
}
function onSelect(taskId, shift) {
if (!taskId) return;
if (shift) {
if (selected.has(taskId)) selected.delete(taskId);
else selected.add(taskId);
} else {
selected.clear();
selected.add(taskId);
}
updateToolbar();
refresh();
}
async function refresh() {
if (destroyed || !opts.fetch) return;
try {
const data = await opts.fetch();
lastData = data;
boardHost.innerHTML = '';
renderBoardDOM(boardHost, data, {
interactive: opts.interactive !== false,
laneByProfile: !!opts.laneByProfile,
showTrash: opts.showTrash !== false,
selected,
onSelect: opts.interactive !== false ? onSelect : null,
onOpen: (card) => openTaskDrawer(card, { apiBase: opts.drawerApiBase || apiBase, onRefresh: refresh }),
onMove: async (taskId, status) => {
await (opts.onMove || defaultMove)(taskId, status);
await refresh();
},
onDelete: async (taskId) => {
await (opts.onDelete || defaultDelete)(taskId);
await refresh();
},
});
updateToolbar();
} catch (e) {
boardHost.innerHTML = `<div class="db-alert db-alert-error">${e.message}</div>`;
}
}
function connectEvents() {
if (!opts.eventsUrl || typeof EventSource === 'undefined') return;
const url = opts.eventsUrl + (opts.eventsUrl.includes('?') ? '&' : '?') + `board=${encodeURIComponent(opts.currentBoard || 'default')}`;
try {
eventSource = new EventSource(url);
eventSource.onmessage = () => refresh();
} catch (_) { /* polling fallback */ }
}
refresh();
if (opts.pollMs) pollTimer = setInterval(refresh, opts.pollMs);
connectEvents();
return {
refresh,
destroy() {
destroyed = true;
if (pollTimer) clearInterval(pollTimer);
if (eventSource) eventSource.close();
root.innerHTML = '';
},
};
}
/** Read-only polling board — backward-compatible with sdk.createBoard. */
export function createBoard(root, opts = {}) {
return createInteractiveBoard(root, { ...opts, interactive: false, showTrash: false });
}
export { renderBoardDOM, el };
@@ -0,0 +1,35 @@
/**
* CanvasPreviewComponent — dedicated live A2UI preview panel.
*/
import {
connectSurfaceWs,
disconnectSurfaceWs,
loadSurface,
renderSurfaceContent,
} from './surface-utils.js';
let _mount = null;
export function mountPreview(host, options) {
if (!host) return;
unmountPreview();
const surfaceId = options?.surfaceId || 'main';
_mount = { host, surfaceId };
loadSurface(host, surfaceId);
connectSurfaceWs(host, surfaceId);
}
export function refreshPreview() {
if (!_mount) return;
return loadSurface(_mount.host, _mount.surfaceId);
}
export function updatePreviewMessages(messages) {
if (!_mount) return;
renderSurfaceContent(_mount.host, _mount.surfaceId, messages || []);
}
export function unmountPreview() {
disconnectSurfaceWs();
_mount = null;
}
@@ -24,6 +24,8 @@ let _activeCleanup = null; // cleanup function for the active view
// Paths are relative to /plugins/ where dashboard.js is served from
const BUILTIN_VIEWS = {
chat: '/plugins/views/chat.js',
'chat-canvas': '/plugins/views/chat-canvas.js',
canvas: '/plugins/views/canvas.js',
overview: '/plugins/views/overview.js',
agents: '/plugins/views/agents.js',
sessions: '/plugins/views/sessions.js',
@@ -49,6 +51,59 @@ const BUILTIN_VIEWS = {
traces: '/plugins/views/traces.js',
security: '/plugins/views/security.js',
'theme-picker': '/plugins/views/theme-picker.js',
feedback: '/plugins/views/feedback.js',
jobs: '/plugins/views/jobs.js',
kanban: '/plugins/views/kanban.js',
'jobs-board': '/plugins/views/jobs-board.js',
auth: '/plugins/views/auth.js',
api: '/plugins/views/api.js',
};
const SLOT_REGISTRY = {};
let _dashboardPluginManifests = [];
const aiuiSdk = {
version: '1',
async fetchJSON(url, options) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
},
el(tag, attrs, children) {
const node = document.createElement(tag);
if (attrs) {
Object.entries(attrs).forEach(([k, v]) => {
if (k === 'className') node.className = v;
else if (k === 'text') node.textContent = v;
else if (k.startsWith('on') && typeof v === 'function') node.addEventListener(k.slice(2).toLowerCase(), v);
else node.setAttribute(k, v);
});
}
(children || []).forEach((c) => { if (c != null) node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); });
return node;
},
themeVar(name, fallback) {
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return v || fallback || '';
},
createBoard(root, opts) {
let destroyed = false;
const wrap = document.createElement('div');
wrap.className = 'aiui-board';
root.appendChild(wrap);
async function refresh() {
if (destroyed || !opts?.fetch) return;
try {
const data = await opts.fetch();
wrap.innerHTML = '';
if (data?._components) data._components.forEach((c) => wrap.appendChild(renderComponent(c)));
else if (data?.columns) wrap.appendChild(renderBoard({ type: 'board', columns: data.columns }));
} catch (e) { wrap.innerHTML = `<div class="db-alert db-alert-error">${e.message}</div>`; }
}
refresh();
const interval = opts?.pollMs ? setInterval(refresh, opts.pollMs) : null;
return { refresh, destroy() { destroyed = true; if (interval) clearInterval(interval); wrap.remove(); } };
},
};
// Public API for extending the dashboard (protocol-first, extendable)
@@ -60,7 +115,16 @@ window.aiui.views = VIEW_REGISTRY;
window.aiui.registerComponent = function(type, renderFn) {
COMPONENT_REGISTRY[type] = renderFn;
};
const SURFACE_REGISTRY = {};
window.aiui.registerSurfaceRenderer = function(surfaceId, renderFn) {
SURFACE_REGISTRY[surfaceId] = renderFn;
};
window.aiui.surfaces = SURFACE_REGISTRY;
window.aiui.version = '1';
window.aiui.components = COMPONENT_REGISTRY;
window.aiui.registerSlot = function (name, renderFn) { SLOT_REGISTRY[name] = renderFn; };
window.aiui.slots = SLOT_REGISTRY;
window.aiui.sdk = aiuiSdk;
// ── Sidebar State ────────────────────────────────────────────────
let sidebarCollapsed = false;
@@ -188,6 +252,50 @@ const DASHBOARD_STYLE = `
/* Component layout helpers */
.db-columns { display: grid; gap: 16px; }
.aiui-board-columns { display: flex; gap: 12px; align-items: start; overflow-x: auto; padding-bottom: 8px; }
.aiui-board-column { flex: 0 0 280px; display: flex; flex-direction: column; gap: 8px;
background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); padding: 10px; min-height: 120px; }
.aiui-board-column-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin: 0 0 4px; }
.aiui-board-column-cards { display: flex; flex-direction: column; gap: 8px; }
.aiui-board-card .db-card { margin: 0; }
.aiui-board-card-selected .db-card { outline: 2px solid var(--db-accent); }
.aiui-board-dragging { opacity: 0.5; }
.aiui-board-drop-target { background: var(--db-accent-glow); border-radius: var(--db-radius); min-height: 40px; }
.aiui-board-trash {
margin-top: 16px; padding: 12px; text-align: center; border: 2px dashed var(--db-border);
border-radius: var(--db-radius); color: var(--db-text-dim); font-size: 13px;
}
.aiui-board-toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
.aiui-board-toolbar-label { font-size: 13px; color: var(--db-text-dim); }
.aiui-board-lane { margin-bottom: 8px; padding: 8px; background: rgba(255,255,255,0.02); border-radius: var(--db-radius); }
.aiui-board-lane-title { font-size: 11px; font-weight: 600; color: var(--db-text-dim); margin-bottom: 6px; text-transform: uppercase; }
.aiui-board-drawer-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 9999;
display: flex; justify-content: flex-end;
}
.aiui-board-drawer {
width: min(420px, 90vw); height: 100%; background: var(--db-sidebar-bg);
border-left: 1px solid var(--db-border); padding: 20px; overflow-y: auto; position: relative;
}
.aiui-board-drawer-close {
position: absolute; top: 12px; right: 12px; background: none; border: none;
color: var(--db-text); font-size: 24px; cursor: pointer;
}
.aiui-board-drawer-body { white-space: pre-wrap; font-size: 13px; color: var(--db-text-dim); }
.aiui-board-drawer-meta { font-size: 12px; color: var(--db-text-dim); margin: 8px 0; }
.aiui-board-drawer-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.aiui-board-page-header { margin-bottom: 8px; }
.db-slot-host { min-height: 0; }
.db-sidebar-footer { margin-top: auto; padding: 10px 14px; border-top: 1px solid var(--db-border); font-size: 11px; color: var(--db-text-dim); }
.db-mobile-header { display: none; position: fixed; top: 0; left: 0; right: 0; z-index: 40; min-height: 48px; align-items: center; gap: 8px; padding: 8px 12px; background: var(--db-sidebar-bg); border-bottom: 1px solid var(--db-border); }
.db-mobile-overlay { display: none; position: fixed; inset: 0; z-index: 35; background: rgba(0,0,0,0.5); }
@media (max-width: 1023px) {
.db-mobile-header { display: flex; }
.db-sidebar { position: fixed; z-index: 45; transform: translateX(-100%); transition: transform 0.2s ease; }
.db-sidebar.db-sidebar-open { transform: translateX(0); }
.db-mobile-overlay.db-visible { display: block; }
.db-main { padding-top: 48px; }
}
.db-card {
background: var(--db-card-bg); border: 1px solid var(--db-border);
border-radius: var(--db-radius); padding: 20px;
@@ -608,6 +716,71 @@ async function applyThemeFromConfig(cfg) {
themeStyle.textContent = cssText;
}
function renderSlot(name, parent) {
const fn = SLOT_REGISTRY[name];
if (!fn || !parent) return;
const host = document.createElement('div');
host.className = 'db-slot-host';
host.dataset.slot = name;
try { const node = fn(); if (node) host.appendChild(node); } catch (e) { console.warn(`[AIUI] Slot '${name}':`, e); }
parent.appendChild(host);
}
async function loadDashboardPlugins() {
_dashboardPluginManifests = [];
try {
const res = await fetch('/api/dashboard/plugins');
if (!res.ok) return;
_dashboardPluginManifests = (await res.json()).plugins || [];
} catch (e) { console.warn('[AIUI] Dashboard plugins:', e); return; }
for (const manifest of _dashboardPluginManifests) {
if (manifest.css) {
const href = `/dashboard-plugins/${manifest.name}/${manifest.css}`;
if (!document.querySelector(`link[data-aiui-plugin-css="${manifest.name}"]`)) {
const link = document.createElement('link');
link.rel = 'stylesheet'; link.href = href; link.dataset.aiuiPluginCss = manifest.name;
document.head.appendChild(link);
}
}
if (manifest.entry && !document.querySelector(`script[data-aiui-plugin="${manifest.name}"]`)) {
await new Promise((resolve) => {
const script = document.createElement('script');
script.src = `/dashboard-plugins/${manifest.name}/${manifest.entry}`;
script.async = true; script.dataset.aiuiPlugin = manifest.name;
script.onload = script.onerror = () => resolve();
document.head.appendChild(script);
});
}
if (manifest.page && !pagesData.find((pg) => pg.id === manifest.page.id)) pagesData.push(manifest.page);
}
}
function setupMobileNav(root, sidebar) {
if (!sidebar) return;
let overlay = document.getElementById('db-mobile-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'db-mobile-overlay'; overlay.className = 'db-mobile-overlay';
overlay.addEventListener('click', () => { sidebar.classList.remove('db-sidebar-open'); overlay.classList.remove('db-visible'); });
root.insertBefore(overlay, root.firstChild);
}
if (!document.getElementById('db-mobile-header')) {
const header = document.createElement('div');
header.id = 'db-mobile-header'; header.className = 'db-mobile-header';
const menuBtn = document.createElement('button');
menuBtn.type = 'button'; menuBtn.textContent = '☰';
menuBtn.style.cssText = 'background:transparent;border:1px solid var(--db-border);color:var(--db-text);border-radius:6px;padding:6px 10px;cursor:pointer';
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('db-sidebar-open');
overlay.classList.toggle('db-visible', sidebar.classList.contains('db-sidebar-open'));
});
const title = document.createElement('span'); title.id = 'db-mobile-title'; title.style.fontWeight = '600';
header.appendChild(menuBtn); header.appendChild(title);
root.insertBefore(header, overlay.nextSibling);
}
}
async function init() {
// Inject styles
const style = document.createElement('style');
@@ -623,8 +796,7 @@ async function init() {
console.warn('[AIUI] Failed to load theme config:', e);
}
// Build the dashboard (after theme is applied)
buildDashboard();
await buildDashboard();
}
async function buildDashboard() {
@@ -648,6 +820,8 @@ async function buildDashboard() {
pagesData = pagesJson.pages || [];
const uiConfig = await configRes.json();
dashboardConfig = uiConfig.dashboard || {};
window.__aiuiDashboardModules = dashboardConfig.modules || [];
await loadDashboardPlugins();
} catch (e) {
root.innerHTML = '<div class="db-loading"><p>Failed to load pages.</p></div>';
return;
@@ -668,11 +842,20 @@ async function buildDashboard() {
root.innerHTML = '';
let sidebar = null;
if (showSidebar) {
const sidebar = buildSidebar(pagesData);
sidebar = buildSidebar(pagesData);
const slotFooter = document.createElement('div');
slotFooter.className = 'db-sidebar-footer';
renderSlot('shell:sidebar:footer', slotFooter);
if (slotFooter.children.length) sidebar.appendChild(slotFooter);
root.appendChild(sidebar);
setupMobileNav(root, sidebar);
}
const shellHeader = document.createElement('div');
shellHeader.id = 'db-shell-header-slot';
renderSlot('shell:header', shellHeader);
if (shellHeader.children.length) root.insertBefore(shellHeader, main);
root.appendChild(main);
// Resolve initial page from URL path or default to first page
@@ -838,17 +1021,27 @@ async function selectPage(pageId) {
// Set page header (controlled by dashboard config protocol)
const showPageHeader = main.dataset.showPageHeader !== 'false';
const toolbarSlotId = `page:${pageId}:toolbar`;
const headerHtml = showPageHeader ? `
<div class="db-page-header">
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap">
<div>
<h1 class="db-page-title">${page.icon || ''} ${page.title}</h1>
${page.description ? `<p class="db-page-desc">${page.description}</p>` : ''}
</div>
<div id="db-page-toolbar-slot" class="db-slot-host" data-slot="${toolbarSlotId}"></div>
</div>
</div>
` : '';
main.innerHTML = `
${headerHtml}
<div data-page="${pageId}" id="db-page-content"></div>
`;
main.innerHTML = `${headerHtml}<div data-page="${pageId}" id="db-page-content"></div>`;
if (showPageHeader) {
const toolbarHost = document.getElementById('db-page-toolbar-slot');
if (toolbarHost && SLOT_REGISTRY[toolbarSlotId]) {
try { const node = SLOT_REGISTRY[toolbarSlotId](); if (node) toolbarHost.appendChild(node); } catch (e) {}
}
}
const mobileTitle = document.getElementById('db-mobile-title');
if (mobileTitle) mobileTitle.textContent = page.title || '';
const container = document.querySelector(`[data-page="${pageId}"]`);
if (!container) return;
@@ -891,6 +1084,8 @@ async function selectPage(pageId) {
window.dispatchEvent(new CustomEvent('aiui:page-change', { detail: { pageId, page } }));
}
window.aiui.selectPage = selectPage;
async function loadGenericViewer(page, container) {
const endpoint = page.api_endpoint || `/api/pages/${page.id}/data`;
try {
@@ -907,6 +1102,23 @@ async function loadGenericViewer(page, container) {
* Structured format: { _components: [ { type: "card", ... }, { type: "columns", ... } ] }
*/
function renderComponents(data, container) {
if (data && data._surface) {
const sid = data._surface.id || 'main';
const wrap = document.createElement('div');
wrap.className = 'db-surface-host';
wrap.dataset.surfaceId = sid;
if (SURFACE_REGISTRY[sid]) {
try { SURFACE_REGISTRY[sid](wrap, data._surface.messages || []); } catch (e) { console.warn('Surface renderer error:', e); }
} else {
const pre = document.createElement('pre');
pre.className = 'db-a2ui-fallback';
pre.textContent = JSON.stringify(data._surface.messages || [], null, 2);
wrap.appendChild(pre);
}
container.innerHTML = '';
container.appendChild(wrap);
return;
}
if (data && data._components) {
// Structured component rendering
container.innerHTML = '';
@@ -970,6 +1182,17 @@ function renderComponent(comp) {
case 'dialog': return renderDialog(comp);
case 'caption': return renderCaption(comp);
case 'html_embed': return renderHtmlEmbed(comp);
case 'a2ui_surface': {
const wrap = document.createElement('div');
wrap.className = 'db-a2ui-surface';
const sid = comp.surface_id || 'main';
if (SURFACE_REGISTRY[sid]) {
try { SURFACE_REGISTRY[sid](wrap, comp.messages || []); } catch (e) { console.warn(e); }
} else {
wrap.innerHTML = '<pre>' + JSON.stringify(comp.messages || [], null, 2) + '</pre>';
}
return wrap;
}
case 'skeleton': return renderSkeleton(comp);
case 'tooltip_wrap': return renderTooltipWrap(comp);
case 'time_input': return renderTimeInput(comp);
@@ -979,6 +1202,7 @@ function renderComponent(comp) {
case 'key_value_list': return renderKeyValueList(comp);
case 'popover': return renderPopover(comp);
case 'form_action': return renderFormAction(comp);
case 'board': return renderBoard(comp);
default: {
const div = document.createElement('div');
div.className = 'db-viewer';
@@ -988,6 +1212,37 @@ function renderComponent(comp) {
}
}
function renderBoard(comp) {
const wrap = document.createElement('div');
wrap.className = 'aiui-board';
const row = document.createElement('div');
row.className = 'aiui-board-columns';
(comp.columns || []).forEach((col) => {
const colEl = document.createElement('div');
colEl.className = 'aiui-board-column';
const title = document.createElement('div');
title.className = 'aiui-board-column-title';
title.textContent = col.title || col.id || 'Column';
colEl.appendChild(title);
const cardsWrap = document.createElement('div');
cardsWrap.className = 'aiui-board-column-cards';
(col.cards || []).forEach((cardComp) => {
const cardWrap = document.createElement('div');
cardWrap.className = 'aiui-board-card';
const normalized = cardComp?.type ? cardComp : {
type: 'card', title: cardComp.title || cardComp.name || 'Card',
value: cardComp.value, footer: cardComp.footer || cardComp.assignee || cardComp.status,
};
cardWrap.appendChild(renderComponent(normalized));
cardsWrap.appendChild(cardWrap);
});
colEl.appendChild(cardsWrap);
row.appendChild(colEl);
});
wrap.appendChild(row);
return wrap;
}
function renderCard(comp) {
const card = document.createElement('div');
card.className = 'db-card';
@@ -2129,4 +2384,37 @@ function onContentChange(root) {
}
}
window.aiui.registerSlot('shell:header', () => {
const el = document.createElement('div');
el.className = 'db-shell-header-brand';
el.style.cssText = 'font-size:12px;color:var(--db-text-dim);padding:4px 0';
fetch('/ui-config.json').then((r) => r.json()).then((cfg) => {
const t = cfg?.site?.title;
if (t) el.textContent = t;
}).catch(() => {});
return el;
});
window.aiui.registerSlot('shell:sidebar:footer', () => {
const el = document.createElement('div');
el.innerHTML = '<span class="db-health-dot" style="color:var(--db-text-dim)">●</span> <span class="db-health-label">Checking…</span>';
fetch('/api/health').then((r) => r.json()).then((d) => {
const ok = d && (d.status === 'ok' || d.status === 'healthy');
const dot = el.querySelector('.db-health-dot');
const label = el.querySelector('.db-health-label');
const gaps = (d && d.sdk_gaps) || [];
if (dot) dot.style.color = ok && !gaps.length ? '#22c55e' : (gaps.length ? '#f59e0b' : '#ef4444');
if (label) {
if (gaps.length) {
label.textContent = `SDK gaps (${gaps.length})`;
label.title = gaps.map((g) => `${g.feature}: ${g.message}`).join('\n');
} else {
label.textContent = ok ? (d.integrated ? 'Integrated' : 'Connected') : 'Degraded';
label.title = '';
}
}
}).catch(() => { const label = el.querySelector('.db-health-label'); if (label) label.textContent = 'Offline'; });
return el;
});
export { init, onContentChange };
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more