mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-22 20:36:08 +00:00
feat: add LangBot Space ChatCompletions requester and integrate with ModelsDialog and EmbeddingForm components
This commit is contained in:
@@ -9,7 +9,7 @@ import sqlalchemy.ext.asyncio as sqlalchemy_asyncio
|
|||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from . import database, migration
|
from . import database, migration
|
||||||
from ..entity.persistence import base, pipeline, metadata
|
from ..entity.persistence import base, pipeline, metadata, model as persistence_model
|
||||||
from ..entity import persistence
|
from ..entity import persistence
|
||||||
from ..core import app
|
from ..core import app
|
||||||
from ..utils import constants, importutil
|
from ..utils import constants, importutil
|
||||||
@@ -79,6 +79,7 @@ class PersistenceManager:
|
|||||||
self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.')
|
self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.')
|
||||||
|
|
||||||
await self.write_default_pipeline()
|
await self.write_default_pipeline()
|
||||||
|
await self.write_space_model_providers()
|
||||||
|
|
||||||
async def create_tables(self):
|
async def create_tables(self):
|
||||||
# create tables
|
# create tables
|
||||||
@@ -123,7 +124,28 @@ class PersistenceManager:
|
|||||||
|
|
||||||
await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data))
|
await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data))
|
||||||
|
|
||||||
# =================================
|
async def write_space_model_providers(self):
|
||||||
|
# write space model providers
|
||||||
|
result = await self.execute_async(
|
||||||
|
sqlalchemy.select(persistence_model.ModelProvider).where(
|
||||||
|
persistence_model.ModelProvider.requester == 'space-chat-completions'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if result.first() is None:
|
||||||
|
self.ap.logger.info('Creating space model providers...')
|
||||||
|
space_chat_completions_model_provider = {
|
||||||
|
'uuid': str(uuid.uuid4()),
|
||||||
|
'name': 'LangBot Models',
|
||||||
|
'requester': 'space-chat-completions',
|
||||||
|
'base_url': 'https://api.langbot.cloud/v1',
|
||||||
|
'api_keys': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.execute_async(
|
||||||
|
sqlalchemy.insert(persistence_model.ModelProvider).values(space_chat_completions_model_provider)
|
||||||
|
)
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
|
||||||
async def execute_async(self, *args, **kwargs) -> sqlalchemy.engine.cursor.CursorResult:
|
async def execute_async(self, *args, **kwargs) -> sqlalchemy.engine.cursor.CursorResult:
|
||||||
async with self.get_db_engine().connect() as conn:
|
async with self.get_db_engine().connect() as conn:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import typing
|
||||||
|
import openai
|
||||||
|
|
||||||
|
from . import chatcmpl
|
||||||
|
|
||||||
|
|
||||||
|
class LangBotSpaceChatCompletions(chatcmpl.OpenAIChatCompletions):
|
||||||
|
"""LangBot Space ChatCompletion API 请求器"""
|
||||||
|
|
||||||
|
client: openai.AsyncClient
|
||||||
|
|
||||||
|
default_config: dict[str, typing.Any] = {
|
||||||
|
'base_url': 'https://api.langbot.cloud/v1',
|
||||||
|
'timeout': 120,
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: LLMAPIRequester
|
||||||
|
metadata:
|
||||||
|
name: space-chat-completions
|
||||||
|
label:
|
||||||
|
en_US: Space
|
||||||
|
zh_Hans: Space
|
||||||
|
icon: space.webp
|
||||||
|
spec:
|
||||||
|
config:
|
||||||
|
- name: base_url
|
||||||
|
label:
|
||||||
|
en_US: Base URL
|
||||||
|
zh_Hans: 基础 URL
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
default: https://api.langbot.cloud/v1
|
||||||
|
- name: timeout
|
||||||
|
label:
|
||||||
|
en_US: Timeout
|
||||||
|
zh_Hans: 超时时间
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
default: 120
|
||||||
|
support_type:
|
||||||
|
- llm
|
||||||
|
- text-embedding
|
||||||
|
provider_category: maas
|
||||||
|
execution:
|
||||||
|
python:
|
||||||
|
path: ./spacechatcmpl.py
|
||||||
|
attr: LangBotSpaceChatCompletions
|
||||||
@@ -53,6 +53,7 @@ interface ModelsDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LANGBOT_MODELS_PROVIDER_NAME = 'LangBot Models';
|
const LANGBOT_MODELS_PROVIDER_NAME = 'LangBot Models';
|
||||||
|
const LANGBOT_MODELS_PROVIDER_REQUESTER = 'space-chat-completions';
|
||||||
|
|
||||||
export default function ModelsDialog({
|
export default function ModelsDialog({
|
||||||
open,
|
open,
|
||||||
@@ -253,10 +254,10 @@ export default function ModelsDialog({
|
|||||||
|
|
||||||
// Separate LangBot Models provider
|
// Separate LangBot Models provider
|
||||||
const langbotProvider = providers.find(
|
const langbotProvider = providers.find(
|
||||||
(p) => p.name === LANGBOT_MODELS_PROVIDER_NAME,
|
(p) => p.requester === LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||||
);
|
);
|
||||||
const otherProviders = providers.filter(
|
const otherProviders = providers.filter(
|
||||||
(p) => p.name !== LANGBOT_MODELS_PROVIDER_NAME,
|
(p) => p.requester !== LANGBOT_MODELS_PROVIDER_REQUESTER,
|
||||||
);
|
);
|
||||||
|
|
||||||
function renderProviderCard(
|
function renderProviderCard(
|
||||||
@@ -501,58 +502,6 @@ export default function ModelsDialog({
|
|||||||
if (langbotProvider) {
|
if (langbotProvider) {
|
||||||
return renderProviderCard(langbotProvider, true);
|
return renderProviderCard(langbotProvider, true);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Card className="mb-2">
|
|
||||||
<CardHeader className="p-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-9 h-9 rounded-lg overflow-hidden flex-shrink-0">
|
|
||||||
<img
|
|
||||||
src={langbotIcon.src}
|
|
||||||
alt="LangBot"
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-base">
|
|
||||||
{LANGBOT_MODELS_PROVIDER_NAME}
|
|
||||||
</CardTitle>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t('models.langbotModelsDescription')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{accountType !== 'space' ? (
|
|
||||||
<Button variant="outline" size="sm" onClick={handleSpaceLogin}>
|
|
||||||
<LogIn className="h-4 w-4 mr-1" />
|
|
||||||
{t('models.loginWithSpace')}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
spaceCredits !== null && (
|
|
||||||
<div className="flex items-center gap-1 border rounded-md px-2 h-8 text-sm">
|
|
||||||
<span>
|
|
||||||
{(spaceCredits / 5000).toFixed(2)} {t('models.credits')}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-5 w-5"
|
|
||||||
onClick={() =>
|
|
||||||
window.open(
|
|
||||||
`${systemInfo.cloud_service_url}/billing`,
|
|
||||||
'_blank',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFormClose() {
|
function handleFormClose() {
|
||||||
|
|||||||
+24
-8
@@ -373,11 +373,15 @@ export default function EmbeddingForm({
|
|||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{providers.map((p) => (
|
{providers
|
||||||
<SelectItem key={p.uuid} value={p.uuid}>
|
.filter(
|
||||||
{p.name} ({p.base_url || 'default'})
|
(p) => p.requester !== 'space-chat-completions',
|
||||||
</SelectItem>
|
)
|
||||||
))}
|
.map((p) => (
|
||||||
|
<SelectItem key={p.uuid} value={p.uuid}>
|
||||||
|
{p.name} ({p.base_url || 'default'})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -413,7 +417,11 @@ export default function EmbeddingForm({
|
|||||||
{t('models.modelManufacturer')}
|
{t('models.modelManufacturer')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'manufacturer')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'manufacturer' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
@@ -425,7 +433,11 @@ export default function EmbeddingForm({
|
|||||||
{t('models.aggregationPlatform')}
|
{t('models.aggregationPlatform')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'maas')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'maas' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
@@ -437,7 +449,11 @@ export default function EmbeddingForm({
|
|||||||
{t('models.selfDeployed')}
|
{t('models.selfDeployed')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'self-hosted')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'self-hosted' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
|
|||||||
@@ -385,11 +385,15 @@ export default function LLMForm({
|
|||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{providers.map((p) => (
|
{providers
|
||||||
<SelectItem key={p.uuid} value={p.uuid}>
|
.filter(
|
||||||
{p.name} ({p.base_url || 'default'})
|
(p) => p.requester !== 'space-chat-completions',
|
||||||
</SelectItem>
|
)
|
||||||
))}
|
.map((p) => (
|
||||||
|
<SelectItem key={p.uuid} value={p.uuid}>
|
||||||
|
{p.name} ({p.base_url || 'default'})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -425,7 +429,11 @@ export default function LLMForm({
|
|||||||
{t('models.modelManufacturer')}
|
{t('models.modelManufacturer')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'manufacturer')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'manufacturer' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
@@ -437,7 +445,11 @@ export default function LLMForm({
|
|||||||
{t('models.aggregationPlatform')}
|
{t('models.aggregationPlatform')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'maas')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'maas' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
@@ -449,7 +461,11 @@ export default function LLMForm({
|
|||||||
{t('models.selfDeployed')}
|
{t('models.selfDeployed')}
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
{requesterList
|
{requesterList
|
||||||
.filter((r) => r.category === 'self-hosted')
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.category === 'self-hosted' &&
|
||||||
|
r.value !== 'space-chat-completions',
|
||||||
|
)
|
||||||
.map((r) => (
|
.map((r) => (
|
||||||
<SelectItem key={r.value} value={r.value}>
|
<SelectItem key={r.value} value={r.value}>
|
||||||
{r.label}
|
{r.label}
|
||||||
|
|||||||
Reference in New Issue
Block a user