fix(migration): improve runner migration and plugin installation feedback

This commit is contained in:
RockChinQ
2026-09-18 13:10:32 +08:00
parent e77acfa3ab
commit 60606e1997
28 changed files with 1197 additions and 131 deletions
@@ -0,0 +1,213 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
CheckCircle2,
ChevronDown,
Loader2,
Puzzle,
XCircle,
} from 'lucide-react';
import type { MigrationInstallation } from '@/app/infra/entities/api/pipeline-migration';
import type { PluginV4 } from '@/app/infra/entities/plugin';
import { getCloudServiceClientSync } from '@/app/infra/http';
import { extractI18nObject } from '@/i18n/I18nProvider';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
const stageKeys: Record<string, string> = {
checking: 'pipelineMigration.checkingPlugin',
downloading: 'plugins.installProgress.downloading',
validating: 'plugins.installProgress.validating',
preparing: 'pipelineMigration.preparingPlugin',
installing_deps: 'plugins.installProgress.installingDeps',
activating: 'plugins.installProgress.initializing',
refreshing: 'plugins.installProgress.activating',
done: 'plugins.installProgress.completed',
};
function bytes(value: number) {
return value < 1024 * 1024
? `${(value / 1024).toFixed(1)} KB`
: `${(value / 1024 / 1024).toFixed(1)} MB`;
}
function InstallationPluginIdentity({ item }: { item: MigrationInstallation }) {
const { t } = useTranslation();
const [plugin, setPlugin] = useState<PluginV4 | null>(null);
useEffect(() => {
let active = true;
void getCloudServiceClientSync()
.getPluginDetail(item.author, item.name)
.then(({ plugin }) => {
if (active) setPlugin(plugin);
})
.catch(() => {
// Marketplace metadata is optional and must not interrupt installation.
});
return () => {
active = false;
};
}, [item.author, item.name]);
const name = (plugin?.label && extractI18nObject(plugin.label)) || item.name;
const description =
(plugin?.description && extractI18nObject(plugin.description)) ||
t('market.noDescription');
const iconURL = getCloudServiceClientSync().resolveMarketplaceIconURL(
'plugin',
item.author,
item.name,
plugin?.icon,
);
return (
<div className="flex items-start gap-3">
<Avatar className="size-10 rounded-lg">
<AvatarImage src={iconURL} alt={name} className="object-contain" />
<AvatarFallback className="rounded-lg">
<Puzzle className="size-5 text-muted-foreground" />
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-center gap-2">
<span
className="min-w-0 flex-1 truncate text-sm font-medium"
title={name}
>
{name}
</span>
<Badge variant="secondary" className="shrink-0">
{item.version}
</Badge>
</div>
<p
className="line-clamp-2 break-words text-xs text-muted-foreground"
title={description}
>
{description}
</p>
</div>
</div>
);
}
export default function MigrationInstallProgress({
installations,
}: {
installations: MigrationInstallation[];
}) {
const { t } = useTranslation();
if (!installations.length) return null;
return (
<div className="space-y-2" data-testid="migration-installations">
{installations.map((item) => {
const failed = item.status === 'failed';
const done = item.status === 'completed';
const Icon = failed ? XCircle : done ? CheckCircle2 : Loader2;
const label = t(
stageKeys[item.stage] ?? 'pipelineMigration.installing',
);
const end = item.finished_at ?? item.updated_at;
const seconds = (start: number, finish: number) =>
`${Math.max(0, finish - start).toFixed(1)} s`;
return (
<Collapsible
key={`${item.author}/${item.name}/${item.version}`}
className="rounded-md border p-3"
>
<InstallationPluginIdentity item={item} />
<div className="mt-3 flex items-center justify-between gap-2 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<Icon
className={`size-3.5 shrink-0 ${failed ? 'text-destructive' : !done ? 'animate-spin' : 'text-green-600'}`}
/>
{failed ? t('plugins.installProgress.failed') : label}
</span>
<span className="tabular-nums">
{seconds(item.started_at, end)}
</span>
</div>
{!failed && !done && (
<Progress
className="mt-2 h-1.5"
value={item.progress_percent ?? 0}
aria-label={t('pipelineMigration.stageProgress')}
/>
)}
{failed && (
<p className="mt-2 text-xs text-destructive">
{t(
item.code === 'plugin_runtime_unavailable'
? 'pipelineMigration.notices.runtimeUnavailable'
: `pipelineMigration.installErrors.${item.code}`,
{ defaultValue: t('pipelineMigration.installFailed') },
)}
</p>
)}
<CollapsibleTrigger asChild>
<Button
variant="ghost"
size="sm"
className="group mt-1 h-7 px-0 text-xs text-muted-foreground hover:bg-transparent"
>
<ChevronDown className="size-3 group-data-[state=open]:rotate-180" />
{t('pipelineMigration.installDetails')}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-2 pt-1 text-xs">
<p className="break-all text-muted-foreground">
{item.author}/{item.name}
</p>
{item.steps.map((step, index) => (
<div key={index} className="flex justify-between gap-2">
<span
className={
failed && index === item.steps.length - 1
? 'text-destructive'
: ''
}
>
{t(stageKeys[step.stage] ?? 'pipelineMigration.installing')}
</span>
<span className="shrink-0 tabular-nums text-muted-foreground">
{seconds(step.started_at, step.finished_at ?? end)}
</span>
</div>
))}
{item.download_current !== undefined && (
<p className="text-muted-foreground">
{t('pipelineMigration.downloaded', {
size: bytes(item.download_current),
})}
{item.download_total
? ` / ${bytes(item.download_total)}`
: ''}
{item.status === 'installing' &&
item.stage === 'downloading' &&
item.download_speed
? ` · ${bytes(item.download_speed)}/s`
: ''}
</p>
)}
{item.deps_total !== undefined && (
<p className="text-muted-foreground">
{t('plugins.installProgress.depsInfo', {
count: item.deps_total,
})}
</p>
)}
</CollapsibleContent>
</Collapsible>
);
})}
</div>
);
}
@@ -3,9 +3,11 @@ import { useTranslation } from 'react-i18next';
import { AlertTriangle, ChevronDown, Loader2 } from 'lucide-react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { getCurrentWorkspaceSnapshot } from '@/app/infra/http/currentWorkspaceStore';
import MigrationInstallProgress from './MigrationInstallProgress';
import { migrationIssueKey } from './pipeline-migration-issues';
import type { CurrentWorkspace } from '@/app/infra/entities/workspace';
import type {
MigrationInstallation,
PipelineMigrationIssue,
PipelineMigrationPreview,
PipelineMigrationResult,
@@ -70,6 +72,9 @@ export default function PipelineMigration({
const [phase, setPhase] = useState('migrating');
const [dataOnly, setDataOnly] = useState(false);
const [status, setStatus] = useState<Status>('idle');
const [installations, setInstallations] = useState<MigrationInstallation[]>(
[],
);
const [results, setResults] = useState<PipelineMigrationResult[]>([]);
const active = useRef(false);
const submitting = useRef(false);
@@ -135,6 +140,12 @@ export default function PipelineMigration({
}, [refresh]);
const busy = status === 'submitting' || status === 'running';
const completed =
status === 'finished' &&
results.length > 0 &&
results.every((item) =>
['migrated', 'already_current'].includes(item.state),
);
const rows = preview?.items ?? [];
const count = rows.filter(
(item) => !['already_current', 'not_legacy'].includes(item.state),
@@ -142,6 +153,17 @@ export default function PipelineMigration({
function renderIssue(issue: PipelineMigrationIssue, warning = false) {
// Unknown server codes use localized fallbacks, never raw upstream messages.
if (
[
'plugin_version_unavailable',
'plugin_download_timeout',
'plugin_marketplace_unavailable',
'plugin_download_failed',
'dependency_prepare_failed',
'plugin_launch_failed',
].includes(issue.code)
)
return <span>{t(`pipelineMigration.installErrors.${issue.code}`)}</span>;
if (issue.code === 'plugin_install_failed')
return <span>{t('pipelineMigration.installFailed')}</span>;
const key = migrationIssueKey(issue.code);
@@ -169,6 +191,7 @@ export default function PipelineMigration({
let items = rows.filter(
(item) => !['already_current', 'not_legacy'].includes(item.state),
);
setInstallations([]);
setDataOnly(!installPlugins);
setPhase(installPlugins ? 'installing' : 'migrating');
submitting.current = true;
@@ -267,6 +290,8 @@ export default function PipelineMigration({
},
);
setResults(scopedResults);
if (Array.isArray(metadata.installations))
setInstallations(metadata.installations as MigrationInstallation[]);
if (metadata.phase === 'installing' || metadata.phase === 'migrating')
setPhase(metadata.phase);
if (task.runtime.done) {
@@ -309,7 +334,7 @@ export default function PipelineMigration({
function changeOpen(next: boolean) {
setOpen(next);
if (!submitting.current) {
if (next) void refresh(status === 'idle');
if (next) void refresh();
}
}
@@ -386,7 +411,17 @@ export default function PipelineMigration({
{t('pipelineMigration.detected', { count })}
</p>
)}
{!busy && (count > 0 || results.length > 0) && (
<MigrationInstallProgress installations={installations} />
{busy && results.length > 0 && (
<p className="text-xs text-muted-foreground">
{t('pipelineMigration.processed', {
completed: results.filter((r) => r.state !== 'pending')
.length,
total: results.length,
})}
</p>
)}
{(count > 0 || results.length > 0) && (
<Collapsible>
<CollapsibleTrigger asChild>
<Button
@@ -439,6 +474,14 @@ export default function PipelineMigration({
{item.target_plugin.name}
</p>
)}
{item.warnings.some(
(issue) =>
issue.code === 'local.box_state_reset',
) && (
<p className="text-xs text-muted-foreground">
{t('pipelineMigration.notices.boxReset')}
</p>
)}
{result?.code && result.code !== 'data_only' ? (
<p className="text-xs text-destructive">
{renderIssue({ code: result.code })}
@@ -472,39 +515,36 @@ export default function PipelineMigration({
)}
</div>
<DialogFooter className="shrink-0 flex-col gap-2 sm:flex-col">
{!busy && (
<>
<Button
disabled={!canManage || loading || !valid || !count}
className="w-full"
onClick={() => void execute(true)}
>
{t('pipelineMigration.autoInstall')}
</Button>
<Button
variant="outline"
disabled={!canManage || loading || !valid || !count}
className="w-full"
onClick={() => void execute(false)}
>
{t('pipelineMigration.dataOnly')}
</Button>
<p className="text-center text-xs text-muted-foreground">
{t('pipelineMigration.dataOnlyHint')}
</p>
{(status !== 'idle' || previewError) && (
{completed ? (
<Button
className="w-full"
onClick={() => window.location.reload()}
>
{t('pipelineMigration.complete')}
</Button>
) : (
!busy && (
<>
<Button
disabled={!canManage || loading || !valid || !count}
className="w-full"
onClick={() => void execute(true)}
>
{t('pipelineMigration.autoInstall')}
</Button>
<Button
variant="outline"
disabled={loading}
onClick={() => {
setStatus('idle');
void refresh();
}}
disabled={!canManage || loading || !valid || !count}
className="w-full"
onClick={() => void execute(false)}
>
{t('pipelineMigration.refresh')}
{t('pipelineMigration.dataOnly')}
</Button>
)}
</>
<p className="text-center text-xs text-muted-foreground">
{t('pipelineMigration.dataOnlyHint')}
</p>
</>
)
)}
<Button variant="ghost" onClick={() => changeOpen(false)}>
{t('common.close')}
@@ -1,4 +1,6 @@
const issueKeys: Record<string, string> = {
plugin_runtime_unavailable: 'runtimeUnavailable',
migration_failed: 'executionFailed',
plugin_missing: 'pluginRequired',
plugin_disabled: 'pluginRequired',
'local.context_defaults': 'contextDefaults',
@@ -25,7 +27,9 @@ const issueKeys: Record<string, string> = {
'runtime.schema_invalid': 'schemaChanged',
'runtime.schema_missing': 'schemaChanged',
'extensions.runner_excluded': 'runnerExcluded',
'local.box_scope': 'boxScope',
'local.box_template_invalid': 'boxTemplateInvalid',
runner_schema_incompatible: 'schemaChanged',
plugin_version_incompatible: 'pluginVersion',
'runtime.pending_interaction': 'pendingInteraction',
};
@@ -55,4 +55,23 @@ export interface PipelineMigrationTaskMetadata {
kind: 'pipeline_migration';
phase?: 'installing' | 'migrating' | 'finished';
results: PipelineMigrationResult[];
installations?: MigrationInstallation[];
}
export interface MigrationInstallation {
author: string;
name: string;
version: string;
status: 'installing' | 'completed' | 'failed';
stage: string;
code: string | null;
progress_percent?: number;
download_current?: number;
download_total?: number;
download_speed?: number;
deps_total?: number;
started_at: number;
updated_at: number;
finished_at: number | null;
steps: { stage: string; started_at: number; finished_at: number | null }[];
}
+1
View File
@@ -9,6 +9,7 @@ const Progress = React.forwardRef<
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
value={value}
className={cn(
'relative h-2 w-full overflow-hidden rounded-full bg-primary/20',
className,
@@ -1,4 +1,25 @@
export default {
complete: 'Done',
checkingPlugin: 'Check plugin',
preparingPlugin: 'Prepare installation',
installDetails: 'Installation details',
stageProgress: 'Stage progress',
downloaded: 'Downloaded {{size}}',
processed: 'Processed {{completed}} / {{total}} pipelines',
installErrors: {
plugin_version_unavailable:
'The required plugin version is not available in the marketplace. Retry after it is published, or migrate data only.',
plugin_download_timeout:
'Plugin download timed out. Check the network and retry.',
plugin_marketplace_unavailable:
'The marketplace could not serve the plugin package. Retry later.',
plugin_download_failed:
'Could not download the plugin. Check the network and retry.',
dependency_prepare_failed:
'Plugin dependencies could not be installed. Check the runtime environment and retry.',
plugin_launch_failed:
'The plugin could not start. Check the runtime environment and retry.',
},
autoDescription:
'Legacy runners are now plugins. Migrate all pipelines while keeping your settings. Original configurations are backed up; conversations start fresh.',
viewPipelines: 'View pipelines',
@@ -10,12 +31,15 @@ export default {
migrating: 'Migrating pipelines…',
summary: '{{migrated}} migrated; {{remaining}} need attention.',
installFailed:
'Plugin installation failed. Check your network and extension quota, then retry, or migrate data only.',
'Plugin installation failed. Expand the details to see the failed stage, then retry or migrate data only.',
activationRetryHint:
'After checking the runtime, refresh, select this pipeline and confirm to retry activation only. Its saved configuration will not be migrated again.',
details: 'Migration details',
notices: {
runtimeUnavailable:
'The plugin runtime is disconnected. Restore the connection and retry, or migrate data only.',
executionFailed: 'Migration failed. Check the server logs and retry.',
pluginRequired:
'Install or enable the runner plugin shown above, then refresh the preview.',
legacyArchive:
@@ -28,7 +52,7 @@ export default {
retrievalDefaults:
'Retrieval uses the new top-k and result limits. Review them after migration.',
boxReset:
'Existing Box session state is not transferred; a new isolated session will be created.',
'Sandbox reuse settings are preserved. Existing container state is not migrated; new sandboxes follow the same reuse rules.',
persistentHistory:
'New conversations use persistent, isolated history. Existing remote history is not imported.',
tweaksDefault: 'Langflow tweaks default to an empty object.',
@@ -53,8 +77,8 @@ export default {
'The installed runner configuration does not match the migration target. Check the plugin version and refresh.',
runnerExcluded:
'This pipeline excludes the required runner plugin. Update its extension settings first.',
boxScope:
'A custom Box session template cannot be migrated safely. Remove it or review the isolation requirements first.',
boxTemplateInvalid:
'Invalid sandbox reuse template. Use {variable_name}; positional fields, format conversions and attribute access are not supported.',
pendingInteraction:
'A conversation is waiting for input. Complete or cancel it before migration.',
},
@@ -1,4 +1,24 @@
export default {
complete: 'Finalizar',
checkingPlugin: 'Comprobar plugin',
preparingPlugin: 'Preparar instalación',
installDetails: 'Detalles de instalación',
stageProgress: 'Progreso por etapas',
downloaded: 'Descargado {{size}}',
processed: 'Procesados {{completed}} / {{total}} pipelines',
installErrors: {
plugin_version_unavailable:
'La versión requerida aún no está disponible en el mercado. Reintenta tras su publicación o migra solo los datos.',
plugin_download_timeout:
'La descarga agotó el tiempo de espera. Revisa la red y reintenta.',
plugin_marketplace_unavailable:
'El mercado no puede servir el paquete. Reintenta más tarde.',
plugin_download_failed: 'No se pudo descargar el plugin. Revisa la red.',
dependency_prepare_failed:
'No se pudieron instalar las dependencias. Revisa el entorno de ejecución.',
plugin_launch_failed:
'El plugin no pudo iniciarse. Revisa el entorno de ejecución.',
},
autoDescription:
'Los ejecutores antiguos ahora son plugins. Migra todos los pipelines conservando sus ajustes. Se guardará una copia de la configuración y las conversaciones empezarán de nuevo.',
viewPipelines: 'Ver pipelines',
@@ -10,12 +30,16 @@ export default {
migrating: 'Migrando pipelines…',
summary: '{{migrated}} migrados; {{remaining}} requieren atención.',
installFailed:
'No se pudieron instalar los plugins. Comprueba la red y la cuota de extensiones y reintenta, o migra solo los datos.',
'La instalación falló. Consulta la etapa fallida en los detalles y reintenta, o migra solo los datos.',
activationRetryHint:
'Tras comprobar el entorno, actualiza, selecciona esta canalización y confirma para reintentar solo la activación. No se volverá a migrar la configuración guardada.',
details: 'Detalles de migración',
notices: {
runtimeUnavailable:
'El entorno de plugins está desconectado. Restablece la conexión y reintenta, o migra solo los datos.',
executionFailed:
'La migración falló. Revisa los registros del servidor y reintenta.',
pluginRequired:
'Instala o activa el plugin de ejecución indicado arriba y actualiza la vista previa.',
legacyArchive:
@@ -28,7 +52,7 @@ export default {
retrievalDefaults:
'La recuperación usará los nuevos límites de top-k y longitud. Revísalos después de migrar.',
boxReset:
'No se transfiere el estado de Box; se creará una nueva sesión aislada.',
'Se conserva la configuración de reutilización del sandbox. El estado del contenedor no se migra; los nuevos sandboxes siguen las mismas reglas.',
persistentHistory:
'Las nuevas conversaciones tendrán un historial persistente y aislado. No se importa el historial remoto anterior.',
tweaksDefault:
@@ -59,8 +83,8 @@ export default {
'La configuración del Runner instalado no coincide con el destino. Revisa la versión del plugin y actualiza.',
runnerExcluded:
'Esta canalización excluye el plugin Runner necesario. Ajusta primero la configuración de extensiones.',
boxScope:
'La plantilla personalizada de sesión de Box no se puede migrar de forma segura. Elimínala o revisa los requisitos de aislamiento.',
boxTemplateInvalid:
'Plantilla de reutilización inválida. Use {nombre_variable}; no se admiten campos posicionales, conversiones de formato ni acceso a atributos.',
pendingInteraction:
'Hay una conversación esperando una respuesta. Complétala o cancélala antes de migrar.',
},
@@ -1,4 +1,25 @@
export default {
complete: '完了',
checkingPlugin: 'プラグインを確認',
preparingPlugin: 'インストールを準備',
installDetails: 'インストール詳細',
stageProgress: '段階の進捗',
downloaded: 'ダウンロード済み {{size}}',
processed: '{{completed}} / {{total}} 件のパイプラインを処理済み',
installErrors: {
plugin_version_unavailable:
'必要なバージョンはまだマーケットにありません。公開後に再試行するかデータのみ移行してください。',
plugin_download_timeout:
'ダウンロードがタイムアウトしました。ネットワークを確認して再試行してください。',
plugin_marketplace_unavailable:
'マーケットからプラグインを取得できません。後で再試行してください。',
plugin_download_failed:
'ダウンロードに失敗しました。ネットワークを確認してください。',
dependency_prepare_failed:
'依存関係のインストールに失敗しました。実行環境を確認してください。',
plugin_launch_failed:
'プラグインを起動できません。実行環境を確認してください。',
},
autoDescription:
'従来の実行方式はプラグインになりました。設定を保持して全パイプラインを移行します。元の設定はバックアップされ、会話は新しく始まります。',
viewPipelines: 'パイプラインを表示',
@@ -10,12 +31,16 @@ export default {
migrating: 'パイプラインを移行中…',
summary: '{{migrated}} 件移行済み、{{remaining}} 件の確認が必要です。',
installFailed:
'プラグインのインストールに失敗しました。ネットワークと拡張機能の上限を確認して再試行するか、データのみ移行してください。',
'インストールに失敗しました。詳細で失敗した段階を確認し、再試行するかデータのみ移行してください。',
activationRetryHint:
'実行環境を確認して再読み込みし、このパイプラインを選択して確定すると、有効化のみを再試行します。保存済み設定は再移行しません。',
details: '移行の詳細',
notices: {
runtimeUnavailable:
'プラグインランタイムが未接続です。接続を復旧して再試行するか、データのみ移行してください。',
executionFailed:
'移行に失敗しました。サーバーログを確認して再試行してください。',
pluginRequired:
'上記のランナープラグインをインストールまたは有効化し、プレビューを更新してください。',
legacyArchive:
@@ -27,7 +52,7 @@ export default {
retrievalDefaults:
'検索には新しい top-k と結果長の既定値を適用します。移行後に確認してください。',
boxReset:
'既存の Box セッション状態は引き継がず、新しい分離セッションを作成します。',
'サンドボックスの再利用設定は保持されます。既存コンテナの状態は移行せず、同じ再利用ルールで新しく作成されます。',
persistentHistory:
'新しい会話の履歴は分離して永続化します。既存のリモート履歴は取り込みません。',
tweaksDefault: 'Langflow tweaks の既定値は空のオブジェクトです。',
@@ -53,8 +78,8 @@ export default {
'インストール済み Runner の設定形式が移行先と一致しません。プラグインのバージョンを確認してください。',
runnerExcluded:
'このパイプラインでは必要な Runner プラグインが除外されています。拡張機能の設定を変更してください。',
boxScope:
'独自の Box セッションテンプレートは安全に移行できません。削除するか、分離要件を確認してください。',
boxTemplateInvalid:
'再利用テンプレートが無効です。{変数名} を使用してください。位置引数、書式変換、属性アクセスは使用できません。',
pendingInteraction:
'入力待ちの会話があります。完了またはキャンセルしてから移行してください。',
},
@@ -1,4 +1,24 @@
export default {
complete: 'Готово',
checkingPlugin: 'Проверка плагина',
preparingPlugin: 'Подготовка установки',
installDetails: 'Подробности установки',
stageProgress: 'Прогресс этапов',
downloaded: 'Загружено {{size}}',
processed: 'Обработано {{completed}} / {{total}} конвейеров',
installErrors: {
plugin_version_unavailable:
'Нужная версия ещё не опубликована в магазине. Повторите после публикации либо перенесите только данные.',
plugin_download_timeout:
'Время загрузки истекло. Проверьте сеть и повторите попытку.',
plugin_marketplace_unavailable:
'Магазин не может предоставить пакет. Повторите позже.',
plugin_download_failed: 'Не удалось загрузить плагин. Проверьте сеть.',
dependency_prepare_failed:
'Не удалось установить зависимости. Проверьте среду выполнения.',
plugin_launch_failed:
'Не удалось запустить плагин. Проверьте среду выполнения.',
},
autoDescription:
'Прежние исполнители стали плагинами. Все конвейеры будут перенесены с сохранением настроек. Исходные настройки будут скопированы, а разговоры начнутся заново.',
viewPipelines: 'Посмотреть конвейеры',
@@ -10,12 +30,16 @@ export default {
migrating: 'Перенос конвейеров…',
summary: 'Перенесено: {{migrated}}; требуют внимания: {{remaining}}.',
installFailed:
'Не удалось установить плагины. Проверьте сеть и квоту расширений и повторите попытку либо перенесите только данные.',
'Установка не удалась. Откройте подробности, проверьте этап сбоя и повторите попытку либо перенесите только данные.',
activationRetryHint:
'Проверьте среду выполнения, обновите список, выберите конвейер и подтвердите повторную активацию. Сохранённая конфигурация не будет мигрировать повторно.',
details: 'Подробности переноса',
notices: {
runtimeUnavailable:
'Среда плагинов отключена. Восстановите подключение и повторите попытку или перенесите только данные.',
executionFailed:
'Миграция не удалась. Проверьте журнал сервера и повторите попытку.',
pluginRequired:
'Установите или включите указанный выше плагин исполнителя, затем обновите предпросмотр.',
legacyArchive:
@@ -28,7 +52,7 @@ export default {
retrievalDefaults:
'Для поиска применяются новые значения top-k и ограничения длины результатов. Проверьте их после миграции.',
boxReset:
'Состояние сеанса Box не переносится; будет создан новый изолированный сеанс.',
'Настройки повторного использования песочницы сохраняются. Состояние контейнера не переносится; новые песочницы используют те же правила.',
persistentHistory:
'Новые беседы используют постоянную изолированную историю. Прежняя удалённая история не импортируется.',
tweaksDefault:
@@ -56,8 +80,8 @@ export default {
'Схема установленного Runner не соответствует целевой. Проверьте версию плагина и обновите список.',
runnerExcluded:
'В этом конвейере исключён нужный плагин Runner. Сначала измените настройки расширений.',
boxScope:
'Пользовательский шаблон сеанса Box нельзя безопасно перенести. Удалите его или проверьте требования изоляции.',
boxTemplateInvalid:
'Неверный шаблон песочницы. Используйте {имя_переменной}; позиционные поля, преобразования формата и доступ к атрибутам не поддерживаются.',
pendingInteraction:
'Беседа ожидает ввода. Завершите или отмените её перед миграцией.',
},
@@ -1,4 +1,24 @@
export default {
complete: 'เสร็จสิ้น',
checkingPlugin: 'ตรวจสอบปลั๊กอิน',
preparingPlugin: 'เตรียมติดตั้ง',
installDetails: 'รายละเอียดการติดตั้ง',
stageProgress: 'ความคืบหน้าแต่ละขั้นตอน',
downloaded: 'ดาวน์โหลดแล้ว {{size}}',
processed: 'ดำเนินการแล้ว {{completed}} / {{total}} ไปป์ไลน์',
installErrors: {
plugin_version_unavailable:
'เวอร์ชันที่ต้องการยังไม่มีในตลาด กรุณาลองใหม่หลังเผยแพร่หรือย้ายเฉพาะข้อมูล',
plugin_download_timeout:
'การดาวน์โหลดหมดเวลา กรุณาตรวจสอบเครือข่ายแล้วลองใหม่',
plugin_marketplace_unavailable:
'ตลาดไม่สามารถให้แพ็กเกจปลั๊กอินได้ กรุณาลองใหม่ภายหลัง',
plugin_download_failed: 'ดาวน์โหลดปลั๊กอินไม่สำเร็จ กรุณาตรวจสอบเครือข่าย',
dependency_prepare_failed:
'ติดตั้งส่วนพึ่งพาไม่สำเร็จ กรุณาตรวจสอบสภาพแวดล้อมรันไทม์',
plugin_launch_failed:
'เริ่มปลั๊กอินไม่สำเร็จ กรุณาตรวจสอบสภาพแวดล้อมรันไทม์',
},
autoDescription:
'รันเนอร์เดิมเปลี่ยนเป็นปลั๊กอินแล้ว ย้ายไปป์ไลน์ทั้งหมดโดยคงการตั้งค่าเดิม ระบบจะสำรองการตั้งค่าและเริ่มการสนทนาใหม่',
viewPipelines: 'ดูไปป์ไลน์',
@@ -10,12 +30,16 @@ export default {
migrating: 'กำลังย้ายไปป์ไลน์…',
summary: 'ย้ายแล้ว {{migrated}} รายการ ต้องตรวจสอบ {{remaining}} รายการ',
installFailed:
'ติดตั้งปลั๊กอินไม่สำเร็จ ตรวจสอบเครือข่ายและโควตาส่วนขยายแล้วลองใหม่ หรือย้ายเฉพาะข้อมูล',
'ติดตั้งไม่สำเร็จ เปิดรายละเอียดเพื่อดูขั้นตอนที่ล้มเหลว แล้วลองใหม่หรือย้ายเฉพาะข้อมูล',
activationRetryHint:
'หลังตรวจสอบสภาพแวดล้อมการทำงาน ให้รีเฟรช เลือกไปป์ไลน์นี้และยืนยันเพื่อลองเปิดใช้งานอีกครั้งเท่านั้น การตั้งค่าที่บันทึกไว้จะไม่ถูกย้ายซ้ำ',
details: 'รายละเอียดการย้าย',
notices: {
runtimeUnavailable:
'ไม่ได้เชื่อมต่อรันไทม์ปลั๊กอิน โปรดเชื่อมต่อใหม่แล้วลองอีกครั้ง หรือย้ายเฉพาะข้อมูล',
executionFailed:
'การย้ายข้อมูลล้มเหลว โปรดตรวจสอบบันทึกเซิร์ฟเวอร์แล้วลองอีกครั้ง',
pluginRequired:
'ติดตั้งหรือเปิดใช้งานปลั๊กอินรันเนอร์ที่แสดงด้านบน แล้วรีเฟรชตัวอย่าง',
legacyArchive:
@@ -27,7 +51,8 @@ export default {
serialTools: 'หลังย้ายข้อมูล เครื่องมือยังคงทำงานตามลำดับ',
retrievalDefaults:
'การค้นคืนจะใช้ค่า top-k และขีดจำกัดความยาวผลลัพธ์ใหม่ โปรดตรวจสอบหลังย้ายข้อมูล',
boxReset: 'สถานะเซสชัน Box เดิมจะไม่ถูกย้าย ระบบจะสร้างเซสชันแยกใหม่',
boxReset:
'คงการตั้งค่าการใช้แซนด์บ็อกซ์ซ้ำไว้ ไม่ย้ายสถานะคอนเทนเนอร์เดิม แซนด์บ็อกซ์ใหม่จะใช้กฎการใช้ซ้ำเดิม',
persistentHistory:
'บทสนทนาใหม่จะมีประวัติถาวรที่แยกจากกัน โดยไม่นำเข้าประวัติระยะไกลเดิม',
tweaksDefault: 'Langflow tweaks จะใช้วัตถุว่างเป็นค่าเริ่มต้น',
@@ -50,8 +75,8 @@ export default {
'รูปแบบการตั้งค่า Runner ที่ติดตั้งไม่ตรงกับเป้าหมาย โปรดตรวจสอบเวอร์ชันปลั๊กอินแล้วรีเฟรช',
runnerExcluded:
'ไปป์ไลน์นี้ยกเว้นปลั๊กอิน Runner ที่จำเป็น โปรดแก้ไขการตั้งค่าส่วนขยายก่อน',
boxScope:
'ไม่สามารถย้ายเทมเพลตเซสชัน Box แบบกำหนดเองได้อย่างปลอดภัย โปรดลบหรือทบทวนข้อกำหนดการแยกเซสชัน',
boxTemplateInvalid:
'รูปแบบเทมเพลตไม่ถูกต้อง ใช้ {ชื่อตัวแปร} โดยไม่ใช้ฟิลด์ตามตำแหน่ง การแปลงรูปแบบ หรือการเข้าถึงแอตทริบิวต์',
pendingInteraction:
'มีบทสนทนาที่รอข้อมูลอยู่ โปรดดำเนินการให้เสร็จหรือยกเลิกก่อนย้ายข้อมูล',
},
@@ -1,4 +1,24 @@
export default {
complete: 'Hoàn tất',
checkingPlugin: 'Kiểm tra plugin',
preparingPlugin: 'Chuẩn bị cài đặt',
installDetails: 'Chi tiết cài đặt',
stageProgress: 'Tiến độ từng giai đoạn',
downloaded: 'Đã tải {{size}}',
processed: 'Đã xử lý {{completed}} / {{total}} pipeline',
installErrors: {
plugin_version_unavailable:
'Phiên bản cần thiết chưa có trên chợ. Thử lại sau khi phát hành hoặc chỉ chuyển dữ liệu.',
plugin_download_timeout:
'Tải plugin quá thời gian chờ. Kiểm tra mạng rồi thử lại.',
plugin_marketplace_unavailable:
'Chợ chưa thể cung cấp gói plugin. Vui lòng thử lại sau.',
plugin_download_failed: 'Không thể tải plugin. Vui lòng kiểm tra mạng.',
dependency_prepare_failed:
'Không thể cài các thư viện phụ thuộc. Kiểm tra môi trường chạy.',
plugin_launch_failed:
'Không thể khởi động plugin. Kiểm tra môi trường chạy.',
},
autoDescription:
'Các runner cũ đã chuyển thành plugin. Chuyển đổi tất cả pipeline và giữ lại thiết lập. Cấu hình cũ sẽ được sao lưu; hội thoại sẽ bắt đầu lại.',
viewPipelines: 'Xem pipeline',
@@ -10,12 +30,16 @@ export default {
migrating: 'Đang chuyển đổi pipeline…',
summary: 'Đã chuyển {{migrated}}; {{remaining}} cần xử lý.',
installFailed:
'Cài plugin thất bại. Kiểm tra mạng và hạn mức tiện ích rồi thử lại, hoặc chỉ chuyển đổi dữ liệu.',
'Cài đặt thất bại. Mở chi tiết để xem giai đoạn lỗi rồi thử lại, hoặc chỉ chuyển dữ liệu.',
activationRetryHint:
'Sau khi kiểm tra môi trường chạy, làm mới, chọn pipeline này và xác nhận để chỉ thử kích hoạt lại. Cấu hình đã lưu sẽ không được di chuyển lần nữa.',
details: 'Chi tiết chuyển đổi',
notices: {
runtimeUnavailable:
'Môi trường chạy plugin bị ngắt kết nối. Khôi phục kết nối rồi thử lại, hoặc chỉ di chuyển dữ liệu.',
executionFailed:
'Di chuyển thất bại. Kiểm tra nhật ký máy chủ rồi thử lại.',
pluginRequired:
'Cài đặt hoặc bật plugin runner ở trên, rồi làm mới bản xem trước.',
legacyArchive:
@@ -28,7 +52,7 @@ export default {
retrievalDefaults:
'Truy xuất dùng giới hạn top-k và độ dài kết quả mới. Hãy kiểm tra sau khi di chuyển.',
boxReset:
'Trạng thái phiên Box hiện tại không được chuyển; một phiên cách ly mới sẽ được tạo.',
'Giữ nguyên cấu hình tái sử dụng sandbox. Không chuyển trạng thái container cũ; sandbox mới dùng cùng quy tắc tái sử dụng.',
persistentHistory:
'Hội thoại mới dùng lịch sử bền vững và riêng biệt. Lịch sử từ xa cũ không được nhập.',
tweaksDefault: 'Langflow tweaks mặc định là một đối tượng rỗng.',
@@ -53,8 +77,8 @@ export default {
'Cấu hình Runner đã cài không khớp với đích di chuyển. Kiểm tra phiên bản plugin rồi làm mới.',
runnerExcluded:
'Pipeline này loại trừ plugin Runner cần thiết. Hãy chỉnh thiết lập tiện ích trước.',
boxScope:
'Mẫu phiên Box tùy chỉnh không thể di chuyển an toàn. Hãy xóa mẫu hoặc kiểm tra yêu cầu cách ly.',
boxTemplateInvalid:
'Mẫu tái sử dụng không hợp lệ. Dùng {tên_biến}; không hỗ trợ trường vị trí, chuyển đổi định dạng hoặc truy cập thuộc tính.',
pendingInteraction:
'Có hội thoại đang chờ nhập liệu. Hãy hoàn tất hoặc hủy trước khi di chuyển.',
},
@@ -1,4 +1,20 @@
export default {
complete: '完成',
checkingPlugin: '检查插件',
preparingPlugin: '准备安装',
installDetails: '安装详情',
stageProgress: '阶段进度',
downloaded: '已下载 {{size}}',
processed: '已处理 {{completed}} / {{total}} 条流水线',
installErrors: {
plugin_version_unavailable:
'扩展市场尚未提供所需插件版本,请等待上架后重试,或选择仅迁移数据。',
plugin_download_timeout: '下载插件超时,请检查网络后重试。',
plugin_marketplace_unavailable: '扩展市场暂时无法提供插件包,请稍后重试。',
plugin_download_failed: '插件下载失败,请检查网络后重试。',
dependency_prepare_failed: '插件依赖安装失败,请检查运行时环境后重试。',
plugin_launch_failed: '插件启动失败,请检查运行时环境后重试。',
},
autoDescription:
'旧版运行方式已改为插件。迁移全部流水线并保留现有设置,原配置会自动备份;迁移后开始新会话。',
viewPipelines: '查看流水线',
@@ -8,12 +24,16 @@ export default {
installing: '正在安装所需插件…',
migrating: '正在迁移流水线…',
summary: '已迁移 {{migrated}} 条,{{remaining}} 条需要处理。',
installFailed: '插件安装失败,请检查网络和扩展配额后重试,或选择仅迁移数据。',
installFailed:
'插件安装失败。展开详情查看失败阶段,处理后重试,或选择仅迁移数据。',
activationRetryHint:
'检查运行环境后刷新,选中此流水线并确认,即可仅重试激活,不会再次迁移已保存的配置。',
details: '迁移详情',
notices: {
runtimeUnavailable:
'插件运行时未连接,请恢复连接后重试,或选择仅迁移数据。',
executionFailed: '迁移执行失败,请查看服务日志后重试。',
pluginRequired: '请先安装或启用上方所示的运行器插件,再刷新预览。',
legacyArchive:
'活动配置仅保留所选 Runner;全部旧 Runner 配置(包括未启用的配置)保存在迁移备份中。',
@@ -22,7 +42,8 @@ export default {
modelReasoning: '各模型的推理设置会保留,并由主程序应用。',
serialTools: '迁移后工具调用仍按顺序执行。',
retrievalDefaults: '检索采用新的 top-k 与结果长度默认值,请在迁移后检查。',
boxReset: '现有 Box 会话状态不迁移,后续将创建新的隔离会话。',
boxReset:
'沙箱复用范围会保留;旧容器状态不迁移,后续按原复用规则创建沙箱。',
persistentHistory:
'新会话采用持久化、相互隔离的历史记录,不导入原有远端历史。',
tweaksDefault: 'Langflow tweaks 默认设为空对象。',
@@ -40,7 +61,8 @@ export default {
schemaChanged:
'已安装 Runner 的配置格式与迁移目标不一致,请检查插件版本后刷新。',
runnerExcluded: '此流水线排除了所需 Runner 插件,请先调整扩展设置。',
boxScope: '自定义 Box 会话模板无法安全迁移,请先移除或检查隔离要求。',
boxTemplateInvalid:
'沙箱复用模板格式有误,请使用 {变量名};不支持位置参数、格式转换或属性访问。',
pendingInteraction: '有会话正在等待输入,请完成或取消后再迁移。',
},
title: '流水线迁移',
@@ -1,4 +1,21 @@
export default {
complete: '完成',
checkingPlugin: '檢查外掛',
preparingPlugin: '準備安裝',
installDetails: '安裝詳情',
stageProgress: '階段進度',
downloaded: '已下載 {{size}}',
processed: '已處理 {{completed}} / {{total}} 條流水線',
installErrors: {
plugin_version_unavailable:
'擴充市集尚未提供所需外掛版本,請等待上架後重試,或選擇僅遷移資料。',
plugin_download_timeout: '下載外掛逾時,請檢查網路後重試。',
plugin_marketplace_unavailable:
'擴充市集暫時無法提供外掛套件,請稍後重試。',
plugin_download_failed: '外掛下載失敗,請檢查網路後重試。',
dependency_prepare_failed: '外掛相依套件安裝失敗,請檢查執行環境後重試。',
plugin_launch_failed: '外掛啟動失敗,請檢查執行環境後重試。',
},
autoDescription:
'舊版執行方式已改為外掛。遷移全部流水線並保留現有設定,原設定會自動備份;遷移後開始新對話。',
viewPipelines: '查看流水線',
@@ -8,12 +25,16 @@ export default {
installing: '正在安裝所需外掛…',
migrating: '正在遷移流水線…',
summary: '已遷移 {{migrated}} 條,{{remaining}} 條需要處理。',
installFailed: '外掛安裝失敗,請檢查網路和擴充配額後重試,或選擇僅遷移資料。',
installFailed:
'外掛安裝失敗。展開詳情查看失敗階段,處理後重試,或選擇僅遷移資料。',
activationRetryHint:
'檢查執行環境後重新整理,選取此流水線並確認,即可只重試啟用,不會再次遷移已儲存的設定。',
details: '遷移詳情',
notices: {
runtimeUnavailable:
'外掛執行環境未連線,請恢復連線後重試,或選擇僅遷移資料。',
executionFailed: '遷移執行失敗,請查看服務日誌後重試。',
pluginRequired: '請先安裝或啟用上方所示的執行器外掛,再重新整理預覽。',
legacyArchive:
'作用中的設定僅保留所選 Runner;全部舊 Runner 設定(包括未啟用的設定)保存在遷移備份中。',
@@ -22,7 +43,8 @@ export default {
modelReasoning: '各模型的推理設定會保留,並由主程式套用。',
serialTools: '遷移後工具呼叫仍依序執行。',
retrievalDefaults: '檢索採用新的 top-k 與結果長度預設值,請在遷移後檢查。',
boxReset: '現有 Box 工作階段狀態不遷移,後續將建立新的隔離工作階段。',
boxReset:
'沙箱複用範圍會保留;舊容器狀態不遷移,後續按原複用規則建立沙箱。',
persistentHistory:
'新對話採用持久化、相互隔離的歷史記錄,不匯入原有遠端歷史。',
tweaksDefault: 'Langflow tweaks 預設為空物件。',
@@ -40,7 +62,8 @@ export default {
schemaChanged:
'已安裝 Runner 的設定格式與遷移目標不一致,請檢查外掛版本後重新整理。',
runnerExcluded: '此流水線排除了所需 Runner 外掛,請先調整擴充設定。',
boxScope: '自訂 Box 工作階段範本無法安全遷移,請先移除或檢查隔離要求。',
boxTemplateInvalid:
'沙箱複用範本格式有誤,請使用 {變數名};不支援位置參數、格式轉換或屬性存取。',
pendingInteraction: '有對話正在等待輸入,請完成或取消後再遷移。',
},
title: '流水線遷移',