veryfront 0.1.48 → 0.1.49
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/esm/cli/app/components/inline-input.d.ts +1 -4
- package/esm/cli/app/components/inline-input.d.ts.map +1 -1
- package/esm/cli/app/components/inline-input.js +1 -1
- package/esm/cli/app/components/list-select.d.ts +0 -8
- package/esm/cli/app/components/list-select.d.ts.map +1 -1
- package/esm/cli/app/components/list-select.js +0 -13
- package/esm/cli/app/operations/project-creation.d.ts +2 -14
- package/esm/cli/app/operations/project-creation.d.ts.map +1 -1
- package/esm/cli/app/operations/project-creation.js +3 -68
- package/esm/cli/app/shell.js +1 -1
- package/esm/cli/app/utils.d.ts +1 -2
- package/esm/cli/app/utils.d.ts.map +1 -1
- package/esm/cli/app/utils.js +1 -17
- package/esm/cli/app/views/dashboard.d.ts +0 -4
- package/esm/cli/app/views/dashboard.d.ts.map +1 -1
- package/esm/cli/app/views/dashboard.js +0 -15
- package/esm/cli/app/views/startup.d.ts +0 -4
- package/esm/cli/app/views/startup.d.ts.map +1 -1
- package/esm/cli/app/views/startup.js +0 -7
- package/esm/cli/auth/login.d.ts.map +1 -1
- package/esm/cli/auth/login.js +1 -2
- package/esm/cli/auth/token-store.js +1 -1
- package/esm/cli/auth/utils.d.ts +2 -1
- package/esm/cli/auth/utils.d.ts.map +1 -1
- package/esm/cli/commands/generate/integration-generator.js +1 -1
- package/esm/cli/commands/install/types.d.ts +2 -2
- package/esm/cli/help/formatters.d.ts +1 -1
- package/esm/cli/help/formatters.d.ts.map +1 -1
- package/esm/cli/help/formatters.js +8 -4
- package/esm/cli/help/main-help.d.ts.map +1 -1
- package/esm/cli/help/main-help.js +2 -2
- package/esm/cli/mcp/server.d.ts +0 -1
- package/esm/cli/mcp/server.d.ts.map +1 -1
- package/esm/cli/mcp/server.js +2 -5
- package/esm/cli/mcp/tools/dev-tools.d.ts +1 -1
- package/esm/cli/mcp/tools/dev-tools.d.ts.map +1 -1
- package/esm/cli/mcp/tools/dev-tools.js +1 -2
- package/esm/cli/mcp/tools/scaffold-tools.d.ts +2 -2
- package/esm/cli/mcp/tools.d.ts +1 -2
- package/esm/cli/mcp/tools.d.ts.map +1 -1
- package/esm/cli/mcp/tools.js +1 -2
- package/esm/cli/shared/reserve-slug.d.ts.map +1 -1
- package/esm/cli/shared/reserve-slug.js +1 -3
- package/esm/cli/sync/ignore.d.ts +1 -1
- package/esm/cli/sync/ignore.d.ts.map +1 -1
- package/esm/cli/sync/ignore.js +22 -18
- package/esm/cli/ui/colors.d.ts +0 -1
- package/esm/cli/ui/colors.d.ts.map +1 -1
- package/esm/cli/ui/colors.js +0 -10
- package/esm/cli/ui/dot-matrix.d.ts.map +1 -1
- package/esm/cli/ui/dot-matrix.js +2 -5
- package/esm/cli/ui/tui.js +2 -3
- package/esm/cli/utils/index.js +1 -2
- package/esm/cli/utils/package-manager.d.ts.map +1 -1
- package/esm/cli/utils/package-manager.js +3 -4
- package/esm/deno.d.ts +3 -0
- package/esm/deno.js +22 -19
- package/esm/src/agent/chat-handler.d.ts.map +1 -1
- package/esm/src/agent/chat-handler.js +8 -23
- package/esm/src/agent/factory.d.ts +0 -8
- package/esm/src/agent/factory.d.ts.map +1 -1
- package/esm/src/agent/factory.js +50 -1
- package/esm/src/agent/index.d.ts +16 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +16 -1
- package/esm/src/agent/memory/memory.d.ts +2 -2
- package/esm/src/agent/memory/memory.d.ts.map +1 -1
- package/esm/src/agent/memory/memory.js +0 -1
- package/esm/src/agent/react/use-chat/types.d.ts +2 -0
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.js +15 -9
- package/esm/src/agent/runtime/index.d.ts +18 -0
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +167 -36
- package/esm/src/agent/runtime/tool-helpers.d.ts +6 -1
- package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -1
- package/esm/src/agent/runtime/tool-helpers.js +10 -1
- package/esm/src/agent/schemas/agent.schema.d.ts +4 -4
- package/esm/src/agent/types.d.ts +10 -0
- package/esm/src/agent/types.d.ts.map +1 -1
- package/esm/src/ai/registry-manager.d.ts.map +1 -1
- package/esm/src/ai/registry-manager.js +2 -1
- package/esm/src/build/bundler/code-splitter/manifest-builder.d.ts +0 -2
- package/esm/src/build/bundler/code-splitter/manifest-builder.d.ts.map +1 -1
- package/esm/src/build/bundler/code-splitter/manifest-builder.js +1 -1
- package/esm/src/build/compiler/mdx-compiler/validator.d.ts.map +1 -1
- package/esm/src/build/compiler/mdx-compiler/validator.js +2 -3
- package/esm/src/build/compiler/mdx-to-js.d.ts +0 -2
- package/esm/src/build/compiler/mdx-to-js.d.ts.map +1 -1
- package/esm/src/build/compiler/mdx-to-js.js +0 -81
- package/esm/src/build/index.d.ts +0 -6
- package/esm/src/build/index.d.ts.map +1 -1
- package/esm/src/build/index.js +0 -1
- package/esm/src/build/production-build/build/route-collector.d.ts.map +1 -1
- package/esm/src/build/production-build/build/route-collector.js +2 -3
- package/esm/src/build/production-build/templates.d.ts +5 -9
- package/esm/src/build/production-build/templates.d.ts.map +1 -1
- package/esm/src/build/production-build/templates.js +6 -18
- package/esm/src/build/vendor-cache.d.ts.map +1 -1
- package/esm/src/build/vendor-cache.js +0 -5
- package/esm/src/cache/tokenizing-gateway.d.ts +0 -2
- package/esm/src/cache/tokenizing-gateway.d.ts.map +1 -1
- package/esm/src/cache/tokenizing-gateway.js +1 -3
- package/esm/src/chat/index.d.ts +2 -2
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/chat/index.js +1 -1
- package/esm/src/config/define-config.d.ts.map +1 -1
- package/esm/src/config/define-config.js +28 -25
- package/esm/src/config/loader.d.ts.map +1 -1
- package/esm/src/config/loader.js +10 -7
- package/esm/src/config/schemas/config.schema.d.ts +58 -12
- package/esm/src/config/schemas/config.schema.d.ts.map +1 -1
- package/esm/src/config/schemas/config.schema.js +12 -0
- package/esm/src/data/data-fetcher.d.ts +1 -2
- package/esm/src/data/data-fetcher.d.ts.map +1 -1
- package/esm/src/data/data-fetcher.js +14 -15
- package/esm/src/data/server-data-fetcher.d.ts +0 -3
- package/esm/src/data/server-data-fetcher.d.ts.map +1 -1
- package/esm/src/data/server-data-fetcher.js +1 -8
- package/esm/src/data/static-data-fetcher.d.ts +1 -3
- package/esm/src/data/static-data-fetcher.d.ts.map +1 -1
- package/esm/src/data/static-data-fetcher.js +1 -3
- package/esm/src/discovery/discovery-engine.d.ts.map +1 -1
- package/esm/src/discovery/discovery-engine.js +19 -1
- package/esm/src/discovery/discovery-utils.d.ts +0 -4
- package/esm/src/discovery/discovery-utils.d.ts.map +1 -1
- package/esm/src/discovery/discovery-utils.js +0 -6
- package/esm/src/discovery/file-discovery.d.ts +3 -1
- package/esm/src/discovery/file-discovery.d.ts.map +1 -1
- package/esm/src/discovery/file-discovery.js +3 -8
- package/esm/src/discovery/handlers/index.d.ts +1 -0
- package/esm/src/discovery/handlers/index.d.ts.map +1 -1
- package/esm/src/discovery/handlers/index.js +1 -0
- package/esm/src/discovery/handlers/skill-handler.d.ts +26 -0
- package/esm/src/discovery/handlers/skill-handler.d.ts.map +1 -0
- package/esm/src/discovery/handlers/skill-handler.js +87 -0
- package/esm/src/discovery/handlers/task-handler.d.ts.map +1 -1
- package/esm/src/discovery/handlers/task-handler.js +1 -5
- package/esm/src/discovery/transpiler.d.ts.map +1 -1
- package/esm/src/discovery/transpiler.js +7 -4
- package/esm/src/discovery/types.d.ts +3 -0
- package/esm/src/discovery/types.d.ts.map +1 -1
- package/esm/src/embedding/resolve.d.ts.map +1 -1
- package/esm/src/embedding/resolve.js +1 -3
- package/esm/src/embedding/upload-handler.d.ts.map +1 -1
- package/esm/src/embedding/upload-handler.js +4 -3
- package/esm/src/embedding/upload-store.js +4 -4
- package/esm/src/errors/error-registry.d.ts +2 -1
- package/esm/src/errors/error-registry.d.ts.map +1 -1
- package/esm/src/errors/http-error.d.ts +1 -2
- package/esm/src/errors/http-error.d.ts.map +1 -1
- package/esm/src/errors/http-error.js +2 -2
- package/esm/src/errors/middleware/cli-error-boundary.d.ts.map +1 -1
- package/esm/src/errors/middleware/cli-error-boundary.js +13 -2
- package/esm/src/errors/middleware/wrap-unknown.d.ts +0 -7
- package/esm/src/errors/middleware/wrap-unknown.d.ts.map +1 -1
- package/esm/src/errors/middleware/wrap-unknown.js +0 -9
- package/esm/src/errors/veryfront-error.d.ts +0 -3
- package/esm/src/errors/veryfront-error.d.ts.map +1 -1
- package/esm/src/errors/veryfront-error.js +0 -5
- package/esm/src/html/schemas/html.schema.d.ts +2 -2
- package/esm/src/html/styles-builder/css-hash-cache.d.ts.map +1 -1
- package/esm/src/html/styles-builder/css-hash-cache.js +1 -2
- package/esm/src/html/styles-builder/plugin-loader.d.ts.map +1 -1
- package/esm/src/html/styles-builder/plugin-loader.js +50 -21
- package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler-cache.js +12 -4
- package/esm/src/html/utils.d.ts +0 -7
- package/esm/src/html/utils.d.ts.map +1 -1
- package/esm/src/html/utils.js +0 -25
- package/esm/src/modules/component-registry/registry.d.ts +0 -3
- package/esm/src/modules/component-registry/registry.d.ts.map +1 -1
- package/esm/src/modules/component-registry/registry.js +0 -3
- package/esm/src/oauth/handlers/callback-handler.d.ts +2 -0
- package/esm/src/oauth/handlers/callback-handler.d.ts.map +1 -1
- package/esm/src/oauth/handlers/callback-handler.js +10 -4
- package/esm/src/oauth/providers/common.d.ts.map +1 -1
- package/esm/src/oauth/providers/common.js +0 -23
- package/esm/src/observability/auto-instrument/react-instrumentation.js +2 -1
- package/esm/src/observability/instruments/instruments-factory.d.ts +1 -1
- package/esm/src/observability/instruments/instruments-factory.d.ts.map +1 -1
- package/esm/src/observability/instruments/instruments-factory.js +5 -4
- package/esm/src/observability/metrics/config.js +5 -4
- package/esm/src/observability/metrics/manager.js +1 -1
- package/esm/src/observability/tracing/span-operations.d.ts.map +1 -1
- package/esm/src/observability/tracing/span-operations.js +14 -8
- package/esm/src/platform/compat/console/node.d.ts +0 -1
- package/esm/src/platform/compat/console/node.d.ts.map +1 -1
- package/esm/src/platform/compat/console/node.js +4 -11
- package/esm/src/platform/compat/fs.d.ts.map +1 -1
- package/esm/src/platform/compat/fs.js +4 -2
- package/esm/src/platform/compat/opaque-deps.d.ts +4 -2
- package/esm/src/platform/compat/opaque-deps.d.ts.map +1 -1
- package/esm/src/platform/compat/opaque-deps.js +1 -1
- package/esm/src/platform/compat/path/basic-operations.d.ts.map +1 -1
- package/esm/src/platform/compat/path/basic-operations.js +8 -7
- package/esm/src/platform/compat/path/resolution.d.ts.map +1 -1
- package/esm/src/platform/compat/path/resolution.js +15 -10
- package/esm/src/platform/compat/path/url-conversion.d.ts.map +1 -1
- package/esm/src/platform/compat/path/url-conversion.js +3 -2
- package/esm/src/platform/compat/process.d.ts +2 -14
- package/esm/src/platform/compat/process.d.ts.map +1 -1
- package/esm/src/platform/compat/process.js +92 -70
- package/esm/src/provider/index.d.ts +1 -1
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -1
- package/esm/src/provider/local/ai-sdk-adapter.d.ts.map +1 -1
- package/esm/src/provider/local/ai-sdk-adapter.js +18 -18
- package/esm/src/provider/local/env.d.ts.map +1 -1
- package/esm/src/provider/local/env.js +2 -1
- package/esm/src/provider/model-registry.d.ts +10 -0
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +43 -0
- package/esm/src/proxy/retry.d.ts +3 -3
- package/esm/src/proxy/retry.d.ts.map +1 -1
- package/esm/src/proxy/retry.js +0 -7
- package/esm/src/proxy/tracing.d.ts +1 -5
- package/esm/src/proxy/tracing.d.ts.map +1 -1
- package/esm/src/proxy/tracing.js +1 -7
- package/esm/src/react/components/ai/chat/components/code-block.js +1 -1
- package/esm/src/react/components/ai/chat/components/skill-badge.d.ts +12 -0
- package/esm/src/react/components/ai/chat/components/skill-badge.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/skill-badge.js +34 -0
- package/esm/src/react/components/ai/chat/components/step-indicator.js +4 -4
- package/esm/src/react/components/ai/chat/composition/chat-message-list.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/composition/chat-message-list.js +8 -2
- package/esm/src/react/components/ai/chat/composition/message.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/composition/message.js +8 -2
- package/esm/src/react/components/ai/chat/index.d.ts +4 -1
- package/esm/src/react/components/ai/chat/index.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/index.js +4 -3
- package/esm/src/react/components/ai/chat/utils/message-parts.d.ts +2 -0
- package/esm/src/react/components/ai/chat/utils/message-parts.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/utils/message-parts.js +9 -0
- package/esm/src/react/components/ai/chat-with-sidebar.d.ts +1 -0
- package/esm/src/react/components/ai/chat-with-sidebar.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat-with-sidebar.js +1 -0
- package/esm/src/react/components/ai/chat.d.ts +1 -1
- package/esm/src/react/components/ai/chat.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat.js +1 -1
- package/esm/src/rendering/cache/index.d.ts +1 -5
- package/esm/src/rendering/cache/index.d.ts.map +1 -1
- package/esm/src/rendering/cache/index.js +1 -5
- package/esm/src/rendering/orchestrator/pipeline.d.ts +11 -2
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/shared/context-aware-cache.d.ts.map +1 -1
- package/esm/src/rendering/shared/context-aware-cache.js +0 -4
- package/esm/src/repositories/schemas/index.d.ts +1 -1
- package/esm/src/repositories/schemas/index.d.ts.map +1 -1
- package/esm/src/repositories/schemas/index.js +1 -1
- package/esm/src/repositories/schemas/repository.schema.d.ts +0 -11
- package/esm/src/repositories/schemas/repository.schema.d.ts.map +1 -1
- package/esm/src/repositories/schemas/repository.schema.js +0 -13
- package/esm/src/repositories/types.d.ts +1 -1
- package/esm/src/repositories/types.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader.js +63 -7
- package/esm/src/routing/api/openapi/path-utils.d.ts +0 -19
- package/esm/src/routing/api/openapi/path-utils.d.ts.map +1 -1
- package/esm/src/routing/api/openapi/path-utils.js +0 -34
- package/esm/src/routing/api/openapi/spec-generator.d.ts.map +1 -1
- package/esm/src/routing/api/openapi/spec-generator.js +1 -19
- package/esm/src/routing/api/openapi/types.d.ts +1 -0
- package/esm/src/routing/api/openapi/types.d.ts.map +1 -1
- package/esm/src/routing/api/openapi/types.js +18 -0
- package/esm/src/sandbox/sandbox.d.ts +1 -0
- package/esm/src/sandbox/sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.js +7 -8
- package/esm/src/security/http/cors/constants.d.ts +0 -2
- package/esm/src/security/http/cors/constants.d.ts.map +1 -1
- package/esm/src/security/http/cors/constants.js +0 -2
- package/esm/src/security/http/response/index.d.ts +3 -4
- package/esm/src/security/http/response/index.d.ts.map +1 -1
- package/esm/src/security/http/response/index.js +2 -3
- package/esm/src/server/context/enriched-context.d.ts +0 -8
- package/esm/src/server/context/enriched-context.d.ts.map +1 -1
- package/esm/src/server/context/enriched-context.js +1 -12
- package/esm/src/server/dev-server/server.d.ts.map +1 -1
- package/esm/src/server/dev-server/server.js +11 -4
- package/esm/src/server/dev-ui/manifest.d.ts +20 -20
- package/esm/src/server/dev-ui/manifest.js +20 -20
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +426 -179
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +14 -24
- package/esm/src/server/shared/renderer/memory/pressure.d.ts +0 -7
- package/esm/src/server/shared/renderer/memory/pressure.d.ts.map +1 -1
- package/esm/src/server/shared/renderer/memory/pressure.js +1 -13
- package/esm/src/server/utils/domain-lookup.d.ts +0 -4
- package/esm/src/server/utils/domain-lookup.d.ts.map +1 -1
- package/esm/src/server/utils/domain-lookup.js +0 -3
- package/esm/src/skill/allowed-tools.d.ts +54 -0
- package/esm/src/skill/allowed-tools.d.ts.map +1 -0
- package/esm/src/skill/allowed-tools.js +87 -0
- package/esm/src/skill/executor.d.ts +28 -0
- package/esm/src/skill/executor.d.ts.map +1 -0
- package/esm/src/skill/executor.js +187 -0
- package/esm/src/skill/index.d.ts +19 -0
- package/esm/src/skill/index.d.ts.map +1 -0
- package/esm/src/skill/index.js +24 -0
- package/esm/src/skill/parser.d.ts +30 -0
- package/esm/src/skill/parser.d.ts.map +1 -0
- package/esm/src/skill/parser.js +162 -0
- package/esm/src/skill/path-safety.d.ts +22 -0
- package/esm/src/skill/path-safety.d.ts.map +1 -0
- package/esm/src/skill/path-safety.js +156 -0
- package/esm/src/skill/prompt-augmentation.d.ts +19 -0
- package/esm/src/skill/prompt-augmentation.d.ts.map +1 -0
- package/esm/src/skill/prompt-augmentation.js +36 -0
- package/esm/src/skill/registry.d.ts +25 -0
- package/esm/src/skill/registry.d.ts.map +1 -0
- package/esm/src/skill/registry.js +42 -0
- package/esm/src/skill/tools.d.ts +27 -0
- package/esm/src/skill/tools.d.ts.map +1 -0
- package/esm/src/skill/tools.js +149 -0
- package/esm/src/skill/types.d.ts +85 -0
- package/esm/src/skill/types.d.ts.map +1 -0
- package/esm/src/skill/types.js +27 -0
- package/esm/src/studio/bridge/bridge-bundle.generated.d.ts +1 -1
- package/esm/src/studio/bridge/bridge-bundle.generated.d.ts.map +1 -1
- package/esm/src/studio/bridge/bridge-bundle.generated.js +1 -1
- package/esm/src/studio/element-selector-injector.d.ts +0 -2
- package/esm/src/studio/element-selector-injector.d.ts.map +1 -1
- package/esm/src/task/runner.d.ts +6 -0
- package/esm/src/task/runner.d.ts.map +1 -1
- package/esm/src/task/runner.js +8 -8
- package/esm/src/tool/factory.js +31 -39
- package/esm/src/transforms/esm/http-cache-helpers.d.ts +0 -8
- package/esm/src/transforms/esm/http-cache-helpers.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache-helpers.js +0 -20
- package/esm/src/transforms/esm/path-resolver.d.ts +0 -14
- package/esm/src/transforms/esm/path-resolver.d.ts.map +1 -1
- package/esm/src/transforms/esm/path-resolver.js +1 -92
- package/esm/src/transforms/esm/source-url-embed.d.ts +0 -14
- package/esm/src/transforms/esm/source-url-embed.d.ts.map +1 -1
- package/esm/src/transforms/esm/source-url-embed.js +0 -47
- package/esm/src/transforms/esm/transform-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/transform-cache.js +2 -6
- package/esm/src/transforms/mdx/index.d.ts +0 -1
- package/esm/src/transforms/mdx/index.d.ts.map +1 -1
- package/esm/src/transforms/mdx/index.js +0 -4
- package/esm/src/transforms/pipeline/context.d.ts +0 -1
- package/esm/src/transforms/pipeline/context.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/context.js +0 -1
- package/esm/src/transforms/pipeline/index.d.ts +1 -2
- package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/index.js +0 -3
- package/esm/src/transforms/pipeline/stages/ssr-http-cache.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-http-cache.js +0 -1
- package/esm/src/types/entities/getEntityInfo.js +1 -1
- package/esm/src/types/index.d.ts +2 -13
- package/esm/src/types/index.d.ts.map +1 -1
- package/esm/src/types/server.d.ts +2 -1
- package/esm/src/types/server.d.ts.map +1 -1
- package/esm/src/utils/cache-file-ops.d.ts +1 -1
- package/esm/src/utils/cache-file-ops.d.ts.map +1 -1
- package/esm/src/utils/cache-file-ops.js +5 -6
- package/esm/src/utils/lru-wrapper.d.ts.map +1 -1
- package/esm/src/utils/lru-wrapper.js +2 -4
- package/esm/src/workflow/claude-code/event-publisher.d.ts +0 -4
- package/esm/src/workflow/claude-code/event-publisher.d.ts.map +1 -1
- package/esm/src/workflow/claude-code/event-publisher.js +2 -6
- package/esm/src/workflow/executor/workflow-executor.d.ts.map +1 -1
- package/esm/src/workflow/executor/workflow-executor.js +6 -1
- package/esm/src/workflow/react/use-workflow-list.d.ts.map +1 -1
- package/esm/src/workflow/react/use-workflow-list.js +2 -1
- package/esm/src/workflow/schemas/workflow.schema.d.ts +8 -8
- package/package.json +1 -2
- package/src/cli/app/components/inline-input.ts +0 -5
- package/src/cli/app/components/list-select.ts +0 -21
- package/src/cli/app/operations/project-creation.ts +4 -109
- package/src/cli/app/shell.ts +1 -1
- package/src/cli/app/utils.ts +0 -22
- package/src/cli/app/views/dashboard.ts +0 -17
- package/src/cli/app/views/startup.ts +0 -13
- package/src/cli/auth/login.ts +1 -2
- package/src/cli/auth/token-store.ts +1 -1
- package/src/cli/auth/utils.ts +2 -1
- package/src/cli/commands/generate/integration-generator.ts +1 -1
- package/src/cli/help/formatters.ts +11 -4
- package/src/cli/help/main-help.ts +2 -3
- package/src/cli/mcp/server.ts +2 -5
- package/src/cli/mcp/tools/dev-tools.ts +1 -2
- package/src/cli/mcp/tools.ts +8 -2
- package/src/cli/shared/reserve-slug.ts +1 -4
- package/src/cli/sync/ignore.ts +26 -21
- package/src/cli/ui/colors.ts +0 -12
- package/src/cli/ui/dot-matrix.ts +3 -6
- package/src/cli/ui/tui.ts +3 -3
- package/src/cli/utils/index.ts +1 -1
- package/src/cli/utils/package-manager.ts +3 -4
- package/src/deno.js +22 -19
- package/src/src/agent/chat-handler.ts +8 -23
- package/src/src/agent/factory.ts +58 -9
- package/src/src/agent/index.ts +16 -1
- package/src/src/agent/memory/memory.ts +0 -9
- package/src/src/agent/react/use-chat/types.ts +2 -0
- package/src/src/agent/react/use-chat/use-chat.ts +15 -9
- package/src/src/agent/runtime/index.ts +213 -35
- package/src/src/agent/runtime/tool-helpers.ts +9 -0
- package/src/src/agent/types.ts +10 -0
- package/src/src/ai/registry-manager.ts +2 -1
- package/src/src/build/bundler/code-splitter/manifest-builder.ts +1 -1
- package/src/src/build/compiler/mdx-compiler/validator.ts +3 -7
- package/src/src/build/compiler/mdx-to-js.ts +0 -101
- package/src/src/build/index.ts +0 -8
- package/src/src/build/production-build/build/route-collector.ts +2 -4
- package/src/src/build/production-build/templates.ts +9 -18
- package/src/src/build/vendor-cache.ts +0 -6
- package/src/src/cache/tokenizing-gateway.ts +1 -9
- package/src/src/chat/index.ts +3 -0
- package/src/src/config/define-config.ts +30 -29
- package/src/src/config/loader.ts +10 -9
- package/src/src/config/schemas/config.schema.ts +12 -0
- package/src/src/data/data-fetcher.ts +15 -21
- package/src/src/data/server-data-fetcher.ts +1 -8
- package/src/src/data/static-data-fetcher.ts +1 -6
- package/src/src/discovery/discovery-engine.ts +27 -0
- package/src/src/discovery/discovery-utils.ts +0 -7
- package/src/src/discovery/file-discovery.ts +3 -9
- package/src/src/discovery/handlers/index.ts +1 -0
- package/src/src/discovery/handlers/skill-handler.ts +123 -0
- package/src/src/discovery/handlers/task-handler.ts +1 -5
- package/src/src/discovery/transpiler.ts +7 -4
- package/src/src/discovery/types.ts +3 -0
- package/src/src/embedding/resolve.ts +1 -3
- package/src/src/embedding/upload-handler.ts +7 -3
- package/src/src/embedding/upload-store.ts +4 -4
- package/src/src/errors/error-registry.ts +2 -2
- package/src/src/errors/http-error.ts +7 -3
- package/src/src/errors/middleware/cli-error-boundary.ts +28 -2
- package/src/src/errors/middleware/wrap-unknown.ts +0 -10
- package/src/src/errors/veryfront-error.ts +0 -9
- package/src/src/html/styles-builder/css-hash-cache.ts +5 -2
- package/src/src/html/styles-builder/plugin-loader.ts +58 -21
- package/src/src/html/styles-builder/tailwind-compiler-cache.ts +11 -4
- package/src/src/html/utils.ts +0 -33
- package/src/src/modules/component-registry/registry.ts +0 -3
- package/src/src/modules/server/websocket-handler.ts +1 -1
- package/src/src/oauth/handlers/callback-handler.ts +17 -5
- package/src/src/oauth/providers/base.ts +3 -3
- package/src/src/oauth/providers/common.ts +0 -23
- package/src/src/observability/auto-instrument/react-instrumentation.ts +2 -2
- package/src/src/observability/instruments/instruments-factory.ts +6 -6
- package/src/src/observability/metrics/config.ts +5 -5
- package/src/src/observability/metrics/manager.ts +1 -1
- package/src/src/observability/tracing/span-operations.ts +14 -9
- package/src/src/platform/compat/console/node.ts +4 -14
- package/src/src/platform/compat/fs.ts +14 -3
- package/src/src/platform/compat/opaque-deps.ts +10 -5
- package/src/src/platform/compat/path/basic-operations.ts +9 -7
- package/src/src/platform/compat/path/resolution.ts +15 -8
- package/src/src/platform/compat/path/url-conversion.ts +10 -6
- package/src/src/platform/compat/process.ts +133 -76
- package/src/src/provider/index.ts +1 -0
- package/src/src/provider/local/ai-sdk-adapter.ts +40 -37
- package/src/src/provider/local/env.ts +4 -1
- package/src/src/provider/model-registry.ts +47 -0
- package/src/src/proxy/retry.ts +0 -9
- package/src/src/proxy/tracing.ts +1 -9
- package/src/src/react/components/ai/chat/components/code-block.tsx +1 -1
- package/src/src/react/components/ai/chat/components/skill-badge.tsx +51 -0
- package/src/src/react/components/ai/chat/components/step-indicator.tsx +4 -4
- package/src/src/react/components/ai/chat/composition/chat-message-list.tsx +9 -2
- package/src/src/react/components/ai/chat/composition/message.tsx +9 -2
- package/src/src/react/components/ai/chat/index.tsx +6 -1
- package/src/src/react/components/ai/chat/utils/message-parts.ts +11 -0
- package/src/src/react/components/ai/chat-with-sidebar.tsx +2 -0
- package/src/src/react/components/ai/chat.tsx +3 -0
- package/src/src/rendering/cache/index.ts +12 -5
- package/src/src/rendering/orchestrator/pipeline.ts +12 -2
- package/src/src/rendering/renderer.ts +1 -1
- package/src/src/rendering/shared/context-aware-cache.ts +0 -5
- package/src/src/repositories/schemas/index.ts +0 -2
- package/src/src/repositories/schemas/repository.schema.ts +0 -15
- package/src/src/repositories/types.ts +1 -6
- package/src/src/routing/api/module-loader/loader.ts +88 -3
- package/src/src/routing/api/openapi/path-utils.ts +0 -39
- package/src/src/routing/api/openapi/spec-generator.ts +1 -20
- package/src/src/routing/api/openapi/types.ts +20 -0
- package/src/src/sandbox/sandbox.ts +8 -8
- package/src/src/security/http/cors/constants.ts +0 -4
- package/src/src/security/http/response/index.ts +3 -9
- package/src/src/server/context/enriched-context.ts +1 -19
- package/src/src/server/dev-server/server.ts +11 -4
- package/src/src/server/dev-ui/manifest.js +20 -20
- package/src/src/server/handlers/dev/framework-candidates.generated.ts +426 -179
- package/src/src/server/runtime-handler/index.ts +17 -28
- package/src/src/server/shared/renderer/memory/pressure.ts +2 -15
- package/src/src/server/utils/domain-lookup.ts +0 -4
- package/src/src/skill/allowed-tools.ts +107 -0
- package/src/src/skill/executor.ts +215 -0
- package/src/src/skill/index.ts +60 -0
- package/src/src/skill/parser.ts +214 -0
- package/src/src/skill/path-safety.ts +203 -0
- package/src/src/skill/prompt-augmentation.ts +48 -0
- package/src/src/skill/registry.ts +51 -0
- package/src/src/skill/tools.ts +197 -0
- package/src/src/skill/types.ts +107 -0
- package/src/src/studio/bridge/bridge-bundle.generated.ts +1 -1
- package/src/src/studio/element-selector-injector.ts +0 -2
- package/src/src/task/runner.ts +10 -8
- package/src/src/tool/factory.ts +54 -54
- package/src/src/transforms/esm/http-cache-helpers.ts +0 -20
- package/src/src/transforms/esm/path-resolver.ts +1 -140
- package/src/src/transforms/esm/source-url-embed.ts +0 -53
- package/src/src/transforms/esm/transform-cache.ts +3 -7
- package/src/src/transforms/mdx/index.ts +0 -5
- package/src/src/transforms/pipeline/context.ts +0 -2
- package/src/src/transforms/pipeline/index.ts +0 -4
- package/src/src/transforms/pipeline/stages/ssr-http-cache.ts +0 -1
- package/src/src/types/entities/getEntityInfo.ts +1 -1
- package/src/src/types/index.ts +1 -20
- package/src/src/types/server.ts +1 -1
- package/src/src/utils/cache-file-ops.ts +5 -5
- package/src/src/utils/lru-wrapper.ts +2 -8
- package/src/src/workflow/claude-code/event-publisher.ts +13 -4
- package/src/src/workflow/executor/workflow-executor.ts +7 -2
- package/src/src/workflow/react/use-workflow-list.ts +3 -2
- package/esm/src/transforms/mdx/parser.d.ts +0 -4
- package/esm/src/transforms/mdx/parser.d.ts.map +0 -1
- package/esm/src/transforms/mdx/parser.js +0 -49
- package/src/src/transforms/mdx/parser.ts +0 -65
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"version": 1,
|
|
3
3
|
"files": {
|
|
4
|
-
"
|
|
5
|
-
"projects/App.tsx": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { EmptyState } from \"./components/EmptyState.tsx\";\nimport { Header } from \"./components/Header.tsx\";\nimport { ProjectCard } from \"./components/ProjectCard.tsx\";\nimport { SearchInput } from \"./components/SearchInput.tsx\";\n\ninterface Project {\n id: string;\n name: string;\n slug: string;\n description?: string;\n updated_at?: string;\n}\n\ninterface Config {\n domain: string;\n port: string;\n hasToken: boolean;\n}\n\nexport function App(): React.JSX.Element {\n const [projects, setProjects] = useState<Project[]>([]);\n const [search, setSearch] = useState<string>(\"\");\n const [loading, setLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [config, setConfig] = useState<Config | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n fetch(\"/_projects/api/config\")\n .then((r) => r.json())\n .then(setConfig)\n .catch((e) => console.error(\"Failed to load config:\", e));\n }, []);\n\n const fetchProjects = useCallback(async (searchQuery: string): Promise<void> => {\n abortControllerRef.current?.abort();\n const controller = new AbortController();\n abortControllerRef.current = controller;\n\n setLoading(true);\n setError(null);\n\n try {\n const params = new URLSearchParams({\n sort_by: \"updated_at\",\n sort_order: \"desc\",\n limit: \"100\",\n });\n\n if (searchQuery) params.set(\"search\", searchQuery);\n\n const response = await fetch(`/_vf/api/projects?${params}`, {\n signal: controller.signal,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n setError(data.error ?? data.detail ?? \"Failed to load projects\");\n setProjects([]);\n return;\n }\n\n setProjects(data.data ?? []);\n } catch (e) {\n if (e instanceof Error && e.name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : \"Failed to load projects\");\n setProjects([]);\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n fetchProjects(\"\");\n }, [fetchProjects]);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n fetchProjects(search);\n }, 300);\n\n return () => clearTimeout(timer);\n }, [fetchProjects, search]);\n\n function getProjectUrl(slug: string): string {\n if (!config) return \"#\";\n const portSuffix = config.port ? `:${config.port}` : \"\";\n return `http://${slug}.${config.domain}${portSuffix}/`;\n }\n\n function renderContent(): React.JSX.Element {\n if (error) {\n return (\n <EmptyState\n title=\"Unable to load projects\"\n description={error}\n variant=\"error\"\n />\n );\n }\n\n if (loading && projects.length === 0) {\n return (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3, 4, 5, 6].map((i) => (\n <div\n key={i}\n className=\"bg-white rounded-xl p-5 border border-gray-200 animate-pulse\"\n >\n <div className=\"h-5 bg-gray-200 rounded w-2/3 mb-3\" />\n <div className=\"h-4 bg-gray-100 rounded w-1/2 mb-4\" />\n <div className=\"h-3 bg-gray-100 rounded w-1/3\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (projects.length === 0) {\n const title = search ? \"No projects found\" : \"No projects yet\";\n const description = search\n ? \"Try a different search term\"\n : \"Create a project to get started\";\n\n return <EmptyState title={title} description={description} showWorkspaceGuide={!search} />;\n }\n\n return (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {projects.map((project) => (\n <ProjectCard\n key={project.id}\n name={project.name}\n slug={project.slug}\n description={project.description}\n updatedAt={project.updated_at}\n href={getProjectUrl(project.slug)}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div className=\"min-h-screen\">\n <div className=\"max-w-6xl mx-auto px-5 py-10\">\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-10\">\n <Header />\n {config?.hasToken\n ? (\n <SearchInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search...\"\n loading={loading && search.length > 0}\n />\n )\n : null}\n </div>\n\n {renderContent()}\n </div>\n </div>\n );\n}\n",
|
|
6
|
-
"projects/components/ProjectCard.tsx": "interface ProjectCardProps {\n name: string;\n slug: string;\n description?: string;\n updatedAt?: string;\n href: string;\n}\n\nfunction formatRelativeTime(dateString?: string): string {\n if (!dateString) return \"\";\n\n const date = new Date(dateString);\n const diffSecs = Math.floor((Date.now() - date.getTime()) / 1000);\n\n if (diffSecs < 60) return \"just now\";\n\n const diffMins = Math.floor(diffSecs / 60);\n if (diffMins === 1) return \"1 minute ago\";\n if (diffMins < 60) return `${diffMins} minutes ago`;\n\n const diffHours = Math.floor(diffMins / 60);\n if (diffHours === 1) return \"1 hour ago\";\n if (diffHours < 24) return `${diffHours} hours ago`;\n\n const diffDays = Math.floor(diffHours / 24);\n if (diffDays === 1) return \"yesterday\";\n if (diffDays < 7) return `${diffDays} days ago`;\n if (diffDays < 14) return \"1 week ago\";\n if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;\n\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n}\n\nexport function ProjectCard({\n name,\n slug,\n description,\n updatedAt,\n href,\n}: ProjectCardProps): JSX.Element {\n const relativeTime = formatRelativeTime(updatedAt);\n\n return (\n <a\n href={href}\n className=\"block bg-vf-card rounded-xl p-5 border border-vf-border hover:border-[#ccc] transition-colors\"\n >\n <h3 className=\"text-lg font-semibold text-vf-text leading-tight\">{name}</h3>\n <p className=\"text-sm text-[#1a1a1a]/50 mt-1\">{slug}</p>\n {description && <p className=\"text-sm text-vf-muted mt-2 line-clamp-2\">{description}</p>}\n {relativeTime && <p className=\"text-xs text-[#999] mt-2\">Updated {relativeTime}</p>}\n </a>\n );\n}\n",
|
|
7
|
-
"projects/components/Header.tsx": "export function Header(): React.JSX.Element {\n return (\n <header>\n <a href=\"/\" className=\"inline-block text-[#1a1a1a] hover:text-[#333] transition-colors\">\n <svg\n viewBox=\"0 0 190 37\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-[26px] w-[138px] md:h-[30px] md:w-[159px] lg:h-9 lg:w-[190px]\"\n >\n <path\n d=\"M104.736 13.3105L108.983 24.168L113.384 13.3135L117.345 13.3154L110.176 30.4102C109.647 31.7303 109.075 32.831 108.458 33.7109C107.842 34.5907 107.159 35.2506 106.411 35.6904C105.663 36.1523 104.771 36.3832 103.736 36.3828C103.362 36.3826 102.912 36.3382 102.384 36.25C101.878 36.1838 101.382 36.0517 100.898 35.8535L101.361 32.4209C101.735 32.6411 102.099 32.7947 102.451 32.8828C102.825 32.9709 103.133 33.0155 103.375 33.0156C103.925 33.0158 104.399 32.8728 104.795 32.5869C105.213 32.301 105.576 31.8828 105.884 31.333C106.214 30.8051 106.533 30.1781 106.842 29.4521L107.162 28.6611L100.775 13.3086L104.736 13.3105ZM16.9912 23.8398L14.7559 35.8057L14.3652 35.7285C13.5662 35.5713 12.7841 35.3611 12.0264 35.1025L11.6963 34.9902L13.583 25.8643L13.2197 25.6543L6.26562 31.8623L6.12109 31.7363L6.00391 31.6357C5.3913 31.1039 4.81391 30.5314 4.27734 29.9229L4.01172 29.6221L13.2656 21.6855L16.9912 23.8398ZM31.7686 29.5967L31.6221 29.7627L31.5039 29.8965C30.971 30.5031 30.3984 31.0746 29.79 31.6055L29.6729 31.707L29.5283 31.834L22.5967 25.6514L22.2324 25.8613L24.1191 34.9688L23.791 35.083C23.0309 35.3452 22.2474 35.5586 21.4453 35.7188L21.0537 35.7969L18.8525 23.9893L18.8252 23.8369L22.5498 21.6826L31.7686 29.5967ZM150.34 12.998C151.902 12.9987 153.31 13.3734 154.564 14.1221C155.84 14.8487 156.842 15.8504 157.567 17.127C158.315 18.4034 158.688 19.8338 158.688 21.418C158.687 22.6063 158.467 23.7179 158.026 24.752C157.608 25.7639 157.014 26.655 156.243 27.4248C155.495 28.1726 154.614 28.7551 153.602 29.1729C152.589 29.5906 151.499 29.7993 150.333 29.7988C148.771 29.7982 147.352 29.4345 146.076 28.708C144.8 27.9593 143.788 26.9465 143.04 25.6699C142.314 24.3935 141.952 22.9742 141.952 21.4121C141.953 20.2239 142.163 19.1234 142.581 18.1113C143.022 17.0773 143.616 16.1751 144.364 15.4053C145.135 14.6354 146.027 14.0417 147.039 13.624C148.051 13.2063 149.152 12.9976 150.34 12.998ZM79.3574 12.9697C80.8098 12.9703 82.1303 13.3237 83.3184 14.0283C84.5062 14.7109 85.4526 15.6465 86.1562 16.835C86.8598 18.0235 87.2115 19.3661 87.2109 20.8623C87.2109 21.0163 87.1989 21.2365 87.1768 21.5225C87.1766 21.8083 87.1656 22.0501 87.1436 22.248L74.5146 22.2432C74.5965 22.9402 74.7892 23.5787 75.0947 24.1582C75.5125 24.9285 76.0846 25.5342 76.8105 25.9746C77.5586 26.415 78.4175 26.6354 79.3857 26.6357C80.0897 26.636 80.7165 26.5366 81.2666 26.3389C81.8388 26.119 82.3011 25.8222 82.6533 25.4482C83.0274 25.0744 83.2696 24.6348 83.3799 24.1289L87.0107 24.1299C86.8343 25.274 86.3939 26.2749 85.6895 27.1328C84.985 27.9687 84.094 28.6184 83.0156 29.0801C81.9372 29.5418 80.7373 29.772 79.417 29.7715C77.8546 29.7709 76.4352 29.3961 75.1592 28.6475C73.9053 27.8988 72.9049 26.8859 72.1572 25.6094C71.4096 24.3329 71.0356 22.9026 71.0361 21.3184C71.0366 20.1301 71.2465 19.0297 71.665 18.0176C72.1055 17.0056 72.6999 16.1258 73.4482 15.3779C74.2187 14.608 75.0994 14.0134 76.0898 13.5957C77.1021 13.1781 78.1913 12.9693 79.3574 12.9697ZM185.777 9.01758L185.775 13.3418L190 13.3438L189.999 16.8418L185.773 16.8398L185.771 23.3428C185.771 24.377 186.057 25.1143 186.629 25.5547C187.201 25.995 187.927 26.2154 188.807 26.2158C189.005 26.2159 189.203 26.2055 189.401 26.1836C189.621 26.1617 189.819 26.1289 189.995 26.085L189.994 29.4844C189.73 29.5503 189.411 29.5943 189.037 29.6162C188.663 29.6601 188.322 29.6817 188.014 29.6816C186.825 29.6812 185.78 29.4498 184.878 28.9873C183.998 28.5028 183.305 27.7985 182.799 26.874C182.315 25.9277 182.074 24.7723 182.074 23.4082L182.076 16.8389L179.007 16.8379L179.009 13.3389L182.078 13.3398L182.08 9.0166L185.777 9.01758ZM170.416 13.0059C171.802 13.0064 172.99 13.3039 173.98 13.8984C174.993 14.471 175.762 15.3077 176.29 16.4082C176.84 17.4866 177.115 18.7742 177.114 20.2705L177.11 29.4795L173.413 29.4775L173.417 20.7305C173.417 19.4543 173.055 18.4418 172.329 17.6934C171.625 16.9229 170.69 16.5376 169.523 16.5371C168.313 16.5367 167.345 16.9215 166.618 17.6914C165.937 18.3925 165.575 19.3258 165.532 20.4912L165.528 20.7275L165.524 29.4746L161.86 29.4736L161.867 13.332L165.531 13.334L165.529 15.8486C165.987 15.06 166.559 14.4311 167.247 13.9619C168.15 13.3241 169.206 13.0054 170.416 13.0059ZM139.62 12.9941C139.862 12.9943 140.104 13.0155 140.346 13.0596C140.61 13.1037 140.874 13.1483 141.138 13.1924L141.137 16.5596C140.873 16.4935 140.598 16.4377 140.312 16.3936C140.048 16.3495 139.784 16.3273 139.52 16.3271C138.75 16.3268 138.034 16.5028 137.374 16.8545C136.736 17.2063 136.218 17.7233 135.821 18.4053C135.447 19.0652 135.26 19.8795 135.26 20.8477L135.256 29.4629L131.592 29.4619L131.599 13.3203L135.263 13.3223L135.262 15.5664C135.724 14.7965 136.33 14.1805 137.078 13.7188C137.826 13.2351 138.674 12.9938 139.62 12.9941ZM127.037 5.66113C127.345 5.66125 127.686 5.67219 128.061 5.69434C128.435 5.7165 128.754 5.77127 129.018 5.85938L129.017 9.25977C128.841 9.21569 128.642 9.18225 128.422 9.16016C128.224 9.1381 128.037 9.12704 127.861 9.12695C126.959 9.1266 126.222 9.34641 125.649 9.78613C125.077 10.226 124.79 10.9638 124.79 11.998L124.789 13.3174L129.015 13.3193L129.014 16.8184L124.788 16.8164L124.783 29.459L121.086 29.458L121.091 16.8154L118.021 16.8145L118.022 13.3154L121.092 13.3164L121.093 11.9297C121.093 10.5658 121.347 9.4219 121.854 8.49805C122.36 7.5521 123.053 6.84752 123.934 6.38574C124.814 5.90196 125.849 5.66067 127.037 5.66113ZM98.3594 12.9775C98.6013 12.9776 98.8431 12.9999 99.085 13.0439C99.349 13.0881 99.6139 13.1317 99.8779 13.1758L99.876 16.543C99.612 16.4769 99.3367 16.422 99.0508 16.3779C98.7868 16.3338 98.5228 16.3116 98.2588 16.3115C97.4887 16.3112 96.7735 16.4871 96.1133 16.8389C95.475 17.1907 94.9579 17.7076 94.5615 18.3896C94.1872 19.0497 93.9994 19.8638 93.999 20.832L93.9961 29.4473L90.332 29.4453L90.3379 13.3047L94.002 13.3057L94.001 15.5508C94.4634 14.7808 95.0691 14.1649 95.8174 13.7031C96.5658 13.2193 97.4131 12.9772 98.3594 12.9775ZM55.7764 6.32617L61.9756 23.2617L68.1875 6.33105L72.248 6.33301L63.5576 29.4355L60.3555 29.4336L51.6504 6.32422L55.7764 6.32617ZM150.339 16.4971C149.437 16.4967 148.622 16.7164 147.896 17.1562C147.169 17.596 146.586 18.1896 146.146 18.9375C145.727 19.6635 145.518 20.4889 145.518 21.4131C145.517 22.3372 145.726 23.1624 146.144 23.8887C146.583 24.615 147.167 25.1983 147.893 25.6387C148.619 26.0571 149.433 26.2672 150.335 26.2676C151.237 26.2679 152.04 26.0583 152.744 25.6406C153.47 25.2008 154.044 24.6185 154.462 23.8926C154.902 23.1446 155.123 22.3192 155.123 21.417C155.123 20.493 154.904 19.6677 154.464 18.9414C154.046 18.1932 153.474 17.5986 152.748 17.1582C152.044 16.7179 151.241 16.4975 150.339 16.4971ZM12.3467 15.7891V20.0967L0.837891 24.1553L0.708984 23.7773C0.450188 23.0188 0.239646 22.2366 0.0820312 21.4365L0.0527344 21.2861L0.015625 21.0967L8.89062 18.1543V17.7324L0.000976562 14.791L0.0654297 14.4512C0.219983 13.6463 0.428694 12.8601 0.685547 12.0967L0.8125 11.7158L12.3467 15.7891ZM35.0801 12.1123C35.335 12.8723 35.5407 13.6559 35.6943 14.457L35.7236 14.6074L35.7598 14.7969L26.9189 17.7256V18.1484L35.749 21.0693L35.6826 21.4102C35.5255 22.2141 35.3158 22.9997 35.0566 23.7617L34.9277 24.1387L23.4639 20.0908V15.7832L34.9531 11.7324L35.0801 12.1123ZM79.2246 16.0732C78.5204 16.073 77.8706 16.1937 77.2764 16.4355C76.7043 16.6774 76.2092 17.0294 75.791 17.4912C75.3728 17.9311 75.0421 18.4918 74.7998 19.1738C74.7571 19.3019 74.719 19.4338 74.6846 19.5693L83.3164 19.5732C83.2726 18.9131 83.0521 18.3193 82.6562 17.791C82.2825 17.2409 81.7989 16.8224 81.2051 16.5361C80.6111 16.2279 79.9507 16.0736 79.2246 16.0732ZM16.9854 12.043L13.2607 14.1973L3.97559 6.22559L4.12012 6.05957L4.2373 5.92578C4.76963 5.31808 5.34322 4.74671 5.95117 4.21484L6.21289 3.98535L13.2139 10.2295L13.5771 10.0186L11.6729 0.822266L12.002 0.709961C12.7629 0.449081 13.5469 0.235752 14.3496 0.0771484L14.5234 0.0429688L14.7402 0L16.9854 12.043ZM21.46 0.0878906C22.2588 0.24807 23.0397 0.460164 23.7969 0.72168L24.124 0.834961L22.2266 10.0156L22.5908 10.2266L29.5654 4L29.8271 4.22949C30.4368 4.76433 31.0106 5.33998 31.5439 5.95117L31.8057 6.25195L22.5449 14.1943L18.8193 12.04L21.0684 0.00878906L21.46 0.0878906Z\"\n fill=\"currentColor\"\n />\n </svg>\n </a>\n </header>\n );\n}\n",
|
|
8
|
-
"projects/components/EmptyState.tsx": "interface EmptyStateProps {\n title: string;\n description: string;\n variant?: \"default\" | \"error\";\n showWorkspaceGuide?: boolean;\n}\n\nfunction WorkspaceGuide(): JSX.Element {\n return (\n <div className=\"mt-8 max-w-md mx-auto text-left\">\n <p className=\"text-sm font-medium text-gray-500 mb-3\">Get started</p>\n <div className=\"bg-white rounded-lg border border-gray-200 p-4\">\n <p className=\"text-sm text-gray-600 mb-3\">\n Create a project, or place projects in a{\" \"}\n <code className=\"text-xs bg-gray-100 px-1.5 py-0.5 rounded font-mono\">projects/</code>\n {\" \"}\n folder:\n </p>\n <pre className=\"text-xs font-mono text-gray-500 leading-relaxed bg-gray-50 rounded p-3\">{\n`my-workspace/\n projects/\n site-a/\n app/ ← detected\n site-b/\n app/ ← detected`\n }</pre>\n <div className=\"mt-3 pt-3 border-t border-gray-100\">\n <p className=\"text-xs text-gray-400\">\n Or run{\" \"}\n <code className=\"bg-gray-100 px-1.5 py-0.5 rounded font-mono\">\n veryfront init my-app\n </code>{\" \"}\n to scaffold a new project.\n </p>\n </div>\n </div>\n </div>\n );\n}\n\nexport function EmptyState({\n title,\n description,\n variant = \"default\",\n showWorkspaceGuide = false,\n}: EmptyStateProps): JSX.Element {\n const titleClassName = variant === \"error\" ? \"text-amber-600\" : \"text-gray-600\";\n const showGuide = variant === \"default\" && showWorkspaceGuide;\n\n return (\n <div className=\"text-center py-16 px-6\">\n <p className={`text-lg mb-2 ${titleClassName}`}>{title}</p>\n <p className=\"text-sm text-gray-400\">{description}</p>\n {showGuide && <WorkspaceGuide />}\n </div>\n );\n}\n",
|
|
9
|
-
"projects/components/SearchInput.tsx": "interface SearchInputProps {\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n loading?: boolean;\n}\n\nexport function SearchInput({\n value,\n onChange,\n placeholder,\n loading,\n}: SearchInputProps): JSX.Element {\n const showClear = value.length > 0;\n const showLoading = !!loading && !showClear;\n\n return (\n <div className=\"relative group\">\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg\n className=\"w-4 h-4 text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n strokeWidth={2}\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\"\n />\n </svg>\n </div>\n\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n className=\"w-full sm:w-80 pl-9 pr-9 py-2.5 text-sm bg-white border border-gray-200 rounded-lg outline-none focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 placeholder:text-gray-400\"\n />\n\n {showClear\n ? (\n <button\n type=\"button\"\n onClick={() => onChange(\"\")}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\"\n >\n <svg\n className=\"w-4 h-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n strokeWidth={2}\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n )\n : showLoading\n ? (\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <svg\n className=\"w-4 h-4 text-gray-400 animate-spin\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n </div>\n )\n : null}\n </div>\n );\n}\n",
|
|
10
|
-
"shared/mount-react-app.tsx": "import type { ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nexport function mountReactApp(app: ReactNode): void {\n const root = document.getElementById(\"root\");\n\n if (!root) {\n throw new Error('Root element with id \"root\" not found');\n }\n\n createRoot(root).render(app);\n}\n",
|
|
11
|
-
"dashboard/index.tsx": "import { App } from \"./App.tsx\";\nimport { mountReactApp } from \"../shared/mount-react-app.tsx\";\n\nmountReactApp(<App />);\n",
|
|
12
|
-
"dashboard/App.tsx": "import { useEffect, useState } from \"react\";\nimport { Header } from \"./components/Header.tsx\";\nimport { TabNav } from \"./components/TabNav.tsx\";\nimport { AITab } from \"./components/AITab.tsx\";\nimport { ServerTab } from \"./components/ServerTab.tsx\";\nimport { RuntimeTab } from \"./components/RuntimeTab.tsx\";\nimport { FilesTab } from \"./components/FilesTab.tsx\";\nimport { ErrorsTab } from \"./components/ErrorsTab.tsx\";\nimport { ConfigTab } from \"./components/ConfigTab.tsx\";\nimport { APITab } from \"./components/APITab.tsx\";\n\nexport interface Tool {\n id: string;\n type: string;\n description: string;\n schema: { properties?: Record<string, unknown>; required?: string[] } | null;\n mcp: { enabled: boolean };\n}\n\nexport interface Resource {\n id: string;\n pattern: string;\n description: string;\n mcp: { enabled: boolean };\n}\n\nexport interface Prompt {\n id: string;\n description: string;\n}\n\nexport interface Agent {\n id: string;\n description: string;\n model: string;\n system: string | null;\n tools: Record<string, boolean>;\n memory: { type: string; maxTokens: number } | null;\n streaming: boolean;\n maxSteps: number | null;\n}\n\nexport interface FileItem {\n name: string;\n type: \"file\" | \"directory\";\n path: string;\n}\n\nexport interface Handler {\n name: string;\n priority: number;\n patterns: Array<{ pattern: string; exact?: boolean; prefix?: boolean; method?: string }>;\n enabled: string;\n}\n\nexport type TabId = \"ai\" | \"server\" | \"runtime\" | \"files\" | \"errors\" | \"config\" | \"api\";\n\nconst TABS: Array<{ id: TabId; label: string }> = [\n { id: \"ai\", label: \"AI\" },\n { id: \"server\", label: \"Server\" },\n { id: \"runtime\", label: \"Runtime\" },\n { id: \"files\", label: \"Files\" },\n { id: \"errors\", label: \"Errors\" },\n { id: \"config\", label: \"Config\" },\n { id: \"api\", label: \"API\" },\n];\n\nasync function fetchJson(url: string): Promise<unknown> {\n const res = await fetch(url);\n return res.json();\n}\n\nexport function App(): JSX.Element {\n const [currentTab, setCurrentTab] = useState<TabId>(\"ai\");\n const [tools, setTools] = useState<Tool[]>([]);\n const [resources, setResources] = useState<Resource[]>([]);\n const [prompts, setPrompts] = useState<Prompt[]>([]);\n const [agents, setAgents] = useState<Agent[]>([]);\n\n useEffect((): void => {\n async function fetchData(): Promise<void> {\n try {\n const [t, r, p, a] = await Promise.all([\n fetchJson(\"/_dev/api/tools\"),\n fetchJson(\"/_dev/api/resources\"),\n fetchJson(\"/_dev/api/prompts\"),\n fetchJson(\"/_dev/api/agents\"),\n ]);\n\n setTools((t as any)?.tools ?? []);\n setResources((r as any)?.resources ?? []);\n setPrompts((p as any)?.prompts ?? []);\n setAgents((a as any)?.agents ?? []);\n } catch (e) {\n console.error(\"Failed to fetch data:\", e);\n }\n }\n\n void fetchData();\n }, []);\n\n return (\n <div className=\"min-h-screen bg-gray-50\">\n <Header />\n <TabNav tabs={TABS} currentTab={currentTab} onTabChange={setCurrentTab} />\n\n <div className=\"tab-content\">\n {currentTab === \"ai\" && (\n <AITab tools={tools} resources={resources} prompts={prompts} agents={agents} />\n )}\n {currentTab === \"server\" && <ServerTab />}\n {currentTab === \"runtime\" && <RuntimeTab />}\n {currentTab === \"files\" && <FilesTab />}\n {currentTab === \"errors\" && <ErrorsTab />}\n {currentTab === \"config\" && <ConfigTab />}\n {currentTab === \"api\" && <APITab />}\n </div>\n </div>\n );\n}\n",
|
|
13
|
-
"dashboard/components/MCPTab.tsx": "import { useEffect, useState } from \"react\";\nimport type { Prompt, Resource, Tool } from \"../App.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { ActionButton, DetailHeader, EmptyState, ResultBox, TwoColumnLayout } from \"./shared.tsx\";\n\ntype SubTab = \"tools\" | \"resources\" | \"prompts\";\n\ninterface MCPTabProps {\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n}\n\nfunction getItemId(item: Tool | Resource | Prompt): string {\n return \"pattern\" in item ? item.pattern : item.id;\n}\n\nfunction isTool(item: Tool | Resource | Prompt): item is Tool {\n return \"schema\" in item;\n}\n\nfunction isResource(item: Tool | Resource | Prompt): item is Resource {\n return \"pattern\" in item;\n}\n\nfunction isPrompt(item: Tool | Resource | Prompt): item is Prompt {\n return \"id\" in item && !(\"schema\" in item);\n}\n\nexport function MCPTab({ tools, resources, prompts }: MCPTabProps): JSX.Element {\n const [subTab, setSubTab] = useState<SubTab>(\"tools\");\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n\n useEffect(() => {\n function handleNavigate(e: CustomEvent<{ subTab: SubTab; itemId: string }>): void {\n setSubTab(e.detail.subTab);\n setSelectedId(e.detail.itemId);\n }\n\n const listener = handleNavigate as unknown as EventListener;\n globalThis.addEventListener(\"mcp-navigate\", listener);\n return () => globalThis.removeEventListener(\"mcp-navigate\", listener);\n }, []);\n\n let items: Array<Tool | Resource | Prompt>;\n if (subTab === \"tools\") items = tools;\n else if (subTab === \"resources\") items = resources;\n else items = prompts;\n\n const searchLower = search.toLowerCase();\n const filteredItems = items.filter((item) => getItemId(item).toLowerCase().includes(searchLower));\n const selectedItem = items.find((item) => getItemId(item) === selectedId);\n\n function renderDetail(): JSX.Element {\n if (!selectedItem) return <EmptyState message=\"Select an item to inspect\" />;\n\n if (subTab === \"tools\" && isTool(selectedItem)) return <ToolDetail tool={selectedItem} />;\n if (subTab === \"resources\" && isResource(selectedItem)) {\n return <ResourceDetail resource={selectedItem} />;\n }\n if (subTab === \"prompts\" && isPrompt(selectedItem)) {\n return <PromptDetail prompt={selectedItem} />;\n }\n\n return <EmptyState message=\"Select an item to inspect\" />;\n }\n\n const sidebar = (\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n subTabs={[\n { id: \"tools\", label: `Tools (${tools.length})` },\n { id: \"resources\", label: `Resources (${resources.length})` },\n { id: \"prompts\", label: `Prompts (${prompts.length})` },\n ]}\n currentSubTab={subTab}\n onSubTabChange={(id) => {\n setSubTab(id as SubTab);\n setSelectedId(null);\n }}\n items={filteredItems.map((item) => {\n const id = getItemId(item);\n return { id, label: id };\n })}\n selectedId={selectedId}\n onSelect={setSelectedId}\n emptyMessage={`No ${subTab} registered`}\n />\n );\n\n return <TwoColumnLayout sidebar={sidebar}>{renderDetail()}</TwoColumnLayout>;\n}\n\nfunction generateExampleFromSchema(schema: Tool[\"schema\"]): Record<string, unknown> {\n if (!schema?.properties) return {};\n\n const example: Record<string, unknown> = {};\n\n for (const [name, prop] of Object.entries(schema.properties)) {\n const propDef = prop as { type?: string; default?: unknown; enum?: unknown[] };\n\n if (propDef.default !== undefined) {\n example[name] = propDef.default;\n continue;\n }\n\n if (propDef.enum?.length) {\n example[name] = propDef.enum[0];\n continue;\n }\n\n switch (propDef.type) {\n case \"string\":\n example[name] = `example-${name}`;\n break;\n case \"number\":\n case \"integer\":\n example[name] = 1;\n break;\n case \"boolean\":\n example[name] = true;\n break;\n case \"array\":\n example[name] = [];\n break;\n case \"object\":\n example[name] = {};\n break;\n default:\n example[name] = null;\n }\n }\n\n return example;\n}\n\nfunction ToolDetail({ tool }: { tool: Tool }): JSX.Element {\n const [args, setArgs] = useState(() => {\n const example = generateExampleFromSchema(tool.schema);\n return JSON.stringify(example, null, 2);\n });\n const [result, setResult] = useState<\n { success: boolean; data: string; duration?: number } | null\n >(\n null,\n );\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n const example = generateExampleFromSchema(tool.schema);\n setArgs(JSON.stringify(example, null, 2));\n setResult(null);\n }, [tool.id, tool.schema]);\n\n async function execute(): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(args);\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n return;\n }\n\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/execute-tool\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ toolId: tool.id, args: parsed }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({\n success: true,\n data: JSON.stringify(d.result, null, 2),\n duration: d.duration,\n });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader title={tool.id} description={tool.description || \"No description\"} />\n\n {tool.schema?.properties && (\n <Card title=\"Input Schema\" className=\"mb-6\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Name\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Type\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Description\n </th>\n </tr>\n </thead>\n <tbody>\n {Object.entries(tool.schema.properties).map(([name, prop]) => {\n const p = prop as { type?: string; description?: string };\n return (\n <tr key={name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2.5\">\n <code className=\"text-xs text-sky-600 font-medium\">{name}</code>\n {tool.schema?.required?.includes(name) && (\n <span className=\"ml-1.5 px-1 py-0.5 bg-red-50 text-red-600 text-[9px] font-semibold uppercase rounded\">\n required\n </span>\n )}\n </td>\n <td className=\"px-3 py-2.5\">\n <span className=\"px-1.5 py-0.5 bg-gray-100 text-gray-500 text-[10px] font-medium font-mono rounded\">\n {p.type || \"any\"}\n </span>\n </td>\n <td className=\"px-3 py-2.5 text-gray-600\">{p.description || \"-\"}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n )}\n\n <Card title=\"Execute\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n Arguments (JSON)\n </label>\n <textarea\n value={args}\n onChange={(e) => setArgs(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm font-mono focus:outline-none focus:border-sky-500 focus:bg-white min-h-[80px] resize-y\"\n />\n <ActionButton onClick={execute} loading={loading} loadingText=\"Running...\">\n Run\n </ActionButton>\n {result && (\n <ResultBox\n success={result.success}\n label={result.success ? \"Success\" : \"Error\"}\n duration={result.duration}\n >\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n\nfunction ResourceDetail({ resource }: { resource: Resource }): JSX.Element {\n const [uri, setUri] = useState(resource.pattern);\n const [result, setResult] = useState<\n { success: boolean; data: string; duration?: number } | null\n >(\n null,\n );\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n setUri(resource.pattern);\n setResult(null);\n }, [resource.pattern]);\n\n async function read(): Promise<void> {\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/read-resource\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ uri }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({\n success: true,\n data: JSON.stringify(d.data, null, 2),\n duration: d.duration,\n });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader\n title={resource.pattern}\n description={resource.description || \"No description\"}\n />\n\n <Card title=\"Read Resource\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n URI\n </label>\n <input\n type=\"text\"\n value={uri}\n onChange={(e) => setUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm focus:outline-none focus:border-sky-500 focus:bg-white\"\n />\n <ActionButton onClick={read} loading={loading} loadingText=\"Reading...\">\n Read\n </ActionButton>\n {result && (\n <ResultBox\n success={result.success}\n label={result.success ? \"Success\" : \"Error\"}\n duration={result.duration}\n >\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n\nfunction PromptDetail({ prompt }: { prompt: Prompt }): JSX.Element {\n const [variables, setVariables] = useState(\"{}\");\n const [result, setResult] = useState<{ success: boolean; data: string } | null>(null);\n const [loading, setLoading] = useState(false);\n\n async function render(): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(variables);\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n return;\n }\n\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/render-prompt\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ promptId: prompt.id, variables: parsed }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({ success: true, data: d.content });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader title={prompt.id} description={prompt.description || \"No description\"} />\n\n <Card title=\"Render Prompt\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n Variables (JSON)\n </label>\n <textarea\n value={variables}\n onChange={(e) => setVariables(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm font-mono focus:outline-none focus:border-sky-500 focus:bg-white min-h-[80px] resize-y\"\n />\n <ActionButton onClick={render} loading={loading} loadingText=\"Rendering...\">\n Render\n </ActionButton>\n {result && (\n <ResultBox success={result.success} label={result.success ? \"Rendered\" : \"Error\"}>\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n",
|
|
14
|
-
"dashboard/components/AITab.tsx": "import { useEffect, useState } from \"react\";\nimport type { Agent, Prompt, Resource, Tool } from \"../App.tsx\";\nimport { MCPTab } from \"./MCPTab.tsx\";\nimport { AgentsTab } from \"./AgentsTab.tsx\";\nimport { WorkflowsTab } from \"./WorkflowsTab.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"mcp\" | \"agents\" | \"workflows\" | \"providers\";\n\ninterface Provider {\n name: string;\n configured: boolean;\n}\n\ninterface NodeInfo {\n id: string;\n type: string;\n agent?: string;\n tool?: string;\n dependsOn?: string[];\n children?: string[];\n message?: string;\n}\n\ninterface WorkflowMetadata {\n id: string;\n description?: string;\n version?: string;\n timeout?: string | number;\n nodeCount: number;\n nodeTypes: string[];\n nodes: NodeInfo[];\n agentRefs: string[];\n toolRefs: string[];\n hasInputSchema: boolean;\n hasOutputSchema: boolean;\n inputSchemaJson?: Record<string, unknown>;\n registeredAt: string;\n}\n\ninterface AITabProps {\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n agents: Agent[];\n}\n\nexport function AITab({ tools, resources, prompts, agents }: AITabProps): React.ReactElement {\n const [subTab, setSubTab] = useState<SubTab>(\"mcp\");\n\n const [providers, setProviders] = useState<Provider[]>([]);\n const [providersLoading, setProvidersLoading] = useState(true);\n const [providersError, setProvidersError] = useState<string | null>(null);\n\n const [workflows, setWorkflows] = useState<WorkflowMetadata[]>([]);\n const [workflowsLoading, setWorkflowsLoading] = useState(true);\n const [workflowsError, setWorkflowsError] = useState<string | null>(null);\n\n useEffect(() => {\n fetch(\"/_dev/api/infrastructure\")\n .then((res) => res.json())\n .then((d) => {\n setProviders(d.providers ?? []);\n setProvidersError(null);\n })\n .catch((e: unknown) => setProvidersError(e instanceof Error ? e.message : String(e)))\n .finally(() => setProvidersLoading(false));\n\n fetch(\"/_dev/api/workflows\")\n .then((res) => res.json())\n .then((d) => {\n setWorkflows(d.workflows ?? []);\n setWorkflowsError(null);\n })\n .catch((e: unknown) => setWorkflowsError(e instanceof Error ? e.message : String(e)))\n .finally(() => setWorkflowsLoading(false));\n }, []);\n\n function navigateToMCP(mcpSubTab: \"tools\" | \"resources\" | \"prompts\", itemId: string): void {\n setSubTab(\"mcp\");\n globalThis.dispatchEvent(\n new CustomEvent(\"mcp-navigate\", { detail: { subTab: mcpSubTab, itemId } }),\n );\n }\n\n const mcpCount = tools.length + resources.length + prompts.length;\n const configuredProvidersCount = providers.filter((p) => p.configured).length;\n\n return (\n <div className=\"min-h-screen\">\n <div className=\"bg-white border-b\">\n <div className=\"px-6 py-4\">\n <div className=\"mb-3\">\n <h1 className=\"text-xl font-semibold text-gray-900\">AI</h1>\n <p className=\"text-sm text-gray-500\">MCP, agents, workflows, and providers</p>\n </div>\n <div className=\"flex gap-1 border border-gray-200 rounded-lg p-1 bg-gray-50 w-fit\">\n <TabButton\n active={subTab === \"mcp\"}\n onClick={() => setSubTab(\"mcp\")}\n label={`MCP (${mcpCount})`}\n />\n <TabButton\n active={subTab === \"agents\"}\n onClick={() => setSubTab(\"agents\")}\n label={`Agents (${agents.length})`}\n />\n <TabButton\n active={subTab === \"workflows\"}\n onClick={() => setSubTab(\"workflows\")}\n label={`Workflows (${workflows.length})`}\n />\n <TabButton\n active={subTab === \"providers\"}\n onClick={() => setSubTab(\"providers\")}\n label={`Providers (${configuredProvidersCount})`}\n />\n </div>\n </div>\n </div>\n\n {subTab === \"mcp\" && <MCPTab tools={tools} resources={resources} prompts={prompts} />}\n\n {subTab === \"agents\" && (\n <AgentsTab agents={agents} tools={tools} onNavigateToMCP={navigateToMCP} />\n )}\n\n {subTab === \"workflows\" && (\n <WorkflowsSection\n loading={workflowsLoading}\n error={workflowsError}\n workflows={workflows}\n agents={agents}\n tools={tools}\n onNavigateToTool={(toolId) => navigateToMCP(\"tools\", toolId)}\n onNavigateToAgent={() => {\n setSubTab(\"agents\");\n // AgentsTab will need to handle this\n }}\n />\n )}\n\n {subTab === \"providers\" && (\n <ProvidersSection providers={providers} loading={providersLoading} error={providersError} />\n )}\n </div>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.ReactElement {\n const className = `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n active ? \"bg-white text-sky-600 shadow-sm\" : \"text-gray-500 hover:text-gray-700\"\n }`;\n\n return (\n <button type=\"button\" onClick={onClick} className={className}>\n {label}\n </button>\n );\n}\n\nfunction WorkflowsSection({\n loading,\n error,\n workflows,\n agents,\n tools,\n onNavigateToAgent,\n onNavigateToTool,\n}: {\n loading: boolean;\n error: string | null;\n workflows: WorkflowMetadata[];\n agents: Agent[];\n tools: Tool[];\n onNavigateToAgent: (agentId: string) => void;\n onNavigateToTool: (toolId: string) => void;\n}): React.ReactElement {\n if (loading) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <LoadingState message=\"Loading workflows...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n return (\n <WorkflowsTab\n workflows={workflows}\n agents={agents}\n tools={tools}\n onNavigateToAgent={onNavigateToAgent}\n onNavigateToTool={onNavigateToTool}\n />\n );\n}\n\nfunction ProvidersSection({\n providers,\n loading,\n error,\n}: {\n providers: Provider[];\n loading: boolean;\n error: string | null;\n}): React.ReactElement {\n if (loading) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <LoadingState message=\"Loading providers...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n const configured: Provider[] = [];\n const notConfigured: Provider[] = [];\n\n for (const provider of providers) {\n if (provider.configured) {\n configured.push(provider);\n } else {\n notConfigured.push(provider);\n }\n }\n\n return (\n <div className=\"max-w-7xl mx-auto px-6 py-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 mb-6\">\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{configured.length}</div>\n <div className=\"text-sm text-gray-500\">Configured</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-400\">{notConfigured.length}</div>\n <div className=\"text-sm text-gray-500\">Not Configured</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{providers.length}</div>\n <div className=\"text-sm text-gray-500\">Total Available</div>\n </Card>\n </div>\n\n <Card title=\"AI PROVIDERS\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Provider\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Status\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n API Key\n </th>\n </tr>\n </thead>\n <tbody>\n {providers.map((provider) => {\n const status = provider.configured\n ? {\n className: \"inline-flex items-center gap-1.5 text-green-600\",\n dotClassName: \"w-2 h-2 rounded-full bg-green-500\",\n label: \"Ready\",\n apiKey: <span className=\"text-green-600\">(set)</span>,\n }\n : {\n className: \"inline-flex items-center gap-1.5 text-gray-400\",\n dotClassName: \"w-2 h-2 rounded-full bg-gray-300\",\n label: \"Not configured\",\n apiKey: <span className=\"text-gray-400\">(not set)</span>,\n };\n\n return (\n <tr key={provider.name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-3\">\n <code className=\"text-sm text-sky-600 font-medium\">{provider.name}</code>\n </td>\n <td className=\"px-3 py-3\">\n <span className={status.className}>\n <span className={status.dotClassName} />\n {status.label}\n </span>\n </td>\n <td className=\"px-3 py-3 text-gray-500 text-sm\">{status.apiKey}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n </div>\n );\n}\n",
|
|
15
|
-
"dashboard/components/FilesTab.tsx": "import { useEffect, useMemo, useState } from \"react\";\nimport type { FileItem } from \"../App.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { DetailHeader, ErrorState, formatSize, LoadingState, TwoColumnLayout } from \"./shared.tsx\";\n\nexport function FilesTab(): React.ReactElement {\n const [currentPath, setCurrentPath] = useState(\"\");\n const [files, setFiles] = useState<FileItem[]>([]);\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function loadFiles(path: string): Promise<void> {\n setLoading(true);\n try {\n const res = await fetch(`/_dev/api/files?path=${encodeURIComponent(path)}`);\n const data = await res.json();\n setFiles(data.files ?? []);\n } catch (e) {\n console.error(\"Failed to load files:\", e);\n setFiles([]);\n } finally {\n setLoading(false);\n }\n }\n\n void loadFiles(currentPath);\n }, [currentPath]);\n\n const filteredFiles = useMemo((): FileItem[] => {\n if (!search) return files;\n const q = search.toLowerCase();\n return files.filter((f) => f.name.toLowerCase().includes(q));\n }, [files, search]);\n\n function handleSelect(id: string): void {\n const file = files.find((f) => f.path === id);\n if (!file) return;\n\n if (file.type !== \"directory\") {\n setSelectedFile(file.path);\n return;\n }\n\n setCurrentPath(file.path);\n setSelectedFile(null);\n setSearch(\"\");\n }\n\n function handleBack(): void {\n setCurrentPath(currentPath.split(\"/\").slice(0, -1).join(\"/\"));\n setSelectedFile(null);\n }\n\n const sidebar = (\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n items={filteredFiles.map((f) => ({\n id: f.path,\n label: f.type === \"directory\" ? `${f.name}/` : f.name,\n bold: f.type === \"directory\",\n }))}\n selectedId={selectedFile}\n onSelect={handleSelect}\n emptyMessage={loading ? \"Loading...\" : \"No files found\"}\n onBack={currentPath && !search ? handleBack : undefined}\n />\n );\n\n return (\n <TwoColumnLayout sidebar={sidebar}>\n {selectedFile\n ? <FileDetail path={selectedFile} />\n : <DirectoryInfo path={currentPath} fileCount={files.length} />}\n </TwoColumnLayout>\n );\n}\n\nfunction DirectoryInfo(\n { path, fileCount }: { path: string; fileCount: number },\n): React.ReactElement {\n return (\n <div>\n <DetailHeader title={path || \"Project Root\"} description={`${fileCount} items`} />\n <Card>\n <div className=\"p-4 text-sm text-gray-400\">Select a file to view its contents</div>\n </Card>\n </div>\n );\n}\n\ninterface FileContent {\n content?: string;\n lines?: number;\n size?: number;\n isBinary?: boolean;\n message?: string;\n error?: string;\n}\n\nfunction FileDetail({ path }: { path: string }): React.ReactElement {\n const [content, setContent] = useState<FileContent | null>(null);\n const [loading, setLoading] = useState(true);\n\n const filename = useMemo((): string => path.split(\"/\").pop() ?? \"\", [path]);\n const ext = useMemo((): string => path.split(\".\").pop()?.toLowerCase() ?? \"\", [path]);\n\n useEffect(() => {\n setLoading(true);\n\n fetch(`/_dev/api/file-content?path=${encodeURIComponent(path)}`)\n .then((res) => res.json())\n .then(setContent)\n .catch((e: unknown) => {\n const message = e instanceof Error ? e.message : String(e);\n setContent({ error: message });\n })\n .finally(() => setLoading(false));\n }, [path]);\n\n const title = !loading && content?.content !== undefined ? \"Contents\" : undefined;\n const titleRight = content?.lines\n ? <span className=\"text-[11px] text-gray-400 font-normal\">{content.lines} lines</span>\n : undefined;\n\n let body: React.ReactNode = null;\n if (loading) {\n body = <LoadingState message=\"Loading file contents...\" />;\n } else if (content?.error) {\n body = <ErrorState error={content.error} />;\n } else if (content?.isBinary) {\n body = <div className=\"p-4 text-sm text-gray-400\">{content.message}</div>;\n } else if (content?.content !== undefined) {\n body = (\n <pre className=\"p-3 text-xs font-mono text-gray-600 overflow-auto max-h-[500px] whitespace-pre-wrap bg-gray-50\">\n {content.content}\n </pre>\n );\n }\n\n return (\n <div>\n <DetailHeader title={filename} description={path} />\n\n <Card title=\"File Info\" className=\"mb-4\">\n <table className=\"w-full text-sm\">\n <tbody>\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 w-24 font-medium text-gray-600\">Path</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{path}</td>\n </tr>\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Extension</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{ext}</td>\n </tr>\n {content?.lines\n ? (\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Lines</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{content.lines}</td>\n </tr>\n )\n : null}\n {content?.size\n ? (\n <tr>\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Size</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{formatSize(content.size)}</td>\n </tr>\n )\n : null}\n </tbody>\n </table>\n </Card>\n\n <Card title={title} titleRight={titleRight}>\n {body}\n </Card>\n </div>\n );\n}\n",
|
|
16
|
-
"dashboard/components/TabNav.tsx": "import type { TabId } from \"../App.tsx\";\n\ninterface TabNavProps {\n tabs: { id: TabId; label: string }[];\n currentTab: TabId;\n onTabChange: (tab: TabId) => void;\n}\n\nexport function TabNav({ tabs, currentTab, onTabChange }: TabNavProps): JSX.Element {\n return (\n <nav className=\"bg-white border-b border-gray-200 px-5 flex gap-0.5\">\n {tabs.map((tab) => {\n const isActive = currentTab === tab.id;\n\n let className = \"px-3 py-2.5 text-xs font-medium border-b-2 -mb-px transition-colors \";\n if (isActive) {\n className += \"text-sky-500 border-sky-500\";\n } else {\n className += \"text-gray-400 border-transparent hover:text-gray-600\";\n }\n\n return (\n <button\n key={tab.id}\n type=\"button\"\n className={className}\n onClick={() => onTabChange(tab.id)}\n >\n {tab.label}\n </button>\n );\n })}\n </nav>\n );\n}\n",
|
|
17
|
-
"dashboard/components/Card.tsx": "import type { ReactNode } from \"react\";\n\ninterface CardProps {\n title?: string;\n titleRight?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport function Card({\n title,\n titleRight,\n children,\n className = \"\",\n}: CardProps): ReactNode {\n return (\n <div\n className={`bg-white border border-gray-200 rounded-md shadow-sm overflow-hidden ${className}`}\n >\n {title && (\n <div className=\"px-4 py-3 border-b border-gray-100 flex items-center justify-between\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wide text-gray-500\">\n {title}\n </span>\n {titleRight}\n </div>\n )}\n {children}\n </div>\n );\n}\n",
|
|
18
|
-
"dashboard/components/RuntimeTab.tsx": "import { useEffect, useState } from \"react\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"metrics\" | \"memory\";\n\ninterface HeapStats {\n usedHeapSizeMB: number;\n totalHeapSizeMB: number;\n heapSizeLimitMB: number;\n heapUsedPercent: number;\n rss?: number;\n}\n\ninterface CacheStats {\n name: string;\n entries: number;\n maxEntries?: number;\n}\n\ninterface Pressure {\n critical: boolean;\n warning: boolean;\n heapUsedPercent: number;\n}\n\ninterface MemoryData {\n heap: HeapStats;\n caches: CacheStats[];\n pressure: Pressure;\n}\n\nexport function RuntimeTab(): React.JSX.Element {\n const [subTab, setSubTab] = useState<SubTab>(\"metrics\");\n const [metrics, setMetrics] = useState<Record<string, number | unknown>>({});\n const [memory, setMemory] = useState<MemoryData | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n function loadData(): void {\n setLoading(true);\n\n Promise.all([\n fetch(\"/_dev/api/metrics\").then((r) => r.json()),\n fetch(\"/_dev/api/memory\").then((r) => r.json()),\n ])\n .then(([m, mem]) => {\n setMetrics(m.counters || {});\n setMemory(mem);\n setError(null);\n })\n .catch((e) => setError((e as Error).message))\n .finally(() => setLoading(false));\n }\n\n useEffect(() => {\n loadData();\n const interval = setInterval(loadData, 15000);\n return () => clearInterval(interval);\n }, []);\n\n if (!memory && loading) {\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <Card>\n <LoadingState message=\"Loading runtime info...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (!memory && error) {\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n const metricsCount = Object.keys(metrics).length;\n\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <div className=\"flex items-center justify-between mb-6\">\n <div className=\"flex gap-1 border-b border-gray-200 pb-2\">\n <TabButton\n active={subTab === \"metrics\"}\n onClick={() => setSubTab(\"metrics\")}\n label={`Metrics (${metricsCount})`}\n />\n <TabButton\n active={subTab === \"memory\"}\n onClick={() => setSubTab(\"memory\")}\n label={`Memory (${memory?.caches.length ?? 0} caches)`}\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-gray-400\">Auto-refresh: 15s</span>\n <button\n type=\"button\"\n onClick={loadData}\n disabled={loading}\n className=\"px-3 py-1.5 bg-white border border-gray-200 text-sm text-gray-600 rounded hover:bg-gray-50 disabled:opacity-50\"\n >\n Refresh\n </button>\n </div>\n </div>\n\n {subTab === \"metrics\" && <MetricsSection metrics={metrics} />}\n {subTab === \"memory\" && memory && <MemorySection memory={memory} />}\n </PageLayout>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.JSX.Element {\n const className = active\n ? \"bg-white text-sky-600 border border-gray-200 border-b-white -mb-[1px]\"\n : \"text-gray-500 hover:text-gray-700\";\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`px-3 py-1.5 text-sm font-medium rounded-t transition-colors ${className}`}\n >\n {label}\n </button>\n );\n}\n\nfunction MetricsSection({\n metrics,\n}: {\n metrics: Record<string, number | unknown>;\n}): React.JSX.Element {\n const groups: Record<string, Array<{ key: string; val: unknown }>> = {};\n\n for (const [key, val] of Object.entries(metrics)) {\n const [group = \"general\"] = key.split(\".\");\n (groups[group] ??= []).push({ key, val });\n }\n\n if (Object.keys(groups).length === 0) {\n return (\n <Card>\n <div className=\"p-6 text-center text-gray-400\">No metrics recorded yet</div>\n </Card>\n );\n }\n\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {Object.entries(groups)\n .sort()\n .map(([group, items]) => (\n <Card key={group} title={group.toUpperCase()}>\n <table className=\"w-full text-sm\">\n <tbody>\n {items.map(({ key, val }) => (\n <tr key={key} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600\">\n {key.replace(`${group}.`, \"\")}\n </code>\n </td>\n <td className=\"px-3 py-2 text-right font-medium\">\n {typeof val === \"number\" ? val.toLocaleString() : JSON.stringify(val)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n ))}\n </div>\n );\n}\n\nfunction MemorySection({ memory }: { memory: MemoryData }): React.JSX.Element {\n const progressPercent = memory.heap.heapUsedPercent;\n\n let progressColor = \"bg-green-500\";\n if (memory.pressure.critical) progressColor = \"bg-red-500\";\n else if (memory.pressure.warning) progressColor = \"bg-amber-500\";\n\n let pressureColor = \"text-green-600\";\n if (memory.pressure.critical) pressureColor = \"text-red-600\";\n else if (memory.pressure.warning) pressureColor = \"text-amber-600\";\n\n let pressureLabel = \"OK\";\n if (memory.pressure.critical) pressureLabel = \"CRITICAL\";\n else if (memory.pressure.warning) pressureLabel = \"WARNING\";\n\n return (\n <>\n <Card className=\"mb-4\">\n <div className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <span className=\"text-sm font-medium text-gray-700\">Heap Usage</span>\n <span className={`text-sm font-semibold ${pressureColor}`}>{pressureLabel}</span>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"flex-1 h-3 bg-gray-100 rounded-full overflow-hidden\">\n <div\n className={`h-full ${progressColor} transition-all duration-500`}\n style={{ width: `${Math.min(progressPercent, 100)}%` }}\n />\n </div>\n <span className=\"text-sm text-gray-600 w-32 text-right\">\n {memory.heap.usedHeapSizeMB.toFixed(0)} / {memory.heap.heapSizeLimitMB} MB\n </span>\n </div>\n <div className=\"flex gap-6 mt-3 text-xs text-gray-500\">\n <span>RSS: {memory.heap.rss?.toFixed(0) ?? \"—\"} MB</span>\n <span>Total: {memory.heap.totalHeapSizeMB.toFixed(0)} MB</span>\n </div>\n </div>\n </Card>\n\n <Card title={`CACHES (${memory.caches.length})`}>\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Cache\n </th>\n <th className=\"text-right px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Entries\n </th>\n <th className=\"text-right px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Max\n </th>\n </tr>\n </thead>\n <tbody>\n {memory.caches.map((cache) => (\n <tr key={cache.name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600\">{cache.name}</code>\n </td>\n <td className=\"px-3 py-2 text-right font-medium\">{cache.entries}</td>\n <td className=\"px-3 py-2 text-right text-gray-500\">\n {cache.maxEntries ?? \"—\"}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </>\n );\n}\n",
|
|
4
|
+
"dashboard/components/ErrorsTab.tsx": "import { useEffect, useState } from \"react\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ninterface ErrorEntry {\n code: string;\n title: string;\n category: string;\n message: string;\n steps?: string[];\n docsUrl?: string;\n}\n\ninterface ErrorsData {\n errors: ErrorEntry[];\n categories: Record<string, number>;\n count: number;\n timestamp: string;\n}\n\nconst categoryColors: Record<string, string> = {\n config: \"bg-blue-100 text-blue-700\",\n build: \"bg-purple-100 text-purple-700\",\n runtime: \"bg-orange-100 text-orange-700\",\n route: \"bg-green-100 text-green-700\",\n module: \"bg-pink-100 text-pink-700\",\n server: \"bg-red-100 text-red-700\",\n rsc: \"bg-cyan-100 text-cyan-700\",\n dev: \"bg-yellow-100 text-yellow-700\",\n deployment: \"bg-indigo-100 text-indigo-700\",\n general: \"bg-gray-100 text-gray-700\",\n};\n\nfunction getCategoryClass(category: string): string {\n return categoryColors[category] ?? \"bg-gray-100 text-gray-700\";\n}\n\nexport function ErrorsTab(): React.JSX.Element {\n const [data, setData] = useState<ErrorsData | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n const [selectedCategory, setSelectedCategory] = useState<string | null>(null);\n const [selectedError, setSelectedError] = useState<ErrorEntry | null>(null);\n\n function loadData(): void {\n setLoading(true);\n\n fetch(\"/_dev/api/errors\")\n .then((res) => res.json())\n .then((d: ErrorsData) => {\n setData(d);\n setError(null);\n })\n .catch((e: unknown) => {\n setError(e instanceof Error ? e.message : String(e));\n })\n .finally(() => setLoading(false));\n }\n\n useEffect(() => {\n loadData();\n }, []);\n\n if (loading) {\n return (\n <PageLayout title=\"Errors\" description=\"Error catalog and solutions\">\n <Card>\n <LoadingState message=\"Loading error catalog...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"Errors\" description=\"Error catalog and solutions\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n const searchLower = search.toLowerCase();\n const filteredErrors = data?.errors.filter((err) => {\n const matchesSearch = !search ||\n err.code.toLowerCase().includes(searchLower) ||\n err.title.toLowerCase().includes(searchLower) ||\n err.message.toLowerCase().includes(searchLower);\n\n const matchesCategory = !selectedCategory || err.category === selectedCategory;\n\n return matchesSearch && matchesCategory;\n }) ?? [];\n\n const count = data?.count ?? 0;\n const categories = data?.categories ?? {};\n\n return (\n <PageLayout title=\"Errors\" description={`Error catalog (${count} codes)`}>\n <div className=\"mb-4\">\n <input\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search errors...\"\n className=\"w-full max-w-md px-3 py-1.5 bg-white border border-gray-200 rounded text-sm focus:outline-none focus:border-sky-500\"\n />\n </div>\n\n <Card title=\"CATEGORIES\" className=\"mb-4\">\n <div className=\"p-3 flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setSelectedCategory(null)}\n className={`px-3 py-1.5 text-sm font-medium rounded-full transition-colors ${\n selectedCategory === null\n ? \"bg-sky-500 text-white\"\n : \"bg-gray-100 text-gray-600 hover:bg-gray-200\"\n }`}\n >\n All ({count})\n </button>\n\n {Object.entries(categories)\n .sort()\n .map(([cat, catCount]) => {\n const isSelected = selectedCategory === cat;\n\n return (\n <button\n type=\"button\"\n key={cat}\n onClick={() => setSelectedCategory(isSelected ? null : cat)}\n className={`px-3 py-1.5 text-sm font-medium rounded-full transition-colors ${\n isSelected ? \"bg-sky-500 text-white\" : getCategoryClass(cat)\n }`}\n >\n {cat} ({catCount})\n </button>\n );\n })}\n </div>\n </Card>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4\">\n <Card title={`ERRORS (${filteredErrors.length})`} className=\"max-h-[600px] overflow-y-auto\">\n <div className=\"divide-y\">\n {filteredErrors.map((err) => (\n <button\n key={err.code}\n type=\"button\"\n onClick={() => setSelectedError(err)}\n className={`w-full px-3 py-2.5 text-left hover:bg-gray-50 transition-colors ${\n selectedError?.code === err.code ? \"bg-sky-50\" : \"\"\n }`}\n >\n <div className=\"flex items-center gap-2\">\n <code className=\"text-xs font-bold text-red-600\">{err.code}</code>\n <span\n className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${\n getCategoryClass(\n err.category,\n )\n }`}\n >\n {err.category}\n </span>\n </div>\n <div className=\"text-sm text-gray-900 mt-1\">{err.title}</div>\n </button>\n ))}\n </div>\n </Card>\n\n <Card title=\"ERROR DETAILS\">\n {!selectedError\n ? <div className=\"p-4 text-sm text-gray-400\">Select an error to view details</div>\n : (\n <div className=\"p-4\">\n <div className=\"mb-4\">\n <code className=\"text-lg font-bold text-red-600\">{selectedError.code}</code>\n <h3 className=\"text-lg font-semibold text-gray-900 mt-1\">\n {selectedError.title}\n </h3>\n <span\n className={`inline-block mt-2 px-2 py-0.5 text-xs font-medium rounded ${\n getCategoryClass(\n selectedError.category,\n )\n }`}\n >\n {selectedError.category}\n </span>\n </div>\n\n <div className=\"mb-4\">\n <div className=\"text-xs font-semibold text-gray-500 uppercase mb-1\">Message</div>\n <p className=\"text-sm text-gray-700\">{selectedError.message}</p>\n </div>\n\n {selectedError.steps?.length\n ? (\n <div className=\"mb-4\">\n <div className=\"text-xs font-semibold text-gray-500 uppercase mb-2\">\n Resolution Steps\n </div>\n <ol className=\"list-decimal list-inside space-y-1\">\n {selectedError.steps.map((step, idx) => (\n <li key={idx} className=\"text-sm text-gray-700\">\n {step}\n </li>\n ))}\n </ol>\n </div>\n )\n : null}\n\n {selectedError.docsUrl\n ? (\n <a\n href={selectedError.docsUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-sky-600 hover:text-sky-700\"\n >\n View Documentation\n <svg className=\"w-4 h-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path\n fillRule=\"evenodd\"\n d=\"M5.22 14.78a.75.75 0 001.06 0l7.22-7.22v5.69a.75.75 0 001.5 0v-7.5a.75.75 0 00-.75-.75h-7.5a.75.75 0 000 1.5h5.69l-7.22 7.22a.75.75 0 000 1.06z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </a>\n )\n : null}\n </div>\n )}\n </Card>\n </div>\n </PageLayout>\n );\n}\n",
|
|
19
5
|
"dashboard/components/shared.tsx": "import type { ReactNode } from \"react\";\n\ninterface EmptyStateProps {\n message: string;\n}\n\nexport function EmptyState({ message }: EmptyStateProps): ReactNode {\n return (\n <div className=\"flex flex-col items-center justify-center py-12 text-gray-400\">\n <div className=\"w-10 h-10 border border-gray-200 rounded-lg mb-3 flex items-center justify-center bg-white\">\n <div className=\"w-3.5 h-3.5 border-2 border-gray-200 rounded\" />\n </div>\n <p className=\"text-sm\">{message}</p>\n </div>\n );\n}\n\nexport function LoadingSpinner(): ReactNode {\n return (\n <div className=\"w-4 h-4 border-2 border-gray-200 border-t-sky-500 rounded-full animate-spin\" />\n );\n}\n\ninterface LoadingStateProps {\n message?: string;\n}\n\nexport function LoadingState({ message = \"Loading...\" }: LoadingStateProps): ReactNode {\n return (\n <div className=\"p-4 flex items-center gap-2 text-sm text-gray-400\">\n <LoadingSpinner />\n {message}\n </div>\n );\n}\n\ninterface ErrorStateProps {\n error: string;\n}\n\nexport function ErrorState({ error }: ErrorStateProps): ReactNode {\n return <div className=\"p-4 text-sm text-red-600\">Error: {error}</div>;\n}\n\ninterface ResultBoxProps {\n success: boolean;\n label: string;\n duration?: number;\n children: ReactNode;\n}\n\nexport function ResultBox({ success, label, duration, children }: ResultBoxProps): ReactNode {\n const headerClassName = success ? \"bg-green-50 text-green-700\" : \"bg-red-50 text-red-700\";\n\n return (\n <div className=\"mt-4 border rounded overflow-hidden\">\n <div className={`px-3 py-2 text-xs font-medium flex items-center gap-2 ${headerClassName}`}>\n {label}\n {duration !== undefined && (\n <span className=\"ml-auto text-gray-400 font-normal\">{duration}ms</span>\n )}\n </div>\n <pre className=\"p-3 text-xs font-mono text-gray-600 overflow-auto max-h-60 whitespace-pre-wrap\">\n {children}\n </pre>\n </div>\n );\n}\n\ninterface ActionButtonProps {\n onClick: () => void;\n disabled?: boolean;\n loading?: boolean;\n loadingText?: string;\n children: ReactNode;\n}\n\nexport function ActionButton({\n onClick,\n disabled,\n loading,\n loadingText,\n children,\n}: ActionButtonProps): ReactNode {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled || loading}\n className=\"mt-3 px-4 py-2 bg-sky-500 text-white text-sm font-medium rounded hover:bg-sky-600 disabled:opacity-50\"\n >\n {loading ? loadingText : children}\n </button>\n );\n}\n\ninterface DetailHeaderProps {\n title: string;\n description?: string;\n}\n\nexport function DetailHeader({ title, description }: DetailHeaderProps): ReactNode {\n return (\n <div className=\"mb-6\">\n <h1 className=\"text-lg font-semibold tracking-tight\">{title}</h1>\n {description && <p className=\"text-sm text-gray-500\">{description}</p>}\n </div>\n );\n}\n\ninterface PageLayoutProps {\n title: string;\n description?: string;\n children: ReactNode;\n}\n\nexport function PageLayout({ title, description, children }: PageLayoutProps): ReactNode {\n return (\n <div className=\"h-[calc(100vh-89px)] overflow-y-auto\">\n <main className=\"p-5 bg-gray-50 max-w-5xl\">\n <DetailHeader title={title} description={description} />\n {children}\n </main>\n </div>\n );\n}\n\ninterface TwoColumnLayoutProps {\n sidebar: ReactNode;\n children: ReactNode;\n}\n\nexport function TwoColumnLayout({ sidebar, children }: TwoColumnLayoutProps): ReactNode {\n return (\n <div className=\"grid grid-cols-[240px_1fr] h-[calc(100vh-89px)]\">\n {sidebar}\n <main className=\"overflow-y-auto p-5 bg-gray-50\">{children}</main>\n </div>\n );\n}\n\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n",
|
|
20
|
-
"dashboard/components/APITab.tsx": "export function APITab(): JSX.Element {\n return (\n <div className=\"h-[calc(100vh-89px)] overflow-y-auto\">\n <main className=\"bg-gray-50 p-5\">\n <div className=\"mb-6\">\n <h1 className=\"text-lg font-semibold tracking-tight\">API Documentation</h1>\n <p className=\"text-sm text-gray-500\">\n Interactive API docs powered by Scalar.{\" \"}\n <a\n href=\"/_docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sky-500 hover:text-sky-600\"\n >\n Open in new tab\n </a>\n </p>\n </div>\n\n <div className=\"overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm\">\n <iframe\n src=\"/_docs\"\n className=\"w-full border-0\"\n style={{ height: \"calc(100vh - 180px)\" }}\n />\n </div>\n </main>\n </div>\n );\n}\n",
|
|
21
6
|
"dashboard/components/ServerTab.tsx": "import { useEffect, useState } from \"react\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"handlers\" | \"build\";\n\ninterface Handler {\n name: string;\n priority: number;\n patterns: Array<{ pattern: string }>;\n enabled: string;\n}\n\ninterface TransformStage {\n stage: number;\n name: string;\n description: string;\n}\n\ninterface Plugin {\n name: string;\n description: string;\n}\n\ninterface BuildInfo {\n transformStages: TransformStage[];\n remarkPlugins: Plugin[];\n rehypePlugins: Plugin[];\n}\n\nexport function ServerTab(): React.ReactElement {\n const [subTab, setSubTab] = useState<SubTab>(\"handlers\");\n const [handlers, setHandlers] = useState<Handler[]>([]);\n const [build, setBuild] = useState<BuildInfo | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n\n async function load(): Promise<void> {\n setLoading(true);\n\n try {\n const [handlersRes, buildRes] = await Promise.all([\n fetch(\"/_dev/api/handlers\").then((r) => r.json()),\n fetch(\"/_dev/api/build\").then((r) => r.json()),\n ]);\n\n if (cancelled) return;\n\n setHandlers(handlersRes.handlers ?? []);\n setBuild(buildRes);\n setError(null);\n } catch (e) {\n if (cancelled) return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) {\n setLoading(false);\n }\n }\n }\n\n void load();\n\n return () => {\n cancelled = true;\n };\n }, []);\n\n const layout = (content: React.ReactNode): React.ReactElement => (\n <PageLayout title=\"Server\" description=\"Request handling and build pipeline\">\n {content}\n </PageLayout>\n );\n\n if (loading) {\n return layout(\n <Card>\n <LoadingState message=\"Loading server info...\" />\n </Card>,\n );\n }\n\n if (error) {\n return layout(\n <Card>\n <ErrorState error={error} />\n </Card>,\n );\n }\n\n return layout(\n <>\n <div className=\"flex gap-1 mb-6 border-b border-gray-200 pb-2\">\n <TabButton\n active={subTab === \"handlers\"}\n onClick={() => setSubTab(\"handlers\")}\n label={`Handlers (${handlers.length})`}\n />\n <TabButton\n active={subTab === \"build\"}\n onClick={() => setSubTab(\"build\")}\n label={`Build (${build?.transformStages.length ?? 0} stages)`}\n />\n </div>\n\n {subTab === \"handlers\" && <HandlersSection handlers={handlers} />}\n {subTab === \"build\" && build && <BuildSection build={build} />}\n </>,\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.ReactElement {\n const className = active\n ? \"px-3 py-1.5 text-sm font-medium rounded-t transition-colors bg-white text-sky-600 border border-gray-200 border-b-white -mb-[1px]\"\n : \"px-3 py-1.5 text-sm font-medium rounded-t transition-colors text-gray-500 hover:text-gray-700\";\n\n return (\n <button type=\"button\" onClick={onClick} className={className}>\n {label}\n </button>\n );\n}\n\nfunction HandlersSection({ handlers }: { handlers: Handler[] }): React.ReactElement {\n return (\n <Card title=\"REQUEST HANDLER CHAIN\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500 w-20\">\n Priority\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Handler\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Patterns\n </th>\n </tr>\n </thead>\n <tbody>\n {handlers.map((h, i) => {\n const patterns = h.patterns.map((p) => p.pattern).join(\", \") || \"*\";\n\n return (\n <tr key={i} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2\">\n <span className=\"px-1.5 py-0.5 bg-gray-100 text-gray-600 text-[10px] font-mono rounded\">\n {h.priority}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600 font-medium\">{h.name}</code>\n </td>\n <td className=\"px-3 py-2 text-gray-500 text-xs truncate max-w-xs\">{patterns}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n );\n}\n\nfunction BuildSection({ build }: { build: BuildInfo }): React.ReactElement {\n return (\n <>\n <Card title=\"TRANSFORM PIPELINE\" className=\"mb-4\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500 w-16\">\n Stage\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Name\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Purpose\n </th>\n </tr>\n </thead>\n <tbody>\n {build.transformStages.map((stage) => (\n <tr key={stage.stage} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2 text-gray-500\">{stage.stage}</td>\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600 font-medium\">{stage.name}</code>\n </td>\n <td className=\"px-3 py-2 text-gray-600 text-sm\">{stage.description}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <Card title={`REMARK (${build.remarkPlugins.length})`}>\n <div className=\"divide-y\">\n {build.remarkPlugins.map((p) => (\n <div key={p.name} className=\"px-3 py-2\">\n <code className=\"text-xs text-purple-600 font-medium\">{p.name}</code>\n <div className=\"text-xs text-gray-500 mt-0.5\">{p.description}</div>\n </div>\n ))}\n </div>\n </Card>\n\n <Card title={`REHYPE (${build.rehypePlugins.length})`}>\n <div className=\"divide-y\">\n {build.rehypePlugins.map((p) => (\n <div key={p.name} className=\"px-3 py-2\">\n <code className=\"text-xs text-teal-600 font-medium\">{p.name}</code>\n <div className=\"text-xs text-gray-500 mt-0.5\">{p.description}</div>\n </div>\n ))}\n </div>\n </Card>\n </div>\n </>\n );\n}\n",
|
|
22
|
-
"dashboard/components/
|
|
23
|
-
"dashboard/components/
|
|
7
|
+
"dashboard/components/Card.tsx": "import type { ReactNode } from \"react\";\n\ninterface CardProps {\n title?: string;\n titleRight?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport function Card({\n title,\n titleRight,\n children,\n className = \"\",\n}: CardProps): ReactNode {\n return (\n <div\n className={`bg-white border border-gray-200 rounded-md shadow-sm overflow-hidden ${className}`}\n >\n {title && (\n <div className=\"px-4 py-3 border-b border-gray-100 flex items-center justify-between\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wide text-gray-500\">\n {title}\n </span>\n {titleRight}\n </div>\n )}\n {children}\n </div>\n );\n}\n",
|
|
8
|
+
"dashboard/components/RuntimeTab.tsx": "import { useEffect, useState } from \"react\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"metrics\" | \"memory\";\n\ninterface HeapStats {\n usedHeapSizeMB: number;\n totalHeapSizeMB: number;\n heapSizeLimitMB: number;\n heapUsedPercent: number;\n rss?: number;\n}\n\ninterface CacheStats {\n name: string;\n entries: number;\n maxEntries?: number;\n}\n\ninterface Pressure {\n critical: boolean;\n warning: boolean;\n heapUsedPercent: number;\n}\n\ninterface MemoryData {\n heap: HeapStats;\n caches: CacheStats[];\n pressure: Pressure;\n}\n\nexport function RuntimeTab(): React.JSX.Element {\n const [subTab, setSubTab] = useState<SubTab>(\"metrics\");\n const [metrics, setMetrics] = useState<Record<string, number | unknown>>({});\n const [memory, setMemory] = useState<MemoryData | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n function loadData(): void {\n setLoading(true);\n\n Promise.all([\n fetch(\"/_dev/api/metrics\").then((r) => r.json()),\n fetch(\"/_dev/api/memory\").then((r) => r.json()),\n ])\n .then(([m, mem]) => {\n setMetrics(m.counters || {});\n setMemory(mem);\n setError(null);\n })\n .catch((e) => setError((e as Error).message))\n .finally(() => setLoading(false));\n }\n\n useEffect(() => {\n loadData();\n const interval = setInterval(loadData, 15000);\n return () => clearInterval(interval);\n }, []);\n\n if (!memory && loading) {\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <Card>\n <LoadingState message=\"Loading runtime info...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (!memory && error) {\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n const metricsCount = Object.keys(metrics).length;\n\n return (\n <PageLayout title=\"Runtime\" description=\"Metrics, memory, and caches\">\n <div className=\"flex items-center justify-between mb-6\">\n <div className=\"flex gap-1 border-b border-gray-200 pb-2\">\n <TabButton\n active={subTab === \"metrics\"}\n onClick={() => setSubTab(\"metrics\")}\n label={`Metrics (${metricsCount})`}\n />\n <TabButton\n active={subTab === \"memory\"}\n onClick={() => setSubTab(\"memory\")}\n label={`Memory (${memory?.caches.length ?? 0} caches)`}\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-gray-400\">Auto-refresh: 15s</span>\n <button\n type=\"button\"\n onClick={loadData}\n disabled={loading}\n className=\"px-3 py-1.5 bg-white border border-gray-200 text-sm text-gray-600 rounded hover:bg-gray-50 disabled:opacity-50\"\n >\n Refresh\n </button>\n </div>\n </div>\n\n {subTab === \"metrics\" && <MetricsSection metrics={metrics} />}\n {subTab === \"memory\" && memory && <MemorySection memory={memory} />}\n </PageLayout>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.JSX.Element {\n const className = active\n ? \"bg-white text-sky-600 border border-gray-200 border-b-white -mb-[1px]\"\n : \"text-gray-500 hover:text-gray-700\";\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`px-3 py-1.5 text-sm font-medium rounded-t transition-colors ${className}`}\n >\n {label}\n </button>\n );\n}\n\nfunction MetricsSection({\n metrics,\n}: {\n metrics: Record<string, number | unknown>;\n}): React.JSX.Element {\n const groups: Record<string, Array<{ key: string; val: unknown }>> = {};\n\n for (const [key, val] of Object.entries(metrics)) {\n const [group = \"general\"] = key.split(\".\");\n (groups[group] ??= []).push({ key, val });\n }\n\n if (Object.keys(groups).length === 0) {\n return (\n <Card>\n <div className=\"p-6 text-center text-gray-400\">No metrics recorded yet</div>\n </Card>\n );\n }\n\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {Object.entries(groups)\n .sort()\n .map(([group, items]) => (\n <Card key={group} title={group.toUpperCase()}>\n <table className=\"w-full text-sm\">\n <tbody>\n {items.map(({ key, val }) => (\n <tr key={key} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600\">\n {key.replace(`${group}.`, \"\")}\n </code>\n </td>\n <td className=\"px-3 py-2 text-right font-medium\">\n {typeof val === \"number\" ? val.toLocaleString() : JSON.stringify(val)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n ))}\n </div>\n );\n}\n\nfunction MemorySection({ memory }: { memory: MemoryData }): React.JSX.Element {\n const progressPercent = memory.heap.heapUsedPercent;\n\n let progressColor = \"bg-green-500\";\n if (memory.pressure.critical) progressColor = \"bg-red-500\";\n else if (memory.pressure.warning) progressColor = \"bg-amber-500\";\n\n let pressureColor = \"text-green-600\";\n if (memory.pressure.critical) pressureColor = \"text-red-600\";\n else if (memory.pressure.warning) pressureColor = \"text-amber-600\";\n\n let pressureLabel = \"OK\";\n if (memory.pressure.critical) pressureLabel = \"CRITICAL\";\n else if (memory.pressure.warning) pressureLabel = \"WARNING\";\n\n return (\n <>\n <Card className=\"mb-4\">\n <div className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <span className=\"text-sm font-medium text-gray-700\">Heap Usage</span>\n <span className={`text-sm font-semibold ${pressureColor}`}>{pressureLabel}</span>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"flex-1 h-3 bg-gray-100 rounded-full overflow-hidden\">\n <div\n className={`h-full ${progressColor} transition-all duration-500`}\n style={{ width: `${Math.min(progressPercent, 100)}%` }}\n />\n </div>\n <span className=\"text-sm text-gray-600 w-32 text-right\">\n {memory.heap.usedHeapSizeMB.toFixed(0)} / {memory.heap.heapSizeLimitMB} MB\n </span>\n </div>\n <div className=\"flex gap-6 mt-3 text-xs text-gray-500\">\n <span>RSS: {memory.heap.rss?.toFixed(0) ?? \"—\"} MB</span>\n <span>Total: {memory.heap.totalHeapSizeMB.toFixed(0)} MB</span>\n </div>\n </div>\n </Card>\n\n <Card title={`CACHES (${memory.caches.length})`}>\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Cache\n </th>\n <th className=\"text-right px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Entries\n </th>\n <th className=\"text-right px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Max\n </th>\n </tr>\n </thead>\n <tbody>\n {memory.caches.map((cache) => (\n <tr key={cache.name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2\">\n <code className=\"text-xs text-sky-600\">{cache.name}</code>\n </td>\n <td className=\"px-3 py-2 text-right font-medium\">{cache.entries}</td>\n <td className=\"px-3 py-2 text-right text-gray-500\">\n {cache.maxEntries ?? \"—\"}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </>\n );\n}\n",
|
|
9
|
+
"dashboard/components/Sidebar.tsx": "interface SidebarProps {\n search: string;\n onSearchChange: (value: string) => void;\n subTabs?: { id: string; label: string }[];\n currentSubTab?: string;\n onSubTabChange?: (id: string) => void;\n items: { id: string; label: string; bold?: boolean }[];\n selectedId: string | null;\n onSelect: (id: string) => void;\n emptyMessage: string;\n onBack?: () => void;\n backLabel?: string;\n}\n\nexport function Sidebar({\n search,\n onSearchChange,\n subTabs,\n currentSubTab,\n onSubTabChange,\n items,\n selectedId,\n onSelect,\n emptyMessage,\n onBack,\n backLabel,\n}: SidebarProps): React.ReactElement {\n const showSubTabs = subTabs && onSubTabChange;\n\n return (\n <aside className=\"bg-white border-r border-gray-200 flex flex-col overflow-hidden\">\n <div className=\"p-3 border-b border-gray-100\">\n <input\n type=\"text\"\n value={search}\n onChange={(e) => onSearchChange(e.target.value)}\n placeholder=\"Search...\"\n className=\"w-full px-2.5 py-1.5 bg-gray-50 border border-gray-200 rounded text-sm focus:outline-none focus:border-sky-500 focus:bg-white placeholder:text-gray-400\"\n />\n </div>\n\n {showSubTabs\n ? (\n <div className=\"flex px-3 py-2 gap-0.5 border-b border-gray-100\">\n {subTabs.map((tab) => {\n const isActive = currentSubTab === tab.id;\n\n return (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => onSubTabChange(tab.id)}\n className={`px-2.5 py-1 text-[11px] font-medium rounded transition-colors ${\n isActive\n ? \"bg-sky-50 text-sky-600\"\n : \"text-gray-400 hover:bg-gray-50 hover:text-gray-600\"\n }`}\n >\n {tab.label}\n </button>\n );\n })}\n </div>\n )\n : null}\n\n <div className=\"flex-1 overflow-y-auto p-1.5\">\n {onBack\n ? (\n <button\n type=\"button\"\n onClick={onBack}\n className=\"w-full px-2.5 py-2 text-sm text-sky-600 rounded hover:bg-gray-50 flex items-center gap-1.5 text-left\"\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-sky-500\" />\n {backLabel ?? \".. (back)\"}\n </button>\n )\n : null}\n\n {items.length === 0\n ? (\n <div className=\"flex items-center justify-center py-8 text-gray-400 text-sm\">\n {emptyMessage}\n </div>\n )\n : (\n items.map((item) => {\n const isSelected = selectedId === item.id;\n\n return (\n <button\n type=\"button\"\n key={item.id}\n onClick={() => onSelect(item.id)}\n className={`w-full px-2.5 py-2 text-sm rounded flex items-center gap-1.5 text-left transition-colors ${\n isSelected\n ? \"bg-sky-500 text-white\"\n : \"text-gray-600 hover:bg-gray-50 hover:text-gray-900\"\n } ${item.bold ? \"font-semibold\" : \"\"}`}\n >\n <span\n className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${\n isSelected ? \"bg-white\" : \"bg-current opacity-30\"\n }`}\n />\n {item.label}\n </button>\n );\n })\n )}\n </div>\n </aside>\n );\n}\n",
|
|
10
|
+
"dashboard/components/AITab.tsx": "import { useEffect, useState } from \"react\";\nimport type { Agent, Prompt, Resource, Tool } from \"../App.tsx\";\nimport { MCPTab } from \"./MCPTab.tsx\";\nimport { AgentsTab } from \"./AgentsTab.tsx\";\nimport { WorkflowsTab } from \"./WorkflowsTab.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"mcp\" | \"agents\" | \"workflows\" | \"providers\";\n\ninterface Provider {\n name: string;\n configured: boolean;\n}\n\ninterface NodeInfo {\n id: string;\n type: string;\n agent?: string;\n tool?: string;\n dependsOn?: string[];\n children?: string[];\n message?: string;\n}\n\ninterface WorkflowMetadata {\n id: string;\n description?: string;\n version?: string;\n timeout?: string | number;\n nodeCount: number;\n nodeTypes: string[];\n nodes: NodeInfo[];\n agentRefs: string[];\n toolRefs: string[];\n hasInputSchema: boolean;\n hasOutputSchema: boolean;\n inputSchemaJson?: Record<string, unknown>;\n registeredAt: string;\n}\n\ninterface AITabProps {\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n agents: Agent[];\n}\n\nexport function AITab({ tools, resources, prompts, agents }: AITabProps): React.ReactElement {\n const [subTab, setSubTab] = useState<SubTab>(\"mcp\");\n\n const [providers, setProviders] = useState<Provider[]>([]);\n const [providersLoading, setProvidersLoading] = useState(true);\n const [providersError, setProvidersError] = useState<string | null>(null);\n\n const [workflows, setWorkflows] = useState<WorkflowMetadata[]>([]);\n const [workflowsLoading, setWorkflowsLoading] = useState(true);\n const [workflowsError, setWorkflowsError] = useState<string | null>(null);\n\n useEffect(() => {\n fetch(\"/_dev/api/infrastructure\")\n .then((res) => res.json())\n .then((d) => {\n setProviders(d.providers ?? []);\n setProvidersError(null);\n })\n .catch((e: unknown) => setProvidersError(e instanceof Error ? e.message : String(e)))\n .finally(() => setProvidersLoading(false));\n\n fetch(\"/_dev/api/workflows\")\n .then((res) => res.json())\n .then((d) => {\n setWorkflows(d.workflows ?? []);\n setWorkflowsError(null);\n })\n .catch((e: unknown) => setWorkflowsError(e instanceof Error ? e.message : String(e)))\n .finally(() => setWorkflowsLoading(false));\n }, []);\n\n function navigateToMCP(mcpSubTab: \"tools\" | \"resources\" | \"prompts\", itemId: string): void {\n setSubTab(\"mcp\");\n globalThis.dispatchEvent(\n new CustomEvent(\"mcp-navigate\", { detail: { subTab: mcpSubTab, itemId } }),\n );\n }\n\n const mcpCount = tools.length + resources.length + prompts.length;\n const configuredProvidersCount = providers.filter((p) => p.configured).length;\n\n return (\n <div className=\"min-h-screen\">\n <div className=\"bg-white border-b\">\n <div className=\"px-6 py-4\">\n <div className=\"mb-3\">\n <h1 className=\"text-xl font-semibold text-gray-900\">AI</h1>\n <p className=\"text-sm text-gray-500\">MCP, agents, workflows, and providers</p>\n </div>\n <div className=\"flex gap-1 border border-gray-200 rounded-lg p-1 bg-gray-50 w-fit\">\n <TabButton\n active={subTab === \"mcp\"}\n onClick={() => setSubTab(\"mcp\")}\n label={`MCP (${mcpCount})`}\n />\n <TabButton\n active={subTab === \"agents\"}\n onClick={() => setSubTab(\"agents\")}\n label={`Agents (${agents.length})`}\n />\n <TabButton\n active={subTab === \"workflows\"}\n onClick={() => setSubTab(\"workflows\")}\n label={`Workflows (${workflows.length})`}\n />\n <TabButton\n active={subTab === \"providers\"}\n onClick={() => setSubTab(\"providers\")}\n label={`Providers (${configuredProvidersCount})`}\n />\n </div>\n </div>\n </div>\n\n {subTab === \"mcp\" && <MCPTab tools={tools} resources={resources} prompts={prompts} />}\n\n {subTab === \"agents\" && (\n <AgentsTab agents={agents} tools={tools} onNavigateToMCP={navigateToMCP} />\n )}\n\n {subTab === \"workflows\" && (\n <WorkflowsSection\n loading={workflowsLoading}\n error={workflowsError}\n workflows={workflows}\n agents={agents}\n tools={tools}\n onNavigateToTool={(toolId) => navigateToMCP(\"tools\", toolId)}\n onNavigateToAgent={() => {\n setSubTab(\"agents\");\n // AgentsTab will need to handle this\n }}\n />\n )}\n\n {subTab === \"providers\" && (\n <ProvidersSection providers={providers} loading={providersLoading} error={providersError} />\n )}\n </div>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.ReactElement {\n const className = `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n active ? \"bg-white text-sky-600 shadow-sm\" : \"text-gray-500 hover:text-gray-700\"\n }`;\n\n return (\n <button type=\"button\" onClick={onClick} className={className}>\n {label}\n </button>\n );\n}\n\nfunction WorkflowsSection({\n loading,\n error,\n workflows,\n agents,\n tools,\n onNavigateToAgent,\n onNavigateToTool,\n}: {\n loading: boolean;\n error: string | null;\n workflows: WorkflowMetadata[];\n agents: Agent[];\n tools: Tool[];\n onNavigateToAgent: (agentId: string) => void;\n onNavigateToTool: (toolId: string) => void;\n}): React.ReactElement {\n if (loading) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <LoadingState message=\"Loading workflows...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n return (\n <WorkflowsTab\n workflows={workflows}\n agents={agents}\n tools={tools}\n onNavigateToAgent={onNavigateToAgent}\n onNavigateToTool={onNavigateToTool}\n />\n );\n}\n\nfunction ProvidersSection({\n providers,\n loading,\n error,\n}: {\n providers: Provider[];\n loading: boolean;\n error: string | null;\n}): React.ReactElement {\n if (loading) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <LoadingState message=\"Loading providers...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title=\"\" description=\"\">\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n const configured: Provider[] = [];\n const notConfigured: Provider[] = [];\n\n for (const provider of providers) {\n if (provider.configured) {\n configured.push(provider);\n } else {\n notConfigured.push(provider);\n }\n }\n\n return (\n <div className=\"max-w-7xl mx-auto px-6 py-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 mb-6\">\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{configured.length}</div>\n <div className=\"text-sm text-gray-500\">Configured</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-400\">{notConfigured.length}</div>\n <div className=\"text-sm text-gray-500\">Not Configured</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{providers.length}</div>\n <div className=\"text-sm text-gray-500\">Total Available</div>\n </Card>\n </div>\n\n <Card title=\"AI PROVIDERS\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Provider\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Status\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n API Key\n </th>\n </tr>\n </thead>\n <tbody>\n {providers.map((provider) => {\n const status = provider.configured\n ? {\n className: \"inline-flex items-center gap-1.5 text-green-600\",\n dotClassName: \"w-2 h-2 rounded-full bg-green-500\",\n label: \"Ready\",\n apiKey: <span className=\"text-green-600\">(set)</span>,\n }\n : {\n className: \"inline-flex items-center gap-1.5 text-gray-400\",\n dotClassName: \"w-2 h-2 rounded-full bg-gray-300\",\n label: \"Not configured\",\n apiKey: <span className=\"text-gray-400\">(not set)</span>,\n };\n\n return (\n <tr key={provider.name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-3\">\n <code className=\"text-sm text-sky-600 font-medium\">{provider.name}</code>\n </td>\n <td className=\"px-3 py-3\">\n <span className={status.className}>\n <span className={status.dotClassName} />\n {status.label}\n </span>\n </td>\n <td className=\"px-3 py-3 text-gray-500 text-sm\">{status.apiKey}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n </div>\n );\n}\n",
|
|
11
|
+
"dashboard/components/MCPTab.tsx": "import { useEffect, useState } from \"react\";\nimport type { Prompt, Resource, Tool } from \"../App.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { ActionButton, DetailHeader, EmptyState, ResultBox, TwoColumnLayout } from \"./shared.tsx\";\n\ntype SubTab = \"tools\" | \"resources\" | \"prompts\";\n\ninterface MCPTabProps {\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n}\n\nfunction getItemId(item: Tool | Resource | Prompt): string {\n return \"pattern\" in item ? item.pattern : item.id;\n}\n\nfunction isTool(item: Tool | Resource | Prompt): item is Tool {\n return \"schema\" in item;\n}\n\nfunction isResource(item: Tool | Resource | Prompt): item is Resource {\n return \"pattern\" in item;\n}\n\nfunction isPrompt(item: Tool | Resource | Prompt): item is Prompt {\n return \"id\" in item && !(\"schema\" in item);\n}\n\nexport function MCPTab({ tools, resources, prompts }: MCPTabProps): JSX.Element {\n const [subTab, setSubTab] = useState<SubTab>(\"tools\");\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n\n useEffect(() => {\n function handleNavigate(e: CustomEvent<{ subTab: SubTab; itemId: string }>): void {\n setSubTab(e.detail.subTab);\n setSelectedId(e.detail.itemId);\n }\n\n const listener = handleNavigate as unknown as EventListener;\n globalThis.addEventListener(\"mcp-navigate\", listener);\n return () => globalThis.removeEventListener(\"mcp-navigate\", listener);\n }, []);\n\n let items: Array<Tool | Resource | Prompt>;\n if (subTab === \"tools\") items = tools;\n else if (subTab === \"resources\") items = resources;\n else items = prompts;\n\n const searchLower = search.toLowerCase();\n const filteredItems = items.filter((item) => getItemId(item).toLowerCase().includes(searchLower));\n const selectedItem = items.find((item) => getItemId(item) === selectedId);\n\n function renderDetail(): JSX.Element {\n if (!selectedItem) return <EmptyState message=\"Select an item to inspect\" />;\n\n if (subTab === \"tools\" && isTool(selectedItem)) return <ToolDetail tool={selectedItem} />;\n if (subTab === \"resources\" && isResource(selectedItem)) {\n return <ResourceDetail resource={selectedItem} />;\n }\n if (subTab === \"prompts\" && isPrompt(selectedItem)) {\n return <PromptDetail prompt={selectedItem} />;\n }\n\n return <EmptyState message=\"Select an item to inspect\" />;\n }\n\n const sidebar = (\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n subTabs={[\n { id: \"tools\", label: `Tools (${tools.length})` },\n { id: \"resources\", label: `Resources (${resources.length})` },\n { id: \"prompts\", label: `Prompts (${prompts.length})` },\n ]}\n currentSubTab={subTab}\n onSubTabChange={(id) => {\n setSubTab(id as SubTab);\n setSelectedId(null);\n }}\n items={filteredItems.map((item) => {\n const id = getItemId(item);\n return { id, label: id };\n })}\n selectedId={selectedId}\n onSelect={setSelectedId}\n emptyMessage={`No ${subTab} registered`}\n />\n );\n\n return <TwoColumnLayout sidebar={sidebar}>{renderDetail()}</TwoColumnLayout>;\n}\n\nfunction generateExampleFromSchema(schema: Tool[\"schema\"]): Record<string, unknown> {\n if (!schema?.properties) return {};\n\n const example: Record<string, unknown> = {};\n\n for (const [name, prop] of Object.entries(schema.properties)) {\n const propDef = prop as { type?: string; default?: unknown; enum?: unknown[] };\n\n if (propDef.default !== undefined) {\n example[name] = propDef.default;\n continue;\n }\n\n if (propDef.enum?.length) {\n example[name] = propDef.enum[0];\n continue;\n }\n\n switch (propDef.type) {\n case \"string\":\n example[name] = `example-${name}`;\n break;\n case \"number\":\n case \"integer\":\n example[name] = 1;\n break;\n case \"boolean\":\n example[name] = true;\n break;\n case \"array\":\n example[name] = [];\n break;\n case \"object\":\n example[name] = {};\n break;\n default:\n example[name] = null;\n }\n }\n\n return example;\n}\n\nfunction ToolDetail({ tool }: { tool: Tool }): JSX.Element {\n const [args, setArgs] = useState(() => {\n const example = generateExampleFromSchema(tool.schema);\n return JSON.stringify(example, null, 2);\n });\n const [result, setResult] = useState<\n { success: boolean; data: string; duration?: number } | null\n >(\n null,\n );\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n const example = generateExampleFromSchema(tool.schema);\n setArgs(JSON.stringify(example, null, 2));\n setResult(null);\n }, [tool.id, tool.schema]);\n\n async function execute(): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(args);\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n return;\n }\n\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/execute-tool\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ toolId: tool.id, args: parsed }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({\n success: true,\n data: JSON.stringify(d.result, null, 2),\n duration: d.duration,\n });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader title={tool.id} description={tool.description || \"No description\"} />\n\n {tool.schema?.properties && (\n <Card title=\"Input Schema\" className=\"mb-6\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Name\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Type\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Description\n </th>\n </tr>\n </thead>\n <tbody>\n {Object.entries(tool.schema.properties).map(([name, prop]) => {\n const p = prop as { type?: string; description?: string };\n return (\n <tr key={name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2.5\">\n <code className=\"text-xs text-sky-600 font-medium\">{name}</code>\n {tool.schema?.required?.includes(name) && (\n <span className=\"ml-1.5 px-1 py-0.5 bg-red-50 text-red-600 text-[9px] font-semibold uppercase rounded\">\n required\n </span>\n )}\n </td>\n <td className=\"px-3 py-2.5\">\n <span className=\"px-1.5 py-0.5 bg-gray-100 text-gray-500 text-[10px] font-medium font-mono rounded\">\n {p.type || \"any\"}\n </span>\n </td>\n <td className=\"px-3 py-2.5 text-gray-600\">{p.description || \"-\"}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n )}\n\n <Card title=\"Execute\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n Arguments (JSON)\n </label>\n <textarea\n value={args}\n onChange={(e) => setArgs(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm font-mono focus:outline-none focus:border-sky-500 focus:bg-white min-h-[80px] resize-y\"\n />\n <ActionButton onClick={execute} loading={loading} loadingText=\"Running...\">\n Run\n </ActionButton>\n {result && (\n <ResultBox\n success={result.success}\n label={result.success ? \"Success\" : \"Error\"}\n duration={result.duration}\n >\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n\nfunction ResourceDetail({ resource }: { resource: Resource }): JSX.Element {\n const [uri, setUri] = useState(resource.pattern);\n const [result, setResult] = useState<\n { success: boolean; data: string; duration?: number } | null\n >(\n null,\n );\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n setUri(resource.pattern);\n setResult(null);\n }, [resource.pattern]);\n\n async function read(): Promise<void> {\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/read-resource\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ uri }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({\n success: true,\n data: JSON.stringify(d.data, null, 2),\n duration: d.duration,\n });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader\n title={resource.pattern}\n description={resource.description || \"No description\"}\n />\n\n <Card title=\"Read Resource\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n URI\n </label>\n <input\n type=\"text\"\n value={uri}\n onChange={(e) => setUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm focus:outline-none focus:border-sky-500 focus:bg-white\"\n />\n <ActionButton onClick={read} loading={loading} loadingText=\"Reading...\">\n Read\n </ActionButton>\n {result && (\n <ResultBox\n success={result.success}\n label={result.success ? \"Success\" : \"Error\"}\n duration={result.duration}\n >\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n\nfunction PromptDetail({ prompt }: { prompt: Prompt }): JSX.Element {\n const [variables, setVariables] = useState(\"{}\");\n const [result, setResult] = useState<{ success: boolean; data: string } | null>(null);\n const [loading, setLoading] = useState(false);\n\n async function render(): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(variables);\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n return;\n }\n\n setLoading(true);\n try {\n const res = await fetch(\"/_dev/api/render-prompt\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ promptId: prompt.id, variables: parsed }),\n });\n const d = await res.json();\n\n if (d.error) {\n setResult({ success: false, data: d.error });\n return;\n }\n\n setResult({ success: true, data: d.content });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div>\n <DetailHeader title={prompt.id} description={prompt.description || \"No description\"} />\n\n <Card title=\"Render Prompt\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n Variables (JSON)\n </label>\n <textarea\n value={variables}\n onChange={(e) => setVariables(e.target.value)}\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm font-mono focus:outline-none focus:border-sky-500 focus:bg-white min-h-[80px] resize-y\"\n />\n <ActionButton onClick={render} loading={loading} loadingText=\"Rendering...\">\n Render\n </ActionButton>\n {result && (\n <ResultBox success={result.success} label={result.success ? \"Rendered\" : \"Error\"}>\n {result.data}\n </ResultBox>\n )}\n </div>\n </Card>\n </div>\n );\n}\n",
|
|
24
12
|
"dashboard/components/WorkflowsTab.tsx": "import { useEffect, useState } from \"react\";\nimport type { Agent, Tool } from \"../App.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { ActionButton, DetailHeader, EmptyState, ResultBox, TwoColumnLayout } from \"./shared.tsx\";\n\ninterface NodeInfo {\n id: string;\n type: string;\n agent?: string;\n tool?: string;\n dependsOn?: string[];\n children?: string[];\n message?: string;\n}\n\ninterface WorkflowMetadata {\n id: string;\n description?: string;\n version?: string;\n timeout?: string | number;\n nodeCount: number;\n nodeTypes: string[];\n nodes: NodeInfo[];\n agentRefs: string[];\n toolRefs: string[];\n hasInputSchema: boolean;\n hasOutputSchema: boolean;\n inputSchemaJson?: Record<string, unknown>;\n registeredAt: string;\n}\n\ninterface WorkflowsTabProps {\n workflows: WorkflowMetadata[];\n agents: Agent[];\n tools: Tool[];\n onNavigateToAgent?: (agentId: string) => void;\n onNavigateToTool?: (toolId: string) => void;\n}\n\ninterface NodeTypeStyle {\n badge: string;\n node: string;\n}\n\nconst DEFAULT_NODE_TYPE_STYLE: NodeTypeStyle = {\n badge: \"bg-gray-100 text-gray-600\",\n node: \"bg-gray-50 border-gray-200 text-gray-700\",\n};\n\nconst NODE_TYPE_STYLES: Record<string, NodeTypeStyle> = {\n step: {\n badge: \"bg-blue-50 text-blue-600\",\n node: \"bg-blue-50 border-blue-200 text-blue-700\",\n },\n parallel: {\n badge: \"bg-green-50 text-green-600\",\n node: \"bg-green-50 border-green-200 text-green-700\",\n },\n branch: {\n badge: \"bg-yellow-50 text-yellow-700\",\n node: \"bg-yellow-50 border-yellow-200 text-yellow-700\",\n },\n wait: {\n badge: \"bg-orange-50 text-orange-600\",\n node: \"bg-orange-50 border-orange-200 text-orange-700\",\n },\n};\n\nfunction getNodeTypeStyle(type: string): NodeTypeStyle {\n return NODE_TYPE_STYLES[type] ?? DEFAULT_NODE_TYPE_STYLE;\n}\n\nexport function WorkflowsTab({\n workflows,\n agents,\n tools,\n onNavigateToAgent,\n onNavigateToTool,\n}: WorkflowsTabProps): React.ReactElement {\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n\n const searchLower = search.toLowerCase();\n const filteredWorkflows = workflows.filter((wf) => wf.id.toLowerCase().includes(searchLower));\n const selectedWorkflow = workflows.find((wf) => wf.id === selectedId);\n\n const sidebar = (\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n items={filteredWorkflows.map((wf) => ({\n id: wf.id,\n label: wf.id,\n badge: wf.nodeCount > 0 ? `${wf.nodeCount} nodes` : \"dynamic\",\n }))}\n selectedId={selectedId}\n onSelect={setSelectedId}\n emptyMessage=\"No workflows registered\"\n />\n );\n\n return (\n <TwoColumnLayout sidebar={sidebar}>\n {selectedWorkflow\n ? (\n <WorkflowDetail\n workflow={selectedWorkflow}\n agents={agents}\n tools={tools}\n onNavigateToAgent={onNavigateToAgent}\n onNavigateToTool={onNavigateToTool}\n />\n )\n : <EmptyState message=\"Select a workflow to inspect\" />}\n </TwoColumnLayout>\n );\n}\n\nfunction WorkflowDetail({\n workflow,\n agents,\n tools,\n onNavigateToAgent,\n onNavigateToTool,\n}: {\n workflow: WorkflowMetadata;\n agents: Agent[];\n tools: Tool[];\n onNavigateToAgent?: (agentId: string) => void;\n onNavigateToTool?: (toolId: string) => void;\n}): React.ReactElement {\n const agentMap = new Map(agents.map((a) => [a.id, a]));\n const toolMap = new Map(tools.map((t) => [t.id, t]));\n\n function getNodeTypeBadgeClass(type: string): string {\n return getNodeTypeStyle(type).badge;\n }\n\n return (\n <div>\n <DetailHeader title={workflow.id} description={workflow.description || \"No description\"} />\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 mb-6\">\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{workflow.nodeCount || \"dynamic\"}</div>\n <div className=\"text-xs text-gray-500 uppercase tracking-wide\">Nodes</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{workflow.agentRefs.length}</div>\n <div className=\"text-xs text-gray-500 uppercase tracking-wide\">Agents</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-2xl font-bold text-gray-900\">{workflow.toolRefs.length}</div>\n <div className=\"text-xs text-gray-500 uppercase tracking-wide\">Tools</div>\n </Card>\n <Card className=\"p-4\">\n <div className=\"text-lg font-bold text-gray-900\">{workflow.timeout || \"-\"}</div>\n <div className=\"text-xs text-gray-500 uppercase tracking-wide\">Timeout</div>\n </Card>\n </div>\n\n {workflow.nodeTypes.length > 0\n ? (\n <Card title=\"Node Types\" className=\"mb-6\">\n <div className=\"p-4 flex flex-wrap gap-2\">\n {workflow.nodeTypes.map((type) => (\n <span\n key={type}\n className=\"px-2.5 py-1 bg-purple-50 text-purple-700 text-sm font-medium rounded-full\"\n >\n {type}\n </span>\n ))}\n </div>\n </Card>\n )\n : null}\n\n <Card title=\"Workflow Steps\" className=\"mb-6\">\n {workflow.nodes.length > 0\n ? (\n <div className=\"divide-y\">\n {workflow.nodes.map((node, index) => {\n const agentId = node.agent;\n const toolId = node.tool;\n\n return (\n <div key={node.id} className=\"p-4\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex-shrink-0 w-8 h-8 rounded-full bg-sky-100 text-sky-700 flex items-center justify-center text-sm font-medium\">\n {index + 1}\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <code className=\"text-sm font-medium text-gray-900\">{node.id}</code>\n <span\n className={`px-1.5 py-0.5 text-[10px] font-semibold uppercase rounded ${\n getNodeTypeBadgeClass(\n node.type,\n )\n }`}\n >\n {node.type}\n </span>\n </div>\n\n <div className=\"text-sm text-gray-600 space-y-1\">\n {agentId\n ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-gray-400\">Agent:</span>\n {agentMap.has(agentId)\n ? (\n <button\n type=\"button\"\n onClick={() => onNavigateToAgent?.(agentId)}\n className=\"text-sky-600 hover:underline\"\n >\n {agentId}\n </button>\n )\n : <span className=\"text-red-500\">{agentId} (not found)</span>}\n </div>\n )\n : null}\n\n {toolId\n ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-gray-400\">Tool:</span>\n {toolMap.has(toolId)\n ? (\n <button\n type=\"button\"\n onClick={() => onNavigateToTool?.(toolId)}\n className=\"text-sky-600 hover:underline\"\n >\n {toolId}\n </button>\n )\n : <span className=\"text-red-500\">{toolId} (not found)</span>}\n </div>\n )\n : null}\n\n {node.dependsOn?.length\n ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-gray-400\">Depends on:</span>\n <span className=\"text-gray-600\">{node.dependsOn.join(\", \")}</span>\n </div>\n )\n : null}\n\n {node.children?.length\n ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-gray-400\">Contains:</span>\n <span className=\"text-gray-600\">{node.children.join(\", \")}</span>\n </div>\n )\n : null}\n\n {node.message\n ? (\n <div className=\"flex items-start gap-2\">\n <span className=\"text-gray-400\">Message:</span>\n <span className=\"text-gray-600 italic\">\"{node.message}\"</span>\n </div>\n )\n : null}\n </div>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )\n : (\n <div className=\"p-8 text-center text-gray-400\">\n <div className=\"mb-2\">Dynamic steps</div>\n <div className=\"text-sm text-gray-500\">\n This workflow uses a function to generate steps at runtime\n </div>\n </div>\n )}\n </Card>\n\n {workflow.nodes.length > 0\n ? (\n <Card title=\"Workflow Flow\" className=\"mb-6\">\n <div className=\"p-4 font-mono text-sm bg-gray-50 overflow-x-auto\">\n <WorkflowDAG nodes={workflow.nodes} />\n </div>\n </Card>\n )\n : null}\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <Card title={`Agents Used (${workflow.agentRefs.length})`}>\n {workflow.agentRefs.length > 0\n ? (\n <div className=\"divide-y\">\n {workflow.agentRefs.map((agentId) => {\n const agent = agentMap.get(agentId);\n return (\n <div key={agentId} className=\"p-3 flex items-center justify-between\">\n <div>\n <button\n type=\"button\"\n onClick={() => onNavigateToAgent?.(agentId)}\n className=\"text-sm text-sky-600 hover:underline font-medium\"\n >\n {agentId}\n </button>\n {agent ? <div className=\"text-xs text-gray-500\">{agent.model}</div> : null}\n </div>\n {agent\n ? <span className=\"w-2 h-2 rounded-full bg-green-500\" title=\"Found\" />\n : <span className=\"w-2 h-2 rounded-full bg-red-500\" title=\"Not found\" />}\n </div>\n );\n })}\n </div>\n )\n : <div className=\"p-4 text-center text-gray-400 text-sm\">No agents referenced</div>}\n </Card>\n\n <Card title={`Tools Used (${workflow.toolRefs.length})`}>\n {workflow.toolRefs.length > 0\n ? (\n <div className=\"divide-y\">\n {workflow.toolRefs.map((toolId) => {\n const tool = toolMap.get(toolId);\n return (\n <div key={toolId} className=\"p-3 flex items-center justify-between\">\n <div>\n <button\n type=\"button\"\n onClick={() => onNavigateToTool?.(toolId)}\n className=\"text-sm text-sky-600 hover:underline font-medium\"\n >\n {toolId}\n </button>\n {tool\n ? (\n <div className=\"text-xs text-gray-500 truncate max-w-[200px]\">\n {tool.description}\n </div>\n )\n : null}\n </div>\n {tool\n ? <span className=\"w-2 h-2 rounded-full bg-green-500\" title=\"Found\" />\n : <span className=\"w-2 h-2 rounded-full bg-red-500\" title=\"Not found\" />}\n </div>\n );\n })}\n </div>\n )\n : <div className=\"p-4 text-center text-gray-400 text-sm\">No tools referenced</div>}\n </Card>\n </div>\n\n <Card title=\"Schema\" className=\"mt-6\">\n <div className=\"p-4 flex gap-4\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-3 h-3 rounded-full ${\n workflow.hasInputSchema ? \"bg-green-500\" : \"bg-gray-300\"\n }`}\n />\n <span className=\"text-sm text-gray-600\">Input Schema</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-3 h-3 rounded-full ${\n workflow.hasOutputSchema ? \"bg-green-500\" : \"bg-gray-300\"\n }`}\n />\n <span className=\"text-sm text-gray-600\">Output Schema</span>\n </div>\n </div>\n </Card>\n\n <WorkflowExecutor workflowId={workflow.id} inputSchema={workflow.inputSchemaJson} />\n </div>\n );\n}\n\nfunction generateExampleFromSchema(schema?: Record<string, unknown>): Record<string, unknown> {\n if (!schema || schema.type !== \"object\") return {};\n\n const properties = schema.properties as Record<string, Record<string, unknown>> | undefined;\n if (!properties) return {};\n\n const example: Record<string, unknown> = {};\n\n for (const [name, prop] of Object.entries(properties)) {\n if (prop.default !== undefined) {\n example[name] = prop.default;\n continue;\n }\n\n if (Array.isArray(prop.enum) && prop.enum.length > 0) {\n example[name] = prop.enum[0];\n continue;\n }\n\n const nameLower = name.toLowerCase();\n\n switch (prop.type) {\n case \"string\":\n if (nameLower.includes(\"url\") || nameLower.includes(\"uri\")) {\n example[name] = \"https://example.com/data\";\n } else if (nameLower.includes(\"email\")) {\n example[name] = \"user@example.com\";\n } else {\n example[name] = `example-${name}`;\n }\n break;\n case \"number\":\n case \"integer\":\n example[name] = 1;\n break;\n case \"boolean\":\n example[name] = true;\n break;\n case \"array\":\n example[name] = [];\n break;\n case \"object\":\n example[name] = generateExampleFromSchema(prop as Record<string, unknown>);\n break;\n default:\n example[name] = null;\n }\n }\n\n return example;\n}\n\nfunction WorkflowExecutor({\n workflowId,\n inputSchema,\n}: {\n workflowId: string;\n inputSchema?: Record<string, unknown>;\n}): React.ReactElement {\n const [input, setInput] = useState<string>(() => {\n const example = generateExampleFromSchema(inputSchema);\n return JSON.stringify(example, null, 2);\n });\n\n const [result, setResult] = useState<\n {\n success: boolean;\n data: string;\n duration?: number;\n runId?: string;\n status?: string;\n } | null\n >(null);\n\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n const example = generateExampleFromSchema(inputSchema);\n setInput(JSON.stringify(example, null, 2));\n setResult(null);\n }, [workflowId, inputSchema]);\n\n async function startWorkflow(): Promise<void> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input);\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n return;\n }\n\n setLoading(true);\n setResult(null);\n\n try {\n const res = await fetch(\"/_dev/api/start-workflow\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ workflowId, input: parsed }),\n });\n\n const d = await res.json();\n\n if (d.error) {\n setResult({\n success: false,\n data: d.error + (d.hint ? `\\n\\n${d.hint}` : \"\"),\n });\n return;\n }\n\n setResult({\n success: true,\n data: JSON.stringify(d.result, null, 2),\n duration: d.duration,\n runId: d.runId,\n status: d.status,\n });\n } catch (e) {\n setResult({ success: false, data: (e as Error).message });\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <Card title=\"Start Workflow\" className=\"mt-6\">\n <div className=\"p-4\">\n <label className=\"block text-xs font-medium uppercase tracking-wide text-gray-600 mb-1\">\n Input (JSON)\n </label>\n <textarea\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder='{\"topic\": \"AI Safety\", \"requiresApproval\": false}'\n className=\"w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded text-sm font-mono focus:outline-none focus:border-sky-500 focus:bg-white min-h-[80px] resize-y\"\n />\n <ActionButton onClick={startWorkflow} loading={loading} loadingText=\"Running...\">\n Start\n </ActionButton>\n\n {result\n ? (\n <div>\n {result.runId\n ? (\n <div className=\"mt-2 text-xs text-gray-500\">\n Run ID: <code className=\"text-sky-600\">{result.runId}</code>\n {result.status\n ? (\n <span className=\"ml-2\">\n Status:{\" \"}\n <span\n className={result.status === \"completed\"\n ? \"text-green-600\"\n : \"text-yellow-600\"}\n >\n {result.status}\n </span>\n </span>\n )\n : null}\n </div>\n )\n : null}\n\n <ResultBox\n success={result.success}\n label={result.success ? \"Result\" : \"Error\"}\n duration={result.duration}\n >\n {result.data}\n </ResultBox>\n </div>\n )\n : null}\n </div>\n </Card>\n );\n}\n\nfunction WorkflowDAG({ nodes }: { nodes: NodeInfo[] }): React.ReactElement {\n if (nodes.length === 0) {\n return (\n <div className=\"text-gray-400 text-center py-4\">(dynamic - steps generated at runtime)</div>\n );\n }\n\n const dependsOnMap = new Map<string, string[]>();\n const dependentsMap = new Map<string, string[]>();\n\n for (const node of nodes) {\n dependsOnMap.set(node.id, node.dependsOn || []);\n dependentsMap.set(node.id, []);\n }\n\n for (const node of nodes) {\n for (const dep of node.dependsOn || []) {\n dependentsMap.get(dep)?.push(node.id);\n }\n }\n\n const rootNodes = nodes.filter((n) => !n.dependsOn?.length);\n\n const levels = new Map<string, number>();\n const queue = rootNodes.map((n) => n.id);\n\n for (const id of queue) levels.set(id, 0);\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current) continue;\n\n for (const dependent of dependentsMap.get(current) || []) {\n const deps = dependsOnMap.get(dependent) || [];\n if (levels.has(dependent) || !deps.every((d) => levels.has(d))) continue;\n\n const maxDepLevel = Math.max(...deps.map((d) => levels.get(d) ?? 0));\n levels.set(dependent, maxDepLevel + 1);\n queue.push(dependent);\n }\n }\n\n const levelGroups = new Map<number, NodeInfo[]>();\n for (const node of nodes) {\n const level = levels.get(node.id) ?? 0;\n const group = levelGroups.get(level);\n if (group) group.push(node);\n else levelGroups.set(level, [node]);\n }\n\n const maxLevel = Math.max(...Array.from(levels.values()), 0);\n\n function getNodeStyle(type: string): string {\n return getNodeTypeStyle(type).node;\n }\n\n return (\n <div className=\"flex flex-col items-center gap-2\">\n {Array.from({ length: maxLevel + 1 }, (_, level) => {\n const nodesAtLevel = levelGroups.get(level) || [];\n const isParallel = nodesAtLevel.length > 1;\n\n return (\n <div key={level}>\n {level > 0\n ? (\n <div className=\"flex justify-center py-1\">\n <svg width=\"24\" height=\"20\" viewBox=\"0 0 24 20\" className=\"text-gray-400\">\n <path\n d=\"M12 0 L12 14 M6 10 L12 16 L18 10\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n fill=\"none\"\n />\n </svg>\n </div>\n )\n : null}\n\n <div className={`flex gap-3 ${isParallel ? \"items-start\" : \"justify-center\"}`}>\n {isParallel\n ? (\n <div className=\"flex items-center text-gray-400 text-xs font-mono self-center\">\n [\n </div>\n )\n : null}\n\n {nodesAtLevel.map((node, idx) => (\n <div key={node.id} className=\"flex items-center gap-2\">\n <div\n className={`px-3 py-2 rounded-lg border text-sm font-medium ${\n getNodeStyle(\n node.type,\n )\n }`}\n >\n <div className=\"font-semibold\">{node.id}</div>\n {node.tool\n ? <div className=\"text-xs opacity-75 mt-0.5\">tool: {node.tool}</div>\n : null}\n {node.agent\n ? <div className=\"text-xs opacity-75 mt-0.5\">agent: {node.agent}</div>\n : null}\n </div>\n\n {isParallel && idx < nodesAtLevel.length - 1\n ? <div className=\"text-gray-400 text-xs font-mono\">,</div>\n : null}\n </div>\n ))}\n\n {isParallel\n ? (\n <div className=\"flex items-center text-gray-400 text-xs font-mono self-center\">\n ]\n </div>\n )\n : null}\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n",
|
|
13
|
+
"dashboard/components/TabNav.tsx": "import type { TabId } from \"../App.tsx\";\n\ninterface TabNavProps {\n tabs: { id: TabId; label: string }[];\n currentTab: TabId;\n onTabChange: (tab: TabId) => void;\n}\n\nexport function TabNav({ tabs, currentTab, onTabChange }: TabNavProps): JSX.Element {\n return (\n <nav className=\"bg-white border-b border-gray-200 px-5 flex gap-0.5\">\n {tabs.map((tab) => {\n const isActive = currentTab === tab.id;\n\n let className = \"px-3 py-2.5 text-xs font-medium border-b-2 -mb-px transition-colors \";\n if (isActive) {\n className += \"text-sky-500 border-sky-500\";\n } else {\n className += \"text-gray-400 border-transparent hover:text-gray-600\";\n }\n\n return (\n <button\n key={tab.id}\n type=\"button\"\n className={className}\n onClick={() => onTabChange(tab.id)}\n >\n {tab.label}\n </button>\n );\n })}\n </nav>\n );\n}\n",
|
|
14
|
+
"dashboard/components/AgentsTab.tsx": "import { useMemo, useState } from \"react\";\nimport type { Agent, Tool } from \"../App.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { DetailHeader, EmptyState, TwoColumnLayout } from \"./shared.tsx\";\n\ninterface AgentsTabProps {\n agents: Agent[];\n tools: Tool[];\n onNavigateToMCP: (subTab: \"tools\" | \"resources\" | \"prompts\", itemId: string) => void;\n}\n\nexport function AgentsTab({ agents, tools, onNavigateToMCP }: AgentsTabProps): React.JSX.Element {\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n\n const filteredAgents = useMemo(() => {\n const searchLower = search.toLowerCase();\n return agents.filter((a) => a.id.toLowerCase().includes(searchLower));\n }, [agents, search]);\n\n const selectedAgent = useMemo(() => {\n if (!selectedId) return undefined;\n return agents.find((a) => a.id === selectedId);\n }, [agents, selectedId]);\n\n return (\n <TwoColumnLayout\n sidebar={\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n items={filteredAgents.map((a) => ({ id: a.id, label: a.id }))}\n selectedId={selectedId}\n onSelect={setSelectedId}\n emptyMessage=\"No agents registered\"\n />\n }\n >\n {selectedAgent\n ? <AgentDetail agent={selectedAgent} tools={tools} onNavigateToMCP={onNavigateToMCP} />\n : <EmptyState message=\"Select an agent\" />}\n </TwoColumnLayout>\n );\n}\n\ninterface AgentDetailProps {\n agent: Agent;\n tools: Tool[];\n onNavigateToMCP: (subTab: \"tools\" | \"resources\" | \"prompts\", itemId: string) => void;\n}\n\nfunction AgentDetail({ agent, tools, onNavigateToMCP }: AgentDetailProps): React.JSX.Element {\n const toolIds = Object.keys(agent.tools ?? {}).filter((k) => agent.tools?.[k]);\n\n return (\n <div>\n <DetailHeader title={agent.id} description={agent.description ?? \"No description\"} />\n\n <Card title=\"Configuration\" className=\"mb-6\">\n <table className=\"w-full text-sm\">\n <tbody>\n <ConfigRow label=\"Model\" value={agent.model} />\n <ConfigRow label=\"Streaming\" value={agent.streaming ? \"Enabled\" : \"Disabled\"} />\n <ConfigRow label=\"Max Steps\" value={agent.maxSteps ?? \"Default\"} last />\n </tbody>\n </table>\n </Card>\n\n {agent.system\n ? (\n <Card title=\"System Prompt\" className=\"mb-6\">\n <div className=\"p-4\">\n <pre className=\"text-sm text-gray-700 whitespace-pre-wrap font-mono bg-gray-50 p-3 rounded-lg max-h-64 overflow-auto\">\n {agent.system}\n </pre>\n </div>\n </Card>\n )\n : null}\n\n <Card title={`Tools (${toolIds.length})`} className=\"mb-6\">\n <div className=\"p-3\">\n {toolIds.length === 0\n ? <span className=\"text-sm text-gray-400\">No tools configured</span>\n : (\n <div className=\"flex flex-wrap gap-1.5\">\n {toolIds.map((id) => {\n const exists = tools.some((t) => t.id === id);\n const onClick = exists ? () => onNavigateToMCP(\"tools\", id) : undefined;\n\n return <Badge key={id} label={id} exists={exists} onClick={onClick} />;\n })}\n </div>\n )}\n </div>\n </Card>\n\n {agent.memory\n ? (\n <Card title=\"Memory\">\n <table className=\"w-full text-sm\">\n <tbody>\n <ConfigRow label=\"Type\" value={agent.memory.type || \"-\"} />\n <ConfigRow label=\"Max Tokens\" value={agent.memory.maxTokens || \"-\"} last />\n </tbody>\n </table>\n </Card>\n )\n : null}\n </div>\n );\n}\n\nfunction ConfigRow({\n label,\n value,\n last,\n}: {\n label: string;\n value: string | number;\n last?: boolean;\n}): React.JSX.Element {\n return (\n <tr className={last ? \"\" : \"border-b\"}>\n <td className=\"px-3 py-2.5 w-28 font-medium text-gray-600\">{label}</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{value}</td>\n </tr>\n );\n}\n\nfunction Badge({\n label,\n exists,\n onClick,\n}: {\n label: string;\n exists: boolean;\n onClick?: () => void;\n}): React.JSX.Element {\n const base = \"px-2.5 py-1 text-xs font-medium rounded transition-colors\";\n\n if (!exists) {\n return <span className={`${base} bg-gray-100 text-gray-400`}>{label} (not found)</span>;\n }\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`${base} bg-sky-50 text-sky-600 hover:bg-sky-500 hover:text-white cursor-pointer`}\n >\n {label}\n </button>\n );\n}\n",
|
|
25
15
|
"dashboard/components/Header.tsx": "export function Header(): JSX.Element {\n return (\n <header className=\"bg-white border-b border-gray-200 px-5 h-12 flex items-center sticky top-0 z-50\">\n <div className=\"flex items-center gap-2\">\n <div className=\"w-6 h-6 bg-sky-500 rounded flex items-center justify-center text-white font-bold text-xs\">\n V\n </div>\n <span className=\"font-semibold text-sm tracking-tight\">Dev</span>\n </div>\n </header>\n );\n}\n",
|
|
26
|
-
"dashboard/components/
|
|
27
|
-
"dashboard/components/
|
|
16
|
+
"dashboard/components/FilesTab.tsx": "import { useEffect, useMemo, useState } from \"react\";\nimport type { FileItem } from \"../App.tsx\";\nimport { Card } from \"./Card.tsx\";\nimport { Sidebar } from \"./Sidebar.tsx\";\nimport { DetailHeader, ErrorState, formatSize, LoadingState, TwoColumnLayout } from \"./shared.tsx\";\n\nexport function FilesTab(): React.ReactElement {\n const [currentPath, setCurrentPath] = useState(\"\");\n const [files, setFiles] = useState<FileItem[]>([]);\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n const [search, setSearch] = useState(\"\");\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function loadFiles(path: string): Promise<void> {\n setLoading(true);\n try {\n const res = await fetch(`/_dev/api/files?path=${encodeURIComponent(path)}`);\n const data = await res.json();\n setFiles(data.files ?? []);\n } catch (e) {\n console.error(\"Failed to load files:\", e);\n setFiles([]);\n } finally {\n setLoading(false);\n }\n }\n\n void loadFiles(currentPath);\n }, [currentPath]);\n\n const filteredFiles = useMemo((): FileItem[] => {\n if (!search) return files;\n const q = search.toLowerCase();\n return files.filter((f) => f.name.toLowerCase().includes(q));\n }, [files, search]);\n\n function handleSelect(id: string): void {\n const file = files.find((f) => f.path === id);\n if (!file) return;\n\n if (file.type !== \"directory\") {\n setSelectedFile(file.path);\n return;\n }\n\n setCurrentPath(file.path);\n setSelectedFile(null);\n setSearch(\"\");\n }\n\n function handleBack(): void {\n setCurrentPath(currentPath.split(\"/\").slice(0, -1).join(\"/\"));\n setSelectedFile(null);\n }\n\n const sidebar = (\n <Sidebar\n search={search}\n onSearchChange={setSearch}\n items={filteredFiles.map((f) => ({\n id: f.path,\n label: f.type === \"directory\" ? `${f.name}/` : f.name,\n bold: f.type === \"directory\",\n }))}\n selectedId={selectedFile}\n onSelect={handleSelect}\n emptyMessage={loading ? \"Loading...\" : \"No files found\"}\n onBack={currentPath && !search ? handleBack : undefined}\n />\n );\n\n return (\n <TwoColumnLayout sidebar={sidebar}>\n {selectedFile\n ? <FileDetail path={selectedFile} />\n : <DirectoryInfo path={currentPath} fileCount={files.length} />}\n </TwoColumnLayout>\n );\n}\n\nfunction DirectoryInfo(\n { path, fileCount }: { path: string; fileCount: number },\n): React.ReactElement {\n return (\n <div>\n <DetailHeader title={path || \"Project Root\"} description={`${fileCount} items`} />\n <Card>\n <div className=\"p-4 text-sm text-gray-400\">Select a file to view its contents</div>\n </Card>\n </div>\n );\n}\n\ninterface FileContent {\n content?: string;\n lines?: number;\n size?: number;\n isBinary?: boolean;\n message?: string;\n error?: string;\n}\n\nfunction FileDetail({ path }: { path: string }): React.ReactElement {\n const [content, setContent] = useState<FileContent | null>(null);\n const [loading, setLoading] = useState(true);\n\n const filename = useMemo((): string => path.split(\"/\").pop() ?? \"\", [path]);\n const ext = useMemo((): string => path.split(\".\").pop()?.toLowerCase() ?? \"\", [path]);\n\n useEffect(() => {\n setLoading(true);\n\n fetch(`/_dev/api/file-content?path=${encodeURIComponent(path)}`)\n .then((res) => res.json())\n .then(setContent)\n .catch((e: unknown) => {\n const message = e instanceof Error ? e.message : String(e);\n setContent({ error: message });\n })\n .finally(() => setLoading(false));\n }, [path]);\n\n const title = !loading && content?.content !== undefined ? \"Contents\" : undefined;\n const titleRight = content?.lines\n ? <span className=\"text-[11px] text-gray-400 font-normal\">{content.lines} lines</span>\n : undefined;\n\n let body: React.ReactNode = null;\n if (loading) {\n body = <LoadingState message=\"Loading file contents...\" />;\n } else if (content?.error) {\n body = <ErrorState error={content.error} />;\n } else if (content?.isBinary) {\n body = <div className=\"p-4 text-sm text-gray-400\">{content.message}</div>;\n } else if (content?.content !== undefined) {\n body = (\n <pre className=\"p-3 text-xs font-mono text-gray-600 overflow-auto max-h-[500px] whitespace-pre-wrap bg-gray-50\">\n {content.content}\n </pre>\n );\n }\n\n return (\n <div>\n <DetailHeader title={filename} description={path} />\n\n <Card title=\"File Info\" className=\"mb-4\">\n <table className=\"w-full text-sm\">\n <tbody>\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 w-24 font-medium text-gray-600\">Path</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{path}</td>\n </tr>\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Extension</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{ext}</td>\n </tr>\n {content?.lines\n ? (\n <tr className=\"border-b\">\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Lines</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{content.lines}</td>\n </tr>\n )\n : null}\n {content?.size\n ? (\n <tr>\n <td className=\"px-3 py-2.5 font-medium text-gray-600\">Size</td>\n <td className=\"px-3 py-2.5 text-gray-900\">{formatSize(content.size)}</td>\n </tr>\n )\n : null}\n </tbody>\n </table>\n </Card>\n\n <Card title={title} titleRight={titleRight}>\n {body}\n </Card>\n </div>\n );\n}\n",
|
|
17
|
+
"dashboard/components/ConfigTab.tsx": "import { useEffect, useState } from \"react\";\nimport { Card } from \"./Card.tsx\";\nimport { ErrorState, LoadingState, PageLayout } from \"./shared.tsx\";\n\ntype SubTab = \"settings\" | \"debug\";\n\ninterface FeatureFlag {\n name: string;\n value: boolean;\n source: string;\n}\n\ninterface ConfigData {\n featureFlags: FeatureFlag[];\n environment: Record<string, string | boolean>;\n projectDir: string;\n mode: string;\n timestamp: string;\n}\n\nexport function ConfigTab(): React.JSX.Element {\n const [subTab, setSubTab] = useState<SubTab>(\"settings\");\n const [data, setData] = useState<ConfigData | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [debugContent, setDebugContent] = useState<string>(\"\");\n const [debugLoading, setDebugLoading] = useState<boolean>(true);\n\n function loadConfig(): void {\n setLoading(true);\n\n fetch(\"/_dev/api/config\")\n .then((res) => res.json())\n .then((d) => {\n setData(d);\n setError(null);\n })\n .catch((e: unknown) => {\n setError(e instanceof Error ? e.message : String(e));\n })\n .finally(() => setLoading(false));\n }\n\n function loadDebug(): void {\n setDebugLoading(true);\n\n fetch(\"/_vf_debug/context\")\n .then((res) => res.json())\n .then((d) => setDebugContent(JSON.stringify(d, null, 2)))\n .catch((e: unknown) => {\n const message = e instanceof Error ? e.message : String(e);\n setDebugContent(`Error: ${message}`);\n })\n .finally(() => setDebugLoading(false));\n }\n\n function refresh(): void {\n loadConfig();\n loadDebug();\n }\n\n useEffect(() => {\n refresh();\n }, []);\n\n const layoutProps = {\n title: \"Config\",\n description: \"Configuration, environment, and runtime context\",\n };\n\n if (loading && !data) {\n return (\n <PageLayout {...layoutProps}>\n <Card>\n <LoadingState message=\"Loading configuration...\" />\n </Card>\n </PageLayout>\n );\n }\n\n if (error && !data) {\n return (\n <PageLayout {...layoutProps}>\n <Card>\n <ErrorState error={error} />\n </Card>\n </PageLayout>\n );\n }\n\n let content: React.JSX.Element;\n if (subTab === \"settings\") {\n content = <SettingsSection data={data} />;\n } else {\n content = <DebugSection content={debugContent} loading={debugLoading} />;\n }\n\n return (\n <PageLayout {...layoutProps}>\n <div className=\"flex items-center justify-between mb-6\">\n <div className=\"flex gap-1 border-b border-gray-200 pb-2\">\n <TabButton\n active={subTab === \"settings\"}\n onClick={() => setSubTab(\"settings\")}\n label=\"Settings\"\n />\n <TabButton active={subTab === \"debug\"} onClick={() => setSubTab(\"debug\")} label=\"Debug\" />\n </div>\n\n <button\n type=\"button\"\n onClick={refresh}\n disabled={loading || debugLoading}\n className=\"px-3 py-1.5 bg-white border border-gray-200 text-sm text-gray-600 rounded hover:bg-gray-50 disabled:opacity-50\"\n >\n Refresh\n </button>\n </div>\n\n {content}\n </PageLayout>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n}): React.JSX.Element {\n const className = active\n ? \"px-3 py-1.5 text-sm font-medium rounded-t transition-colors bg-white text-sky-600 border border-gray-200 border-b-white -mb-[1px]\"\n : \"px-3 py-1.5 text-sm font-medium rounded-t transition-colors text-gray-500 hover:text-gray-700\";\n\n return (\n <button type=\"button\" onClick={onClick} className={className}>\n {label}\n </button>\n );\n}\n\nfunction SettingsSection({ data }: { data: ConfigData | null }): React.JSX.Element {\n return (\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <Card className=\"p-4\">\n <div className=\"text-xs text-gray-500 uppercase mb-1\">Mode</div>\n <div className=\"text-lg font-semibold text-gray-900\">{data?.mode || \"unknown\"}</div>\n </Card>\n\n <Card className=\"p-4\">\n <div className=\"text-xs text-gray-500 uppercase mb-1\">Project Directory</div>\n <code className=\"text-sm text-sky-600 break-all\">{data?.projectDir}</code>\n </Card>\n </div>\n\n <Card title=\"FEATURE FLAGS\" className=\"mb-4\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Flag\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Value\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Source\n </th>\n </tr>\n </thead>\n <tbody>\n {data?.featureFlags.map((flag) => {\n const valueEl = flag.value\n ? (\n <span className=\"inline-flex items-center gap-1.5 text-green-600\">\n <span className=\"w-2 h-2 rounded-full bg-green-500\" />\n Enabled\n </span>\n )\n : (\n <span className=\"inline-flex items-center gap-1.5 text-gray-400\">\n <span className=\"w-2 h-2 rounded-full bg-gray-300\" />\n Disabled\n </span>\n );\n\n return (\n <tr key={flag.name} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2.5\">\n <code className=\"text-xs text-sky-600 font-medium\">{flag.name}</code>\n </td>\n <td className=\"px-3 py-2.5\">{valueEl}</td>\n <td className=\"px-3 py-2.5\">\n <code className=\"text-xs text-gray-500\">{flag.source}</code>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n\n <Card title=\"ENVIRONMENT VARIABLES\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-gray-50 border-b\">\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Variable\n </th>\n <th className=\"text-left px-3 py-2 text-[10px] font-semibold uppercase tracking-wide text-gray-500\">\n Value\n </th>\n </tr>\n </thead>\n <tbody>\n {Object.entries(data?.environment ?? {}).map(([key, value]) => {\n let className = \"text-gray-900 text-sm\";\n\n if (typeof value === \"string\") {\n if (value.includes(\"(set)\")) {\n className = \"text-green-600 text-sm\";\n } else if (value.includes(\"(not set)\")) {\n className = \"text-gray-400 text-sm\";\n }\n }\n\n return (\n <tr key={key} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2.5\">\n <code className=\"text-xs text-sky-600 font-medium\">{key}</code>\n </td>\n <td className=\"px-3 py-2.5\">\n <span className={className}>{String(value)}</span>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Card>\n </>\n );\n}\n\nfunction DebugSection(\n { content, loading }: { content: string; loading: boolean },\n): React.JSX.Element {\n if (loading) {\n return (\n <Card title=\"RUNTIME CONTEXT\">\n <LoadingState message=\"Loading debug context...\" />\n </Card>\n );\n }\n\n return (\n <Card title=\"RUNTIME CONTEXT\">\n <pre className=\"p-4 text-xs font-mono text-gray-600 overflow-auto max-h-[500px] whitespace-pre-wrap bg-gray-50\">\n {content}\n </pre>\n </Card>\n );\n}\n",
|
|
18
|
+
"dashboard/components/APITab.tsx": "export function APITab(): JSX.Element {\n return (\n <div className=\"h-[calc(100vh-89px)] overflow-y-auto\">\n <main className=\"bg-gray-50 p-5\">\n <div className=\"mb-6\">\n <h1 className=\"text-lg font-semibold tracking-tight\">API Documentation</h1>\n <p className=\"text-sm text-gray-500\">\n Interactive API docs powered by Scalar.{\" \"}\n <a\n href=\"/_docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sky-500 hover:text-sky-600\"\n >\n Open in new tab\n </a>\n </p>\n </div>\n\n <div className=\"overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm\">\n <iframe\n src=\"/_docs\"\n className=\"w-full border-0\"\n style={{ height: \"calc(100vh - 180px)\" }}\n />\n </div>\n </main>\n </div>\n );\n}\n",
|
|
19
|
+
"dashboard/App.tsx": "import { useEffect, useState } from \"react\";\nimport { Header } from \"./components/Header.tsx\";\nimport { TabNav } from \"./components/TabNav.tsx\";\nimport { AITab } from \"./components/AITab.tsx\";\nimport { ServerTab } from \"./components/ServerTab.tsx\";\nimport { RuntimeTab } from \"./components/RuntimeTab.tsx\";\nimport { FilesTab } from \"./components/FilesTab.tsx\";\nimport { ErrorsTab } from \"./components/ErrorsTab.tsx\";\nimport { ConfigTab } from \"./components/ConfigTab.tsx\";\nimport { APITab } from \"./components/APITab.tsx\";\n\nexport interface Tool {\n id: string;\n type: string;\n description: string;\n schema: { properties?: Record<string, unknown>; required?: string[] } | null;\n mcp: { enabled: boolean };\n}\n\nexport interface Resource {\n id: string;\n pattern: string;\n description: string;\n mcp: { enabled: boolean };\n}\n\nexport interface Prompt {\n id: string;\n description: string;\n}\n\nexport interface Agent {\n id: string;\n description: string;\n model: string;\n system: string | null;\n tools: Record<string, boolean>;\n memory: { type: string; maxTokens: number } | null;\n streaming: boolean;\n maxSteps: number | null;\n}\n\nexport interface FileItem {\n name: string;\n type: \"file\" | \"directory\";\n path: string;\n}\n\nexport interface Handler {\n name: string;\n priority: number;\n patterns: Array<{ pattern: string; exact?: boolean; prefix?: boolean; method?: string }>;\n enabled: string;\n}\n\nexport type TabId = \"ai\" | \"server\" | \"runtime\" | \"files\" | \"errors\" | \"config\" | \"api\";\n\nconst TABS: Array<{ id: TabId; label: string }> = [\n { id: \"ai\", label: \"AI\" },\n { id: \"server\", label: \"Server\" },\n { id: \"runtime\", label: \"Runtime\" },\n { id: \"files\", label: \"Files\" },\n { id: \"errors\", label: \"Errors\" },\n { id: \"config\", label: \"Config\" },\n { id: \"api\", label: \"API\" },\n];\n\nasync function fetchJson(url: string): Promise<unknown> {\n const res = await fetch(url);\n return res.json();\n}\n\nexport function App(): JSX.Element {\n const [currentTab, setCurrentTab] = useState<TabId>(\"ai\");\n const [tools, setTools] = useState<Tool[]>([]);\n const [resources, setResources] = useState<Resource[]>([]);\n const [prompts, setPrompts] = useState<Prompt[]>([]);\n const [agents, setAgents] = useState<Agent[]>([]);\n\n useEffect((): void => {\n async function fetchData(): Promise<void> {\n try {\n const [t, r, p, a] = await Promise.all([\n fetchJson(\"/_dev/api/tools\"),\n fetchJson(\"/_dev/api/resources\"),\n fetchJson(\"/_dev/api/prompts\"),\n fetchJson(\"/_dev/api/agents\"),\n ]);\n\n setTools((t as any)?.tools ?? []);\n setResources((r as any)?.resources ?? []);\n setPrompts((p as any)?.prompts ?? []);\n setAgents((a as any)?.agents ?? []);\n } catch (e) {\n console.error(\"Failed to fetch data:\", e);\n }\n }\n\n void fetchData();\n }, []);\n\n return (\n <div className=\"min-h-screen bg-gray-50\">\n <Header />\n <TabNav tabs={TABS} currentTab={currentTab} onTabChange={setCurrentTab} />\n\n <div className=\"tab-content\">\n {currentTab === \"ai\" && (\n <AITab tools={tools} resources={resources} prompts={prompts} agents={agents} />\n )}\n {currentTab === \"server\" && <ServerTab />}\n {currentTab === \"runtime\" && <RuntimeTab />}\n {currentTab === \"files\" && <FilesTab />}\n {currentTab === \"errors\" && <ErrorsTab />}\n {currentTab === \"config\" && <ConfigTab />}\n {currentTab === \"api\" && <APITab />}\n </div>\n </div>\n );\n}\n",
|
|
20
|
+
"dashboard/index.tsx": "import { App } from \"./App.tsx\";\nimport { mountReactApp } from \"../shared/mount-react-app.tsx\";\n\nmountReactApp(<App />);\n",
|
|
21
|
+
"projects/components/ProjectCard.tsx": "interface ProjectCardProps {\n name: string;\n slug: string;\n description?: string;\n updatedAt?: string;\n href: string;\n}\n\nfunction formatRelativeTime(dateString?: string): string {\n if (!dateString) return \"\";\n\n const date = new Date(dateString);\n const diffSecs = Math.floor((Date.now() - date.getTime()) / 1000);\n\n if (diffSecs < 60) return \"just now\";\n\n const diffMins = Math.floor(diffSecs / 60);\n if (diffMins === 1) return \"1 minute ago\";\n if (diffMins < 60) return `${diffMins} minutes ago`;\n\n const diffHours = Math.floor(diffMins / 60);\n if (diffHours === 1) return \"1 hour ago\";\n if (diffHours < 24) return `${diffHours} hours ago`;\n\n const diffDays = Math.floor(diffHours / 24);\n if (diffDays === 1) return \"yesterday\";\n if (diffDays < 7) return `${diffDays} days ago`;\n if (diffDays < 14) return \"1 week ago\";\n if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;\n\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n}\n\nexport function ProjectCard({\n name,\n slug,\n description,\n updatedAt,\n href,\n}: ProjectCardProps): JSX.Element {\n const relativeTime = formatRelativeTime(updatedAt);\n\n return (\n <a\n href={href}\n className=\"block bg-vf-card rounded-xl p-5 border border-vf-border hover:border-[#ccc] transition-colors\"\n >\n <h3 className=\"text-lg font-semibold text-vf-text leading-tight\">{name}</h3>\n <p className=\"text-sm text-[#1a1a1a]/50 mt-1\">{slug}</p>\n {description && <p className=\"text-sm text-vf-muted mt-2 line-clamp-2\">{description}</p>}\n {relativeTime && <p className=\"text-xs text-[#999] mt-2\">Updated {relativeTime}</p>}\n </a>\n );\n}\n",
|
|
22
|
+
"projects/components/SearchInput.tsx": "interface SearchInputProps {\n value: string;\n onChange: (value: string) => void;\n placeholder?: string;\n loading?: boolean;\n}\n\nexport function SearchInput({\n value,\n onChange,\n placeholder,\n loading,\n}: SearchInputProps): JSX.Element {\n const showClear = value.length > 0;\n const showLoading = !!loading && !showClear;\n\n return (\n <div className=\"relative group\">\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none\">\n <svg\n className=\"w-4 h-4 text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n strokeWidth={2}\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\"\n />\n </svg>\n </div>\n\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n className=\"w-full sm:w-80 pl-9 pr-9 py-2.5 text-sm bg-white border border-gray-200 rounded-lg outline-none focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 placeholder:text-gray-400\"\n />\n\n {showClear\n ? (\n <button\n type=\"button\"\n onClick={() => onChange(\"\")}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\"\n >\n <svg\n className=\"w-4 h-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n strokeWidth={2}\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n )\n : showLoading\n ? (\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <svg\n className=\"w-4 h-4 text-gray-400 animate-spin\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n </div>\n )\n : null}\n </div>\n );\n}\n",
|
|
23
|
+
"projects/components/EmptyState.tsx": "interface EmptyStateProps {\n title: string;\n description: string;\n variant?: \"default\" | \"error\";\n showWorkspaceGuide?: boolean;\n}\n\nfunction WorkspaceGuide(): JSX.Element {\n return (\n <div className=\"mt-8 max-w-md mx-auto text-left\">\n <p className=\"text-sm font-medium text-gray-500 mb-3\">Get started</p>\n <div className=\"bg-white rounded-lg border border-gray-200 p-4\">\n <p className=\"text-sm text-gray-600 mb-3\">\n Create a project, or place projects in a{\" \"}\n <code className=\"text-xs bg-gray-100 px-1.5 py-0.5 rounded font-mono\">projects/</code>\n {\" \"}\n folder:\n </p>\n <pre className=\"text-xs font-mono text-gray-500 leading-relaxed bg-gray-50 rounded p-3\">{\n`my-workspace/\n projects/\n site-a/\n app/ ← detected\n site-b/\n app/ ← detected`\n }</pre>\n <div className=\"mt-3 pt-3 border-t border-gray-100\">\n <p className=\"text-xs text-gray-400\">\n Or run{\" \"}\n <code className=\"bg-gray-100 px-1.5 py-0.5 rounded font-mono\">\n veryfront init my-app\n </code>{\" \"}\n to scaffold a new project.\n </p>\n </div>\n </div>\n </div>\n );\n}\n\nexport function EmptyState({\n title,\n description,\n variant = \"default\",\n showWorkspaceGuide = false,\n}: EmptyStateProps): JSX.Element {\n const titleClassName = variant === \"error\" ? \"text-amber-600\" : \"text-gray-600\";\n const showGuide = variant === \"default\" && showWorkspaceGuide;\n\n return (\n <div className=\"text-center py-16 px-6\">\n <p className={`text-lg mb-2 ${titleClassName}`}>{title}</p>\n <p className=\"text-sm text-gray-400\">{description}</p>\n {showGuide && <WorkspaceGuide />}\n </div>\n );\n}\n",
|
|
24
|
+
"projects/components/Header.tsx": "export function Header(): React.JSX.Element {\n return (\n <header>\n <a href=\"/\" className=\"inline-block text-[#1a1a1a] hover:text-[#333] transition-colors\">\n <svg\n viewBox=\"0 0 190 37\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-[26px] w-[138px] md:h-[30px] md:w-[159px] lg:h-9 lg:w-[190px]\"\n >\n <path\n d=\"M104.736 13.3105L108.983 24.168L113.384 13.3135L117.345 13.3154L110.176 30.4102C109.647 31.7303 109.075 32.831 108.458 33.7109C107.842 34.5907 107.159 35.2506 106.411 35.6904C105.663 36.1523 104.771 36.3832 103.736 36.3828C103.362 36.3826 102.912 36.3382 102.384 36.25C101.878 36.1838 101.382 36.0517 100.898 35.8535L101.361 32.4209C101.735 32.6411 102.099 32.7947 102.451 32.8828C102.825 32.9709 103.133 33.0155 103.375 33.0156C103.925 33.0158 104.399 32.8728 104.795 32.5869C105.213 32.301 105.576 31.8828 105.884 31.333C106.214 30.8051 106.533 30.1781 106.842 29.4521L107.162 28.6611L100.775 13.3086L104.736 13.3105ZM16.9912 23.8398L14.7559 35.8057L14.3652 35.7285C13.5662 35.5713 12.7841 35.3611 12.0264 35.1025L11.6963 34.9902L13.583 25.8643L13.2197 25.6543L6.26562 31.8623L6.12109 31.7363L6.00391 31.6357C5.3913 31.1039 4.81391 30.5314 4.27734 29.9229L4.01172 29.6221L13.2656 21.6855L16.9912 23.8398ZM31.7686 29.5967L31.6221 29.7627L31.5039 29.8965C30.971 30.5031 30.3984 31.0746 29.79 31.6055L29.6729 31.707L29.5283 31.834L22.5967 25.6514L22.2324 25.8613L24.1191 34.9688L23.791 35.083C23.0309 35.3452 22.2474 35.5586 21.4453 35.7188L21.0537 35.7969L18.8525 23.9893L18.8252 23.8369L22.5498 21.6826L31.7686 29.5967ZM150.34 12.998C151.902 12.9987 153.31 13.3734 154.564 14.1221C155.84 14.8487 156.842 15.8504 157.567 17.127C158.315 18.4034 158.688 19.8338 158.688 21.418C158.687 22.6063 158.467 23.7179 158.026 24.752C157.608 25.7639 157.014 26.655 156.243 27.4248C155.495 28.1726 154.614 28.7551 153.602 29.1729C152.589 29.5906 151.499 29.7993 150.333 29.7988C148.771 29.7982 147.352 29.4345 146.076 28.708C144.8 27.9593 143.788 26.9465 143.04 25.6699C142.314 24.3935 141.952 22.9742 141.952 21.4121C141.953 20.2239 142.163 19.1234 142.581 18.1113C143.022 17.0773 143.616 16.1751 144.364 15.4053C145.135 14.6354 146.027 14.0417 147.039 13.624C148.051 13.2063 149.152 12.9976 150.34 12.998ZM79.3574 12.9697C80.8098 12.9703 82.1303 13.3237 83.3184 14.0283C84.5062 14.7109 85.4526 15.6465 86.1562 16.835C86.8598 18.0235 87.2115 19.3661 87.2109 20.8623C87.2109 21.0163 87.1989 21.2365 87.1768 21.5225C87.1766 21.8083 87.1656 22.0501 87.1436 22.248L74.5146 22.2432C74.5965 22.9402 74.7892 23.5787 75.0947 24.1582C75.5125 24.9285 76.0846 25.5342 76.8105 25.9746C77.5586 26.415 78.4175 26.6354 79.3857 26.6357C80.0897 26.636 80.7165 26.5366 81.2666 26.3389C81.8388 26.119 82.3011 25.8222 82.6533 25.4482C83.0274 25.0744 83.2696 24.6348 83.3799 24.1289L87.0107 24.1299C86.8343 25.274 86.3939 26.2749 85.6895 27.1328C84.985 27.9687 84.094 28.6184 83.0156 29.0801C81.9372 29.5418 80.7373 29.772 79.417 29.7715C77.8546 29.7709 76.4352 29.3961 75.1592 28.6475C73.9053 27.8988 72.9049 26.8859 72.1572 25.6094C71.4096 24.3329 71.0356 22.9026 71.0361 21.3184C71.0366 20.1301 71.2465 19.0297 71.665 18.0176C72.1055 17.0056 72.6999 16.1258 73.4482 15.3779C74.2187 14.608 75.0994 14.0134 76.0898 13.5957C77.1021 13.1781 78.1913 12.9693 79.3574 12.9697ZM185.777 9.01758L185.775 13.3418L190 13.3438L189.999 16.8418L185.773 16.8398L185.771 23.3428C185.771 24.377 186.057 25.1143 186.629 25.5547C187.201 25.995 187.927 26.2154 188.807 26.2158C189.005 26.2159 189.203 26.2055 189.401 26.1836C189.621 26.1617 189.819 26.1289 189.995 26.085L189.994 29.4844C189.73 29.5503 189.411 29.5943 189.037 29.6162C188.663 29.6601 188.322 29.6817 188.014 29.6816C186.825 29.6812 185.78 29.4498 184.878 28.9873C183.998 28.5028 183.305 27.7985 182.799 26.874C182.315 25.9277 182.074 24.7723 182.074 23.4082L182.076 16.8389L179.007 16.8379L179.009 13.3389L182.078 13.3398L182.08 9.0166L185.777 9.01758ZM170.416 13.0059C171.802 13.0064 172.99 13.3039 173.98 13.8984C174.993 14.471 175.762 15.3077 176.29 16.4082C176.84 17.4866 177.115 18.7742 177.114 20.2705L177.11 29.4795L173.413 29.4775L173.417 20.7305C173.417 19.4543 173.055 18.4418 172.329 17.6934C171.625 16.9229 170.69 16.5376 169.523 16.5371C168.313 16.5367 167.345 16.9215 166.618 17.6914C165.937 18.3925 165.575 19.3258 165.532 20.4912L165.528 20.7275L165.524 29.4746L161.86 29.4736L161.867 13.332L165.531 13.334L165.529 15.8486C165.987 15.06 166.559 14.4311 167.247 13.9619C168.15 13.3241 169.206 13.0054 170.416 13.0059ZM139.62 12.9941C139.862 12.9943 140.104 13.0155 140.346 13.0596C140.61 13.1037 140.874 13.1483 141.138 13.1924L141.137 16.5596C140.873 16.4935 140.598 16.4377 140.312 16.3936C140.048 16.3495 139.784 16.3273 139.52 16.3271C138.75 16.3268 138.034 16.5028 137.374 16.8545C136.736 17.2063 136.218 17.7233 135.821 18.4053C135.447 19.0652 135.26 19.8795 135.26 20.8477L135.256 29.4629L131.592 29.4619L131.599 13.3203L135.263 13.3223L135.262 15.5664C135.724 14.7965 136.33 14.1805 137.078 13.7188C137.826 13.2351 138.674 12.9938 139.62 12.9941ZM127.037 5.66113C127.345 5.66125 127.686 5.67219 128.061 5.69434C128.435 5.7165 128.754 5.77127 129.018 5.85938L129.017 9.25977C128.841 9.21569 128.642 9.18225 128.422 9.16016C128.224 9.1381 128.037 9.12704 127.861 9.12695C126.959 9.1266 126.222 9.34641 125.649 9.78613C125.077 10.226 124.79 10.9638 124.79 11.998L124.789 13.3174L129.015 13.3193L129.014 16.8184L124.788 16.8164L124.783 29.459L121.086 29.458L121.091 16.8154L118.021 16.8145L118.022 13.3154L121.092 13.3164L121.093 11.9297C121.093 10.5658 121.347 9.4219 121.854 8.49805C122.36 7.5521 123.053 6.84752 123.934 6.38574C124.814 5.90196 125.849 5.66067 127.037 5.66113ZM98.3594 12.9775C98.6013 12.9776 98.8431 12.9999 99.085 13.0439C99.349 13.0881 99.6139 13.1317 99.8779 13.1758L99.876 16.543C99.612 16.4769 99.3367 16.422 99.0508 16.3779C98.7868 16.3338 98.5228 16.3116 98.2588 16.3115C97.4887 16.3112 96.7735 16.4871 96.1133 16.8389C95.475 17.1907 94.9579 17.7076 94.5615 18.3896C94.1872 19.0497 93.9994 19.8638 93.999 20.832L93.9961 29.4473L90.332 29.4453L90.3379 13.3047L94.002 13.3057L94.001 15.5508C94.4634 14.7808 95.0691 14.1649 95.8174 13.7031C96.5658 13.2193 97.4131 12.9772 98.3594 12.9775ZM55.7764 6.32617L61.9756 23.2617L68.1875 6.33105L72.248 6.33301L63.5576 29.4355L60.3555 29.4336L51.6504 6.32422L55.7764 6.32617ZM150.339 16.4971C149.437 16.4967 148.622 16.7164 147.896 17.1562C147.169 17.596 146.586 18.1896 146.146 18.9375C145.727 19.6635 145.518 20.4889 145.518 21.4131C145.517 22.3372 145.726 23.1624 146.144 23.8887C146.583 24.615 147.167 25.1983 147.893 25.6387C148.619 26.0571 149.433 26.2672 150.335 26.2676C151.237 26.2679 152.04 26.0583 152.744 25.6406C153.47 25.2008 154.044 24.6185 154.462 23.8926C154.902 23.1446 155.123 22.3192 155.123 21.417C155.123 20.493 154.904 19.6677 154.464 18.9414C154.046 18.1932 153.474 17.5986 152.748 17.1582C152.044 16.7179 151.241 16.4975 150.339 16.4971ZM12.3467 15.7891V20.0967L0.837891 24.1553L0.708984 23.7773C0.450188 23.0188 0.239646 22.2366 0.0820312 21.4365L0.0527344 21.2861L0.015625 21.0967L8.89062 18.1543V17.7324L0.000976562 14.791L0.0654297 14.4512C0.219983 13.6463 0.428694 12.8601 0.685547 12.0967L0.8125 11.7158L12.3467 15.7891ZM35.0801 12.1123C35.335 12.8723 35.5407 13.6559 35.6943 14.457L35.7236 14.6074L35.7598 14.7969L26.9189 17.7256V18.1484L35.749 21.0693L35.6826 21.4102C35.5255 22.2141 35.3158 22.9997 35.0566 23.7617L34.9277 24.1387L23.4639 20.0908V15.7832L34.9531 11.7324L35.0801 12.1123ZM79.2246 16.0732C78.5204 16.073 77.8706 16.1937 77.2764 16.4355C76.7043 16.6774 76.2092 17.0294 75.791 17.4912C75.3728 17.9311 75.0421 18.4918 74.7998 19.1738C74.7571 19.3019 74.719 19.4338 74.6846 19.5693L83.3164 19.5732C83.2726 18.9131 83.0521 18.3193 82.6562 17.791C82.2825 17.2409 81.7989 16.8224 81.2051 16.5361C80.6111 16.2279 79.9507 16.0736 79.2246 16.0732ZM16.9854 12.043L13.2607 14.1973L3.97559 6.22559L4.12012 6.05957L4.2373 5.92578C4.76963 5.31808 5.34322 4.74671 5.95117 4.21484L6.21289 3.98535L13.2139 10.2295L13.5771 10.0186L11.6729 0.822266L12.002 0.709961C12.7629 0.449081 13.5469 0.235752 14.3496 0.0771484L14.5234 0.0429688L14.7402 0L16.9854 12.043ZM21.46 0.0878906C22.2588 0.24807 23.0397 0.460164 23.7969 0.72168L24.124 0.834961L22.2266 10.0156L22.5908 10.2266L29.5654 4L29.8271 4.22949C30.4368 4.76433 31.0106 5.33998 31.5439 5.95117L31.8057 6.25195L22.5449 14.1943L18.8193 12.04L21.0684 0.00878906L21.46 0.0878906Z\"\n fill=\"currentColor\"\n />\n </svg>\n </a>\n </header>\n );\n}\n",
|
|
25
|
+
"projects/App.tsx": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { EmptyState } from \"./components/EmptyState.tsx\";\nimport { Header } from \"./components/Header.tsx\";\nimport { ProjectCard } from \"./components/ProjectCard.tsx\";\nimport { SearchInput } from \"./components/SearchInput.tsx\";\n\ninterface Project {\n id: string;\n name: string;\n slug: string;\n description?: string;\n updated_at?: string;\n}\n\ninterface Config {\n domain: string;\n port: string;\n hasToken: boolean;\n}\n\nexport function App(): React.JSX.Element {\n const [projects, setProjects] = useState<Project[]>([]);\n const [search, setSearch] = useState<string>(\"\");\n const [loading, setLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [config, setConfig] = useState<Config | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n fetch(\"/_projects/api/config\")\n .then((r) => r.json())\n .then(setConfig)\n .catch((e) => console.error(\"Failed to load config:\", e));\n }, []);\n\n const fetchProjects = useCallback(async (searchQuery: string): Promise<void> => {\n abortControllerRef.current?.abort();\n const controller = new AbortController();\n abortControllerRef.current = controller;\n\n setLoading(true);\n setError(null);\n\n try {\n const params = new URLSearchParams({\n sort_by: \"updated_at\",\n sort_order: \"desc\",\n limit: \"100\",\n });\n\n if (searchQuery) params.set(\"search\", searchQuery);\n\n const response = await fetch(`/_vf/api/projects?${params}`, {\n signal: controller.signal,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n setError(data.error ?? data.detail ?? \"Failed to load projects\");\n setProjects([]);\n return;\n }\n\n setProjects(data.data ?? []);\n } catch (e) {\n if (e instanceof Error && e.name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : \"Failed to load projects\");\n setProjects([]);\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n fetchProjects(\"\");\n }, [fetchProjects]);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n fetchProjects(search);\n }, 300);\n\n return () => clearTimeout(timer);\n }, [fetchProjects, search]);\n\n function getProjectUrl(slug: string): string {\n if (!config) return \"#\";\n const portSuffix = config.port ? `:${config.port}` : \"\";\n return `http://${slug}.${config.domain}${portSuffix}/`;\n }\n\n function renderContent(): React.JSX.Element {\n if (error) {\n return (\n <EmptyState\n title=\"Unable to load projects\"\n description={error}\n variant=\"error\"\n />\n );\n }\n\n if (loading && projects.length === 0) {\n return (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3, 4, 5, 6].map((i) => (\n <div\n key={i}\n className=\"bg-white rounded-xl p-5 border border-gray-200 animate-pulse\"\n >\n <div className=\"h-5 bg-gray-200 rounded w-2/3 mb-3\" />\n <div className=\"h-4 bg-gray-100 rounded w-1/2 mb-4\" />\n <div className=\"h-3 bg-gray-100 rounded w-1/3\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (projects.length === 0) {\n const title = search ? \"No projects found\" : \"No projects yet\";\n const description = search\n ? \"Try a different search term\"\n : \"Create a project to get started\";\n\n return <EmptyState title={title} description={description} showWorkspaceGuide={!search} />;\n }\n\n return (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {projects.map((project) => (\n <ProjectCard\n key={project.id}\n name={project.name}\n slug={project.slug}\n description={project.description}\n updatedAt={project.updated_at}\n href={getProjectUrl(project.slug)}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div className=\"min-h-screen\">\n <div className=\"max-w-6xl mx-auto px-5 py-10\">\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-10\">\n <Header />\n {config?.hasToken\n ? (\n <SearchInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search...\"\n loading={loading && search.length > 0}\n />\n )\n : null}\n </div>\n\n {renderContent()}\n </div>\n </div>\n );\n}\n",
|
|
26
|
+
"projects/index.tsx": "import { App } from \"./App.tsx\";\nimport { mountReactApp } from \"../shared/mount-react-app.tsx\";\n\nmountReactApp(<App />);\n",
|
|
27
|
+
"shared/mount-react-app.tsx": "import type { ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nexport function mountReactApp(app: ReactNode): void {\n const root = document.getElementById(\"root\");\n\n if (!root) {\n throw new Error('Root element with id \"root\" not found');\n }\n\n createRoot(root).render(app);\n}\n"
|
|
28
28
|
}
|
|
29
29
|
};
|