Compare commits

..

1 Commits

Author SHA1 Message Date
6mvp6 6bb73297e0 feat(wecom): add user feedback support for WeChat Work AI Bot
This commit implements user feedback functionality (like/dislike) for
WeChat Work AI Bot conversations, including:

Backend changes:
- Add feedback_id and stream_id fields to WecomBotEvent
- Implement feedback event handling in WecomBotClient (api.py)
- Add StreamSessionManager._feedback_index for feedback_id lookup
- Add on_feedback decorator for custom feedback handlers
- Create MonitoringFeedback entity for database persistence
- Add dbm025 migration for monitoring_feedback table
- Implement FeedbackMonitor helper class
- Update all platform adapters with ap parameter support
- Update botmgr to pass bot_info for monitoring context

Frontend changes:
- Add FeedbackCard and FeedbackList components
- Add useFeedbackData hook for feedback data fetching
- Add feedback tab to monitoring page
- Add feedback types and interfaces
- Add i18n translations (zh-Hans, en-US)

Other changes:
- Update Dockerfile with Chinese mirror for faster builds
- Update docker-compose.yaml with network configuration
- Update .gitignore for docker data and backup files

Note: Known issues that need future improvement:
- feedback_type=3 (cancel) is recorded but not properly handled
- Duplicate feedback records are not deduplicated
2026-03-30 00:05:27 +08:00
70 changed files with 1593 additions and 3945 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: 漏洞反馈 name: 漏洞反馈
description: 【供中文用户】报错或漏洞请使用这个模板创建,不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题,参考文档 https://link.langbot.app/zh/docs/network description: 【供中文用户】报错或漏洞请使用这个模板创建,不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题,参考文档 https://docs.langbot.app/zh/workshop/network-details.html
title: "[Bug]: " title: "[Bug]: "
labels: ["bug?"] labels: ["bug?"]
body: body:
+1 -1
View File
@@ -1,5 +1,5 @@
name: Bug report name: Bug report
description: Report bugs or vulnerabilities using this template. For container network connection issues, refer to the documentation https://link.langbot.app/en/docs/network description: Report bugs or vulnerabilities using this template. For container network connection issues, refer to the documentation https://docs.langbot.app/en/workshop/network-details.html
title: "[Bug]: " title: "[Bug]: "
labels: ["bug?"] labels: ["bug?"]
body: body:
+8
View File
@@ -52,3 +52,11 @@ src/langbot/web/
/dist /dist
/build /build
*.egg-info *.egg-info
# Docker 部署产生的本地文件
docker/data/
docker/docker-compose.override.yaml
# 备份目录
LangBot_backup_*/
*.bak
+6 -2
View File
@@ -10,14 +10,18 @@ FROM python:3.12.7-slim
WORKDIR /app WORKDIR /app
# Use Chinese mirror for faster and more reliable package downloads
RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list 2>/dev/null || true
COPY . . COPY . .
COPY --from=node /app/web/out ./web/out COPY --from=node /app/web/out ./web/out
RUN apt update \ RUN apt update \
&& apt install gcc -y \ && apt install gcc -y \
&& python -m pip install --no-cache-dir uv \ && python -m pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple uv \
&& uv sync \ && uv sync --index-url https://pypi.tuna.tsinghua.edu.cn/simple \
&& touch /.dockerenv && touch /.dockerenv
CMD [ "uv", "run", "--no-sync", "main.py" ] CMD [ "uv", "run", "--no-sync", "main.py" ]
+6 -6
View File
@@ -19,9 +19,9 @@ English / [简体中文](README_CN.md) / [繁體中文](README_TW.md) / [日本
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">Website</a> <a href="https://langbot.app">Website</a>
<a href="https://link.langbot.app/en/docs/features">Features</a> <a href="https://docs.langbot.app/en/insight/features">Features</a>
<a href="https://link.langbot.app/en/docs/guide">Docs</a> <a href="https://docs.langbot.app/en/insight/guide">Docs</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme">API</a>
<a href="https://space.langbot.app/cloud">Cloud</a> <a href="https://space.langbot.app/cloud">Cloud</a>
<a href="https://space.langbot.app">Plugin Market</a> <a href="https://space.langbot.app">Plugin Market</a>
<a href="https://langbot.featurebase.app/roadmap">Roadmap</a> <a href="https://langbot.featurebase.app/roadmap">Roadmap</a>
@@ -45,7 +45,7 @@ LangBot is an **open-source, production-grade platform** for building AI-powered
- **Web Management Panel** — Configure, manage, and monitor your bots through an intuitive browser interface. No YAML editing required. - **Web Management Panel** — Configure, manage, and monitor your bots through an intuitive browser interface. No YAML editing required.
- **Multi-Pipeline Architecture** — Different bots for different scenarios, with comprehensive monitoring and exception handling. - **Multi-Pipeline Architecture** — Different bots for different scenarios, with comprehensive monitoring and exception handling.
[→ Learn more about all features](https://link.langbot.app/en/docs/features) [→ Learn more about all features](https://docs.langbot.app/en/insight/features)
--- ---
@@ -76,7 +76,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**More options:** [Docker](https://link.langbot.app/en/docs/docker) · [Manual](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **More options:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker) · [Manual](https://docs.langbot.app/en/deploy/langbot/manual) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -124,7 +124,7 @@ docker compose up -d
| [接口 AI](https://jiekou.ai/) | Gateway | ✅ | | [接口 AI](https://jiekou.ai/) | Gateway | ✅ |
| [302.AI](https://share.302.ai/SuTG99) | Gateway | ✅ | | [302.AI](https://share.302.ai/SuTG99) | Gateway | ✅ |
[→ View all integrations](https://link.langbot.app/en/docs/features) [→ View all integrations](https://docs.langbot.app/en/insight/features)
--- ---
+6 -6
View File
@@ -21,9 +21,9 @@
[![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot) [![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot)
<a href="https://langbot.app">官网</a> <a href="https://langbot.app">官网</a>
<a href="https://link.langbot.app/zh/docs/features">特性</a> <a href="https://docs.langbot.app/zh/insight/features.html">特性</a>
<a href="https://link.langbot.app/zh/docs/guide">文档</a> <a href="https://docs.langbot.app/zh/insight/guide.html">文档</a>
<a href="https://link.langbot.app/zh/docs/api">API</a> <a href="https://docs.langbot.app/zh/tags/readme.html">API</a>
<a href="https://space.langbot.app/cloud">Cloud</a> <a href="https://space.langbot.app/cloud">Cloud</a>
<a href="https://space.langbot.app">插件市场</a> <a href="https://space.langbot.app">插件市场</a>
<a href="https://langbot.featurebase.app/roadmap">路线图</a> <a href="https://langbot.featurebase.app/roadmap">路线图</a>
@@ -45,7 +45,7 @@ LangBot 是一个**开源的生产级平台**,用于构建 AI 驱动的即时
- **Web 管理面板** — 通过浏览器直观地配置、管理和监控机器人,无需手动编辑配置文件。 - **Web 管理面板** — 通过浏览器直观地配置、管理和监控机器人,无需手动编辑配置文件。
- **多流水线架构** — 不同机器人用于不同场景,具备全面的监控和异常处理能力。 - **多流水线架构** — 不同机器人用于不同场景,具备全面的监控和异常处理能力。
[→ 了解更多功能特性](https://link.langbot.app/zh/docs/features) [→ 了解更多功能特性](https://docs.langbot.app/zh/insight/features.html)
--- ---
@@ -76,7 +76,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**更多方式:** [Docker](https://link.langbot.app/zh/docs/docker) · [手动部署](https://link.langbot.app/zh/docs/manual-deploy) · [宝塔面板](https://link.langbot.app/zh/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **更多方式:** [Docker](https://docs.langbot.app/zh/deploy/langbot/docker.html) · [手动部署](https://docs.langbot.app/zh/deploy/langbot/manual.html) · [宝塔面板](https://docs.langbot.app/zh/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -125,7 +125,7 @@ docker compose up -d
| [小马算力](https://www.tokenpony.cn/453z1) | 聚合平台 | ✅ | | [小马算力](https://www.tokenpony.cn/453z1) | 聚合平台 | ✅ |
| [百宝箱Tbox](https://www.tbox.cn/open) | 智能体平台 | ✅ | | [百宝箱Tbox](https://www.tbox.cn/open) | 智能体平台 | ✅ |
[→ 查看完整集成列表](https://link.langbot.app/zh/docs/features) [→ 查看完整集成列表](https://docs.langbot.app/zh/insight/features.html)
### TTS(语音合成) ### TTS(语音合成)
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">Inicio</a> <a href="https://langbot.app">Inicio</a>
<a href="https://link.langbot.app/en/docs/features">Características</a> <a href="https://docs.langbot.app/en/insight/features.html">Características</a>
<a href="https://link.langbot.app/en/docs/guide">Documentación</a> <a href="https://docs.langbot.app/en/insight/guide.html">Documentación</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme.html">API</a>
<a href="https://space.langbot.app">Mercado de Plugins</a> <a href="https://space.langbot.app">Mercado de Plugins</a>
<a href="https://langbot.featurebase.app/roadmap">Hoja de Ruta</a> <a href="https://langbot.featurebase.app/roadmap">Hoja de Ruta</a>
@@ -44,7 +44,7 @@ LangBot es una **plataforma de código abierto y grado de producción** para con
- **Panel de Gestión Web** — Configure, gestione y monitoree sus bots a través de una interfaz de navegador intuitiva. Sin necesidad de editar YAML. - **Panel de Gestión Web** — Configure, gestione y monitoree sus bots a través de una interfaz de navegador intuitiva. Sin necesidad de editar YAML.
- **Arquitectura Multi-Pipeline** — Diferentes bots para diferentes escenarios, con monitoreo completo y manejo de excepciones. - **Arquitectura Multi-Pipeline** — Diferentes bots para diferentes escenarios, con monitoreo completo y manejo de excepciones.
[→ Conocer más sobre todas las funcionalidades](https://link.langbot.app/en/docs/features) [→ Conocer más sobre todas las funcionalidades](https://docs.langbot.app/en/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**Más opciones:** [Docker](https://link.langbot.app/en/docs/docker) · [Manual](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **Más opciones:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [Manual](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [接口 AI](https://jiekou.ai/) | Pasarela | ✅ | | [接口 AI](https://jiekou.ai/) | Pasarela | ✅ |
| [302.AI](https://share.302.ai/SuTG99) | Pasarela | ✅ | | [302.AI](https://share.302.ai/SuTG99) | Pasarela | ✅ |
[→ Ver todas las integraciones](https://link.langbot.app/en/docs/features) [→ Ver todas las integraciones](https://docs.langbot.app/en/insight/features.html)
--- ---
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">Accueil</a> <a href="https://langbot.app">Accueil</a>
<a href="https://link.langbot.app/en/docs/features">Fonctionnalités</a> <a href="https://docs.langbot.app/en/insight/features.html">Fonctionnalités</a>
<a href="https://link.langbot.app/en/docs/guide">Documentation</a> <a href="https://docs.langbot.app/en/insight/guide.html">Documentation</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme.html">API</a>
<a href="https://space.langbot.app">Marché des Plugins</a> <a href="https://space.langbot.app">Marché des Plugins</a>
<a href="https://langbot.featurebase.app/roadmap">Feuille de Route</a> <a href="https://langbot.featurebase.app/roadmap">Feuille de Route</a>
@@ -44,7 +44,7 @@ LangBot est une **plateforme open-source de niveau production** pour créer des
- **Panneau de Gestion Web** — Configurez, gérez et surveillez vos bots via une interface navigateur intuitive. Aucune édition de YAML requise. - **Panneau de Gestion Web** — Configurez, gérez et surveillez vos bots via une interface navigateur intuitive. Aucune édition de YAML requise.
- **Architecture Multi-Pipeline** — Différents bots pour différents scénarios, avec surveillance complète et gestion des exceptions. - **Architecture Multi-Pipeline** — Différents bots pour différents scénarios, avec surveillance complète et gestion des exceptions.
[→ En savoir plus sur toutes les fonctionnalités](https://link.langbot.app/en/docs/features) [→ En savoir plus sur toutes les fonctionnalités](https://docs.langbot.app/en/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**Plus d'options :** [Docker](https://link.langbot.app/en/docs/docker) · [Manuel](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **Plus d'options :** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [Manuel](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | Plateforme GPU | ✅ | | [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | Plateforme GPU | ✅ |
| [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Plateforme GPU | ✅ | | [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Plateforme GPU | ✅ |
[→ Voir toutes les intégrations](https://link.langbot.app/en/docs/features) [→ Voir toutes les intégrations](https://docs.langbot.app/en/insight/features.html)
--- ---
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">ホーム</a> <a href="https://langbot.app">ホーム</a>
<a href="https://link.langbot.app/ja/docs/features">機能</a> <a href="https://docs.langbot.app/ja/insight/features.html">機能</a>
<a href="https://link.langbot.app/ja/docs/guide">ドキュメント</a> <a href="https://docs.langbot.app/ja/insight/guide.html">ドキュメント</a>
<a href="https://link.langbot.app/ja/docs/api">API</a> <a href="https://docs.langbot.app/ja/tags/readme.html">API</a>
<a href="https://space.langbot.app">プラグインマーケット</a> <a href="https://space.langbot.app">プラグインマーケット</a>
<a href="https://langbot.featurebase.app/roadmap">ロードマップ</a> <a href="https://langbot.featurebase.app/roadmap">ロードマップ</a>
@@ -44,7 +44,7 @@ LangBot は、AI搭載のインスタントメッセージングボットを構
- **Web管理パネル** — 直感的なブラウザインターフェースからボットの設定、管理、監視が可能。YAML編集は不要。 - **Web管理パネル** — 直感的なブラウザインターフェースからボットの設定、管理、監視が可能。YAML編集は不要。
- **マルチパイプラインアーキテクチャ** — 異なるシナリオに異なるボットを配置し、包括的な監視と例外処理を実現。 - **マルチパイプラインアーキテクチャ** — 異なるシナリオに異なるボットを配置し、包括的な監視と例外処理を実現。
[→ すべての機能について詳しく見る](https://link.langbot.app/ja/docs/features) [→ すべての機能について詳しく見る](https://docs.langbot.app/ja/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**その他:** [Docker](https://link.langbot.app/en/docs/docker) · [手動デプロイ](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **その他:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [手動デプロイ](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [接口 AI](https://jiekou.ai/) | ゲートウェイ | ✅ | | [接口 AI](https://jiekou.ai/) | ゲートウェイ | ✅ |
| [302.AI](https://share.302.ai/SuTG99) | ゲートウェイ | ✅ | | [302.AI](https://share.302.ai/SuTG99) | ゲートウェイ | ✅ |
[→ すべての統合を表示](https://link.langbot.app/en/docs/features) [→ すべての統合を表示](https://docs.langbot.app/en/insight/features.html)
--- ---
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">홈</a> <a href="https://langbot.app">홈</a>
<a href="https://link.langbot.app/en/docs/features">기능</a> <a href="https://docs.langbot.app/en/insight/features.html">기능</a>
<a href="https://link.langbot.app/en/docs/guide">문서</a> <a href="https://docs.langbot.app/en/insight/guide.html">문서</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme.html">API</a>
<a href="https://space.langbot.app">플러그인 마켓</a> <a href="https://space.langbot.app">플러그인 마켓</a>
<a href="https://langbot.featurebase.app/roadmap">로드맵</a> <a href="https://langbot.featurebase.app/roadmap">로드맵</a>
@@ -44,7 +44,7 @@ LangBot은 AI 기반 인스턴트 메시징 봇을 구축하기 위한 **오픈
- **웹 관리 패널** — 직관적인 브라우저 인터페이스로 봇을 구성, 관리 및 모니터링. YAML 편집 불필요. - **웹 관리 패널** — 직관적인 브라우저 인터페이스로 봇을 구성, 관리 및 모니터링. YAML 편집 불필요.
- **멀티 파이프라인 아키텍처** — 다양한 시나리오에 맞는 다양한 봇 구성, 종합 모니터링 및 예외 처리. - **멀티 파이프라인 아키텍처** — 다양한 시나리오에 맞는 다양한 봇 구성, 종합 모니터링 및 예외 처리.
[→ 모든 기능 자세히 보기](https://link.langbot.app/en/docs/features) [→ 모든 기능 자세히 보기](https://docs.langbot.app/en/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**더 많은 옵션:** [Docker](https://link.langbot.app/en/docs/docker) · [수동 배포](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **더 많은 옵션:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [수동 배포](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [接口 AI](https://jiekou.ai/) | 게이트웨이 | ✅ | | [接口 AI](https://jiekou.ai/) | 게이트웨이 | ✅ |
| [302.AI](https://share.302.ai/SuTG99) | 게이트웨이 | ✅ | | [302.AI](https://share.302.ai/SuTG99) | 게이트웨이 | ✅ |
[→ 모든 통합 보기](https://link.langbot.app/en/docs/features) [→ 모든 통합 보기](https://docs.langbot.app/en/insight/features.html)
--- ---
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">Главная</a> <a href="https://langbot.app">Главная</a>
<a href="https://link.langbot.app/en/docs/features">Возможности</a> <a href="https://docs.langbot.app/en/insight/features.html">Возможности</a>
<a href="https://link.langbot.app/en/docs/guide">Документация</a> <a href="https://docs.langbot.app/en/insight/guide.html">Документация</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme.html">API</a>
<a href="https://space.langbot.app">Магазин плагинов</a> <a href="https://space.langbot.app">Магазин плагинов</a>
<a href="https://langbot.featurebase.app/roadmap">Дорожная карта</a> <a href="https://langbot.featurebase.app/roadmap">Дорожная карта</a>
@@ -44,7 +44,7 @@ LangBot — это **платформа с открытым исходным к
- **Веб-панель управления** — Настраивайте, управляйте и мониторьте ваших ботов через интуитивный браузерный интерфейс. Ручное редактирование YAML не требуется. - **Веб-панель управления** — Настраивайте, управляйте и мониторьте ваших ботов через интуитивный браузерный интерфейс. Ручное редактирование YAML не требуется.
- **Мультиконвейерная архитектура** — Разные боты для разных сценариев с комплексным мониторингом и обработкой исключений. - **Мультиконвейерная архитектура** — Разные боты для разных сценариев с комплексным мониторингом и обработкой исключений.
[→ Подробнее обо всех возможностях](https://link.langbot.app/en/docs/features) [→ Подробнее обо всех возможностях](https://docs.langbot.app/en/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**Другие варианты:** [Docker](https://link.langbot.app/en/docs/docker) · [Ручная установка](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **Другие варианты:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [Ручная установка](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | Платформа GPU | ✅ | | [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | Платформа GPU | ✅ |
| [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Платформа GPU | ✅ | | [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Платформа GPU | ✅ |
[→ Смотреть все интеграции](https://link.langbot.app/en/docs/features) [→ Смотреть все интеграции](https://docs.langbot.app/en/insight/features.html)
--- ---
+6 -6
View File
@@ -21,9 +21,9 @@
[![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot) [![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot)
<a href="https://langbot.app">官網</a> <a href="https://langbot.app">官網</a>
<a href="https://link.langbot.app/zh/docs/features">特性</a> <a href="https://docs.langbot.app/zh/insight/features.html">特性</a>
<a href="https://link.langbot.app/zh/docs/guide">文件</a> <a href="https://docs.langbot.app/zh/insight/guide.html">文件</a>
<a href="https://link.langbot.app/zh/docs/api">API</a> <a href="https://docs.langbot.app/zh/tags/readme.html">API</a>
<a href="https://space.langbot.app">外掛市場</a> <a href="https://space.langbot.app">外掛市場</a>
<a href="https://langbot.featurebase.app/roadmap">路線圖</a> <a href="https://langbot.featurebase.app/roadmap">路線圖</a>
@@ -46,7 +46,7 @@ LangBot 是一個**開源的生產級平台**,用於建構 AI 驅動的即時
- **Web 管理面板** — 透過瀏覽器直觀地配置、管理和監控機器人,無需手動編輯設定檔。 - **Web 管理面板** — 透過瀏覽器直觀地配置、管理和監控機器人,無需手動編輯設定檔。
- **多流水線架構** — 不同機器人用於不同場景,具備全面的監控和異常處理能力。 - **多流水線架構** — 不同機器人用於不同場景,具備全面的監控和異常處理能力。
[→ 了解更多功能特性](https://link.langbot.app/zh/docs/features) [→ 了解更多功能特性](https://docs.langbot.app/zh/insight/features.html)
--- ---
@@ -77,7 +77,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**更多方式:** [Docker](https://link.langbot.app/zh/docs/docker) · [手動部署](https://link.langbot.app/zh/docs/manual-deploy) · [寶塔面板](https://link.langbot.app/zh/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **更多方式:** [Docker](https://docs.langbot.app/zh/deploy/langbot/docker.html) · [手動部署](https://docs.langbot.app/zh/deploy/langbot/manual.html) · [寶塔面板](https://docs.langbot.app/zh/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -139,7 +139,7 @@ docker compose up -d
|-----------|------| |-----------|------|
| 阿里雲百煉 | [外掛](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin) | | 阿里雲百煉 | [外掛](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin) |
[→ 查看完整整合列表](https://link.langbot.app/zh/docs/features) [→ 查看完整整合列表](https://docs.langbot.app/zh/insight/features.html)
--- ---
+6 -6
View File
@@ -19,9 +19,9 @@
[![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/langbot-app/LangBot?style=social)](https://github.com/langbot-app/LangBot/stargazers)
<a href="https://langbot.app">Trang chủ</a> <a href="https://langbot.app">Trang chủ</a>
<a href="https://link.langbot.app/en/docs/features">Tính năng</a> <a href="https://docs.langbot.app/en/insight/features.html">Tính năng</a>
<a href="https://link.langbot.app/en/docs/guide">Tài liệu</a> <a href="https://docs.langbot.app/en/insight/guide.html">Tài liệu</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://docs.langbot.app/en/tags/readme.html">API</a>
<a href="https://space.langbot.app">Chợ Plugin</a> <a href="https://space.langbot.app">Chợ Plugin</a>
<a href="https://langbot.featurebase.app/roadmap">Lộ trình</a> <a href="https://langbot.featurebase.app/roadmap">Lộ trình</a>
@@ -44,7 +44,7 @@ LangBot là một **nền tảng mã nguồn mở, cấp sản xuất** để x
- **Bảng quản lý Web** — Cấu hình, quản lý và giám sát bot thông qua giao diện trình duyệt trực quan. Không cần chỉnh sửa YAML. - **Bảng quản lý Web** — Cấu hình, quản lý và giám sát bot thông qua giao diện trình duyệt trực quan. Không cần chỉnh sửa YAML.
- **Kiến trúc đa Pipeline** — Các bot khác nhau cho các kịch bản khác nhau, với giám sát toàn diện và xử lý ngoại lệ. - **Kiến trúc đa Pipeline** — Các bot khác nhau cho các kịch bản khác nhau, với giám sát toàn diện và xử lý ngoại lệ.
[→ Tìm hiểu thêm về tất cả tính năng](https://link.langbot.app/en/docs/features) [→ Tìm hiểu thêm về tất cả tính năng](https://docs.langbot.app/en/insight/features.html)
--- ---
@@ -75,7 +75,7 @@ docker compose up -d
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH) [![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF) [![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
**Thêm tùy chọn:** [Docker](https://link.langbot.app/en/docs/docker) · [Thủ công](https://link.langbot.app/en/docs/manual-deploy) · [BTPanel](https://link.langbot.app/en/docs/bt-panel) · [Kubernetes](./docker/README_K8S.md) **Thêm tùy chọn:** [Docker](https://docs.langbot.app/en/deploy/langbot/docker.html) · [Thủ công](https://docs.langbot.app/en/deploy/langbot/manual.html) · [BTPanel](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) · [Kubernetes](./docker/README_K8S.md)
--- ---
@@ -123,7 +123,7 @@ docker compose up -d
| [接口 AI](https://jiekou.ai/) | Cổng | ✅ | | [接口 AI](https://jiekou.ai/) | Cổng | ✅ |
| [302.AI](https://share.302.ai/SuTG99) | Cổng | ✅ | | [302.AI](https://share.302.ai/SuTG99) | Cổng | ✅ |
[→ Xem tất cả tích hợp](https://link.langbot.app/en/docs/features) [→ Xem tất cả tích hợp](https://docs.langbot.app/en/insight/features.html)
--- ---
+2 -2
View File
@@ -312,7 +312,7 @@ spec:
### 参考资源 ### 参考资源
- [LangBot 官方文档](https://docs.langbot.app) - [LangBot 官方文档](https://docs.langbot.app)
- [Docker 部署文档](https://link.langbot.app/zh/docs/docker) - [Docker 部署文档](https://docs.langbot.app/zh/deploy/langbot/docker.html)
- [Kubernetes 官方文档](https://kubernetes.io/docs/) - [Kubernetes 官方文档](https://kubernetes.io/docs/)
--- ---
@@ -625,5 +625,5 @@ spec:
### References ### References
- [LangBot Official Documentation](https://docs.langbot.app) - [LangBot Official Documentation](https://docs.langbot.app)
- [Docker Deployment Guide](https://link.langbot.app/zh/docs/docker) - [Docker Deployment Guide](https://docs.langbot.app/zh/deploy/langbot/docker.html)
- [Kubernetes Official Documentation](https://kubernetes.io/docs/) - [Kubernetes Official Documentation](https://kubernetes.io/docs/)
+2 -2
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "langbot" name = "langbot"
version = "4.9.5" version = "4.9.4"
description = "Production-grade platform for building agentic IM bots" description = "Production-grade platform for building agentic IM bots"
readme = "README.md" readme = "README.md"
license-files = ["LICENSE"] license-files = ["LICENSE"]
@@ -64,7 +64,7 @@ dependencies = [
"chromadb>=1.0.0,<2.0.0", "chromadb>=1.0.0,<2.0.0",
"qdrant-client (>=1.15.1,<2.0.0)", "qdrant-client (>=1.15.1,<2.0.0)",
"pyseekdb==1.1.0.post3", "pyseekdb==1.1.0.post3",
"langbot-plugin==0.3.6", "langbot-plugin==0.3.5",
"asyncpg>=0.30.0", "asyncpg>=0.30.0",
"line-bot-sdk>=3.19.0", "line-bot-sdk>=3.19.0",
"tboxsdk>=0.0.10", "tboxsdk>=0.0.10",
+1 -1
View File
@@ -1,3 +1,3 @@
"""LangBot - Production-grade platform for building agentic IM bots""" """LangBot - Production-grade platform for building agentic IM bots"""
__version__ = '4.9.5' __version__ = '4.9.4'
+2 -1
View File
@@ -900,7 +900,8 @@ class WecomBotClient:
session = self.stream_sessions.get_session_by_feedback_id(feedback_id) session = self.stream_sessions.get_session_by_feedback_id(feedback_id)
if session: if session:
await self.logger.info( await self.logger.info(
f'反馈关联到会话: stream_id={session.stream_id}, msg_id={session.msg_id}, user_id={session.user_id}' f'反馈关联到会话: stream_id={session.stream_id}, msg_id={session.msg_id}, '
f'user_id={session.user_id}'
) )
for handler in self._message_handlers.get('feedback', []): for handler in self._message_handlers.get('feedback', []):
try: try:
+23 -16
View File
@@ -1301,19 +1301,28 @@ class MonitoringService:
satisfaction_rate = (total_likes / total_feedback * 100) if total_feedback > 0 else 0 satisfaction_rate = (total_likes / total_feedback * 100) if total_feedback > 0 else 0
# Get feedback by bot # Get feedback by bot
bot_stats_query = sqlalchemy.select( bot_stats_query = (
persistence_monitoring.MonitoringFeedback.bot_id, sqlalchemy.select(
persistence_monitoring.MonitoringFeedback.bot_name, persistence_monitoring.MonitoringFeedback.bot_id,
sqlalchemy.func.count(persistence_monitoring.MonitoringFeedback.id).label('total'), persistence_monitoring.MonitoringFeedback.bot_name,
sqlalchemy.func.sum( sqlalchemy.func.count(persistence_monitoring.MonitoringFeedback.id).label('total'),
sqlalchemy.case((persistence_monitoring.MonitoringFeedback.feedback_type == 1, 1), else_=0) sqlalchemy.func.sum(
).label('likes'), sqlalchemy.case(
sqlalchemy.func.sum( (persistence_monitoring.MonitoringFeedback.feedback_type == 1, 1),
sqlalchemy.case((persistence_monitoring.MonitoringFeedback.feedback_type == 2, 1), else_=0) else_=0
).label('dislikes'), )
).group_by( ).label('likes'),
persistence_monitoring.MonitoringFeedback.bot_id, sqlalchemy.func.sum(
persistence_monitoring.MonitoringFeedback.bot_name, sqlalchemy.case(
(persistence_monitoring.MonitoringFeedback.feedback_type == 2, 1),
else_=0
)
).label('dislikes'),
)
.group_by(
persistence_monitoring.MonitoringFeedback.bot_id,
persistence_monitoring.MonitoringFeedback.bot_name,
)
) )
if conditions: if conditions:
bot_stats_query = bot_stats_query.where(sqlalchemy.and_(*conditions)) bot_stats_query = bot_stats_query.where(sqlalchemy.and_(*conditions))
@@ -1424,9 +1433,7 @@ class MonitoringService:
'id': row[0].id if isinstance(row, tuple) else row.id, 'id': row[0].id if isinstance(row, tuple) else row.id,
'timestamp': self._format_timestamp(row[0].timestamp if isinstance(row, tuple) else row.timestamp), 'timestamp': self._format_timestamp(row[0].timestamp if isinstance(row, tuple) else row.timestamp),
'feedback_id': row[0].feedback_id if isinstance(row, tuple) else row.feedback_id, 'feedback_id': row[0].feedback_id if isinstance(row, tuple) else row.feedback_id,
'feedback_type': 'like' 'feedback_type': 'like' if (row[0].feedback_type if isinstance(row, tuple) else row.feedback_type) == 1 else 'dislike',
if (row[0].feedback_type if isinstance(row, tuple) else row.feedback_type) == 1
else 'dislike',
'feedback_content': row[0].feedback_content if isinstance(row, tuple) else row.feedback_content, 'feedback_content': row[0].feedback_content if isinstance(row, tuple) else row.feedback_content,
'inaccurate_reasons': row[0].inaccurate_reasons if isinstance(row, tuple) else row.inaccurate_reasons, 'inaccurate_reasons': row[0].inaccurate_reasons if isinstance(row, tuple) else row.inaccurate_reasons,
'bot_id': row[0].bot_id if isinstance(row, tuple) else row.bot_id, 'bot_id': row[0].bot_id if isinstance(row, tuple) else row.bot_id,
@@ -0,0 +1,75 @@
import sqlalchemy
from .. import migration
@migration.migration_class(25)
class DBMigrateFeedbackStats(migration.DBMigration):
"""Add monitoring_feedback table for storing user feedback from AI Bot conversations"""
async def _table_exists(self, table_name: str) -> bool:
"""Check if a table exists (works for both SQLite and PostgreSQL)."""
if self.ap.persistence_mgr.db.name == 'postgresql':
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.text(
'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = :table_name);'
).bindparams(table_name=table_name)
)
return bool(result.scalar())
else:
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.text("SELECT name FROM sqlite_master WHERE type='table' AND name=:table_name;").bindparams(
table_name=table_name
)
)
return result.first() is not None
async def upgrade(self):
"""Create monitoring_feedback table."""
if await self._table_exists('monitoring_feedback'):
self.ap.logger.debug('monitoring_feedback table already exists, skipping migration.')
return
# Create monitoring_feedback table with all columns
create_table_sql = '''
CREATE TABLE monitoring_feedback (
id VARCHAR(255) PRIMARY KEY,
timestamp DATETIME NOT NULL,
feedback_id VARCHAR(255) NOT NULL UNIQUE,
feedback_type INTEGER NOT NULL,
feedback_content TEXT,
inaccurate_reasons TEXT,
bot_id VARCHAR(255),
bot_name VARCHAR(255),
pipeline_id VARCHAR(255),
pipeline_name VARCHAR(255),
session_id VARCHAR(255),
message_id VARCHAR(255),
stream_id VARCHAR(255),
user_id VARCHAR(255),
platform VARCHAR(255)
)
'''
await self.ap.persistence_mgr.execute_async(sqlalchemy.text(create_table_sql))
# Create indexes
indexes = [
'CREATE INDEX ix_monitoring_feedback_timestamp ON monitoring_feedback (timestamp)',
'CREATE UNIQUE INDEX ix_monitoring_feedback_feedback_id ON monitoring_feedback (feedback_id)',
'CREATE INDEX ix_monitoring_feedback_bot_id ON monitoring_feedback (bot_id)',
'CREATE INDEX ix_monitoring_feedback_pipeline_id ON monitoring_feedback (pipeline_id)',
'CREATE INDEX ix_monitoring_feedback_session_id ON monitoring_feedback (session_id)',
'CREATE INDEX ix_monitoring_feedback_message_id ON monitoring_feedback (message_id)',
'CREATE INDEX ix_monitoring_feedback_stream_id ON monitoring_feedback (stream_id)',
]
for index_sql in indexes:
await self.ap.persistence_mgr.execute_async(sqlalchemy.text(index_sql))
self.ap.logger.info('Created monitoring_feedback table with indexes.')
async def downgrade(self):
"""Drop monitoring_feedback table."""
if await self._table_exists('monitoring_feedback'):
await self.ap.persistence_mgr.execute_async(
sqlalchemy.text('DROP TABLE monitoring_feedback')
)
self.ap.logger.info('Dropped monitoring_feedback table.')
@@ -353,3 +353,62 @@ class LLMCallMonitor:
) )
return False # Don't suppress exceptions return False # Don't suppress exceptions
class FeedbackMonitor:
"""Helper for recording user feedback from AI Bot conversations"""
@staticmethod
async def record_feedback(
ap: app.Application,
feedback_id: str,
feedback_type: int,
feedback_content: str | None = None,
inaccurate_reasons: list[str] | None = None,
bot_id: str | None = None,
bot_name: str | None = None,
pipeline_id: str | None = None,
pipeline_name: str | None = None,
session_id: str | None = None,
message_id: str | None = None,
stream_id: str | None = None,
user_id: str | None = None,
platform: str = 'wecom',
):
"""Record user feedback (like/dislike) from AI Bot conversation.
Args:
ap: Application instance
feedback_id: Unique feedback identifier from platform
feedback_type: 1 = like, 2 = dislike
feedback_content: Optional user feedback text
inaccurate_reasons: List of reasons for inaccurate response
bot_id: Bot UUID
bot_name: Bot name
pipeline_id: Pipeline UUID
pipeline_name: Pipeline name
session_id: Session ID
message_id: Message ID
stream_id: Stream ID
user_id: User ID
platform: Platform name (default: wecom)
"""
try:
await ap.monitoring_service.record_feedback(
feedback_id=feedback_id,
feedback_type=feedback_type,
feedback_content=feedback_content,
inaccurate_reasons=inaccurate_reasons,
bot_id=bot_id,
bot_name=bot_name,
pipeline_id=pipeline_id,
pipeline_name=pipeline_name,
session_id=session_id,
message_id=message_id,
stream_id=stream_id,
user_id=user_id,
platform=platform,
)
ap.logger.info(f'Recorded feedback: feedback_id={feedback_id}, type={feedback_type}')
except Exception as e:
ap.logger.error(f'Failed to record feedback: {e}')
+23 -44
View File
@@ -142,50 +142,6 @@ class RuntimeBot:
self.adapter.register_listener(platform_events.FriendMessage, on_friend_message) self.adapter.register_listener(platform_events.FriendMessage, on_friend_message)
self.adapter.register_listener(platform_events.GroupMessage, on_group_message) self.adapter.register_listener(platform_events.GroupMessage, on_group_message)
# Register feedback listener (only effective on adapters that support it)
async def on_feedback(
event: platform_events.FeedbackEvent,
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
):
try:
# Resolve pipeline name
pipeline_name = ''
if self.bot_entity.use_pipeline_uuid:
try:
pipeline_result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_pipeline.LegacyPipeline.name).where(
persistence_pipeline.LegacyPipeline.uuid == self.bot_entity.use_pipeline_uuid
)
)
pipeline_row = pipeline_result.first()
if pipeline_row:
pipeline_name = pipeline_row[0]
except Exception:
pass
await self.ap.monitoring_service.record_feedback(
feedback_id=event.feedback_id,
feedback_type=event.feedback_type,
feedback_content=event.feedback_content,
inaccurate_reasons=event.inaccurate_reasons,
bot_id=self.bot_entity.uuid,
bot_name=self.bot_entity.name,
pipeline_id=self.bot_entity.use_pipeline_uuid or '',
pipeline_name=pipeline_name,
session_id=event.session_id,
message_id=event.message_id,
stream_id=event.stream_id,
user_id=event.user_id,
platform=adapter.__class__.__name__,
)
await self.logger.info(
f'Recorded feedback: feedback_id={event.feedback_id}, type={event.feedback_type}'
)
except Exception:
await self.logger.error(f'Failed to record feedback: {traceback.format_exc()}')
self.adapter.register_listener(platform_events.FeedbackEvent, on_feedback)
async def run(self): async def run(self):
async def exception_wrapper(): async def exception_wrapper():
try: try:
@@ -312,12 +268,35 @@ class PlatformManager:
adapter_inst = self.adapter_dict[bot_entity.adapter]( adapter_inst = self.adapter_dict[bot_entity.adapter](
bot_entity.adapter_config, bot_entity.adapter_config,
logger, logger,
ap=self.ap,
) )
# 如果 adapter 支持 set_bot_uuid 方法,设置 bot_uuid(用于统一 webhook # 如果 adapter 支持 set_bot_uuid 方法,设置 bot_uuid(用于统一 webhook
if hasattr(adapter_inst, 'set_bot_uuid'): if hasattr(adapter_inst, 'set_bot_uuid'):
adapter_inst.set_bot_uuid(bot_entity.uuid) adapter_inst.set_bot_uuid(bot_entity.uuid)
# 如果 adapter 支持 set_bot_info 方法,设置 bot 信息(用于监控记录)
if hasattr(adapter_inst, 'set_bot_info'):
pipeline_name = ''
if bot_entity.use_pipeline_uuid:
try:
pipeline_result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_pipeline.LegacyPipeline.name).where(
persistence_pipeline.LegacyPipeline.uuid == bot_entity.use_pipeline_uuid
)
)
pipeline_row = pipeline_result.first()
if pipeline_row:
pipeline_name = pipeline_row[0]
except Exception:
pass
adapter_inst.set_bot_info(
bot_id=bot_entity.uuid,
bot_name=bot_entity.name,
pipeline_id=bot_entity.use_pipeline_uuid or '',
pipeline_name=pipeline_name,
)
runtime_bot = RuntimeBot(ap=self.ap, bot_entity=bot_entity, adapter=adapter_inst, logger=logger) runtime_bot = RuntimeBot(ap=self.ap, bot_entity=bot_entity, adapter=adapter_inst, logger=logger)
await runtime_bot.initialize() await runtime_bot.initialize()
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- protocol - protocol
help_links:
zh: https://link.langbot.app/zh/platforms/aiocqhttp
en: https://link.langbot.app/en/platforms/aiocqhttp
ja: https://link.langbot.app/ja/platforms/aiocqhttp
config: config:
- name: host - name: host
label: label:
+1 -1
View File
@@ -139,7 +139,7 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
dict # 回复卡片消息字典,key为消息id,value为回复卡片实例id,用于在流式消息时判断是否发送到指定卡片 dict # 回复卡片消息字典,key为消息id,value为回复卡片实例id,用于在流式消息时判断是否发送到指定卡片
) )
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
required_keys = [ required_keys = [
'client_id', 'client_id',
'client_secret', 'client_secret',
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/dingtalk
en: https://link.langbot.app/en/platforms/dingtalk
ja: https://link.langbot.app/ja/platforms/dingtalk
config: config:
- name: client_id - name: client_id
label: label:
@@ -23,10 +23,6 @@ spec:
categories: categories:
- popular - popular
- global - global
help_links:
zh: https://link.langbot.app/zh/platforms/discord
en: https://link.langbot.app/en/platforms/discord
ja: https://link.langbot.app/ja/platforms/discord
config: config:
- name: client_id - name: client_id
label: label:
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/kook
en: https://link.langbot.app/en/platforms/kook
ja: https://link.langbot.app/ja/platforms/kook
config: config:
- name: token - name: token
label: label:
@@ -18,10 +18,6 @@ spec:
- popular - popular
- china - china
- global - global
help_links:
zh: https://link.langbot.app/zh/platforms/lark
en: https://link.langbot.app/en/platforms/lark
ja: https://link.langbot.app/ja/platforms/lark
config: config:
- name: app_id - name: app_id
label: label:
+1 -1
View File
@@ -136,7 +136,7 @@ class LINEAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
seq: int # 用于在发送卡片消息中识别消息顺序,直接以seq作为标识 seq: int # 用于在发送卡片消息中识别消息顺序,直接以seq作为标识
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
configuration = Configuration(access_token=config['channel_access_token']) configuration = Configuration(access_token=config['channel_access_token'])
line_webhook = WebhookHandler(config['channel_secret']) line_webhook = WebhookHandler(config['channel_secret'])
parser = WebhookParser(config['channel_secret']) parser = WebhookParser(config['channel_secret'])
@@ -21,10 +21,6 @@ metadata:
spec: spec:
categories: categories:
- global - global
help_links:
zh: https://link.langbot.app/zh/platforms/line
en: https://link.langbot.app/en/platforms/line
ja: https://link.langbot.app/ja/platforms/line
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -60,7 +60,7 @@ class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAd
bot: typing.Union[OAClient, OAClientForLongerResponse] = pydantic.Field(exclude=True) bot: typing.Union[OAClient, OAClientForLongerResponse] = pydantic.Field(exclude=True)
bot_uuid: str = None bot_uuid: str = None
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
# 校验必填项 # 校验必填项
required_keys = ['token', 'EncodingAESKey', 'AppSecret', 'AppID', 'Mode'] required_keys = ['token', 'EncodingAESKey', 'AppSecret', 'AppID', 'Mode']
missing_keys = [k for k in required_keys if k not in config] missing_keys = [k for k in required_keys if k not in config]
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/officialaccount
en: https://link.langbot.app/en/platforms/officialaccount
ja: https://link.langbot.app/ja/platforms/officialaccount
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -15,10 +15,6 @@ spec:
categories: categories:
- popular - popular
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/openclaw_weixin
en: https://link.langbot.app/en/platforms/openclaw_weixin
ja: https://link.langbot.app/ja/platforms/openclaw_weixin
config: config:
- name: base_url - name: base_url
label: label:
@@ -132,7 +132,7 @@ class QQOfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter
message_converter: QQOfficialMessageConverter = QQOfficialMessageConverter() message_converter: QQOfficialMessageConverter = QQOfficialMessageConverter()
event_converter: QQOfficialEventConverter = QQOfficialEventConverter() event_converter: QQOfficialEventConverter = QQOfficialEventConverter()
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
bot = QQOfficialClient( bot = QQOfficialClient(
app_id=config['appid'], secret=config['secret'], token=config['token'], logger=logger, unified_mode=True app_id=config['appid'], secret=config['secret'], token=config['token'], logger=logger, unified_mode=True
) )
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/qqofficial
en: https://link.langbot.app/en/platforms/qqofficial
ja: https://link.langbot.app/ja/platforms/qqofficial
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -20,10 +20,6 @@ metadata:
spec: spec:
categories: categories:
- protocol - protocol
help_links:
zh: https://link.langbot.app/zh/platforms/satori
en: https://link.langbot.app/en/platforms/satori
ja: https://link.langbot.app/ja/platforms/satori
config: config:
- name: platform - name: platform
label: label:
+1 -1
View File
@@ -99,7 +99,7 @@ class SlackAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
event_converter: SlackEventConverter = SlackEventConverter() event_converter: SlackEventConverter = SlackEventConverter()
config: dict config: dict
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
required_keys = [ required_keys = [
'bot_token', 'bot_token',
'signing_secret', 'signing_secret',
@@ -23,10 +23,6 @@ spec:
categories: categories:
- popular - popular
- global - global
help_links:
zh: https://link.langbot.app/zh/platforms/slack
en: https://link.langbot.app/en/platforms/slack
ja: https://link.langbot.app/ja/platforms/slack
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -23,10 +23,6 @@ spec:
categories: categories:
- popular - popular
- global - global
help_links:
zh: https://link.langbot.app/zh/platforms/telegram
en: https://link.langbot.app/en/platforms/telegram
ja: https://link.langbot.app/ja/platforms/telegram
config: config:
- name: token - name: token
label: label:
@@ -539,7 +539,7 @@ class WeChatPadAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter)
typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None], typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
] = {} ] = {}
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
quart_app = quart.Quart(__name__) quart_app = quart.Quart(__name__)
message_converter = WeChatPadMessageConverter(config, logger) message_converter = WeChatPadMessageConverter(config, logger)
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/wechatpad
en: https://link.langbot.app/en/platforms/wechatpad
ja: https://link.langbot.app/ja/platforms/wechatpad
config: config:
- name: wechatpad_url - name: wechatpad_url
label: label:
+1 -1
View File
@@ -206,7 +206,7 @@ class WecomAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
config: dict config: dict
bot_uuid: str = None bot_uuid: str = None
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap=None, **kwargs):
# 校验必填项 # 校验必填项
required_keys = [ required_keys = [
'corpid', 'corpid',
@@ -15,10 +15,6 @@ spec:
categories: categories:
- popular - popular
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/wecom
en: https://link.langbot.app/en/platforms/wecom
ja: https://link.langbot.app/ja/platforms/wecom
config: config:
- name: webhook_url - name: webhook_url
label: label:
+64 -36
View File
@@ -10,8 +10,10 @@ import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
from ..logger import EventLogger from ..logger import EventLogger
from langbot.libs.wecom_ai_bot_api.wecombotevent import WecomBotEvent from langbot.libs.wecom_ai_bot_api.wecombotevent import WecomBotEvent
from langbot.libs.wecom_ai_bot_api.api import WecomBotClient from langbot.libs.wecom_ai_bot_api.api import WecomBotClient, StreamSession
from langbot.libs.wecom_ai_bot_api.ws_client import WecomBotWsClient from langbot.libs.wecom_ai_bot_api.ws_client import WecomBotWsClient
from ...core import app as langbot_app
from ...pipeline.monitoring_helper import FeedbackMonitor
class WecomBotMessageConverter(abstract_platform_adapter.AbstractMessageConverter): class WecomBotMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
@@ -192,8 +194,10 @@ class WecomBotAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
_ws_mode: bool = False _ws_mode: bool = False
bot_name: str = '' bot_name: str = ''
listeners: dict = {} listeners: dict = {}
ap: langbot_app.Application = None # Application reference for monitoring
_bot_info: dict = None # Bot info for monitoring (bot_id, bot_name, pipeline_id, pipeline_name)
def __init__(self, config: dict, logger: EventLogger): def __init__(self, config: dict, logger: EventLogger, ap: langbot_app.Application = None, **kwargs):
enable_webhook = config.get('enable-webhook', False) enable_webhook = config.get('enable-webhook', False)
bot_name = config.get('robot_name', '') bot_name = config.get('robot_name', '')
@@ -228,8 +232,14 @@ class WecomBotAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
bot_account_id=bot_account_id, bot_account_id=bot_account_id,
bot_name=bot_name, bot_name=bot_name,
event_converter=event_converter, event_converter=event_converter,
**kwargs,
) )
self.listeners = {} self.listeners = {}
object.__setattr__(self, '_ws_mode', ws_mode)
object.__setattr__(self, 'ap', ap)
# Register feedback handler for monitoring
self._register_feedback_handler()
async def reply_message( async def reply_message(
self, self,
@@ -311,9 +321,6 @@ class WecomBotAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
self.bot.on_message('single')(self.on_message) self.bot.on_message('single')(self.on_message)
elif event_type == platform_events.GroupMessage: elif event_type == platform_events.GroupMessage:
self.bot.on_message('group')(self.on_message) self.bot.on_message('group')(self.on_message)
elif event_type == platform_events.FeedbackEvent:
if hasattr(self.bot, 'on_feedback'):
self.bot.on_feedback()(self._on_feedback)
except Exception: except Exception:
print(traceback.format_exc()) print(traceback.format_exc())
@@ -321,44 +328,65 @@ class WecomBotAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
"""设置 bot UUID(用于生成 webhook URL""" """设置 bot UUID(用于生成 webhook URL"""
self.bot_uuid = bot_uuid self.bot_uuid = bot_uuid
async def _on_feedback(self, **kwargs): def set_bot_info(
"""Handle feedback event from WeChat Work AI Bot SDK and dispatch as FeedbackEvent.""" self,
try: bot_id: str,
feedback_id = kwargs.get('feedback_id', '') bot_name: str,
feedback_type = kwargs.get('feedback_type', 0) pipeline_id: str,
feedback_content = kwargs.get('feedback_content', '') or None pipeline_name: str,
inaccurate_reasons = kwargs.get('inaccurate_reasons', []) or None ):
session = kwargs.get('session') """设置 bot 信息(用于监控记录)"""
self._bot_info = {
'bot_id': bot_id,
'bot_name': bot_name,
'pipeline_id': pipeline_id,
'pipeline_name': pipeline_name,
}
session_id = None def _register_feedback_handler(self):
user_id = None """注册用户反馈处理器,用于持久化反馈数据到监控服务"""
message_id = None
stream_id = None async def handle_feedback(
if session: feedback_id: str,
feedback_type: int,
feedback_content: str,
inaccurate_reasons: list[str],
session: StreamSession,
):
"""处理用户反馈事件,持久化到监控服务"""
if not self.ap or not self._bot_info:
return
try:
# Build session_id from session info
session_id = None
if session.chat_id: if session.chat_id:
session_id = f'group_{session.chat_id}' session_id = f'group_{session.chat_id}'
elif session.user_id: elif session.user_id:
session_id = f'person_{session.user_id}' session_id = f'person_{session.user_id}'
user_id = session.user_id
message_id = session.msg_id
stream_id = session.stream_id
event = platform_events.FeedbackEvent( await FeedbackMonitor.record_feedback(
feedback_id=feedback_id, ap=self.ap,
feedback_type=feedback_type, feedback_id=feedback_id,
feedback_content=feedback_content, feedback_type=feedback_type,
inaccurate_reasons=inaccurate_reasons, feedback_content=feedback_content if feedback_content else None,
user_id=user_id, inaccurate_reasons=inaccurate_reasons if inaccurate_reasons else None,
session_id=session_id, bot_id=self._bot_info['bot_id'],
message_id=message_id, bot_name=self._bot_info['bot_name'],
stream_id=stream_id, pipeline_id=self._bot_info['pipeline_id'],
source_platform_object=session, pipeline_name=self._bot_info['pipeline_name'],
) session_id=session_id,
message_id=session.msg_id if session else None,
stream_id=session.stream_id if session else None,
user_id=session.user_id if session else None,
platform='wecom',
)
except Exception:
await self.logger.error(f'Failed to record feedback: {traceback.format_exc()}')
if platform_events.FeedbackEvent in self.listeners: # Register the feedback handler with the bot client
await self.listeners[platform_events.FeedbackEvent](event, self) if hasattr(self.bot, 'on_feedback'):
except Exception: self.bot.on_feedback()(handle_feedback)
await self.logger.error(f'Error in wecombot feedback callback: {traceback.format_exc()}')
async def handle_unified_webhook(self, bot_uuid: str, path: str, request): async def handle_unified_webhook(self, bot_uuid: str, path: str, request):
_ws_mode = not self.config.get('enable-webhook', False) _ws_mode = not self.config.get('enable-webhook', False)
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/wecombot
en: https://link.langbot.app/en/platforms/wecombot
ja: https://link.langbot.app/ja/platforms/wecombot
config: config:
- name: BotId - name: BotId
label: label:
@@ -14,10 +14,6 @@ metadata:
spec: spec:
categories: categories:
- china - china
help_links:
zh: https://link.langbot.app/zh/platforms/wecomcs
en: https://link.langbot.app/en/platforms/wecomcs
ja: https://link.langbot.app/ja/platforms/wecomcs
config: config:
- name: webhook_url - name: webhook_url
label: label:
+1 -1
View File
@@ -2,7 +2,7 @@ import langbot
semantic_version = f'v{langbot.__version__}' semantic_version = f'v{langbot.__version__}'
required_database_version = 24 required_database_version = 25
"""Tag the version of the database schema, used to check if the database needs to be migrated""" """Tag the version of the database schema, used to check if the database needs to be migrated"""
debug_mode = False debug_mode = False
+1 -1
View File
@@ -203,7 +203,7 @@ class VersionManager:
try: try:
if await self.ap.ver_mgr.is_new_version_available(): if await self.ap.ver_mgr.is_new_version_available():
return ( return (
'New version available:\n有新版本可用,根据文档更新: \nhttps://link.langbot.app/zh/docs/update', 'New version available:\n有新版本可用,根据文档更新: \nhttps://docs.langbot.app/zh/deploy/update.html',
logging.INFO, logging.INFO,
) )
Generated
+5 -5
View File
@@ -1832,7 +1832,7 @@ wheels = [
[[package]] [[package]]
name = "langbot" name = "langbot"
version = "4.9.5" version = "4.9.4"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiocqhttp" }, { name = "aiocqhttp" },
@@ -1937,7 +1937,7 @@ requires-dist = [
{ name = "ebooklib", specifier = ">=0.18" }, { name = "ebooklib", specifier = ">=0.18" },
{ name = "gewechat-client", specifier = ">=0.1.5" }, { name = "gewechat-client", specifier = ">=0.1.5" },
{ name = "html2text", specifier = ">=2024.2.26" }, { name = "html2text", specifier = ">=2024.2.26" },
{ name = "langbot-plugin", specifier = "==0.3.6" }, { name = "langbot-plugin", specifier = "==0.3.5" },
{ name = "langchain", specifier = ">=0.2.0" }, { name = "langchain", specifier = ">=0.2.0" },
{ name = "langchain-text-splitters", specifier = ">=0.0.1" }, { name = "langchain-text-splitters", specifier = ">=0.0.1" },
{ name = "lark-oapi", specifier = ">=1.4.15" }, { name = "lark-oapi", specifier = ">=1.4.15" },
@@ -1993,7 +1993,7 @@ dev = [
[[package]] [[package]]
name = "langbot-plugin" name = "langbot-plugin"
version = "0.3.6" version = "0.3.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },
@@ -2011,9 +2011,9 @@ dependencies = [
{ name = "watchdog" }, { name = "watchdog" },
{ name = "websockets" }, { name = "websockets" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ff/f0/e5561bd1ebda0b9345ad6b98718b5f002bb3ca79b5ec294dc77cc10957b9/langbot_plugin-0.3.6.tar.gz", hash = "sha256:20db981e416a640f22246e54517abc2a095d8ccf5e69e06c2674fb8a443f5dbe", size = 179266, upload-time = "2026-03-30T15:58:58.523Z" } sdist = { url = "https://files.pythonhosted.org/packages/1c/8f/0a22e4461b0893ac2afb1b6aaebafe04c921df6dbbf4b8bd6c83cf6a97b2/langbot_plugin-0.3.5.tar.gz", hash = "sha256:79c7feb08f788f480435de8cdefc3cfed4de2dfb03978a460251b8c9d1c271d3", size = 171927, upload-time = "2026-03-25T13:53:18.334Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/f5/ac424c2620e1be98a54a0b8ec0ed256a9c06cea7cd32a30732a1aea5fdc5/langbot_plugin-0.3.6-py3-none-any.whl", hash = "sha256:3238448436c41d50a0a0cf37438d845f0a1371159d440af3411a984e3d4e9eb7", size = 156752, upload-time = "2026-03-30T15:59:00.229Z" }, { url = "https://files.pythonhosted.org/packages/cd/93/fdd4eb54434a358a3917aec74190e2e1b64351a5bb955677f634d29fc4fd/langbot_plugin-0.3.5-py3-none-any.whl", hash = "sha256:4d31f92338e1e2dc343ae00982e4facbe7abae84f4d1c4e1375cdcac9d7155d7", size = 146575, upload-time = "2026-03-25T13:53:16.987Z" },
] ]
[[package]] [[package]]
+1 -1
View File
@@ -1 +1 @@
NEXT_PUBLIC_API_BASE_URL=http://localhost:5300 NEXT_PUBLIC_API_BASE_URL=http://192.168.1.97:5300
+1 -1
View File
@@ -1,3 +1,3 @@
# Debug LangBot Frontend # Debug LangBot Frontend
Please refer to the [Development Guide](https://link.langbot.app/en/docs/dev-config) for more information. Please refer to the [Development Guide](https://docs.langbot.app/en/develop/dev-config.html) for more information.
+1099 -3276
View File
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import i18n from 'i18next';
import { import {
IChooseAdapterEntity, IChooseAdapterEntity,
IPipelineEntity, IPipelineEntity,
@@ -14,8 +13,6 @@ import { UUID } from 'uuidjs';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
import { Bot } from '@/app/infra/entities/api'; import { Bot } from '@/app/infra/entities/api';
import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
import { ExternalLink } from 'lucide-react';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -105,9 +102,6 @@ export default function BotForm({
const [adapterDescriptionList, setAdapterDescriptionList] = useState< const [adapterDescriptionList, setAdapterDescriptionList] = useState<
Record<string, string> Record<string, string>
>({}); >({});
const [adapterHelpLinks, setAdapterHelpLinks] = useState<
Record<string, Record<string, string>>
>({});
const [pipelineNameList, setPipelineNameList] = useState<IPipelineEntity[]>( const [pipelineNameList, setPipelineNameList] = useState<IPipelineEntity[]>(
[], [],
@@ -215,18 +209,6 @@ export default function BotForm({
), ),
); );
setAdapterHelpLinks(
adaptersRes.adapters.reduce(
(acc, item) => {
if (item.spec.help_links) {
acc[item.name] = item.spec.help_links;
}
return acc;
},
{} as Record<string, Record<string, string>>,
),
);
adaptersRes.adapters.forEach((rawAdapter) => { adaptersRes.adapters.forEach((rawAdapter) => {
adapterNameToDynamicConfigMap.set( adapterNameToDynamicConfigMap.set(
rawAdapter.name, rawAdapter.name,
@@ -487,81 +469,59 @@ export default function BotForm({
<span className="text-destructive">*</span> <span className="text-destructive">*</span>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<div className="flex items-center gap-2"> <Select
<Select onValueChange={(value) => {
onValueChange={(value) => { field.onChange(value);
field.onChange(value); handleAdapterSelect(value);
handleAdapterSelect(value); }}
}} value={field.value}
value={field.value} >
> <SelectTrigger className="w-[240px]">
<SelectTrigger className="w-[240px]"> {field.value ? (
{field.value ? ( <div className="flex items-center gap-2">
<div className="flex items-center gap-2"> <img
<img src={httpClient.getAdapterIconURL(field.value)}
src={httpClient.getAdapterIconURL(field.value)} alt=""
alt="" className="h-5 w-5 rounded"
className="h-5 w-5 rounded"
/>
<span>
{adapterNameList.find(
(a) => a.value === field.value,
)?.label ?? field.value}
</span>
</div>
) : (
<SelectValue
placeholder={t('bots.selectAdapter')}
/> />
)} <span>
</SelectTrigger> {adapterNameList.find(
<SelectContent> (a) => a.value === field.value,
{groupedAdapters.map((group) => ( )?.label ?? field.value}
<SelectGroup </span>
key={group.categoryId ?? 'uncategorized'} </div>
> ) : (
{group.categoryId && ( <SelectValue placeholder={t('bots.selectAdapter')} />
<SelectLabel> )}
{getCategoryLabel(t, group.categoryId)} </SelectTrigger>
</SelectLabel> <SelectContent>
)} {groupedAdapters.map((group) => (
{group.items.map((item) => ( <SelectGroup
<SelectItem key={item.value} value={item.value}> key={group.categoryId ?? 'uncategorized'}
<div className="flex items-center gap-2"> >
<img {group.categoryId && (
src={httpClient.getAdapterIconURL( <SelectLabel>
item.value, {getCategoryLabel(t, group.categoryId)}
)} </SelectLabel>
alt="" )}
className="h-5 w-5 rounded" {group.items.map((item) => (
/> <SelectItem key={item.value} value={item.value}>
<span>{item.label}</span> <div className="flex items-center gap-2">
</div> <img
</SelectItem> src={httpClient.getAdapterIconURL(
))} item.value,
</SelectGroup> )}
))} alt=""
</SelectContent> className="h-5 w-5 rounded"
</Select> />
{currentAdapter && <span>{item.label}</span>
(() => { </div>
const docUrl = getAdapterDocUrl( </SelectItem>
adapterHelpLinks[currentAdapter], ))}
i18n.language, </SelectGroup>
); ))}
return docUrl ? ( </SelectContent>
<a </Select>
href={docUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex shrink-0 items-center gap-1 text-xs text-primary hover:underline"
>
{t('bots.viewAdapterDocs')}
<ExternalLink className="h-3 w-3" />
</a>
) : null;
})()}
</div>
</FormControl> </FormControl>
{currentAdapter && adapterDescriptionList[currentAdapter] && ( {currentAdapter && adapterDescriptionList[currentAdapter] && (
<FormDescription> <FormDescription>
@@ -1423,12 +1423,12 @@ export default function HomeSidebar({
localStorage.getItem('langbot_language'); localStorage.getItem('langbot_language');
if (language === 'zh-Hans' || language === 'zh-Hant') { if (language === 'zh-Hans' || language === 'zh-Hant') {
window.open( window.open(
'https://link.langbot.app/zh/docs/guide', 'https://docs.langbot.app/zh/insight/guide',
'_blank', '_blank',
); );
} else { } else {
window.open( window.open(
'https://link.langbot.app/en/docs/guide', 'https://docs.langbot.app/en/insight/guide',
'_blank', '_blank',
); );
} }
@@ -67,9 +67,9 @@ export const sidebarConfigList = [
route: '/home/bots', route: '/home/bots',
description: t('bots.description'), description: t('bots.description'),
helpLink: { helpLink: {
en_US: 'https://link.langbot.app/en/docs/platforms', en_US: 'https://docs.langbot.app/en/usage/platforms/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/platforms', zh_Hans: 'https://docs.langbot.app/zh/usage/platforms/readme',
ja_JP: 'https://link.langbot.app/ja/docs/platforms', ja_JP: 'https://docs.langbot.app/ja/usage/platforms/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -89,9 +89,9 @@ export const sidebarConfigList = [
route: '/home/pipelines', route: '/home/pipelines',
description: t('pipelines.description'), description: t('pipelines.description'),
helpLink: { helpLink: {
en_US: 'https://link.langbot.app/en/docs/pipelines', en_US: 'https://docs.langbot.app/en/usage/pipelines/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/pipelines', zh_Hans: 'https://docs.langbot.app/zh/usage/pipelines/readme',
ja_JP: 'https://link.langbot.app/ja/docs/pipelines', ja_JP: 'https://docs.langbot.app/ja/usage/pipelines/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -111,9 +111,9 @@ export const sidebarConfigList = [
route: '/home/knowledge', route: '/home/knowledge',
description: t('knowledge.description'), description: t('knowledge.description'),
helpLink: { helpLink: {
en_US: 'https://link.langbot.app/en/docs/knowledge', en_US: 'https://docs.langbot.app/en/usage/knowledge/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/knowledge', zh_Hans: 'https://docs.langbot.app/zh/usage/knowledge/readme',
ja_JP: 'https://link.langbot.app/ja/docs/knowledge', ja_JP: 'https://docs.langbot.app/ja/usage/knowledge/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -135,9 +135,9 @@ export const sidebarConfigList = [
route: '/home/plugins', route: '/home/plugins',
description: t('plugins.description'), description: t('plugins.description'),
helpLink: { helpLink: {
en_US: 'https://link.langbot.app/en/docs/plugins', en_US: 'https://docs.langbot.app/en/usage/plugin/plugin-intro',
zh_Hans: 'https://link.langbot.app/zh/docs/plugins', zh_Hans: 'https://docs.langbot.app/zh/usage/plugin/plugin-intro',
ja_JP: 'https://link.langbot.app/ja/docs/plugins', ja_JP: 'https://docs.langbot.app/ja/usage/plugin/plugin-intro',
}, },
section: 'extensions', section: 'extensions',
}), }),
@@ -157,9 +157,9 @@ export const sidebarConfigList = [
route: '/home/market', route: '/home/market',
description: t('plugins.description'), description: t('plugins.description'),
helpLink: { helpLink: {
en_US: 'https://link.langbot.app/en/docs/plugins', en_US: 'https://docs.langbot.app/en/usage/plugin/plugin-intro',
zh_Hans: 'https://link.langbot.app/zh/docs/plugins', zh_Hans: 'https://docs.langbot.app/zh/usage/plugin/plugin-intro',
ja_JP: 'https://link.langbot.app/ja/docs/plugins', ja_JP: 'https://docs.langbot.app/ja/usage/plugin/plugin-intro',
}, },
section: 'extensions', section: 'extensions',
}), }),
@@ -36,11 +36,11 @@ export default function NewVersionDialog({
const getUpdateDocsUrl = () => { const getUpdateDocsUrl = () => {
const language = i18n.language; const language = i18n.language;
if (language === 'zh-Hans' || language === 'zh-Hant') { if (language === 'zh-Hans' || language === 'zh-Hant') {
return 'https://link.langbot.app/zh/docs/update'; return 'https://docs.langbot.app/zh/deploy/update';
} else if (language === 'ja-JP') { } else if (language === 'ja-JP') {
return 'https://link.langbot.app/ja/docs/update'; return 'https://docs.langbot.app/ja/deploy/update';
} else { } else {
return 'https://link.langbot.app/en/docs/update'; return 'https://docs.langbot.app/en/deploy/update';
} }
}; };
@@ -2,13 +2,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { ThumbsUp, ThumbsDown, TrendingUp, TrendingDown, Minus } from 'lucide-react';
ThumbsUp,
ThumbsDown,
TrendingUp,
TrendingDown,
Minus,
} from 'lucide-react';
interface FeedbackCardProps { interface FeedbackCardProps {
title: string; title: string;
@@ -34,10 +28,8 @@ export function FeedbackCard({
}: FeedbackCardProps) { }: FeedbackCardProps) {
const variantStyles = { const variantStyles = {
default: 'bg-white dark:bg-[#2a2a2e] border-gray-200 dark:border-gray-700', default: 'bg-white dark:bg-[#2a2a2e] border-gray-200 dark:border-gray-700',
success: success: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800',
'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800', warning: 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800',
warning:
'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800',
danger: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800', danger: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800',
}; };
@@ -72,9 +64,7 @@ export function FeedbackCard({
} }
return ( return (
<div <div className={`p-6 rounded-xl border shadow-sm ${variantStyles[variant]}`}>
className={`p-6 rounded-xl border shadow-sm ${variantStyles[variant]}`}
>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<p className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1"> <p className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
@@ -89,28 +79,15 @@ export function FeedbackCard({
</p> </p>
)} )}
{trend && ( {trend && (
<div <div className={`flex items-center mt-2 text-sm ${trendStyles[trend.direction]}`}>
className={`flex items-center mt-2 text-sm ${trendStyles[trend.direction]}`} {trend.direction === 'up' && <TrendingUp className="w-4 h-4 mr-1" />}
> {trend.direction === 'down' && <TrendingDown className="w-4 h-4 mr-1" />}
{trend.direction === 'up' && ( {trend.direction === 'neutral' && <Minus className="w-4 h-4 mr-1" />}
<TrendingUp className="w-4 h-4 mr-1" /> <span>{trend.value > 0 ? '+' : ''}{trend.value}%</span>
)}
{trend.direction === 'down' && (
<TrendingDown className="w-4 h-4 mr-1" />
)}
{trend.direction === 'neutral' && (
<Minus className="w-4 h-4 mr-1" />
)}
<span>
{trend.value > 0 ? '+' : ''}
{trend.value}%
</span>
</div> </div>
)} )}
</div> </div>
<div <div className={`p-3 rounded-lg bg-gray-100 dark:bg-gray-800 ${iconStyles[variant]}`}>
className={`p-3 rounded-lg bg-gray-100 dark:bg-gray-800 ${iconStyles[variant]}`}
>
{icon} {icon}
</div> </div>
</div> </div>
@@ -162,11 +139,7 @@ export function FeedbackStatsCards({ stats, loading }: FeedbackStatsProps) {
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" /> <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z" />
</svg> </svg>
), ),
variant: (stats && stats.satisfactionRate >= 80 variant: (stats && stats.satisfactionRate >= 80 ? 'success' : stats && stats.satisfactionRate >= 50 ? 'warning' : 'danger') as 'default' | 'success' | 'warning' | 'danger',
? 'success'
: stats && stats.satisfactionRate >= 50
? 'warning'
: 'danger') as 'default' | 'success' | 'warning' | 'danger',
}, },
]; ];
@@ -2,13 +2,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { ThumbsUp, ThumbsDown, ChevronRight, ChevronDown, ExternalLink } from 'lucide-react';
ThumbsUp,
ThumbsDown,
ChevronRight,
ChevronDown,
ExternalLink,
} from 'lucide-react';
import { FeedbackRecord } from '../types/monitoring'; import { FeedbackRecord } from '../types/monitoring';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { LoadingSpinner } from '@/components/ui/loading-spinner'; import { LoadingSpinner } from '@/components/ui/loading-spinner';
@@ -19,11 +13,7 @@ interface FeedbackListProps {
onViewMessage?: (messageId: string) => void; onViewMessage?: (messageId: string) => void;
} }
export function FeedbackList({ export function FeedbackList({ feedback, loading, onViewMessage }: FeedbackListProps) {
feedback,
loading,
onViewMessage,
}: FeedbackListProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [expandedId, setExpandedId] = React.useState<string | null>(null); const [expandedId, setExpandedId] = React.useState<string | null>(null);
@@ -90,13 +80,9 @@ export function FeedbackList({
{/* Expand Icon */} {/* Expand Icon */}
<div className="mr-3 mt-0.5"> <div className="mr-3 mt-0.5">
{expandedId === item.id ? ( {expandedId === item.id ? (
<ChevronDown <ChevronDown className={`w-5 h-5 ${item.feedbackType === 'like' ? 'text-green-500' : 'text-red-500'}`} />
className={`w-5 h-5 ${item.feedbackType === 'like' ? 'text-green-500' : 'text-red-500'}`}
/>
) : ( ) : (
<ChevronRight <ChevronRight className={`w-5 h-5 ${item.feedbackType === 'like' ? 'text-green-500' : 'text-red-500'}`} />
className={`w-5 h-5 ${item.feedbackType === 'like' ? 'text-green-500' : 'text-red-500'}`}
/>
)} )}
</div> </div>
@@ -109,12 +95,8 @@ export function FeedbackList({
) : ( ) : (
<ThumbsDown className="w-5 h-5 text-red-500" /> <ThumbsDown className="w-5 h-5 text-red-500" />
)} )}
<span <span className={`text-sm font-medium ${item.feedbackType === 'like' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
className={`text-sm font-medium ${item.feedbackType === 'like' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`} {item.feedbackType === 'like' ? t('monitoring.feedback.like') : t('monitoring.feedback.dislike')}
>
{item.feedbackType === 'like'
? t('monitoring.feedback.like')
: t('monitoring.feedback.dislike')}
</span> </span>
{item.botName && ( {item.botName && (
<> <>
@@ -137,19 +119,18 @@ export function FeedbackList({
</p> </p>
)} )}
{item.inaccurateReasons && {item.inaccurateReasons && item.inaccurateReasons.length > 0 && (
item.inaccurateReasons.length > 0 && ( <div className="flex flex-wrap gap-1 mt-2">
<div className="flex flex-wrap gap-1 mt-2"> {item.inaccurateReasons.map((reason, idx) => (
{item.inaccurateReasons.map((reason, idx) => ( <span
<span key={idx}
key={idx} className="text-xs px-2 py-0.5 rounded bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400"
className="text-xs px-2 py-0.5 rounded bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400" >
> {reason}
{reason} </span>
</span> ))}
))} </div>
</div> )}
)}
</div> </div>
</div> </div>
@@ -164,13 +145,9 @@ export function FeedbackList({
{/* Expanded Details */} {/* Expanded Details */}
{expandedId === item.id && ( {expandedId === item.id && (
<div <div className={`border-t p-5 bg-white dark:bg-gray-900 ${
className={`border-t p-5 bg-white dark:bg-gray-900 ${ item.feedbackType === 'like' ? 'border-green-200 dark:border-green-900' : 'border-red-200 dark:border-red-900'
item.feedbackType === 'like' }`}>
? 'border-green-200 dark:border-green-900'
: 'border-red-200 dark:border-red-900'
}`}
>
<div className="space-y-4 pl-8 border-l-2 border-gray-200 dark:border-gray-700 ml-4"> <div className="space-y-4 pl-8 border-l-2 border-gray-200 dark:border-gray-700 ml-4">
{/* Context Info */} {/* Context Info */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-3"> <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-3">
@@ -54,7 +54,10 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const paramsStr = useMemo(() => JSON.stringify(params), [params]); const paramsStr = useMemo(
() => JSON.stringify(params),
[params],
);
const fetchStats = useCallback(async () => { const fetchStats = useCallback(async () => {
try { try {
@@ -63,9 +66,7 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
params.botIds.forEach((id) => queryParams.append('botId', id)); params.botIds.forEach((id) => queryParams.append('botId', id));
} }
if (params.pipelineIds) { if (params.pipelineIds) {
params.pipelineIds.forEach((id) => params.pipelineIds.forEach((id) => queryParams.append('pipelineId', id));
queryParams.append('pipelineId', id),
);
} }
if (params.startTime) { if (params.startTime) {
queryParams.append('startTime', params.startTime); queryParams.append('startTime', params.startTime);
@@ -90,8 +91,7 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
totalFeedback: bot.total, totalFeedback: bot.total,
totalLikes: bot.likes, totalLikes: bot.likes,
totalDislikes: bot.dislikes, totalDislikes: bot.dislikes,
satisfactionRate: satisfactionRate: bot.total > 0 ? Math.round((bot.likes / bot.total) * 100) : 0,
bot.total > 0 ? Math.round((bot.likes / bot.total) * 100) : 0,
})), })),
}); });
} }
@@ -110,9 +110,7 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
params.botIds.forEach((id) => queryParams.append('botId', id)); params.botIds.forEach((id) => queryParams.append('botId', id));
} }
if (params.pipelineIds) { if (params.pipelineIds) {
params.pipelineIds.forEach((id) => params.pipelineIds.forEach((id) => queryParams.append('pipelineId', id));
queryParams.append('pipelineId', id),
);
} }
if (params.startTime) { if (params.startTime) {
queryParams.append('startTime', params.startTime); queryParams.append('startTime', params.startTime);
@@ -121,10 +119,7 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
queryParams.append('endTime', params.endTime); queryParams.append('endTime', params.endTime);
} }
if (params.feedbackType) { if (params.feedbackType) {
queryParams.append( queryParams.append('feedbackType', params.feedbackType === 'like' ? '1' : '2');
'feedbackType',
params.feedbackType === 'like' ? '1' : '2',
);
} }
if (params.limit) { if (params.limit) {
queryParams.append('limit', params.limit.toString()); queryParams.append('limit', params.limit.toString());
@@ -139,27 +134,25 @@ export function useFeedbackData(params: UseFeedbackDataParams = {}) {
}>(`/api/v1/monitoring/feedback?${queryParams.toString()}`); }>(`/api/v1/monitoring/feedback?${queryParams.toString()}`);
if (result) { if (result) {
const transformedFeedback: FeedbackRecord[] = result.feedback.map( const transformedFeedback: FeedbackRecord[] = result.feedback.map((item) => ({
(item) => ({ id: item.id,
id: item.id, timestamp: new Date(item.timestamp),
timestamp: new Date(item.timestamp), feedbackId: item.feedback_id,
feedbackId: item.feedback_id, feedbackType: item.feedback_type === 1 ? 'like' : 'dislike',
feedbackType: item.feedback_type === 1 ? 'like' : 'dislike', feedbackContent: item.feedback_content,
feedbackContent: item.feedback_content, inaccurateReasons: item.inaccurate_reasons
inaccurateReasons: item.inaccurate_reasons ? JSON.parse(item.inaccurate_reasons)
? JSON.parse(item.inaccurate_reasons) : undefined,
: undefined, botId: item.bot_id,
botId: item.bot_id, botName: item.bot_name,
botName: item.bot_name, pipelineId: item.pipeline_id,
pipelineId: item.pipeline_id, pipelineName: item.pipeline_name,
pipelineName: item.pipeline_name, sessionId: item.session_id,
sessionId: item.session_id, messageId: item.message_id,
messageId: item.message_id, streamId: item.stream_id,
streamId: item.stream_id, userId: item.user_id,
userId: item.user_id, platform: item.platform,
platform: item.platform, }));
}),
);
setFeedback(transformedFeedback); setFeedback(transformedFeedback);
setTotal(result.total); setTotal(result.total);
@@ -5,6 +5,8 @@ import {
ModelCall, ModelCall,
LLMCall, LLMCall,
EmbeddingCall, EmbeddingCall,
FeedbackRecord,
FeedbackStats,
} from '../types/monitoring'; } from '../types/monitoring';
import { backendClient } from '@/app/infra/http'; import { backendClient } from '@/app/infra/http';
import { parseUTCTimestamp } from '../utils/dateUtils'; import { parseUTCTimestamp } from '../utils/dateUtils';
+5 -9
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { Suspense, useState, useMemo } from 'react'; import React, { Suspense, useState, useMemo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -114,16 +114,12 @@ function MonitoringPageContent() {
const { const {
feedback: feedbackList, feedback: feedbackList,
stats: feedbackStats, stats: feedbackStats,
total: feedbackTotal,
loading: feedbackLoading, loading: feedbackLoading,
refetch: refetchFeedback,
} = useFeedbackData({ } = useFeedbackData({
botIds: botIds: filterState.selectedBots.length > 0 ? filterState.selectedBots : undefined,
filterState.selectedBots.length > 0 pipelineIds: filterState.selectedPipelines.length > 0 ? filterState.selectedPipelines : undefined,
? filterState.selectedBots
: undefined,
pipelineIds:
filterState.selectedPipelines.length > 0
? filterState.selectedPipelines
: undefined,
startTime: feedbackTimeRange.startTime, startTime: feedbackTimeRange.startTime,
endTime: feedbackTimeRange.endTime, endTime: feedbackTimeRange.endTime,
limit: 50, limit: 50,
@@ -1,23 +0,0 @@
/**
* Resolves the documentation URL for a given adapter from its
* spec.help_links map, selecting the best match for the current locale
* with a fallback to English.
*/
export function getAdapterDocUrl(
helpLinks: Record<string, string> | undefined,
locale: string,
): string | null {
if (!helpLinks) return null;
// Map locale to simplified language key
let lang: string;
if (locale.startsWith('zh')) {
lang = 'zh';
} else if (locale.startsWith('ja')) {
lang = 'ja';
} else {
lang = 'en';
}
return helpLinks[lang] ?? helpLinks['en'] ?? null;
}
-1
View File
@@ -118,7 +118,6 @@ export interface Adapter {
icon?: string; icon?: string;
spec: { spec: {
categories?: string[]; categories?: string[];
help_links?: Record<string, string>;
config: IDynamicFormItemSchema[]; config: IDynamicFormItemSchema[];
}; };
} }
+5 -49
View File
@@ -13,7 +13,6 @@ import {
PartyPopper, PartyPopper,
Loader2, Loader2,
X, X,
ExternalLink,
} from 'lucide-react'; } from 'lucide-react';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
@@ -46,8 +45,6 @@ import {
groupByCategory, groupByCategory,
getCategoryLabel, getCategoryLabel,
} from '@/app/infra/entities/adapter-categories'; } from '@/app/infra/entities/adapter-categories';
import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
import i18n from 'i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
@@ -801,24 +798,6 @@ function StepPlatform({
<p className="text-sm text-muted-foreground line-clamp-2"> <p className="text-sm text-muted-foreground line-clamp-2">
{extractI18nObject(adapter.description)} {extractI18nObject(adapter.description)}
</p> </p>
{(() => {
const docUrl = getAdapterDocUrl(
adapter.spec.help_links,
i18n.language,
);
return docUrl ? (
<a
href={docUrl}
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex items-center text-xs text-primary hover:underline"
onClick={(e) => e.stopPropagation()}
>
<ExternalLink className="mr-1 h-3 w-3" />
{t('bots.viewAdapterDocs')}
</a>
) : null;
})()}
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@@ -888,34 +867,11 @@ function StepBotConfig({
{adapterConfigItems.length > 0 && ( {adapterConfigItems.length > 0 && (
<Card> <Card>
<CardHeader className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2"> <CardHeader className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
<div className="flex items-center gap-2"> <CardTitle className="text-base">
<CardTitle className="text-base"> {t('wizard.config.platformConfig', {
{t('wizard.config.platformConfig', { platform: adapterLabel,
platform: adapterLabel, })}
})} </CardTitle>
</CardTitle>
{selectedAdapterName &&
(() => {
const selectedAdapter = adapters.find(
(a) => a.name === selectedAdapterName,
);
const docUrl = getAdapterDocUrl(
selectedAdapter?.spec.help_links,
i18n.language,
);
return docUrl ? (
<a
href={docUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-xs text-primary hover:underline"
>
<ExternalLink className="mr-1 h-3 w-3" />
{t('bots.viewAdapterDocs')}
</a>
) : null;
})()}
</div>
<Button <Button
size="sm" size="sm"
onClick={onSaveBot} onClick={onSaveBot}
+2 -2
View File
@@ -65,7 +65,8 @@ const enUS = {
privacyPolicy: 'Privacy Policy', privacyPolicy: 'Privacy Policy',
and: 'and', and: 'and',
dataCollectionPolicy: 'Data Collection Policy', dataCollectionPolicy: 'Data Collection Policy',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/en/insight/data-collection-policy',
loading: 'Loading...', loading: 'Loading...',
fieldRequired: 'This field is required', fieldRequired: 'This field is required',
or: 'or', or: 'or',
@@ -288,7 +289,6 @@ const enUS = {
platformAdapter: 'Platform/Adapter Selection', platformAdapter: 'Platform/Adapter Selection',
selectAdapter: 'Select Adapter', selectAdapter: 'Select Adapter',
adapterConfig: 'Adapter Configuration', adapterConfig: 'Adapter Configuration',
viewAdapterDocs: 'View Docs',
bindPipeline: 'Bind Pipeline', bindPipeline: 'Bind Pipeline',
selectPipeline: 'Select Pipeline', selectPipeline: 'Select Pipeline',
selectBot: 'Select Bot', selectBot: 'Select Bot',
+2 -23
View File
@@ -67,7 +67,8 @@ const esES = {
privacyPolicy: 'Política de privacidad', privacyPolicy: 'Política de privacidad',
and: 'y', and: 'y',
dataCollectionPolicy: 'Política de recopilación de datos', dataCollectionPolicy: 'Política de recopilación de datos',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/en/insight/data-collection-policy',
loading: 'Cargando...', loading: 'Cargando...',
fieldRequired: 'Este campo es obligatorio', fieldRequired: 'Este campo es obligatorio',
or: 'o', or: 'o',
@@ -297,7 +298,6 @@ const esES = {
platformAdapter: 'Selección de plataforma/adaptador', platformAdapter: 'Selección de plataforma/adaptador',
selectAdapter: 'Seleccionar adaptador', selectAdapter: 'Seleccionar adaptador',
adapterConfig: 'Configuración del adaptador', adapterConfig: 'Configuración del adaptador',
viewAdapterDocs: 'Ver documentación',
bindPipeline: 'Vincular Pipeline', bindPipeline: 'Vincular Pipeline',
selectPipeline: 'Seleccionar Pipeline', selectPipeline: 'Seleccionar Pipeline',
selectBot: 'Seleccionar Bot', selectBot: 'Seleccionar Bot',
@@ -1062,7 +1062,6 @@ const esES = {
embeddingCalls: 'Llamadas Embedding', embeddingCalls: 'Llamadas Embedding',
modelCalls: 'Llamadas a modelos', modelCalls: 'Llamadas a modelos',
sessions: 'Análisis de sesiones', sessions: 'Análisis de sesiones',
feedback: 'Comentarios de usuarios',
errors: 'Registros de errores', errors: 'Registros de errores',
}, },
messageList: { messageList: {
@@ -1142,26 +1141,6 @@ const esES = {
noErrors: 'No se encontraron errores', noErrors: 'No se encontraron errores',
stackTrace: 'Traza de pila', stackTrace: 'Traza de pila',
}, },
feedback: {
title: 'Comentarios de usuarios',
totalFeedback: 'Total de comentarios',
totalLikes: 'Me gusta',
totalDislikes: 'No me gusta',
satisfactionRate: 'Tasa de satisfacción',
like: 'Me gusta',
dislike: 'No me gusta',
noFeedback: 'Aún no hay comentarios',
noFeedbackDescription: 'Los comentarios de los usuarios aparecerán aquí',
feedbackList: 'Lista de comentarios',
feedbackContent: 'Contenido del comentario',
contextInfo: 'Información de contexto',
userId: 'ID de usuario',
messageId: 'ID de mensaje',
streamId: 'ID de flujo',
inaccurateReasons: 'Razones de inexactitud',
platform: 'Plataforma',
exportFeedback: 'Exportar comentarios',
},
queries: { queries: {
title: 'Consultas', title: 'Consultas',
}, },
+2 -23
View File
@@ -66,7 +66,8 @@
privacyPolicy: 'プライバシーポリシー', privacyPolicy: 'プライバシーポリシー',
and: 'および', and: 'および',
dataCollectionPolicy: 'データ収集ポリシー', dataCollectionPolicy: 'データ収集ポリシー',
dataCollectionPolicyUrl: 'https://link.langbot.app/ja/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/ja/insight/data-collection-policy',
loading: '読み込み中...', loading: '読み込み中...',
fieldRequired: 'この項目は必須です', fieldRequired: 'この項目は必須です',
or: 'または', or: 'または',
@@ -293,7 +294,6 @@
platformAdapter: 'プラットフォーム/アダプター選択', platformAdapter: 'プラットフォーム/アダプター選択',
selectAdapter: 'アダプターを選択', selectAdapter: 'アダプターを選択',
adapterConfig: 'アダプター設定', adapterConfig: 'アダプター設定',
viewAdapterDocs: 'ドキュメントを見る',
bindPipeline: 'パイプラインを紐付け', bindPipeline: 'パイプラインを紐付け',
selectPipeline: 'パイプラインを選択', selectPipeline: 'パイプラインを選択',
selectBot: 'ボットを選択してください', selectBot: 'ボットを選択してください',
@@ -1024,7 +1024,6 @@
embeddingCalls: 'Embedding呼び出し', embeddingCalls: 'Embedding呼び出し',
modelCalls: 'モデル呼び出し', modelCalls: 'モデル呼び出し',
sessions: 'セッション分析', sessions: 'セッション分析',
feedback: 'ユーザーフィードバック',
errors: 'エラーログ', errors: 'エラーログ',
}, },
messageList: { messageList: {
@@ -1094,26 +1093,6 @@
stackTrace: 'スタックトレース', stackTrace: 'スタックトレース',
title: 'エラー', title: 'エラー',
}, },
feedback: {
title: 'ユーザーフィードバック',
totalFeedback: 'フィードバック合計',
totalLikes: 'いいね数',
totalDislikes: 'よくないね数',
satisfactionRate: '満足度',
like: 'いいね',
dislike: 'よくないね',
noFeedback: 'フィードバックはまだありません',
noFeedbackDescription: 'ユーザーフィードバックがここに表示されます',
feedbackList: 'フィードバック一覧',
feedbackContent: 'フィードバック内容',
contextInfo: 'コンテキスト情報',
userId: 'ユーザーID',
messageId: 'メッセージID',
streamId: 'ストリームID',
inaccurateReasons: '不正確な理由',
platform: 'プラットフォーム',
exportFeedback: 'フィードバックをエクスポート',
},
messageDetails: { messageDetails: {
noData: 'このクエリにはLLM呼び出しやエラーがありません', noData: 'このクエリにはLLM呼び出しやエラーがありません',
}, },
+2 -23
View File
@@ -65,7 +65,8 @@ const thTH = {
privacyPolicy: 'นโยบายความเป็นส่วนตัว', privacyPolicy: 'นโยบายความเป็นส่วนตัว',
and: 'และ', and: 'และ',
dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล', dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/en/insight/data-collection-policy',
loading: 'กำลังโหลด...', loading: 'กำลังโหลด...',
fieldRequired: 'ช่องนี้จำเป็นต้องกรอก', fieldRequired: 'ช่องนี้จำเป็นต้องกรอก',
or: 'หรือ', or: 'หรือ',
@@ -283,7 +284,6 @@ const thTH = {
platformAdapter: 'การเลือกแพลตฟอร์ม/อะแดปเตอร์', platformAdapter: 'การเลือกแพลตฟอร์ม/อะแดปเตอร์',
selectAdapter: 'เลือกอะแดปเตอร์', selectAdapter: 'เลือกอะแดปเตอร์',
adapterConfig: 'การกำหนดค่าอะแดปเตอร์', adapterConfig: 'การกำหนดค่าอะแดปเตอร์',
viewAdapterDocs: 'ดูเอกสาร',
bindPipeline: 'ผูก Pipeline', bindPipeline: 'ผูก Pipeline',
selectPipeline: 'เลือก Pipeline', selectPipeline: 'เลือก Pipeline',
selectBot: 'เลือก Bot', selectBot: 'เลือก Bot',
@@ -1011,7 +1011,6 @@ const thTH = {
embeddingCalls: 'การเรียก Embedding', embeddingCalls: 'การเรียก Embedding',
modelCalls: 'การเรียกโมเดล', modelCalls: 'การเรียกโมเดล',
sessions: 'การวิเคราะห์เซสชัน', sessions: 'การวิเคราะห์เซสชัน',
feedback: 'ความคิดเห็นผู้ใช้',
errors: 'บันทึกข้อผิดพลาด', errors: 'บันทึกข้อผิดพลาด',
}, },
messageList: { messageList: {
@@ -1090,26 +1089,6 @@ const thTH = {
noErrors: 'ไม่พบข้อผิดพลาด', noErrors: 'ไม่พบข้อผิดพลาด',
stackTrace: 'Stack Trace', stackTrace: 'Stack Trace',
}, },
feedback: {
title: 'ความคิดเห็นผู้ใช้',
totalFeedback: 'ความคิดเห็นทั้งหมด',
totalLikes: 'ถูกใจ',
totalDislikes: 'ไม่ถูกใจ',
satisfactionRate: 'อัตราความพึงพอใจ',
like: 'ถูกใจ',
dislike: 'ไม่ถูกใจ',
noFeedback: 'ยังไม่มีความคิดเห็น',
noFeedbackDescription: 'ความคิดเห็นของผู้ใช้จะแสดงที่นี่',
feedbackList: 'รายการความคิดเห็น',
feedbackContent: 'เนื้อหาความคิดเห็น',
contextInfo: 'ข้อมูลบริบท',
userId: 'ID ผู้ใช้',
messageId: 'ID ข้อความ',
streamId: 'ID สตรีม',
inaccurateReasons: 'เหตุผลที่ไม่ถูกต้อง',
platform: 'แพลตฟอร์ม',
exportFeedback: 'ส่งออกความคิดเห็น',
},
queries: { queries: {
title: 'คำค้นหา', title: 'คำค้นหา',
}, },
+2 -23
View File
@@ -65,7 +65,8 @@ const viVN = {
privacyPolicy: 'Chính sách bảo mật', privacyPolicy: 'Chính sách bảo mật',
and: 'và', and: 'và',
dataCollectionPolicy: 'Chính sách thu thập dữ liệu', dataCollectionPolicy: 'Chính sách thu thập dữ liệu',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/en/insight/data-collection-policy',
loading: 'Đang tải...', loading: 'Đang tải...',
fieldRequired: 'Trường này là bắt buộc', fieldRequired: 'Trường này là bắt buộc',
or: 'hoặc', or: 'hoặc',
@@ -292,7 +293,6 @@ const viVN = {
platformAdapter: 'Nền tảng/Lựa chọn Adapter', platformAdapter: 'Nền tảng/Lựa chọn Adapter',
selectAdapter: 'Chọn Adapter', selectAdapter: 'Chọn Adapter',
adapterConfig: 'Cấu hình Adapter', adapterConfig: 'Cấu hình Adapter',
viewAdapterDocs: 'Xem tài liệu',
bindPipeline: 'Liên kết Pipeline', bindPipeline: 'Liên kết Pipeline',
selectPipeline: 'Chọn Pipeline', selectPipeline: 'Chọn Pipeline',
selectBot: 'Chọn Bot', selectBot: 'Chọn Bot',
@@ -1032,7 +1032,6 @@ const viVN = {
embeddingCalls: 'Cuộc gọi Embedding', embeddingCalls: 'Cuộc gọi Embedding',
modelCalls: 'Cuộc gọi mô hình', modelCalls: 'Cuộc gọi mô hình',
sessions: 'Phân tích phiên', sessions: 'Phân tích phiên',
feedback: 'Phản hồi người dùng',
errors: 'Nhật ký lỗi', errors: 'Nhật ký lỗi',
}, },
messageList: { messageList: {
@@ -1111,26 +1110,6 @@ const viVN = {
noErrors: 'Không tìm thấy lỗi', noErrors: 'Không tìm thấy lỗi',
stackTrace: 'Stack Trace', stackTrace: 'Stack Trace',
}, },
feedback: {
title: 'Phản hồi người dùng',
totalFeedback: 'Tổng phản hồi',
totalLikes: 'Lượt thích',
totalDislikes: 'Lượt không thích',
satisfactionRate: 'Tỷ lệ hài lòng',
like: 'Thích',
dislike: 'Không thích',
noFeedback: 'Chưa có phản hồi',
noFeedbackDescription: 'Phản hồi của người dùng sẽ hiển thị tại đây',
feedbackList: 'Danh sách phản hồi',
feedbackContent: 'Nội dung phản hồi',
contextInfo: 'Thông tin ngữ cảnh',
userId: 'ID người dùng',
messageId: 'ID tin nhắn',
streamId: 'ID luồng',
inaccurateReasons: 'Lý do không chính xác',
platform: 'Nền tảng',
exportFeedback: 'Xuất phản hồi',
},
queries: { queries: {
title: 'Truy vấn', title: 'Truy vấn',
}, },
+2 -2
View File
@@ -64,7 +64,8 @@ const zhHans = {
privacyPolicy: '隐私政策', privacyPolicy: '隐私政策',
and: '和', and: '和',
dataCollectionPolicy: '数据收集政策', dataCollectionPolicy: '数据收集政策',
dataCollectionPolicyUrl: 'https://link.langbot.app/zh/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/zh/insight/data-collection-policy',
loading: '加载中...', loading: '加载中...',
fieldRequired: '此字段为必填项', fieldRequired: '此字段为必填项',
or: '或', or: '或',
@@ -276,7 +277,6 @@ const zhHans = {
platformAdapter: '平台/适配器选择', platformAdapter: '平台/适配器选择',
selectAdapter: '选择适配器', selectAdapter: '选择适配器',
adapterConfig: '适配器配置', adapterConfig: '适配器配置',
viewAdapterDocs: '查看文档',
bindPipeline: '绑定流水线', bindPipeline: '绑定流水线',
selectPipeline: '选择流水线', selectPipeline: '选择流水线',
selectBot: '请选择机器人', selectBot: '请选择机器人',
+2 -23
View File
@@ -64,7 +64,8 @@ const zhHant = {
privacyPolicy: '隱私政策', privacyPolicy: '隱私政策',
and: '和', and: '和',
dataCollectionPolicy: '數據收集政策', dataCollectionPolicy: '數據收集政策',
dataCollectionPolicyUrl: 'https://link.langbot.app/zh/docs/data-policy', dataCollectionPolicyUrl:
'https://docs.langbot.app/zh/insight/data-collection-policy',
loading: '載入中...', loading: '載入中...',
fieldRequired: '此欄位為必填', fieldRequired: '此欄位為必填',
or: '或', or: '或',
@@ -275,7 +276,6 @@ const zhHant = {
platformAdapter: '平台/適配器選擇', platformAdapter: '平台/適配器選擇',
selectAdapter: '選擇適配器', selectAdapter: '選擇適配器',
adapterConfig: '適配器設定', adapterConfig: '適配器設定',
viewAdapterDocs: '查看文檔',
bindPipeline: '綁定流程線', bindPipeline: '綁定流程線',
selectPipeline: '選擇流程線', selectPipeline: '選擇流程線',
selectBot: '請選擇機器人', selectBot: '請選擇機器人',
@@ -963,7 +963,6 @@ const zhHant = {
embeddingCalls: 'Embedding調用', embeddingCalls: 'Embedding調用',
modelCalls: '模型調用', modelCalls: '模型調用',
sessions: '會話分析', sessions: '會話分析',
feedback: '使用者反饋',
errors: '錯誤日誌', errors: '錯誤日誌',
}, },
messageList: { messageList: {
@@ -1033,26 +1032,6 @@ const zhHant = {
stackTrace: '堆疊追蹤', stackTrace: '堆疊追蹤',
title: '錯誤', title: '錯誤',
}, },
feedback: {
title: '使用者反饋',
totalFeedback: '總反饋數',
totalLikes: '按讚數',
totalDislikes: '按倒讚數',
satisfactionRate: '滿意度',
like: '按讚',
dislike: '按倒讚',
noFeedback: '暫無反饋',
noFeedbackDescription: '使用者反饋將在此顯示',
feedbackList: '反饋列表',
feedbackContent: '反饋內容',
contextInfo: '上下文資訊',
userId: '使用者ID',
messageId: '訊息ID',
streamId: '串流ID',
inaccurateReasons: '不準確原因',
platform: '平台',
exportFeedback: '匯出反饋',
},
messageDetails: { messageDetails: {
noData: '此查詢沒有LLM調用或錯誤記錄', noData: '此查詢沒有LLM調用或錯誤記錄',
}, },