Compare commits

...

13 Commits

Author SHA1 Message Date
Tynwink 3a82aa5fcc Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-09-09 14:39:49 +08:00
Tynwink2000 d90253cc77 feat(api): advertise provider and model management 2026-09-09 11:18:01 +08:00
Tynwink2000 d8b3dad212 feat(api): support explicit provider secret projection 2026-09-09 10:38:16 +08:00
Tynwink2000 8281eb18c9 feat(api): advertise plugin and skill management 2026-09-09 00:12:40 +08:00
Tynwink2000 4eea3419e8 feat(api): advertise knowledge and MCP management 2026-09-08 23:07:43 +08:00
Tynwink2000 814740ea68 feat(api): advertise managed read operations 2026-09-08 18:53:55 +08:00
Tynwink2000 e6e8258545 feat(api): advertise extension operation capabilities 2026-09-08 17:43:11 +08:00
Tynwink2000 1a69747a06 feat(api): expose task status to api keys 2026-09-08 14:06:13 +08:00
Tynwink2000 1fa5e2f755 feat(api): add system capabilities endpoint 2026-09-08 13:03:02 +08:00
Tynwink2000 bc32eb3ca0 feat(api): add system context endpoint for lbctl 2026-09-07 14:22:27 +08:00
Hyu ec63978ecf docs: replace legacy documentation shortlinks (#2510)
* docs: replace legacy documentation shortlinks

* style: format updated documentation URLs

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-09-05 01:19:28 +08:00
Hyu 1cfe87186c docs: update published documentation links (#2509)
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-09-05 00:55:18 +08:00
leonoxo 9794df0933 feat(n8n-runner): support async response handling (#2487)
* feat(n8n-runner): support async response handling

* fix(n8n-runner): expose response handling in form

* fix(n8n-runner): preserve async response semantics

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-09-04 22:36:40 +08:00
73 changed files with 1181 additions and 206 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
name: 漏洞反馈 name: 漏洞反馈
description: 【供中文用户】报错或漏洞请使用这个模板创建,不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题,参考文档 https://link.langbot.app/zh/docs/network description: 【供中文用户】报错或漏洞请使用这个模板创建,不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题,参考文档 https://langbot.app/docs/zh/workshop/network-details
title: "[Bug]: " title: "[Bug]: "
labels: ["bug?"] labels: ["bug?"]
body: body:
@@ -22,7 +22,7 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: 异常情况 label: 异常情况
description: 完整描述异常情况,什么时候发生的、发生了什么。**请附带日志信息。** description: 完整描述异常情况,什么时候发生的、发生了什么。**请附带日志信息。**
validations: validations:
required: true required: true
- type: textarea - type: textarea
+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://langbot.app/docs/en/workshop/network-details
title: "[Bug]: " title: "[Bug]: "
labels: ["bug?"] labels: ["bug?"]
body: body:
+2 -2
View File
@@ -43,8 +43,8 @@ Run the narrowest useful test first, then broader checks when confidence is need
## Where to Look ## Where to Look
- Architecture map: `ARCHITECTURE.md`. - Architecture map: `ARCHITECTURE.md`.
- Dev environment guide: https://docs.langbot.app/zh/develop/dev-config. - Dev environment guide: https://langbot.app/docs/zh/develop/dev-config.
- Plugin runtime / CLI / SDK debugging: https://docs.langbot.app/zh/develop/plugin-runtime. - Plugin runtime / CLI / SDK debugging: https://langbot.app/docs/zh/develop/plugin-runtime.
- API-key auth: `docs/API_KEY_AUTH.md`. - API-key auth: `docs/API_KEY_AUTH.md`.
- Box deep-dive notes: `docs/review/box-architecture.md` and related files. - Box deep-dive notes: `docs/review/box-architecture.md` and related files.
- In-repo skills: `skills/` is the single source of truth for LangBot agent skills. - In-repo skills: `skills/` is the single source of truth for LangBot agent skills.
+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://langbot.app/docs/en/insight/features">Features</a>
<a href="https://link.langbot.app/en/docs/guide">Docs</a> <a href="https://langbot.app/docs/en/insight/guide">Docs</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/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>
@@ -49,7 +49,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://langbot.app/docs/en/insight/features)
📍 Practical guides: [deploy a multi-platform AI bot in 5 minutes](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [connect DeepSeek to WeChat, Discord, and Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [run a Dify Agent in Discord, Telegram, and Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/), and [build an n8n-powered chatbot](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 Practical guides: [deploy a multi-platform AI bot in 5 minutes](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [connect DeepSeek to WeChat, Discord, and Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [run a Dify Agent in Discord, Telegram, and Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/), and [build an n8n-powered chatbot](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -89,7 +89,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **More options:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [Manual](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -151,7 +151,7 @@ _Note: Public demo environment. Do not enter sensitive information._
| [302.AI](https://share.302ai.cn/SuTG99) | Gateway | ✅ | | [302.AI](https://share.302ai.cn/SuTG99) | Gateway | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | Gateway | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | Gateway | ✅ |
[→ View all integrations](https://link.langbot.app/en/docs/features) [→ View all integrations](https://langbot.app/docs/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://langbot.app/docs/zh/insight/features">特性</a>
<a href="https://link.langbot.app/zh/docs/guide">文档</a> <a href="https://langbot.app/docs/zh/insight/guide">文档</a>
<a href="https://link.langbot.app/zh/docs/api">API</a> <a href="https://langbot.app/docs/zh/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">扩展市场</a> <a href="https://space.langbot.app">扩展市场</a>
<a href="https://langbot.featurebase.app/roadmap">路线图</a> <a href="https://langbot.featurebase.app/roadmap">路线图</a>
@@ -49,7 +49,7 @@ LangBot 是一个**开源的生产级平台**,用于构建 AI 驱动的即时
- **Web 管理面板** — 通过浏览器直观地配置、管理和监控机器人,无需手动编辑配置文件。 - **Web 管理面板** — 通过浏览器直观地配置、管理和监控机器人,无需手动编辑配置文件。
- **多流水线架构** — 不同机器人用于不同场景,具备全面的监控和异常处理能力。 - **多流水线架构** — 不同机器人用于不同场景,具备全面的监控和异常处理能力。
[→ 了解更多功能特性](https://link.langbot.app/zh/docs/features) [→ 了解更多功能特性](https://langbot.app/docs/zh/insight/features)
📍 实践指南:[5 分钟部署多平台 AI 机器人](https://langbot.app/zh/blog/deploy-ai-bot-in-5-minutes/)、[将 DeepSeek 接入微信、企业微信与 Discord](https://langbot.app/zh/blog/connect-deepseek-to-wechat/)、[让 Dify Agent 跑在 Discord、Telegram 和 Slack 上](https://langbot.app/zh/blog/dify-agent-discord-telegram-slack/),以及[用 n8n 构建多平台 AI 聊天机器人](https://langbot.app/zh/blog/n8n-multi-platform-ai-chatbot/)。 📍 实践指南:[5 分钟部署多平台 AI 机器人](https://langbot.app/zh/blog/deploy-ai-bot-in-5-minutes/)、[将 DeepSeek 接入微信、企业微信与 Discord](https://langbot.app/zh/blog/connect-deepseek-to-wechat/)、[让 Dify Agent 跑在 Discord、Telegram 和 Slack 上](https://langbot.app/zh/blog/dify-agent-discord-telegram-slack/),以及[用 n8n 构建多平台 AI 聊天机器人](https://langbot.app/zh/blog/n8n-multi-platform-ai-chatbot/)。
@@ -89,7 +89,7 @@ docker compose --profile all 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](https://docs.langbot.app/zh/deploy/langbot/kubernetes) **更多方式:** [Docker](https://langbot.app/docs/zh/deploy/langbot/docker) · [手动部署](https://langbot.app/docs/zh/deploy/langbot/manual) · [宝塔面板](https://langbot.app/docs/zh/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/zh/deploy/langbot/kubernetes)
--- ---
@@ -152,7 +152,7 @@ docker compose --profile all up -d
| [百宝箱Tbox](https://www.tbox.cn/open) | 智能体平台 | ✅ | | [百宝箱Tbox](https://www.tbox.cn/open) | 智能体平台 | ✅ |
| [七牛云Qiniu](https://www.qiniu.com/ai/agent) | 聚合平台 | ✅ | | [七牛云Qiniu](https://www.qiniu.com/ai/agent) | 聚合平台 | ✅ |
[→ 查看完整集成列表](https://link.langbot.app/zh/docs/features) [→ 查看完整集成列表](https://langbot.app/docs/zh/insight/features)
### 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://langbot.app/docs/en/insight/features">Características</a>
<a href="https://link.langbot.app/en/docs/guide">Documentación</a> <a href="https://langbot.app/docs/en/insight/guide">Documentación</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/en/tags/readme">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>
@@ -48,7 +48,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://langbot.app/docs/en/insight/features)
📍 Guías prácticas: [desplegar un bot de IA multiplataforma en 5 minutos](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [conectar DeepSeek a WeChat, Discord y Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [ejecutar un Dify Agent en Discord, Telegram y Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) y [crear un chatbot con n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 Guías prácticas: [desplegar un bot de IA multiplataforma en 5 minutos](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [conectar DeepSeek a WeChat, Discord y Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [ejecutar un Dify Agent en Discord, Telegram y Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) y [crear un chatbot con n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **Más opciones:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [Manual](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [302.AI](https://share.302ai.cn/SuTG99) | Pasarela | ✅ | | [302.AI](https://share.302ai.cn/SuTG99) | Pasarela | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | Pasarela | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | Pasarela | ✅ |
[→ Ver todas las integraciones](https://link.langbot.app/en/docs/features) [→ Ver todas las integraciones](https://langbot.app/docs/en/insight/features)
--- ---
+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://langbot.app/docs/en/insight/features">Fonctionnalités</a>
<a href="https://link.langbot.app/en/docs/guide">Documentation</a> <a href="https://langbot.app/docs/en/insight/guide">Documentation</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/en/tags/readme">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>
@@ -48,7 +48,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://langbot.app/docs/en/insight/features)
📍 Guides pratiques : [déployer un bot IA multiplateforme en 5 minutes](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [connecter DeepSeek à WeChat, Discord et Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [exécuter un Dify Agent dans Discord, Telegram et Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) et [créer un chatbot avec n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 Guides pratiques : [déployer un bot IA multiplateforme en 5 minutes](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [connecter DeepSeek à WeChat, Discord et Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [exécuter un Dify Agent dans Discord, Telegram et Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) et [créer un chatbot avec n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **Plus d'options :** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [Manuel](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Plateforme GPU | ✅ | | [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Plateforme GPU | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | Passerelle | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | Passerelle | ✅ |
[→ Voir toutes les intégrations](https://link.langbot.app/en/docs/features) [→ Voir toutes les intégrations](https://langbot.app/docs/en/insight/features)
--- ---
+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://langbot.app/docs/ja/insight/features">機能</a>
<a href="https://link.langbot.app/ja/docs/guide">ドキュメント</a> <a href="https://langbot.app/docs/ja/insight/guide">ドキュメント</a>
<a href="https://link.langbot.app/ja/docs/api">API</a> <a href="https://langbot.app/docs/ja/tags/readme">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>
@@ -48,7 +48,7 @@ LangBot は、AI搭載のインスタントメッセージングボットを構
- **Web管理パネル** — 直感的なブラウザインターフェースからボットの設定、管理、監視が可能。YAML編集は不要。 - **Web管理パネル** — 直感的なブラウザインターフェースからボットの設定、管理、監視が可能。YAML編集は不要。
- **マルチパイプラインアーキテクチャ** — 異なるシナリオに異なるボットを配置し、包括的な監視と例外処理を実現。 - **マルチパイプラインアーキテクチャ** — 異なるシナリオに異なるボットを配置し、包括的な監視と例外処理を実現。
[→ すべての機能について詳しく見る](https://link.langbot.app/ja/docs/features) [→ すべての機能について詳しく見る](https://langbot.app/docs/ja/insight/features)
📍 実践ガイド: [5分でマルチプラットフォームAIボットをデプロイ](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/)、[DeepSeekをWeChat・Discord・Telegramに接続](https://langbot.app/en/blog/connect-deepseek-to-wechat/)、[Dify AgentをDiscord・Telegram・Slackで動かす](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/)、[n8n連携チャットボットを構築](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/)。 📍 実践ガイド: [5分でマルチプラットフォームAIボットをデプロイ](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/)、[DeepSeekをWeChat・Discord・Telegramに接続](https://langbot.app/en/blog/connect-deepseek-to-wechat/)、[Dify AgentをDiscord・Telegram・Slackで動かす](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/)、[n8n連携チャットボットを構築](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/)。
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **その他:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [手動デプロイ](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [302.AI](https://share.302ai.cn/SuTG99) | ゲートウェイ | ✅ | | [302.AI](https://share.302ai.cn/SuTG99) | ゲートウェイ | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | ゲートウェイ | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | ゲートウェイ | ✅ |
[→ すべての統合を表示](https://link.langbot.app/en/docs/features) [→ すべての統合を表示](https://langbot.app/docs/en/insight/features)
--- ---
+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://langbot.app/docs/en/insight/features">기능</a>
<a href="https://link.langbot.app/en/docs/guide">문서</a> <a href="https://langbot.app/docs/en/insight/guide">문서</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/en/tags/readme">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>
@@ -48,7 +48,7 @@ LangBot은 AI 기반 인스턴트 메시징 봇을 구축하기 위한 **오픈
- **웹 관리 패널** — 직관적인 브라우저 인터페이스로 봇을 구성, 관리 및 모니터링. YAML 편집 불필요. - **웹 관리 패널** — 직관적인 브라우저 인터페이스로 봇을 구성, 관리 및 모니터링. YAML 편집 불필요.
- **멀티 파이프라인 아키텍처** — 다양한 시나리오에 맞는 다양한 봇 구성, 종합 모니터링 및 예외 처리. - **멀티 파이프라인 아키텍처** — 다양한 시나리오에 맞는 다양한 봇 구성, 종합 모니터링 및 예외 처리.
[→ 모든 기능 자세히 보기](https://link.langbot.app/en/docs/features) [→ 모든 기능 자세히 보기](https://langbot.app/docs/en/insight/features)
📍 실전 가이드: [5분 만에 멀티 플랫폼 AI 봇 배포하기](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [DeepSeek를 WeChat, Discord, Telegram에 연결하기](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [Dify Agent를 Discord, Telegram, Slack에서 실행하기](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/), [n8n 기반 챗봇 만들기](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 실전 가이드: [5분 만에 멀티 플랫폼 AI 봇 배포하기](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [DeepSeek를 WeChat, Discord, Telegram에 연결하기](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [Dify Agent를 Discord, Telegram, Slack에서 실행하기](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/), [n8n 기반 챗봇 만들기](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **더 많은 옵션:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [수동 배포](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [302.AI](https://share.302ai.cn/SuTG99) | 게이트웨이 | ✅ | | [302.AI](https://share.302ai.cn/SuTG99) | 게이트웨이 | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | 게이트웨이 | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | 게이트웨이 | ✅ |
[→ 모든 통합 보기](https://link.langbot.app/en/docs/features) [→ 모든 통합 보기](https://langbot.app/docs/en/insight/features)
--- ---
+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://langbot.app/docs/en/insight/features">Возможности</a>
<a href="https://link.langbot.app/en/docs/guide">Документация</a> <a href="https://langbot.app/docs/en/insight/guide">Документация</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/en/tags/readme">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>
@@ -48,7 +48,7 @@ LangBot — это **платформа с открытым исходным к
- **Веб-панель управления** — Настраивайте, управляйте и мониторьте ваших ботов через интуитивный браузерный интерфейс. Ручное редактирование YAML не требуется. - **Веб-панель управления** — Настраивайте, управляйте и мониторьте ваших ботов через интуитивный браузерный интерфейс. Ручное редактирование YAML не требуется.
- **Мультиконвейерная архитектура** — Разные боты для разных сценариев с комплексным мониторингом и обработкой исключений. - **Мультиконвейерная архитектура** — Разные боты для разных сценариев с комплексным мониторингом и обработкой исключений.
[→ Подробнее обо всех возможностях](https://link.langbot.app/en/docs/features) [→ Подробнее обо всех возможностях](https://langbot.app/docs/en/insight/features)
📍 Практические руководства: [развернуть мультиплатформенного ИИ-бота за 5 минут](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [подключить DeepSeek к WeChat, Discord и Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [запустить Dify Agent в Discord, Telegram и Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) и [создать чат-бота на n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 Практические руководства: [развернуть мультиплатформенного ИИ-бота за 5 минут](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [подключить DeepSeek к WeChat, Discord и Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [запустить Dify Agent в Discord, Telegram и Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) и [создать чат-бота на n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **Другие варианты:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [Ручная установка](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Платформа GPU | ✅ | | [ShengSuanYun](https://www.shengsuanyun.com/?from=CH_KYIPP758) | Платформа GPU | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | Шлюз | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | Шлюз | ✅ |
[→ Смотреть все интеграции](https://link.langbot.app/en/docs/features) [→ Смотреть все интеграции](https://langbot.app/docs/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://langbot.app/docs/zh/insight/features">特性</a>
<a href="https://link.langbot.app/zh/docs/guide">文件</a> <a href="https://langbot.app/docs/zh/insight/guide">文件</a>
<a href="https://link.langbot.app/zh/docs/api">API</a> <a href="https://langbot.app/docs/zh/tags/readme">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>
@@ -50,7 +50,7 @@ LangBot 是一個**開源的生產級平台**,用於建構 AI 驅動的即時
- **Web 管理面板** — 透過瀏覽器直觀地配置、管理和監控機器人,無需手動編輯設定檔。 - **Web 管理面板** — 透過瀏覽器直觀地配置、管理和監控機器人,無需手動編輯設定檔。
- **多流水線架構** — 不同機器人用於不同場景,具備全面的監控和異常處理能力。 - **多流水線架構** — 不同機器人用於不同場景,具備全面的監控和異常處理能力。
[→ 了解更多功能特性](https://link.langbot.app/zh/docs/features) [→ 了解更多功能特性](https://langbot.app/docs/zh/insight/features)
📍 實踐指南:[5 分鐘部署多平台 AI 機器人](https://langbot.app/zh/blog/deploy-ai-bot-in-5-minutes/)、[將 DeepSeek 接入微信、企業微信與 Discord](https://langbot.app/zh/blog/connect-deepseek-to-wechat/)、[讓 Dify Agent 跑在 Discord、Telegram 和 Slack 上](https://langbot.app/zh/blog/dify-agent-discord-telegram-slack/),以及[用 n8n 建構多平台 AI 聊天機器人](https://langbot.app/zh/blog/n8n-multi-platform-ai-chatbot/)。 📍 實踐指南:[5 分鐘部署多平台 AI 機器人](https://langbot.app/zh/blog/deploy-ai-bot-in-5-minutes/)、[將 DeepSeek 接入微信、企業微信與 Discord](https://langbot.app/zh/blog/connect-deepseek-to-wechat/)、[讓 Dify Agent 跑在 Discord、Telegram 和 Slack 上](https://langbot.app/zh/blog/dify-agent-discord-telegram-slack/),以及[用 n8n 建構多平台 AI 聊天機器人](https://langbot.app/zh/blog/n8n-multi-platform-ai-chatbot/)。
@@ -90,7 +90,7 @@ docker compose --profile all 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](https://docs.langbot.app/zh/deploy/langbot/kubernetes) **更多方式:** [Docker](https://langbot.app/docs/zh/deploy/langbot/docker) · [手動部署](https://langbot.app/docs/zh/deploy/langbot/manual) · [寶塔面板](https://langbot.app/docs/zh/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/zh/deploy/langbot/kubernetes)
--- ---
@@ -165,7 +165,7 @@ docker compose --profile all up -d
|-----------|------| |-----------|------|
| 阿里雲百煉 | [外掛](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin) | | 阿里雲百煉 | [外掛](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin) |
[→ 查看完整整合列表](https://link.langbot.app/zh/docs/features) [→ 查看完整整合列表](https://langbot.app/docs/zh/insight/features)
--- ---
+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://langbot.app/docs/en/insight/features">Tính năng</a>
<a href="https://link.langbot.app/en/docs/guide">Tài liệu</a> <a href="https://langbot.app/docs/en/insight/guide">Tài liệu</a>
<a href="https://link.langbot.app/en/docs/api">API</a> <a href="https://langbot.app/docs/en/tags/readme">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>
@@ -48,7 +48,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://langbot.app/docs/en/insight/features)
📍 Hướng dẫn thực hành: [triển khai bot AI đa nền tảng trong 5 phút](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [kết nối DeepSeek với WeChat, Discord và Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [chạy Dify Agent trên Discord, Telegram và Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) và [xây dựng chatbot với n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/). 📍 Hướng dẫn thực hành: [triển khai bot AI đa nền tảng trong 5 phút](https://langbot.app/en/blog/deploy-ai-bot-in-5-minutes/), [kết nối DeepSeek với WeChat, Discord và Telegram](https://langbot.app/en/blog/connect-deepseek-to-wechat/), [chạy Dify Agent trên Discord, Telegram và Slack](https://langbot.app/en/blog/dify-agent-discord-telegram-slack/) và [xây dựng chatbot với n8n](https://langbot.app/en/blog/n8n-multi-platform-ai-chatbot/).
@@ -88,7 +88,7 @@ docker compose --profile all 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](https://docs.langbot.app/en/deploy/langbot/kubernetes) **Thêm tùy chọn:** [Docker](https://langbot.app/docs/en/deploy/langbot/docker) · [Thủ công](https://langbot.app/docs/en/deploy/langbot/manual) · [BTPanel](https://langbot.app/docs/en/deploy/langbot/one-click/bt) · [Kubernetes](https://langbot.app/docs/en/deploy/langbot/kubernetes)
--- ---
@@ -149,7 +149,7 @@ docker compose --profile all up -d
| [302.AI](https://share.302ai.cn/SuTG99) | Cổng | ✅ | | [302.AI](https://share.302ai.cn/SuTG99) | Cổng | ✅ |
| [Qiniu](https://www.qiniu.com/ai/agent) | Cổng | ✅ | | [Qiniu](https://www.qiniu.com/ai/agent) | 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://langbot.app/docs/en/insight/features)
--- ---
+1 -1
View File
@@ -1,5 +1,5 @@
# Docker Compose configuration for LangBot # Docker Compose configuration for LangBot
# For Kubernetes deployment, see kubernetes.yaml and the deployment guide at https://docs.langbot.app # For Kubernetes deployment, see kubernetes.yaml and the deployment guide at https://langbot.app/docs
version: "3" version: "3"
services: services:
+1 -1
View File
@@ -1,7 +1,7 @@
# Kubernetes Deployment for LangBot # Kubernetes Deployment for LangBot
# This file provides Kubernetes deployment manifests for LangBot based on docker-compose.yaml # This file provides Kubernetes deployment manifests for LangBot based on docker-compose.yaml
# #
# Full deployment guide (zh/en/ja): https://docs.langbot.app -> Installation -> Kubernetes # Full deployment guide (zh/en/ja): https://langbot.app/docs -> Installation -> Kubernetes
# #
# Usage: # Usage:
# kubectl -n langbot create secret generic langbot-plugin-runtime-control \ # kubectl -n langbot create secret generic langbot-plugin-runtime-control \
+17
View File
@@ -88,6 +88,23 @@ Each endpoint accepts **either**:
1. **User Token** (via `Authorization: Bearer <user_jwt_token>`) - for web UI and authenticated users 1. **User Token** (via `Authorization: Bearer <user_jwt_token>`) - for web UI and authenticated users
2. **API Key** (via `X-API-Key` or `Authorization: Bearer <api_key>`) - for external services 2. **API Key** (via `X-API-Key` or `Authorization: Bearer <api_key>`) - for external services
### Inspecting API Key Identity
`GET /api/v1/system/context` validates an API key (user JWT not accepted) and returns its bound identity without requiring resource permissions:
```json
{
"code": 0,
"msg": "ok",
"data": {
"instance_uuid": "...",
"workspace_uuid": "...",
"api_key_id": "...",
"permissions": ["..."]
}
}
```
## Example: Model Management ## Example: Model Management
### List All LLM Models ### List All LLM Models
+2 -2
View File
@@ -218,8 +218,8 @@ metadata:
spec: spec:
categories: [popular, global] categories: [popular, global]
help_links: help_links:
zh: https://docs.langbot.app/zh/platforms/http-bot zh: https://langbot.app/docs/zh/platforms/http-bot
en: https://docs.langbot.app/en/platforms/http-bot en: https://langbot.app/docs/en/platforms/http-bot
config: config:
- { name: inbound_secret, type: string, required: true, default: "" } - { name: inbound_secret, type: string, required: true, default: "" }
- { name: callback_url, type: string, required: false, default: "" } - { name: callback_url, type: string, required: false, default: "" }
+1 -1
View File
@@ -243,7 +243,7 @@ For large datasets:
- SeekDB GitHub: https://github.com/oceanbase/seekdb - SeekDB GitHub: https://github.com/oceanbase/seekdb
- pyseekdb SDK: https://github.com/oceanbase/pyseekdb - pyseekdb SDK: https://github.com/oceanbase/pyseekdb
- OceanBase Documentation: https://oceanbase.ai - OceanBase Documentation: https://oceanbase.ai
- LangBot Documentation: https://docs.langbot.app - LangBot Documentation: https://langbot.app/docs
## License ## License
+1 -1
View File
@@ -6,7 +6,7 @@ Minimal, dependency-light clients for the LangBot **HTTP Bot** platform adapter.
They show the whole loop: signing a request, pushing a message, and receiving They show the whole loop: signing a request, pushing a message, and receiving
multi-part replies on a callback endpoint. multi-part replies on a callback endpoint.
Full guide: [docs.langbot.app — HTTP Bot](https://docs.langbot.app/en/usage/platforms/http-bot). Full guide: [docs.langbot.app — HTTP Bot](https://langbot.app/docs/en/usage/platforms/http-bot).
Machine-readable contract: [`docs/http-bot-openapi.json`](../../docs/http-bot-openapi.json). Machine-readable contract: [`docs/http-bot-openapi.json`](../../docs/http-bot-openapi.json).
## Files ## Files
+1 -1
View File
@@ -6,7 +6,7 @@
它们完整展示了整条链路:对请求签名、推送一条消息、在回调端点接收 它们完整展示了整条链路:对请求签名、推送一条消息、在回调端点接收
1→M 的多段回复。 1→M 的多段回复。
完整指南:[docs.langbot.app —— HTTP Bot](https://docs.langbot.app/zh/usage/platforms/http-bot)。 完整指南:[docs.langbot.app —— HTTP Bot](https://langbot.app/docs/zh/usage/platforms/http-bot)。
机器可读的接口契约:[`docs/http-bot-openapi.json`](../../docs/http-bot-openapi.json)。 机器可读的接口契约:[`docs/http-bot-openapi.json`](../../docs/http-bot-openapi.json)。
## 文件清单 ## 文件清单
+1 -1
View File
@@ -6,7 +6,7 @@ A single self-contained HTML page that demos the LangBot **Page Bot**
(`web_page_bot`) embeddable chat widget — the one you drop onto any website with (`web_page_bot`) embeddable chat widget — the one you drop onto any website with
a single `<script>` tag. a single `<script>` tag.
Full guide: [docs.langbot.app — Page Bot](https://docs.langbot.app/en/usage/platforms/webpage). Full guide: [docs.langbot.app — Page Bot](https://langbot.app/docs/en/usage/platforms/webpage).
## Files ## Files
+1 -1
View File
@@ -6,7 +6,7 @@
(`web_page_bot`) 的可嵌入聊天组件 —— 也就是你用一行 `<script>` 标签就能放到任意 (`web_page_bot`) 的可嵌入聊天组件 —— 也就是你用一行 `<script>` 标签就能放到任意
网站上的那个组件。 网站上的那个组件。
完整指南:[docs.langbot.app —— 页面机器人](https://docs.langbot.app/zh/usage/platforms/webpage)。 完整指南:[docs.langbot.app —— 页面机器人](https://langbot.app/docs/zh/usage/platforms/webpage)。
## 文件清单 ## 文件清单
+1 -1
View File
@@ -114,7 +114,7 @@ seekdb = [
[project.urls] [project.urls]
Homepage = "https://langbot.app" Homepage = "https://langbot.app"
Documentation = "https://docs.langbot.app" Documentation = "https://langbot.app/docs"
Repository = "https://github.com/langbot-app/LangBot" Repository = "https://github.com/langbot-app/LangBot"
[project.scripts] [project.scripts]
+1 -1
View File
@@ -48,7 +48,7 @@ tools, skill add/edit, and stdio MCP are disabled. Set `box.enabled: false`
## Kubernetes ## Kubernetes
See `docker/kubernetes.yaml` and the deployment guide at See `docker/kubernetes.yaml` and the deployment guide at
https://docs.langbot.app. `docker/deploy-k8s-test.sh` is a test helper. https://langbot.app/docs. `docker/deploy-k8s-test.sh` is a test helper.
## config.yaml (generated at `data/config.yaml` on first run) ## config.yaml (generated at `data/config.yaml` on first run)
+2
View File
@@ -43,6 +43,8 @@ Two kinds of key are accepted:
Invalid, revoked, or expired keys get `401 Unauthorized`. A valid key whose Invalid, revoked, or expired keys get `401 Unauthorized`. A valid key whose
scopes do not authorize a tool gets `403 Forbidden`. scopes do not authorize a tool gets `403 Forbidden`.
To inspect key identity and permissions, call `GET /api/v1/system/context` with the API key.
## Client configuration ## Client configuration
```json ```json
+1 -1
View File
@@ -16,7 +16,7 @@ asciiart = r"""
|___/ |___/
Open Source 开源地址: https://github.com/langbot-app/LangBot Open Source 开源地址: https://github.com/langbot-app/LangBot
📖 Documentation 文档地址: https://docs.langbot.app 📖 Documentation 文档地址: https://langbot.app/docs
""" """
@@ -3,6 +3,7 @@ import quart
from ....authz import Permission, has_permission from ....authz import Permission, has_permission
from ....context import RequestContext from ....context import RequestContext
from ... import group from ... import group
from .query import resolve_include_secret
@group.group_class('models/llm', '/api/v1/provider/models/llm') @group.group_class('models/llm', '/api/v1/provider/models/llm')
@@ -16,7 +17,12 @@ class LLMModelsRouterGroup(group.RouterGroup):
) )
async def _(request_context: RequestContext) -> str: async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid') provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE) include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
if provider_uuid: if provider_uuid:
models = await self.ap.llm_model_service.get_llm_models_by_provider( models = await self.ap.llm_model_service.get_llm_models_by_provider(
request_context, request_context,
@@ -53,10 +59,16 @@ class LLMModelsRouterGroup(group.RouterGroup):
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(model_uuid: str, request_context: RequestContext) -> str: async def _(model_uuid: str, request_context: RequestContext) -> str:
include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
model = await self.ap.llm_model_service.get_llm_model( model = await self.ap.llm_model_service.get_llm_model(
request_context, request_context,
model_uuid, model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE), include_secret=include_secret,
) )
if model is None: if model is None:
return self.http_status(404, -1, 'model not found') return self.http_status(404, -1, 'model not found')
@@ -111,7 +123,12 @@ class EmbeddingModelsRouterGroup(group.RouterGroup):
) )
async def _(request_context: RequestContext) -> str: async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid') provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE) include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
if provider_uuid: if provider_uuid:
models = await self.ap.embedding_models_service.get_embedding_models_by_provider( models = await self.ap.embedding_models_service.get_embedding_models_by_provider(
request_context, request_context,
@@ -148,10 +165,16 @@ class EmbeddingModelsRouterGroup(group.RouterGroup):
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(model_uuid: str, request_context: RequestContext) -> str: async def _(model_uuid: str, request_context: RequestContext) -> str:
include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
model = await self.ap.embedding_models_service.get_embedding_model( model = await self.ap.embedding_models_service.get_embedding_model(
request_context, request_context,
model_uuid, model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE), include_secret=include_secret,
) )
if model is None: if model is None:
return self.http_status(404, -1, 'model not found') return self.http_status(404, -1, 'model not found')
@@ -208,7 +231,12 @@ class RerankModelsRouterGroup(group.RouterGroup):
) )
async def _(request_context: RequestContext) -> str: async def _(request_context: RequestContext) -> str:
provider_uuid = quart.request.args.get('provider_uuid') provider_uuid = quart.request.args.get('provider_uuid')
include_secret = has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE) include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
if provider_uuid: if provider_uuid:
models = await self.ap.rerank_models_service.get_rerank_models_by_provider( models = await self.ap.rerank_models_service.get_rerank_models_by_provider(
request_context, request_context,
@@ -245,10 +273,16 @@ class RerankModelsRouterGroup(group.RouterGroup):
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(model_uuid: str, request_context: RequestContext) -> str: async def _(model_uuid: str, request_context: RequestContext) -> str:
include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
model = await self.ap.rerank_models_service.get_rerank_model( model = await self.ap.rerank_models_service.get_rerank_model(
request_context, request_context,
model_uuid, model_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE), include_secret=include_secret,
) )
if model is None: if model is None:
return self.http_status(404, -1, 'model not found') return self.http_status(404, -1, 'model not found')
@@ -3,6 +3,7 @@ import quart
from ....authz import Permission, has_permission from ....authz import Permission, has_permission
from ....context import RequestContext from ....context import RequestContext
from ... import group from ... import group
from .query import resolve_include_secret
@group.group_class('models/providers', '/api/v1/provider/providers') @group.group_class('models/providers', '/api/v1/provider/providers')
@@ -15,9 +16,15 @@ class ModelProvidersRouterGroup(group.RouterGroup):
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(request_context: RequestContext) -> str: async def _(request_context: RequestContext) -> str:
include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
providers = await self.ap.provider_service.get_providers( providers = await self.ap.provider_service.get_providers(
request_context, request_context,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE), include_secret=include_secret,
) )
for provider in providers: for provider in providers:
counts = await self.ap.provider_service.get_provider_model_counts(request_context, provider['uuid']) counts = await self.ap.provider_service.get_provider_model_counts(request_context, provider['uuid'])
@@ -47,10 +54,16 @@ class ModelProvidersRouterGroup(group.RouterGroup):
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(provider_uuid: str, request_context: RequestContext) -> str: async def _(provider_uuid: str, request_context: RequestContext) -> str:
include_secret, error = resolve_include_secret(
quart.request.args.get('include_secret'),
permitted=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE),
)
if error:
return self.http_status(400, -1, error)
provider = await self.ap.provider_service.get_provider( provider = await self.ap.provider_service.get_provider(
request_context, request_context,
provider_uuid, provider_uuid,
include_secret=has_permission(request_context, Permission.PROVIDER_SECRET_MANAGE), include_secret=include_secret,
) )
if provider is None: if provider is None:
return self.http_status(404, -1, 'provider not found') return self.http_status(404, -1, 'provider not found')
@@ -0,0 +1,15 @@
from __future__ import annotations
def resolve_include_secret(raw_value: str | None, *, permitted: bool) -> tuple[bool, str | None]:
"""Resolve the optional secret projection query parameter."""
if raw_value is None:
return permitted, None
value = raw_value.strip().lower()
if value == 'false':
return False, None
if value == 'true':
return permitted, None
return False, 'include_secret must be either true or false'
@@ -7,14 +7,116 @@ from .. import group
from .....utils import constants from .....utils import constants
from .....entity.persistence.metadata import WorkspaceMetadata from .....entity.persistence.metadata import WorkspaceMetadata
from ...authz import Permission from ...authz import Permission
from ...context import RequestContext from ...context import PrincipalType, RequestContext
from .....provider.tools.loaders.mcp_policy import stdio_mcp_enabled from .....provider.tools.loaders.mcp_policy import stdio_mcp_enabled
from .....workspace.invitation_delivery import InvitationDeliveryService from .....workspace.invitation_delivery import InvitationDeliveryService
SYSTEM_CAPABILITY_OPERATIONS = (
'bot.list',
'bot.get',
'bot.create',
'bot.update',
'bot.delete',
'pipeline.list',
'pipeline.get',
'pipeline.create',
'pipeline.update',
'pipeline.delete',
'pipeline.copy',
'task.list',
'task.get',
'knowledge_base.list',
'knowledge_base.get',
'knowledge_base.create',
'knowledge_base.update',
'knowledge_base.delete',
'knowledge_base.file.list',
'knowledge_base.file.store',
'knowledge_base.file.delete',
'knowledge_base.retrieve',
'file.document.upload',
'plugin.install.github',
'plugin.install.marketplace',
'plugin.install.local',
'plugin.upgrade',
'plugin.get',
'plugin.list',
'plugin.config.get',
'plugin.config.update',
'plugin.logs',
'plugin.delete',
'provider.list',
'provider.get',
'provider.create',
'provider.update',
'provider.delete',
'provider.scan_models',
'model.llm.list',
'model.llm.get',
'model.llm.create',
'model.llm.update',
'model.llm.delete',
'model.llm.test',
'model.embedding.list',
'model.embedding.get',
'model.embedding.create',
'model.embedding.update',
'model.embedding.delete',
'model.embedding.test',
'model.rerank.list',
'model.rerank.get',
'model.rerank.create',
'model.rerank.update',
'model.rerank.delete',
'model.rerank.test',
'skill.list',
'skill.get',
'skill.create',
'skill.update',
'skill.delete',
'skill.files.list',
'skill.files.read',
'skill.files.write',
'skill.preview',
'skill.install.github',
'skill.install.upload',
'mcp_server.list',
'mcp_server.get',
'mcp_server.create',
'mcp_server.update',
'mcp_server.delete',
'mcp_server.resources',
'mcp_server.resource_templates',
'mcp_server.resource_read',
'mcp_server.logs',
'mcp_server.test',
)
@group.group_class('system', '/api/v1/system') @group.group_class('system', '/api/v1/system')
class SystemRouterGroup(group.RouterGroup): class SystemRouterGroup(group.RouterGroup):
async def initialize(self) -> None: async def initialize(self) -> None:
@self.route('/context', methods=['GET'], auth_type=group.AuthType.API_KEY)
async def _(request_context: RequestContext) -> str:
return self.success(
data={
'instance_uuid': request_context.instance_uuid,
'workspace_uuid': request_context.workspace_uuid,
'api_key_id': request_context.principal.api_key_uuid,
'permissions': sorted(request_context.workspace.permissions),
}
)
@self.route('/capabilities', methods=['GET'], auth_type=group.AuthType.API_KEY)
async def _() -> str:
return self.success(
data={
'schema_version': 1,
'operations': {operation: {'supported': True} for operation in SYSTEM_CAPABILITY_OPERATIONS},
}
)
@self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE) @self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE)
async def _() -> str: async def _() -> str:
# Read wizard_status and wizard_progress from metadata table # Read wizard_status and wizard_progress from metadata table
@@ -223,7 +325,7 @@ class SystemRouterGroup(group.RouterGroup):
@self.route( @self.route(
'/tasks', '/tasks',
methods=['GET'], methods=['GET'],
auth_type=group.AuthType.USER_TOKEN, auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(request_context: RequestContext) -> str: async def _(request_context: RequestContext) -> str:
@@ -242,18 +344,23 @@ class SystemRouterGroup(group.RouterGroup):
instance_uuid=request_context.instance_uuid, instance_uuid=request_context.instance_uuid,
workspace_uuid=request_context.workspace_uuid, workspace_uuid=request_context.workspace_uuid,
placement_generation=request_context.placement_generation, placement_generation=request_context.placement_generation,
public=request_context.principal.principal_type == PrincipalType.API_KEY,
) )
) )
@self.route( @self.route(
'/tasks/<task_id>', '/tasks/<task_id>',
methods=['GET'], methods=['GET'],
auth_type=group.AuthType.USER_TOKEN, auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
permission=Permission.RESOURCE_VIEW, permission=Permission.RESOURCE_VIEW,
) )
async def _(task_id: str, request_context: RequestContext) -> str: async def _(task_id: str, request_context: RequestContext) -> str:
try:
task_index = int(task_id)
except (TypeError, ValueError):
return self.http_status(404, 404, 'Task not found')
task = self.ap.task_mgr.get_task_by_id( task = self.ap.task_mgr.get_task_by_id(
int(task_id), task_index,
instance_uuid=request_context.instance_uuid, instance_uuid=request_context.instance_uuid,
workspace_uuid=request_context.workspace_uuid, workspace_uuid=request_context.workspace_uuid,
placement_generation=request_context.placement_generation, placement_generation=request_context.placement_generation,
@@ -262,6 +369,8 @@ class SystemRouterGroup(group.RouterGroup):
if task is None: if task is None:
return self.http_status(404, 404, 'Task not found') return self.http_status(404, 404, 'Task not found')
if request_context.principal.principal_type == PrincipalType.API_KEY:
return self.success(data=task.to_public_dict())
return self.success(data=task.to_dict()) return self.success(data=task.to_dict())
@self.route( @self.route(
+2 -2
View File
@@ -635,9 +635,9 @@ class Application:
frontend_path = paths.get_frontend_path() frontend_path = paths.get_frontend_path()
if not os.path.exists(frontend_path): if not os.path.exists(frontend_path):
self.logger.warning('WebUI 文件缺失,请根据文档部署:https://docs.langbot.app/zh') self.logger.warning('WebUI 文件缺失,请根据文档部署:https://langbot.app/docs/zh')
self.logger.warning( self.logger.warning(
'WebUI files are missing, please deploy according to the documentation: https://docs.langbot.app/en' 'WebUI files are missing, please deploy according to the documentation: https://langbot.app/docs/en'
) )
return return
+49 -12
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import json
import typing import typing
import datetime import datetime
import time import time
@@ -197,6 +198,41 @@ class TaskWrapper:
}, },
} }
def to_public_dict(self) -> dict:
"""Return the stable task projection exposed to API-key callers."""
if self.task.cancelled():
status = 'cancelled'
error = {'type': 'task_cancelled', 'message': 'Task was cancelled'}
result = None
elif not self.task.done():
status = 'running'
error = None
result = None
else:
exception = self.assume_exception()
if exception is not None:
status = 'failed'
error = {'type': 'task_failed', 'message': 'Task execution failed'}
result = None
else:
status = 'succeeded'
error = None
result = self.assume_result()
try:
json.dumps(result)
except (TypeError, ValueError):
result = None
return {
'id': self.id,
'task_type': self.task_type,
'kind': self.kind,
'status': status,
'error': error,
'result': result,
'created_at': self.created_at,
}
def cancel(self): def cancel(self):
self.task.cancel() self.task.cancel()
@@ -325,19 +361,20 @@ class AsyncTaskManager:
instance_uuid: str | None = None, instance_uuid: str | None = None,
workspace_uuid: str | None = None, workspace_uuid: str | None = None,
placement_generation: int | None = None, placement_generation: int | None = None,
public: bool = False,
) -> dict: ) -> dict:
return { tasks = [
'tasks': [ t.to_public_dict() if public else t.to_dict()
t.to_dict() for t in self.tasks
for t in self.tasks if (type is None or t.task_type == type)
if (type is None or t.task_type == type) and (kind is None or t.kind == kind)
and (kind is None or t.kind == kind) and (instance_uuid is None or t.instance_uuid == instance_uuid)
and (instance_uuid is None or t.instance_uuid == instance_uuid) and (workspace_uuid is None or t.workspace_uuid == workspace_uuid)
and (workspace_uuid is None or t.workspace_uuid == workspace_uuid) and (placement_generation is None or t.placement_generation == placement_generation)
and (placement_generation is None or t.placement_generation == placement_generation) ]
], if public:
'id_index': TaskWrapper._id_index, return {'tasks': tasks}
} return {'tasks': tasks, 'id_index': TaskWrapper._id_index}
def get_stats(self) -> dict: def get_stats(self) -> dict:
completed = sum(1 for t in self.tasks if t.task.done()) completed = sum(1 for t in self.tasks if t.task.done())
@@ -15,9 +15,9 @@ spec:
categories: categories:
- protocol - protocol
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/aiocqhttp zh: https://langbot.app/docs/zh/usage/platforms/qq/aiocqhttp/napcat
en: https://link.langbot.app/en/platforms/aiocqhttp en: https://langbot.app/docs/en/usage/platforms/qq/aiocqhttp/napcat
ja: https://link.langbot.app/ja/platforms/aiocqhttp ja: https://langbot.app/docs/ja/usage/platforms/qq/aiocqhttp/napcat
config: config:
- name: host - name: host
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/dingtalk zh: https://langbot.app/docs/zh/usage/platforms/dingtalk
en: https://link.langbot.app/en/platforms/dingtalk en: https://langbot.app/docs/en/usage/platforms/dingtalk
ja: https://link.langbot.app/ja/platforms/dingtalk ja: https://langbot.app/docs/ja/usage/platforms/dingtalk
config: config:
- name: one-click-create - name: one-click-create
label: label:
@@ -24,9 +24,9 @@ spec:
- popular - popular
- global - global
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/discord zh: https://langbot.app/docs/zh/usage/platforms/discord
en: https://link.langbot.app/en/platforms/discord en: https://langbot.app/docs/en/usage/platforms/discord
ja: https://link.langbot.app/ja/platforms/discord ja: https://langbot.app/docs/ja/usage/platforms/discord
config: config:
- name: client_id - name: client_id
label: label:
@@ -18,9 +18,9 @@ spec:
- popular - popular
- global - global
help_links: help_links:
zh: https://docs.langbot.app/zh/platforms/http-bot zh: https://langbot.app/docs/zh/platforms/http-bot
en: https://docs.langbot.app/en/platforms/http-bot en: https://langbot.app/docs/en/platforms/http-bot
ja: https://docs.langbot.app/ja/platforms/http-bot ja: https://langbot.app/docs/ja/platforms/http-bot
config: config:
- name: webhook_url - name: webhook_url
label: label:
+3 -3
View File
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/kook zh: https://langbot.app/docs/zh/usage/platforms/kook
en: https://link.langbot.app/en/platforms/kook en: https://langbot.app/docs/en/usage/platforms/kook
ja: https://link.langbot.app/ja/platforms/kook ja: https://langbot.app/docs/ja/usage/platforms/kook
config: config:
- name: token - name: token
label: label:
+3 -3
View File
@@ -19,9 +19,9 @@ spec:
- china - china
- global - global
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/lark zh: https://langbot.app/docs/zh/usage/platforms/lark
en: https://link.langbot.app/en/platforms/lark en: https://langbot.app/docs/en/usage/platforms/lark
ja: https://link.langbot.app/ja/platforms/lark ja: https://langbot.app/docs/ja/usage/platforms/lark
config: config:
- name: domain - name: domain
label: label:
+3 -3
View File
@@ -22,9 +22,9 @@ spec:
categories: categories:
- global - global
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/line zh: https://langbot.app/docs/zh/usage/platforms/line
en: https://link.langbot.app/en/platforms/line en: https://langbot.app/docs/en/usage/platforms/line
ja: https://link.langbot.app/ja/platforms/line ja: https://langbot.app/docs/ja/usage/platforms/line
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/officialaccount zh: https://langbot.app/docs/zh/usage/platforms/wxoa
en: https://link.langbot.app/en/platforms/officialaccount en: https://langbot.app/docs/en/usage/platforms/wxoa
ja: https://link.langbot.app/ja/platforms/officialaccount ja: https://langbot.app/docs/ja/usage/platforms/wxoa
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -16,9 +16,9 @@ spec:
- popular - popular
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/openclaw_weixin zh: https://langbot.app/docs/zh/usage/platforms/wechat/weixin
en: https://link.langbot.app/en/platforms/openclaw_weixin en: https://langbot.app/docs/en/usage/platforms/readme
ja: https://link.langbot.app/ja/platforms/openclaw_weixin ja: https://langbot.app/docs/ja/usage/platforms/readme
config: config:
- name: base_url - name: base_url
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/qqofficial zh: https://langbot.app/docs/zh/usage/platforms/qq/official_webhook
en: https://link.langbot.app/en/platforms/qqofficial en: https://langbot.app/docs/en/usage/platforms/qq/official_webhook
ja: https://link.langbot.app/ja/platforms/qqofficial ja: https://langbot.app/docs/ja/usage/platforms/qq/official_webhook
config: config:
- name: __system.outbound_ips - name: __system.outbound_ips
label: label:
+3 -3
View File
@@ -21,9 +21,9 @@ spec:
categories: categories:
- protocol - protocol
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/satori zh: https://langbot.app/docs/zh/usage/platforms/readme
en: https://link.langbot.app/en/platforms/satori en: https://langbot.app/docs/en/usage/platforms/readme
ja: https://link.langbot.app/ja/platforms/satori ja: https://langbot.app/docs/ja/usage/platforms/readme
config: config:
- name: platform - name: platform
label: label:
+3 -3
View File
@@ -24,9 +24,9 @@ spec:
- popular - popular
- global - global
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/slack zh: https://langbot.app/docs/zh/usage/platforms/slack
en: https://link.langbot.app/en/platforms/slack en: https://langbot.app/docs/en/usage/platforms/slack
ja: https://link.langbot.app/ja/platforms/slack ja: https://langbot.app/docs/ja/usage/platforms/slack
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -24,9 +24,9 @@ spec:
- popular - popular
- global - global
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/telegram zh: https://langbot.app/docs/zh/usage/platforms/telegram
en: https://link.langbot.app/en/platforms/telegram en: https://langbot.app/docs/en/usage/platforms/telegram
ja: https://link.langbot.app/ja/platforms/telegram ja: https://langbot.app/docs/ja/usage/platforms/telegram
config: config:
- name: token - name: token
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/wechatpad zh: https://langbot.app/docs/zh/usage/platforms/wechat/wechatpad
en: https://link.langbot.app/en/platforms/wechatpad en: https://langbot.app/docs/en/usage/platforms/readme
ja: https://link.langbot.app/ja/platforms/wechatpad ja: https://langbot.app/docs/ja/usage/platforms/readme
config: config:
- name: wechatpad_url - name: wechatpad_url
label: label:
+3 -3
View File
@@ -16,9 +16,9 @@ spec:
- popular - popular
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/wecom zh: https://langbot.app/docs/zh/usage/platforms/wecom/wecom
en: https://link.langbot.app/en/platforms/wecom en: https://langbot.app/docs/en/usage/platforms/wecom/wecom
ja: https://link.langbot.app/ja/platforms/wecom ja: https://langbot.app/docs/ja/usage/platforms/wecom/wecom
config: config:
- name: webhook_url - name: webhook_url
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/wecombot zh: https://langbot.app/docs/zh/usage/platforms/wecom/wecombot
en: https://link.langbot.app/en/platforms/wecombot en: https://langbot.app/docs/en/usage/platforms/wecom/wecombot
ja: https://link.langbot.app/ja/platforms/wecombot ja: https://langbot.app/docs/ja/usage/platforms/wecom/wecombot
config: config:
- name: one-click-create - name: one-click-create
label: label:
@@ -15,9 +15,9 @@ spec:
categories: categories:
- china - china
help_links: help_links:
zh: https://link.langbot.app/zh/platforms/wecomcs zh: https://langbot.app/docs/zh/usage/platforms/wecom/wecomcs
en: https://link.langbot.app/en/platforms/wecomcs en: https://langbot.app/docs/en/usage/platforms/wecom/wecomcs
ja: https://link.langbot.app/ja/platforms/wecomcs ja: https://langbot.app/docs/ja/usage/platforms/wecom/wecomcs
config: config:
- name: webhook_url - name: webhook_url
label: label:
+13 -1
View File
@@ -39,6 +39,9 @@ class N8nServiceAPIRunner(runner.RequestRunner):
# 获取输出键名,默认为response # 获取输出键名,默认为response
self.output_key = self.pipeline_config['ai']['n8n-service-api'].get('output-key', 'response') self.output_key = self.pipeline_config['ai']['n8n-service-api'].get('output-key', 'response')
self.response_handling = self.pipeline_config['ai']['n8n-service-api'].get('response-handling', 'reply')
if self.response_handling not in {'reply', 'ignore'}:
raise ValueError(f'Invalid n8n response-handling: {self.response_handling}')
# 获取认证类型,默认为none # 获取认证类型,默认为none
self.auth_type = self.pipeline_config['ai']['n8n-service-api'].get('auth-type', 'none') self.auth_type = self.pipeline_config['ai']['n8n-service-api'].get('auth-type', 'none')
@@ -262,7 +265,11 @@ class N8nServiceAPIRunner(runner.RequestRunner):
async with session.post( async with session.post(
self.webhook_url, json=payload, headers=headers, auth=auth, timeout=self.timeout self.webhook_url, json=payload, headers=headers, auth=auth, timeout=self.timeout
) as response: ) as response:
if response.status != 200: if self.response_handling == 'ignore':
status_ok = 200 <= response.status < 300
else:
status_ok = response.status == 200
if not status_ok:
error_text = ( error_text = (
await httpclient.read_limited( await httpclient.read_limited(
response, response,
@@ -272,6 +279,11 @@ class N8nServiceAPIRunner(runner.RequestRunner):
self.ap.logger.error(f'n8n webhook call failed: {response.status}, {error_text}') self.ap.logger.error(f'n8n webhook call failed: {response.status}, {error_text}')
raise Exception(f'n8n webhook call failed: {response.status}, {error_text}') raise Exception(f'n8n webhook call failed: {response.status}, {error_text}')
if self.response_handling == 'ignore':
response.release()
self.ap.logger.debug('n8n async webhook accepted; response body ignored')
return
async for chunk in self._process_response(response): async for chunk in self._process_response(response):
if is_stream: if is_stream:
yield chunk yield chunk
+1 -1
View File
@@ -83,7 +83,7 @@ class VersionManager:
try: try:
if await self.is_new_version_available(): if await self.is_new_version_available():
return ( return (
'New version available. Update guide: https://link.langbot.app/en/docs/update', 'New version available. Update guide: https://langbot.app/docs/en/deploy/update',
logging.INFO, logging.INFO,
) )
except Exception as e: except Exception as e:
@@ -269,7 +269,7 @@ class InvitationDeliveryService:
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width:100%;max-width:600px;"> <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width:100%;max-width:600px;">
<tr> <tr>
<td style="padding:0 4px 20px;"> <td style="padding:0 4px 20px;">
<img src="https://docs.langbot.app/langbot-logo.png" alt="LangBot" width="34" height="34" style="display:inline-block;width:34px;height:34px;border:0;vertical-align:middle;"> <img src="https://langbot.app/docs/langbot-logo.png" alt="LangBot" width="34" height="34" style="display:inline-block;width:34px;height:34px;border:0;vertical-align:middle;">
<span style="display:inline-block;margin-left:10px;vertical-align:middle;font-size:18px;font-weight:700;letter-spacing:-.01em;">LangBot</span> <span style="display:inline-block;margin-left:10px;vertical-align:middle;font-size:18px;font-weight:700;letter-spacing:-.01em;">LangBot</span>
</td> </td>
</tr> </tr>
@@ -80,7 +80,8 @@
"header-name": "", "header-name": "",
"header-value": "", "header-value": "",
"timeout": 120, "timeout": 120,
"output-key": "response" "output-key": "response",
"response-handling": "reply"
}, },
"langflow-api": { "langflow-api": {
"base-url": "http://localhost:7860", "base-url": "http://localhost:7860",
@@ -475,6 +475,25 @@ stages:
type: string type: string
required: false required: false
default: 'response' default: 'response'
- name: response-handling
label:
en_US: Webhook Response Handling
zh_Hans: Webhook 响应处理方式
description:
en_US: Choose whether LangBot forwards the n8n webhook response to the chat user. Ignore mode requires the n8n Webhook node to use Respond Immediately.
zh_Hans: 选择是否将 n8n Webhook 响应转发给聊天用户。忽略模式要求 n8n Webhook 节点使用“立即响应”。
type: select
required: false
default: 'reply'
options:
- name: reply
label:
en_US: Forward as chat reply
zh_Hans: 转发为聊天回复
- name: ignore
label:
en_US: Ignore response body (asynchronous workflow)
zh_Hans: 忽略响应正文(异步工作流)
- name: coze-api - name: coze-api
label: label:
en_US: coze API en_US: coze API
+260
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import datetime
import json import json
import logging import logging
from types import SimpleNamespace from types import SimpleNamespace
@@ -22,6 +23,7 @@ from langbot.pkg.api.http.service.apikey import ApiKeyService
from langbot.pkg.api.http.service.user import ControlPlaneDirectoryRequiredError, UserService from langbot.pkg.api.http.service.user import ControlPlaneDirectoryRequiredError, UserService
from langbot.pkg.entity.persistence.base import Base from langbot.pkg.entity.persistence.base import Base
from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata
from langbot.pkg.entity.persistence import apikey
from langbot.pkg.entity.persistence.user import User from langbot.pkg.entity.persistence.user import User
from langbot.pkg.entity.persistence.workspace import ( from langbot.pkg.entity.persistence.workspace import (
Workspace, Workspace,
@@ -440,6 +442,264 @@ async def test_api_key_secret_is_one_time_and_viewer_cannot_manage_keys(workspac
assert (await forbidden.get_json())['code'] == 'permission_denied' assert (await forbidden.get_json())['code'] == 'permission_denied'
async def test_api_key_context_returns_bound_identity_without_workspace_permission(workspace_api):
application, client, _, owner_token = workspace_api
current_response = await client.get('/api/v1/workspaces/current', headers=_auth(owner_token))
workspace_uuid = (await current_response.get_json())['data']['workspace']['uuid']
create_response = await client.post(
'/api/v1/apikeys',
headers=_auth(owner_token, workspace_uuid),
json={'name': 'Context probe', 'scopes': []},
)
assert create_response.status_code == 200
created = (await create_response.get_json())['data']['key']
missing_auth = await client.get('/api/v1/system/context')
assert missing_auth.status_code == 401
invalid_auth = await client.get(
'/api/v1/system/context',
headers={'X-API-Key': 'lbk_invalid'},
)
assert invalid_auth.status_code == 401
invalid_capabilities = await client.get(
'/api/v1/system/capabilities',
headers={'X-API-Key': 'lbk_invalid'},
)
assert invalid_capabilities.status_code == 401
response = await client.get(
'/api/v1/system/context',
headers={
'X-API-Key': created['key'],
'X-Workspace-Id': 'caller-selected-workspace-must-be-ignored',
},
)
assert response.status_code == 200
assert (await response.get_json())['data'] == {
'instance_uuid': application.workspace_service.instance_uuid,
'workspace_uuid': workspace_uuid,
'api_key_id': created['uuid'],
'permissions': [],
}
capabilities_response = await client.get(
'/api/v1/system/capabilities',
headers={
'X-API-Key': created['key'],
'X-Workspace-Id': 'caller-selected-workspace-must-be-ignored',
},
)
assert capabilities_response.status_code == 200
capabilities = (await capabilities_response.get_json())['data']
assert capabilities['schema_version'] == 1
assert sorted(capabilities['operations']) == sorted(
[
'bot.list',
'bot.get',
'bot.create',
'bot.update',
'bot.delete',
'pipeline.list',
'pipeline.get',
'pipeline.create',
'pipeline.update',
'pipeline.delete',
'pipeline.copy',
'task.list',
'task.get',
'knowledge_base.list',
'knowledge_base.get',
'knowledge_base.create',
'knowledge_base.update',
'knowledge_base.delete',
'knowledge_base.file.list',
'knowledge_base.file.store',
'knowledge_base.file.delete',
'knowledge_base.retrieve',
'file.document.upload',
'plugin.install.github',
'plugin.install.marketplace',
'plugin.install.local',
'plugin.upgrade',
'plugin.get',
'plugin.list',
'plugin.config.get',
'plugin.config.update',
'plugin.logs',
'plugin.delete',
'provider.list',
'provider.get',
'provider.create',
'provider.update',
'provider.delete',
'provider.scan_models',
'model.llm.list',
'model.llm.get',
'model.llm.create',
'model.llm.update',
'model.llm.delete',
'model.llm.test',
'model.embedding.list',
'model.embedding.get',
'model.embedding.create',
'model.embedding.update',
'model.embedding.delete',
'model.embedding.test',
'model.rerank.list',
'model.rerank.get',
'model.rerank.create',
'model.rerank.update',
'model.rerank.delete',
'model.rerank.test',
'skill.list',
'skill.get',
'skill.create',
'skill.update',
'skill.delete',
'skill.files.list',
'skill.files.read',
'skill.files.write',
'skill.preview',
'skill.install.github',
'skill.install.upload',
'mcp_server.list',
'mcp_server.get',
'mcp_server.create',
'mcp_server.update',
'mcp_server.delete',
'mcp_server.resources',
'mcp_server.resource_templates',
'mcp_server.resource_read',
'mcp_server.logs',
'mcp_server.test',
]
)
assert all(item == {'supported': True} for item in capabilities['operations'].values())
assert created['key'] not in await capabilities_response.get_data(as_text=True)
bearer_response = await client.get(
'/api/v1/system/context',
headers={'Authorization': f'Bearer {created["key"]}'},
)
assert bearer_response.status_code == 200
assert (await bearer_response.get_json())['data']['api_key_id'] == created['uuid']
jwt_response = await client.get(
'/api/v1/system/context',
headers={'Authorization': f'Bearer {owner_token}'},
)
assert jwt_response.status_code == 401
await application.persistence_mgr.execute_async(
sqlalchemy.update(apikey.ApiKey)
.where(apikey.ApiKey.uuid == created['uuid'])
.values(expires_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - datetime.timedelta(seconds=1))
)
expired_capabilities = await client.get(
'/api/v1/system/capabilities',
headers={'X-API-Key': created['key']},
)
assert expired_capabilities.status_code == 401
revoke_response = await client.delete(
f'/api/v1/apikeys/{created["id"]}',
headers=_auth(owner_token, workspace_uuid),
)
assert revoke_response.status_code == 200
revoked_response = await client.get(
'/api/v1/system/context',
headers={'X-API-Key': created['key']},
)
assert revoked_response.status_code == 401
revoked_capabilities = await client.get(
'/api/v1/system/capabilities',
headers={'X-API-Key': created['key']},
)
assert revoked_capabilities.status_code == 401
async def test_api_key_can_query_tasks_with_public_contract_and_resource_permission(workspace_api):
application, client, _, owner_token = workspace_api
task_query = {}
task_lookup = {}
fake_task = SimpleNamespace(
to_public_dict=lambda: {'id': 7, 'status': 'running', 'error': None, 'result': None},
to_dict=lambda: {'id': 7, 'runtime': {'state': 'PENDING'}},
)
def get_tasks_dict(*args, **kwargs):
task_query.update(kwargs)
if kwargs.get('public'):
return {'tasks': []}
return {'tasks': [], 'id_index': 1}
def get_task_by_id(*args, **kwargs):
task_lookup.update(kwargs)
return fake_task if args and args[0] == 7 else None
application.task_mgr = SimpleNamespace(
get_tasks_dict=get_tasks_dict,
get_task_by_id=get_task_by_id,
)
current_response = await client.get('/api/v1/workspaces/current', headers=_auth(owner_token))
workspace_uuid = (await current_response.get_json())['data']['workspace']['uuid']
create_response = await client.post(
'/api/v1/apikeys',
headers=_auth(owner_token, workspace_uuid),
json={'name': 'Task reader', 'scopes': ['resource.view']},
)
assert create_response.status_code == 200
key = (await create_response.get_json())['data']['key']['key']
listing = await client.get('/api/v1/system/tasks', headers={'X-API-Key': key})
assert listing.status_code == 200
assert (await listing.get_json())['data'] == {'tasks': []}
assert task_query['instance_uuid'] == application.workspace_service.instance_uuid
assert task_query['workspace_uuid'] == workspace_uuid
assert task_query['placement_generation'] == 1
assert task_query['public'] is True
bearer_listing = await client.get('/api/v1/system/tasks', headers=_auth(owner_token, workspace_uuid))
assert bearer_listing.status_code == 200
assert (await bearer_listing.get_json())['data'] == {'tasks': [], 'id_index': 1}
public_task = await client.get('/api/v1/system/tasks/7', headers={'X-API-Key': key})
assert public_task.status_code == 200
assert (await public_task.get_json())['data'] == {
'id': 7,
'status': 'running',
'error': None,
'result': None,
}
assert task_lookup == {
'instance_uuid': application.workspace_service.instance_uuid,
'workspace_uuid': workspace_uuid,
'placement_generation': 1,
}
legacy_task = await client.get('/api/v1/system/tasks/7', headers=_auth(owner_token, workspace_uuid))
assert legacy_task.status_code == 200
assert (await legacy_task.get_json())['data'] == {'id': 7, 'runtime': {'state': 'PENDING'}}
missing = await client.get('/api/v1/system/tasks/not-an-id', headers={'X-API-Key': key})
assert missing.status_code == 404
no_permission_response = await client.post(
'/api/v1/apikeys',
headers=_auth(owner_token, workspace_uuid),
json={'name': 'Task denied', 'scopes': []},
)
assert no_permission_response.status_code == 200
no_permission_key = (await no_permission_response.get_json())['data']['key']['key']
denied = await client.get('/api/v1/system/tasks', headers={'X-API-Key': no_permission_key})
assert denied.status_code == 403
async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in_core( async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in_core(
workspace_api, workspace_api,
): ):
@@ -0,0 +1,182 @@
from __future__ import annotations
import copy
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
import quart
from langbot.pkg.api.http.controller.groups.provider.models import (
EmbeddingModelsRouterGroup,
LLMModelsRouterGroup,
RerankModelsRouterGroup,
)
from langbot.pkg.api.http.controller.groups.provider.providers import ModelProvidersRouterGroup
from langbot.pkg.api.http.controller.groups.provider.query import resolve_include_secret
from langbot.pkg.api.http.service.secrets import redact_secrets
pytestmark = pytest.mark.asyncio
RAW_PROVIDER = {
'uuid': 'provider-test',
'name': 'Test Provider',
'api_keys': ['provider-secret'],
}
RAW_MODEL = {
'uuid': 'model-test',
'name': 'Test Model',
'extra_args': {'headers': {'Authorization': 'Bearer model-secret'}},
}
def _access(role: str):
return SimpleNamespace(
execution=SimpleNamespace(instance_uuid='instance-test', placement_generation=1),
workspace=SimpleNamespace(uuid='workspace-test'),
membership=SimpleNamespace(uuid='membership-test', role=role, projection_revision=1),
)
def _project(value: dict, include_secret: bool) -> dict:
value = copy.deepcopy(value)
return value if include_secret else redact_secrets(value)
async def _create_client(role: str):
application = SimpleNamespace()
account = SimpleNamespace(uuid='account-test', user='test@example.com')
application.user_service = SimpleNamespace(get_authenticated_account=AsyncMock(return_value=account))
application.apikey_service = SimpleNamespace(authenticate_api_key=AsyncMock(return_value=None))
application.workspace_collaboration_service = SimpleNamespace(
resolve_account_workspace=AsyncMock(return_value=_access(role))
)
async def get_providers(_context, *, include_secret=False):
return [_project(RAW_PROVIDER, include_secret)]
async def get_provider(_context, _uuid, *, include_secret=False):
return _project(RAW_PROVIDER, include_secret)
application.provider_service = SimpleNamespace(
get_providers=AsyncMock(side_effect=get_providers),
get_provider=AsyncMock(side_effect=get_provider),
get_provider_model_counts=AsyncMock(
return_value={'llm_count': 1, 'embedding_count': 1, 'rerank_count': 1}
),
)
def model_service(list_name: str, get_name: str):
async def get_models(_context, *, include_secret=False):
return [_project(RAW_MODEL, include_secret)]
async def get_model(_context, _uuid, *, include_secret=False):
return _project(RAW_MODEL, include_secret)
return SimpleNamespace(
**{
list_name: AsyncMock(side_effect=get_models),
get_name: AsyncMock(side_effect=get_model),
}
)
application.llm_model_service = model_service('get_llm_models', 'get_llm_model')
application.embedding_models_service = model_service('get_embedding_models', 'get_embedding_model')
application.rerank_models_service = model_service('get_rerank_models', 'get_rerank_model')
quart_app = quart.Quart(__name__)
for router_type in (
ModelProvidersRouterGroup,
LLMModelsRouterGroup,
EmbeddingModelsRouterGroup,
RerankModelsRouterGroup,
):
await router_type(application, quart_app).initialize()
return application, quart_app.test_client()
def _headers() -> dict[str, str]:
return {'Authorization': 'Bearer test-token'}
@pytest.mark.parametrize(
('raw_value', 'permitted', 'expected', 'error'),
[
(None, True, True, None),
(None, False, False, None),
('false', True, False, None),
('true', True, True, None),
('true', False, False, None),
('invalid', True, False, 'include_secret must be either true or false'),
],
)
def test_resolve_include_secret(raw_value, permitted, expected, error):
assert resolve_include_secret(raw_value, permitted=permitted) == (expected, error)
@pytest.mark.parametrize(
'endpoint',
[
'/api/v1/provider/providers',
'/api/v1/provider/models/llm',
'/api/v1/provider/models/embedding',
'/api/v1/provider/models/rerank',
],
)
async def test_default_preserves_secrets_and_explicit_false_redacts_high_permission_reads(endpoint):
application, client = await _create_client('developer')
default_response = await client.get(endpoint, headers=_headers())
false_response = await client.get(f'{endpoint}?include_secret=false', headers=_headers())
assert default_response.status_code == 200
assert false_response.status_code == 200
default_data = await default_response.get_json()
false_data = await false_response.get_json()
default_value = default_data['data'].get('providers', default_data['data'].get('models'))[0]
false_value = false_data['data'].get('providers', false_data['data'].get('models'))[0]
assert '***' not in str(default_value)
assert '***' in str(false_value)
@pytest.mark.parametrize(
'endpoint',
[
'/api/v1/provider/providers',
'/api/v1/provider/models/llm',
'/api/v1/provider/models/embedding',
'/api/v1/provider/models/rerank',
],
)
async def test_explicit_true_does_not_grant_low_permission_reads(endpoint):
_application, client = await _create_client('viewer')
response = await client.get(f'{endpoint}?include_secret=true', headers=_headers())
assert response.status_code == 200
data = await response.get_json()
value = data['data'].get('providers', data['data'].get('models'))[0]
assert '***' in str(value)
@pytest.mark.parametrize(
'endpoint',
[
'/api/v1/provider/providers',
'/api/v1/provider/providers/provider-test',
'/api/v1/provider/models/llm',
'/api/v1/provider/models/llm/model-test',
'/api/v1/provider/models/embedding',
'/api/v1/provider/models/embedding/model-test',
'/api/v1/provider/models/rerank',
'/api/v1/provider/models/rerank/model-test',
],
)
async def test_invalid_include_secret_returns_bad_request(endpoint):
_application, client = await _create_client('developer')
response = await client.get(f'{endpoint}?include_secret=maybe', headers=_headers())
assert response.status_code == 400
assert (await response.get_json())['msg'] == 'include_secret must be either true or false'
+124
View File
@@ -338,6 +338,70 @@ class TestTaskWrapper:
assert result['runtime']['exception'] == 'Test error' assert result['runtime']['exception'] == 'Test error'
assert 'exception_traceback' in result['runtime'] assert 'exception_traceback' in result['runtime']
@pytest.mark.asyncio
async def test_public_dict_has_stable_success_projection(self):
_, TaskWrapper, _ = get_taskmgr_classes()
mock_app = create_mock_app()
async def successful_coro():
return {'file_id': 'file-a'}
wrapper = TaskWrapper(mock_app, successful_coro(), kind='knowledge_base.store')
await wrapper.task
result = wrapper.to_public_dict()
assert result == {
'id': wrapper.id,
'task_type': 'system',
'kind': 'knowledge_base.store',
'status': 'succeeded',
'error': None,
'result': {'file_id': 'file-a'},
'created_at': result['created_at'],
}
assert 'runtime' not in result
assert 'traceback' not in str(result).lower()
@pytest.mark.asyncio
async def test_public_dict_hides_exception_traceback(self):
_, TaskWrapper, _ = get_taskmgr_classes()
mock_app = create_mock_app()
async def failing_coro():
raise ValueError('private failure')
wrapper = TaskWrapper(mock_app, failing_coro())
try:
await wrapper.task
except ValueError:
# Expected failure: task must complete in failed state for public serialization checks.
pass
result = wrapper.to_public_dict()
assert result['status'] == 'failed'
assert result['error'] == {'type': 'task_failed', 'message': 'Task execution failed'}
assert 'runtime' not in result
assert 'traceback' not in str(result).lower()
@pytest.mark.asyncio
async def test_public_dict_does_not_change_success_when_result_is_not_json_serializable(self):
_, TaskWrapper, _ = get_taskmgr_classes()
mock_app = create_mock_app()
async def successful_coro():
return object()
wrapper = TaskWrapper(mock_app, successful_coro())
await wrapper.task
result = wrapper.to_public_dict()
assert result['status'] == 'succeeded'
assert result['error'] is None
assert result['result'] is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_cancel_task(self): async def test_cancel_task(self):
"""Test cancel method cancels the asyncio task.""" """Test cancel method cancels the asyncio task."""
@@ -487,6 +551,66 @@ class TestAsyncTaskManager:
w2.cancel() w2.cancel()
w3.cancel() w3.cancel()
@pytest.mark.asyncio
async def test_public_task_queries_keep_workspace_and_generation_isolation(self):
_, _, AsyncTaskManager = get_taskmgr_classes()
mock_app = create_mock_app()
manager = AsyncTaskManager(mock_app)
async def dummy_coro():
await asyncio.sleep(10)
current = manager.create_user_task(
dummy_coro(),
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=2,
)
other_workspace = manager.create_user_task(
dummy_coro(),
instance_uuid='instance-a',
workspace_uuid='workspace-b',
placement_generation=2,
)
stale_generation = manager.create_user_task(
dummy_coro(),
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=1,
)
result = manager.get_tasks_dict(
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=2,
public=True,
)
assert [task['id'] for task in result['tasks']] == [current.id]
assert 'id_index' not in result
assert (
manager.get_task_by_id(
other_workspace.id,
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=2,
)
is None
)
assert (
manager.get_task_by_id(
stale_generation.id,
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=2,
)
is None
)
current.cancel()
other_workspace.cancel()
stale_generation.cancel()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_cancel_by_scope(self): async def test_cancel_by_scope(self):
"""Test cancel_by_scope cancels matching tasks.""" """Test cancel_by_scope cancels matching tasks."""
+88 -1
View File
@@ -55,7 +55,7 @@ finally:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def make_runner(output_key: str = 'response') -> N8nServiceAPIRunner: def make_runner(output_key: str = 'response', response_handling: str = 'reply') -> N8nServiceAPIRunner:
ap = Mock() ap = Mock()
ap.logger = Mock() ap.logger = Mock()
pipeline_config = { pipeline_config = {
@@ -63,6 +63,7 @@ def make_runner(output_key: str = 'response') -> N8nServiceAPIRunner:
'n8n-service-api': { 'n8n-service-api': {
'webhook-url': 'http://test-n8n/webhook', 'webhook-url': 'http://test-n8n/webhook',
'output-key': output_key, 'output-key': output_key,
'response-handling': response_handling,
'auth-type': 'none', 'auth-type': 'none',
} }
} }
@@ -287,6 +288,7 @@ def make_http_session_mock(response_bytes: bytes, status: int = 200):
"""Mock httpclient.get_session() returning a session whose post() yields response_bytes.""" """Mock httpclient.get_session() returning a session whose post() yields response_bytes."""
mock_response = make_mock_response([response_bytes], status=status) mock_response = make_mock_response([response_bytes], status=status)
mock_response.status = status mock_response.status = status
mock_response.headers = {}
mock_cm = AsyncMock() mock_cm = AsyncMock()
mock_cm.__aenter__ = AsyncMock(return_value=mock_response) mock_cm.__aenter__ = AsyncMock(return_value=mock_response)
@@ -314,6 +316,91 @@ async def test_call_webhook_nonstream_adapter_plain_json():
assert results[0].content == 'result text' assert results[0].content == 'result text'
@pytest.mark.asyncio
@pytest.mark.parametrize('status', [200, 201, 202, 204])
@pytest.mark.parametrize(
'response_body',
[
b'{"message":"Workflow was started"}',
b'{"response":"must not be forwarded"}',
b'plain acknowledgement',
],
)
async def test_call_webhook_ignore_response_body(response_body: bytes, status: int):
"""Ignore mode accepts any HTTP 2xx response without emitting chat output."""
runner = make_runner(response_handling='ignore')
query = make_query(is_stream=False)
http_session = make_http_session_mock(response_body, status=status)
with patch('langbot.pkg.provider.runners.n8nsvapi.httpclient.get_session', return_value=http_session):
results = []
async for message in runner._call_webhook(query):
results.append(message)
assert results == []
@pytest.mark.asyncio
async def test_call_webhook_ignore_releases_without_reading_response_body():
"""Ignore mode returns after the success status without waiting for the body."""
runner = make_runner(response_handling='ignore')
query = make_query(is_stream=False)
mock_response = make_mock_response([], status=202)
mock_response.headers = {}
mock_response.release = Mock()
async def fail_if_read(_size):
raise AssertionError('ignore mode must not read the response body')
yield b''
mock_response.content.iter_chunked = fail_if_read
mock_cm = AsyncMock()
mock_cm.__aenter__ = AsyncMock(return_value=mock_response)
mock_cm.__aexit__ = AsyncMock(return_value=False)
mock_session = Mock()
mock_session.post = Mock(return_value=mock_cm)
with patch('langbot.pkg.provider.runners.n8nsvapi.httpclient.get_session', return_value=mock_session):
results = [message async for message in runner._call_webhook(query)]
assert results == []
mock_response.release.assert_called_once_with()
@pytest.mark.asyncio
@pytest.mark.parametrize('status', [201, 202, 204])
async def test_call_webhook_reply_mode_preserves_http_200_contract(status: int):
"""Reply mode remains backward compatible and rejects non-200 statuses."""
runner = make_runner(response_handling='reply')
query = make_query(is_stream=False)
http_session = make_http_session_mock(b'', status=status)
with patch('langbot.pkg.provider.runners.n8nsvapi.httpclient.get_session', return_value=http_session):
with pytest.raises(N8nAPIError, match=f'n8n webhook call failed: {status}'):
async for _ in runner._call_webhook(query):
pass
@pytest.mark.asyncio
async def test_call_webhook_ignore_mode_preserves_http_error():
"""Ignore mode must not swallow a failed n8n webhook response."""
runner = make_runner(response_handling='ignore')
query = make_query(is_stream=False)
http_session = make_http_session_mock(b'{"error":"unavailable"}', status=500)
with patch('langbot.pkg.provider.runners.n8nsvapi.httpclient.get_session', return_value=http_session):
with pytest.raises(N8nAPIError, match='n8n webhook call exception'):
async for _ in runner._call_webhook(query):
pass
@pytest.mark.asyncio
async def test_invalid_response_handling_is_rejected():
"""Configuration errors should fail fast instead of silently changing reply behavior."""
with pytest.raises(ValueError, match='Invalid n8n response-handling'):
make_runner(response_handling='unexpected')
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_call_webhook_stream_adapter_stream_format(): async def test_call_webhook_stream_adapter_stream_format():
"""Stream adapter + stream format → MessageChunks, last is_final.""" """Stream adapter + stream format → MessageChunks, last is_final."""
@@ -113,7 +113,7 @@ async def test_invitation_email_uses_quiet_brand_lockup_and_compact_fallback_lin
html = service._html("RockChinQ's Workspace", link) html = service._html("RockChinQ's Workspace", link)
assert 'https://docs.langbot.app/langbot-logo.png' in html assert 'https://langbot.app/docs/langbot-logo.png' in html
assert '>LangBot<' in html assert '>LangBot<' in html
assert 'Workspace invitation' in html assert 'Workspace invitation' in html
assert 'Open invitation link' in html assert 'Open invitation link' in html
+1 -1
View File
@@ -1,6 +1,6 @@
# 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://langbot.app/docs/en/develop/dev-config) for more information.
## Tests ## Tests
@@ -0,0 +1,22 @@
const COMMON_N8N_CONFIG_FIELDS = new Set([
'webhook-url',
'auth-type',
'timeout',
'output-key',
'response-handling',
]);
export function shouldShowN8nConfigField(
fieldName: string,
authType: string,
): boolean {
if (COMMON_N8N_CONFIG_FIELDS.has(fieldName)) {
return true;
}
return (
(authType === 'basic' && fieldName.startsWith('basic-')) ||
(authType === 'jwt' && fieldName.startsWith('jwt-')) ||
(authType === 'header' && fieldName.startsWith('header-'))
);
}
@@ -13,6 +13,7 @@ import {
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic'; import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent'; import DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent';
import { normalizeDynamicFormValuesForSave } from '@/app/home/components/dynamic-form/DynamicFormSaveValues'; import { normalizeDynamicFormValuesForSave } from '@/app/home/components/dynamic-form/DynamicFormSaveValues';
import { shouldShowN8nConfigField } from '@/app/home/components/dynamic-form/N8nAuthFieldVisibility';
import { extractI18nObject } from '@/i18n/I18nProvider'; import { extractI18nObject } from '@/i18n/I18nProvider';
/** /**
@@ -181,29 +182,9 @@ export default function N8nAuthFormComponent({
}, [form, itemConfigList]); }, [form, itemConfigList]);
// 根据认证类型过滤表单项 // 根据认证类型过滤表单项
const filteredConfigList = itemConfigList.filter((config) => { const filteredConfigList = itemConfigList.filter((config) =>
// 始终显示webhook-url、auth-type、timeout和output-key shouldShowN8nConfigField(config.name, authType),
if ( );
['webhook-url', 'auth-type', 'timeout', 'output-key'].includes(
config.name,
)
) {
return true;
}
// 根据认证类型显示相应的表单项
if (authType === 'basic' && config.name.startsWith('basic-')) {
return true;
}
if (authType === 'jwt' && config.name.startsWith('jwt-')) {
return true;
}
if (authType === 'header' && config.name.startsWith('header-')) {
return true;
}
return false;
});
return ( return (
<Form {...form}> <Form {...form}>
@@ -2195,12 +2195,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://langbot.app/docs/zh/insight/guide',
'_blank', '_blank',
); );
} else { } else {
window.open( window.open(
'https://link.langbot.app/en/docs/guide', 'https://langbot.app/docs/en/insight/guide',
'_blank', '_blank',
); );
} }
@@ -49,9 +49,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://langbot.app/docs/en/usage/platforms/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/platforms', zh_Hans: 'https://langbot.app/docs/zh/usage/platforms/readme',
ja_JP: 'https://link.langbot.app/ja/docs/platforms', ja_JP: 'https://langbot.app/docs/ja/usage/platforms/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -62,9 +62,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://langbot.app/docs/en/usage/pipelines/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/pipelines', zh_Hans: 'https://langbot.app/docs/zh/usage/pipelines/readme',
ja_JP: 'https://link.langbot.app/ja/docs/pipelines', ja_JP: 'https://langbot.app/docs/ja/usage/pipelines/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -75,9 +75,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://langbot.app/docs/en/usage/knowledge/readme',
zh_Hans: 'https://link.langbot.app/zh/docs/knowledge', zh_Hans: 'https://langbot.app/docs/zh/usage/knowledge/readme',
ja_JP: 'https://link.langbot.app/ja/docs/knowledge', ja_JP: 'https://langbot.app/docs/ja/usage/knowledge/readme',
}, },
section: 'home', section: 'home',
}), }),
@@ -89,9 +89,9 @@ export const sidebarConfigList = [
route: '/home/extensions', route: '/home/extensions',
description: t('plugins.description'), description: t('plugins.description'),
helpLink: { helpLink: {
en_US: 'https://docs.langbot.app/en/plugin/plugin-intro', en_US: 'https://langbot.app/docs/en/plugin/plugin-intro',
zh_Hans: 'https://docs.langbot.app/zh/plugin/plugin-intro', zh_Hans: 'https://langbot.app/docs/zh/plugin/plugin-intro',
ja_JP: 'https://docs.langbot.app/ja/plugin/plugin-intro', ja_JP: 'https://langbot.app/docs/ja/plugin/plugin-intro',
}, },
section: 'extensions', section: 'extensions',
}), }),
@@ -102,9 +102,9 @@ export const sidebarConfigList = [
route: '/home/add-extension', route: '/home/add-extension',
description: t('plugins.description'), description: t('plugins.description'),
helpLink: { helpLink: {
en_US: 'https://docs.langbot.app/en/plugin/plugin-intro', en_US: 'https://langbot.app/docs/en/plugin/plugin-intro',
zh_Hans: 'https://docs.langbot.app/zh/plugin/plugin-intro', zh_Hans: 'https://langbot.app/docs/zh/plugin/plugin-intro',
ja_JP: 'https://docs.langbot.app/ja/plugin/plugin-intro', ja_JP: 'https://langbot.app/docs/ja/plugin/plugin-intro',
}, },
section: 'extensions', section: 'extensions',
}), }),
@@ -34,11 +34,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://langbot.app/docs/zh/deploy/update';
} else if (language === 'ja-JP') { } else if (language === 'ja-JP') {
return 'https://link.langbot.app/ja/docs/update'; return 'https://langbot.app/docs/ja/deploy/update';
} else { } else {
return 'https://link.langbot.app/en/docs/update'; return 'https://langbot.app/docs/en/deploy/update';
} }
}; };
+2 -1
View File
@@ -76,7 +76,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://langbot.app/docs/en/insight/data-collection-policy',
loading: 'Loading...', loading: 'Loading...',
fieldRequired: 'This field is required', fieldRequired: 'This field is required',
or: 'or', or: 'or',
+2 -1
View File
@@ -79,7 +79,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://langbot.app/docs/en/insight/data-collection-policy',
loading: 'Cargando...', loading: 'Cargando...',
fieldRequired: 'Este campo es obligatorio', fieldRequired: 'Este campo es obligatorio',
or: 'o', or: 'o',
+2 -1
View File
@@ -77,7 +77,8 @@ const jaJP = {
privacyPolicy: 'プライバシーポリシー', privacyPolicy: 'プライバシーポリシー',
and: 'および', and: 'および',
dataCollectionPolicy: 'データ収集ポリシー', dataCollectionPolicy: 'データ収集ポリシー',
dataCollectionPolicyUrl: 'https://link.langbot.app/ja/docs/data-policy', dataCollectionPolicyUrl:
'https://langbot.app/docs/ja/insight/data-collection-policy',
loading: '読み込み中...', loading: '読み込み中...',
fieldRequired: 'この項目は必須です', fieldRequired: 'この項目は必須です',
or: 'または', or: 'または',
+2 -1
View File
@@ -76,7 +76,8 @@ const ruRU = {
privacyPolicy: 'Политикой конфиденциальности', privacyPolicy: 'Политикой конфиденциальности',
and: 'и', and: 'и',
dataCollectionPolicy: 'Политикой сбора данных', dataCollectionPolicy: 'Политикой сбора данных',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://langbot.app/docs/en/insight/data-collection-policy',
loading: 'Загрузка...', loading: 'Загрузка...',
fieldRequired: 'Это поле обязательно для заполнения', fieldRequired: 'Это поле обязательно для заполнения',
or: 'или', or: 'или',
+2 -1
View File
@@ -76,7 +76,8 @@ const thTH = {
privacyPolicy: 'นโยบายความเป็นส่วนตัว', privacyPolicy: 'นโยบายความเป็นส่วนตัว',
and: 'และ', and: 'และ',
dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล', dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล',
dataCollectionPolicyUrl: 'https://link.langbot.app/en/docs/data-policy', dataCollectionPolicyUrl:
'https://langbot.app/docs/en/insight/data-collection-policy',
loading: 'กำลังโหลด...', loading: 'กำลังโหลด...',
fieldRequired: 'ช่องนี้จำเป็นต้องกรอก', fieldRequired: 'ช่องนี้จำเป็นต้องกรอก',
or: 'หรือ', or: 'หรือ',
+2 -1
View File
@@ -77,7 +77,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://langbot.app/docs/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',
+2 -1
View File
@@ -75,7 +75,8 @@ const zhHans = {
privacyPolicy: '隐私政策', privacyPolicy: '隐私政策',
and: '和', and: '和',
dataCollectionPolicy: '数据收集政策', dataCollectionPolicy: '数据收集政策',
dataCollectionPolicyUrl: 'https://link.langbot.app/zh/docs/data-policy', dataCollectionPolicyUrl:
'https://langbot.app/docs/zh/insight/data-collection-policy',
loading: '加载中...', loading: '加载中...',
fieldRequired: '此字段为必填项', fieldRequired: '此字段为必填项',
or: '或', or: '或',
+2 -1
View File
@@ -75,7 +75,8 @@ const zhHant = {
privacyPolicy: '隱私政策', privacyPolicy: '隱私政策',
and: '和', and: '和',
dataCollectionPolicy: '數據收集政策', dataCollectionPolicy: '數據收集政策',
dataCollectionPolicyUrl: 'https://link.langbot.app/zh/docs/data-policy', dataCollectionPolicyUrl:
'https://langbot.app/docs/zh/insight/data-collection-policy',
loading: '載入中...', loading: '載入中...',
fieldRequired: '此欄位為必填', fieldRequired: '此欄位為必填',
or: '或', or: '或',
@@ -0,0 +1,52 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import ts from 'typescript';
import { fileURLToPath } from 'node:url';
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
const sourcePath = path.resolve(
currentDirectory,
'../../src/app/home/components/dynamic-form/N8nAuthFieldVisibility.ts',
);
function loadVisibilityPolicy() {
const source = fs.readFileSync(sourcePath, 'utf8');
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS },
}).outputText;
const loadedModule = { exports: {} };
new Function('require', 'module', 'exports', compiled)(
() => {
throw new Error('N8nAuthFieldVisibility must not have runtime imports');
},
loadedModule,
loadedModule.exports,
);
return loadedModule.exports;
}
test('shows response handling with the other common n8n fields', () => {
const { shouldShowN8nConfigField } = loadVisibilityPolicy();
for (const field of [
'webhook-url',
'auth-type',
'timeout',
'output-key',
'response-handling',
]) {
assert.equal(shouldShowN8nConfigField(field, 'none'), true, field);
}
});
test('shows only fields for the selected n8n authentication method', () => {
const { shouldShowN8nConfigField } = loadVisibilityPolicy();
assert.equal(shouldShowN8nConfigField('basic-username', 'basic'), true);
assert.equal(shouldShowN8nConfigField('basic-password', 'jwt'), false);
assert.equal(shouldShowN8nConfigField('jwt-secret', 'jwt'), true);
assert.equal(shouldShowN8nConfigField('header-name', 'header'), true);
assert.equal(shouldShowN8nConfigField('unrelated-field', 'none'), false);
});