mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-24 01:15:24 +08:00
feat(assistant): add streaming background sessions and refresh workspace UI
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
ChevronDown,
|
||||
CircleAlert,
|
||||
MinusCircle,
|
||||
LoaderCircle,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -16,11 +17,11 @@ export type AssistantTool = {
|
||||
export default function AssistantToolResult({
|
||||
tool,
|
||||
content,
|
||||
defaultCollapsed = false,
|
||||
defaultCollapsed = true,
|
||||
}: {
|
||||
tool?: AssistantTool;
|
||||
content: string;
|
||||
/** Turn finished: collapse to one line until the user expands it again. */
|
||||
/** Keep tool steps quiet until the user requests details. */
|
||||
defaultCollapsed?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -36,11 +37,17 @@ export default function AssistantToolResult({
|
||||
result && typeof result === 'object' && !Array.isArray(result)
|
||||
? (result as Record<string, unknown>)
|
||||
: {};
|
||||
const running = data.status === 'running';
|
||||
const failed = !!data.error;
|
||||
const denied = data.status === 'denied';
|
||||
const partial = !!data.truncated;
|
||||
const Icon =
|
||||
failed || partial ? CircleAlert : denied ? MinusCircle : CheckCircle2;
|
||||
const Icon = running
|
||||
? LoaderCircle
|
||||
: failed || partial
|
||||
? CircleAlert
|
||||
: denied
|
||||
? MinusCircle
|
||||
: CheckCircle2;
|
||||
const items = Array.isArray(result)
|
||||
? result
|
||||
: Array.isArray(data.items)
|
||||
@@ -56,13 +63,15 @@ export default function AssistantToolResult({
|
||||
: t(`assistant.operations.${tool?.name}`, {
|
||||
defaultValue: t('assistant.toolResult'),
|
||||
});
|
||||
const status = failed
|
||||
? 'failed'
|
||||
: denied
|
||||
? 'denied'
|
||||
: partial
|
||||
? 'partial'
|
||||
: 'completed';
|
||||
const status = running
|
||||
? 'toolRunning'
|
||||
: failed
|
||||
? 'failed'
|
||||
: denied
|
||||
? 'denied'
|
||||
: partial
|
||||
? 'partial'
|
||||
: 'completed';
|
||||
const url =
|
||||
typeof data.url === 'string' &&
|
||||
/^\/home\/(pipelines|knowledge)\?id=[\w-]+$/.test(data.url)
|
||||
@@ -79,7 +88,7 @@ export default function AssistantToolResult({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded-lg border bg-background px-3 py-1.5 text-left text-xs hover:bg-muted"
|
||||
className="flex w-full items-center gap-1.5 rounded-md px-0.5 py-1 text-left text-[11px] leading-4 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-primary"
|
||||
aria-expanded={false}
|
||||
onClick={() => {
|
||||
setManual(true);
|
||||
@@ -87,25 +96,25 @@ export default function AssistantToolResult({
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className={`size-3.5 shrink-0 ${failed ? 'text-destructive' : 'text-muted-foreground'}`}
|
||||
className={`size-3 shrink-0 ${running ? 'animate-spin motion-reduce:animate-none' : ''} ${failed ? 'text-destructive' : 'text-muted-foreground'}`}
|
||||
/>
|
||||
<span className="truncate font-medium">{label}</span>
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
<span className="ml-auto shrink-0 text-muted-foreground">
|
||||
{tool && t(`assistant.${status}`)}
|
||||
</span>
|
||||
<ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-2 rounded-xl border bg-background p-3 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<section className="min-w-0 space-y-1.5 px-0.5 py-1 text-[11px] leading-4 text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon
|
||||
className={`size-4 shrink-0 ${failed ? 'text-destructive' : 'text-muted-foreground'}`}
|
||||
className={`size-3 shrink-0 ${running ? 'animate-spin motion-reduce:animate-none' : ''} ${failed ? 'text-destructive' : 'text-muted-foreground'}`}
|
||||
/>
|
||||
<span className="font-medium">{label}</span>
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
<span className="min-w-0 flex-1 break-words">{label}</span>
|
||||
<span className="ml-auto shrink-0 text-[11px] text-muted-foreground">
|
||||
{tool && t(`assistant.${status}`)}
|
||||
</span>
|
||||
<button
|
||||
@@ -118,10 +127,12 @@ export default function AssistantToolResult({
|
||||
setCollapsed(true);
|
||||
}}
|
||||
>
|
||||
<ChevronDown className="size-3.5 rotate-180" />
|
||||
<ChevronDown className="size-3 rotate-180" />
|
||||
</button>
|
||||
</div>
|
||||
{failed ? (
|
||||
{running ? (
|
||||
<p>{t('assistant.toolRunning')}</p>
|
||||
) : failed ? (
|
||||
<p className="text-destructive">{t('assistant.operationFailed')}</p>
|
||||
) : denied ? (
|
||||
<p className="text-muted-foreground">
|
||||
@@ -136,7 +147,7 @@ export default function AssistantToolResult({
|
||||
)}
|
||||
{name && <p className="break-words">{name}</p>}
|
||||
{items && (
|
||||
<ul className="space-y-1 text-xs text-muted-foreground">
|
||||
<ul className="space-y-0.5 text-[11px] text-muted-foreground">
|
||||
{items.slice(0, 6).map((item: unknown, index: number) => {
|
||||
const entry =
|
||||
item && typeof item === 'object'
|
||||
@@ -168,9 +179,9 @@ export default function AssistantToolResult({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<details className="text-xs text-muted-foreground">
|
||||
<details className="text-[11px] text-muted-foreground">
|
||||
<summary className="cursor-pointer">{t('assistant.details')}</summary>
|
||||
<pre className="mt-2 max-h-48 overflow-auto whitespace-pre-wrap break-all">
|
||||
<pre className="mt-1.5 max-h-32 overflow-auto whitespace-pre-wrap break-all">
|
||||
{tool ? JSON.stringify(tool.result, null, 2) : content}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Pure geometry + state helpers for the floating workspace assistant button.
|
||||
*
|
||||
* The component keeps these decisions here so the dock/rail transitions stay
|
||||
* testable without a DOM. The key invariant is that the *same* edge value
|
||||
* drives the collapsed strip and the expanded button, so they can never drift
|
||||
* apart or fight each other (the race the UI is careful to avoid).
|
||||
*/
|
||||
|
||||
export const ASSISTANT_BUTTON_SIZE = 48;
|
||||
/** Distance to the viewport edge that snaps the button into the rail. */
|
||||
export const ASSISTANT_SNAP_THRESHOLD = 24;
|
||||
/** Rail strip that stays visible while collapsed, in pixels. */
|
||||
export const ASSISTANT_RAIL_WIDTH = 6;
|
||||
|
||||
export type AssistantDragPosition = { x: number; y: number };
|
||||
export type AssistantEdge = 'left' | 'right' | null;
|
||||
|
||||
/** Viewport box the button may occupy. Kept explicit so tests can pin a size. */
|
||||
export type AssistantViewport = { width: number; height: number };
|
||||
|
||||
export function clampAssistantPosition(
|
||||
x: number,
|
||||
y: number,
|
||||
viewport: AssistantViewport,
|
||||
): AssistantDragPosition {
|
||||
const maxX = Math.max(0, viewport.width - ASSISTANT_BUTTON_SIZE);
|
||||
const maxY = Math.max(0, viewport.height - ASSISTANT_BUTTON_SIZE);
|
||||
return {
|
||||
x: Math.min(Math.max(0, x), maxX),
|
||||
y: Math.min(Math.max(0, y), maxY),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether a resting position is docked against a vertical edge.
|
||||
* The left edge wins ties so a centred drag always resolves deterministically.
|
||||
*/
|
||||
export function resolveAssistantEdge(
|
||||
x: number,
|
||||
viewport: AssistantViewport,
|
||||
): AssistantEdge {
|
||||
const maxX = Math.max(0, viewport.width - ASSISTANT_BUTTON_SIZE);
|
||||
const leftGap = x;
|
||||
const rightGap = maxX - x;
|
||||
if (leftGap <= ASSISTANT_SNAP_THRESHOLD && leftGap <= rightGap) return 'left';
|
||||
if (rightGap <= ASSISTANT_SNAP_THRESHOLD) return 'right';
|
||||
return null;
|
||||
}
|
||||
|
||||
export type AssistantHoverInput = {
|
||||
dockedEdge: AssistantEdge;
|
||||
railExpanded: boolean;
|
||||
/** A long press has armed the drag, or the pointer is actively moving. */
|
||||
dragging: boolean;
|
||||
};
|
||||
|
||||
/** Distance to the viewport edge that snaps the button into the rail. */
|
||||
|
||||
/**
|
||||
* Hovering any part of the control must reveal the full button again.
|
||||
* Only a docked *and still collapsed* button needs to expand, so a pointer that
|
||||
* merely jitters inside an already expanded button does no state churn.
|
||||
*/
|
||||
export function shouldExpandRail(input: AssistantHoverInput): boolean {
|
||||
if (input.dragging) return false;
|
||||
return !!input.dockedEdge && !input.railExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* A docked button collapses to the strip when the pointer leaves. This is the
|
||||
* mirror of `shouldExpandRail` and is keyed on `railExpanded` being *true* —
|
||||
* the earlier `!railExpanded` form made a revealed button impossible to
|
||||
* re-collapse, which is the bug this guards against. The panel being open pins
|
||||
* it, because the popover stays anchored to the button.
|
||||
*/
|
||||
export function shouldCollapseRail(input: AssistantHoverInput): boolean {
|
||||
if (input.dragging) return false;
|
||||
return !!input.dockedEdge && input.railExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* After a drop the button always starts collapsed, even though the pointer is
|
||||
* still over it. The rail is then held collapsed until the pointer exits, which
|
||||
* avoids the "drops under the cursor and never comes back" race.
|
||||
*/
|
||||
export function restingRailExpanded(): boolean {
|
||||
return false;
|
||||
}
|
||||
@@ -132,6 +132,7 @@ export default function DynamicFormItemComponent({
|
||||
systemContext,
|
||||
requiredModelAbility,
|
||||
compactModelSelector = false,
|
||||
selectedModelLabel,
|
||||
}: {
|
||||
config: IDynamicFormItemSchema;
|
||||
field: ControllerRenderProps<any, any>;
|
||||
@@ -141,6 +142,7 @@ export default function DynamicFormItemComponent({
|
||||
systemContext?: Record<string, unknown>;
|
||||
requiredModelAbility?: string;
|
||||
compactModelSelector?: boolean;
|
||||
selectedModelLabel?: string;
|
||||
}) {
|
||||
const [llmModels, setLlmModels] = useState<LLMModel[]>([]);
|
||||
const [embeddingModels, setEmbeddingModels] = useState<EmbeddingModel[]>([]);
|
||||
@@ -639,11 +641,15 @@ export default function DynamicFormItemComponent({
|
||||
aria-label={t('models.selectModel')}
|
||||
className={
|
||||
compactModelSelector
|
||||
? 'w-full min-w-0 gap-1 border-0 bg-transparent px-1 text-xs text-muted-foreground shadow-none hover:bg-muted data-[size=default]:h-7 [&_[data-slot=select-value]_svg]:hidden'
|
||||
? 'w-full min-w-0 gap-1 rounded-lg border border-border bg-background px-2 text-xs text-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] hover:border-muted-foreground/40 hover:bg-background dark:bg-background dark:hover:bg-background data-[state=open]:border-muted-foreground/50 focus-visible:border-muted-foreground/50 focus-visible:ring-1 focus-visible:ring-muted-foreground/20 data-[size=default]:h-7 [&_[data-slot=select-value]_svg]:hidden'
|
||||
: MODEL_SELECT_TRIGGER_CLASS
|
||||
}
|
||||
>
|
||||
<SelectValue placeholder={t('models.selectModel')} />
|
||||
<SelectValue placeholder={t('models.selectModel')}>
|
||||
{compactModelSelector && selectedModelLabel
|
||||
? selectedModelLabel
|
||||
: undefined}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(groupedModels).map(([providerName, models]) => (
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/* Brief gestures separated by long rests keep the collapsed dock quiet. */
|
||||
.magic {
|
||||
animation: assistant-wave 9s ease-in-out infinite;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
.sparkle {
|
||||
animation: assistant-sparkle 9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.dock:hover .magic,
|
||||
.dock:hover .sparkle,
|
||||
.dock:focus-visible .magic,
|
||||
.dock:focus-visible .sparkle {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes assistant-wave {
|
||||
0%,
|
||||
84%,
|
||||
100% {
|
||||
transform: translateY(0) rotate(0);
|
||||
}
|
||||
87% {
|
||||
transform: translateY(-2px) rotate(-10deg);
|
||||
}
|
||||
90% {
|
||||
transform: translateY(-2px) rotate(8deg);
|
||||
}
|
||||
93% {
|
||||
transform: translateY(-1px) rotate(-5deg);
|
||||
}
|
||||
96% {
|
||||
transform: translateY(0) rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes assistant-sparkle {
|
||||
0%,
|
||||
86%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
90% {
|
||||
transform: scale(1.2) rotate(12deg);
|
||||
}
|
||||
96% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.magic,
|
||||
.sparkle {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-color: var(--border);
|
||||
background: var(--background);
|
||||
box-shadow: 0 -4px 20px #0000000d;
|
||||
transform-origin: bottom center;
|
||||
animation: assistant-arrive 220ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
}
|
||||
.closing {
|
||||
animation: assistant-leave 180ms ease-in both;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dock {
|
||||
animation: assistant-arrive 160ms ease-out both;
|
||||
}
|
||||
.conversationPanel {
|
||||
height: min(420px, calc(100dvh - 48px));
|
||||
}
|
||||
.header {
|
||||
background: var(--background);
|
||||
}
|
||||
.avatar {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 10px;
|
||||
color: #2288ee;
|
||||
background: color-mix(in srgb, #2288ee 8%, var(--background));
|
||||
}
|
||||
.welcome {
|
||||
padding: 6px 0;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.prompt {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 10px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
background: var(--muted);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 160ms ease,
|
||||
border-color 160ms ease;
|
||||
}
|
||||
.promptIcon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
color: #2288ee;
|
||||
}
|
||||
.prompt:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
border-color: var(--border);
|
||||
}
|
||||
.prompt:focus-visible {
|
||||
outline: 2px solid #2288ee;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.prompt:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.userMessage {
|
||||
background: color-mix(in srgb, #2288ee 8%, var(--background));
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.assistantMessage {
|
||||
background: var(--muted);
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
/* Use the debug dialog's Markdown styling, scaled to the compact assistant. */
|
||||
.messageContent:global(.markdown-body) {
|
||||
color: var(--foreground);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.messageContent:global(.markdown-body) :is(p, ul, ol, blockquote, pre, table) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.messageContent:global(.markdown-body) :is(h1, h2, h3, h4, h5, h6) {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.messageContent:global(.markdown-body) > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.messageContent:global(.markdown-body) > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.messageContent:global(.markdown-body) :is(ul, ol) {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
.messageContent:global(.markdown-body) li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.messageContent:global(.markdown-body) li > p {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.messageContent:global(.markdown-body) pre {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background: var(--background);
|
||||
font-size: 12px;
|
||||
}
|
||||
.messageContent:global(.markdown-body) table {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
.messageContent:global(.markdown-body) :is(th, td) {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.composer {
|
||||
background: var(--muted);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.input {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.send,
|
||||
.dock {
|
||||
background: #2288ee;
|
||||
box-shadow: none;
|
||||
}
|
||||
.send:hover:not(:disabled),
|
||||
.dock:hover {
|
||||
background: #1878d8;
|
||||
}
|
||||
@keyframes assistant-arrive {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px) scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes assistant-leave {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) scale(0.98);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.panel,
|
||||
.closing,
|
||||
.dock {
|
||||
animation: none;
|
||||
}
|
||||
.prompt,
|
||||
.composer {
|
||||
transition: none;
|
||||
}
|
||||
.prompt:hover:not(:disabled) {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
@@ -439,6 +439,58 @@ export class BackendClient extends BaseHttpClient {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async streamAssistant<T>(
|
||||
uuid: string,
|
||||
payload: {
|
||||
revision: number;
|
||||
text?: string;
|
||||
approved?: boolean;
|
||||
model_uuid?: string;
|
||||
},
|
||||
onEvent: (event: { kind: string; data: unknown }) => void,
|
||||
signal: AbortSignal,
|
||||
): Promise<T> {
|
||||
let offset = 0;
|
||||
let result: T | undefined;
|
||||
let failure: { code: string; msg: string } | undefined;
|
||||
const consume = (text: string) => {
|
||||
let end: number;
|
||||
while ((end = text.indexOf('\n', offset)) !== -1) {
|
||||
const line = text.slice(offset, end).trim();
|
||||
offset = end + 1;
|
||||
if (!line) continue;
|
||||
const frame = JSON.parse(line);
|
||||
if (frame.kind === 'completed') result = frame.data;
|
||||
else if (frame.kind === 'error') failure = frame;
|
||||
else onEvent(frame);
|
||||
}
|
||||
};
|
||||
const response = await this.instance.post<string>(
|
||||
`/api/v1/assistant/conversations/${encodeURIComponent(uuid)}/turn/stream`,
|
||||
payload,
|
||||
{
|
||||
adapter: 'xhr',
|
||||
responseType: 'text',
|
||||
timeout: 0,
|
||||
signal,
|
||||
headers: { Accept: 'application/x-ndjson' },
|
||||
transformResponse: [(data) => data],
|
||||
onDownloadProgress: (progress) => {
|
||||
const xhr = progress.event?.target as XMLHttpRequest | undefined;
|
||||
if (xhr?.status === 200) consume(xhr.responseText);
|
||||
},
|
||||
},
|
||||
);
|
||||
consume(response.data);
|
||||
if (failure) throw failure;
|
||||
if (!result)
|
||||
throw {
|
||||
code: 'assistant_protocol_error',
|
||||
msg: 'Assistant stream ended before completion',
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public getGeneralPipelineMetadata(): Promise<GetPipelineMetadataResponseData> {
|
||||
// as designed, this method will be deprecated, and only for developer to check the prefered config schema
|
||||
return this.get('/api/v1/pipelines/_/metadata');
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
import pipelineMigration from './pipeline-migration/en-US';
|
||||
const enUS = {
|
||||
assistant: {
|
||||
sessions: 'Conversations',
|
||||
currentChat: 'Current conversation',
|
||||
stop: 'Stop task',
|
||||
stopped:
|
||||
'Stopped. An in-progress tool may already have taken effect. Check its resource before retrying.',
|
||||
setupRequired:
|
||||
'Sign in to LangBot Account to use LangBot Models, or configure your own model provider to start chatting.',
|
||||
loginAccount: 'Sign in to LangBot Account',
|
||||
configureModels: 'Configure models',
|
||||
providerCheckFailed: 'Could not check model providers. Please try again.',
|
||||
retrySetup: 'Retry',
|
||||
preparing: 'Preparing assistant…',
|
||||
thinking: 'Model responding · Round {{round}}',
|
||||
executing: 'Executing tool…',
|
||||
toolRunning: 'Running',
|
||||
recommendationFailed:
|
||||
'Could not load a recommended model. Please select a model.',
|
||||
expand: 'Expand workspace assistant',
|
||||
collapse: 'Collapse workspace assistant',
|
||||
modelHint: 'Switch assistant model for your next message',
|
||||
details: 'View raw data',
|
||||
found: 'Found {{count}} items',
|
||||
@@ -35,7 +54,7 @@ const enUS = {
|
||||
newChat: 'New conversation',
|
||||
close: 'Close',
|
||||
welcome:
|
||||
'Tell me what you want to build. I can inspect this Workspace and propose knowledge-base and Pipeline changes for you to confirm.',
|
||||
'Tell me what you want to build. I can inspect this Workspace and propose configurations for bots, Agents, knowledge bases, Pipelines and more, then apply them after your confirmation.',
|
||||
discover: 'What models, knowledge bases and Pipelines are available?',
|
||||
build: 'Help me create a knowledge-base question-answering application.',
|
||||
toolResult: 'Operation result',
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
import pipelineMigration from './pipeline-migration/es-ES';
|
||||
const esES = {
|
||||
assistant: {
|
||||
sessions: 'Conversaciones',
|
||||
currentChat: 'Conversación actual',
|
||||
stop: 'Detener tarea',
|
||||
stopped:
|
||||
'Detenido. Una herramienta en curso puede haber surtido efecto. Comprueba el recurso antes de reintentar.',
|
||||
setupRequired:
|
||||
'Inicia sesión en LangBot Account para usar LangBot Models o configura tu proveedor de modelos para empezar a conversar.',
|
||||
loginAccount: 'Iniciar sesión en LangBot Account',
|
||||
configureModels: 'Configurar modelos',
|
||||
providerCheckFailed:
|
||||
'No se pudieron comprobar los proveedores. Inténtalo de nuevo.',
|
||||
retrySetup: 'Reintentar',
|
||||
preparing: 'Preparando asistente…',
|
||||
thinking: 'Modelo respondiendo · Ronda {{round}}',
|
||||
executing: 'Ejecutando herramienta…',
|
||||
toolRunning: 'En ejecución',
|
||||
recommendationFailed:
|
||||
'No se pudo cargar el modelo recomendado. Selecciona un modelo.',
|
||||
expand: 'Expandir asistente del espacio de trabajo',
|
||||
collapse: 'Contraer asistente del espacio de trabajo',
|
||||
modelHint: 'Cambia el modelo del asistente para el próximo mensaje',
|
||||
details: 'Ver datos sin procesar',
|
||||
found: 'Se encontraron {{count}} elementos',
|
||||
@@ -35,7 +55,7 @@ const esES = {
|
||||
newChat: 'Nueva conversación',
|
||||
close: 'Cerrar',
|
||||
welcome:
|
||||
'Dime qué quieres crear. Puedo inspeccionar este espacio de trabajo y proponer cambios en la base de conocimiento y el Pipeline para que los confirmes.',
|
||||
'Dime qué quieres crear. Puedo revisar este espacio de trabajo y proponer configuraciones para bots, Agents, bases de conocimiento, Pipelines y más, y aplicarlas tras tu confirmación.',
|
||||
discover:
|
||||
'¿Qué modelos, bases de conocimiento y Pipelines hay disponibles?',
|
||||
build:
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
import pipelineMigration from './pipeline-migration/ja-JP';
|
||||
const jaJP = {
|
||||
assistant: {
|
||||
sessions: '会話を切り替え',
|
||||
currentChat: '現在の会話',
|
||||
stop: 'タスクを停止',
|
||||
stopped:
|
||||
'停止しました。実行中のツールが反映済みの場合があります。再試行前にリソースを確認してください。',
|
||||
setupRequired:
|
||||
'LangBot Account にログインして LangBot Models を使うか、モデルプロバイダーを設定すると会話を始められます。',
|
||||
loginAccount: 'LangBot Account にログイン',
|
||||
configureModels: 'モデルを設定',
|
||||
providerCheckFailed:
|
||||
'モデルプロバイダーを確認できません。再試行してください。',
|
||||
retrySetup: '再試行',
|
||||
preparing: '準備中…',
|
||||
thinking: 'モデル応答中 · {{round}} 回目',
|
||||
executing: 'ツール実行中…',
|
||||
toolRunning: '実行中',
|
||||
recommendationFailed:
|
||||
'推奨モデルを取得できません。モデルを選択してください。',
|
||||
expand: 'ワークスペースアシスタントを展開',
|
||||
collapse: 'ワークスペースアシスタントを折りたたむ',
|
||||
modelHint: '次のメッセージからモデルを切り替え',
|
||||
details: '元のデータを見る',
|
||||
found: '{{count}} 件見つかりました',
|
||||
@@ -35,7 +55,7 @@ const jaJP = {
|
||||
newChat: '新しい会話',
|
||||
close: '閉じる',
|
||||
welcome:
|
||||
'作りたいアプリを教えてください。現在のリソースを確認し、承認後にナレッジベースや Pipeline を設定します。',
|
||||
'作りたいアプリを教えてください。現在のワークスペースを確認し、ボット、Agent、ナレッジベース、Pipeline などの設定を提案し、承認後に適用します。',
|
||||
discover: '利用できるモデル、ナレッジベース、Pipeline は?',
|
||||
build: 'ナレッジベースを使う質問応答アプリを作りたい。',
|
||||
toolResult: '操作結果',
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
import pipelineMigration from './pipeline-migration/ru-RU';
|
||||
const ruRU = {
|
||||
assistant: {
|
||||
sessions: 'Диалоги',
|
||||
currentChat: 'Текущий диалог',
|
||||
stop: 'Остановить задачу',
|
||||
stopped:
|
||||
'Остановлено. Инструмент мог уже внести изменения. Проверьте ресурс перед повтором.',
|
||||
setupRequired:
|
||||
'Войдите в LangBot Account для доступа к LangBot Models или настройте своего провайдера моделей, чтобы начать диалог.',
|
||||
loginAccount: 'Войти в LangBot Account',
|
||||
configureModels: 'Настроить модели',
|
||||
providerCheckFailed:
|
||||
'Не удалось проверить провайдеров. Попробуйте ещё раз.',
|
||||
retrySetup: 'Повторить',
|
||||
preparing: 'Подготовка помощника…',
|
||||
thinking: 'Ответ модели · Шаг {{round}}',
|
||||
executing: 'Выполнение инструмента…',
|
||||
toolRunning: 'Выполняется',
|
||||
recommendationFailed:
|
||||
'Не удалось загрузить рекомендуемую модель. Выберите модель.',
|
||||
expand: 'Развернуть помощника рабочего пространства',
|
||||
collapse: 'Свернуть помощника рабочего пространства',
|
||||
modelHint: 'Сменить модель ассистента для следующего сообщения',
|
||||
details: 'Показать исходные данные',
|
||||
found: 'Найдено элементов: {{count}}',
|
||||
@@ -35,7 +55,7 @@ const ruRU = {
|
||||
newChat: 'Новый диалог',
|
||||
close: 'Закрыть',
|
||||
welcome:
|
||||
'Расскажите, что вы хотите создать. Я изучу это рабочее пространство и предложу изменения для базы знаний и Pipeline, которые вы подтвердите.',
|
||||
'Расскажите, что вы хотите создать. Я могу изучить рабочее пространство и предложить настройки ботов, Agents, баз знаний, Pipelines и других ресурсов, а затем применить их после вашего подтверждения.',
|
||||
discover: 'Какие модели, базы знаний и Pipelines доступны?',
|
||||
build: 'Помогите создать приложение для ответов на вопросы по базе знаний.',
|
||||
toolResult: 'Результат операции',
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import pipelineMigration from './pipeline-migration/th-TH';
|
||||
const thTH = {
|
||||
assistant: {
|
||||
sessions: 'การสนทนา',
|
||||
currentChat: 'การสนทนาปัจจุบัน',
|
||||
stop: 'หยุดงาน',
|
||||
stopped:
|
||||
'หยุดแล้ว เครื่องมืออาจดำเนินการไปแล้ว โปรดตรวจสอบทรัพยากรก่อนลองอีกครั้ง',
|
||||
setupRequired:
|
||||
'เข้าสู่ระบบ LangBot Account เพื่อใช้ LangBot Models หรือตั้งค่าผู้ให้บริการโมเดลของคุณเพื่อเริ่มสนทนา',
|
||||
loginAccount: 'เข้าสู่ระบบ LangBot Account',
|
||||
configureModels: 'ตั้งค่าโมเดล',
|
||||
providerCheckFailed: 'ไม่สามารถตรวจสอบผู้ให้บริการโมเดลได้ โปรดลองอีกครั้ง',
|
||||
retrySetup: 'ลองอีกครั้ง',
|
||||
preparing: 'กำลังเตรียมผู้ช่วย…',
|
||||
thinking: 'โมเดลกำลังตอบ · รอบ {{round}}',
|
||||
executing: 'กำลังใช้เครื่องมือ…',
|
||||
toolRunning: 'กำลังทำงาน',
|
||||
recommendationFailed: 'โหลดโมเดลแนะนำไม่สำเร็จ โปรดเลือกโมเดล',
|
||||
expand: 'ขยายผู้ช่วยพื้นที่ทำงาน',
|
||||
collapse: 'ย่อผู้ช่วยพื้นที่ทำงาน',
|
||||
modelHint: 'สลับโมเดลผู้ช่วยสำหรับข้อความถัดไป',
|
||||
details: 'ดูข้อมูลดิบ',
|
||||
found: 'พบ {{count}} รายการ',
|
||||
@@ -35,7 +53,7 @@ const thTH = {
|
||||
newChat: 'บทสนทนาใหม่',
|
||||
close: 'ปิด',
|
||||
welcome:
|
||||
'บอกฉันว่าคุณอยากสร้างอะไร ฉันตรวจสอบพื้นที่ทำงานนี้และเสนอการตั้งค่าฐานความรู้และ Pipeline ให้คุณยืนยันก่อนดำเนินการ',
|
||||
'บอกฉันว่าคุณต้องการสร้างแอปอะไร ฉันสามารถตรวจสอบพื้นที่ทำงานนี้และเสนอการตั้งค่าสำหรับบอต, Agent, ฐานความรู้, Pipeline และอื่น ๆ แล้วดำเนินการหลังจากคุณยืนยันได้',
|
||||
discover: 'มีโมเดล ฐานความรู้ และ Pipeline อะไรที่ใช้ได้บ้าง?',
|
||||
build: 'ช่วยสร้างแอปตอบคำถามจากฐานความรู้ให้หน่อย',
|
||||
toolResult: 'ผลลัพธ์การดำเนินการ',
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
import pipelineMigration from './pipeline-migration/vi-VN';
|
||||
const viVN = {
|
||||
assistant: {
|
||||
sessions: 'Cuộc trò chuyện',
|
||||
currentChat: 'Cuộc trò chuyện hiện tại',
|
||||
stop: 'Dừng tác vụ',
|
||||
stopped:
|
||||
'Đã dừng. Công cụ có thể đã thực hiện thay đổi. Kiểm tra tài nguyên trước khi thử lại.',
|
||||
setupRequired:
|
||||
'Đăng nhập LangBot Account để dùng LangBot Models hoặc cấu hình nhà cung cấp mô hình riêng để bắt đầu trò chuyện.',
|
||||
loginAccount: 'Đăng nhập LangBot Account',
|
||||
configureModels: 'Cấu hình mô hình',
|
||||
providerCheckFailed: 'Không thể kiểm tra nhà cung cấp. Vui lòng thử lại.',
|
||||
retrySetup: 'Thử lại',
|
||||
preparing: 'Đang chuẩn bị trợ lý…',
|
||||
thinking: 'Mô hình đang trả lời · Vòng {{round}}',
|
||||
executing: 'Đang chạy công cụ…',
|
||||
toolRunning: 'Đang chạy',
|
||||
recommendationFailed:
|
||||
'Không tải được mô hình đề xuất. Vui lòng chọn mô hình.',
|
||||
expand: 'Mở rộng trợ lý không gian làm việc',
|
||||
collapse: 'Thu gọn trợ lý không gian làm việc',
|
||||
modelHint: 'Đổi mô hình trợ lý cho tin nhắn tiếp theo',
|
||||
details: 'Xem dữ liệu thô',
|
||||
found: 'Tìm thấy {{count}} mục',
|
||||
@@ -36,7 +55,7 @@ const viVN = {
|
||||
newChat: 'Cuộc trò chuyện mới',
|
||||
close: 'Đóng',
|
||||
welcome:
|
||||
'Hãy cho tôi biết bạn muốn xây dựng gì. Tôi có thể xem không gian làm việc này và đề xuất cấu hình cơ sở tri thức cùng Pipeline để bạn xác nhận.',
|
||||
'Hãy cho tôi biết bạn muốn xây dựng gì. Tôi có thể xem không gian làm việc này, đề xuất cấu hình cho bot, Agent, cơ sở tri thức, Pipeline và các tài nguyên khác, rồi áp dụng sau khi bạn xác nhận.',
|
||||
discover: 'Có những mô hình, cơ sở tri thức và Pipeline nào khả dụng?',
|
||||
build: 'Giúp tôi tạo ứng dụng hỏi đáp dựa trên cơ sở tri thức.',
|
||||
toolResult: 'Kết quả thao tác',
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
import pipelineMigration from './pipeline-migration/zh-Hans';
|
||||
const zhHans = {
|
||||
assistant: {
|
||||
sessions: '切换会话',
|
||||
currentChat: '当前会话',
|
||||
stop: '停止任务',
|
||||
stopped: '已停止。执行中的工具可能已经生效,请检查相关资源后再尝试。',
|
||||
setupRequired:
|
||||
'登录 LangBot Account 使用 LangBot Models,或先配置自己的模型供应商,即可开始对话。',
|
||||
loginAccount: '登录 LangBot Account',
|
||||
configureModels: '配置模型',
|
||||
providerCheckFailed: '无法检查模型供应商,请重试。',
|
||||
retrySetup: '重试',
|
||||
preparing: '正在准备助手…',
|
||||
thinking: '模型响应中 · 第 {{round}} 轮',
|
||||
executing: '正在执行工具…',
|
||||
toolRunning: '执行中',
|
||||
recommendationFailed: '推荐模型加载失败,请手动选择模型。',
|
||||
expand: '展开工作区助手',
|
||||
collapse: '收起工作区助手',
|
||||
modelHint: '切换助手模型,从下一条消息生效',
|
||||
details: '查看原始数据',
|
||||
found: '找到 {{count}} 项',
|
||||
@@ -33,7 +50,7 @@ const zhHans = {
|
||||
newChat: '新对话',
|
||||
close: '关闭',
|
||||
welcome:
|
||||
'告诉我你想构建什么应用。我可以查看当前工作区,并提出知识库和 Pipeline 配置方案,由你确认后执行。',
|
||||
'告诉我你想构建什么应用。我可以查看当前工作区,为机器人、Agent、知识库和 Pipeline 等提供配置方案,由你确认后执行。',
|
||||
discover: '当前有哪些可用的模型、知识库和 Pipeline?',
|
||||
build: '帮我创建一个知识库问答应用。',
|
||||
toolResult: '操作结果',
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
import pipelineMigration from './pipeline-migration/zh-Hant';
|
||||
const zhHant = {
|
||||
assistant: {
|
||||
sessions: '切換對話',
|
||||
currentChat: '目前對話',
|
||||
stop: '停止任務',
|
||||
stopped: '已停止。執行中的工具可能已經生效,請檢查相關資源後再嘗試。',
|
||||
setupRequired:
|
||||
'登入 LangBot Account 使用 LangBot Models,或先設定自己的模型供應商,即可開始對話。',
|
||||
loginAccount: '登入 LangBot Account',
|
||||
configureModels: '設定模型',
|
||||
providerCheckFailed: '無法檢查模型供應商,請重試。',
|
||||
retrySetup: '重試',
|
||||
preparing: '正在準備助手…',
|
||||
thinking: '模型回應中 · 第 {{round}} 輪',
|
||||
executing: '正在執行工具…',
|
||||
toolRunning: '執行中',
|
||||
recommendationFailed: '推薦模型載入失敗,請手動選擇模型。',
|
||||
expand: '展開工作區助手',
|
||||
collapse: '收起工作區助手',
|
||||
modelHint: '切換助手模型,從下一條訊息生效',
|
||||
details: '檢視原始資料',
|
||||
found: '找到 {{count}} 項',
|
||||
@@ -33,7 +50,7 @@ const zhHant = {
|
||||
newChat: '新對話',
|
||||
close: '關閉',
|
||||
welcome:
|
||||
'告訴我你想建構什麼應用。我可以查看目前工作區,並提出知識庫和 Pipeline 設定方案,由你確認後執行。',
|
||||
'告訴我你想建構什麼應用。我可以查看目前工作區,為機器人、Agent、知識庫和 Pipeline 等提供設定方案,由你確認後執行。',
|
||||
discover: '目前有哪些可用的模型、知識庫和 Pipeline?',
|
||||
build: '幫我建立一個知識庫問答應用。',
|
||||
toolResult: '操作結果',
|
||||
|
||||
Reference in New Issue
Block a user