mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
Compare commits
67
Commits
No files matched your search
@@ -138,7 +138,23 @@ Final rule: conclude only when evidence shows missing = 0.
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
PUBLISH WORKFLOW (reference)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Step 1 — Publish praisonaiagents (Core SDK):
|
||||
GitHub Actions (recommended):
|
||||
GitHub → Actions → "PyPI Release" → Run workflow → bump: patch
|
||||
Requires repo secrets: PYPI_TOKEN, GH_TOKEN
|
||||
Requires GitHub Environment "pypi" with required reviewers (Settings → Environments)
|
||||
|
||||
Manual — Publish aiui (PraisonAIUI):
|
||||
cd /Users/praison/PraisonAIUI
|
||||
# Bump version in pyproject.toml and src/praisonaiui/__version__.py
|
||||
rm -rf dist && uv lock && uv build && uv publish --token "$PYPI_TOKEN"
|
||||
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
|
||||
git commit -m "Release vX.Y.Z" && git tag vX.Y.Z && git push && git push --tags
|
||||
gh release create vX.Y.Z --title "PraisonAIUI vX.Y.Z" --notes "Release vX.Y.Z" --latest
|
||||
|
||||
PyPI package name: aiui
|
||||
Verify: pip index versions aiui | head -1
|
||||
|
||||
PraisonAI stack (separate repo — publish SDK before wrapper):
|
||||
cd /Users/praison/praisonai-package/src/praisonai-agents
|
||||
praisonai publish pypi
|
||||
# Uses uv internally: uv lock → uv build → uv publish
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Idempotent CodeRabbit/Qodo kick for bot-opened PRs.
|
||||
* @see .github/workflows/auto-pr-comment.yml, claude.yml, bot-pr-recovery.yml
|
||||
*/
|
||||
|
||||
const KICK_AUTHORS = new Set(['MervinPraison', 'github-actions[bot]']);
|
||||
const BOT_PR_AUTHORS = new Set(['praisonai-triage-agent[bot]', 'github-actions[bot]']);
|
||||
|
||||
async function listAllComments(github, owner, repo, issueNumber) {
|
||||
if (typeof github.paginate === 'function') {
|
||||
return github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
}
|
||||
const { data } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function kickAuthored(comment, marker) {
|
||||
return (
|
||||
KICK_AUTHORS.has(comment.user?.login) &&
|
||||
(comment.body || '').includes(marker)
|
||||
);
|
||||
}
|
||||
|
||||
function coderabbitKickPosted(comments) {
|
||||
return comments.some((c) => kickAuthored(c, '@coderabbitai review'));
|
||||
}
|
||||
|
||||
function qodoKickPosted(comments) {
|
||||
return comments.some((c) => kickAuthored(c, '/review'));
|
||||
}
|
||||
|
||||
function chainKickPosted(comments) {
|
||||
return coderabbitKickPosted(comments) && qodoKickPosted(comments);
|
||||
}
|
||||
|
||||
function isBotOpenedPr(pr) {
|
||||
if (BOT_PR_AUTHORS.has(pr.user?.login)) return true;
|
||||
return pr.user?.type === 'Bot';
|
||||
}
|
||||
|
||||
async function kickReviewChain(github, owner, repo, prNumber, core, preFetchedComments = null) {
|
||||
const comments = preFetchedComments || await listAllComments(github, owner, repo, prNumber);
|
||||
const needCoderabbit = !coderabbitKickPosted(comments);
|
||||
const needQodo = !qodoKickPosted(comments);
|
||||
|
||||
if (!needCoderabbit && !needQodo) {
|
||||
core?.info?.(`Review chain already kicked on PR #${prNumber}`);
|
||||
return { kicked: false, reason: 'already_kicked' };
|
||||
}
|
||||
|
||||
if (needCoderabbit) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: '@coderabbitai review',
|
||||
});
|
||||
}
|
||||
if (needQodo) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: '/review',
|
||||
});
|
||||
}
|
||||
core?.info?.(`Kicked review chain for PR #${prNumber} (coderabbit=${needCoderabbit}, qodo=${needQodo})`);
|
||||
return { kicked: true, coderabbit: needCoderabbit, qodo: needQodo };
|
||||
}
|
||||
|
||||
async function findOpenPrForIssue(github, owner, repo, issueNumber) {
|
||||
const prefix = `claude/issue-${issueNumber}-`;
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
sort: 'created',
|
||||
direction: 'desc',
|
||||
per_page: 30,
|
||||
});
|
||||
return (
|
||||
prs.find(
|
||||
(p) => (p.head?.ref || '').startsWith(prefix) && isBotOpenedPr(p)
|
||||
) || null
|
||||
);
|
||||
}
|
||||
|
||||
async function kickReviewChainForIssue(github, owner, repo, issueNumber, core) {
|
||||
const pr = await findOpenPrForIssue(github, owner, repo, issueNumber);
|
||||
if (!pr) {
|
||||
core?.info?.(`No open PR for issue #${issueNumber}, skipping review kick`);
|
||||
return { kicked: false, reason: 'no_pr' };
|
||||
}
|
||||
const result = await kickReviewChain(github, owner, repo, pr.number, core);
|
||||
return { ...result, prNumber: pr.number };
|
||||
}
|
||||
|
||||
async function recoverStalledBotPrs(github, owner, repo, options, core) {
|
||||
const { prNumber = null, minAgeMs = 10 * 60 * 1000, maxRecover = 10 } = options || {};
|
||||
let prs;
|
||||
if (prNumber) {
|
||||
const { data } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
prs = [data];
|
||||
} else if (typeof github.paginate === 'function') {
|
||||
prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
} else {
|
||||
const { data } = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 100 });
|
||||
prs = data;
|
||||
}
|
||||
|
||||
const cutoff = Date.now() - minAgeMs;
|
||||
let recovered = 0;
|
||||
for (const pr of prs) {
|
||||
if (recovered >= maxRecover) break;
|
||||
if (!isBotOpenedPr(pr)) continue;
|
||||
if (!prNumber && new Date(pr.created_at).getTime() > cutoff) continue;
|
||||
const comments = await listAllComments(github, owner, repo, pr.number);
|
||||
if (chainKickPosted(comments)) continue;
|
||||
await kickReviewChain(github, owner, repo, pr.number, core, comments);
|
||||
recovered += 1;
|
||||
}
|
||||
core?.info?.(`Recovery complete (${recovered} PR(s) kicked)`);
|
||||
return recovered;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
KICK_AUTHORS,
|
||||
BOT_PR_AUTHORS,
|
||||
listAllComments,
|
||||
chainKickPosted,
|
||||
coderabbitKickPosted,
|
||||
qodoKickPosted,
|
||||
isBotOpenedPr,
|
||||
kickReviewChain,
|
||||
findOpenPrForIssue,
|
||||
kickReviewChainForIssue,
|
||||
recoverStalledBotPrs,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Run: node .github/scripts/ci-failure-claude-selftest.js
|
||||
*/
|
||||
const ciFix = require('./ci-failure-claude.js');
|
||||
const mergeGate = require('./merge-gate.js');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
let failed = 0;
|
||||
function assert(name, cond) {
|
||||
if (!cond) {
|
||||
console.error('FAIL:', name);
|
||||
failed += 1;
|
||||
} else {
|
||||
console.log('ok:', name);
|
||||
}
|
||||
}
|
||||
|
||||
const LOG = `
|
||||
python UNKNOWN STEP FAILED (0.0100s) tests/unit/test_example.py::test_foo - AssertionError: bar
|
||||
python UNKNOWN STEP ##[error]Process completed with exit code 1.
|
||||
`;
|
||||
|
||||
const parsed = ciFix.parsePytestFailures(LOG);
|
||||
assert('parses pytest failure', parsed.length === 1);
|
||||
assert('uses CI workflow list', config.ciFailureWorkflowRuns.includes('CI'));
|
||||
|
||||
const comment = ciFix.buildCiFixComment({
|
||||
headSha: 'abc1234567890abcdef1234567890abcdef12',
|
||||
failedChecks: [{ name: 'python', workflow: 'CI', html_url: 'https://example.com/job/1' }],
|
||||
failureSummaries: [{ jobName: 'python', failures: parsed }],
|
||||
});
|
||||
assert('comment mentions product guardrails', comment.includes('Product guardrails'));
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Post @claude comments with CI failure details for internal PRs.
|
||||
* @see .github/workflows/ci-failure-claude.yml
|
||||
*/
|
||||
|
||||
const mergeGate = require('./merge-gate.js');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
const CI_FIX_LABEL = 'claude-ci-fix-pending';
|
||||
const COOLDOWN_MS = 12 * 60 * 60 * 1000;
|
||||
const AUTO_ACTORS = mergeGate.AUTO_ACTORS;
|
||||
const MAX_FAILURES = 15;
|
||||
const MAX_JOBS_TO_FETCH = 5;
|
||||
|
||||
function shortSha(headSha) {
|
||||
return (headSha || '').slice(0, 8);
|
||||
}
|
||||
|
||||
function ciFixShaMarker(headSha) {
|
||||
return `ci failed on head \`${shortSha(headSha)}`.toLowerCase();
|
||||
}
|
||||
|
||||
function isCiFixComment(comment) {
|
||||
const body = (comment.body || '').toLowerCase();
|
||||
return body.includes('@claude') && body.includes('ci failed on head');
|
||||
}
|
||||
|
||||
function hasCiFixCommentForSha(comments, headSha) {
|
||||
const marker = shortSha(headSha).toLowerCase();
|
||||
return comments.some((c) => {
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
if (!isCiFixComment(c)) return false;
|
||||
return (c.body || '').toLowerCase().includes(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function hasRecentCiFixComment(comments, headSha) {
|
||||
const cutoff = Date.now() - COOLDOWN_MS;
|
||||
const marker = shortSha(headSha).toLowerCase();
|
||||
return comments.some((c) => {
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
if (!isCiFixComment(c)) return false;
|
||||
const body = (c.body || '').toLowerCase();
|
||||
if (body.includes(marker)) return true;
|
||||
return new Date(c.created_at).getTime() > cutoff;
|
||||
});
|
||||
}
|
||||
|
||||
function shouldSkipCiFix({ comments, headSha, labels, hasFinal, claudeInProgress, failedChecks }) {
|
||||
if (!failedChecks.length) return { skip: true, reason: 'no failed checks' };
|
||||
if (labels.includes(CI_FIX_LABEL)) return { skip: true, reason: 'ci fix pending' };
|
||||
if (!hasFinal) return { skip: true, reason: 'awaiting final claude review trigger' };
|
||||
if (claudeInProgress) return { skip: true, reason: 'claude in progress' };
|
||||
if (hasCiFixCommentForSha(comments, headSha)) {
|
||||
return { skip: true, reason: 'already commented for this sha' };
|
||||
}
|
||||
if (hasRecentCiFixComment(comments, headSha)) {
|
||||
return { skip: true, reason: 'recent ci fix comment cooldown' };
|
||||
}
|
||||
return { skip: false };
|
||||
}
|
||||
|
||||
function parsePytestFailures(logText) {
|
||||
if (!logText) return [];
|
||||
const lines = logText.split('\n');
|
||||
const failures = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (let i = lines.length - 1; i >= 0 && failures.length < MAX_FAILURES; i -= 1) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
const pytestMatch = line.match(
|
||||
/FAILED\s*(?:\([^)]+\))?\s+(tests\/[^\s]+(?:::[^\s-]+)*)\s*-\s*(.+)/i
|
||||
);
|
||||
if (pytestMatch) {
|
||||
const testId = pytestMatch[1];
|
||||
if (!seen.has(testId)) {
|
||||
seen.add(testId);
|
||||
failures.push({ testId, error: pytestMatch[2].trim() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const errorMatch = line.match(/##\[error\](.+)/);
|
||||
if (errorMatch && /tests\//.test(errorMatch[1])) {
|
||||
const msg = errorMatch[1].trim();
|
||||
if (!seen.has(msg)) {
|
||||
seen.add(msg);
|
||||
failures.push({ testId: msg, error: msg });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
async function fetchJobFailureSummary(github, owner, repo, jobId, jobName) {
|
||||
try {
|
||||
const response = await github.rest.actions.downloadJobLogsForWorkflowRun({
|
||||
owner,
|
||||
repo,
|
||||
job_id: jobId,
|
||||
});
|
||||
let logText = typeof response.data === 'string' ? response.data : String(response.data || '');
|
||||
if (logText.length > 100000) {
|
||||
logText = logText.slice(-100000);
|
||||
}
|
||||
return { jobName, jobId, failures: parsePytestFailures(logText) };
|
||||
} catch (err) {
|
||||
return { jobName, jobId, failures: [], error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
function mapFailedChecks(runs) {
|
||||
return runs.map((run) => ({
|
||||
name: run.name,
|
||||
id: run.id,
|
||||
html_url: run.html_url || run.details_url,
|
||||
workflow: run.app?.slug || run.check_suite?.app?.slug || run.name,
|
||||
}));
|
||||
}
|
||||
|
||||
function buildCiFixComment({ headSha, failedChecks, failureSummaries }) {
|
||||
const parts = [
|
||||
`@claude CI failed on HEAD \`${shortSha(headSha)}\`. Please fix the failures below and push to this branch.`,
|
||||
'',
|
||||
'## Failed checks',
|
||||
];
|
||||
|
||||
for (const check of failedChecks) {
|
||||
parts.push(`- **${check.workflow || check.name}** / \`${check.name}\` — ${check.html_url}`);
|
||||
}
|
||||
|
||||
parts.push('', '## Failures (extracted)');
|
||||
let idx = 0;
|
||||
for (const summary of failureSummaries) {
|
||||
for (const failure of summary.failures) {
|
||||
idx += 1;
|
||||
if (idx > MAX_FAILURES) break;
|
||||
parts.push(`${idx}. \`${failure.testId}\` — \`${failure.error}\``);
|
||||
parts.push(` - Job: \`${summary.jobName}\``);
|
||||
}
|
||||
if (idx >= MAX_FAILURES) break;
|
||||
}
|
||||
|
||||
if (idx === 0) {
|
||||
parts.push('_(Could not extract pytest details — see job logs above.)_');
|
||||
}
|
||||
|
||||
const exampleTest = failureSummaries.find((s) => s.failures.length)?.failures[0]?.testId;
|
||||
parts.push(
|
||||
'',
|
||||
'## Critical review first',
|
||||
'Before changing code or tests, decide **which side is wrong**:',
|
||||
'1. **Legitimate feature change** — the PR intent is correct but implementation or tests need updating. Preserve product guarantees; update tests only when behaviour intentionally changed and document why.',
|
||||
'2. **Regression / bug in this PR** — the failure exposes a real breakage introduced here. Fix the implementation; **do not weaken, skip, or delete tests** just to go green.',
|
||||
'3. **Pre-existing flake or unrelated failure** — say so explicitly; prefer fixing the root cause over masking it.',
|
||||
'',
|
||||
'**Product guardrails (AGENTS.md):**',
|
||||
'- Do not disturb PraisonAIUI contracts or frontend/backend layering to accommodate scope creep.',
|
||||
'- Tests must continue to guard backward compatibility and UX behaviour — passing CI by lowering test standards is not acceptable.',
|
||||
'- If the feature does not genuinely add product value, recommend reverting or narrowing scope instead of patching around failures.',
|
||||
'',
|
||||
'## What to do',
|
||||
'1. State your verdict: **legitimate fix**, **regression fix**, or **needs human review** — and why (1–3 sentences).',
|
||||
'2. Fix root cause with **minimal changes**; never bloat the Agent class with extra params.',
|
||||
exampleTest
|
||||
? `3. Run failing tests locally, e.g. \`pytest ${exampleTest} -q\`, plus any related tests touched by the PR.`
|
||||
: '3. Run failing tests locally with targeted pytest, plus any related tests touched by the PR.',
|
||||
'4. Push to this branch and comment: files changed, review verdict, and why tests still protect product behaviour.',
|
||||
);
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
async function maybeClearCiFixLabel(github, owner, repo, prNumber, labels, headSha, core) {
|
||||
if (!labels.includes(CI_FIX_LABEL)) return false;
|
||||
const ciGreen = await mergeGate.allChecksGreenOnSha(github, owner, repo, headSha, core);
|
||||
if (!ciGreen) return false;
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: CI_FIX_LABEL,
|
||||
});
|
||||
core?.info?.(`Removed ${CI_FIX_LABEL} from PR #${prNumber}`);
|
||||
} catch (err) {
|
||||
if (err.status !== 404) throw err;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts = {}) {
|
||||
const baseRepo = `${owner}/${repo}`;
|
||||
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
|
||||
const pr = ctx.pr;
|
||||
|
||||
if (pr.state !== 'open') return { skipped: true, reason: 'not open' };
|
||||
if (pr.draft) return { skipped: true, reason: 'draft' };
|
||||
|
||||
const headRepo = pr.head.repo?.full_name;
|
||||
if (headRepo && headRepo !== baseRepo && !pr.maintainer_can_modify) {
|
||||
return { skipped: true, reason: 'fork without maintainer edits' };
|
||||
}
|
||||
|
||||
const headSha = pr.head.sha;
|
||||
const labels = ctx.labels;
|
||||
|
||||
if (await maybeClearCiFixLabel(github, owner, repo, prNumber, labels, headSha, core)) {
|
||||
return { skipped: true, reason: 'ci green, label cleared' };
|
||||
}
|
||||
|
||||
const ciGreen = await mergeGate.allChecksGreenOnSha(github, owner, repo, headSha, core);
|
||||
if (ciGreen) return { skipped: true, reason: 'ci green' };
|
||||
|
||||
const runs = await mergeGate.listChecksOnSha(github, owner, repo, headSha);
|
||||
const failedRuns = mergeGate.listFailedChecksOnSha(runs);
|
||||
if (failedRuns.length === 0) {
|
||||
return { skipped: true, reason: 'no failed checks yet (may be pending)' };
|
||||
}
|
||||
|
||||
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(ctx.comments);
|
||||
const claudeInProgress = await mergeGate.hasInProgressClaudeAssistant(
|
||||
github, owner, repo, prNumber
|
||||
);
|
||||
const skip = shouldSkipCiFix({
|
||||
comments: ctx.comments,
|
||||
headSha,
|
||||
labels,
|
||||
hasFinal,
|
||||
claudeInProgress,
|
||||
failedChecks: failedRuns,
|
||||
});
|
||||
if (skip.skip) return { skipped: true, reason: skip.reason };
|
||||
|
||||
const failedChecks = mapFailedChecks(failedRuns);
|
||||
const failureSummaries = [];
|
||||
for (const check of failedChecks.slice(0, MAX_JOBS_TO_FETCH)) {
|
||||
failureSummaries.push(
|
||||
await fetchJobFailureSummary(github, owner, repo, check.id, check.name)
|
||||
);
|
||||
}
|
||||
|
||||
const body = buildCiFixComment({ headSha, failedChecks, failureSummaries });
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: [CI_FIX_LABEL],
|
||||
});
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
|
||||
core?.info?.(`Posted CI fix @claude on PR #${prNumber} (${shortSha(headSha)})`);
|
||||
return { triggered: true, headSha: shortSha(headSha) };
|
||||
}
|
||||
|
||||
async function processWorkflowRunFailure(github, owner, repo, workflowRun, core) {
|
||||
if (workflowRun.conclusion !== 'failure') {
|
||||
return { skipped: true, reason: `conclusion=${workflowRun.conclusion}` };
|
||||
}
|
||||
const allowed = config.ciFailureWorkflowRuns;
|
||||
if (!allowed.includes(workflowRun.name)) {
|
||||
return { skipped: true, reason: `workflow ${workflowRun.name}` };
|
||||
}
|
||||
const prNumber = await mergeGate.resolvePrNumberFromWorkflowRun(
|
||||
github, owner, repo, workflowRun
|
||||
);
|
||||
if (!prNumber) return { skipped: true, reason: 'no linked PR' };
|
||||
return maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CI_FIX_LABEL,
|
||||
COOLDOWN_MS,
|
||||
ciFixShaMarker,
|
||||
isCiFixComment,
|
||||
hasCiFixCommentForSha,
|
||||
hasRecentCiFixComment,
|
||||
shouldSkipCiFix,
|
||||
parsePytestFailures,
|
||||
fetchJobFailureSummary,
|
||||
buildCiFixComment,
|
||||
maybeClearCiFixLabel,
|
||||
maybeTriggerCiFixClaude,
|
||||
processWorkflowRunFailure,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* PraisonAIUI gate configuration — imported by merge-gate, release-gate, ci-failure-claude.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
repoFullName: 'MervinPraison/PraisonAIUI',
|
||||
productPathPrefixes: ['src/praisonaiui/', 'src/frontend/', 'tests/'],
|
||||
sensitivePathPatterns: [
|
||||
/^\.github\/workflows\//,
|
||||
/^pyproject\.toml$/,
|
||||
/credentials\.json$/i,
|
||||
],
|
||||
requiredCheckPatterns: [/^ci$/i, /python/i, /test/i, /lint/i, /ruff/i],
|
||||
ciWorkflowFile: 'ci.yml',
|
||||
ciWorkflowName: 'CI',
|
||||
mergeGateWorkflowRuns: ['CI', 'Claude Assistant'],
|
||||
ciFailureWorkflowRuns: ['CI'],
|
||||
pypiPackageName: 'aiui',
|
||||
packagePaths: ['src/praisonaiui', 'src/frontend', 'pyproject.toml'],
|
||||
finalClaudeScope:
|
||||
'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). '
|
||||
+ 'Do NOT expand into praisonaiagents or the monorepo unless the PR explicitly requires it.',
|
||||
finalClaudeProductValue:
|
||||
'4. Product value: review whether the change genuinely adds PraisonAIUI UX value and correct '
|
||||
+ 'layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep.',
|
||||
agentPyChecks: false,
|
||||
reviewBotLogins: [
|
||||
'coderabbitai[bot]',
|
||||
'qodo-code-review[bot]',
|
||||
'greptile-apps[bot]',
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Local self-test for merge-gate.js heuristics (no GitHub API).
|
||||
* Run: node .github/scripts/merge-gate-selftest.js
|
||||
*/
|
||||
const mg = require('./merge-gate.js');
|
||||
|
||||
let failed = 0;
|
||||
function assert(name, cond) {
|
||||
if (!cond) {
|
||||
console.error('FAIL:', name);
|
||||
failed++;
|
||||
} else {
|
||||
console.log('ok:', name);
|
||||
}
|
||||
}
|
||||
|
||||
// Stale FINAL: push after FINAL, no @claude since head
|
||||
const finals = [
|
||||
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T08:00:00Z' },
|
||||
];
|
||||
assert('stale when head after final', mg.isStaleFinalAfterPush(finals, '2026-06-12T09:00:00Z'));
|
||||
|
||||
const withRecovery = [
|
||||
...finals,
|
||||
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T09:30:00Z' },
|
||||
];
|
||||
assert('not stale when @claude after head', !mg.isStaleFinalAfterPush(withRecovery, '2026-06-12T09:00:00Z'));
|
||||
|
||||
const withClaudeReply = [
|
||||
...finals,
|
||||
{
|
||||
user: { login: 'praisonai-triage-agent[bot]' },
|
||||
body: "**Claude finished @MervinPraison's task** —— [View job](https://github.com/)",
|
||||
created_at: '2026-06-12T08:30:00Z',
|
||||
},
|
||||
];
|
||||
assert('not stale when Claude replied after FINAL', !mg.isStaleFinalAfterPush(withClaudeReply, '2026-06-12T09:00:00Z'));
|
||||
assert('claude final reply detected', mg.isClaudeFinalReplyComment(withClaudeReply[1]));
|
||||
|
||||
assert('cancelled detect-and-trigger does not block', mg.OPTIONAL_CANCELLED_CHECKS.has('detect-and-trigger'));
|
||||
|
||||
// Stale-FINAL recovery guards (PR #2560 push loop)
|
||||
const nowMs = Date.now();
|
||||
const iso = (ms) => new Date(ms).toISOString();
|
||||
const firstFinal = {
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude You are the FINAL architecture reviewer.',
|
||||
created_at: iso(nowMs - 20 * 60 * 1000),
|
||||
};
|
||||
const pushSoonAfter = mg.shouldSkipStaleFinalRecovery(
|
||||
[firstFinal],
|
||||
iso(nowMs - 16 * 60 * 1000)
|
||||
);
|
||||
assert('debounce push soon after FINAL', pushSoonAfter.skip && pushSoonAfter.reason.includes('soon after'));
|
||||
|
||||
const secondFinal = {
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude You are the FINAL architecture reviewer.',
|
||||
created_at: iso(nowMs - 20 * 60 * 1000),
|
||||
};
|
||||
const capped = mg.shouldSkipStaleFinalRecovery(
|
||||
[firstFinal, secondFinal],
|
||||
iso(nowMs - 2 * 60 * 1000)
|
||||
);
|
||||
assert('hourly cap blocks third FINAL in window', capped.skip && capped.reason.includes('capped'));
|
||||
|
||||
const botPush = mg.shouldSkipStaleFinalRecovery(
|
||||
[firstFinal],
|
||||
iso(nowMs - 16 * 60 * 1000),
|
||||
'praisonai-triage-agent[bot]'
|
||||
);
|
||||
assert('skip when automation pushed head', botPush.skip && botPush.reason.includes('automation'));
|
||||
|
||||
assert('claude automation login', mg.isClaudeAutomationLogin('praisonai-triage-agent[bot]'));
|
||||
|
||||
// Bot CHANGES_REQUESTED then APPROVE
|
||||
const reviews = [
|
||||
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T08:00:00Z' },
|
||||
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'APPROVED', submitted_at: '2026-06-12T09:00:00Z' },
|
||||
];
|
||||
assert('bot approve clears CR', !mg.hasAnyChangesRequested(reviews));
|
||||
assert('human CR blocks', mg.hasAnyChangesRequested([
|
||||
{ user: { login: 'MervinPraison', type: 'User' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T09:00:00Z' },
|
||||
]));
|
||||
|
||||
// Verdict after HEAD
|
||||
const verdictComments = [
|
||||
{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T08:00:00Z' },
|
||||
];
|
||||
assert('verdict before head rejected', mg.findMergeGateVerdict(verdictComments, null, '2026-06-12T09:00:00Z') === null);
|
||||
assert('verdict after head accepted', mg.findMergeGateVerdict(
|
||||
[{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T10:00:00Z' }],
|
||||
null,
|
||||
'2026-06-12T09:00:00Z'
|
||||
) === 'APPROVE');
|
||||
|
||||
const fallbackApprove = [
|
||||
{
|
||||
body: 'MERGE_GATE_VERDICT: APPROVE\n\nAutomated fallback — Claude assess did not post a verdict comment.',
|
||||
created_at: '2026-06-12T10:00:00Z',
|
||||
},
|
||||
];
|
||||
assert(
|
||||
'automated fallback APPROVE ignored when Opus required',
|
||||
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z', { excludeAutomatedFallback: true }) === null
|
||||
);
|
||||
assert(
|
||||
'automated fallback APPROVE still visible without Opus-only filter',
|
||||
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z') === 'APPROVE'
|
||||
);
|
||||
|
||||
const fallbackBlockNewFormat = [
|
||||
{
|
||||
body: 'MERGE_GATE_VERDICT: BLOCK\n\nAutomated fallback — Opus merge gate assessment did not complete.',
|
||||
created_at: '2026-06-12T10:00:00Z',
|
||||
},
|
||||
];
|
||||
assert(
|
||||
'new-format fallback BLOCK ignored when Opus required',
|
||||
mg.findMergeGateVerdict(fallbackBlockNewFormat, null, '2026-06-12T09:00:00Z', { excludeAutomatedFallback: true }) === null
|
||||
);
|
||||
assert(
|
||||
'new-format fallback BLOCK detected by marker helper',
|
||||
mg.isAutomatedFallbackVerdict(fallbackBlockNewFormat[0].body)
|
||||
);
|
||||
assert(
|
||||
'findMergeGateVerdict null-safe with explicit null options',
|
||||
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z', null) === 'APPROVE'
|
||||
);
|
||||
|
||||
const noise = [{ user: { login: 'MervinPraison' }, body: '**Merge gate scan** — wait for `@claude`', created_at: new Date().toISOString() }];
|
||||
assert('diagnostic comment not a trigger', !mg.hasRecentClaudeTrigger(noise, 35));
|
||||
|
||||
// Cooldown skip requires an actual verdict on HEAD, not just a FINAL trigger comment
|
||||
const recentFinalOnHead = [
|
||||
{
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude You are the FINAL architecture reviewer.',
|
||||
created_at: '2026-06-27T10:00:00Z',
|
||||
},
|
||||
];
|
||||
assert(
|
||||
'final trigger alone does not count as verdict on head',
|
||||
mg.findMergeGateVerdict(recentFinalOnHead, null, '2026-06-27T09:55:00Z') === null
|
||||
);
|
||||
const recentVerdictOnHead = [
|
||||
...recentFinalOnHead,
|
||||
{
|
||||
user: { login: 'github-actions[bot]' },
|
||||
body: 'MERGE_GATE_VERDICT: APPROVE',
|
||||
created_at: '2026-06-27T10:05:00Z',
|
||||
},
|
||||
];
|
||||
assert(
|
||||
'verdict on head skips cooldown gate',
|
||||
mg.findMergeGateVerdict(recentVerdictOnHead, null, '2026-06-27T09:55:00Z') === 'APPROVE'
|
||||
);
|
||||
|
||||
// Sensitive + secrets
|
||||
assert('workflow path sensitive', mg.sensitivePathReasons([{ filename: '.github/workflows/foo.yml' }]).length === 1);
|
||||
assert('ci-only label exempts workflows', mg.sensitivePathReasons(
|
||||
[{ filename: '.github/workflows/foo.yml' }],
|
||||
[mg.WORKFLOW_ONLY_LABEL]
|
||||
).length === 0);
|
||||
assert('ci-only label not exempt mixed changes', mg.sensitivePathReasons(
|
||||
[{ filename: '.github/workflows/foo.yml' }, { filename: 'src/praisonaiui/foo.py' }],
|
||||
[mg.WORKFLOW_ONLY_LABEL]
|
||||
).length === 1);
|
||||
assert('secret in patch', mg.secretScanReasons([{ filename: 'x.py', patch: '+key = "ghp_abcdefghijklmnopqrstuvwxyz1234567890"' }]).length === 1);
|
||||
|
||||
// Claude run scoping — other PR branches must not block
|
||||
assert('other branch claude does not block', !mg.hasBlockingClaudeRunForPr(
|
||||
[{ event: 'issue_comment', head_branch: 'other-branch' }],
|
||||
'my-branch'
|
||||
));
|
||||
assert('same branch claude blocks', mg.hasBlockingClaudeRunForPr(
|
||||
[{ event: 'issue_comment', head_branch: 'my-branch' }],
|
||||
'my-branch'
|
||||
));
|
||||
assert('issues event never blocks', !mg.hasBlockingClaudeRunForPr(
|
||||
[{ event: 'issues', head_branch: 'my-branch' }],
|
||||
'my-branch'
|
||||
));
|
||||
|
||||
// Conflict rebase clears after bot completion + FINAL on HEAD (within 12h cooldown)
|
||||
const conflictNowMs = Date.now();
|
||||
const conflictIso = (offsetMs) => new Date(conflictNowMs + offsetMs).toISOString();
|
||||
const conflictTrigger = {
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude this PR has merge conflicts with main. Please rebase',
|
||||
created_at: conflictIso(-30 * 60 * 1000),
|
||||
};
|
||||
const rebaseDone = {
|
||||
user: { login: 'praisonai-triage-agent[bot]' },
|
||||
body: 'Rebase complete — PR #2308 onto latest main',
|
||||
created_at: conflictIso(-29 * 60 * 1000),
|
||||
};
|
||||
const finalAfterRebase = {
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude You are the FINAL architecture reviewer.',
|
||||
created_at: conflictIso(-20 * 60 * 1000),
|
||||
};
|
||||
const headAfterRebase = conflictIso(-21 * 60 * 1000);
|
||||
const rebaseComments = [conflictTrigger, rebaseDone, finalAfterRebase];
|
||||
assert('conflict blocks before rebase done', mg.hasRecentConflictComment([conflictTrigger], headAfterRebase));
|
||||
assert('conflict clears after rebase + FINAL on HEAD', !mg.hasRecentConflictComment(rebaseComments, headAfterRebase));
|
||||
assert('conflict still blocks without FINAL on HEAD', mg.hasRecentConflictComment(
|
||||
[conflictTrigger, rebaseDone],
|
||||
headAfterRebase
|
||||
));
|
||||
|
||||
// Tests heuristic
|
||||
assert('product code without tests', mg.missingTestsReason([{ filename: 'src/praisonaiui/a/b.py', additions: 3 }]) !== null);
|
||||
assert('product code with tests ok', mg.missingTestsReason([
|
||||
{ filename: 'src/praisonaiui/a/b.py', additions: 3 },
|
||||
{ filename: 'tests/test_x.py', additions: 10 },
|
||||
]) === null);
|
||||
|
||||
// PR size
|
||||
assert('large PR blocked', mg.prSizeReasons([{ additions: 900 }]).length > 0);
|
||||
|
||||
assert('internal PR link accepted', mg.isInternalPullRequestLink(
|
||||
{ base: { repo: { full_name: 'MervinPraison/PraisonAIUI' } } },
|
||||
'MervinPraison',
|
||||
'PraisonAIUI'
|
||||
));
|
||||
assert('fork sync PR link rejected', !mg.isInternalPullRequestLink(
|
||||
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAIUI' } } },
|
||||
'MervinPraison',
|
||||
'PraisonAIUI'
|
||||
));
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
@@ -0,0 +1,900 @@
|
||||
/**
|
||||
* Shared merge-gate helpers for claude-merge-gate.yml
|
||||
* @see .github/workflows/claude-merge-gate.yml
|
||||
*/
|
||||
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
const CLAUDE_TRIGGER_LOGINS = ['MervinPraison', 'github-actions[bot]'];
|
||||
const AUTO_ACTORS = CLAUDE_TRIGGER_LOGINS;
|
||||
const CONFLICT_COOLDOWN_MS = 12 * 60 * 60 * 1000;
|
||||
const CLAUDE_ACTIVE_MS = 35 * 60 * 1000;
|
||||
const STALE_FINAL_RECOVERY_WINDOW_MS = 60 * 60 * 1000;
|
||||
const STALE_FINAL_MAX_PER_WINDOW = 2;
|
||||
const PUSH_AFTER_FINAL_DEBOUNCE_MS = 15 * 60 * 1000;
|
||||
const ALLOWED_MERGE_STATES = new Set(['CLEAN', 'UNSTABLE']);
|
||||
const AGENT_PY_MAX_AUTO_LINES = 100;
|
||||
const PR_MAX_AUTO_ADDITIONS = 800;
|
||||
const PR_MAX_AUTO_FILES = 30;
|
||||
const MANUAL_ONLY_LABELS = new Set(['security', 'breaking-change', 'needs-manual-review', 'release']);
|
||||
const WORKFLOW_ONLY_LABEL = 'merge-gate-ci-only';
|
||||
const CI_ONLY_PATH_PREFIXES = ['.github/workflows/', '.github/actions/', '.github/scripts/merge-gate'];
|
||||
const SDK_PATH_PREFIXES = config.productPathPrefixes;
|
||||
const SENSITIVE_PATH_PATTERNS = config.sensitivePathPatterns;
|
||||
const REQUIRED_SDK_CHECK_PATTERNS = config.requiredCheckPatterns;
|
||||
const SECRET_PATTERNS = [
|
||||
/sk-[a-zA-Z0-9]{20,}/,
|
||||
/AKIA[0-9A-Z]{16}/,
|
||||
/ghp_[a-zA-Z0-9]{36,}/,
|
||||
/github_pat_/,
|
||||
/Bearer eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/,
|
||||
/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
||||
];
|
||||
const BLOCK_LABELS = new Set([
|
||||
'claude-conflict-pending',
|
||||
'claude-merge-gate-active',
|
||||
'no-auto-merge',
|
||||
'auto-merged-by-gate',
|
||||
]);
|
||||
const MERGE_READY_LABEL = 'pipeline/merge-ready';
|
||||
/** Superseded concurrency runs; must not block merge when real tests passed. */
|
||||
const OPTIONAL_CANCELLED_CHECKS = new Set(['detect-and-trigger']);
|
||||
const BOT_REVIEWER_PATTERNS = [
|
||||
'coderabbit',
|
||||
'qodo',
|
||||
'gemini',
|
||||
'copilot',
|
||||
'greptile',
|
||||
];
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
function isFinalClaudeTriggerComment(c) {
|
||||
const body = (c.body || '').toLowerCase();
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
if (!body.includes('@claude')) return false;
|
||||
if (body.includes('merge conflict')) return false;
|
||||
return body.includes('final architecture reviewer') || body.includes('lead engineer');
|
||||
}
|
||||
|
||||
function isClaudeTriggerNoise(c) {
|
||||
const body = c.body || '';
|
||||
if (body.includes('Merge gate scan')) return true;
|
||||
if (body.includes('MERGE_GATE_VERDICT')) return true;
|
||||
if (body.includes('Merged by **Claude PR merge gate**')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function isClaudeFinalReplyComment(c) {
|
||||
const login = (c.user?.login || '').toLowerCase();
|
||||
if (!login.includes('praisonai-triage')) return false;
|
||||
return (c.body || '').includes('Claude finished');
|
||||
}
|
||||
|
||||
function hasRecentClaudeTrigger(comments, minutes = 35) {
|
||||
const cutoff = Date.now() - minutes * 60 * 1000;
|
||||
return comments.some((c) => {
|
||||
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
|
||||
if (isClaudeTriggerNoise(c)) return false;
|
||||
if (!(c.body || '').includes('@claude')) return false;
|
||||
return new Date(c.created_at).getTime() > cutoff;
|
||||
});
|
||||
}
|
||||
|
||||
function isConflictRebaseTriggerComment(c) {
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
const body = (c.body || '').toLowerCase();
|
||||
return body.includes('@claude') && body.includes('merge conflict');
|
||||
}
|
||||
|
||||
function isConflictRebaseCompletionComment(c) {
|
||||
const login = (c.user?.login || '').toLowerCase();
|
||||
if (!login.includes('praisonai-triage') && !login.includes('github-actions')) return false;
|
||||
const body = (c.body || '').toLowerCase();
|
||||
return (
|
||||
body.includes('rebase complete') ||
|
||||
body.includes('rebase onto') ||
|
||||
(body.includes('conflict') && body.includes('resolved'))
|
||||
);
|
||||
}
|
||||
|
||||
function conflictRebaseQuiescent(comments, headPushedAt) {
|
||||
const conflictTriggers = comments.filter(isConflictRebaseTriggerComment);
|
||||
if (conflictTriggers.length === 0) return true;
|
||||
|
||||
const latestConflict = conflictTriggers.reduce((a, b) =>
|
||||
new Date(a.created_at) > new Date(b.created_at) ? a : b
|
||||
);
|
||||
const conflictTime = new Date(latestConflict.created_at).getTime();
|
||||
|
||||
const rebaseDone = comments.some(
|
||||
(c) =>
|
||||
new Date(c.created_at).getTime() > conflictTime && isConflictRebaseCompletionComment(c)
|
||||
);
|
||||
if (!rebaseDone) return false;
|
||||
|
||||
return finalClaudeCompletedOnSha(comments, headPushedAt);
|
||||
}
|
||||
|
||||
function hasRecentConflictComment(comments, headPushedAt = null) {
|
||||
const cutoff = Date.now() - CONFLICT_COOLDOWN_MS;
|
||||
const hasRecentTrigger = comments.some((c) => {
|
||||
if (!isConflictRebaseTriggerComment(c)) return false;
|
||||
return new Date(c.created_at).getTime() > cutoff;
|
||||
});
|
||||
if (!hasRecentTrigger) return false;
|
||||
|
||||
if (headPushedAt && conflictRebaseQuiescent(comments, headPushedAt)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isBotReviewer(login, userType) {
|
||||
const lower = (login || '').toLowerCase();
|
||||
if (userType === 'Bot') return true;
|
||||
if (lower.endsWith('[bot]')) return true;
|
||||
return ['coderabbit', 'qodo', 'gemini', 'copilot', 'greptile'].some((p) => lower.includes(p));
|
||||
}
|
||||
|
||||
function latestReviewsByUser(reviews) {
|
||||
const latestByUser = new Map();
|
||||
for (const r of reviews) {
|
||||
const login = r.user?.login;
|
||||
if (!login) continue;
|
||||
const prev = latestByUser.get(login);
|
||||
if (!prev || new Date(r.submitted_at) > new Date(prev.submitted_at)) {
|
||||
latestByUser.set(login, r);
|
||||
}
|
||||
}
|
||||
return latestByUser;
|
||||
}
|
||||
|
||||
function hasHumanChangesRequested(reviews) {
|
||||
for (const [login, review] of latestReviewsByUser(reviews)) {
|
||||
if (review.state !== 'CHANGES_REQUESTED') continue;
|
||||
if (!isBotReviewer(login, review.user?.type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasAnyChangesRequested(reviews) {
|
||||
for (const [, review] of latestReviewsByUser(reviews)) {
|
||||
if (review.state === 'CHANGES_REQUESTED') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasFinalClaudeReviewTrigger(comments) {
|
||||
return comments.some(isFinalClaudeTriggerComment);
|
||||
}
|
||||
|
||||
function isStaleFinalAfterPush(comments, headPushedAt) {
|
||||
if (!headPushedAt) return false;
|
||||
const headTime = new Date(headPushedAt).getTime();
|
||||
const finals = comments.filter(isFinalClaudeTriggerComment);
|
||||
if (finals.length === 0) return true;
|
||||
const latestFinal = finals.reduce((a, b) =>
|
||||
new Date(a.created_at) > new Date(b.created_at) ? a : b
|
||||
);
|
||||
const finalTime = new Date(latestFinal.created_at).getTime();
|
||||
if (headTime <= finalTime + 60000) return false;
|
||||
const claudeRepliedAfterFinal = comments.some((c) => {
|
||||
if (!isClaudeFinalReplyComment(c)) return false;
|
||||
return new Date(c.created_at).getTime() >= finalTime - 60000;
|
||||
});
|
||||
if (claudeRepliedAfterFinal) return false;
|
||||
const claudeSinceHead = comments.some((c) => {
|
||||
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
|
||||
if (isClaudeTriggerNoise(c)) return false;
|
||||
if (!(c.body || '').includes('@claude')) return false;
|
||||
return new Date(c.created_at).getTime() >= headTime - 60000;
|
||||
});
|
||||
return !claudeSinceHead;
|
||||
}
|
||||
|
||||
function needsStaleFinalRecovery(comments, headPushedAt) {
|
||||
return (
|
||||
hasFinalClaudeReviewTrigger(comments) &&
|
||||
isStaleFinalAfterPush(comments, headPushedAt)
|
||||
);
|
||||
}
|
||||
|
||||
function shouldSkipFinalRecovery(comments, headPushedAt) {
|
||||
const isStale = isStaleFinalAfterPush(comments, headPushedAt);
|
||||
if (isStale) return false;
|
||||
return hasRecentClaudeTrigger(comments, 35);
|
||||
}
|
||||
|
||||
function countFinalTriggersSince(comments, sinceMs) {
|
||||
return comments.filter(
|
||||
(c) => isFinalClaudeTriggerComment(c) && new Date(c.created_at).getTime() > sinceMs
|
||||
).length;
|
||||
}
|
||||
|
||||
function isClaudeAutomationLogin(login) {
|
||||
const lower = (login || '').toLowerCase();
|
||||
return lower.includes('praisonai-triage') || lower === 'github-actions[bot]';
|
||||
}
|
||||
|
||||
function isPushSoonAfterLatestFinal(comments, headPushedAt) {
|
||||
if (!headPushedAt) return false;
|
||||
const finals = comments.filter(isFinalClaudeTriggerComment);
|
||||
if (finals.length === 0) return false;
|
||||
const latestFinal = finals.reduce((a, b) =>
|
||||
new Date(a.created_at) > new Date(b.created_at) ? a : b
|
||||
);
|
||||
const finalTime = new Date(latestFinal.created_at).getTime();
|
||||
const headTime = new Date(headPushedAt).getTime();
|
||||
if (headTime <= finalTime) return false;
|
||||
return headTime - finalTime < PUSH_AFTER_FINAL_DEBOUNCE_MS;
|
||||
}
|
||||
|
||||
/** Returns { skip: true, reason } when stale-FINAL recovery should not post. */
|
||||
function shouldSkipStaleFinalRecovery(comments, headPushedAt, headPusherLogin = null) {
|
||||
if (!isStaleFinalAfterPush(comments, headPushedAt)) {
|
||||
return { skip: true, reason: 'not stale' };
|
||||
}
|
||||
if (headPusherLogin && isClaudeAutomationLogin(headPusherLogin)) {
|
||||
return { skip: true, reason: 'head pushed by Claude automation' };
|
||||
}
|
||||
if (isPushSoonAfterLatestFinal(comments, headPushedAt)) {
|
||||
return {
|
||||
skip: true,
|
||||
reason: 'head pushed soon after FINAL (wait for CI / batched fixes)',
|
||||
};
|
||||
}
|
||||
const windowStart = Date.now() - STALE_FINAL_RECOVERY_WINDOW_MS;
|
||||
const finalsInWindow = countFinalTriggersSince(comments, windowStart);
|
||||
if (finalsInWindow >= STALE_FINAL_MAX_PER_WINDOW) {
|
||||
return {
|
||||
skip: true,
|
||||
reason: `stale-FINAL capped (${finalsInWindow} FINAL triggers in last hour)`,
|
||||
};
|
||||
}
|
||||
return { skip: false, reason: '' };
|
||||
}
|
||||
|
||||
function finalClaudeCompletedOnSha(comments, headPushedAt) {
|
||||
if (!hasFinalClaudeReviewTrigger(comments)) return false;
|
||||
if (isStaleFinalAfterPush(comments, headPushedAt)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FINAL_CLAUDE_REVIEW_BODY =
|
||||
`@claude You are the FINAL architecture reviewer. If the branch is under ${config.repoFullName} (not a fork), you are able to make modifications to this branch and push directly. ${config.finalClaudeScope} Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n${config.finalClaudeProductValue}\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items`;
|
||||
|
||||
async function getMergeState(github, owner, repo, prNumber) {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
mergeStateStatus
|
||||
maintainerCanModify
|
||||
isDraft
|
||||
headRefOid
|
||||
headRef { repository { nameWithOwner } }
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
const result = await github.graphql(query, { owner, repo, number: prNumber });
|
||||
const prGql = result.repository.pullRequest;
|
||||
const status = (prGql?.mergeStateStatus || '').toUpperCase();
|
||||
if (status && status !== 'UNKNOWN') {
|
||||
return {
|
||||
status,
|
||||
isDraft: prGql.isDraft,
|
||||
headRepo: prGql.headRef?.repository?.nameWithOwner,
|
||||
headSha: prGql.headRefOid,
|
||||
maintainerCanModify: prGql.maintainerCanModify === true,
|
||||
};
|
||||
}
|
||||
if (attempt < 2) await sleep(10000);
|
||||
}
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
return {
|
||||
status: (pr.mergeable_state || '').toUpperCase(),
|
||||
isDraft: pr.draft,
|
||||
headRepo: pr.head.repo?.full_name,
|
||||
headSha: pr.head.sha,
|
||||
maintainerCanModify: pr.maintainer_can_modify === true,
|
||||
};
|
||||
}
|
||||
|
||||
async function listChecksOnSha(github, owner, repo, sha) {
|
||||
const { data } = await github.rest.checks.listForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: sha,
|
||||
per_page: 100,
|
||||
});
|
||||
return (data.check_runs || []).filter((r) => r.head_sha === sha);
|
||||
}
|
||||
|
||||
function listFailedChecksOnSha(runs) {
|
||||
return (runs || []).filter((run) => {
|
||||
if (run.status !== 'completed') return false;
|
||||
return run.conclusion === 'failure';
|
||||
});
|
||||
}
|
||||
|
||||
async function allChecksGreenOnSha(github, owner, repo, sha, core) {
|
||||
const runs = await listChecksOnSha(github, owner, repo, sha);
|
||||
if (runs.length === 0) {
|
||||
core?.info?.(`No check runs on ${sha.slice(0, 7)} — allowing (e.g. docs-only PR)`);
|
||||
return true;
|
||||
}
|
||||
for (const run of runs) {
|
||||
if (run.status !== 'completed') {
|
||||
core?.info?.(`Check pending: ${run.name} (${run.status})`);
|
||||
return false;
|
||||
}
|
||||
const ok =
|
||||
['success', 'neutral', 'skipped'].includes(run.conclusion) ||
|
||||
(run.conclusion === 'cancelled' && OPTIONAL_CANCELLED_CHECKS.has(run.name));
|
||||
if (!ok) {
|
||||
core?.info?.(`Check failed: ${run.name} (${run.conclusion})`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function claudeRunBlocksPr(run, headRef) {
|
||||
if (!run || run.event === 'issues') return false;
|
||||
if (!headRef) return true;
|
||||
return run.head_branch === headRef;
|
||||
}
|
||||
|
||||
function hasBlockingClaudeRunForPr(runs, headRef) {
|
||||
return (runs || []).some((r) => claudeRunBlocksPr(r, headRef));
|
||||
}
|
||||
|
||||
async function hasInProgressClaudeAssistant(github, owner, repo, prNumber = null) {
|
||||
try {
|
||||
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: 'claude.yml',
|
||||
status: 'in_progress',
|
||||
per_page: 20,
|
||||
});
|
||||
const runs = (data.workflow_runs || []).filter((r) => r.event !== 'issues');
|
||||
if (runs.length === 0) return false;
|
||||
if (prNumber == null) return runs.length > 0;
|
||||
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
const headRef = pr.head.ref;
|
||||
return hasBlockingClaudeRunForPr(runs, headRef);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isCiOnlyChange(files) {
|
||||
if (!files.length) return false;
|
||||
return files.every((f) => CI_ONLY_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
|
||||
}
|
||||
|
||||
function isInternalPullRequestLink(link, owner, repo) {
|
||||
if (link?.base?.repo?.full_name === `${owner}/${repo}`) return true;
|
||||
const baseUrl = link?.base?.repo?.url || '';
|
||||
return baseUrl.endsWith(`/repos/${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async function resolvePrNumberFromLinkedPullRequests(github, owner, repo, linked) {
|
||||
const repoFull = `${owner}/${repo}`;
|
||||
const internal = (linked || []).filter((l) => isInternalPullRequestLink(l, owner, repo));
|
||||
for (const link of internal) {
|
||||
if (!link.number) continue;
|
||||
try {
|
||||
const { data } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: link.number,
|
||||
});
|
||||
if (data.state === 'open' && data.base?.repo?.full_name === repoFull) {
|
||||
return data.number;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.status !== 404) throw err;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolvePrNumberFromHeadBranch(github, owner, repo, branch) {
|
||||
if (!branch || branch === 'main' || branch === 'master') return null;
|
||||
const { data } = await github.rest.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
head: `${owner}:${branch}`,
|
||||
per_page: 1,
|
||||
});
|
||||
return data[0]?.number || null;
|
||||
}
|
||||
|
||||
async function resolvePrNumberFromWorkflowRun(github, owner, repo, workflowRun) {
|
||||
const fromLinked = await resolvePrNumberFromLinkedPullRequests(
|
||||
github, owner, repo, workflowRun.pull_requests
|
||||
);
|
||||
if (fromLinked) return fromLinked;
|
||||
|
||||
const fromBranch = await resolvePrNumberFromHeadBranch(
|
||||
github, owner, repo, workflowRun.head_branch
|
||||
);
|
||||
if (fromBranch) return fromBranch;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function listPullFiles(github, owner, repo, prNumber) {
|
||||
if (typeof github.paginate === 'function') {
|
||||
return github.paginate(github.rest.pulls.listFiles, {
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
}
|
||||
const { data } = await github.rest.pulls.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function countNewAgentParams(patch) {
|
||||
if (!patch) return 0;
|
||||
return patch
|
||||
.split('\n')
|
||||
.filter((l) => l.startsWith('+') && !l.startsWith('+++'))
|
||||
.filter((l) => {
|
||||
const t = l.slice(1).trim();
|
||||
if (!t || t.startsWith('#') || t.startsWith('"""') || t.startsWith("'''")) return false;
|
||||
if (/^(def|class|@|return\b|if\b|elif\b|else\b|for\b|while\b)/.test(t)) return false;
|
||||
if (/^\w+\s*[:=]/.test(t) && !t.startsWith('self.')) return true;
|
||||
return false;
|
||||
}).length;
|
||||
}
|
||||
|
||||
function getAgentPyChangeFromFiles(files) {
|
||||
const agentFile = files.find((f) => f.filename.endsWith('praisonaiagents/agent/agent.py'));
|
||||
if (!agentFile) {
|
||||
return { touched: false, additions: 0, newParams: 0 };
|
||||
}
|
||||
return {
|
||||
touched: true,
|
||||
additions: agentFile.additions || 0,
|
||||
newParams: countNewAgentParams(agentFile.patch || ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function getAgentPyChange(github, owner, repo, prNumber) {
|
||||
const files = await listPullFiles(github, owner, repo, prNumber);
|
||||
return getAgentPyChangeFromFiles(files);
|
||||
}
|
||||
|
||||
function touchesSdk(files) {
|
||||
return files.some((f) => SDK_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
|
||||
}
|
||||
|
||||
function hasManualOnlyLabel(labels) {
|
||||
return labels.some((l) => MANUAL_ONLY_LABELS.has(l));
|
||||
}
|
||||
|
||||
function sensitivePathReasons(files, labels = []) {
|
||||
if (labels.includes(WORKFLOW_ONLY_LABEL) && isCiOnlyChange(files)) {
|
||||
return [];
|
||||
}
|
||||
const reasons = [];
|
||||
for (const f of files) {
|
||||
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(f.filename))) {
|
||||
reasons.push(`sensitive path: ${f.filename}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return reasons;
|
||||
}
|
||||
|
||||
function prSizeReasons(files) {
|
||||
const reasons = [];
|
||||
const totalAdditions = files.reduce((sum, f) => sum + (f.additions || 0), 0);
|
||||
if (totalAdditions > PR_MAX_AUTO_ADDITIONS) {
|
||||
reasons.push(`PR +${totalAdditions} lines (>${PR_MAX_AUTO_ADDITIONS}) requires manual review`);
|
||||
}
|
||||
if (files.length > PR_MAX_AUTO_FILES) {
|
||||
reasons.push(`${files.length} files changed (>${PR_MAX_AUTO_FILES}) requires manual review`);
|
||||
}
|
||||
return reasons;
|
||||
}
|
||||
|
||||
function missingTestsReason(files) {
|
||||
const productAdds = files.filter(
|
||||
(f) =>
|
||||
SDK_PATH_PREFIXES.some((p) => f.filename.startsWith(p)) &&
|
||||
f.filename.startsWith('src/praisonaiui/') &&
|
||||
f.filename.endsWith('.py') &&
|
||||
!f.filename.endsWith('__init__.py') &&
|
||||
(f.additions || 0) > 0
|
||||
);
|
||||
if (productAdds.length === 0) return null;
|
||||
const hasTestChange = files.some(
|
||||
(f) => /\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename) || /_test\.py$/.test(f.filename)
|
||||
);
|
||||
if (!hasTestChange) return 'Product code added without test file changes — requires manual review';
|
||||
return null;
|
||||
}
|
||||
|
||||
function secretScanReasons(files) {
|
||||
for (const f of files) {
|
||||
if (/\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename)) continue;
|
||||
const patch = f.patch || '';
|
||||
if (!patch) continue;
|
||||
for (const pattern of SECRET_PATTERNS) {
|
||||
if (pattern.test(patch)) {
|
||||
return [`possible secret in diff (${f.filename}) — requires manual review`];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async function sdkTestChecksReason(github, owner, repo, sha, files, core) {
|
||||
if (!touchesSdk(files)) return null;
|
||||
const { data } = await github.rest.checks.listForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: sha,
|
||||
per_page: 100,
|
||||
});
|
||||
const runs = (data.check_runs || []).filter((r) => r.head_sha === sha);
|
||||
if (runs.length === 0) {
|
||||
return 'SDK code changed but no CI checks on HEAD';
|
||||
}
|
||||
const testRuns = runs.filter((r) => REQUIRED_SDK_CHECK_PATTERNS.some((p) => p.test(r.name || '')));
|
||||
if (testRuns.length === 0) {
|
||||
return 'SDK code changed but no test check runs on HEAD';
|
||||
}
|
||||
core?.info?.(`SDK test checks on HEAD: ${testRuns.map((r) => r.name).join(', ')}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
function manualReviewReasonForAgentPy(agentChange) {
|
||||
if (!agentChange.touched) return null;
|
||||
if (agentChange.additions > AGENT_PY_MAX_AUTO_LINES) {
|
||||
return `agent.py +${agentChange.additions} lines (>${AGENT_PY_MAX_AUTO_LINES}) requires manual review`;
|
||||
}
|
||||
if (agentChange.newParams > 0) {
|
||||
return `agent.py adds ${agentChange.newParams} new Agent param(s) — requires manual review`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function listAllComments(github, owner, repo, issueNumber) {
|
||||
if (typeof github.paginate === 'function') {
|
||||
return github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
}
|
||||
const { data } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getHeadCommitDate(github, owner, repo, prNumber) {
|
||||
try {
|
||||
let commits;
|
||||
if (typeof github.paginate === 'function') {
|
||||
commits = await github.paginate(github.rest.pulls.listCommits, {
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
} else {
|
||||
const { data } = await github.rest.pulls.listCommits({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
commits = data;
|
||||
}
|
||||
const last = commits[commits.length - 1];
|
||||
return last?.commit?.committer?.date || last?.commit?.author?.date || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPrContext(github, owner, repo, prNumber) {
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: prNumber });
|
||||
const comments = await listAllComments(github, owner, repo, prNumber);
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
const mergeState = await getMergeState(github, owner, repo, prNumber);
|
||||
const headSha = mergeState.headSha || pr.head.sha;
|
||||
const headCommitDate = await getHeadCommitDate(github, owner, repo, prNumber);
|
||||
const headPushedAt = headCommitDate || pr.updated_at;
|
||||
|
||||
return {
|
||||
pr,
|
||||
issue,
|
||||
comments,
|
||||
reviews,
|
||||
mergeState,
|
||||
headSha,
|
||||
headPushedAt,
|
||||
labels: issue.labels.map((l) => l.name),
|
||||
baseRepo: `${owner}/${repo}`,
|
||||
};
|
||||
}
|
||||
|
||||
async function evaluatePipelineQuiescent(github, owner, repo, prNumber, core, options = {}) {
|
||||
const {
|
||||
forMergeStep = false,
|
||||
skipGlobalClaudeRunCheck = false,
|
||||
skipRecentClaudeCooldown = false,
|
||||
} = options;
|
||||
const ctx = await loadPrContext(github, owner, repo, prNumber);
|
||||
const reasons = [];
|
||||
|
||||
if (ctx.pr.draft) reasons.push('draft');
|
||||
if (ctx.pr.state !== 'open') reasons.push('not open');
|
||||
if (ctx.labels.includes('auto-merged-by-gate')) reasons.push('already merged by gate');
|
||||
if (ctx.labels.includes('no-auto-merge')) reasons.push('no-auto-merge label');
|
||||
if (ctx.labels.includes('claude-conflict-pending')) reasons.push('claude-conflict-pending');
|
||||
if (!forMergeStep && ctx.labels.includes('claude-merge-gate-active')) {
|
||||
reasons.push('claude-merge-gate-active');
|
||||
}
|
||||
|
||||
const { status, headRepo, maintainerCanModify } = ctx.mergeState;
|
||||
if (!ALLOWED_MERGE_STATES.has(status)) reasons.push(`mergeState=${status}`);
|
||||
|
||||
if (headRepo && headRepo !== ctx.baseRepo) {
|
||||
reasons.push('fork PR');
|
||||
}
|
||||
|
||||
if (hasRecentConflictComment(ctx.comments, ctx.headPushedAt)) {
|
||||
reasons.push('recent merge-conflict @claude');
|
||||
}
|
||||
if (!skipRecentClaudeCooldown && hasRecentClaudeTrigger(ctx.comments, 35)) {
|
||||
const verdictOnHead = findMergeGateVerdict(ctx.comments, null, ctx.headPushedAt) !== null;
|
||||
if (!verdictOnHead) reasons.push('recent @claude within 35min');
|
||||
}
|
||||
|
||||
if (!skipGlobalClaudeRunCheck && (await hasInProgressClaudeAssistant(github, owner, repo, prNumber))) {
|
||||
reasons.push('claude.yml in progress');
|
||||
}
|
||||
|
||||
const checksOk = await allChecksGreenOnSha(github, owner, repo, ctx.headSha, core);
|
||||
if (!checksOk) reasons.push('CI not green on HEAD');
|
||||
|
||||
if (!finalClaudeCompletedOnSha(ctx.comments, ctx.headPushedAt)) {
|
||||
if (!hasFinalClaudeReviewTrigger(ctx.comments)) {
|
||||
reasons.push('no FINAL Claude review trigger');
|
||||
} else if (isStaleFinalAfterPush(ctx.comments, ctx.headPushedAt)) {
|
||||
reasons.push('stale FINAL after new commits (needs @claude re-review)');
|
||||
} else {
|
||||
reasons.push('FINAL Claude not complete on HEAD');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAnyChangesRequested(ctx.reviews)) reasons.push('CHANGES_REQUESTED review');
|
||||
|
||||
if (hasManualOnlyLabel(ctx.labels)) {
|
||||
const manualLabel = ctx.labels.find((l) => MANUAL_ONLY_LABELS.has(l));
|
||||
reasons.push(`manual-only label: ${manualLabel}`);
|
||||
}
|
||||
|
||||
const pullFiles = await listPullFiles(github, owner, repo, prNumber);
|
||||
|
||||
if (config.agentPyChecks) {
|
||||
const agentChange = getAgentPyChangeFromFiles(pullFiles);
|
||||
const agentManual = manualReviewReasonForAgentPy(agentChange);
|
||||
if (agentManual) reasons.push(agentManual);
|
||||
}
|
||||
|
||||
reasons.push(...sensitivePathReasons(pullFiles, ctx.labels));
|
||||
reasons.push(...prSizeReasons(pullFiles));
|
||||
reasons.push(...secretScanReasons(pullFiles));
|
||||
|
||||
const testsReason = missingTestsReason(pullFiles);
|
||||
if (testsReason) reasons.push(testsReason);
|
||||
|
||||
const sdkCheckReason = await sdkTestChecksReason(github, owner, repo, ctx.headSha, pullFiles, core);
|
||||
if (sdkCheckReason) reasons.push(sdkCheckReason);
|
||||
|
||||
if (ctx.pr.mergeable === false) reasons.push('not mergeable');
|
||||
|
||||
const ready = reasons.length === 0;
|
||||
return {
|
||||
ready,
|
||||
reasons,
|
||||
headSha: ctx.headSha,
|
||||
prNumber,
|
||||
};
|
||||
}
|
||||
|
||||
async function listPrNumbersForMergeGateScan(github, owner, repo, core) {
|
||||
let mergeReadyIssues;
|
||||
if (typeof github.paginate === 'function') {
|
||||
mergeReadyIssues = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
labels: MERGE_READY_LABEL,
|
||||
per_page: 100,
|
||||
});
|
||||
} else {
|
||||
({ data: mergeReadyIssues } = await github.rest.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
labels: MERGE_READY_LABEL,
|
||||
per_page: 100,
|
||||
}));
|
||||
}
|
||||
const mergeReady = mergeReadyIssues
|
||||
.filter((issue) => issue.pull_request)
|
||||
.map((issue) => issue.number)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
if (mergeReady.length > 0) {
|
||||
core?.info?.(
|
||||
`Merge gate scan: ${mergeReady.length} ${MERGE_READY_LABEL} PR(s) (skipping unlabelled open PRs)`
|
||||
);
|
||||
return mergeReady;
|
||||
}
|
||||
|
||||
core?.info?.(`No ${MERGE_READY_LABEL} PRs — scanning oldest open PRs`);
|
||||
const prs =
|
||||
typeof github.paginate === 'function'
|
||||
? await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
sort: 'created',
|
||||
direction: 'asc',
|
||||
})
|
||||
: (await github.rest.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
sort: 'created',
|
||||
direction: 'asc',
|
||||
})).data;
|
||||
return prs.map((pr) => pr.number);
|
||||
}
|
||||
|
||||
async function selectMergeGateCandidates(github, owner, repo, prNumbers, maxCandidates, core) {
|
||||
const readyList = [];
|
||||
const skipped = [];
|
||||
for (const num of prNumbers) {
|
||||
if (readyList.length >= maxCandidates) {
|
||||
core?.info?.(`Found ${maxCandidates} candidate(s) — skipping remaining PR scans`);
|
||||
break;
|
||||
}
|
||||
const result = await evaluatePipelineQuiescent(github, owner, repo, num, core);
|
||||
if (result.ready) {
|
||||
readyList.push({ pr_number: num, head_sha: result.headSha });
|
||||
} else {
|
||||
skipped.push({ pr: num, reasons: result.reasons });
|
||||
}
|
||||
}
|
||||
readyList.sort((a, b) => a.pr_number - b.pr_number);
|
||||
return {
|
||||
candidates: readyList.slice(0, maxCandidates),
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
const AUTOMATED_FALLBACK_MARKER = 'Automated fallback —';
|
||||
|
||||
function isAutomatedFallbackVerdict(body) {
|
||||
return (body || '').includes(AUTOMATED_FALLBACK_MARKER);
|
||||
}
|
||||
|
||||
function findMergeGateVerdict(comments, minCreatedAt = null, headPushedAt = null, options = {}) {
|
||||
const { excludeAutomatedFallback = false } = options || {};
|
||||
const minTime = minCreatedAt ? new Date(minCreatedAt).getTime() : 0;
|
||||
const headTime = headPushedAt ? new Date(headPushedAt).getTime() - 60000 : 0;
|
||||
const gateComments = comments
|
||||
.filter((c) => {
|
||||
if (!(c.body || '').includes('MERGE_GATE_VERDICT:')) return false;
|
||||
if (excludeAutomatedFallback && isAutomatedFallbackVerdict(c.body)) return false;
|
||||
const created = new Date(c.created_at).getTime();
|
||||
if (minTime && created < minTime) return false;
|
||||
if (headTime && created < headTime) return false;
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||
if (gateComments.length === 0) return null;
|
||||
const body = gateComments[0].body || '';
|
||||
if (body.includes('MERGE_GATE_VERDICT: APPROVE')) return 'APPROVE';
|
||||
if (body.includes('MERGE_GATE_VERDICT: BLOCK')) return 'BLOCK';
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CLAUDE_TRIGGER_LOGINS,
|
||||
AUTO_ACTORS,
|
||||
isFinalClaudeTriggerComment,
|
||||
isClaudeTriggerNoise,
|
||||
isClaudeFinalReplyComment,
|
||||
hasRecentClaudeTrigger,
|
||||
hasRecentConflictComment,
|
||||
isConflictRebaseTriggerComment,
|
||||
isConflictRebaseCompletionComment,
|
||||
conflictRebaseQuiescent,
|
||||
hasHumanChangesRequested,
|
||||
hasAnyChangesRequested,
|
||||
hasFinalClaudeReviewTrigger,
|
||||
finalClaudeCompletedOnSha,
|
||||
getMergeState,
|
||||
OPTIONAL_CANCELLED_CHECKS,
|
||||
listChecksOnSha,
|
||||
listFailedChecksOnSha,
|
||||
allChecksGreenOnSha,
|
||||
hasInProgressClaudeAssistant,
|
||||
claudeRunBlocksPr,
|
||||
hasBlockingClaudeRunForPr,
|
||||
isCiOnlyChange,
|
||||
isInternalPullRequestLink,
|
||||
resolvePrNumberFromLinkedPullRequests,
|
||||
resolvePrNumberFromHeadBranch,
|
||||
resolvePrNumberFromWorkflowRun,
|
||||
WORKFLOW_ONLY_LABEL,
|
||||
getAgentPyChange,
|
||||
getAgentPyChangeFromFiles,
|
||||
manualReviewReasonForAgentPy,
|
||||
countNewAgentParams,
|
||||
listPullFiles,
|
||||
touchesSdk,
|
||||
hasManualOnlyLabel,
|
||||
sensitivePathReasons,
|
||||
prSizeReasons,
|
||||
missingTestsReason,
|
||||
secretScanReasons,
|
||||
sdkTestChecksReason,
|
||||
listAllComments,
|
||||
getHeadCommitDate,
|
||||
isStaleFinalAfterPush,
|
||||
needsStaleFinalRecovery,
|
||||
shouldSkipFinalRecovery,
|
||||
shouldSkipStaleFinalRecovery,
|
||||
isClaudeAutomationLogin,
|
||||
isPushSoonAfterLatestFinal,
|
||||
countFinalTriggersSince,
|
||||
STALE_FINAL_RECOVERY_WINDOW_MS,
|
||||
STALE_FINAL_MAX_PER_WINDOW,
|
||||
FINAL_CLAUDE_REVIEW_BODY,
|
||||
loadPrContext,
|
||||
evaluatePipelineQuiescent,
|
||||
MERGE_READY_LABEL,
|
||||
listPrNumbersForMergeGateScan,
|
||||
selectMergeGateCandidates,
|
||||
isAutomatedFallbackVerdict,
|
||||
AUTOMATED_FALLBACK_MARKER,
|
||||
findMergeGateVerdict,
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Run: node .github/scripts/pipeline-status-selftest.js
|
||||
*/
|
||||
const ps = require('./pipeline-status.js');
|
||||
const mg = require('./merge-gate.js');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
let failed = 0;
|
||||
function assert(name, cond) {
|
||||
if (!cond) {
|
||||
console.error('FAIL:', name);
|
||||
failed++;
|
||||
} else {
|
||||
console.log('ok:', name);
|
||||
}
|
||||
}
|
||||
|
||||
const kickComments = [
|
||||
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
|
||||
{ user: { login: 'MervinPraison' }, body: '/review' },
|
||||
];
|
||||
|
||||
const finalComment = {
|
||||
user: { login: 'MervinPraison' },
|
||||
body: '@claude You are the FINAL architecture reviewer.',
|
||||
created_at: '2026-06-26T10:00:00Z',
|
||||
};
|
||||
|
||||
assert(
|
||||
'reviews pending when not kicked',
|
||||
ps.deriveStage([], { ready: false, reasons: ['no FINAL Claude review trigger'] })
|
||||
=== 'pipeline/reviews-pending'
|
||||
);
|
||||
assert(
|
||||
'final pending after kick',
|
||||
ps.deriveStage(kickComments, { ready: false, reasons: ['no FINAL Claude review trigger'] })
|
||||
=== 'pipeline/final-claude-pending'
|
||||
);
|
||||
assert(
|
||||
'awaiting merge gate after bot review comment',
|
||||
ps.deriveStage(
|
||||
[
|
||||
{ user: { login: 'coderabbitai[bot]' }, body: 'review summary' },
|
||||
finalComment,
|
||||
],
|
||||
{ ready: false, reasons: ['recent @claude within 35min'] }
|
||||
) === 'pipeline/awaiting-merge-gate'
|
||||
);
|
||||
|
||||
assert('review bot logins configured', config.reviewBotLogins.includes('coderabbitai[bot]'));
|
||||
assert(
|
||||
'merge ready',
|
||||
ps.deriveStage([...kickComments, finalComment], { ready: true, reasons: [] })
|
||||
=== 'pipeline/merge-ready'
|
||||
);
|
||||
|
||||
const blockers = ps.deriveBlockerLabels({
|
||||
ready: false,
|
||||
reasons: ['CI not green on HEAD', 'SDK code added without test file changes — requires manual review'],
|
||||
});
|
||||
assert('maps ci blocker', blockers.includes('pipeline/blocked:ci'));
|
||||
assert('maps manual blocker', blockers.includes('pipeline/blocked:manual-review'));
|
||||
|
||||
assert(
|
||||
'internal link matches upstream base',
|
||||
mg.isInternalPullRequestLink(
|
||||
{ base: { repo: { full_name: 'MervinPraison/PraisonAI' } } },
|
||||
'MervinPraison',
|
||||
'PraisonAI'
|
||||
)
|
||||
);
|
||||
assert(
|
||||
'fork sync link rejected',
|
||||
!mg.isInternalPullRequestLink(
|
||||
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAI' } } },
|
||||
'MervinPraison',
|
||||
'PraisonAI'
|
||||
)
|
||||
);
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Sync pipeline stage/blocker labels on open PRs.
|
||||
* @see .github/workflows/pipeline-status-sync.yml
|
||||
*/
|
||||
|
||||
const mergeGate = require('./merge-gate.js');
|
||||
const chain = require('./bot-pr-review-chain.js');
|
||||
const ciFix = require('./ci-failure-claude.js');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
const PIPELINE_PREFIX = 'pipeline/';
|
||||
const STAGE_LABELS = [
|
||||
'pipeline/reviews-pending',
|
||||
'pipeline/final-claude-pending',
|
||||
'pipeline/awaiting-merge-gate',
|
||||
'pipeline/merge-ready',
|
||||
'pipeline/merged',
|
||||
];
|
||||
const BLOCKER_LABELS = [
|
||||
'pipeline/blocked:ci',
|
||||
'pipeline/blocked:conflict',
|
||||
'pipeline/blocked:manual-review',
|
||||
'pipeline/blocked:cooldown',
|
||||
'pipeline/blocked:stale-final',
|
||||
'pipeline/blocked:no-final',
|
||||
];
|
||||
const ALL_PIPELINE_LABELS = [...STAGE_LABELS, ...BLOCKER_LABELS];
|
||||
|
||||
const LABEL_SPECS = [
|
||||
{ name: 'pipeline/reviews-pending', color: 'fbca04', description: 'Waiting for CodeRabbit/Qodo/Copilot reviews' },
|
||||
{ name: 'pipeline/final-claude-pending', color: 'd4c5f9', description: 'Reviews done; waiting for FINAL @claude' },
|
||||
{ name: 'pipeline/awaiting-merge-gate', color: 'c5def5', description: 'FINAL done; waiting for merge gate / CI' },
|
||||
{ name: 'pipeline/merge-ready', color: '0e8a16', description: 'Eligible for merge gate auto-merge' },
|
||||
{ name: 'pipeline/merged', color: '6f42c1', description: 'Merged via pipeline' },
|
||||
{ name: 'pipeline/blocked:ci', color: 'd93f0b', description: 'Blocked: CI not green on HEAD' },
|
||||
{ name: 'pipeline/blocked:conflict', color: 'b60205', description: 'Blocked: merge conflict or rebase pending' },
|
||||
{ name: 'pipeline/blocked:manual-review', color: 'e99695', description: 'Blocked: requires manual review' },
|
||||
{ name: 'pipeline/blocked:cooldown', color: 'fef2c0', description: 'Blocked: post-push or @claude cooldown' },
|
||||
{ name: 'pipeline/blocked:stale-final', color: 'f9d0c4', description: 'Blocked: FINAL stale after new commits' },
|
||||
{ name: 'pipeline/blocked:no-final', color: 'ededed', description: 'Blocked: no FINAL @claude trigger yet' },
|
||||
];
|
||||
|
||||
function reviewChainStarted(comments) {
|
||||
if (chain.chainKickPosted(comments)) return true;
|
||||
return comments.some((c) => config.reviewBotLogins.includes(c.user?.login));
|
||||
}
|
||||
|
||||
function deriveStage(comments, evalResult) {
|
||||
if (evalResult.ready) return 'pipeline/merge-ready';
|
||||
if (!reviewChainStarted(comments)) return 'pipeline/reviews-pending';
|
||||
if (!mergeGate.hasFinalClaudeReviewTrigger(comments)) return 'pipeline/final-claude-pending';
|
||||
return 'pipeline/awaiting-merge-gate';
|
||||
}
|
||||
|
||||
function reasonToBlockerLabel(reason) {
|
||||
const r = (reason || '').toLowerCase();
|
||||
if (r.includes('ci not green') || r.includes('sdk code changed but no ci')) return 'pipeline/blocked:ci';
|
||||
if (
|
||||
r.includes('conflict') ||
|
||||
r.includes('mergestate=dirty') ||
|
||||
r.includes('not mergeable')
|
||||
) return 'pipeline/blocked:conflict';
|
||||
if (
|
||||
r.includes('manual') ||
|
||||
r.includes('requires manual') ||
|
||||
r.includes('sensitive path') ||
|
||||
r.includes('no-auto-merge') ||
|
||||
r.includes('manual-only label') ||
|
||||
r.includes('without test') ||
|
||||
r.includes('product code') ||
|
||||
r.includes('files changed') ||
|
||||
r.includes('possible secret')
|
||||
) return 'pipeline/blocked:manual-review';
|
||||
if (r.includes('recent @claude') || r.includes('post-push buffer')) return 'pipeline/blocked:cooldown';
|
||||
if (r.includes('stale final')) return 'pipeline/blocked:stale-final';
|
||||
if (r.includes('no final claude')) return 'pipeline/blocked:no-final';
|
||||
if (r.includes('final claude not complete')) return 'pipeline/blocked:stale-final';
|
||||
if (r.includes('claude.yml in progress') || r.includes('claude-merge-gate-active')) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function deriveBlockerLabels(evalResult) {
|
||||
if (evalResult.ready) return [];
|
||||
const labels = new Set();
|
||||
for (const reason of evalResult.reasons || []) {
|
||||
const mapped = reasonToBlockerLabel(reason);
|
||||
if (mapped && mapped.startsWith('pipeline/blocked:')) labels.add(mapped);
|
||||
}
|
||||
return [...labels];
|
||||
}
|
||||
|
||||
function computePipelineLabels(comments, evalResult) {
|
||||
const stage = deriveStage(comments, evalResult);
|
||||
const blockers = stage === 'pipeline/merge-ready' ? [] : deriveBlockerLabels(evalResult);
|
||||
return { stage, blockers, all: [stage, ...blockers] };
|
||||
}
|
||||
|
||||
async function ensurePipelineLabels(github, owner, repo, core) {
|
||||
let existing;
|
||||
if (typeof github.paginate === 'function') {
|
||||
existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
});
|
||||
} else {
|
||||
({ data: existing } = await github.rest.issues.listLabelsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
}));
|
||||
}
|
||||
const names = new Set(existing.map((l) => l.name));
|
||||
for (const spec of LABEL_SPECS) {
|
||||
if (names.has(spec.name)) continue;
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: spec.name,
|
||||
color: spec.color,
|
||||
description: spec.description,
|
||||
});
|
||||
core?.info?.(`Created label ${spec.name}`);
|
||||
} catch (err) {
|
||||
if (err.status !== 422) throw err;
|
||||
core?.info?.(`Label ${spec.name} already exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function syncPipelineLabels(github, owner, repo, prNumber, core) {
|
||||
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
|
||||
if (ctx.pr.state !== 'open') return { synced: false, reason: 'not_open' };
|
||||
|
||||
const evalResult = await mergeGate.evaluatePipelineQuiescent(
|
||||
github, owner, repo, prNumber, core
|
||||
);
|
||||
const { stage, blockers, all } = computePipelineLabels(ctx.comments, evalResult);
|
||||
|
||||
const current = ctx.labels.filter((l) => l.startsWith(PIPELINE_PREFIX));
|
||||
const desired = new Set(all);
|
||||
const toRemove = current.filter((l) => !desired.has(l) && l !== 'pipeline/merged');
|
||||
const toAdd = all.filter((l) => !current.includes(l));
|
||||
|
||||
for (const name of toRemove) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name });
|
||||
} catch (err) {
|
||||
if (err.status !== 404) throw err;
|
||||
}
|
||||
}
|
||||
if (toAdd.length) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: toAdd,
|
||||
});
|
||||
}
|
||||
|
||||
core?.info?.(`PR #${prNumber}: stage=${stage} blockers=[${blockers.join(', ')}]`);
|
||||
|
||||
const ciNotGreen = (evalResult.reasons || []).some((r) =>
|
||||
r.toLowerCase().includes('ci not green')
|
||||
);
|
||||
if (ciNotGreen || blockers.includes('pipeline/blocked:ci')) {
|
||||
await ciFix.maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
|
||||
} else {
|
||||
await ciFix.maybeClearCiFixLabel(
|
||||
github, owner, repo, prNumber, ctx.labels, ctx.headSha, core
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
synced: true,
|
||||
stage,
|
||||
blockers,
|
||||
ready: evalResult.ready,
|
||||
reasons: evalResult.reasons,
|
||||
labels: ctx.labels,
|
||||
createdAt: new Date(ctx.pr.created_at).getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
async function dispatchMergeGateForOldestReady(github, owner, repo, readyCandidates, core) {
|
||||
if (!readyCandidates.length) return 0;
|
||||
readyCandidates.sort((a, b) => a.createdAt - b.createdAt);
|
||||
for (const cand of readyCandidates) {
|
||||
if ((cand.labels || []).includes('claude-merge-gate-active')) {
|
||||
core?.info?.(`Skip dispatch PR #${cand.prNumber}: merge gate already active`);
|
||||
continue;
|
||||
}
|
||||
await github.rest.repos.createDispatchEvent({
|
||||
owner,
|
||||
repo,
|
||||
event_type: 'claude-merge-gate',
|
||||
client_payload: { pr_number: cand.prNumber },
|
||||
});
|
||||
core?.info?.(`Dispatched merge gate for ready PR #${cand.prNumber}`);
|
||||
return cand.prNumber;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function syncOpenPullRequests(github, owner, repo, options, core) {
|
||||
const { maxPrs = 20, dispatchMergeGate = true } = options || {};
|
||||
await ensurePipelineLabels(github, owner, repo, core);
|
||||
let prs;
|
||||
if (maxPrs <= 100) {
|
||||
({ data: prs } = await github.rest.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
}));
|
||||
} else {
|
||||
prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
}
|
||||
let synced = 0;
|
||||
const readyCandidates = [];
|
||||
for (const pr of prs) {
|
||||
if (synced >= maxPrs) break;
|
||||
if (pr.draft) continue;
|
||||
if (pr.head?.repo?.full_name && pr.head.repo.full_name !== `${owner}/${repo}`) continue;
|
||||
const result = await syncPipelineLabels(github, owner, repo, pr.number, core);
|
||||
if (result.ready) {
|
||||
readyCandidates.push({
|
||||
prNumber: pr.number,
|
||||
createdAt: result.createdAt || new Date(pr.created_at).getTime(),
|
||||
labels: result.labels || [],
|
||||
});
|
||||
}
|
||||
synced += 1;
|
||||
}
|
||||
let dispatched = 0;
|
||||
if (dispatchMergeGate && readyCandidates.length) {
|
||||
dispatched = await dispatchMergeGateForOldestReady(
|
||||
github, owner, repo, readyCandidates, core
|
||||
);
|
||||
}
|
||||
core?.info?.(`Pipeline label sync complete (${synced} PR(s), dispatched=${dispatched || 'none'})`);
|
||||
return synced;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
STAGE_LABELS,
|
||||
BLOCKER_LABELS,
|
||||
ALL_PIPELINE_LABELS,
|
||||
deriveStage,
|
||||
deriveBlockerLabels,
|
||||
computePipelineLabels,
|
||||
ensurePipelineLabels,
|
||||
syncPipelineLabels,
|
||||
syncOpenPullRequests,
|
||||
dispatchMergeGateForOldestReady,
|
||||
};
|
||||
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* PR review pipeline: CodeRabbit / Greptile (+ optional Qodo/Gemini) → Claude (FINAL).
|
||||
* Copilot step optional — set REVIEW_CHAIN_SKIP_COPILOT=1 or skipCopilot: true to bypass.
|
||||
* @see .github/workflows/auto-pr-comment.yml
|
||||
*/
|
||||
|
||||
function isSkipCopilot(options = {}) {
|
||||
if (options.skipCopilot === true) return true;
|
||||
if (options.skipCopilot === false) return false;
|
||||
return process.env.REVIEW_CHAIN_SKIP_COPILOT === '1';
|
||||
}
|
||||
|
||||
const COPILOT_TRIGGER_LOGINS = new Set(['MervinPraison', 'github-actions[bot]']);
|
||||
const CLAUDE_TRIGGER_LOGINS = new Set(['MervinPraison', 'github-actions[bot]']);
|
||||
|
||||
const REQUIRED_PRIOR = ['coderabbit', 'greptile'];
|
||||
const OPTIONAL_PRIOR = ['qodo', 'gemini'];
|
||||
const OPTIONAL_PRIOR_WAIT_MS = 20 * 60 * 1000;
|
||||
|
||||
function loginOf(item) {
|
||||
return (item?.user?.login || '').toLowerCase();
|
||||
}
|
||||
|
||||
function bodyOf(item) {
|
||||
return item?.body || '';
|
||||
}
|
||||
|
||||
function hasCoderabbitSummary(comments) {
|
||||
return comments.some(
|
||||
(c) =>
|
||||
loginOf(c).includes('coderabbit') &&
|
||||
bodyOf(c).toLowerCase().includes('summarize by coderabbit')
|
||||
);
|
||||
}
|
||||
|
||||
function hasGreptileReview(comments) {
|
||||
return comments.some((c) => {
|
||||
if (!loginOf(c).includes('greptile')) return false;
|
||||
const body = bodyOf(c);
|
||||
return body.includes('Greptile Summary') || body.includes('<h3>Greptile Summary</h3>') || body.length > 150;
|
||||
});
|
||||
}
|
||||
|
||||
function hasQodoReview(comments, reviews = []) {
|
||||
if (comments.some((c) => loginOf(c).includes('qodo'))) return true;
|
||||
return reviews.some((r) => loginOf(r).includes('qodo'));
|
||||
}
|
||||
|
||||
function hasGeminiReview(comments, reviews = []) {
|
||||
if (reviews.some((r) => loginOf(r).includes('gemini'))) return true;
|
||||
return comments.some((c) => {
|
||||
if (loginOf(c).includes('gemini')) return true;
|
||||
const body = bodyOf(c);
|
||||
return (
|
||||
body.includes('Review completed by Gemini CLI') ||
|
||||
body.includes('The fix is ready for review. Please test')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const PRIOR_CHECKS = {
|
||||
coderabbit: (comments) => hasCoderabbitSummary(comments),
|
||||
greptile: (comments) => hasGreptileReview(comments),
|
||||
qodo: (comments, reviews) => hasQodoReview(comments, reviews),
|
||||
gemini: (comments, reviews) => hasGeminiReview(comments, reviews),
|
||||
};
|
||||
|
||||
function priorReviewerStatus(comments, reviews = []) {
|
||||
const status = {};
|
||||
for (const [id, check] of Object.entries(PRIOR_CHECKS)) {
|
||||
status[id] = check(comments, reviews);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
function priorReviewersReady(comments, reviews = [], options = {}) {
|
||||
const waitMs = options.optionalWaitMs ?? OPTIONAL_PRIOR_WAIT_MS;
|
||||
const prCreatedAt = options.prCreatedAt || null;
|
||||
const status = priorReviewerStatus(comments, reviews);
|
||||
|
||||
const missingRequired = REQUIRED_PRIOR.filter((id) => !status[id]);
|
||||
if (missingRequired.length) {
|
||||
return { ready: false, reason: `waiting for ${missingRequired.join(', ')}`, status };
|
||||
}
|
||||
|
||||
const missingOptional = OPTIONAL_PRIOR.filter((id) => !status[id]);
|
||||
if (missingOptional.length && prCreatedAt) {
|
||||
const age = Date.now() - new Date(prCreatedAt).getTime();
|
||||
if (age < waitMs) {
|
||||
return {
|
||||
ready: false,
|
||||
reason: `waiting for optional ${missingOptional.join(', ')} (${Math.round((waitMs - age) / 60000)}m left)`,
|
||||
status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { ready: true, reason: '', status };
|
||||
}
|
||||
|
||||
function copilotTriggerComment(comments) {
|
||||
return comments.find(
|
||||
(c) =>
|
||||
bodyOf(c).includes('@copilot') &&
|
||||
COPILOT_TRIGGER_LOGINS.has(c.user?.login) &&
|
||||
!bodyOf(c).toLowerCase().includes('@claude')
|
||||
);
|
||||
}
|
||||
|
||||
function copilotTriggered(comments) {
|
||||
return Boolean(copilotTriggerComment(comments));
|
||||
}
|
||||
|
||||
function copilotReviewReady(comments, reviews = []) {
|
||||
const trigger = copilotTriggerComment(comments);
|
||||
if (!trigger) {
|
||||
return { ready: false, reason: 'copilot not triggered yet' };
|
||||
}
|
||||
const triggerTime = new Date(trigger.created_at).getTime();
|
||||
const copilotComment = comments.some((c) => {
|
||||
if (c === trigger) return false;
|
||||
if (new Date(c.created_at).getTime() < triggerTime) return false;
|
||||
const login = loginOf(c);
|
||||
return (
|
||||
login.includes('copilot') ||
|
||||
c.user?.login === 'Copilot' ||
|
||||
c.user?.login === 'copilot-swe-agent'
|
||||
);
|
||||
});
|
||||
const copilotReview = reviews.some((r) => {
|
||||
if (new Date(r.submitted_at).getTime() < triggerTime) return false;
|
||||
return loginOf(r).includes('copilot');
|
||||
});
|
||||
if (copilotComment || copilotReview) {
|
||||
return { ready: true, reason: '' };
|
||||
}
|
||||
return { ready: false, reason: 'awaiting copilot review' };
|
||||
}
|
||||
|
||||
function isFinalClaudeTriggerComment(c) {
|
||||
const body = bodyOf(c).toLowerCase();
|
||||
if (!CLAUDE_TRIGGER_LOGINS.has(c.user?.login)) return false;
|
||||
if (!body.includes('@claude')) return false;
|
||||
if (body.includes('merge conflict')) return false;
|
||||
return body.includes('final architecture reviewer') || body.includes('lead engineer');
|
||||
}
|
||||
|
||||
function claudeFinalAlreadyTriggered(comments) {
|
||||
return comments.some(isFinalClaudeTriggerComment);
|
||||
}
|
||||
|
||||
function claudeFinalReady(comments, reviews = [], options = {}) {
|
||||
if (claudeFinalAlreadyTriggered(comments)) {
|
||||
return { ready: false, reason: 'claude FINAL already triggered', already: true };
|
||||
}
|
||||
const prior = priorReviewersReady(comments, reviews, options);
|
||||
if (!prior.ready) {
|
||||
return { ready: false, reason: prior.reason };
|
||||
}
|
||||
if (isSkipCopilot(options)) {
|
||||
return { ready: true, reason: 'copilot skipped', copilotSkipped: true };
|
||||
}
|
||||
const copilot = copilotReviewReady(comments, reviews);
|
||||
if (copilot.ready) {
|
||||
return { ready: true, reason: '' };
|
||||
}
|
||||
if (options.allowCopilotTimeout && copilotTriggered(comments)) {
|
||||
return { ready: true, reason: 'copilot timeout fallback', copilotSkipped: true };
|
||||
}
|
||||
return { ready: false, reason: copilot.reason };
|
||||
}
|
||||
|
||||
/** Post @copilot when prior reviewers are ready, then @claude FINAL when Copilot done (or timeout). */
|
||||
async function advanceReviewChain(github, owner, repo, prNumber, finalBody, core, options = {}) {
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
const chainOpts = {
|
||||
prCreatedAt: pr.created_at,
|
||||
optionalWaitMs: options.optionalWaitMs ?? 0,
|
||||
};
|
||||
const copilot = options.skipCopilot
|
||||
? { triggered: false, reason: 'skipped' }
|
||||
: await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
|
||||
const claude = await maybeTriggerClaudeFinal(
|
||||
github, owner, repo, prNumber, finalBody, core,
|
||||
{ ...options, ...chainOpts }
|
||||
);
|
||||
return { copilot, claude };
|
||||
}
|
||||
|
||||
async function maybeTriggerClaudeFinal(github, owner, repo, prNumber, finalBody, core, options = {}) {
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
|
||||
const gate = claudeFinalReady(comments, reviews, {
|
||||
prCreatedAt: pr.created_at,
|
||||
optionalWaitMs: options.optionalWaitMs,
|
||||
allowCopilotTimeout: options.allowCopilotTimeout !== false,
|
||||
});
|
||||
if (!gate.ready) {
|
||||
core?.info?.(`PR #${prNumber}: skip Claude FINAL — ${gate.reason}`);
|
||||
return { posted: false, reason: gate.reason };
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: finalBody,
|
||||
});
|
||||
const note = gate.copilotSkipped ? ' (Copilot timeout fallback)' : '';
|
||||
core?.info?.(`Posted Claude FINAL on PR #${prNumber}${note}`);
|
||||
return { posted: true, reason: '' };
|
||||
}
|
||||
|
||||
const COPILOT_REVIEW_BODY =
|
||||
'@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, CodeRabbit, Gemini, and Greptile first — incorporate their findings.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused? Any unnecessary code or scope creep?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. **Address reviewer feedback**: If Qodo, CodeRabbit, Gemini, or Greptile flagged valid issues, include them in your review\n8. Suggest specific improvements with code examples where possible';
|
||||
|
||||
async function listCommentsAndReviews(github, owner, repo, prNumber) {
|
||||
let comments;
|
||||
if (typeof github.paginate === 'function') {
|
||||
comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
} else {
|
||||
const { data } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
comments = data;
|
||||
}
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return { comments, reviews };
|
||||
}
|
||||
|
||||
async function maybeTriggerCopilot(github, owner, repo, prNumber, core, options = {}) {
|
||||
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
|
||||
if (copilotTriggered(comments)) {
|
||||
core?.info?.(`Copilot already triggered on PR #${prNumber}`);
|
||||
return { triggered: false, reason: 'already_triggered' };
|
||||
}
|
||||
const prior = priorReviewersReady(comments, reviews, options);
|
||||
if (!prior.ready) {
|
||||
core?.info?.(`PR #${prNumber}: not ready for Copilot — ${prior.reason}`);
|
||||
return { triggered: false, reason: prior.reason };
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: COPILOT_REVIEW_BODY,
|
||||
});
|
||||
core?.info?.(`Posted @copilot on PR #${prNumber} after prior reviewers`);
|
||||
return { triggered: true };
|
||||
}
|
||||
|
||||
async function pollCopilotResponse(github, owner, repo, prNumber, options = {}) {
|
||||
const maxAttempts = options.maxAttempts ?? 20;
|
||||
const delayMs = options.delayMs ?? 30000;
|
||||
for (let i = 0; i < maxAttempts; i += 1) {
|
||||
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
|
||||
const copilot = copilotReviewReady(comments, reviews);
|
||||
if (copilot.ready) {
|
||||
return { ready: true, reason: '' };
|
||||
}
|
||||
if (i < maxAttempts - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
return { ready: false, reason: 'copilot timeout' };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isSkipCopilot,
|
||||
REQUIRED_PRIOR,
|
||||
OPTIONAL_PRIOR,
|
||||
COPILOT_REVIEW_BODY,
|
||||
priorReviewerStatus,
|
||||
priorReviewersReady,
|
||||
copilotTriggered,
|
||||
copilotReviewReady,
|
||||
claudeFinalReady,
|
||||
claudeFinalAlreadyTriggered,
|
||||
isFinalClaudeTriggerComment,
|
||||
maybeTriggerCopilot,
|
||||
maybeTriggerClaudeFinal,
|
||||
advanceReviewChain,
|
||||
pollCopilotResponse,
|
||||
hasGeminiReview,
|
||||
listCommentsAndReviews,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Run: node .github/scripts/release-gate-selftest.js
|
||||
*/
|
||||
const rg = require('./release-gate.js');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
let failed = 0;
|
||||
function assert(name, cond) {
|
||||
if (!cond) {
|
||||
console.error('FAIL:', name);
|
||||
failed += 1;
|
||||
} else {
|
||||
console.log('ok:', name);
|
||||
}
|
||||
}
|
||||
|
||||
assert('package name is aiui', config.pypiPackageName === 'aiui');
|
||||
assert('bumpPatch works', rg.bumpPatch('0.3.127') === '0.3.128');
|
||||
|
||||
try {
|
||||
const versions = rg.readVersionsFromTree(process.cwd());
|
||||
assert('reads current version', /^\d+\.\d+\.\d+$/.test(versions.current));
|
||||
assert('computes target patch', versions.target === rg.bumpPatch(versions.current));
|
||||
} catch (err) {
|
||||
console.error('FAIL: readVersionsFromTree', err.message);
|
||||
failed += 1;
|
||||
}
|
||||
|
||||
assert('PACKAGE_PATHS includes pyproject', rg.PACKAGE_PATHS.includes('pyproject.toml'));
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Release gate preflight — path changes, CI SHA, dedupe, PyPI version checks (PraisonAIUI).
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const config = require('./gate-config.js');
|
||||
|
||||
const PACKAGE_PATHS = config.packagePaths;
|
||||
const ACTIVE_RELEASE_STATUSES = new Set([
|
||||
'queued', 'in_progress', 'waiting', 'pending', 'requested',
|
||||
]);
|
||||
|
||||
function bumpPatch(version) {
|
||||
const parts = version.split('.');
|
||||
if (parts.length !== 3) throw new Error(`Invalid version: ${version}`);
|
||||
return `${parts[0]}.${parts[1]}.${Number(parts[2]) + 1}`;
|
||||
}
|
||||
|
||||
function readVersionsFromTree(root = '.') {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const toml = fs.readFileSync(path.join(root, 'pyproject.toml'), 'utf8');
|
||||
const match = toml.match(/^version\s*=\s*"([^"]+)"/m);
|
||||
if (!match) throw new Error('Could not read aiui version from pyproject.toml');
|
||||
const current = match[1];
|
||||
const target = bumpPatch(current);
|
||||
return { current, target, packageName: config.pypiPackageName };
|
||||
}
|
||||
|
||||
function pypiVersionExists(packageName, version) {
|
||||
return new Promise((resolve) => {
|
||||
const req = https.get(
|
||||
`https://pypi.org/pypi/${packageName}/${version}/json`,
|
||||
{ timeout: 15000 },
|
||||
(res) => {
|
||||
res.resume();
|
||||
resolve(res.statusCode === 200);
|
||||
}
|
||||
);
|
||||
req.on('error', () => resolve(false));
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function hasActiveReleaseRun(github, owner, repo) {
|
||||
const runs = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: 'pypi-release.yml',
|
||||
per_page: 20,
|
||||
});
|
||||
return runs.data.workflow_runs.some(
|
||||
(r) => ACTIVE_RELEASE_STATUSES.has(r.status) && !r.conclusion
|
||||
);
|
||||
}
|
||||
|
||||
function utcDayStart(now = new Date()) {
|
||||
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
||||
}
|
||||
|
||||
async function hasSuccessfulReleaseToday(github, owner, repo, now = new Date()) {
|
||||
const dayStart = utcDayStart(now);
|
||||
const runs = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: 'pypi-release.yml',
|
||||
status: 'completed',
|
||||
per_page: 30,
|
||||
});
|
||||
return runs.data.workflow_runs.some(
|
||||
(r) => r.conclusion === 'success' && new Date(r.created_at) >= dayStart
|
||||
);
|
||||
}
|
||||
|
||||
async function lastGreenCiSha(github, owner, repo) {
|
||||
const runs = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: config.ciWorkflowFile,
|
||||
branch: 'main',
|
||||
status: 'completed',
|
||||
per_page: 30,
|
||||
});
|
||||
const hit = runs.data.workflow_runs.find((r) => r.conclusion === 'success');
|
||||
return hit ? hit.head_sha : '';
|
||||
}
|
||||
|
||||
async function evaluateReleasePreflight(github, owner, repo, options, core) {
|
||||
const {
|
||||
headSha,
|
||||
isCiTrigger = false,
|
||||
bump = 'patch',
|
||||
} = options;
|
||||
|
||||
const reasons = [];
|
||||
const out = {
|
||||
ready: false,
|
||||
reasons,
|
||||
headSha: headSha || '',
|
||||
lastTag: '',
|
||||
targetVersion: '',
|
||||
packageName: config.pypiPackageName,
|
||||
};
|
||||
|
||||
if (bump !== 'patch') {
|
||||
reasons.push('only patch auto-release supported');
|
||||
return out;
|
||||
}
|
||||
|
||||
if (await hasActiveReleaseRun(github, owner, repo)) {
|
||||
reasons.push('PyPI Release already in progress or awaiting approval');
|
||||
return out;
|
||||
}
|
||||
|
||||
const referenceTime = options.now instanceof Date ? options.now : new Date();
|
||||
if (await hasSuccessfulReleaseToday(github, owner, repo, referenceTime)) {
|
||||
const day = referenceTime.toISOString().slice(0, 10);
|
||||
reasons.push(`already released today (UTC ${day}); max one patch release per day`);
|
||||
return out;
|
||||
}
|
||||
|
||||
let versions;
|
||||
try {
|
||||
versions = readVersionsFromTree();
|
||||
} catch (err) {
|
||||
reasons.push(err.message);
|
||||
return out;
|
||||
}
|
||||
out.targetVersion = versions.target;
|
||||
|
||||
const onPypi = await pypiVersionExists(config.pypiPackageName, versions.target);
|
||||
if (onPypi) {
|
||||
reasons.push(`already published: ${config.pypiPackageName}==${versions.target}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
let lastTag = '';
|
||||
try {
|
||||
lastTag = execSync('git describe --tags --match "v*" --abbrev=0', { encoding: 'utf8' }).trim();
|
||||
} catch {
|
||||
reasons.push('no v* tag found');
|
||||
return out;
|
||||
}
|
||||
out.lastTag = lastTag;
|
||||
|
||||
let changed = '';
|
||||
try {
|
||||
changed = execSync(
|
||||
`git diff --name-only ${lastTag} HEAD -- ${PACKAGE_PATHS.join(' ')}`,
|
||||
{ encoding: 'utf8' }
|
||||
).trim();
|
||||
} catch {
|
||||
reasons.push('git diff failed');
|
||||
return out;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
reasons.push(`no changes in ${PACKAGE_PATHS.join(' or ')} since ${lastTag}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
if (isCiTrigger) {
|
||||
let mainSha = '';
|
||||
try {
|
||||
mainSha = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();
|
||||
} catch {
|
||||
reasons.push('could not resolve origin/main');
|
||||
return out;
|
||||
}
|
||||
if (headSha !== mainSha) {
|
||||
reasons.push(`superseded: green SHA ${headSha.slice(0, 7)} != main ${mainSha.slice(0, 7)}`);
|
||||
return out;
|
||||
}
|
||||
out.headSha = headSha;
|
||||
} else {
|
||||
const evalSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
||||
out.headSha = evalSha;
|
||||
const greenSha = await lastGreenCiSha(github, owner, repo);
|
||||
if (greenSha !== evalSha) {
|
||||
reasons.push(`CI not green on HEAD (last green: ${greenSha ? greenSha.slice(0, 7) : 'none'})`);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
out.ready = true;
|
||||
out.reasons = ['ready'];
|
||||
if (core) core.info(`Release preflight passed for ${out.headSha || headSha}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bumpPatch,
|
||||
readVersionsFromTree,
|
||||
pypiVersionExists,
|
||||
hasSuccessfulReleaseToday,
|
||||
utcDayStart,
|
||||
evaluateReleasePreflight,
|
||||
lastGreenCiSha,
|
||||
ACTIVE_RELEASE_STATUSES,
|
||||
PACKAGE_PATHS,
|
||||
};
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
types: [opened, synchronize]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
@@ -34,6 +34,9 @@ env:
|
||||
CLAUDE_TRIGGER_LOGINS: '["MervinPraison","github-actions[bot]"]'
|
||||
# GH_TOKEN (user PAT) posts as MervinPraison for @copilot; fallback to github.token for @claude
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
CLAUDE_UI_SCOPE: 'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Do not expand into praisonaiagents or the monorepo unless the issue explicitly requires it.'
|
||||
CLAUDE_FINAL_PHASES_COMPACT: "**Phase 1: Review per AGENTS.md** (lazy imports, backward compat, import-time <200ms; SDK/product value gate — genuine UI value, correct UI vs praisonaiagents layering, no scope creep)\n**Phase 2: FIX valid issues** found by prior reviewers — push to THIS branch\n**Phase 3: Final verdict** — approve PR if ready, else request changes"
|
||||
CLAUDE_FINAL_PHASES_DETAILED: "**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n4. SDK/product value: review whether the change genuinely adds value — stronger PraisonAIUI UX and correct layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep; request changes or recommend closing if value is unclear\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items"
|
||||
|
||||
jobs:
|
||||
# ================================================================
|
||||
@@ -200,12 +203,12 @@ jobs:
|
||||
if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments');
|
||||
if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments');
|
||||
const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected';
|
||||
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Read ALL prior reviewer comments. ' + reviewSummary + '.\n\n' +
|
||||
const scope = process.env.CLAUDE_UI_SCOPE;
|
||||
const phases = process.env.CLAUDE_FINAL_PHASES_COMPACT;
|
||||
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL prior reviewer comments. ' + reviewSummary + '.\n\n' +
|
||||
'Review Context: ' + comments.data.length + ' total comments, ' +
|
||||
(reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' +
|
||||
'**Phase 1: Review per AGENTS.md** (lazy imports, backward compat, import-time <200ms)\n' +
|
||||
'**Phase 2: FIX valid issues** found by prior reviewers — push to THIS branch\n' +
|
||||
'**Phase 3: Final verdict** — approve PR if ready, else request changes';
|
||||
phases;
|
||||
await github.rest.issues.createComment({issue_number: pr, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody});
|
||||
console.log('Claude fallback triggered on PR #' + pr + ' with ' + reviewSummary);
|
||||
|
||||
@@ -226,6 +229,10 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Wait for Copilot to respond (poll)
|
||||
id: poll
|
||||
uses: actions/github-script@v7
|
||||
@@ -326,14 +333,30 @@ jobs:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const pr = parseInt(process.env.PR_NUMBER, 10);
|
||||
const scope = process.env.CLAUDE_UI_SCOPE;
|
||||
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: pr,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
|
||||
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n' + phases
|
||||
});
|
||||
console.log(`Claude triggered on PR #${pr}`);
|
||||
|
||||
- name: Sync pipeline status labels
|
||||
if: steps.check_claude.outputs.already_triggered != 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
|
||||
const pr = parseInt(process.env.PR_NUMBER, 10);
|
||||
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
|
||||
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
|
||||
|
||||
# ================================================================
|
||||
# Claude after Qodo fallback path (same polling approach)
|
||||
# ================================================================
|
||||
@@ -430,11 +453,13 @@ jobs:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const pr = parseInt(process.env.PR_NUMBER, 10);
|
||||
const scope = process.env.CLAUDE_UI_SCOPE;
|
||||
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: pr,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
|
||||
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n' + phases
|
||||
});
|
||||
console.log(`Claude triggered on PR #${pr}`);
|
||||
|
||||
@@ -481,11 +506,13 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const scope = process.env.CLAUDE_UI_SCOPE;
|
||||
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '@claude You are the Lead Engineer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
|
||||
body: '@claude You are the Lead Engineer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n' + phases
|
||||
});
|
||||
|
||||
# ================================================================
|
||||
@@ -595,7 +622,7 @@ jobs:
|
||||
issue_number: pr,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '@claude You are the FINAL architecture reviewer on this bot-authored PR. Push fixes directly to THIS branch (do NOT open a new PR). SCOPE: PraisonAIUI only (src/praisonaiui, src/frontend, tests, docs). Read ALL CodeRabbit/Qodo comments above.\n\n**Phase 1:** Review per AGENTS.md (lazy imports, backward compat, import-time <200ms)\n**Phase 2:** FIX valid reviewer issues on this branch\n**Phase 3:** Approve PR if ready, else request changes with clear action items'
|
||||
body: '@claude You are the FINAL architecture reviewer on this bot-authored PR. Push fixes directly to THIS branch (do NOT open a new PR). ' + process.env.CLAUDE_UI_SCOPE + ' Read ALL CodeRabbit/Qodo comments above.\n\n' + process.env.CLAUDE_FINAL_PHASES_COMPACT
|
||||
});
|
||||
console.log('Claude final review triggered on bot PR #' + pr);
|
||||
|
||||
@@ -635,6 +662,48 @@ jobs:
|
||||
issue_number: pr,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '@claude You are the FINAL architecture reviewer. Push fixes directly to THIS branch. SCOPE: PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Read all prior reviewer comments.\n\n**Phase 1:** Review per AGENTS.md\n**Phase 2:** FIX valid issues on this branch\n**Phase 3:** Approve PR if ready, else request changes'
|
||||
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + process.env.CLAUDE_UI_SCOPE + ' Read all prior reviewer comments.\n\n' + process.env.CLAUDE_FINAL_PHASES_COMPACT
|
||||
});
|
||||
console.log('Claude manually triggered on PR #' + pr);
|
||||
console.log('Claude manually triggered on PR #' + pr);
|
||||
|
||||
# ================================================================
|
||||
# BOT PRs: kick CodeRabbit + Qodo
|
||||
# ================================================================
|
||||
bot-pr-trigger-reviews:
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.action == 'opened' &&
|
||||
(
|
||||
github.event.pull_request.user.login == 'github-actions[bot]' ||
|
||||
github.event.pull_request.user.login == 'praisonai-triage-agent[bot]' ||
|
||||
github.event.pull_request.user.type == 'Bot'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Trigger CodeRabbit and Qodo reviews
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
|
||||
const pr = context.payload.pull_request.number;
|
||||
await chain.kickReviewChain(github, context.repo.owner, context.repo.repo, pr, core);
|
||||
|
||||
- name: Sync pipeline status labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
|
||||
const pr = context.payload.pull_request.number;
|
||||
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
|
||||
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
|
||||
@@ -0,0 +1,102 @@
|
||||
name: Auto CI Failure → Claude
|
||||
|
||||
# Detects internal PRs with failing CI and posts @claude with extracted failure details.
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'Optional PR number to process (default: scan blocked PRs)'
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: '15 */6 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ci-failure-scan-${{ github.repository }}-${{ github.event.pull_request.number || github.event.workflow_run.id || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# PAT posts as MervinPraison so issue_comment triggers claude.yml (GITHUB_TOKEN cannot chain workflows)
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
issues: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
detect-and-trigger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Detect CI failures and trigger Claude
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const ciFix = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/ci-failure-claude.js'));
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const baseRepo = `${owner}/${repo}`;
|
||||
|
||||
async function processPR(prNumber) {
|
||||
return ciFix.maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
|
||||
}
|
||||
|
||||
let results = [];
|
||||
|
||||
if (context.eventName === 'workflow_run') {
|
||||
const wr = context.payload.workflow_run;
|
||||
const result = await ciFix.processWorkflowRunFailure(
|
||||
github, owner, repo, wr, core
|
||||
);
|
||||
results.push({ event: 'workflow_run', workflow: wr.name, ...result });
|
||||
} else if (context.eventName === 'pull_request') {
|
||||
results.push({
|
||||
pr: context.payload.pull_request.number,
|
||||
...(await processPR(context.payload.pull_request.number)),
|
||||
});
|
||||
} else {
|
||||
let prNumbers = [];
|
||||
if (context.eventName === 'workflow_dispatch' && context.payload.inputs?.pr_number) {
|
||||
prNumbers = [Number(context.payload.inputs.pr_number)];
|
||||
} else {
|
||||
const prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
for (const pr of prs) {
|
||||
if (pr.draft) continue;
|
||||
const headRepo = pr.head?.repo?.full_name;
|
||||
if (headRepo && headRepo !== baseRepo) continue;
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
});
|
||||
const labels = issue.labels.map((l) => l.name);
|
||||
if (labels.includes('pipeline/blocked:ci') || labels.includes(ciFix.CI_FIX_LABEL)) {
|
||||
prNumbers.push(pr.number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const num of prNumbers) {
|
||||
results.push({ pr: num, ...(await processPR(num)) });
|
||||
}
|
||||
}
|
||||
|
||||
core.summary.addRaw('```json\n' + JSON.stringify(results, null, 2) + '\n```');
|
||||
await core.summary.write();
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
python:
|
||||
name: python
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
name: Claude PR Merge Gate
|
||||
|
||||
# Read-only Claude assesses merge readiness; GH_TOKEN merges (no @claude, no gh pr review).
|
||||
# Does not overlap with merge-conflict-claude.yml or auto-pr-comment @claude chains.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Scan for merge-ready PRs every 10 minutes (assess runs only when candidates exist).
|
||||
- cron: '*/10 * * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'Optional PR number (scan all open internal PRs if empty)'
|
||||
required: false
|
||||
type: string
|
||||
merge_method:
|
||||
description: 'Merge method'
|
||||
required: false
|
||||
type: choice
|
||||
default: merge
|
||||
options:
|
||||
- merge
|
||||
- squash
|
||||
- rebase
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Claude Assistant
|
||||
- CI
|
||||
types:
|
||||
- completed
|
||||
repository_dispatch:
|
||||
types:
|
||||
- claude-merge-gate
|
||||
|
||||
concurrency:
|
||||
group: claude-merge-gate-${{ github.repository }}-${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || github.event.workflow_run.id || 'scan' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
MAX_CANDIDATES: 3
|
||||
DISPATCH_PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || '' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
auto-dispatch:
|
||||
if: github.event_name == 'workflow_run'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
actions: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
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: Dispatch merge gate when PR pipeline is ready
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
|
||||
const wr = context.payload.workflow_run;
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
if (wr.conclusion !== 'success') {
|
||||
core.info(`Skip dispatch: ${wr.name} conclusion=${wr.conclusion}`);
|
||||
return;
|
||||
}
|
||||
const allowed = ['Claude Assistant', 'CI'];
|
||||
if (!allowed.includes(wr.name)) {
|
||||
core.info(`Skip dispatch: workflow ${wr.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const prNumber = await mergeGate.resolvePrNumberFromWorkflowRun(github, owner, repo, wr);
|
||||
if (!prNumber) {
|
||||
core.info('Skip dispatch: could not resolve PR from workflow run');
|
||||
return;
|
||||
}
|
||||
|
||||
const fromClaude = wr.name === 'Claude Assistant';
|
||||
const check = await mergeGate.evaluatePipelineQuiescent(
|
||||
github, owner, repo, prNumber, core, {
|
||||
skipGlobalClaudeRunCheck: fromClaude,
|
||||
skipRecentClaudeCooldown: fromClaude,
|
||||
}
|
||||
);
|
||||
if (!check.ready) {
|
||||
core.info(`PR #${prNumber} not ready: ${check.reasons.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.repos.createDispatchEvent({
|
||||
owner,
|
||||
repo,
|
||||
event_type: 'claude-merge-gate',
|
||||
client_payload: { pr_number: prNumber },
|
||||
});
|
||||
core.info(`Dispatched merge gate for PR #${prNumber} after ${wr.name}`);
|
||||
|
||||
scan-candidates:
|
||||
if: github.event_name != 'workflow_run'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: write
|
||||
actions: read
|
||||
id-token: write
|
||||
outputs:
|
||||
matrix: ${{ steps.scan.outputs.matrix }}
|
||||
has_candidates: ${{ steps.scan.outputs.has_candidates }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
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: Scan open PRs for merge gate eligibility
|
||||
id: scan
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
|
||||
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const maxCandidates = parseInt(process.env.MAX_CANDIDATES || '5', 10);
|
||||
const inputPr = String(process.env.DISPATCH_PR_NUMBER || '').trim();
|
||||
|
||||
const candidates = [];
|
||||
const skipped = [];
|
||||
|
||||
if (inputPr) {
|
||||
const n = parseInt(inputPr, 10);
|
||||
if (Number.isNaN(n)) {
|
||||
throw new Error(`Invalid pr_number input: ${inputPr}`);
|
||||
}
|
||||
const result = await mergeGate.evaluatePipelineQuiescent(github, owner, repo, n, core);
|
||||
if (result.ready) {
|
||||
candidates.push({ pr_number: n, head_sha: result.headSha });
|
||||
} else {
|
||||
skipped.push({ pr: n, reasons: result.reasons });
|
||||
}
|
||||
} else {
|
||||
const prNumbers = await mergeGate.listPrNumbersForMergeGateScan(
|
||||
github, owner, repo, core
|
||||
);
|
||||
const selected = await mergeGate.selectMergeGateCandidates(
|
||||
github, owner, repo, prNumbers, maxCandidates, core
|
||||
);
|
||||
candidates.push(...selected.candidates);
|
||||
skipped.push(...selected.skipped);
|
||||
}
|
||||
|
||||
core.info('Skipped: ' + JSON.stringify(skipped));
|
||||
core.setOutput('matrix', JSON.stringify({ include: candidates }));
|
||||
core.setOutput('has_candidates', candidates.length > 0 ? 'true' : 'false');
|
||||
|
||||
core.summary.addRaw('## Merge gate scan\n');
|
||||
core.summary.addRaw(`Candidates: ${candidates.length}\n`);
|
||||
core.summary.addRaw('```json\n' + JSON.stringify({ candidates, skipped }, null, 2) + '\n```');
|
||||
await core.summary.write();
|
||||
|
||||
if (inputPr && skipped.length === 1 && skipped[0].pr === parseInt(inputPr, 10)) {
|
||||
const reasons = skipped[0].reasons;
|
||||
const body = [
|
||||
'**Merge gate scan** — not eligible for auto-merge.',
|
||||
'',
|
||||
...reasons.map((r) => `- \`${r}\``),
|
||||
'',
|
||||
'**Actions:** wait for CI and the Claude review chain, or add label `needs-manual-review` and merge manually.',
|
||||
'**Opt out:** label `no-auto-merge`.',
|
||||
].join('\n');
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: skipped[0].pr, body,
|
||||
});
|
||||
} else if (inputPr && candidates.length === 1) {
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: candidates[0].pr_number,
|
||||
body: '**Merge gate scan** — eligible for assessment. Claude merge gate will assess and may auto-merge if `MERGE_GATE_VERDICT: APPROVE`.',
|
||||
});
|
||||
}
|
||||
|
||||
await ps.syncOpenPullRequests(github, owner, repo, { maxPrs: 10 }, core);
|
||||
|
||||
claude-assess:
|
||||
needs: scan-candidates
|
||||
if: needs.scan-candidates.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: write
|
||||
actions: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: app-token
|
||||
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 }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Mark merge gate active
|
||||
id: mark_active
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
core.setOutput('started_at', new Date().toISOString());
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ matrix.pr_number }},
|
||||
labels: ['claude-merge-gate-active'],
|
||||
});
|
||||
|
||||
- name: Stash merge gate helpers
|
||||
run: cp .github/scripts/merge-gate.js /tmp/merge-gate.js
|
||||
|
||||
- name: Fetch PR branch
|
||||
env:
|
||||
PR_NUMBER: ${{ matrix.pr_number }}
|
||||
run: |
|
||||
git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"
|
||||
git checkout "pr-${PR_NUMBER}"
|
||||
|
||||
- name: Claude merge gate assessment (read-only)
|
||||
id: assess
|
||||
uses: ./.github/actions/claude-code-action
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CLAUDE_CODE_SUBAGENT_MODEL: inherit
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4-6
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
github_token: ${{ steps.app-token.outputs.token }}
|
||||
model: claude-opus-4-8
|
||||
trigger_phrase: '@claude-merge-gate-internal'
|
||||
direct_prompt: |
|
||||
You are the MERGE GATE reviewer for PraisonAI PR #${{ matrix.pr_number }}.
|
||||
READ-ONLY: do not modify code, commit, push, rebase, or create branches.
|
||||
|
||||
CRITICAL OUTPUT RULE:
|
||||
Post exactly one PR comment via GitHub MCP containing either:
|
||||
MERGE_GATE_VERDICT: APPROVE
|
||||
or
|
||||
MERGE_GATE_VERDICT: BLOCK
|
||||
followed by a short rationale on the next lines.
|
||||
|
||||
NEVER include @claude in any comment. NEVER mention merge conflict resolution.
|
||||
|
||||
Review checklist:
|
||||
1. Read AGENTS.md and the PR diff on the current branch.
|
||||
2. Read all PR comments and reviews (FINAL architecture reviewer / Lead Engineer must have run).
|
||||
3. Confirm SDK value gate: change strengthens praisonaiagents (no scope creep).
|
||||
4. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
|
||||
5. If agent.py adds >100 lines or new Agent __init__ params, respond MERGE_GATE_VERDICT: BLOCK (manual review required).
|
||||
6. BLOCK if labels security/breaking-change/needs-manual-review/release, sensitive paths (.github/workflows, auth, pyproject.toml), secrets in diff, PR >800 lines or >30 files, or SDK changes without tests. Label `merge-gate-ci-only` exempts CI-only changes under `.github/workflows/`, `.github/actions/`, or merge-gate scripts.
|
||||
7. Confirm no CHANGES_REQUESTED reviews and CI green on HEAD ${{ matrix.head_sha }}.
|
||||
8. If ready to merge: MERGE_GATE_VERDICT: APPROVE. Otherwise: MERGE_GATE_VERDICT: BLOCK with reasons.
|
||||
|
||||
Do not approve or merge via GitHub UI/API — only post the verdict comment.
|
||||
disallowed_tools: |
|
||||
Bash
|
||||
Edit
|
||||
Write
|
||||
Replace
|
||||
allowed_tools: |
|
||||
View
|
||||
GlobTool
|
||||
GrepTool
|
||||
Read
|
||||
timeout_minutes: 15
|
||||
|
||||
- name: Post fallback BLOCK if Opus did not comment
|
||||
id: fallback_verdict
|
||||
if: steps.assess.outcome != 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const mergeGate = require('/tmp/merge-gate.js');
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const prNumber = ${{ matrix.pr_number }};
|
||||
|
||||
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
|
||||
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
|
||||
const existing = mergeGate.findMergeGateVerdict(
|
||||
ctx.comments, minCreatedAt, ctx.headPushedAt, { excludeAutomatedFallback: true }
|
||||
);
|
||||
if (existing) {
|
||||
core.info(`Opus verdict already posted: ${existing}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const check = await mergeGate.evaluatePipelineQuiescent(
|
||||
github, owner, repo, prNumber, core, { forMergeStep: true }
|
||||
);
|
||||
const lines = [
|
||||
'MERGE_GATE_VERDICT: BLOCK',
|
||||
'',
|
||||
'Automated fallback — Opus merge gate assessment did not complete.',
|
||||
];
|
||||
if (check.ready) {
|
||||
lines.push('Deterministic gates passed, but Opus MERGE_GATE_VERDICT is required before merge.');
|
||||
} else {
|
||||
lines.push('Blockers:', ...check.reasons.map((r) => `- ${r}`));
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: prNumber,
|
||||
body: lines.join('\n'),
|
||||
});
|
||||
core.info('Posted fallback BLOCK — Opus verdict required');
|
||||
|
||||
- name: Require Opus MERGE_GATE_VERDICT
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const mergeGate = require('/tmp/merge-gate.js');
|
||||
const comments = await mergeGate.listAllComments(
|
||||
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
|
||||
);
|
||||
const ctx = await mergeGate.loadPrContext(
|
||||
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
|
||||
);
|
||||
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
|
||||
const verdict = mergeGate.findMergeGateVerdict(
|
||||
comments, minCreatedAt, ctx.headPushedAt, { excludeAutomatedFallback: true }
|
||||
);
|
||||
if (!verdict) {
|
||||
core.setFailed(
|
||||
'Opus merge gate assessment must post MERGE_GATE_VERDICT on this PR (automated fallback APPROVE is not accepted).'
|
||||
);
|
||||
return;
|
||||
}
|
||||
core.info(`Opus merge gate verdict: ${verdict}`);
|
||||
core.setOutput('verdict', verdict);
|
||||
|
||||
- name: Remove merge gate active label
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const prNumber = ${{ matrix.pr_number }};
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
name: 'claude-merge-gate-active',
|
||||
});
|
||||
core.info(`Removed claude-merge-gate-active from PR #${prNumber}`);
|
||||
} catch (e) {
|
||||
core.info(`Label claude-merge-gate-active already removed or missing on PR #${prNumber}`);
|
||||
}
|
||||
|
||||
- name: Fail job if assessment failed
|
||||
if: steps.assess.outcome == 'failure'
|
||||
run: exit 1
|
||||
|
||||
merge-only:
|
||||
needs: [scan-candidates, claude-assess]
|
||||
if: |
|
||||
always() &&
|
||||
needs.scan-candidates.outputs.has_candidates == 'true' &&
|
||||
needs.claude-assess.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
actions: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
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: Re-check gates and merge
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
MERGE_METHOD: ${{ github.event.inputs.merge_method || 'merge' }}
|
||||
SCAN_HEAD_SHA: ${{ matrix.head_sha }}
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const prNumber = ${{ matrix.pr_number }};
|
||||
const scanHeadSha = process.env.SCAN_HEAD_SHA;
|
||||
|
||||
const removeLabels = async () => {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: prNumber, name: 'claude-merge-gate-active',
|
||||
});
|
||||
} catch (e) {
|
||||
core.info('Label claude-merge-gate-active already removed or missing');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
|
||||
const comments = ctx.comments;
|
||||
const verdictMin = new Date(Date.now() - 25 * 60 * 1000).toISOString();
|
||||
const verdict = mergeGate.findMergeGateVerdict(
|
||||
comments, verdictMin, ctx.headPushedAt, { excludeAutomatedFallback: true }
|
||||
);
|
||||
if (verdict !== 'APPROVE') {
|
||||
core.info(`Skip merge: verdict=${verdict || 'NONE'}`);
|
||||
await removeLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
const check = await mergeGate.evaluatePipelineQuiescent(
|
||||
github, owner, repo, prNumber, core, { forMergeStep: true }
|
||||
);
|
||||
if (!check.ready) {
|
||||
core.info(`Skip merge: ${check.reasons.join(', ')}`);
|
||||
await removeLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
if (check.headSha !== scanHeadSha) {
|
||||
core.info(`Skip merge: HEAD moved ${scanHeadSha.slice(0, 7)} → ${check.headSha.slice(0, 7)}`);
|
||||
await removeLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
const method = process.env.MERGE_METHOD || 'merge';
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
let merged = false;
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
try {
|
||||
await github.rest.pulls.merge({
|
||||
owner, repo, pull_number: prNumber, merge_method: method,
|
||||
});
|
||||
merged = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
const msg = (e.message || '').toLowerCase();
|
||||
if (attempt < 3 && msg.includes('base branch was modified')) {
|
||||
core.info(`Merge attempt ${attempt} failed (base moved), retrying in 8s...`);
|
||||
await sleep(8000);
|
||||
const recheck = await mergeGate.evaluatePipelineQuiescent(
|
||||
github, owner, repo, prNumber, core, { forMergeStep: true }
|
||||
);
|
||||
if (!recheck.ready) {
|
||||
core.info(`Skip merge retry: ${recheck.reasons.join(', ')}`);
|
||||
await removeLabels();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
core.info('Skip merge: exhausted retries');
|
||||
await removeLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: prNumber, labels: ['auto-merged-by-gate'],
|
||||
});
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: prNumber,
|
||||
body: [
|
||||
'Merged by **Claude PR merge gate** (`claude-merge-gate.yml`).',
|
||||
`Verdict: MERGE_GATE_VERDICT: APPROVE`,
|
||||
`SHA: \`${check.headSha.slice(0, 7)}\``,
|
||||
`Method: ${method}`,
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
core.info(`Merged PR #${prNumber}`);
|
||||
} finally {
|
||||
await removeLabels();
|
||||
}
|
||||
@@ -268,6 +268,26 @@ jobs:
|
||||
core.setOutput('pr_branch', pr.data.head.ref);
|
||||
const headRepo = pr.data.head.repo?.full_name || context.repo.full_name;
|
||||
core.setOutput('head_repo', headRepo);
|
||||
|
||||
let mergeState = '';
|
||||
try {
|
||||
const gql = await github.graphql(
|
||||
`query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) { mergeStateStatus }
|
||||
}
|
||||
}`,
|
||||
{ owner: context.repo.owner, repo: context.repo.repo, number: prNumber }
|
||||
);
|
||||
mergeState = (gql.repository.pullRequest.mergeStateStatus || '').toUpperCase();
|
||||
} catch (e) {
|
||||
core.warning(`GraphQL mergeStateStatus failed: ${e.message}`);
|
||||
}
|
||||
if (!mergeState) {
|
||||
mergeState = (pr.data.mergeable_state || pr.data.merge_state_status || '').toUpperCase();
|
||||
}
|
||||
core.setOutput('merge_state', mergeState);
|
||||
|
||||
if (headRepo !== `${context.repo.owner}/${context.repo.repo}`) {
|
||||
core.setOutput('is_fork', 'true');
|
||||
core.setOutput('clone_url', pr.data.head.repo.clone_url);
|
||||
@@ -279,6 +299,7 @@ jobs:
|
||||
}
|
||||
core.setOutput('is_pr_review', 'false');
|
||||
core.setOutput('is_fork', 'false');
|
||||
core.setOutput('merge_state', '');
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -316,11 +337,13 @@ jobs:
|
||||
allowed_bots: 'praisonai-triage-agent[bot]'
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
github_token: ${{ steps.github-token.outputs.token }}
|
||||
model: opus
|
||||
model: claude-opus-4-8
|
||||
trigger_phrase: "@claude"
|
||||
timeout_minutes: "30"
|
||||
direct_prompt: |
|
||||
${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: FORK PR — comment only, do not push.\n\n' || 'PR on MervinPraison/PraisonAIUI — SAME REPO, NOT A FORK. You HAVE push permission to this branch. NEVER say this is a fork.\n\n' }}
|
||||
${{ steps.check_fork.outputs.is_fork != 'true' && steps.check_fork.outputs.merge_state == 'DIRTY' && format('PRIORITY — RESOLVE MERGE CONFLICTS (branch: {0}, PR #{1}):\n- DO NOT create a new branch or PR\n- git fetch origin main && git rebase origin/main\n- Resolve conflicts: keep this PR intent, merge in main newer logic\n- git add -A && git rebase --continue (repeat until done)\n- git push --force-with-lease origin {0}\n- Run targeted pytest/ruff on changed paths\n- Comment files resolved and what you skipped\n- Only after conflicts are resolved, proceed with review phases below if still needed\n\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number) || '' }}
|
||||
${{ steps.check_fork.outputs.merge_state != 'DIRTY' && format('CONTEXT: PR #{0} on branch {1}. merge_state={2}.\n\n', steps.check_fork.outputs.pr_number, steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.merge_state) || '' }}
|
||||
You are the Lead Engineer doing FINAL review on this open PR in MervinPraison/PraisonAIUI.
|
||||
Read ALL reviewer comments (CodeRabbit, Gemini, Qodo, Copilot, human). Follow AGENTS.md.
|
||||
SCOPE: src/praisonaiui, src/frontend, tests, docs only.
|
||||
@@ -359,7 +382,7 @@ jobs:
|
||||
allowed_bots: 'praisonai-triage-agent[bot]'
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
github_token: ${{ steps.github-token.outputs.token }}
|
||||
model: opus
|
||||
model: claude-opus-4-8
|
||||
mode: ${{ github.event_name == 'workflow_dispatch' && 'agent' || 'tag' }}
|
||||
trigger_phrase: "@claude"
|
||||
label_trigger: "claude"
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
name: Auto Merge Conflict → Claude
|
||||
|
||||
# Detects internal PRs with mergeStateStatus=DIRTY and posts @claude to trigger claude.yml.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 */6 * * *'
|
||||
|
||||
concurrency:
|
||||
group: merge-conflict-scan-${{ github.repository }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# PAT posts as MervinPraison so issue_comment triggers claude.yml (GITHUB_TOKEN cannot chain workflows)
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
detect-and-trigger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Detect conflicting PRs and trigger Claude
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ env.GH_TOKEN }}
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const baseRepo = `${owner}/${repo}`;
|
||||
const COOLDOWN_MS = 12 * 60 * 60 * 1000;
|
||||
const AUTO_ACTORS = ['github-actions[bot]', 'MervinPraison'];
|
||||
const LABEL = 'claude-conflict-pending';
|
||||
const COMMENT_BODY = [
|
||||
'@claude this PR has merge conflicts with `main`.',
|
||||
'Please rebase onto latest `main`, resolve conflicts (keep this PR\'s intent, merge in newer main logic),',
|
||||
'run targeted tests, and force-push with `--force-with-lease`.',
|
||||
'Comment which files you resolved.'
|
||||
].join(' ');
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
async function getMergeState(prNumber) {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
mergeStateStatus
|
||||
maintainerCanModify
|
||||
isDraft
|
||||
headRef { repository { nameWithOwner } }
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
const result = await github.graphql(query, { owner, repo, number: prNumber });
|
||||
const prGql = result.repository.pullRequest;
|
||||
const status = (prGql?.mergeStateStatus || '').toUpperCase();
|
||||
if (status && status !== 'UNKNOWN') {
|
||||
return {
|
||||
status,
|
||||
isDraft: prGql.isDraft,
|
||||
headRepo: prGql.headRef?.repository?.nameWithOwner,
|
||||
maintainerCanModify: prGql.maintainerCanModify === true,
|
||||
};
|
||||
}
|
||||
if (attempt < 2) await sleep(10000);
|
||||
}
|
||||
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
return {
|
||||
status: (pr.data.mergeable_state || '').toUpperCase(),
|
||||
isDraft: pr.data.draft,
|
||||
headRepo: pr.data.head.repo?.full_name,
|
||||
maintainerCanModify: pr.data.maintainer_can_modify === true,
|
||||
};
|
||||
}
|
||||
|
||||
function hasFinalClaudeReviewTrigger(comments) {
|
||||
return comments.some((c) => {
|
||||
const body = (c.body || '').toLowerCase();
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
if (!body.includes('@claude')) return false;
|
||||
if (body.includes('merge conflict')) return false;
|
||||
return body.includes('final architecture reviewer') || body.includes('lead engineer');
|
||||
});
|
||||
}
|
||||
|
||||
function hasRecentAutoComment(comments) {
|
||||
const cutoff = Date.now() - COOLDOWN_MS;
|
||||
return comments.some((c) => {
|
||||
if (!AUTO_ACTORS.includes(c.user.login)) return false;
|
||||
const body = (c.body || '').toLowerCase();
|
||||
if (!body.includes('@claude') || !body.includes('merge conflict')) return false;
|
||||
return new Date(c.created_at).getTime() > cutoff;
|
||||
});
|
||||
}
|
||||
|
||||
async function processPR(prNumber) {
|
||||
const { status, isDraft, headRepo, maintainerCanModify } = await getMergeState(prNumber);
|
||||
core.info(`PR #${prNumber}: mergeState=${status}, head=${headRepo}, draft=${isDraft}, maintainerCanModify=${maintainerCanModify}`);
|
||||
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
const labels = issue.labels.map((l) => l.name);
|
||||
|
||||
if (status !== 'DIRTY' && labels.includes(LABEL)) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: LABEL,
|
||||
});
|
||||
core.info(`Removed ${LABEL} from PR #${prNumber}`);
|
||||
}
|
||||
|
||||
if (status !== 'DIRTY') return { skipped: true, reason: 'not dirty' };
|
||||
if (isDraft) return { skipped: true, reason: 'draft' };
|
||||
if (headRepo && headRepo !== baseRepo && !maintainerCanModify) {
|
||||
return { skipped: true, reason: 'fork without maintainer edits' };
|
||||
}
|
||||
if (labels.includes(LABEL)) {
|
||||
return { skipped: true, reason: 'conflict resolution pending' };
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
if (hasRecentAutoComment(comments)) {
|
||||
return { skipped: true, reason: 'recent auto comment' };
|
||||
}
|
||||
|
||||
if (!hasFinalClaudeReviewTrigger(comments)) {
|
||||
return { skipped: true, reason: 'awaiting final claude review trigger' };
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: [LABEL],
|
||||
});
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: COMMENT_BODY,
|
||||
});
|
||||
return { triggered: true };
|
||||
}
|
||||
|
||||
let prNumbers = [];
|
||||
if (context.eventName === 'pull_request') {
|
||||
prNumbers = [context.payload.pull_request.number];
|
||||
} else {
|
||||
const prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
prNumbers = prs.map((p) => p.number);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const num of prNumbers) {
|
||||
results.push({ pr: num, ...(await processPR(num)) });
|
||||
}
|
||||
core.summary.addRaw('```json\n' + JSON.stringify(results, null, 2) + '\n```');
|
||||
await core.summary.write();
|
||||
@@ -0,0 +1,129 @@
|
||||
name: Nightly Release Gate
|
||||
|
||||
# Preflight before PyPI Release (patch bump).
|
||||
# Primary: CI success on main (workflow_run).
|
||||
# Backstop: nightly cron at 00:00 UTC.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Run preflight only; do not dispatch pypi-release.yml'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
workflow_run:
|
||||
workflows:
|
||||
- CI
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: nightly-release-gate
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'schedule' ||
|
||||
(
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_branch == 'main' &&
|
||||
github.event.workflow_run.event == 'push'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
steps:
|
||||
- name: Resolve trigger context
|
||||
id: ctx
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_run" ]; then
|
||||
echo "checkout_sha=${{ github.event.workflow_run.head_sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "source=ci" >> "$GITHUB_OUTPUT"
|
||||
echo "is_ci=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "checkout_sha=main" >> "$GITHUB_OUTPUT"
|
||||
echo "source=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "is_ci=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
ref: ${{ steps.ctx.outputs.checkout_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch main and tags
|
||||
run: git fetch --tags origin main
|
||||
|
||||
- name: Preflight — path changes, CI SHA, dedupe
|
||||
id: preflight
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
HEAD_SHA: ${{ steps.ctx.outputs.checkout_sha }}
|
||||
IS_CI: ${{ steps.ctx.outputs.is_ci }}
|
||||
with:
|
||||
github-token: ${{ secrets.GH_TOKEN }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const rg = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/release-gate.js'));
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const headSha = process.env.HEAD_SHA;
|
||||
const isCi = process.env.IS_CI === 'true';
|
||||
|
||||
const result = await rg.evaluateReleasePreflight(
|
||||
github, owner, repo,
|
||||
{ headSha, isCiTrigger: isCi, bump: 'patch' },
|
||||
core
|
||||
);
|
||||
|
||||
core.setOutput('should_release', result.ready ? 'true' : 'false');
|
||||
core.setOutput('reason', result.reasons.join('; '));
|
||||
core.setOutput('head_sha', result.headSha || headSha);
|
||||
core.setOutput('last_tag', result.lastTag || '');
|
||||
core.setOutput('target_version', result.targetVersion || '');
|
||||
|
||||
core.summary.addRaw('## Release gate preflight\n');
|
||||
core.summary.addRaw(`Ready: ${result.ready}\n`);
|
||||
core.summary.addRaw(`Reason: ${result.reasons.join('; ')}\n`);
|
||||
if (result.targetVersion) {
|
||||
core.summary.addRaw(`Target: aiui==${result.targetVersion}\n`);
|
||||
}
|
||||
await core.summary.write();
|
||||
|
||||
if (!result.ready) {
|
||||
core.info(`Skip release: ${result.reasons.join('; ')}`);
|
||||
}
|
||||
|
||||
- name: Dry run summary
|
||||
if: inputs.dry_run == true
|
||||
run: |
|
||||
echo "Dry run — preflight only, no pypi-release dispatch."
|
||||
echo "should_release=${{ steps.preflight.outputs.should_release }}"
|
||||
echo "reason=${{ steps.preflight.outputs.reason }}"
|
||||
|
||||
- name: Dispatch PyPI Release (patch)
|
||||
if: steps.preflight.outputs.should_release == 'true' && inputs.dry_run != true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh workflow run pypi-release.yml \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--ref main \
|
||||
-f bump=patch \
|
||||
-f source=${{ steps.ctx.outputs.source }} \
|
||||
-f trigger_sha=${{ steps.preflight.outputs.head_sha }} \
|
||||
-f dry_run=false
|
||||
echo "Dispatched pypi-release.yml (patch, source=${{ steps.ctx.outputs.source }})"
|
||||
echo "Uses pypi-auto environment for ci/nightly patch releases."
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Pipeline Status Sync
|
||||
|
||||
# Sync pipeline/* stage and blocker labels on open internal PRs.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/10 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
issues: write
|
||||
actions: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
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: Sync pipeline labels on open PRs
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const path = require('path');
|
||||
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
|
||||
await ps.syncOpenPullRequests(
|
||||
github, context.repo.owner, context.repo.repo,
|
||||
{ maxPrs: 30, dispatchMergeGate: true }, core
|
||||
);
|
||||
@@ -0,0 +1,232 @@
|
||||
name: PyPI Release
|
||||
|
||||
# Requires GitHub Environment "pypi" (manual) or "pypi-auto" (ci/nightly patch).
|
||||
# Secrets: PYPI_TOKEN (or PYPI_API_TOKEN), GH_TOKEN
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump:
|
||||
description: 'Version bump type for aiui'
|
||||
required: true
|
||||
default: patch
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
dry_run:
|
||||
description: 'Preview versions and steps without publishing or pushing'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
description: 'Override version (empty = auto bump from pyproject.toml)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
source:
|
||||
description: 'Trigger source (audit trail)'
|
||||
required: false
|
||||
default: manual
|
||||
type: string
|
||||
trigger_sha:
|
||||
description: 'SHA that triggered this release'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: pypi-release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
environment: >-
|
||||
${{
|
||||
(inputs.source == 'ci' || inputs.source == 'nightly') &&
|
||||
inputs.bump == 'patch' &&
|
||||
'pypi-auto' || 'pypi'
|
||||
}}
|
||||
env:
|
||||
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PACKAGE: aiui
|
||||
steps:
|
||||
- name: Release audit
|
||||
run: |
|
||||
echo "Source: ${{ inputs.source }}"
|
||||
echo "Trigger SHA: ${{ inputs.trigger_sha }}"
|
||||
echo "Bump: ${{ inputs.bump }}"
|
||||
echo "Environment: ${{ (inputs.source == 'ci' || inputs.source == 'nightly') && inputs.bump == 'patch' && 'pypi-auto' || 'pypi' }}"
|
||||
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "MervinPraison"
|
||||
git config user.email "[email protected]"
|
||||
gh auth setup-git
|
||||
|
||||
- name: Compute release version
|
||||
id: versions
|
||||
env:
|
||||
BUMP: ${{ inputs.bump }}
|
||||
VERSION_OVERRIDE: ${{ inputs.version }}
|
||||
run: |
|
||||
python <<'PY'
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SEMVER = re.compile(r"^\d+\.\d+\.\d+$")
|
||||
bump = os.environ["BUMP"]
|
||||
override = os.environ.get("VERSION_OVERRIDE", "").strip()
|
||||
|
||||
def bump_version(version: str) -> str:
|
||||
parts = version.split(".")
|
||||
if len(parts) != 3:
|
||||
print(f"Invalid version: {version}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
maj, min_, patch = (int(parts[0]), int(parts[1]), int(parts[2]))
|
||||
if bump == "major":
|
||||
return f"{maj + 1}.0.0"
|
||||
if bump == "minor":
|
||||
return f"{maj}.{min_ + 1}.0"
|
||||
return f"{maj}.{min_}.{patch + 1}"
|
||||
|
||||
pyproject = Path("pyproject.toml")
|
||||
content = pyproject.read_text()
|
||||
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
|
||||
if not match:
|
||||
print("Could not read version from pyproject.toml", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
current = match.group(1)
|
||||
if not SEMVER.match(current):
|
||||
print(f"Invalid current version: {current}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if override:
|
||||
if not SEMVER.match(override):
|
||||
print(f"Invalid version override: {override}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
new_version = override
|
||||
else:
|
||||
new_version = bump_version(current)
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
|
||||
fh.write(f"current_version={current}\n")
|
||||
fh.write(f"new_version={new_version}\n")
|
||||
print(f"aiui: {current} -> {new_version}")
|
||||
PY
|
||||
|
||||
- name: Dry run summary
|
||||
if: inputs.dry_run
|
||||
run: |
|
||||
echo "Dry run — no publish, commit, or push."
|
||||
echo "Package: ${{ env.PACKAGE }}"
|
||||
echo "Version: ${{ steps.versions.outputs.current_version }} -> ${{ steps.versions.outputs.new_version }}"
|
||||
echo "Would: bump pyproject.toml + __version__.py, uv publish, commit, tag, GitHub release"
|
||||
|
||||
- name: Bump version files
|
||||
if: inputs.dry_run != true
|
||||
env:
|
||||
CURRENT: ${{ steps.versions.outputs.current_version }}
|
||||
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python <<'PY'
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
current = os.environ["CURRENT"]
|
||||
new = os.environ["NEW_VERSION"]
|
||||
|
||||
pyproject = Path("pyproject.toml")
|
||||
content = pyproject.read_text()
|
||||
pyproject.write_text(content.replace(f'version = "{current}"', f'version = "{new}"', 1))
|
||||
|
||||
version_py = Path("src/praisonaiui/__version__.py")
|
||||
vcontent = version_py.read_text()
|
||||
version_py.write_text(re.sub(
|
||||
r'__version__ = "[^"]+"',
|
||||
f'__version__ = "{new}"',
|
||||
vcontent,
|
||||
count=1,
|
||||
))
|
||||
PY
|
||||
|
||||
- name: Publish aiui to PyPI
|
||||
if: inputs.dry_run != true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf dist
|
||||
uv lock
|
||||
uv build
|
||||
uv publish
|
||||
|
||||
- name: Wait for aiui on PyPI
|
||||
if: inputs.dry_run != true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.versions.outputs.new_version }}"
|
||||
MAX_WAIT=600
|
||||
INTERVAL=30
|
||||
START=$(date +%s)
|
||||
|
||||
while true; do
|
||||
if curl -fsSL "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json" >/dev/null; then
|
||||
echo "✅ ${PACKAGE}==${VERSION} is available on PyPI"
|
||||
exit 0
|
||||
fi
|
||||
ELAPSED=$(( $(date +%s) - START ))
|
||||
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
|
||||
echo "❌ Timeout waiting for ${PACKAGE}==${VERSION} on PyPI"
|
||||
exit 1
|
||||
fi
|
||||
echo "⏳ Waiting for ${PACKAGE}==${VERSION} ($(( MAX_WAIT - ELAPSED ))s remaining...)"
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
|
||||
- name: Commit, tag, and release
|
||||
if: inputs.dry_run != true
|
||||
env:
|
||||
VERSION: ${{ steps.versions.outputs.new_version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="v${VERSION}"
|
||||
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
|
||||
git diff --cached --quiet && { echo "No version files to commit"; exit 0; }
|
||||
git commit -m "Release ${TAG}"
|
||||
git tag -f "${TAG}"
|
||||
git pull --rebase origin main
|
||||
git tag -f "${TAG}"
|
||||
git push origin main
|
||||
git push --tags -f
|
||||
gh release create "${TAG}" \
|
||||
--title "PraisonAIUI ${TAG}" \
|
||||
--notes "Release ${TAG}" \
|
||||
--latest
|
||||
|
||||
- name: Verify release
|
||||
if: inputs.dry_run != true
|
||||
run: echo "✅ ${{ env.PACKAGE }}==${{ steps.versions.outputs.new_version }}"
|
||||
@@ -25,7 +25,8 @@ jobs:
|
||||
run: |
|
||||
gh workflow run sync-secrets-to-aiui.yml \
|
||||
--repo MervinPraison/PraisonAI \
|
||||
--ref main
|
||||
--ref main \
|
||||
-f target_repo=MervinPraison/PraisonAIUI
|
||||
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,"
|
||||
|
||||
@@ -138,7 +138,23 @@ Final rule: conclude only when evidence shows missing = 0.
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
PUBLISH WORKFLOW (reference)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Step 1 — Publish praisonaiagents (Core SDK):
|
||||
GitHub Actions (recommended):
|
||||
GitHub → Actions → "PyPI Release" → Run workflow → bump: patch
|
||||
Requires repo secrets: PYPI_TOKEN, GH_TOKEN
|
||||
Requires GitHub Environment "pypi" with required reviewers (Settings → Environments)
|
||||
|
||||
Manual — Publish aiui (PraisonAIUI):
|
||||
cd /Users/praison/PraisonAIUI
|
||||
# Bump version in pyproject.toml and src/praisonaiui/__version__.py
|
||||
rm -rf dist && uv lock && uv build && uv publish --token "$PYPI_TOKEN"
|
||||
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
|
||||
git commit -m "Release vX.Y.Z" && git tag vX.Y.Z && git push && git push --tags
|
||||
gh release create vX.Y.Z --title "PraisonAIUI vX.Y.Z" --notes "Release vX.Y.Z" --latest
|
||||
|
||||
PyPI package name: aiui
|
||||
Verify: pip index versions aiui | head -1
|
||||
|
||||
PraisonAI stack (separate repo — publish SDK before wrapper):
|
||||
cd /Users/praison/praisonai-package/src/praisonai-agents
|
||||
praisonai publish pypi
|
||||
# Uses uv internally: uv lock → uv build → uv publish
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# Health API Reference
|
||||
|
||||
The AIUI health API provides endpoints to monitor server and provider health status.
|
||||
|
||||
## Health Status Values
|
||||
|
||||
The following status values are used throughout the health API:
|
||||
|
||||
| Status | Meaning | Color Code |
|
||||
|--------|---------|------------|
|
||||
| `"ok"` | Service is functioning normally | Green |
|
||||
| `"healthy"` | Service is functioning normally (legacy) | Green |
|
||||
| `"degraded"` | Service is partially functional | Yellow |
|
||||
| `"error"` | Service has errors | Red |
|
||||
| `"unknown"` | Status cannot be determined | Yellow |
|
||||
|
||||
> **Note:** Both `"ok"` and `"healthy"` indicate successful operation. The system accepts either value for backward compatibility.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET /health
|
||||
|
||||
Returns the overall server health status.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-06-23T10:00:00Z",
|
||||
"integrated": true,
|
||||
"sdk_gaps": []
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/provider
|
||||
|
||||
Returns information about the active AI provider.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"name": "PraisonAIProvider",
|
||||
"module": "praisonaiui.providers",
|
||||
"status": "ok",
|
||||
"agents": []
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/provider/health
|
||||
|
||||
Returns detailed JSON health information from the provider gateway. Always
|
||||
responds with `Content-Type: application/json`. The base fields from the
|
||||
provider's `health()` payload are merged with the normalised fields below.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"provider": "PraisonAIProvider",
|
||||
"type": "PraisonAIProvider",
|
||||
"agents": 5,
|
||||
"detail": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | string | One of the health status values above. |
|
||||
| `type` | string | Provider type (from `provider` field, falls back to class name). |
|
||||
| `agents` | integer | Count of agents reported by the provider. |
|
||||
| `detail` | string | Human-readable detail; `"ok"` for healthy providers. |
|
||||
|
||||
> **Note:** Any unmatched `/api/*` path returns a JSON `404` (never the SPA
|
||||
> HTML shell), so API clients can rely on parsing the response body.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### health-check
|
||||
|
||||
Check server health status:
|
||||
|
||||
```bash
|
||||
aiui health-check --server http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
The command displays:
|
||||
- Green panel for `"ok"` or `"healthy"` status
|
||||
- Yellow panel for other statuses
|
||||
- Red error message if server is unreachable
|
||||
|
||||
### doctor
|
||||
|
||||
Run comprehensive diagnostics:
|
||||
|
||||
```bash
|
||||
aiui doctor --server http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
The doctor command checks:
|
||||
1. Server Health
|
||||
2. Provider Status
|
||||
3. Gateway Status
|
||||
4. Features Loaded
|
||||
5. Config Store
|
||||
6. Datastore
|
||||
7. Channels
|
||||
|
||||
### provider status
|
||||
|
||||
Check provider health:
|
||||
|
||||
```bash
|
||||
aiui provider status --server http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
## Python API
|
||||
|
||||
```python
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
# Check if a status indicates success
|
||||
if is_success_status("ok"):
|
||||
print("Service is healthy")
|
||||
|
||||
# Available success statuses
|
||||
from praisonaiui.health_utils import SUCCESS_STATUSES
|
||||
# frozenset({'ok', 'healthy'})
|
||||
```
|
||||
|
||||
## JavaScript/Dashboard
|
||||
|
||||
The dashboard automatically accepts both `"ok"` and `"healthy"` as success statuses:
|
||||
|
||||
```javascript
|
||||
const ok = d && (d.status === 'ok' || d.status === 'healthy');
|
||||
```
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
volumes:
|
||||
- aiui-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8082/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8082/health/live"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -81,7 +81,7 @@ curl -H "Authorization: Bearer <api-key>" http://localhost:8082/api/config
|
||||
```
|
||||
|
||||
**Exempt paths** (always accessible without auth):
|
||||
- `/health`, `/api/health` — health checks
|
||||
- `/health`, `/health/live`, `/health/ready`, `/api/health` — health checks
|
||||
- `/api/auth/login`, `/login` — login endpoints
|
||||
- `/api/protocol`, `/api/protocol/negotiate` — protocol discovery
|
||||
|
||||
@@ -146,8 +146,36 @@ This affects:
|
||||
### Health Endpoint
|
||||
|
||||
```bash
|
||||
# Fast liveness probe (<500ms) - use for load balancers and K8s livenessProbe
|
||||
curl http://localhost:8082/health/live
|
||||
# Returns: {"status": "ok", "timestamp": "2024-..."}
|
||||
|
||||
# Readiness probe with parallel feature checks - use for K8s readinessProbe
|
||||
curl http://localhost:8082/health/ready
|
||||
|
||||
# Deep diagnostics (parallel feature checks, deep by default)
|
||||
curl http://localhost:8082/health
|
||||
# Returns: {"status": "healthy", "timestamp": "2024-..."}
|
||||
# Pass ?deep=false for an immediate liveness response
|
||||
```
|
||||
|
||||
### Kubernetes Probes
|
||||
|
||||
Use the fast liveness endpoint for probes to avoid timeouts:
|
||||
|
||||
```yaml
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8082
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 1
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8082
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
```
|
||||
|
||||
### Docker HEALTHCHECK
|
||||
@@ -156,17 +184,17 @@ The Dockerfile includes a built-in health check:
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${AIUI_PORT}/health || exit 1
|
||||
CMD curl -f http://localhost:${AIUI_PORT}/health/live || exit 1
|
||||
```
|
||||
|
||||
### CLI Health Commands
|
||||
|
||||
```bash
|
||||
# Quick health check
|
||||
aiui health --server http://localhost:8082
|
||||
aiui health-check --server http://localhost:8082
|
||||
|
||||
# Detailed per-feature health
|
||||
aiui health --server http://localhost:8082 --detailed
|
||||
# Detailed per-feature health
|
||||
aiui health-check --server http://localhost:8082 --detailed
|
||||
|
||||
# Full diagnostic (7 checks)
|
||||
aiui doctor --server http://localhost:8082
|
||||
@@ -199,7 +227,7 @@ curl -H "X-API-Key: <key>" http://localhost:8082/api/config
|
||||
|
||||
- **Internal networks**: Use `AUTH_ENFORCE=false` if behind a trusted reverse proxy
|
||||
- **Public exposure**: Always set `AUTH_ENFORCE=true` and use HTTPS
|
||||
- **Load balancers**: Health check path is `/health` (exempt from auth)
|
||||
- **Load balancers**: Health check path is `/health/live` (fast, exempt from auth)
|
||||
|
||||
## Managed Hosting
|
||||
|
||||
@@ -209,7 +237,9 @@ Hosting platforms should expose these endpoints:
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/health` | Health check (returns `{"status": "healthy"}`) |
|
||||
| `/health/live` | Fast liveness probe (returns `{"status": "ok"}`) |
|
||||
| `/health/ready` | Readiness probe (parallel feature checks) |
|
||||
| `/health` | Deep health (parallel feature checks; `?deep=false` for liveness) |
|
||||
| `/api/protocol` | Protocol version and capabilities |
|
||||
| `/api/protocol/negotiate` | Capability negotiation |
|
||||
| `/api/features` | List of registered features |
|
||||
|
||||
@@ -276,10 +276,13 @@ aiui features list # List all registered features
|
||||
aiui features status # Feature health summary
|
||||
```
|
||||
|
||||
### `aiui health`
|
||||
### `aiui health-check`
|
||||
|
||||
Check server health. Can also be invoked as `aiui health` for convenience.
|
||||
|
||||
```bash
|
||||
aiui health [--detailed] # Check server health
|
||||
aiui health-check [--detailed] # Check server health
|
||||
aiui health [--detailed] # Alias for health-check
|
||||
```
|
||||
|
||||
### `aiui session-ext`
|
||||
|
||||
@@ -101,6 +101,6 @@ If something isn't working:
|
||||
|
||||
```bash
|
||||
aiui doctor # Check server health
|
||||
aiui health --detailed # Detailed health check
|
||||
aiui health-check --detailed # Detailed health check (alias: aiui health)
|
||||
aiui test all # Run all integration tests
|
||||
```
|
||||
@@ -0,0 +1,209 @@
|
||||
# Health Endpoints Guide
|
||||
|
||||
## Overview
|
||||
|
||||
PraisonAIUI provides multiple health endpoints optimized for different use cases:
|
||||
|
||||
- **`/health/live`** - Fast liveness check (<500ms)
|
||||
- **`/health/ready`** - Readiness check with optional deep checks
|
||||
- **`/health`** - Legacy endpoint (backward compatibility)
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Liveness Check: `/health/live`
|
||||
|
||||
Fast endpoint to verify the server is running. Returns immediately with minimal processing.
|
||||
|
||||
**Use for:**
|
||||
- Load balancer health probes
|
||||
- Kubernetes liveness probes
|
||||
- High-frequency monitoring
|
||||
|
||||
**Response time:** <500ms (typically <100ms)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl http://localhost:8082/health/live
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2024-01-20T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Readiness Check: `/health/ready`
|
||||
|
||||
Verifies the server is ready to handle requests. Performs parallel health checks on all features with timeout protection.
|
||||
|
||||
**Use for:**
|
||||
- Kubernetes readiness probes
|
||||
- Pre-deployment validation
|
||||
- Detailed diagnostics
|
||||
|
||||
**Query parameters:**
|
||||
- `?deep=false` - Skip feature checks for faster response
|
||||
|
||||
**Response time:**
|
||||
- With deep checks: <1s (parallel execution)
|
||||
- Without deep checks: <200ms
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Full readiness check
|
||||
curl http://localhost:8082/health/ready
|
||||
|
||||
# Basic readiness (no feature checks)
|
||||
curl http://localhost:8082/health/ready?deep=false
|
||||
```
|
||||
|
||||
**Response with deep checks:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2024-01-20T10:30:00.000Z",
|
||||
"provider": {
|
||||
"name": "PraisonAIProvider",
|
||||
"status": "ok"
|
||||
},
|
||||
"features": {
|
||||
"agents": {"status": "ok", "feature": "agents"},
|
||||
"chat": {"status": "ok", "feature": "chat"},
|
||||
"memory": {"healthy": false, "detail": "timeout"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Health: `/api/health`
|
||||
|
||||
Cached version of readiness check for dashboard polling. 30-second TTL to reduce load.
|
||||
|
||||
**Use for:**
|
||||
- Dashboard health indicators
|
||||
- Frequent client polling
|
||||
- Status pages
|
||||
|
||||
**Response:** Same as `/health/ready` but cached for 30 seconds
|
||||
|
||||
## Deployment Configurations
|
||||
|
||||
### Kubernetes
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: praisonaiui
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: praisonaiui
|
||||
image: praisonaiui:latest
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 8082
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready?deep=false
|
||||
port: 8082
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
```
|
||||
|
||||
### AWS ALB/ELB
|
||||
|
||||
```terraform
|
||||
resource "aws_lb_target_group" "praisonaiui" {
|
||||
health_check {
|
||||
enabled = true
|
||||
path = "/health/live"
|
||||
interval = 30
|
||||
timeout = 5
|
||||
healthy_threshold = 2
|
||||
unhealthy_threshold = 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
praisonaiui:
|
||||
image: praisonaiui:latest
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8082/health/live"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
upstream praisonaiui {
|
||||
server backend1:8082 max_fails=2 fail_timeout=5s;
|
||||
server backend2:8082 max_fails=2 fail_timeout=5s;
|
||||
|
||||
# Health check configuration
|
||||
check interval=5000 rise=2 fall=3 timeout=2000 type=http;
|
||||
check_http_send "GET /health/live HTTP/1.0\r\n\r\n";
|
||||
check_http_expect_alive http_2xx;
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you're upgrading from a version using only `/health`:
|
||||
|
||||
1. **Update monitoring:** Change health checks to use `/health/live` for liveness
|
||||
2. **Update readiness:** Use `/health/ready?deep=false` for readiness probes
|
||||
3. **Update dashboards:** Point status indicators to `/api/health` (cached)
|
||||
4. **Backward compatibility:** The `/health` endpoint remains available but may be slow
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
| Endpoint | Cold Start | Warm (p50) | Warm (p95) | Use Case |
|
||||
|----------|------------|------------|------------|----------|
|
||||
| `/health/live` | <100ms | <20ms | <50ms | Liveness probes |
|
||||
| `/health/ready?deep=false` | <200ms | <50ms | <100ms | Basic readiness |
|
||||
| `/health/ready` | <1s | <500ms | <800ms | Full diagnostics |
|
||||
| `/api/health` (cached) | <1s* | <20ms | <50ms | Dashboard polling |
|
||||
|
||||
*First request only, then cached for 30s
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Slow `/health` responses
|
||||
|
||||
If you're seeing slow responses on the legacy `/health` endpoint:
|
||||
- Switch to `/health/live` for fast checks
|
||||
- Use `/health/ready?deep=false` if you don't need feature health
|
||||
- Enable caching with `/api/health` for frequent polling
|
||||
|
||||
### Feature timeouts
|
||||
|
||||
Features that timeout in `/health/ready` will show:
|
||||
```json
|
||||
{
|
||||
"feature_name": {
|
||||
"healthy": false,
|
||||
"detail": "timeout"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This indicates the feature took longer than 500ms to respond. The server is still operational, but that specific feature may be experiencing issues.
|
||||
|
||||
### High latency under load
|
||||
|
||||
Under high load, use `/health/live` exclusively for monitoring. It has minimal overhead and won't impact performance.
|
||||
+1
-1
@@ -23,7 +23,7 @@ See also: [`docs/examples/basic.md`](../docs/examples/basic.md) and [`docs/examp
|
||||
| 09 | [widget](python/09-widget/) | Copilot/sidebar mode | `aiui run app.py` |
|
||||
| 10 | [feature-showcase](python/10-feature-showcase/) | All protocol features seeded, `create_app()` | `aiui run app.py` |
|
||||
| 11 | [ui-integration](python/11-ui-integration/) | Gradio ASGI mount, Streamlit iframe, REST embedding | `aiui run app.py` |
|
||||
| 12 | [agent-dashboard](python/12-agent-dashboard/) | OpenClaw-style rendered HTML admin panel | `python app.py` |
|
||||
| 12 | ~~agent-dashboard~~ → Use [dashboard-test](python/15-dashboard-test/) | ~~Removed in v0.5.0 (XSS vulnerability)~~ | See example 15 |
|
||||
| 13 | [real-dashboard](python/13-real-dashboard/) | Real PraisonAI Agent + native dashboard + live metrics | `aiui run app.py --style dashboard` |
|
||||
| 14 | [all-features](python/14-all-features/) | Integration test suite for all 16 feature APIs | `python app.py` |
|
||||
| 15 | [dashboard-test](python/15-dashboard-test/) | Protocol-first page registration, view registry | `python app.py` |
|
||||
|
||||
@@ -1,508 +0,0 @@
|
||||
"""Agent Dashboard — OpenClaw/agent-ui style admin panel.
|
||||
|
||||
⚠️ DEPRECATED: This example uses unsafe innerHTML rendering and will be removed in v0.5.0.
|
||||
For secure admin dashboards, use examples/15-dashboard-test/ or examples/13-real-dashboard/
|
||||
which use the component-based rendering model.
|
||||
|
||||
What's New (vs full-dashboard/):
|
||||
• Rendered HTML dashboard — not just JSON APIs
|
||||
• Sidebar navigation with live page switching
|
||||
• Status badges, toggle buttons, real-time data
|
||||
• Glassmorphism dark theme
|
||||
• All data from PraisonAIUI feature APIs
|
||||
|
||||
Run:
|
||||
python app.py
|
||||
# Dashboard at http://localhost:8082
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import uvicorn
|
||||
from starlette.responses import HTMLResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
from praisonaiui.server import create_app
|
||||
|
||||
# Use shared seed data helper
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from _shared.seed_data import seed_demo_data
|
||||
|
||||
# ── HTML Template ────────────────────────────────────────────────────
|
||||
|
||||
DASHBOARD_HTML = """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Agent Dashboard — PraisonAIUI</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #0a0a12;
|
||||
--bg-card: #12121f;
|
||||
--bg-hover: #1a1a2e;
|
||||
--border: #2a2a4a;
|
||||
--text-primary: #e0e0e8;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent-purple: #7c3aed;
|
||||
--accent-cyan: #06b6d4;
|
||||
--accent-green: #22c55e;
|
||||
--accent-amber: #f59e0b;
|
||||
--accent-red: #ef4444;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg-primary);
|
||||
color: var(--text-primary); display: flex; min-height: 100vh; }
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar { width: 240px; background: var(--bg-card); border-right: 1px solid var(--border);
|
||||
display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 10; }
|
||||
.sidebar-header { padding: 1.25rem; border-bottom: 1px solid var(--border); }
|
||||
.sidebar-header h1 { font-size: 1.1rem; font-weight: 700;
|
||||
background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.sidebar-header p { font-size: 0.7rem; color: var(--text-secondary); margin-top: 2px; }
|
||||
.nav-group { padding: 0.75rem 0; }
|
||||
.nav-group-label { font-size: 0.65rem; color: var(--text-secondary); text-transform: uppercase;
|
||||
letter-spacing: 0.1em; padding: 0 1rem; margin-bottom: 0.25rem; font-weight: 600; }
|
||||
.nav-item { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 1rem;
|
||||
cursor: pointer; transition: all 0.2s; font-size: 0.85rem; color: var(--text-secondary);
|
||||
border-left: 3px solid transparent; }
|
||||
.nav-item:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
.nav-item.active { background: var(--bg-hover); color: var(--accent-purple);
|
||||
border-left-color: var(--accent-purple); font-weight: 500; }
|
||||
.nav-icon { font-size: 1rem; width: 1.5rem; text-align: center; }
|
||||
.nav-badge { margin-left: auto; background: var(--accent-purple); color: white;
|
||||
font-size: 0.65rem; padding: 1px 6px; border-radius: 10px; }
|
||||
|
||||
/* Main content */
|
||||
.main { margin-left: 240px; flex: 1; padding: 1.5rem; }
|
||||
.page-header { margin-bottom: 1.5rem; }
|
||||
.page-header h2 { font-size: 1.5rem; font-weight: 600; }
|
||||
.page-header p { color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem; }
|
||||
|
||||
/* Cards grid */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem; margin-bottom: 1.5rem; }
|
||||
.stat-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 1.25rem; transition: border-color 0.3s; }
|
||||
.stat-card:hover { border-color: var(--accent-purple); }
|
||||
.stat-label { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase;
|
||||
letter-spacing: 0.05em; }
|
||||
.stat-value { font-size: 1.75rem; font-weight: 700; margin-top: 0.25rem;
|
||||
background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.stat-sub { font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem; }
|
||||
|
||||
/* Table */
|
||||
.data-table { width: 100%; background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: 10px; overflow: hidden; }
|
||||
.data-table th { text-align: left; padding: 0.75rem 1rem; font-size: 0.75rem;
|
||||
text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary);
|
||||
background: var(--bg-hover); border-bottom: 1px solid var(--border); font-weight: 600; }
|
||||
.data-table td { padding: 0.75rem 1rem; font-size: 0.85rem; border-bottom: 1px solid var(--border); }
|
||||
.data-table tr:last-child td { border-bottom: none; }
|
||||
.data-table tr:hover td { background: rgba(124, 58, 237, 0.05); }
|
||||
|
||||
/* Badges */
|
||||
.badge { padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; }
|
||||
.badge-online { background: rgba(34, 197, 94, 0.15); color: var(--accent-green); }
|
||||
.badge-offline { background: rgba(239, 68, 68, 0.15); color: var(--accent-red); }
|
||||
.badge-enabled { background: rgba(34, 197, 94, 0.15); color: var(--accent-green); }
|
||||
.badge-disabled { background: rgba(148, 163, 184, 0.15); color: var(--text-secondary); }
|
||||
.badge-running { background: rgba(6, 182, 212, 0.15); color: var(--accent-cyan); }
|
||||
.badge-platform { background: rgba(124, 58, 237, 0.1); color: var(--accent-purple); }
|
||||
|
||||
/* Toggle switch */
|
||||
.toggle { position: relative; width: 36px; height: 20px; cursor: pointer; }
|
||||
.toggle input { display: none; }
|
||||
.toggle-slider { position: absolute; inset: 0; background: #333; border-radius: 10px;
|
||||
transition: 0.3s; }
|
||||
.toggle-slider::before { content: ''; position: absolute; left: 2px; top: 2px; width: 16px;
|
||||
height: 16px; background: white; border-radius: 50%; transition: 0.3s; }
|
||||
.toggle input:checked + .toggle-slider { background: var(--accent-purple); }
|
||||
.toggle input:checked + .toggle-slider::before { transform: translateX(16px); }
|
||||
|
||||
/* Section */
|
||||
.section { margin-bottom: 1.5rem; }
|
||||
.section-title { font-size: 0.85rem; color: var(--text-secondary); font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem;
|
||||
display: flex; align-items: center; gap: 0.5rem; }
|
||||
.section-count { background: var(--accent-purple); color: white; font-size: 0.65rem;
|
||||
padding: 1px 6px; border-radius: 10px; }
|
||||
|
||||
/* Page display */
|
||||
.page { display: none; }
|
||||
.page.active { display: block; }
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); } }
|
||||
.page.active { animation: fadeIn 0.3s ease; }
|
||||
|
||||
/* Feature list */
|
||||
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem; }
|
||||
.feature-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 1.25rem; transition: all 0.3s; }
|
||||
.feature-card:hover { border-color: var(--accent-purple); transform: translateY(-2px); }
|
||||
.feature-name { font-weight: 600; margin-bottom: 0.25rem; }
|
||||
.feature-desc { font-size: 0.8rem; color: var(--text-secondary); }
|
||||
.feature-routes { margin-top: 0.75rem; font-size: 0.75rem; color: var(--text-secondary); }
|
||||
.feature-route { font-family: monospace; font-size: 0.7rem; color: var(--accent-cyan);
|
||||
display: block; margin-top: 2px; }
|
||||
|
||||
/* Empty state */
|
||||
.empty { text-align: center; padding: 3rem; color: var(--text-secondary); }
|
||||
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h1>🤖 Agent Dashboard</h1>
|
||||
<p>PraisonAIUI Admin</p>
|
||||
</div>
|
||||
<nav>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label">Overview</div>
|
||||
<div class="nav-item active" data-page="overview">
|
||||
<span class="nav-icon">📊</span> Overview
|
||||
</div>
|
||||
<div class="nav-item" data-page="features">
|
||||
<span class="nav-icon">⚡</span> Features
|
||||
<span class="nav-badge" id="nav-feature-count">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label">Messaging</div>
|
||||
<div class="nav-item" data-page="channels">
|
||||
<span class="nav-icon">📡</span> Channels
|
||||
<span class="nav-badge" id="nav-channel-count">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label">Infrastructure</div>
|
||||
<div class="nav-item" data-page="nodes">
|
||||
<span class="nav-icon">🖥️</span> Nodes
|
||||
<span class="nav-badge" id="nav-node-count">—</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="instances">
|
||||
<span class="nav-icon">📻</span> Instances
|
||||
<span class="nav-badge" id="nav-instance-count">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label">Automation</div>
|
||||
<div class="nav-item" data-page="schedules">
|
||||
<span class="nav-icon">⏰</span> Schedules
|
||||
<span class="nav-badge" id="nav-schedule-count">—</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="skills">
|
||||
<span class="nav-icon">🧩</span> Skills
|
||||
<span class="nav-badge" id="nav-skill-count">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<!-- OVERVIEW PAGE -->
|
||||
<div class="page active" id="page-overview">
|
||||
<div class="page-header">
|
||||
<h2>Overview</h2>
|
||||
<p>Real-time system status at a glance</p>
|
||||
</div>
|
||||
<div class="stats-grid" id="overview-stats"></div>
|
||||
</div>
|
||||
|
||||
<!-- FEATURES PAGE -->
|
||||
<div class="page" id="page-features">
|
||||
<div class="page-header">
|
||||
<h2>Features</h2>
|
||||
<p>All registered protocol feature modules</p>
|
||||
</div>
|
||||
<div class="feature-grid" id="features-grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- CHANNELS PAGE -->
|
||||
<div class="page" id="page-channels">
|
||||
<div class="page-header">
|
||||
<h2>Channels</h2>
|
||||
<p>Multi-platform messaging channels</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Active Channels <span class="section-count" id="channel-count">0</span></div>
|
||||
<table class="data-table" id="channels-table">
|
||||
<thead><tr>
|
||||
<th>Name</th><th>Platform</th><th>Status</th><th>Enabled</th><th>Last Activity</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NODES PAGE -->
|
||||
<div class="page" id="page-nodes">
|
||||
<div class="page-header">
|
||||
<h2>Nodes</h2>
|
||||
<p>Execution nodes and agent bindings</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Registered Nodes <span class="section-count" id="node-count">0</span></div>
|
||||
<table class="data-table" id="nodes-table">
|
||||
<thead><tr>
|
||||
<th>Name</th><th>Host</th><th>Platform</th><th>Status</th><th>Agents</th><th>Policy</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INSTANCES PAGE -->
|
||||
<div class="page" id="page-instances">
|
||||
<div class="page-header">
|
||||
<h2>Instances</h2>
|
||||
<p>Connected clients and workers</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Connected Instances <span class="section-count" id="instance-count">0</span></div>
|
||||
<table class="data-table" id="instances-table">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>Host</th><th>Platform</th><th>Mode</th><th>Roles</th><th>Last Seen</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCHEDULES PAGE -->
|
||||
<div class="page" id="page-schedules">
|
||||
<div class="page-header">
|
||||
<h2>Schedules</h2>
|
||||
<p>Cron jobs and scheduled tasks</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Scheduled Jobs <span class="section-count" id="schedule-count">0</span></div>
|
||||
<table class="data-table" id="schedules-table">
|
||||
<thead><tr>
|
||||
<th>Name</th><th>Schedule</th><th>Agent</th><th>Enabled</th><th>Runs</th><th>Last Run</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SKILLS PAGE -->
|
||||
<div class="page" id="page-skills">
|
||||
<div class="page-header">
|
||||
<h2>Skills</h2>
|
||||
<p>Registered agent capabilities</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Agent Skills <span class="section-count" id="skill-count">0</span></div>
|
||||
<table class="data-table" id="skills-table">
|
||||
<thead><tr>
|
||||
<th>Name</th><th>ID</th><th>Enabled</th><th>Config</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ── Navigation ──────────────────────────────────────────────────────
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
document.getElementById('page-' + item.dataset.page).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Data loading ────────────────────────────────────────────────────
|
||||
async function fetchJSON(url) {
|
||||
const r = await fetch(url);
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function timeAgo(ts) {
|
||||
if (!ts) return '—';
|
||||
const s = Math.floor(Date.now() / 1000 - ts);
|
||||
if (s < 60) return s + 's ago';
|
||||
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
||||
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
||||
return Math.floor(s / 86400) + 'd ago';
|
||||
}
|
||||
|
||||
async function loadOverview() {
|
||||
const [features, channels, nodes, instances, schedules, skills] = await Promise.all([
|
||||
fetchJSON('/api/features'),
|
||||
fetchJSON('/api/channels'),
|
||||
fetchJSON('/api/nodes'),
|
||||
fetchJSON('/api/instances'),
|
||||
fetchJSON('/api/schedules'),
|
||||
fetchJSON('/api/skills'),
|
||||
]);
|
||||
|
||||
// Nav badges
|
||||
document.getElementById('nav-feature-count').textContent = features.count;
|
||||
document.getElementById('nav-channel-count').textContent = channels.count;
|
||||
document.getElementById('nav-node-count').textContent = nodes.count;
|
||||
document.getElementById('nav-instance-count').textContent = instances.count;
|
||||
document.getElementById('nav-schedule-count').textContent = schedules.count;
|
||||
document.getElementById('nav-skill-count').textContent = skills.count;
|
||||
|
||||
// Overview stats
|
||||
const statsEl = document.getElementById('overview-stats');
|
||||
const runningChannels = channels.channels.filter(c => c.running).length;
|
||||
const onlineNodes = nodes.nodes.filter(n => n.status === 'online').length;
|
||||
statsEl.innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Features</div>
|
||||
<div class="stat-value">${features.count}</div>
|
||||
<div class="stat-sub">Protocol modules active</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Channels</div>
|
||||
<div class="stat-value">${channels.count}</div>
|
||||
<div class="stat-sub">${runningChannels} running</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Nodes</div>
|
||||
<div class="stat-value">${nodes.count}</div>
|
||||
<div class="stat-sub">${onlineNodes} online</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Instances</div>
|
||||
<div class="stat-value">${instances.count}</div>
|
||||
<div class="stat-sub">Connected clients & workers</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Schedules</div>
|
||||
<div class="stat-value">${schedules.count}</div>
|
||||
<div class="stat-sub">Cron jobs registered</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Skills</div>
|
||||
<div class="stat-value">${skills.count}</div>
|
||||
<div class="stat-sub">Agent capabilities</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Features grid
|
||||
const fg = document.getElementById('features-grid');
|
||||
fg.innerHTML = features.features.map(f => `
|
||||
<div class="feature-card">
|
||||
<div class="feature-name">${f.name}</div>
|
||||
<div class="feature-desc">${f.description}</div>
|
||||
<div class="feature-routes">${f.routes.length} routes
|
||||
${f.routes.slice(0, 3).map(r => '<span class="feature-route">' + r + '</span>').join('')}
|
||||
${f.routes.length > 3 ? '<span class="feature-route">... +' + (f.routes.length - 3) + ' more</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Channels table
|
||||
document.getElementById('channel-count').textContent = channels.count;
|
||||
document.querySelector('#channels-table tbody').innerHTML = channels.channels.map(c => `
|
||||
<tr>
|
||||
<td><strong>${c.name}</strong></td>
|
||||
<td><span class="badge badge-platform">${c.platform}</span></td>
|
||||
<td>${c.running ? '<span class="badge badge-running">● running</span>' : '<span class="badge badge-offline">● stopped</span>'}</td>
|
||||
<td>${c.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
|
||||
<td>${timeAgo(c.last_activity)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Nodes table
|
||||
document.getElementById('node-count').textContent = nodes.count;
|
||||
document.querySelector('#nodes-table tbody').innerHTML = nodes.nodes.map(n => `
|
||||
<tr>
|
||||
<td><strong>${n.name}</strong></td>
|
||||
<td><code>${n.host}</code></td>
|
||||
<td>${n.platform}</td>
|
||||
<td>${n.status === 'online' ? '<span class="badge badge-online">● online</span>' : '<span class="badge badge-offline">● offline</span>'}</td>
|
||||
<td>${(n.agents || []).map(a => '<span class="badge badge-platform">' + a + '</span> ').join('')}</td>
|
||||
<td><span class="badge">${n.approval_policy}</span></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Instances table
|
||||
document.getElementById('instance-count').textContent = instances.count;
|
||||
document.querySelector('#instances-table tbody').innerHTML = instances.instances.map(i => `
|
||||
<tr>
|
||||
<td><code>${i.id}</code></td>
|
||||
<td>${i.host}</td>
|
||||
<td>${i.platform}</td>
|
||||
<td><span class="badge badge-${i.mode === 'worker' ? 'running' : 'enabled'}">${i.mode}</span></td>
|
||||
<td>${(i.roles || []).join(', ')}</td>
|
||||
<td>${timeAgo(i.last_seen)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Schedules table
|
||||
document.getElementById('schedule-count').textContent = schedules.count;
|
||||
document.querySelector('#schedules-table tbody').innerHTML = schedules.schedules.map(s => `
|
||||
<tr>
|
||||
<td><strong>${s.name}</strong></td>
|
||||
<td><code>${s.schedule}</code></td>
|
||||
<td>${s.agent_id || '—'}</td>
|
||||
<td>${s.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
|
||||
<td>${s.run_count || 0}</td>
|
||||
<td>${timeAgo(s.last_run)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Skills table
|
||||
document.getElementById('skill-count').textContent = skills.count;
|
||||
document.querySelector('#skills-table tbody').innerHTML = skills.skills.map(s => `
|
||||
<tr>
|
||||
<td><strong>${s.name}</strong></td>
|
||||
<td><code>${s.id}</code></td>
|
||||
<td>${s.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
|
||||
<td><code>${JSON.stringify(s.config || {})}</code></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Auto-refresh every 10 seconds
|
||||
loadOverview();
|
||||
setInterval(loadOverview, 10000);
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
|
||||
# ── Entry point ──────────────────────────────────────────────────────
|
||||
|
||||
seed_demo_data()
|
||||
|
||||
if __name__ == "__main__":
|
||||
warnings.warn(
|
||||
"examples/12-agent-dashboard uses unsafe innerHTML rendering and will be removed in v0.5.0. "
|
||||
"Use examples/15-dashboard-test/ or examples/13-real-dashboard/ for secure admin dashboards.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
|
||||
app = create_app()
|
||||
|
||||
# Add the dashboard HTML landing page
|
||||
async def dashboard_landing(request):
|
||||
return HTMLResponse(DASHBOARD_HTML)
|
||||
|
||||
app.routes.insert(0, Route("/", dashboard_landing, methods=["GET"]))
|
||||
|
||||
print("✅ Agent Dashboard at http://localhost:8082")
|
||||
print("⚠️ DEPRECATED: This example uses unsafe innerHTML rendering")
|
||||
print(" For secure dashboards, use examples/15-dashboard-test/")
|
||||
print(" JSON API: http://localhost:8082/api/features")
|
||||
host = os.getenv("HOST", "127.0.0.1")
|
||||
uvicorn.run(app, host=host, port=8082, log_level="info")
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Real Agent Dashboard — PraisonAI agents + PraisonAIUI dashboard.
|
||||
|
||||
Unlike 12-agent-dashboard (mock data), this runs REAL PraisonAI agents.
|
||||
This example uses secure component-based rendering with REAL PraisonAI agents.
|
||||
Uses PraisonAIUI's native dashboard with @reply, @page, register_agent()
|
||||
— all backed by actual LLM calls.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "aiui"
|
||||
version = "0.3.121"
|
||||
version = "0.3.127"
|
||||
description = "YAML-driven website generator - CLI and compiler"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
+100
-73
@@ -9,6 +9,7 @@ import { ZoneWidgets } from './Widgets'
|
||||
import { Toc } from './Toc'
|
||||
import { Footer } from './Footer'
|
||||
import { ChatLayout, AgentUILayout, CopilotWidget, PlaygroundLayout } from './layouts'
|
||||
import { LocaleProvider } from './i18n'
|
||||
|
||||
function SkipLink({ enabled }: { enabled?: boolean }) {
|
||||
if (!enabled) return null
|
||||
@@ -31,84 +32,32 @@ export default function App() {
|
||||
const [selectedItem, setSelectedItem] = useState<NavItem | null>(null)
|
||||
const [activeItemPath, setActiveItemPath] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
async function loadManifests() {
|
||||
try {
|
||||
const [configRes, navRes, routesRes] = await Promise.all([
|
||||
fetch('/ui-config.json'),
|
||||
fetch('/docs-nav.json'),
|
||||
fetch('/route-manifest.json'),
|
||||
])
|
||||
|
||||
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
|
||||
throw new Error('Failed to load manifests')
|
||||
// Helper function to find nav item by path
|
||||
const findNavItemByPath = (navData: DocsNav, path: string): NavItem | null => {
|
||||
const findItem = (items: NavItem[]): NavItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.path === path || item.path === path.replace(/^\//, '')) {
|
||||
return item
|
||||
}
|
||||
|
||||
const configData = await configRes.json()
|
||||
const navData = await navRes.json()
|
||||
setConfig(configData)
|
||||
setNav(navData)
|
||||
setRoutes(await routesRes.json())
|
||||
|
||||
// Apply theme from YAML config
|
||||
const theme = configData.site?.theme
|
||||
applyTheme(
|
||||
theme?.preset || 'zinc',
|
||||
theme?.darkMode !== false,
|
||||
theme?.radius || 'md'
|
||||
)
|
||||
|
||||
// Handle initial URL path for SPA routing
|
||||
const currentPath = window.location.pathname
|
||||
if (currentPath && currentPath !== '/') {
|
||||
// Find matching nav item by path
|
||||
const findItem = (items: NavItem[]): NavItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.path === currentPath || item.path === currentPath.replace(/^\//, '')) {
|
||||
return item
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findItem(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Search all nav groups
|
||||
if (navData.items) {
|
||||
for (const group of navData.items) {
|
||||
if (group.children) {
|
||||
const found = findItem(group.children)
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findItem(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
loadManifests()
|
||||
|
||||
// Handle browser back/forward navigation
|
||||
const handlePopState = () => {
|
||||
const path = window.location.pathname
|
||||
if (path === '/') {
|
||||
setSelectedItem(null)
|
||||
setActiveItemPath('')
|
||||
// Search all nav groups
|
||||
if (navData.items) {
|
||||
for (const group of navData.items) {
|
||||
if (group.children) {
|
||||
const found = findItem(group.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => window.removeEventListener('popstate', handlePopState)
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
|
||||
// Update SEO meta tags dynamically
|
||||
const updateSEO = (title: string, path: string, description?: string) => {
|
||||
@@ -116,7 +65,12 @@ export default function App() {
|
||||
const titleTemplate = config.seo?.titleTemplate || '%s | %s'
|
||||
const siteName = config.site?.title || 'Documentation'
|
||||
if (titleTemplate.includes('%s')) {
|
||||
document.title = titleTemplate.replace('%s', title).replace('%s', siteName)
|
||||
// Replace first %s with page title, second %s (if exists) with site name
|
||||
let formattedTitle = titleTemplate.replace('%s', title)
|
||||
if (formattedTitle.includes('%s')) {
|
||||
formattedTitle = formattedTitle.replace('%s', siteName)
|
||||
}
|
||||
document.title = formattedTitle
|
||||
} else {
|
||||
document.title = `${title} | ${siteName}`
|
||||
}
|
||||
@@ -181,6 +135,75 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function loadManifests() {
|
||||
try {
|
||||
const [configRes, navRes, routesRes] = await Promise.all([
|
||||
fetch('/ui-config.json'),
|
||||
fetch('/docs-nav.json'),
|
||||
fetch('/route-manifest.json'),
|
||||
])
|
||||
|
||||
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
|
||||
throw new Error('Failed to load manifests')
|
||||
}
|
||||
|
||||
const configData = await configRes.json()
|
||||
const navData = await navRes.json()
|
||||
setConfig(configData)
|
||||
setNav(navData)
|
||||
setRoutes(await routesRes.json())
|
||||
|
||||
// Apply theme from YAML config
|
||||
const theme = configData.site?.theme
|
||||
applyTheme(
|
||||
theme?.preset || 'zinc',
|
||||
theme?.darkMode !== false,
|
||||
theme?.radius || 'md'
|
||||
)
|
||||
|
||||
// Handle initial URL path for SPA routing
|
||||
const currentPath = window.location.pathname
|
||||
if (currentPath && currentPath !== '/') {
|
||||
const found = findNavItemByPath(navData, currentPath)
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadManifests()
|
||||
}, [])
|
||||
|
||||
// Handle browser back/forward navigation in a separate effect
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const path = window.location.pathname
|
||||
const found = findNavItemByPath(nav, path)
|
||||
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
// Use updateSEO function for consistency
|
||||
updateSEO(found.title, path)
|
||||
} else if (path === '/') {
|
||||
setSelectedItem(null)
|
||||
setActiveItemPath('')
|
||||
// Reset title to site title
|
||||
document.title = config.site?.title || 'Documentation'
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => window.removeEventListener('popstate', handlePopState)
|
||||
}, [nav, config])
|
||||
|
||||
const handleItemClick = (item: NavItem) => {
|
||||
setSelectedItem(item)
|
||||
setActiveItemPath(item.path || item.title)
|
||||
@@ -372,5 +395,9 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
return renderByStyle()
|
||||
return (
|
||||
<LocaleProvider config={config.i18n}>
|
||||
{renderByStyle()}
|
||||
</LocaleProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Header component
|
||||
import type { UIConfig } from './types'
|
||||
import { LocaleSwitcher } from './i18n'
|
||||
|
||||
export function Header({ config }: { config: UIConfig }) {
|
||||
// Resolve header component via template slot ref (e.g., header_main)
|
||||
@@ -56,6 +57,7 @@ export function Header({ config }: { config: UIConfig }) {
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<LocaleSwitcher />
|
||||
{header?.cta && (
|
||||
<a
|
||||
href={header.cta.href}
|
||||
|
||||
@@ -320,6 +320,21 @@ function handleStoreEvent(
|
||||
break
|
||||
}
|
||||
|
||||
case 'approval_required':
|
||||
case 'approval_pending':
|
||||
case 'run_paused': {
|
||||
// Check if this is an approval event via extra_data
|
||||
const extra = event.extra_data as Record<string, unknown> | undefined
|
||||
if (extra?.type === 'approval_required') {
|
||||
// Emit custom event for dashboard to handle
|
||||
const approvalData = extra
|
||||
window.dispatchEvent(new CustomEvent('aiui:approval-required', {
|
||||
detail: approvalData
|
||||
}))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'end':
|
||||
case 'done':
|
||||
case 'run_cancelled':
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
||||
import type { I18nConfig } from '../types'
|
||||
|
||||
interface LocaleContextValue {
|
||||
locale: string
|
||||
locales: string[]
|
||||
rtlLocales: string[]
|
||||
setLocale: (locale: string) => void
|
||||
t: (key: string, variables?: Record<string, string>) => string
|
||||
isRTL: boolean
|
||||
strings: Record<string, string>
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const LocaleContext = createContext<LocaleContextValue | null>(null)
|
||||
|
||||
export function useLocale() {
|
||||
const context = useContext(LocaleContext)
|
||||
if (!context) {
|
||||
throw new Error('useLocale must be used within LocaleProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface LocaleProviderProps {
|
||||
config?: I18nConfig
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function LocaleProvider({ config, children }: LocaleProviderProps) {
|
||||
const [locale, setLocaleState] = useState(config?.defaultLocale || 'en')
|
||||
const [strings, setStrings] = useState<Record<string, string>>({})
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const locales = config?.locales || ['en']
|
||||
const rtlLocales = config?.rtlLocales || []
|
||||
const isRTL = rtlLocales.includes(locale)
|
||||
|
||||
// Apply RTL to document
|
||||
useEffect(() => {
|
||||
if (isRTL) {
|
||||
document.documentElement.dir = 'rtl'
|
||||
} else {
|
||||
document.documentElement.dir = 'ltr'
|
||||
}
|
||||
}, [isRTL])
|
||||
|
||||
// Load strings from API when locale changes
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController()
|
||||
|
||||
async function loadStrings() {
|
||||
if (!config || locales.length <= 1) return
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch(`/api/i18n/strings/${locale}`, {
|
||||
signal: abortController.signal
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setStrings(data.strings || {})
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore abort errors
|
||||
if (error instanceof Error && error.name === 'AbortError') return
|
||||
console.error('Failed to load translations:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadStrings()
|
||||
|
||||
// Cleanup: abort the request if component unmounts or locale changes
|
||||
return () => abortController.abort()
|
||||
}, [locale, config, locales.length])
|
||||
|
||||
const setLocale = useCallback(async (newLocale: string) => {
|
||||
// Validate locale format (e.g., 'en', 'en-US', 'zh-CN')
|
||||
if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(newLocale)) {
|
||||
console.error('Invalid locale format:', newLocale)
|
||||
return
|
||||
}
|
||||
|
||||
if (!locales.includes(newLocale)) return
|
||||
|
||||
setLocaleState(newLocale)
|
||||
|
||||
// Update server-side locale
|
||||
try {
|
||||
await fetch('/api/i18n/locale', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ locale: newLocale })
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to update server locale:', error)
|
||||
}
|
||||
}, [locales])
|
||||
|
||||
const t = useCallback((key: string, variables?: Record<string, string>) => {
|
||||
let text = strings[key] || key
|
||||
|
||||
// Apply variable substitution with proper escaping
|
||||
if (variables) {
|
||||
Object.entries(variables).forEach(([k, v]) => {
|
||||
// Escape special regex characters in the key to prevent injection
|
||||
const escapedKey = k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
text = text.replace(new RegExp(`{${escapedKey}}`, 'g'), v)
|
||||
})
|
||||
}
|
||||
|
||||
return text
|
||||
}, [strings])
|
||||
|
||||
const value: LocaleContextValue = {
|
||||
locale,
|
||||
locales,
|
||||
rtlLocales,
|
||||
setLocale,
|
||||
t,
|
||||
isRTL,
|
||||
strings,
|
||||
loading
|
||||
}
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={value}>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useLocale } from './LocaleProvider'
|
||||
|
||||
export function LocaleSwitcher() {
|
||||
const { locale, locales, setLocale } = useLocale()
|
||||
|
||||
// Don't show switcher if only one locale
|
||||
if (locales.length <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
value={locale}
|
||||
onChange={(e) => setLocale(e.target.value)}
|
||||
className="px-3 py-1.5 rounded-md border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
|
||||
aria-label="Language"
|
||||
>
|
||||
{locales.map((loc) => (
|
||||
<option key={loc} value={loc}>
|
||||
{loc.toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { LocaleProvider, useLocale } from './LocaleProvider'
|
||||
export { LocaleSwitcher } from './LocaleSwitcher'
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Version information."""
|
||||
|
||||
__version__ = "0.3.114"
|
||||
__version__ = "0.3.124"
|
||||
+122
-41
@@ -1,6 +1,7 @@
|
||||
"""CLI module - Typer-based command line interface."""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -10,6 +11,39 @@ from rich.panel import Panel
|
||||
|
||||
from praisonaiui.__version__ import __version__
|
||||
|
||||
# Configure UTF-8 on Windows to handle Unicode output
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _supports_unicode() -> bool:
|
||||
"""Return True if stdout can encode non-ASCII (UTF-aware) output.
|
||||
|
||||
Legacy Windows consoles (cp1252/cp437) cannot encode emoji/box-drawing
|
||||
characters and raise UnicodeEncodeError. When reconfigure() above succeeds
|
||||
the encoding becomes utf-8 and rich symbols are kept; otherwise we fall
|
||||
back to ASCII so output never crashes.
|
||||
"""
|
||||
try:
|
||||
encoding = sys.stdout.encoding or ""
|
||||
except Exception:
|
||||
return False
|
||||
return "utf" in encoding.lower()
|
||||
|
||||
|
||||
def _icon(symbol: str, fallback: str) -> str:
|
||||
"""Return ``symbol`` on UTF-aware terminals, else ``fallback`` (ASCII).
|
||||
|
||||
Preserves the rich emoji UI on macOS/Linux and modern Windows terminals
|
||||
while degrading gracefully on legacy code pages instead of crashing.
|
||||
"""
|
||||
return symbol if _supports_unicode() else fallback
|
||||
|
||||
|
||||
app = typer.Typer(
|
||||
name="aiui",
|
||||
help="PraisonAIUI - YAML-driven website generator",
|
||||
@@ -123,7 +157,7 @@ routes:
|
||||
template: "docs"
|
||||
"""
|
||||
|
||||
config_path.write_text(minimal_config)
|
||||
config_path.write_text(minimal_config, encoding="utf-8")
|
||||
console.print(Panel(f"[green]✓[/green] Created {config_path}", title="Success"))
|
||||
|
||||
|
||||
@@ -213,7 +247,7 @@ def validate(
|
||||
raise typer.Exit(code=2)
|
||||
|
||||
try:
|
||||
with open(config) as f:
|
||||
with open(config, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
cfg = Config.model_validate(data)
|
||||
except Exception as e:
|
||||
@@ -264,7 +298,7 @@ def build(
|
||||
raise typer.Exit(code=2)
|
||||
|
||||
try:
|
||||
with open(config) as f:
|
||||
with open(config, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
cfg = Config.model_validate(data)
|
||||
except Exception as e:
|
||||
@@ -341,7 +375,7 @@ def serve(
|
||||
|
||||
# Build first unless --no-build
|
||||
if not no_build:
|
||||
console.print("[yellow]⏳[/yellow] Building manifests...")
|
||||
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Building manifests...")
|
||||
build(config=config, output=output, minify=False)
|
||||
|
||||
# Check if output directory exists
|
||||
@@ -369,7 +403,9 @@ def serve(
|
||||
raise typer.Exit(code=4)
|
||||
|
||||
if actual_port != port:
|
||||
console.print(f"[yellow]⚠️[/yellow] Port {port} in use, using {actual_port}")
|
||||
console.print(
|
||||
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Port {port} in use, using {actual_port}"
|
||||
)
|
||||
|
||||
# Build Starlette app with security
|
||||
from starlette.applications import Starlette
|
||||
@@ -549,7 +585,7 @@ def dev(
|
||||
)
|
||||
tmp_cfg = None
|
||||
if has_override and config_file.exists():
|
||||
with open(config_file) as f:
|
||||
with open(config_file, encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f.read())
|
||||
if "site" not in config:
|
||||
config["site"] = {}
|
||||
@@ -562,7 +598,7 @@ def dev(
|
||||
if dark_mode is not None:
|
||||
config["site"]["theme"]["darkMode"] = dark_mode
|
||||
tmp_cfg = example_path / ".aiui.tmp.yaml"
|
||||
with open(tmp_cfg, "w") as f:
|
||||
with open(tmp_cfg, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config, f, default_flow_style=False)
|
||||
build_config = str(tmp_cfg)
|
||||
|
||||
@@ -945,7 +981,7 @@ def dev(
|
||||
yaml_path = examples_dir / example_name / "aiui.template.yaml"
|
||||
yaml_content = ""
|
||||
if yaml_path.exists():
|
||||
with open(yaml_path) as f:
|
||||
with open(yaml_path, encoding="utf-8") as f:
|
||||
yaml_content = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
@@ -989,7 +1025,7 @@ def dev(
|
||||
if example_name and example_name in examples:
|
||||
yaml_path = examples_dir / example_name / "aiui.template.yaml"
|
||||
# Write the new YAML
|
||||
with open(yaml_path, "w") as f:
|
||||
with open(yaml_path, "w", encoding="utf-8") as f:
|
||||
f.write(yaml_content)
|
||||
console.print(
|
||||
f"[yellow]Rebuilding {example_name} with edited YAML...[/yellow]"
|
||||
@@ -1348,10 +1384,10 @@ def run(
|
||||
|
||||
if is_yaml:
|
||||
# Load YAML chat configuration
|
||||
console.print(f"[yellow]⏳[/yellow] Loading {app_file}...")
|
||||
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Loading {app_file}...")
|
||||
import yaml as _yaml
|
||||
|
||||
with open(app_file) as f:
|
||||
with open(app_file, encoding="utf-8") as f:
|
||||
chat_yaml = _yaml.safe_load(f) or {}
|
||||
|
||||
# Apply config from YAML (override CLI defaults only if present)
|
||||
@@ -1386,7 +1422,7 @@ def run(
|
||||
is_chat_mode = True
|
||||
else:
|
||||
# Load the user's app module FIRST to register callbacks
|
||||
console.print(f"[yellow]⏳[/yellow] Loading {app_file}...")
|
||||
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Loading {app_file}...")
|
||||
spec = importlib.util.spec_from_file_location("user_app", app_file)
|
||||
if spec is None or spec.loader is None:
|
||||
console.print(f"[red]Error:[/red] Could not load {app_file}")
|
||||
@@ -1452,7 +1488,7 @@ def run(
|
||||
|
||||
# Build static files only if --config was explicitly provided
|
||||
if config is not None and config.exists():
|
||||
console.print("[yellow]⏳[/yellow] Building static files...")
|
||||
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Building static files...")
|
||||
build(config=config, output=output, minify=False)
|
||||
|
||||
# If chat mode: set up chat frontend directly from templates
|
||||
@@ -1484,12 +1520,14 @@ def run(
|
||||
},
|
||||
"style": style,
|
||||
}
|
||||
with open(ui_config_path, "w") as f:
|
||||
with open(ui_config_path, "w", encoding="utf-8") as f:
|
||||
_json.dump(ui_cfg, f, indent=2)
|
||||
|
||||
# Write minimal route-manifest and docs-nav so the SPA doesn't error
|
||||
(output / "route-manifest.json").write_text('{"routes": []}')
|
||||
(output / "docs-nav.json").write_text('{"items": []}')
|
||||
(output / "route-manifest.json").write_text(
|
||||
'{"routes": []}', encoding="utf-8"
|
||||
)
|
||||
(output / "docs-nav.json").write_text('{"items": []}', encoding="utf-8")
|
||||
|
||||
# Find available port
|
||||
def find_available_port(start_port: int, max_attempts: int = 10) -> int:
|
||||
@@ -1510,7 +1548,9 @@ def run(
|
||||
raise typer.Exit(code=4)
|
||||
|
||||
if actual_port != port:
|
||||
console.print(f"[yellow]⚠️[/yellow] Port {port} in use, using {actual_port}")
|
||||
console.print(
|
||||
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Port {port} in use, using {actual_port}"
|
||||
)
|
||||
|
||||
static_dir = output if output.exists() else None
|
||||
|
||||
@@ -1799,7 +1839,7 @@ def sessions_messages(
|
||||
# ---------------------------------------------------------------------------
|
||||
# Subcommand: health
|
||||
# ---------------------------------------------------------------------------
|
||||
@app.command()
|
||||
@app.command("health-check")
|
||||
def health_check(
|
||||
server: str = typer.Option(
|
||||
"http://127.0.0.1:8000",
|
||||
@@ -1818,15 +1858,17 @@ def health_check(
|
||||
import json as _json
|
||||
from urllib.request import urlopen
|
||||
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
try:
|
||||
with urlopen(f"{server}/health") as resp:
|
||||
with urlopen(f"{server}/health/live") as resp:
|
||||
data = _json.loads(resp.read())
|
||||
status = data.get("status", "unknown")
|
||||
ts = data.get("timestamp", "")
|
||||
if status == "healthy":
|
||||
if is_success_status(status):
|
||||
console.print(
|
||||
Panel.fit(
|
||||
f"Server: [green]healthy[/green] ({ts})",
|
||||
f"Server: [green]{status}[/green] ({ts})",
|
||||
title="Health Check",
|
||||
border_style="green",
|
||||
)
|
||||
@@ -1853,10 +1895,36 @@ def health_check(
|
||||
health = feat.get("health", {})
|
||||
healthy = health.get("healthy", True)
|
||||
detail = health.get("detail", "ok")
|
||||
icon = "[green]✅[/green]" if healthy else "[yellow]⚠️[/yellow]"
|
||||
icon = (
|
||||
f"[green]{_icon('✅', '[OK]')}[/green]"
|
||||
if healthy
|
||||
else f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow]"
|
||||
)
|
||||
console.print(f" {icon} {name}: {detail}")
|
||||
except Exception as e:
|
||||
console.print(f"[yellow]⚠️[/yellow] Could not fetch feature health: {e}")
|
||||
console.print(
|
||||
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Could not fetch feature health: {e}"
|
||||
)
|
||||
|
||||
|
||||
# Register "health" as an alias for health-check (backward compatibility)
|
||||
@app.command("health")
|
||||
def health(
|
||||
server: str = typer.Option(
|
||||
"http://127.0.0.1:8000",
|
||||
"--server",
|
||||
"-s",
|
||||
help="Server URL",
|
||||
),
|
||||
detailed: bool = typer.Option(
|
||||
False,
|
||||
"--detailed",
|
||||
"-d",
|
||||
help="Show per-feature health",
|
||||
),
|
||||
) -> None:
|
||||
"""Check server health (alias for health-check)."""
|
||||
health_check(server=server, detailed=detailed)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1883,13 +1951,15 @@ def provider_status(
|
||||
import json as _json
|
||||
from urllib.request import urlopen
|
||||
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
try:
|
||||
with urlopen(f"{server}/api/provider") as resp:
|
||||
data = _json.loads(resp.read())
|
||||
console.print(f"[bold]Provider:[/bold] {data.get('name', 'unknown')}")
|
||||
console.print(f"[bold]Module:[/bold] {data.get('module', 'unknown')}")
|
||||
status = data.get("status", "unknown")
|
||||
color = "green" if status == "ok" else "red"
|
||||
color = "green" if is_success_status(status) else "red"
|
||||
console.print(f"[bold]Status:[/bold] [{color}]{status}[/{color}]")
|
||||
agents = data.get("agents", [])
|
||||
if agents:
|
||||
@@ -1920,7 +1990,7 @@ def provider_health(
|
||||
from urllib.request import urlopen
|
||||
|
||||
try:
|
||||
with urlopen(f"{server}/health") as resp:
|
||||
with urlopen(f"{server}/health/ready?deep=false") as resp:
|
||||
data = _json.loads(resp.read())
|
||||
provider = data.get("provider", {})
|
||||
console.print(f"[green]✓[/green] Provider: {provider.get('name', 'unknown')}")
|
||||
@@ -2021,12 +2091,14 @@ app.add_typer(features_app, name="features")
|
||||
@features_app.command("list")
|
||||
def features_list(server: str = _SERVER_OPT) -> None:
|
||||
"""List all registered protocol features."""
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
try:
|
||||
data = _api_get(server, "/api/features")
|
||||
for f in data.get("features", []):
|
||||
h = f.get("health", {})
|
||||
status = h.get("status", "?")
|
||||
color = "green" if status == "ok" else "red"
|
||||
color = "green" if is_success_status(status) else "red"
|
||||
console.print(f" [{color}]●[/{color}] {f['name']} — {f.get('description', '')}")
|
||||
if f.get("routes"):
|
||||
console.print(f" routes: {', '.join(f['routes'])}")
|
||||
@@ -2038,15 +2110,17 @@ def features_list(server: str = _SERVER_OPT) -> None:
|
||||
@features_app.command("status")
|
||||
def features_status(server: str = _SERVER_OPT) -> None:
|
||||
"""Show feature health summary."""
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
try:
|
||||
data = _api_get(server, "/api/features")
|
||||
features = data.get("features", [])
|
||||
ok = sum(1 for f in features if f.get("health", {}).get("status") == "ok")
|
||||
ok = sum(1 for f in features if is_success_status(f.get("health", {}).get("status")))
|
||||
console.print(f"Features: {ok}/{len(features)} healthy")
|
||||
for f in features:
|
||||
h = f.get("health", {})
|
||||
status = h.get("status", "?")
|
||||
color = "green" if status == "ok" else "red"
|
||||
color = "green" if is_success_status(status) else "red"
|
||||
console.print(f" [{color}]●[/{color}] {f['name']}: {status}")
|
||||
except Exception as e:
|
||||
console.print(f"[red]✗[/red] {e}")
|
||||
@@ -3018,13 +3092,15 @@ def doctor(
|
||||
return {"name": name, "status": "fail", "detail": str(e)}
|
||||
|
||||
# Check 1: Server Health
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
def _health_extractor(data):
|
||||
status = data.get("status", "unknown")
|
||||
if status == "healthy":
|
||||
if is_success_status(status):
|
||||
return "pass", f"running on {server.split('://')[-1]}"
|
||||
return "warn", f"status: {status}"
|
||||
|
||||
checks.append(_check("Server Health", "/health", _health_extractor))
|
||||
checks.append(_check("Server Health", "/health/live", _health_extractor))
|
||||
|
||||
# Check 2: Provider Status
|
||||
def _provider_extractor(data):
|
||||
@@ -3035,11 +3111,14 @@ def doctor(
|
||||
|
||||
# Check 3: Gateway Status
|
||||
def _gateway_extractor(data):
|
||||
gw_type = data.get("type", "unknown")
|
||||
agents = data.get("agents", 0)
|
||||
return "pass", f"{gw_type} ({agents} agents)"
|
||||
status = data.get("status", "unknown")
|
||||
provider_type = data.get("provider", data.get("name", "unknown"))
|
||||
agents_list = data.get("agents", [])
|
||||
agent_count = len(agents_list) if isinstance(agents_list, list) else 0
|
||||
check_status = "pass" if status in ("ok", "healthy") else "warn"
|
||||
return check_status, f"{provider_type} ({agent_count} agents)"
|
||||
|
||||
checks.append(_check("Gateway Status", "/api/provider/health", _gateway_extractor))
|
||||
checks.append(_check("Gateway Status", "/api/provider", _gateway_extractor))
|
||||
|
||||
# Check 4: Features Loaded
|
||||
def _features_extractor(data):
|
||||
@@ -3099,21 +3178,23 @@ def doctor(
|
||||
console.print()
|
||||
|
||||
status_icons = {
|
||||
"pass": "[green]✅[/green]",
|
||||
"warn": "[yellow]⚠️[/yellow]",
|
||||
"fail": "[red]❌[/red]",
|
||||
"pass": f"[green]{_icon('✅', '[PASS]')}[/green]",
|
||||
"warn": f"[yellow]{_icon('⚠️', '[WARN]')}[/yellow]",
|
||||
"fail": f"[red]{_icon('❌', '[FAIL]')}[/red]",
|
||||
}
|
||||
|
||||
arrow = _icon("▶", ">")
|
||||
border = _icon("═", "=") * 43
|
||||
for i, check in enumerate(checks, 1):
|
||||
icon = status_icons.get(check["status"], "❓")
|
||||
console.print(f"▶ {i}. {check['name']:20} {icon} {check['detail']}")
|
||||
icon = status_icons.get(check["status"], _icon("❓", "[?]"))
|
||||
console.print(f"{arrow} {i}. {check['name']:20} {icon} {check['detail']}")
|
||||
|
||||
console.print()
|
||||
console.print("═" * 43)
|
||||
console.print(border)
|
||||
console.print(
|
||||
f" SUMMARY: [green]{passed} passed[/green], [yellow]{warnings} warning{'s' if warnings != 1 else ''}[/yellow], [red]{failed} failed[/red]"
|
||||
)
|
||||
console.print("═" * 43)
|
||||
console.print(border)
|
||||
|
||||
if failed > 0:
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@@ -333,7 +333,7 @@ class Compiler:
|
||||
"ariaLabels": self.config.a11y.aria_labels,
|
||||
}
|
||||
|
||||
# I18n configuration - emit for frontend consumption (experimental)
|
||||
# I18n configuration - emit for frontend consumption
|
||||
if self.config.i18n:
|
||||
result["i18n"] = {
|
||||
"defaultLocale": self.config.i18n.default_locale,
|
||||
@@ -422,7 +422,9 @@ class Compiler:
|
||||
|
||||
def _write_json(self, path: Path, data: dict, indent: int | None) -> None:
|
||||
"""Write JSON data to file."""
|
||||
path.write_text(json.dumps(data, indent=indent, ensure_ascii=False))
|
||||
path.write_text(
|
||||
json.dumps(data, indent=indent, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
|
||||
def _copy_viewer(self, output_dir: Path) -> None:
|
||||
"""Copy frontend bundle to output directory."""
|
||||
@@ -471,14 +473,14 @@ class Compiler:
|
||||
"""Patch anti-flicker script in index.html to respect darkMode."""
|
||||
theme = self.config.site.theme
|
||||
dark_mode = theme.dark_mode if theme else True
|
||||
html = html_path.read_text()
|
||||
html = html_path.read_text(encoding="utf-8")
|
||||
if not dark_mode:
|
||||
# Remove the dark class addition for light-mode sites
|
||||
html = html.replace(
|
||||
"document.documentElement.classList.add('dark');",
|
||||
"/* light mode — no dark class */",
|
||||
)
|
||||
html_path.write_text(html)
|
||||
html_path.write_text(html, encoding="utf-8")
|
||||
|
||||
def _copy_plugins(self, output_dir: Path, frontend_dir: Path) -> None:
|
||||
"""Copy frontend plugins and generate plugins.json config."""
|
||||
@@ -519,7 +521,9 @@ class Compiler:
|
||||
ordered.append(p)
|
||||
|
||||
plugins_config = {"plugins": ordered}
|
||||
(plugins_dst / "plugins.json").write_text(json.dumps(plugins_config, indent=2))
|
||||
(plugins_dst / "plugins.json").write_text(
|
||||
json.dumps(plugins_config, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
def _generate_route_pages(self, output_dir: Path, nav: dict) -> list[str]:
|
||||
"""Generate per-route HTML files for SPA fallback and SEO.
|
||||
@@ -538,7 +542,7 @@ class Compiler:
|
||||
if not template_path.exists():
|
||||
return []
|
||||
|
||||
template_html = template_path.read_text()
|
||||
template_html = template_path.read_text(encoding="utf-8")
|
||||
site_title = self.config.site.title
|
||||
site_desc = self.config.site.description or f"Documentation built with {site_title}"
|
||||
files: list[str] = []
|
||||
@@ -593,7 +597,7 @@ class Compiler:
|
||||
f'<div id="root"><noscript>{noscript_content}</noscript></div>',
|
||||
)
|
||||
|
||||
page_file.write_text(html)
|
||||
page_file.write_text(html, encoding="utf-8")
|
||||
files.append(f"{relative}/index.html")
|
||||
|
||||
return files
|
||||
@@ -677,7 +681,7 @@ class Compiler:
|
||||
md_content = None
|
||||
for candidate in md_candidates:
|
||||
if candidate.exists():
|
||||
md_content = candidate.read_text()
|
||||
md_content = candidate.read_text(encoding="utf-8")
|
||||
break
|
||||
|
||||
if not md_content:
|
||||
|
||||
@@ -222,7 +222,7 @@ def update_component_exports(frontend_path: Optional[Path] = None) -> bool:
|
||||
return False
|
||||
|
||||
# Read current exports
|
||||
current_content = index_path.read_text()
|
||||
current_content = index_path.read_text(encoding="utf-8")
|
||||
|
||||
# Check which components are missing from exports
|
||||
missing_exports = []
|
||||
|
||||
@@ -145,7 +145,7 @@ class YAMLConfigStore:
|
||||
try:
|
||||
import yaml
|
||||
|
||||
with open(self._path) as f:
|
||||
with open(self._path, encoding="utf-8") as f:
|
||||
loaded = yaml.safe_load(f) or {}
|
||||
# Merge with defaults so new keys are always present
|
||||
self._data = copy.deepcopy(_DEFAULT_CONFIG)
|
||||
@@ -171,7 +171,7 @@ class YAMLConfigStore:
|
||||
# Write to temp file in same directory, then atomic rename
|
||||
fd, tmp_path = tempfile.mkstemp(dir=str(self._path.parent), suffix=".yaml.tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
yaml.dump(
|
||||
self._data,
|
||||
f,
|
||||
@@ -200,7 +200,7 @@ class YAMLConfigStore:
|
||||
return
|
||||
|
||||
try:
|
||||
with open(json_path) as f:
|
||||
with open(json_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
agents = data.get("agents", {})
|
||||
if agents:
|
||||
|
||||
@@ -224,13 +224,15 @@ class ChannelsFeature(BaseFeatureProtocol):
|
||||
try:
|
||||
error = await self._start_channel_bot(channel_id, entry)
|
||||
if error:
|
||||
entry["running"] = False
|
||||
entry["start_error"] = error
|
||||
logger.warning("Auto-start '%s' failed: %s", channel_id, error)
|
||||
else:
|
||||
logger.info("Auto-started channel '%s' (%s)", channel_id, entry.get("platform"))
|
||||
except Exception as e:
|
||||
entry["running"] = False
|
||||
entry["start_error"] = str(e)
|
||||
logger.warning("Auto-start '%s' exception: %s", channel_id, e)
|
||||
logger.exception("Auto-start '%s' exception: %s", channel_id, e)
|
||||
self._sync_running_status()
|
||||
|
||||
# ── Gateway integration ──────────────────────────────────────────
|
||||
@@ -255,6 +257,10 @@ class ChannelsFeature(BaseFeatureProtocol):
|
||||
all_bots.setdefault(ch_id, info.get("bot"))
|
||||
|
||||
for ch_id, ch in _channels.items():
|
||||
if ch.get("start_error"):
|
||||
ch["running"] = False
|
||||
continue
|
||||
|
||||
bot = all_bots.get(ch_id)
|
||||
info = _live_bots.get(ch_id, {})
|
||||
task = info.get("task")
|
||||
@@ -411,12 +417,19 @@ class ChannelsFeature(BaseFeatureProtocol):
|
||||
|
||||
# Also register in gateway if available
|
||||
if gw is not None:
|
||||
channel_bots = getattr(gw, "_channel_bots", None)
|
||||
channel_tasks = getattr(gw, "_channel_tasks", None)
|
||||
if channel_bots is not None:
|
||||
channel_bots[channel_id] = bot
|
||||
if channel_tasks is not None:
|
||||
channel_tasks.append(task)
|
||||
try:
|
||||
channel_bots = getattr(gw, "_channel_bots", None)
|
||||
if isinstance(channel_bots, dict):
|
||||
channel_bots[channel_id] = bot
|
||||
channel_tasks = getattr(gw, "_channel_tasks", None)
|
||||
if isinstance(channel_tasks, dict):
|
||||
channel_tasks[channel_id] = task
|
||||
elif isinstance(channel_tasks, list):
|
||||
channel_tasks.append(task)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Gateway task registration failed for '%s': %s", channel_id, e
|
||||
)
|
||||
|
||||
entry["running"] = True
|
||||
entry.pop("start_error", None)
|
||||
|
||||
@@ -8,6 +8,7 @@ Architecture:
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@@ -19,6 +20,10 @@ from ._base import BaseFeatureProtocol
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Compile regex patterns once for performance
|
||||
LOCALE_PATTERN = re.compile(r'^[a-z]{2}(-[A-Z]{2})?$')
|
||||
KEY_PATTERN = re.compile(r'^[a-zA-Z0-9._-]+$')
|
||||
|
||||
|
||||
# ── I18n Protocol ────────────────────────────────────────────────────
|
||||
|
||||
@@ -200,6 +205,11 @@ class I18nFeature(BaseFeatureProtocol):
|
||||
async def _strings(self, request: Request) -> JSONResponse:
|
||||
mgr = get_i18n_manager()
|
||||
locale = request.path_params["locale"]
|
||||
|
||||
# Validate locale format to prevent injection
|
||||
if not LOCALE_PATTERN.match(locale):
|
||||
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
|
||||
|
||||
strings = mgr.get_strings(locale)
|
||||
if not strings:
|
||||
return JSONResponse({"error": f"Locale '{locale}' not found"}, status_code=404)
|
||||
@@ -208,10 +218,21 @@ class I18nFeature(BaseFeatureProtocol):
|
||||
async def _translate(self, request: Request) -> JSONResponse:
|
||||
mgr = get_i18n_manager()
|
||||
body = await request.json()
|
||||
text = mgr.t(
|
||||
body.get("key", ""), locale=body.get("locale", ""), **body.get("variables", {})
|
||||
)
|
||||
return JSONResponse({"key": body.get("key", ""), "text": text})
|
||||
|
||||
# Validate locale if provided
|
||||
locale = body.get("locale", "")
|
||||
if locale:
|
||||
if not LOCALE_PATTERN.match(locale):
|
||||
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
|
||||
|
||||
# Validate key format (alphanumeric with dots)
|
||||
key = body.get("key", "")
|
||||
if key:
|
||||
if not KEY_PATTERN.match(key):
|
||||
return JSONResponse({"error": f"Invalid key format: '{key}'"}, status_code=400)
|
||||
|
||||
text = mgr.t(key, locale=locale, **body.get("variables", {}))
|
||||
return JSONResponse({"key": key, "text": text})
|
||||
|
||||
async def _get_locale(self, request: Request) -> JSONResponse:
|
||||
mgr = get_i18n_manager()
|
||||
@@ -220,7 +241,13 @@ class I18nFeature(BaseFeatureProtocol):
|
||||
async def _set_locale(self, request: Request) -> JSONResponse:
|
||||
mgr = get_i18n_manager()
|
||||
body = await request.json()
|
||||
mgr.set_locale(body.get("locale", "en"))
|
||||
locale = body.get("locale", "en")
|
||||
|
||||
# Validate locale format to prevent injection
|
||||
if not LOCALE_PATTERN.match(locale):
|
||||
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
|
||||
|
||||
mgr.set_locale(locale)
|
||||
return JSONResponse({"locale": mgr.get_locale()})
|
||||
|
||||
|
||||
|
||||
@@ -46,10 +46,33 @@ def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _child_progress(task: Dict[str, Any]) -> Optional[Dict[str, int]]:
|
||||
"""Derive done/total subtask progress from task.children or meta.
|
||||
|
||||
Accepts either a list of child dicts under "children" (each with a status)
|
||||
or an explicit {"done": int, "total": int} under meta["progress"].
|
||||
Returns None when there is nothing to report.
|
||||
"""
|
||||
meta = task.get("meta") or {}
|
||||
explicit = meta.get("progress")
|
||||
if isinstance(explicit, dict):
|
||||
total = explicit.get("total")
|
||||
done = explicit.get("done")
|
||||
if isinstance(total, int) and total > 0 and isinstance(done, int):
|
||||
return {"done": max(0, min(done, total)), "total": total}
|
||||
children = task.get("children")
|
||||
if isinstance(children, list) and children:
|
||||
total = len(children)
|
||||
done = sum(1 for c in children if isinstance(c, dict) and c.get("status") == "done")
|
||||
return {"done": done, "total": total}
|
||||
return None
|
||||
|
||||
|
||||
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]
|
||||
comments = task.get("comments") or []
|
||||
return {
|
||||
"id": task.get("id"),
|
||||
"title": title,
|
||||
@@ -57,8 +80,12 @@ def _task_card(task: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"status": task.get("status"),
|
||||
"assignee": task.get("assignee"),
|
||||
"priority": task.get("priority"),
|
||||
"tenant": task.get("tenant"),
|
||||
"created_at": task.get("created_at"),
|
||||
"comment_count": len(comments),
|
||||
"progress": _child_progress(task),
|
||||
"body": task.get("body"),
|
||||
"comments": task.get("comments") or [],
|
||||
"comments": comments,
|
||||
"meta": task.get("meta") or {},
|
||||
}
|
||||
|
||||
@@ -90,6 +117,9 @@ class KanbanStoreProtocol(ABC):
|
||||
@abstractmethod
|
||||
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]: ...
|
||||
|
||||
@abstractmethod
|
||||
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]: ...
|
||||
|
||||
@abstractmethod
|
||||
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]: ...
|
||||
|
||||
@@ -174,6 +204,7 @@ class SimpleKanbanStore(KanbanStoreProtocol):
|
||||
"priority": data.get("priority"),
|
||||
"tenant": data.get("tenant"),
|
||||
"board": data.get("board", "default"),
|
||||
"children": data.get("children") or [],
|
||||
"comments": [],
|
||||
"meta": data.get("meta") or {},
|
||||
"created_at": _now_iso(),
|
||||
@@ -188,7 +219,7 @@ class SimpleKanbanStore(KanbanStoreProtocol):
|
||||
task = self._tasks.get(task_id)
|
||||
if not task:
|
||||
return None
|
||||
for key in ("title", "body", "assignee", "priority", "tenant", "meta"):
|
||||
for key in ("title", "body", "assignee", "priority", "tenant", "meta", "children"):
|
||||
if key in data:
|
||||
task[key] = data[key]
|
||||
if "status" in data and data["status"] in VALID_STATUSES:
|
||||
@@ -209,6 +240,24 @@ class SimpleKanbanStore(KanbanStoreProtocol):
|
||||
self._emit("task_moved", task, {"from_status": old, "to_status": status})
|
||||
return dict(task)
|
||||
|
||||
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
task = self._tasks.get(task_id)
|
||||
if not task:
|
||||
return None
|
||||
text = str((comment or {}).get("text") or "").strip()
|
||||
if not text:
|
||||
return None
|
||||
entry = {
|
||||
"id": f"c_{uuid.uuid4().hex[:8]}",
|
||||
"author": (comment or {}).get("author") or "human",
|
||||
"text": text,
|
||||
"created_at": _now_iso(),
|
||||
}
|
||||
task.setdefault("comments", []).append(entry)
|
||||
task["updated_at"] = _now_iso()
|
||||
self._emit("task_commented", task, {"comment_id": entry["id"]})
|
||||
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"]}
|
||||
@@ -264,6 +313,9 @@ class InjectedKanbanStore(KanbanStoreProtocol):
|
||||
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]:
|
||||
return self._call("move_task", task_id, status)
|
||||
|
||||
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return self._call("add_comment", task_id, comment)
|
||||
|
||||
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]:
|
||||
return self._call("bulk_update", task_ids, status)
|
||||
|
||||
@@ -331,6 +383,7 @@ class KanbanFeature(BaseFeatureProtocol):
|
||||
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/tasks/{task_id}/comments", self._add_comment, 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"]),
|
||||
@@ -380,6 +433,16 @@ class KanbanFeature(BaseFeatureProtocol):
|
||||
return JSONResponse({"error": "not found or invalid status"}, status_code=404)
|
||||
return JSONResponse(task)
|
||||
|
||||
async def _add_comment(self, request: Request) -> JSONResponse:
|
||||
store = get_kanban_store()
|
||||
body = await request.json()
|
||||
if not isinstance(body, dict) or not str(body.get("text") or "").strip():
|
||||
return JSONResponse({"error": "text required"}, status_code=400)
|
||||
task = store.add_comment(request.path_params["task_id"], body)
|
||||
if not task:
|
||||
return JSONResponse({"error": "not found"}, status_code=404)
|
||||
return JSONResponse(task)
|
||||
|
||||
async def _bulk(self, request: Request) -> JSONResponse:
|
||||
store = get_kanban_store()
|
||||
body = await request.json()
|
||||
|
||||
@@ -413,7 +413,7 @@ class SDKMemoryManager(MemoryProtocol):
|
||||
if json_file.name == "config.json":
|
||||
continue
|
||||
try:
|
||||
data = json.loads(json_file.read_text())
|
||||
data = json.loads(json_file.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, list):
|
||||
continue
|
||||
# Determine type from filename
|
||||
|
||||
@@ -278,7 +278,7 @@ def _save_data() -> None:
|
||||
"saved_at": time.time(),
|
||||
}
|
||||
_data_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(_data_file, "w") as f:
|
||||
with open(_data_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f)
|
||||
except Exception:
|
||||
pass # Silently fail
|
||||
@@ -290,7 +290,7 @@ def _load_data() -> None:
|
||||
if not _data_file or not _data_file.exists():
|
||||
return
|
||||
try:
|
||||
with open(_data_file) as f:
|
||||
with open(_data_file, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
_aggregates.update(data.get("aggregates", {}))
|
||||
for record in data.get("records", []):
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Health status utilities for consistent health checking across AIUI."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
SUCCESS_STATUSES = frozenset({"ok", "healthy"})
|
||||
|
||||
def is_success_status(status: Optional[str]) -> bool:
|
||||
"""Check if a health status indicates success.
|
||||
|
||||
Args:
|
||||
status: The health status string to check
|
||||
|
||||
Returns:
|
||||
True if status is 'ok' or 'healthy', False otherwise
|
||||
"""
|
||||
return (status or "") in SUCCESS_STATUSES
|
||||
@@ -299,7 +299,7 @@ def create_gateway_from_yaml(
|
||||
if not os.path.exists(config_path):
|
||||
raise FileNotFoundError(f"Config not found: {config_path}")
|
||||
|
||||
with open(config_path) as f:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# Extract gateway settings
|
||||
|
||||
@@ -144,6 +144,75 @@ def _stream_event_to_run_event(stream_event) -> Optional[RunEvent]:
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Approval helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Module-level cache for approval manager
|
||||
_approval_manager_cache = None
|
||||
_approval_manager_load_attempted = False
|
||||
|
||||
def _get_approval_manager_cached():
|
||||
"""Lazy load and cache the approval manager."""
|
||||
global _approval_manager_cache, _approval_manager_load_attempted
|
||||
if not _approval_manager_load_attempted:
|
||||
try:
|
||||
from praisonaiui.features.approvals import get_approval_manager
|
||||
_approval_manager_cache = get_approval_manager()
|
||||
except ImportError:
|
||||
_approval_manager_cache = None
|
||||
_approval_manager_load_attempted = True
|
||||
return _approval_manager_cache
|
||||
|
||||
# High-risk tools that should require approval
|
||||
HIGH_RISK_TOOLS = {
|
||||
"execute_command", "run_shell", "bash", "shell", "cmd",
|
||||
"write_file", "delete_file", "modify_file",
|
||||
"send_email", "make_api_call", "http_request",
|
||||
"database_query", "sql_execute",
|
||||
}
|
||||
|
||||
def _should_check_approval(tool_name: str) -> bool:
|
||||
"""Check if a tool requires approval before execution."""
|
||||
if not tool_name:
|
||||
return False
|
||||
# Check if tool is in high-risk list
|
||||
tool_lower = tool_name.lower()
|
||||
return any(risk in tool_lower for risk in HIGH_RISK_TOOLS)
|
||||
|
||||
def _get_tool_risk_level(tool_name: str) -> str:
|
||||
"""Determine risk level of a tool."""
|
||||
if not tool_name:
|
||||
return "medium"
|
||||
tool_lower = tool_name.lower()
|
||||
if any(word in tool_lower for word in ["delete", "remove", "drop", "destroy"]):
|
||||
return "critical"
|
||||
if any(word in tool_lower for word in ["write", "modify", "execute", "send", "sql"]):
|
||||
return "high"
|
||||
if any(word in tool_lower for word in ["read", "get", "list", "search"]):
|
||||
return "low"
|
||||
return "medium"
|
||||
|
||||
def _request_tool_approval(tool_name: str, arguments: Any, agent_name: str, risk_level: str) -> Optional[str]:
|
||||
"""Request approval for tool execution via the approval feature."""
|
||||
try:
|
||||
# Import is at module level via _get_approval_manager_cached
|
||||
mgr = _get_approval_manager_cached()
|
||||
if mgr is None:
|
||||
return None
|
||||
|
||||
entry = mgr.request_approval({
|
||||
"tool_name": tool_name,
|
||||
"arguments": arguments,
|
||||
"agent_name": agent_name,
|
||||
"risk_level": risk_level,
|
||||
"description": f"Tool {tool_name} requires approval for execution",
|
||||
})
|
||||
return entry.get("id")
|
||||
except Exception as e:
|
||||
logger.debug("Failed to request approval: %s", e)
|
||||
return None
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hook → RunEvent bridge
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -154,11 +223,40 @@ def _hook_event_to_run_events(hook_event_name: str, event_data) -> List[RunEvent
|
||||
events = []
|
||||
|
||||
if hook_event_name == "before_tool":
|
||||
tool_name = getattr(event_data, "tool_name", None)
|
||||
args = getattr(event_data, "arguments", None)
|
||||
|
||||
# Check if this tool requires approval
|
||||
should_check_approval = _should_check_approval(tool_name)
|
||||
if should_check_approval:
|
||||
# Create approval request
|
||||
approval_id = _request_tool_approval(
|
||||
tool_name=tool_name,
|
||||
arguments=args,
|
||||
agent_name=getattr(event_data, "agent_name", None),
|
||||
risk_level=_get_tool_risk_level(tool_name),
|
||||
)
|
||||
if approval_id:
|
||||
# Emit approval required event - using RUN_PAUSED as closest match
|
||||
# The extra_data contains the approval info for frontend handling
|
||||
events.append(
|
||||
RunEvent(
|
||||
type=RunEventType.RUN_PAUSED,
|
||||
extra_data={
|
||||
"type": "approval_required",
|
||||
"id": approval_id,
|
||||
"tool_name": tool_name,
|
||||
"arguments": args,
|
||||
"risk_level": _get_tool_risk_level(tool_name),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
events.append(
|
||||
RunEvent(
|
||||
type=RunEventType.TOOL_CALL_STARTED,
|
||||
name=getattr(event_data, "tool_name", None),
|
||||
args=getattr(event_data, "arguments", None),
|
||||
name=tool_name,
|
||||
args=args,
|
||||
)
|
||||
)
|
||||
elif hook_event_name == "after_tool":
|
||||
|
||||
@@ -59,8 +59,8 @@ class ConfigFeatureRegistry:
|
||||
"SEO configuration with title templates, OG tags, Twitter meta - basic implementation"),
|
||||
"a11y": FeatureInfo("a11y", FeatureStatus.IMPLEMENTED, "0.5.0",
|
||||
"Accessibility configuration - basic implementation for skip links and ARIA labels"),
|
||||
"i18n": FeatureInfo("i18n", FeatureStatus.EXPERIMENTAL, None,
|
||||
"Internationalization accepted in schema but content pipeline/routing not implemented"),
|
||||
"i18n": FeatureInfo("i18n", FeatureStatus.IMPLEMENTED, "0.5.0",
|
||||
"Internationalization configuration with multi-language support"),
|
||||
}
|
||||
|
||||
def get_feature(self, name: str) -> FeatureInfo | None:
|
||||
|
||||
+250
-24
@@ -825,8 +825,17 @@ async def _plugins_config(request: Request) -> JSONResponse:
|
||||
return JSONResponse({"plugins": plugins})
|
||||
|
||||
|
||||
async def _serve_index(request: Request) -> HTMLResponse:
|
||||
"""Serve dynamically generated HTML based on active style."""
|
||||
async def _serve_index(request: Request) -> Response:
|
||||
"""Serve dynamically generated HTML based on active style.
|
||||
|
||||
Unmatched ``/api/*`` paths return a JSON 404 instead of the SPA shell so
|
||||
API clients never receive HTML with a 200 status.
|
||||
"""
|
||||
if request.url.path.startswith("/api/"):
|
||||
return JSONResponse(
|
||||
{"error": "Not Found", "path": request.url.path},
|
||||
status_code=404,
|
||||
)
|
||||
style = _effective_style # Single source of truth (set by create_app)
|
||||
return HTMLResponse(_build_html(style))
|
||||
|
||||
@@ -938,37 +947,82 @@ def register_page_action(page_id: str):
|
||||
return decorator
|
||||
|
||||
|
||||
_DEEP_HEALTH_CACHE: Dict[str, Any] = {}
|
||||
_DEEP_HEALTH_CACHE_TIME: float = 0.0
|
||||
_DEEP_HEALTH_TTL: float = 5.0
|
||||
|
||||
|
||||
async def health(request: Request) -> JSONResponse:
|
||||
"""Health check endpoint."""
|
||||
"""Health check endpoint - deep health with feature checks.
|
||||
|
||||
The provider check and every feature check run concurrently under a single
|
||||
overall deadline so neither the provider nor a slow feature can serialise
|
||||
the response. A short-TTL cache absorbs repeated probes. Pass ?deep=false
|
||||
for an immediate liveness response. Use /health/live for the fastest
|
||||
liveness probe or /health/ready for the readiness probe.
|
||||
"""
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
if request.query_params.get("deep", "true").lower() == "false":
|
||||
return await health_live(request)
|
||||
|
||||
global _DEEP_HEALTH_CACHE, _DEEP_HEALTH_CACHE_TIME
|
||||
|
||||
now = time.monotonic()
|
||||
if _DEEP_HEALTH_CACHE and (now - _DEEP_HEALTH_CACHE_TIME) < _DEEP_HEALTH_TTL:
|
||||
cached = dict(_DEEP_HEALTH_CACHE)
|
||||
cached["timestamp"] = datetime.utcnow().isoformat()
|
||||
cached["cached"] = True
|
||||
return JSONResponse(cached)
|
||||
|
||||
provider = get_provider()
|
||||
provider_info = {"name": type(provider).__name__}
|
||||
try:
|
||||
provider_health = await provider.health()
|
||||
provider_info.update(provider_health)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _safe_provider() -> Dict[str, Any]:
|
||||
try:
|
||||
return await asyncio.wait_for(provider.health(), timeout=0.5)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
sdk_gaps: list = []
|
||||
integrated_flag: Optional[bool] = None
|
||||
|
||||
async def _safe_health(name: str, feat) -> tuple[str, Dict[str, Any]]:
|
||||
try:
|
||||
result = await asyncio.wait_for(feat.health(), timeout=0.5)
|
||||
return (name, result)
|
||||
except asyncio.TimeoutError:
|
||||
return (name, {"healthy": False, "detail": "timeout"})
|
||||
except Exception as e:
|
||||
return (name, {"healthy": False, "detail": str(e)})
|
||||
|
||||
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
|
||||
features = get_features()
|
||||
feature_tasks = [_safe_health(name, feat) for name, feat in features.items()]
|
||||
|
||||
provider_result, *feature_results = await asyncio.gather(
|
||||
_safe_provider(), *feature_tasks
|
||||
)
|
||||
provider_info.update(provider_result)
|
||||
|
||||
for name, fh in feature_results:
|
||||
if fh.get("sdk_gap"):
|
||||
sdk_gaps.append(
|
||||
{
|
||||
"feature": name,
|
||||
"message": fh.get("sdk_gap_message", "SDK gap"),
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
provider_info.update(await _safe_provider())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
payload: Dict[str, Any] = {
|
||||
"status": "ok",
|
||||
@@ -979,9 +1033,126 @@ async def health(request: Request) -> JSONResponse:
|
||||
payload["sdk_gaps"] = sdk_gaps
|
||||
if integrated_flag is not None:
|
||||
payload["integrated"] = integrated_flag
|
||||
|
||||
_DEEP_HEALTH_CACHE = payload
|
||||
_DEEP_HEALTH_CACHE_TIME = time.monotonic()
|
||||
return JSONResponse(payload)
|
||||
|
||||
|
||||
async def health_live(request: Request) -> JSONResponse:
|
||||
"""Fast liveness check - confirms server is running.
|
||||
|
||||
Returns immediately with minimal checks. Use for load balancer health probes.
|
||||
Target: <500ms response time.
|
||||
"""
|
||||
return JSONResponse({
|
||||
"status": "ok",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
|
||||
async def health_ready(request: Request) -> JSONResponse:
|
||||
"""Readiness check with parallel feature health checks.
|
||||
|
||||
Performs health checks on all features in parallel with timeout.
|
||||
Use ?deep=false for basic readiness without feature checks.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
# Check for deep query parameter
|
||||
deep = request.query_params.get("deep", "true").lower() != "false"
|
||||
|
||||
provider = get_provider()
|
||||
provider_info = {"name": type(provider).__name__}
|
||||
try:
|
||||
provider_health = await provider.health()
|
||||
provider_info.update(provider_health)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
features_health = {}
|
||||
sdk_gaps: list = []
|
||||
integrated_flag: Optional[bool] = None
|
||||
|
||||
if deep:
|
||||
try:
|
||||
from praisonaiui.backends import is_integrated_mode
|
||||
from praisonaiui.features import get_features
|
||||
|
||||
integrated_flag = is_integrated_mode()
|
||||
|
||||
async def _safe_health(name: str, feat) -> tuple[str, Dict[str, Any]]:
|
||||
"""Wrapper to safely call feature health with timeout."""
|
||||
try:
|
||||
result = await asyncio.wait_for(feat.health(), timeout=0.5)
|
||||
return (name, result)
|
||||
except asyncio.TimeoutError:
|
||||
return (name, {"healthy": False, "detail": "timeout"})
|
||||
except Exception as e:
|
||||
return (name, {"healthy": False, "detail": str(e)})
|
||||
|
||||
# Run all feature health checks in parallel
|
||||
features = get_features()
|
||||
health_tasks = [_safe_health(name, feat) for name, feat in features.items()]
|
||||
results = await asyncio.gather(*health_tasks)
|
||||
|
||||
# Process results
|
||||
for name, health_data in results:
|
||||
features_health[name] = health_data
|
||||
if health_data.get("sdk_gap"):
|
||||
sdk_gaps.append({
|
||||
"feature": name,
|
||||
"message": health_data.get("sdk_gap_message", "SDK gap"),
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
payload: Dict[str, Any] = {
|
||||
"status": "ok",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"provider": provider_info,
|
||||
}
|
||||
|
||||
if deep and features_health:
|
||||
payload["features"] = features_health
|
||||
|
||||
if sdk_gaps:
|
||||
payload["sdk_gaps"] = sdk_gaps
|
||||
|
||||
if integrated_flag is not None:
|
||||
payload["integrated"] = integrated_flag
|
||||
|
||||
return JSONResponse(payload)
|
||||
|
||||
|
||||
# Health cache for dashboard polling
|
||||
_health_cache: Dict[str, Any] = {}
|
||||
_health_cache_time: float = 0
|
||||
|
||||
|
||||
async def health_cached(request: Request) -> JSONResponse:
|
||||
"""Cached health endpoint for dashboard - 30 second TTL.
|
||||
|
||||
Returns cached deep health status to avoid repeated expensive checks.
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
|
||||
global _health_cache, _health_cache_time
|
||||
|
||||
current_time = time.time()
|
||||
if _health_cache and (current_time - _health_cache_time) < 30:
|
||||
return JSONResponse(_health_cache)
|
||||
|
||||
# Refresh cache by calling health_ready
|
||||
response = await health_ready(request)
|
||||
# Extract JSON content from response
|
||||
_health_cache = json.loads(response.body.decode())
|
||||
_health_cache_time = current_time
|
||||
|
||||
return JSONResponse(_health_cache)
|
||||
|
||||
|
||||
async def list_agents(request: Request) -> JSONResponse:
|
||||
"""List all registered agents (merges registry + provider).
|
||||
|
||||
@@ -1322,7 +1493,7 @@ async def api_config_handler(request: Request) -> JSONResponse:
|
||||
config_data = body.get("config", body)
|
||||
import yaml
|
||||
|
||||
with open(_config_path, "w") as f:
|
||||
with open(_config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
|
||||
_config_cache = config_data
|
||||
return JSONResponse({"status": "saved", "config_path": str(_config_path)})
|
||||
@@ -1409,6 +1580,34 @@ async def api_provider(request: Request) -> JSONResponse:
|
||||
return JSONResponse(info)
|
||||
|
||||
|
||||
async def api_provider_health(request: Request) -> JSONResponse:
|
||||
"""Return JSON health information for the active provider gateway."""
|
||||
provider = get_provider()
|
||||
try:
|
||||
health = await provider.health()
|
||||
except Exception as exc:
|
||||
health = {"status": "error", "detail": str(exc)}
|
||||
agents: list = []
|
||||
try:
|
||||
listed = await provider.list_agents()
|
||||
if isinstance(listed, list):
|
||||
agents = listed
|
||||
except Exception:
|
||||
agents = []
|
||||
status = health.get("status", "unknown")
|
||||
detail = health.get("detail") or (
|
||||
"ok" if status in ("ok", "healthy") else str(health)
|
||||
)
|
||||
body = {
|
||||
**health,
|
||||
"type": health.get("provider", type(provider).__name__),
|
||||
"status": status,
|
||||
"agents": len(agents),
|
||||
"detail": detail,
|
||||
}
|
||||
return JSONResponse(body)
|
||||
|
||||
|
||||
async def api_action_click(request: Request) -> JSONResponse:
|
||||
"""Handle action button clicks and dispatch to registered callbacks.
|
||||
|
||||
@@ -2502,7 +2701,7 @@ def load_config_from_yaml(config_path: Path) -> Optional[dict]:
|
||||
try:
|
||||
import yaml
|
||||
|
||||
with open(config_path) as f:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
except Exception:
|
||||
return None
|
||||
@@ -2583,6 +2782,10 @@ class AuthEnforcementMiddleware:
|
||||
EXEMPT_PATHS = {
|
||||
"/health",
|
||||
"/api/health",
|
||||
"/health/live",
|
||||
"/health/ready",
|
||||
"/api/health/live",
|
||||
"/api/health/ready",
|
||||
"/api/auth/login",
|
||||
"/login",
|
||||
"/api/protocol",
|
||||
@@ -2859,7 +3062,11 @@ def create_app(
|
||||
|
||||
routes = [
|
||||
Route("/health", health, methods=["GET"]),
|
||||
Route("/api/health", health, methods=["GET"]),
|
||||
Route("/api/health", health_cached, methods=["GET"]),
|
||||
Route("/health/live", health_live, methods=["GET"]),
|
||||
Route("/health/ready", health_ready, methods=["GET"]),
|
||||
Route("/api/health/live", health_live, methods=["GET"]),
|
||||
Route("/api/health/ready", health_ready, methods=["GET"]),
|
||||
Route("/login", login_handler, methods=["POST"]),
|
||||
Route("/register", register_handler, methods=["POST"]),
|
||||
Route("/logout", logout_handler, methods=["POST"]),
|
||||
@@ -2900,6 +3107,7 @@ def create_app(
|
||||
# Note: /api/usage is now provided by PraisonAIUsage feature
|
||||
Route("/api/debug", api_debug, methods=["GET"]),
|
||||
Route("/api/provider", api_provider, methods=["GET"]),
|
||||
Route("/api/provider/health", api_provider_health, methods=["GET"]),
|
||||
# Action callback API
|
||||
Route("/api/actions/{action_id}/click", api_action_click, methods=["POST"]),
|
||||
# Page registry protocol
|
||||
@@ -3188,6 +3396,14 @@ def create_app(
|
||||
"description": "Execution nodes & presence",
|
||||
"order": 15,
|
||||
},
|
||||
{
|
||||
"id": "mcp",
|
||||
"title": "MCP",
|
||||
"icon": "🔌",
|
||||
"group": "Agent",
|
||||
"description": "Model Context Protocol servers",
|
||||
"order": 30,
|
||||
},
|
||||
{
|
||||
"id": "config",
|
||||
"title": "Config",
|
||||
@@ -3282,6 +3498,7 @@ def create_app(
|
||||
"sessions": "/sessions",
|
||||
"cron": "/api/schedules",
|
||||
"channels": "/api/channels",
|
||||
"mcp": "/api/mcp/servers",
|
||||
}
|
||||
|
||||
# Read page whitelist from config.yaml if set_pages() was not called
|
||||
@@ -3373,6 +3590,15 @@ def create_app(
|
||||
response_status = 404
|
||||
|
||||
if response_status == 404:
|
||||
request_path = scope.get("path", "")
|
||||
if request_path.startswith("/api/"):
|
||||
# Never serve the SPA HTML shell for unmatched API paths.
|
||||
response = JSONResponse(
|
||||
{"error": "Not Found", "path": request_path},
|
||||
status_code=404,
|
||||
)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
# SPA fallback — serve the generated index page
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
var b;(function(s){s.assertEqual=n=>{};function e(n){}s.assertIs=e;function t(n){throw new Error}s.assertNever=t,s.arrayToEnum=n=>{const i={};for(const a of n)i[a]=a;return i},s.getValidEnumValues=n=>{const i=s.objectKeys(n).filter(o=>typeof n[n[o]]!="number"),a={};for(const o of i)a[o]=n[o];return s.objectValues(a)},s.objectValues=n=>s.objectKeys(n).map(function(i){return n[i]}),s.objectKeys=typeof Object.keys=="function"?n=>Object.keys(n):n=>{const i=[];for(const a in n)Object.prototype.hasOwnProperty.call(n,a)&&i.push(a);return i},s.find=(n,i)=>{for(const a of n)if(i(a))return a},s.isInteger=typeof Number.isInteger=="function"?n=>Number.isInteger(n):n=>typeof n=="number"&&Number.isFinite(n)&&Math.floor(n)===n;function r(n,i=" | "){return n.map(a=>typeof a=="string"?`'${a}'`:a).join(i)}s.joinValues=r,s.jsonStringifyReplacer=(n,i)=>typeof i=="bigint"?i.toString():i})(b||(b={}));var Ie;(function(s){s.mergeShapes=(e,t)=>({...e,...t})})(Ie||(Ie={}));const f=b.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),I=s=>{switch(typeof s){case"undefined":return f.undefined;case"string":return f.string;case"number":return Number.isNaN(s)?f.nan:f.number;case"boolean":return f.boolean;case"function":return f.function;case"bigint":return f.bigint;case"symbol":return f.symbol;case"object":return Array.isArray(s)?f.array:s===null?f.null:s.then&&typeof s.then=="function"&&s.catch&&typeof s.catch=="function"?f.promise:typeof Map<"u"&&s instanceof Map?f.map:typeof Set<"u"&&s instanceof Set?f.set:typeof Date<"u"&&s instanceof Date?f.date:f.object;default:return f.unknown}},c=b.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]);class T extends Error{get errors(){return this.issues}constructor(e){super(),this.issues=[],this.addIssue=r=>{this.issues=[...this.issues,r]},this.addIssues=(r=[])=>{this.issues=[...this.issues,...r]};const t=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,t):this.__proto__=t,this.name="ZodError",this.issues=e}format(e){const t=e||function(i){return i.message},r={_errors:[]},n=i=>{for(const a of i.issues)if(a.code==="invalid_union")a.unionErrors.map(n);else if(a.code==="invalid_return_type")n(a.returnTypeError);else if(a.code==="invalid_arguments")n(a.argumentsError);else if(a.path.length===0)r._errors.push(t(a));else{let o=r,l=0;for(;l<a.path.length;){const d=a.path[l];l===a.path.length-1?(o[d]=o[d]||{_errors:[]},o[d]._errors.push(t(a))):o[d]=o[d]||{_errors:[]},o=o[d],l++}}};return n(this),r}static assert(e){if(!(e instanceof T))throw new Error(`Not a ZodError: ${e}`)}toString(){return this.message}get message(){return JSON.stringify(this.issues,b.jsonStringifyReplacer,2)}get isEmpty(){return this.issues.length===0}flatten(e=t=>t.message){const t={},r=[];for(const n of this.issues)if(n.path.length>0){const i=n.path[0];t[i]=t[i]||[],t[i].push(e(n))}else r.push(e(n));return{formErrors:r,fieldErrors:t}}get formErrors(){return this.flatten()}}T.create=s=>new T(s);const _e=(s,e)=>{let t;switch(s.code){case c.invalid_type:s.received===f.undefined?t="Required":t=`Expected ${s.expected}, received ${s.received}`;break;case c.invalid_literal:t=`Invalid literal value, expected ${JSON.stringify(s.expected,b.jsonStringifyReplacer)}`;break;case c.unrecognized_keys:t=`Unrecognized key(s) in object: ${b.joinValues(s.keys,", ")}`;break;case c.invalid_union:t="Invalid input";break;case c.invalid_union_discriminator:t=`Invalid discriminator value. Expected ${b.joinValues(s.options)}`;break;case c.invalid_enum_value:t=`Invalid enum value. Expected ${b.joinValues(s.options)}, received '${s.received}'`;break;case c.invalid_arguments:t="Invalid function arguments";break;case c.invalid_return_type:t="Invalid function return type";break;case c.invalid_date:t="Invalid date";break;case c.invalid_string:typeof s.validation=="object"?"includes"in s.validation?(t=`Invalid input: must include "${s.validation.includes}"`,typeof s.validation.position=="number"&&(t=`${t} at one or more positions greater than or equal to ${s.validation.position}`)):"startsWith"in s.validation?t=`Invalid input: must start with "${s.validation.startsWith}"`:"endsWith"in s.validation?t=`Invalid input: must end with "${s.validation.endsWith}"`:b.assertNever(s.validation):s.validation!=="regex"?t=`Invalid ${s.validation}`:t="Invalid";break;case c.too_small:s.type==="array"?t=`Array must contain ${s.exact?"exactly":s.inclusive?"at least":"more than"} ${s.minimum} element(s)`:s.type==="string"?t=`String must contain ${s.exact?"exactly":s.inclusive?"at least":"over"} ${s.minimum} character(s)`:s.type==="number"?t=`Number must be ${s.exact?"exactly equal to ":s.iLine truncated
|
||||
@@ -1 +1 @@
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-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
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-500:oklch(60.6% .25 292.717);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--contLine truncated
|
||||
@@ -1,60 +1,61 @@
|
||||
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
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index2.js","assets/common-types.js","assets/index3.js"])))=>i.map(i=>d[i]);
|
||||
function j1(e,t){for(var r=0;r<t.length;r++){const a=t[r];if(typeof a!="string"&&!Array.isArray(a)){for(const o in a)if(o!=="default"&&!(o in e)){const s=Object.getOwnPropertyDescriptor(a,o);s&&Object.defineProperty(e,o,s.get?s:{enumerable:!0,get:()=>a[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const u of s.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&a(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(o){if(o.ep)return;o.ep=!0;const s=r(o);fetch(o.href,s)}})();function Qu(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var of={exports:{}},al={};var LT;function U1(){if(LT)return al;LT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,o,s){var u=null;if(s!==void 0&&(u=""+s),o.key!==void 0&&(u=""+o.key),"key"in o){s={};for(var d in o)d!=="key"&&(s[d]=o[d])}else s=o;return o=s.ref,{$$typeof:e,type:a,key:u,ref:o!==void 0?o:null,props:s}}return al.Fragment=t,al.jsx=r,al.jsxs=r,al}var DT;function F1(){return DT||(DT=1,of.exports=U1()),of.exports}var g=F1(),lf={exports:{}},Ne={};var MT;function B1(){if(MT)return Ne;MT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function E(U){return U===null||typeof U!="object"?null:(U=v&&U[v]||U["@@iterator"],typeof U=="function"?U:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},T=Object.assign,k={};function A(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}A.prototype.isReactComponent={},A.prototype.setState=function(U,J){if(typeof U!="object"&&typeof U!="function"&&U!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,U,J,"setState")},A.prototype.forceUpdate=function(U){this.updater.enqueueForceUpdate(this,U,"forceUpdate")};function _(){}_.prototype=A.prototype;function R(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}var L=R.prototype=new _;L.constructor=R,T(L,A.prototype),L.isPureReactComponent=!0;var j=Array.isArray;function D(){}var z={H:null,A:null,T:null,S:null},F=Object.prototype.hasOwnProperty;function V(U,J,I){var ue=I.ref;return{$$typeof:e,type:U,key:J,ref:ue!==void 0?ue:null,props:I}}function M(U,J){return V(U.type,J,U.props)}function H(U){return typeof U=="object"&&U!==null&&U.$$typeof===e}function K(U){var J={"=":"=0",":":"=2"};return"$"+U.replace(/[=:]/g,function(I){return J[I]})}var Z=/\/+/g;function te(U,J){return typeof U=="object"&&U!==null&&U.key!=null?K(""+U.key):J.toString(36)}function Q(U){switch(U.status){case"fulfilled":return U.value;case"rejected":throw U.reason;default:switch(typeof U.status=="string"?U.then(D,D):(U.status="pending",U.then(function(J){U.status==="pending"&&(U.status="fulfilled",U.value=J)},function(J){U.status==="pending"&&(U.status="rejected",U.reason=J)})),U.status){case"fulfilled":return U.value;case"rejected":throw U.reason}}throw U}function P(U,J,I,ue,fe){var de=typeof U;(de==="undefined"||de==="boolean")&&(U=null);var le=!1;if(U===null)le=!0;else switch(de){case"bigint":case"string":case"number":le=!0;break;case"object":switch(U.$$typeof){case e:case t:le=!0;break;case h:return le=U._init,P(le(U._payload),J,I,ue,fe)}}if(le)return fe=fe(U),le=ue===""?"."+te(U,0):ue,j(fe)?(I="",le!=null&&(I=le.replace(Z,"$&/")+"/"),P(fe,J,I,"",function(Re){return Re})):fe!=null&&(H(fe)&&(fe=M(fe,I+(fe.key==null||U&&U.key===fe.key?"":(""+fe.key).replace(Z,"$&/")+"/")+le)),J.push(fe)),1;le=0;var we=ue===""?".":ue+":";if(j(U))for(var me=0;me<U.length;me++)ue=U[me],de=we+te(ue,me),le+=P(ue,J,I,de,fe);else if(me=E(U),typeof me=="function")for(U=me.call(U),me=0;!(ue=U.next()).done;)ue=ue.value,de=we+te(ue,me++),le+=P(ue,J,I,de,fe);else if(de==="object"){if(typeof U.then=="function")return P(Q(U),J,I,ue,fe);throw J=String(U),Error("Objects are not valid as a React child (found: "+(J==="[object Object]"?"object with keys {"+Object.keys(U).join(", ")+"}":J)+"). If you meant to render a collection of children, use an arLine truncated
|
||||
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`
|
||||
`+qe+n+dt}var qn=!1;function Yn(n,i){if(!n||qn)return"";qn=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var c={DetermineComponentFrameRoot:function(){try{if(i){var oe=function(){throw Error()};if(Object.defineProperty(oe.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(oe,[])}catch(ee){var X=ee}Reflect.construct(n,[],oe)}else{try{oe.call()}catch(ee){X=ee}n.call(oe.prototype)}}else{try{throw Error()}catch(ee){X=ee}(oe=n())&&typeof oe.catch=="function"&&oe.catch(function(){})}}catch(ee){if(ee&&X&&typeof ee.stack=="string")return[ee.stack,X.stack]}return[null,null]}};c.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var m=Object.getOwnPropertyDescriptor(c.DetermineComponentFrameRoot,"name");m&&m.configurable&&Object.defineProperty(c.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var y=c.DetermineComponentFrameRoot(),x=y[0],N=y[1];if(x&&N){var B=x.split(`
|
||||
`),Y=N.split(`
|
||||
`);for(m=c=0;c<B.length&&!B[c].includes("DetermineComponentFrameRoot");)c++;for(;m<Y.length&&!Y[m].includes("DetermineComponentFrameRoot");)m++;if(c===B.length||m===Y.length)for(c=B.length-1,m=Y.length-1;1<=c&&0<=m&&B[c]!==Y[m];)m--;for(;1<=c&&0<=m;c--,m--)if(B[c]!==Y[m]){if(c!==1||m!==1)do if(c--,m--,0>m||B[c]!==Y[m]){var re=`
|
||||
`+B[c].replace(" at new "," at ");return n.displayName&&re.includes("<anonymous>")&&(re=re.replace("<anonymous>",n.displayName)),re}while(1<=c&&0<=m);break}}}finally{qn=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?st(l):""}function Cr(n,i){switch(n.tag){case 26:case 27:case 5:return st(n.type);case 16:return st("Lazy");case 13:return n.child!==i&&i!==null?st("Suspense Fallback"):st("Suspense");case 19:return st("SuspenseList");case 0:case 15:return Yn(n.type,!1);case 11:return Yn(n.type.render,!1);case 1:return Yn(n.type,!0);case 31:return st("Activity");default:return""}}function ql(n){try{var i="",l=null;do i+=Cr(n,l),l=n,n=n.return;while(n);return i}catch(c){return`
|
||||
Error generating stack: `+c.message+`
|
||||
`+c.stack}}var 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
|
||||
`+c.stack}}var Wa=Object.prototype.hasOwnProperty,Ka=e.unstable_scheduleCallback,po=e.unstable_cancelCallback,Hc=e.unstable_shouldYield,Vc=e.unstable_requestPaint,zt=e.unstable_now,qc=e.unstable_getCurrentPriorityLevel,ae=e.unstable_ImmediatePriority,pe=e.unstable_UserBlockingPriority,ke=e.unstable_NormalPriority,Me=e.unstable_LowPriority,We=e.unstable_IdlePriority,nn=e.log,or=e.unstable_setDisableYieldValue,Pt=null,vt=null;function Vt(n){if(typeof nn=="function"&&or(n),vt&&typeof vt.setStrictMode=="function")try{vt.setStrictMode(Pt,n)}catch{}}var tt=Math.clz32?Math.clz32:xO,Or=Math.log,In=Math.LN2;function xO(n){return n>>>=0,n===0?32:31-(Or(n)/In|0)|0}var Yl=256,Wl=262144,Kl=4194304;function ma(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function Xl(n,i,l){var c=n.pendingLanes;if(c===0)return 0;var m=0,y=n.suspendedLanes,x=n.pingedLanes;n=n.warmLanes;var N=c&134217727;return N!==0?(c=N&~y,c!==0?m=ma(c):(x&=N,x!==0?m=ma(x):l||(l=N&~n,l!==0&&(m=ma(l))))):(N=c&~y,N!==0?m=ma(N):x!==0?m=ma(x):l||(l=c&~n,l!==0&&(m=ma(l)))),m===0?0:i!==0&&i!==m&&(i&y)===0&&(y=m&-m,l=i&-i,y>=l||y===32&&(l&4194048)!==0)?i:m}function fo(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function wO(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uv(){var n=Kl;return Kl<<=1,(Kl&62914560)===0&&(Kl=4194304),n}function Yc(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function go(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function TO(n,i,l,c,m,y){var x=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var N=n.entanglements,B=n.expirationTimes,Y=n.hiddenUpdates;for(l=x&~l;0<l;){var re=31-tt(l),oe=1<<re;N[re]=0,B[re]=-1;var X=Y[re];if(X!==null)for(Y[re]=null,re=0;re<X.length;re++){var ee=X[re];ee!==null&&(ee.lane&=-536870913)}l&=~oe}c!==0&&Fv(n,c,0),y!==0&&m===0&&n.tag!==0&&(n.suspendedLanes|=y&~(x&~i))}function Fv(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var c=31-tt(i);n.entangledLanes|=i,n.entanglements[c]=n.entanglements[c]|1073741824|l&261930}function Bv(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var c=31-tt(l),m=1<<c;m&i|n[c]&i&&(n[c]|=i),l&=~m}}function zv(n,i){var l=i&-i;return l=(l&42)!==0?1:Wc(l),(l&(n.suspendedLanes|i))!==0?0:l}function Wc(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function Kc(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Pv(){var n=W.p;return n!==0?n:(n=window.event,n===void 0?32:kT(n.type))}function $v(n,i){var l=W.p;try{return W.p=n,i()}finally{W.p=l}}var Ir=Math.random().toString(36).slice(2),kt="__reactFiber$"+Ir,qt="__reactProps$"+Ir,Xa="__reactContainer$"+Ir,Xc="__reactEvents$"+Ir,AO="__reactListeners$"+Ir,kO="__reactHandles$"+Ir,Gv="__reactResources$"+Ir,mo="__reactMarker$"+Ir;function Zc(n){delete n[kt],delete n[qt],delete n[Xc],delete n[AO],delete n[kO]}function Za(n){var i=n[kt];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Xa]||l[kt]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=cT(n);n!==null;){if(l=n[kt])return l;n=cT(n)}return i}n=l,l=n.parentNode}return null}function Qa(n){if(n=n[kt]||n[Xa]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function ho(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Ja(n){var i=n[Gv];return i||(i=n[Gv]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[mo]=!0}var Hv=new Set,Vv={};function ha(n,i){ei(n,i),ei(n+"Capture",i)}function ei(n,i){for(Vv[n]=i,n=0;n<i.length;n++)Hv.add(i[n])}var _O=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u20
Line truncated
|
||||
`).replace(a1,"")}function eT(n,i){return i=Jw(i),Jw(n)===i}function Je(n,i,l,c,m,y){switch(l){case"children":typeof c=="string"?i==="body"||i==="textarea"&&c===""||ni(n,c):(typeof c=="number"||typeof c=="bigint")&&i!=="body"&&ni(n,""+c);break;case"className":Ql(n,"class",c);break;case"tabIndex":Ql(n,"tabindex",c);break;case"dir":case"role":case"viewBox":case"width":case"height":Ql(n,l,c);break;case"style":eS(n,c,y);break;case"data":if(i!=="object"){Ql(n,"data",c);break}case"src":case"href":if(c===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(c==null||typeof c=="function"||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"action":case"formAction":if(typeof c=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof y=="function"&&(l==="formAction"?(i!=="input"&&Je(n,i,"name",m.name,m,null),Je(n,i,"formEncType",m.formEncType,m,null),Je(n,i,"formMethod",m.formMethod,m,null),Je(n,i,"formTarget",m.formTarget,m,null)):(Je(n,i,"encType",m.encType,m,null),Je(n,i,"method",m.method,m,null),Je(n,i,"target",m.target,m,null)));if(c==null||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"onClick":c!=null&&(n.onclick=sr);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=c&&typeof c!="function"&&typeof c!="symbol";break;case"muted":n.muted=c&&typeof c!="function"&&typeof c!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(c==null||typeof c=="function"||typeof c=="boolean"||typeof c=="symbol"){n.removeAttribute("xlink:href");break}l=es(""+c),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""+c):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":c&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":c===!0?n.setAttribute(l,""):c!==!1&&c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,c):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":c!=null&&typeof c!="function"&&typeof c!="symbol"&&!isNaN(c)&&1<=c?n.setAttribute(l,c):n.removeAttribute(l);break;case"rowSpan":case"start":c==null||typeof c=="function"||typeof c=="symbol"||isNaN(c)?n.removeAttribute(l):n.setAttribute(l,c);break;case"popover":Ue("beforetoggle",n),Ue("toggle",n),Zl(n,"popover",c);break;case"xlinkActuate":lr(n,"http://www.w3.org/1999/xlink","xlink:actuate",c);break;case"xlinkArcrole":lr(n,"http://www.w3.org/1999/xlink","xlink:arcrole",c);break;case"xlinkRole":lr(n,"http://www.w3.org/1999/xlink","xlink:role",c);break;case"xlinkShow":lr(n,"http://www.w3.org/1999/xlink","xlink:show",c);break;case"xlinkTitle":lr(n,"http://www.w3.org/1999/xlink","xlink:title",c);break;case"xlinkType":lr(n,"http://www.w3.org/1999/xlink","xlink:type",c);break;case"xmlBase":lr(n,"http://www.w3.org/XML/1998/namespace","xml:base",c);break;case"xmlLang":lr(n,"http://www.w3.org/XML/1998/namespace","xml:lang",c);break;case"xmlSpace":lr(n,"http://www.w3.org/XML/1998/namespace","xml:space",c);break;case"is":Zl(n,"is",c);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=IO.get(l)||l,Zl(n,l,c))}}function Pp(n,i,l,c,m,y){switch(l){case"style":eS(n,c,y);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof c=="string"?ni(n,c):(typeof c=="number"||typeof c=="bigint")&&ni(n,""+c);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"onClick":c!=null&&(n.onclick=sr);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne
Line truncated
|
||||
`,f="/",h="*",b="",v="comment",E="declaration";function w(k,A){if(typeof k!="string")throw new TypeError("First argument must be a string");if(!k)return[];A=A||{};var _=1,R=1;function L(te){var Q=te.match(t);Q&&(_+=Q.length);var P=te.lastIndexOf(p);R=~P?te.length-P:R+te.length}function j(){var te={line:_,column:R};return function(Q){return Q.position=new D(te),V(),Q}}function D(te){this.start=te,this.end={line:_,column:R},this.source=A.source}D.prototype.content=k;function z(te){var Q=new Error(A.source+":"+_+":"+R+": "+te);if(Q.reason=te,Q.filename=A.source,Q.line=_,Q.column=R,Q.source=k,!A.silent)throw Q}function F(te){var Q=te.exec(k);if(Q){var P=Q[0];return L(P),k=k.slice(P.length),Q}}function V(){F(r)}function M(te){var Q;for(te=te||[];Q=H();)Q!==!1&&te.push(Q);return te}function H(){var te=j();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var Q=2;b!=k.charAt(Q)&&(h!=k.charAt(Q)||f!=k.charAt(Q+1));)++Q;if(Q+=2,b===k.charAt(Q-1))return z("End of comment missing");var P=k.slice(2,Q-2);return R+=2,L(P),k=k.slice(Q),R+=2,te({type:v,comment:P})}}function K(){var te=j(),Q=F(a);if(Q){if(H(),!F(o))return z("property missing ':'");var P=F(s),W=te({type:E,property:T(Q[0].replace(e,b)),value:P?T(P[0].replace(e,b)):b});return F(u),W}}function Z(){var te=[];M(te);for(var Q;Q=K();)Q!==!1&&(te.push(Q),M(te));return te}return V(),Z()}function T(k){return k?k.replace(d,b):b}return mf=w,mf}var iA;function VD(){if(iA)return Di;iA=1;var e=Di&&Di.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Di,"__esModule",{value:!0}),Di.default=r;const t=e(HD());function r(a,o){let s=null;if(!a||typeof a!="string")return s;const u=(0,t.default)(a),d=typeof o=="function";return u.forEach(p=>{if(p.type!=="declaration")return;const{property:f,value:h}=p;d?o(f,h,p):h&&(s=s||{},s[f]=h)}),s}return Di}var ll={},oA;function qD(){if(oA)return ll;oA=1,Object.defineProperty(ll,"__esModule",{value:!0}),ll.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,o=/^-(ms)-/,s=function(f){return!f||r.test(f)||e.test(f)},u=function(f,h){return h.toUpperCase()},d=function(f,h){return"".concat(h,"-")},p=function(f,h){return h===void 0&&(h={}),s(f)?f:(f=f.toLowerCase(),h.reactCompat?f=f.replace(o,d):f=f.replace(a,d),f.replace(t,u))};return ll.camelCase=p,ll}var sl,lA;function YD(){if(lA)return sl;lA=1;var e=sl&&sl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e(VD()),r=qD();function a(o,s){var u={};return!o||typeof o!="string"||(0,t.default)(o,function(d,p){d&&p&&(u[(0,r.camelCase)(d,s)]=p)}),u}return a.default=a,sl=a,sl}var WD=YD();const KD=Qu(WD),M_=j_("end"),jg=j_("start");function j_(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function XD(e){const t=jg(e),r=M_(e);if(t&&r)return{start:t,end:r}}function gl(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?sA(e.position):"start"in e||"end"in e?sA(e):"line"in e||"column"in e?Kf(e):""}function Kf(e){return uA(e&&e.line)+":"+uA(e&&e.column)}function sA(e){return Kf(e&&e.start)+"-"+Kf(e&&e.end)}function uA(e){return e&&typeof e=="number"?e:1}class Ut extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let o="",s={},u=!1;if(r&&("line"in r&&"column"in r?s={place:r}:"start"in r&&"end"in r?s={place:r}:"type"in r?s={ancestors:[r],place:r.position}:s={...r}),typeof t=="string"?o=t:!s.cause&&t&&(u=!0,o=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof a=="string"){const p=a.indexOf(":");p===-1?s.ruleId=a:(s.source=a.slice(0,p),s.ruleId=a.slice(p+1))}if(!s.place&&s.ancestors&&s.ancestors){const p=s.ancestors[s.ancestors.length-1];p&&(s.place=p.position)}const d=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=o,this.line=d?d.line:void 0,this.name=gl(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=u&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Ut.prototype.file="";Ut.prototype.name="";Ut.prototype.reason="";Ut.prototype.message="";Ut.prototype.stack="";Ut.prototype.column=void 0;Ut.prototype.line=void 0;Ut.prototype.ancestors=void 0;Ut.prototype.cause=void 0;Ut.prototype.fatal=void 0;Ut.prototype.place=void 0;Ut.prototype.ruleId=void 0;Ut.prototype.source=void 0;const Ug={}.hasOwnProperty,ZD=new Map,QD=/[A-Z]/g,JD=new Set(["table","tbody","thead","tfoot","tr"]),eM=new Set(["td","th"]),U_="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function tM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if(
Line truncated
|
||||
`;break}case-3:{u=`\r
|
||||
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function 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:`
|
||||
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function uj(e){const a={constructs:$_([ij,...(e||{}).extensions||[]]),content:o(TM),defined:[],document:o(kM),flow:o(H2),lazy:{},string:o(Y2),text:o(W2)};return a;function o(s){return u;function u(d){return oj(a,s,d)}}}function cj(e){for(;!Y_(e););return e}const EA=/[\0\t\n\r]/g;function dj(){let e=1,t="",r=!0,a;return o;function o(s,u,d){const p=[];let f,h,b,v,E;for(s=t+(typeof s=="string"?s.toString():new TextDecoder(u||void 0).decode(s)),b=0,t="",r&&(s.charCodeAt(0)===65279&&b++,r=void 0);b<s.length;){if(EA.lastIndex=b,f=EA.exec(s),v=f&&f.index!==void 0?f.index:s.length,E=s.charCodeAt(v),!f){t=s.slice(b);break}if(E===10&&b===v&&a)p.push(-3),a=void 0;else switch(a&&(p.push(-5),a=void 0),b<v&&(p.push(s.slice(b,v)),e+=v-b),E){case 0:{p.push(65533),e++;break}case 9:{for(h=Math.ceil(e/4)*4,p.push(-2);e++<h;)p.push(-1);break}case 10:{p.push(-4),e=1;break}default:a=!0,e=1}b=v+1}return d&&(a&&p.push(-5),t&&p.push(t),p.push(null)),p}}const pj=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function fj(e){return e.replace(pj,gj)}function gj(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return G_(r.slice(s?2:1),s?16:10)}return xl(r)||e}const J_={}.hasOwnProperty;function mj(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),hj(r)(cj(uj(r).document().write(dj()(e,t,!0))))}function hj(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Wa),autolinkProtocol:V,autolinkEmail:V,atxHeading:s(qn),blockQuote:s(Re),characterEscape:V,characterReference:V,codeFenced:s(Oe),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Oe,u),codeText:s(qe,u),codeTextData:V,data:V,codeFlowValue:V,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Yn),hardBreakTrailing:s(Yn),htmlFlow:s(Cr,u),htmlFlowData:V,htmlText:s(Cr,u),htmlTextData:V,image:s(ql),label:u,link:s(Wa),listItem:s(po),listItemValue:v,listOrdered:s(Ka,b),listUnordered:s(Ka),paragraph:s(Hc),reference:I,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(qn),strong:s(Vc),thematicBreak:s(qc)},exit:{atxHeading:p(),atxHeadingSequence:j,autolink:p(),autolinkEmail:me,autolinkProtocol:we,blockQuote:p(),characterEscapeValue:M,characterReferenceMarkerHexadecimal:fe,characterReferenceMarkerNumeric:fe,characterReferenceValue:de,characterReference:le,codeFenced:p(k),codeFencedFence:T,codeFencedFenceInfo:E,codeFencedFenceMeta:w,codeFlowValue:M,codeIndented:p(A),codeText:p(Q),codeTextData:M,data:M,definition:p(),definitionDestinationString:L,definitionLabelString:_,definitionTitleString:R,emphasis:p(),hardBreakEscape:p(K),hardBreakTrailing:p(K),htmlFlow:p(Z),htmlFlowData:M,htmlText:p(te),htmlTextData:M,image:p(W),label:ce,labelText:ne,lineEnding:H,link:p(P),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:ue,resourceDestinationString:C,resourceTitleString:U,resource:J,setextHeading:p(F),setextHeadingLineSequence:z,setextHeadingText:D,strong:p(),thematicBreak:p()}};eN(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ae){let pe={type:"root",children:[]};const ke={stack:[pe],tokenStack:[],config:t,enter:d,exit:f,buffer:u,resume:h,data:r},Me=[];let We=-1;for(;++We<ae.length;)if(ae[We][1].type==="listOrdered"||ae[We][1].type==="listUnordered")if(ae[We][0]==="enter")Me.push(We);else{const nn=Me.pop();We=o(ae,nn,We)}for(We=-1;++We<ae.length;){const nn=t[ae[We][0]];J_.call(nn,ae[We][1].type)&&nn[ae[We][1].type].call(Object.assign({sliceSerialize:ae[We][2].sliceSerialize},ke),ae[We][1])}if(ke.tokenStack.length>0){const nn=ke.tokenStack[ke.tokenStack.length-1];(nn[1]||vA).call(ke,void 0,nn[0])}for(pe.position={start:aa(ae.length>0?ae[0][1].start:{line:1,column:1,offset:0}),end:aa(ae.length>0?ae[ae.length-2][1].end:{line:1,column:1,offset:0})},We=-1;++We<t.transforms.length;)pe=t.transforms[We](pe)||pe;return pe}function o(ae,pe,ke){let Me=pe-1,We=-1,nn=!1,or,Pt,vt,Vt;for(;++Me<=ke;){const tt=ae[Me];switch(tt[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{tt[0]==="enter"?We++:We--,Vt=void 0;break}case"lineEndingBlank":{tt[0]==="enter"&&(or&&!Vt&&!We&&!vt&&(vt=Me),Vt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Vt=void 0}if(!We&&tt[0]==="enter"&&tt[1].type==="listItemPrefix"||We===-1&&tt[0]==="exit"&&(tt[1].type==="listUnordered"||tt[1].type==="listOrdered")){if(or){let Or=Me;for(Pt=void 0;Or--;){const In=ae[Or];if(In[1].type==="lineEnding"||In[1].type==="lineEndingBlank"){if(In[0]==="exit")continue;Pt&&(ae[Pt][1].type="lineEndingBlank",nn=!0),In[1].type="lineEnding",Pt=Or}else if(!(In[1].type==="linePrefix"||In[1].type==="blockQuotePrefix"||In[1].type==="blockQuotePrefixWhitespLine truncated
|
||||
`}]}function Sj(e,t){const r=t.value?t.value+`
|
||||
`:"",a={},o=t.lang?t.lang.split(/\s+/):[];o.length>0&&(a.className=["language-"+o[0]]);let s={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(s.data={meta:t.meta}),e.patch(t,s),s=e.applyData(t,s),s={type:"element",tagName:"pre",properties:{},children:[s]},e.patch(t,s),s}function xj(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wj(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Tj(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),o=Ji(a.toLowerCase()),s=e.footnoteOrder.indexOf(a);let u,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),u=e.footnoteOrder.length):u=s+1,d+=1,e.footnoteCounts.set(a,d);const p={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+o,id:r+"fnref-"+o+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(u)}]};e.patch(t,p);const f={type:"element",tagName:"sup",properties:{},children:[p]};return e.patch(t,f),e.applyData(t,f)}function Aj(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function kj(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function tN(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const o=e.all(t),s=o[0];s&&s.type==="text"?s.value="["+s.value:o.unshift({type:"text",value:"["});const u=o[o.length-1];return u&&u.type==="text"?u.value+=a:o.push({type:"text",value:a}),o}function _j(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={src:Ji(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"img",properties:o,children:[]};return e.patch(t,s),e.applyData(t,s)}function Nj(e,t){const r={src:Ji(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function Rj(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function Cj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={href:Ji(a.url||"")};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"a",properties:o,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function Oj(e,t){const r={href:Ji(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Ij(e,t,r){const a=e.all(t),o=r?Lj(r):nN(t),s={},u=[];if(typeof t.checked=="boolean"){const h=a[0];let b;h&&h.type==="element"&&h.tagName==="p"?b=h:(b={type:"element",tagName:"p",properties:{},children:[]},a.unshift(b)),b.children.length>0&&b.children.unshift({type:"text",value:" "}),b.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const h=a[d];(o||d!==0||h.type!=="element"||h.tagName!=="p")&&u.push({type:"text",value:`
|
||||
`}),h.type==="element"&&h.tagName==="p"&&!o?u.push(...h.children):u.push(h)}const p=a[a.length-1];p&&(o||p.type!=="element"||p.tagName!=="p")&&u.push({type:"text",value:`
|
||||
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function 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
|
||||
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function Lj(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=nN(r[a])}return t}function nN(e){const t=e.spread;return t??e.children.length>1}function Dj(e,t){const r={},a=e.all(t);let o=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++o<a.length;){const u=a[o];if(u.type==="element"&&u.tagName==="li"&&u.properties&&Array.isArray(u.properties.className)&&u.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const s={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,s),e.applyData(t,s)}function Mj(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function jj(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function Uj(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Fj(e,t){const r=e.all(t),a=r.shift(),o=[];if(a){const u={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],u),o.push(u)}if(r.length>0){const u={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=jg(t.children[1]),p=M_(t.children[t.children.length-1]);d&&p&&(u.position={start:d,end:p}),o.push(u)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(o,!0)};return e.patch(t,s),e.applyData(t,s)}function Bj(e,t,r){const a=r?r.children:void 0,s=(a?a.indexOf(t):1)===0?"th":"td",u=r&&r.type==="table"?r.align:void 0,d=u?u.length:t.children.length;let p=-1;const f=[];for(;++p<d;){const b=t.children[p],v={},E=u?u[p]:void 0;E&&(v.align=E);let w={type:"element",tagName:s,properties:v,children:[]};b&&(w.children=e.all(b),e.patch(b,w),w=e.applyData(b,w)),f.push(w)}const h={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,h),e.applyData(t,h)}function zj(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const SA=9,xA=32;function Pj(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(wA(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(wA(t.slice(o),o>0,!1)),s.join("")}function wA(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===SA||s===xA;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===SA||s===xA;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function $j(e,t){const r={type:"text",value:Pj(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function Gj(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const Hj={blockquote:Ej,break:vj,code:Sj,delete:xj,emphasis:wj,footnoteReference:Tj,heading:Aj,html:kj,imageReference:_j,image:Nj,inlineCode:Rj,linkReference:Cj,link:Oj,listItem:Ij,list:Dj,paragraph:Mj,root:jj,strong:Uj,table:Fj,tableCell:zj,tableRow:Bj,text:$j,thematicBreak:Gj,toml:fu,yaml:fu,definition:fu,footnoteDefinition:fu};function fu(){}const rN=-1,ic=0,hl=1,zu=2,$g=3,Gg=4,Hg=5,Vg=6,aN=7,iN=8,TA=typeof self=="object"?self:globalThis,Vj=(e,t)=>{const r=(o,s)=>(e.set(s,o),o),a=o=>{if(e.has(o))return e.get(o);const[s,u]=t[o];switch(s){case ic:case rN:return r(u,o);case hl:{const d=r([],o);for(const p of u)d.push(a(p));return d}case zu:{const d=r({},o);for(const[p,f]of u)d[a(p)]=a(f);return d}case $g:return r(new Date(u),o);case Gg:{const{source:d,flags:p}=u;return r(new RegExp(d,p),o)}case Hg:{const d=r(new Map,o);for(const[p,f]of u)d.set(a(p),a(f));return d}case Vg:{const d=r(new Set,o);for(const p of u)d.add(a(p));return d}case aN:{const{name:d,message:p}=u;return r(new TA[d](p),o)}case iN:return r(BigInt(u),o);case"BigInt":return r(Object(BigInt(u)),o);case"ArrayBuffer":return r(new Uint8Array(u).buffer,u);case"DataView":{const{buffer:d}=new Uint8Array(u);return r(new DataView(d),u)}}return r(new TA[s](u),o)};return a},AA=e=>Vj(new Map,e)(0),Mi="",{toString:qj}={},{keys:Yj}=Object,cl=e=>{const t=typeof e;if(t!=="object"||!e)return[ic,t];const r=qj.call(e).slice(8,-1);switch(r){case"Array":return[hl,Mi];case"Object":return[zu,Mi];case"Date":return[$g,Mi];case"RegExp":return[Gg,Mi];case"Map":return[Hg,Mi];case"Set":return[Vg,Mi];case"DataView":return[hl,r]}return r.includes("Array")?[hl,r]:r.includes("Error")?[aN,r]:[zu,r]},gu=([e,t])=>e===ic&&(t==="function"||t==="symbol"),Wj=(e,t,r,a)=>{const o=(u,d)=>{const p=a.push(u)-1;return r.set(d,p),p},s=u=>{if(r.has(u))return r.get(u);let[d,p]=cl(u);switch(d){case ic:{let h=u;switch(p){case"bigint":d=iN,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([rN],u)}return o([d,h],u)}case hl:{if(p){let v=u;return p==="DataView"?v=new Uint8Array(u.buffer):p==="ArrayBuffer"&&(v=new Uint8Array(
Line truncated
|
||||
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
|
||||
`}]}}const 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:`
|
||||
`}]}}const oc=(function(e){if(e==null)return tU;if(typeof e=="function")return lc(e);if(typeof e=="object")return Array.isArray(e)?Qj(e):Jj(e);if(typeof e=="string")return eU(e);throw new Error("Expected function, string, or object as test")});function Qj(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=oc(e[r]);return lc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function Jj(e){const t=e;return lc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function eU(e){return lc(t);function t(r){return r&&r.type===e}}function lc(e){return t;function t(r,a,o){return!!(nU(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function tU(){return!0}function nU(e){return e!==null&&typeof e=="object"&&"type"in e}const oN=[],rU=!0,Qf=!1,aU="skip";function lN(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=oc(o),u=a?-1:1;d(e,void 0,[])();function d(p,f,h){const b=p&&typeof p=="object"?p:{};if(typeof b.type=="string"){const E=typeof b.tagName=="string"?b.tagName:typeof b.name=="string"?b.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(p.type+(E?"<"+E+">":""))+")"})}return v;function v(){let E=oN,w,T,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=iU(r(p,h)),E[0]===Qf))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==aU)for(T=(a?A.children.length:-1)+u,k=h.concat(A);T>-1&&T<A.children.length;){const _=A.children[T];if(w=d(_,T,k)(),w[0]===Qf)return w;T=typeof w[1]=="number"?w[1]:T+u}}return E}}}function iU(e){return Array.isArray(e)?e:typeof e=="number"?[rU,e]:e==null?oN:[e]}function qg(e,t,r,a){let o,s,u;typeof t=="function"&&typeof r!="function"?(s=void 0,u=t,o=r):(s=t,u=r,o=a),lN(e,s,d,o);function d(p,f){const h=f[f.length-1],b=h?h.children.indexOf(p):void 0;return u(p,b,h)}}const Jf={}.hasOwnProperty,oU={};function lU(e,t){const r=t||oU,a=new Map,o=new Map,s=new Map,u={...Hj,...r.handlers},d={all:f,applyData:uU,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:sU,wrap:dU};return qg(e,function(h){if(h.type==="definition"||h.type==="footnoteDefinition"){const b=h.type==="definition"?a:o,v=String(h.identifier).toUpperCase();b.has(v)||b.set(v,h)}}),d;function p(h,b){const v=h.type,E=d.handlers[v];if(Jf.call(d.handlers,v)&&E)return E(d,h,b);if(d.options.passThrough&&d.options.passThrough.includes(v)){if("children"in h){const{children:T,...k}=h,A=Pu(k);return A.children=d.all(h),A}return Pu(h)}return(d.options.unknownHandler||cU)(d,h,b)}function f(h){const b=[];if("children"in h){const v=h.children;let E=-1;for(;++E<v.length;){const w=d.one(v[E],h);if(w){if(E&&v[E-1].type==="break"&&(!Array.isArray(w)&&w.type==="text"&&(w.value=_A(w.value)),!Array.isArray(w)&&w.type==="element")){const T=w.children[0];T&&T.type==="text"&&(T.value=_A(T.value))}Array.isArray(w)?b.push(...w):b.push(w)}}}return b}}function sU(e,t){e.position&&(t.position=XD(e))}function uU(e,t){let r=t;if(e&&e.data){const a=e.data.hName,o=e.data.hChildren,s=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const u="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:u}}r.type==="element"&&s&&Object.assign(r.properties,Pu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function cU(e,t){const r=t.data||{},a="value"in t&&!(Jf.call(r,"hProperties")||Jf.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function dU(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
|
||||
`});++a<e.length;)a&&r.push({type:"text",value:`
|
||||
`}),r.push(e[a]);return t&&e.length>0&&r.push({type:"text",value:`
|
||||
`}),r}function 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:`
|
||||
`}),r}function _A(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function NA(e,t){const r=lU(e,t),a=r.one(e,void 0),o=Zj(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
|
||||
`},o),s}function pU(e,t){return e&&"run"in e?async function(r,a){const o=NA(r,{file:a,...t});await e.run(o,a)}:function(r,a){return NA(r,{file:a,...e||t})}}function RA(e){if(e)throw e}var Ef,CA;function fU(){if(CA)return Ef;CA=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},s=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var h=e.call(f,"constructor"),b=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!h&&!b)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},u=function(f,h){r&&h.name==="__proto__"?r(f,h.name,{enumerable:!0,configurable:!0,value:h.newValue,writable:!0}):f[h.name]=h.newValue},d=function(f,h){if(h==="__proto__")if(e.call(f,h)){if(a)return a(f,h).value}else return;return f[h]};return Ef=function p(){var f,h,b,v,E,w,T=arguments[0],k=1,A=arguments.length,_=!1;for(typeof T=="boolean"&&(_=T,T=arguments[1]||{},k=2),(T==null||typeof T!="object"&&typeof T!="function")&&(T={});k<A;++k)if(f=arguments[k],f!=null)for(h in f)b=d(T,h),v=d(f,h),T!==v&&(_&&v&&(s(v)||(E=o(v)))?(E?(E=!1,w=b&&o(b)?b:[]):w=b&&s(b)?b:{},u(T,{name:h,newValue:p(_,w,v)})):typeof v<"u"&&u(T,{name:h,newValue:v}));return T},Ef}var gU=fU();const vf=Qu(gU);function eg(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function mU(){const e=[],t={run:r,use:a};return t;function r(...o){let s=-1;const u=o.pop();if(typeof u!="function")throw new TypeError("Expected function as last argument, not "+u);d(null,...o);function d(p,...f){const h=e[++s];let b=-1;if(p){u(p);return}for(;++b<o.length;)(f[b]===null||f[b]===void 0)&&(f[b]=o[b]);o=f,h?hU(h,d)(...f):u(null,...f)}}function a(o){if(typeof o!="function")throw new TypeError("Expected `middelware` to be a function, not "+o);return e.push(o),t}}function hU(e,t){let r;return a;function a(...u){const d=e.length>u.length;let p;d&&u.push(o);try{p=e.apply(this,u)}catch(f){const h=f;if(d&&r)throw h;return o(h)}d||(p&&p.then&&typeof p.then=="function"?p.then(s,o):p instanceof Error?o(p):s(p))}function o(u,...d){r||(r=!0,t(u,...d))}function s(u){o(null,u)}}const Qn={basename:bU,dirname:yU,extname:EU,join:vU,sep:"/"};function bU(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Ol(e);let r=0,a=-1,o=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else a<0&&(s=!0,a=o+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let u=-1,d=t.length-1;for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else u<0&&(s=!0,u=o+1),d>-1&&(e.codePointAt(o)===t.codePointAt(d--)?d<0&&(a=o):(d=-1,a=u));return r===a?a=u:a<0&&(a=e.length),e.slice(r,a)}function yU(e){if(Ol(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function EU(e){Ol(e);let t=e.length,r=-1,a=0,o=-1,s=0,u;for(;t--;){const d=e.codePointAt(t);if(d===47){if(u){a=t+1;break}continue}r<0&&(u=!0,r=t+1),d===46?o<0?o=t:s!==1&&(s=1):o>-1&&(s=-1)}return o<0||r<0||s===0||s===1&&o===r-1&&o===a+1?"":e.slice(o,r)}function vU(...e){let t=-1,r;for(;++t<e.length;)Ol(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":SU(r)}function SU(e){Ol(e);const t=e.codePointAt(0)===47;let r=xU(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function xU(e,t){let r="",a=0,o=-1,s=0,u=-1,d,p;for(;++u<=e.length;){if(u<e.length)d=e.codePointAt(u);else{if(d===47)break;d=47}if(d===47){if(!(o===u-1||s===1))if(o!==u-1&&s===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(p=r.lastIndexOf("/"),p!==r.length-1){p<0?(r="",a=0):(r=r.slice(0,p),a=r.length-1-r.lastIndexOf("/")),o=u,s=0;continue}}else if(r.length>0){r="",a=0,o=u,s=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(o+1,u):r=e.slice(o+1,u),a=u-o-1;o=u,s=0}else d===46&&s>-1?s++:s=-1}return r}function Ol(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const wU={cwd:TU};function TU(){return"/"}function tg(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function AU(e){if(typeof e=="string")e=new URL(e);else if(!tg(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return kU(e)}function kU(e){if(e.hostname!==""){consLine truncated
|
||||
`:" ")+s.indentLines(s.containerFlow(a,d.current()),t?pN:mF))),f(),p}}function mF(e,t,r){return t===0?e:pN(e,t,r)}function pN(e,t,r){return(r?"":" ")+e}const hF=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];fN.peek=SF;function bF(){return{canContainEols:["delete"],enter:{strikethrough:EF},exit:{strikethrough:vF}}}function yF(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:hF}],handlers:{delete:fN}}}function EF(e){this.enter({type:"delete",children:[]},e)}function vF(e){this.exit(e)}function fN(e,t,r,a){const o=r.createTracker(a),s=r.enter("strikethrough");let u=o.move("~~");return u+=r.containerPhrasing(e,{...o.current(),before:u,after:"~"}),u+=o.move("~~"),s(),u}function SF(){return"~"}function xF(e){return e.length}function wF(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||xF,s=[],u=[],d=[],p=[];let f=0,h=-1;for(;++h<e.length;){const T=[],k=[];let A=-1;for(e[h].length>f&&(f=e[h].length);++A<e[h].length;){const _=TF(e[h][A]);if(r.alignDelimiters!==!1){const R=o(_);k[A]=R,(p[A]===void 0||R>p[A])&&(p[A]=R)}T.push(_)}u[h]=T,d[h]=k}let b=-1;if(typeof a=="object"&&"length"in a)for(;++b<f;)s[b]=UA(a[b]);else{const T=UA(a);for(;++b<f;)s[b]=T}b=-1;const v=[],E=[];for(;++b<f;){const T=s[b];let k="",A="";T===99?(k=":",A=":"):T===108?k=":":T===114&&(A=":");let _=r.alignDelimiters===!1?1:Math.max(1,p[b]-k.length-A.length);const R=k+"-".repeat(_)+A;r.alignDelimiters!==!1&&(_=k.length+_+A.length,_>p[b]&&(p[b]=_),E[b]=_),v[b]=R}u.splice(1,0,v),d.splice(1,0,E),h=-1;const w=[];for(;++h<u.length;){const T=u[h],k=d[h];b=-1;const A=[];for(;++b<f;){const _=T[b]||"";let R="",L="";if(r.alignDelimiters!==!1){const j=p[b]-(k[b]||0),D=s[b];D===114?R=" ".repeat(j):D===99?j%2?(R=" ".repeat(j/2+.5),L=" ".repeat(j/2-.5)):(R=" ".repeat(j/2),L=R):L=" ".repeat(j)}r.delimiterStart!==!1&&!b&&A.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&_==="")&&(r.delimiterStart!==!1||b)&&A.push(" "),r.alignDelimiters!==!1&&A.push(R),A.push(_),r.alignDelimiters!==!1&&A.push(L),r.padding!==!1&&A.push(" "),(r.delimiterEnd!==!1||b!==f-1)&&A.push("|")}w.push(r.delimiterEnd===!1?A.join("").replace(/ +$/,""):A.join(""))}return w.join(`
|
||||
`)}function TF(e){return e==null?"":String(e)}function UA(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function AF(e,t,r,a){const o=r.enter("blockquote"),s=r.createTracker(a);s.move("> "),s.shift(2);const u=r.indentLines(r.containerFlow(e,s.current()),kF);return o(),u}function kF(e,t,r){return">"+(r?"":" ")+e}function _F(e,t){return FA(e,t.inConstruct,!0)&&!FA(e,t.notInConstruct,!1)}function FA(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function BA(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
|
||||
`&&_F(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
|
||||
`}function NF(e,t){const r=String(e);let a=r.indexOf(t),o=a,s=0,u=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===o?++s>u&&(u=s):s=1,o=a+t.length,a=r.indexOf(t,o);return u}function RF(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function CF(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function OF(e,t,r,a){const o=CF(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(RF(e,r)){const b=r.enter("codeIndented"),v=r.indentLines(s,IF);return b(),v}const d=r.createTracker(a),p=o.repeat(Math.max(NF(s,o)+1,3)),f=r.enter("codeFenced");let h=d.move(p);if(e.lang){const b=r.enter(`codeFencedLang${u}`);h+=d.move(r.safe(e.lang,{before:h,after:" ",encode:["`"],...d.current()})),b()}if(e.lang&&e.meta){const b=r.enter(`codeFencedMeta${u}`);h+=d.move(" "),h+=d.move(r.safe(e.meta,{before:h,after:`
|
||||
`,encode:["`"],...d.current()})),b()}return h+=d.move(`
|
||||
`),s&&(h+=d.move(s+`
|
||||
`)),h+=d.move(p),f(),h}function 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:`
|
||||
`)),h+=d.move(p),f(),h}function IF(e,t,r){return(r?"":" ")+e}function Wg(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function LF(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("definition");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("[");return f+=p.move(r.safe(r.associationId(e),{before:f,after:"]",...p.current()})),f+=p.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":`
|
||||
`,...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),u(),f}function DF(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function wl(e){return"&#x"+e.toString(16).toUpperCase()+";"}function $u(e,t,r){const a=Wi(e),o=Wi(t);return a===void 0?o===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}gN.peek=MF;function gN(e,t,r,a){const o=DF(r),s=r.enter("emphasis"),u=r.createTracker(a),d=u.move(o);let p=u.move(r.containerPhrasing(e,{after:o,before:d,...u.current()}));const f=p.charCodeAt(0),h=$u(a.before.charCodeAt(a.before.length-1),f,o);h.inside&&(p=wl(f)+p.slice(1));const b=p.charCodeAt(p.length-1),v=$u(a.after.charCodeAt(0),b,o);v.inside&&(p=p.slice(0,-1)+wl(b));const E=u.move(o);return s(),r.attentionEncodeSurroundingInfo={after:v.outside,before:h.outside},d+p+E}function MF(e,t,r){return r.options.emphasis||"*"}function jF(e,t){let r=!1;return qg(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Qf}),!!((!e.depth||e.depth<3)&&zg(e)&&(t.options.setext||r))}function UF(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(jF(e,r)){const h=r.enter("headingSetext"),b=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
|
||||
`,after:`
|
||||
`});return b(),h(),v+`
|
||||
`+(o===1?"=":"-").repeat(v.length-(Math.max(v.lastIndexOf("\r"),v.lastIndexOf(`
|
||||
`))+1))}const u="#".repeat(o),d=r.enter("headingAtx"),p=r.enter("phrasing");s.move(u+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
|
||||
`,...s.current()});return/^[\t ]/.test(f)&&(f=wl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),d(),f}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
|
||||
`,...s.current()});return/^[\t ]/.test(f)&&(f=wl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),d(),f}mN.peek=FF;function mN(e){return e.value||""}function FF(){return"<"}hN.peek=BF;function hN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("image");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("![");return f+=p.move(r.safe(e.alt,{before:f,after:"]",...p.current()})),f+=p.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":")",...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),f+=p.move(")"),u(),f}function BF(){return"!"}bN.peek=zF;function bN(e,t,r,a){const o=e.referenceType,s=r.enter("imageReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("![");const f=r.safe(e.alt,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function zF(){return"!"}yN.peek=PF;function yN(e,t,r){let a=e.value||"",o="`",s=-1;for(;new RegExp("(^|[^`])"+o+"([^`]|$)").test(a);)o+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++s<r.unsafe.length;){const u=r.unsafe[s],d=r.compilePattern(u);let p;if(u.atBreak)for(;p=d.exec(a);){let f=p.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(p.index+1)}}return o+a+o}function PF(){return"`"}function EN(e,t){const r=zg(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}vN.peek=$F;function vN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let d,p;if(EN(e,r)){const h=r.stack;r.stack=[],d=r.enter("autolink");let b=u.move("<");return b+=u.move(r.containerPhrasing(e,{before:b,after:">",...u.current()})),b+=u.move(">"),d(),r.stack=h,b}d=r.enter("link"),p=r.enter("label");let f=u.move("[");return f+=u.move(r.containerPhrasing(e,{before:f,after:"](",...u.current()})),f+=u.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=u.move("<"),f+=u.move(r.safe(e.url,{before:f,after:">",...u.current()})),f+=u.move(">")):(p=r.enter("destinationRaw"),f+=u.move(r.safe(e.url,{before:f,after:e.title?" ":")",...u.current()}))),p(),e.title&&(p=r.enter(`title${s}`),f+=u.move(" "+o),f+=u.move(r.safe(e.title,{before:f,after:o,...u.current()})),f+=u.move(o),p()),f+=u.move(")"),d(),f}function $F(e,t,r){return EN(e,r)?"<":"["}SN.peek=GF;function SN(e,t,r,a){const o=e.referenceType,s=r.enter("linkReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("[");const f=r.containerPhrasing(e,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function GF(){return"["}function Kg(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function HF(e){const t=Kg(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function VF(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function xN(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function qF(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?VF(r):Kg(r);const d=e.ordered?u==="."?")":".":HF(r);let p=t&&r.bulletLastUsed?u===r.bulletLastUsed:!1;if(!e.ordered){const h=e.children?e.children[0]:void 0;if((u==="*"||u==="-")&&h&&(!h.children||!h.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(p=!0),xN(r)===u&&h){let b=-1;for(;++b<e.chilLine truncated
|
||||
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:v,table:u,tableCell:p,tableRow:d}};function u(E,w,T,k){return f(h(E,T,k),E.align)}function d(E,w,T,k){const A=b(E,T,k),_=f([A]);return _.slice(0,_.indexOf(`
|
||||
`))}function p(E,w,T,k){const A=T.enter("tableCell"),_=T.enter("phrasing"),R=T.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),R}function f(E,w){return 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:
|
||||
`))}function p(E,w,T,k){const A=T.enter("tableCell"),_=T.enter("phrasing"),R=T.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),R}function f(E,w){return wF(E,{align:w,alignDelimiters:a,padding:r,stringLength:o})}function h(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("table");for(;++A<k.length;)_[A]=b(k[A],w,T);return R(),_}function b(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("tableRow");for(;++A<k.length;)_[A]=p(k[A],E,w,T);return R(),_}function v(E,w,T){let k=TN.inlineCode(E,w,T);return T.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function cB(){return{exit:{taskListCheckValueChecked:PA,taskListCheckValueUnchecked:PA,paragraph:pB}}}function dB(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:fB}}}function PA(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function pB(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const o=t.children;let s=-1,u;for(;++s<o.length;){const d=o[s];if(d.type==="paragraph"){u=d;break}}u===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function fB(e,t,r,a){const o=e.children[0],s=typeof e.checked=="boolean"&&o&&o.type==="paragraph",u="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);s&&d.move(u);let p=TN.listItem(e,t,r,{...a,...d.current()});return s&&(p=p.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),p;function f(h){return h+u}}function gB(){return[qU(),fF(),bF(),rB(),cB()]}function mB(e){return{extensions:[YU(),gF(e),yF(),uB(e),dB()]}}const hB={tokenize:xB,partial:!0},AN={tokenize:wB,partial:!0},kN={tokenize:TB,partial:!0},_N={tokenize:AB,partial:!0},bB={tokenize:kB,partial:!0},NN={name:"wwwAutolink",tokenize:vB,previous:CN},RN={name:"protocolAutolink",tokenize:SB,previous:ON},Nr={name:"emailAutolink",tokenize:EB,previous:IN},rr={};function yB(){return{text:rr}}let Da=48;for(;Da<123;)rr[Da]=Nr,Da++,Da===58?Da=65:Da===91&&(Da=97);rr[43]=Nr;rr[45]=Nr;rr[46]=Nr;rr[95]=Nr;rr[72]=[Nr,RN];rr[104]=[Nr,RN];rr[87]=[Nr,NN];rr[119]=[Nr,NN];function EB(e,t,r){const a=this;let o,s;return u;function u(b){return!ng(b)||!IN.call(a,a.previous)||Xg(a.events)?r(b):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(b))}function d(b){return ng(b)?(e.consume(b),d):b===64?(e.consume(b),p):r(b)}function p(b){return b===46?e.check(bB,h,f)(b):b===45||b===95||Dt(b)?(s=!0,e.consume(b),p):h(b)}function f(b){return e.consume(b),o=!0,p}function h(b){return s&&o&&Ht(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(b)):r(b)}}function vB(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!CN.call(a,a.previous)||Xg(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(hB,e.attempt(AN,e.attempt(kN,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function SB(e,t,r){const a=this;let o="",s=!1;return u;function u(b){return(b===72||b===104)&&ON.call(a,a.previous)&&!Xg(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),o+=String.fromCodePoint(b),e.consume(b),d):r(b)}function d(b){if(Ht(b)&&o.length<5)return o+=String.fromCodePoint(b),e.consume(b),d;if(b===58){const v=o.toLowerCase();if(v==="http"||v==="https")return e.consume(b),p}return r(b)}function p(b){return b===47?(e.consume(b),s?f:(s=!0,p)):r(b)}function f(b){return b===null||Bu(b)||rt(b)||Fa(b)||rc(b)?r(b):e.attempt(AN,e.attempt(kN,h),r)(b)}function h(b){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(b)}}function xB(e,t,r){let a=0;return o;function o(u){return(u===87||u===119)&&a<3?(a++,e.consume(u),o):u===46&&a===3?(e.consume(u),s):r(u)}function s(u){return u===null?r(u):t(u)}}function wB(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(_N,p,d)(f):f===null||rt(f)||Fa(f)||f!==45&&rc(f)?p(f):(s=!0,e.consume(f),u)}function d(f){return f===95?a=!0:(o=a,a=void 0),e.consume(f),u}function p(f){return o||a||!s?r(f):t(f)}}function TB(e,t){let r=0,a=0;return o;function o(u){return u===40?(r++,e.consume(u),o):u===41&&a<r?s(u):u===33||u===34||u===38||u===39||u===41||u===42||u===44||u===46||u===58||u===59||u===60||u===63||u===93||u===95||u===126?e.check(_N,t,s)(u):u===null||rt(u)||Fa(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function AB(e,t,r){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),s):d===93?(e.consume(d),o):d===60||d===null||rt(d)||Fa(d)?t(d):r(d)}function o(d){return d===null||d===40||d===91||rt(d)||Fa(d)?t(d):a(d)}function s(d){return Ht(d)?u(d):r(d)}function u(d){re
Line truncated
|
||||
`)===-1?g.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:h}):g.jsx("code",{className:"block font-mono",children:h}),pre:({children:f})=>g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:f}),table:({children:f})=>g.jsx("div",{className:"overflow-auto my-4",children:g.jsx("table",{className:"w-full border-collapse text-sm",children:f})}),thead:({children:f})=>g.jsx("thead",{className:"bg-muted/50",children:f}),tr:({children:f})=>g.jsx("tr",{className:"border-b",children:f}),th:({children:f})=>g.jsx("th",{className:"px-4 py-2 text-left font-medium",children:f}),td:({children:f})=>g.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:f}),hr:()=>g.jsx("hr",{className:"my-6 border-border"}),strong:({children:f})=>g.jsx("strong",{className:"font-semibold text-foreground",children:f}),em:({children:f})=>g.jsx("em",{children:f})};return r?g.jsx("main",{id:"main-content",className:"flex-1 p-8 max-w-3xl",children:s?g.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):a?g.jsx("article",{className:"prose max-w-none",children:g.jsx(uN,{remarkPlugins:[LN],components:p,children:a})}):g.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:g.jsxs("p",{className:"text-muted-foreground",children:["Content for ",g.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})}):g.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[g.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),g.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),g.jsx(Pi,{className:"my-8"}),g.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),g.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[g.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:g.jsx("strong",{children:"Current theme from YAML:"})}),g.jsx("pre",{className:"text-primary text-sm",children:`site:
|
||||
theme:
|
||||
preset: "${d?.preset||"zinc"}"
|
||||
radius: "${d?.radius||"md"}"
|
||||
darkMode: ${d?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(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
|
||||
darkMode: ${d?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(Y1).map(f=>g.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${f===(d?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:f},f))}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),g.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("span",{className:"font-medium text-primary",children:t.routes?.length||0})," routes configured."]}),g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(t.routes?.slice(0,3),null,2)})]})}const GA=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,HA=u_,Zg=(e,t)=>r=>{var a;if(t?.variants==null)return HA(e,r?.class,r?.className);const{variants:o,defaultVariants:s}=t,u=Object.keys(o).map(f=>{const h=r?.[f],b=s?.[f];if(h===null)return null;const v=GA(h)||GA(b);return o[f][v]}),d=r&&Object.entries(r).reduce((f,h)=>{let[b,v]=h;return v===void 0||(f[b]=v),f},{}),p=t==null||(a=t.compoundVariants)===null||a===void 0?void 0:a.reduce((f,h)=>{let{class:b,className:v,...E}=h;return Object.entries(E).every(w=>{let[T,k]=w;return Array.isArray(k)?k.includes({...s,...d}[T]):{...s,...d}[T]===k})?[...f,b,v]:f},[]);return HA(e,u,p,r?.class,r?.className)},DN=Zg("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-xs":"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3","icon-sm":"size-8","icon-lg":"size-10"}},defaultVariants:{variant:"default",size:"default"}});function $i({className:e,variant:t="default",size:r="default",asChild:a=!1,...o}){const s=a?Dg:"button";return g.jsx(s,{"data-slot":"button","data-variant":t,"data-size":r,className:be(DN({variant:t,size:r,className:e})),...o})}function _n({className:e,...t}){return g.jsx("div",{"data-slot":"card",className:be("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...t})}function Bn({className:e,...t}){return g.jsx("div",{"data-slot":"card-header",className:be("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",e),...t})}function zn({className:e,...t}){return g.jsx("div",{"data-slot":"card-title",className:be("leading-none font-semibold",e),...t})}function Qg({className:e,...t}){return g.jsx("div",{"data-slot":"card-description",className:be("text-muted-foreground text-sm",e),...t})}function Nn({className:e,...t}){return g.jsx("div",{"data-slot":"card-content",className:be("px-6",e),...t})}function KB({className:e,...t}){return g.jsx("div",{"data-slot":"card-footer",className:be("flex items-center px-6 [.border-t]:pt-6",e),...t})}function XB({props:e}){const t=e?.title||"Statistics",r=e?.value||"0",a=e?.change||"",o=e?.changeType||"positive";return g.jsxs(_n,{className:"mb-4",children:[g.jsxs(Bn,{className:"pb-2",children:[g.jsx(Qg,{children:t}),g.jsx(zn,{className:"text-2xl",children:r})]}),a&&g.jsx(Nn,{className:"pt-0",children:g.jsx("span",{className:o==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:a})})]})}function ZB({props:e}){const t=e?.title||"Quick Links",r=e?.links||[];return g.jsxs(_n,{className:"mb-4",children:[g.jsx(Bn,{className:"pb-2",children:g.jsx(zn,{className:"text-sm font-medium",children:t})}),g.jsx(Nn,{className:"space-y-1",children:r.map((a,o)=>g.jsx("a",{href:a.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:a.label},o))})]})}function QB({
Line truncated
|
||||
`);p=b.pop()||"";for(const v of b)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));Ez(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=Ua.get(e);u&&An(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=Ua.get(e);s&&(s.abortController=null),An(e,{isStreaming:!1}),o?.onEnd?.()}}function Ez(e,t,r){const a=Ua.get(e);if(!a)return;const o=a.toolCallMap;switch(t.type){case"session":t.session_id&&typeof t.session_id=="string"&&r?.onSessionId?.(t.session_id);break;case"token":case"run_content":case"team_run_content":{const u=t.token??t.content??"";u&&An(e,{currentResponse:a.state.currentResponse+u});break}case"message":case"run_completed":case"team_run_completed":{const u=t.content;u&&An(e,{currentResponse:u});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const u=t.step;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,u]});break}case"reasoning_started":case"team_reasoning_started":An(e,{thinkingSteps:[...a.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const u={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"running",tool_call_id:t.tool_call_id,args:t.args};u.tool_call_id&&o.set(u.tool_call_id,u);const d=qA(a.state.toolCalls,u);An(e,{toolCalls:d});break}case"tool_call_completed":case"team_tool_call_completed":{const u=t.extra_data,d=t.a2ui??u?.a2ui,p=t.elements??u?.elements,f={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"done",tool_call_id:t.tool_call_id,args:t.args,formatted_result:t.formatted_result,result:t.result,surface_id:t.surface_id??u?.surface_id,a2ui:d?.length?{messages:d,surface_id:t.surface_id??u?.surface_id}:void 0,elements:p};f.tool_call_id&&o.set(f.tool_call_id,f);const h=qA(a.state.toolCalls,f),b=p?.length?[...a.state.pendingElements,...p]:a.state.pendingElements;An(e,{toolCalls:h,pendingElements:b});break}case"message_element":{const u=t.extra_data,d=t.element??u?.element;d&&An(e,{pendingElements:[...a.state.pendingElements,d]});break}case"error":case"run_error":case"team_run_error":{const u=t.error;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,`Error: ${u}`]});break}case"end":case"done":case"run_cancelled":case"team_run_cancelled":break;default:gz(e,t);break}}function qA(e,t){if(t.tool_call_id){const r=e.findIndex(a=>a.tool_call_id===t.tool_call_id);if(r>=0){const a=[...e];return a[r]={...a[r],...t},a}}return[...e,t]}function UN(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>yu(t));S.useEffect(()=>{if(!t){a(yu(null));return}return a(yu(t)),mz(t,()=>{a(yu(t))})},[t]);const o=S.useCallback((u,d)=>{const p=t??`new-${Date.now()}`;bz(p,u,d,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&hz(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function vz(e,t){if(e==null)return{};var r={};for(var a in e)if({}.hasOwnProperty.call(e,a)){if(t.indexOf(a)!==-1)continue;r[a]=e[a]}return r}function Sz(e,t){if(e==null)return{};var r,a,o=vz(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)r=s[a],t.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function ig(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,a=Array(t);r<t;r++)a[r]=e[r];return a}function xz(e){if(Array.isArray(e))return ig(e)}function wz(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function Tz(e,t){if(e){if(typeof e=="string")return ig(e,t);var r={}.toString.call(e).slice(8,-1);return r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set"?Array.from(e):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?ig(e,t):void 0}}function Az(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
|
||||
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function og(e){return xz(e)||wz(e)||Tz(e)||Az()}function Tl(e){"@babel/helpers - typeof";return Tl=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Tl(e)}function kz(e,t){if(Tl(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var a=r.call(e,t);if(Tl(a)!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function _z(e){var t=kz(e,"string");return Tl(t)=="symbol"?t:t+""}function FN(e,t,r){return(t=_z(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function lg(){return lg=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},lg.apply(null,arguments)}function YA(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),r.push.apply(r,a)}return r}function Gi(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?arguments[t]:{};t%2?YA(Object(r),!0).forEach(function(a){FN(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):YA(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function Nz(e){var t=e.length;if(t===0||t===1)return e;if(t===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(t===3)return[e[0],e[1],e[2],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0])];if(t>=4)return[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[
Line truncated
|
||||
`))})}function Uz(e){var t=e.codeString,r=e.codeStyle,a=e.containerStyle,o=a===void 0?{float:"left",paddingRight:"10px"}:a,s=e.numberStyle,u=s===void 0?{}:s,d=e.startingLineNumber;return Ke.createElement("code",{style:Object.assign({},r,o)},jz({lines:t.replace(/\n$/,"").split(`
|
||||
`),style:u,startingLineNumber:d}))}function Fz(e){return"".concat(e.toString().length,".25em")}function zN(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function PN(e,t,r){var a={display:"inline-block",minWidth:Fz(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=oa(oa({},a),o);return s}function Ru(e){var t=e.children,r=e.lineNumber,a=e.lineNumberStyle,o=e.largestLineNumber,s=e.showInlineLineNumbers,u=e.lineProps,d=u===void 0?{}:u,p=e.className,f=p===void 0?[]:p,h=e.showLineNumbers,b=e.wrapLongLines,v=e.wrapLines,E=v===void 0?!1:v,w=E?oa({},typeof d=="function"?d(r):d):{};if(w.className=w.className?[].concat(og(w.className.trim().split(/\s+/)),og(f)):f,r&&s){var T=PN(a,r,o);t.unshift(zN(r,T))}return b&h&&(w.style=oa({display:"flex"},w.style)),{type:"element",tagName:"span",properties:w,children:t}}function $N(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];e.length===void 0&&(e=[e]);for(var a=0;a<e.length;a++){var o=e[a];if(o.type==="text")r.push(Ru({children:[o],className:og(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);$N(o.children,u).forEach(function(d){return r.push(d)})}}return r}function Bz(e,t,r,a,o,s,u,d,p){var f,h=$N(e.value),b=[],v=-1,E=0;function w(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Ru({children:j,lineNumber:D,lineNumberStyle:d,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:z,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function T(j,D){if(a&&D&&o){var z=PN(d,D,u);j.unshift(zN(D,z))}return j}function k(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||z.length>0?w(j,D,z):T(j,D)}for(var A=function(){var D=h[E],z=D.children[0].value,F=Mz(z);if(F){var V=z.split(`
|
||||
`);V.forEach(function(M,H){var K=a&&b.length+s,Z={type:"text",value:"".concat(M,`
|
||||
`)};if(H===0){var te=h.slice(v+1,E).concat(Ru({children:[Z],className:D.properties.className})),Q=k(te,K);b.push(Q)}else if(H===V.length-1){var P=h[E+1]&&h[E+1].children&&h[E+1].children[0],W={type:"text",value:"".concat(M)};if(P){var ne=Ru({children:[W],className:D.properties.className});h.splice(E+1,0,ne)}else{var ce=[W],C=k(ce,K,D.properties.className);b.push(C)}}else{var U=[Z],J=k(U,K,D.properties.className);b.push(J)}}),v=E}E++};E<h.length;)A();if(v!==h.length-1){var _=h.slice(v+1,h.length);if(_&&_.length){var R=a&&b.length+s,L=k(_,R);b.push(L)}}return t?b:(f=[]).concat.apply(f,b)}function zz(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return BN({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function GN(e){return e&&typeof e.highlightAuto<"u"}function Pz(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if(GN(t)){var s=Iz(t,r);return r==="text"?{value:o,language:"text"}:s?t.highlight(r,a):t.highlightAuto(a)}try{return r&&r!=="text"?{value:t.highlight(a,r)}:{value:o}}catch{return{value:o}}}function $z(e,t){return function(a){var o,s,u=a.language,d=a.children,p=a.style,f=p===void 0?t:p,h=a.customStyle,b=h===void 0?{}:h,v=a.codeTagProps,E=v===void 0?{className:u?"language-".concat(u):void 0,style:oa(oa({},f['code[class*="language-"]']),f['code[class*="language-'.concat(u,'"]')])}:v,w=a.useInlineStyles,T=w===void 0?!0:w,k=a.showLineNumbers,A=k===void 0?!1:k,_=a.showInlineLineNumbers,R=_===void 0?!0:_,L=a.startingLineNumber,j=L===void 0?1:L,D=a.lineNumberContainerStyle,z=a.lineNumberStyle,F=z===void 0?{}:z,V=a.wrapLines,M=a.wrapLongLines,H=M===void 0?!1:M,K=a.lineProps,Z=K===void 0?{}:K,te=a.renderer,Q=a.PreTag,P=Q===void 0?"pre":Q,W=a.CodeTag,ne=W===void 0?"code":W,ce=a.code,C=ce===void 0?(Array.isArray(d)?d[0]:d)||"":ce,U=a.astGenerator,J=Sz(a,Lz);U=U||e;var I=A?Ke.createElement(Uz,{containerStyle:D,codeStyle:E.style||{},numberStyle:F,startingLineNumber:j,codeString:C}):null,ue=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},fe=GN(U)?"hljs":"prismjs",de=T?Object.assign({},J,{style:Object.assign({},ue,b)}):Object.assign({},J,{className:J.className?"".concat(fe," ").concat(J.className):fe,style:Object.assign({},b)});if(H?E.style=oa({whiteSpace:"pre-wrap"},E.style):E.style=oa({whiteSpace:"pre"},E.style),!U)return Ke.createElement(P,de,I,Ke.createElement(ne,E,C));(V===void 0&&te||H)&&(V=!0),te=te||zz;var le=[{type:"text",value:C}],we=Pz({astGenerator:U,language:u,code:C,defaultCodeValue:le});we.language===null&&(we.value=le);var me=(o=(s=C.match(/\n/g))===null||s===void 0?void 0:s.length)!==null&&o!==void 0?o:0,Re=j+me,Oe=Bz(we,V,Z,A,R,j,Re,F,H);return Ke.createElement(P,de,Ke.createElement(ne,E,!R&&I,te({rows:Oe,stylesheet:f,useInlineStyles:T})))}}const Gz="modulepreload",Hz=function(e){return"/"+e},XA={},ZA=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let p=function(f){return Promise.all(f.map(h=>Promise.resolve(h).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const u=document.querySelector("meta[property=csp-nonce]"),d=u?.nonce||u?.getAttribute("nonce");o=p(r.map(f=>{if(f=Hz(f),f in XA)return;XA[f]=!0;const h=f.endsWith(".css"),b=h?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${f}"]${b}`))return;const v=document.createElement("link");if(v.rel=h?"stylesheet":Gz,h||(v.as="script"),v.crossOrigin="",v.href=f,d&&v.setAttribute("nonce",d),document.head.appendChild(v),h)return new Promise((E,w)=>{v.addEventListener("load",E),v.addEventListener("error",()=>w(new Error(`Unable to preload CSS for ${f}`)))})}))}function s(u){const d=new Event("vite:preloadError",{cancelable:!0});if(d.payload=u,window.dispatchEvent(d),!d.defaultPrevented)throw u}return o.then(u=>{for(const d of u||[])d.status==="rejected"&&s(d.reason);return t().catch(s)})},Vz=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apex","apl","applescript","aql","arduino","arff","armasm","arturo","asciidoc","asm6502","asmatmel","aspnet","autohotkey","autoit","avisynth","avro-idl","awk","bash","basic","batch","bbcode","bbj","bicep","birb","bison","bnf","bqn","brainfuck","brightscript","bro","bsl","c","cfscript","chaiscript","cil","cilkc","cilkcpp","clike","clojure","cmake","cobol","coffeescript","concurnas","cooklang","coq","cpp","crystal","csharp","cshtml","csp","css-extras","css","csv","cue","cypher","d","dart","dataweave","dax","dhall","diff","django","dns-zone-file","docker","dot","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","false","firestore-security-rules","flow","fortran","fsharp","ftl","gap","gcode","gdscript","gedcom","gettext","gherkin","git","glsl","gml","gn","go-module","go","gradle","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hoon","hpkp","hsts","http","ichigojam","icon","icu-message-format","idris","iecst","ignore","inform7","ini","io","j","java","javado
Line truncated
|
||||
?|
|
||||
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}Bt.displayName="markup-templating";Bt.aliases=[];function Bt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var d=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=d.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return d[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function d(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var b=u[s],v=a.tokenStack[b],E=typeof h=="string"?h:h.content,w=r(o,b),T=E.indexOf(w);if(T>-1){++s;var k=E.substring(0,T),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(T+w.length),R=[];k&&R.push.apply(R,d([k])),R.push(A),_&&R.push.apply(R,d([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(R)):h.content=R}}else h.content&&d(h.content)}return p}d(a.tokens)}}})})(e)}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
|
||||
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}Bt.displayName="markup-templating";Bt.aliases=[];function Bt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var d=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=d.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return d[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function d(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var b=u[s],v=a.tokenStack[b],E=typeof h=="string"?h:h.content,w=r(o,b),T=E.indexOf(w);if(T>-1){++s;var k=E.substring(0,T),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(T+w.length),R=[];k&&R.push.apply(R,d([k])),R.push(A),_&&R.push.apply(R,d([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(R)):h.content=R}}else h.content&&d(h.content)}return p}d(a.tokens)}}})})(e)}uh.displayName="django";uh.aliases=["jinja2"];function uh(e){e.register(Bt),(function(t){t.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var r=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,a=t.languages["markup-templating"];t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"django",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"jinja2",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"jinja2")})})(e)}ch.displayName="dns-zone-file";ch.aliases=["dns-zone"];function ch(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}dh.displayName="docker";dh.aliases=["dockerfile"];function dh(e){(function(t){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,a=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,s=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return o}),u={pattern:RegExp(o),greedy:!0},d={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function p(f,h){return f=f.replace(/<OPT>/g,function(){return s}).replace(/<SP>/g,function(){return a}),RegExp(f,h)}t.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:p(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[u,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:p(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:d,string:u,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper
Line truncated
|
||||
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+a+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+a),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Ul.displayName="t4-templating";Ul.aliases=[];function Ul(e){(function(t){function r(o,s,u){return{pattern:RegExp("<#"+o+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+o+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:s,alias:u}}}}function a(o){var s=t.languages[o],u="language-"+o;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:r("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:r("=",s,u),"class-feature":r("\\+",s,u),standard:r("",s,u)}}}}t.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:a})})(e)}TE.displayName="t4-cs";TE.aliases=["t4"];function TE(e){e.register(to),e.register(Ul),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}bc.displayName="vbnet";bc.aliases=[];function bc(e){e.register(dc),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}AE.displayName="t4-vb";AE.aliases=[];function AE(e){e.register(Ul),e.register(bc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}kE.displayName="tap";kE.aliases=[];function kE(e){e.register(cc),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}_E.displayName="tcl";_E.aliases=[];function _E(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\b/m,lookbehind:!0},/\b(?:else|elseif)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait
Line truncated
|
||||
- a positive number
|
||||
- less than the value passed to \`max\` (or ${uv} if no \`max\` prop is set)
|
||||
- less than the value passed to \`max\` (or ${dv} if no \`max\` prop is set)
|
||||
- \`null\` or \`undefined\` if the progress is indeterminate.
|
||||
|
||||
Defaulting to \`null\`.`}var 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,` {
|
||||
Defaulting to \`null\`.`}var b3=PR,y3=GR;function E3({className:e,value:t,...r}){return g.jsx(b3,{"data-slot":"progress",className:be("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(y3,{"data-slot":"progress-indicator",className:"bg-primary h-full w-full flex-1 transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})})}function Bl(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function zl(e){const[t,r]=S.useState(void 0);return gt(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});const a=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const s=o[0];let u,d;if("borderBoxSize"in s){const p=s.borderBoxSize,f=Array.isArray(p)?p[0]:p;u=f.inlineSize,d=f.blockSize}else u=e.offsetWidth,d=e.offsetHeight;r({width:u,height:d})});return a.observe(e,{box:"border-box"}),()=>a.unobserve(e)}else r(void 0)},[e]),t}var Ac="Switch",[v3]=jt(Ac),[S3,x3]=v3(Ac),VR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,name:a,checked:o,defaultChecked:s,required:u,disabled:d,value:p="on",onCheckedChange:f,form:h,...b}=e,[v,E]=S.useState(null),w=_e(t,R=>E(R)),T=S.useRef(!1),k=v?h||!!v.closest("form"):!0,[A,_]=en({prop:o,defaultProp:s??!1,onChange:f,caller:Ac});return g.jsxs(S3,{scope:r,checked:A,disabled:d,children:[g.jsx(Se.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":KR(A),"data-disabled":d?"":void 0,disabled:d,value:p,...b,ref:w,onClick:ge(e.onClick,R=>{_(L=>!L),k&&(T.current=R.isPropagationStopped(),T.current||R.stopPropagation())})}),k&&g.jsx(WR,{control:v,bubbles:!T.current,name:a,value:p,checked:A,required:u,disabled:d,form:h,style:{transform:"translateX(-100%)"}})]})});VR.displayName=Ac;var qR="SwitchThumb",YR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=x3(qR,r);return g.jsx(Se.span,{"data-state":KR(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});YR.displayName=qR;var w3="SwitchBubbleInput",WR=S.forwardRef(({__scopeSwitch:e,control:t,checked:r,bubbles:a=!0,...o},s)=>{const u=S.useRef(null),d=_e(u,s),p=Bl(r),f=zl(t);return S.useEffect(()=>{const h=u.current;if(!h)return;const b=window.HTMLInputElement.prototype,E=Object.getOwnPropertyDescriptor(b,"checked").set;if(p!==r&&E){const w=new Event("click",{bubbles:a});E.call(h,r),h.dispatchEvent(w)}},[p,r,a]),g.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:r,...o,tabIndex:-1,ref:d,style:{...o.style,...f,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});WR.displayName=w3;function KR(e){return e?"checked":"unchecked"}var T3=VR,A3=YR;function k3({className:e,size:t="default",...r}){return g.jsx(T3,{"data-slot":"switch","data-size":t,className:be("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",e),...r,children:g.jsx(A3,{"data-slot":"switch-thumb",className:be("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}var kc="Checkbox",[_3]=jt(kc),[N3,pv]=_3(kc);function R3(e){const{__scopeCheckbox:t,checked:r,children:a,defaultChecked:o,disabled:s,form:u,name:d,onCheckedChange:p,required:f,value:h="on",internal_do_not_use_render:b}=e,[v,E]=en({prop:r,defaultProp:o??!1,onChange:p,caller:kc}),[w,T]=S.useState(null),[k,A]=S.useState(null),_=S.useRef(!1),R=w?!!u||!!w.closest("form"):!0,L={checked:v,disabled:s,setChecked:E,control:w,setControl:T,name:d,form:u,value:h,hasConsumerStoppedPropagationRef:_,required:f,defaultChecked:la(o)?!1:o,isFormControl:R,bubbleInput:k,setBubbleInput:A};return g.jsx(N3,{scope:t,...L,children:C3(b)?b(L):a})}var XR="CheckboxTrigger",ZR=S.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:r,...a},o)=>{const{control:s,value:u,disabled:d,checked:p,required:f,setControl:h,setChecked:b,hasConsumerStoppedPropagationRef:v,isFormControl:E,bubbleInput:w}=pv(XR,e),T=_e(o,h),k=S.useRef(p);return S.useEffect(()=>{const A=s?.form;if(A){const _=()=>b(k.current);return A.addEventListener("reset",_),()=>A.removeEventListener("reset",_)}},[s,b]),g.jsx(Se.button,{type:"button",role:"checkbox","aria-checked":la(p)?"mixed":p,"aria-required":f,"data-state":rC(p),"data-disabled":d?"":void 0,disabled:d,value:u,...a,ref:T,onKeyDown:ge(t,A=>{A.key==="Enter"&&A.preventDefault()}),onClick:ge(r,A=>{b(_=>la(_)?!0:!_),w&&E&&(v.current=A.isPropagatiLine truncated
|
||||
.`.concat(OG,` {
|
||||
overflow: hidden `).concat(a,`;
|
||||
padding-right: `).concat(d,"px ").concat(a,`;
|
||||
}
|
||||
@@ -71,37 +72,37 @@ Defaulting to \`null\`.`}var h3=zR,b3=$R;function y3({className:e,value:t,...r})
|
||||
`),r==="padding"&&"padding-right: ".concat(d,"px ").concat(a,";")].filter(Boolean).join(""),`
|
||||
}
|
||||
|
||||
.`).concat(Ou,` {
|
||||
.`).concat(Iu,` {
|
||||
right: `).concat(d,"px ").concat(a,`;
|
||||
}
|
||||
|
||||
.`).concat(Iu,` {
|
||||
.`).concat(Lu,` {
|
||||
margin-right: `).concat(d,"px ").concat(a,`;
|
||||
}
|
||||
|
||||
.`).concat(Ou," .").concat(Ou,` {
|
||||
.`).concat(Iu," .").concat(Iu,` {
|
||||
right: 0 `).concat(a,`;
|
||||
}
|
||||
|
||||
.`).concat(Iu," .").concat(Iu,` {
|
||||
.`).concat(Lu," .").concat(Lu,` {
|
||||
margin-right: 0 `).concat(a,`;
|
||||
}
|
||||
|
||||
body[`).concat(qi,`] {
|
||||
`).concat(OG,": ").concat(d,`px;
|
||||
`).concat(IG,": ").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`
|
||||
`)},Dk=function(){var e=parseInt(document.body.getAttribute(qi)||"0",10);return isFinite(e)?e:0},ZG=function(){S.useEffect(function(){return document.body.setAttribute(qi,(Dk()+1).toString()),function(){var e=Dk()-1;e<=0?document.body.removeAttribute(qi):document.body.setAttribute(qi,e.toString())}},[])},QG=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;ZG();var s=S.useMemo(function(){return WG(o)},[o]);return S.createElement(KG,{styles:XG(s,!t,o,r?"":"!important")})},bg=!1;if(typeof window<"u")try{var Tu=Object.defineProperty({},"passive",{get:function(){return bg=!0,!0}});window.addEventListener("test",Tu,Tu),window.removeEventListener("test",Tu,Tu)}catch{bg=!1}var Ui=bg?{passive:!1}:!1,JG=function(e){return e.tagName==="TEXTAREA"},YC=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!JG(e)&&r[t]==="visible")},eH=function(e){return YC(e,"overflowY")},tH=function(e){return YC(e,"overflowX")},Mk=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=WC(e,a);if(o){var s=KC(e,a),u=s[1],d=s[2];if(u>d)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},nH=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},rH=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},WC=function(e,t){return e==="v"?eH(t):tH(t)},KC=function(e,t){return e==="v"?nH(t):rH(t)},aH=function(e,t){return e==="h"&&t==="rtl"?-1:1},iH=function(e,t,r,a,o){var s=aH(e,window.getComputedStyle(t).direction),u=s*a,d=r.target,p=t.contains(d),f=!1,h=u>0,b=0,v=0;do{if(!d)break;var E=KC(e,d),w=E[0],T=E[1],k=E[2],A=T-k-s*w;(w||A)&&WC(e,d)&&(b+=A,v+=w);var _=d.parentNode;d=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!p&&d!==document.body||p&&(t.contains(d)||t===d));return(h&&Math.abs(b)<1||!h&&Math.abs(v)<1)&&(f=!0),f},Au=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},jk=function(e){return[e.deltaX,e.deltaY]},Uk=function(e){return e&&"current"in e?e.current:e},oH=function(e,t){return e[0]===t[0]&&e[1]===t[1]},lH=function(e){return`
|
||||
.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
|
||||
`)},sH=0,Fi=[];function uH(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(sH++)[0],s=S.useState(qC)[0],u=S.useRef(e);S.useEffect(function(){u.current=e},[e]),S.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var T=CG([e.lockRef.current],(e.shards||[]).map(Uk),!0).filter(Boolean);return T.forEach(function(k){return k.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),T.forEach(function(k){return k.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var d=S.useCallback(function(T,k){if("touches"in T&&T.touches.length===2||T.type==="wheel"&&T.ctrlKey)return!u.current.allowPinchZoom;var A=Au(T),_=r.current,R="deltaX"in T?T.deltaX:_[0]-A[0],L="deltaY"in T?T.deltaY:_[1]-A[1],j,D=T.target,z=Math.abs(R)>Math.abs(L)?"h":"v";if("touches"in T&&z==="h"&&D.type==="range")return!1;var F=window.getSelection(),V=F&&F.anchorNode,M=V?V===D||V.contains(D):!1;if(M)return!1;var H=Mk(z,D);if(!H)return!0;if(H?j=z:(j=z==="v"?"h":"v",H=Mk(z,D)),!H)return!1;if(!a.current&&"changedTouches"in T&&(R||L)&&(a.current=j),!j)return!0;var K=a.current||j;return iH(K,k,T,K==="h"?R:L)},[]),p=S.useCallback(function(T){var k=T;if(!(!Fi.length||Fi[Fi.length-1]!==s)){var A="deltaY"in k?jk(k):Au(k),_=t.current.filter(function(j){return j.name===k.type&&(j.target===k.target||k.target===j.shadowParent)&&oH(j.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var R=(u.current.shards||[]).map(Uk).filter(Boolean).filter(function(j){return j.contains(k.target)}),L=R.length>0?d(k,R[0]):!u.current.noIsolation;L&&k.cancelable&&k.preventDefault()}}},[]),f=S.useCallback(function(T,k,A,_){var R={name:T,delta:k,target:A,should:_,shadowParent:cH(A)};t.current.push(R),setTimeout(function(){t.current=t.current.filter(function(L){return L!==R})},1)},[]),h=S.useCallback(function(T){r.current=Au(T),a.current=void 0},[]),b=S.useCallback(function(T){f(T.type,jk(T),T.target,d(T,e.lockRef.current))},[]),v=S.useCallback(function(T){f(T.type,Au(T),T.target,d(T,e.lockRef.current))},[]);S.useEffect(function(){return Fi.push(s),e.setCallbacks({onScrollCapture:b,onWheelCapture:b,onTouchMoveCapture:v}),document.addEventListener("wheel",p,Ui),document.addEventListener("touchmove",p,Ui),document.addEventListener("touchstart",h,Ui),function(){Fi=Fi.filter(function(T){return T!==s}),document.removeEventListener("wheel",p,Ui),document.removeEventListener("touchmove",p,Ui),document.removeEventListener("touchstart",h,Ui)}},[]);var E=e.removeScrollBar,w=e.inert;return S.createElement(S.Fragment,null,w?S.createElement(s,{styles:lH(o)}):null,E?S.createElement(QG,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function cH(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const dH=BG(VC,uH);var Mc=S.forwardRef(function(e,t){return S.createElement(Dc,Jn({},e,{ref:t,sideCar:dH}))});Mc.classNames=Dc.classNames;var pH=[" ","Enter","ArrowUp","ArrowDown"],fH=[" ","Enter"],za="Select",[jc,Uc,gH]=yc(za),[uo]=jt(za,[gH,so]),Fc=so(),[mH,pa]=uo(za),[hH,bH]=uo(za),XC=e=>{const{__scopeSelect:t,children:r,open:a,defaultOpen:o,onOpenChange:s,value:u,defaultValue:d,onValueChange:p,dir:f,name:h,autoComplete:b,disabled:v,required:E,form:w}=e,T=Fc(t),[k,A]=S.useState(null),[_,R]=S.useState(null),[L,j]=S.useState(!1),D=Ha(f),[z,F]=en({prop:a,defaultProp:o??!1,onChange:s,caller:za}),[V,M]=en({prop:u,defaultProp:d,onChange:p,caller:za}),H=S.useRef(null),K=k?w||!!k.closest("form"):!0,[Z,te]=S.useState(new Set),Q=Array.from(Z).map(P=>P.props.value).join(";");return g.jsx(wv,{...T,children:g.jsxs(mH,{required:E,scope:t,trigger:k,onTriggerChange:A,valueNode:_,onValueNodeChange:R,valueNodeHasChildren:L,onValueNodeHasChildrenChange:j,contentId:Rn(),value:V,onValueChange:M,open:z,onOpenChange:F,dir:D,triggerPointerDownPosRef:H,disabled:v,children:[g.jsx(jc.Provider,{scope:t,children:g.jsx(hH,{scope:e.__scopeSelect,onNativeOptionAdd:S.useCallback(P=>{te(W=>new Set(W).add(P))},[]),onNativeOptionRemove:S.useCallback(P=>{te(W=>{const ne=new Set(W);return ne.delete(P),ne})},[]),children:r})}),K?g.jsxs(y0,{"aria-hidden":!0,required:E,tabIndex:-1,name:h,autoComplete:b,value:V,onChange:P=>M(P.target.value),disabled:v,form:w,children:[V===void 0?g.jsx("option",{value:""}):null,Array.from(Z)]},Q):null]})})};XC.displayName=za;var ZC="SelectTrigger",QC=S.forwardRef((e,t)=>{const{__scopeSelect:r,disabled:a=!1,...o}=e,s=Fc(r),u=pa(ZC,r),d=u.disabled||a,p=_e(t,u.onTriggerChange),f=Uc(r),h=S.useRef("touch"),[b,v,E]=v0(T=>{const k=f().filter(R=>!R.disabled),A=k.find(R=>R.value===u.value),_=S0(k,T,A);_!==void 0&&u.onValueChange(_.value)}),w=T=>{d||(u.onOpenChange(!0),E()),T&&(u.triggerPointerDownPosRef.current={x:Math.round(T.pageX),y:Math.round(T.pageY)})};return g.jsx(Lc,{asChild:!0,...s,children:g.jsx(Se.button,{type:"button",role:"combobox","aria-controls":u.contentId,"aria-ex
Line truncated
|
||||
|
||||
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 more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return S.useEffect(()=>{e&&(document.getElementById(e)||console.error(r))},[r,e]),null},L5="DialogDescriptionWarning",D5=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${Z0(L5).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},M5=U0,j5=B0,U5=P0,F5=$0,B5=G0,z5=V0,P5=Y0,$5=K0;function G5({...e}){return g.jsx(M5,{"data-slot":"dialog",...e})}function H5({...e}){return g.jsx(j5,{"data-slot":"dialog-trigger",...e})}function V5({...e}){return g.jsx(U5,{"data-slot":"dialog-portal",...e})}function q5({className:e,...t}){return g.jsx(F5,{"data-slot":"dialog-overlay",className:be("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",e),...t})}function Y5({className:e,children:t,showCloseButton:r=!0,...a}){return g.jsxs(V5,{"data-slot":"dialog-portal",children:[g.jsx(q5,{}),g.jsxs(B5,{"data-slot":"dialog-content",className:be("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",e),...a,children:[t,r&&g.jsxs($5,{"data-slot":"dialog-close",className:"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",children:[g.jsx(z$,{}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function W5({className:e,...t}){return g.jsx("div",{"data-slot":"dialog-header",className:be("flex flex-col gap-2 text-center sm:text-left",e),...t})}function K5({className:e,...t}){return g.jsx(z5,{"data-slot":"dialog-title",className:be("text-lg leading-none font-semibold",e),...t})}function X5({className:e,...t}){return g.jsx(P5,{"data-slot":"dialog-description",className:be("text-muted-foreground text-sm",e),...t})}var Z5=Symbol("radix.slottable");function Q5(e){const t=({children:r})=>g.jsx(g.Fragment,{children:r});return t.displayName=`${e}.Slottable`,t.__radixId=Z5,t}var[Pc]=jt("Tooltip",[so]),$c=so(),Q0="TooltipProvider",J5=700,kg="tooltip.open",[e6,Iv]=Pc(Q0),J0=e=>{const{__scopeTooltip:t,delayDuration:r=J5,skipDelayDuration:a=300,disableHoverableContent:o=!1,children:s}=e,u=S.useRef(!0),d=S.useRef(!1),p=S.useRef(0);return S.useEffect(()=>{const f=p.current;return()=>window.clearTimeout(f)},[]),g.jsx(e6,{scope:t,isOpenDelayedRef:u,delayDuration:r,onOpen:S.useCallback(()=>{window.clearTimeout(p.current),u.current=!1},[]),onClose:S.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>u.current=!0,a)},[a]),isPointerInTransitRef:d,onPointerInTransitChange:S.useCallback(f=>{d.current=f},[]),disableHoverableContent:o,children:s})};J0.displayName=Q0;var kl="Tooltip",[t6,Hl]=Pc(kl),eO=e=>{const{__scopeTooltip:t,children:r,open:a,defaultOpen:o,onOpenChange:s,disableHoverableContent:u,delayDuration:d}=e,p=Iv(kl,e.__scopeTooltip),f=$c(t),[h,b]=S.useState(null),v=Rn(),E=S.useRef(0),w=u??p.disableHoverableContent,T=d??p.delayDuration,k=S.useRef(!1),[A,_]=en({prop:a,defaultProp:o??!1,onChange:z=>{z?(p.onOpen(),document.dispatchEvent(new CustomEvent(kg))):p.onClose(),s?.(z)},caller:kl}),R=S.useMemo(()=>A?k.current?"delayed-open":"instant-open":"closed",[A]),L=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,k.current=!1,_(!0)},[_]),j=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,_(!1)},[_]),D=S.useCallback(()=>{window.clearTimeout(E.current),E.current=window.setTimeout(()=>{k.current=!0,_(!0),E.current=0},T)},[T,_]);return S.useEffect(()=>()=>{E.current&&(window.clearTimeout(E.current),E.current=0)},[]),g.jsx(wv,{...f,children:g.jsx(t6,{scope:t,contentId:v,open:A,stateAttribute:R,trigger:h,onTriggerChange:b,onTriggerEnter:S.useCallback(()=>{p.isOpenDelayedRef.current?D():L()},[p.isOpenDelayedRef,D,L]),onTriggerLeave:S.useCallback(()=>{w?j():(window.clearTimeout(E.current),E.current=0)},[j,w]),onOpen:L,onClose:j,disableHoverableContent:w,children:r})})};eO.displayName=kl;var _g="TooltipTrigger",tO=S.forwardRef((e,t)=>{const{__scopeTooltip:r,...a}=e,o=Hl(_g,r),s=Iv(_g,r),u=$c(r),d=S.useRef(null),p=_e(t,d,o.onTriggerChange),f=S.useRef(!1),h=S.useRef(!1),b=S.useCallback(()=>f.current=!1,[]);return S.useEffect(()=>()=>document.removeEventListener("pointerup",b),[b]),g.jsx(Lc,{asChild:!0,...u,children:g.jsx(Se.button,
Line truncated
|
||||
`);for(;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(`
|
||||
`)}function wV({language:e,children:t}){const[r,a]=S.useState(!1),o=S.useCallback(async()=>{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)},[t]);return g.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[g.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[g.jsx("span",{children:e||"code"}),g.jsx("button",{onClick:o,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),g.jsx(KN,{style:yP,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function TV(e){const t=e.split(`
|
||||
`);let r=!1,a=!1,o="",s=0;for(let u=0;u<t.length;u++){const d=t[u];if(/^[ \t]*```/.test(d)){r=!r;continue}if(r)continue;const p=d.match(/^([ \t]*)(\d+)\.[ \t]/);if(p){const f=p[1];!a||f!==o?(a=!0,o=f,s=1):s++,t[u]=d.replace(/^([ \t]*)\d+\./,`${f}${s}.`)}else d.trim()===""&&a||a&&/^[ \t]+\S/.test(d)||a&&(a=!1,s=0)}return t.join(`
|
||||
`)}function 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
|
||||
`)}function vO({content:e}){const t=TV(e);return g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:p-0 prose-pre:bg-transparent",children:g.jsx(uN,{remarkPlugins:[LN],components:{ul({children:r,ordered:a,start:o}){if(a){const s=o!=null?Number(o):1;return g.jsx("ol",{start:s,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return g.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return g.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:a,...o}){const s=/language-(\w+)/.exec(r||""),u=xV(String(a));return!s&&!u.includes(`
|
||||
`)?g.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...o,children:a}):g.jsx(wV,{language:s?.[1],children:u})},a({href:r,children:a}){return g.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:a})},table({children:r}){return g.jsx("div",{className:"overflow-x-auto my-3",children:g.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:t})})}function AV({message:e,sessionId:t}){return e.role==="user"?g.jsx("div",{className:"flex justify-end",children:g.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[g.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:g.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),g.jsx(Zu,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(Zu,{role:"assistant"}),g.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&g.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((a,o)=>g.jsx(EO,{toolCall:a},o))}),g.jsx(vO,{content:e.content}),e.elements&&e.elements.length>0&&g.jsx("div",{className:"mt-3 space-y-3",children:e.elements.map((a,o)=>g.jsx(jv,{element:a,className:"w-full"},o))}),e.images&&e.images.length>0&&g.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((a,o)=>g.jsx("img",{src:a,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&g.jsx(mV,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(vV,{text:e.content}),g.jsx(SV,{messageId:e.id})]})]})]})}function kV({onSend:e,onCancel:t,isStreaming:r=!1,placeholder:a="Type a message...",enableFileUpload:o=!1}){const[s,u]=S.useState(""),[d,p]=S.useState([]),f=S.useRef(null),h=S.useRef(null),b=S.useCallback(()=>{const A=s.trim();(A||d.length>0)&&!r&&(e(A,d.length>0?d:void 0),u(""),p([]),f.current&&(f.current.style.height="auto"))},[s,d,r,e]),v=S.useCallback(A=>{const _=Array.from(A.target.files||[]);p(R=>[...R,..._]),h.current&&(h.current.value="")},[]),E=S.useCallback(()=>{h.current?.click()},[]),w=S.useCallback(A=>{p(_=>_.filter((R,L)=>L!==A))},[]),T=S.useCallback(A=>{A.key==="Enter"&&!A.shiftKey&&(A.preventDefault(),b())},[b]),k=S.useCallback(()=>{const A=f.current;A&&(A.style.height="auto",A.style.height=`${Math.min(A.scrollHeight,200)}px`)},[]);return g.jsx("div",{className:"p-4 pb-6",children:g.jsxs("div",{className:"max-w-3xl mx-auto",children:[g.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[d.length>0&&g.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:d.map((A,_)=>g.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[g.jsx("span",{className:"truncate max-w-[100px]",children:A.name}),g.jsx("button",{type:"button",onClick:()=>w(_),className:"hover:text-destructive",children:"×"})]},`${A.name}-${_}`))}),g.jsxs("div",{className:"flex items-end gap-2 p-2",children:[o&&g.jsxs(g.Fragment,{children:[g.jsx("input",{ref:h,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),g.jsx("button",{type:"button",onClick:E,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})})})]}),g.jsx("textarea",{ref:f,value:s,onChange:A=>u(A.target.value),onKeyDown:T,onInput:k,placeholder:a,disabled:r,rows:1,className:"flex-1 resize-none bg-transparent px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 placeholder:text-muted-foreground/60",style:{maxHeight:"200px"}}),r?g.jsx("button",{type:"button",onClick:t,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:g.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):g.jsx("button",{type:"button",onClick:b,disabled:!s.trim(),className:"p-2 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",title:"Send",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),g.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line"
Line truncated
|
||||
`),b=S.useCallback(async()=>{!r.trim()||s||p(r)},[r,s,p]),v=S.useCallback(()=>{a("")},[]);return g.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[g.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[g.jsx("h1",{className:"text-xl font-semibold",children:t||"Playground"}),g.jsx("div",{className:"flex items-center gap-2",children:g.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),g.jsxs("div",{className:"flex-1 flex",children:[g.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[g.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),g.jsx("div",{className:"flex-1 p-4",children:g.jsx("textarea",{value:r,onChange:E=>a(E.target.value),placeholder:"Enter your prompt here...",className:"w-full h-full resize-none bg-transparent border rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-primary"})}),g.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:s?g.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):g.jsx("button",{onClick:b,disabled:!r.trim(),className:"px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",children:"Submit"})})]}),g.jsxs("div",{className:"flex-1 flex flex-col",children:[g.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),s&&g.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),g.jsx("div",{className:"flex-1 p-4 overflow-auto",children:h?g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:h}):g.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&g.jsxs("div",{className:"border-t px-6 py-3 bg-muted/20 flex items-center gap-4 text-sm text-muted-foreground",children:[e.features.streaming&&g.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&g.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&g.jsx("span",{children:"✓ Tools"})]})]})}function _u({enabled:e}){return e?g.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-primary text-primary-foreground px-4 py-2 rounded-md text-sm font-medium",children:"Skip to main content"}):null}function jV(){const[e,t]=S.useState({}),[r,a]=S.useState({}),[o,s]=S.useState({}),[u,d]=S.useState(!0),[p,f]=S.useState(null),[h,b]=S.useState(null),[v,E]=S.useState("");S.useEffect(()=>{async function L(){try{const[j,D,z]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!j.ok||!D.ok||!z.ok)throw new Error("Failed to load manifests");const F=await j.json(),V=await D.json();t(F),a(V),s(await z.json());const M=F.site?.theme;K1(M?.preset||"zinc",M?.darkMode!==!1,M?.radius||"md");const H=window.location.pathname;if(H&&H!=="/"){const K=Z=>{for(const te of Z){if(te.path===H||te.path===H.replace(/^\//,""))return te;if(te.children){const Q=K(te.children);if(Q)return Q}}return null};if(V.items){for(const Z of V.items)if(Z.children){const te=K(Z.children);if(te){b(te),E(te.path||te.title);break}}}}}catch(j){f(j instanceof Error?j.message:"Unknown error")}finally{d(!1)}}L()},[]),S.useEffect(()=>{const L=()=>{const j=window.location.pathname,D=F=>{for(const V of F){if(V.path===j||V.path===j.replace(/^\//,""))return V;if(V.children){const M=D(V.children);if(M)return M}}return null};let z=null;if(r.items){for(const F of r.items)if(F.children&&(z=D(F.children),z))break}if(z){b(z),E(z.path||z.title);const F=e.site?.title||"Documentation",V=e.seo?.titleTemplate||"%s | %s";if(V.includes("%s")){let M=V.replace("%s",z.title);M.includes("%s")&&(M=M.replace("%s",F)),document.title=M}else document.title=`${z.title} | ${F}`}else j==="/"&&(b(null),E(""),document.title=e.site?.title||"Documentation")};return window.addEventListener("popstate",L),()=>window.removeEventListener("popstate",L)},[r,e]);const w=(L,j,D)=>{const z=e.seo?.titleTemplate||"%s | %s",F=e.site?.title||"Documentation";if(z.includes("%s")){let Z=z.replace("%s",L);Z.includes("%s")&&(Z=Z.replace("%s",F)),document.title=Z}else document.title=`${L} | ${F}`;let V=document.querySelector('link[rel="canonical"]');V||(V=document.createElement("link"),V.rel="canonical",document.head.appendChild(V)),V.href=window.location.origin+j;let M=document.querySelector('meta[name="description"]');M||(M=document.createElement("meta"),M.name="description",document.head.appendChild(M)),M.content=`${L} - ${e.site?.description||""}`;let H=document.querySele
Line truncated
|
||||
@@ -0,0 +1,5 @@
|
||||
import{y as Q,A as _,j as X,n as E,a as w,o as f,r as q,b as O,s as m,l as S,u as $,c as N,d as k,e as Y,f as K}from"./common-types.js";import{g as mt,h as ht,i as gt,k as yt,C as bt,m as vt,p as St,q as At,t as _t,v as wt,w as Mt,D as It,x as Zt,z as kt,B as Dt,E as Pt,F as jt,G as Ct,H as Ot,I as $t,J as Nt,K as Et,L as Tt,M as xt,N as Ut}from"./common-types.js";class v{constructor(){this.listeners=new Set}subscribe(e){return this.listeners.add(e),{unsubscribe:()=>this.listeners.delete(e)}}async emit(e){for(const t of this.listeners)try{await t(e)}catch(a){console.error("EventEmitter error:",a)}}dispose(){this.listeners.clear()}}function D(n){return/^\d+$/.test(n)}class ee{constructor(e={}){this.data={},this.signals=new Map,this.subscriptions=new Set,this.data=e}getSignal(e){const t=this.normalizePath(e);return this.signals.has(t)||this.signals.set(t,Q(this.get(t))),this.signals.get(t)}set(e,t){if(e==null)throw new _("Path cannot be null or undefined.");if(e==="/"||e==="")return this.data=t,this.notifyAllSignals(),this;const a=this.parsePath(e),s=a.pop();this.data||(this.data={});let r=this.data;for(let i=0;i<a.length;i++){const o=a[i];if(Array.isArray(r)&&!D(o))throw new _(`Cannot use non-numeric segment '${o}' on an array in path '${e}'.`,e);if(r[o]!==void 0&&r[o]!==null&&typeof r[o]!="object")throw new _(`Cannot set path '${e}': segment '${o}' is a primitive value.`,e);if(r[o]===void 0||r[o]===null){const c=i<a.length-1?a[i+1]:s;r[o]=D(c)?[]:{}}r=r[o]}if(Array.isArray(r)&&!D(s))throw new _(`Cannot use non-numeric segment '${s}' on an array in path '${e}'.`,e);return t===void 0?Array.isArray(r)?r[parseInt(s,10)]=void 0:delete r[s]:r[s]=t,this.notifySignals(e),this}get(e){if(e==null)throw new _("Path cannot be null or undefined.");if(e==="/"||e==="")return this.data;const t=this.parsePath(e);let a=this.data;for(const s of t){if(a==null)return;a=a[s]}return a}subscribe(e,t){const a=this.getSignal(e);let s=!0,r=a.peek();const i=X(()=>{const o=a.value;r=o,s||t(o)});return s=!1,this.subscriptions.add(i),{get value(){return r},unsubscribe:()=>{i(),this.subscriptions.delete(i)}}}dispose(){for(const e of this.subscriptions)e();this.subscriptions.clear(),this.signals.clear()}normalizePath(e){return e.length>1&&e.endsWith("/")?e.slice(0,-1):e||"/"}parsePath(e){return e.split("/").filter(t=>t.length>0)}notifySignals(e){const t=this.normalizePath(e);E(()=>{this.updateSignal(t);let a=t;for(;a!=="/"&&a!=="";)a=a.substring(0,a.lastIndexOf("/"))||"/",this.updateSignal(a);for(const s of this.signals.keys())this.isDescendant(s,t)&&this.updateSignal(s)})}updateSignal(e){const t=this.signals.get(e);if(t){const a=this.get(e);Array.isArray(a)?t.value=[...a]:typeof a=="object"&&a!==null?t.value={...a}:t.value=a}}notifyAllSignals(){E(()=>{for(const e of this.signals.keys())this.updateSignal(e)})}isDescendant(e,t){return t==="/"||t===""?e!=="/":e.startsWith(t+"/")}}class te{constructor(){this.components=new Map,this._onCreated=new v,this._onDeleted=new v,this.onCreated=this._onCreated,this.onDeleted=this._onDeleted}get(e){return this.components.get(e)}get entries(){return this.components.entries()}addComponent(e){if(this.components.has(e.id))throw new w(`Component with id '${e.id}' already exists.`);this.components.set(e.id,e),this._onCreated.emit(e)}removeComponent(e){const t=this.components.get(e);t&&(this.components.delete(e),t.dispose(),this._onDeleted.emit(e))}dispose(){for(const e of this.components.values())e.dispose();this.components.clear(),this._onCreated.dispose(),this._onDeleted.dispose()}}const V=f({name:m().describe("The name of the action, taken from the component's action.event.name property."),surfaceId:m().describe("The id of the surface where the event originated."),sourceComponentId:m().describe("The id of the component that triggered the event."),timestamp:m().datetime().describe("An ISO 8601 timestamp of when the event occurred."),context:q(O()).describe("A JSON object containing the key-value pairs from the component's action.event.context, after resolving all data bindings.")}).strict(),ne=f({code:S("VALIDATION_FAILED"),surfaceId:m().describe("The id of the surface where the error occurred."),path:m().describe("The JSON pointer to the field that failed validation (e.g. '/components/0/text')."),message:m().describe("A short one or two sentence description of why validation failed.")}).strict(),ae=f({code:m().refine(n=>n!=="VALIDATION_FAILED"),message:m().describe("A short one or two sentence description of why the error occurred."),surfaceId:m().describe("The id of the surface where the error occurred.")}).passthrough(),se=$([ne,ae]),re=f({version:S("v0.9")}).and($([f({action:V}),f({error:se})])),ot=f({version:S("v0.9"),surfaces:q(f({}).passthrough()).describe("A map of surface IDs to their current data models.")}).strict(),ie=N(re).describe("A list of client messages."),ct=f({messages:ie}).strict().describe("An object wrapping a list of client messages.");class oe{constructor(e,t,a={},s=!1){this.id=e,thLine truncated
|
||||
]))`;continue}else if(a[c]==="$"){s+=`($|(?=[\r
|
||||
]))`;continue}}if(t.s&&a[c]==="."){s+=i?`${a[c]}\r
|
||||
`:`[${a[c]}\r
|
||||
]`;continue}s+=a[c],a[c]==="\\"?r=!0:i&&a[c]==="]"?i=!1:!i&&a[c]==="["&&(i=!0)}try{new RegExp(s)}catch{return console.warn(`Could not convert regex pattern at ${e.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`),n.source}return s}function H(n,e){if(e.target==="openAi"&&console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead."),e.target==="openApi3"&&n.keyType?._def.typeName===u.ZodEnum)return{type:"object",required:n.keyType._def.values,properties:n.keyType._def.values.reduce((a,s)=>({...a,[s]:l(n.valueType._def,{...e,currentPath:[...e.currentPath,"properties",s]})??g(e)}),{}),additionalProperties:e.rejectedAdditionalProperties};const t={type:"object",additionalProperties:l(n.valueType._def,{...e,currentPath:[...e.currentPath,"additionalProperties"]})??e.allowedAdditionalProperties};if(e.target==="openApi3")return t;if(n.keyType?._def.typeName===u.ZodString&&n.keyType._def.checks?.length){const{type:a,...s}=G(n.keyType._def,e);return{...t,propertyNames:s}}else{if(n.keyType?._def.typeName===u.ZodEnum)return{...t,propertyNames:{enum:n.keyType._def.values}};if(n.keyType?._def.typeName===u.ZodBranded&&n.keyType._def.type._def.typeName===u.ZodString&&n.keyType._def.type._def.checks?.length){const{type:a,...s}=J(n.keyType._def,e);return{...t,propertyNames:s}}}return t}function Ie(n,e){if(e.mapStrategy==="record")return H(n,e);const t=l(n.keyType._def,{...e,currentPath:[...e.currentPath,"items","items","0"]})||g(e),a=l(n.valueType._def,{...e,currentPath:[...e.currentPath,"items","items","1"]})||g(e);return{type:"array",maxItems:125,items:{type:"array",items:[t,a],minItems:2,maxItems:2}}}function Ze(n){const e=n.values,a=Object.keys(n.values).filter(r=>typeof e[e[r]]!="number").map(r=>e[r]),s=Array.from(new Set(a.map(r=>typeof r)));return{type:s.length===1?s[0]==="string"?"string":"number":["string","number"],enum:a}}function ke(n){return n.target==="openAi"?void 0:{not:g({...n,currentPath:[...n.currentPath,"not"]})}}function De(n){return n.target==="openApi3"?{enum:["null"],nullable:!0}:{type:"null"}}const Z={ZodString:"string",ZodNumber:"number",ZodBigInt:"integer",ZodBoolean:"boolean",ZodNull:"null"};function Pe(n,e){if(e.target==="openApi3")return R(n,e);const t=n.options instanceof Map?Array.from(n.options.values()):n.options;if(t.every(a=>a._def.typeName in Z&&(!a._def.checks||!a._def.checks.length))){const a=t.reduce((s,r)=>{const i=Z[r._def.typeName];return i&&!s.includes(i)?[...s,i]:s},[]);return{type:a.length>1?a:a[0]}}else if(t.every(a=>a._def.typeName==="ZodLiteral"&&!a.description)){const a=t.reduce((s,r)=>{const i=typeof r._def.value;switch(i){case"string":case"number":case"boolean":return[...s,i];case"bigint":return[...s,"integer"];case"object":if(r._def.value===null)return[...s,"null"];default:return s}},[]);if(a.length===t.length){const s=a.filter((r,i,o)=>o.indexOf(r)===i);return{type:s.length>1?s:s[0],enum:t.reduce((r,i)=>r.includes(i._def.value)?r:[...r,i._def.value],[])}}}else if(t.every(a=>a._def.typeName==="ZodEnum"))return{type:"string",enum:t.reduce((a,s)=>[...a,...s._def.values.filter(r=>!a.includes(r))],[])};return R(n,e)}const R=(n,e)=>{const t=(n.options instanceof Map?Array.from(n.options.values()):n.options).map((a,s)=>l(a._def,{...e,currentPath:[...e.currentPath,"anyOf",`${s}`]})).filter(a=>!!a&&(!e.strictUnions||typeof a=="object"&&Object.keys(a).length>0));return t.length?{anyOf:t}:void 0};function je(n,e){if(["ZodString","ZodNumber","ZodBigInt","ZodBoolean","ZodNull"].includes(n.innerType._def.typeName)&&(!n.innerType._def.checks||!n.innerType._def.checks.length))return e.target==="openApi3"?{type:Z[n.innerType._def.typeName],nullable:!0}:{type:[Z[n.innerType._def.typeName],"null"]};if(e.target==="openApi3"){const a=l(n.innerType._def,{...e,currentPath:[...e.currentPath]});return a&&"$ref"in a?{allOf:[a],nullable:!0}:a&&{...a,nullable:!0}}const t=l(n.innerType._def,{...e,currentPath:[...e.currentPath,"anyOf","0"]});return t&&{anyOf:[t,{type:"null"}]}}function Ce(n,e){const t={type:"number"};if(!n.checks)return t;for(const a of n.checks)switch(a.kind){case"int":t.type="integer",B(t,"type",a.message,e);break;case"min":e.target==="jsonSchema7"?a.inclusive?d(t,"minimum",a.value,a.message,e):d(t,"exclusiveMinimum",a.value,a.message,e):(a.inclusive||(t.exclusiveMinimum=!0),d(t,"minimum",a.value,a.message,e));break;case"max":e.target==="jsonSchema7"?a.inclusive?d(t,"maximum",a.value,a.message,e):d(t,"exclusiveMaximum",a.value,a.message,e):(a.inclusive||(t.exclusiveMaximum=!0),d(t,"maximum",a.value,a.message,e));break;case"multipleOf":d(t,"multipleOf",a.value,a.message,e);break}return t}function Oe(n,e){const t=e.target==="openAi",a={type:"object",properties:{}},s=[],r=n.shape();for(const o in r){let c=r[o];if(c===void 0||c._def===void 0)continue;let p=Ne(c);p&&t&&(c._def.typeName==="ZodOptional"&&(c=c._def.innerType),c.isNullable()||(c=c.nullable()),p=!1);const A=l(c._def,{...e,currentPath:[..
Line truncated
|
||||
@@ -0,0 +1,67 @@
|
||||
import{j as s,r as y,R as W}from"./index.js";import{h as E,o as u,O as g,P as m,b as D,c as X,s as v,e as q,L as b,K as ke,M as Se,Q as V,i as Te,R as T,F as S,u as Z,q as te,w as $,p as F,k as Ce,z as De,E as Me,B as Oe,C as Ae,v as Pe,I as ie}from"./common-types.js";const U=class U{parse(t,n=0){if(n>U.MAX_DEPTH)throw new E("Max recursion depth reached in parse");if(!t||!t.includes("${"))return[t];const a=[],r=new oe(t);for(;!r.isAtEnd();)if(r.matches("${")){r.advance(2);const i=this.extractInterpolationContent(r),o=this.parseExpression(i,n+1);o!==null&&a.push(o)}else if(r.peek()==="\\"&&r.peek(1)==="$"&&r.peek(2)==="{")r.advance(),a.push("${"),r.advance(2);else{const i=r.pos;for(;!r.isAtEnd()&&!(r.matches("${")||r.peek()==="\\"&&r.peek(1)==="$"&&r.peek(2)==="{");)r.advance();a.push(r.input.substring(i,r.pos))}return a.filter(i=>i!==null&&i!=="")}extractInterpolationContent(t){const n=t.pos;let a=1;for(;!t.isAtEnd()&&a>0;){const r=t.advance();if(r==="{")a++;else if(r==="}")a--;else if(r==="'"||r==='"'){const i=r;for(;!t.isAtEnd();){const o=t.advance();if(o==="\\")t.advance();else if(o===i)break}}}if(a>0)throw new E("Unclosed interpolation: missing '}'");return t.input.substring(n,t.pos-1)}parseExpression(t,n=0){if(t=t.trim(),!t)return"";const a=new oe(t),r=this.parseExpressionInternal(a,n);if(!a.isAtEnd())throw new E(`Unexpected characters at end of expression: '${a.input.substring(a.pos)}'`);return r}parseExpressionInternal(t,n){if(t.skipWhitespace(),t.isAtEnd())return"";if(t.matches("${")){t.advance(2);const r=this.extractInterpolationContent(t);return this.parseExpression(r,n+1)}if(t.matchesString("'")||t.matchesString('"'))return this.parseStringLiteral(t);if(this.isDigit(t.peek()))return this.parseNumberLiteral(t);if(t.matchesKeyword("true"))return!0;if(t.matchesKeyword("false"))return!1;if(t.matchesKeyword("null"))return"";const a=this.scanPathOrIdentifier(t);return t.skipWhitespace(),t.peek()==="("?this.parseFunctionCall(a,t,n):a?{path:a}:""}scanPathOrIdentifier(t){const n=t.pos;for(;!t.isAtEnd();){const a=t.peek();if(this.isAlnum(a)||a==="/"||a==="."||a==="_"||a==="-")t.advance();else break}return t.input.substring(n,t.pos)}parseFunctionCall(t,n,a){n.match("("),n.skipWhitespace();const r={};for(;!n.isAtEnd()&&n.peek()!==")";){const i=this.scanIdentifier(n);if(n.skipWhitespace(),!n.match(":"))throw new E(`Expected ':' after argument name '${i}' in function '${t}'`);n.skipWhitespace(),r[i]=this.parseExpressionInternal(n,a),n.skipWhitespace(),n.peek()===","&&(n.advance(),n.skipWhitespace())}if(!n.match(")"))throw new E(`Expected ')' after function arguments for '${t}'`);return{call:t,args:r,returnType:"any"}}scanIdentifier(t){const n=t.pos;for(;!t.isAtEnd()&&(this.isAlnum(t.peek())||t.peek()==="_");)t.advance();return t.input.substring(n,t.pos)}parseStringLiteral(t){const n=t.advance();let a="";for(;!t.isAtEnd();){const r=t.advance();if(r==="\\"){const i=t.advance();i==="n"?a+=`
|
||||
`:i==="t"?a+=" ":i==="r"?a+="\r":a+=i}else{if(r===n)break;a+=r}}return a}parseNumberLiteral(t){const n=t.pos;for(;!t.isAtEnd()&&(this.isDigit(t.peek())||t.peek()===".");)t.advance();return Number(t.input.substring(n,t.pos))}isAlnum(t){return t>="a"&&t<="z"||t>="A"&&t<="Z"||t>="0"&&t<="9"}isDigit(t){return t>="0"&&t<="9"}};U.MAX_DEPTH=10;let ee=U;class oe{constructor(t){this.input=t,this.pos=0}isAtEnd(){return this.pos>=this.input.length}peek(t=0){return this.pos+t>=this.input.length?"\0":this.input[this.pos+t]}advance(t=1){const n=this.input.substring(this.pos,this.pos+t);return this.pos+=t,n}match(t){return this.peek()===t?(this.advance(),!0):!1}matches(t){return!!this.input.startsWith(t,this.pos)}matchesString(t){return this.peek()===t}matchesKeyword(t){if(this.input.startsWith(t,this.pos)){const n=this.peek(t.length);if(!/[a-zA-Z0-9_]/.test(n))return this.advance(t.length),!0}return!1}skipWhitespace(){for(;!this.isAtEnd()&&/\s/.test(this.peek());)this.advance()}}const be=6048e5,Ie=864e5,se=Symbol.for("constructDateFrom");function P(e,t){return typeof e=="function"?e(t):e&&typeof e=="object"&&se in e?e[se](t):e instanceof Date?new e.constructor(t):new Date(t)}function C(e,t){return P(t||e,e)}let je={};function G(){return je}function L(e,t){const n=G(),a=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,r=C(e,t?.in),i=r.getDay(),o=(i<a?7:0)+i-a;return r.setDate(r.getDate()-o),r.setHours(0,0,0,0),r}function H(e,t){return L(e,{...t,weekStartsOn:1})}function ge(e,t){const n=C(e,t?.in),a=n.getFullYear(),r=P(n,0);r.setFullYear(a+1,0,4),r.setHours(0,0,0,0);const i=H(r),o=P(n,0);o.setFullYear(a,0,4),o.setHours(0,0,0,0);const c=H(o);return n.getTime()>=i.getTime()?a+1:n.getTime()>=c.getTime()?a:a-1}function ce(e){const t=C(e),n=new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes(),t.getSeconds(),t.getMilliseconds()));return n.setUTCFullYear(t.getFullYear()),+e-+n}function Ne(e,...t){const n=P.bind(null,t.find(a=>typeof a=="object"));return t.map(n)}function ue(e,t){const n=C(e,t?.in);return n.setHours(0,0,0,0),n}function Ee(e,t,n){const[a,r]=Ne(n?.in,e,t),i=ue(a),o=ue(r),c=+i-ce(i),h=+o-ce(o);return Math.round((c-h)/Ie)}function We(e,t){const n=ge(e,t),a=P(e,0);return a.setFullYear(n,0,4),a.setHours(0,0,0,0),H(a)}function Fe(e){return e instanceof Date||typeof e=="object"&&Object.prototype.toString.call(e)==="[object Date]"}function ze(e){return!(!Fe(e)&&typeof e!="number"||isNaN(+C(e)))}function Re(e,t){const n=C(e,t?.in);return n.setFullYear(n.getFullYear(),0,1),n.setHours(0,0,0,0),n}const Ye={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}},qe=(e,t,n)=>{let a;const r=Ye[e];return typeof r=="string"?a=r:t===1?a=r.one:a=r.other.replace("{{count}}",t.toString()),n?.addSuffix?n.comparison&&n.comparison>0?"in "+a:a+" ago":a};function J(e){return(t={})=>{const n=t.width?String(t.width):e.defaultWidth;return e.formats[n]||e.formats[e.defaultWidth]}}const $e={full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},Le={full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},_e={full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},Be={date:J({formats:$e,defaultWidth:"full"}),time:J({formats:Le,defaultWidth:"full"}),dateTime:J({formats:_e,defaultWidth:"full"})},Ve={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"},He=(e,t,n,a)=>Ve[e];function z(e){return(t,n)=>{const a=n?.context?String(n.context):"standalone";let r;if(a==="formatting"&&e.formattingValues){const o=e.defaultFormattingWidth||e.defaultWidth,c=n?.width?String(n.width):o;r=e.formattingValues[c]||e.formattingValues[o]}else{const o=e.defaultWidth,c=n?.width?String(n.width):e.defaultWidth;r=e.values[c]||e.values[o]}const i=e.argumentCallback?e.argumentCallback(t):t;return r[i]}}const Ue={narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},Xe={narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter"
Line truncated
|
||||
:where(:root) {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
:where(.a2ui-dark) {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:where(.a2ui-light) {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
:where(:root), :where(.a2ui-dark), :where(.a2ui-light) {
|
||||
--a2ui-color-background: light-dark(#eee, #111);
|
||||
--a2ui-color-on-background: light-dark(#333, #eee);
|
||||
|
||||
--a2ui-color-surface: light-dark(
|
||||
color-mix(in oklab, var(--a2ui-color-background) 85%, white),
|
||||
color-mix(in oklab, var(--a2ui-color-background) 95%, white)
|
||||
);
|
||||
--a2ui-color-on-surface: light-dark(#333, #eee);
|
||||
|
||||
--a2ui-color-primary: #17e;
|
||||
--a2ui-color-primary-light: ${N("light",{colorVar:"--a2ui-color-primary"})};
|
||||
--a2ui-color-primary-dark: ${N("dark",{colorVar:"--a2ui-color-primary"})};
|
||||
--a2ui-color-primary-hover: ${N("hover",{darkVar:"--a2ui-color-primary-dark",lightVar:"--a2ui-color-primary-light"})};
|
||||
--a2ui-color-on-primary: #fff;
|
||||
|
||||
--a2ui-color-secondary: light-dark(#ddd, #333);
|
||||
--a2ui-color-secondary-light: ${N("light",{colorVar:"--a2ui-color-secondary"})};
|
||||
--a2ui-color-secondary-dark: ${N("dark",{colorVar:"--a2ui-color-secondary",percentage:95})};
|
||||
--a2ui-color-secondary-hover: ${N("hover",{darkVar:"--a2ui-color-secondary-dark",lightVar:"--a2ui-color-secondary-light"})};
|
||||
--a2ui-color-on-secondary: light-dark(#333, #eee);
|
||||
|
||||
--a2ui-border-radius: 0.25rem;
|
||||
--a2ui-color-border: light-dark(#ccc, #444);
|
||||
--a2ui-border-width: 1px;
|
||||
--a2ui-border: 1px solid var(--a2ui-color-border, #ccc);
|
||||
|
||||
--a2ui-font-family-title: inherit;
|
||||
--a2ui-font-family-monospace: monospace;
|
||||
--a2ui-color-input: light-dark(#fff, #2a2a2a);
|
||||
--a2ui-color-on-input: light-dark(#333, #eee);
|
||||
|
||||
--a2ui-grid-base: 0.5rem;
|
||||
--a2ui-spacing-xs: calc(var(--a2ui-spacing-s) / 2);
|
||||
--a2ui-spacing-s: calc(var(--a2ui-spacing-m) / 2);
|
||||
--a2ui-spacing-m: var(--a2ui-grid-base);
|
||||
--a2ui-spacing-l: calc(var(--a2ui-spacing-m) * 2);
|
||||
--a2ui-spacing-xl: calc(var(--a2ui-spacing-l) * 2);
|
||||
|
||||
--a2ui-font-size: 1rem;
|
||||
--a2ui-font-scale: 1.2;
|
||||
--a2ui-font-size-xs: calc(var(--a2ui-font-size-s) / var(--a2ui-font-scale));
|
||||
--a2ui-font-size-s: calc(var(--a2ui-font-size-m) / var(--a2ui-font-scale));
|
||||
--a2ui-font-size-m: var(--a2ui-font-size);
|
||||
--a2ui-font-size-l: calc(var(--a2ui-font-size-m) * var(--a2ui-font-scale));
|
||||
--a2ui-font-size-xl: calc(var(--a2ui-font-size-l) * var(--a2ui-font-scale));
|
||||
--a2ui-font-size-2xl: calc(var(--a2ui-font-size-xl) * var(--a2ui-font-scale));
|
||||
|
||||
--a2ui-line-height-headings: 1.2;
|
||||
--a2ui-line-height-body: 1.5;
|
||||
}
|
||||
`;let B;function na(){return B||(B=new CSSStyleSheet,B.replaceSync(ta)),B}function aa(){if(typeof document>"u")return;const e=na();document.adoptedStyleSheets.includes(e)||(document.adoptedStyleSheets=[...document.adoptedStyleSheets,e])}function N(e,t){switch(e){case"light":{const n=t;return`color-mix(in oklab, var(${n.colorVar}) ${n.percentage??85}%, ${n.mixColor??"white"})`}case"dark":{const n=t;return`color-mix(in oklab, var(${n.colorVar}) ${n.percentage??85}%, ${n.mixColor??"black"})`}case"hover":{const n=t;return`light-dark(var(${n.darkVar}), var(${n.lightVar}))`}}}var xe=y.memo(({surface:e,id:t,basePath:n,compImpl:a,componentModel:r})=>{const i=a.render,o=y.useMemo(()=>new Pe(e,t,n),[e,t,n,r]),c=y.useCallback((h,l)=>{const d=l||o.dataContext.path;return s.jsx(ne,{surface:e,id:h,basePath:d},`${h}-${d}`)},[e,o.dataContext.path]);return s.jsx(i,{context:o,buildChild:c})});xe.displayName="ResolvedChild";var ne=y.memo(({surface:e,id:t,basePath:n})=>{const a=y.useMemo(()=>{let o=0;return{subscribe:c=>{const h=e.componentsModel.onCreated.subscribe(d=>{d.id===t&&(o++,c())}),l=e.componentsModel.onDeleted.subscribe(d=>{d===t&&(o++,c())});return()=>{h.unsubscribe(),l.unsubscribe()}},getSnapshot:()=>{const c=e.componentsModel.get(t);return c?`${c.type}-${o}`:`missing-${o}`}}},[e,t]);y.useSyncExternalStore(a.subscribe,a.getSnapshot);const r=e.componentsModel.get(t);if(!r)return s.jsxs("div",{style:{color:"gray",padding:"4px"},children:["[Loading ",t,"...]"]});const i=e.catalog.components.get(r.type);return i?s.jsx(xe,{surface:e,id:t,basePath:n,componentModel:r,compImpl:i}):s.jsxs("div",{style:{color:"red"},children:["Unknown component: ",r.type]})});ne.displayName="DeferredChild";var ja=({surface:e})=>s.jsx(ne,{surface:e,id:"root",basePath:"/"});function w(e,t){const n=y.memo(t,(r,i)=>!(r.props!==i.props||r.context.componentModel.id!==i.context.componentModel.id||r.context.dataContext.path!==i.context.dataContext.path)),a=({context:r,buildChild:i})=>{const o=y.useRef(null);o.current?o.current.context!==r&&(o.current.dispose(),o.current=new ie(r,e.schema)):o.current=new ie(r,e.schema);const c=o.current,h=y.useCallback(f=>{const M=c.subscribe(f);return()=>M.unsubscribe()},[c]),l=y.useCallback(()=>c.snapshot,[c]),d=y.useSyncExternalStore(h,l);return y.useEffect(()=>()=>c.dispose(),[c]),s.jsx(n,{props:d||{},buildChild:i,context:r})};return{name:e.name,schema:e.schema,render:a}}function Na(e,t){return{name:e.name,schema:e.schema,render:t}}var k=()=>{y.useEffect(()=>{typeof document<"u"&&document.adoptedStyleSheets&&aa()},[])},we=e=>{switch(e){case"center":return"center";case"end":return"flex-end";case"spaceAround":return"space-around";case"spaceBetween":return"space-between";case"spaceEvenly":return"space-evenly";case"start":return"flex-start";case"stretch":return"stretch";default:return"flex-start"}},ae=e=>{switch(e){case"start":return"flex-start";case"center":return"center";case"end":return"flex-end";case"stretch":return"stretch";default:return"stretch"}},Q=()=>({boxSizing:"border-box"}),ra=()=>({boxSizing:"border-box"}),_=e=>typeof e!="number"?{}:{flex:`${e}`,minWidth:0,minHeight:0},ia=y.createContext(void 0),oa=()=>y.useContext(ia),fe=!1;function sa(e,t){const n=oa(),[a,r]=y.useState(null),i=JSON.stringify(t);return y.useEffect(()=>{if(!n){fe||(console.warn("[useMarkdown]",`can't render markdown because no markdown renderer is configured.
|
||||
`,"Use `@a2ui/markdown-it`, or your own markdown renderer."),fe=!0),r(null);return}let o=!0;const c=i?JSON.parse(i):void 0;return n(e,c).then(h=>{o&&r(h)}).catch(h=>{console.error("[useMarkdown] Render failed:",h)}),()=>{o=!1}},[e,n,i]),a}var pe={},ca=(e,t)=>{switch(t){case"h1":return`# ${e}`;case"h2":return`## ${e}`;case"h3":return`### ${e}`;case"h4":return`#### ${e}`;case"h5":return`##### ${e}`;case"caption":return`*${e}*`;default:return e}},ua=w(Fn,({props:e})=>{k();const t=typeof e.text=="string"?e.text:String(e.text??""),n=ca(t,e.variant),a=sa(n),r={...Q(),..._(e.weight)},i=e.variant==="caption",o=[pe.a2uiText,i?pe.a2uiCaption:e.variant||"body"];a===null&&o.push("no-markdown-renderer");const c=a!==null?{dangerouslySetInnerHTML:{__html:a}}:{children:n};return i?s.jsx("span",{className:o.join(" "),style:r,...c}):s.jsx("div",{className:o.join(" "),style:r,...c})}),la=w(zn,({props:e})=>{k();const t=a=>a==="scaleDown"?"scale-down":a||"fill",n={...Q(),..._(e.weight),objectFit:t(e.fit),display:"block",borderRadius:"var(--a2ui-image-border-radius, 0)"};return e.variant==="icon"?(n.width="var(--a2ui-image-icon-size, 24px)",n.height="var(--a2ui-image-icon-size, 24px)"):e.variant==="avatar"?(n.width="var(--a2ui-image-avatar-size, 40px)",n.height="var(--a2ui-image-avatar-size, 40px)",n.borderRadius="50%"):e.variant==="smallFeature"?n.maxWidth="var(--a2ui-image-small-feature-size, 100px)":e.variant==="largeFeature"?n.maxHeight="var(--a2ui-image-large-feature-size, 400px)":e.variant==="header"&&(n.height="var(--a2ui-image-header-size, 200px)",n.objectFit="cover"),s.jsx("img",{src:e.url,alt:e.description||"",style:n})}),da={play:"play_arrow",rewind:"fast_rewind",favoriteOff:"favorite_border",starOff:"star_border"};function ha(e){return da[e]??e.replace(/[A-Z]/g,t=>"_"+t.toLowerCase())}var ma=w(Yn,({props:e})=>{k();const t=typeof e.name=="object"&&e.name!==null&&"svgPath"in e.name,n={...Q(),display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:"var(--a2ui-icon-size, var(--a2ui-font-size-xl, 24px))",color:"var(--a2ui-icon-color, inherit)",lineHeight:1};if(t){const i=e.name.svgPath;return s.jsx("svg",{className:"a2ui-icon svg",viewBox:"0 0 24 24",style:{...n,fill:"currentColor",width:"var(--a2ui-icon-size, 24px)",height:"var(--a2ui-icon-size, 24px)"},children:s.jsx("path",{d:i})})}const a=typeof e.name=="string"?ha(e.name):"",r={...n,fontFamily:'var(--a2ui-icon-font-family, "Material Symbols Outlined", sans-serif)',fontVariationSettings:'var(--a2ui-icon-font-variation-settings, "FILL" 1)',fontWeight:"normal",fontStyle:"normal",letterSpacing:"normal",textTransform:"none"};return s.jsx("span",{className:"material-symbols-outlined",style:r,children:a})}),fa=w(qn,({props:e})=>{k();const t={...Q(),width:"100%",height:"auto",borderRadius:"var(--a2ui-video-border-radius, 0)"};return s.jsx("video",{src:e.url,controls:!0,style:t})}),pa=w($n,({props:e})=>{k();const t={display:"flex",flexDirection:"column",gap:"var(--a2ui-spacing-xs, 0.25rem)",background:"var(--a2ui-audioplayer-background, transparent)",borderRadius:"var(--a2ui-audioplayer-border-radius, 0)",padding:"var(--a2ui-audioplayer-padding, 0)"};return s.jsxs("div",{style:t,children:[e.description&&s.jsx("span",{style:{fontSize:"var(--a2ui-font-size-xs, 0.75rem)",color:"var(--a2ui-text-caption-color, light-dark(#666, #aaa))"},children:e.description}),s.jsx("audio",{src:e.url,controls:!0})]})}),re=({childList:e,buildChild:t})=>Array.isArray(e)?s.jsx(s.Fragment,{children:e.map((n,a)=>{if(n&&typeof n=="object"&&"id"in n){const r=n;return s.jsx(W.Fragment,{children:t(r.id,r.basePath)},`${r.id}-${a}`)}return typeof n=="string"?s.jsx(W.Fragment,{children:t(n)},`${n}-${a}`):null})}):null,ba=w(Ln,({props:e,buildChild:t,context:n})=>(k(),s.jsx("div",{style:{..._(e.weight),display:"flex",flexDirection:"row",justifyContent:we(e.justify),alignItems:ae(e.align),gap:"var(--a2ui-row-gap, var(--a2ui-spacing-m))"},children:s.jsx(re,{childList:e.children,buildChild:t,context:n})}))),ga=w(_n,({props:e,buildChild:t,context:n})=>(k(),s.jsx("div",{style:{..._(e.weight),display:"flex",flexDirection:"column",justifyContent:we(e.justify),alignItems:ae(e.align),gap:"var(--a2ui-column-gap, var(--a2ui-spacing-m))"},children:s.jsx(re,{childList:e.children,buildChild:t,context:n})}))),ya=w(Bn,({props:e,buildChild:t,context:n})=>{k();const a=e.direction==="horizontal",r={display:"flex",flexDirection:a?"row":"column",alignItems:ae(e.align),overflowX:a?"auto":"hidden",overflowY:a?"hidden":"auto",gap:"var(--a2ui-list-gap, var(--a2ui-spacing-s))",padding:"var(--a2ui-list-padding, 0)"};return s.jsx("div",{style:r,children:s.jsx(re,{childList:e.children,buildChild:t,context:n})})}),va=w(Vn,({props:e,buildChild:t})=>{k();const n={...ra(),..._(e.weight),display:"block",border:"var(--a2ui-card-border, var(--a2ui-border))",borderRadius:"var(--a2ui-card-border-radius, var(--a2ui-border-radius, 8px))",padding:"var(--a2ui-card-padding, var(--a2ui-spacing-m, 16px))",background:"var(--a2ui-card-b
Line truncated
|
||||
@@ -35,6 +35,60 @@ function el(tag, attrs, children) {
|
||||
return node;
|
||||
}
|
||||
|
||||
function relativeTime(value) {
|
||||
if (!value) return '';
|
||||
const then = typeof value === 'number' ? value * 1000 : Date.parse(value);
|
||||
if (!then || Number.isNaN(then)) return '';
|
||||
const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
|
||||
if (secs < 60) return `${secs}s ago`;
|
||||
const mins = Math.floor(secs / 60);
|
||||
if (mins < 60) return `${mins}m ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `${hrs}h ago`;
|
||||
const days = Math.floor(hrs / 24);
|
||||
return `${days}d ago`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text == null ? '' : text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function renderMarkdown(text) {
|
||||
const safe = escapeHtml(text);
|
||||
const lines = safe.split(/\r?\n/);
|
||||
const html = [];
|
||||
let inCode = false;
|
||||
let listOpen = false;
|
||||
const closeList = () => { if (listOpen) { html.push('</ul>'); listOpen = false; } };
|
||||
lines.forEach((raw) => {
|
||||
if (/^```/.test(raw.trim())) {
|
||||
if (inCode) { html.push('</code></pre>'); inCode = false; }
|
||||
else { closeList(); html.push('<pre><code>'); inCode = true; }
|
||||
return;
|
||||
}
|
||||
if (inCode) { html.push(raw + '\n'); return; }
|
||||
let line = raw;
|
||||
line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
line = line.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
|
||||
const heading = line.match(/^(#{1,3})\s+(.*)$/);
|
||||
if (heading) { closeList(); html.push(`<h${heading[1].length}>${heading[2]}</h${heading[1].length}>`); return; }
|
||||
const bullet = line.match(/^\s*[-*]\s+(.*)$/);
|
||||
if (bullet) { if (!listOpen) { html.push('<ul>'); listOpen = true; } html.push(`<li>${bullet[1]}</li>`); return; }
|
||||
closeList();
|
||||
if (line.trim() === '') html.push('<br/>');
|
||||
else html.push(`<p>${line}</p>`);
|
||||
});
|
||||
closeList();
|
||||
if (inCode) html.push('</code></pre>');
|
||||
return html.join('');
|
||||
}
|
||||
|
||||
function cardElement(card, { selectable, selected, onSelect, onOpen }) {
|
||||
const wrap = el('div', {
|
||||
className: `aiui-board-card${selected ? ' aiui-board-card-selected' : ''}`,
|
||||
@@ -42,18 +96,48 @@ function cardElement(card, { selectable, selected, onSelect, onOpen }) {
|
||||
'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 }));
|
||||
const inner = el('div', { className: 'db-card aiui-board-card-dense' });
|
||||
|
||||
const head = el('div', { className: 'aiui-board-card-head' });
|
||||
if (selectable && onSelect) {
|
||||
const box = el('input', {
|
||||
type: 'checkbox',
|
||||
className: 'aiui-board-card-check',
|
||||
'aria-label': 'Select task',
|
||||
});
|
||||
box.checked = !!selected;
|
||||
box.addEventListener('click', (e) => { e.stopPropagation(); onSelect(card.id, true); });
|
||||
head.appendChild(box);
|
||||
}
|
||||
if (card.id) head.appendChild(el('span', { className: 'aiui-board-card-id', text: card.id }));
|
||||
const colour = STATUS_COLOURS[card.status];
|
||||
if (card.status) {
|
||||
const dot = el('span', { className: 'aiui-board-status-dot', text: '●', title: card.status });
|
||||
if (colour) dot.style.color = colour;
|
||||
head.appendChild(dot);
|
||||
}
|
||||
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',
|
||||
head.appendChild(el('span', { className: 'aiui-board-badge aiui-board-priority', text: String(card.priority) }));
|
||||
}
|
||||
inner.appendChild(head);
|
||||
|
||||
if (card.title) inner.appendChild(el('div', { className: 'db-card-title aiui-board-card-name', text: card.title }));
|
||||
if (card.value != null) inner.appendChild(el('div', { className: 'db-card-value', text: String(card.value) }));
|
||||
|
||||
const foot = el('div', { className: 'aiui-board-card-foot' });
|
||||
if (card.tenant) foot.appendChild(el('span', { className: 'aiui-board-chip aiui-board-tenant', text: String(card.tenant) }));
|
||||
if (card.assignee) foot.appendChild(el('span', { className: 'aiui-board-chip', text: String(card.assignee) }));
|
||||
const age = relativeTime(card.created_at);
|
||||
if (age) foot.appendChild(el('span', { className: 'aiui-board-card-age', text: age }));
|
||||
if (card.comment_count) foot.appendChild(el('span', { className: 'aiui-board-card-count', text: `\uD83D\uDCAC ${card.comment_count}` }));
|
||||
if (card.progress && card.progress.total) {
|
||||
foot.appendChild(el('span', {
|
||||
className: 'aiui-board-progress',
|
||||
text: `${card.progress.done}/${card.progress.total}`,
|
||||
}));
|
||||
}
|
||||
if (foot.childNodes.length) inner.appendChild(foot);
|
||||
|
||||
wrap.appendChild(inner);
|
||||
wrap.addEventListener('click', (e) => {
|
||||
if (e.shiftKey && onSelect) onSelect(card.id, true);
|
||||
@@ -148,7 +232,9 @@ function renderBoardDOM(container, data, opts) {
|
||||
e.preventDefault();
|
||||
trash.classList.remove('aiui-board-drop-target');
|
||||
const taskId = e.dataTransfer.getData('text/plain');
|
||||
if (taskId && opts.onDelete) await opts.onDelete(taskId);
|
||||
if (!taskId || !opts.onDelete) return;
|
||||
if (typeof confirm === 'function' && !confirm('Permanently delete this task?')) return;
|
||||
await opts.onDelete(taskId);
|
||||
});
|
||||
wrap.appendChild(trash);
|
||||
}
|
||||
@@ -156,46 +242,170 @@ function renderBoardDOM(container, data, opts) {
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function openTaskDrawer(card, { apiBase, onClose, onRefresh }) {
|
||||
function historyRows(card) {
|
||||
const meta = card.meta || {};
|
||||
const rows = [];
|
||||
const events = meta.events || meta.history;
|
||||
if (Array.isArray(events)) {
|
||||
events.forEach((ev) => {
|
||||
if (typeof ev === 'string') rows.push({ text: ev });
|
||||
else if (ev && typeof ev === 'object') {
|
||||
const label = ev.type || ev.event || ev.status || 'event';
|
||||
const when = relativeTime(ev.ts || ev.created_at || ev.time);
|
||||
rows.push({ text: ev.text || ev.message || label, when });
|
||||
}
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function openTaskDrawer(card, { apiBase, onClose, onRefresh, fetchTask }) {
|
||||
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);
|
||||
let current = card;
|
||||
let closed = false;
|
||||
const close = () => { closed = true; overlay.remove(); if (onClose) onClose(); };
|
||||
|
||||
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();
|
||||
},
|
||||
}));
|
||||
const editable = !!(apiBase && card.id);
|
||||
|
||||
async function patch(data) {
|
||||
if (!editable) return;
|
||||
await fetch(`${apiBase}/tasks/${current.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
drawer.appendChild(actions);
|
||||
if (onRefresh) await onRefresh();
|
||||
await reload();
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
if (closed) return;
|
||||
if (fetchTask && current.id) {
|
||||
try {
|
||||
const fresh = await fetchTask(current.id);
|
||||
if (fresh) current = { ...current, ...fresh };
|
||||
} catch (_) { /* keep stale */ }
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
function field(label, value, onSave) {
|
||||
const row = el('div', { className: 'aiui-board-drawer-field' });
|
||||
row.appendChild(el('label', { className: 'aiui-board-drawer-label', text: label }));
|
||||
const input = el('input', { className: 'aiui-board-drawer-input', value: value == null ? '' : String(value) });
|
||||
if (!editable) input.disabled = true;
|
||||
input.addEventListener('change', () => onSave(input.value));
|
||||
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onSave(input.value); } });
|
||||
row.appendChild(input);
|
||||
return row;
|
||||
}
|
||||
|
||||
function render() {
|
||||
drawer.innerHTML = '';
|
||||
drawer.appendChild(el('button', { className: 'aiui-board-drawer-close', text: '×', onclick: close }));
|
||||
|
||||
drawer.appendChild(field('Title', current.title, (v) => patch({ title: v })));
|
||||
|
||||
if (current.body != null) {
|
||||
const bodyEl = el('div', { className: 'aiui-board-drawer-body aiui-board-markdown' });
|
||||
bodyEl.innerHTML = renderMarkdown(current.body);
|
||||
drawer.appendChild(bodyEl);
|
||||
}
|
||||
|
||||
const grid = el('div', { className: 'aiui-board-drawer-grid' });
|
||||
grid.appendChild(field('Assignee', current.assignee, (v) => patch({ assignee: v })));
|
||||
grid.appendChild(field('Priority', current.priority, (v) => patch({ priority: v })));
|
||||
drawer.appendChild(grid);
|
||||
|
||||
const meta = el('div', { className: 'aiui-board-drawer-meta' });
|
||||
meta.textContent = [current.status, current.tenant, relativeTime(current.created_at)].filter(Boolean).join(' · ');
|
||||
drawer.appendChild(meta);
|
||||
|
||||
const hist = historyRows(current);
|
||||
if (hist.length) {
|
||||
drawer.appendChild(el('h4', { className: 'aiui-board-drawer-h', text: 'History' }));
|
||||
const histWrap = el('div', { className: 'aiui-board-history' });
|
||||
hist.forEach((r) => {
|
||||
const row = el('div', { className: 'aiui-board-history-row' });
|
||||
row.appendChild(el('span', { className: 'aiui-board-history-text', text: r.text }));
|
||||
if (r.when) row.appendChild(el('span', { className: 'aiui-board-history-when', text: r.when }));
|
||||
histWrap.appendChild(row);
|
||||
});
|
||||
drawer.appendChild(histWrap);
|
||||
}
|
||||
|
||||
drawer.appendChild(el('h4', { className: 'aiui-board-drawer-h', text: 'Comments' }));
|
||||
const thread = el('div', { className: 'aiui-board-comments' });
|
||||
(current.comments || []).forEach((c) => {
|
||||
const item = el('div', { className: 'aiui-board-comment' });
|
||||
const head = el('div', { className: 'aiui-board-comment-head' });
|
||||
head.appendChild(el('span', { className: 'aiui-board-comment-author', text: c.author || 'human' }));
|
||||
const when = relativeTime(c.created_at);
|
||||
if (when) head.appendChild(el('span', { className: 'aiui-board-comment-when', text: when }));
|
||||
item.appendChild(head);
|
||||
const text = el('div', { className: 'aiui-board-comment-text aiui-board-markdown' });
|
||||
text.innerHTML = renderMarkdown(typeof c === 'string' ? c : (c.text || ''));
|
||||
item.appendChild(text);
|
||||
thread.appendChild(item);
|
||||
});
|
||||
drawer.appendChild(thread);
|
||||
|
||||
if (editable) {
|
||||
const box = el('textarea', {
|
||||
className: 'aiui-board-comment-input',
|
||||
placeholder: 'Add a comment… (Enter to submit, Shift+Enter for newline)',
|
||||
rows: '2',
|
||||
});
|
||||
const submit = async () => {
|
||||
const value = box.value.trim();
|
||||
if (!value) return;
|
||||
box.value = '';
|
||||
await fetch(`${apiBase}/tasks/${current.id}/comments`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: value }),
|
||||
});
|
||||
if (onRefresh) await onRefresh();
|
||||
await reload();
|
||||
};
|
||||
box.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
|
||||
});
|
||||
drawer.appendChild(box);
|
||||
|
||||
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 () => {
|
||||
if ((st === 'done' || st === 'blocked') && !confirm(`Move task to "${st}"?`)) return;
|
||||
await fetch(`${apiBase}/tasks/${current.id}/move`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: st }),
|
||||
});
|
||||
if (onRefresh) await onRefresh();
|
||||
close();
|
||||
},
|
||||
}));
|
||||
});
|
||||
drawer.appendChild(actions);
|
||||
}
|
||||
}
|
||||
|
||||
render();
|
||||
overlay.appendChild(drawer);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
return {
|
||||
id: card.id,
|
||||
isOpen: () => !closed,
|
||||
refresh: reload,
|
||||
close,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,8 +415,11 @@ export function createInteractiveBoard(root, opts = {}) {
|
||||
let destroyed = false;
|
||||
let pollTimer = null;
|
||||
let eventSource = null;
|
||||
let debounceTimer = null;
|
||||
let openDrawer = null;
|
||||
const selected = new Set();
|
||||
let lastData = null;
|
||||
const debounceMs = opts.debounceMs != null ? opts.debounceMs : 300;
|
||||
|
||||
const toolbar = el('div', { className: 'aiui-board-toolbar' });
|
||||
root.appendChild(toolbar);
|
||||
@@ -231,6 +444,14 @@ export function createInteractiveBoard(root, opts = {}) {
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
}
|
||||
|
||||
const drawerApiBase = (opts.drawerApiBase || apiBase).replace(/\/$/, '');
|
||||
|
||||
async function fetchTask(taskId) {
|
||||
const res = await fetch(`${drawerApiBase}/tasks/${taskId}`);
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function defaultBulk(status) {
|
||||
if (!selected.size) return;
|
||||
const res = await fetch(`${apiBase}/tasks/bulk`, {
|
||||
@@ -303,7 +524,15 @@ export function createInteractiveBoard(root, opts = {}) {
|
||||
showTrash: opts.showTrash !== false,
|
||||
selected,
|
||||
onSelect: opts.interactive !== false ? onSelect : null,
|
||||
onOpen: (card) => openTaskDrawer(card, { apiBase: opts.drawerApiBase || apiBase, onRefresh: refresh }),
|
||||
onOpen: (card) => {
|
||||
if (openDrawer && openDrawer.isOpen()) openDrawer.close();
|
||||
openDrawer = openTaskDrawer(card, {
|
||||
apiBase: drawerApiBase,
|
||||
onRefresh: refresh,
|
||||
fetchTask,
|
||||
onClose: () => { openDrawer = null; },
|
||||
});
|
||||
},
|
||||
onMove: async (taskId, status) => {
|
||||
await (opts.onMove || defaultMove)(taskId, status);
|
||||
await refresh();
|
||||
@@ -319,12 +548,28 @@ export function createInteractiveBoard(root, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function debouncedRefresh() {
|
||||
if (!debounceMs) { refresh(); return; }
|
||||
if (debounceTimer) clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => { debounceTimer = null; refresh(); }, debounceMs);
|
||||
}
|
||||
|
||||
function handleEvent(raw) {
|
||||
let ev = null;
|
||||
try { ev = raw ? JSON.parse(raw) : null; } catch (_) { ev = null; }
|
||||
if (openDrawer && openDrawer.isOpen() && ev && ev.task_id && ev.task_id === openDrawer.id) {
|
||||
openDrawer.refresh();
|
||||
}
|
||||
debouncedRefresh();
|
||||
}
|
||||
|
||||
function connectEvents() {
|
||||
if (!opts.eventsUrl || typeof EventSource === 'undefined') return;
|
||||
const url = opts.eventsUrl + (opts.eventsUrl.includes('?') ? '&' : '?') + `board=${encodeURIComponent(opts.currentBoard || 'default')}`;
|
||||
const activeBoard = opts.boardState?.id || opts.currentBoard || 'default';
|
||||
const url = opts.eventsUrl + (opts.eventsUrl.includes('?') ? '&' : '?') + `board=${encodeURIComponent(activeBoard)}`;
|
||||
try {
|
||||
eventSource = new EventSource(url);
|
||||
eventSource.onmessage = () => refresh();
|
||||
eventSource.onmessage = (e) => handleEvent(e && e.data);
|
||||
} catch (_) { /* polling fallback */ }
|
||||
}
|
||||
|
||||
@@ -337,7 +582,9 @@ export function createInteractiveBoard(root, opts = {}) {
|
||||
destroy() {
|
||||
destroyed = true;
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
if (debounceTimer) clearTimeout(debounceTimer);
|
||||
if (eventSource) eventSource.close();
|
||||
if (openDrawer && openDrawer.isOpen()) openDrawer.close();
|
||||
root.innerHTML = '';
|
||||
},
|
||||
};
|
||||
@@ -348,4 +595,4 @@ export function createBoard(root, opts = {}) {
|
||||
return createInteractiveBoard(root, { ...opts, interactive: false, showTrash: false });
|
||||
}
|
||||
|
||||
export { renderBoardDOM, el };
|
||||
export { renderBoardDOM, el, renderMarkdown, relativeTime, escapeHtml };
|
||||
@@ -9,6 +9,123 @@
|
||||
// Record load time for overview stats
|
||||
window.__aiuiLoadTime = window.__aiuiLoadTime || Date.now();
|
||||
|
||||
// ── Approval Modal System ────────────────────────────────────────────
|
||||
// Handles approval_required events from SSE stream
|
||||
let approvalModalShown = false;
|
||||
let pendingApprovals = [];
|
||||
let approvalSSESource = null;
|
||||
|
||||
function showApprovalModal(approval) {
|
||||
if (approvalModalShown) {
|
||||
pendingApprovals.push(approval);
|
||||
return;
|
||||
}
|
||||
approvalModalShown = true;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'db-modal-overlay';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;animation:fadeIn 0.2s';
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'db-approval-modal';
|
||||
modal.style.cssText = 'background:var(--db-card-bg);border:1px solid var(--db-border);border-radius:12px;padding:24px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto;animation:slideIn 0.2s;backdrop-filter:blur(10px)';
|
||||
|
||||
const riskIcons = {low: '✅', medium: '⚠️', high: '🟠', critical: '🔴'};
|
||||
const icon = riskIcons[approval.risk_level] || '⚠️';
|
||||
|
||||
modal.innerHTML = `
|
||||
<h2 style="margin:0 0 16px;font-size:20px;font-weight:600;color:var(--db-text)">Tool Execution Approval Required</h2>
|
||||
<div style="padding:16px;background:rgba(255,255,255,0.03);border-radius:8px;margin-bottom:20px">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
||||
<span style="font-size:24px">${icon}</span>
|
||||
<div>
|
||||
<div style="font-weight:500;font-size:16px">${approval.tool_name || 'Unknown Tool'}</div>
|
||||
<div style="font-size:13px;color:var(--db-text-dim);margin-top:4px">Risk Level: ${approval.risk_level || 'medium'}</div>
|
||||
</div>
|
||||
</div>
|
||||
${approval.agent_name ? `<div style="font-size:13px;color:var(--db-text-dim);margin-top:8px">Agent: ${approval.agent_name}</div>` : ''}
|
||||
${approval.description ? `<div style="font-size:13px;margin-top:8px">${approval.description}</div>` : ''}
|
||||
${approval.arguments ? `<details style="margin-top:12px"><summary style="cursor:pointer;font-size:13px;color:var(--db-text-dim)">Arguments</summary><pre style="font-size:11px;margin:8px 0 0;padding:8px;background:rgba(0,0,0,0.2);border-radius:4px;overflow-x:auto">${JSON.stringify(approval.arguments, null, 2)}</pre></details>` : ''}
|
||||
</div>
|
||||
<div style="margin-bottom:16px">
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px">
|
||||
<input type="checkbox" id="approval-always" style="cursor:pointer">
|
||||
<span>Always ${approval.risk_level === 'critical' ? 'deny' : 'allow'} this tool</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;justify-content:flex-end">
|
||||
<button id="approval-deny" style="padding:8px 20px;background:rgba(239,68,68,0.15);color:#ef4444;border:1px solid rgba(239,68,68,0.3);border-radius:6px;cursor:pointer;font-size:14px;font-weight:500">✗ Deny</button>
|
||||
<button id="approval-approve" style="padding:8px 20px;background:rgba(34,197,94,0.15);color:#22c55e;border:1px solid rgba(34,197,94,0.3);border-radius:6px;cursor:pointer;font-size:14px;font-weight:500">✓ Approve</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
async function resolveApproval(approved) {
|
||||
const always = document.getElementById('approval-always').checked;
|
||||
const endpoint = approved ? `/api/approvals/${approval.id}/approve` : `/api/approvals/${approval.id}/deny`;
|
||||
|
||||
try {
|
||||
await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({always, reason: approved ? 'Approved via UI' : 'Denied via UI'})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to resolve approval:', e);
|
||||
}
|
||||
|
||||
overlay.style.animation = 'fadeOut 0.2s';
|
||||
setTimeout(() => {
|
||||
overlay.remove();
|
||||
approvalModalShown = false;
|
||||
if (pendingApprovals.length > 0) {
|
||||
showApprovalModal(pendingApprovals.shift());
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
document.getElementById('approval-approve').addEventListener('click', () => resolveApproval(true));
|
||||
document.getElementById('approval-deny').addEventListener('click', () => resolveApproval(false));
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
resolveApproval(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initApprovalStream() {
|
||||
if (approvalSSESource) return;
|
||||
|
||||
try {
|
||||
approvalSSESource = new EventSource('/api/approvals/stream');
|
||||
|
||||
approvalSSESource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === 'new' && data.data) {
|
||||
showApprovalModal(data.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse approval SSE:', e);
|
||||
}
|
||||
};
|
||||
|
||||
approvalSSESource.onerror = () => {
|
||||
console.warn('Approval SSE connection error, retrying...');
|
||||
if (approvalSSESource) {
|
||||
approvalSSESource.close();
|
||||
approvalSSESource = null;
|
||||
}
|
||||
setTimeout(initApprovalStream, 5000);
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to init approval stream:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Extensible Component Registry ────────────────────────────────────
|
||||
// Maps component type names to custom render functions.
|
||||
// Custom renderers take priority over the built-in switch in renderComponent().
|
||||
@@ -57,6 +174,7 @@ const BUILTIN_VIEWS = {
|
||||
'jobs-board': '/plugins/views/jobs-board.js',
|
||||
auth: '/plugins/views/auth.js',
|
||||
api: '/plugins/views/api.js',
|
||||
mcp: '/plugins/views/mcp.js',
|
||||
};
|
||||
|
||||
const SLOT_REGISTRY = {};
|
||||
@@ -290,6 +408,37 @@ const DASHBOARD_STYLE = `
|
||||
.aiui-board-drawer-body { white-space: pre-wrap; font-size: 13px; color: var(--db-text-dim); }
|
||||
.aiui-board-drawer-meta { font-size: 12px; color: var(--db-text-dim); margin: 8px 0; }
|
||||
.aiui-board-drawer-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
|
||||
.aiui-board-card-dense { padding: 10px 12px; }
|
||||
.aiui-board-card-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
|
||||
.aiui-board-card-check { margin: 0; cursor: pointer; }
|
||||
.aiui-board-card-id { font-family: var(--db-mono, monospace); font-size: 10px; color: var(--db-text-dim); }
|
||||
.aiui-board-status-dot { font-size: 10px; line-height: 1; }
|
||||
.aiui-board-priority { margin-left: auto; font-size: 10px; opacity: 0.85; }
|
||||
.aiui-board-card-name { font-size: 13px; text-transform: none; letter-spacing: 0; color: var(--db-text); margin: 0 0 6px; font-weight: 500; }
|
||||
.aiui-board-card-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11px; color: var(--db-text-dim); }
|
||||
.aiui-board-chip { padding: 1px 6px; border-radius: 999px; background: rgba(255,255,255,0.06); font-size: 10px; }
|
||||
.aiui-board-tenant { background: var(--db-accent-glow); color: var(--db-accent); }
|
||||
.aiui-board-card-age { opacity: 0.8; }
|
||||
.aiui-board-card-count { opacity: 0.9; }
|
||||
.aiui-board-progress { margin-left: auto; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--db-border); font-size: 10px; }
|
||||
.aiui-board-drawer-field { margin: 8px 0; }
|
||||
.aiui-board-drawer-label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin-bottom: 4px; }
|
||||
.aiui-board-drawer-input { width: 100%; padding: 6px 8px; background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); color: var(--db-text); font-size: 13px; }
|
||||
.aiui-board-drawer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.aiui-board-drawer-h { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin: 16px 0 8px; }
|
||||
.aiui-board-markdown { font-size: 13px; line-height: 1.5; color: var(--db-text-dim); }
|
||||
.aiui-board-markdown p { margin: 0 0 6px; }
|
||||
.aiui-board-markdown pre { background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); padding: 8px; overflow-x: auto; }
|
||||
.aiui-board-markdown code { font-family: var(--db-mono, monospace); font-size: 12px; }
|
||||
.aiui-board-history { display: flex; flex-direction: column; gap: 4px; }
|
||||
.aiui-board-history-row { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--db-text-dim); }
|
||||
.aiui-board-history-when { opacity: 0.7; white-space: nowrap; }
|
||||
.aiui-board-comments { display: flex; flex-direction: column; gap: 10px; }
|
||||
.aiui-board-comment { border-left: 2px solid var(--db-border); padding-left: 10px; }
|
||||
.aiui-board-comment-head { display: flex; gap: 8px; align-items: baseline; }
|
||||
.aiui-board-comment-author { font-size: 12px; font-weight: 600; color: var(--db-text); }
|
||||
.aiui-board-comment-when { font-size: 11px; color: var(--db-text-dim); }
|
||||
.aiui-board-comment-input { width: 100%; margin-top: 10px; padding: 8px; background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); color: var(--db-text); font-size: 13px; resize: vertical; }
|
||||
.aiui-board-page-header { margin-bottom: 8px; }
|
||||
.db-slot-host { min-height: 0; }
|
||||
.db-sidebar-footer { margin-top: auto; padding: 10px 14px; border-top: 1px solid var(--db-border); font-size: 11px; color: var(--db-text-dim); }
|
||||
@@ -576,6 +725,19 @@ const DASHBOARD_STYLE = `
|
||||
.db-dialog-title { font-size: 18px; font-weight: 600; margin: 0 0 4px; }
|
||||
.db-dialog-desc { font-size: 13px; color: var(--db-text-dim); margin: 0 0 16px; }
|
||||
|
||||
/* ── Session Search Palette (Ctrl+K) ─────────────────── */
|
||||
.db-session-search-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 300; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; backdrop-filter: blur(4px); }
|
||||
.db-session-search { background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: 12px; width: 560px; max-width: 90vw; max-height: 60vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,0.4); }
|
||||
.db-session-search-input { width: 100%; padding: 16px 18px; font-size: 15px; color: var(--db-text); background: transparent; border: none; border-bottom: 1px solid var(--db-border); outline: none; box-sizing: border-box; }
|
||||
.db-session-search-list { overflow-y: auto; padding: 6px; }
|
||||
.db-session-search-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--db-text-dim); }
|
||||
.db-session-search-group { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); padding: 10px 12px 4px; }
|
||||
.db-session-search-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-radius: 8px; cursor: pointer; }
|
||||
.db-session-search-item:hover, .db-session-search-item.active { background: var(--db-hover); }
|
||||
.db-session-search-item-title { font-size: 14px; font-weight: 500; color: var(--db-text); }
|
||||
.db-session-search-item-meta { font-size: 12px; color: var(--db-text-dim); }
|
||||
.db-session-search-item-current { font-size: 11px; padding: 2px 8px; border-radius: 6px; background: var(--db-accent); color: #fff; }
|
||||
|
||||
/* ── Caption ─────────────────────────────────────────── */
|
||||
.db-caption { font-size: 12px; color: var(--db-text-dim); margin-bottom: 8px; }
|
||||
|
||||
@@ -625,6 +787,25 @@ const DASHBOARD_STYLE = `
|
||||
.db-popover { position: relative; display: inline-block; }
|
||||
.db-popover-content { display: none; position: absolute; top: calc(100% + 8px); left: 0; min-width: 200px; padding: 16px; background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); z-index: 100; box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
|
||||
.db-popover.open .db-popover-content { display: block; }
|
||||
|
||||
/* ── Approval Modal Animations ──────────────────────── */
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||||
|
||||
/* ── Command Palette (Ctrl+K) ────────────────────────── */
|
||||
.db-cmdk-overlay { position: fixed; inset: 0; z-index: 10002; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); animation: fadeIn 0.15s ease; }
|
||||
.db-cmdk-box { width: 560px; max-width: 92vw; background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: 12px; box-shadow: 0 16px 60px rgba(0,0,0,0.55); overflow: hidden; animation: slideIn 0.15s ease; }
|
||||
.db-cmdk-input { width: 100%; box-sizing: border-box; padding: 16px 20px; font-size: 15px; color: var(--db-text); background: transparent; border: none; border-bottom: 1px solid var(--db-border); outline: none; }
|
||||
.db-cmdk-input::placeholder { color: var(--db-text-dim); }
|
||||
.db-cmdk-list { max-height: 50vh; overflow-y: auto; padding: 6px; }
|
||||
.db-cmdk-item { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 8px; cursor: pointer; font-size: 14px; color: var(--db-text); }
|
||||
.db-cmdk-item.active { background: var(--db-accent); color: #fff; }
|
||||
.db-cmdk-item.active .db-cmdk-group { color: rgba(255,255,255,0.8); }
|
||||
.db-cmdk-icon { width: 20px; text-align: center; flex-shrink: 0; }
|
||||
.db-cmdk-title { flex: 1; }
|
||||
.db-cmdk-group { font-size: 11px; color: var(--db-text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.db-cmdk-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--db-text-dim); }
|
||||
`;
|
||||
|
||||
let activePageId = null;
|
||||
@@ -787,6 +968,284 @@ function setupMobileNav(root, sidebar) {
|
||||
}
|
||||
}
|
||||
|
||||
let _paletteEl = null;
|
||||
let _paletteActive = -1;
|
||||
let _paletteResults = [];
|
||||
|
||||
function openCommandPalette() {
|
||||
if (_paletteEl) { closeCommandPalette(); return; }
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'db-cmdk-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="db-cmdk-box" role="dialog" aria-label="Command palette">
|
||||
<input class="db-cmdk-input" type="text" placeholder="Search pages and actions…" autocomplete="off" spellcheck="false">
|
||||
<div class="db-cmdk-list"></div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
_paletteEl = overlay;
|
||||
const input = overlay.querySelector('.db-cmdk-input');
|
||||
const listEl = overlay.querySelector('.db-cmdk-list');
|
||||
|
||||
const sessionAction = { id: '__sessions', title: 'Search sessions…', icon: '⌕', group: 'Actions', action: 'sessions' };
|
||||
const renderResults = (q) => {
|
||||
const query = q.trim().toLowerCase();
|
||||
const candidates = pagesData
|
||||
.filter((p) => p.id !== activePageId || query)
|
||||
.concat([sessionAction]);
|
||||
const items = candidates
|
||||
.map((p) => ({ page: p, score: paletteScore(p, query) }))
|
||||
.filter((x) => query === '' || x.score > 0)
|
||||
.sort((a, b) => b.score - a.score || (a.page.order || 0) - (b.page.order || 0))
|
||||
.slice(0, 50)
|
||||
.map((x) => x.page);
|
||||
_paletteResults = items;
|
||||
_paletteActive = items.length ? 0 : -1;
|
||||
listEl.innerHTML = items.length
|
||||
? items
|
||||
.map(
|
||||
(p, i) =>
|
||||
`<div class="db-cmdk-item${i === 0 ? ' active' : ''}" data-idx="${i}" data-id="${p.id}"><span class="db-cmdk-icon">${p.icon || '•'}</span><span class="db-cmdk-title">${p.title}</span>${p.group ? `<span class="db-cmdk-group">${p.group}</span>` : ''}</div>`
|
||||
)
|
||||
.join('')
|
||||
: '<div class="db-cmdk-empty">No matches</div>';
|
||||
listEl.querySelectorAll('.db-cmdk-item').forEach((el) => {
|
||||
el.addEventListener('click', () => runPaletteItem(parseInt(el.dataset.idx, 10)));
|
||||
el.addEventListener('mousemove', () => setPaletteActive(parseInt(el.dataset.idx, 10)));
|
||||
});
|
||||
};
|
||||
|
||||
input.addEventListener('input', () => renderResults(input.value));
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); setPaletteActive(_paletteActive + 1); }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); setPaletteActive(_paletteActive - 1); }
|
||||
else if (e.key === 'Enter') { e.preventDefault(); runPaletteItem(_paletteActive); }
|
||||
else if (e.key === 'Escape') { e.preventDefault(); closeCommandPalette(); }
|
||||
});
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeCommandPalette(); });
|
||||
renderResults('');
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function paletteScore(page, query) {
|
||||
if (!query) return 1;
|
||||
const hay = `${page.title} ${page.id} ${page.group || ''} ${page.description || ''}`.toLowerCase();
|
||||
if (hay.includes(query)) return 100 - hay.indexOf(query);
|
||||
let qi = 0;
|
||||
for (let i = 0; i < hay.length && qi < query.length; i++) {
|
||||
if (hay[i] === query[qi]) qi++;
|
||||
}
|
||||
return qi === query.length ? 1 : 0;
|
||||
}
|
||||
|
||||
function setPaletteActive(idx) {
|
||||
if (!_paletteEl || !_paletteResults.length) return;
|
||||
const n = _paletteResults.length;
|
||||
_paletteActive = ((idx % n) + n) % n;
|
||||
const items = _paletteEl.querySelectorAll('.db-cmdk-item');
|
||||
items.forEach((el, i) => el.classList.toggle('active', i === _paletteActive));
|
||||
const activeEl = items[_paletteActive];
|
||||
if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function runPaletteItem(idx) {
|
||||
const page = _paletteResults[idx];
|
||||
if (!page) return;
|
||||
closeCommandPalette();
|
||||
if (page.action === 'sessions') {
|
||||
openSessionSearch();
|
||||
return;
|
||||
}
|
||||
selectPage(page.id);
|
||||
}
|
||||
|
||||
function closeCommandPalette() {
|
||||
if (_paletteEl) { _paletteEl.remove(); _paletteEl = null; }
|
||||
_paletteActive = -1;
|
||||
_paletteResults = [];
|
||||
}
|
||||
|
||||
function initCommandPalette() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
|
||||
e.preventDefault();
|
||||
openCommandPalette();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Session Search Palette ──────────────────────────────────────────
|
||||
// Vanilla-JS port of the React SessionSearch / useSessionSearch behavior so
|
||||
// that the dashboard style reaches C-01 parity. The palette lists sessions
|
||||
// from GET /sessions with live filtering; Enter or click navigates to the
|
||||
// chat page and loads the chosen session. It is reachable from the Ctrl+K
|
||||
// command palette ("Search sessions…") and via window.aiui.openSessionSearch.
|
||||
let sessionSearchOpen = false;
|
||||
let sessionSearchOverlay = null;
|
||||
let sessionSearchInitialized = false;
|
||||
|
||||
function getCurrentSessionId() {
|
||||
try {
|
||||
const params = new URLSearchParams(location.search);
|
||||
return params.get('session') || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function selectSession(sessionId) {
|
||||
closeSessionSearch();
|
||||
// Navigate to the chat page first so its view mounts and registers the
|
||||
// aiui:session-select listener, then dispatch on the next tick.
|
||||
const navigate = (typeof selectPage === 'function')
|
||||
? Promise.resolve(selectPage('chat'))
|
||||
: Promise.resolve();
|
||||
navigate.finally(() => {
|
||||
window.dispatchEvent(new CustomEvent('aiui:session-select', { detail: { sessionId } }));
|
||||
});
|
||||
}
|
||||
|
||||
function renderSessionSearchList(listEl, sessions, query, currentSessionId) {
|
||||
const q = (query || '').trim().toLowerCase();
|
||||
const filtered = q
|
||||
? sessions.filter((s) => (`${s.title || ''} ${s.id || ''}`).toLowerCase().includes(q))
|
||||
: sessions;
|
||||
|
||||
listEl.innerHTML = '';
|
||||
if (!filtered.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'db-session-search-empty';
|
||||
empty.textContent = sessions.length ? 'No sessions matching your search.' : 'No sessions found. Start a chat to create one.';
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach((session, i) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'db-session-search-item' + (i === 0 ? ' active' : '');
|
||||
item.dataset.sessionId = session.id || '';
|
||||
|
||||
const info = document.createElement('div');
|
||||
const title = document.createElement('div');
|
||||
title.className = 'db-session-search-item-title';
|
||||
title.textContent = session.title || 'Untitled Session';
|
||||
info.appendChild(title);
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'db-session-search-item-meta';
|
||||
const count = session.message_count !== undefined ? `${session.message_count} messages` : '';
|
||||
meta.textContent = count;
|
||||
info.appendChild(meta);
|
||||
item.appendChild(info);
|
||||
|
||||
if (currentSessionId && currentSessionId === session.id) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'db-session-search-item-current';
|
||||
badge.textContent = 'Current';
|
||||
item.appendChild(badge);
|
||||
}
|
||||
|
||||
item.addEventListener('click', () => selectSession(session.id));
|
||||
item.addEventListener('mouseenter', () => {
|
||||
listEl.querySelectorAll('.db-session-search-item').forEach((el) => el.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
});
|
||||
listEl.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async function openSessionSearch() {
|
||||
if (sessionSearchOpen) return;
|
||||
sessionSearchOpen = true;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'db-session-search-overlay';
|
||||
const palette = document.createElement('div');
|
||||
palette.className = 'db-session-search';
|
||||
palette.setAttribute('role', 'dialog');
|
||||
palette.setAttribute('aria-label', 'Search sessions');
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.className = 'db-session-search-input';
|
||||
input.type = 'text';
|
||||
input.placeholder = 'Search sessions...';
|
||||
input.setAttribute('aria-label', 'Search sessions');
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'db-session-search-list';
|
||||
list.innerHTML = '<div class="db-session-search-empty">Loading sessions...</div>';
|
||||
|
||||
palette.appendChild(input);
|
||||
palette.appendChild(list);
|
||||
overlay.appendChild(palette);
|
||||
document.body.appendChild(overlay);
|
||||
sessionSearchOverlay = overlay;
|
||||
|
||||
const currentSessionId = getCurrentSessionId();
|
||||
let sessions = [];
|
||||
|
||||
let debounce = null;
|
||||
input.addEventListener('input', () => {
|
||||
if (debounce) clearTimeout(debounce);
|
||||
debounce = setTimeout(() => {
|
||||
renderSessionSearchList(list, sessions, input.value, currentSessionId);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const active = list.querySelector('.db-session-search-item.active') || list.querySelector('.db-session-search-item');
|
||||
if (active && active.dataset.sessionId) selectSession(active.dataset.sessionId);
|
||||
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
const items = Array.from(list.querySelectorAll('.db-session-search-item'));
|
||||
if (!items.length) return;
|
||||
let idx = items.findIndex((el) => el.classList.contains('active'));
|
||||
items.forEach((el) => el.classList.remove('active'));
|
||||
idx = e.key === 'ArrowDown' ? Math.min(items.length - 1, idx + 1) : Math.max(0, idx - 1);
|
||||
items[idx].classList.add('active');
|
||||
items[idx].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSessionSearch(); });
|
||||
|
||||
try {
|
||||
const res = await fetch('/sessions');
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
sessions = data.sessions || [];
|
||||
} catch (err) {
|
||||
console.error('[AIUI] Failed to fetch sessions:', err);
|
||||
sessions = [];
|
||||
}
|
||||
renderSessionSearchList(list, sessions, '', currentSessionId);
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function closeSessionSearch() {
|
||||
sessionSearchOpen = false;
|
||||
if (sessionSearchOverlay) {
|
||||
sessionSearchOverlay.remove();
|
||||
sessionSearchOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
function initSessionSearch() {
|
||||
if (sessionSearchInitialized) return;
|
||||
sessionSearchInitialized = true;
|
||||
// The Ctrl+K binding is owned by the command palette (initCommandPalette),
|
||||
// which exposes a "Search sessions…" entry that calls openSessionSearch.
|
||||
// Here we only handle Escape so the session palette can be dismissed.
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && sessionSearchOpen) {
|
||||
closeSessionSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.aiui = window.aiui || {};
|
||||
window.aiui.openSessionSearch = openSessionSearch;
|
||||
|
||||
async function init() {
|
||||
// Inject styles
|
||||
const style = document.createElement('style');
|
||||
@@ -803,6 +1262,22 @@ async function init() {
|
||||
}
|
||||
|
||||
await buildDashboard();
|
||||
|
||||
// Initialize Ctrl+K / Cmd+K command palette
|
||||
initCommandPalette();
|
||||
|
||||
// Initialize approval stream for modal notifications
|
||||
initApprovalStream();
|
||||
|
||||
// Initialize Ctrl+K session search palette
|
||||
initSessionSearch();
|
||||
|
||||
// Listen for approval events from chat stream
|
||||
window.addEventListener('aiui:approval-required', (e) => {
|
||||
if (e.detail) {
|
||||
showApprovalModal(e.detail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function buildDashboard() {
|
||||
@@ -858,11 +1333,11 @@ async function buildDashboard() {
|
||||
root.appendChild(sidebar);
|
||||
setupMobileNav(root, sidebar);
|
||||
}
|
||||
root.appendChild(main);
|
||||
const shellHeader = document.createElement('div');
|
||||
shellHeader.id = 'db-shell-header-slot';
|
||||
renderSlot('shell:header', shellHeader);
|
||||
if (shellHeader.children.length) root.insertBefore(shellHeader, main);
|
||||
root.appendChild(main);
|
||||
|
||||
// Resolve initial page from URL path or default to first page
|
||||
const pathId = location.pathname.replace(/^\//, '') || '';
|
||||
|
||||
@@ -19,6 +19,7 @@ let currentRunId = null;
|
||||
let agents = [];
|
||||
let messageQueue = [];
|
||||
let focusMode = false;
|
||||
let sessionSelectHandler = null;
|
||||
let containerRef = null;
|
||||
let currentDeltaEl = null;
|
||||
let currentDeltaBodyEl = null;
|
||||
@@ -784,10 +785,21 @@ export async function render(container) {
|
||||
|
||||
await loadSessions();
|
||||
connectWebSocket();
|
||||
|
||||
// Load a session chosen from the dashboard Ctrl+K search palette
|
||||
sessionSelectHandler = (e) => {
|
||||
const id = e.detail && e.detail.sessionId;
|
||||
if (id) loadSession(id);
|
||||
};
|
||||
window.addEventListener('aiui:session-select', sessionSelectHandler);
|
||||
}
|
||||
|
||||
export function cleanup() {
|
||||
if (ws) { try { ws.close(); } catch(e) {} ws = null; }
|
||||
if (sessionSelectHandler) {
|
||||
window.removeEventListener('aiui:session-select', sessionSelectHandler);
|
||||
sessionSelectHandler = null;
|
||||
}
|
||||
currentSessionId = null;
|
||||
currentAgentName = null;
|
||||
isStreaming = false;
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* MCP Manager View — list, connect, and disconnect Model Context Protocol servers.
|
||||
* API: GET /api/mcp/servers, POST /api/mcp/connect, POST /api/mcp/disconnect/{name}
|
||||
*/
|
||||
import { showToast, showConfirm } from '../toast.js';
|
||||
|
||||
const STATUS_STYLE = {
|
||||
connected: 'background:rgba(34,197,94,0.15);color:#22c55e',
|
||||
connecting: 'background:rgba(234,179,8,0.15);color:#eab308',
|
||||
error: 'background:rgba(239,68,68,0.15);color:#ef4444',
|
||||
disconnected: 'background:rgba(113,113,122,0.2);color:var(--db-text-dim)',
|
||||
};
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s == null ? '' : String(s);
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
export async function render(container) {
|
||||
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
|
||||
|
||||
let servers = [];
|
||||
let loadError = null;
|
||||
try {
|
||||
const r = await fetch('/api/mcp/servers');
|
||||
const d = await r.json();
|
||||
servers = d.servers || [];
|
||||
if (d.error) loadError = d.error;
|
||||
} catch (e) {
|
||||
loadError = e.message;
|
||||
}
|
||||
|
||||
const connected = servers.filter((s) => s.status === 'connected').length;
|
||||
const totalTools = servers.reduce((n, s) => n + ((s.tools && s.tools.length) || 0), 0);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="db-columns" style="grid-template-columns:repeat(3,1fr);margin-bottom:24px">
|
||||
<div class="db-card"><div class="db-card-title">Servers</div><div class="db-card-value">${servers.length}</div><div class="db-card-footer">${connected} connected</div></div>
|
||||
<div class="db-card"><div class="db-card-title">Tools</div><div class="db-card-value">${totalTools}</div></div>
|
||||
<div class="db-card"><div class="db-card-title">Health</div><div class="db-card-value">${servers.length ? Math.round((connected / servers.length) * 100) : 0}%</div></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
||||
<h3 style="margin:0;font-size:15px;font-weight:600">MCP Servers</h3>
|
||||
<button id="mcp-add" class="db-btn db-btn-primary">+ Connect Server</button>
|
||||
</div>
|
||||
${loadError ? `<div class="db-alert db-alert-warning"><span>⚠️</span><span>${esc(loadError)}</span></div>` : ''}
|
||||
<div id="mcp-list"></div>
|
||||
<div id="mcp-form-host"></div>
|
||||
`;
|
||||
|
||||
const list = container.querySelector('#mcp-list');
|
||||
servers.forEach((s) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'db-card';
|
||||
card.style.cssText = 'margin-bottom:10px;padding:16px 20px';
|
||||
const isConnected = s.status === 'connected';
|
||||
const statusStyle = STATUS_STYLE[s.status] || STATUS_STYLE.disconnected;
|
||||
const tools = s.tools || [];
|
||||
card.innerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">
|
||||
<div style="display:flex;align-items:center;gap:10px;flex:1;min-width:0">
|
||||
<span style="font-size:20px">🔌</span>
|
||||
<div style="min-width:0">
|
||||
<div style="font-weight:500;font-size:14px">${esc(s.name)}</div>
|
||||
<div style="font-size:12px;color:var(--db-text-dim)">${esc(s.transport || '')} · ${tools.length} tool${tools.length !== 1 ? 's' : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<span style="font-size:11px;padding:3px 10px;border-radius:12px;${statusStyle}">${esc(s.status || 'unknown')}</span>
|
||||
${isConnected ? `<button class="mcp-disconnect db-btn db-btn-destructive" data-name="${esc(s.name)}" style="padding:4px 12px;font-size:11px">Disconnect</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${s.last_error ? `<div style="font-size:12px;color:#ef4444;margin-top:8px">${esc(s.last_error)}</div>` : ''}
|
||||
${tools.length ? `<details style="margin-top:10px"><summary style="cursor:pointer;font-size:12px;color:var(--db-text-dim)">Tools (${tools.length})</summary><div style="margin-top:8px;display:flex;flex-direction:column;gap:6px">${tools
|
||||
.map(
|
||||
(t) =>
|
||||
`<div style="font-size:12px"><code style="color:var(--db-text)">${esc(t.name)}</code>${t.description ? ` <span style="color:var(--db-text-dim)">— ${esc(t.description)}</span>` : ''}</div>`
|
||||
)
|
||||
.join('')}</div></details>` : ''}
|
||||
`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
if (servers.length === 0 && !loadError) {
|
||||
list.innerHTML = '<div class="db-viewer"><pre>No MCP servers connected. Click "Connect Server" to add one.</pre></div>';
|
||||
}
|
||||
|
||||
container.querySelectorAll('.mcp-disconnect').forEach((b) =>
|
||||
b.addEventListener('click', async () => {
|
||||
const name = b.dataset.name;
|
||||
if (!(await showConfirm('Disconnect MCP Server', `Disconnect "${name}"?`))) return;
|
||||
try {
|
||||
const r = await fetch(`/api/mcp/disconnect/${encodeURIComponent(name)}`, { method: 'POST' });
|
||||
const d = await r.json();
|
||||
if (r.ok && d.success) {
|
||||
showToast(`Disconnected from ${name}`, 'success');
|
||||
render(container);
|
||||
} else {
|
||||
showToast(d.error || 'Disconnect failed', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const addBtn = container.querySelector('#mcp-add');
|
||||
const formHost = container.querySelector('#mcp-form-host');
|
||||
addBtn.addEventListener('click', () => openConnectForm(formHost, container));
|
||||
}
|
||||
|
||||
function openConnectForm(host, container) {
|
||||
host.innerHTML = `
|
||||
<div class="db-card" style="margin-top:16px;padding:20px">
|
||||
<h3 style="margin:0 0 16px;font-size:14px;font-weight:600">Connect MCP Server</h3>
|
||||
<div class="db-form-group">
|
||||
<label class="db-form-label">Name</label>
|
||||
<input id="mcp-f-name" class="db-form-input" placeholder="my-server">
|
||||
</div>
|
||||
<div class="db-form-group">
|
||||
<label class="db-form-label">Transport</label>
|
||||
<select id="mcp-f-transport" class="db-form-select">
|
||||
<option value="stdio">stdio (command)</option>
|
||||
<option value="sse">SSE / HTTP (url)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="mcp-f-stdio">
|
||||
<div class="db-form-group">
|
||||
<label class="db-form-label">Command</label>
|
||||
<input id="mcp-f-command" class="db-form-input" placeholder="npx">
|
||||
</div>
|
||||
<div class="db-form-group">
|
||||
<label class="db-form-label">Arguments (space-separated)</label>
|
||||
<input id="mcp-f-args" class="db-form-input" placeholder="-y @modelcontextprotocol/server-filesystem /tmp">
|
||||
</div>
|
||||
</div>
|
||||
<div id="mcp-f-url" style="display:none">
|
||||
<div class="db-form-group">
|
||||
<label class="db-form-label">URL</label>
|
||||
<input id="mcp-f-urlval" class="db-form-input" placeholder="https://example.com/sse">
|
||||
</div>
|
||||
</div>
|
||||
<div class="db-button-group" style="margin:0">
|
||||
<button id="mcp-f-submit" class="db-btn db-btn-primary">Connect</button>
|
||||
<button id="mcp-f-cancel" class="db-btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const transportSel = host.querySelector('#mcp-f-transport');
|
||||
const stdioBlock = host.querySelector('#mcp-f-stdio');
|
||||
const urlBlock = host.querySelector('#mcp-f-url');
|
||||
transportSel.addEventListener('change', () => {
|
||||
const isStdio = transportSel.value === 'stdio';
|
||||
stdioBlock.style.display = isStdio ? '' : 'none';
|
||||
urlBlock.style.display = isStdio ? 'none' : '';
|
||||
});
|
||||
|
||||
host.querySelector('#mcp-f-cancel').addEventListener('click', () => {
|
||||
host.innerHTML = '';
|
||||
});
|
||||
|
||||
host.querySelector('#mcp-f-submit').addEventListener('click', async () => {
|
||||
const name = host.querySelector('#mcp-f-name').value.trim();
|
||||
if (!name) {
|
||||
showToast('Name is required', 'error');
|
||||
return;
|
||||
}
|
||||
const body = { name };
|
||||
if (transportSel.value === 'stdio') {
|
||||
const command = host.querySelector('#mcp-f-command').value.trim();
|
||||
if (!command) {
|
||||
showToast('Command is required for stdio', 'error');
|
||||
return;
|
||||
}
|
||||
body.command = command;
|
||||
const argsRaw = host.querySelector('#mcp-f-args').value.trim();
|
||||
body.args = argsRaw ? argsRaw.split(/\s+/) : [];
|
||||
} else {
|
||||
const url = host.querySelector('#mcp-f-urlval').value.trim();
|
||||
if (!url) {
|
||||
showToast('URL is required', 'error');
|
||||
return;
|
||||
}
|
||||
body.url = url;
|
||||
}
|
||||
|
||||
const submitBtn = host.querySelector('#mcp-f-submit');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Connecting…';
|
||||
try {
|
||||
const r = await fetch('/api/mcp/connect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (r.ok && d.server) {
|
||||
showToast(`Connected to ${d.server.name}`, 'success');
|
||||
host.innerHTML = '';
|
||||
render(container);
|
||||
} else {
|
||||
showToast(d.error || d.hint || 'Connection failed', 'error');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Connect';
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Connect';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -311,7 +311,7 @@ def test_sessions(server: str = _SERVER_OPT) -> None:
|
||||
r.check(f"Session file exists: {session_file.name}", session_file.exists())
|
||||
|
||||
if session_file.exists():
|
||||
data = json.loads(session_file.read_text())
|
||||
data = json.loads(session_file.read_text(encoding="utf-8"))
|
||||
file_msgs = data.get("messages", [])
|
||||
r.check(
|
||||
"File contains messages",
|
||||
@@ -354,9 +354,11 @@ def test_endpoints(server: str = _SERVER_OPT) -> None:
|
||||
|
||||
# ── Health ───────────────────────────────────────────────────────
|
||||
console.print("\n[dim]Health[/dim]")
|
||||
from praisonaiui.health_utils import is_success_status
|
||||
|
||||
try:
|
||||
data = _get(server, "/health")
|
||||
r.check("GET /health", data.get("status") == "ok")
|
||||
r.check("GET /health", is_success_status(data.get("status")))
|
||||
except Exception as e:
|
||||
r.check("GET /health", False, str(e))
|
||||
|
||||
@@ -798,7 +800,7 @@ def test_features(server: str = _SERVER_OPT) -> None:
|
||||
# Try loading from .env
|
||||
for env_path in [Path.cwd() / ".env", Path.home() / ".env"]:
|
||||
if env_path.exists():
|
||||
for line in env_path.read_text().splitlines():
|
||||
for line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
if line.startswith("TELEGRAM_BOT_TOKEN="):
|
||||
tg_token = line.split("=", 1)[1].strip().strip('"').strip("'")
|
||||
break
|
||||
|
||||
@@ -246,7 +246,7 @@ def _read_disk_cache() -> Optional[dict]:
|
||||
age = time.time() - path.stat().st_mtime
|
||||
if age > _DISK_CACHE_TTL:
|
||||
return None
|
||||
return json.loads(path.read_text())
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
|
||||
@@ -256,7 +256,7 @@ def _write_disk_cache(data: dict) -> None:
|
||||
try:
|
||||
path = _disk_cache_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data))
|
||||
path.write_text(json.dumps(data), encoding="utf-8")
|
||||
except OSError:
|
||||
pass # Non-critical — build still works
|
||||
|
||||
@@ -357,7 +357,7 @@ def inject_theme_css(
|
||||
# Write to theme.css
|
||||
theme_file = output_dir / "assets" / "theme.css"
|
||||
theme_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
theme_file.write_text(css)
|
||||
theme_file.write_text(css, encoding="utf-8")
|
||||
|
||||
|
||||
def get_available_themes() -> list[str]:
|
||||
|
||||
@@ -734,6 +734,19 @@ class TestPagesAPI:
|
||||
if ev:
|
||||
assert ev["group"] == "Control"
|
||||
|
||||
def test_mcp_page_registered(self, client):
|
||||
r = client.get("/api/pages")
|
||||
pages = r.json().get("pages", r.json())
|
||||
mcp = next((p for p in pages if p["id"] == "mcp"), None)
|
||||
assert mcp is not None, "MCP page should be registered"
|
||||
assert mcp["group"] == "Agent"
|
||||
assert mcp["api_endpoint"] == "/api/mcp/servers"
|
||||
|
||||
def test_mcp_servers_endpoint(self, client):
|
||||
r = client.get("/api/mcp/servers")
|
||||
assert r.status_code == 200
|
||||
assert "servers" in r.json()
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# Persistence across restart
|
||||
|
||||
@@ -5,7 +5,12 @@ from __future__ import annotations
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from praisonaiui.backends import clear_backends, set_backend
|
||||
from praisonaiui.features.kanban import get_kanban_store, reset_kanban_store
|
||||
from praisonaiui.features.kanban import (
|
||||
_child_progress,
|
||||
_task_card,
|
||||
get_kanban_store,
|
||||
reset_kanban_store,
|
||||
)
|
||||
from praisonaiui.server import create_app, reset_state
|
||||
|
||||
|
||||
@@ -81,6 +86,9 @@ def test_kanban_uses_injected_store():
|
||||
def move_task(self, task_id, status):
|
||||
return None
|
||||
|
||||
def add_comment(self, task_id, comment):
|
||||
return None
|
||||
|
||||
def bulk_update(self, task_ids, status):
|
||||
return {"updated": 0}
|
||||
|
||||
@@ -105,6 +113,92 @@ def test_kanban_uses_injected_store():
|
||||
reset_kanban_store()
|
||||
|
||||
|
||||
def test_task_card_enriched_dto():
|
||||
card = _task_card(
|
||||
{
|
||||
"id": "t1",
|
||||
"title": "Refactor auth",
|
||||
"status": "running",
|
||||
"assignee": "coder",
|
||||
"priority": "P2",
|
||||
"tenant": "acme",
|
||||
"created_at": "2024-01-01T00:00:00+00:00",
|
||||
"comments": [{"text": "a"}, {"text": "b"}],
|
||||
}
|
||||
)
|
||||
assert card["tenant"] == "acme"
|
||||
assert card["created_at"] == "2024-01-01T00:00:00+00:00"
|
||||
assert card["comment_count"] == 2
|
||||
assert card["progress"] is None
|
||||
|
||||
|
||||
def test_child_progress_from_children_and_meta():
|
||||
children = {"children": [{"status": "done"}, {"status": "ready"}, {"status": "done"}]}
|
||||
assert _child_progress(children) == {"done": 2, "total": 3}
|
||||
|
||||
explicit = {"meta": {"progress": {"done": 1, "total": 4}}}
|
||||
assert _child_progress(explicit) == {"done": 1, "total": 4}
|
||||
|
||||
assert _child_progress({}) is None
|
||||
assert _child_progress({"meta": {"progress": {"done": 0, "total": 0}}}) is None
|
||||
|
||||
|
||||
def test_card_in_board_carries_progress_and_counts():
|
||||
clear_backends()
|
||||
client = _client()
|
||||
store = get_kanban_store()
|
||||
store.create_task(
|
||||
{
|
||||
"id": "px",
|
||||
"title": "Parent",
|
||||
"status": "todo",
|
||||
"tenant": "acme",
|
||||
"children": [{"status": "done"}, {"status": "todo"}],
|
||||
}
|
||||
)
|
||||
board = client.get("/api/kanban/board").json()
|
||||
todo_cards = next(c for c in board["columns"] if c["id"] == "todo")["cards"]
|
||||
card = next(c for c in todo_cards if c["id"] == "px")
|
||||
assert card["tenant"] == "acme"
|
||||
assert card["progress"] == {"done": 1, "total": 2}
|
||||
assert card["comment_count"] == 0
|
||||
clear_backends()
|
||||
|
||||
|
||||
def test_kanban_add_comment():
|
||||
clear_backends()
|
||||
client = _client()
|
||||
create = client.post("/api/kanban/tasks", json={"title": "Has comments", "status": "todo"})
|
||||
task_id = create.json()["id"]
|
||||
|
||||
resp = client.post(f"/api/kanban/tasks/{task_id}/comments", json={"text": "please unblock"})
|
||||
assert resp.status_code == 200
|
||||
task = resp.json()
|
||||
assert len(task["comments"]) == 1
|
||||
assert task["comments"][0]["text"] == "please unblock"
|
||||
assert task["comments"][0]["author"] == "human"
|
||||
|
||||
board = client.get("/api/kanban/board").json()
|
||||
todo_cards = next(c for c in board["columns"] if c["id"] == "todo")["cards"]
|
||||
card = next(c for c in todo_cards if c["id"] == task_id)
|
||||
assert card["comment_count"] == 1
|
||||
clear_backends()
|
||||
|
||||
|
||||
def test_kanban_add_comment_validation():
|
||||
clear_backends()
|
||||
client = _client()
|
||||
create = client.post("/api/kanban/tasks", json={"title": "T", "status": "todo"})
|
||||
task_id = create.json()["id"]
|
||||
|
||||
empty = client.post(f"/api/kanban/tasks/{task_id}/comments", json={"text": " "})
|
||||
assert empty.status_code == 400
|
||||
|
||||
missing = client.post("/api/kanban/tasks/nope/comments", json={"text": "hi"})
|
||||
assert missing.status_code == 404
|
||||
clear_backends()
|
||||
|
||||
|
||||
def test_jobs_board_endpoint():
|
||||
clear_backends()
|
||||
client = _client()
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Tests for channel auto-start lifecycle and running-status correctness.
|
||||
|
||||
Regression coverage for issue #167: auto-start raised
|
||||
``'dict' object has no attribute 'append'`` when the gateway exposed
|
||||
``_channel_tasks`` as a dict, and reported ``running: true`` despite a
|
||||
``start_error`` being set.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from praisonaiui.features import channels as channels_mod
|
||||
from praisonaiui.features.channels import ChannelsFeature
|
||||
|
||||
|
||||
class _FakeBot:
|
||||
def __init__(self) -> None:
|
||||
self.is_running = False
|
||||
self.started = False
|
||||
|
||||
async def start(self) -> None:
|
||||
self.started = True
|
||||
|
||||
|
||||
class _GatewayDictTasks:
|
||||
def __init__(self) -> None:
|
||||
self._channel_bots: dict = {}
|
||||
self._channel_tasks: dict = {}
|
||||
self._agents: dict = {}
|
||||
|
||||
|
||||
class _GatewayListTasks:
|
||||
def __init__(self) -> None:
|
||||
self._channel_bots: dict = {}
|
||||
self._channel_tasks: list = []
|
||||
self._agents: dict = {}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_channel_state():
|
||||
saved_channels = dict(channels_mod._channels)
|
||||
saved_bots = dict(channels_mod._live_bots)
|
||||
saved_flag = ChannelsFeature._auto_started
|
||||
channels_mod._channels.clear()
|
||||
channels_mod._live_bots.clear()
|
||||
ChannelsFeature._auto_started = False
|
||||
yield
|
||||
channels_mod._channels.clear()
|
||||
channels_mod._channels.update(saved_channels)
|
||||
channels_mod._live_bots.clear()
|
||||
channels_mod._live_bots.update(saved_bots)
|
||||
ChannelsFeature._auto_started = saved_flag
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_channel_bot_dict_channel_tasks_no_attribute_error():
|
||||
feature = ChannelsFeature()
|
||||
gw = _GatewayDictTasks()
|
||||
bot = _FakeBot()
|
||||
entry = {"id": "ch1", "platform": "discord", "config": {"bot_token": "x" * 30}}
|
||||
|
||||
with patch.object(feature, "_get_gateway", return_value=gw), patch.object(
|
||||
feature, "_create_bot_direct", return_value=bot
|
||||
), patch.object(feature, "_attach_chat_bridge"):
|
||||
error = await feature._start_channel_bot("ch1", entry)
|
||||
|
||||
assert error is None
|
||||
assert "ch1" in gw._channel_tasks
|
||||
assert gw._channel_bots["ch1"] is bot
|
||||
assert entry["running"] is True
|
||||
assert "start_error" not in entry
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_channel_bot_list_channel_tasks_no_regression():
|
||||
feature = ChannelsFeature()
|
||||
gw = _GatewayListTasks()
|
||||
bot = _FakeBot()
|
||||
entry = {"id": "ch2", "platform": "discord", "config": {"bot_token": "x" * 30}}
|
||||
|
||||
with patch.object(feature, "_get_gateway", return_value=gw), patch.object(
|
||||
feature, "_create_bot_direct", return_value=bot
|
||||
), patch.object(feature, "_attach_chat_bridge"):
|
||||
error = await feature._start_channel_bot("ch2", entry)
|
||||
|
||||
assert error is None
|
||||
assert len(gw._channel_tasks) == 1
|
||||
assert gw._channel_bots["ch2"] is bot
|
||||
assert entry["running"] is True
|
||||
|
||||
|
||||
def test_sync_running_status_start_error_forces_not_running():
|
||||
feature = ChannelsFeature()
|
||||
channels_mod._channels["chx"] = {
|
||||
"id": "chx",
|
||||
"platform": "discord",
|
||||
"running": True,
|
||||
"start_error": "boom",
|
||||
}
|
||||
|
||||
with patch.object(feature, "_get_gateway", return_value=None):
|
||||
feature._sync_running_status()
|
||||
|
||||
assert channels_mod._channels["chx"]["running"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_start_runs_once_per_process():
|
||||
feature = ChannelsFeature()
|
||||
channels_mod._channels["ch3"] = {
|
||||
"id": "ch3",
|
||||
"platform": "discord",
|
||||
"enabled": True,
|
||||
"config": {"bot_token": "x" * 30},
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
feature, "_start_channel_bot", return_value=None
|
||||
) as mock_start:
|
||||
await feature._auto_start_enabled_channels()
|
||||
await feature._auto_start_enabled_channels()
|
||||
|
||||
assert mock_start.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_start_skips_disabled_channels():
|
||||
feature = ChannelsFeature()
|
||||
channels_mod._channels["ch4"] = {
|
||||
"id": "ch4",
|
||||
"platform": "discord",
|
||||
"enabled": False,
|
||||
"config": {"bot_token": "x" * 30},
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
feature, "_start_channel_bot", return_value=None
|
||||
) as mock_start:
|
||||
await feature._auto_start_enabled_channels()
|
||||
|
||||
assert mock_start.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_start_failure_sets_not_running_and_error():
|
||||
feature = ChannelsFeature()
|
||||
entry = {
|
||||
"id": "ch5",
|
||||
"platform": "discord",
|
||||
"enabled": True,
|
||||
"running": True,
|
||||
"config": {"bot_token": "x" * 30},
|
||||
}
|
||||
channels_mod._channels["ch5"] = entry
|
||||
|
||||
async def _boom(channel_id, e):
|
||||
raise AttributeError("'dict' object has no attribute 'append'")
|
||||
|
||||
with patch.object(feature, "_start_channel_bot", side_effect=_boom):
|
||||
await feature._auto_start_enabled_channels()
|
||||
|
||||
assert entry["running"] is False
|
||||
assert "start_error" in entry
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Unit tests for CLI features commands."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from praisonaiui.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
class TestFeaturesCommands:
|
||||
"""Test the features CLI commands."""
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_features_list_shows_green_for_success_statuses(self, status):
|
||||
"""Test features list command shows green indicator for both ok and healthy."""
|
||||
features_data = {
|
||||
"features": [
|
||||
{
|
||||
"name": "Feature1",
|
||||
"description": "Test feature 1",
|
||||
"health": {"status": status},
|
||||
"routes": ["/api/feature1", "/api/feature1/status"],
|
||||
},
|
||||
{
|
||||
"name": "Feature2",
|
||||
"description": "Test feature 2",
|
||||
"health": {"status": status},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["features", "list"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Feature1" in result.stdout
|
||||
assert "Feature2" in result.stdout
|
||||
assert "Test feature 1" in result.stdout
|
||||
assert "/api/feature1" in result.stdout
|
||||
|
||||
@pytest.mark.parametrize("status", ["error", "degraded", "unknown"])
|
||||
def test_features_list_shows_red_for_non_success_statuses(self, status):
|
||||
"""Test features list command shows red indicator for non-success statuses."""
|
||||
features_data = {
|
||||
"features": [
|
||||
{
|
||||
"name": "FailingFeature",
|
||||
"description": "Feature with error",
|
||||
"health": {"status": status},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["features", "list"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "FailingFeature" in result.stdout
|
||||
assert "Feature with error" in result.stdout
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_features_status_counts_success_statuses_correctly(self, status):
|
||||
"""Test features status command counts both ok and healthy as success."""
|
||||
features_data = {
|
||||
"features": [
|
||||
{"name": "Feature1", "health": {"status": status}},
|
||||
{"name": "Feature2", "health": {"status": status}},
|
||||
{"name": "Feature3", "health": {"status": "error"}},
|
||||
]
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["features", "status"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Features: 2/3 healthy" in result.stdout
|
||||
assert "Feature1" in result.stdout
|
||||
assert "Feature2" in result.stdout
|
||||
assert "Feature3" in result.stdout
|
||||
|
||||
def test_features_status_mixed_success_statuses(self):
|
||||
"""Test features status handles mix of ok and healthy statuses."""
|
||||
features_data = {
|
||||
"features": [
|
||||
{"name": "Feature1", "health": {"status": "ok"}},
|
||||
{"name": "Feature2", "health": {"status": "healthy"}},
|
||||
{"name": "Feature3", "health": {"status": "degraded"}},
|
||||
{"name": "Feature4", "health": {"status": "error"}},
|
||||
]
|
||||
}
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["features", "status"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
# Both ok and healthy should be counted as healthy
|
||||
assert "Features: 2/4 healthy" in result.stdout
|
||||
@@ -0,0 +1,168 @@
|
||||
"""Unit tests for CLI health-check command."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from praisonaiui.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
class TestHealthCheckCommand:
|
||||
"""Test the health-check CLI command."""
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_health_check_shows_green_for_success_statuses(self, status):
|
||||
"""Test health-check command shows green panel for both ok and healthy."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(
|
||||
{"status": status, "timestamp": "2026-06-23T10:00:00Z"}
|
||||
).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["health-check"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Health Check" in result.stdout
|
||||
# Check that status is displayed (Rich strips color codes in test output)
|
||||
assert status in result.stdout
|
||||
# The test would need more complex setup to verify colors in Rich output
|
||||
|
||||
@pytest.mark.parametrize("status", ["degraded", "error", "unknown"])
|
||||
def test_health_check_shows_yellow_for_non_success_statuses(self, status):
|
||||
"""Test health-check command shows yellow panel for non-success statuses."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(
|
||||
{"status": status, "timestamp": "2026-06-23T10:00:00Z"}
|
||||
).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["health-check"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Health Check" in result.stdout
|
||||
# Check that status is displayed (Rich strips color codes in test output)
|
||||
assert status in result.stdout
|
||||
|
||||
def test_health_check_handles_server_unreachable(self):
|
||||
"""Test health-check command handles unreachable server."""
|
||||
with patch("urllib.request.urlopen", side_effect=Exception("Connection refused")):
|
||||
result = runner.invoke(app, ["health-check"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "Server unreachable" in result.stdout
|
||||
assert "✗" in result.stdout # Rich strips color codes
|
||||
|
||||
|
||||
class TestDoctorCommand:
|
||||
"""Test the doctor CLI command."""
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_doctor_health_extractor_accepts_success_statuses(self, status):
|
||||
"""Test doctor command's health extractor accepts both ok and healthy."""
|
||||
def mock_api_get(server, path):
|
||||
if path == "/health/live":
|
||||
return {"status": status}
|
||||
elif path == "/api/provider":
|
||||
return {"name": "TestProvider"}
|
||||
elif path == "/api/provider/health":
|
||||
return {"type": "gateway", "agents": 2}
|
||||
elif path == "/api/features":
|
||||
return {"features": []}
|
||||
elif path == "/api/config":
|
||||
return {"config": {}}
|
||||
elif path == "/sessions":
|
||||
return []
|
||||
elif path == "/api/channels":
|
||||
return {"channels": []}
|
||||
return {}
|
||||
|
||||
with patch("praisonaiui.cli._api_get", side_effect=mock_api_get):
|
||||
result = runner.invoke(app, ["doctor", "--json"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
output = json.loads(result.stdout)
|
||||
|
||||
# Find the Server Health check
|
||||
health_check = next(
|
||||
(c for c in output["checks"] if c["name"] == "Server Health"), None
|
||||
)
|
||||
assert health_check is not None
|
||||
assert health_check["status"] == "pass"
|
||||
assert "running on" in health_check["detail"]
|
||||
|
||||
@pytest.mark.parametrize("status", ["degraded", "error"])
|
||||
def test_doctor_health_extractor_warns_for_non_success_statuses(self, status):
|
||||
"""Test doctor command's health extractor warns for non-success statuses."""
|
||||
def mock_api_get(server, path):
|
||||
if path == "/health/live":
|
||||
return {"status": status}
|
||||
return {}
|
||||
|
||||
with patch("praisonaiui.cli._api_get", side_effect=mock_api_get):
|
||||
result = runner.invoke(app, ["doctor", "--json"])
|
||||
|
||||
# Since we're not providing all required endpoints, exit code might be 1
|
||||
# But we can still check the output
|
||||
output = json.loads(result.stdout)
|
||||
|
||||
# Find the Server Health check
|
||||
health_check = next(
|
||||
(c for c in output["checks"] if c["name"] == "Server Health"), None
|
||||
)
|
||||
assert health_check is not None
|
||||
assert health_check["status"] == "warn"
|
||||
assert f"status: {status}" in health_check["detail"]
|
||||
|
||||
|
||||
class TestProviderStatusCommand:
|
||||
"""Test the provider status CLI command."""
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_provider_status_shows_green_for_success_statuses(self, status):
|
||||
"""Test provider status command shows green for both ok and healthy."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(
|
||||
{
|
||||
"name": "TestProvider",
|
||||
"module": "test.provider",
|
||||
"status": status,
|
||||
"agents": []
|
||||
}
|
||||
).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["provider", "status"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert status in result.stdout # Rich strips color codes in test output
|
||||
|
||||
@pytest.mark.parametrize("status", ["error", "degraded"])
|
||||
def test_provider_status_shows_red_for_non_success_statuses(self, status):
|
||||
"""Test provider status command shows red for non-success statuses."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps(
|
||||
{
|
||||
"name": "TestProvider",
|
||||
"module": "test.provider",
|
||||
"status": status,
|
||||
"agents": []
|
||||
}
|
||||
).encode("utf-8")
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.return_value.__enter__.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["provider", "status"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert status in result.stdout # Rich strips color codes in test output
|
||||
@@ -599,3 +599,56 @@ class TestCompositionResolver:
|
||||
assert len(left_sidebar_widgets) == 1
|
||||
assert left_sidebar_widgets[0]["props"]["searchable"] is True
|
||||
assert "collapsible" not in left_sidebar_widgets[0]["props"]
|
||||
|
||||
|
||||
class TestCompilerUtf8Writes:
|
||||
"""Regression tests for issue #150 — UTF-8 on write (Windows cp1252)."""
|
||||
|
||||
SPARKLE = "\u2728"
|
||||
|
||||
def test_doc_page_written_as_utf8(self, tmp_path: Path):
|
||||
docs_dir = tmp_path / "docs"
|
||||
docs_dir.mkdir()
|
||||
(docs_dir / "index.md").write_text(
|
||||
f"# Welcome\n\n## {self.SPARKLE} Features\n\nHello!", encoding="utf-8"
|
||||
)
|
||||
|
||||
config = Config(
|
||||
site=SiteConfig(title="Unicode Site"),
|
||||
content=ContentConfig(docs=ContentSourceConfig(dir=str(docs_dir))),
|
||||
templates={"docs": TemplateConfig(layout="Default", slots={})},
|
||||
routes=[RouteConfig(match="/docs/**", template="docs")],
|
||||
)
|
||||
|
||||
compiler = Compiler(config, base_path=tmp_path)
|
||||
result = compiler.compile(tmp_path / "output")
|
||||
assert result.success is True
|
||||
|
||||
page = tmp_path / "output" / "docs" / "index" / "index.html"
|
||||
assert page.exists()
|
||||
raw = page.read_bytes()
|
||||
assert self.SPARKLE in raw.decode("utf-8")
|
||||
assert b"\xe2\x9c\xa8" in raw
|
||||
|
||||
def test_write_json_uses_utf8(self, minimal_config: Config, tmp_path: Path):
|
||||
compiler = Compiler(minimal_config, base_path=tmp_path)
|
||||
target = tmp_path / "out.json"
|
||||
compiler._write_json(target, {"sparkle": self.SPARKLE}, indent=2)
|
||||
raw = target.read_bytes()
|
||||
assert self.SPARKLE in raw.decode("utf-8")
|
||||
assert b"\xe2\x9c\xa8" in raw
|
||||
|
||||
def test_plugins_json_uses_utf8(self, tmp_path: Path):
|
||||
frontend_dir = tmp_path / "frontend"
|
||||
plugins_dst = frontend_dir / "plugins"
|
||||
plugins_dst.mkdir(parents=True)
|
||||
config = Config(
|
||||
site=SiteConfig(title="Plugin Site"),
|
||||
templates={"docs": TemplateConfig(layout="Default", slots={})},
|
||||
routes=[RouteConfig(match="/docs/**", template="docs")],
|
||||
)
|
||||
compiler = Compiler(config, base_path=tmp_path)
|
||||
compiler._copy_plugins(tmp_path / "output", frontend_dir)
|
||||
plugins_file = tmp_path / "output" / "plugins" / "plugins.json"
|
||||
assert plugins_file.exists()
|
||||
assert plugins_file.read_bytes().decode("utf-8")
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Regression tests for #166 — buildDashboard() insertBefore NotFoundError.
|
||||
|
||||
buildDashboard() called `root.insertBefore(shellHeader, main)` before `main`
|
||||
was a child of `root`, violating the DOM contract and throwing
|
||||
NotFoundError. That aborted the dashboard plugin init, blocking Ctrl+K and
|
||||
the browser chat input. These source-parsing tests assert `main` is appended
|
||||
to `root` before any insertBefore references it, mirroring
|
||||
`test_session_search_dashboard.py`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
FRONTEND = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
|
||||
)
|
||||
DASHBOARD_JS = FRONTEND / "dashboard.js"
|
||||
|
||||
|
||||
class TestDashboardShellOrder:
|
||||
def _build_body(self) -> str:
|
||||
src = DASHBOARD_JS.read_text()
|
||||
m = re.search(
|
||||
r"function buildDashboard\([^)]*\)\s*\{(.*?)^}",
|
||||
src, re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert m, "buildDashboard not found"
|
||||
return m.group(1)
|
||||
|
||||
def test_main_appended_before_insert_before(self):
|
||||
body = self._build_body()
|
||||
append_idx = body.find("root.appendChild(main)")
|
||||
insert_idx = body.find("root.insertBefore(shellHeader, main)")
|
||||
assert append_idx != -1, "root.appendChild(main) not found"
|
||||
assert insert_idx != -1, "root.insertBefore(shellHeader, main) not found"
|
||||
assert append_idx < insert_idx, (
|
||||
"main must be appended to root before insertBefore(shellHeader, main)"
|
||||
)
|
||||
|
||||
def test_shell_header_insert_guarded_by_children(self):
|
||||
body = self._build_body()
|
||||
m = re.search(
|
||||
r"if \(shellHeader\.children\.length\)\s*"
|
||||
r"root\.insertBefore\(shellHeader, main\)",
|
||||
body,
|
||||
)
|
||||
assert m, "shellHeader insert must be guarded by children.length"
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Test frontend navigation functionality."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestFrontendNavigation:
|
||||
"""Tests for browser history navigation in the frontend."""
|
||||
|
||||
def test_frontend_bundle_exists(self):
|
||||
"""Test that the built frontend bundle exists in templates."""
|
||||
bundle_path = Path("src/praisonaiui/templates/frontend/assets/index.js")
|
||||
assert bundle_path.exists(), "Frontend bundle should exist in templates"
|
||||
|
||||
def test_index_html_includes_assets(self):
|
||||
"""Test that index.html references the correct assets."""
|
||||
index_path = Path("src/praisonaiui/templates/frontend/index.html")
|
||||
assert index_path.exists(), "index.html should exist"
|
||||
|
||||
content = index_path.read_text()
|
||||
assert "/assets/index.js" in content, "Should reference index.js"
|
||||
assert "/assets/index.css" in content, "Should reference index.css"
|
||||
|
||||
def test_popstate_handler_in_bundle(self):
|
||||
"""Test that popstate handler is present in the bundle."""
|
||||
bundle_path = Path("src/praisonaiui/templates/frontend/assets/index.js")
|
||||
if bundle_path.exists():
|
||||
# Check if the bundle contains popstate handling code
|
||||
# Note: The bundle is minified, so we check for the event name
|
||||
content = bundle_path.read_text()
|
||||
assert "popstate" in content, "Bundle should contain popstate event handler"
|
||||
|
||||
def test_title_template_handling(self):
|
||||
"""Test that title template handling is robust."""
|
||||
# This tests the logic, not the actual implementation
|
||||
# as the JS is bundled and minified
|
||||
|
||||
test_cases = [
|
||||
("%s | Site", "Page", "Page | Site"),
|
||||
("%s", "Page", "Page"),
|
||||
("%s | %s", "Page", "Page | Site"), # Second %s would be replaced with site name
|
||||
]
|
||||
|
||||
for template, page_title, expected_start in test_cases:
|
||||
# Simulate the title replacement logic
|
||||
result = template.replace("%s", page_title, 1)
|
||||
assert result.startswith(expected_start.split("|")[0].strip())
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Test that both health-check and health commands work."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from praisonaiui.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def test_health_check_command_exists():
|
||||
"""Test that health-check command is registered."""
|
||||
result = runner.invoke(app, ["health-check", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Check server health" in result.stdout
|
||||
|
||||
|
||||
def test_health_alias_exists():
|
||||
"""Test that health alias is registered."""
|
||||
result = runner.invoke(app, ["health", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Check server health" in result.stdout
|
||||
|
||||
|
||||
@patch("urllib.request.urlopen")
|
||||
def test_health_check_command_works(mock_urlopen):
|
||||
"""Test that health-check command works correctly."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps({
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-06-23T12:00:00"
|
||||
}).encode()
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
mock_response.__exit__.return_value = None
|
||||
mock_urlopen.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["health-check", "--server", "http://127.0.0.1:8000"])
|
||||
assert result.exit_code == 0
|
||||
assert "healthy" in result.stdout.lower()
|
||||
|
||||
|
||||
@patch("urllib.request.urlopen")
|
||||
def test_health_alias_works(mock_urlopen):
|
||||
"""Test that health alias works correctly."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps({
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-06-23T12:00:00"
|
||||
}).encode()
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
mock_response.__exit__.return_value = None
|
||||
mock_urlopen.return_value = mock_response
|
||||
|
||||
result = runner.invoke(app, ["health", "--server", "http://127.0.0.1:8000"])
|
||||
assert result.exit_code == 0
|
||||
assert "healthy" in result.stdout.lower()
|
||||
|
||||
|
||||
@patch("urllib.request.urlopen")
|
||||
@patch("praisonaiui.cli._api_get")
|
||||
def test_health_detailed_works(mock_api_get, mock_urlopen):
|
||||
"""Test that both commands work with --detailed flag."""
|
||||
# Mock health check response
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = json.dumps({
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-06-23T12:00:00"
|
||||
}).encode()
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
mock_response.__exit__.return_value = None
|
||||
mock_urlopen.return_value = mock_response
|
||||
|
||||
# Mock features response
|
||||
mock_api_get.return_value = {
|
||||
"features": [
|
||||
{"name": "test_feature", "health": {"healthy": True, "detail": "ok"}}
|
||||
]
|
||||
}
|
||||
|
||||
# Test health-check with --detailed
|
||||
result1 = runner.invoke(app, ["health-check", "--server", "http://127.0.0.1:8000", "--detailed"])
|
||||
assert result1.exit_code == 0
|
||||
assert "healthy" in result1.stdout.lower()
|
||||
assert "test_feature" in result1.stdout
|
||||
|
||||
# Test health alias with --detailed
|
||||
result2 = runner.invoke(app, ["health", "--server", "http://127.0.0.1:8000", "--detailed"])
|
||||
assert result2.exit_code == 0
|
||||
assert "healthy" in result2.stdout.lower()
|
||||
assert "test_feature" in result2.stdout
|
||||
|
||||
|
||||
def test_help_shows_both_commands():
|
||||
"""Test that main help shows both health commands."""
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
# At least one health-related command should be shown
|
||||
assert "health" in result.stdout.lower()
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Unit tests for health status utilities."""
|
||||
|
||||
import pytest
|
||||
|
||||
from praisonaiui.health_utils import SUCCESS_STATUSES, is_success_status
|
||||
|
||||
|
||||
class TestHealthUtils:
|
||||
"""Test health status utility functions."""
|
||||
|
||||
def test_success_statuses_constant(self):
|
||||
"""Test SUCCESS_STATUSES contains expected values."""
|
||||
assert "ok" in SUCCESS_STATUSES
|
||||
assert "healthy" in SUCCESS_STATUSES
|
||||
assert len(SUCCESS_STATUSES) == 2
|
||||
|
||||
@pytest.mark.parametrize("status", ["ok", "healthy"])
|
||||
def test_is_success_status_with_valid_statuses(self, status):
|
||||
"""Test is_success_status returns True for valid success statuses."""
|
||||
assert is_success_status(status) is True
|
||||
|
||||
@pytest.mark.parametrize("status", ["error", "degraded", "unknown", "failing", ""])
|
||||
def test_is_success_status_with_invalid_statuses(self, status):
|
||||
"""Test is_success_status returns False for invalid statuses."""
|
||||
assert is_success_status(status) is False
|
||||
|
||||
def test_is_success_status_with_none(self):
|
||||
"""Test is_success_status handles None input gracefully."""
|
||||
assert is_success_status(None) is False
|
||||
|
||||
def test_is_success_status_case_sensitive(self):
|
||||
"""Test is_success_status is case sensitive."""
|
||||
assert is_success_status("OK") is False
|
||||
assert is_success_status("HEALTHY") is False
|
||||
assert is_success_status("Ok") is False
|
||||
assert is_success_status("Healthy") is False
|
||||
@@ -1,12 +1,14 @@
|
||||
"""Tests for i18n and accessibility configuration."""
|
||||
|
||||
|
||||
from praisonaiui.schema.features import get_feature_registry
|
||||
from praisonaiui.schema.models import (
|
||||
A11yConfig,
|
||||
Config,
|
||||
I18nConfig,
|
||||
SiteConfig,
|
||||
)
|
||||
from praisonaiui.schema.validators import validate_config
|
||||
|
||||
|
||||
class TestI18nConfig:
|
||||
@@ -112,3 +114,28 @@ class TestConfigWithI18nA11y:
|
||||
|
||||
assert config.i18n.locales == ["en", "ja"]
|
||||
assert config.a11y.reduce_motion is True
|
||||
|
||||
def test_i18n_implemented_status(self):
|
||||
"""Test that i18n is marked as IMPLEMENTED in feature registry."""
|
||||
registry = get_feature_registry()
|
||||
assert registry.is_implemented("i18n") is True
|
||||
assert registry.is_experimental("i18n") is False
|
||||
|
||||
def test_strict_validation_passes_with_i18n(self):
|
||||
"""Test that strict validation passes with valid i18n config."""
|
||||
config = Config(
|
||||
site=SiteConfig(title="Test Site"),
|
||||
i18n=I18nConfig(
|
||||
defaultLocale="en",
|
||||
locales=["en", "es", "fr"],
|
||||
rtlLocales=["ar"],
|
||||
fallbackLocale="en"
|
||||
),
|
||||
)
|
||||
|
||||
result = validate_config(config, strict=True)
|
||||
|
||||
# Should not have experimental error (code 4001) for i18n
|
||||
assert not any(e.code == 4001 and 'i18n' in e.message for e in result.errors)
|
||||
# Validation should pass with no errors related to i18n being experimental
|
||||
assert result.valid or len([e for e in result.errors if 'i18n' in e.message and 'experimental' in e.message]) == 0
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Tests for i18n feature implementation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from praisonaiui.features.i18n import I18nFeature, JSONLocaleManager, get_i18n_manager
|
||||
|
||||
|
||||
class TestI18nFeature:
|
||||
"""Tests for I18nFeature class."""
|
||||
|
||||
def test_feature_metadata(self):
|
||||
"""Test feature metadata properties."""
|
||||
feature = I18nFeature()
|
||||
assert feature.name == "i18n"
|
||||
assert "Internationalization" in feature.description
|
||||
|
||||
def test_feature_routes(self):
|
||||
"""Test that i18n feature exposes correct routes."""
|
||||
feature = I18nFeature()
|
||||
routes = feature.routes()
|
||||
|
||||
# Check expected routes exist
|
||||
route_paths = [route.path for route in routes]
|
||||
assert "/api/i18n/locales" in route_paths
|
||||
assert "/api/i18n/strings/{locale}" in route_paths
|
||||
assert "/api/i18n/translate" in route_paths
|
||||
assert "/api/i18n/locale" in route_paths
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_feature_health(self):
|
||||
"""Test health endpoint."""
|
||||
feature = I18nFeature()
|
||||
health = await feature.health()
|
||||
|
||||
assert health["status"] == "ok"
|
||||
assert health["feature"] == "i18n"
|
||||
assert "provider" in health
|
||||
|
||||
|
||||
class TestJSONLocaleManager:
|
||||
"""Tests for JSONLocaleManager."""
|
||||
|
||||
def test_default_initialization(self):
|
||||
"""Test default initialization."""
|
||||
manager = JSONLocaleManager()
|
||||
assert manager.get_locale() == "en"
|
||||
assert len(manager.list_locales()) > 0
|
||||
|
||||
def test_translation_lookup(self):
|
||||
"""Test translation key lookup."""
|
||||
manager = JSONLocaleManager()
|
||||
|
||||
# Test English translation
|
||||
text = manager.t("app.welcome", locale="en")
|
||||
assert "Welcome" in text
|
||||
|
||||
# Test Spanish translation
|
||||
text = manager.t("app.welcome", locale="es")
|
||||
assert "Bienvenido" in text
|
||||
|
||||
# Test French translation
|
||||
text = manager.t("app.welcome", locale="fr")
|
||||
assert "Bienvenue" in text
|
||||
|
||||
def test_translation_with_variables(self):
|
||||
"""Test translation with variable substitution."""
|
||||
manager = JSONLocaleManager()
|
||||
manager._strings["en"]["greeting"] = "Hello {name}!"
|
||||
|
||||
text = manager.t("greeting", locale="en", name="World")
|
||||
assert text == "Hello World!"
|
||||
|
||||
def test_unknown_key_returns_key(self):
|
||||
"""Test that unknown keys return the key itself."""
|
||||
manager = JSONLocaleManager()
|
||||
text = manager.t("unknown.key", locale="en")
|
||||
assert text == "unknown.key"
|
||||
|
||||
def test_locale_switching(self):
|
||||
"""Test switching default locale."""
|
||||
manager = JSONLocaleManager()
|
||||
|
||||
assert manager.get_locale() == "en"
|
||||
manager.set_locale("es")
|
||||
assert manager.get_locale() == "es"
|
||||
|
||||
# Test that default locale is used when none specified
|
||||
text = manager.t("app.welcome")
|
||||
assert "Bienvenido" in text
|
||||
|
||||
def test_get_strings(self):
|
||||
"""Test getting all strings for a locale."""
|
||||
manager = JSONLocaleManager()
|
||||
|
||||
en_strings = manager.get_strings("en")
|
||||
assert "app.title" in en_strings
|
||||
assert "chat.placeholder" in en_strings
|
||||
|
||||
es_strings = manager.get_strings("es")
|
||||
assert "app.title" in es_strings
|
||||
assert es_strings["chat.placeholder"] == "Escribe tu mensaje..."
|
||||
|
||||
def test_list_locales(self):
|
||||
"""Test listing available locales."""
|
||||
manager = JSONLocaleManager()
|
||||
locales = manager.list_locales()
|
||||
|
||||
# Check structure
|
||||
assert isinstance(locales, list)
|
||||
assert len(locales) >= 3 # At least en, es, fr
|
||||
|
||||
# Check format
|
||||
en_locale = next((loc for loc in locales if loc["code"] == "en"), None)
|
||||
assert en_locale is not None
|
||||
assert en_locale["name"] == "English"
|
||||
assert en_locale["native"] == "English"
|
||||
|
||||
|
||||
class TestI18nIntegration:
|
||||
"""Integration tests for i18n system."""
|
||||
|
||||
def test_singleton_manager(self):
|
||||
"""Test that get_i18n_manager returns singleton."""
|
||||
mgr1 = get_i18n_manager()
|
||||
mgr2 = get_i18n_manager()
|
||||
assert mgr1 is mgr2
|
||||
|
||||
def test_manager_health(self):
|
||||
"""Test manager health check."""
|
||||
manager = get_i18n_manager()
|
||||
health = manager.health()
|
||||
|
||||
assert health["status"] == "ok"
|
||||
assert "provider" in health
|
||||
assert "default_locale" in health
|
||||
@@ -20,6 +20,10 @@ def client():
|
||||
# Clear state before each test
|
||||
_agents.clear()
|
||||
_callbacks.clear()
|
||||
import praisonaiui.server as _srv
|
||||
|
||||
_srv._DEEP_HEALTH_CACHE = {}
|
||||
_srv._DEEP_HEALTH_CACHE_TIME = 0.0
|
||||
set_datastore(MemoryDataStore())
|
||||
app = create_app()
|
||||
return TestClient(app)
|
||||
@@ -36,6 +40,194 @@ class TestHealthEndpoint:
|
||||
assert data["status"] == "ok"
|
||||
assert "timestamp" in data
|
||||
|
||||
def test_health_live_is_fast(self, client):
|
||||
"""Test liveness endpoint returns quickly."""
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
response = client.get("/health/live")
|
||||
duration = time.time() - start
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "timestamp" in data
|
||||
# Should respond in under 100ms (generous for tests)
|
||||
assert duration < 0.1
|
||||
|
||||
def test_health_ready_with_deep_check(self, client):
|
||||
"""Test readiness endpoint with deep feature checks."""
|
||||
response = client.get("/health/ready")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "timestamp" in data
|
||||
assert "provider" in data
|
||||
|
||||
def test_health_ready_without_deep_check(self, client):
|
||||
"""Test readiness endpoint with deep=false skips feature checks."""
|
||||
response = client.get("/health/ready?deep=false")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "timestamp" in data
|
||||
# Features should not be present when deep=false
|
||||
assert "features" not in data
|
||||
|
||||
def test_api_health_is_cached(self, client):
|
||||
"""Test /api/health endpoint uses caching."""
|
||||
import time
|
||||
|
||||
# First request
|
||||
response1 = client.get("/api/health")
|
||||
assert response1.status_code == 200
|
||||
data1 = response1.json()
|
||||
|
||||
# Second request should be cached
|
||||
time.sleep(0.1)
|
||||
response2 = client.get("/api/health")
|
||||
assert response2.status_code == 200
|
||||
data2 = response2.json()
|
||||
|
||||
# Timestamps should be identical (cached)
|
||||
assert data1["timestamp"] == data2["timestamp"]
|
||||
|
||||
def test_health_endpoints_accessible(self, client):
|
||||
"""Test all health endpoints are accessible."""
|
||||
endpoints = [
|
||||
"/health",
|
||||
"/health/live",
|
||||
"/health/ready",
|
||||
"/api/health",
|
||||
"/api/health/live",
|
||||
"/api/health/ready",
|
||||
]
|
||||
for endpoint in endpoints:
|
||||
response = client.get(endpoint)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_health_live_under_500ms(self, client):
|
||||
"""Liveness probe must respond well under 500ms."""
|
||||
import time
|
||||
|
||||
start = time.perf_counter()
|
||||
response = client.get("/health/live")
|
||||
duration = time.perf_counter() - start
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "ok"
|
||||
assert duration < 0.5
|
||||
|
||||
def test_health_deep_false_is_liveness(self, client):
|
||||
"""Legacy /health?deep=false returns an immediate liveness response."""
|
||||
response = client.get("/health?deep=false")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "timestamp" in data
|
||||
assert "provider" not in data
|
||||
|
||||
def test_health_slow_feature_does_not_block(self, client):
|
||||
"""A slow feature must not block the legacy /health response."""
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from praisonaiui.features import _features
|
||||
|
||||
class _SlowFeature:
|
||||
async def health(self):
|
||||
await asyncio.sleep(5)
|
||||
return {"healthy": True}
|
||||
|
||||
_features["__slow_test__"] = _SlowFeature()
|
||||
try:
|
||||
start = time.perf_counter()
|
||||
response = client.get("/health")
|
||||
duration = time.perf_counter() - start
|
||||
assert response.status_code == 200
|
||||
assert duration < 2.0
|
||||
finally:
|
||||
_features.pop("__slow_test__", None)
|
||||
|
||||
def test_health_many_features_under_budget(self, client):
|
||||
"""Deep /health with many slow features stays bounded (parallel)."""
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from praisonaiui.features import _features
|
||||
|
||||
class _SlowFeature:
|
||||
async def health(self):
|
||||
await asyncio.sleep(0.4)
|
||||
return {"healthy": True}
|
||||
|
||||
for i in range(40):
|
||||
_features[f"__perf_{i}__"] = _SlowFeature()
|
||||
try:
|
||||
start = time.perf_counter()
|
||||
response = client.get("/health")
|
||||
duration = time.perf_counter() - start
|
||||
assert response.status_code == 200
|
||||
assert duration < 1.5
|
||||
finally:
|
||||
for i in range(40):
|
||||
_features.pop(f"__perf_{i}__", None)
|
||||
|
||||
def test_health_deep_is_cached(self, client):
|
||||
"""Repeat /health probes hit the short-TTL cache and stay fast."""
|
||||
import time
|
||||
|
||||
first = client.get("/health")
|
||||
assert first.status_code == 200
|
||||
|
||||
start = time.perf_counter()
|
||||
second = client.get("/health")
|
||||
duration = time.perf_counter() - start
|
||||
assert second.status_code == 200
|
||||
assert second.json().get("cached") is True
|
||||
assert duration < 0.1
|
||||
|
||||
|
||||
class TestProviderHealthEndpoint:
|
||||
"""Tests for the provider health JSON route (#151)."""
|
||||
|
||||
def test_api_provider_health_is_json(self, client):
|
||||
"""/api/provider/health returns JSON, not the SPA HTML shell."""
|
||||
response = client.get("/api/provider/health")
|
||||
assert response.status_code == 200
|
||||
assert "application/json" in response.headers["content-type"]
|
||||
body = response.json()
|
||||
assert "status" in body
|
||||
assert "type" in body
|
||||
assert "agents" in body
|
||||
assert "detail" in body
|
||||
|
||||
def test_api_provider_health_accept_json(self, client):
|
||||
"""Accept: application/json still yields JSON body."""
|
||||
response = client.get(
|
||||
"/api/provider/health", headers={"Accept": "application/json"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "application/json" in response.headers["content-type"]
|
||||
assert "status" in response.json()
|
||||
|
||||
def test_unknown_api_path_not_html(self, client):
|
||||
"""Unmatched /api/* paths return JSON 404, never SPA HTML 200."""
|
||||
response = client.get("/api/this-route-does-not-exist")
|
||||
assert response.status_code == 404
|
||||
assert "text/html" not in response.headers.get("content-type", "")
|
||||
assert "application/json" in response.headers["content-type"]
|
||||
|
||||
def test_provider_health_route_before_catchall(self, client):
|
||||
"""The dedicated route is registered before the SPA catch-all."""
|
||||
paths = [getattr(r, "path", None) for r in client.app.routes]
|
||||
assert "/api/provider/health" in paths
|
||||
catchall = next(
|
||||
(i for i, p in enumerate(paths) if p == "/{path:path}"), None
|
||||
)
|
||||
if catchall is not None:
|
||||
assert paths.index("/api/provider/health") < catchall
|
||||
|
||||
|
||||
class TestAgentsEndpoint:
|
||||
"""Tests for agents endpoint."""
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Regression tests for C-01 — Ctrl+K session search in the dashboard style.
|
||||
|
||||
Issue #155: PR #145 shipped SessionSearch in React layouts only. The dashboard
|
||||
style (`dashboard.js`) skips React entirely, so Ctrl+K never reached the primary
|
||||
ops example. These tests assert the vanilla-JS palette is wired into
|
||||
`dashboard.js` (and its session-select handler into the chat view), mirroring the
|
||||
source-parsing approach used by `test_form_input_names.py`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
FRONTEND = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
|
||||
)
|
||||
DASHBOARD_JS = FRONTEND / "dashboard.js"
|
||||
CHAT_JS = FRONTEND / "views" / "chat.js"
|
||||
|
||||
|
||||
class TestDashboardSessionSearch:
|
||||
def _src(self) -> str:
|
||||
return DASHBOARD_JS.read_text()
|
||||
|
||||
def test_ctrl_k_keydown_handler_present(self):
|
||||
src = self._src()
|
||||
assert "initSessionSearch" in src
|
||||
m = re.search(
|
||||
r"function initSessionSearch\(\)\s*\{(.*?)^}",
|
||||
src, re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert m, "initSessionSearch not found"
|
||||
body = m.group(1)
|
||||
assert "keydown" in body
|
||||
assert "ctrlKey" in body and "metaKey" in body
|
||||
assert "'k'" in body or '"k"' in body
|
||||
assert "preventDefault" in body
|
||||
|
||||
def test_palette_fetches_sessions_endpoint(self):
|
||||
src = self._src()
|
||||
m = re.search(
|
||||
r"async function openSessionSearch\(\)\s*\{(.*?)^}",
|
||||
src, re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert m, "openSessionSearch not found"
|
||||
assert "/sessions" in m.group(1)
|
||||
|
||||
def test_select_navigates_via_selectPage(self):
|
||||
src = self._src()
|
||||
m = re.search(
|
||||
r"function selectSession\(sessionId\)\s*\{(.*?)^}",
|
||||
src, re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert m, "selectSession not found"
|
||||
body = m.group(1)
|
||||
assert "selectPage" in body
|
||||
assert "aiui:session-select" in body
|
||||
|
||||
def test_init_invokes_session_search(self):
|
||||
src = self._src()
|
||||
init_idx = src.find("async function init()")
|
||||
assert init_idx != -1, "init() not found"
|
||||
invoke_idx = src.find("initSessionSearch()", init_idx)
|
||||
approval_idx = src.find("initApprovalStream()", init_idx)
|
||||
build_idx = src.find("await buildDashboard()", init_idx)
|
||||
assert invoke_idx != -1, "initSessionSearch() not invoked in init()"
|
||||
assert build_idx < invoke_idx, "session search must init after dashboard build"
|
||||
assert abs(invoke_idx - approval_idx) < 400, (
|
||||
"initSessionSearch() should be wired alongside initApprovalStream()"
|
||||
)
|
||||
|
||||
def test_public_api_exposes_open_session_search(self):
|
||||
src = self._src()
|
||||
assert "window.aiui.openSessionSearch" in src
|
||||
|
||||
def test_escape_closes_palette(self):
|
||||
src = self._src()
|
||||
assert "closeSessionSearch" in src
|
||||
m = re.search(
|
||||
r"function initSessionSearch\(\)\s*\{(.*?)^}",
|
||||
src, re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert "Escape" in m.group(1)
|
||||
|
||||
|
||||
class TestChatViewSessionSelectListener:
|
||||
def _src(self) -> str:
|
||||
return CHAT_JS.read_text()
|
||||
|
||||
def test_chat_listens_for_session_select(self):
|
||||
src = self._src()
|
||||
assert "aiui:session-select" in src
|
||||
assert "addEventListener('aiui:session-select'" in src
|
||||
|
||||
def test_chat_loads_selected_session(self):
|
||||
src = self._src()
|
||||
m = re.search(
|
||||
r"sessionSelectHandler\s*=\s*\(e\)\s*=>\s*\{(.*?)\};",
|
||||
src, re.DOTALL,
|
||||
)
|
||||
assert m, "sessionSelectHandler not found"
|
||||
assert "loadSession" in m.group(1)
|
||||
|
||||
def test_chat_cleanup_removes_listener(self):
|
||||
src = self._src()
|
||||
m = re.search(
|
||||
r"export function cleanup\(\)\s*\{(.*?)\n}",
|
||||
src, re.DOTALL,
|
||||
)
|
||||
assert m, "cleanup() not found"
|
||||
assert "removeEventListener('aiui:session-select'" in m.group(1)
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Tests for version consistency between pyproject.toml and __version__.py"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_version_consistency():
|
||||
"""Test that pyproject.toml version matches __version__.py"""
|
||||
# Python 3.11+ has tomllib in stdlib, older versions need tomli
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
try:
|
||||
import tomli as tomllib
|
||||
except ImportError:
|
||||
# For Python < 3.11 without tomli, use a simple regex approach
|
||||
import re
|
||||
|
||||
# Read pyproject.toml
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
pyproject_path = project_root / "pyproject.toml"
|
||||
with open(pyproject_path, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract version from pyproject.toml
|
||||
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
|
||||
if not match:
|
||||
raise ValueError("Could not find version in pyproject.toml")
|
||||
pyproject_version = match.group(1)
|
||||
|
||||
# Import and check __version__
|
||||
from praisonaiui import __version__
|
||||
|
||||
assert __version__ == pyproject_version, (
|
||||
f"Version mismatch: pyproject.toml has {pyproject_version} "
|
||||
f"but __version__.py has {__version__}"
|
||||
)
|
||||
return
|
||||
|
||||
# Standard approach with tomllib/tomli
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
pyproject_path = project_root / "pyproject.toml"
|
||||
|
||||
with open(pyproject_path, "rb") as f:
|
||||
pyproject = tomllib.load(f)
|
||||
|
||||
pyproject_version = pyproject["project"]["version"]
|
||||
|
||||
from praisonaiui import __version__
|
||||
|
||||
assert __version__ == pyproject_version, (
|
||||
f"Version mismatch: pyproject.toml has {pyproject_version} "
|
||||
f"but __version__.py has {__version__}"
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Tests for Windows encoding compatibility in CLI output.
|
||||
|
||||
These verify the dynamic ``_icon()`` fallback: rich Unicode symbols are kept on
|
||||
UTF-aware terminals (macOS/Linux/Windows Terminal) and degrade to ASCII only on
|
||||
legacy Windows code pages (cp1252/cp437) so output never raises
|
||||
``UnicodeEncodeError``.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from praisonaiui import cli
|
||||
from praisonaiui.cli import _icon, _supports_unicode
|
||||
|
||||
|
||||
class TestSupportsUnicode:
|
||||
"""Detection of UTF-aware terminals."""
|
||||
|
||||
def test_utf8_encoding_is_supported(self):
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = "utf-8"
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
assert _supports_unicode() is True
|
||||
|
||||
def test_cp1252_encoding_is_not_supported(self):
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = "cp1252"
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
assert _supports_unicode() is False
|
||||
|
||||
def test_missing_encoding_falls_back_to_ascii(self):
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = None
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
assert _supports_unicode() is False
|
||||
|
||||
|
||||
class TestIconHelper:
|
||||
"""The _icon() helper picks symbol vs fallback based on terminal."""
|
||||
|
||||
def test_returns_symbol_on_utf8(self):
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = "utf-8"
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
assert _icon("✅", "[OK]") == "✅"
|
||||
|
||||
def test_returns_fallback_on_cp1252(self):
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = "cp1252"
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
assert _icon("✅", "[OK]") == "[OK]"
|
||||
|
||||
def test_fallback_is_pure_ascii(self):
|
||||
"""Every fallback must be encodable on a legacy code page."""
|
||||
mock_stdout = mock.MagicMock()
|
||||
mock_stdout.encoding = "cp1252"
|
||||
with mock.patch("sys.stdout", mock_stdout):
|
||||
for symbol, fallback in [
|
||||
("✅", "[PASS]"),
|
||||
("⚠️", "[WARN]"),
|
||||
("❌", "[FAIL]"),
|
||||
("⏳", "..."),
|
||||
("▶", ">"),
|
||||
("═", "="),
|
||||
]:
|
||||
result = _icon(symbol, fallback)
|
||||
result.encode("cp1252") # must not raise
|
||||
|
||||
|
||||
class TestUtf8Reconfigure:
|
||||
"""The module reconfigures stdio to UTF-8 on Windows at import time."""
|
||||
|
||||
def test_utf8_reconfigure_on_windows(self):
|
||||
import importlib
|
||||
|
||||
with mock.patch("sys.platform", "win32"):
|
||||
mock_reconfigure = mock.MagicMock()
|
||||
with mock.patch.object(sys.stdout, "reconfigure", mock_reconfigure, create=True):
|
||||
with mock.patch.object(sys.stderr, "reconfigure", mock_reconfigure, create=True):
|
||||
importlib.reload(cli)
|
||||
assert mock_reconfigure.call_count >= 1
|
||||
mock_reconfigure.assert_called_with(encoding="utf-8")
|
||||
|
||||
# Reload once more without the Windows patch so other tests see the
|
||||
# real module state.
|
||||
importlib.reload(cli)
|
||||
@@ -207,7 +207,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "aiui"
|
||||
version = "0.3.121"
|
||||
version = "0.3.127"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
@@ -367,9 +367,9 @@ requires-dist = [
|
||||
{ name = "mem0ai", marker = "extra == 'knowledge'", specifier = ">=0.1.0" },
|
||||
{ name = "mem0ai", extras = ["graph"], marker = "extra == 'graph'", specifier = ">=0.1.0" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0" },
|
||||
{ name = "praisonai", extras = ["bot", "api", "os"], marker = "extra == 'praisonai'", specifier = ">=2.0.0" },
|
||||
{ name = "praisonai", extras = ["bot", "api", "os"], marker = "extra == 'praisonai'", specifier = ">=4.6.0" },
|
||||
{ name = "praisonaiagents", extras = ["a2ui"], marker = "extra == 'a2ui'", specifier = ">=1.6.38" },
|
||||
{ name = "praisonaiagents", extras = ["knowledge", "search", "crawl"], marker = "extra == 'praisonai'", specifier = ">=1.5.56" },
|
||||
{ name = "praisonaiagents", extras = ["knowledge", "search", "crawl"], marker = "extra == 'praisonai'", specifier = ">=1.6.46" },
|
||||
{ name = "praisonaiagents", extras = ["llm"], marker = "extra == 'dev'", specifier = ">=1.5.56" },
|
||||
{ name = "psutil", marker = "extra == 'praisonai'", specifier = ">=5.9.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0" },
|
||||
@@ -4961,13 +4961,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonai"
|
||||
version = "4.5.33"
|
||||
version = "4.6.71"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "litellm" },
|
||||
{ name = "markdown" },
|
||||
{ name = "mcp" },
|
||||
{ name = "praisonaiagents" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyparsing" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -4975,9 +4976,9 @@ dependencies = [
|
||||
{ name = "textual" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/cd/afe5d205376ab485d218d30e6a882efd4cc02a8340f15d397138c60712ee/praisonai-4.5.33.tar.gz", hash = "sha256:ae32eacb9a0247088a6f6fc43be6460eeaa8cc8c07a2aad46fcd01fc44bfa777", size = 1686757, upload-time = "2026-03-10T06:07:47.771Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/609ab1bdeef2575a32e8042d8014c739201561573db946db4d703be91511/praisonai-4.6.71.tar.gz", hash = "sha256:eddea7915f4d70f0932e968c5b26fb4f3cb17cb83972d4a583418559dae61f09", size = 2129838, upload-time = "2026-06-22T23:54:32.224Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/e4/1130314a1f9ddaa90612d931d23fca80feca95a2b18d74d76859fa660b24/praisonai-4.5.33-py3-none-any.whl", hash = "sha256:3bc4ac16af293609126c5b7e3788cde95927a98bedd6e2c8528eb3328253f38a", size = 1979940, upload-time = "2026-03-10T06:07:45.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/53/ece82f6b7c01f0b0fe9bf34442ae3fa2ab87c909f36babb5534b4984c80b/praisonai-4.6.71-py3-none-any.whl", hash = "sha256:fc60aa5a9b87430ec7be2f5a638bc1330b26bbb19275ff4f6ec20f4a49088fc3", size = 2559663, upload-time = "2026-06-22T23:54:29.49Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -4989,8 +4990,11 @@ api = [
|
||||
]
|
||||
bot = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "aiui" },
|
||||
{ name = "brotlicffi" },
|
||||
{ name = "ddgs" },
|
||||
{ name = "discord-py" },
|
||||
{ name = "psutil" },
|
||||
{ name = "python-telegram-bot" },
|
||||
{ name = "slack-bolt" },
|
||||
{ name = "slack-sdk" },
|
||||
@@ -5004,7 +5008,7 @@ os = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "1.6.43"
|
||||
version = "1.6.71"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@@ -5014,9 +5018,9 @@ dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/73/d37d1478c8471869b1a7aa7af0d1b70cdff26de39fc4abebd5529f1c49f7/praisonaiagents-1.6.43.tar.gz", hash = "sha256:6ec949d38b650969a91f7494828f21bfcad0da36a3ec433eb46e8fa072dda42e", size = 1436634, upload-time = "2026-05-20T11:09:16.645Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/9b/4e1431d7b2125dab034192984d5f419e96bd412d314871450855d52e5bab/praisonaiagents-1.6.71.tar.gz", hash = "sha256:ea048bc8dc00bea911be050e881c7bbaf167ac2d4805b4c3013958ba8ee63e59", size = 1589730, upload-time = "2026-06-22T23:52:30.753Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/97/768038d2d3a2f791de7e6cf0df2783c590b9262ba3ec895308236715b033/praisonaiagents-1.6.43-py3-none-any.whl", hash = "sha256:27028366b0bde0267abdbfe4140d4d5a4eed7e5c9bdb544243973dba8d5cb57b", size = 1511490, upload-time = "2026-05-20T11:09:14.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/cf/b177db73453470093a421f8cdb07e959d546ebfc147842cfdd5937827e0e/praisonaiagents-1.6.71-py3-none-any.whl", hash = "sha256:50878751aa9b27960aeeda80190ed291c6bd2fa8ab9cf398cda3f55908860eae", size = 1679696, upload-time = "2026-06-22T23:52:28.673Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
||||
Reference in new issue
Block a user