From fe324fac818bc8dc11ff710a385d5aa5b89d5ab9 Mon Sep 17 00:00:00 2001 From: Hyu Date: Fri, 9 Oct 2026 00:29:39 +0800 Subject: [PATCH] fix(web): improve assistant setup and model login guidance --- .../home/components/WorkspaceAssistant.tsx | 320 ++++++++++-------- .../models-dialog/components/ProviderCard.tsx | 20 +- web/src/i18n/locales/en-US.ts | 3 +- web/src/i18n/locales/ja-JP.ts | 2 +- web/src/i18n/locales/zh-Hans.ts | 7 +- 5 files changed, 197 insertions(+), 155 deletions(-) diff --git a/web/src/app/home/components/WorkspaceAssistant.tsx b/web/src/app/home/components/WorkspaceAssistant.tsx index d7c42f3af..82d8ff235 100644 --- a/web/src/app/home/components/WorkspaceAssistant.tsx +++ b/web/src/app/home/components/WorkspaceAssistant.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useId } from 'react'; +import { useEffect, useRef, useState, useId, type ReactNode } from 'react'; import { createPortal } from 'react-dom'; import { Plus, Send, ChevronDown, LoaderCircle, Square } from 'lucide-react'; import { @@ -14,7 +14,12 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeHighlight from 'rehype-highlight'; import '@/styles/github-markdown.css'; -import { backendClient, useCurrentWorkspace, userInfo } from '@/app/infra/http'; +import { + backendClient, + systemInfo, + useCurrentWorkspace, + userInfo, +} from '@/app/infra/http'; import { toast } from 'sonner'; import { httpClient } from '@/app/infra/http/HttpClient'; import SettingsDialog, { @@ -31,6 +36,7 @@ import { import DynamicFormItemComponent from './dynamic-form/DynamicFormItemComponent'; import { DynamicFormItemType } from '@/app/infra/entities/form/dynamic'; import AssistantToolResult, { AssistantTool } from './AssistantToolResult'; +import { LANGBOT_MODELS_PROVIDER_REQUESTER } from './models-dialog/types'; type Conversation = { uuid: string; @@ -68,6 +74,9 @@ export default function WorkspaceAssistant() { function AssistantEntry({ storageKey }: { storageKey: string }) { const { t } = useTranslation(); const [available, setAvailable] = useState(null); + const [localModel, setLocalModel] = useState< + { uuid: string; name: string } | undefined + >(); const [failed, setFailed] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [section, setSection] = useState('models'); @@ -78,9 +87,25 @@ function AssistantEntry({ storageKey }: { storageKey: string }) { let active = true; async function check() { try { - const { providers } = await backendClient.getModelProviders(); + const { models } = await backendClient.getProviderLLMModels(); if (active) { - setAvailable(providers.length > 0); + const usableModels = models.filter( + (model) => + !!model.uuid?.trim() && + model.abilities?.includes('func_call') && + (model.provider?.requester !== + LANGBOT_MODELS_PROVIDER_REQUESTER || + (userInfo?.account_type === 'space' && + !systemInfo.disable_models_service)), + ); + setAvailable(usableModels.length > 0); + const firstLocalModel = + userInfo?.account_type !== 'space' ? usableModels[0] : undefined; + setLocalModel( + firstLocalModel + ? { uuid: firstLocalModel.uuid, name: firstLocalModel.name } + : undefined, + ); setFailed(false); } } catch { @@ -98,8 +123,8 @@ function AssistantEntry({ storageKey }: { storageKey: string }) { async function login() { setLoginBusy(true); try { - const response = await httpClient.getSpaceAuthorizeUrl( - `${window.location.origin}/auth/space/callback`, + const response = await httpClient.getSpaceBindAuthorizeUrl( + `${window.location.origin}/auth/space/callback?mode=bind`, ); window.location.href = response.authorize_url; } catch { @@ -110,29 +135,25 @@ function AssistantEntry({ storageKey }: { storageKey: string }) { return ( <> - {available ? ( - - ) : available === false || failed ? ( - createPortal( - , - document.body, - ) - ) : null} + + } + /> value + 1); } - return ( - - ); + return ; } const ASSISTANT_HEIGHT_STORAGE_KEY = 'langbot-assistant-panel-height'; @@ -194,12 +220,16 @@ const ASSISTANT_POSITION_STORAGE_KEY = 'langbot-assistant-panel-position'; function AssistantPanel({ storageKey, + canUseModels, + initialModel, + setup, onSelect, -}: { - storageKey: string; +}: AssistantPanelProps & { onSelect: (id: string | null) => void; }) { const { t } = useTranslation(); + const initialModelUuid = initialModel?.uuid; + const initialModelName = initialModel?.name; const [open, setOpen] = useState(true); const [closing, setClosing] = useState(false); const [horizontalPosition, setHorizontalPosition] = useState(() => { @@ -331,13 +361,16 @@ function AssistantPanel({ if (abort.signal.aborted) return; setConversation(saved); } + if (!canUseModels) return; const model = saved?.model_uuid ? { uuid: saved.model_uuid, name: saved.model_name ?? undefined } - : await backendClient.request<{ uuid: string; name: string }>({ - method: 'GET', - url: '/api/v1/assistant/recommended-model', - signal: abort.signal, - }); + : initialModelUuid + ? { uuid: initialModelUuid, name: initialModelName } + : await backendClient.request<{ uuid: string; name: string }>({ + method: 'GET', + url: '/api/v1/assistant/recommended-model', + signal: abort.signal, + }); if (!abort.signal.aborted && !manualModel.current) { setModelUuid(model.uuid); setModelName(model.name); @@ -350,7 +383,7 @@ function AssistantPanel({ } void initialize(); return () => abort.abort(); - }, [storageKey]); + }, [storageKey, canUseModels, initialModelUuid, initialModelName]); useEffect(() => { let active = true; @@ -438,6 +471,7 @@ function AssistantPanel({ async function submit(approved?: boolean) { if ( + !canUseModels || busy || (approved === undefined && (!text.trim() || @@ -893,7 +927,7 @@ function AssistantPanel({