@hed-hog/core 0.0.349 → 0.0.351
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/dist/access-log/access-log.controller.d.ts +16 -0
- package/dist/access-log/access-log.controller.d.ts.map +1 -0
- package/dist/access-log/access-log.controller.js +44 -0
- package/dist/access-log/access-log.controller.js.map +1 -0
- package/dist/access-log/access-log.interceptor.d.ts +9 -0
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -0
- package/dist/access-log/access-log.interceptor.js +46 -0
- package/dist/access-log/access-log.interceptor.js.map +1 -0
- package/dist/access-log/access-log.module.d.ts +3 -0
- package/dist/access-log/access-log.module.d.ts.map +1 -0
- package/dist/access-log/access-log.module.js +29 -0
- package/dist/access-log/access-log.module.js.map +1 -0
- package/dist/access-log/access-log.service.d.ts +47 -0
- package/dist/access-log/access-log.service.d.ts.map +1 -0
- package/dist/access-log/access-log.service.js +113 -0
- package/dist/access-log/access-log.service.js.map +1 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts +8 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -0
- package/dist/access-log/dto/query-access-log.dto.js +42 -0
- package/dist/access-log/dto/query-access-log.dto.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +12 -12
- package/dist/auth/auth.service.d.ts +27 -27
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +0 -4
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +2 -2
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +0 -1
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core/system.module.d.ts.map +1 -1
- package/dist/core/system.module.js +1 -0
- package/dist/core/system.module.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +15 -0
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard-user/dashboard-user.controller.d.ts +3 -3
- package/dist/dashboard/dashboard-user/dashboard-user.service.d.ts +3 -3
- package/dist/dashboard/dashboard.module.d.ts.map +1 -1
- package/dist/dashboard/dashboard.module.js +4 -1
- package/dist/dashboard/dashboard.module.js.map +1 -1
- package/dist/file/file.controller.js +0 -1
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +65 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +151 -3
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +8 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/azure.provider.d.ts +4 -0
- package/dist/file/provider/azure.provider.d.ts.map +1 -1
- package/dist/file/provider/azure.provider.js +14 -0
- package/dist/file/provider/azure.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts +4 -0
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +14 -0
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts +4 -0
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +19 -0
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/provider.factory.d.ts.map +1 -1
- package/dist/file/provider/provider.factory.js +3 -0
- package/dist/file/provider/provider.factory.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +4 -0
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +17 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.d.ts +6 -1
- package/dist/mcp/guards/mcp-auth.guard.d.ts.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js +29 -2
- package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
- package/dist/mcp/mcp-api-key.controller.d.ts +32 -0
- package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -0
- package/dist/mcp/mcp-api-key.controller.js +64 -0
- package/dist/mcp/mcp-api-key.controller.js.map +1 -0
- package/dist/mcp/mcp-api-key.service.d.ts +27 -0
- package/dist/mcp/mcp-api-key.service.d.ts.map +1 -0
- package/dist/mcp/mcp-api-key.service.js +74 -0
- package/dist/mcp/mcp-api-key.service.js.map +1 -0
- package/dist/mcp/mcp-audit.service.d.ts +3 -1
- package/dist/mcp/mcp-audit.service.d.ts.map +1 -1
- package/dist/mcp/mcp-audit.service.js +14 -2
- package/dist/mcp/mcp-audit.service.js.map +1 -1
- package/dist/mcp/mcp-registry.service.d.ts +1 -2
- package/dist/mcp/mcp-registry.service.d.ts.map +1 -1
- package/dist/mcp/mcp-registry.service.js +6 -2
- package/dist/mcp/mcp-registry.service.js.map +1 -1
- package/dist/mcp/mcp.controller.d.ts +3 -1
- package/dist/mcp/mcp.controller.d.ts.map +1 -1
- package/dist/mcp/mcp.controller.js +10 -2
- package/dist/mcp/mcp.controller.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +80 -1
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-ai.mcp-tools.d.ts +45 -0
- package/dist/mcp/tools/core-ai.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-ai.mcp-tools.js +214 -0
- package/dist/mcp/tools/core-ai.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts +21 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js +89 -0
- package/dist/mcp/tools/core-dashboard-items.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts +29 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.js +149 -0
- package/dist/mcp/tools/core-dashboard.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts +30 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js +185 -0
- package/dist/mcp/tools/core-event-webhooks.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.d.ts +26 -0
- package/dist/mcp/tools/core-files.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.js +140 -0
- package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts +26 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.js +168 -0
- package/dist/mcp/tools/core-integration-events.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +32 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js +178 -0
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts +14 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js +106 -0
- package/dist/mcp/tools/core-mail-sent.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mail.mcp-tools.d.ts +36 -0
- package/dist/mcp/tools/core-mail.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mail.mcp-tools.js +184 -0
- package/dist/mcp/tools/core-mail.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts +21 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.js +118 -0
- package/dist/mcp/tools/core-mcp-chat.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-menus.mcp-tools.d.ts +40 -0
- package/dist/mcp/tools/core-menus.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-menus.mcp-tools.js +268 -0
- package/dist/mcp/tools/core-menus.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.d.ts +31 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.js +178 -0
- package/dist/mcp/tools/core-notifications.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-profile.mcp-tools.d.ts +20 -0
- package/dist/mcp/tools/core-profile.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-profile.mcp-tools.js +111 -0
- package/dist/mcp/tools/core-profile.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-roles.mcp-tools.d.ts +70 -0
- package/dist/mcp/tools/core-roles.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-roles.mcp-tools.js +355 -0
- package/dist/mcp/tools/core-roles.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-routes.mcp-tools.d.ts +52 -0
- package/dist/mcp/tools/core-routes.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-routes.mcp-tools.js +254 -0
- package/dist/mcp/tools/core-routes.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-screens.mcp-tools.d.ts +47 -0
- package/dist/mcp/tools/core-screens.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-screens.mcp-tools.js +250 -0
- package/dist/mcp/tools/core-screens.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.d.ts +20 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.js +128 -0
- package/dist/mcp/tools/core-sessions.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-settings.mcp-tools.d.ts +25 -0
- package/dist/mcp/tools/core-settings.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-settings.mcp-tools.js +138 -0
- package/dist/mcp/tools/core-settings.mcp-tools.js.map +1 -1
- package/dist/mcp/tools/core-system.mcp-tools.d.ts +8 -0
- package/dist/mcp/tools/core-system.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-system.mcp-tools.js +40 -0
- package/dist/mcp/tools/core-system.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts +46 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.js +266 -0
- package/dist/mcp/tools/core-webhooks.mcp-tools.js.map +1 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts +5 -1
- package/dist/mcp-chat/mcp-chat.controller.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.controller.js +46 -0
- package/dist/mcp-chat/mcp-chat.controller.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts +31 -3
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +776 -38
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/notification/dto/create-notification.dto.d.ts +26 -0
- package/dist/notification/dto/create-notification.dto.d.ts.map +1 -0
- package/dist/notification/dto/create-notification.dto.js +86 -0
- package/dist/notification/dto/create-notification.dto.js.map +1 -0
- package/dist/notification/dto/update-progress.dto.d.ts +6 -0
- package/dist/notification/dto/update-progress.dto.d.ts.map +1 -0
- package/dist/notification/dto/update-progress.dto.js +33 -0
- package/dist/notification/dto/update-progress.dto.js.map +1 -0
- package/dist/notification/notification.controller.d.ts +112 -0
- package/dist/notification/notification.controller.d.ts.map +1 -0
- package/dist/notification/notification.controller.js +130 -0
- package/dist/notification/notification.controller.js.map +1 -0
- package/dist/notification/notification.module.d.ts +3 -0
- package/dist/notification/notification.module.d.ts.map +1 -0
- package/dist/notification/notification.module.js +29 -0
- package/dist/notification/notification.module.js.map +1 -0
- package/dist/notification/notification.service.d.ts +112 -0
- package/dist/notification/notification.service.d.ts.map +1 -0
- package/dist/notification/notification.service.js +269 -0
- package/dist/notification/notification.service.js.map +1 -0
- package/dist/pdf/pdf.service.js +1 -1
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +4 -4
- package/dist/profile/profile.controller.js +0 -1
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.module.d.ts.map +1 -1
- package/dist/profile/profile.module.js +1 -0
- package/dist/profile/profile.module.js.map +1 -1
- package/dist/profile/profile.service.d.ts +4 -4
- package/dist/session/session.controller.d.ts +3 -3
- package/dist/session/session.service.d.ts +9 -9
- package/dist/setting/dto/update-setting-list.dto.d.ts +9 -0
- package/dist/setting/dto/update-setting-list.dto.d.ts.map +1 -0
- package/dist/setting/dto/update-setting-list.dto.js +44 -0
- package/dist/setting/dto/update-setting-list.dto.js.map +1 -0
- package/dist/setting/setting.controller.d.ts +20 -1
- package/dist/setting/setting.controller.d.ts.map +1 -1
- package/dist/setting/setting.controller.js +12 -0
- package/dist/setting/setting.controller.js.map +1 -1
- package/dist/setting/setting.service.d.ts +20 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +80 -4
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +13 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +85 -0
- package/dist/task/task.service.js.map +1 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +0 -1
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +11 -11
- package/dist/user/user.service.d.ts +27 -27
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts +22 -0
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -0
- package/dist/webhook-integration/dto/webhook-action.dto.js +120 -0
- package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +7 -5
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +18 -5
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +18 -5
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +342 -59
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/dist/webhook-integration/webhook-utils.d.ts +1 -0
- package/dist/webhook-integration/webhook-utils.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-utils.js +38 -0
- package/dist/webhook-integration/webhook-utils.js.map +1 -1
- package/hedhog/data/menu.yaml +63 -4
- package/hedhog/data/route.yaml +1437 -0
- package/hedhog/data/setting_group.yaml +112 -0
- package/hedhog/data/setting_subgroup.yaml +32 -0
- package/hedhog/frontend/app/access-log/page.tsx.ejs +772 -0
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +285 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +32 -55
- package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +10 -3
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +33 -13
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +12 -5
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +12 -5
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +3559 -1835
- package/hedhog/frontend/app/mcp_chat/chat-components.tsx.ejs +633 -0
- package/hedhog/frontend/app/mcp_chat/chat-types.ts.ejs +16 -0
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +583 -384
- package/hedhog/frontend/app/menu/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/roles/page.tsx.ejs +18 -6
- package/hedhog/frontend/app/routes/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +822 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +445 -203
- package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +310 -304
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +41 -44
- package/hedhog/frontend/messages/en.json +343 -3
- package/hedhog/frontend/messages/pt.json +343 -3
- package/hedhog/table/access_log.yaml +31 -0
- package/hedhog/table/mcp_api_key.yaml +30 -0
- package/hedhog/table/notification.yaml +72 -0
- package/hedhog/table/webhook_action_log.yaml +49 -0
- package/package.json +5 -4
- package/src/access-log/access-log.controller.ts +29 -0
- package/src/access-log/access-log.interceptor.ts +38 -0
- package/src/access-log/access-log.module.ts +16 -0
- package/src/access-log/access-log.service.ts +129 -0
- package/src/access-log/dto/query-access-log.dto.ts +23 -0
- package/src/auth/auth.service.ts +0 -7
- package/src/challenge/challenge.service.ts +0 -2
- package/src/core/system.module.ts +1 -0
- package/src/core.module.ts +15 -0
- package/src/dashboard/dashboard.module.ts +4 -1
- package/src/file/file.controller.ts +14 -14
- package/src/file/file.service.ts +198 -4
- package/src/file/provider/abstract.provider.ts +10 -0
- package/src/file/provider/azure.provider.ts +24 -0
- package/src/file/provider/gcs.provider.ts +20 -0
- package/src/file/provider/local.provider.ts +30 -2
- package/src/file/provider/provider.factory.ts +3 -0
- package/src/file/provider/s3.provider.ts +22 -0
- package/src/index.ts +5 -0
- package/src/mcp/guards/mcp-auth.guard.ts +64 -32
- package/src/mcp/mcp-api-key.controller.ts +42 -0
- package/src/mcp/mcp-api-key.service.ts +66 -0
- package/src/mcp/mcp-audit.service.ts +47 -36
- package/src/mcp/mcp-registry.service.ts +75 -69
- package/src/mcp/mcp.controller.ts +40 -30
- package/src/mcp/mcp.module.ts +115 -36
- package/src/mcp/tools/core-ai.mcp-tools.ts +156 -0
- package/src/mcp/tools/core-dashboard-items.mcp-tools.ts +63 -0
- package/src/mcp/tools/core-dashboard.mcp-tools.ts +103 -0
- package/src/mcp/tools/core-event-webhooks.mcp-tools.ts +134 -0
- package/src/mcp/tools/core-files.mcp-tools.ts +105 -0
- package/src/mcp/tools/core-integration-events.mcp-tools.ts +118 -0
- package/src/mcp/tools/core-mail-profile.mcp-tools.ts +128 -0
- package/src/mcp/tools/core-mail-sent.mcp-tools.ts +76 -0
- package/src/mcp/tools/core-mail.mcp-tools.ts +130 -0
- package/src/mcp/tools/core-mcp-chat.mcp-tools.ts +81 -0
- package/src/mcp/tools/core-menus.mcp-tools.ts +197 -0
- package/src/mcp/tools/core-notifications.mcp-tools.ts +128 -0
- package/src/mcp/tools/core-profile.mcp-tools.ts +74 -0
- package/src/mcp/tools/core-roles.mcp-tools.ts +288 -0
- package/src/mcp/tools/core-routes.mcp-tools.ts +196 -0
- package/src/mcp/tools/core-screens.mcp-tools.ts +186 -0
- package/src/mcp/tools/core-sessions.mcp-tools.ts +97 -0
- package/src/mcp/tools/core-settings.mcp-tools.ts +171 -53
- package/src/mcp/tools/core-system.mcp-tools.ts +19 -0
- package/src/mcp/tools/core-webhooks.mcp-tools.ts +199 -0
- package/src/mcp-chat/mcp-chat.controller.ts +113 -60
- package/src/mcp-chat/mcp-chat.service.ts +1343 -332
- package/src/notification/dto/create-notification.dto.ts +71 -0
- package/src/notification/dto/update-progress.dto.ts +16 -0
- package/src/notification/notification.controller.ts +83 -0
- package/src/notification/notification.module.ts +16 -0
- package/src/notification/notification.service.ts +324 -0
- package/src/pdf/pdf.service.ts +1 -1
- package/src/profile/profile.controller.ts +1 -1
- package/src/profile/profile.module.ts +1 -0
- package/src/setting/dto/update-setting-list.dto.ts +32 -0
- package/src/setting/setting.controller.ts +9 -0
- package/src/setting/setting.service.ts +99 -11
- package/src/task/task.service.ts +103 -0
- package/src/token/token.service.ts +0 -2
- package/src/webhook-integration/dto/webhook-action.dto.ts +96 -0
- package/src/webhook-integration/webhook-integration.controller.ts +124 -103
- package/src/webhook-integration/webhook-integration.service.ts +1137 -735
- package/src/webhook-integration/webhook-utils.ts +136 -79
|
@@ -12,232 +12,71 @@ import {
|
|
|
12
12
|
} from '@/components/ui/alert-dialog';
|
|
13
13
|
import { Button } from '@/components/ui/button';
|
|
14
14
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
15
|
-
import { Textarea } from '@/components/ui/textarea';
|
|
16
|
-
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
17
15
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} from '
|
|
16
|
+
Sheet,
|
|
17
|
+
SheetDescription,
|
|
18
|
+
SheetHeader,
|
|
19
|
+
SheetTitle,
|
|
20
|
+
} from '@/components/ui/sheet';
|
|
21
|
+
import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
|
|
22
|
+
import { SidebarTrigger } from '@/components/ui/sidebar';
|
|
23
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
24
|
+
import { useIsMobile } from '@/hooks/use-mobile';
|
|
25
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
26
|
+
import { Bot, MessageSquare } from 'lucide-react';
|
|
27
27
|
import { useTranslations } from 'next-intl';
|
|
28
|
-
import { useEffect, useRef, useState } from 'react';
|
|
29
|
-
import ReactMarkdown from 'react-markdown';
|
|
30
|
-
import remarkGfm from 'remark-gfm';
|
|
28
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
31
29
|
import { toast } from 'sonner';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
id: number;
|
|
35
|
-
title: string | null;
|
|
36
|
-
created_at: string;
|
|
37
|
-
updated_at: string;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type Message = {
|
|
41
|
-
id: number;
|
|
42
|
-
conversation_id: number;
|
|
43
|
-
role: 'user' | 'assistant' | 'tool_call' | 'tool_result';
|
|
44
|
-
content: string;
|
|
45
|
-
tool_name: string | null;
|
|
46
|
-
tool_call_id: string | null;
|
|
47
|
-
created_at: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type MarkdownComponentProps = {
|
|
51
|
-
children?: React.ReactNode;
|
|
52
|
-
className?: string;
|
|
53
|
-
href?: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const markdownComponents: Record<
|
|
57
|
-
string,
|
|
58
|
-
(props: MarkdownComponentProps) => React.ReactNode
|
|
59
|
-
> = {
|
|
60
|
-
p({ children }) {
|
|
61
|
-
return <p className="mb-2 last:mb-0 leading-relaxed">{children}</p>;
|
|
62
|
-
},
|
|
63
|
-
ul({ children }) {
|
|
64
|
-
return <ul className="mb-2 list-disc pl-4 space-y-1">{children}</ul>;
|
|
65
|
-
},
|
|
66
|
-
ol({ children }) {
|
|
67
|
-
return <ol className="mb-2 list-decimal pl-4 space-y-1">{children}</ol>;
|
|
68
|
-
},
|
|
69
|
-
li({ children }) {
|
|
70
|
-
return <li className="leading-relaxed">{children}</li>;
|
|
71
|
-
},
|
|
72
|
-
strong({ children }) {
|
|
73
|
-
return <strong className="font-semibold">{children}</strong>;
|
|
74
|
-
},
|
|
75
|
-
em({ children }) {
|
|
76
|
-
return <em className="italic">{children}</em>;
|
|
77
|
-
},
|
|
78
|
-
code({ children, className }) {
|
|
79
|
-
const isBlock = className?.includes('language-');
|
|
80
|
-
return isBlock ? (
|
|
81
|
-
<code className="block rounded-md bg-black/10 dark:bg-white/10 px-3 py-2 text-xs font-mono whitespace-pre-wrap my-2">
|
|
82
|
-
{children}
|
|
83
|
-
</code>
|
|
84
|
-
) : (
|
|
85
|
-
<code className="rounded bg-black/10 dark:bg-white/10 px-1 py-0.5 text-xs font-mono">
|
|
86
|
-
{children}
|
|
87
|
-
</code>
|
|
88
|
-
);
|
|
89
|
-
},
|
|
90
|
-
pre({ children }) {
|
|
91
|
-
return <pre className="my-2 overflow-x-auto rounded-md">{children}</pre>;
|
|
92
|
-
},
|
|
93
|
-
blockquote({ children }) {
|
|
94
|
-
return (
|
|
95
|
-
<blockquote className="border-l-2 border-current/30 pl-3 italic my-2 text-current/70">
|
|
96
|
-
{children}
|
|
97
|
-
</blockquote>
|
|
98
|
-
);
|
|
99
|
-
},
|
|
100
|
-
h1({ children }) {
|
|
101
|
-
return (
|
|
102
|
-
<h1 className="text-base font-bold mb-2 mt-3 first:mt-0">{children}</h1>
|
|
103
|
-
);
|
|
104
|
-
},
|
|
105
|
-
h2({ children }) {
|
|
106
|
-
return (
|
|
107
|
-
<h2 className="text-sm font-bold mb-2 mt-3 first:mt-0">{children}</h2>
|
|
108
|
-
);
|
|
109
|
-
},
|
|
110
|
-
h3({ children }) {
|
|
111
|
-
return (
|
|
112
|
-
<h3 className="text-sm font-semibold mb-1 mt-2 first:mt-0">{children}</h3>
|
|
113
|
-
);
|
|
114
|
-
},
|
|
115
|
-
a({ href, children }) {
|
|
116
|
-
return (
|
|
117
|
-
<a
|
|
118
|
-
href={href}
|
|
119
|
-
target="_blank"
|
|
120
|
-
rel="noopener noreferrer"
|
|
121
|
-
className="underline underline-offset-2 hover:opacity-80"
|
|
122
|
-
>
|
|
123
|
-
{children}
|
|
124
|
-
</a>
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
hr() {
|
|
128
|
-
return <hr className="my-3 border-current/20" />;
|
|
129
|
-
},
|
|
130
|
-
table({ children }) {
|
|
131
|
-
return (
|
|
132
|
-
<div className="my-2 overflow-x-auto">
|
|
133
|
-
<table className="text-xs border-collapse w-full">{children}</table>
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
},
|
|
137
|
-
th({ children }) {
|
|
138
|
-
return (
|
|
139
|
-
<th className="border border-current/20 px-2 py-1 font-semibold text-left bg-current/5">
|
|
140
|
-
{children}
|
|
141
|
-
</th>
|
|
142
|
-
);
|
|
143
|
-
},
|
|
144
|
-
td({ children }) {
|
|
145
|
-
return <td className="border border-current/20 px-2 py-1">{children}</td>;
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
function ToolBadge({
|
|
150
|
-
message,
|
|
151
|
-
variant,
|
|
152
|
-
}: {
|
|
153
|
-
message: Message;
|
|
154
|
-
variant: 'call' | 'result';
|
|
155
|
-
}) {
|
|
156
|
-
const [expanded, setExpanded] = useState(false);
|
|
157
|
-
let parsed: unknown;
|
|
158
|
-
try {
|
|
159
|
-
parsed = JSON.parse(message.content);
|
|
160
|
-
} catch {
|
|
161
|
-
parsed = message.content;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const colorClass =
|
|
165
|
-
variant === 'call'
|
|
166
|
-
? 'border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-800 dark:bg-violet-950 dark:text-violet-300'
|
|
167
|
-
: 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300';
|
|
168
|
-
|
|
169
|
-
const preClass =
|
|
170
|
-
variant === 'call'
|
|
171
|
-
? 'border-violet-100 bg-violet-50/50 text-violet-800 dark:border-violet-900 dark:bg-violet-950/50 dark:text-violet-200'
|
|
172
|
-
: 'border-emerald-100 bg-emerald-50/50 text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950/50 dark:text-emerald-200';
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<div className="my-1 flex flex-col items-start">
|
|
176
|
-
<button
|
|
177
|
-
type="button"
|
|
178
|
-
className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors hover:opacity-80 ${colorClass}`}
|
|
179
|
-
onClick={() => setExpanded((v) => !v)}
|
|
180
|
-
>
|
|
181
|
-
<Wrench className="h-3 w-3 shrink-0" />
|
|
182
|
-
<span className="font-mono">{message.tool_name}</span>
|
|
183
|
-
{expanded ? (
|
|
184
|
-
<ChevronDown className="h-3 w-3 shrink-0" />
|
|
185
|
-
) : (
|
|
186
|
-
<ChevronRight className="h-3 w-3 shrink-0" />
|
|
187
|
-
)}
|
|
188
|
-
</button>
|
|
189
|
-
{expanded && (
|
|
190
|
-
<pre
|
|
191
|
-
className={`mt-1.5 w-full max-w-lg rounded-lg border px-3 py-2 text-xs font-mono overflow-x-auto whitespace-pre-wrap ${preClass}`}
|
|
192
|
-
>
|
|
193
|
-
{typeof parsed === 'string'
|
|
194
|
-
? parsed
|
|
195
|
-
: JSON.stringify(parsed, null, 2)}
|
|
196
|
-
</pre>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function TypingIndicator() {
|
|
203
|
-
return (
|
|
204
|
-
<div className="flex items-start gap-3">
|
|
205
|
-
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted ring-2 ring-background">
|
|
206
|
-
<Bot className="h-4 w-4 text-muted-foreground" />
|
|
207
|
-
</div>
|
|
208
|
-
<div className="flex items-center gap-1 rounded-2xl rounded-tl-sm bg-muted px-4 py-3 shadow-sm">
|
|
209
|
-
<span
|
|
210
|
-
className="h-1.5 w-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
|
|
211
|
-
style={{ animationDelay: '0ms' }}
|
|
212
|
-
/>
|
|
213
|
-
<span
|
|
214
|
-
className="h-1.5 w-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
|
|
215
|
-
style={{ animationDelay: '150ms' }}
|
|
216
|
-
/>
|
|
217
|
-
<span
|
|
218
|
-
className="h-1.5 w-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
|
|
219
|
-
style={{ animationDelay: '300ms' }}
|
|
220
|
-
/>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
30
|
+
import { ChatInput, ConversationSidebar, MessageFeed } from './chat-components';
|
|
31
|
+
import { Conversation, Message } from './chat-types';
|
|
225
32
|
|
|
226
33
|
export default function McpChatPage() {
|
|
227
|
-
const { request, currentLocaleCode, refetchUser } = useApp();
|
|
34
|
+
const { request, currentLocaleCode, refetchUser, accessToken, getSettingValue } = useApp();
|
|
228
35
|
const t = useTranslations('core.McpChatPage');
|
|
36
|
+
const mcpEnabled = getSettingValue('mcp-enabled') !== false;
|
|
37
|
+
const isMobile = useIsMobile();
|
|
229
38
|
|
|
230
39
|
const [selectedConvId, setSelectedConvId] = useState<number | null>(null);
|
|
231
40
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
232
41
|
const [input, setInput] = useState('');
|
|
42
|
+
const [attachments, setAttachments] = useState<
|
|
43
|
+
Array<{ id: number; name: string }>
|
|
44
|
+
>([]);
|
|
45
|
+
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
233
46
|
const [isSending, setIsSending] = useState(false);
|
|
47
|
+
const [isStreamingResponse, setIsStreamingResponse] = useState(false);
|
|
48
|
+
const [isLoadingMessages, setIsLoadingMessages] = useState(false);
|
|
49
|
+
const [isMobileConversationsOpen, setIsMobileConversationsOpen] =
|
|
50
|
+
useState(false);
|
|
234
51
|
const [convToDelete, setConvToDelete] = useState<Conversation | null>(null);
|
|
235
52
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
236
53
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
54
|
+
const streamingTimeoutRef = useRef<number | null>(null);
|
|
55
|
+
const streamingRunRef = useRef(0);
|
|
56
|
+
const streamAbortRef = useRef<AbortController | null>(null);
|
|
237
57
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
58
|
+
const selectedConversationDraft = useFormDraft<number | null>({
|
|
59
|
+
storageKey: 'mcp-chat-selected-conversation-draft',
|
|
60
|
+
value: selectedConvId,
|
|
61
|
+
hasData: selectedConvId !== null,
|
|
62
|
+
debounceMs: 250,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
clearDraft: clearMessageInputDraft,
|
|
67
|
+
loadDraft: loadMessageInputDraft,
|
|
68
|
+
} = useFormDraft<string>({
|
|
69
|
+
storageKey: `mcp-chat-input-${selectedConvId ?? 'new'}-draft`,
|
|
70
|
+
value: input,
|
|
71
|
+
hasData: input.trim().length > 0,
|
|
72
|
+
debounceMs: 300,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
data: conversations,
|
|
77
|
+
refetch: refetchConvs,
|
|
78
|
+
isLoading: isLoadingConversations,
|
|
79
|
+
} = useQuery<Conversation[]>({
|
|
241
80
|
queryKey: ['mcp-chat-conversations', currentLocaleCode],
|
|
242
81
|
queryFn: async () => {
|
|
243
82
|
const res = await request<Conversation[]>({
|
|
@@ -246,35 +85,312 @@ export default function McpChatPage() {
|
|
|
246
85
|
});
|
|
247
86
|
return res.data;
|
|
248
87
|
},
|
|
88
|
+
enabled: mcpEnabled,
|
|
249
89
|
});
|
|
250
90
|
|
|
251
|
-
const convList = conversations ?? [];
|
|
91
|
+
const convList = useMemo(() => conversations ?? [], [conversations]);
|
|
252
92
|
const selectedConv = convList.find((c) => c.id === selectedConvId) ?? null;
|
|
93
|
+
const activeMessageConversationId = mcpEnabled ? selectedConvId : null;
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (selectedConvId !== null || convList.length === 0) return;
|
|
97
|
+
const draft = selectedConversationDraft.loadDraft();
|
|
98
|
+
const draftConvId = draft?.payload;
|
|
99
|
+
if (typeof draftConvId !== 'number') return;
|
|
100
|
+
|
|
101
|
+
if (convList.some((conv) => conv.id === draftConvId)) {
|
|
102
|
+
setSelectedConvId(draftConvId);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
selectedConversationDraft.clearDraft();
|
|
107
|
+
}, [convList, selectedConvId, selectedConversationDraft]);
|
|
253
108
|
|
|
254
109
|
useEffect(() => {
|
|
255
|
-
|
|
110
|
+
const draft = loadMessageInputDraft();
|
|
111
|
+
if (typeof draft?.payload === 'string') {
|
|
112
|
+
setInput(draft.payload);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setInput('');
|
|
117
|
+
}, [selectedConvId, loadMessageInputDraft]);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!activeMessageConversationId) {
|
|
121
|
+
setMessages([]);
|
|
122
|
+
setIsLoadingMessages(false);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let isMounted = true;
|
|
127
|
+
setIsLoadingMessages(true);
|
|
128
|
+
|
|
256
129
|
request<Message[]>({
|
|
257
|
-
url: `/mcp-chat/${
|
|
130
|
+
url: `/mcp-chat/${activeMessageConversationId}/messages`,
|
|
258
131
|
method: 'GET',
|
|
259
132
|
})
|
|
260
|
-
.then((res) =>
|
|
261
|
-
|
|
262
|
-
|
|
133
|
+
.then((res) => {
|
|
134
|
+
if (isMounted) {
|
|
135
|
+
setMessages(res.data);
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
if (isMounted) {
|
|
140
|
+
setMessages([]);
|
|
141
|
+
toast.error(t('loadMessagesError'));
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
.finally(() => {
|
|
145
|
+
if (isMounted) {
|
|
146
|
+
setIsLoadingMessages(false);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
isMounted = false;
|
|
152
|
+
};
|
|
153
|
+
}, [request, activeMessageConversationId, t]);
|
|
263
154
|
|
|
264
155
|
useEffect(() => {
|
|
265
156
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
266
|
-
}, [messages, isSending]);
|
|
157
|
+
}, [messages, isSending, isLoadingMessages, isStreamingResponse]);
|
|
158
|
+
|
|
159
|
+
function stopStreaming() {
|
|
160
|
+
streamingRunRef.current += 1;
|
|
161
|
+
if (streamAbortRef.current) {
|
|
162
|
+
streamAbortRef.current.abort();
|
|
163
|
+
streamAbortRef.current = null;
|
|
164
|
+
}
|
|
165
|
+
if (streamingTimeoutRef.current !== null) {
|
|
166
|
+
window.clearTimeout(streamingTimeoutRef.current);
|
|
167
|
+
streamingTimeoutRef.current = null;
|
|
168
|
+
}
|
|
169
|
+
setIsStreamingResponse(false);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
return () => {
|
|
174
|
+
if (streamAbortRef.current) {
|
|
175
|
+
streamAbortRef.current.abort();
|
|
176
|
+
}
|
|
177
|
+
if (streamingTimeoutRef.current !== null) {
|
|
178
|
+
window.clearTimeout(streamingTimeoutRef.current);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
async function streamAssistantMessage(responseMessages: Message[]) {
|
|
184
|
+
const lastAssistantIdx = [...responseMessages]
|
|
185
|
+
.map((msg, idx) => ({ msg, idx }))
|
|
186
|
+
.reverse()
|
|
187
|
+
.find(({ msg }) => msg.role === 'assistant')?.idx;
|
|
188
|
+
|
|
189
|
+
if (lastAssistantIdx === undefined) {
|
|
190
|
+
setMessages(responseMessages);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const target = responseMessages[lastAssistantIdx] as Message;
|
|
195
|
+
const fullContent = target.content ?? '';
|
|
196
|
+
|
|
197
|
+
if (fullContent.length < 24) {
|
|
198
|
+
setMessages(responseMessages);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
stopStreaming();
|
|
203
|
+
const runId = ++streamingRunRef.current;
|
|
204
|
+
setIsStreamingResponse(true);
|
|
205
|
+
|
|
206
|
+
const baseMessages: Message[] = responseMessages.map(
|
|
207
|
+
(msg, idx): Message =>
|
|
208
|
+
idx === lastAssistantIdx ? { ...msg, content: '' } : msg
|
|
209
|
+
);
|
|
210
|
+
setMessages(baseMessages);
|
|
211
|
+
|
|
212
|
+
await new Promise<void>((resolve) => {
|
|
213
|
+
let cursor = 0;
|
|
214
|
+
const chunkSize = Math.max(
|
|
215
|
+
8,
|
|
216
|
+
Math.min(32, Math.ceil(fullContent.length / 80))
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const tick = () => {
|
|
220
|
+
if (streamingRunRef.current !== runId) {
|
|
221
|
+
resolve();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
cursor = Math.min(fullContent.length, cursor + chunkSize);
|
|
226
|
+
const nextMessages: Message[] = responseMessages.map(
|
|
227
|
+
(msg, idx): Message =>
|
|
228
|
+
idx === lastAssistantIdx
|
|
229
|
+
? { ...target, content: fullContent.slice(0, cursor) }
|
|
230
|
+
: msg
|
|
231
|
+
);
|
|
232
|
+
setMessages(nextMessages);
|
|
233
|
+
|
|
234
|
+
if (cursor >= fullContent.length) {
|
|
235
|
+
resolve();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
streamingTimeoutRef.current = window.setTimeout(tick, 16);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
tick();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (streamingRunRef.current === runId) {
|
|
246
|
+
setMessages(responseMessages);
|
|
247
|
+
setIsStreamingResponse(false);
|
|
248
|
+
streamingTimeoutRef.current = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function sendMessageViaStream(
|
|
253
|
+
conversationId: number,
|
|
254
|
+
message: string,
|
|
255
|
+
tempAssistantId: number
|
|
256
|
+
): Promise<Message[] | null> {
|
|
257
|
+
const rawBase = process.env.NEXT_PUBLIC_API_BASE_URL || '/api';
|
|
258
|
+
const apiBase = rawBase.replace(/\/$/, '');
|
|
259
|
+
const url = `${apiBase}/mcp-chat/${conversationId}/messages/stream`;
|
|
260
|
+
|
|
261
|
+
const controller = new AbortController();
|
|
262
|
+
streamAbortRef.current = controller;
|
|
263
|
+
setIsStreamingResponse(true);
|
|
264
|
+
|
|
265
|
+
const headers: Record<string, string> = {
|
|
266
|
+
'Content-Type': 'application/json',
|
|
267
|
+
Accept: 'text/event-stream',
|
|
268
|
+
'Accept-Language': currentLocaleCode,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (accessToken) {
|
|
272
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const response = await fetch(url, {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
headers,
|
|
278
|
+
body: JSON.stringify({ message }),
|
|
279
|
+
signal: controller.signal,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (!response.ok || !response.body) {
|
|
283
|
+
throw new Error(`stream-unavailable:${response.status}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let accumulated = '';
|
|
287
|
+
setMessages((prev) => [
|
|
288
|
+
...prev,
|
|
289
|
+
{
|
|
290
|
+
id: tempAssistantId,
|
|
291
|
+
conversation_id: conversationId,
|
|
292
|
+
role: 'assistant',
|
|
293
|
+
content: '',
|
|
294
|
+
tool_name: null,
|
|
295
|
+
tool_call_id: null,
|
|
296
|
+
created_at: new Date().toISOString(),
|
|
297
|
+
},
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
const reader = response.body.getReader();
|
|
301
|
+
const decoder = new TextDecoder();
|
|
302
|
+
let buffer = '';
|
|
303
|
+
let doneMessages: Message[] | null = null;
|
|
304
|
+
|
|
305
|
+
while (true) {
|
|
306
|
+
const { value, done } = await reader.read();
|
|
307
|
+
if (done) break;
|
|
308
|
+
|
|
309
|
+
buffer += decoder.decode(value, { stream: true });
|
|
310
|
+
const events = buffer.split('\n\n');
|
|
311
|
+
buffer = events.pop() ?? '';
|
|
312
|
+
|
|
313
|
+
for (const eventBlock of events) {
|
|
314
|
+
const dataLines = eventBlock
|
|
315
|
+
.split('\n')
|
|
316
|
+
.filter((line) => line.startsWith('data:'))
|
|
317
|
+
.map((line) => line.slice(5).trim());
|
|
318
|
+
|
|
319
|
+
if (dataLines.length === 0) continue;
|
|
320
|
+
const payloadText = dataLines.join('\n');
|
|
321
|
+
|
|
322
|
+
let payload: {
|
|
323
|
+
type?: 'chunk' | 'done' | 'error';
|
|
324
|
+
content?: string;
|
|
325
|
+
message?: string;
|
|
326
|
+
messages?: Message[];
|
|
327
|
+
};
|
|
328
|
+
try {
|
|
329
|
+
payload = JSON.parse(payloadText);
|
|
330
|
+
} catch {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (payload.type === 'chunk') {
|
|
335
|
+
const chunk = payload.content ?? '';
|
|
336
|
+
if (!chunk) continue;
|
|
337
|
+
accumulated += chunk;
|
|
338
|
+
setMessages((prev) =>
|
|
339
|
+
prev.map((msg) =>
|
|
340
|
+
msg.id === tempAssistantId
|
|
341
|
+
? { ...msg, content: accumulated }
|
|
342
|
+
: msg
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (payload.type === 'done' && Array.isArray(payload.messages)) {
|
|
348
|
+
doneMessages = payload.messages;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (payload.type === 'error') {
|
|
352
|
+
throw new Error(payload.message || 'stream-error');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return doneMessages;
|
|
358
|
+
}
|
|
267
359
|
|
|
268
360
|
// Auto-resize textarea
|
|
269
361
|
useEffect(() => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
362
|
+
const textarea = textareaRef.current;
|
|
363
|
+
if (!textarea) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
textarea.style.height = 'auto';
|
|
368
|
+
const maxHeight = isMobile ? 160 : 300;
|
|
369
|
+
const nextHeight = Math.min(textarea.scrollHeight, maxHeight);
|
|
370
|
+
|
|
371
|
+
const frameId = window.requestAnimationFrame(() => {
|
|
372
|
+
textarea.style.height = `${nextHeight}px`;
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return () => {
|
|
376
|
+
window.cancelAnimationFrame(frameId);
|
|
377
|
+
};
|
|
378
|
+
}, [input, isMobile]);
|
|
379
|
+
|
|
380
|
+
function handleSelectConversation(convId: number) {
|
|
381
|
+
stopStreaming();
|
|
382
|
+
setSelectedConvId(convId);
|
|
383
|
+
setMessages([]);
|
|
384
|
+
if (isMobile) {
|
|
385
|
+
setIsMobileConversationsOpen(false);
|
|
273
386
|
}
|
|
274
|
-
}
|
|
387
|
+
}
|
|
275
388
|
|
|
276
389
|
async function handleNewConversation() {
|
|
390
|
+
if (!mcpEnabled) return null;
|
|
391
|
+
|
|
277
392
|
try {
|
|
393
|
+
stopStreaming();
|
|
278
394
|
const res = await request<Conversation>({
|
|
279
395
|
url: '/mcp-chat',
|
|
280
396
|
method: 'POST',
|
|
@@ -283,15 +399,25 @@ export default function McpChatPage() {
|
|
|
283
399
|
await refetchConvs();
|
|
284
400
|
setSelectedConvId(res.data.id);
|
|
285
401
|
setMessages([]);
|
|
402
|
+
setInput('');
|
|
403
|
+
if (isMobile) {
|
|
404
|
+
setIsMobileConversationsOpen(false);
|
|
405
|
+
}
|
|
286
406
|
return res.data.id;
|
|
287
407
|
} catch {
|
|
288
|
-
toast.error('
|
|
408
|
+
toast.error(t('createConversationError'));
|
|
289
409
|
return null;
|
|
290
410
|
}
|
|
291
411
|
}
|
|
292
412
|
|
|
293
413
|
async function handleSend() {
|
|
294
|
-
if (
|
|
414
|
+
if (
|
|
415
|
+
!mcpEnabled ||
|
|
416
|
+
(!input.trim() && attachments.length === 0) ||
|
|
417
|
+
isSending ||
|
|
418
|
+
isUploadingFile
|
|
419
|
+
)
|
|
420
|
+
return;
|
|
295
421
|
let activeConvId = selectedConvId;
|
|
296
422
|
if (!activeConvId) {
|
|
297
423
|
activeConvId = await handleNewConversation();
|
|
@@ -299,15 +425,27 @@ export default function McpChatPage() {
|
|
|
299
425
|
}
|
|
300
426
|
setIsSending(true);
|
|
301
427
|
const currentInput = input;
|
|
428
|
+
const currentAttachments = attachments;
|
|
429
|
+
const tempId = -Date.now();
|
|
430
|
+
const attachmentsBlock =
|
|
431
|
+
currentAttachments.length > 0
|
|
432
|
+
? `\n\n${t('attachedFilesPrefix')}\n${currentAttachments
|
|
433
|
+
.map((file) => `- ${file.name} (file_id: ${file.id})`)
|
|
434
|
+
.join('\n')}`
|
|
435
|
+
: '';
|
|
436
|
+
const payloadMessage = `${currentInput.trim()}${attachmentsBlock}`.trim();
|
|
437
|
+
|
|
302
438
|
setInput('');
|
|
439
|
+
setAttachments([]);
|
|
440
|
+
clearMessageInputDraft();
|
|
303
441
|
|
|
304
442
|
setMessages((prev) => [
|
|
305
443
|
...prev,
|
|
306
444
|
{
|
|
307
|
-
id:
|
|
445
|
+
id: tempId,
|
|
308
446
|
conversation_id: activeConvId!,
|
|
309
447
|
role: 'user',
|
|
310
|
-
content:
|
|
448
|
+
content: payloadMessage,
|
|
311
449
|
tool_name: null,
|
|
312
450
|
tool_call_id: null,
|
|
313
451
|
created_at: new Date().toISOString(),
|
|
@@ -315,25 +453,101 @@ export default function McpChatPage() {
|
|
|
315
453
|
]);
|
|
316
454
|
|
|
317
455
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
456
|
+
stopStreaming();
|
|
457
|
+
const tempAssistantId = -(Date.now() + 1);
|
|
458
|
+
let finalMessages: Message[] | null = null;
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
finalMessages = await sendMessageViaStream(
|
|
462
|
+
activeConvId,
|
|
463
|
+
payloadMessage,
|
|
464
|
+
tempAssistantId
|
|
465
|
+
);
|
|
466
|
+
} catch {
|
|
467
|
+
setMessages((prev) => prev.filter((m) => m.id !== tempAssistantId));
|
|
468
|
+
|
|
469
|
+
const fallbackRes = await request<Message[]>({
|
|
470
|
+
url: `/mcp-chat/${activeConvId}/messages`,
|
|
471
|
+
method: 'POST',
|
|
472
|
+
data: { message: payloadMessage },
|
|
473
|
+
});
|
|
474
|
+
finalMessages = fallbackRes.data;
|
|
475
|
+
await streamAssistantMessage(finalMessages);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (finalMessages) {
|
|
479
|
+
setMessages(finalMessages);
|
|
480
|
+
}
|
|
481
|
+
|
|
324
482
|
await refetchConvs();
|
|
325
|
-
const hadToolUse =
|
|
483
|
+
const hadToolUse = (finalMessages ?? []).some(
|
|
326
484
|
(m) => m.role === 'tool_call' || m.role === 'tool_result'
|
|
327
485
|
);
|
|
328
486
|
if (hadToolUse) await refetchUser();
|
|
329
487
|
} catch {
|
|
330
|
-
toast.error('
|
|
331
|
-
setMessages((prev) => prev.filter((m) => m.id !==
|
|
488
|
+
toast.error(t('sendMessageError'));
|
|
489
|
+
setMessages((prev) => prev.filter((m) => m.id !== tempId));
|
|
490
|
+
setInput(currentInput);
|
|
491
|
+
setAttachments(currentAttachments);
|
|
332
492
|
} finally {
|
|
493
|
+
streamAbortRef.current = null;
|
|
494
|
+
setIsStreamingResponse(false);
|
|
333
495
|
setIsSending(false);
|
|
334
496
|
}
|
|
335
497
|
}
|
|
336
498
|
|
|
499
|
+
const uploadAttachment = async (file: File) => {
|
|
500
|
+
const existingNames = new Set(attachments.map((item) => item.name));
|
|
501
|
+
if (existingNames.has(file.name)) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
setIsUploadingFile(true);
|
|
506
|
+
try {
|
|
507
|
+
const formData = new FormData();
|
|
508
|
+
formData.append('file', file);
|
|
509
|
+
formData.append('destination', 'mcp-chat/attachments');
|
|
510
|
+
|
|
511
|
+
const { data } = await request<{ id: number; filename?: string }>({
|
|
512
|
+
url: '/file',
|
|
513
|
+
method: 'POST',
|
|
514
|
+
data: formData,
|
|
515
|
+
headers: {
|
|
516
|
+
'Content-Type': 'multipart/form-data',
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
if (!data?.id) {
|
|
521
|
+
throw new Error('invalid-file-id');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
setAttachments((prev) => [
|
|
525
|
+
...prev,
|
|
526
|
+
{
|
|
527
|
+
id: data.id,
|
|
528
|
+
name: data.filename || file.name,
|
|
529
|
+
},
|
|
530
|
+
]);
|
|
531
|
+
} catch {
|
|
532
|
+
toast.error(t('uploadFileError'));
|
|
533
|
+
} finally {
|
|
534
|
+
setIsUploadingFile(false);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const handleUploadFiles = (files: FileList) => {
|
|
539
|
+
const queue = Array.from(files);
|
|
540
|
+
void (async () => {
|
|
541
|
+
for (const file of queue) {
|
|
542
|
+
await uploadAttachment(file);
|
|
543
|
+
}
|
|
544
|
+
})();
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const handleRemoveAttachment = (id: number) => {
|
|
548
|
+
setAttachments((prev) => prev.filter((item) => item.id !== id));
|
|
549
|
+
};
|
|
550
|
+
|
|
337
551
|
async function handleDeleteConversation() {
|
|
338
552
|
if (!convToDelete) return;
|
|
339
553
|
try {
|
|
@@ -342,121 +556,103 @@ export default function McpChatPage() {
|
|
|
342
556
|
method: 'DELETE',
|
|
343
557
|
data: { ids: [convToDelete.id] },
|
|
344
558
|
});
|
|
345
|
-
toast.success('
|
|
559
|
+
toast.success(t('deleteConversationSuccess'));
|
|
346
560
|
if (selectedConvId === convToDelete.id) {
|
|
347
561
|
setSelectedConvId(null);
|
|
348
562
|
setMessages([]);
|
|
563
|
+
setInput('');
|
|
349
564
|
}
|
|
350
565
|
setConvToDelete(null);
|
|
351
566
|
await refetchConvs();
|
|
352
567
|
} catch {
|
|
353
|
-
toast.error('
|
|
568
|
+
toast.error(t('deleteConversationError'));
|
|
354
569
|
}
|
|
355
570
|
}
|
|
356
571
|
|
|
357
|
-
const
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}}
|
|
372
|
-
rows={1}
|
|
373
|
-
/>
|
|
374
|
-
<Button
|
|
375
|
-
size="icon"
|
|
376
|
-
className="shrink-0 h-8 w-8 rounded-xl cursor-pointer"
|
|
377
|
-
disabled={isSending || !input.trim()}
|
|
378
|
-
onClick={() => void handleSend()}
|
|
379
|
-
>
|
|
380
|
-
<Send className="h-3.5 w-3.5" />
|
|
381
|
-
</Button>
|
|
382
|
-
</div>
|
|
572
|
+
const sidebarContent = (
|
|
573
|
+
<ConversationSidebar
|
|
574
|
+
conversations={convList}
|
|
575
|
+
selectedConvId={selectedConvId}
|
|
576
|
+
isLoading={isLoadingConversations}
|
|
577
|
+
emptyLabel={t('emptyConversations')}
|
|
578
|
+
conversationsLabel={t('conversations')}
|
|
579
|
+
newConversationLabel={t('newConversation')}
|
|
580
|
+
onCreate={() => {
|
|
581
|
+
void handleNewConversation();
|
|
582
|
+
}}
|
|
583
|
+
onSelect={handleSelectConversation}
|
|
584
|
+
onDelete={setConvToDelete}
|
|
585
|
+
/>
|
|
383
586
|
);
|
|
384
587
|
|
|
588
|
+
if (!mcpEnabled) {
|
|
589
|
+
return (
|
|
590
|
+
<div className="flex h-dvh min-h-0 items-center justify-center bg-background px-4">
|
|
591
|
+
<div className="flex max-w-md flex-col items-center gap-3 text-center">
|
|
592
|
+
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-muted text-muted-foreground ring-1 ring-border">
|
|
593
|
+
<Bot className="h-7 w-7" />
|
|
594
|
+
</div>
|
|
595
|
+
<h1 className="text-xl font-semibold tracking-tight">
|
|
596
|
+
{t('title')}
|
|
597
|
+
</h1>
|
|
598
|
+
<p className="text-sm text-muted-foreground">{t('mcpDisabled')}</p>
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
385
604
|
return (
|
|
386
|
-
<div className="flex h-
|
|
605
|
+
<div className="flex h-dvh min-h-0 overflow-hidden bg-background">
|
|
387
606
|
{/* Sidebar */}
|
|
388
|
-
<aside className="w-64 shrink-0 flex
|
|
389
|
-
|
|
390
|
-
<Button
|
|
391
|
-
variant="outline"
|
|
392
|
-
className="w-full justify-start gap-2 cursor-pointer rounded-xl h-9 text-sm font-medium"
|
|
393
|
-
onClick={handleNewConversation}
|
|
394
|
-
>
|
|
395
|
-
<Plus className="h-4 w-4" />
|
|
396
|
-
{t('newConversation')}
|
|
397
|
-
</Button>
|
|
398
|
-
</div>
|
|
399
|
-
<div className="px-4 pt-4 pb-1.5">
|
|
400
|
-
<p className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">
|
|
401
|
-
{t('conversations')}
|
|
402
|
-
</p>
|
|
403
|
-
</div>
|
|
404
|
-
<ScrollArea className="flex-1 px-2 pb-2">
|
|
405
|
-
{convList.length === 0 ? (
|
|
406
|
-
<div className="flex flex-col items-center gap-2 py-8 px-4 text-center">
|
|
407
|
-
<MessageSquare className="h-8 w-8 text-muted-foreground/40" />
|
|
408
|
-
<p className="text-xs text-muted-foreground">
|
|
409
|
-
{t('emptyConversations')}
|
|
410
|
-
</p>
|
|
411
|
-
</div>
|
|
412
|
-
) : (
|
|
413
|
-
<div className="space-y-0.5 pt-1">
|
|
414
|
-
{convList.map((conv) => (
|
|
415
|
-
<div
|
|
416
|
-
key={conv.id}
|
|
417
|
-
className={`group flex items-center justify-between rounded-xl px-3 py-2 cursor-pointer transition-colors text-sm ${
|
|
418
|
-
selectedConvId === conv.id
|
|
419
|
-
? 'bg-primary text-primary-foreground'
|
|
420
|
-
: 'hover:bg-accent text-foreground'
|
|
421
|
-
}`}
|
|
422
|
-
onClick={() => {
|
|
423
|
-
setSelectedConvId(conv.id);
|
|
424
|
-
setMessages([]);
|
|
425
|
-
}}
|
|
426
|
-
>
|
|
427
|
-
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
428
|
-
<MessageSquare className="h-3.5 w-3.5 shrink-0 opacity-60" />
|
|
429
|
-
<span className="truncate text-xs font-medium">
|
|
430
|
-
{conv.title || `#${conv.id}`}
|
|
431
|
-
</span>
|
|
432
|
-
</div>
|
|
433
|
-
<Button
|
|
434
|
-
variant="ghost"
|
|
435
|
-
size="icon"
|
|
436
|
-
className={`h-6 w-6 opacity-0 group-hover:opacity-100 shrink-0 cursor-pointer transition-opacity ${
|
|
437
|
-
selectedConvId === conv.id
|
|
438
|
-
? 'hover:bg-primary-foreground/20 text-primary-foreground'
|
|
439
|
-
: 'hover:bg-destructive/10 hover:text-destructive'
|
|
440
|
-
}`}
|
|
441
|
-
onClick={(e) => {
|
|
442
|
-
e.stopPropagation();
|
|
443
|
-
setConvToDelete(conv);
|
|
444
|
-
}}
|
|
445
|
-
>
|
|
446
|
-
<Trash2 className="h-3 w-3" />
|
|
447
|
-
</Button>
|
|
448
|
-
</div>
|
|
449
|
-
))}
|
|
450
|
-
</div>
|
|
451
|
-
)}
|
|
452
|
-
</ScrollArea>
|
|
607
|
+
<aside className="hidden md:flex w-64 shrink-0 flex-col border-r bg-muted/30">
|
|
608
|
+
{sidebarContent}
|
|
453
609
|
</aside>
|
|
454
610
|
|
|
611
|
+
<Sheet
|
|
612
|
+
open={isMobileConversationsOpen}
|
|
613
|
+
onOpenChange={setIsMobileConversationsOpen}
|
|
614
|
+
>
|
|
615
|
+
<ResizableSheetContent
|
|
616
|
+
sheetId="core-mcp-chat-mobile-conversations-sheet"
|
|
617
|
+
defaultWidth={320}
|
|
618
|
+
minWidth={280}
|
|
619
|
+
maxWidth={560}
|
|
620
|
+
side="left"
|
|
621
|
+
className="w-[85vw] max-w-[320px] p-0 [&>button]:top-2 [&>button]:right-2 [&>button]:z-20"
|
|
622
|
+
>
|
|
623
|
+
<SheetHeader className="sr-only">
|
|
624
|
+
<SheetTitle>{t('conversations')}</SheetTitle>
|
|
625
|
+
<SheetDescription>{t('openConversations')}</SheetDescription>
|
|
626
|
+
</SheetHeader>
|
|
627
|
+
<div className="flex h-full flex-col bg-muted/30 pt-11">
|
|
628
|
+
{sidebarContent}
|
|
629
|
+
</div>
|
|
630
|
+
</ResizableSheetContent>
|
|
631
|
+
</Sheet>
|
|
632
|
+
|
|
455
633
|
{/* Main area */}
|
|
456
|
-
<main className="flex-1 flex
|
|
634
|
+
<main className="flex-1 flex min-h-0 min-w-0 flex-col overflow-hidden">
|
|
635
|
+
{isMobile && (
|
|
636
|
+
<header className="h-12 shrink-0 border-b flex items-center px-3 gap-2 bg-background/90 backdrop-blur-sm md:hidden">
|
|
637
|
+
<SidebarTrigger className="h-8 w-8 cursor-pointer" />
|
|
638
|
+
<Button
|
|
639
|
+
variant="ghost"
|
|
640
|
+
size="icon"
|
|
641
|
+
className="h-8 w-8 cursor-pointer"
|
|
642
|
+
onClick={() => setIsMobileConversationsOpen(true)}
|
|
643
|
+
aria-label={t('openConversations')}
|
|
644
|
+
>
|
|
645
|
+
<MessageSquare className="h-4 w-4" />
|
|
646
|
+
</Button>
|
|
647
|
+
<span className="text-sm font-medium truncate">
|
|
648
|
+
{selectedConv?.title || t('title')}
|
|
649
|
+
</span>
|
|
650
|
+
</header>
|
|
651
|
+
)}
|
|
652
|
+
|
|
457
653
|
{!selectedConvId ? (
|
|
458
654
|
/* Empty state */
|
|
459
|
-
<div className="flex flex-1 flex-col items-center justify-center gap-8 px-6">
|
|
655
|
+
<div className="flex flex-1 flex-col items-center justify-center gap-6 px-4 md:gap-8 md:px-6">
|
|
460
656
|
<div className="flex flex-col items-center gap-3 text-center">
|
|
461
657
|
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10 ring-1 ring-primary/20">
|
|
462
658
|
<Bot className="h-8 w-8 text-primary" />
|
|
@@ -468,13 +664,35 @@ export default function McpChatPage() {
|
|
|
468
664
|
{t('description')}
|
|
469
665
|
</p>
|
|
470
666
|
</div>
|
|
471
|
-
<div className="w-full max-w-2xl">
|
|
667
|
+
<div className="w-full max-w-2xl px-1 pb-[max(0.5rem,env(safe-area-inset-bottom))] md:px-0 md:pb-0">
|
|
668
|
+
<ChatInput
|
|
669
|
+
textareaRef={textareaRef}
|
|
670
|
+
submitOnEnter={!isMobile}
|
|
671
|
+
input={input}
|
|
672
|
+
isSending={isSending}
|
|
673
|
+
isUploadingFile={isUploadingFile}
|
|
674
|
+
disabled={!mcpEnabled}
|
|
675
|
+
attachments={attachments}
|
|
676
|
+
placeholder={t('placeholder')}
|
|
677
|
+
sendLabel={t('send')}
|
|
678
|
+
sendingLabel={t('sending')}
|
|
679
|
+
attachLabel={t('attachFile')}
|
|
680
|
+
uploadingFileLabel={t('uploadingFile')}
|
|
681
|
+
removeAttachmentLabel={t('removeAttachment')}
|
|
682
|
+
onInputChange={setInput}
|
|
683
|
+
onSend={() => {
|
|
684
|
+
void handleSend();
|
|
685
|
+
}}
|
|
686
|
+
onUploadFiles={handleUploadFiles}
|
|
687
|
+
onRemoveAttachment={handleRemoveAttachment}
|
|
688
|
+
/>
|
|
689
|
+
</div>
|
|
472
690
|
</div>
|
|
473
691
|
) : (
|
|
474
692
|
/* Chat state */
|
|
475
693
|
<>
|
|
476
694
|
{/* Header */}
|
|
477
|
-
<header className="h-14 shrink-0 border-b
|
|
695
|
+
<header className="h-14 shrink-0 border-b items-center px-4 md:px-6 gap-3 bg-background/80 backdrop-blur-sm hidden md:flex">
|
|
478
696
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10">
|
|
479
697
|
<Bot className="h-4 w-4 text-primary" />
|
|
480
698
|
</div>
|
|
@@ -489,60 +707,41 @@ export default function McpChatPage() {
|
|
|
489
707
|
</header>
|
|
490
708
|
|
|
491
709
|
{/* Messages */}
|
|
492
|
-
<ScrollArea className="flex-1
|
|
493
|
-
<
|
|
494
|
-
{messages
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
variant="call"
|
|
501
|
-
/>
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
if (msg.role === 'tool_result') {
|
|
505
|
-
return (
|
|
506
|
-
<ToolBadge
|
|
507
|
-
key={msg.id ?? idx}
|
|
508
|
-
message={msg}
|
|
509
|
-
variant="result"
|
|
510
|
-
/>
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
if (msg.role === 'user') {
|
|
514
|
-
return (
|
|
515
|
-
<div key={msg.id ?? idx} className="flex justify-end">
|
|
516
|
-
<div className="max-w-[75%] rounded-2xl rounded-tr-md bg-primary px-4 py-2.5 text-sm text-primary-foreground shadow-sm">
|
|
517
|
-
{msg.content}
|
|
518
|
-
</div>
|
|
519
|
-
</div>
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
return (
|
|
523
|
-
<div key={msg.id ?? idx} className="flex items-start gap-3">
|
|
524
|
-
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted ring-2 ring-background shadow-sm">
|
|
525
|
-
<Bot className="h-4 w-4 text-muted-foreground" />
|
|
526
|
-
</div>
|
|
527
|
-
<div className="max-w-[75%] rounded-2xl rounded-tl-md bg-muted px-4 py-2.5 text-sm shadow-sm prose-sm">
|
|
528
|
-
<ReactMarkdown
|
|
529
|
-
remarkPlugins={[remarkGfm]}
|
|
530
|
-
components={markdownComponents as never}
|
|
531
|
-
>
|
|
532
|
-
{msg.content}
|
|
533
|
-
</ReactMarkdown>
|
|
534
|
-
</div>
|
|
535
|
-
</div>
|
|
536
|
-
);
|
|
537
|
-
})}
|
|
538
|
-
{isSending && <TypingIndicator />}
|
|
539
|
-
<div ref={messagesEndRef} />
|
|
540
|
-
</div>
|
|
710
|
+
<ScrollArea className="min-h-0 flex-1 px-3 py-4 md:px-4 md:py-6">
|
|
711
|
+
<MessageFeed
|
|
712
|
+
messages={messages}
|
|
713
|
+
isLoadingMessages={isLoadingMessages}
|
|
714
|
+
isSending={isSending}
|
|
715
|
+
isStreamingResponse={isStreamingResponse}
|
|
716
|
+
messagesEndRef={messagesEndRef}
|
|
717
|
+
/>
|
|
541
718
|
</ScrollArea>
|
|
542
719
|
|
|
543
720
|
{/* Input */}
|
|
544
|
-
<div className="shrink-0 border-t bg-background/80 backdrop-blur-sm px-6 py-4">
|
|
545
|
-
<div className="max-w-3xl mx-auto">
|
|
721
|
+
<div className="shrink-0 border-t bg-background/80 px-3 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] backdrop-blur-sm md:px-6 md:py-4">
|
|
722
|
+
<div className="max-w-3xl mx-auto">
|
|
723
|
+
<ChatInput
|
|
724
|
+
textareaRef={textareaRef}
|
|
725
|
+
submitOnEnter={!isMobile}
|
|
726
|
+
input={input}
|
|
727
|
+
isSending={isSending}
|
|
728
|
+
isUploadingFile={isUploadingFile}
|
|
729
|
+
disabled={!mcpEnabled}
|
|
730
|
+
attachments={attachments}
|
|
731
|
+
placeholder={t('placeholder')}
|
|
732
|
+
sendLabel={t('send')}
|
|
733
|
+
sendingLabel={t('sending')}
|
|
734
|
+
attachLabel={t('attachFile')}
|
|
735
|
+
uploadingFileLabel={t('uploadingFile')}
|
|
736
|
+
removeAttachmentLabel={t('removeAttachment')}
|
|
737
|
+
onInputChange={setInput}
|
|
738
|
+
onSend={() => {
|
|
739
|
+
void handleSend();
|
|
740
|
+
}}
|
|
741
|
+
onUploadFiles={handleUploadFiles}
|
|
742
|
+
onRemoveAttachment={handleRemoveAttachment}
|
|
743
|
+
/>
|
|
744
|
+
</div>
|
|
546
745
|
</div>
|
|
547
746
|
</>
|
|
548
747
|
)}
|