mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9403f6ba58 | ||
|
|
66acd35496 | ||
|
|
8a483a62dd | ||
|
|
d418dcf443 | ||
|
|
c467643c15 | ||
|
|
8caa8d51d0 | ||
|
|
541ada0399 | ||
|
|
fb28f2a2ee | ||
|
|
ac0a4f32a8 | ||
|
|
3a0fa72c3d | ||
|
|
f38aa27345 | ||
|
|
39615089ba | ||
|
|
77b67271ec | ||
|
|
7099e9f2ce | ||
|
|
3f02c32740 | ||
|
|
a9dfc97e8c | ||
|
|
5429a287e7 | ||
|
|
a4167b1477 | ||
|
|
0923aac53a | ||
|
|
b589c6fce2 | ||
|
|
76d01dbd19 |
No files matched your search
@@ -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
@@ -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
@@ -55,4 +55,5 @@ aiui-output/
|
||||
_site
|
||||
|
||||
.praisonai
|
||||
tmp/
|
||||
tmp/
|
||||
*.db
|
||||
@@ -22,6 +22,7 @@ Deep Analysis → Gap Analysis → Critical Review → Plan → Create Issue →
|
||||
- **No competitor names in public surfaces**: Never mention competitor product, company, or project names in GitHub issues, PRs, commit messages, release notes, docs, or any other public-facing content. Refer to capabilities generically (e.g., "self-improving agent pattern", "external agent framework").
|
||||
- **Test before merge**: never merge unvalidated PRs. Always clone the branch locally and run the tests.
|
||||
- **Minimal code change**: prefer upstream one-line fixes over downstream workarounds.
|
||||
- **Autonomous polling — NEVER ask the user to "check now"**: after posting feedback to `@claude`, submitting a trigger comment, or any state that requires an external agent to push commits, the assistant itself waits ~10 minutes (`sleep 600`) and then re-checks. Do not stop and wait for the human to type "check now". Only surface control back to the human when a round is fully complete (PR merged or 3-round feedback cap hit) or a blocking decision is required (e.g. rebase strategy, conflicting feature intent).
|
||||
|
||||
---
|
||||
|
||||
@@ -536,6 +537,11 @@ sleep 600
|
||||
# → return to Phase 12 (fetch new commits, re-run checks)
|
||||
```
|
||||
|
||||
**The assistant runs this `sleep 600` itself — it does NOT pause the loop to ask the user "check now".** After the 10-minute wait completes, the assistant immediately re-fetches the PR, re-runs the test suite, and either merges, posts the next feedback round, or reports a final status. The human is only prompted when:
|
||||
- The PR merges successfully (report + move to next issue).
|
||||
- The 3-round feedback cap is hit (escalate).
|
||||
- A design decision is needed that the assistant cannot make unilaterally (e.g. "should `allow` policy persist or not?").
|
||||
|
||||
Repeat Phases 12 → 13 → 14 until the mergeability gate in Phase 13 is fully green, or escalate to a human after 3 failed rounds.
|
||||
|
||||
### When the PR has merge conflicts
|
||||
@@ -668,3 +674,4 @@ gh issue view $ISSUE_NUM --repo $REPO --json state
|
||||
6. **Always verify** the issue auto-closed after merge.
|
||||
7. **All `run_command` calls use `timeout`** to avoid hangs (user global rule).
|
||||
8. **No proactive `.md` creation** beyond this workflow file (user global rule).
|
||||
9. **Never ask the user "check now"**: between rounds the assistant runs `sleep 600` itself and keeps driving. Only return control to the human on merge, 3-round cap, or an irresolvable design decision.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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` |
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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")))
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "aiui"
|
||||
version = "0.3.112"
|
||||
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 = [
|
||||
|
||||
@@ -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))
|
||||
Generated
+185
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
@@ -197,7 +203,20 @@ def __getattr__(name: str):
|
||||
"JSONFileDataStore",
|
||||
"SQLAlchemyDataStore",
|
||||
}
|
||||
_provider_attrs = {"BaseProvider", "RunEvent", "RunEventType"}
|
||||
_provider_attrs = {
|
||||
"BaseProvider",
|
||||
"RunEvent",
|
||||
"RunEventType",
|
||||
# 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"}
|
||||
_action_attrs = {"Action", "action_callback"}
|
||||
@@ -421,6 +440,8 @@ __all__ = [
|
||||
"on",
|
||||
"resume",
|
||||
"page",
|
||||
"surface_page",
|
||||
"surface_action",
|
||||
# Lifecycle hooks
|
||||
"on_app_startup",
|
||||
"on_app_shutdown",
|
||||
@@ -455,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",
|
||||
@@ -474,6 +499,14 @@ __all__ = [
|
||||
"RunEvent",
|
||||
"RunEventType",
|
||||
"PraisonAIProvider",
|
||||
# Discovery, citations, reasoning schema (Issues #48, #49, #50)
|
||||
# PraisonAI-native, noun-first names (not lifted from any external framework)
|
||||
"ModelCard",
|
||||
"AgentCard",
|
||||
"TeamCard",
|
||||
"SourceChunk",
|
||||
"SourceBundle",
|
||||
"ThoughtStep",
|
||||
# Configuration
|
||||
"configure",
|
||||
# Action classes and decorators
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Version information."""
|
||||
|
||||
__version__ = "0.3.112"
|
||||
__version__ = "0.3.114"
|
||||
@@ -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 []
|
||||
@@ -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(),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"],
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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}")
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
+236
-1
@@ -69,6 +69,12 @@ class RunEventType(str, Enum):
|
||||
RUN_PAUSED = "run_paused"
|
||||
RUN_CONTINUED = "run_continued"
|
||||
|
||||
# --- 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"
|
||||
@@ -84,6 +90,146 @@ class RunEventType(str, Enum):
|
||||
TEAM_MEMORY_UPDATE_COMPLETED = "team_memory_update_completed"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Structured payload dataclasses (Issues #48, #49, #50)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
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
|
||||
provider: str
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {"name": self.name, "model": self.model, "provider": self.provider}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentCard:
|
||||
"""Agent discovery card — what a chat frontend shows in its picker (Issue #48)."""
|
||||
|
||||
agent_id: str
|
||||
name: str
|
||||
description: str = ""
|
||||
model: Optional[ModelCard] = None
|
||||
storage: bool = False
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
d: Dict[str, Any] = {
|
||||
"agent_id": self.agent_id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"storage": self.storage,
|
||||
}
|
||||
if self.model is not None:
|
||||
d["model"] = self.model.to_dict()
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
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[ModelCard] = None
|
||||
storage: bool = False
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
d: Dict[str, Any] = {
|
||||
"team_id": self.team_id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"storage": self.storage,
|
||||
}
|
||||
if self.model is not None:
|
||||
d["model"] = self.model.to_dict()
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
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
|
||||
chunk: int = 0
|
||||
chunk_size: int = 0
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"name": self.name,
|
||||
"content": self.content,
|
||||
"chunk": self.chunk,
|
||||
"chunk_size": self.chunk_size,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
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
|
||||
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": [c.to_dict() for c in self.chunks],
|
||||
}
|
||||
if self.time_ms is not None:
|
||||
d["time_ms"] = self.time_ms
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
class ThoughtStep:
|
||||
"""Structured reasoning-step payload (Issue #50).
|
||||
|
||||
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
|
||||
result: str = ""
|
||||
reasoning: str = ""
|
||||
action: Optional[str] = None # e.g. "search", "plan", "verify"
|
||||
confidence: Optional[float] = None # 0.0 .. 1.0
|
||||
next_action: Optional[str] = None # what the agent plans to do next
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
d: Dict[str, Any] = {
|
||||
"title": self.title,
|
||||
"result": self.result,
|
||||
"reasoning": self.reasoning,
|
||||
}
|
||||
for key in ("action", "confidence", "next_action"):
|
||||
val = getattr(self, key)
|
||||
if val is not None:
|
||||
d[key] = val
|
||||
return d
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RunEvent — the single data structure emitted by providers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -126,12 +272,21 @@ class RunEvent:
|
||||
result: Optional[Any] = None
|
||||
tool_call_id: Optional[str] = None
|
||||
|
||||
# Reasoning
|
||||
# Reasoning (Issue #50 — structured fields are optional)
|
||||
step: Optional[str] = None
|
||||
action: Optional[str] = None
|
||||
confidence: Optional[float] = None
|
||||
next_action: Optional[str] = None
|
||||
|
||||
# Agent / Team
|
||||
agent_id: Optional[str] = None
|
||||
agent_name: Optional[str] = None
|
||||
team_id: Optional[str] = None
|
||||
|
||||
# RAG / Citations (Issue #49)
|
||||
query: Optional[str] = None
|
||||
references: Optional[List[Dict[str, Any]]] = None
|
||||
time_ms: Optional[float] = None
|
||||
|
||||
# Error
|
||||
error: Optional[str] = None
|
||||
@@ -152,8 +307,15 @@ class RunEvent:
|
||||
"result",
|
||||
"tool_call_id",
|
||||
"step",
|
||||
"action",
|
||||
"confidence",
|
||||
"next_action",
|
||||
"agent_id",
|
||||
"agent_name",
|
||||
"team_id",
|
||||
"query",
|
||||
"references",
|
||||
"time_ms",
|
||||
"error",
|
||||
"event_id",
|
||||
"timestamp",
|
||||
@@ -162,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
|
||||
|
||||
|
||||
@@ -220,6 +390,71 @@ class BaseProvider(ABC):
|
||||
"""List available agents. Override if your backend has agents."""
|
||||
return []
|
||||
|
||||
async def list_teams(self) -> List[Dict[str, Any]]:
|
||||
"""List available multi-agent teams. Override if your backend has teams.
|
||||
|
||||
Default returns ``[]`` so providers that don't model teams keep working
|
||||
unchanged (Issue #48).
|
||||
"""
|
||||
return []
|
||||
|
||||
async def run_team(
|
||||
self,
|
||||
team_id: str,
|
||||
message: str,
|
||||
*,
|
||||
session_id: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterator[RunEvent]:
|
||||
"""Run a multi-agent team. Override if your backend has teams.
|
||||
|
||||
The default implementation delegates to ``run()`` with ``agent_name``
|
||||
set to ``team_id`` so single-agent backends transparently treat a
|
||||
team_id as an agent_name (Issue #48).
|
||||
"""
|
||||
async for ev in self.run(
|
||||
message, session_id=session_id, agent_name=team_id, **kwargs
|
||||
):
|
||||
yield ev
|
||||
|
||||
async def health(self) -> Dict[str, Any]:
|
||||
"""Health check endpoint data. Override for custom checks."""
|
||||
return {"status": "ok", "provider": self.__class__.__name__}
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Convenience emit helpers (Issues #49, #50) — keep provider code terse.
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
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=bundle.query,
|
||||
references=[c.to_dict() for c in bundle.chunks],
|
||||
time_ms=bundle.time_ms,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
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,
|
||||
action=step.action,
|
||||
confidence=step.confidence,
|
||||
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},
|
||||
)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+368
-34
@@ -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,23 +926,58 @@ 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:
|
||||
"""List all registered agents (merges registry + provider)."""
|
||||
"""List all registered agents (merges registry + provider).
|
||||
|
||||
Returns the enriched third-party-chat-frontend-compatible schema
|
||||
(``agent_id``, ``name``, ``description``, ``model``, ``storage``,
|
||||
Issue #48) while keeping the legacy ``created_at`` field for
|
||||
backward compatibility.
|
||||
"""
|
||||
agents = [
|
||||
{
|
||||
"agent_id": name,
|
||||
"name": info["name"],
|
||||
"description": info.get("description", ""),
|
||||
"storage": bool(info.get("storage", False)),
|
||||
"created_at": info["created_at"],
|
||||
}
|
||||
for info in _agents.values()
|
||||
for name, info in _agents.items()
|
||||
]
|
||||
# Also ask the provider for agents
|
||||
try:
|
||||
@@ -870,12 +986,64 @@ async def list_agents(request: Request) -> JSONResponse:
|
||||
existing_names = {a["name"] for a in agents}
|
||||
for pa in provider_agents:
|
||||
if pa.get("name") not in existing_names:
|
||||
# Ensure every entry carries an agent_id for client routing.
|
||||
if "agent_id" not in pa:
|
||||
pa = {**pa, "agent_id": pa.get("name", "")}
|
||||
agents.append(pa)
|
||||
except Exception:
|
||||
pass
|
||||
return JSONResponse({"agents": agents})
|
||||
|
||||
|
||||
async def list_teams(request: Request) -> JSONResponse:
|
||||
"""List all multi-agent teams (Issue #48).
|
||||
|
||||
Delegates to ``provider.list_teams()``; default providers return ``[]``
|
||||
so single-agent backends transparently return an empty list.
|
||||
"""
|
||||
teams: list[dict[str, Any]] = []
|
||||
try:
|
||||
provider = get_provider()
|
||||
provider_teams = await provider.list_teams()
|
||||
for pt in provider_teams:
|
||||
if "team_id" not in pt:
|
||||
pt = {**pt, "team_id": pt.get("name", "")}
|
||||
teams.append(pt)
|
||||
except Exception:
|
||||
pass
|
||||
return JSONResponse({"teams": teams})
|
||||
|
||||
|
||||
async def run_team_by_id(request: Request) -> StreamingResponse:
|
||||
"""Run a specific team by ID with SSE streaming (Issue #48).
|
||||
|
||||
Mirrors ``run_agent_by_id``; ``BaseProvider.run_team`` falls back to
|
||||
``run()`` when the provider doesn't override it, so single-agent
|
||||
backends still work when a client addresses them as a team.
|
||||
"""
|
||||
team_id = request.path_params["team_id"]
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
return JSONResponse({"error": "Invalid JSON"}, status_code=400)
|
||||
|
||||
body["team"] = team_id
|
||||
# Team run is modelled as an agent run with agent_name=team_id so the
|
||||
# SSE plumbing (session persistence, event forwarding) is identical.
|
||||
body.setdefault("agent", team_id)
|
||||
request._body = body
|
||||
return await run_agent(request, body)
|
||||
|
||||
|
||||
async def delete_team_session(request: Request) -> JSONResponse:
|
||||
"""Delete a session scoped to a team (Issue #48).
|
||||
|
||||
Provider-agnostic — delegates to the same session-delete path as the
|
||||
per-agent API.
|
||||
"""
|
||||
return await delete_session(request)
|
||||
|
||||
|
||||
async def list_sessions(request: Request) -> JSONResponse:
|
||||
"""List all sessions."""
|
||||
sessions = await _datastore.list_sessions()
|
||||
@@ -1074,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",
|
||||
@@ -1090,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": {
|
||||
@@ -1147,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),
|
||||
@@ -1297,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})
|
||||
|
||||
|
||||
@@ -1330,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:
|
||||
@@ -1380,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,
|
||||
@@ -1411,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)
|
||||
@@ -1748,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):
|
||||
@@ -1939,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
|
||||
@@ -1979,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 (
|
||||
@@ -2009,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:
|
||||
@@ -2031,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)
|
||||
@@ -2494,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
|
||||
@@ -2527,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,
|
||||
@@ -2542,6 +2818,14 @@ def create_app(
|
||||
if _dash_cfg
|
||||
else {}
|
||||
),
|
||||
**(
|
||||
{
|
||||
"settings": _agent_settings,
|
||||
}
|
||||
if _agent_settings
|
||||
else {}
|
||||
),
|
||||
"jobs": dict(_jobs_api_config),
|
||||
"debug": _debug,
|
||||
}
|
||||
)
|
||||
@@ -2575,6 +2859,16 @@ def create_app(
|
||||
Route("/cancel", cancel_run, methods=["POST"]),
|
||||
Route("/agents/{agent_id}/runs", run_agent_by_id, methods=["POST"]),
|
||||
Route("/api/agents/{agent_id}/runs", run_agent_by_id, methods=["POST"]),
|
||||
# Teams REST surface (Issue #48)
|
||||
Route("/teams", list_teams, methods=["GET"]),
|
||||
Route("/api/teams", list_teams, methods=["GET"]),
|
||||
Route("/teams/{team_id}/runs", run_team_by_id, methods=["POST"]),
|
||||
Route("/api/teams/{team_id}/runs", run_team_by_id, methods=["POST"]),
|
||||
Route(
|
||||
"/teams/{team_id}/sessions/{session_id}",
|
||||
delete_team_session,
|
||||
methods=["DELETE"],
|
||||
),
|
||||
# Dashboard API
|
||||
Route("/api/feedback", api_feedback, methods=["POST"]),
|
||||
Route("/api/feedback", api_feedback_list, methods=["GET"]),
|
||||
@@ -2625,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.
|
||||
@@ -2726,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",
|
||||
@@ -2774,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;
|
||||
}
|
||||
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user