glirastes 0.2.1 → 0.3.1
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/CHANGELOG.md +96 -0
- package/CODE_OF_CONDUCT.md +85 -0
- package/CONTRIBUTING.md +87 -0
- package/README.md +268 -23
- package/SECURITY.md +46 -0
- package/codegen/package.json +5 -0
- package/dist/cjs/codegen/asset-sync.d.ts +20 -0
- package/dist/cjs/codegen/asset-sync.js +28 -0
- package/dist/cjs/codegen/coverage.d.ts +66 -0
- package/dist/cjs/codegen/coverage.js +408 -0
- package/dist/cjs/codegen/endpoint-scanner.d.ts +16 -0
- package/dist/cjs/codegen/endpoint-scanner.js +197 -0
- package/dist/cjs/codegen/format.d.ts +17 -0
- package/dist/cjs/codegen/format.js +34 -0
- package/dist/cjs/codegen/fs-utils.d.ts +45 -0
- package/dist/cjs/codegen/fs-utils.js +165 -0
- package/dist/cjs/codegen/index.d.ts +13 -0
- package/dist/cjs/codegen/index.js +33 -0
- package/dist/cjs/codegen/mcp-generator.d.ts +50 -0
- package/dist/cjs/codegen/mcp-generator.js +803 -0
- package/dist/cjs/codegen/module-generator.d.ts +11 -0
- package/dist/cjs/codegen/module-generator.js +353 -0
- package/dist/cjs/codegen/nestjs-scanner.d.ts +43 -0
- package/dist/cjs/codegen/nestjs-scanner.js +241 -0
- package/dist/cjs/codegen/output-recommender.d.ts +23 -0
- package/dist/cjs/codegen/output-recommender.js +33 -0
- package/dist/cjs/codegen/scaffolder.d.ts +36 -0
- package/dist/cjs/codegen/scaffolder.js +262 -0
- package/dist/cjs/codegen/skill-generator.d.ts +49 -0
- package/dist/cjs/codegen/skill-generator.js +967 -0
- package/dist/cjs/codegen/types.d.ts +52 -0
- package/dist/cjs/codegen/types.js +16 -0
- package/dist/cjs/codegen/ui-scanner.d.ts +24 -0
- package/dist/cjs/codegen/ui-scanner.js +202 -0
- package/dist/cjs/codegen/validator.d.ts +35 -0
- package/dist/cjs/codegen/validator.js +292 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/openapi/index.d.ts +78 -0
- package/dist/cjs/openapi/index.js +347 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react/audio-worklet-processor.d.ts +16 -0
- package/dist/cjs/react/audio-worklet-processor.js +27 -0
- package/dist/cjs/react/bridges/rest-session-bridge.d.ts +113 -0
- package/dist/cjs/react/bridges/rest-session-bridge.js +209 -0
- package/dist/cjs/react/components/ai-chat-panel.d.ts +12 -0
- package/dist/cjs/react/components/ai-chat-panel.js +67 -0
- package/dist/cjs/react/components/ai-trigger-button.d.ts +2 -0
- package/dist/cjs/react/components/ai-trigger-button.js +239 -0
- package/dist/cjs/react/components/approval-card.d.ts +9 -0
- package/dist/cjs/react/components/approval-card.js +24 -0
- package/dist/cjs/react/components/bulk-approval-card.d.ts +9 -0
- package/dist/cjs/react/components/bulk-approval-card.js +36 -0
- package/dist/cjs/react/components/chat-input.d.ts +10 -0
- package/dist/cjs/react/components/chat-input.js +60 -0
- package/dist/cjs/react/components/chat-window.d.ts +2 -0
- package/dist/cjs/react/components/chat-window.js +134 -0
- package/dist/cjs/react/components/confirm-dialog.d.ts +32 -0
- package/dist/cjs/react/components/confirm-dialog.js +48 -0
- package/dist/cjs/react/components/floating-chat-ui.d.ts +90 -0
- package/dist/cjs/react/components/floating-chat-ui.js +163 -0
- package/dist/cjs/react/components/mention-chip.d.ts +16 -0
- package/dist/cjs/react/components/mention-chip.js +42 -0
- package/dist/cjs/react/components/mention-input.d.ts +8 -0
- package/dist/cjs/react/components/mention-input.js +117 -0
- package/dist/cjs/react/components/message-bubble.d.ts +11 -0
- package/dist/cjs/react/components/message-bubble.js +171 -0
- package/dist/cjs/react/components/message-list.d.ts +8 -0
- package/dist/cjs/react/components/message-list.js +52 -0
- package/dist/cjs/react/components/pipeline-timeline.d.ts +7 -0
- package/dist/cjs/react/components/pipeline-timeline.js +72 -0
- package/dist/cjs/react/components/recording-bar.d.ts +2 -0
- package/dist/cjs/react/components/recording-bar.js +81 -0
- package/dist/cjs/react/components/recording-controls.d.ts +18 -0
- package/dist/cjs/react/components/recording-controls.js +38 -0
- package/dist/cjs/react/components/rich-mention-input.d.ts +2 -0
- package/dist/cjs/react/components/rich-mention-input.js +571 -0
- package/dist/cjs/react/components/session-switcher.d.ts +14 -0
- package/dist/cjs/react/components/session-switcher.js +133 -0
- package/dist/cjs/react/components/suggestion-bar.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-bar.js +47 -0
- package/dist/cjs/react/components/suggestion-chip.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-chip.js +14 -0
- package/dist/cjs/react/components/voice-input-addon.d.ts +13 -0
- package/dist/cjs/react/components/voice-input-addon.js +31 -0
- package/dist/cjs/react/components/voice-input-button.d.ts +2 -0
- package/dist/cjs/react/components/voice-input-button.js +55 -0
- package/dist/cjs/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/cjs/react/components/wavesurfer-loader.js +36 -0
- package/dist/cjs/react/hooks/use-ai-chat.d.ts +22 -0
- package/dist/cjs/react/hooks/use-ai-chat.js +51 -0
- package/dist/cjs/react/hooks/use-approvals.d.ts +23 -0
- package/dist/cjs/react/hooks/use-approvals.js +63 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.d.ts +2 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.js +329 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.d.ts +48 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.js +308 -0
- package/dist/cjs/react/hooks/use-draggable-position.d.ts +25 -0
- package/dist/cjs/react/hooks/use-draggable-position.js +134 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.d.ts +20 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.js +93 -0
- package/dist/cjs/react/hooks/use-mentions.d.ts +23 -0
- package/dist/cjs/react/hooks/use-mentions.js +42 -0
- package/dist/cjs/react/hooks/use-suggestions.d.ts +22 -0
- package/dist/cjs/react/hooks/use-suggestions.js +39 -0
- package/dist/cjs/react/index.d.ts +43 -0
- package/dist/cjs/react/index.js +111 -0
- package/dist/cjs/react/provider/ai-chat-provider.d.ts +45 -0
- package/dist/cjs/react/provider/ai-chat-provider.js +1024 -0
- package/dist/cjs/react/provider/chat-context.d.ts +84 -0
- package/dist/cjs/react/provider/chat-context.js +13 -0
- package/dist/cjs/react/template.d.ts +84 -0
- package/dist/cjs/react/template.js +90 -0
- package/dist/cjs/react/themes.d.ts +24 -0
- package/dist/cjs/react/themes.js +273 -0
- package/dist/cjs/react/transports/langgraph/index.d.ts +2 -0
- package/dist/cjs/react/transports/langgraph/index.js +8 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.d.ts +62 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.js +94 -0
- package/dist/cjs/react/transports/langgraph.d.ts +71 -0
- package/dist/cjs/react/transports/langgraph.js +369 -0
- package/dist/cjs/react/transports/vercel/index.d.ts +2 -0
- package/dist/cjs/react/transports/vercel/index.js +8 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.d.ts +49 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.js +81 -0
- package/dist/cjs/react/transports/vercel.d.ts +30 -0
- package/dist/cjs/react/transports/vercel.js +69 -0
- package/dist/cjs/react/types.d.ts +760 -0
- package/dist/cjs/react/types.js +2 -0
- package/dist/cjs/react/ui.d.ts +73 -0
- package/dist/cjs/react/ui.js +183 -0
- package/dist/cjs/react/utils/create-approval-description.d.ts +53 -0
- package/dist/cjs/react/utils/create-approval-description.js +122 -0
- package/dist/cjs/react/utils/create-tool-result-handler.d.ts +90 -0
- package/dist/cjs/react/utils/create-tool-result-handler.js +168 -0
- package/dist/cjs/react/utils/mention-markup.d.ts +13 -0
- package/dist/cjs/react/utils/mention-markup.js +117 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.d.ts +19 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.js +81 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.d.ts +23 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.d.ts +10 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.js +42 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.d.ts +32 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.js +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.d.ts +11 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.js +80 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.d.ts +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.js +119 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.d.ts +13 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.js +131 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.d.ts +19 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.js +108 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.d.ts +8 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.js +59 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.js +73 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.js +72 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.js +13 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.js +15 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.js +22 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.js +7 -0
- package/dist/cjs/server/adapters/nestjs/index.d.ts +14 -0
- package/dist/cjs/server/adapters/nestjs/index.js +42 -0
- package/dist/cjs/server/adapters/nestjs/metadata.d.ts +39 -0
- package/dist/cjs/server/adapters/nestjs/metadata.js +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.d.ts +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.js +159 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.d.ts +37 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.js +44 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.d.ts +28 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.js +41 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.d.ts +16 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.js +117 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.d.ts +26 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.js +259 -0
- package/dist/cjs/server/adapters/nestjs/swagger.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/swagger.js +39 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.d.ts +22 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.js +60 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.d.ts +94 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.js +965 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.d.ts +11 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.js +41 -0
- package/dist/cjs/server/adapters/nextjs/index.d.ts +8 -0
- package/dist/cjs/server/adapters/nextjs/index.js +22 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.d.ts +58 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.js +114 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.d.ts +64 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.js +489 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.d.ts +7 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.js +40 -0
- package/dist/cjs/server/adapters/nextjs/types.d.ts +246 -0
- package/dist/cjs/server/adapters/nextjs/types.js +2 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.d.ts +9 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.js +12 -0
- package/dist/cjs/server/core/followup-tool.d.ts +9 -0
- package/dist/cjs/server/core/followup-tool.js +82 -0
- package/dist/cjs/server/core/index.d.ts +139 -0
- package/dist/cjs/server/core/index.js +818 -0
- package/dist/cjs/server/core/intent-classifier.d.ts +94 -0
- package/dist/cjs/server/core/intent-classifier.js +324 -0
- package/dist/cjs/server/core/model-selector.d.ts +12 -0
- package/dist/cjs/server/core/model-selector.js +48 -0
- package/dist/cjs/server/core/module-router.d.ts +14 -0
- package/dist/cjs/server/core/module-router.js +110 -0
- package/dist/cjs/server/core/zod-compact.d.ts +18 -0
- package/dist/cjs/server/core/zod-compact.js +129 -0
- package/dist/cjs/server/index.d.ts +6 -0
- package/dist/cjs/server/index.js +28 -0
- package/dist/cjs/server/lancer/cache.d.ts +9 -0
- package/dist/cjs/server/lancer/cache.js +33 -0
- package/dist/cjs/server/lancer/client.d.ts +2 -0
- package/dist/cjs/server/lancer/client.js +279 -0
- package/dist/cjs/server/lancer/errors.d.ts +13 -0
- package/dist/cjs/server/lancer/errors.js +21 -0
- package/dist/cjs/server/lancer/index.d.ts +3 -0
- package/dist/cjs/server/lancer/index.js +7 -0
- package/dist/cjs/server/lancer/telemetry-buffer.d.ts +15 -0
- package/dist/cjs/server/lancer/telemetry-buffer.js +64 -0
- package/dist/cjs/server/lancer/types.d.ts +201 -0
- package/dist/cjs/server/lancer/types.js +5 -0
- package/dist/cjs/server/pii-shield/aegis-detector.d.ts +26 -0
- package/dist/cjs/server/pii-shield/aegis-detector.js +26 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.d.ts +2 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.js +191 -0
- package/dist/cjs/server/pii-shield/deep-walk.d.ts +1 -0
- package/dist/cjs/server/pii-shield/deep-walk.js +19 -0
- package/dist/cjs/server/pii-shield/fake-data.d.ts +5 -0
- package/dist/cjs/server/pii-shield/fake-data.js +170 -0
- package/dist/cjs/server/pii-shield/index.d.ts +4 -0
- package/dist/cjs/server/pii-shield/index.js +9 -0
- package/dist/cjs/server/pii-shield/leakage.d.ts +1 -0
- package/dist/cjs/server/pii-shield/leakage.js +15 -0
- package/dist/cjs/server/pii-shield/session-mapping.d.ts +12 -0
- package/dist/cjs/server/pii-shield/session-mapping.js +70 -0
- package/dist/cjs/server/pipeline.d.ts +26 -0
- package/dist/cjs/server/pipeline.js +127 -0
- package/dist/cjs/server/testing/approvals.d.ts +11 -0
- package/dist/cjs/server/testing/approvals.js +41 -0
- package/dist/cjs/server/testing/combination.d.ts +3 -0
- package/dist/cjs/server/testing/combination.js +83 -0
- package/dist/cjs/server/testing/create-test-suite.d.ts +29 -0
- package/dist/cjs/server/testing/create-test-suite.js +81 -0
- package/dist/cjs/server/testing/edge-cases.d.ts +8 -0
- package/dist/cjs/server/testing/edge-cases.js +277 -0
- package/dist/cjs/server/testing/guardrails.d.ts +14 -0
- package/dist/cjs/server/testing/guardrails.js +152 -0
- package/dist/cjs/server/testing/helpers.d.ts +14 -0
- package/dist/cjs/server/testing/helpers.js +119 -0
- package/dist/cjs/server/testing/index.d.ts +12 -0
- package/dist/cjs/server/testing/index.js +37 -0
- package/dist/cjs/server/testing/mock-executor.d.ts +8 -0
- package/dist/cjs/server/testing/mock-executor.js +40 -0
- package/dist/cjs/server/testing/regression.d.ts +7 -0
- package/dist/cjs/server/testing/regression.js +27 -0
- package/dist/cjs/server/testing/routing.d.ts +12 -0
- package/dist/cjs/server/testing/routing.js +63 -0
- package/dist/cjs/server/testing/schema-consistency.d.ts +6 -0
- package/dist/cjs/server/testing/schema-consistency.js +56 -0
- package/dist/cjs/server/testing/smoke.d.ts +11 -0
- package/dist/cjs/server/testing/smoke.js +64 -0
- package/dist/cjs/server/testing/types.d.ts +156 -0
- package/dist/cjs/server/testing/types.js +2 -0
- package/dist/cjs/types.d.ts +696 -0
- package/dist/cjs/types.js +91 -0
- package/dist/cli/commands/install-skills.d.ts +17 -0
- package/dist/cli/commands/install-skills.js +185 -0
- package/dist/cli/commands/install-skills.js.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/react/components/ai-trigger-button.js +4 -6
- package/dist/react/components/ai-trigger-button.js.map +1 -1
- package/dist/react/components/recording-bar.js +4 -6
- package/dist/react/components/recording-bar.js.map +1 -1
- package/dist/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/react/components/wavesurfer-loader.js +34 -0
- package/dist/react/components/wavesurfer-loader.js.map +1 -0
- package/dist/server/adapters/nestjs/chat/ai-chat.module.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js +3 -2
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js +10 -8
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js +37 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js.map +1 -0
- package/dist/server/adapters/nextjs/create-ai-chat-handler.d.ts +16 -0
- package/dist/server/adapters/nextjs/create-ai-chat-handler.js +14 -3
- package/dist/server/adapters/nextjs/create-ai-chat-handler.js.map +1 -1
- package/dist/server/adapters/nextjs/filter-fallback-error.d.ts +11 -0
- package/dist/server/adapters/nextjs/filter-fallback-error.js +38 -0
- package/dist/server/adapters/nextjs/filter-fallback-error.js.map +1 -0
- package/openapi/package.json +5 -0
- package/package.json +66 -21
- package/react/langgraph/package.json +5 -0
- package/react/package.json +5 -0
- package/react/vercel/package.json +5 -0
- package/server/nestjs/package.json +5 -0
- package/server/nextjs/package.json +5 -0
- package/server/package.json +5 -0
- package/server/testing/package.json +5 -0
- package/skills/README.md +107 -0
- package/skills/building-glirastes-chat-ui/SKILL.md +378 -0
- package/skills/integrating-glirastes-nestjs/SKILL.md +346 -0
- package/skills/integrating-glirastes-nextjs/SKILL.md +306 -0
- package/skills/integrating-glirastes-nextjs-with-nestjs-backend/SKILL.md +359 -0
- package/skills/maintaining-glirastes-tools/SKILL.md +463 -0
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_LOCALE = void 0;
|
|
4
|
+
exports.AiChatProvider = AiChatProvider;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const themes_js_1 = require("../themes.js");
|
|
8
|
+
const chat_context_js_1 = require("./chat-context.js");
|
|
9
|
+
const mention_markup_js_1 = require("../utils/mention-markup.js");
|
|
10
|
+
const merge_pipeline_steps_js_1 = require("../utils/merge-pipeline-steps.js");
|
|
11
|
+
exports.DEFAULT_LOCALE = {
|
|
12
|
+
placeholder: 'Type a message...',
|
|
13
|
+
sendButton: 'Send',
|
|
14
|
+
stopButton: 'Stop',
|
|
15
|
+
clearButton: 'Clear',
|
|
16
|
+
loadMoreButton: 'Load more',
|
|
17
|
+
emptyState: 'Start a conversation',
|
|
18
|
+
errorMessage: 'Something went wrong. Please try again.',
|
|
19
|
+
approveButton: 'Approve',
|
|
20
|
+
rejectButton: 'Reject',
|
|
21
|
+
approveAllButton: 'Approve all',
|
|
22
|
+
rejectAllButton: 'Reject all',
|
|
23
|
+
continueButton: 'Continue',
|
|
24
|
+
dismissButton: 'Dismiss',
|
|
25
|
+
cancelButton: 'Cancel',
|
|
26
|
+
confirmButton: 'Confirm',
|
|
27
|
+
moreItemsLabel: 'more',
|
|
28
|
+
allCountLabel: 'All {count}',
|
|
29
|
+
pipelineTitle: 'Pipeline',
|
|
30
|
+
pipelineRunningLabel: 'Running',
|
|
31
|
+
pipelineCompletedLabel: 'Completed',
|
|
32
|
+
pipelineSafetyStopLabel: 'Safety stop',
|
|
33
|
+
pipelineAbortedLabel: 'Stopped',
|
|
34
|
+
pipelineErrorLabel: 'Error',
|
|
35
|
+
pipelineStepLabel: 'Step',
|
|
36
|
+
pipelineApprovalPendingLabel: 'Approval pending',
|
|
37
|
+
chatTitle: 'AI Assistant',
|
|
38
|
+
closeChatAriaLabel: 'Close chat',
|
|
39
|
+
clearChatAriaLabel: 'Clear conversation',
|
|
40
|
+
confirmClearPrompt: 'Clear this conversation?',
|
|
41
|
+
triggerAriaLabel: 'AI Assistant',
|
|
42
|
+
micAriaLabel: 'Start recording',
|
|
43
|
+
stopRecordingAriaLabel: 'Stop recording',
|
|
44
|
+
cancelRecordingAriaLabel: 'Cancel recording',
|
|
45
|
+
newChatLabel: 'New chat',
|
|
46
|
+
noSessionsLabel: 'No conversations yet',
|
|
47
|
+
sessionsLoadingLabel: 'Loading…',
|
|
48
|
+
deleteSessionAriaLabel: 'Delete conversation',
|
|
49
|
+
confirmDeleteSessionPrompt: 'Delete this conversation?',
|
|
50
|
+
renameSessionAriaLabel: 'Rename conversation',
|
|
51
|
+
renameSessionPlaceholder: 'Conversation title',
|
|
52
|
+
};
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Helpers
|
|
55
|
+
// ============================================================================
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return typeof value === 'object' && value !== null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Coerce a row returned by `SessionConfig.load` into the SDK's
|
|
61
|
+
* `ChatMessage` shape. Accepts either a full SDK message (already has
|
|
62
|
+
* `parts`) or a flat `{ role, content }` wire message from a typical
|
|
63
|
+
* REST backend. Rows with neither `parts` nor `content` are dropped.
|
|
64
|
+
*/
|
|
65
|
+
function coerceLoadedMessages(rows) {
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
if (!isRecord(row))
|
|
69
|
+
continue;
|
|
70
|
+
if (Array.isArray(row.parts)) {
|
|
71
|
+
result.push(row);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (typeof row.content === 'string' && row.content.length > 0) {
|
|
75
|
+
const role = row.role === 'assistant' || row.role === 'system' ? row.role : 'user';
|
|
76
|
+
const createdAtRaw = row.createdAt ?? row.created_at;
|
|
77
|
+
result.push({
|
|
78
|
+
id: typeof row.id === 'string' ? row.id : `msg-${result.length}`,
|
|
79
|
+
role,
|
|
80
|
+
parts: [{ type: 'text', text: row.content }],
|
|
81
|
+
createdAt: createdAtRaw ? new Date(createdAtRaw) : undefined,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
function getMentionTypesForTrigger(types, trigger) {
|
|
88
|
+
if (trigger === '/') {
|
|
89
|
+
const slashTypes = types.filter((t) => t === 'task' || t === 'command');
|
|
90
|
+
return slashTypes.length > 0 ? slashTypes : types;
|
|
91
|
+
}
|
|
92
|
+
if (trigger === '@') {
|
|
93
|
+
const atTypes = types.filter((t) => t !== 'command');
|
|
94
|
+
return atTypes.length > 0 ? atTypes : types;
|
|
95
|
+
}
|
|
96
|
+
return types;
|
|
97
|
+
}
|
|
98
|
+
/** Default approval description — returns raw tool name and JSON args */
|
|
99
|
+
function defaultApprovalDescription(toolName, approvalId, args) {
|
|
100
|
+
return {
|
|
101
|
+
id: approvalId,
|
|
102
|
+
toolName,
|
|
103
|
+
args: isRecord(args) ? args : {},
|
|
104
|
+
messageId: '',
|
|
105
|
+
description: toolName,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Remove orphaned tool calls (approval-requested without response)
|
|
110
|
+
* from message history to prevent AI_MissingToolResultsError.
|
|
111
|
+
*/
|
|
112
|
+
function sanitizeMessages(msgs) {
|
|
113
|
+
return msgs.map((message) => {
|
|
114
|
+
if (message.role !== 'assistant')
|
|
115
|
+
return message;
|
|
116
|
+
const sanitizedParts = message.parts.filter((part) => {
|
|
117
|
+
if (isRecord(part)) {
|
|
118
|
+
const partType = typeof part.type === 'string' ? part.type : '';
|
|
119
|
+
const partState = typeof part.state === 'string' ? part.state : '';
|
|
120
|
+
if (partType.startsWith('tool-') && partState === 'approval-requested') {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
return { ...message, parts: sanitizedParts };
|
|
127
|
+
}).filter((message) => {
|
|
128
|
+
if (message.role === 'assistant') {
|
|
129
|
+
const hasContent = message.parts.some((part) => isRecord(part) && typeof part.text === 'string' &&
|
|
130
|
+
part.text.trim().length > 0);
|
|
131
|
+
const hasToolOutput = message.parts.some((part) => {
|
|
132
|
+
if (isRecord(part)) {
|
|
133
|
+
const partType = typeof part.type === 'string' ? part.type : '';
|
|
134
|
+
return partType.startsWith('tool-') && part.state === 'output-available';
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
});
|
|
138
|
+
return hasContent || hasToolOutput || message.parts.length === 0;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Merge consecutive assistant messages by keeping only the last one in each run.
|
|
145
|
+
*
|
|
146
|
+
* When a pipeline resumes after approval, `useChat` creates a new assistant
|
|
147
|
+
* message for each server response. The AI model regenerates cumulative text
|
|
148
|
+
* (all prior context + new content), resulting in multiple bubbles with
|
|
149
|
+
* progressively longer duplicate text.
|
|
150
|
+
*
|
|
151
|
+
* By keeping only the last message in each consecutive assistant run, we show
|
|
152
|
+
* only the most complete version. Tool result processing and suggestion
|
|
153
|
+
* extraction operate on `rawMessages` independently, so no data is lost.
|
|
154
|
+
*/
|
|
155
|
+
function consolidateAssistantMessages(messages) {
|
|
156
|
+
if (messages.length <= 1)
|
|
157
|
+
return messages;
|
|
158
|
+
const result = [];
|
|
159
|
+
for (let i = 0; i < messages.length; i++) {
|
|
160
|
+
const msg = messages[i];
|
|
161
|
+
if (msg.role !== 'assistant') {
|
|
162
|
+
result.push(msg);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Check if this assistant message is followed by another assistant message
|
|
166
|
+
// (without a user message in between). If so, it's intermediate — skip it.
|
|
167
|
+
let isIntermediate = false;
|
|
168
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
169
|
+
if (messages[j].role === 'user')
|
|
170
|
+
break;
|
|
171
|
+
if (messages[j].role === 'assistant') {
|
|
172
|
+
isIntermediate = true;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!isIntermediate) {
|
|
177
|
+
result.push(msg);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function normalizePipelineStepReport(data) {
|
|
183
|
+
if (!isRecord(data))
|
|
184
|
+
return null;
|
|
185
|
+
if (typeof data.stepNumber !== 'number' || !Number.isFinite(data.stepNumber)) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const stepNumber = Math.max(0, Math.floor(data.stepNumber));
|
|
189
|
+
const toolCalls = Array.isArray(data.toolCalls)
|
|
190
|
+
? data.toolCalls.filter((tool) => typeof tool === 'string')
|
|
191
|
+
: [];
|
|
192
|
+
const toolResults = typeof data.toolResults === 'number' && Number.isFinite(data.toolResults)
|
|
193
|
+
? Math.max(0, Math.floor(data.toolResults))
|
|
194
|
+
: 0;
|
|
195
|
+
const pendingApprovals = typeof data.pendingApprovals === 'number' && Number.isFinite(data.pendingApprovals)
|
|
196
|
+
? Math.max(0, Math.floor(data.pendingApprovals))
|
|
197
|
+
: Math.max(toolCalls.length - toolResults, 0);
|
|
198
|
+
const usage = isRecord(data.usage) ? data.usage : {};
|
|
199
|
+
return {
|
|
200
|
+
stepNumber,
|
|
201
|
+
finishReason: typeof data.finishReason === 'string' ? data.finishReason : 'other',
|
|
202
|
+
toolCalls,
|
|
203
|
+
toolResults,
|
|
204
|
+
requiresApproval: data.requiresApproval === true || pendingApprovals > 0,
|
|
205
|
+
pendingApprovals,
|
|
206
|
+
summary: typeof data.summary === 'string' ? data.summary : `Step ${stepNumber + 1}`,
|
|
207
|
+
usage: {
|
|
208
|
+
inputTokens: typeof usage.inputTokens === 'number' && Number.isFinite(usage.inputTokens)
|
|
209
|
+
? usage.inputTokens
|
|
210
|
+
: 0,
|
|
211
|
+
outputTokens: typeof usage.outputTokens === 'number' && Number.isFinite(usage.outputTokens)
|
|
212
|
+
? usage.outputTokens
|
|
213
|
+
: 0,
|
|
214
|
+
totalTokens: typeof usage.totalTokens === 'number' && Number.isFinite(usage.totalTokens)
|
|
215
|
+
? usage.totalTokens
|
|
216
|
+
: 0,
|
|
217
|
+
},
|
|
218
|
+
createdAt: typeof data.createdAt === 'string' ? data.createdAt : new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function normalizePipelineState(data) {
|
|
222
|
+
if (!isRecord(data))
|
|
223
|
+
return null;
|
|
224
|
+
if (data.status !== 'running' &&
|
|
225
|
+
data.status !== 'completed' &&
|
|
226
|
+
data.status !== 'safety-stop' &&
|
|
227
|
+
data.status !== 'aborted' &&
|
|
228
|
+
data.status !== 'error') {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
if (typeof data.maxSteps !== 'number' || !Number.isFinite(data.maxSteps)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
if (data.stepLimitSource !== 'explicit' &&
|
|
235
|
+
data.stepLimitSource !== 'module' &&
|
|
236
|
+
data.stepLimitSource !== 'safety') {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
status: data.status,
|
|
241
|
+
totalSteps: typeof data.totalSteps === 'number' && Number.isFinite(data.totalSteps)
|
|
242
|
+
? Math.max(0, Math.floor(data.totalSteps))
|
|
243
|
+
: 0,
|
|
244
|
+
finishReason: typeof data.finishReason === 'string' ? data.finishReason : undefined,
|
|
245
|
+
maxSteps: Math.max(1, Math.floor(data.maxSteps)),
|
|
246
|
+
stepLimitSource: data.stepLimitSource,
|
|
247
|
+
updatedAt: typeof data.updatedAt === 'string' ? data.updatedAt : new Date().toISOString(),
|
|
248
|
+
message: typeof data.message === 'string' ? data.message : undefined,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Provider component that manages chat state and exposes it to child components.
|
|
253
|
+
*
|
|
254
|
+
* Transport-agnostic: pick a transport hook (Vercel, LangGraph, or your own)
|
|
255
|
+
* and pass it via the `transport` prop.
|
|
256
|
+
*
|
|
257
|
+
* Features:
|
|
258
|
+
* - Message sanitization (removes orphaned approval-requested tool parts)
|
|
259
|
+
* - Session persistence (load on mount, save on change)
|
|
260
|
+
* - Approval extraction and response handling
|
|
261
|
+
* - Truncation detection via `data-truncation` data parts
|
|
262
|
+
* - Suggestion extraction from `suggest_followups` tool output
|
|
263
|
+
* - Pagination (visible message count, loadMore)
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```tsx
|
|
267
|
+
* import { AiChatProvider, AiChatPanel } from 'glirastes/react';
|
|
268
|
+
* import { useVercelAiChatTransport } from 'glirastes/react/vercel';
|
|
269
|
+
*
|
|
270
|
+
* function App() {
|
|
271
|
+
* const transport = useVercelAiChatTransport({ endpoint: '/api/ai/chat' });
|
|
272
|
+
* return (
|
|
273
|
+
* <AiChatProvider transport={transport}>
|
|
274
|
+
* <AiChatPanel />
|
|
275
|
+
* </AiChatProvider>
|
|
276
|
+
* );
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
function AiChatProvider({ transport, mentions, toolResults: userToolResults, voice: userVoice, session, classNames: userClassNames, components: userComponents, suggestions: userSuggestions, locale: userLocale, theme: userTheme, onToolResult, onBeforeSend, approvalDescription: userApprovalDescription, continueMessage = 'Please continue.', toolLabels: userToolLabels, pipelineMessageTranslator, stepSummaryFormatter, children, }) {
|
|
281
|
+
// Merge caller-supplied locale overrides with English defaults.
|
|
282
|
+
const locale = (0, react_1.useMemo)(() => ({ ...exports.DEFAULT_LOCALE, ...(userLocale ?? {}) }), [userLocale]);
|
|
283
|
+
// Resolve `theme` prop → CSS variables spread onto widget roots.
|
|
284
|
+
const themeVars = (0, react_1.useMemo)(() => (0, themes_js_1.getThemeVars)(userTheme), [userTheme]);
|
|
285
|
+
const themeEffect = (0, react_1.useMemo)(() => (0, themes_js_1.getThemeEffect)(userTheme), [userTheme]);
|
|
286
|
+
const classNames = (0, react_1.useMemo)(() => userClassNames ?? {}, [userClassNames]);
|
|
287
|
+
const components = (0, react_1.useMemo)(() => userComponents ?? {}, [userComponents]);
|
|
288
|
+
const suggestionsConfig = (0, react_1.useMemo)(() => ({ enabled: true, count: 3, autoSuggest: true, ...userSuggestions }), [userSuggestions]);
|
|
289
|
+
const toolResultConfig = (0, react_1.useMemo)(() => ({ mode: 'default', allowlist: [], ...userToolResults }), [userToolResults]);
|
|
290
|
+
const voiceConfig = (0, react_1.useMemo)(() => ({
|
|
291
|
+
enabled: false,
|
|
292
|
+
language: 'de',
|
|
293
|
+
recordingBar: { enabled: true, variant: 'inline', ...(userVoice?.recordingBar ?? {}) },
|
|
294
|
+
...userVoice,
|
|
295
|
+
}), [userVoice]);
|
|
296
|
+
const toolLabels = (0, react_1.useMemo)(() => userToolLabels ?? {}, [userToolLabels]);
|
|
297
|
+
const approvalDescriptionFn = userApprovalDescription ?? defaultApprovalDescription;
|
|
298
|
+
// Approvals state (Map for O(1) lookup)
|
|
299
|
+
const [pendingApprovalsMap, setPendingApprovalsMap] = (0, react_1.useState)(new Map());
|
|
300
|
+
const [processingApprovalId, setProcessingApprovalId] = (0, react_1.useState)(null);
|
|
301
|
+
const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Multi-session state
|
|
304
|
+
//
|
|
305
|
+
// `session.activeId` is the controlled form — when the drop-in widget owns
|
|
306
|
+
// the id (so it can feed it to the transport as `threadId`). Otherwise we
|
|
307
|
+
// fall back to an internal `uncontrolledActiveId`, seeded from
|
|
308
|
+
// `defaultActiveId` / `'default'`.
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
const [uncontrolledActiveId, setUncontrolledActiveId] = (0, react_1.useState)(() => session?.activeId ?? session?.defaultActiveId ?? 'default');
|
|
311
|
+
const activeSessionId = session?.activeId ?? uncontrolledActiveId;
|
|
312
|
+
const [sessions, setSessions] = (0, react_1.useState)([]);
|
|
313
|
+
const [isSessionsLoading, setIsSessionsLoading] = (0, react_1.useState)(false);
|
|
314
|
+
const sessionsSupported = Boolean(session && session.list && session.create && session.remove);
|
|
315
|
+
// Keep a ref so callbacks inside effects read the current session config
|
|
316
|
+
// without re-binding on every render.
|
|
317
|
+
const sessionRef = (0, react_1.useRef)(session);
|
|
318
|
+
sessionRef.current = session;
|
|
319
|
+
// Truncation
|
|
320
|
+
const [truncation, setTruncation] = (0, react_1.useState)(null);
|
|
321
|
+
const [pipelineSteps, setPipelineSteps] = (0, react_1.useState)([]);
|
|
322
|
+
const [pipelineState, setPipelineState] = (0, react_1.useState)(null);
|
|
323
|
+
// Pagination
|
|
324
|
+
const [visibleMessageCount, setVisibleMessageCount] = (0, react_1.useState)(30);
|
|
325
|
+
// Suggestions
|
|
326
|
+
const [suggestionChips, setSuggestionChips] = (0, react_1.useState)(suggestionsConfig.staticChips ?? []);
|
|
327
|
+
const isSuggestionsGenerating = false; // Suggestions come from tool output, not async generation
|
|
328
|
+
// Mentions
|
|
329
|
+
const [mentionQuery, setMentionQuery] = (0, react_1.useState)('');
|
|
330
|
+
const [mentionTrigger, setMentionTrigger] = (0, react_1.useState)(null);
|
|
331
|
+
const [mentionResults, setMentionResults] = (0, react_1.useState)([]);
|
|
332
|
+
const [activeMentions, setActiveMentions] = (0, react_1.useState)([]);
|
|
333
|
+
const [isMentionSearching, setIsMentionSearching] = (0, react_1.useState)(false);
|
|
334
|
+
// Refs for deduplication
|
|
335
|
+
const processedToolCallsRef = (0, react_1.useRef)(new Set());
|
|
336
|
+
const handledApprovalIdsRef = (0, react_1.useRef)(new Set());
|
|
337
|
+
// Stable refs for callbacks to avoid re-creating transport
|
|
338
|
+
const onToolResultRef = (0, react_1.useRef)(onToolResult);
|
|
339
|
+
onToolResultRef.current = onToolResult;
|
|
340
|
+
const approvalDescriptionRef = (0, react_1.useRef)(approvalDescriptionFn);
|
|
341
|
+
approvalDescriptionRef.current = approvalDescriptionFn;
|
|
342
|
+
// Data part handler — processes pipeline and truncation events from any transport
|
|
343
|
+
const handleDataPart = (0, react_1.useCallback)((dataPart) => {
|
|
344
|
+
if (dataPart.type === 'data-truncation') {
|
|
345
|
+
const data = dataPart.data;
|
|
346
|
+
setTruncation({
|
|
347
|
+
completedTools: data.completedTools,
|
|
348
|
+
message: data.message,
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (dataPart.type === 'data-step-report') {
|
|
353
|
+
const report = normalizePipelineStepReport(dataPart.data);
|
|
354
|
+
if (!report)
|
|
355
|
+
return;
|
|
356
|
+
setPipelineSteps((prev) => (0, merge_pipeline_steps_js_1.mergePipelineSteps)(prev, [report]));
|
|
357
|
+
setPipelineState((prev) => prev ?? {
|
|
358
|
+
status: 'running',
|
|
359
|
+
totalSteps: report.stepNumber + 1,
|
|
360
|
+
maxSteps: 1,
|
|
361
|
+
stepLimitSource: 'safety',
|
|
362
|
+
updatedAt: new Date().toISOString(),
|
|
363
|
+
});
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (dataPart.type === 'data-pipeline-state') {
|
|
367
|
+
const nextState = normalizePipelineState(dataPart.data);
|
|
368
|
+
if (nextState) {
|
|
369
|
+
setPipelineState(nextState);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}, []);
|
|
373
|
+
const resolvedTransport = transport;
|
|
374
|
+
(0, react_1.useEffect)(() => {
|
|
375
|
+
return resolvedTransport.subscribeToData?.(handleDataPart);
|
|
376
|
+
}, [resolvedTransport, handleDataPart]);
|
|
377
|
+
const { messages: rawMessages, sendMessage, status, error, stop: transportStop, setMessages, addToolApprovalResponse, } = resolvedTransport;
|
|
378
|
+
const isLoading = status === 'submitted' || status === 'streaming';
|
|
379
|
+
(0, react_1.useEffect)(() => {
|
|
380
|
+
if (!error)
|
|
381
|
+
return;
|
|
382
|
+
setPipelineState((prev) => ({
|
|
383
|
+
status: 'error',
|
|
384
|
+
totalSteps: prev?.totalSteps ?? pipelineSteps.length,
|
|
385
|
+
finishReason: 'error',
|
|
386
|
+
maxSteps: prev?.maxSteps ?? 1,
|
|
387
|
+
stepLimitSource: prev?.stepLimitSource ?? 'safety',
|
|
388
|
+
updatedAt: new Date().toISOString(),
|
|
389
|
+
message: typeof error.message === 'string' ? error.message : 'Pipeline failed.',
|
|
390
|
+
}));
|
|
391
|
+
}, [error, pipelineSteps.length]);
|
|
392
|
+
// Mark stored messages as already processed (prevents re-dispatching tool results)
|
|
393
|
+
const markStoredMessagesAsProcessed = (0, react_1.useCallback)((storedMessages) => {
|
|
394
|
+
for (const message of storedMessages) {
|
|
395
|
+
if (!isRecord(message))
|
|
396
|
+
continue;
|
|
397
|
+
const messageId = typeof message.id === 'string' ? message.id : 'stored-message';
|
|
398
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
399
|
+
for (const part of parts) {
|
|
400
|
+
if (!isRecord(part))
|
|
401
|
+
continue;
|
|
402
|
+
const partType = typeof part.type === 'string' ? part.type : '';
|
|
403
|
+
if (partType.startsWith('tool-')) {
|
|
404
|
+
const toolCallId = typeof part.toolCallId === 'string'
|
|
405
|
+
? part.toolCallId
|
|
406
|
+
: `${messageId}-${partType}`;
|
|
407
|
+
processedToolCallsRef.current.add(toolCallId);
|
|
408
|
+
if (part.state === 'approval-requested' && isRecord(part.approval) && typeof part.approval.id === 'string') {
|
|
409
|
+
handledApprovalIdsRef.current.add(part.approval.id);
|
|
410
|
+
}
|
|
411
|
+
if (part.state === 'output-available' && isRecord(part.output) && part.output.success && part.output.taskId) {
|
|
412
|
+
processedToolCallsRef.current.add(`task-${toolCallId}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}, []);
|
|
418
|
+
// Load messages from session whenever the active session changes.
|
|
419
|
+
// Runs on mount and on every switchSession() call.
|
|
420
|
+
(0, react_1.useEffect)(() => {
|
|
421
|
+
const cfg = sessionRef.current;
|
|
422
|
+
if (!cfg?.load) {
|
|
423
|
+
if (!isInitialized)
|
|
424
|
+
setIsInitialized(true);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
// Don't try to load the bootstrap sentinel when multi-session mode is
|
|
428
|
+
// active — `'default'` is unlikely to be a valid session id in the
|
|
429
|
+
// consumer's backend, and the auto-select effect (below) will switch
|
|
430
|
+
// us to the first real session as soon as `list()` resolves.
|
|
431
|
+
const isBootstrapSentinel = activeSessionId === 'default' &&
|
|
432
|
+
cfg.list !== undefined &&
|
|
433
|
+
cfg.activeId === undefined;
|
|
434
|
+
if (isBootstrapSentinel) {
|
|
435
|
+
if (!isInitialized)
|
|
436
|
+
setIsInitialized(true);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
let cancelled = false;
|
|
440
|
+
(async () => {
|
|
441
|
+
try {
|
|
442
|
+
const loaded = await Promise.resolve(cfg.load(activeSessionId));
|
|
443
|
+
if (cancelled)
|
|
444
|
+
return;
|
|
445
|
+
processedToolCallsRef.current.clear();
|
|
446
|
+
handledApprovalIdsRef.current.clear();
|
|
447
|
+
// Coerce wire-shape rows (with `content: string`) into SDK
|
|
448
|
+
// messages (with `parts: [...]`) so bridges don't have to.
|
|
449
|
+
const coerced = loaded ? coerceLoadedMessages(loaded) : [];
|
|
450
|
+
if (coerced.length > 0) {
|
|
451
|
+
markStoredMessagesAsProcessed(coerced);
|
|
452
|
+
const sanitized = sanitizeMessages(coerced);
|
|
453
|
+
setMessages(sanitized);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
setMessages([]);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
if (cancelled)
|
|
461
|
+
return;
|
|
462
|
+
console.warn(`[AiChatProvider] session.load("${activeSessionId}") failed:`, err);
|
|
463
|
+
setMessages([]);
|
|
464
|
+
}
|
|
465
|
+
finally {
|
|
466
|
+
if (!cancelled)
|
|
467
|
+
setIsInitialized(true);
|
|
468
|
+
}
|
|
469
|
+
})();
|
|
470
|
+
return () => {
|
|
471
|
+
cancelled = true;
|
|
472
|
+
};
|
|
473
|
+
// rawMessages intentionally excluded — we only re-load on session switch.
|
|
474
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
475
|
+
}, [activeSessionId, setMessages, markStoredMessagesAsProcessed]);
|
|
476
|
+
// Save messages to session on change (scoped to the active session).
|
|
477
|
+
(0, react_1.useEffect)(() => {
|
|
478
|
+
if (!isInitialized)
|
|
479
|
+
return;
|
|
480
|
+
if (rawMessages.length === 0)
|
|
481
|
+
return;
|
|
482
|
+
const cfg = sessionRef.current;
|
|
483
|
+
if (!cfg?.save)
|
|
484
|
+
return;
|
|
485
|
+
void Promise.resolve(cfg.save(activeSessionId, rawMessages));
|
|
486
|
+
}, [rawMessages, isInitialized, activeSessionId]);
|
|
487
|
+
// Process tool results and approvals from streaming messages
|
|
488
|
+
(0, react_1.useEffect)(() => {
|
|
489
|
+
if (rawMessages.length === 0)
|
|
490
|
+
return;
|
|
491
|
+
const lastMessage = rawMessages[rawMessages.length - 1];
|
|
492
|
+
if (lastMessage.role !== 'assistant')
|
|
493
|
+
return;
|
|
494
|
+
for (const part of lastMessage.parts) {
|
|
495
|
+
if (!('type' in part))
|
|
496
|
+
continue;
|
|
497
|
+
const partType = part.type;
|
|
498
|
+
if (partType.startsWith('tool-')) {
|
|
499
|
+
const toolPart = part;
|
|
500
|
+
const toolName = partType.replace('tool-', '');
|
|
501
|
+
// Detect approval-requested
|
|
502
|
+
if (toolPart.state === 'approval-requested' &&
|
|
503
|
+
toolPart.approval?.id &&
|
|
504
|
+
!handledApprovalIdsRef.current.has(toolPart.approval.id)) {
|
|
505
|
+
handledApprovalIdsRef.current.add(toolPart.approval.id);
|
|
506
|
+
const pending = approvalDescriptionRef.current(toolName, toolPart.approval.id, toolPart.input);
|
|
507
|
+
setPendingApprovalsMap(prev => new Map(prev).set(toolPart.approval.id, pending));
|
|
508
|
+
}
|
|
509
|
+
// Tool result available — call onToolResult
|
|
510
|
+
if (toolPart.state === 'output-available' && isRecord(toolPart.output)) {
|
|
511
|
+
const toolCallId = toolPart.toolCallId || `${lastMessage.id}-${partType}`;
|
|
512
|
+
if (!processedToolCallsRef.current.has(toolCallId)) {
|
|
513
|
+
processedToolCallsRef.current.add(toolCallId);
|
|
514
|
+
onToolResultRef.current?.(toolName, toolPart.output);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}, [rawMessages]);
|
|
520
|
+
// Extract suggestions from suggest_followups tool
|
|
521
|
+
(0, react_1.useEffect)(() => {
|
|
522
|
+
if (rawMessages.length === 0)
|
|
523
|
+
return;
|
|
524
|
+
const count = suggestionsConfig.count ?? 3;
|
|
525
|
+
let latestFollowups = null;
|
|
526
|
+
for (let messageIndex = rawMessages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
527
|
+
const message = rawMessages[messageIndex];
|
|
528
|
+
if (message.role !== 'assistant')
|
|
529
|
+
continue;
|
|
530
|
+
for (const part of message.parts) {
|
|
531
|
+
if (!('type' in part))
|
|
532
|
+
continue;
|
|
533
|
+
const partType = part.type;
|
|
534
|
+
if (partType !== 'tool-suggest_followups')
|
|
535
|
+
continue;
|
|
536
|
+
const toolPart = part;
|
|
537
|
+
if (toolPart.state !== 'output-available' || !isRecord(toolPart.output)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const result = toolPart.output;
|
|
541
|
+
if (!Array.isArray(result.followups))
|
|
542
|
+
continue;
|
|
543
|
+
const followups = result.followups.filter((f) => typeof f === 'string' && f.trim().length > 0);
|
|
544
|
+
if (followups.length > 0) {
|
|
545
|
+
latestFollowups = followups.slice(0, count);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (latestFollowups && latestFollowups.length > 0) {
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (latestFollowups && latestFollowups.length > 0) {
|
|
553
|
+
setSuggestionChips(latestFollowups);
|
|
554
|
+
}
|
|
555
|
+
}, [rawMessages, suggestionsConfig.count]);
|
|
556
|
+
// Fallback to static chips when model doesn't call suggest_followups
|
|
557
|
+
const prevIsLoadingRef = (0, react_1.useRef)(false);
|
|
558
|
+
(0, react_1.useEffect)(() => {
|
|
559
|
+
// Trigger when loading transitions from true → false
|
|
560
|
+
if (prevIsLoadingRef.current && !isLoading) {
|
|
561
|
+
// If no dynamic chips were generated, restore static chips
|
|
562
|
+
setSuggestionChips((current) => {
|
|
563
|
+
if (current.length === 0 && suggestionsConfig.staticChips?.length) {
|
|
564
|
+
return suggestionsConfig.staticChips;
|
|
565
|
+
}
|
|
566
|
+
return current;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
prevIsLoadingRef.current = isLoading;
|
|
570
|
+
}, [isLoading, suggestionsConfig.staticChips]);
|
|
571
|
+
// Mention search
|
|
572
|
+
(0, react_1.useEffect)(() => {
|
|
573
|
+
let cancelled = false;
|
|
574
|
+
const run = async () => {
|
|
575
|
+
if (!mentions || mentionQuery.trim().length === 0) {
|
|
576
|
+
setMentionResults([]);
|
|
577
|
+
setIsMentionSearching(false);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
setIsMentionSearching(true);
|
|
581
|
+
const query = mentionQuery.trim();
|
|
582
|
+
const searchTypes = getMentionTypesForTrigger(mentions.types, mentionTrigger);
|
|
583
|
+
const grouped = await Promise.all(searchTypes.map(async (type) => {
|
|
584
|
+
try {
|
|
585
|
+
return await mentions.search(query, type);
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
}));
|
|
591
|
+
if (cancelled)
|
|
592
|
+
return;
|
|
593
|
+
const deduped = new Map();
|
|
594
|
+
for (const items of grouped) {
|
|
595
|
+
for (const item of items) {
|
|
596
|
+
deduped.set(`${item.type}:${item.id}`, item);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
setMentionResults(Array.from(deduped.values()));
|
|
600
|
+
setIsMentionSearching(false);
|
|
601
|
+
};
|
|
602
|
+
void run();
|
|
603
|
+
return () => {
|
|
604
|
+
cancelled = true;
|
|
605
|
+
};
|
|
606
|
+
}, [mentionQuery, mentionTrigger, mentions]);
|
|
607
|
+
// Actions
|
|
608
|
+
const send = (0, react_1.useCallback)(async (message) => {
|
|
609
|
+
// Clear truncation and suggestion chips on new message
|
|
610
|
+
setTruncation(null);
|
|
611
|
+
setSuggestionChips([]);
|
|
612
|
+
setPipelineSteps([]);
|
|
613
|
+
setPipelineState(null);
|
|
614
|
+
// Preprocess message
|
|
615
|
+
let processedMessage = message;
|
|
616
|
+
const mentionSerializeConfig = mentions?.serialize;
|
|
617
|
+
if (mentionSerializeConfig?.enabled !== false) {
|
|
618
|
+
processedMessage = (0, mention_markup_js_1.serializeMentionsForSend)(processedMessage, activeMentions, mentionSerializeConfig);
|
|
619
|
+
}
|
|
620
|
+
if (onBeforeSend) {
|
|
621
|
+
processedMessage = onBeforeSend(processedMessage, activeMentions);
|
|
622
|
+
}
|
|
623
|
+
// Sanitize messages before sending to prevent AI_MissingToolResultsError
|
|
624
|
+
const currentMessages = rawMessages;
|
|
625
|
+
const sanitized = sanitizeMessages(currentMessages);
|
|
626
|
+
if (sanitized.length !== currentMessages.length || JSON.stringify(sanitized) !== JSON.stringify(currentMessages)) {
|
|
627
|
+
setMessages(sanitized);
|
|
628
|
+
setPendingApprovalsMap(new Map());
|
|
629
|
+
}
|
|
630
|
+
// Auto-reject pending approvals when user sends new message
|
|
631
|
+
if (pendingApprovalsMap.size > 0) {
|
|
632
|
+
const approvalIds = Array.from(pendingApprovalsMap.keys());
|
|
633
|
+
setPendingApprovalsMap(new Map());
|
|
634
|
+
for (const approvalId of approvalIds) {
|
|
635
|
+
await addToolApprovalResponse({
|
|
636
|
+
id: approvalId,
|
|
637
|
+
approved: false,
|
|
638
|
+
reason: 'Auto-rejected: user sent a new message.',
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
await sendMessage({ text: processedMessage });
|
|
643
|
+
setActiveMentions([]);
|
|
644
|
+
setMentionQuery('');
|
|
645
|
+
setMentionTrigger(null);
|
|
646
|
+
setMentionResults([]);
|
|
647
|
+
setIsMentionSearching(false);
|
|
648
|
+
}, [
|
|
649
|
+
rawMessages,
|
|
650
|
+
setMessages,
|
|
651
|
+
sendMessage,
|
|
652
|
+
addToolApprovalResponse,
|
|
653
|
+
pendingApprovalsMap,
|
|
654
|
+
mentions,
|
|
655
|
+
onBeforeSend,
|
|
656
|
+
activeMentions,
|
|
657
|
+
]);
|
|
658
|
+
const stop = (0, react_1.useCallback)(() => {
|
|
659
|
+
setPipelineState((prev) => ({
|
|
660
|
+
status: 'aborted',
|
|
661
|
+
totalSteps: prev?.totalSteps ?? pipelineSteps.length,
|
|
662
|
+
finishReason: 'stop',
|
|
663
|
+
maxSteps: prev?.maxSteps ?? 1,
|
|
664
|
+
stepLimitSource: prev?.stepLimitSource ?? 'safety',
|
|
665
|
+
updatedAt: new Date().toISOString(),
|
|
666
|
+
message: 'Stopped by user.',
|
|
667
|
+
}));
|
|
668
|
+
transportStop();
|
|
669
|
+
}, [transportStop, pipelineSteps.length]);
|
|
670
|
+
const clear = (0, react_1.useCallback)(() => {
|
|
671
|
+
setMessages([]);
|
|
672
|
+
void Promise.resolve(sessionRef.current?.clear?.(activeSessionId));
|
|
673
|
+
processedToolCallsRef.current.clear();
|
|
674
|
+
handledApprovalIdsRef.current.clear();
|
|
675
|
+
setPendingApprovalsMap(new Map());
|
|
676
|
+
setVisibleMessageCount(30);
|
|
677
|
+
setTruncation(null);
|
|
678
|
+
setPipelineSteps([]);
|
|
679
|
+
setPipelineState(null);
|
|
680
|
+
setMentionQuery('');
|
|
681
|
+
setMentionTrigger(null);
|
|
682
|
+
setMentionResults([]);
|
|
683
|
+
setActiveMentions([]);
|
|
684
|
+
setIsMentionSearching(false);
|
|
685
|
+
setSuggestionChips(suggestionsConfig.staticChips ?? []);
|
|
686
|
+
}, [setMessages, activeSessionId, suggestionsConfig.staticChips]);
|
|
687
|
+
// ---------------------------------------------------------------------------
|
|
688
|
+
// Multi-session actions
|
|
689
|
+
// ---------------------------------------------------------------------------
|
|
690
|
+
const applyActiveId = (0, react_1.useCallback)((id) => {
|
|
691
|
+
if (session?.activeId === undefined) {
|
|
692
|
+
// Uncontrolled
|
|
693
|
+
setUncontrolledActiveId(id);
|
|
694
|
+
}
|
|
695
|
+
session?.onActiveIdChange?.(id);
|
|
696
|
+
}, [session]);
|
|
697
|
+
const refreshSessions = (0, react_1.useCallback)(async () => {
|
|
698
|
+
const cfg = sessionRef.current;
|
|
699
|
+
if (!cfg?.list)
|
|
700
|
+
return;
|
|
701
|
+
setIsSessionsLoading(true);
|
|
702
|
+
try {
|
|
703
|
+
const list = await cfg.list();
|
|
704
|
+
setSessions(list);
|
|
705
|
+
// Auto-select the first session if we're still on the bootstrap
|
|
706
|
+
// sentinel and the consumer doesn't control `activeId` externally.
|
|
707
|
+
if (list.length > 0 &&
|
|
708
|
+
cfg.activeId === undefined &&
|
|
709
|
+
(uncontrolledActiveIdRef.current === 'default' ||
|
|
710
|
+
!list.some((s) => s.id === uncontrolledActiveIdRef.current))) {
|
|
711
|
+
const next = list[0].id;
|
|
712
|
+
setUncontrolledActiveId(next);
|
|
713
|
+
cfg.onActiveIdChange?.(next);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
catch (err) {
|
|
717
|
+
console.warn('[AiChatProvider] session.list failed:', err);
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
setIsSessionsLoading(false);
|
|
721
|
+
}
|
|
722
|
+
}, []);
|
|
723
|
+
// Mirror state into a ref so refreshSessions doesn't need it as a dep.
|
|
724
|
+
const uncontrolledActiveIdRef = (0, react_1.useRef)(uncontrolledActiveId);
|
|
725
|
+
uncontrolledActiveIdRef.current = uncontrolledActiveId;
|
|
726
|
+
// Auto-fetch sessions when the consumer supplies a list() function.
|
|
727
|
+
(0, react_1.useEffect)(() => {
|
|
728
|
+
if (!sessionsSupported)
|
|
729
|
+
return;
|
|
730
|
+
void refreshSessions();
|
|
731
|
+
}, [sessionsSupported, refreshSessions]);
|
|
732
|
+
// Refetch sessions when a streaming response finishes, so backend-generated
|
|
733
|
+
// titles (auto-generated after the first exchange) appear without a reload.
|
|
734
|
+
const wasLoadingRef = (0, react_1.useRef)(false);
|
|
735
|
+
(0, react_1.useEffect)(() => {
|
|
736
|
+
if (!sessionsSupported) {
|
|
737
|
+
wasLoadingRef.current = isLoading;
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (wasLoadingRef.current && !isLoading) {
|
|
741
|
+
void refreshSessions();
|
|
742
|
+
}
|
|
743
|
+
wasLoadingRef.current = isLoading;
|
|
744
|
+
}, [isLoading, sessionsSupported, refreshSessions]);
|
|
745
|
+
const switchSession = (0, react_1.useCallback)(async (id) => {
|
|
746
|
+
if (id === activeSessionId)
|
|
747
|
+
return;
|
|
748
|
+
// Reset transient UI state before loading new session
|
|
749
|
+
setPendingApprovalsMap(new Map());
|
|
750
|
+
setTruncation(null);
|
|
751
|
+
setPipelineSteps([]);
|
|
752
|
+
setPipelineState(null);
|
|
753
|
+
setSuggestionChips(suggestionsConfig.staticChips ?? []);
|
|
754
|
+
setVisibleMessageCount(30);
|
|
755
|
+
applyActiveId(id);
|
|
756
|
+
}, [activeSessionId, applyActiveId, suggestionsConfig.staticChips]);
|
|
757
|
+
const createSession = (0, react_1.useCallback)(async (opts) => {
|
|
758
|
+
const cfg = sessionRef.current;
|
|
759
|
+
if (!cfg?.create)
|
|
760
|
+
return null;
|
|
761
|
+
const created = await cfg.create(opts);
|
|
762
|
+
await refreshSessions();
|
|
763
|
+
await switchSession(created.id);
|
|
764
|
+
return created;
|
|
765
|
+
}, [refreshSessions, switchSession]);
|
|
766
|
+
const removeSession = (0, react_1.useCallback)(async (id) => {
|
|
767
|
+
const cfg = sessionRef.current;
|
|
768
|
+
if (!cfg?.remove)
|
|
769
|
+
return;
|
|
770
|
+
await cfg.remove(id);
|
|
771
|
+
// Re-fetch first so the fallback target reflects reality
|
|
772
|
+
const cfgList = cfg.list ? await cfg.list() : [];
|
|
773
|
+
setSessions(cfgList);
|
|
774
|
+
if (id === activeSessionId) {
|
|
775
|
+
const next = cfgList.find((s) => s.id !== id);
|
|
776
|
+
if (next) {
|
|
777
|
+
await switchSession(next.id);
|
|
778
|
+
}
|
|
779
|
+
else if (cfg.create) {
|
|
780
|
+
// No sessions left — spin up a fresh one
|
|
781
|
+
const created = await cfg.create();
|
|
782
|
+
const refreshed = cfg.list ? await cfg.list() : [created];
|
|
783
|
+
setSessions(refreshed);
|
|
784
|
+
await switchSession(created.id);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}, [activeSessionId, switchSession]);
|
|
788
|
+
const renameSession = (0, react_1.useCallback)(async (id, title) => {
|
|
789
|
+
const cfg = sessionRef.current;
|
|
790
|
+
if (!cfg?.rename)
|
|
791
|
+
return;
|
|
792
|
+
await cfg.rename(id, title);
|
|
793
|
+
await refreshSessions();
|
|
794
|
+
}, [refreshSessions]);
|
|
795
|
+
// Pagination + consolidation (removes duplicate assistant bubbles from pipeline resumes)
|
|
796
|
+
const allMessages = rawMessages;
|
|
797
|
+
const consolidated = consolidateAssistantMessages(allMessages);
|
|
798
|
+
const hasMore = consolidated.length > visibleMessageCount;
|
|
799
|
+
const visibleMessages = hasMore
|
|
800
|
+
? consolidated.slice(-visibleMessageCount)
|
|
801
|
+
: consolidated;
|
|
802
|
+
const loadMore = (0, react_1.useCallback)(() => {
|
|
803
|
+
setVisibleMessageCount(prev => prev + 10);
|
|
804
|
+
}, []);
|
|
805
|
+
// Approval handlers
|
|
806
|
+
const approve = (0, react_1.useCallback)(async (id) => {
|
|
807
|
+
setProcessingApprovalId(id);
|
|
808
|
+
setPendingApprovalsMap(prev => {
|
|
809
|
+
const next = new Map(prev);
|
|
810
|
+
next.delete(id);
|
|
811
|
+
return next;
|
|
812
|
+
});
|
|
813
|
+
await addToolApprovalResponse({ id, approved: true });
|
|
814
|
+
setProcessingApprovalId(null);
|
|
815
|
+
}, [addToolApprovalResponse]);
|
|
816
|
+
const reject = (0, react_1.useCallback)(async (id) => {
|
|
817
|
+
setProcessingApprovalId(id);
|
|
818
|
+
setPendingApprovalsMap(prev => {
|
|
819
|
+
const next = new Map(prev);
|
|
820
|
+
next.delete(id);
|
|
821
|
+
return next;
|
|
822
|
+
});
|
|
823
|
+
await addToolApprovalResponse({
|
|
824
|
+
id,
|
|
825
|
+
approved: false,
|
|
826
|
+
reason: 'Rejected by user.',
|
|
827
|
+
});
|
|
828
|
+
setProcessingApprovalId(null);
|
|
829
|
+
}, [addToolApprovalResponse]);
|
|
830
|
+
// Truncation
|
|
831
|
+
const continueTruncation = (0, react_1.useCallback)(async () => {
|
|
832
|
+
setTruncation(null);
|
|
833
|
+
await sendMessage({ text: continueMessage });
|
|
834
|
+
}, [sendMessage, continueMessage]);
|
|
835
|
+
const dismissTruncation = (0, react_1.useCallback)(() => {
|
|
836
|
+
setTruncation(null);
|
|
837
|
+
}, []);
|
|
838
|
+
// Pending approvals as array (for context consumers)
|
|
839
|
+
const pendingApprovals = (0, react_1.useMemo)(() => Array.from(pendingApprovalsMap.values()), [pendingApprovalsMap]);
|
|
840
|
+
// Mention actions
|
|
841
|
+
const setMentionSearch = (0, react_1.useCallback)((query, trigger) => {
|
|
842
|
+
setMentionQuery(query);
|
|
843
|
+
setMentionTrigger(trigger);
|
|
844
|
+
}, []);
|
|
845
|
+
const clearMentionSearch = (0, react_1.useCallback)(() => {
|
|
846
|
+
setMentionQuery('');
|
|
847
|
+
setMentionTrigger(null);
|
|
848
|
+
setMentionResults([]);
|
|
849
|
+
setIsMentionSearching(false);
|
|
850
|
+
}, []);
|
|
851
|
+
const addActiveMention = (0, react_1.useCallback)((mention) => {
|
|
852
|
+
const mentionId = typeof mention.id === 'string' ? mention.id : '';
|
|
853
|
+
const mentionType = typeof mention.type === 'string' ? mention.type : '';
|
|
854
|
+
if (!mentionId || !mentionType)
|
|
855
|
+
return;
|
|
856
|
+
setActiveMentions((prev) => {
|
|
857
|
+
const mentionKey = `${mentionType}:${mentionId}`;
|
|
858
|
+
const next = prev.filter((item) => `${item.type}:${item.id}` !== mentionKey);
|
|
859
|
+
next.push(mention);
|
|
860
|
+
return next;
|
|
861
|
+
});
|
|
862
|
+
}, []);
|
|
863
|
+
const removeActiveMention = (0, react_1.useCallback)((mentionId) => {
|
|
864
|
+
setActiveMentions((prev) => prev.filter((item) => item.id !== mentionId));
|
|
865
|
+
}, []);
|
|
866
|
+
const clearActiveMentions = (0, react_1.useCallback)(() => {
|
|
867
|
+
setActiveMentions([]);
|
|
868
|
+
}, []);
|
|
869
|
+
const selectMention = (0, react_1.useCallback)((result) => {
|
|
870
|
+
if (mentions?.resolve) {
|
|
871
|
+
void mentions.resolve(result.id, result.type)
|
|
872
|
+
.then((resolved) => {
|
|
873
|
+
addActiveMention({
|
|
874
|
+
...resolved,
|
|
875
|
+
id: result.id,
|
|
876
|
+
type: result.type,
|
|
877
|
+
label: typeof resolved.label === 'string' ? resolved.label : result.label,
|
|
878
|
+
displayName: typeof resolved.displayName === 'string'
|
|
879
|
+
? resolved.displayName
|
|
880
|
+
: result.label,
|
|
881
|
+
});
|
|
882
|
+
})
|
|
883
|
+
.catch(() => {
|
|
884
|
+
addActiveMention({
|
|
885
|
+
id: result.id,
|
|
886
|
+
type: result.type,
|
|
887
|
+
label: result.label,
|
|
888
|
+
displayName: result.label,
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
addActiveMention({
|
|
894
|
+
id: result.id,
|
|
895
|
+
type: result.type,
|
|
896
|
+
label: result.label,
|
|
897
|
+
displayName: result.label,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
setMentionQuery('');
|
|
901
|
+
setMentionTrigger(null);
|
|
902
|
+
setMentionResults([]);
|
|
903
|
+
setIsMentionSearching(false);
|
|
904
|
+
}, [mentions, addActiveMention]);
|
|
905
|
+
const selectSuggestion = (0, react_1.useCallback)((suggestion) => {
|
|
906
|
+
send(suggestion);
|
|
907
|
+
}, [send]);
|
|
908
|
+
const refreshSuggestions = (0, react_1.useCallback)(() => {
|
|
909
|
+
// Suggestions are driven by tool output, not manual refresh
|
|
910
|
+
}, []);
|
|
911
|
+
// Build context value
|
|
912
|
+
const contextValue = (0, react_1.useMemo)(() => ({
|
|
913
|
+
classNames,
|
|
914
|
+
components,
|
|
915
|
+
locale,
|
|
916
|
+
themeVars,
|
|
917
|
+
themeEffect,
|
|
918
|
+
suggestions: suggestionsConfig,
|
|
919
|
+
toolLabels,
|
|
920
|
+
toolResults: toolResultConfig,
|
|
921
|
+
voice: voiceConfig,
|
|
922
|
+
mentionConfig: mentions,
|
|
923
|
+
pipelineMessageTranslator,
|
|
924
|
+
stepSummaryFormatter,
|
|
925
|
+
messages: visibleMessages,
|
|
926
|
+
isLoading,
|
|
927
|
+
error: error ?? null,
|
|
928
|
+
send,
|
|
929
|
+
stop,
|
|
930
|
+
clear,
|
|
931
|
+
loadMore,
|
|
932
|
+
hasMore,
|
|
933
|
+
pipelineSteps,
|
|
934
|
+
pipelineState,
|
|
935
|
+
pendingApprovals,
|
|
936
|
+
approve,
|
|
937
|
+
reject,
|
|
938
|
+
isProcessingApproval: processingApprovalId !== null,
|
|
939
|
+
approvalDescription: approvalDescriptionRef.current,
|
|
940
|
+
truncation,
|
|
941
|
+
continueTruncation,
|
|
942
|
+
dismissTruncation,
|
|
943
|
+
mentionQuery,
|
|
944
|
+
mentionTrigger,
|
|
945
|
+
mentionResults,
|
|
946
|
+
setMentionSearch,
|
|
947
|
+
clearMentionSearch,
|
|
948
|
+
selectMention,
|
|
949
|
+
activeMentions,
|
|
950
|
+
addActiveMention,
|
|
951
|
+
removeActiveMention,
|
|
952
|
+
clearActiveMentions,
|
|
953
|
+
isMentionSearching,
|
|
954
|
+
hasMentions: !!mentions,
|
|
955
|
+
suggestionChips,
|
|
956
|
+
selectSuggestion,
|
|
957
|
+
refreshSuggestions,
|
|
958
|
+
isSuggestionsGenerating,
|
|
959
|
+
sessions,
|
|
960
|
+
activeSessionId,
|
|
961
|
+
sessionsSupported,
|
|
962
|
+
isSessionsLoading,
|
|
963
|
+
switchSession,
|
|
964
|
+
createSession,
|
|
965
|
+
removeSession,
|
|
966
|
+
renameSession,
|
|
967
|
+
refreshSessions,
|
|
968
|
+
}), [
|
|
969
|
+
classNames,
|
|
970
|
+
components,
|
|
971
|
+
locale,
|
|
972
|
+
themeVars,
|
|
973
|
+
themeEffect,
|
|
974
|
+
suggestionsConfig,
|
|
975
|
+
toolLabels,
|
|
976
|
+
toolResultConfig,
|
|
977
|
+
voiceConfig,
|
|
978
|
+
mentions,
|
|
979
|
+
pipelineMessageTranslator,
|
|
980
|
+
stepSummaryFormatter,
|
|
981
|
+
visibleMessages,
|
|
982
|
+
isLoading,
|
|
983
|
+
error,
|
|
984
|
+
send,
|
|
985
|
+
stop,
|
|
986
|
+
clear,
|
|
987
|
+
loadMore,
|
|
988
|
+
hasMore,
|
|
989
|
+
pipelineSteps,
|
|
990
|
+
pipelineState,
|
|
991
|
+
pendingApprovals,
|
|
992
|
+
approve,
|
|
993
|
+
reject,
|
|
994
|
+
processingApprovalId,
|
|
995
|
+
truncation,
|
|
996
|
+
continueTruncation,
|
|
997
|
+
dismissTruncation,
|
|
998
|
+
mentionQuery,
|
|
999
|
+
mentionTrigger,
|
|
1000
|
+
mentionResults,
|
|
1001
|
+
setMentionSearch,
|
|
1002
|
+
clearMentionSearch,
|
|
1003
|
+
selectMention,
|
|
1004
|
+
activeMentions,
|
|
1005
|
+
addActiveMention,
|
|
1006
|
+
removeActiveMention,
|
|
1007
|
+
clearActiveMentions,
|
|
1008
|
+
isMentionSearching,
|
|
1009
|
+
suggestionChips,
|
|
1010
|
+
selectSuggestion,
|
|
1011
|
+
refreshSuggestions,
|
|
1012
|
+
isSuggestionsGenerating,
|
|
1013
|
+
sessions,
|
|
1014
|
+
activeSessionId,
|
|
1015
|
+
sessionsSupported,
|
|
1016
|
+
isSessionsLoading,
|
|
1017
|
+
switchSession,
|
|
1018
|
+
createSession,
|
|
1019
|
+
removeSession,
|
|
1020
|
+
renameSession,
|
|
1021
|
+
refreshSessions,
|
|
1022
|
+
]);
|
|
1023
|
+
return ((0, jsx_runtime_1.jsx)(chat_context_js_1.ChatContext.Provider, { value: contextValue, children: children }));
|
|
1024
|
+
}
|