diff --git a/src/langbot/pkg/persistence/mgr.py b/src/langbot/pkg/persistence/mgr.py index 8e147799..ead20a8b 100644 --- a/src/langbot/pkg/persistence/mgr.py +++ b/src/langbot/pkg/persistence/mgr.py @@ -2,18 +2,16 @@ from __future__ import annotations import datetime import typing -import json -import uuid + import sqlalchemy.ext.asyncio as sqlalchemy_asyncio import sqlalchemy from . import database, migration -from ..entity.persistence import base, pipeline, metadata, model as persistence_model +from ..entity.persistence import base, metadata, model as persistence_model from ..entity import persistence from ..core import app from ..utils import constants, importutil -from ..api.http.service import pipeline as pipeline_service from . import databases, migrations importutil.import_modules_in_pkg(databases) @@ -78,7 +76,6 @@ class PersistenceManager: self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.') - await self.write_default_pipeline() await self.write_space_model_providers() async def create_tables(self): @@ -101,29 +98,6 @@ class PersistenceManager: if row is None: await self.execute_async(sqlalchemy.insert(metadata.Metadata).values(item)) - async def write_default_pipeline(self): - # write default pipeline - result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline)) - default_pipeline_uuid = None - if result.first() is None: - self.ap.logger.info('Creating default pipeline...') - - pipeline_config = json.loads(importutil.read_resource_file('templates/default-pipeline-config.json')) - - default_pipeline_uuid = str(uuid.uuid4()) - pipeline_data = { - 'uuid': default_pipeline_uuid, - 'for_version': self.ap.ver_mgr.get_current_version(), - 'stages': pipeline_service.default_stage_order, - 'is_default': True, - 'name': 'ChatPipeline', - 'description': 'Default pipeline, new bots will be bound to this pipeline | 默认提供的流水线,您配置的机器人将自动绑定到此流水线', - 'config': pipeline_config, - 'extensions_preferences': {}, - } - - await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data)) - async def write_space_model_providers(self): space_models_gateway_api_url = self.ap.instance_config.data.get('space', {}).get( 'models_gateway_api_url', 'https://api.langbot.cloud/v1' diff --git a/src/langbot/templates/metadata/pipeline/ai.yaml b/src/langbot/templates/metadata/pipeline/ai.yaml index 46f5d463..c94a1f4c 100644 --- a/src/langbot/templates/metadata/pipeline/ai.yaml +++ b/src/langbot/templates/metadata/pipeline/ai.yaml @@ -74,6 +74,10 @@ stages: type: integer required: true default: 10 + show_if: + field: __system.is_wizard + operator: neq + value: true - name: prompt label: en_US: Prompt @@ -83,6 +87,9 @@ stages: zh_Hans: 除非您了解消息结构,否则请只使用 system 单提示词 type: prompt-editor required: true + default: + - role: system + content: "You are a helpful assistant." - name: knowledge-bases label: en_US: Knowledge Bases @@ -93,6 +100,10 @@ stages: type: knowledge-base-multi-selector required: false default: [] + show_if: + field: __system.is_wizard + operator: neq + value: true - name: tbox-app-api label: en_US: Tbox App API @@ -107,12 +118,14 @@ stages: zh_Hans: API 密钥 type: string required: true + default: '' - name: app-id label: en_US: App ID zh_Hans: 应用 ID type: string required: true + default: '' - name: dify-service-api label: en_US: Dify Service API @@ -127,6 +140,7 @@ stages: zh_Hans: 基础 URL type: string required: true + default: 'https://api.dify.ai/v1' - name: base-prompt label: en_US: Base PROMPT @@ -163,6 +177,7 @@ stages: zh_Hans: API 密钥 type: string required: true + default: 'your-api-key' - name: dashscope-app-api label: en_US: Aliyun Dashscope App API @@ -193,12 +208,14 @@ stages: zh_Hans: API 密钥 type: string required: true + default: 'your-api-key' - name: app-id label: en_US: App ID zh_Hans: 应用 ID type: string required: true + default: 'your-app-id' - name: references_quote label: en_US: References Quote @@ -226,6 +243,7 @@ stages: zh_Hans: n8n 工作流的 webhook URL type: string required: true + default: 'http://your-n8n-webhook-url' - name: auth-type label: en_US: Authentication Type @@ -263,6 +281,10 @@ stages: type: string required: false default: '' + show_if: + field: auth-type + operator: eq + value: 'basic' - name: basic-password label: en_US: Password @@ -273,6 +295,10 @@ stages: type: string required: false default: '' + show_if: + field: auth-type + operator: eq + value: 'basic' - name: jwt-secret label: en_US: Secret @@ -283,6 +309,10 @@ stages: type: string required: false default: '' + show_if: + field: auth-type + operator: eq + value: 'jwt' - name: jwt-algorithm label: en_US: Algorithm @@ -293,6 +323,10 @@ stages: type: string required: false default: 'HS256' + show_if: + field: auth-type + operator: eq + value: 'jwt' - name: header-name label: en_US: Header Name @@ -303,6 +337,10 @@ stages: type: string required: false default: '' + show_if: + field: auth-type + operator: eq + value: 'header' - name: header-value label: en_US: Header Value @@ -313,6 +351,10 @@ stages: type: string required: false default: '' + show_if: + field: auth-type + operator: eq + value: 'header' - name: timeout label: en_US: Timeout @@ -350,6 +392,7 @@ stages: zh_Hans: Langflow 服务器的基础 URL type: string required: true + default: 'http://localhost:7860' - name: api-key label: en_US: API Key @@ -359,6 +402,7 @@ stages: zh_Hans: Langflow 服务器的 API 密钥 type: string required: true + default: 'your-api-key' - name: flow-id label: en_US: Flow ID @@ -368,6 +412,7 @@ stages: zh_Hans: 要运行的流程 ID type: string required: true + default: 'your-flow-id' - name: input-type label: en_US: Input Type @@ -415,6 +460,7 @@ stages: zh_Hans: Coze服务器的 API 密钥 type: string required: true + default: '' - name: bot-id label: en_US: Bot ID @@ -424,6 +470,7 @@ stages: zh_Hans: 要运行的机器人 ID type: string required: true + default: '' - name: api-base label: en_US: API Base URL diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx index bf9ec024..cb4c12a1 100644 --- a/web/src/app/auth/space/callback/page.tsx +++ b/web/src/app/auth/space/callback/page.tsx @@ -46,8 +46,12 @@ function SpaceOAuthCallbackContent() { } setStatus('success'); toast.success(t('common.spaceLoginSuccess')); + + // If wizard state exists, redirect back to wizard instead of home + const wizardState = localStorage.getItem('langbot_wizard_state'); + const redirectTo = wizardState ? '/wizard' : '/home'; setTimeout(() => { - router.push('/home'); + router.push(redirectTo); }, 1000); } catch (err) { setStatus('error'); diff --git a/web/src/app/home/bots/components/bot-log/view/BotLogCard.tsx b/web/src/app/home/bots/components/bot-log/view/BotLogCard.tsx index c3d9755f..3dc13066 100644 --- a/web/src/app/home/bots/components/bot-log/view/BotLogCard.tsx +++ b/web/src/app/home/bots/components/bot-log/view/BotLogCard.tsx @@ -19,11 +19,17 @@ const LEVEL_STYLES: Record = { const SHORT_TEXT_LIMIT = 120; -export function BotLogCard({ botLog }: { botLog: BotLog }) { +export function BotLogCard({ + botLog, + defaultExpanded = false, +}: { + botLog: BotLog; + defaultExpanded?: boolean; +}) { const { t } = useTranslation(); const baseURL = httpClient.getBaseUrl(); const [copied, setCopied] = useState(false); - const [expanded, setExpanded] = useState(false); + const [expanded, setExpanded] = useState(defaultExpanded); function copySessionId() { const text = botLog.message_session_id; diff --git a/web/src/app/home/bots/components/bot-log/view/BotLogListComponent.tsx b/web/src/app/home/bots/components/bot-log/view/BotLogListComponent.tsx index 41010033..e8ac519e 100644 --- a/web/src/app/home/bots/components/bot-log/view/BotLogListComponent.tsx +++ b/web/src/app/home/bots/components/bot-log/view/BotLogListComponent.tsx @@ -17,7 +17,20 @@ import { debounce } from 'lodash'; import { useTranslation } from 'react-i18next'; import { useRouter } from 'next/navigation'; -export function BotLogListComponent({ botId }: { botId: string }) { +export function BotLogListComponent({ + botId, + autoExpandImages = false, + hideDetailedLogsLink = false, + hideToolbar = false, +}: { + botId: string; + /** When true, log entries with images are rendered expanded by default */ + autoExpandImages?: boolean; + /** When true, hides the "View Detailed Logs" navigation button */ + hideDetailedLogsLink?: boolean; + /** When true, hides the entire toolbar (auto-refresh, level filter, detailed logs link) */ + hideToolbar?: boolean; +}) { const { t } = useTranslation(); const router = useRouter(); const manager = useRef(new BotLogManager(botId)).current; @@ -158,77 +171,91 @@ export function BotLogListComponent({ botId }: { botId: string }) { ref={listContainerRef} > {/* Toolbar */} -
- {/* Auto-refresh toggle */} -
- - {t('bots.enableAutoRefresh')} - - setAutoFlush(v)} - /> -
+ {!hideToolbar && ( +
+ {/* Auto-refresh toggle */} +
+ + {t('bots.enableAutoRefresh')} + + setAutoFlush(v)} + /> +
- {/* Level filter */} -
- - {t('bots.logLevel')} - - - - - - -
- {logLevels.map((level) => ( -
- handleLevelToggle(level.value)} - /> -
+ handleLevelToggle(level.value)} + /> + +
+ ))} +
+ + +
- {/* Link to detailed logs */} - -
+ {/* Link to detailed logs */} + {!hideDetailedLogsLink && ( + + )} + + )} {/* Log cards */} -
- {filteredLogs.map((botLog) => ( - - ))} -
+ {filteredLogs.length === 0 ? ( +
+

{t('bots.noLogs')}

+
+ ) : ( +
+ {filteredLogs.map((botLog) => ( + 0} + /> + ))} +
+ )} ); } diff --git a/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx index 4de1dad1..f9e86815 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx @@ -16,6 +16,30 @@ import { extractI18nObject } from '@/i18n/I18nProvider'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; +/** + * Resolve the value referenced by a `show_if.field` string. + * + * Fields prefixed with `__system.` are looked up in the caller-supplied + * `systemContext` dictionary (e.g. `__system.is_wizard` → `systemContext.is_wizard`). + * All other field names are resolved from the live form values first, then + * fall back to `externalDependentValues`. + */ +function resolveShowIfValue( + field: string, + watchedValues: Record, + externalDependentValues?: Record, + systemContext?: Record, +): unknown { + if (field.startsWith('__system.')) { + const key = field.slice('__system.'.length); + return systemContext?.[key]; + } + if (watchedValues[field] !== undefined) { + return watchedValues[field]; + } + return externalDependentValues?.[field]; +} + export default function DynamicFormComponent({ itemConfigList, onSubmit, @@ -23,6 +47,7 @@ export default function DynamicFormComponent({ onFileUploaded, isEditing, externalDependentValues, + systemContext, }: { itemConfigList: IDynamicFormItemSchema[]; onSubmit?: (val: object) => unknown; @@ -30,6 +55,9 @@ export default function DynamicFormComponent({ onFileUploaded?: (fileKey: string) => void; isEditing?: boolean; externalDependentValues?: Record; + /** Extra variables accessible via the `__system.*` namespace in show_if conditions. + * e.g. `{ is_wizard: true }` makes `show_if: { field: "__system.is_wizard", ... }` work. */ + systemContext?: Record; }) { const isInitialMount = useRef(true); const previousInitialValues = useRef(initialValues); @@ -61,6 +89,13 @@ export default function DynamicFormComponent({ fallbacks: [], }; } + if (item.type === 'prompt-editor') { + if (Array.isArray(value)) { + return value; + } + // Default to a single empty system prompt entry + return [{ role: 'system', content: '' }]; + } return value; }; @@ -241,14 +276,12 @@ export default function DynamicFormComponent({
{itemConfigList.map((config) => { if (config.show_if) { - const dependValue = - watchedValues[ - config.show_if.field as keyof typeof watchedValues - ] !== undefined - ? watchedValues[ - config.show_if.field as keyof typeof watchedValues - ] - : externalDependentValues?.[config.show_if.field]; + const dependValue = resolveShowIfValue( + config.show_if.field, + watchedValues as Record, + externalDependentValues, + systemContext, + ); if ( config.show_if.operator === 'eq' && diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 367657cf..78abf506 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -775,10 +775,18 @@ export default function DynamicFormItemComponent({ ); - case DynamicFormItemType.PROMPT_EDITOR: + case DynamicFormItemType.PROMPT_EDITOR: { + // Guard: field.value may be undefined when the form resets or + // initialValues haven't propagated yet. Fall back to a default + // single system-prompt entry to prevent the .map() crash. + const promptItems: { role: string; content: string }[] = Array.isArray( + field.value, + ) + ? field.value + : [{ role: 'system', content: '' }]; return (
- {field.value.map( + {promptItems.map( (item: { role: string; content: string }, index: number) => (
{/* 角色选择 */} @@ -790,7 +798,7 @@ export default function DynamicFormItemComponent({