sea-dev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/tasks/README.md +89 -0
- package/.cursor/rules/commits.mdc +31 -0
- package/.cursor/rules/general.mdc +84 -0
- package/.github/workflows/ci-cd.yml +141 -0
- package/CLAUDE.md +337 -0
- package/README.md +129 -0
- package/apps/api/.prettierignore +6 -0
- package/apps/api/.prettierrc.js +3 -0
- package/apps/api/dotenvx-safe.sh +11 -0
- package/apps/api/eslint.config.mjs +3 -0
- package/apps/api/package.json +58 -0
- package/apps/api/src/clients/posthog.ts +25 -0
- package/apps/api/src/dal/submission.ts +59 -0
- package/apps/api/src/errors.ts +55 -0
- package/apps/api/src/index.ts +21 -0
- package/apps/api/src/lib/channel.ts +28 -0
- package/apps/api/src/lib/config.ts +9 -0
- package/apps/api/src/lib/fmt.test.ts +9 -0
- package/apps/api/src/lib/fmt.ts +62 -0
- package/apps/api/src/lib/invariant.ts +23 -0
- package/apps/api/src/middleware/auth.ts +66 -0
- package/apps/api/src/routes/index.ts +20 -0
- package/apps/api/src/routes/v2/chat/handlers.ts +693 -0
- package/apps/api/src/routes/v2/chat/index.ts +257 -0
- package/apps/api/src/routes/v2/chat/schemas.ts +43 -0
- package/apps/api/src/routes/v2/deals/handlers.ts +64 -0
- package/apps/api/src/routes/v2/deals/index.ts +88 -0
- package/apps/api/src/routes/v2/deals/schemas.ts +38 -0
- package/apps/api/src/routes/v2/forms/handlers.ts +415 -0
- package/apps/api/src/routes/v2/forms/index.ts +382 -0
- package/apps/api/src/routes/v2/forms/schemas.ts +243 -0
- package/apps/api/src/routes/v2/index.ts +19 -0
- package/apps/api/src/routes/v2/pipelines/handlers.ts +261 -0
- package/apps/api/src/routes/v2/pipelines/index.ts +224 -0
- package/apps/api/src/routes/v2/pipelines/schemas.ts +173 -0
- package/apps/api/src/routes/v2/submissions/handlers.ts +555 -0
- package/apps/api/src/routes/v2/submissions/index.ts +366 -0
- package/apps/api/src/routes/v2/submissions/schemas.ts +233 -0
- package/apps/api/src/routes/v2/workflows/handlers.ts +81 -0
- package/apps/api/src/routes/v2/workflows/index.ts +88 -0
- package/apps/api/src/routes/v2/workflows/schemas.ts +40 -0
- package/apps/api/src/server.ts +146 -0
- package/apps/api/src/static/favicon.ico +0 -0
- package/apps/api/src/types/api.ts +14 -0
- package/apps/api/src/types/result.ts +3 -0
- package/apps/api/tsconfig.json +22 -0
- package/apps/api/vite.config.ts +28 -0
- package/apps/api/vitest.config.ts +14 -0
- package/apps/conversion-worker/Dockerfile +59 -0
- package/apps/conversion-worker/package.json +31 -0
- package/apps/conversion-worker/src/lib/config.ts +7 -0
- package/apps/conversion-worker/src/main.ts +22 -0
- package/apps/conversion-worker/src/workflows/convert-pptx.ts +116 -0
- package/apps/conversion-worker/tsconfig.json +27 -0
- package/apps/conversion-worker/vite.config.ts +33 -0
- package/apps/main/.prettierignore +6 -0
- package/apps/main/.prettierrc.js +3 -0
- package/apps/main/CLAUDE.md +245 -0
- package/apps/main/Procfile +1 -0
- package/apps/main/README.md +193 -0
- package/apps/main/db-tests.jsonl +116 -0
- package/apps/main/dotenvx-safe.sh +11 -0
- package/apps/main/drizzle/meta/_journal.json +1 -0
- package/apps/main/drizzle.config.ts +25 -0
- package/apps/main/eslint.config.mjs +3 -0
- package/apps/main/generate-routes.mjs +5 -0
- package/apps/main/package.json +131 -0
- package/apps/main/playwright.config.ts +23 -0
- package/apps/main/postcss.config.ts +5 -0
- package/apps/main/public/bg-dark.svg +10 -0
- package/apps/main/public/bg.svg +10 -0
- package/apps/main/public/favicon.ico +0 -0
- package/apps/main/run.sh +146 -0
- package/apps/main/scripts/browser.ts +14 -0
- package/apps/main/scripts/db-test-cov.ts +277 -0
- package/apps/main/scripts/login.ts +78 -0
- package/apps/main/scripts/repl.ts +61 -0
- package/apps/main/src/_foo.ts +31 -0
- package/apps/main/src/_tests/db.test.ts +19 -0
- package/apps/main/src/_tests/mock-db.ts +60 -0
- package/apps/main/src/client.tsx +13 -0
- package/apps/main/src/clients/loops.ts +13 -0
- package/apps/main/src/clients/polar.ts +12 -0
- package/apps/main/src/clients/posthog.ts +12 -0
- package/apps/main/src/components/chat/chat-context.tsx +99 -0
- package/apps/main/src/components/chat/chat-messages.tsx +184 -0
- package/apps/main/src/components/chat/chat-status.tsx +140 -0
- package/apps/main/src/components/chat/chat.tsx +458 -0
- package/apps/main/src/components/chat/citation-modal.tsx +54 -0
- package/apps/main/src/components/cta.tsx +21 -0
- package/apps/main/src/components/data-display/derived.tsx +40 -0
- package/apps/main/src/components/data-display/group-single.tsx +57 -0
- package/apps/main/src/components/data-display/group-table.tsx +165 -0
- package/apps/main/src/components/data-display/group-wrapper.tsx +54 -0
- package/apps/main/src/components/data-display/item.tsx +678 -0
- package/apps/main/src/components/error.tsx +45 -0
- package/apps/main/src/components/forms/error.tsx +22 -0
- package/apps/main/src/components/grid.tsx +7 -0
- package/apps/main/src/components/header/container.tsx +73 -0
- package/apps/main/src/components/header/header-bar.tsx +102 -0
- package/apps/main/src/components/modals/copy-display.tsx +37 -0
- package/apps/main/src/components/modals/copy-form.tsx +152 -0
- package/apps/main/src/components/modals/duplicate-workflow.tsx +89 -0
- package/apps/main/src/components/modals/field-correction.tsx +323 -0
- package/apps/main/src/components/modals/form-viewer.tsx +126 -0
- package/apps/main/src/components/modals/modals.tsx +44 -0
- package/apps/main/src/components/modals/new-deal.tsx +78 -0
- package/apps/main/src/components/modals/new-form.tsx +133 -0
- package/apps/main/src/components/modals/new-pipeline.tsx +70 -0
- package/apps/main/src/components/modals/new-submission.tsx +321 -0
- package/apps/main/src/components/modals/new-workflow.tsx +342 -0
- package/apps/main/src/components/modals/transformation-sources-modal.tsx +157 -0
- package/apps/main/src/components/modals/view-report.tsx +193 -0
- package/apps/main/src/components/not-found.tsx +14 -0
- package/apps/main/src/components/search/search-bar.tsx +178 -0
- package/apps/main/src/components/sheet-selector.tsx +135 -0
- package/apps/main/src/components/side-panel/doc-list.tsx +480 -0
- package/apps/main/src/components/sidebar/admin-sidebar.tsx +75 -0
- package/apps/main/src/components/sidebar/app-sidebar.tsx +417 -0
- package/apps/main/src/components/sidebar/model-select.tsx +134 -0
- package/apps/main/src/components/sidebar/settings-sidebar.tsx +132 -0
- package/apps/main/src/components/sidebar/sidebar-right.tsx +22 -0
- package/apps/main/src/components/sidebar/stop-impersonate.tsx +21 -0
- package/apps/main/src/components/svg/loading.tsx +33 -0
- package/apps/main/src/components/theme-selector.tsx +43 -0
- package/apps/main/src/components/unsaved-badge.tsx +19 -0
- package/apps/main/src/components/upload/file-upload.tsx +354 -0
- package/apps/main/src/fns/submission-groups.ts +28 -0
- package/apps/main/src/fns/submission-items.ts +11 -0
- package/apps/main/src/global-middleware.ts +16 -0
- package/apps/main/src/hooks/use-update-state.ts +18 -0
- package/apps/main/src/lib/auth-client.ts +16 -0
- package/apps/main/src/lib/auth.test.ts +359 -0
- package/apps/main/src/lib/auth.ts +144 -0
- package/apps/main/src/lib/billing.ts +23 -0
- package/apps/main/src/lib/config-iso.ts +76 -0
- package/apps/main/src/lib/config.ts +61 -0
- package/apps/main/src/lib/excel.ts +16 -0
- package/apps/main/src/lib/feedback-cache.ts +70 -0
- package/apps/main/src/lib/logger.ts +44 -0
- package/apps/main/src/lib/models.ts +22 -0
- package/apps/main/src/lib/not-found.ts +17 -0
- package/apps/main/src/lib/pdf.ts +16 -0
- package/apps/main/src/lib/tabularize.ts +54 -0
- package/apps/main/src/lib/utils.ts +10 -0
- package/apps/main/src/lib/zfd.ts +217 -0
- package/apps/main/src/middleware.ts +55 -0
- package/apps/main/src/routeTree.gen.ts +1255 -0
- package/apps/main/src/router.tsx +24 -0
- package/apps/main/src/routes/__root.tsx +92 -0
- package/apps/main/src/routes/_authed/_app/(dashboard)/index.tsx +227 -0
- package/apps/main/src/routes/_authed/_app/agents/$agentId/config.tsx +224 -0
- package/apps/main/src/routes/_authed/_app/agents/$agentId/index.tsx +206 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-actions-menu.tsx +94 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-artifacts.tsx +153 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-chat.tsx +220 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-history-menu.tsx +81 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-model-select.tsx +84 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-relevant-items.tsx +226 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-upload-button.tsx +298 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/context-modal.tsx +187 -0
- package/apps/main/src/routes/_authed/_app/agents/-fns.ts +560 -0
- package/apps/main/src/routes/_authed/_app/agents/index.tsx +65 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId/-components/citation-tree.tsx +268 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId.tsx +655 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/doc-loading.tsx +37 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/share-link.tsx +42 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-card.tsx +89 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-filter.tsx +193 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submissions.tsx +36 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/summary.tsx +82 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/upload-doc.tsx +120 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-fns.ts +653 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/index.tsx +259 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/route.tsx +29 -0
- package/apps/main/src/routes/_authed/_app/deals/index.tsx +104 -0
- package/apps/main/src/routes/_authed/_app/feedback/index.tsx +639 -0
- package/apps/main/src/routes/_authed/_app/feedback/insights.tsx +250 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/blockers-panel.tsx +260 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/manual-input-panel.tsx +301 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/submission-selector-modal.tsx +143 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/upload-doc.tsx +120 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/index.tsx +1485 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/dag-view.tsx +296 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/step-config-modal.tsx +634 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/index.tsx +911 -0
- package/apps/main/src/routes/_authed/_app/pipelines/-fns.ts +510 -0
- package/apps/main/src/routes/_authed/_app/pipelines/index.tsx +103 -0
- package/apps/main/src/routes/_authed/_app/reports/$reportId.tsx +397 -0
- package/apps/main/src/routes/_authed/_app/reports/-fns.ts +11 -0
- package/apps/main/src/routes/_authed/_app/reports/index.tsx +22 -0
- package/apps/main/src/routes/_authed/_app/route.tsx +48 -0
- package/apps/main/src/routes/_authed/_app/submissions/-columns.tsx +161 -0
- package/apps/main/src/routes/_authed/_app/submissions/-fns.ts +128 -0
- package/apps/main/src/routes/_authed/_app/submissions/index.tsx +190 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/$formId.tsx +542 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/derived.tsx +154 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/field.tsx +369 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/group.tsx +475 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/index.tsx +263 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/route.tsx +33 -0
- package/apps/main/src/routes/_authed/_app/workflows/-components/form-card.tsx +315 -0
- package/apps/main/src/routes/_authed/_app/workflows/index.tsx +86 -0
- package/apps/main/src/routes/_authed/admin/index.tsx +12 -0
- package/apps/main/src/routes/_authed/admin/route.tsx +42 -0
- package/apps/main/src/routes/_authed/admin/users/-columns.tsx +124 -0
- package/apps/main/src/routes/_authed/admin/users/-fns.ts +30 -0
- package/apps/main/src/routes/_authed/admin/users/index.tsx +29 -0
- package/apps/main/src/routes/_authed/catchNotFound.tsx +114 -0
- package/apps/main/src/routes/_authed/redirects/forms.$id.tsx +29 -0
- package/apps/main/src/routes/_authed/redirects/submissions.$id.tsx +27 -0
- package/apps/main/src/routes/_authed/redirects/workflows.$id.tsx +27 -0
- package/apps/main/src/routes/_authed/route.tsx +51 -0
- package/apps/main/src/routes/_authed/settings/-components/new-api-key.tsx +85 -0
- package/apps/main/src/routes/_authed/settings/-components/new-invite.tsx +100 -0
- package/apps/main/src/routes/_authed/settings/analytics.tsx +1710 -0
- package/apps/main/src/routes/_authed/settings/billing/-components/price-table.tsx +129 -0
- package/apps/main/src/routes/_authed/settings/billing/-fns.ts +76 -0
- package/apps/main/src/routes/_authed/settings/billing/index.tsx +119 -0
- package/apps/main/src/routes/_authed/settings/embed.tsx +337 -0
- package/apps/main/src/routes/_authed/settings/index.tsx +12 -0
- package/apps/main/src/routes/_authed/settings/keys.tsx +157 -0
- package/apps/main/src/routes/_authed/settings/members.tsx +276 -0
- package/apps/main/src/routes/_authed/settings/route.tsx +22 -0
- package/apps/main/src/routes/_authed/settings/user.tsx +87 -0
- package/apps/main/src/routes/_authed/settings/workspace.tsx +206 -0
- package/apps/main/src/routes/_public/-components/sign-in-up.tsx +96 -0
- package/apps/main/src/routes/_public/embedded.tsx +57 -0
- package/apps/main/src/routes/_public/invite.$inviteId.tsx +143 -0
- package/apps/main/src/routes/_public/no-access.tsx +38 -0
- package/apps/main/src/routes/_public/no-invite.tsx +39 -0
- package/apps/main/src/routes/_public/otp.tsx +103 -0
- package/apps/main/src/routes/_public/route.tsx +15 -0
- package/apps/main/src/routes/_public/sign-in.tsx +111 -0
- package/apps/main/src/routes/_public/sign-up.tsx +114 -0
- package/apps/main/src/routes/api/auth/$.ts +11 -0
- package/apps/main/src/routes/api/billing/paid.ts +42 -0
- package/apps/main/src/routes/api/billing/webhooks.ts +70 -0
- package/apps/main/src/routes/api/chat/agent.ts +40 -0
- package/apps/main/src/routes/api/chat/key.ts +42 -0
- package/apps/main/src/routes/api/chat/member.ts +35 -0
- package/apps/main/src/routes/api/test/index.ts +19 -0
- package/apps/main/src/server.tsx +6 -0
- package/apps/main/src/styles/app.css +23 -0
- package/apps/main/src/vite-env.d.ts +7 -0
- package/apps/main/test.http +6 -0
- package/apps/main/tsconfig.json +17 -0
- package/apps/main/vite.config.ts +24 -0
- package/apps/main/vitest.config.js +17 -0
- package/apps/mcp/README.md +171 -0
- package/apps/mcp/eslint.config.mjs +3 -0
- package/apps/mcp/package.json +37 -0
- package/apps/mcp/src/index.ts +414 -0
- package/apps/mcp/tsconfig.json +19 -0
- package/apps/mcp/vite.config.ts +22 -0
- package/apps/posthog-proxy/index.html +9 -0
- package/apps/workers/.prettierignore +7 -0
- package/apps/workers/.prettierrc.js +3 -0
- package/apps/workers/dotenvx-safe.sh +11 -0
- package/apps/workers/eslint.config.mjs +3 -0
- package/apps/workers/package.json +65 -0
- package/apps/workers/src/lib/config.ts +7 -0
- package/apps/workers/src/lib/messages.ts +0 -0
- package/apps/workers/src/lib/posthog.ts +25 -0
- package/apps/workers/src/main.ts +58 -0
- package/apps/workers/src/workflows/extraction.ts +866 -0
- package/apps/workers/src/workflows/index.ts +3 -0
- package/apps/workers/src/workflows/pipeline-dag.ts +210 -0
- package/apps/workers/src/workflows/pipeline-steps.ts +1393 -0
- package/apps/workers/tsconfig.json +16 -0
- package/apps/workers/vite.config.ts +35 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/agent-templates-and-runs.md +859 -0
- package/docs/aws-migration-plan.md +267 -0
- package/docs/impl-p0-form-builder-improvements.md +683 -0
- package/docs/on-prem-deployment-spec.docx +0 -0
- package/docs/on-prem-deployment-spec.md +378 -0
- package/docs/prd-form-builder-strategy.md +1120 -0
- package/docs/widget-ng-apf-packaging-spec.md +43 -0
- package/infra/k8s/charts/seadotdev/Chart.yaml +6 -0
- package/infra/k8s/charts/seadotdev/templates/_helpers.tpl +27 -0
- package/infra/k8s/charts/seadotdev/templates/api-v2.yaml +105 -0
- package/infra/k8s/charts/seadotdev/templates/external-secrets.yaml +83 -0
- package/infra/k8s/charts/seadotdev/templates/ingress.yaml +54 -0
- package/infra/k8s/charts/seadotdev/templates/main-app.yaml +104 -0
- package/infra/k8s/charts/seadotdev/templates/workers.yaml +182 -0
- package/infra/k8s/charts/seadotdev/values.yaml +143 -0
- package/infra/terraform/main.tf +399 -0
- package/libs/ai/.prettierignore +2 -0
- package/libs/ai/.prettierrc.js +5 -0
- package/libs/ai/README.md +139 -0
- package/libs/ai/eslint.config.mjs +3 -0
- package/libs/ai/package.json +42 -0
- package/libs/ai/src/index.ts +5 -0
- package/libs/ai/src/models.ts +19 -0
- package/libs/ai/src/rag/index.ts +1 -0
- package/libs/ai/src/rag/rag.test.ts +99 -0
- package/libs/ai/src/rag/rag.ts +510 -0
- package/libs/ai/tsconfig.json +21 -0
- package/libs/ai/vite.config.ts +38 -0
- package/libs/cache/.prettierignore +2 -0
- package/libs/cache/eslint.config.mjs +3 -0
- package/libs/cache/package.json +35 -0
- package/libs/cache/src/feedback.ts +77 -0
- package/libs/cache/src/index.ts +2 -0
- package/libs/cache/tsconfig.json +19 -0
- package/libs/cache/vite.config.ts +36 -0
- package/libs/clients/.prettierignore +6 -0
- package/libs/clients/eslint.config.mjs +3 -0
- package/libs/clients/package.json +59 -0
- package/libs/clients/src/azure.ts +249 -0
- package/libs/clients/src/gcp.ts +220 -0
- package/libs/clients/src/hatchet.ts +86 -0
- package/libs/clients/src/index.ts +8 -0
- package/libs/clients/src/loops.ts +86 -0
- package/libs/clients/src/polar.ts +77 -0
- package/libs/clients/src/posthog.ts +55 -0
- package/libs/clients/tsconfig.json +19 -0
- package/libs/clients/vite.config.ts +35 -0
- package/libs/config/.prettierignore +6 -0
- package/libs/config/.prettierrc.js +12 -0
- package/libs/config/eslint.config.mjs +3 -0
- package/libs/config/package.json +50 -0
- package/libs/config/src/azure.ts +54 -0
- package/libs/config/src/db.ts +18 -0
- package/libs/config/src/gcp.ts +53 -0
- package/libs/config/src/google.ts +17 -0
- package/libs/config/src/hatchet.ts +20 -0
- package/libs/config/src/index.ts +108 -0
- package/libs/config/src/llm.ts +17 -0
- package/libs/config/src/polar.ts +24 -0
- package/libs/config/src/util.ts +8 -0
- package/libs/config/src/vercel.ts +26 -0
- package/libs/config/tsconfig.json +19 -0
- package/libs/config/vite.config.ts +34 -0
- package/libs/core/.prettierignore +2 -0
- package/libs/core/eslint.config.mjs +3 -0
- package/libs/core/package.json +59 -0
- package/libs/core/src/chat/derived.ts +97 -0
- package/libs/core/src/chat/feedback.ts +293 -0
- package/libs/core/src/chat/index.ts +6 -0
- package/libs/core/src/chat/model.ts +92 -0
- package/libs/core/src/chat/prepare-tools.ts +286 -0
- package/libs/core/src/chat/prompts.ts +623 -0
- package/libs/core/src/chat/stream.ts +311 -0
- package/libs/core/src/chat/summarize.ts +168 -0
- package/libs/core/src/chat/tools/agent.ts +403 -0
- package/libs/core/src/chat/tools/chart-agent.ts +526 -0
- package/libs/core/src/chat/tools/chart-helpers/sandbox.ts +47 -0
- package/libs/core/src/chat/tools/chart.ts +86 -0
- package/libs/core/src/chat/tools/credit-agent.ts +1383 -0
- package/libs/core/src/chat/tools/credit.ts +1435 -0
- package/libs/core/src/chat/tools/deep-dive-agent.ts +100 -0
- package/libs/core/src/chat/tools/deep-dive.ts +141 -0
- package/libs/core/src/chat/tools/form.ts +449 -0
- package/libs/core/src/chat/tools/helpers.ts +91 -0
- package/libs/core/src/chat/tools/index.ts +42 -0
- package/libs/core/src/chat/tools/pipeline-artifact.ts +76 -0
- package/libs/core/src/chat/tools/report.ts +40 -0
- package/libs/core/src/chat/tools/search.ts +390 -0
- package/libs/core/src/chat/tools/submission.ts +227 -0
- package/libs/core/src/chat/tools/workflow.ts +684 -0
- package/libs/core/src/chat/types.ts +3 -0
- package/libs/core/src/data-extraction/classification/azure.ts +168 -0
- package/libs/core/src/data-extraction/classification/index.ts +1 -0
- package/libs/core/src/data-extraction/dal.ts +246 -0
- package/libs/core/src/data-extraction/form-structure-extractor.ts +294 -0
- package/libs/core/src/data-extraction/index.ts +4 -0
- package/libs/core/src/data-extraction/layout/azure.ts +730 -0
- package/libs/core/src/data-extraction/layout/excel.ts +180 -0
- package/libs/core/src/data-extraction/layout/gcp.ts +1071 -0
- package/libs/core/src/data-extraction/layout/index.ts +266 -0
- package/libs/core/src/data-extraction/layout/plaintext.ts +45 -0
- package/libs/core/src/data-extraction/models.ts +38 -0
- package/libs/core/src/data-extraction/pdf-utils.ts +96 -0
- package/libs/core/src/data-extraction/structuring/bank-statement.ts +1182 -0
- package/libs/core/src/data-extraction/structuring/custom.ts +495 -0
- package/libs/core/src/data-extraction/structuring/index.ts +290 -0
- package/libs/core/src/data-extraction/structuring/prompts.ts +69 -0
- package/libs/core/src/data-extraction/type-guards.ts +110 -0
- package/libs/core/src/data-extraction/types.ts +84 -0
- package/libs/core/src/data-extraction/utils.ts +31 -0
- package/libs/core/src/data-extraction/validation/bank-statement.ts +127 -0
- package/libs/core/src/deals.ts +17 -0
- package/libs/core/src/documents.ts +152 -0
- package/libs/core/src/index.ts +5 -0
- package/libs/core/src/pipelines/display.ts +678 -0
- package/libs/core/src/pipelines/execute.ts +2342 -0
- package/libs/core/src/pipelines/index.ts +4 -0
- package/libs/core/src/pipelines/list.ts +12 -0
- package/libs/core/src/pipelines/runs.ts +53 -0
- package/libs/core/tsconfig.json +20 -0
- package/libs/core/vite.config.ts +56 -0
- package/libs/dal/.prettierignore +6 -0
- package/libs/dal/.prettierrc.js +12 -0
- package/libs/dal/eslint.config.mjs +3 -0
- package/libs/dal/package.json +57 -0
- package/libs/dal/src/_tests/db.test.ts +19 -0
- package/libs/dal/src/_tests/mock-db.ts +60 -0
- package/libs/dal/src/api-key.test.ts +397 -0
- package/libs/dal/src/api-key.ts +110 -0
- package/libs/dal/src/billing.ts +23 -0
- package/libs/dal/src/conversation.test.ts +655 -0
- package/libs/dal/src/conversation.ts +532 -0
- package/libs/dal/src/deal.test.ts +45 -0
- package/libs/dal/src/deal.ts +87 -0
- package/libs/dal/src/defaults-consumer-lending-uk.ts +33 -0
- package/libs/dal/src/defaults-consumer-lending-us.ts +33 -0
- package/libs/dal/src/defaults-private-credit.ts +57 -0
- package/libs/dal/src/defaults-private-equity.ts +51 -0
- package/libs/dal/src/defaults-smb-lending-us.ts +1569 -0
- package/libs/dal/src/defaults-sme-lending-uk-express.ts +1527 -0
- package/libs/dal/src/defaults-sme-lending-uk.ts +1669 -0
- package/libs/dal/src/defaults-types.ts +23 -0
- package/libs/dal/src/defaults.ts +550 -0
- package/libs/dal/src/document.test.ts +70 -0
- package/libs/dal/src/document.ts +192 -0
- package/libs/dal/src/feedback.ts +255 -0
- package/libs/dal/src/form.test.ts +637 -0
- package/libs/dal/src/form.ts +1165 -0
- package/libs/dal/src/index.ts +20 -0
- package/libs/dal/src/invitation.test.ts +746 -0
- package/libs/dal/src/invitation.ts +207 -0
- package/libs/dal/src/member.test.ts +185 -0
- package/libs/dal/src/member.ts +80 -0
- package/libs/dal/src/organization.ts +116 -0
- package/libs/dal/src/permission.ts +25 -0
- package/libs/dal/src/pipeline.test.ts +388 -0
- package/libs/dal/src/pipeline.ts +4222 -0
- package/libs/dal/src/report.ts +199 -0
- package/libs/dal/src/result.ts +16 -0
- package/libs/dal/src/search.ts +172 -0
- package/libs/dal/src/session.test.ts +110 -0
- package/libs/dal/src/session.ts +31 -0
- package/libs/dal/src/submission.test.ts +1304 -0
- package/libs/dal/src/submission.ts +1396 -0
- package/libs/dal/src/tool.ts +159 -0
- package/libs/dal/src/user.ts +16 -0
- package/libs/dal/src/workflow.test.ts +89 -0
- package/libs/dal/src/workflow.ts +262 -0
- package/libs/dal/tsconfig.build.json +4 -0
- package/libs/dal/tsconfig.json +22 -0
- package/libs/dal/vite.config.ts +34 -0
- package/libs/db/.prettierignore +6 -0
- package/libs/db/.prettierrc.js +12 -0
- package/libs/db/eslint.config.mjs +3 -0
- package/libs/db/package.json +52 -0
- package/libs/db/src/index.ts +24 -0
- package/libs/db/src/relations.ts +549 -0
- package/libs/db/src/schema.ts +2 -0
- package/libs/db/src/schemas/api.ts +35 -0
- package/libs/db/src/schemas/conversations.ts +175 -0
- package/libs/db/src/schemas/core.ts +359 -0
- package/libs/db/src/schemas/documents.ts +181 -0
- package/libs/db/src/schemas/feedback.ts +40 -0
- package/libs/db/src/schemas/index.ts +26 -0
- package/libs/db/src/schemas/organisations.ts +97 -0
- package/libs/db/src/schemas/pipelines.ts +440 -0
- package/libs/db/src/schemas/users.ts +95 -0
- package/libs/db/src/types.ts +190 -0
- package/libs/db/src/utils.ts +14 -0
- package/libs/db/tsconfig.json +19 -0
- package/libs/db/vite.config.ts +31 -0
- package/libs/lint/.prettierignore +6 -0
- package/libs/lint/eslint.config.mjs +61 -0
- package/libs/lint/package.json +29 -0
- package/libs/lint/prettier.config.js +12 -0
- package/libs/schemas/.prettierignore +6 -0
- package/libs/schemas/.prettierrc.js +12 -0
- package/libs/schemas/README.md +15 -0
- package/libs/schemas/eslint.config.mjs +3 -0
- package/libs/schemas/package.json +67 -0
- package/libs/schemas/src/core/chat.ts +67 -0
- package/libs/schemas/src/core/core-result.ts +15 -0
- package/libs/schemas/src/core/data-extraction.ts +184 -0
- package/libs/schemas/src/core/layout.ts +478 -0
- package/libs/schemas/src/core/pipeline.ts +128 -0
- package/libs/schemas/src/core/submission.ts +97 -0
- package/libs/schemas/src/db/account.ts +57 -0
- package/libs/schemas/src/db/apiKey.ts +57 -0
- package/libs/schemas/src/db/context.ts +33 -0
- package/libs/schemas/src/db/conversation.ts +65 -0
- package/libs/schemas/src/db/deal.ts +42 -0
- package/libs/schemas/src/db/document.ts +103 -0
- package/libs/schemas/src/db/documentCitation.ts +58 -0
- package/libs/schemas/src/db/documentExtraction.ts +69 -0
- package/libs/schemas/src/db/fieldCorrection.ts +85 -0
- package/libs/schemas/src/db/form.ts +45 -0
- package/libs/schemas/src/db/formField.ts +59 -0
- package/libs/schemas/src/db/formGroup.ts +42 -0
- package/libs/schemas/src/db/impersonation.ts +39 -0
- package/libs/schemas/src/db/index.ts +25 -0
- package/libs/schemas/src/db/invitation.ts +42 -0
- package/libs/schemas/src/db/member.ts +36 -0
- package/libs/schemas/src/db/message.ts +58 -0
- package/libs/schemas/src/db/organization.ts +62 -0
- package/libs/schemas/src/db/session.ts +48 -0
- package/libs/schemas/src/db/submission.ts +54 -0
- package/libs/schemas/src/db/submissionGroup.ts +36 -0
- package/libs/schemas/src/db/submissionItem.ts +33 -0
- package/libs/schemas/src/db/submissionItemVersion.ts +70 -0
- package/libs/schemas/src/db/user.ts +51 -0
- package/libs/schemas/src/db/utils.ts +3 -0
- package/libs/schemas/src/db/verification.ts +36 -0
- package/libs/schemas/src/db/workflow.ts +42 -0
- package/libs/schemas/src/index.ts +10 -0
- package/libs/schemas/tsconfig.json +21 -0
- package/libs/schemas/vite.config.ts +38 -0
- package/libs/ui/.prettierignore +6 -0
- package/libs/ui/.prettierrc.js +12 -0
- package/libs/ui/components.json +24 -0
- package/libs/ui/eslint.config.mjs +3 -0
- package/libs/ui/package.json +142 -0
- package/libs/ui/src/components/chart-viz/chart.tsx +255 -0
- package/libs/ui/src/components/chart-viz/converters.ts +474 -0
- package/libs/ui/src/components/chart-viz/dashboard.tsx +146 -0
- package/libs/ui/src/components/chart-viz/index.ts +37 -0
- package/libs/ui/src/components/chart-viz/markdown.tsx +344 -0
- package/libs/ui/src/components/chart-viz/table.tsx +446 -0
- package/libs/ui/src/components/chart-viz/theme-context.tsx +70 -0
- package/libs/ui/src/components/chart-viz/themes/dark.ts +98 -0
- package/libs/ui/src/components/chart-viz/themes/index.ts +69 -0
- package/libs/ui/src/components/chart-viz/themes/light.ts +98 -0
- package/libs/ui/src/components/chart-viz/themes/tailwind.ts +326 -0
- package/libs/ui/src/components/chart-viz/themes/types.ts +99 -0
- package/libs/ui/src/components/chart-viz/tool-display.tsx +150 -0
- package/libs/ui/src/components/chart-viz/types.ts +95 -0
- package/libs/ui/src/components/doc-viewers/excel/index.tsx +431 -0
- package/libs/ui/src/components/doc-viewers/excel/themes.ts +160 -0
- package/libs/ui/src/components/doc-viewers/image/index.tsx +410 -0
- package/libs/ui/src/components/doc-viewers/pdf/index.tsx +258 -0
- package/libs/ui/src/components/doc-viewers/pdf/virtualized-pdf.tsx +556 -0
- package/libs/ui/src/components/misc/rel-date.tsx +52 -0
- package/libs/ui/src/components/misc/styled-link.tsx +2 -0
- package/libs/ui/src/components/table/data-table.tsx +546 -0
- package/libs/ui/src/components/table/report-table.tsx +305 -0
- package/libs/ui/src/components/table/sortable-column.tsx +34 -0
- package/libs/ui/src/components/ui/accordion.tsx +62 -0
- package/libs/ui/src/components/ui/alert-dialog.tsx +142 -0
- package/libs/ui/src/components/ui/alert.tsx +62 -0
- package/libs/ui/src/components/ui/artifact.tsx +118 -0
- package/libs/ui/src/components/ui/attachments.tsx +388 -0
- package/libs/ui/src/components/ui/avatar.tsx +39 -0
- package/libs/ui/src/components/ui/badge.tsx +43 -0
- package/libs/ui/src/components/ui/breadcrumb.tsx +102 -0
- package/libs/ui/src/components/ui/button-group.tsx +78 -0
- package/libs/ui/src/components/ui/button.tsx +79 -0
- package/libs/ui/src/components/ui/card.tsx +32 -0
- package/libs/ui/src/components/ui/carousel.tsx +228 -0
- package/libs/ui/src/components/ui/chain-of-thought.tsx +198 -0
- package/libs/ui/src/components/ui/checkbox.tsx +27 -0
- package/libs/ui/src/components/ui/citation.tsx +34 -0
- package/libs/ui/src/components/ui/code-block.tsx +500 -0
- package/libs/ui/src/components/ui/collapsible.tsx +19 -0
- package/libs/ui/src/components/ui/command.tsx +161 -0
- package/libs/ui/src/components/ui/conversation.tsx +90 -0
- package/libs/ui/src/components/ui/dialog.tsx +142 -0
- package/libs/ui/src/components/ui/dropdown-menu.tsx +246 -0
- package/libs/ui/src/components/ui/highlight.tsx +3 -0
- package/libs/ui/src/components/ui/hover-card.tsx +36 -0
- package/libs/ui/src/components/ui/inline-citation.tsx +251 -0
- package/libs/ui/src/components/ui/input-group.tsx +156 -0
- package/libs/ui/src/components/ui/input-otp.tsx +78 -0
- package/libs/ui/src/components/ui/input.tsx +21 -0
- package/libs/ui/src/components/ui/label.tsx +19 -0
- package/libs/ui/src/components/ui/model-selector.tsx +174 -0
- package/libs/ui/src/components/ui/multisidebar.tsx +750 -0
- package/libs/ui/src/components/ui/popover.tsx +43 -0
- package/libs/ui/src/components/ui/progress.tsx +28 -0
- package/libs/ui/src/components/ui/reasoning.tsx +178 -0
- package/libs/ui/src/components/ui/resizable.tsx +49 -0
- package/libs/ui/src/components/ui/scroll-area.tsx +54 -0
- package/libs/ui/src/components/ui/select.tsx +171 -0
- package/libs/ui/src/components/ui/separator.tsx +26 -0
- package/libs/ui/src/components/ui/sheet.tsx +128 -0
- package/libs/ui/src/components/ui/shimmer.tsx +53 -0
- package/libs/ui/src/components/ui/skeleton.tsx +13 -0
- package/libs/ui/src/components/ui/sonner.tsx +23 -0
- package/libs/ui/src/components/ui/switch.tsx +26 -0
- package/libs/ui/src/components/ui/table.tsx +96 -0
- package/libs/ui/src/components/ui/tabs.tsx +52 -0
- package/libs/ui/src/components/ui/textarea.tsx +41 -0
- package/libs/ui/src/components/ui/tool.tsx +209 -0
- package/libs/ui/src/components/ui/tooltip.tsx +58 -0
- package/libs/ui/src/components/ui/typography.tsx +113 -0
- package/libs/ui/src/fonts/manrope-v15-latin-300.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-400.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-500.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-600.woff2 +0 -0
- package/libs/ui/src/hooks/use-mobile.ts +19 -0
- package/libs/ui/src/lib/utils.ts +6 -0
- package/libs/ui/src/styles/fonts.css +35 -0
- package/libs/ui/src/styles/style.css +218 -0
- package/libs/ui/tsconfig.json +21 -0
- package/libs/ui/vite.config.ts +80 -0
- package/libs/ui-lit/README.md +245 -0
- package/libs/ui-lit/TESTING_GUIDE.md +296 -0
- package/libs/ui-lit/eslint.config.mjs +3 -0
- package/libs/ui-lit/package.json +41 -0
- package/libs/ui-lit/scripts/build-css.js +43 -0
- package/libs/ui-lit/src/components/sea-alert.ts +132 -0
- package/libs/ui-lit/src/components/sea-button.ts +95 -0
- package/libs/ui-lit/src/components/sea-card.ts +113 -0
- package/libs/ui-lit/src/components/sea-input.ts +184 -0
- package/libs/ui-lit/src/components/sea-spinner.ts +65 -0
- package/libs/ui-lit/src/index.ts +15 -0
- package/libs/ui-lit/src/lib/utils.ts +6 -0
- package/libs/ui-lit/src/styles/tailwind.css +76 -0
- package/libs/ui-lit/src/theme.css +66 -0
- package/libs/ui-lit/src/theme.ts +79 -0
- package/libs/ui-lit/src/vite-env.d.ts +6 -0
- package/libs/ui-lit/tailwind.config.ts +50 -0
- package/libs/ui-lit/test.html +289 -0
- package/libs/ui-lit/tsconfig.json +23 -0
- package/libs/ui-lit/vite.config.ts +31 -0
- package/libs/ui-lit/vite.css.config.ts +20 -0
- package/libs/util/.prettierignore +6 -0
- package/libs/util/.prettierrc.js +12 -0
- package/libs/util/eslint.config.mjs +3 -0
- package/libs/util/package.json +45 -0
- package/libs/util/src/billing.ts +10 -0
- package/libs/util/src/data-transform.ts +19 -0
- package/libs/util/src/encryption.ts +45 -0
- package/libs/util/src/fmt.test.ts +9 -0
- package/libs/util/src/fmt.ts +71 -0
- package/libs/util/src/fuzzy.ts +47 -0
- package/libs/util/src/id.ts +24 -0
- package/libs/util/src/invariant.ts +31 -0
- package/libs/util/src/sub-name.ts +7 -0
- package/libs/util/tsconfig.json +19 -0
- package/libs/util/vite.config.ts +34 -0
- package/package.json +28 -0
- package/packages/widget/.prettierignore +6 -0
- package/packages/widget/.prettierrc.js +12 -0
- package/packages/widget/README.md +95 -0
- package/packages/widget/eslint.config.mjs +11 -0
- package/packages/widget/openapi-ts.config.ts +8 -0
- package/packages/widget/package.json +89 -0
- package/packages/widget/postcss.config.mjs +10 -0
- package/packages/widget/src/clients/api/client/client.ts +187 -0
- package/packages/widget/src/clients/api/client/index.ts +22 -0
- package/packages/widget/src/clients/api/client/types.ts +192 -0
- package/packages/widget/src/clients/api/client/utils.ts +394 -0
- package/packages/widget/src/clients/api/client.gen.ts +18 -0
- package/packages/widget/src/clients/api/core/auth.ts +39 -0
- package/packages/widget/src/clients/api/core/bodySerializer.ts +74 -0
- package/packages/widget/src/clients/api/core/params.ts +132 -0
- package/packages/widget/src/clients/api/core/pathSerializer.ts +169 -0
- package/packages/widget/src/clients/api/core/types.ts +80 -0
- package/packages/widget/src/clients/api/index.ts +3 -0
- package/packages/widget/src/clients/api/sdk.gen.ts +805 -0
- package/packages/widget/src/clients/api/types.gen.ts +2085 -0
- package/packages/widget/src/components/container.tsx +42 -0
- package/packages/widget/src/components/data-display.tsx +384 -0
- package/packages/widget/src/components/data-viewer.tsx +311 -0
- package/packages/widget/src/components/doc-list.tsx +102 -0
- package/packages/widget/src/components/field-correction-modal.tsx +265 -0
- package/packages/widget/src/components/header.tsx +71 -0
- package/packages/widget/src/components/new-submission.tsx +290 -0
- package/packages/widget/src/components/sidebar-right.tsx +19 -0
- package/packages/widget/src/components/submission-card.tsx +66 -0
- package/packages/widget/src/components/submission-page.tsx +75 -0
- package/packages/widget/src/components/upload-doc.tsx +241 -0
- package/packages/widget/src/components/widget.tsx +101 -0
- package/packages/widget/src/index.tsx +167 -0
- package/packages/widget/src/lib/config.ts +2 -0
- package/packages/widget/src/lib/util.ts +40 -0
- package/packages/widget/src/styles/index.css +5 -0
- package/packages/widget/src/styles/tw-properties.css +337 -0
- package/packages/widget/src/vite-env.d.ts +3 -0
- package/packages/widget/tsconfig.app.json +35 -0
- package/packages/widget/tsconfig.json +4 -0
- package/packages/widget/tsconfig.node.json +24 -0
- package/packages/widget/vite.config.ts +116 -0
- package/packages/widget-lit/BOTTLENECKS.md +250 -0
- package/packages/widget-lit/IMPLEMENTATION_SUMMARY.md +295 -0
- package/packages/widget-lit/README.md +232 -0
- package/packages/widget-lit/eslint.config.mjs +3 -0
- package/packages/widget-lit/package.json +52 -0
- package/packages/widget-lit/src/api-client.ts +230 -0
- package/packages/widget-lit/src/api-client.ts.backup +218 -0
- package/packages/widget-lit/src/components/sea-chat.ts +382 -0
- package/packages/widget-lit/src/components/sea-submission-viewer.ts +267 -0
- package/packages/widget-lit/src/components/sea-widget.ts +317 -0
- package/packages/widget-lit/src/index.ts +48 -0
- package/packages/widget-lit/src/react.ts +58 -0
- package/packages/widget-lit/src/style.css +47 -0
- package/packages/widget-lit/tsconfig.json +24 -0
- package/packages/widget-lit/vite.config.ts +29 -0
- package/packages/widget-ng/DEVELOPMENT.md +74 -0
- package/packages/widget-ng/README.md +657 -0
- package/packages/widget-ng/dev.sh +14 -0
- package/packages/widget-ng/eslint.config.mjs +24 -0
- package/packages/widget-ng/ng-package.json +9 -0
- package/packages/widget-ng/package.json +85 -0
- package/packages/widget-ng/src/index.ts +45 -0
- package/packages/widget-ng/src/lib/components/sea-chat.component.ts +737 -0
- package/packages/widget-ng/src/lib/components/sea-data-viewer.component.ts +2240 -0
- package/packages/widget-ng/src/lib/components/sea-deal-form-modal.component.ts +702 -0
- package/packages/widget-ng/src/lib/components/sea-document-list.component.ts +350 -0
- package/packages/widget-ng/src/lib/components/sea-feedback-modal.component.ts +461 -0
- package/packages/widget-ng/src/lib/components/sea-file-upload.component.ts +655 -0
- package/packages/widget-ng/src/lib/components/sea-model-selection-modal.component.ts +367 -0
- package/packages/widget-ng/src/lib/components/sea-new-submission-modal.component.ts +414 -0
- package/packages/widget-ng/src/lib/components/sea-pdf-viewer.component.ts +869 -0
- package/packages/widget-ng/src/lib/components/sea-submission-card.component.ts +251 -0
- package/packages/widget-ng/src/lib/components/sea-widget.component.ts +684 -0
- package/packages/widget-ng/src/lib/models/submission.model.ts +170 -0
- package/packages/widget-ng/src/lib/pipes/markdown.pipe.ts +57 -0
- package/packages/widget-ng/src/lib/services/api-client.service.ts +715 -0
- package/packages/widget-ng/src/lib/services/chat.service.ts +330 -0
- package/packages/widget-ng/src/lib/services/config.service.ts +107 -0
- package/packages/widget-ng/src/web-component.ts +56 -0
- package/packages/widget-ng/tsconfig.json +25 -0
- package/packages/widget-ng/tsconfig.lib.json +9 -0
- package/packages/widget-ng/vite.config.elements.ts +26 -0
- package/packages/widget-ng/vitest.config.ts +19 -0
- package/packages/widget-ng/vitest.setup.ts +13 -0
- package/pnpm-workspace.yaml +18 -0
- package/render.yaml +136 -0
- package/scripts/README.md +57 -0
- package/scripts/package.json +22 -0
- package/scripts/python/.python-version +1 -0
- package/scripts/python/README.md +3 -0
- package/scripts/python/export-org-data.py +693 -0
- package/scripts/python/pyproject.toml +29 -0
- package/scripts/python/requirements-dev.lock +36 -0
- package/scripts/python/requirements.lock +36 -0
- package/scripts/python/src/gen.py +297 -0
- package/scripts/python/test.py +34 -0
- package/scripts/src/fix-storage-provider-mismatch.ts +239 -0
- package/scripts/src/sync-render-yaml.ts +290 -0
- package/scripts/src/test-chat-stream.ts +300 -0
- package/scripts/src/test-reconciliation.ts +230 -0
- package/scripts/tsconfig.json +15 -0
- package/tests/angular-test-app/.vscode/extensions.json +4 -0
- package/tests/angular-test-app/.vscode/launch.json +13 -0
- package/tests/angular-test-app/.vscode/tasks.json +24 -0
- package/tests/angular-test-app/README.md +59 -0
- package/tests/angular-test-app/angular.json +111 -0
- package/tests/angular-test-app/clean-start.sh +14 -0
- package/tests/angular-test-app/package.json +36 -0
- package/tests/angular-test-app/public/favicon.ico +0 -0
- package/tests/angular-test-app/src/app/app.component.ts +220 -0
- package/tests/angular-test-app/src/app/app.config.ts +5 -0
- package/tests/angular-test-app/src/env.d.ts +13 -0
- package/tests/angular-test-app/src/index.html +13 -0
- package/tests/angular-test-app/src/main.ts +6 -0
- package/tests/angular-test-app/src/styles.css +8 -0
- package/tests/angular-test-app/tsconfig.app.json +15 -0
- package/tests/angular-test-app/tsconfig.json +27 -0
- package/tests/crm-viewer-app/API_INTEGRATION_SUMMARY.md +295 -0
- package/tests/crm-viewer-app/CURRENT_ASSETS_FIELDS.md +148 -0
- package/tests/crm-viewer-app/FIELD_ID_MAPPING.md +206 -0
- package/tests/crm-viewer-app/INTEGRATION_GUIDE.md +309 -0
- package/tests/crm-viewer-app/README.md +174 -0
- package/tests/crm-viewer-app/REAL_API_INTEGRATION.md +240 -0
- package/tests/crm-viewer-app/UPDATED_IMPLEMENTATION.md +279 -0
- package/tests/crm-viewer-app/angular.json +114 -0
- package/tests/crm-viewer-app/package.json +35 -0
- package/tests/crm-viewer-app/src/app/app.component.ts +534 -0
- package/tests/crm-viewer-app/src/app/citation.service.ts +316 -0
- package/tests/crm-viewer-app/src/env.d.ts +16 -0
- package/tests/crm-viewer-app/src/index.html +19 -0
- package/tests/crm-viewer-app/src/main.ts +7 -0
- package/tests/crm-viewer-app/src/styles.css +409 -0
- package/tests/crm-viewer-app/src/template.html +2678 -0
- package/tests/crm-viewer-app/tsconfig.app.json +15 -0
- package/tests/crm-viewer-app/tsconfig.json +27 -0
- package/tests/e2e/package.json +17 -0
- package/tests/e2e/playwright.config.ts +75 -0
- package/tests/e2e/tests/api/health.spec.ts +10 -0
- package/tests/e2e/tests/app/example.spec.ts +10 -0
- package/tests/widget-test-app/.prettierignore +6 -0
- package/tests/widget-test-app/README.md +48 -0
- package/tests/widget-test-app/index.html +12 -0
- package/tests/widget-test-app/package.json +24 -0
- package/tests/widget-test-app/src/App.css +192 -0
- package/tests/widget-test-app/src/App.tsx +80 -0
- package/tests/widget-test-app/src/main.tsx +9 -0
- package/tests/widget-test-app/src/vite-env.d.ts +4 -0
- package/tests/widget-test-app/tsconfig.json +25 -0
- package/tests/widget-test-app/tsconfig.node.json +11 -0
- package/tests/widget-test-app/vite.config.ts +14 -0
|
@@ -0,0 +1,1485 @@
|
|
|
1
|
+
import { SubmissionGroupWrapper } from "@/components/data-display/group-wrapper";
|
|
2
|
+
import { Container } from "@/components/header/container";
|
|
3
|
+
import { DocList } from "@/components/side-panel/doc-list";
|
|
4
|
+
import { SidebarRight } from "@/components/sidebar/sidebar-right";
|
|
5
|
+
import { membershipMiddleware } from "@/middleware";
|
|
6
|
+
import { downloadDocumentFn } from "@/routes/_authed/_app/deals/$dealId/-fns";
|
|
7
|
+
import type { ExtractionInfo } from "@sea/core/data-extraction/types";
|
|
8
|
+
import { getSubmissionWithFormAndExtractions } from "@sea/dal/submission";
|
|
9
|
+
import type {
|
|
10
|
+
DocumentCitation,
|
|
11
|
+
PipelineRunStepWithDetails,
|
|
12
|
+
PipelineRunTimeline,
|
|
13
|
+
} from "@sea/db/types";
|
|
14
|
+
import type { ApiReconciliation } from "@sea/schemas/core/submission";
|
|
15
|
+
import { PdfViewer } from "@sea/ui/components/doc-viewers/pdf";
|
|
16
|
+
import { RelDate } from "@sea/ui/components/misc/rel-date";
|
|
17
|
+
import {
|
|
18
|
+
Accordion,
|
|
19
|
+
AccordionContent,
|
|
20
|
+
AccordionItem,
|
|
21
|
+
AccordionTrigger,
|
|
22
|
+
} from "@sea/ui/components/ui/accordion";
|
|
23
|
+
import { Badge } from "@sea/ui/components/ui/badge";
|
|
24
|
+
import { Button } from "@sea/ui/components/ui/button";
|
|
25
|
+
import { Card, CardContent } from "@sea/ui/components/ui/card";
|
|
26
|
+
import {
|
|
27
|
+
Dialog,
|
|
28
|
+
DialogContent,
|
|
29
|
+
DialogDescription,
|
|
30
|
+
DialogFooter,
|
|
31
|
+
DialogHeader,
|
|
32
|
+
DialogTitle,
|
|
33
|
+
DialogTrigger,
|
|
34
|
+
} from "@sea/ui/components/ui/dialog";
|
|
35
|
+
import {
|
|
36
|
+
ResizableHandle,
|
|
37
|
+
ResizablePanel,
|
|
38
|
+
ResizablePanelGroup,
|
|
39
|
+
} from "@sea/ui/components/ui/resizable";
|
|
40
|
+
import { H3 } from "@sea/ui/components/ui/typography";
|
|
41
|
+
import { convertItemsToRecord, groupBy } from "@sea/util/data-transform";
|
|
42
|
+
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
|
43
|
+
import { createServerFn, useServerFn } from "@tanstack/react-start";
|
|
44
|
+
import {
|
|
45
|
+
DownloadIcon,
|
|
46
|
+
FileTextIcon,
|
|
47
|
+
PlayIcon,
|
|
48
|
+
RotateCcwIcon,
|
|
49
|
+
XCircleIcon,
|
|
50
|
+
XIcon,
|
|
51
|
+
} from "lucide-react";
|
|
52
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
53
|
+
import Markdown from "react-markdown";
|
|
54
|
+
import remarkGfm from "remark-gfm";
|
|
55
|
+
import { toast } from "sonner";
|
|
56
|
+
import { DAGView } from "../-components/dag-view";
|
|
57
|
+
import {
|
|
58
|
+
getRunBlockersFn,
|
|
59
|
+
replayPipelineRunFn,
|
|
60
|
+
replayPipelineRunStepFn,
|
|
61
|
+
resolveBlockerWithManualInputFn,
|
|
62
|
+
startPipelineRunFn,
|
|
63
|
+
} from "../../-fns";
|
|
64
|
+
import { BlockersPanel } from "./-components/blockers-panel";
|
|
65
|
+
import { SubmissionSelectorModal } from "./-components/submission-selector-modal";
|
|
66
|
+
import { UploadDocForPipeline } from "./-components/upload-doc";
|
|
67
|
+
|
|
68
|
+
// Helper functions for artifact download
|
|
69
|
+
function downloadBlob(blob: Blob, filename: string) {
|
|
70
|
+
const url = URL.createObjectURL(blob);
|
|
71
|
+
const link = document.createElement("a");
|
|
72
|
+
link.href = url;
|
|
73
|
+
link.download = filename;
|
|
74
|
+
link.click();
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function escapeHtml(text: string) {
|
|
79
|
+
return text
|
|
80
|
+
.replace(/&/g, "&")
|
|
81
|
+
.replace(/</g, "<")
|
|
82
|
+
.replace(/>/g, ">")
|
|
83
|
+
.replace(/"/g, """)
|
|
84
|
+
.replace(/'/g, "'");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Server functions for loader
|
|
88
|
+
const getRunTimelineServerFn = createServerFn({ method: "GET" })
|
|
89
|
+
.middleware([membershipMiddleware])
|
|
90
|
+
.validator((data: { runId: string }) => data)
|
|
91
|
+
.handler(async ({ context: { member }, data: { runId } }) => {
|
|
92
|
+
const { getRunTimeline } = await import("@sea/dal/pipeline");
|
|
93
|
+
return await getRunTimeline({ member, runId });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const getPipelineServerFn = createServerFn({ method: "GET" })
|
|
97
|
+
.middleware([membershipMiddleware])
|
|
98
|
+
.validator((data: { pipelineId: string }) => data)
|
|
99
|
+
.handler(async ({ context: { member }, data: { pipelineId } }) => {
|
|
100
|
+
const { getPipelineDetail } = await import("@sea/dal/pipeline");
|
|
101
|
+
const result = await getPipelineDetail({ member, pipelineId: pipelineId });
|
|
102
|
+
if (!result.ok) return null;
|
|
103
|
+
return result.data;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const getSubmissionDataFn = createServerFn({ method: "GET" })
|
|
107
|
+
.middleware([membershipMiddleware])
|
|
108
|
+
.validator((data: { submissionId: string }) => data)
|
|
109
|
+
.handler(async ({ context: { member }, data: { submissionId } }) => {
|
|
110
|
+
return await getSubmissionWithFormAndExtractions({ member, submissionId });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const getAvailableSubmissionsFn = createServerFn({ method: "GET" })
|
|
114
|
+
.middleware([membershipMiddleware])
|
|
115
|
+
.validator((data: Record<string, never>) => data)
|
|
116
|
+
.handler(async ({ context: { member } }) => {
|
|
117
|
+
// Get all submissions in the organization
|
|
118
|
+
const { getSubmissions } = await import("@sea/dal/submission");
|
|
119
|
+
return await getSubmissions({ member, typeFilter: "all" });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const getRunOutputSubmissionsFn = createServerFn({ method: "GET" })
|
|
123
|
+
.middleware([membershipMiddleware])
|
|
124
|
+
.validator((data: { runId: string }) => data)
|
|
125
|
+
.handler(async ({ context: { member }, data: { runId } }) => {
|
|
126
|
+
const { getRunOutputSubmissions } = await import("@sea/dal/pipeline");
|
|
127
|
+
const result = await getRunOutputSubmissions({ member, runId });
|
|
128
|
+
if (!result.ok) return {};
|
|
129
|
+
// Convert Map to plain object for serialization
|
|
130
|
+
return Object.fromEntries(result.data);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const getSubmissionsByIdsFn = createServerFn({ method: "GET" })
|
|
134
|
+
.middleware([membershipMiddleware])
|
|
135
|
+
.validator((data: { submissionIds: string[] }) => data)
|
|
136
|
+
.handler(async ({ context: { member }, data: { submissionIds } }) => {
|
|
137
|
+
const results = await Promise.all(
|
|
138
|
+
submissionIds.map((id) => getSubmissionWithFormAndExtractions({ member, submissionId: id })),
|
|
139
|
+
);
|
|
140
|
+
return results;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export const Route = createFileRoute("/_authed/_app/pipelines/$pipelineId/$runId/")({
|
|
144
|
+
loader: async ({
|
|
145
|
+
params: { pipelineId, runId },
|
|
146
|
+
}): Promise<{
|
|
147
|
+
timeline: PipelineRunTimeline;
|
|
148
|
+
pipeline: Awaited<ReturnType<typeof getPipelineServerFn>>;
|
|
149
|
+
inputSubmissionData: Map<string, Awaited<ReturnType<typeof getSubmissionDataFn>>>;
|
|
150
|
+
outputSubmissionData: Map<string, Awaited<ReturnType<typeof getSubmissionDataFn>>[]>;
|
|
151
|
+
blockers: Awaited<ReturnType<typeof getRunBlockersFn>>;
|
|
152
|
+
availableSubmissions: Awaited<ReturnType<typeof getAvailableSubmissionsFn>>;
|
|
153
|
+
}> => {
|
|
154
|
+
const timelineResult = await getRunTimelineServerFn({ data: { runId } });
|
|
155
|
+
if (!timelineResult.ok) {
|
|
156
|
+
throw new Error("Failed to load run timeline");
|
|
157
|
+
}
|
|
158
|
+
const timeline = timelineResult.data;
|
|
159
|
+
|
|
160
|
+
// Fetch pipeline data
|
|
161
|
+
const pipeline = await getPipelineServerFn({ data: { pipelineId } });
|
|
162
|
+
|
|
163
|
+
// Load output submissions for each run step
|
|
164
|
+
const outputSubmissionIdsObj = await getRunOutputSubmissionsFn({ data: { runId } });
|
|
165
|
+
const outputSubmissionData = new Map<
|
|
166
|
+
string,
|
|
167
|
+
Awaited<ReturnType<typeof getSubmissionDataFn>>[]
|
|
168
|
+
>();
|
|
169
|
+
|
|
170
|
+
// Load submission data for all output submissions
|
|
171
|
+
for (const [runStepId, submissionIds] of Object.entries(outputSubmissionIdsObj)) {
|
|
172
|
+
if (submissionIds.length > 0) {
|
|
173
|
+
const submissions = await Promise.all(
|
|
174
|
+
submissionIds.map((submissionId) => getSubmissionDataFn({ data: { submissionId } })),
|
|
175
|
+
);
|
|
176
|
+
outputSubmissionData.set(runStepId, submissions);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// TODO: Load input submissions from pipeline run trigger data
|
|
181
|
+
// Input submissions architecture is still being determined
|
|
182
|
+
const inputSubmissionData = new Map<string, Awaited<ReturnType<typeof getSubmissionDataFn>>>();
|
|
183
|
+
|
|
184
|
+
// Fetch blockers for the run
|
|
185
|
+
const blockers = await getRunBlockersFn({ data: { runId } });
|
|
186
|
+
|
|
187
|
+
// Fetch available submissions for manual inputs
|
|
188
|
+
const availableSubmissions = await getAvailableSubmissionsFn({ data: {} });
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
timeline,
|
|
192
|
+
pipeline,
|
|
193
|
+
inputSubmissionData,
|
|
194
|
+
outputSubmissionData,
|
|
195
|
+
blockers,
|
|
196
|
+
availableSubmissions,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
component: RouteComponent,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
type ProcessFlowStepData = PipelineRunStepWithDetails;
|
|
203
|
+
|
|
204
|
+
// Helper component for the process flow
|
|
205
|
+
function ProcessFlowStep({
|
|
206
|
+
step,
|
|
207
|
+
isSelected,
|
|
208
|
+
isFirst,
|
|
209
|
+
isLast,
|
|
210
|
+
stepNumber,
|
|
211
|
+
onClick,
|
|
212
|
+
}: {
|
|
213
|
+
step: ProcessFlowStepData;
|
|
214
|
+
isSelected: boolean;
|
|
215
|
+
isFirst: boolean;
|
|
216
|
+
isLast: boolean;
|
|
217
|
+
stepNumber: number;
|
|
218
|
+
onClick: () => void;
|
|
219
|
+
}) {
|
|
220
|
+
const isCompleted = step.status === "completed" || step.status === "success";
|
|
221
|
+
const isFailed = step.status === "failed" || step.status === "error";
|
|
222
|
+
const isRunning = step.status === "running";
|
|
223
|
+
const isPending = !isCompleted && !isFailed && !isRunning;
|
|
224
|
+
|
|
225
|
+
const getStatusIcon = () => {
|
|
226
|
+
if (isCompleted)
|
|
227
|
+
return (
|
|
228
|
+
<svg
|
|
229
|
+
viewBox="0 0 24 24"
|
|
230
|
+
fill="currentColor"
|
|
231
|
+
aria-hidden="true"
|
|
232
|
+
className="size-6 text-white"
|
|
233
|
+
>
|
|
234
|
+
<path
|
|
235
|
+
d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z"
|
|
236
|
+
clipRule="evenodd"
|
|
237
|
+
fillRule="evenodd"
|
|
238
|
+
/>
|
|
239
|
+
</svg>
|
|
240
|
+
);
|
|
241
|
+
if (isFailed) return <XCircleIcon className="size-6 text-white" />;
|
|
242
|
+
if (isRunning) return <PlayIcon className="size-5 text-white" />;
|
|
243
|
+
return (
|
|
244
|
+
<span className="text-gray-500 dark:text-gray-400">
|
|
245
|
+
{stepNumber.toString().padStart(2, "0")}
|
|
246
|
+
</span>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const getDescription = () => {
|
|
251
|
+
if (step.startedAt && step.completedAt) {
|
|
252
|
+
const durationMs = new Date(step.completedAt).getTime() - new Date(step.startedAt).getTime();
|
|
253
|
+
if (durationMs > 0) {
|
|
254
|
+
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return "Processing step";
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<li className="relative overflow-hidden lg:flex-1">
|
|
262
|
+
<div className="overflow-hidden">
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
onClick={onClick}
|
|
266
|
+
className="group w-full text-left"
|
|
267
|
+
aria-current={isSelected ? "step" : undefined}
|
|
268
|
+
>
|
|
269
|
+
{/* Active indicator bar */}
|
|
270
|
+
<span
|
|
271
|
+
aria-hidden="true"
|
|
272
|
+
className={`absolute top-0 left-0 h-full w-1 lg:top-auto lg:bottom-0 lg:h-1 lg:w-full ${
|
|
273
|
+
isSelected
|
|
274
|
+
? isCompleted
|
|
275
|
+
? "bg-indigo-600 dark:bg-indigo-500"
|
|
276
|
+
: isFailed
|
|
277
|
+
? "bg-red-600 dark:bg-red-500"
|
|
278
|
+
: isRunning
|
|
279
|
+
? "bg-blue-600 dark:bg-blue-500"
|
|
280
|
+
: "bg-gray-300 dark:bg-white/15"
|
|
281
|
+
: "bg-transparent group-hover:bg-gray-200 dark:group-hover:bg-white/20"
|
|
282
|
+
}`}
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<span
|
|
286
|
+
className={`flex items-start px-6 py-5 text-sm font-medium ${!isFirst ? "lg:pl-9" : ""}`}
|
|
287
|
+
>
|
|
288
|
+
<span className="shrink-0">
|
|
289
|
+
{isCompleted ? (
|
|
290
|
+
<span className="flex size-10 items-center justify-center rounded-full bg-indigo-600 dark:bg-indigo-500">
|
|
291
|
+
{getStatusIcon()}
|
|
292
|
+
</span>
|
|
293
|
+
) : isFailed ? (
|
|
294
|
+
<span className="flex size-10 items-center justify-center rounded-full bg-red-600 dark:bg-red-500">
|
|
295
|
+
{getStatusIcon()}
|
|
296
|
+
</span>
|
|
297
|
+
) : isRunning ? (
|
|
298
|
+
<span className="flex size-10 items-center justify-center rounded-full bg-blue-600 dark:bg-blue-500">
|
|
299
|
+
{getStatusIcon()}
|
|
300
|
+
</span>
|
|
301
|
+
) : isSelected ? (
|
|
302
|
+
<span className="flex size-10 items-center justify-center rounded-full border-2 border-indigo-600 dark:border-indigo-500">
|
|
303
|
+
<span className="text-indigo-600 dark:text-indigo-400">{getStatusIcon()}</span>
|
|
304
|
+
</span>
|
|
305
|
+
) : (
|
|
306
|
+
<span className="flex size-10 items-center justify-center rounded-full border-2 border-gray-300 dark:border-white/15">
|
|
307
|
+
{getStatusIcon()}
|
|
308
|
+
</span>
|
|
309
|
+
)}
|
|
310
|
+
</span>
|
|
311
|
+
<span className="mt-0.5 ml-4 flex min-w-0 flex-col">
|
|
312
|
+
<span
|
|
313
|
+
className={`text-sm font-medium ${
|
|
314
|
+
isSelected
|
|
315
|
+
? isCompleted || isRunning
|
|
316
|
+
? "text-indigo-600 dark:text-indigo-400"
|
|
317
|
+
: isFailed
|
|
318
|
+
? "text-red-600 dark:text-red-400"
|
|
319
|
+
: "text-indigo-600 dark:text-indigo-400"
|
|
320
|
+
: "text-gray-500 dark:text-gray-400"
|
|
321
|
+
}`}
|
|
322
|
+
>
|
|
323
|
+
Step {stepNumber}
|
|
324
|
+
</span>
|
|
325
|
+
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
326
|
+
{getDescription()}
|
|
327
|
+
</span>
|
|
328
|
+
</span>
|
|
329
|
+
</span>
|
|
330
|
+
</button>
|
|
331
|
+
|
|
332
|
+
{/* Chevron Separator */}
|
|
333
|
+
{!isFirst && (
|
|
334
|
+
<div aria-hidden="true" className="absolute inset-0 top-0 left-0 hidden w-3 lg:block">
|
|
335
|
+
<svg
|
|
336
|
+
viewBox="0 0 12 82"
|
|
337
|
+
fill="none"
|
|
338
|
+
preserveAspectRatio="none"
|
|
339
|
+
className="size-full text-gray-300 dark:text-white/15"
|
|
340
|
+
>
|
|
341
|
+
<path
|
|
342
|
+
d="M0.5 0V31L10.5 41L0.5 51V82"
|
|
343
|
+
stroke="currentcolor"
|
|
344
|
+
vectorEffect="non-scaling-stroke"
|
|
345
|
+
/>
|
|
346
|
+
</svg>
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
</li>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Component to display submission data
|
|
355
|
+
function SubmissionDataDisplay({
|
|
356
|
+
submission,
|
|
357
|
+
activeCitationId,
|
|
358
|
+
onAnnotationSelect,
|
|
359
|
+
boundingKey,
|
|
360
|
+
setBoundingKey,
|
|
361
|
+
}: {
|
|
362
|
+
submission: Awaited<ReturnType<typeof getSubmissionDataFn>> | null;
|
|
363
|
+
activeCitationId: string | undefined;
|
|
364
|
+
onAnnotationSelect: (id: string | undefined) => void;
|
|
365
|
+
boundingKey: string | undefined;
|
|
366
|
+
setBoundingKey: (key: string | undefined) => void;
|
|
367
|
+
}) {
|
|
368
|
+
// Transform groups to match the structure expected by SubmissionGroupWrapper
|
|
369
|
+
const groups = useMemo(() => {
|
|
370
|
+
if (!submission?.groups) return [];
|
|
371
|
+
|
|
372
|
+
return submission.groups.map((group) => {
|
|
373
|
+
const items = group.items.map((item) => {
|
|
374
|
+
// Use selectedVersion from DAL instead of versions[0]
|
|
375
|
+
// The DAL attaches transformationSource to selectedVersion
|
|
376
|
+
const version = item.selectedVersion ?? item.versions[0];
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
...item,
|
|
380
|
+
selectedVersion: version ?? null,
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
...group,
|
|
386
|
+
items: [...items].sort((a, b) => a.formField.order - b.formField.order),
|
|
387
|
+
itemsMap: convertItemsToRecord(items, "formFieldId"),
|
|
388
|
+
derivedMap: convertItemsToRecord(group.derived, "formDerivedId"),
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
}, [submission]);
|
|
392
|
+
|
|
393
|
+
const grouped = useMemo(() => {
|
|
394
|
+
if (!submission?.form?.groups) return [];
|
|
395
|
+
|
|
396
|
+
const groupObj = groupBy(groups, "formGroupId");
|
|
397
|
+
|
|
398
|
+
return submission.form.groups.map((formGroup) => ({
|
|
399
|
+
formGroup,
|
|
400
|
+
groups: groupObj[formGroup.id] ?? [],
|
|
401
|
+
}));
|
|
402
|
+
}, [submission, groups]);
|
|
403
|
+
|
|
404
|
+
const noop = () => {};
|
|
405
|
+
|
|
406
|
+
if (!submission) {
|
|
407
|
+
return (
|
|
408
|
+
<div className="p-4">
|
|
409
|
+
<p className="text-muted-foreground text-sm">Submission data not available</p>
|
|
410
|
+
</div>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<div className="space-y-4">
|
|
416
|
+
{grouped.length > 0 ? (
|
|
417
|
+
<div className="space-y-4">
|
|
418
|
+
{grouped.map(({ formGroup, groups }) => (
|
|
419
|
+
<SubmissionGroupWrapper
|
|
420
|
+
key={formGroup.id}
|
|
421
|
+
formGroup={formGroup}
|
|
422
|
+
groups={groups}
|
|
423
|
+
setUnsaved={noop}
|
|
424
|
+
disableInput={false}
|
|
425
|
+
annotationId={activeCitationId}
|
|
426
|
+
setAnnotationId={onAnnotationSelect}
|
|
427
|
+
boundingKey={boundingKey}
|
|
428
|
+
setBoundingKey={setBoundingKey}
|
|
429
|
+
submissionId={submission.id}
|
|
430
|
+
/>
|
|
431
|
+
))}
|
|
432
|
+
</div>
|
|
433
|
+
) : (
|
|
434
|
+
<div className="p-4">
|
|
435
|
+
<p className="text-muted-foreground text-sm">No data available</p>
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
</div>
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Helper component for step details
|
|
443
|
+
function StepDetails({
|
|
444
|
+
step,
|
|
445
|
+
allStepsForThisStep,
|
|
446
|
+
outputSubmissionData,
|
|
447
|
+
onReplay,
|
|
448
|
+
isReplaying,
|
|
449
|
+
activeCitationId,
|
|
450
|
+
onAnnotationSelect,
|
|
451
|
+
boundingKey,
|
|
452
|
+
setBoundingKey,
|
|
453
|
+
}: {
|
|
454
|
+
step: PipelineRunStepWithDetails;
|
|
455
|
+
allStepsForThisStep: PipelineRunStepWithDetails[];
|
|
456
|
+
outputSubmissionData: Map<string, Awaited<ReturnType<typeof getSubmissionDataFn>>[]>;
|
|
457
|
+
onReplay: (stepId: string) => void;
|
|
458
|
+
isReplaying: boolean;
|
|
459
|
+
activeCitationId: string | undefined;
|
|
460
|
+
onAnnotationSelect: (id: string | undefined) => void;
|
|
461
|
+
boundingKey: string | undefined;
|
|
462
|
+
setBoundingKey: (key: string | undefined) => void;
|
|
463
|
+
}) {
|
|
464
|
+
const [selectedSubmissionId, setSelectedSubmissionId] = useState<string | undefined>(undefined);
|
|
465
|
+
|
|
466
|
+
// Collect all submission items to display (from all run steps)
|
|
467
|
+
const submissionItems = allStepsForThisStep.flatMap((runStep, runStepIdx) => {
|
|
468
|
+
const submissions = outputSubmissionData.get(runStep.id) ?? [];
|
|
469
|
+
return submissions.map((submission, submissionIdx) => ({
|
|
470
|
+
runStep,
|
|
471
|
+
runStepIdx,
|
|
472
|
+
submission,
|
|
473
|
+
submissionIdx,
|
|
474
|
+
key: `${runStep.id}-${submission?.id ?? submissionIdx}`,
|
|
475
|
+
label:
|
|
476
|
+
allStepsForThisStep.length > 1
|
|
477
|
+
? submissions.length > 1
|
|
478
|
+
? `Instance ${runStepIdx + 1} - ${submission?.form?.name ?? `Form ${submissionIdx + 1}`}`
|
|
479
|
+
: `Instance ${runStepIdx + 1}`
|
|
480
|
+
: submissions.length > 1
|
|
481
|
+
? (submission?.form?.name ?? `Form ${submissionIdx + 1}`)
|
|
482
|
+
: null,
|
|
483
|
+
}));
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// If there are multiple submissions (from multiple run steps OR multiple forms), show accordion
|
|
487
|
+
const isMulti = submissionItems.length > 1;
|
|
488
|
+
|
|
489
|
+
if (isMulti) {
|
|
490
|
+
return (
|
|
491
|
+
<div className="space-y-4">
|
|
492
|
+
<div className="text-muted-foreground mb-2 text-sm">
|
|
493
|
+
{submissionItems.length} output{submissionItems.length !== 1 ? "s" : ""}
|
|
494
|
+
</div>
|
|
495
|
+
<Accordion
|
|
496
|
+
type="single"
|
|
497
|
+
collapsible
|
|
498
|
+
className="space-y-2"
|
|
499
|
+
value={selectedSubmissionId}
|
|
500
|
+
onValueChange={setSelectedSubmissionId}
|
|
501
|
+
>
|
|
502
|
+
{submissionItems.map((item, idx) => {
|
|
503
|
+
return (
|
|
504
|
+
<AccordionItem key={item.key} value={item.key} className="rounded-lg border">
|
|
505
|
+
<AccordionTrigger className="px-4 hover:no-underline">
|
|
506
|
+
<div className="flex min-w-0 flex-1 items-center justify-between gap-3">
|
|
507
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
508
|
+
<div className="flex size-8 shrink-0 items-center justify-center rounded-full bg-indigo-600 text-sm font-medium text-white dark:bg-indigo-500">
|
|
509
|
+
{idx + 1}
|
|
510
|
+
</div>
|
|
511
|
+
<div className="min-w-0 flex-1 text-left">
|
|
512
|
+
<div className="truncate font-medium">
|
|
513
|
+
{item.label ?? `Entry ${idx + 1}`}
|
|
514
|
+
</div>
|
|
515
|
+
{item.submission && (
|
|
516
|
+
<div className="text-muted-foreground truncate text-xs">
|
|
517
|
+
{item.submission.form.name}
|
|
518
|
+
</div>
|
|
519
|
+
)}
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
<Badge
|
|
523
|
+
variant={
|
|
524
|
+
item.runStep.status === "completed"
|
|
525
|
+
? "green"
|
|
526
|
+
: item.runStep.status === "failed"
|
|
527
|
+
? "destructive"
|
|
528
|
+
: "secondary"
|
|
529
|
+
}
|
|
530
|
+
className="ml-2"
|
|
531
|
+
>
|
|
532
|
+
{item.runStep.status}
|
|
533
|
+
</Badge>
|
|
534
|
+
</div>
|
|
535
|
+
</AccordionTrigger>
|
|
536
|
+
<AccordionContent className="px-4 pb-4">
|
|
537
|
+
{item.submission ? (
|
|
538
|
+
<SubmissionDataDisplay
|
|
539
|
+
submission={item.submission}
|
|
540
|
+
activeCitationId={activeCitationId}
|
|
541
|
+
onAnnotationSelect={onAnnotationSelect}
|
|
542
|
+
boundingKey={boundingKey}
|
|
543
|
+
setBoundingKey={setBoundingKey}
|
|
544
|
+
/>
|
|
545
|
+
) : (
|
|
546
|
+
<div className="text-muted-foreground py-4 text-center text-sm">
|
|
547
|
+
No submission data available
|
|
548
|
+
</div>
|
|
549
|
+
)}
|
|
550
|
+
</AccordionContent>
|
|
551
|
+
</AccordionItem>
|
|
552
|
+
);
|
|
553
|
+
})}
|
|
554
|
+
</Accordion>
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Single submission - show directly
|
|
560
|
+
const singleItem = submissionItems[0];
|
|
561
|
+
const submission = singleItem?.submission ?? null;
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
<div>
|
|
565
|
+
<SubmissionDataDisplay
|
|
566
|
+
submission={submission}
|
|
567
|
+
activeCitationId={activeCitationId}
|
|
568
|
+
onAnnotationSelect={onAnnotationSelect}
|
|
569
|
+
boundingKey={boundingKey}
|
|
570
|
+
setBoundingKey={setBoundingKey}
|
|
571
|
+
/>
|
|
572
|
+
</div>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function RouteComponent() {
|
|
577
|
+
const { pipelineId, runId } = Route.useParams();
|
|
578
|
+
const {
|
|
579
|
+
timeline: initialTimeline,
|
|
580
|
+
pipeline,
|
|
581
|
+
inputSubmissionData: initialInputSubmissionData,
|
|
582
|
+
outputSubmissionData: initialOutputSubmissionData,
|
|
583
|
+
blockers: initialBlockers,
|
|
584
|
+
availableSubmissions: initialAvailableSubmissions,
|
|
585
|
+
} = Route.useLoaderData();
|
|
586
|
+
const router = useRouter();
|
|
587
|
+
const downloadDocument = useServerFn(downloadDocumentFn);
|
|
588
|
+
const replayRun = useServerFn(replayPipelineRunFn);
|
|
589
|
+
const replayStep = useServerFn(replayPipelineRunStepFn);
|
|
590
|
+
const startRun = useServerFn(startPipelineRunFn);
|
|
591
|
+
const [selectedStepId, setSelectedStepId] = useState<string | undefined>(undefined);
|
|
592
|
+
const [timeline, setTimeline] = useState(initialTimeline);
|
|
593
|
+
const [inputSubmissionData, setInputSubmissionData] = useState(initialInputSubmissionData);
|
|
594
|
+
const [outputSubmissionData, setOutputSubmissionData] = useState(initialOutputSubmissionData);
|
|
595
|
+
const [blockers, setBlockers] = useState(initialBlockers);
|
|
596
|
+
const [availableSubmissions, setAvailableSubmissions] = useState(initialAvailableSubmissions);
|
|
597
|
+
const [isReplaying, setIsReplaying] = useState(false);
|
|
598
|
+
const [isStarting, setIsStarting] = useState(false);
|
|
599
|
+
const [viewMode, setViewMode] = useState<"flow" | "dag">("flow");
|
|
600
|
+
const crumbs = [
|
|
601
|
+
{ href: "/pipelines", label: "Pipelines" },
|
|
602
|
+
{ href: `/pipelines/${pipelineId}`, label: pipeline?.name ?? pipelineId },
|
|
603
|
+
{ href: `/pipelines/${pipelineId}/${runId}`, label: runId.slice(0, 12) + "..." },
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
// Create unique steps for process flow (one per stepId), ordered by execution level
|
|
607
|
+
const allSteps = useMemo<ProcessFlowStepData[]>(() => {
|
|
608
|
+
if (!timeline?.steps || !pipeline?.steps) return [];
|
|
609
|
+
|
|
610
|
+
// Group by stepId and take the first run step for each
|
|
611
|
+
const uniqueSteps = new Map<string, PipelineRunStepWithDetails>();
|
|
612
|
+
for (const runStep of timeline.steps) {
|
|
613
|
+
if (runStep.stepId && !uniqueSteps.has(runStep.stepId)) {
|
|
614
|
+
uniqueSteps.set(runStep.stepId, runStep);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Pipeline steps are already sorted by execution level in getPipelineDetail
|
|
619
|
+
// Map pipeline step order to get the correct ordering
|
|
620
|
+
const steps: PipelineRunStepWithDetails[] = [];
|
|
621
|
+
for (const pipelineStep of pipeline.steps) {
|
|
622
|
+
const runStep = uniqueSteps.get(pipelineStep.id);
|
|
623
|
+
if (runStep) {
|
|
624
|
+
steps.push(runStep);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return steps;
|
|
629
|
+
}, [timeline, pipeline]);
|
|
630
|
+
|
|
631
|
+
// Sync loader data when it changes (but don't reset selected step on polls)
|
|
632
|
+
useEffect(() => {
|
|
633
|
+
setTimeline(initialTimeline);
|
|
634
|
+
setInputSubmissionData(initialInputSubmissionData);
|
|
635
|
+
setOutputSubmissionData(initialOutputSubmissionData);
|
|
636
|
+
setBlockers(initialBlockers);
|
|
637
|
+
setAvailableSubmissions(initialAvailableSubmissions);
|
|
638
|
+
}, [
|
|
639
|
+
initialTimeline,
|
|
640
|
+
initialInputSubmissionData,
|
|
641
|
+
initialOutputSubmissionData,
|
|
642
|
+
initialBlockers,
|
|
643
|
+
initialAvailableSubmissions,
|
|
644
|
+
]);
|
|
645
|
+
|
|
646
|
+
// Set initial selected step when steps load
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
if (allSteps.length > 0 && !selectedStepId) {
|
|
649
|
+
setSelectedStepId(allSteps[0]?.stepId ?? undefined);
|
|
650
|
+
}
|
|
651
|
+
}, [allSteps, selectedStepId]);
|
|
652
|
+
|
|
653
|
+
// Poll for updates if run is not complete
|
|
654
|
+
const isRunActive = timeline?.status === "pending" || timeline?.status === "running";
|
|
655
|
+
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
if (!isRunActive) return;
|
|
658
|
+
|
|
659
|
+
const interval = setInterval(() => {
|
|
660
|
+
router.invalidate();
|
|
661
|
+
}, 1000); // Poll every second
|
|
662
|
+
|
|
663
|
+
return () => clearInterval(interval);
|
|
664
|
+
}, [isRunActive, router]);
|
|
665
|
+
|
|
666
|
+
// Group run steps by their stepId
|
|
667
|
+
const stepsByStepId = useMemo(() => {
|
|
668
|
+
if (!timeline?.steps) return new Map<string, PipelineRunStepWithDetails[]>();
|
|
669
|
+
const grouped = new Map<string, PipelineRunStepWithDetails[]>();
|
|
670
|
+
for (const runStep of timeline.steps) {
|
|
671
|
+
const stepId = runStep.stepId;
|
|
672
|
+
if (!stepId) continue;
|
|
673
|
+
if (!grouped.has(stepId)) {
|
|
674
|
+
grouped.set(stepId, []);
|
|
675
|
+
}
|
|
676
|
+
grouped.get(stepId)!.push(runStep);
|
|
677
|
+
}
|
|
678
|
+
return grouped;
|
|
679
|
+
}, [timeline]);
|
|
680
|
+
|
|
681
|
+
// Find the first run step for the selected stepId (for display purposes)
|
|
682
|
+
const selectedStep = timeline?.steps?.find((s) => s.stepId === selectedStepId);
|
|
683
|
+
// Get all run steps for the selected stepId (for multi-step display)
|
|
684
|
+
const allStepsForSelected = selectedStepId ? (stepsByStepId.get(selectedStepId) ?? []) : [];
|
|
685
|
+
|
|
686
|
+
const getStatusBadgeVariant = (
|
|
687
|
+
status: string,
|
|
688
|
+
): "default" | "secondary" | "destructive" | "green" => {
|
|
689
|
+
if (status === "completed" || status === "success") return "green";
|
|
690
|
+
if (status === "failed" || status === "error") return "destructive";
|
|
691
|
+
if (status === "running" || status === "pending") return "secondary";
|
|
692
|
+
return "default";
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const getDuration = () => {
|
|
696
|
+
if (!timeline?.completedAt) return null;
|
|
697
|
+
return new Date(timeline.completedAt).getTime() - new Date(timeline.startedAt).getTime();
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const formatDuration = (ms: number | null) => {
|
|
701
|
+
if (!ms) return null;
|
|
702
|
+
const seconds = ms / 1000;
|
|
703
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
704
|
+
const minutes = Math.floor(seconds / 60);
|
|
705
|
+
const remainingSeconds = (seconds % 60).toFixed(0);
|
|
706
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const handleStartRun = async () => {
|
|
710
|
+
if (!timeline || isStarting) return;
|
|
711
|
+
setIsStarting(true);
|
|
712
|
+
try {
|
|
713
|
+
await startRun({ data: { runId, pipelineId } });
|
|
714
|
+
// Invalidate to refresh the page and show updated status
|
|
715
|
+
router.invalidate();
|
|
716
|
+
} catch (error) {
|
|
717
|
+
console.error("Failed to start run:", error);
|
|
718
|
+
alert("Failed to start run. Please try again.");
|
|
719
|
+
} finally {
|
|
720
|
+
setIsStarting(false);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const handleReplayRun = async () => {
|
|
725
|
+
if (!timeline || isReplaying) return;
|
|
726
|
+
setIsReplaying(true);
|
|
727
|
+
try {
|
|
728
|
+
const result = await replayRun({ data: { runId } });
|
|
729
|
+
// Navigate to the new run
|
|
730
|
+
// router.navigate({
|
|
731
|
+
// to: "/pipelines/$pipelineId/$runId",
|
|
732
|
+
// params: { pipelineId, runId: result.runId },
|
|
733
|
+
// });
|
|
734
|
+
} catch (error) {
|
|
735
|
+
console.error("Failed to replay run:", error);
|
|
736
|
+
alert("Failed to replay run. Please try again.");
|
|
737
|
+
} finally {
|
|
738
|
+
setIsReplaying(false);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const handleReplayStep = async (stepId: string) => {
|
|
743
|
+
if (isReplaying) return;
|
|
744
|
+
setIsReplaying(true);
|
|
745
|
+
try {
|
|
746
|
+
const result = await replayStep({ data: { runStepId: stepId } });
|
|
747
|
+
// Navigate to the new run
|
|
748
|
+
// router.navigate({
|
|
749
|
+
// to: "/pipelines/$pipelineId/$runId",
|
|
750
|
+
// params: { pipelineId, runId: result.runId },
|
|
751
|
+
// });
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.error("Failed to replay step:", error);
|
|
754
|
+
alert("Failed to replay step. Please try again.");
|
|
755
|
+
} finally {
|
|
756
|
+
setIsReplaying(false);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
const extractionInfo = useMemo<(ExtractionInfo & { extractionTrigger: "web" | "api" })[]>(() => {
|
|
761
|
+
// Collect all unique extractions from input and output submissions
|
|
762
|
+
const extractionsById = new Map<
|
|
763
|
+
string,
|
|
764
|
+
ExtractionInfo & { extractionTrigger: "web" | "api" }
|
|
765
|
+
>();
|
|
766
|
+
|
|
767
|
+
// Helper to add extractions from a submission
|
|
768
|
+
const addExtractionsFromSubmission = (
|
|
769
|
+
submission: Awaited<ReturnType<typeof getSubmissionDataFn>> | null,
|
|
770
|
+
label: string,
|
|
771
|
+
) => {
|
|
772
|
+
if (!submission?.extractions) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
submission.extractions.forEach((e) => {
|
|
776
|
+
// Filter out deleted documents and extractions
|
|
777
|
+
if (e.deletedAt || e.document.deletedAt) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (!extractionsById.has(e.id)) {
|
|
781
|
+
extractionsById.set(e.id, {
|
|
782
|
+
documentId: e.document.id,
|
|
783
|
+
documentExtractionId: e.id,
|
|
784
|
+
url: e.document.blobUrl,
|
|
785
|
+
urlExpiry: e.document.blobUrlExpiry,
|
|
786
|
+
uploaded: e.document.uploaded,
|
|
787
|
+
name: e.document.name,
|
|
788
|
+
composite: e.document.composite,
|
|
789
|
+
parentId: e.document.parentId,
|
|
790
|
+
classificationLabel: e.document.classificationLabel,
|
|
791
|
+
status: e.status,
|
|
792
|
+
displayType: e.document.displayType,
|
|
793
|
+
extractionModel: e.extractionModel ?? undefined,
|
|
794
|
+
extractionTrigger: e.extractionTrigger,
|
|
795
|
+
extractedData: e.data,
|
|
796
|
+
reconciliationData: e.reconciliation as ApiReconciliation,
|
|
797
|
+
citations: [] as DocumentCitation[],
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// Helper to add citations from a submission's groups
|
|
804
|
+
const addCitationsFromSubmission = (
|
|
805
|
+
submission: Awaited<ReturnType<typeof getSubmissionDataFn>> | null,
|
|
806
|
+
label: string,
|
|
807
|
+
) => {
|
|
808
|
+
if (!submission?.groups) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
submission.groups.forEach((group) => {
|
|
812
|
+
group.items.forEach((item) => {
|
|
813
|
+
const version = item.selectedVersion;
|
|
814
|
+
if (!version) return;
|
|
815
|
+
|
|
816
|
+
// Recursive helper to collect document citations from transformation chains
|
|
817
|
+
const collectDocumentCitations = (v: typeof version): DocumentCitation[] => {
|
|
818
|
+
const citations: DocumentCitation[] = [];
|
|
819
|
+
|
|
820
|
+
// If this version has a direct document source, add it
|
|
821
|
+
if (v.documentSource) {
|
|
822
|
+
citations.push(v.documentSource);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// If this version has transformation sources, recursively collect their citations
|
|
826
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
827
|
+
const transformationSource = (v as any).transformationSource as
|
|
828
|
+
| Array<typeof version>
|
|
829
|
+
| undefined;
|
|
830
|
+
if (transformationSource && Array.isArray(transformationSource)) {
|
|
831
|
+
for (const sourceVersion of transformationSource) {
|
|
832
|
+
citations.push(...collectDocumentCitations(sourceVersion));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return citations;
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
const citations = collectDocumentCitations(version);
|
|
840
|
+
|
|
841
|
+
if (citations.length > 0) {
|
|
842
|
+
citations.forEach((citation) => {
|
|
843
|
+
const extraction = extractionsById.get(citation.documentExtractionId);
|
|
844
|
+
if (extraction) {
|
|
845
|
+
extraction.citations.push(citation);
|
|
846
|
+
} else {
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// Add extractions from all input submissions
|
|
855
|
+
if (inputSubmissionData) {
|
|
856
|
+
let index = 0;
|
|
857
|
+
inputSubmissionData.forEach((submission, submissionId) => {
|
|
858
|
+
const label = `Input[${index}:${submissionId}]`;
|
|
859
|
+
addExtractionsFromSubmission(submission, label);
|
|
860
|
+
addCitationsFromSubmission(submission, label);
|
|
861
|
+
index++;
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Add extractions and citations from all output submissions
|
|
866
|
+
if (outputSubmissionData) {
|
|
867
|
+
let index = 0;
|
|
868
|
+
outputSubmissionData.forEach((submissions, runStepId) => {
|
|
869
|
+
submissions.forEach((submission, submissionIdx) => {
|
|
870
|
+
const label = `Output[${index}:${runStepId}:${submissionIdx}]`;
|
|
871
|
+
addExtractionsFromSubmission(submission, label);
|
|
872
|
+
addCitationsFromSubmission(submission, label);
|
|
873
|
+
index++;
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return Array.from(extractionsById.values());
|
|
879
|
+
}, [inputSubmissionData, outputSubmissionData, timeline]);
|
|
880
|
+
|
|
881
|
+
const [visibleExtractionId, setDocVisible] = useState<string | undefined>(undefined);
|
|
882
|
+
const [activeCitationId, setActiveCitationId] = useState<string | undefined>(undefined);
|
|
883
|
+
const [boundingKey, setBoundingKey] = useState<string | undefined>(undefined);
|
|
884
|
+
|
|
885
|
+
// Filter extraction info to only show documents from the selected step's output submissions
|
|
886
|
+
const selectedStepExtractionInfo = useMemo(() => {
|
|
887
|
+
if (!selectedStepId) return extractionInfo;
|
|
888
|
+
|
|
889
|
+
// Get all run steps for the selected stepId
|
|
890
|
+
const runStepsForSelected = stepsByStepId.get(selectedStepId) ?? [];
|
|
891
|
+
const runStepIds = new Set(runStepsForSelected.map((s) => s.id));
|
|
892
|
+
|
|
893
|
+
// Collect extraction IDs from output submissions of these run steps
|
|
894
|
+
const allowedExtractionIds = new Set<string>();
|
|
895
|
+
runStepIds.forEach((runStepId) => {
|
|
896
|
+
const submissions = outputSubmissionData.get(runStepId) ?? [];
|
|
897
|
+
submissions.forEach((submission) => {
|
|
898
|
+
submission?.extractions?.forEach((extraction) => {
|
|
899
|
+
allowedExtractionIds.add(extraction.id);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Filter to only include extractions from the selected step's output submissions
|
|
905
|
+
return extractionInfo.filter((ext) => allowedExtractionIds.has(ext.documentExtractionId));
|
|
906
|
+
}, [extractionInfo, selectedStepId, stepsByStepId, outputSubmissionData]);
|
|
907
|
+
|
|
908
|
+
const activeDocument = useMemo(
|
|
909
|
+
() =>
|
|
910
|
+
visibleExtractionId
|
|
911
|
+
? extractionInfo.find((p) => p.documentExtractionId === visibleExtractionId && !!p.url)
|
|
912
|
+
: undefined,
|
|
913
|
+
[visibleExtractionId, extractionInfo],
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
const focusDocumentForCitation = useCallback(
|
|
917
|
+
(citationId: string | undefined) => {
|
|
918
|
+
if (citationId === undefined) return;
|
|
919
|
+
const selectedPdf = extractionInfo.find((p) => p.citations.find((c) => c.id === citationId));
|
|
920
|
+
if (!selectedPdf) return;
|
|
921
|
+
setDocVisible(selectedPdf.documentExtractionId);
|
|
922
|
+
},
|
|
923
|
+
[extractionInfo],
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
const setActiveAnnotation = useCallback(
|
|
927
|
+
(id: string | undefined, options: { focus?: boolean } = {}) => {
|
|
928
|
+
const { focus = true } = options;
|
|
929
|
+
setActiveCitationId(id);
|
|
930
|
+
if (focus && id) {
|
|
931
|
+
focusDocumentForCitation(id);
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
[focusDocumentForCitation],
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
const handleFormAnnotationSelect = useCallback(
|
|
938
|
+
(id: string | undefined) => {
|
|
939
|
+
setActiveAnnotation(id, { focus: true });
|
|
940
|
+
},
|
|
941
|
+
[setActiveAnnotation],
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
const handleSelectCitation = useCallback(
|
|
945
|
+
(citationId?: string, key?: string) => {
|
|
946
|
+
setBoundingKey(key);
|
|
947
|
+
setActiveAnnotation(citationId, { focus: true });
|
|
948
|
+
|
|
949
|
+
// Scroll to the form field containing this citation
|
|
950
|
+
setTimeout(() => {
|
|
951
|
+
const element = document.querySelector(`[data-citation-id="${citationId}"]`);
|
|
952
|
+
if (element) {
|
|
953
|
+
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
954
|
+
}
|
|
955
|
+
}, 100);
|
|
956
|
+
},
|
|
957
|
+
[setActiveAnnotation],
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
const refreshPdfUrl = useCallback(() => {
|
|
961
|
+
if (activeDocument) {
|
|
962
|
+
return downloadDocument({ data: { documentId: activeDocument.documentId } }).then(
|
|
963
|
+
({ blobUrl }) => {
|
|
964
|
+
// Update the URL in extractionInfo if needed
|
|
965
|
+
const extraction = extractionInfo.find(
|
|
966
|
+
(e) => e.documentExtractionId === activeDocument.documentExtractionId,
|
|
967
|
+
);
|
|
968
|
+
if (extraction) {
|
|
969
|
+
extraction.url = blobUrl;
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
return Promise.resolve();
|
|
975
|
+
}, [activeDocument, extractionInfo, downloadDocument]);
|
|
976
|
+
|
|
977
|
+
const handleDownload = async (documentId: string) => {
|
|
978
|
+
const { blobUrl } = await downloadDocument({ data: { documentId } });
|
|
979
|
+
const [domain, docPath] = blobUrl.split("/documents/");
|
|
980
|
+
if (domain && docPath) {
|
|
981
|
+
const newUrl = new URL(docPath, "https://ph.sea.dev/doc/").toString();
|
|
982
|
+
window.open(newUrl, "_blank");
|
|
983
|
+
} else {
|
|
984
|
+
window.open(blobUrl, "_blank");
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// State for upload modal
|
|
989
|
+
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
|
990
|
+
const [selectedRunStepId, setSelectedRunStepId] = useState<string | null>(null);
|
|
991
|
+
|
|
992
|
+
// State for submission selector modal
|
|
993
|
+
const [submissionSelectorOpen, setSubmissionSelectorOpen] = useState(false);
|
|
994
|
+
const [selectedBlockerDetails, setSelectedBlockerDetails] = useState<{
|
|
995
|
+
blockerId: string;
|
|
996
|
+
runStepId: string;
|
|
997
|
+
stepName: string;
|
|
998
|
+
requiredFormId?: string;
|
|
999
|
+
requiredFormName?: string;
|
|
1000
|
+
} | null>(null);
|
|
1001
|
+
|
|
1002
|
+
const handleResolveBlocker = useCallback(
|
|
1003
|
+
(blockerId: string, blockerType: string, runStepId: string) => {
|
|
1004
|
+
switch (blockerType) {
|
|
1005
|
+
case "missing_upload":
|
|
1006
|
+
// Open upload modal
|
|
1007
|
+
setSelectedRunStepId(runStepId);
|
|
1008
|
+
setUploadModalOpen(true);
|
|
1009
|
+
break;
|
|
1010
|
+
case "missing_manual_input": {
|
|
1011
|
+
// Find the blocker details to get form info
|
|
1012
|
+
const stepBlockers = blockers?.find((b) => b.runStepId === runStepId);
|
|
1013
|
+
const blocker = stepBlockers?.blockers.find((b) => b.id === blockerId);
|
|
1014
|
+
const runStep = timeline?.steps.find((s) => s.id === runStepId);
|
|
1015
|
+
const pipelineStep = pipeline?.steps.find((s) => s.id === runStep?.stepId);
|
|
1016
|
+
|
|
1017
|
+
const details = blocker?.blockerDetails;
|
|
1018
|
+
const formId =
|
|
1019
|
+
details && "requiredFormId" in details ? details.requiredFormId : undefined;
|
|
1020
|
+
const formName =
|
|
1021
|
+
details && "requiredFormName" in details ? details.requiredFormName : undefined;
|
|
1022
|
+
|
|
1023
|
+
setSelectedBlockerDetails({
|
|
1024
|
+
blockerId,
|
|
1025
|
+
runStepId,
|
|
1026
|
+
stepName: pipelineStep?.name ?? "Unknown Step",
|
|
1027
|
+
requiredFormId: formId,
|
|
1028
|
+
requiredFormName: formName,
|
|
1029
|
+
});
|
|
1030
|
+
setSubmissionSelectorOpen(true);
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
case "missing_config":
|
|
1034
|
+
// Navigate to pipeline configuration
|
|
1035
|
+
toast.info("Navigate to pipeline configuration to fix the step setup", {
|
|
1036
|
+
action: {
|
|
1037
|
+
label: "Go to Config",
|
|
1038
|
+
onClick: () => {
|
|
1039
|
+
router.navigate({
|
|
1040
|
+
to: "/pipelines/$pipelineId",
|
|
1041
|
+
params: { pipelineId },
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
break;
|
|
1047
|
+
default:
|
|
1048
|
+
// Unknown blocker type
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
[pipelineId, router, blockers, timeline, pipeline],
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
const handleSubmitManualInput = async (submissionIds: string[]) => {
|
|
1056
|
+
if (!selectedBlockerDetails) return;
|
|
1057
|
+
|
|
1058
|
+
try {
|
|
1059
|
+
await resolveBlockerWithManualInputFn({
|
|
1060
|
+
data: {
|
|
1061
|
+
blockerId: selectedBlockerDetails.blockerId,
|
|
1062
|
+
runStepId: selectedBlockerDetails.runStepId,
|
|
1063
|
+
submissionIds,
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
toast.success("Manual input added successfully");
|
|
1067
|
+
router.invalidate();
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
console.error("Failed to add manual input:", error);
|
|
1070
|
+
toast.error("Failed to add manual input");
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
const sidebar = (
|
|
1076
|
+
<SidebarRight className="border-none">
|
|
1077
|
+
{activeDocument && (
|
|
1078
|
+
<div className="flex h-full flex-col">
|
|
1079
|
+
<div className="relative flex w-full items-center justify-between gap-4 border-b p-2">
|
|
1080
|
+
<H3>{activeDocument.name}</H3>
|
|
1081
|
+
<div className="flex items-center gap-1">
|
|
1082
|
+
<Button
|
|
1083
|
+
size="icon"
|
|
1084
|
+
variant="ghost"
|
|
1085
|
+
onClick={() => {
|
|
1086
|
+
setDocVisible(undefined);
|
|
1087
|
+
setBoundingKey(undefined);
|
|
1088
|
+
}}
|
|
1089
|
+
>
|
|
1090
|
+
<XIcon />
|
|
1091
|
+
<span className="sr-only">Close</span>
|
|
1092
|
+
</Button>
|
|
1093
|
+
</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
<PdfViewer
|
|
1096
|
+
pdfInfo={activeDocument}
|
|
1097
|
+
activeAnnotationKey={boundingKey}
|
|
1098
|
+
activeCitationId={activeCitationId}
|
|
1099
|
+
onSelectCitation={handleSelectCitation}
|
|
1100
|
+
refreshPdfUrl={refreshPdfUrl}
|
|
1101
|
+
/>
|
|
1102
|
+
</div>
|
|
1103
|
+
)}
|
|
1104
|
+
{!activeDocument && (
|
|
1105
|
+
<ResizablePanelGroup direction="vertical">
|
|
1106
|
+
<ResizablePanel defaultSize={60}>
|
|
1107
|
+
<div className="flex h-full min-h-0 flex-col gap-2 p-2">
|
|
1108
|
+
<H3>Artifacts</H3>
|
|
1109
|
+
<div className="h-full min-h-0 space-y-2 overflow-y-auto">
|
|
1110
|
+
{timeline?.artifacts && timeline.artifacts.length > 0 ? (
|
|
1111
|
+
timeline.artifacts.map((artifact) => {
|
|
1112
|
+
const markdownContent =
|
|
1113
|
+
typeof artifact.content === "object" &&
|
|
1114
|
+
artifact.content !== null &&
|
|
1115
|
+
"markdown" in artifact.content
|
|
1116
|
+
? String(artifact.content.markdown)
|
|
1117
|
+
: typeof artifact.content === "string"
|
|
1118
|
+
? artifact.content
|
|
1119
|
+
: JSON.stringify(artifact.content, null, 2);
|
|
1120
|
+
|
|
1121
|
+
const markdownFileName = `${artifact.title}.md`;
|
|
1122
|
+
const wordFileName = `${artifact.title}.doc`;
|
|
1123
|
+
|
|
1124
|
+
return (
|
|
1125
|
+
<Dialog key={artifact.id}>
|
|
1126
|
+
<div className="group/item bg-muted/50 hover:bg-muted/70 flex items-center justify-between gap-2 rounded-md">
|
|
1127
|
+
<DialogTrigger asChild>
|
|
1128
|
+
<button className="flex grow cursor-pointer items-center justify-between gap-2 p-2 text-left">
|
|
1129
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
1130
|
+
<FileTextIcon className="text-primary h-4 w-4 shrink-0" />
|
|
1131
|
+
<span className="truncate text-sm">{artifact.title}</span>
|
|
1132
|
+
</span>
|
|
1133
|
+
<Badge variant="secondary" className="shrink-0 text-xs">
|
|
1134
|
+
{artifact.type}
|
|
1135
|
+
</Badge>
|
|
1136
|
+
</button>
|
|
1137
|
+
</DialogTrigger>
|
|
1138
|
+
</div>
|
|
1139
|
+
<DialogContent className="h-[90vh] sm:max-w-6xl">
|
|
1140
|
+
<DialogHeader>
|
|
1141
|
+
<DialogTitle>{artifact.title}</DialogTitle>
|
|
1142
|
+
</DialogHeader>
|
|
1143
|
+
<div className="prose prose-sm dark:prose-invert max-h-[60vh] max-w-full overflow-y-auto">
|
|
1144
|
+
{artifact.type === "markdown" ? (
|
|
1145
|
+
<Markdown remarkPlugins={[remarkGfm]}>{markdownContent}</Markdown>
|
|
1146
|
+
) : (
|
|
1147
|
+
<pre className="text-xs">{markdownContent}</pre>
|
|
1148
|
+
)}
|
|
1149
|
+
</div>
|
|
1150
|
+
<DialogFooter className="flex w-full flex-wrap items-center justify-between gap-2">
|
|
1151
|
+
<div className="text-muted-foreground text-xs">
|
|
1152
|
+
Created <RelDate date={artifact.createdAt} />
|
|
1153
|
+
</div>
|
|
1154
|
+
<div className="flex gap-2">
|
|
1155
|
+
<Button
|
|
1156
|
+
variant="secondary"
|
|
1157
|
+
size="sm"
|
|
1158
|
+
onClick={() =>
|
|
1159
|
+
downloadBlob(
|
|
1160
|
+
new Blob([markdownContent], { type: "text/markdown" }),
|
|
1161
|
+
markdownFileName,
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
>
|
|
1165
|
+
<DownloadIcon className="mr-2 h-4 w-4" />
|
|
1166
|
+
Download Markdown
|
|
1167
|
+
</Button>
|
|
1168
|
+
<Button
|
|
1169
|
+
variant="outline"
|
|
1170
|
+
size="sm"
|
|
1171
|
+
onClick={() =>
|
|
1172
|
+
downloadBlob(
|
|
1173
|
+
new Blob(
|
|
1174
|
+
[
|
|
1175
|
+
`<!DOCTYPE html><html><head><meta charset="utf-8" /></head><body><pre>${escapeHtml(
|
|
1176
|
+
markdownContent,
|
|
1177
|
+
)}</pre></body></html>`,
|
|
1178
|
+
],
|
|
1179
|
+
{ type: "application/msword" },
|
|
1180
|
+
),
|
|
1181
|
+
wordFileName,
|
|
1182
|
+
)
|
|
1183
|
+
}
|
|
1184
|
+
>
|
|
1185
|
+
<DownloadIcon className="mr-2 h-4 w-4" />
|
|
1186
|
+
Download Word
|
|
1187
|
+
</Button>
|
|
1188
|
+
</div>
|
|
1189
|
+
</DialogFooter>
|
|
1190
|
+
</DialogContent>
|
|
1191
|
+
</Dialog>
|
|
1192
|
+
);
|
|
1193
|
+
})
|
|
1194
|
+
) : (
|
|
1195
|
+
<div className="text-muted-foreground flex items-center justify-center py-8">
|
|
1196
|
+
No artifacts yet
|
|
1197
|
+
</div>
|
|
1198
|
+
)}
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
</ResizablePanel>
|
|
1202
|
+
<ResizableHandle withHandle={true} />
|
|
1203
|
+
<ResizablePanel defaultSize={40}>
|
|
1204
|
+
<DocList
|
|
1205
|
+
docs={selectedStepExtractionInfo}
|
|
1206
|
+
setDocVisible={setDocVisible}
|
|
1207
|
+
canExtract={true}
|
|
1208
|
+
download={handleDownload}
|
|
1209
|
+
/>
|
|
1210
|
+
</ResizablePanel>
|
|
1211
|
+
</ResizablePanelGroup>
|
|
1212
|
+
)}
|
|
1213
|
+
</SidebarRight>
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1216
|
+
return (
|
|
1217
|
+
<Container crumbs={crumbs} sidebar={sidebar}>
|
|
1218
|
+
<div className="space-y-6">
|
|
1219
|
+
{/* Header with all run metadata */}
|
|
1220
|
+
<div className="space-y-3">
|
|
1221
|
+
<div className="flex items-center justify-between">
|
|
1222
|
+
<h1 className="font-mono text-2xl font-semibold">{runId}</h1>
|
|
1223
|
+
<div className="flex items-center gap-2">
|
|
1224
|
+
{/* View Mode Toggle */}
|
|
1225
|
+
{timeline && allSteps.length > 0 && pipeline && (
|
|
1226
|
+
<div className="flex gap-1 rounded-md border p-1">
|
|
1227
|
+
<button
|
|
1228
|
+
type="button"
|
|
1229
|
+
onClick={() => setViewMode("flow")}
|
|
1230
|
+
className={`rounded px-3 py-0.5 text-sm font-medium transition-colors ${
|
|
1231
|
+
viewMode === "flow" ? "bg-primary text-primary-foreground" : "hover:bg-muted"
|
|
1232
|
+
}`}
|
|
1233
|
+
>
|
|
1234
|
+
Flow
|
|
1235
|
+
</button>
|
|
1236
|
+
<button
|
|
1237
|
+
type="button"
|
|
1238
|
+
onClick={() => setViewMode("dag")}
|
|
1239
|
+
className={`rounded px-3 py-0.5 text-sm font-medium transition-colors ${
|
|
1240
|
+
viewMode === "dag" ? "bg-primary text-primary-foreground" : "hover:bg-muted"
|
|
1241
|
+
}`}
|
|
1242
|
+
>
|
|
1243
|
+
DAG
|
|
1244
|
+
</button>
|
|
1245
|
+
</div>
|
|
1246
|
+
)}
|
|
1247
|
+
{timeline && timeline.status === "pending" && (
|
|
1248
|
+
<Button
|
|
1249
|
+
variant="default"
|
|
1250
|
+
size="sm"
|
|
1251
|
+
onClick={handleStartRun}
|
|
1252
|
+
disabled={
|
|
1253
|
+
isStarting ||
|
|
1254
|
+
(blockers?.some((b) => b.blockers.some((blocker) => !blocker.resolved)) ??
|
|
1255
|
+
false)
|
|
1256
|
+
}
|
|
1257
|
+
>
|
|
1258
|
+
<PlayIcon className="mr-2 size-4" />
|
|
1259
|
+
{isStarting
|
|
1260
|
+
? "Starting..."
|
|
1261
|
+
: (blockers?.some((b) => b.blockers.some((blocker) => !blocker.resolved)) ??
|
|
1262
|
+
false)
|
|
1263
|
+
? "Resolve Blockers First"
|
|
1264
|
+
: "Start Run"}
|
|
1265
|
+
</Button>
|
|
1266
|
+
)}
|
|
1267
|
+
{timeline && (
|
|
1268
|
+
<Button
|
|
1269
|
+
variant="outline"
|
|
1270
|
+
size="sm"
|
|
1271
|
+
onClick={handleReplayRun}
|
|
1272
|
+
disabled={isReplaying}
|
|
1273
|
+
>
|
|
1274
|
+
<RotateCcwIcon className="size-4" />
|
|
1275
|
+
</Button>
|
|
1276
|
+
)}
|
|
1277
|
+
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
|
|
1280
|
+
{timeline && (
|
|
1281
|
+
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm">
|
|
1282
|
+
<div className="flex items-center gap-2">
|
|
1283
|
+
<span className="text-muted-foreground">Created</span>
|
|
1284
|
+
<span>
|
|
1285
|
+
<RelDate date={timeline.createdAt} />
|
|
1286
|
+
</span>
|
|
1287
|
+
</div>
|
|
1288
|
+
<span className="text-muted-foreground">|</span>
|
|
1289
|
+
<div className="flex items-center gap-2">
|
|
1290
|
+
<span className="text-muted-foreground">Started</span>
|
|
1291
|
+
<span>
|
|
1292
|
+
<RelDate date={timeline.startedAt} />
|
|
1293
|
+
</span>
|
|
1294
|
+
</div>
|
|
1295
|
+
<span className="text-muted-foreground">|</span>
|
|
1296
|
+
{timeline.completedAt && (
|
|
1297
|
+
<>
|
|
1298
|
+
<div className="flex items-center gap-2">
|
|
1299
|
+
<span className="text-muted-foreground">
|
|
1300
|
+
{timeline.status === "completed" ? "Succeeded" : "Finished"}
|
|
1301
|
+
</span>
|
|
1302
|
+
<span>
|
|
1303
|
+
<RelDate date={timeline.completedAt} />
|
|
1304
|
+
</span>
|
|
1305
|
+
</div>
|
|
1306
|
+
<span className="text-muted-foreground">|</span>
|
|
1307
|
+
</>
|
|
1308
|
+
)}
|
|
1309
|
+
{getDuration() && (
|
|
1310
|
+
<div className="flex items-center gap-2">
|
|
1311
|
+
<span className="text-muted-foreground">Run took</span>
|
|
1312
|
+
<span className="font-medium">{formatDuration(getDuration())}</span>
|
|
1313
|
+
</div>
|
|
1314
|
+
)}
|
|
1315
|
+
</div>
|
|
1316
|
+
)}
|
|
1317
|
+
</div>
|
|
1318
|
+
|
|
1319
|
+
{/* Blockers Section - Only in Flow View */}
|
|
1320
|
+
{viewMode === "flow" && timeline && allSteps.length > 0 && (
|
|
1321
|
+
<Accordion type="multiple">
|
|
1322
|
+
<AccordionItem value="blockers">
|
|
1323
|
+
<AccordionTrigger className="px-4">
|
|
1324
|
+
<H3 className="text-base font-semibold">Dependencies</H3>
|
|
1325
|
+
</AccordionTrigger>
|
|
1326
|
+
<AccordionContent className="px-4 pb-4">
|
|
1327
|
+
<BlockersPanel
|
|
1328
|
+
steps={timeline.steps.map((step) => {
|
|
1329
|
+
const pipelineStep = pipeline?.steps.find((s) => s.id === step.stepId);
|
|
1330
|
+
const stepBlockerGroup = blockers?.find((b) => b.runStepId === step.id);
|
|
1331
|
+
const stepBlockers = stepBlockerGroup?.blockers ?? [];
|
|
1332
|
+
const hasUnresolvedBlockers = stepBlockers.some((b) => !b.resolved);
|
|
1333
|
+
return {
|
|
1334
|
+
id: step.id,
|
|
1335
|
+
stepId: step.id, // Use run step ID for blocker resolution
|
|
1336
|
+
stepName: pipelineStep?.name ?? "Unknown Step",
|
|
1337
|
+
status: step.status as "pending" | "running" | "completed" | "failed",
|
|
1338
|
+
blockers: stepBlockers,
|
|
1339
|
+
hasUnresolvedBlockers,
|
|
1340
|
+
};
|
|
1341
|
+
})}
|
|
1342
|
+
onResolveBlocker={handleResolveBlocker}
|
|
1343
|
+
/>
|
|
1344
|
+
</AccordionContent>
|
|
1345
|
+
</AccordionItem>
|
|
1346
|
+
</Accordion>
|
|
1347
|
+
)}
|
|
1348
|
+
|
|
1349
|
+
{/* Pipeline Visualization */}
|
|
1350
|
+
{timeline && allSteps.length > 0 && pipeline && (
|
|
1351
|
+
<>
|
|
1352
|
+
{/* Flow View */}
|
|
1353
|
+
{viewMode === "flow" && (
|
|
1354
|
+
<div className="overflow-hidden rounded-lg border border-gray-200 dark:border-white/15">
|
|
1355
|
+
<nav aria-label="Progress">
|
|
1356
|
+
<ol role="list" className="lg:flex">
|
|
1357
|
+
{allSteps.map((step, idx) => {
|
|
1358
|
+
const stepKey = step.stepId;
|
|
1359
|
+
return (
|
|
1360
|
+
<ProcessFlowStep
|
|
1361
|
+
key={stepKey}
|
|
1362
|
+
step={step}
|
|
1363
|
+
isSelected={selectedStepId === stepKey}
|
|
1364
|
+
isFirst={idx === 0}
|
|
1365
|
+
isLast={idx === allSteps.length - 1}
|
|
1366
|
+
stepNumber={idx + 1}
|
|
1367
|
+
onClick={() => setSelectedStepId(stepKey ?? undefined)}
|
|
1368
|
+
/>
|
|
1369
|
+
);
|
|
1370
|
+
})}
|
|
1371
|
+
</ol>
|
|
1372
|
+
</nav>
|
|
1373
|
+
</div>
|
|
1374
|
+
)}
|
|
1375
|
+
|
|
1376
|
+
{/* DAG View */}
|
|
1377
|
+
{viewMode === "dag" && (
|
|
1378
|
+
<div className="h-[500px] rounded-lg border border-gray-200 dark:border-white/15">
|
|
1379
|
+
<DAGView
|
|
1380
|
+
steps={pipeline.steps.map((step) => ({
|
|
1381
|
+
id: step.id,
|
|
1382
|
+
name: step.name,
|
|
1383
|
+
description: step.description ?? "",
|
|
1384
|
+
role: step.role,
|
|
1385
|
+
type: step.type,
|
|
1386
|
+
stepOrder: 0,
|
|
1387
|
+
}))}
|
|
1388
|
+
dependencies={pipeline.steps.flatMap(
|
|
1389
|
+
(step) =>
|
|
1390
|
+
step.parentDependencies?.map((dep) => ({
|
|
1391
|
+
id: dep.id,
|
|
1392
|
+
childStepId: step.id,
|
|
1393
|
+
parentStepId: dep.parentStepId,
|
|
1394
|
+
order: undefined,
|
|
1395
|
+
})) ?? [],
|
|
1396
|
+
)}
|
|
1397
|
+
onNodeClick={(stepId) => {
|
|
1398
|
+
// Find the run step for this pipeline step
|
|
1399
|
+
const runStep = timeline.steps.find((s) => s.stepId === stepId);
|
|
1400
|
+
if (runStep) {
|
|
1401
|
+
setSelectedStepId(stepId);
|
|
1402
|
+
setViewMode("flow"); // Switch to flow view to show details
|
|
1403
|
+
}
|
|
1404
|
+
}}
|
|
1405
|
+
/>
|
|
1406
|
+
</div>
|
|
1407
|
+
)}
|
|
1408
|
+
</>
|
|
1409
|
+
)}
|
|
1410
|
+
|
|
1411
|
+
{/* Step Details - Only in Flow View */}
|
|
1412
|
+
{viewMode === "flow" && selectedStep && (
|
|
1413
|
+
<div>
|
|
1414
|
+
<StepDetails
|
|
1415
|
+
step={selectedStep}
|
|
1416
|
+
allStepsForThisStep={allStepsForSelected}
|
|
1417
|
+
outputSubmissionData={outputSubmissionData}
|
|
1418
|
+
onReplay={handleReplayStep}
|
|
1419
|
+
isReplaying={isReplaying}
|
|
1420
|
+
activeCitationId={activeCitationId}
|
|
1421
|
+
onAnnotationSelect={handleFormAnnotationSelect}
|
|
1422
|
+
boundingKey={boundingKey}
|
|
1423
|
+
setBoundingKey={setBoundingKey}
|
|
1424
|
+
/>
|
|
1425
|
+
</div>
|
|
1426
|
+
)}
|
|
1427
|
+
|
|
1428
|
+
{/* Loading state */}
|
|
1429
|
+
{!timeline && (
|
|
1430
|
+
<Card>
|
|
1431
|
+
<CardContent className="py-12">
|
|
1432
|
+
<p className="text-muted-foreground text-center">Loading run details...</p>
|
|
1433
|
+
</CardContent>
|
|
1434
|
+
</Card>
|
|
1435
|
+
)}
|
|
1436
|
+
</div>
|
|
1437
|
+
|
|
1438
|
+
{/* Upload Documents Modal */}
|
|
1439
|
+
{uploadModalOpen && selectedRunStepId && (
|
|
1440
|
+
<Dialog open={uploadModalOpen} onOpenChange={setUploadModalOpen}>
|
|
1441
|
+
<DialogContent className="sm:max-w-lg">
|
|
1442
|
+
<DialogHeader>
|
|
1443
|
+
<DialogTitle>Upload Documents</DialogTitle>
|
|
1444
|
+
<DialogDescription>
|
|
1445
|
+
Upload documents to resolve the blocker and continue pipeline execution
|
|
1446
|
+
</DialogDescription>
|
|
1447
|
+
</DialogHeader>
|
|
1448
|
+
<UploadDocForPipeline
|
|
1449
|
+
runId={runId}
|
|
1450
|
+
runStepId={selectedRunStepId}
|
|
1451
|
+
isExperimental={false}
|
|
1452
|
+
onUploadComplete={() => {
|
|
1453
|
+
setUploadModalOpen(false);
|
|
1454
|
+
setSelectedRunStepId(null);
|
|
1455
|
+
}}
|
|
1456
|
+
/>
|
|
1457
|
+
</DialogContent>
|
|
1458
|
+
</Dialog>
|
|
1459
|
+
)}
|
|
1460
|
+
|
|
1461
|
+
{/* Submission Selector Modal */}
|
|
1462
|
+
{selectedBlockerDetails && (
|
|
1463
|
+
<SubmissionSelectorModal
|
|
1464
|
+
open={submissionSelectorOpen}
|
|
1465
|
+
onClose={() => {
|
|
1466
|
+
setSubmissionSelectorOpen(false);
|
|
1467
|
+
setSelectedBlockerDetails(null);
|
|
1468
|
+
}}
|
|
1469
|
+
onSubmit={handleSubmitManualInput}
|
|
1470
|
+
availableSubmissions={availableSubmissions.map((sub) => ({
|
|
1471
|
+
id: sub.id,
|
|
1472
|
+
name: sub.name,
|
|
1473
|
+
formId: sub.formId,
|
|
1474
|
+
formName: sub.form.name,
|
|
1475
|
+
status: sub.status,
|
|
1476
|
+
updatedAt: sub.updatedAt,
|
|
1477
|
+
}))}
|
|
1478
|
+
requiredFormId={selectedBlockerDetails.requiredFormId}
|
|
1479
|
+
requiredFormName={selectedBlockerDetails.requiredFormName}
|
|
1480
|
+
stepName={selectedBlockerDetails.stepName}
|
|
1481
|
+
/>
|
|
1482
|
+
)}
|
|
1483
|
+
</Container>
|
|
1484
|
+
);
|
|
1485
|
+
}
|