Compare commits
321 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea54d84135 | |||
| 0dfa55b2ec | |||
| 987752ea8a | |||
| 7ac9077680 | |||
| f46fe0828d | |||
| ebf0e015af | |||
| b4156b2321 | |||
| dc2bae3b72 | |||
| 448ebfed4d | |||
| fd41ff3328 | |||
| eed9a8c987 | |||
| 855ec8a294 | |||
| 643413ff47 | |||
| 7eff0843df | |||
| 44f9863f4c | |||
| fa87c28902 | |||
| 2c17743acb | |||
| 63aba035ab | |||
| 8b384edc08 | |||
| 0ce02d7cc2 | |||
| 700b02777c | |||
| c0ba087c29 | |||
| 39652787be | |||
| d5d48af83e | |||
| eca8ce4027 | |||
| 65547bb683 | |||
| 07c4936770 | |||
| c3ae4138e3 | |||
| 920d277108 | |||
| 0da00bb3ab | |||
| 0b012e06fb | |||
| 131b6487a9 | |||
| ee0f763748 | |||
| b08d9236cb | |||
| 3489daa515 | |||
| 24e92a69b7 | |||
| a306818ed8 | |||
| 56c0092705 | |||
| e2389b6895 | |||
| 9fe6493348 | |||
| 87a76cda0d | |||
| 7e0b44263a | |||
| 15149341e1 | |||
| ce5332ada1 | |||
| ecca8bd982 | |||
| 388f529940 | |||
| 7ee9c23434 | |||
| 85cea19f7e | |||
| b991c5360e | |||
| c79541bacc | |||
| 377002cb3c | |||
| 2ac167a912 | |||
| dc767d7fee | |||
| e1d1d8e20f | |||
| 4114d12be6 | |||
| 45dc85e4e8 | |||
| 226a56b7b7 | |||
| 23f141d7b8 | |||
| 676f35b9e0 | |||
| 098d1c5fab | |||
| 36e539b31f | |||
| 76eedda9dd | |||
| 4eb2510471 | |||
| 9886517312 | |||
| 25a34de6a7 | |||
| 846a0f5f74 | |||
| a3fe6e6408 | |||
| 91ef9bacfa | |||
| f9479344b4 | |||
| d46d5ed298 | |||
| 3658d6fd24 | |||
| 31f9635ad2 | |||
| 56e4223892 | |||
| b060cbc2d0 | |||
| ef4a5c253a | |||
| 57e98a56ca | |||
| 6156c48db7 | |||
| e875c4699e | |||
| 2b60ee21a6 | |||
| 815594eabb | |||
| 283bd18f1f | |||
| 0ba6109d2b | |||
| 8153bf871b | |||
| cc963d0371 | |||
| 0169deb880 | |||
| 0b39093e5a | |||
| e9b66bdb3a | |||
| c0742491f4 | |||
| 8a46d41d60 | |||
| 8f5b2eb141 | |||
| 77bea167b1 | |||
| c57074d725 | |||
| cb6861ef04 | |||
| 5f367e1242 | |||
| 27917bcca5 | |||
| 3f8b1dde09 | |||
| 7189d8a81c | |||
| 7767bbbedc | |||
| a6127a9d82 | |||
| ca841b9fc8 | |||
| a506e60458 | |||
| 8a1c21f216 | |||
| 06a1cc2adf | |||
| b5616f0581 | |||
| b39eaf2686 | |||
| 78176978cd | |||
| a7bac06436 | |||
| bbc161db30 | |||
| 33f838a59b | |||
| e8a7edea76 | |||
| e3766e94d0 | |||
| 76ff1f6da4 | |||
| 2f1f3a846e | |||
| 0ffa190d2b | |||
| 89253d1e5c | |||
| 21f997fc0e | |||
| 883982754f | |||
| 80b72bb2ee | |||
| 87ea15bba5 | |||
| 7c1b1cefed | |||
| 5b64c3be3c | |||
| 7e391d8a57 | |||
| 1aa1484c25 | |||
| 8cb9e0d542 | |||
| ce9f766cde | |||
| c250721b69 | |||
| 8c9b8b5f3c | |||
| cc77ca3ff7 | |||
| 098654742b | |||
| ffc21dc86e | |||
| 262fcf2945 | |||
| 7cf5922a15 | |||
| 5bb2514e4f | |||
| 886cb69436 | |||
| 4596d2ce32 | |||
| 171fcd7a48 | |||
| 5be34ff5ae | |||
| 4c6630a6d8 | |||
| a6486d41f6 | |||
| abac12f89f | |||
| ea90b4cedb | |||
| d8d3e98e74 | |||
| 5136e7a7ee | |||
| 54ba981bd4 | |||
| d774d39066 | |||
| 468a507d74 | |||
| cafbba3e25 | |||
| 9da0e6bad8 | |||
| 651a899c87 | |||
| a31531fd91 | |||
| 75b084cee3 | |||
| 02354c9eda | |||
| 8ecbbe7899 | |||
| 9884510fa3 | |||
| dff27b0ac0 | |||
| 6046d755ad | |||
| 17bf2990b0 | |||
| a3c2cf5223 | |||
| 768b401ceb | |||
| d987b81f0c | |||
| d914b7cd00 | |||
| c37027c07f | |||
| a884dad265 | |||
| 53975efcab | |||
| fbb92a667f | |||
| 5cf2e3c6fd | |||
| dcb29ab16c | |||
| 5604704d0a | |||
| 08949c5757 | |||
| 23710714c1 | |||
| 41d1b45549 | |||
| 7b9f36aba5 | |||
| 8c44806b31 | |||
| 5dd6cf9bff | |||
| bb4668038b | |||
| ed0b98cc5b | |||
| f57cb2f6f3 | |||
| 297216961a | |||
| fdc239c2c7 | |||
| 39809f92bb | |||
| 650514421f | |||
| c980dd1c41 | |||
| 06b2b76963 | |||
| cd9f7848c1 | |||
| c5dbde3912 | |||
| 786331d3f4 | |||
| 378dceefa4 | |||
| 809a60d90c | |||
| 24f47f83f9 | |||
| c3d386691a | |||
| 139323ffc1 | |||
| c9019f23bf | |||
| 8cf8c418bc | |||
| 51ba55dfb4 | |||
| 0db1f753a0 | |||
| 624ab716e6 | |||
| e148fd9c97 | |||
| b652a8fb0a | |||
| 62dd97b688 | |||
| 6481f0bb7a | |||
| 0e39773557 | |||
| e58affe613 | |||
| 0797fac217 | |||
| adbd822851 | |||
| a31f6167bb | |||
| 6ce07b21be | |||
| 908b4be918 | |||
| 50c7570440 | |||
| a021d0ee0e | |||
| 72925ecd0c | |||
| 0633286211 | |||
| e93d27b14d | |||
| e7c0372191 | |||
| 45eae70926 | |||
| 7cec77ae2c | |||
| f7ef381bbb | |||
| 19edff11d7 | |||
| 7cbea6da8a | |||
| 65259e566c | |||
| 09592304f8 | |||
| 19ab3fef16 | |||
| 4884f26ec5 | |||
| b07803d6a8 | |||
| 3925d15fa2 | |||
| 010280afdd | |||
| f2e79fe809 | |||
| cde421edc7 | |||
| f1ac9bf38c | |||
| 8ae66242b5 | |||
| b9c489a115 | |||
| 62f83a6230 | |||
| abcc820239 | |||
| 17f56df3cf | |||
| 66c6e506dc | |||
| 9ad33bdc6e | |||
| 86bac0654c | |||
| a4281e53ef | |||
| a9ed1a634d | |||
| 16dcf8edcd | |||
| 8aeb49fd2d | |||
| 946cd085ac | |||
| 6ba03bf8c3 | |||
| 9e532232d7 | |||
| e76ade32e3 | |||
| a7b89493e4 | |||
| bcc9c54356 | |||
| e53a8db7c9 | |||
| e2ad515379 | |||
| f542b49955 | |||
| 77259e2245 | |||
| d0385e25f1 | |||
| bd9c6e37fc | |||
| 0df9aff7db | |||
| 8a000ac0d8 | |||
| 77f82a37a0 | |||
| 4ceb2ec3ab | |||
| 554fa612b5 | |||
| f9f994f9ff | |||
| 3fa2cc1ec2 | |||
| 6e83440d68 | |||
| 9f1ae50f71 | |||
| e1c4a934dc | |||
| d8d1cc6ddd | |||
| 05c70c6388 | |||
| 0758d68d74 | |||
| 26d3c3eabd | |||
| ef2e2dd1c0 | |||
| d4b7668823 | |||
| 0adcf550d9 | |||
| 5a5484b145 | |||
| 558eac8c21 | |||
| 1c67cf3e05 | |||
| 8b6fac26d4 | |||
| e1d318f56d | |||
| 632d687b63 | |||
| 7e721ba86d | |||
| a8ffdefcdd | |||
| 58f4e0ed8d | |||
| 3b48bf4551 | |||
| 48d0e01434 | |||
| 7d82bb16b9 | |||
| 9923a38d84 | |||
| 97588e6cf4 | |||
| acfe5ea8b0 | |||
| ef6651e305 | |||
| 059e78ba2c | |||
| 765198e00f | |||
| 2cda3e77bc | |||
| 6d9278a018 | |||
| e118e0fa7f | |||
| 7687b21ff0 | |||
| 387ac1e778 | |||
| 6e444c6e5e | |||
| 3d29f8324a | |||
| 9d04179123 | |||
| d598f68313 | |||
| 1cec875a8d | |||
| 336957ec63 | |||
| e678340e57 | |||
| 8905367222 | |||
| 76f3dddf85 | |||
| 5eac228e01 | |||
| 76d546305b | |||
| da928c78dc | |||
| 52c2fdb6db | |||
| 2dd5a72ccd | |||
| f3ab6b8bd7 | |||
| 5ecf59e7e7 | |||
| d0ea7aa45c | |||
| f8ab18d8da | |||
| 965d2829eb | |||
| 33e9767c16 | |||
| 70e54c98bf | |||
| 49f1b97b67 | |||
| 0ed5a6b5ec | |||
| bec44875f7 | |||
| f849d0e102 | |||
| 15102da85d | |||
| 7eee6d1cb2 | |||
| b0e8c4fbb8 | |||
| f1468b7d5a |
@@ -12,7 +12,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: lobehub/lobe-chat-database
|
||||
REGISTRY_URL: jaworldwide.azurecr.io
|
||||
REGISTRY_IMAGE: jaworldwide.azurecr.io/oneja/ai/bot-database
|
||||
PR_TAG_PREFIX: pr-
|
||||
|
||||
jobs:
|
||||
@@ -70,8 +71,9 @@ jobs:
|
||||
- name: Docker login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
registry: ${{ env.REGISTRY_URL }}
|
||||
username: ${{ secrets.CONTAINER_REGISTRY_USER }}
|
||||
password: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Get commit SHA
|
||||
if: github.ref == 'refs/heads/main'
|
||||
@@ -147,8 +149,9 @@ jobs:
|
||||
- name: Docker login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
registry: ${{ env.REGISTRY_URL }}
|
||||
username: ${{ secrets.CONTAINER_REGISTRY_USER }}
|
||||
password: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Lighthouse Badger
|
||||
|
||||
env:
|
||||
TOKEN_NAME: 'GH_TOKEN'
|
||||
REPO_BRANCH: 'lobehub/lobe-chat lighthouse'
|
||||
REPO_BRANCH: 'jaworldwideorg/OneJA-Bot lighthouse'
|
||||
USER_NAME: 'lobehubbot'
|
||||
USER_EMAIL: 'i@lobehub.com'
|
||||
AUDIT_TYPE: 'both'
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
name: Release CI
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
actions: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
||||
@@ -52,3 +52,12 @@ jobs:
|
||||
[lobechat]: https://github.com/lobehub/lobe-chat
|
||||
[tutorial-zh-CN]: https://github.com/lobehub/lobe-chat/wiki/Upstream-Sync.zh-CN
|
||||
[tutorial-en-US]: https://github.com/lobehub/lobe-chat/wiki/Upstream-Sync
|
||||
|
||||
- name: Trigger Release Workflow
|
||||
if: success() && steps.sync.outputs.has_new_commits == 'true'
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/workflows/release.yml/dispatches \
|
||||
-d '{"ref":"main"}'
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
npm run type-check
|
||||
npx --no-install lint-staged
|
||||
# .husky/post-merge
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npm run type-check || echo "Type check failed, please fix issues!"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
const config = require('@lobehub/lint').semanticRelease;
|
||||
|
||||
// Remove NPM publishing by excluding "@semantic-release/npm" plugin
|
||||
// Keep or add other plugins like GitHub Releases
|
||||
config.plugins = config.plugins.filter((plugin) => plugin !== '@semantic-release/npm');
|
||||
|
||||
// Add GitHub only if required
|
||||
config.plugins.push([
|
||||
'@semantic-release/exec',
|
||||
{
|
||||
@@ -7,4 +12,32 @@ config.plugins.push([
|
||||
},
|
||||
]);
|
||||
|
||||
// Override GitHub repository URL without modifying package.json
|
||||
// Make sure @semantic-release/github is present in the plugins
|
||||
if (!config.plugins.some(plugin => Array.isArray(plugin) ? plugin[0] === '@semantic-release/github' : plugin === '@semantic-release/github')) {
|
||||
config.plugins.push([
|
||||
'@semantic-release/github',
|
||||
{
|
||||
repositoryUrl: 'https://github.com/jaworldwideorg/OneJA-Bot.git'
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
// Find and update the existing GitHub plugin configuration
|
||||
config.plugins = config.plugins.map(plugin => {
|
||||
if (Array.isArray(plugin) && plugin[0] === '@semantic-release/github') {
|
||||
return [
|
||||
'@semantic-release/github',
|
||||
{
|
||||
...(plugin[1] || {}),
|
||||
repositoryUrl: 'https://github.com/jaworldwideorg/OneJA-Bot.git'
|
||||
}
|
||||
];
|
||||
}
|
||||
return plugin;
|
||||
});
|
||||
}
|
||||
|
||||
// Set repository URL in global config
|
||||
config.repositoryUrl = 'https://github.com/jaworldwideorg/OneJA-Bot.git';
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -237,7 +237,7 @@ In this way, LobeChat can more flexibly adapt to the needs of different users, w
|
||||
|
||||
We have implemented support for the following model service providers:
|
||||
|
||||
<!-- PROVIDER LIST -->
|
||||
<!-- PROVIDER LIST -->
|
||||
|
||||
- **[OpenAI](https://lobechat.com/discover/provider/openai)**: OpenAI is a global leader in artificial intelligence research, with models like the GPT series pushing the frontiers of natural language processing. OpenAI is committed to transforming multiple industries through innovative and efficient AI solutions. Their products demonstrate significant performance and cost-effectiveness, widely used in research, business, and innovative applications.
|
||||
- **[Ollama](https://lobechat.com/discover/provider/ollama)**: Ollama provides models that cover a wide range of fields, including code generation, mathematical operations, multilingual processing, and conversational interaction, catering to diverse enterprise-level and localized deployment needs.
|
||||
@@ -279,7 +279,7 @@ We have implemented support for the following model service providers:
|
||||
- **[InternLM](https://lobechat.com/discover/provider/internlm)**: An open-source organization dedicated to the research and development of large model toolchains. It provides an efficient and user-friendly open-source platform for all AI developers, making cutting-edge large models and algorithm technologies easily accessible.
|
||||
- **[Higress](https://lobechat.com/discover/provider/higress)**: Higress is a cloud-native API gateway that was developed internally at Alibaba to address the issues of Tengine reload affecting long-lived connections and the insufficient load balancing capabilities for gRPC/Dubbo.
|
||||
- **[Gitee AI](https://lobechat.com/discover/provider/giteeai)**: Gitee AI's Serverless API provides AI developers with an out of the box large model inference API service.
|
||||
- **[Taichu](https://lobechat.com/discover/provider/taichu)**: The Institute of Automation, Chinese Academy of Sciences, and Wuhan Artificial Intelligence Research Institute have launched a new generation of multimodal large models, supporting comprehensive question-answering tasks such as multi-turn Q\&A, text creation, image generation, 3D understanding, and signal analysis, with stronger cognitive, understanding, and creative abilities, providing a new interactive experience.
|
||||
- **[Taichu](https://lobechat.com/discover/provider/taichu)**: The Institute of Automation, Chinese Academy of Sciences, and Wuhan Artificial Intelligence Research Institute have launched a new generation of multimodal large models, supporting comprehensive question-answering tasks such as multi-turn Q&A, text creation, image generation, 3D understanding, and signal analysis, with stronger cognitive, understanding, and creative abilities, providing a new interactive experience.
|
||||
- **[360 AI](https://lobechat.com/discover/provider/ai360)**: 360 AI is an AI model and service platform launched by 360 Company, offering various advanced natural language processing models, including 360GPT2 Pro, 360GPT Pro, 360GPT Turbo, and 360GPT Turbo Responsibility 8K. These models combine large-scale parameters and multimodal capabilities, widely applied in text generation, semantic understanding, dialogue systems, and code generation. With flexible pricing strategies, 360 AI meets diverse user needs, supports developer integration, and promotes the innovation and development of intelligent applications.
|
||||
- **[Search1API](https://lobechat.com/discover/provider/search1api)**: Search1API provides access to the DeepSeek series of models that can connect to the internet as needed, including standard and fast versions, supporting a variety of model sizes.
|
||||
- **[InfiniAI](https://lobechat.com/discover/provider/infiniai)**: Provides high-performance, easy-to-use, and secure large model services for application developers, covering the entire process from large model development to service deployment.
|
||||
@@ -380,7 +380,7 @@ In addition, these plugins are not limited to news aggregation, but can also ext
|
||||
>
|
||||
> Learn more about [📘 Plugin Usage][docs-usage-plugin] by checking it out.
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
| Recent Submits | Description |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -419,7 +419,7 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
||||
> We welcome all users to join this growing ecosystem and participate in the iteration and optimization of agents.
|
||||
> Together, we can create more interesting, practical, and innovative agents, further enriching the diversity and practicality of the agent offerings.
|
||||
|
||||
<!-- AGENT LIST -->
|
||||
<!-- AGENT LIST -->
|
||||
|
||||
| Recent Submits | Description |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|
||||
@@ -237,40 +237,40 @@ LobeChat 支持文件上传与知识库功能,你可以上传文件、图片
|
||||
|
||||
我们已经实现了对以下模型服务商的支持:
|
||||
|
||||
<!-- PROVIDER LIST -->
|
||||
<!-- PROVIDER LIST -->
|
||||
|
||||
- **[OpenAI](https://lobechat.com/discover/provider/openai)**: OpenAI 是全球领先的人工智能研究机构,其开发的模型如 GPT 系列推动了自然语言处理的前沿。OpenAI 致力于通过创新和高效的 AI 解决方案改变多个行业。他们的产品具有显著的性能和经济性,广泛用于研究、商业和创新应用。
|
||||
- **[OpenAI](https://lobechat.com/discover/provider/openai)**: OpenAI 是全球领先的人工智能研究机构,其开发的模型如GPT系列推动了自然语言处理的前沿。OpenAI 致力于通过创新和高效的AI解决方案改变多个行业。他们的产品具有显著的性能和经济性,广泛用于研究、商业和创新应用。
|
||||
- **[Ollama](https://lobechat.com/discover/provider/ollama)**: Ollama 提供的模型广泛涵盖代码生成、数学运算、多语种处理和对话互动等领域,支持企业级和本地化部署的多样化需求。
|
||||
- **[Anthropic](https://lobechat.com/discover/provider/anthropic)**: Anthropic 是一家专注于人工智能研究和开发的公司,提供了一系列先进的语言模型,如 Claude 3.5 Sonnet、Claude 3 Sonnet、Claude 3 Opus 和 Claude 3 Haiku。这些模型在智能、速度和成本之间取得了理想的平衡,适用于从企业级工作负载到快速响应的各种应用场景。Claude 3.5 Sonnet 作为其最新模型,在多项评估中表现优异,同时保持了较高的性价比。
|
||||
- **[Bedrock](https://lobechat.com/discover/provider/bedrock)**: Bedrock 是亚马逊 AWS 提供的一项服务,专注于为企业提供先进的 AI 语言模型和视觉模型。其模型家族包括 Anthropic 的 Claude 系列、Meta 的 Llama 3.1 系列等,涵盖从轻量级到高性能的多种选择,支持文本生成、对话、图像处理等多种任务,适用于不同规模和需求的企业应用。
|
||||
- **[Google](https://lobechat.com/discover/provider/google)**: Google 的 Gemini 系列是其最先进、通用的 AI 模型,由 Google DeepMind 打造,专为多模态设计,支持文本、代码、图像、音频和视频的无缝理解与处理。适用于从数据中心到移动设备的多种环境,极大提升了 AI 模型的效率与应用广泛性。
|
||||
- **[Google](https://lobechat.com/discover/provider/google)**: Google 的 Gemini 系列是其最先进、通用的 AI模型,由 Google DeepMind 打造,专为多模态设计,支持文本、代码、图像、音频和视频的无缝理解与处理。适用于从数据中心到移动设备的多种环境,极大提升了AI模型的效率与应用广泛性。
|
||||
- **[DeepSeek](https://lobechat.com/discover/provider/deepseek)**: DeepSeek 是一家专注于人工智能技术研究和应用的公司,其最新模型 DeepSeek-V3 多项评测成绩超越 Qwen2.5-72B 和 Llama-3.1-405B 等开源模型,性能对齐领军闭源模型 GPT-4o 与 Claude-3.5-Sonnet。
|
||||
- **[Moonshot](https://lobechat.com/discover/provider/moonshot)**: Moonshot 是由北京月之暗面科技有限公司推出的开源平台,提供多种自然语言处理模型,应用领域广泛,包括但不限于内容创作、学术研究、智能推荐、医疗诊断等,支持长文本处理和复杂生成任务。
|
||||
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter 是一个提供多种前沿大模型接口的服务平台,支持 OpenAI、Anthropic、LLaMA 及更多,适合多样化的开发和应用需求。用户可根据自身需求灵活选择最优的模型和价格,助力 AI 体验的提升。
|
||||
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter 是一个提供多种前沿大模型接口的服务平台,支持 OpenAI、Anthropic、LLaMA 及更多,适合多样化的开发和应用需求。用户可根据自身需求灵活选择最优的模型和价格,助力AI体验的提升。
|
||||
- **[HuggingFace](https://lobechat.com/discover/provider/huggingface)**: HuggingFace Inference API 提供了一种快速且免费的方式,让您可以探索成千上万种模型,适用于各种任务。无论您是在为新应用程序进行原型设计,还是在尝试机器学习的功能,这个 API 都能让您即时访问多个领域的高性能模型。
|
||||
- **[Cloudflare Workers AI](https://lobechat.com/discover/provider/cloudflare)**: 在 Cloudflare 的全球网络上运行由无服务器 GPU 驱动的机器学习模型。
|
||||
|
||||
<details><summary><kbd>See more providers (+32)</kbd></summary>
|
||||
|
||||
- **[GitHub](https://lobechat.com/discover/provider/github)**: 通过 GitHub 模型,开发人员可以成为 AI 工程师,并使用行业领先的 AI 模型进行构建。
|
||||
- **[GitHub](https://lobechat.com/discover/provider/github)**: 通过GitHub模型,开发人员可以成为AI工程师,并使用行业领先的AI模型进行构建。
|
||||
- **[Novita](https://lobechat.com/discover/provider/novita)**: Novita AI 是一个提供多种大语言模型与 AI 图像生成的 API 服务的平台,灵活、可靠且具有成本效益。它支持 Llama3、Mistral 等最新的开源模型,并为生成式 AI 应用开发提供了全面、用户友好且自动扩展的 API 解决方案,适合 AI 初创公司的快速发展。
|
||||
- **[PPIO](https://lobechat.com/discover/provider/ppio)**: PPIO 派欧云提供稳定、高性价比的开源模型 API 服务,支持 DeepSeek 全系列、Llama、Qwen 等行业领先大模型。
|
||||
- **[302.AI](https://lobechat.com/discover/provider/ai302)**: 302.AI 是一个按需付费的 AI 应用平台,提供市面上最全的 AI API 和 AI 在线应用
|
||||
- **[Together AI](https://lobechat.com/discover/provider/togetherai)**: Together AI 致力于通过创新的 AI 模型实现领先的性能,提供广泛的自定义能力,包括快速扩展支持和直观的部署流程,满足企业的各种需求。
|
||||
- **[Fireworks AI](https://lobechat.com/discover/provider/fireworksai)**: Fireworks AI 是一家领先的高级语言模型服务商,专注于功能调用和多模态处理。其最新模型 Firefunction V2 基于 Llama-3,优化用于函数调用、对话及指令跟随。视觉语言模型 FireLLaVA-13B 支持图像和文本混合输入。其他 notable 模型包括 Llama 系列和 Mixtral 系列,提供高效的多语言指令跟随与生成支持。
|
||||
- **[Groq](https://lobechat.com/discover/provider/groq)**: Groq 的 LPU 推理引擎在最新的独立大语言模型(LLM)基准测试中表现卓越,以其惊人的速度和效率重新定义了 AI 解决方案的标准。Groq 是一种即时推理速度的代表,在基于云的部署中展现了良好的性能。
|
||||
- **[Perplexity](https://lobechat.com/discover/provider/perplexity)**: Perplexity 是一家领先的对话生成模型提供商,提供多种先进的 Llama 3.1 模型,支持在线和离线应用,特别适用于复杂的自然语言处理任务。
|
||||
- **[Perplexity](https://lobechat.com/discover/provider/perplexity)**: Perplexity 是一家领先的对话生成模型提供商,提供多种先进的Llama 3.1模型,支持在线和离线应用,特别适用于复杂的自然语言处理任务。
|
||||
- **[Mistral](https://lobechat.com/discover/provider/mistral)**: Mistral 提供先进的通用、专业和研究型模型,广泛应用于复杂推理、多语言任务、代码生成等领域,通过功能调用接口,用户可以集成自定义功能,实现特定应用。
|
||||
- **[ModelScope](https://lobechat.com/discover/provider/modelscope)**: ModelScope 是阿里云推出的模型即服务平台,提供丰富的 AI 模型和推理服务。
|
||||
- **[ModelScope](https://lobechat.com/discover/provider/modelscope)**: ModelScope是阿里云推出的模型即服务平台,提供丰富的AI模型和推理服务。
|
||||
- **[Ai21Labs](https://lobechat.com/discover/provider/ai21)**: AI21 Labs 为企业构建基础模型和人工智能系统,加速生成性人工智能在生产中的应用。
|
||||
- **[Upstage](https://lobechat.com/discover/provider/upstage)**: Upstage 专注于为各种商业需求开发 AI 模型,包括 Solar LLM 和文档 AI,旨在实现工作的人造通用智能(AGI)。通过 Chat API 创建简单的对话代理,并支持功能调用、翻译、嵌入以及特定领域应用。
|
||||
- **[Upstage](https://lobechat.com/discover/provider/upstage)**: Upstage 专注于为各种商业需求开发AI模型,包括 Solar LLM 和文档 AI,旨在实现工作的人造通用智能(AGI)。通过 Chat API 创建简单的对话代理,并支持功能调用、翻译、嵌入以及特定领域应用。
|
||||
- **[xAI (Grok)](https://lobechat.com/discover/provider/xai)**: xAI 是一家致力于构建人工智能以加速人类科学发现的公司。我们的使命是推动我们对宇宙的共同理解。
|
||||
- **[Aliyun Bailian](https://lobechat.com/discover/provider/qwen)**: 通义千问是阿里云自主研发的超大规模语言模型,具有强大的自然语言理解和生成能力。它可以回答各种问题、创作文字内容、表达观点看法、撰写代码等,在多个领域发挥作用。
|
||||
- **[Wenxin](https://lobechat.com/discover/provider/wenxin)**: 企业级一站式大模型与 AI 原生应用开发及服务平台,提供最全面易用的生成式人工智能模型开发、应用开发全流程工具链
|
||||
- **[Wenxin](https://lobechat.com/discover/provider/wenxin)**: 企业级一站式大模型与AI原生应用开发及服务平台,提供最全面易用的生成式人工智能模型开发、应用开发全流程工具链
|
||||
- **[Hunyuan](https://lobechat.com/discover/provider/hunyuan)**: 由腾讯研发的大语言模型,具备强大的中文创作能力,复杂语境下的逻辑推理能力,以及可靠的任务执行能力
|
||||
- **[ZhiPu](https://lobechat.com/discover/provider/zhipu)**: 智谱 AI 提供多模态与语言模型的开放平台,支持广泛的 AI 应用场景,包括文本处理、图像理解与编程辅助等。
|
||||
- **[ZhiPu](https://lobechat.com/discover/provider/zhipu)**: 智谱 AI 提供多模态与语言模型的开放平台,支持广泛的AI应用场景,包括文本处理、图像理解与编程辅助等。
|
||||
- **[SiliconCloud](https://lobechat.com/discover/provider/siliconcloud)**: SiliconCloud,基于优秀开源基础模型的高性价比 GenAI 云服务
|
||||
- **[01.AI](https://lobechat.com/discover/provider/zeroone)**: 零一万物致力于推动以人为本的 AI 2.0 技术革命,旨在通过大语言模型创造巨大的经济和社会价值,并开创新的 AI 生态与商业模式。
|
||||
- **[01.AI](https://lobechat.com/discover/provider/zeroone)**: 零一万物致力于推动以人为本的AI 2.0技术革命,旨在通过大语言模型创造巨大的经济和社会价值,并开创新的AI生态与商业模式。
|
||||
- **[Spark](https://lobechat.com/discover/provider/spark)**: 科大讯飞星火大模型提供多领域、多语言的强大 AI 能力,利用先进的自然语言处理技术,构建适用于智能硬件、智慧医疗、智慧金融等多种垂直场景的创新应用。
|
||||
- **[SenseNova](https://lobechat.com/discover/provider/sensenova)**: 商汤日日新,依托商汤大装置的强大的基础支撑,提供高效易用的全栈大模型服务。
|
||||
- **[Stepfun](https://lobechat.com/discover/provider/stepfun)**: 阶级星辰大模型具备行业领先的多模态及复杂推理能力,支持超长文本理解和强大的自主调度搜索引擎功能。
|
||||
@@ -279,7 +279,7 @@ LobeChat 支持文件上传与知识库功能,你可以上传文件、图片
|
||||
- **[InternLM](https://lobechat.com/discover/provider/internlm)**: 致力于大模型研究与开发工具链的开源组织。为所有 AI 开发者提供高效、易用的开源平台,让最前沿的大模型与算法技术触手可及
|
||||
- **[Higress](https://lobechat.com/discover/provider/higress)**: Higress 是一款云原生 API 网关,在阿里内部为解决 Tengine reload 对长连接业务有损,以及 gRPC/Dubbo 负载均衡能力不足而诞生。
|
||||
- **[Gitee AI](https://lobechat.com/discover/provider/giteeai)**: Gitee AI 的 Serverless API 为 AI 开发者提供开箱即用的大模型推理 API 服务。
|
||||
- **[Taichu](https://lobechat.com/discover/provider/taichu)**: 中科院自动化研究所和武汉人工智能研究院推出新一代多模态大模型,支持多轮问答、文本创作、图像生成、3D 理解、信号分析等全面问答任务,拥有更强的认知、理解、创作能力,带来全新互动体验。
|
||||
- **[Taichu](https://lobechat.com/discover/provider/taichu)**: 中科院自动化研究所和武汉人工智能研究院推出新一代多模态大模型,支持多轮问答、文本创作、图像生成、3D理解、信号分析等全面问答任务,拥有更强的认知、理解、创作能力,带来全新互动体验。
|
||||
- **[360 AI](https://lobechat.com/discover/provider/ai360)**: 360 AI 是 360 公司推出的 AI 模型和服务平台,提供多种先进的自然语言处理模型,包括 360GPT2 Pro、360GPT Pro、360GPT Turbo 和 360GPT Turbo Responsibility 8K。这些模型结合了大规模参数和多模态能力,广泛应用于文本生成、语义理解、对话系统与代码生成等领域。通过灵活的定价策略,360 AI 满足多样化用户需求,支持开发者集成,推动智能化应用的革新和发展。
|
||||
- **[Search1API](https://lobechat.com/discover/provider/search1api)**: Search1API 提供可根据需要自行联网的 DeepSeek 系列模型的访问,包括标准版和快速版本,支持多种参数规模的模型选择。
|
||||
- **[InfiniAI](https://lobechat.com/discover/provider/infiniai)**: 为应用开发者提供高性能、易上手、安全可靠的大模型服务,覆盖从大模型开发到大模型服务化部署的全流程。
|
||||
@@ -373,14 +373,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
||||
|
||||
> 通过文档了解更多 [📘 插件使用][docs-usage-plugin]
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
| 最近新增 | 描述 |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-07-21**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
|
||||
| [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自 Google 结果的全面答案。<br/>`网页` `搜索` |
|
||||
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
|
||||
| [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
|
||||
| 最近新增 | 描述 |
|
||||
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-07-21**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
|
||||
| [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自Google结果的全面答案。<br/>`网页` `搜索` |
|
||||
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过BingApi搜索互联网上的信息<br/>`bingsearch` |
|
||||
| [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎API搜索谷歌。<br/>`网络` `搜索` |
|
||||
|
||||
> 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
|
||||
|
||||
@@ -408,14 +408,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
||||
>
|
||||
> 我欢迎所有用户加入这个不断成长的生态系统,共同参与到助手的迭代与优化中来。共同创造出更多有趣、实用且具有创新性的助手,进一步丰富助手的多样性和实用性。
|
||||
|
||||
<!-- AGENT LIST -->
|
||||
<!-- AGENT LIST -->
|
||||
|
||||
| 最近新增 | 描述 |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| 最近新增 | 描述 |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| [海龟汤主持人](https://lobechat.com/discover/assistant/lateral-thinking-puzzle)<br/><sup>By **[CSY2022](https://github.com/CSY2022)** on **2025-06-19**</sup> | 一个海龟汤主持人,需要自己提供汤面,汤底与关键点(猜中的判定条件)。<br/>`海龟汤` `推理` `互动` `谜题` `角色扮演` |
|
||||
| [美食评论员🍟](https://lobechat.com/discover/assistant/food-reviewer)<br/><sup>By **[renhai-lab](https://github.com/renhai-lab)** on **2025-06-17**</sup> | 美食评价专家<br/>`美食` `评价` `写作` |
|
||||
| [学术写作助手](https://lobechat.com/discover/assistant/academic-writing-assistant)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2025-06-17**</sup> | 专业的学术研究论文写作和正式文档编写专家<br/>`学术写作` `研究` `正式风格` |
|
||||
| [Minecraft 资深开发者](https://lobechat.com/discover/assistant/java-development)<br/><sup>By **[iamyuuk](https://github.com/iamyuuk)** on **2025-06-17**</sup> | 擅长高级 Java 开发及 Minecraft 开发<br/>`开发` `编程` `minecraft` `java` |
|
||||
| [美食评论员🍟](https://lobechat.com/discover/assistant/food-reviewer)<br/><sup>By **[renhai-lab](https://github.com/renhai-lab)** on **2025-06-17**</sup> | 美食评价专家<br/>`美食` `评价` `写作` |
|
||||
| [学术写作助手](https://lobechat.com/discover/assistant/academic-writing-assistant)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2025-06-17**</sup> | 专业的学术研究论文写作和正式文档编写专家<br/>`学术写作` `研究` `正式风格` |
|
||||
| [Minecraft资深开发者](https://lobechat.com/discover/assistant/java-development)<br/><sup>By **[iamyuuk](https://github.com/iamyuuk)** on **2025-06-17**</sup> | 擅长高级 Java 开发及 Minecraft 开发<br/>`开发` `编程` `minecraft` `java` |
|
||||
|
||||
> 📊 Total agents: [<kbd>**505**</kbd> ](https://lobechat.com/discover/assistants)
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Proxy, if you need it
|
||||
# HTTP_PROXY=http://localhost:7890
|
||||
# HTTPS_PROXY=http://localhost:7890
|
||||
|
||||
|
||||
# Other environment variables, as needed. You can refer to the environment variables configuration for the client version, making sure not to have ACCESS_CODE.
|
||||
# OPENAI_API_KEY=sk-xxxx
|
||||
# OPENAI_PROXY_URL=https://api.openai.com/v1
|
||||
# OPENAI_MODEL_LIST=...
|
||||
|
||||
|
||||
# ===========================
|
||||
# ====== Preset config ======
|
||||
# ===========================
|
||||
# if no special requirements, no need to change
|
||||
LOBE_PORT=3010
|
||||
MINIO_PORT=9000
|
||||
APP_URL=http://localhost:3010
|
||||
AUTH_URL=http://localhost:3010/api/auth
|
||||
|
||||
# Postgres related, which are the necessary environment variables for DB
|
||||
LOBE_DB_NAME=lobechat
|
||||
POSTGRES_PASSWORD=uWNZugjBqixf8dxC
|
||||
|
||||
# MinIO S3 configuration
|
||||
MINIO_ROOT_USER=admin
|
||||
MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD
|
||||
LLM_VISION_IMAGE_USE_BASE64=1
|
||||
|
||||
# Configure the bucket information of MinIO
|
||||
S3_PUBLIC_DOMAIN=http://localhost:9000
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
S3_ENABLE_PATH_STYLE=1
|
||||
S3_SET_ACL=0
|
||||
MINIO_LOBE_BUCKET=lobe
|
||||
|
||||
#Configure for DB Mode
|
||||
NEXT_PUBLIC_SERVICE_MODE=server
|
||||
DATABASE_DRIVER=node
|
||||
NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
|
||||
@@ -0,0 +1,77 @@
|
||||
name: lobe-chat-development
|
||||
services:
|
||||
postgresql:
|
||||
image: pgvector/pgvector:pg17
|
||||
container_name: lobe-postgres
|
||||
ports:
|
||||
- '5432:5432'
|
||||
volumes:
|
||||
- './data:/var/lib/postgresql/data'
|
||||
environment:
|
||||
- 'POSTGRES_DB=${LOBE_DB_NAME:-lobechat}'
|
||||
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}'
|
||||
env_file:
|
||||
- ../../.env
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: always
|
||||
networks:
|
||||
- lobe-network
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
container_name: lobe-minio
|
||||
ports:
|
||||
- '${MINIO_PORT:-9000}:${MINIO_PORT:-9000}'
|
||||
- '9001:9001'
|
||||
networks:
|
||||
- lobe-network
|
||||
volumes:
|
||||
- './s3_data:/etc/minio/data'
|
||||
environment:
|
||||
- 'MINIO_API_CORS_ALLOW_ORIGIN=*'
|
||||
env_file:
|
||||
- ../../.env
|
||||
restart: always
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
minio server /etc/minio/data --address ':${MINIO_PORT:-9000}' --console-address ':9001' &
|
||||
MINIO_PID=$$!
|
||||
while ! curl -s http://localhost:${MINIO_PORT:-9000}/minio/health/live; do
|
||||
echo 'Waiting for MinIO to start...'
|
||||
sleep 1
|
||||
done
|
||||
sleep 5
|
||||
mc alias set myminio http://localhost:${MINIO_PORT:-9000} ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}
|
||||
echo 'Creating bucket ${MINIO_LOBE_BUCKET:-lobe}'
|
||||
mc mb myminio/${MINIO_LOBE_BUCKET:-lobe}
|
||||
wait $$MINIO_PID
|
||||
"
|
||||
|
||||
searxng:
|
||||
image: searxng/searxng
|
||||
container_name: lobe-searxng
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- './searxng-settings.yml:/etc/searxng/settings.yml'
|
||||
environment:
|
||||
- 'SEARXNG_SETTINGS_FILE=/etc/searxng/settings.yml'
|
||||
restart: always
|
||||
networks:
|
||||
- lobe-network
|
||||
env_file:
|
||||
- ../../.env
|
||||
|
||||
volumes:
|
||||
data:
|
||||
driver: local
|
||||
s3_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
lobe-network:
|
||||
driver: bridge
|
||||
@@ -220,6 +220,56 @@ show_message() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev_dirs_exist)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
echo "检测到开发数据目录存在:"
|
||||
;;
|
||||
*)
|
||||
echo "Detected existing development data directories:"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev_dirs_delete_warning)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
echo "是否删除上述目录以继续?此操作将清空相关持久化数据。"
|
||||
;;
|
||||
*)
|
||||
echo "Do you want to delete the directories above to continue? This will remove persisted data."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev_dirs_abort)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
echo "用户取消删除。无法继续进行开发环境初始化。"
|
||||
;;
|
||||
*)
|
||||
echo "Deletion declined by user. Cannot proceed with development setup."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev_dirs_deleting)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
echo "正在删除目录:"
|
||||
;;
|
||||
*)
|
||||
echo "Deleting directories:"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev_dirs_deleted)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
echo "目录已删除。"
|
||||
;;
|
||||
*)
|
||||
echo "Directories deleted."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
tips_no_executable)
|
||||
case $LANGUAGE in
|
||||
zh_CN)
|
||||
@@ -311,6 +361,7 @@ show_message() {
|
||||
echo "(0) 域名模式(访问时无需指明端口),需要使用反向代理服务 LobeChat, MinIO, Casdoor ,并分别分配一个域名;"
|
||||
echo "(1) 端口模式(访问时需要指明端口,如使用IP访问,或域名+端口访问),需要放开指定端口;"
|
||||
echo "(2) 本地模式(仅供本地测试使用)"
|
||||
echo "(3) 开发模式(仅启动本地开发所需的基础服务,不包含 Casdoor;将 .env 复制到项目根目录)"
|
||||
echo "如果你对这些内容疑惑,可以先选择使用本地模式进行部署,稍后根据文档指引再进行修改。"
|
||||
echo "https://lobehub.com/docs/self-hosting/server-database/docker-compose"
|
||||
;;
|
||||
@@ -319,6 +370,7 @@ show_message() {
|
||||
echo "(0) Domain mode (no need to specify the port when accessing), you need to use the reverse proxy service LobeChat, MinIO, Casdoor, and assign a domain name respectively;"
|
||||
echo "(1) Port mode (need to specify the port when accessing, such as using IP access, or domain name + port access), you need to open the specified port;"
|
||||
echo "(2) Local mode (for local testing only)"
|
||||
echo "(3) Development mode (start local dev infra only, no Casdoor; copy .env to project root)"
|
||||
echo "If you are confused about these contents, you can choose to deploy in local mode first, and then modify according to the document guide later."
|
||||
echo "https://lobehub.com/docs/self-hosting/server-database/docker-compose"
|
||||
;;
|
||||
@@ -378,31 +430,28 @@ download_file() {
|
||||
}
|
||||
|
||||
print_centered() {
|
||||
# Define colors
|
||||
declare -A colors
|
||||
colors=(
|
||||
[black]="\e[30m"
|
||||
[red]="\e[31m"
|
||||
[green]="\e[32m"
|
||||
[yellow]="\e[33m"
|
||||
[blue]="\e[34m"
|
||||
[magenta]="\e[35m"
|
||||
[cyan]="\e[36m"
|
||||
[white]="\e[37m"
|
||||
[reset]="\e[0m"
|
||||
)
|
||||
local text="$1" # Get input texts
|
||||
local color="${2:-reset}" # Get color, default to reset
|
||||
local term_width=$(tput cols) # Get terminal width
|
||||
local text_length=${#text} # Get text length
|
||||
local padding=$(((term_width - text_length) / 2)) # Get padding
|
||||
# Check if the color is valid
|
||||
if [[ -z "${colors[$color]}" ]]; then
|
||||
echo "Invalid color specified. Available colors: ${!colors[@]}"
|
||||
return 1
|
||||
fi
|
||||
# Print the text with padding
|
||||
printf "%*s${colors[$color]}%s${colors[reset]}\n" $padding "" "$text"
|
||||
# Map color name to ANSI code without associative arrays (macOS bash 3.2 compatible)
|
||||
local text="$1"
|
||||
local color="${2:-reset}"
|
||||
local color_code
|
||||
case "$color" in
|
||||
black) color_code="\e[30m" ;;
|
||||
red) color_code="\e[31m" ;;
|
||||
green) color_code="\e[32m" ;;
|
||||
yellow) color_code="\e[33m" ;;
|
||||
blue) color_code="\e[34m" ;;
|
||||
magenta) color_code="\e[35m" ;;
|
||||
cyan) color_code="\e[36m" ;;
|
||||
white) color_code="\e[37m" ;;
|
||||
reset|*) color_code="\e[0m" ;;
|
||||
esac
|
||||
# Determine terminal width (fallback to 80)
|
||||
local term_width
|
||||
term_width=$(tput cols 2>/dev/null || echo 80)
|
||||
local text_length=${#text}
|
||||
local padding=$(((term_width - text_length) / 2))
|
||||
if [ $padding -lt 0 ]; then padding=0; fi
|
||||
printf "%*s${color_code}%s\e[0m\n" $padding "" "$text"
|
||||
}
|
||||
|
||||
# Usage:
|
||||
@@ -420,7 +469,7 @@ ask() {
|
||||
description="$description "
|
||||
fi
|
||||
local result
|
||||
|
||||
|
||||
if [ -n "$default" ]; then
|
||||
read -p "$prompt [${description}${default}]: " result
|
||||
result=${result:-$default}
|
||||
@@ -475,17 +524,344 @@ if [ -z "$LANGUAGE" ]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# ==========================
|
||||
# === Development Mode ====
|
||||
# ==========================
|
||||
sync_dev_s3_env_from_minio() {
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
local DEST_ENV="$PROJECT_ROOT/.env"
|
||||
|
||||
if [ ! -f "$DEST_ENV" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local MINIO_BUCKET MINIO_USER MINIO_PASS
|
||||
MINIO_BUCKET=$(grep -E '^MINIO_LOBE_BUCKET=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
MINIO_USER=$(grep -E '^MINIO_ROOT_USER=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
MINIO_PASS=$(grep -E '^MINIO_ROOT_PASSWORD=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
|
||||
# Defaults when missing
|
||||
if [ -z "$MINIO_BUCKET" ]; then MINIO_BUCKET="lobe"; fi
|
||||
if [ -z "$MINIO_USER" ]; then MINIO_USER="admin"; fi
|
||||
|
||||
if grep -q '^S3_BUCKET=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_BUCKET=.*#S3_BUCKET=${MINIO_BUCKET}#" "$DEST_ENV"
|
||||
else
|
||||
echo "S3_BUCKET=${MINIO_BUCKET}" >> "$DEST_ENV"
|
||||
fi
|
||||
if grep -q '^S3_ACCESS_KEY=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_ACCESS_KEY=.*#S3_ACCESS_KEY=${MINIO_USER}#" "$DEST_ENV"
|
||||
else
|
||||
echo "S3_ACCESS_KEY=${MINIO_USER}" >> "$DEST_ENV"
|
||||
fi
|
||||
if grep -q '^S3_ACCESS_KEY_ID=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_ACCESS_KEY_ID=.*#S3_ACCESS_KEY_ID=${MINIO_USER}#" "$DEST_ENV"
|
||||
else
|
||||
echo "S3_ACCESS_KEY_ID=${MINIO_USER}" >> "$DEST_ENV"
|
||||
fi
|
||||
if grep -q '^S3_SECRET_ACCESS_KEY=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_SECRET_ACCESS_KEY=.*#S3_SECRET_ACCESS_KEY=${MINIO_PASS}#" "$DEST_ENV"
|
||||
else
|
||||
echo "S3_SECRET_ACCESS_KEY=${MINIO_PASS}" >> "$DEST_ENV"
|
||||
fi
|
||||
}
|
||||
|
||||
section_setup_development_env() {
|
||||
# Determine directories
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "Setting up development environment (.env at project root)..."
|
||||
local DEV_ENV_EXAMPLE="$SCRIPT_DIR/development/.env.example"
|
||||
local DEST_ENV="$PROJECT_ROOT/.env"
|
||||
|
||||
if [ ! -f "$DEV_ENV_EXAMPLE" ]; then
|
||||
echo "ERROR: Development .env.example not found at $DEV_ENV_EXAMPLE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local DO_COPY="y"
|
||||
if [ -f "$DEST_ENV" ]; then
|
||||
echo "A .env already exists at project root ($DEST_ENV). Overwrite?"
|
||||
ask "(y/n)" "n"
|
||||
DO_COPY="$ask_result"
|
||||
if [[ "$DO_COPY" != "y" ]]; then
|
||||
echo "Keeping existing .env at project root."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$DO_COPY" == "y" ]]; then
|
||||
cp "$DEV_ENV_EXAMPLE" "$DEST_ENV"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Failed to copy development .env to project root."
|
||||
return 1
|
||||
fi
|
||||
echo "✔️ .env copied to project root: $DEST_ENV"
|
||||
fi
|
||||
|
||||
# Sync S3 variables to concrete values from current MinIO settings
|
||||
sync_dev_s3_env_from_minio
|
||||
}
|
||||
|
||||
section_check_dev_data_dirs() {
|
||||
# Check and optionally delete existing dev data directories (PostgreSQL and MinIO)
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local DEV_DIR="$SCRIPT_DIR/development"
|
||||
local DATA_DIR="$DEV_DIR/data"
|
||||
local S3_DATA_DIR="$DEV_DIR/s3_data"
|
||||
|
||||
local existing_list=""
|
||||
local -a dirs_to_remove
|
||||
|
||||
if [ -d "$DATA_DIR" ]; then
|
||||
existing_list+=" data"
|
||||
dirs_to_remove+=("$DATA_DIR")
|
||||
fi
|
||||
if [ -d "$S3_DATA_DIR" ]; then
|
||||
existing_list+=" s3_data"
|
||||
dirs_to_remove+=("$S3_DATA_DIR")
|
||||
fi
|
||||
|
||||
# Nothing to do
|
||||
if [ -z "$existing_list" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Inform user and ask for deletion
|
||||
echo "$(show_message "dev_dirs_exist")$existing_list"
|
||||
echo "$(show_message "dev_dirs_delete_warning")"
|
||||
ask "(y/n)" "n"
|
||||
if [[ "$ask_result" != "y" ]]; then
|
||||
echo "$(show_message "dev_dirs_abort")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$(show_message "dev_dirs_deleting")$existing_list"
|
||||
# Delete only within DEV_DIR for safety
|
||||
for d in "${dirs_to_remove[@]}"; do
|
||||
case "$d" in
|
||||
"$DEV_DIR"/*)
|
||||
rm -rf -- "$d"
|
||||
;;
|
||||
*)
|
||||
echo "Skip unsafe path: $d"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
echo "$(show_message "dev_dirs_deleted")"
|
||||
}
|
||||
|
||||
section_regenerate_secrets_dev() {
|
||||
# Regenerate development secrets (MinIO, NextAuth, Key Vaults) and DATABASE_URL
|
||||
if ! command -v openssl &> /dev/null ; then
|
||||
echo "openssl" $(show_message "tips_no_executable")
|
||||
return 1
|
||||
fi
|
||||
if ! command -v sed &> /dev/null ; then
|
||||
echo "sed" $(show_message "tips_no_executable")
|
||||
return 1
|
||||
fi
|
||||
|
||||
generate_key() {
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Usage: generate_key <length>"
|
||||
return 1
|
||||
fi
|
||||
echo $(openssl rand -hex $1 | tr -d '\n' | fold -w $1 | head -n 1)
|
||||
}
|
||||
|
||||
generate_base64() {
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Usage: generate_base64 <bytes>"
|
||||
return 1
|
||||
fi
|
||||
echo $(openssl rand -base64 $1 | tr -d '\n')
|
||||
}
|
||||
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
local DEST_ENV="$PROJECT_ROOT/.env"
|
||||
|
||||
if [ ! -f "$DEST_ENV" ]; then
|
||||
echo "WARN: $DEST_ENV not found; skipping secret regeneration."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo $(show_message "security_secrect_regenerate")
|
||||
|
||||
# 1) MINIO_ROOT_PASSWORD (hex)
|
||||
local MINIO_ROOT_PASSWORD
|
||||
MINIO_ROOT_PASSWORD=$(generate_key 8)
|
||||
if [ $? -ne 0 ] || [ -z "$MINIO_ROOT_PASSWORD" ]; then
|
||||
echo $(show_message "security_secrect_regenerate_failed") "MINIO_ROOT_PASSWORD"
|
||||
return 1
|
||||
fi
|
||||
if grep -q '^MINIO_ROOT_PASSWORD=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^MINIO_ROOT_PASSWORD=.*#MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}#" "$DEST_ENV"
|
||||
else
|
||||
echo "MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}" >> "$DEST_ENV"
|
||||
fi
|
||||
|
||||
# 2) POSTGRES_PASSWORD (hex)
|
||||
local POSTGRES_PASSWORD_NEW
|
||||
POSTGRES_PASSWORD_NEW=$(generate_key 8)
|
||||
if [ $? -ne 0 ] || [ -z "$POSTGRES_PASSWORD_NEW" ]; then
|
||||
echo $(show_message "security_secrect_regenerate_failed") "POSTGRES_PASSWORD"
|
||||
else
|
||||
if grep -q '^POSTGRES_PASSWORD=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^POSTGRES_PASSWORD=.*#POSTGRES_PASSWORD=${POSTGRES_PASSWORD_NEW}#" "$DEST_ENV"
|
||||
else
|
||||
echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD_NEW}" >> "$DEST_ENV"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) NEXT_AUTH_SECRET (base64 32)
|
||||
local NEXT_AUTH_SECRET
|
||||
NEXT_AUTH_SECRET=$(generate_base64 32)
|
||||
if [ $? -ne 0 ] || [ -z "$NEXT_AUTH_SECRET" ]; then
|
||||
echo $(show_message "security_secrect_regenerate_failed") "NEXT_AUTH_SECRET"
|
||||
else
|
||||
if grep -q '^NEXT_AUTH_SECRET=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^NEXT_AUTH_SECRET=.*#NEXT_AUTH_SECRET=${NEXT_AUTH_SECRET}#" "$DEST_ENV"
|
||||
else
|
||||
echo "NEXT_AUTH_SECRET=${NEXT_AUTH_SECRET}" >> "$DEST_ENV"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) KEY_VAULTS_SECRET (base64 32)
|
||||
local KEY_VAULTS_SECRET
|
||||
KEY_VAULTS_SECRET=$(generate_base64 32)
|
||||
if [ $? -ne 0 ] || [ -z "$KEY_VAULTS_SECRET" ]; then
|
||||
echo $(show_message "security_secrect_regenerate_failed") "KEY_VAULTS_SECRET"
|
||||
else
|
||||
if grep -q '^KEY_VAULTS_SECRET=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^KEY_VAULTS_SECRET=.*#KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET}#" "$DEST_ENV"
|
||||
else
|
||||
echo "KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET}" >> "$DEST_ENV"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) DATABASE_URL (postgres connection string)
|
||||
local POSTGRES_PASSWORD_VAL
|
||||
local LOBE_DB_NAME_VAL
|
||||
POSTGRES_PASSWORD_VAL=$(grep -E '^POSTGRES_PASSWORD=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
LOBE_DB_NAME_VAL=$(grep -E '^LOBE_DB_NAME=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
if [ -z "$POSTGRES_PASSWORD_VAL" ]; then POSTGRES_PASSWORD_VAL="postgres"; fi
|
||||
if [ -z "$LOBE_DB_NAME_VAL" ]; then LOBE_DB_NAME_VAL="lobechat"; fi
|
||||
local DATABASE_URL
|
||||
DATABASE_URL="postgres://postgres:${POSTGRES_PASSWORD_VAL}@localhost:5432/${LOBE_DB_NAME_VAL}"
|
||||
if grep -q '^DATABASE_URL=' "$DEST_ENV"; then
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^DATABASE_URL=.*#DATABASE_URL=${DATABASE_URL}#" "$DEST_ENV"
|
||||
else
|
||||
echo "DATABASE_URL=${DATABASE_URL}" >> "$DEST_ENV"
|
||||
fi
|
||||
|
||||
# Sync S3 variables to reflect updated MinIO creds
|
||||
sync_dev_s3_env_from_minio
|
||||
}
|
||||
|
||||
section_init_database_dev() {
|
||||
if ! command -v docker &> /dev/null ; then
|
||||
echo "docker" $(show_message "tips_no_executable")
|
||||
return 1
|
||||
fi
|
||||
if ! docker compose &> /dev/null ; then
|
||||
echo "docker compose" $(show_message "tips_no_executable")
|
||||
return 1
|
||||
fi
|
||||
if ! docker stats --no-stream &> /dev/null ; then
|
||||
echo $(show_message "tips_no_docker_permission")
|
||||
return 1
|
||||
fi
|
||||
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "Starting development infrastructure (PostgreSQL, MinIO, SearXNG)..."
|
||||
docker compose --env-file "$PROJECT_ROOT/.env" -f "$SCRIPT_DIR/development/docker-compose.yml" pull
|
||||
docker compose --env-file "$PROJECT_ROOT/.env" -f "$SCRIPT_DIR/development/docker-compose.yml" up --detach postgresql minio searxng
|
||||
# give services a moment to start
|
||||
sleep 5
|
||||
}
|
||||
|
||||
section_display_dev_report() {
|
||||
local SCRIPT_DIR
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
local DEST_ENV="$PROJECT_ROOT/.env"
|
||||
|
||||
# Defaults
|
||||
local LOBE_PORT_VALUE=3210
|
||||
local MINIO_PORT_VALUE=9000
|
||||
|
||||
if [ -f "$DEST_ENV" ]; then
|
||||
local lp mp
|
||||
lp=$(grep -E '^LOBE_PORT=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
mp=$(grep -E '^MINIO_PORT=' "$DEST_ENV" | head -n1 | cut -d'=' -f2)
|
||||
if [ -n "$lp" ]; then LOBE_PORT_VALUE="$lp"; fi
|
||||
if [ -n "$mp" ]; then MINIO_PORT_VALUE="$mp"; fi
|
||||
fi
|
||||
|
||||
printf "\nDevelopment infrastructure started. Service endpoints:\n"
|
||||
echo "- PostgreSQL: localhost:5432"
|
||||
echo "- MinIO API: http://localhost:${MINIO_PORT_VALUE} (Console: http://localhost:9001)"
|
||||
echo "- SearXNG: http://localhost:8080"
|
||||
|
||||
printf "\nYou can now run the app locally with:\n"
|
||||
print_centered "pnpm db:migrate" "green"
|
||||
print_centered "pnpm start:dev" "green"
|
||||
}
|
||||
|
||||
# Prompt for deployment mode early to allow Development mode flow
|
||||
show_message "ask_deploy_mode"
|
||||
ask "(0,1,2,3)" "2"
|
||||
if [[ "$ask_result" == "3" ]]; then
|
||||
# Development mode: copy .env to project root, optionally regenerate secrets/init DB, then report and exit
|
||||
section_setup_development_env || exit 1
|
||||
|
||||
show_message "ask_regenerate_secrets"
|
||||
ask "(y/n)" "y"
|
||||
if [[ "$ask_result" == "y" ]]; then
|
||||
section_regenerate_secrets_dev || true
|
||||
fi
|
||||
|
||||
# Check if dev data directories exist and handle per user choice; abort if declined
|
||||
section_check_dev_data_dirs || exit 1
|
||||
|
||||
show_message "ask_init_database"
|
||||
ask "(y/n)" "y"
|
||||
if [[ "$ask_result" == "y" ]]; then
|
||||
section_init_database_dev || true
|
||||
else
|
||||
show_message "tips_init_database_failed"
|
||||
fi
|
||||
|
||||
section_display_dev_report
|
||||
exit 0
|
||||
fi
|
||||
|
||||
section_download_files(){
|
||||
# Download files asynchronously
|
||||
if ! command -v wget &> /dev/null ; then
|
||||
echo "wget" $(show_message "tips_no_executable")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
download_file "$SOURCE_URL/${FILES[0]}" "docker-compose.yml"
|
||||
download_file "$SOURCE_URL/${FILES[1]}" "init_data.json"
|
||||
download_file "$SOURCE_URL/${FILES[2]}" "searxng-settings.yml"
|
||||
|
||||
|
||||
# Download .env.example with the specified language
|
||||
if [ "$LANGUAGE" = "zh_CN" ]; then
|
||||
download_file "$SOURCE_URL/${ENV_EXAMPLES[0]}" ".env"
|
||||
@@ -522,13 +898,13 @@ section_configurate_host() {
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#http://#https://#" .env
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Check if sed is installed
|
||||
if ! command -v sed "${SED_INPLACE_ARGS[@]}" &> /dev/null ; then
|
||||
echo "sed" $(show_message "tips_no_executable")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# If user not specify host, try to get the server ip
|
||||
if [ -z "$HOST" ]; then
|
||||
HOST=$(hostname -I | awk '{print $1}')
|
||||
@@ -537,8 +913,8 @@ section_configurate_host() {
|
||||
echo $(show_message "tips_private_ip_detected")
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
case $DEPLOY_MODE in
|
||||
0)
|
||||
DEPLOY_MODE="domain"
|
||||
@@ -573,7 +949,7 @@ section_configurate_host() {
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
# lobe host
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^APP_URL=.*#APP_URL=$PROTOCOL://$LOBE_HOST#" .env
|
||||
# auth related
|
||||
@@ -583,15 +959,18 @@ section_configurate_host() {
|
||||
# s3 related
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_PUBLIC_DOMAIN=.*#S3_PUBLIC_DOMAIN=$PROTOCOL://$MINIO_HOST#" .env
|
||||
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_ENDPOINT=.*#S3_ENDPOINT=$PROTOCOL://$MINIO_HOST#" .env
|
||||
|
||||
|
||||
|
||||
# Check if env modified success
|
||||
if [ $? -ne 0 ]; then
|
||||
echo $(show_message "host_regenerate_failed") "$HOST in \`.env\`"
|
||||
fi
|
||||
}
|
||||
show_message "ask_deploy_mode"
|
||||
ask "(0,1,2)" "2"
|
||||
|
||||
if [ -z "$ask_result" ]; then
|
||||
show_message "ask_deploy_mode"
|
||||
ask "(0,1,2)" "2"
|
||||
fi
|
||||
if [[ "$ask_result" == "0" ]] || [[ "$ask_result" == "1" ]] || [[ "$ask_result" == "2" ]]; then
|
||||
section_configurate_host
|
||||
else
|
||||
@@ -620,7 +999,7 @@ section_regenerate_secrets() {
|
||||
echo "head" $(show_message "tips_no_executable")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
generate_key() {
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Usage: generate_key <length>"
|
||||
@@ -628,13 +1007,13 @@ section_regenerate_secrets() {
|
||||
fi
|
||||
echo $(openssl rand -hex $1 | tr -d '\n' | fold -w $1 | head -n 1)
|
||||
}
|
||||
|
||||
|
||||
if ! command -v sed &> /dev/null ; then
|
||||
echo "sed" $(show_message "tips_no_executable")
|
||||
exit 1
|
||||
fi
|
||||
echo $(show_message "security_secrect_regenerate")
|
||||
|
||||
|
||||
# Generate CASDOOR_SECRET
|
||||
CASDOOR_SECRET=$(generate_key 32)
|
||||
if [ $? -ne 0 ]; then
|
||||
@@ -651,7 +1030,7 @@ section_regenerate_secrets() {
|
||||
echo $(show_message "security_secrect_regenerate_failed") "AUTH_CASDOOR_SECRET in \`init_data.json\`"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Generate Casdoor User
|
||||
CASDOOR_USER="admin"
|
||||
CASDOOR_PASSWORD=$(generate_key 10)
|
||||
@@ -722,18 +1101,18 @@ if [[ "$ask_result" == "y" ]]; then
|
||||
if [ $? -ne 0 ]; then
|
||||
echo $(show_message "tips_init_database_failed")
|
||||
fi
|
||||
else
|
||||
else
|
||||
show_message "tips_init_database_failed"
|
||||
fi
|
||||
|
||||
section_display_configurated_report() {
|
||||
# Display configuration reports
|
||||
echo $(show_message "security_secrect_regenerate_report")
|
||||
|
||||
|
||||
echo -e "LobeChat: \n - URL: $PROTOCOL://$LOBE_HOST \n - Username: user \n - Password: ${CASDOOR_PASSWORD} "
|
||||
echo -e "Casdoor: \n - URL: $PROTOCOL://$CASDOOR_HOST \n - Username: admin \n - Password: ${CASDOOR_PASSWORD}\n"
|
||||
echo -e "Minio: \n - URL: $PROTOCOL://$MINIO_HOST \n - Username: admin\n - Password: ${MINIO_ROOT_PASSWORD}\n"
|
||||
|
||||
|
||||
# if user run in domain mode, diplay reverse proxy configuration
|
||||
if [[ "$DEPLOY_MODE" == "domain" ]]; then
|
||||
echo $(show_message "tips_add_reverse_proxy")
|
||||
|
||||
@@ -72,6 +72,34 @@ bun run dev
|
||||
|
||||
Now, you should be able to see the welcome page of LobeChat in your browser. For a detailed environment setup guide, please refer to [Development Environment Setup Guide](/docs/development/basic/setup-development).
|
||||
|
||||
### Start local infrastructure with Docker Compose (Development mode)
|
||||
|
||||
If you want to run LobeChat against a local database and object storage, we provide a Development mode that launches only the infrastructure you need (PostgreSQL, MinIO, and SearXNG) via Docker:
|
||||
|
||||
```bash
|
||||
cd docker-compose
|
||||
bash ./setup.sh
|
||||
# When prompted, choose: (3) Development mode
|
||||
# The script will:
|
||||
# - copy a .env template to the project root (./.env)
|
||||
# - regenerate secrets if you accept the prompt
|
||||
# - start Postgres, MinIO and SearXNG
|
||||
```
|
||||
|
||||
After the infrastructure is up, return to the project root and run:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run db:migrate
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The .env file is stored at the project root so pnpm scripts auto-load it.
|
||||
- Casdoor is not used in Development mode.
|
||||
- MinIO Console is available at [http://localhost:9001](http://localhost:9001) and SearXNG at [http://localhost:8080](http://localhost:8080).
|
||||
|
||||
## Code Style and Contribution Guide
|
||||
|
||||
In the LobeChat project, we place great emphasis on the quality and consistency of the code. For this reason, we have established a series of code style standards and contribution processes to ensure that every developer can smoothly participate in the project. Here are the code style and contribution guidelines you need to follow as a developer.
|
||||
|
||||
@@ -20,6 +20,12 @@ tags:
|
||||
[![][docker-pulls-shield]][docker-pulls-link]
|
||||
</div>
|
||||
|
||||
<Callout type="info">
|
||||
For local development, use the interactive <strong>Development mode</strong> which starts only the infrastructure (PostgreSQL, MinIO, and SearXNG). It stores the <code>.env</code> file at the project root so pnpm scripts auto-load it. Casdoor is not used in this mode.
|
||||
|
||||
See the development guide: <a href="/docs/development/start">/docs/development/start</a>
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning">
|
||||
**Note on Docker Deployment Limitations**
|
||||
The Docker and Docker Compose deployment options do not support injecting the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` through environment variables, which prevents enabling the Clerk authentication service. Recommended alternatives include:
|
||||
|
||||
@@ -2,28 +2,28 @@
|
||||
// if you want to use it in the commercial usage
|
||||
// please contact us for more information: hello@lobehub.com
|
||||
|
||||
export const LOBE_CHAT_CLOUD = 'LobeChat Cloud';
|
||||
export const LOBE_CHAT_CLOUD = 'OneAI';
|
||||
|
||||
export const BRANDING_NAME = 'LobeChat';
|
||||
export const BRANDING_LOGO_URL = '';
|
||||
export const BRANDING_NAME = 'OneAI';
|
||||
export const BRANDING_LOGO_URL = '/icons/icon-512x512.png';
|
||||
|
||||
export const ORG_NAME = 'LobeHub';
|
||||
export const ORG_NAME = 'JA Worldwide';
|
||||
|
||||
export const BRANDING_URL = {
|
||||
help: undefined,
|
||||
privacy: undefined,
|
||||
terms: undefined,
|
||||
help: 'mailto:support@jaworldwide.org',
|
||||
privacy: 'https://www.jaworldwide.org/terms',
|
||||
terms: 'https://www.jaworldwide.org/terms',
|
||||
};
|
||||
|
||||
export const SOCIAL_URL = {
|
||||
discord: 'https://discord.gg/AYFPHvv2jT',
|
||||
github: 'https://github.com/lobehub',
|
||||
medium: 'https://medium.com/@lobehub',
|
||||
x: 'https://x.com/lobehub',
|
||||
youtube: 'https://www.youtube.com/@lobehub',
|
||||
discord: 'https://discord.gg/',
|
||||
github: 'https://github.com/jaworldwideorg',
|
||||
medium: 'https://medium.com/',
|
||||
x: 'https://x.com/jaworldwide',
|
||||
youtube: 'https://www.youtube.com/@jaworldwide',
|
||||
};
|
||||
|
||||
export const BRANDING_EMAIL = {
|
||||
business: 'hello@lobehub.com',
|
||||
support: 'support@lobehub.com',
|
||||
business: 'contact@jaworldwide.org',
|
||||
support: 'support@jaworldwide.org',
|
||||
};
|
||||
|
||||
@@ -340,6 +340,153 @@ describe('LobeAzureOpenAI', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createImage', () => {
|
||||
beforeEach(() => {
|
||||
// ensure images namespace exists and is spy-able
|
||||
expect(instance['client'].images).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should generate image and return url from object response', async () => {
|
||||
const url = 'https://example.com/image.png';
|
||||
const generateSpy = vi
|
||||
.spyOn(instance['client'].images, 'generate')
|
||||
.mockResolvedValue({ data: [{ url }] } as any);
|
||||
|
||||
const res = await instance.createImage({
|
||||
model: 'gpt-image-1',
|
||||
params: { prompt: 'a cat' },
|
||||
});
|
||||
|
||||
expect(generateSpy).toHaveBeenCalledTimes(1);
|
||||
const args = vi.mocked(generateSpy).mock.calls[0][0] as any;
|
||||
expect(args).not.toHaveProperty('image');
|
||||
expect(res).toEqual({ imageUrl: url });
|
||||
});
|
||||
|
||||
it('should parse string JSON response from images.generate', async () => {
|
||||
const url = 'https://example.com/str.png';
|
||||
const payload = JSON.stringify({ data: [{ url }] });
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue(payload as any);
|
||||
|
||||
const res = await instance.createImage({ model: 'gpt-image-1', params: { prompt: 'dog' } });
|
||||
expect(res).toEqual({ imageUrl: url });
|
||||
});
|
||||
|
||||
it('should parse bodyAsText JSON response', async () => {
|
||||
const url = 'https://example.com/bodyAsText.png';
|
||||
const bodyAsText = JSON.stringify({ data: [{ url }] });
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue({ bodyAsText } as any);
|
||||
|
||||
const res = await instance.createImage({ model: 'gpt-image-1', params: { prompt: 'bird' } });
|
||||
expect(res).toEqual({ imageUrl: url });
|
||||
});
|
||||
|
||||
it('should parse body JSON response', async () => {
|
||||
const url = 'https://example.com/body.png';
|
||||
const body = JSON.stringify({ data: [{ url }] });
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue({ body } as any);
|
||||
|
||||
const res = await instance.createImage({ model: 'gpt-image-1', params: { prompt: 'fish' } });
|
||||
expect(res).toEqual({ imageUrl: url });
|
||||
});
|
||||
|
||||
it('should prefer b64_json and return data URL', async () => {
|
||||
const b64 = 'AAA';
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue({
|
||||
data: [{ b64_json: b64 }],
|
||||
} as any);
|
||||
|
||||
const res = await instance.createImage({ model: 'gpt-image-1', params: { prompt: 'sun' } });
|
||||
expect(res.imageUrl).toBe(`data:image/png;base64,${b64}`);
|
||||
});
|
||||
|
||||
it('should throw wrapped error for empty data array', async () => {
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue({ data: [] } as any);
|
||||
|
||||
await expect(
|
||||
instance.createImage({ model: 'gpt-image-1', params: { prompt: 'moon' } }),
|
||||
).rejects.toMatchObject({
|
||||
endpoint: 'https://***.openai.azure.com/',
|
||||
errorType: 'AgentRuntimeError',
|
||||
provider: 'azure',
|
||||
error: {
|
||||
name: 'Error',
|
||||
cause: undefined,
|
||||
message: expect.stringContaining('Invalid image response: missing or empty data array'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw wrapped error when missing both b64_json and url', async () => {
|
||||
vi.spyOn(instance['client'].images, 'generate').mockResolvedValue({
|
||||
data: [{}],
|
||||
} as any);
|
||||
|
||||
await expect(
|
||||
instance.createImage({ model: 'gpt-image-1', params: { prompt: 'stars' } }),
|
||||
).rejects.toEqual({
|
||||
endpoint: 'https://***.openai.azure.com/',
|
||||
errorType: 'AgentRuntimeError',
|
||||
provider: 'azure',
|
||||
error: {
|
||||
name: 'Error',
|
||||
cause: undefined,
|
||||
message: 'Invalid image response: missing both b64_json and url fields',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should call images.edit when imageUrl provided and strip size:auto', async () => {
|
||||
const url = 'https://example.com/edited.png';
|
||||
const editSpy = vi
|
||||
.spyOn(instance['client'].images, 'edit')
|
||||
.mockResolvedValue({ data: [{ url }] } as any);
|
||||
|
||||
const helpers = await import('../utils/openaiHelpers');
|
||||
vi.spyOn(helpers, 'convertImageUrlToFile').mockResolvedValue({} as any);
|
||||
|
||||
const res = await instance.createImage({
|
||||
model: 'gpt-image-1',
|
||||
params: { prompt: 'edit', imageUrl: 'https://example.com/in.png', size: 'auto' as any },
|
||||
});
|
||||
|
||||
expect(editSpy).toHaveBeenCalledTimes(1);
|
||||
const arg = vi.mocked(editSpy).mock.calls[0][0] as any;
|
||||
expect(arg).not.toHaveProperty('size');
|
||||
expect(res).toEqual({ imageUrl: url });
|
||||
});
|
||||
|
||||
it('should convert multiple imageUrls and pass images array to edit', async () => {
|
||||
const url = 'https://example.com/edited2.png';
|
||||
const editSpy = vi
|
||||
.spyOn(instance['client'].images, 'edit')
|
||||
.mockResolvedValue({ data: [{ url }] } as any);
|
||||
|
||||
const helpers = await import('../utils/openaiHelpers');
|
||||
const spy = vi.spyOn(helpers, 'convertImageUrlToFile').mockResolvedValue({} as any);
|
||||
|
||||
await instance.createImage({
|
||||
model: 'gpt-image-1',
|
||||
params: { prompt: 'edit', imageUrls: ['u1', 'u2'] },
|
||||
});
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
const arg = vi.mocked(editSpy).mock.calls[0][0] as any;
|
||||
expect(arg).toHaveProperty('image');
|
||||
});
|
||||
|
||||
it('should not include image in generate options', async () => {
|
||||
const generateSpy = vi
|
||||
.spyOn(instance['client'].images, 'generate')
|
||||
.mockResolvedValue({ data: [{ url: 'https://x/y.png' }] } as any);
|
||||
|
||||
await instance.createImage({ model: 'gpt-image-1', params: { prompt: 'no image' } });
|
||||
|
||||
const arg = vi.mocked(generateSpy).mock.calls[0][0] as any;
|
||||
expect(arg).not.toHaveProperty('image');
|
||||
});
|
||||
});
|
||||
|
||||
describe('private method', () => {
|
||||
describe('tocamelCase', () => {
|
||||
it('should convert string to camel case', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import debug from 'debug';
|
||||
import OpenAI, { AzureOpenAI } from 'openai';
|
||||
import type { Stream } from 'openai/streaming';
|
||||
|
||||
@@ -13,13 +14,15 @@ import {
|
||||
EmbeddingsPayload,
|
||||
ModelProvider,
|
||||
} from '../types';
|
||||
import { CreateImagePayload, CreateImageResponse } from '../types/image';
|
||||
import { AgentRuntimeError } from '../utils/createError';
|
||||
import { debugStream } from '../utils/debugStream';
|
||||
import { transformResponseToStream } from '../utils/openaiCompatibleFactory';
|
||||
import { convertOpenAIMessages } from '../utils/openaiHelpers';
|
||||
import { convertImageUrlToFile, convertOpenAIMessages } from '../utils/openaiHelpers';
|
||||
import { StreamingResponse } from '../utils/response';
|
||||
import { OpenAIStream } from '../utils/streams';
|
||||
|
||||
const azureImageLogger = debug('lobe-image:azure');
|
||||
export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
client: AzureOpenAI;
|
||||
|
||||
@@ -116,6 +119,117 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
}
|
||||
}
|
||||
|
||||
// Create image using Azure OpenAI Images API (gpt-image-1 or DALL·E deployments)
|
||||
async createImage(payload: CreateImagePayload): Promise<CreateImageResponse> {
|
||||
const { model, params } = payload;
|
||||
azureImageLogger('Creating image with model: %s and params: %O', model, params);
|
||||
|
||||
try {
|
||||
// Clone params and remap imageUrls/imageUrl -> image
|
||||
const userInput: Record<string, any> = { ...params };
|
||||
|
||||
// Convert imageUrls to 'image' for edit API
|
||||
if (Array.isArray(userInput.imageUrls) && userInput.imageUrls.length > 0) {
|
||||
const imageFiles = await Promise.all(
|
||||
userInput.imageUrls.map((url: string) => convertImageUrlToFile(url)),
|
||||
);
|
||||
userInput.image = imageFiles.length === 1 ? imageFiles[0] : imageFiles;
|
||||
}
|
||||
|
||||
// Backward compatibility: single imageUrl -> image
|
||||
if (userInput.imageUrl && !userInput.image) {
|
||||
userInput.image = await convertImageUrlToFile(userInput.imageUrl);
|
||||
}
|
||||
|
||||
// Remove non-API parameters to avoid unknown_parameter errors
|
||||
delete userInput.imageUrls;
|
||||
delete userInput.imageUrl;
|
||||
|
||||
const isImageEdit = Boolean(userInput.image);
|
||||
|
||||
azureImageLogger('Is Image Edit: ' + isImageEdit);
|
||||
// Azure/OpenAI Images: remove unsupported/auto values where appropriate
|
||||
if (userInput.size === 'auto') delete userInput.size;
|
||||
|
||||
// Build options: do not force response_format for gpt-image-1
|
||||
const options: any = {
|
||||
model,
|
||||
n: 1,
|
||||
...(isImageEdit ? { input_fidelity: 'high' } : {}),
|
||||
...userInput,
|
||||
};
|
||||
|
||||
// For generate, ensure no 'image' field is sent
|
||||
if (!isImageEdit) delete options.image;
|
||||
|
||||
// Call Azure Images API
|
||||
const img = isImageEdit
|
||||
? await this.client.images.edit(options)
|
||||
: await this.client.images.generate(options);
|
||||
|
||||
// Normalize possible string JSON response -- Sometimes Azure Image API returns a text/plain Content-Type
|
||||
let result: any = img as any;
|
||||
if (typeof result === 'string') {
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch {
|
||||
const truncated = result.length > 500 ? result.slice(0, 500) + '...[truncated]' : result;
|
||||
azureImageLogger(
|
||||
`Failed to parse string response from images API. Raw response: ${truncated}`,
|
||||
);
|
||||
throw new Error('Invalid image response: expected JSON string but parsing failed');
|
||||
}
|
||||
} else if (result && typeof result === 'object') {
|
||||
// Handle common Azure REST shapes
|
||||
if (typeof (result as any).bodyAsText === 'string') {
|
||||
try {
|
||||
result = JSON.parse((result as any).bodyAsText);
|
||||
} catch {
|
||||
const rawText = (result as any).bodyAsText;
|
||||
const truncated =
|
||||
rawText.length > 500 ? rawText.slice(0, 500) + '...[truncated]' : rawText;
|
||||
azureImageLogger(
|
||||
`Failed to parse bodyAsText from images API. Raw response: ${truncated}`,
|
||||
);
|
||||
throw new Error('Invalid image response: bodyAsText not valid JSON');
|
||||
}
|
||||
} else if (typeof (result as any).body === 'string') {
|
||||
try {
|
||||
result = JSON.parse((result as any).body);
|
||||
} catch {
|
||||
azureImageLogger('Failed to parse body from images API response');
|
||||
throw new Error('Invalid image response: body not valid JSON');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate response
|
||||
if (!result || !result.data || !Array.isArray(result.data) || result.data.length === 0) {
|
||||
throw new Error(
|
||||
`Invalid image response: missing or empty data array. Response: ${JSON.stringify(result)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const imageData: any = result.data[0];
|
||||
if (!imageData)
|
||||
throw new Error('Invalid image response: first data item is null or undefined');
|
||||
|
||||
// Prefer base64 if provided, otherwise URL
|
||||
if (imageData.b64_json) {
|
||||
const mimeType = 'image/png';
|
||||
return { imageUrl: `data:${mimeType};base64,${imageData.b64_json}` };
|
||||
}
|
||||
|
||||
if (imageData.url) {
|
||||
return { imageUrl: imageData.url };
|
||||
}
|
||||
|
||||
throw new Error('Invalid image response: missing both b64_json and url fields');
|
||||
} catch (e) {
|
||||
return this.handleError(e, model);
|
||||
}
|
||||
}
|
||||
|
||||
protected handleError(e: any, model?: string): never {
|
||||
let error = e as { [key: string]: any; code: string; message: string };
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
@@ -1,4 +1,4 @@
|
||||
import { AIChatModelCard } from '@/types/aiModel';
|
||||
import { AIChatModelCard, AIImageModelCard } from '@/types/aiModel';
|
||||
|
||||
const azureChatModels: AIChatModelCard[] = [
|
||||
{
|
||||
@@ -282,6 +282,66 @@ const azureChatModels: AIChatModelCard[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const allModels = [...azureChatModels];
|
||||
const azureImageModels: AIImageModelCard[] = [
|
||||
{
|
||||
description: 'FLUX.1 Kontext [pro]',
|
||||
displayName: 'FLUX.1 Kontext [pro]',
|
||||
enabled: true,
|
||||
id: 'FLUX.1-Kontext-pro',
|
||||
parameters: {
|
||||
imageUrl: { default: null },
|
||||
prompt: { default: '' },
|
||||
size: {
|
||||
default: 'auto',
|
||||
enum: ['auto', '1024x1024', '1792x1024', '1024x1792'],
|
||||
},
|
||||
},
|
||||
releasedAt: '2025-06-23',
|
||||
type: 'image',
|
||||
},
|
||||
{
|
||||
description: 'FLUX.1.1 Pro',
|
||||
displayName: 'FLUX.1.1 Pro',
|
||||
enabled: true,
|
||||
id: 'FLUX-1.1-pro',
|
||||
parameters: {
|
||||
imageUrl: { default: null },
|
||||
prompt: { default: '' },
|
||||
},
|
||||
releasedAt: '2025-06-23',
|
||||
type: 'image',
|
||||
},
|
||||
{
|
||||
description: 'DALL·E 3',
|
||||
displayName: 'DALL·E 3',
|
||||
id: 'dall-e-3',
|
||||
parameters: {
|
||||
imageUrl: { default: null },
|
||||
prompt: { default: '' },
|
||||
size: {
|
||||
default: 'auto',
|
||||
enum: ['auto', '1024x1024', '1792x1024', '1024x1792'],
|
||||
},
|
||||
},
|
||||
resolutions: ['1024x1024', '1024x1792', '1792x1024'],
|
||||
type: 'image',
|
||||
},
|
||||
{
|
||||
description: 'ChatGPT Image 1',
|
||||
displayName: 'GPT Image 1',
|
||||
id: 'gpt-image-1',
|
||||
parameters: {
|
||||
imageUrl: { default: null },
|
||||
prompt: { default: '' },
|
||||
size: {
|
||||
default: 'auto',
|
||||
enum: ['auto', '1024x1024', '1536x1024', '1024x1536'],
|
||||
},
|
||||
},
|
||||
type: 'image',
|
||||
},
|
||||
];
|
||||
|
||||
export const allModels = [...azureChatModels, ...azureImageModels];
|
||||
|
||||
export default allModels;
|
||||
|
||||