Compare commits

..

14 Commits

Author SHA1 Message Date
René Wang 1d7fc18cdb 🐛 fix: Interface copy consistency for desktop app (#15968)
* 🐛 fix(topic): correct "Copy Session ID" label to "Copy Topic ID"

The topic item dropdown action copies the topic id
(navigator.clipboard.writeText(id) — the same id the surrounding menu
passes to openTopicInNewWindow and SESSION_CHAT_TOPIC_URL), but it was
labeled "Copy Session ID" / "Session ID copied". Correct the English
source strings to "Copy Topic ID" / "Topic ID copied".

English source locale only; the i18n key actions.copySessionId is kept,
and the other locales regenerate from en-US.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 fix(i18n): correct legacy "session" wording in English UI strings

Follow-up to #15942. Several English strings used "session" where the
code shows the referent is the live conversation, the agent, or where the
word is redundant. "session" is also legitimate elsewhere (auth/sign-in
session, AWS Session Token, Claude Code runtime session, natural English),
and many sessionGroup/defaultSession keys already have correct values, so
only code-verified mislabels are changed:

- chat: "Clear current session messages" / "…clear the current session
  messages…" / "Save current session as topic" → "conversation"
  (the Clear and Save-Topic actions operate on the current conversation)
- setting: "All session messages have been cleared" → "conversation";
  "…display in the session." → "…in conversations."
- setting: "Session Settings" (per-agent page, sibling of "Group
  Settings") → "Agent Settings" / "· {{name}}" / "Agent Profile and chat
  preferences"
- setting: workspace-delete warnings drop the redundant legacy "sessions"
  (agents have their own bucket; topics already listed)

English source locale only; i18n keys unchanged, other locales regenerate
from en-US.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 fix(i18n): align "Discover Assistants" nav with "Agent" terminology

The desktop nav read "Discover Assistants" while the entire discover
feature uses "Agent" (Community Agents, Featured Agents, Add Agent,
Agent List…). Rename the lone outlier to "Discover Agents".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 fix(cmdk): merge Image/AI Video into one "Generation" entry

The command palette's Navigate group listed separate "Image" (/image)
and "AI Video" (/video) entries, but the app sidebar combines both into
a single "Generation" item (useNavLayout's bottomMenuItems →
tab.generation → /image). Mirror that in getNavigableRoutes — its only
consumer is the CMDK navigate list — by dropping the separate video
entry and relabeling image to "Generation", merging both keyword sets so
image and video terms still match. Reuses the existing tab.generation key
so the label stays in sync with the sidebar across every locale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(i18n): apply terminology fixes to the default .ts source of truth

The earlier commits changed only locales/en-US/*.json, but those files are
generated from packages/locales/src/default/*.ts via the workflow:i18n step
(.i18nrc.js entry = locales/en-US), so a regeneration would revert them.
Apply the same English-source edits to the .ts defaults so the source of
truth and the en-US mirror agree:

- topic: copySessionId / copySessionIdSuccess → "Copy Topic ID" / "Topic ID copied"
- chat: clearCurrentMessages / confirmClearCurrentMessages / topic.saveCurrentMessages → "conversation"
- setting: danger.clear.success / llm.modelList.desc → "conversation(s)";
  header.session(+WithName/Desc) → "Agent Settings" / "chat preferences";
  workspace-delete strings drop the redundant legacy "sessions"
- electron: navigation.discoverAssistants → "Discover Agents"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(i18n): unify stray terminology (Focus Mode / Agent / Skill / Library)

Continue the English copy-consistency cleanup, aligning straggler strings
to the terms the app already standardizes on (zh-CN already uses these):

- zenMode label "Zen Mode" → "Focus Mode" (the toast; settings/hotkey
  already say Focus Mode, and zh-CN is 专注模式 everywhere)
- "assistant" → "agent": agent channel descriptions (×9), topic move
  actions (×4), labs agent self-iteration
- "Plugin" → "Skill": lobe-agent-management install labels, discover
  user.plugins / user.noPlugins
- "knowledge base" → "Library": file library-import strings, chat agent
  profile counts + search-library tool label
- "service provider" → "provider": new-provider id description

English source (.ts) + en-US mirror; canonical terms already match zh-CN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(i18n): apply confirmed naming decisions (Orchestrator / Group / Clear)

Resolve the remaining English terminology forks per product decision:
- group coordinator role → "Orchestrator" (was Supervisor / Group Host /
  "host" / "moderator"): chat profile + token labels; setting group-chat
  system-prompt strings and enable-orchestrator description
- cmdk search → "Group" / "Groups" (was "Agent Team(s)")
- data-wipe titles → "Clear Data" / "Clear Workspace Data" (was "Wipe…")
- model ability → "Tool Calling" / "tool calls" (was "Tool Usage" /
  "function calls")
- topic import → "Import Topics" / "Importing topics…" (match Export Topics)

English source (.ts) + en-US mirror.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(i18n): standardize auth on "Sign in" + unify conversation wording

Per product decision (match the /signin route path):
- auth / authError / oauth / error / marketAuth / desktop-onboarding:
  "Log in" / "Login" / "Log Out" → "Sign in" / "Sign-in" / "Sign Out"
  across the auth flow (button, errors, guide titles, OAuth consent,
  unlock screen, session-expiry prompts)
- common "SSO Login" → "SSO Sign-in"; discover user menu "Logout" → "Sign out"
- memory analysis modal: unify "chats" / "topics" → "conversations"

English source (.ts) + en-US mirror (regenerated via workflow:i18n).
External-tool logins (GitHub CLI, WeChat) intentionally kept; non-English
locales left to the i18n CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🔥 chore(zen-mode): remove the Zen/Focus Mode feature

Zen Mode (hide all chrome to show only the current conversation) is no
longer needed. Remove it end to end:

- delete src/features/ZenModeToast
- drop status.zenMode state, the toggleZenMode action, and the inZenMode /
  showChatHeader selectors (the chat header now always renders)
- remove the ToggleZenMode hotkey (chat-scope hook, const registration,
  HotkeyId type)
- un-mask the panel-visibility selectors (drop the !zenMode guard)
- remove the zenMode / toggleZenMode locale keys (chat, hotkey)
- update the affected store/hook tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: add translation

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:53:23 +08:00
AmAzing- 2c66867f65 🐛 fix(edit-lock): allow same-user generic updates (#15969)
* 🐛 fix(edit-lock): allow same-user generic updates

* 🧪 fix(e2e): mock community marketplace APIs

* 💬 docs(e2e): explain community marketplace mocks

* 🧪 fix(e2e): preserve mixed community trpc batches

* 🧪 fix(e2e): unwrap single batched community input
2026-06-17 17:42:17 +08:00
YuTengjing 8718e1d33f 🐛 fix: reject duplicate custom model ids (#15975) 2026-06-17 17:05:27 +08:00
YuTengjing 7bc47071c4 ♻️ refactor: remove Fable campaign paths (#15960) 2026-06-17 16:58:13 +08:00
YuTengjing c66b1fc8fe feat(model): support GLM-5.2 reasoning effort (#15972) 2026-06-17 16:47:24 +08:00
Innei 46439bbd16 feat(spa): bootstrap app initialization (#15937)
*  feat(spa): bootstrap app initialization

* ♻️ refactor: trim bootstrap registration diff

* 🐛 fix: resolve app layer loading helper import
2026-06-17 16:07:52 +08:00
Arvin Xu 25387ada92 🐛 fix(model-runtime): strip null/empty members from enum for Gemini (#15952)
* 🐛 fix(model-runtime): strip null/empty members from enum for Gemini

The memory tool's `updateIdentityMemory.set.memoryType` enum carried a
trailing `null` sentinel (`[...MEMORY_TYPES, null]`). Gemini's schema proto
only accepts STRING enum members, so the `null` was coerced to `""` and
rejected with `enum[10]: cannot be empty`. OpenAI/Anthropic don't validate
enum members, so it only broke on Gemini.

Two layers:
- Source: drop the `null` sentinel from the memoryType enum; nullability is
  already expressed via `type: ['string', 'null']`.
- Conversion fallback: sanitizeGeminiSchema now filters null/non-string/empty
  enum members (and strips the enum entirely if none remain), so any other
  tool that does the same won't break Gemini.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

*  test(model-runtime): align Gemini enum tests with null-member stripping

The sanitizeGeminiSchema fix now filters null/non-string members out of enums,
so the two "preserve null enum" assertions no longer match. Update them to
expect the null sentinel stripped while nullability stays in type: ['string', 'null'].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:52:32 +08:00
Rdmclin2 da3412e202 🔨 feat: support workspace mem (#15971)
feat: support workspace mem
2026-06-17 15:23:14 +08:00
Arvin Xu 7ea84a2695 🐛 fix(server): guard device file mutations against unapproved workspace roots (#15967)
File routes (preview / move / rename / write) received `workingDirectory`
from the same untrusted browser session that supplies the file paths. The
gateway's containment check only proves paths sit inside that directory, not
that the directory itself is legitimate — so `workingDirectory: "/"` passed
trivially and reached any path on the device.

Re-derive approved roots from the server-owned device row (`workingDirs` +
`defaultCwd`) and require the requested root to equal or nest inside one. The
check is built into a shared `workspaceFileProcedure` so every file-mutating
route inherits it and can never forget it.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:22:10 +08:00
René Wang 211e8c1f54 feat(changelog): collapse Improvements & Fixes sections by default (#15916)
The changelog modal renders each entry's full body, so the long
"Improvements" and "Fixes" sections take a lot of vertical space. A remark
plugin now wraps those two standardized headings (and their content) in a
collapsible section that renders collapsed by default; "Features" stays open.

- remarkCollapsibleSections: emits a <collapsible-section> element (via
  data.hName) for exact "Improvements"/"Fixes" headings — the react-markdown
  equivalent of an MDX component
- CollapsibleSection: @lobehub/ui Collapse, collapsed by default
- CustomMDX: accept and merge caller-supplied remarkPlugins
- ChangelogContent: opt in to the plugin + map the custom element

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:16:28 +08:00
Arvin Xu 0ef1309b68 🔖 chore(cli): bump @lobehub/cli to 0.0.31 (#15964)
* 🔖 chore(cli): bump @lobehub/cli to 0.0.32

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* update

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:45:55 +08:00
Arvin Xu 73907480d7 💄 style(chat): rename Agent Gateway menu label and split card title (#15963)
Hardcode "Agent Gateway" as the menu toggle label (brand name, identical
in every language, so no i18n). The info popover keeps a dedicated
cardTitle ("Agent Gateway Mode" / "Agent Gateway 模式").

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:42:08 +08:00
Arvin Xu a38437c1da feat(cli): upload local files via file upload & shared helper (#15949)
*  feat(cli): support uploading local files via `file upload` and shared helper

Add `lh file upload [source]` support for local paths (positional or `-f/--file`)
alongside the existing URL mode, and extract the local-upload logic shared with
`kb upload` into a reusable `uploadLocalFile` helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ️ perf(cli): skip S3 upload when local file hash already exists

`uploadLocalFile` now calls `file.checkFileHash` before the S3 PUT and, when
the same bytes are already stored (and the object still exists), reuses the
existing url instead of re-uploading. Mirrors the dedup short-circuit of the
URL upload mode. Benefits both `file upload` and `kb upload`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:08:31 +08:00
Arvin Xu de207a65c2 🐛 fix(search): drop citations with empty url to avoid Invalid URL crash (#15954)
OpenRouter's built-in web search (and other OpenAI-compatible providers) may
emit empty citation objects like `{}`. These propagated unfiltered through the
OpenAI grounding stream branches, then crashed both the renderer
(`new URL(undefined)` in SearchGrounding) and message persistence (Zod requires
`url` to be a string in GroundingSearchSchema).

- model-runtime: add a shared `filterValidCitations` helper and apply it to all
  OpenAI grounding branches (url_citation / messages / xAI / XiaomiMiMo), plus
  reuse it for the existing Perplexity/Zhipu filter. Guard `url_citation?.*` too.
- SearchGrounding: filter out citations without a url and parse the favicon host
  defensively so a malformed entry can never crash the render.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:21:27 +08:00
256 changed files with 3777 additions and 1939 deletions
-25
View File
@@ -2,31 +2,6 @@
# Changelog
## [Version 2.2.6](https://github.com/lobehub/lobe-chat/compare/v2.2.6-canary.8...v2.2.6)
<sup>Released on **2026-06-17**</sup>
#### ✨ Features
- **agent**: improve connector, document, and fleet workflows.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **agent**: improve connector, document, and fleet workflows, closes [#15936](https://github.com/lobehub/lobe-chat/issues/15936) ([3f82033](https://github.com/lobehub/lobe-chat/commit/3f82033))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.2.1](https://github.com/lobehub/lobe-chat/compare/v0.0.0-nightly.pr15228.13999...v2.2.1)
<sup>Released on **2026-05-29**</sup>
+37
View File
@@ -1,4 +1,7 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
@@ -77,6 +80,40 @@ describe('lh file - E2E', () => {
});
});
// ── upload (local file) ───────────────────────────────
describe('upload', () => {
it('should upload a local file passed as a positional argument', () => {
const tmpFile = path.join(os.tmpdir(), `lh-e2e-upload-${Date.now()}.txt`);
fs.writeFileSync(tmpFile, 'hello from lh e2e upload');
try {
const result = runJson<{ id: string }>(`file upload ${tmpFile} --json id`);
expect(result).toHaveProperty('id');
if (result.id) run(`file delete ${result.id} --yes`);
} finally {
fs.rmSync(tmpFile, { force: true });
}
});
it('should upload a local file passed via --file', () => {
const tmpFile = path.join(os.tmpdir(), `lh-e2e-upload-f-${Date.now()}.txt`);
fs.writeFileSync(tmpFile, 'hello from lh e2e --file upload');
try {
const result = runJson<{ id: string }>(`file upload --file ${tmpFile} --json id`);
expect(result).toHaveProperty('id');
if (result.id) run(`file delete ${result.id} --yes`);
} finally {
fs.rmSync(tmpFile, { force: true });
}
});
it('should error when the local file does not exist', () => {
expect(() => run('file upload -f /no/such/lh-file.txt')).toThrow();
});
});
// ── recent ────────────────────────────────────────────
describe('recent', () => {
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Code generated by `npm run man:generate`; DO NOT EDIT.
.\" Manual command details come from the Commander command tree.
.TH LH 1 "" "@lobehub/cli 0.0.29" "User Commands"
.TH LH 1 "" "@lobehub/cli 0.0.31" "User Commands"
.SH NAME
lh \- LobeHub CLI \- manage and connect to LobeHub services
.SH SYNOPSIS
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@lobehub/cli",
"version": "0.0.29",
"version": "0.0.31",
"type": "module",
"bin": {
"lh": "./dist/index.js",
+117 -3
View File
@@ -1,3 +1,7 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { Command } from 'commander';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -17,6 +21,9 @@ const { mockTrpcClient } = vi.hoisted(() => ({
removeFiles: { mutate: vi.fn() },
updateFile: { mutate: vi.fn() },
},
upload: {
createS3PreSignedUrl: { mutate: vi.fn() },
},
},
}));
@@ -38,9 +45,11 @@ describe('file command', () => {
exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
mockGetTrpcClient.mockResolvedValue(mockTrpcClient);
for (const method of Object.values(mockTrpcClient.file)) {
for (const fn of Object.values(method)) {
(fn as ReturnType<typeof vi.fn>).mockReset();
for (const group of [mockTrpcClient.file, mockTrpcClient.upload]) {
for (const method of Object.values(group)) {
for (const fn of Object.values(method)) {
(fn as ReturnType<typeof vi.fn>).mockReset();
}
}
}
});
@@ -205,6 +214,111 @@ describe('file command', () => {
expect(mockTrpcClient.file.createFile.mutate).not.toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('already exists'));
});
it('should upload a local file passed as a positional argument', async () => {
const tmpFile = path.join(os.tmpdir(), `lh-upload-${process.pid}.txt`);
fs.writeFileSync(tmpFile, 'hello world');
const fetchSpy = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK' } as Response);
mockTrpcClient.file.checkFileHash.mutate.mockResolvedValue({ isExist: false });
mockTrpcClient.upload.createS3PreSignedUrl.mutate.mockResolvedValue('https://s3/presigned');
mockTrpcClient.file.createFile.mutate.mockResolvedValue({
id: 'f-local',
url: 'files/x.txt',
});
try {
const program = createProgram();
await program.parseAsync(['node', 'test', 'file', 'upload', tmpFile]);
expect(mockTrpcClient.upload.createS3PreSignedUrl.mutate).toHaveBeenCalled();
expect(fetchSpy).toHaveBeenCalledWith(
'https://s3/presigned',
expect.objectContaining({ method: 'PUT' }),
);
expect(mockTrpcClient.file.createFile.mutate).toHaveBeenCalledWith(
expect.objectContaining({
fileType: 'text/plain',
name: path.basename(tmpFile),
url: expect.stringContaining('.txt'),
}),
);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('File created'));
} finally {
fetchSpy.mockRestore();
fs.rmSync(tmpFile, { force: true });
}
});
it('should upload a local file passed via --file', async () => {
const tmpFile = path.join(os.tmpdir(), `lh-upload-f-${process.pid}.json`);
fs.writeFileSync(tmpFile, '{}');
const fetchSpy = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK' } as Response);
mockTrpcClient.file.checkFileHash.mutate.mockResolvedValue({ isExist: false });
mockTrpcClient.upload.createS3PreSignedUrl.mutate.mockResolvedValue('https://s3/presigned');
mockTrpcClient.file.createFile.mutate.mockResolvedValue({ id: 'f-json' });
try {
const program = createProgram();
await program.parseAsync(['node', 'test', 'file', 'upload', '--file', tmpFile]);
expect(mockTrpcClient.file.createFile.mutate).toHaveBeenCalledWith(
expect.objectContaining({ fileType: 'application/json' }),
);
} finally {
fetchSpy.mockRestore();
fs.rmSync(tmpFile, { force: true });
}
});
it('should skip the S3 upload when the local file hash already exists', async () => {
const tmpFile = path.join(os.tmpdir(), `lh-upload-dedup-${process.pid}.txt`);
fs.writeFileSync(tmpFile, 'dedup me');
const fetchSpy = vi.spyOn(globalThis, 'fetch');
mockTrpcClient.file.checkFileHash.mutate.mockResolvedValue({
isExist: true,
url: 'files/2024-01-01/existing.txt',
});
mockTrpcClient.file.createFile.mutate.mockResolvedValue({ id: 'f-dedup' });
try {
const program = createProgram();
await program.parseAsync(['node', 'test', 'file', 'upload', tmpFile]);
// No pre-sign and no S3 PUT should happen
expect(mockTrpcClient.upload.createS3PreSignedUrl.mutate).not.toHaveBeenCalled();
expect(fetchSpy).not.toHaveBeenCalled();
// The record reuses the existing url
expect(mockTrpcClient.file.createFile.mutate).toHaveBeenCalledWith(
expect.objectContaining({ url: 'files/2024-01-01/existing.txt' }),
);
} finally {
fetchSpy.mockRestore();
fs.rmSync(tmpFile, { force: true });
}
});
it('should error when local file does not exist', async () => {
const program = createProgram();
await program.parseAsync(['node', 'test', 'file', 'upload', '-f', '/no/such/file.txt']);
expect(log.error).toHaveBeenCalledWith(expect.stringContaining('File not found'));
expect(exitSpy).toHaveBeenCalledWith(1);
});
it('should error when no source is provided', async () => {
const program = createProgram();
await program.parseAsync(['node', 'test', 'file', 'upload']);
expect(log.error).toHaveBeenCalledWith(expect.stringContaining('Provide a local file path'));
expect(exitSpy).toHaveBeenCalledWith(1);
});
});
describe('edit', () => {
+49 -7
View File
@@ -4,6 +4,7 @@ import pc from 'picocolors';
import { getTrpcClient } from '../api/client';
import { confirm, outputJson, printTable, timeAgo, truncate } from '../utils/format';
import { log } from '../utils/logger';
import { uploadLocalFile } from '../utils/uploadLocalFile';
export function registerFileCommand(program: Command) {
const file = program.command('file').description('Manage files');
@@ -113,18 +114,20 @@ export function registerFileCommand(program: Command) {
// ── upload ───────────────────────────────────────────
file
.command('upload <url>')
.description('Upload a file by URL (checks hash first)')
.option('--hash <hash>', 'File hash for deduplication check')
.option('--name <name>', 'File name')
.option('--type <type>', 'File MIME type')
.option('--size <size>', 'File size in bytes')
.command('upload [source]')
.description('Upload a file from a local path or a URL')
.option('-f, --file <path>', 'Local file path to upload')
.option('--hash <hash>', 'File hash for deduplication check (URL mode)')
.option('--name <name>', 'File name (URL mode)')
.option('--type <type>', 'File MIME type (URL mode)')
.option('--size <size>', 'File size in bytes (URL mode)')
.option('--parent-id <id>', 'Parent folder ID')
.option('--json [fields]', 'Output JSON, optionally specify fields (comma-separated)')
.action(
async (
url: string,
source: string | undefined,
options: {
file?: string;
hash?: string;
json?: string | boolean;
name?: string;
@@ -133,8 +136,47 @@ export function registerFileCommand(program: Command) {
type?: string;
},
) => {
const isUrl = (value: string) =>
value.startsWith('http://') || value.startsWith('https://');
// Resolve the local file path: explicit --file, or a positional that is
// not a URL (e.g. `lh file upload ./games_list.txt`).
const localPath = options.file ?? (source && !isUrl(source) ? source : undefined);
const client = await getTrpcClient();
// ── Local file upload ──
if (localPath) {
let result;
try {
result = await uploadLocalFile(client, localPath, { parentId: options.parentId });
} catch (error) {
log.error(error instanceof Error ? error.message : String(error));
process.exit(1);
return;
}
if (options.json !== undefined) {
const fields = typeof options.json === 'string' ? options.json : undefined;
outputJson(result, fields);
return;
}
const r = result as any;
console.log(`${pc.green('✓')} File created: ${pc.bold(r.id || '')}`);
if (r.url) console.log(` URL: ${pc.dim(r.url)}`);
return;
}
// ── URL upload ──
if (!source) {
log.error('Provide a local file path, --file <path>, or a URL to upload.');
process.exit(1);
return;
}
const url = source;
// Check hash first if provided
if (options.hash) {
const check = await client.file.checkFileHash.mutate({ hash: options.hash });
+11 -72
View File
@@ -1,14 +1,12 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import type { Command } from 'commander';
import pc from 'picocolors';
import { getTrpcClient } from '../api/client';
import { getAuthInfo } from '../api/http';
import { confirm, outputJson, printTable, timeAgo, truncate } from '../utils/format';
import { log } from '../utils/logger';
import { uploadLocalFile } from '../utils/uploadLocalFile';
function formatFileType(fileType: string): string {
if (!fileType) return '';
@@ -324,81 +322,22 @@ export function registerKbCommand(program: Command) {
.description('Upload a file to a knowledge base')
.option('--parent <parentId>', 'Parent folder ID')
.action(async (knowledgeBaseId: string, filePath: string, options: { parent?: string }) => {
const resolved = path.resolve(filePath);
if (!fs.existsSync(resolved)) {
log.error(`File not found: ${resolved}`);
process.exit(1);
}
const stat = fs.statSync(resolved);
const fileName = path.basename(resolved);
const fileBuffer = fs.readFileSync(resolved);
// Compute SHA-256 hash
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
// Detect MIME type from extension
const ext = path.extname(fileName).toLowerCase().slice(1);
const mimeMap: Record<string, string> = {
csv: 'text/csv',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
gif: 'image/gif',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
json: 'application/json',
md: 'text/markdown',
mp3: 'audio/mpeg',
mp4: 'video/mp4',
pdf: 'application/pdf',
png: 'image/png',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
svg: 'image/svg+xml',
txt: 'text/plain',
webp: 'image/webp',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
};
const fileType = mimeMap[ext] || 'application/octet-stream';
const client = await getTrpcClient();
const { serverUrl, headers } = await getAuthInfo();
// 1. Get presigned URL
const date = new Date().toLocaleDateString('en-CA'); // YYYY-MM-DD
const pathname = `files/${date}/${hash}.${ext}`;
const presigned = await client.upload.createS3PreSignedUrl.mutate({ pathname });
// 2. Upload to S3
const presignedUrl = typeof presigned === 'string' ? presigned : (presigned as any).url;
const uploadRes = await fetch(presignedUrl, {
body: fileBuffer,
headers: { 'Content-Type': fileType },
method: 'PUT',
});
if (!uploadRes.ok) {
log.error(`Upload failed: ${uploadRes.status} ${uploadRes.statusText}`);
let result;
try {
result = await uploadLocalFile(client, filePath, {
knowledgeBaseId,
parentId: options.parent,
});
} catch (error) {
log.error(error instanceof Error ? error.message : String(error));
process.exit(1);
return;
}
// 3. Create file record
const result = await client.file.createFile.mutate({
fileType,
hash,
knowledgeBaseId,
metadata: {
date,
dirname: '',
filename: fileName,
path: pathname,
},
name: fileName,
parentId: options.parent,
size: stat.size,
url: pathname,
});
console.log(
`${pc.green('✓')} Uploaded ${pc.bold(fileName)}${pc.bold((result as any).id)}`,
`${pc.green('✓')} Uploaded ${pc.bold(path.basename(filePath))}${pc.bold((result as any).id)}`,
);
});
}
+119
View File
@@ -0,0 +1,119 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import type { TrpcClient } from '../api/client';
/**
* Minimal extension → MIME map for files uploaded from the local filesystem.
* Unknown extensions fall back to `application/octet-stream`.
*/
const MIME_MAP: Record<string, string> = {
csv: 'text/csv',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
gif: 'image/gif',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
json: 'application/json',
md: 'text/markdown',
mp3: 'audio/mpeg',
mp4: 'video/mp4',
pdf: 'application/pdf',
png: 'image/png',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
svg: 'image/svg+xml',
txt: 'text/plain',
webp: 'image/webp',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
};
/**
* Detect a MIME type from a file name's extension.
*/
export const detectMimeType = (fileName: string): string => {
const ext = path.extname(fileName).toLowerCase().slice(1);
return MIME_MAP[ext] || 'application/octet-stream';
};
export interface UploadLocalFileOptions {
knowledgeBaseId?: string;
parentId?: string;
}
/**
* Read a file from the local filesystem, upload it to S3 via a pre-signed URL,
* and create the corresponding file record. Shared by `file upload` and
* `kb upload`.
*
* @returns the created file record
*/
export const uploadLocalFile = async (
client: TrpcClient,
filePath: string,
options: UploadLocalFileOptions = {},
) => {
const resolved = path.resolve(filePath);
if (!fs.existsSync(resolved)) {
throw new Error(`File not found: ${resolved}`);
}
const stat = fs.statSync(resolved);
if (!stat.isFile()) {
throw new Error(`Not a file: ${resolved}`);
}
const fileName = path.basename(resolved);
const fileBuffer = fs.readFileSync(resolved);
// Compute SHA-256 hash for deduplication
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
const ext = path.extname(fileName).toLowerCase().slice(1);
const fileType = detectMimeType(fileName);
const date = new Date().toLocaleDateString('en-CA'); // YYYY-MM-DD
// 1. Dedup: if the same bytes are already stored (and the object still
// exists), skip the S3 upload entirely and reuse the existing url.
const existing = (await client.file.checkFileHash.mutate({ hash })) as {
isExist?: boolean;
url?: string;
};
let pathname: string;
if (existing?.isExist && existing.url) {
pathname = existing.url;
} else {
// 2. Get a pre-signed upload URL and PUT the bytes to S3
pathname = ext ? `files/${date}/${hash}.${ext}` : `files/${date}/${hash}`;
const presigned = await client.upload.createS3PreSignedUrl.mutate({ pathname });
const presignedUrl = typeof presigned === 'string' ? presigned : (presigned as any).url;
const uploadRes = await fetch(presignedUrl, {
body: fileBuffer,
headers: { 'Content-Type': fileType },
method: 'PUT',
});
if (!uploadRes.ok) {
throw new Error(`Upload failed: ${uploadRes.status} ${uploadRes.statusText}`);
}
}
// 3. Create the file record
return await client.file.createFile.mutate({
fileType,
hash,
knowledgeBaseId: options.knowledgeBaseId,
metadata: {
date,
dirname: '',
filename: fileName,
path: pathname,
},
name: fileName,
parentId: options.parentId,
size: stat.size,
url: pathname,
});
};
@@ -29,10 +29,12 @@ describe('aiModelRouter', () => {
it('should create ai model', async () => {
const mockCreate = vi.fn().mockResolvedValue({ id: 'model-1' });
const mockFindByIdAndProvider = vi.fn().mockResolvedValue(null);
vi.mocked(AiModelModel).mockImplementation(
() =>
({
create: mockCreate,
findByIdAndProvider: mockFindByIdAndProvider,
}) as any,
);
@@ -44,12 +46,68 @@ describe('aiModelRouter', () => {
});
expect(result).toBe('model-1');
expect(mockFindByIdAndProvider).toHaveBeenCalledWith('test-model', 'test-provider');
expect(mockCreate).toHaveBeenCalledWith({
id: 'test-model',
providerId: 'test-provider',
});
});
it('should reject duplicate ai model before creating', async () => {
const mockCreate = vi.fn();
const mockFindByIdAndProvider = vi.fn().mockResolvedValue({ id: 'test-model' });
vi.mocked(AiModelModel).mockImplementation(
() =>
({
create: mockCreate,
findByIdAndProvider: mockFindByIdAndProvider,
}) as any,
);
const caller = aiModelRouter.createCaller(mockCtx);
await expect(
caller.createAiModel({
id: 'test-model',
providerId: 'test-provider',
}),
).rejects.toMatchObject({
code: 'CONFLICT',
message: 'Model "test-model" already exists',
});
expect(mockCreate).not.toHaveBeenCalled();
});
it('should convert duplicate insert races to conflict errors', async () => {
const duplicateError = Object.assign(new Error('failed query'), {
cause: Object.assign(new Error('duplicate key'), {
code: '23505',
constraint: 'ai_models_id_provider_id_user_id_unique',
}),
});
const mockCreate = vi.fn().mockRejectedValue(duplicateError);
const mockFindByIdAndProvider = vi.fn().mockResolvedValue(null);
vi.mocked(AiModelModel).mockImplementation(
() =>
({
create: mockCreate,
findByIdAndProvider: mockFindByIdAndProvider,
}) as any,
);
const caller = aiModelRouter.createCaller(mockCtx);
await expect(
caller.createAiModel({
id: 'test-model',
providerId: 'test-provider',
}),
).rejects.toMatchObject({
code: 'CONFLICT',
message: 'Model "test-model" already exists',
});
});
it('should get ai model by id', async () => {
const mockModel = {
id: 'model-1',
@@ -0,0 +1,70 @@
import { TRPCError } from '@trpc/server';
import { describe, expect, it, vi } from 'vitest';
import type { DeviceModel } from '@/database/models/device';
import { assertWorkspaceRootApproved } from '../deviceWorkspaceGuard';
const mockModel = (row: { defaultCwd?: string | null; workingDirs?: { path: string }[] } | null) =>
({
findByDeviceId: vi.fn().mockResolvedValue(row),
}) as unknown as DeviceModel;
describe('assertWorkspaceRootApproved', () => {
it('allows a root that exactly matches a bound workingDir', async () => {
const model = mockModel({ workingDirs: [{ path: '/Users/me/proj' }] });
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/proj'),
).resolves.toBeUndefined();
});
it('allows a root nested inside a bound workingDir', async () => {
const model = mockModel({ workingDirs: [{ path: '/Users/me/proj' }] });
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/proj/packages/app'),
).resolves.toBeUndefined();
});
it('allows a root matching defaultCwd when no workingDirs match', async () => {
const model = mockModel({ defaultCwd: '/Users/me/default', workingDirs: [] });
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/default'),
).resolves.toBeUndefined();
});
it('rejects a root that escapes the approved roots (filesystem root)', async () => {
const model = mockModel({ workingDirs: [{ path: '/Users/me/proj' }] });
await expect(assertWorkspaceRootApproved(model, 'dev-1', '/')).rejects.toMatchObject({
code: 'FORBIDDEN',
});
});
it('rejects a sibling directory that shares a path prefix but is not contained', async () => {
const model = mockModel({ workingDirs: [{ path: '/Users/me/proj' }] });
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/proj-evil'),
).rejects.toMatchObject({ code: 'FORBIDDEN' });
});
it('rejects when the device has no approved roots at all', async () => {
const model = mockModel({ workingDirs: [] });
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/proj'),
).rejects.toMatchObject({ code: 'FORBIDDEN' });
});
it('rejects when the device row is missing', async () => {
const model = mockModel(null);
await expect(
assertWorkspaceRootApproved(model, 'dev-1', '/Users/me/proj'),
).rejects.toBeInstanceOf(TRPCError);
});
it('rejects an empty workspace root with BAD_REQUEST before hitting the DB', async () => {
const model = mockModel({ workingDirs: [{ path: '/Users/me/proj' }] });
await expect(assertWorkspaceRootApproved(model, 'dev-1', '')).rejects.toMatchObject({
code: 'BAD_REQUEST',
});
expect(model.findByDeviceId).not.toHaveBeenCalled();
});
});
+36 -2
View File
@@ -1,3 +1,4 @@
import { TRPCError } from '@trpc/server';
import { type AiProviderModelListItem } from 'model-bank';
import {
AiModelTypeSchema,
@@ -18,6 +19,30 @@ import { getServerGlobalConfig } from '@/server/globalConfig';
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
import { type ProviderConfig } from '@/types/user/settings';
const AI_MODEL_UNIQUE_CONSTRAINT = 'ai_models_id_provider_id_user_id_unique';
const getPostgresErrorField = (error: unknown, field: 'code' | 'constraint') => {
let current = error;
while (current && typeof current === 'object') {
const value = (current as Record<string, unknown>)[field];
if (typeof value === 'string') return value;
current = (current as { cause?: unknown }).cause;
}
};
const isDuplicateAiModelError = (error: unknown) =>
getPostgresErrorField(error, 'code') === '23505' &&
getPostgresErrorField(error, 'constraint') === AI_MODEL_UNIQUE_CONSTRAINT;
const throwDuplicateAiModelError = (id: string): never => {
throw new TRPCError({
code: 'CONFLICT',
message: `Model "${id}" already exists`,
});
};
const aiModelProcedure = wsCompatProcedure.use(serverDatabase).use(async (opts) => {
const { ctx } = opts;
const wsId = ctx.workspaceId ?? undefined;
@@ -82,9 +107,18 @@ export const aiModelRouter = router({
.use(withScopedPermission('ai_model:create'))
.input(CreateAiModelSchema)
.mutation(async ({ input, ctx }) => {
const data = await ctx.aiModelModel.create(input);
const existingModel = await ctx.aiModelModel.findByIdAndProvider(input.id, input.providerId);
if (existingModel) throwDuplicateAiModelError(input.id);
return data?.id;
try {
const data = await ctx.aiModelModel.create(input);
return data?.id;
} catch (error) {
if (isDuplicateAiModelError(error)) throwDuplicateAiModelError(input.id);
throw error;
}
}),
getAiModelById: aiModelProcedure
+38 -28
View File
@@ -8,6 +8,7 @@ import { serverDatabase } from '@/libs/trpc/lambda/middleware';
import { deviceGateway } from '@/server/services/deviceGateway';
import { preserveWorkspaceCache } from './deviceWorkingDirs';
import { assertWorkspaceRootApproved } from './deviceWorkspaceGuard';
// Derive the zod enum from the canonical config so new platforms are
// automatically covered without touching this file.
@@ -29,6 +30,23 @@ const deviceProcedure = authedProcedure.use(serverDatabase).use(async (opts) =>
});
});
const workspaceFileInput = z.object({
deviceId: z.string(),
workingDirectory: z.string(),
});
/**
* `deviceProcedure` that additionally requires `workingDirectory` to be an
* approved workspace root for the device. Builds the guard into the procedure
* so every file-mutating route inherits it and can never forget the check —
* see {@link assertWorkspaceRootApproved} for why the check is necessary.
*/
const workspaceFileProcedure = deviceProcedure.input(workspaceFileInput).use(async (opts) => {
const { deviceId, workingDirectory } = workspaceFileInput.parse(await opts.getRawInput());
await assertWorkspaceRootApproved(opts.ctx.deviceModel, deviceId, workingDirectory);
return opts.next();
});
export const deviceRouter = router({
/**
* Probe whether a specific agent platform (openclaw / hermes) is available
@@ -334,24 +352,22 @@ export const deviceRouter = router({
* Read-only local file preview for a file on a remote device. The web client
* receives render data, not a `localfile://` URL; saving remains unsupported.
*/
getLocalFilePreview: deviceProcedure
getLocalFilePreview: workspaceFileProcedure
.input(
z.object({
accept: z.enum(['image']).optional(),
deviceId: z.string(),
path: z.string(),
workingDirectory: z.string(),
}),
)
.query(async ({ ctx, input }) =>
deviceGateway.getLocalFilePreview({
.query(async ({ ctx, input }) => {
return deviceGateway.getLocalFilePreview({
accept: input.accept,
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workingDirectory: input.workingDirectory,
}),
),
});
}),
/**
* Project skills (`.agents/skills` / `.claude/skills`) for a directory on a
@@ -388,68 +404,62 @@ export const deviceRouter = router({
* Move files/folders within a directory on a remote device, via the device's
* `moveLocalFiles` RPC. Powers the Files tree's drag-to-move in device mode.
*/
moveProjectFiles: deviceProcedure
moveProjectFiles: workspaceFileProcedure
.input(
z.object({
deviceId: z.string(),
items: z.array(z.object({ newPath: z.string(), oldPath: z.string() })),
workingDirectory: z.string(),
}),
)
.mutation(async ({ ctx, input }) =>
deviceGateway.moveProjectFiles({
.mutation(async ({ ctx, input }) => {
return deviceGateway.moveProjectFiles({
deviceId: input.deviceId,
items: input.items,
userId: ctx.userId,
workingDirectory: input.workingDirectory,
}),
),
});
}),
/**
* Rename a single file/folder in a directory on a remote device, via the
* device's `renameLocalFile` RPC.
*/
renameProjectFile: deviceProcedure
renameProjectFile: workspaceFileProcedure
.input(
z.object({
deviceId: z.string(),
newName: z.string(),
path: z.string(),
workingDirectory: z.string(),
}),
)
.mutation(async ({ ctx, input }) =>
deviceGateway.renameProjectFile({
.mutation(async ({ ctx, input }) => {
return deviceGateway.renameProjectFile({
deviceId: input.deviceId,
newName: input.newName,
path: input.path,
userId: ctx.userId,
workingDirectory: input.workingDirectory,
}),
),
});
}),
/**
* Save edited content back to a file on a remote device, via the device's
* `writeLocalFile` RPC. Powers remote save in the LocalFile editor.
*/
writeProjectFile: deviceProcedure
writeProjectFile: workspaceFileProcedure
.input(
z.object({
content: z.string(),
deviceId: z.string(),
path: z.string(),
workingDirectory: z.string(),
}),
)
.mutation(async ({ ctx, input }) =>
deviceGateway.writeProjectFile({
.mutation(async ({ ctx, input }) => {
return deviceGateway.writeProjectFile({
content: input.content,
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workingDirectory: input.workingDirectory,
}),
),
});
}),
/**
* Check whether a path exists on a remote device and is a directory, via the
@@ -0,0 +1,52 @@
import { TRPCError } from '@trpc/server';
import type { DeviceModel } from '@/database/models/device';
import { isPathWithinRoot } from '@/server/services/deviceGateway';
/**
* Validate that a client-supplied workspace root is actually one the user has
* bound to this device.
*
* The file routes (move / rename / write / preview) receive `workingDirectory`
* from the same untrusted browser session that supplies the file paths. The
* gateway's `assertPathsWithinWorkspace` only proves the paths sit *inside that
* directory* — it never proves the directory itself is legitimate. So a caller
* could set `workingDirectory` to `/` (or `C:\`), pass that containment check
* trivially, and reach any path on the device.
*
* To close that hole we re-derive the approved roots from the *server-owned*
* device row — the `workingDirs` recent list and `defaultCwd`, both written only
* via `device.updateDevice` / the run path, never trusted from this request —
* and require the requested root to equal or nest inside one of them before any
* RPC is forwarded. The picker upserts every chosen directory into `workingDirs`
* (see `useCommitWorkingDirectory`) and run start upserts the bound cwd, so a
* legitimately-selected workspace is always present here.
*/
export const assertWorkspaceRootApproved = async (
deviceModel: DeviceModel,
deviceId: string,
workingDirectory: string,
): Promise<void> => {
if (!workingDirectory) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'A workspace root is required for file operations',
});
}
const device = await deviceModel.findByDeviceId(deviceId);
const approvedRoots = [
...(device?.workingDirs ?? []).map((dir) => dir.path),
...(device?.defaultCwd ? [device.defaultCwd] : []),
].filter((root): root is string => Boolean(root));
const approved = approvedRoots.some((root) => isPathWithinRoot(root, workingDirectory));
if (!approved) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Working directory is not an approved workspace for this device',
});
}
};
@@ -50,7 +50,7 @@ const log = debug('lobe-server:device-gateway');
* path flavour from the root's shape so a Windows device path is still resolved
* with Windows semantics rather than being mangled by `path.posix`.
*/
const isPathWithinRoot = (root: string, target: string): boolean => {
export const isPathWithinRoot = (root: string, target: string): boolean => {
const p = /^[A-Z]:[/\\]/i.test(root) ? path.win32 : path.posix;
if (!p.isAbsolute(root) || !p.isAbsolute(target)) return false;
const relative = p.relative(p.resolve(root), p.resolve(target));
@@ -171,6 +171,9 @@ describe('EditLockService', () => {
expect(
await new EditLockService('user-2', redis as any).getBlockingHolder('document', 'doc-1'),
).toBe('user-1');
expect(
await new EditLockService('user-1', redis as any).getBlockingHolder('document', 'doc-1'),
).toBeNull();
expect(
await new EditLockService('user-1', redis as any).getBlockingHolder(
'document',
+6 -2
View File
@@ -203,9 +203,13 @@ export class EditLockService {
const holder = await this.getActiveLock(type, id);
if (!holder) return null;
// ownerId is broadcast on lock.changed; it can't authorize on its own.
// Bind to userId first, then keep the stale-tab guard (same user, different
// active ownerId still blocks so a ghost tab can't save over a newer one).
// Bind to userId first. When callers pass an ownerId, also keep the
// stale-tab guard (same user, different active ownerId blocks so a ghost tab
// can't save over a newer one). Callers without owner-scoped writes only
// need to reject other members; otherwise they would block their own generic
// metadata updates while holding the edit lock.
if (holder.userId !== this.userId) return holder.userId;
if (!ownerId) return null;
if (holder.ownerId && holder.ownerId !== ownerId) return holder.userId;
return null;
-5
View File
@@ -1,9 +1,4 @@
[
{
"children": {},
"date": "2026-06-17",
"version": "2.2.6"
},
{
"children": {},
"date": "2026-05-29",
+341 -169
View File
@@ -1,212 +1,384 @@
/**
* Mock data for Discover/Community module
* Mock data for Discover/Community module.
*
* Community E2E tests should not depend on the live marketplace service. These
* fixtures mirror the data shape returned by the app's tRPC market router.
*/
import type {
AssistantListResponse,
DiscoverAssistantItem,
DiscoverMcpItem,
DiscoverModelItem,
DiscoverProviderItem,
McpListResponse,
ModelListResponse,
ProviderListResponse,
} from './types';
const CREATED_AT = '2026-01-01T00:00:00.000Z';
const UPDATED_AT = '2026-01-10T00:00:00.000Z';
// ============================================
// Assistant Mock Data
// ============================================
export const mockAssistantList: AssistantListResponse = {
items: [
{
author: 'LobeHub',
avatar: '🤖',
backgroundColor: '#1890ff',
category: 'general',
createdAt: '2024-01-01T00:00:00.000Z',
description: 'A versatile AI assistant for general tasks and conversations.',
identifier: 'general-assistant',
installCount: 1000,
knowledgeCount: 5,
pluginCount: 3,
title: 'General Assistant',
tokenUsage: 4096,
userName: 'lobehub',
export const mockAssistantItems: DiscoverAssistantItem[] = [
{
author: 'LobeHub',
avatar: '🤖',
backgroundColor: '#1890ff',
category: 'general',
config: {
openingMessage: 'Hello, I am your general assistant.',
openingQuestions: ['What can you do?'],
params: {},
plugins: [],
systemRole: 'You are a helpful general-purpose assistant for E2E tests.',
},
{
author: 'LobeHub',
avatar: '💻',
backgroundColor: '#52c41a',
category: 'programming',
createdAt: '2024-01-02T00:00:00.000Z',
description: 'Expert coding assistant for software development.',
identifier: 'code-assistant',
installCount: 800,
knowledgeCount: 10,
pluginCount: 5,
title: 'Code Assistant',
tokenUsage: 8192,
userName: 'lobehub',
},
{
author: 'LobeHub',
avatar: '✍️',
backgroundColor: '#722ed1',
category: 'copywriting',
createdAt: '2024-01-03T00:00:00.000Z',
description: 'Professional writing assistant for content creation.',
identifier: 'writing-assistant',
installCount: 600,
knowledgeCount: 3,
pluginCount: 2,
title: 'Writing Assistant',
tokenUsage: 4096,
userName: 'lobehub',
},
],
pagination: {
page: 1,
pageSize: 12,
total: 3,
totalPages: 1,
createdAt: CREATED_AT,
description: 'A versatile AI assistant for general tasks and conversations.',
identifier: 'general-assistant',
installCount: 1000,
knowledgeCount: 1,
pluginCount: 0,
summary: 'General-purpose assistant fixture.',
tags: ['general', 'fixture'],
title: 'General Assistant',
tokenUsage: 4096,
type: 'agent',
updatedAt: UPDATED_AT,
userName: 'lobehub',
},
{
author: 'LobeHub',
avatar: '💻',
backgroundColor: '#52c41a',
category: 'programming',
config: {
openingMessage: 'Ready to help with development tasks.',
openingQuestions: ['Review this function'],
params: {},
plugins: [],
systemRole: 'You are an expert coding assistant for E2E tests.',
},
createdAt: CREATED_AT,
description: 'Developer and coding assistant for software engineering workflows.',
identifier: 'code-assistant',
installCount: 800,
knowledgeCount: 2,
pluginCount: 1,
summary: 'Developer assistant fixture.',
tags: ['developer', 'programming'],
title: 'Code Assistant',
tokenUsage: 8192,
type: 'agent',
updatedAt: UPDATED_AT,
userName: 'lobehub',
},
{
author: 'LobeHub',
avatar: '🎓',
backgroundColor: '#faad14',
category: 'academic',
config: {
openingMessage: 'Let us study together.',
openingQuestions: ['Explain this concept'],
params: {},
plugins: [],
systemRole: 'You are an academic tutor for E2E tests.',
},
createdAt: CREATED_AT,
description: 'Academic research and study assistant for reliable category filtering.',
identifier: 'academic-tutor',
installCount: 640,
knowledgeCount: 3,
pluginCount: 0,
summary: 'Academic assistant fixture.',
tags: ['academic', 'education'],
title: 'Academic Tutor',
tokenUsage: 4096,
type: 'agent',
updatedAt: UPDATED_AT,
userName: 'lobehub',
},
{
author: 'LobeHub',
avatar: '✍️',
backgroundColor: '#722ed1',
category: 'copywriting',
config: {
openingMessage: 'Tell me what you want to write.',
openingQuestions: ['Draft a product intro'],
params: {},
plugins: [],
systemRole: 'You are a writing assistant for E2E tests.',
},
createdAt: CREATED_AT,
description: 'Professional writing assistant for content creation.',
identifier: 'writing-assistant',
installCount: 600,
knowledgeCount: 1,
pluginCount: 0,
summary: 'Writing assistant fixture.',
tags: ['copywriting'],
title: 'Writing Assistant',
tokenUsage: 4096,
type: 'agent',
updatedAt: UPDATED_AT,
userName: 'lobehub',
},
];
export const mockAssistantList: AssistantListResponse = {
currentPage: 1,
items: mockAssistantItems,
pageSize: 21,
totalCount: 42,
totalPages: 2,
};
export const mockAssistantCategories = [
{ id: 'general', name: 'General' },
{ id: 'programming', name: 'Programming' },
{ id: 'copywriting', name: 'Copywriting' },
{ id: 'education', name: 'Education' },
{ category: 'general', count: 12 },
{ category: 'programming', count: 10 },
{ category: 'academic', count: 8 },
{ category: 'copywriting', count: 6 },
];
// ============================================
// Model Mock Data
// ============================================
export const mockModelList: ModelListResponse = {
items: [
{
abilities: { functionCall: true, reasoning: true, vision: true },
contextWindowTokens: 128_000,
createdAt: '2024-01-01T00:00:00.000Z',
description: 'Most capable model for complex tasks',
displayName: 'GPT-4o',
id: 'gpt-4o',
providerId: 'openai',
providerName: 'OpenAI',
type: 'chat',
},
{
abilities: { functionCall: true, reasoning: true, vision: false },
contextWindowTokens: 200_000,
createdAt: '2024-01-02T00:00:00.000Z',
description: 'Advanced AI assistant by Anthropic',
displayName: 'Claude 3.5 Sonnet',
id: 'claude-3-5-sonnet-20241022',
providerId: 'anthropic',
providerName: 'Anthropic',
type: 'chat',
},
{
abilities: { functionCall: false, reasoning: false, vision: false },
contextWindowTokens: 32_768,
createdAt: '2024-01-03T00:00:00.000Z',
description: 'Open source language model',
displayName: 'Llama 3.1 70B',
id: 'llama-3.1-70b',
providerId: 'meta',
providerName: 'Meta',
type: 'chat',
},
],
pagination: {
page: 1,
pageSize: 12,
total: 3,
totalPages: 1,
export const mockModelItems: DiscoverModelItem[] = [
{
abilities: { functionCall: true, reasoning: true, vision: true },
contextWindowTokens: 128_000,
description: 'Most capable fixture model for complex tasks.',
displayName: 'GPT-4o',
id: 'gpt-4o',
identifier: 'gpt-4o',
providerCount: 2,
providers: ['openai', 'lobehub'],
releasedAt: CREATED_AT,
type: 'chat',
},
{
abilities: { functionCall: true, reasoning: true, vision: false },
contextWindowTokens: 200_000,
description: 'Advanced AI assistant fixture by Anthropic.',
displayName: 'Claude 3.5 Sonnet',
id: 'claude-3-5-sonnet-20241022',
identifier: 'claude-3-5-sonnet-20241022',
providerCount: 1,
providers: ['anthropic'],
releasedAt: CREATED_AT,
type: 'chat',
},
{
abilities: { functionCall: false, reasoning: false, vision: false },
contextWindowTokens: 32_768,
description: 'Open source language model fixture.',
displayName: 'Llama 3.1 70B',
id: 'llama-3.1-70b',
identifier: 'llama-3.1-70b',
providerCount: 1,
providers: ['meta'],
releasedAt: CREATED_AT,
type: 'chat',
},
];
export const mockModelList: ModelListResponse = {
currentPage: 1,
items: mockModelItems,
pageSize: 21,
totalCount: mockModelItems.length,
totalPages: 1,
};
// ============================================
// Provider Mock Data
// ============================================
export const mockProviderList: ProviderListResponse = {
items: [
{
description: 'Leading AI research company',
id: 'openai',
logo: 'https://example.com/openai.png',
modelCount: 10,
name: 'OpenAI',
},
{
description: 'AI safety focused research company',
id: 'anthropic',
logo: 'https://example.com/anthropic.png',
modelCount: 5,
name: 'Anthropic',
},
{
description: 'Open source AI leader',
id: 'meta',
logo: 'https://example.com/meta.png',
modelCount: 8,
name: 'Meta',
},
],
pagination: {
page: 1,
pageSize: 12,
total: 3,
totalPages: 1,
export const mockProviderItems: DiscoverProviderItem[] = [
{
description: 'Leading AI research company fixture.',
identifier: 'openai',
modelCount: 2,
models: ['gpt-4o', 'gpt-4o-mini'],
name: 'OpenAI',
url: 'https://openai.com',
},
{
description: 'AI safety focused research company fixture.',
identifier: 'anthropic',
modelCount: 1,
models: ['claude-3-5-sonnet-20241022'],
name: 'Anthropic',
url: 'https://anthropic.com',
},
{
description: 'Open source AI leader fixture.',
identifier: 'meta',
modelCount: 1,
models: ['llama-3.1-70b'],
name: 'Meta',
url: 'https://ai.meta.com',
},
];
export const mockProviderList: ProviderListResponse = {
currentPage: 1,
items: mockProviderItems,
pageSize: 21,
totalCount: mockProviderItems.length,
totalPages: 1,
};
// ============================================
// MCP Mock Data
// ============================================
export const mockMcpList: McpListResponse = {
items: [
{
author: 'LobeHub',
avatar: '🔍',
category: 'search',
createdAt: '2024-01-01T00:00:00.000Z',
description: 'Web search capabilities for AI assistants',
identifier: 'web-search',
installCount: 500,
title: 'Web Search',
},
{
author: 'LobeHub',
avatar: '📁',
category: 'file',
createdAt: '2024-01-02T00:00:00.000Z',
description: 'File system operations and management',
identifier: 'file-manager',
installCount: 300,
title: 'File Manager',
},
{
author: 'LobeHub',
avatar: '🗄️',
category: 'database',
createdAt: '2024-01-03T00:00:00.000Z',
description: 'Database query and management tools',
identifier: 'db-tools',
installCount: 200,
title: 'Database Tools',
},
],
pagination: {
page: 1,
pageSize: 12,
total: 3,
totalPages: 1,
export const mockMcpItems: DiscoverMcpItem[] = [
{
author: 'LobeHub',
capabilities: { prompts: false, resources: false, tools: true },
category: 'business',
connectionType: 'stdio',
createdAt: CREATED_AT,
description: 'Business automation MCP tool fixture.',
github: { stars: 1200, url: 'https://github.com/lobehub/e2e-business-mcp' },
icon: '📊',
identifier: 'business-automation',
installCount: 500,
installationMethods: 'npm',
isClaimed: true,
isFeatured: true,
isOfficial: true,
isValidated: true,
manifestUrl: 'https://example.com/business-automation/manifest.json',
name: 'Business Automation',
toolsCount: 3,
updatedAt: UPDATED_AT,
},
{
author: 'LobeHub',
capabilities: { prompts: false, resources: true, tools: true },
category: 'developer',
connectionType: 'stdio',
createdAt: CREATED_AT,
description: 'Developer file-system MCP fixture.',
github: { stars: 900, url: 'https://github.com/lobehub/e2e-file-mcp' },
icon: '📁',
identifier: 'file-manager',
installCount: 300,
installationMethods: 'npm',
isClaimed: true,
isFeatured: false,
isOfficial: false,
isValidated: true,
manifestUrl: 'https://example.com/file-manager/manifest.json',
name: 'File Manager',
resourcesCount: 2,
toolsCount: 5,
updatedAt: UPDATED_AT,
},
{
author: 'LobeHub',
capabilities: { prompts: true, resources: false, tools: true },
category: 'productivity',
connectionType: 'http',
createdAt: CREATED_AT,
description: 'Productivity search MCP fixture.',
github: { stars: 600, url: 'https://github.com/lobehub/e2e-search-mcp' },
icon: '🔍',
identifier: 'web-search',
installCount: 260,
installationMethods: 'docker',
isClaimed: false,
isFeatured: false,
isOfficial: false,
isValidated: true,
manifestUrl: 'https://example.com/web-search/manifest.json',
name: 'Web Search',
promptsCount: 1,
toolsCount: 2,
updatedAt: UPDATED_AT,
},
];
export const mockMcpList: McpListResponse = {
categories: ['business', 'developer', 'productivity'],
currentPage: 1,
items: mockMcpItems,
pageSize: 21,
totalCount: mockMcpItems.length,
totalPages: 1,
};
export const mockMcpCategories = [
{ id: 'search', name: 'Search' },
{ id: 'file', name: 'File' },
{ id: 'database', name: 'Database' },
{ id: 'utility', name: 'Utility' },
{ category: 'business', count: 7 },
{ category: 'developer', count: 5 },
{ category: 'productivity', count: 3 },
];
// ============================================
// Detail Mock Data
// ============================================
export const mockAssistantDetails = mockAssistantItems.map((item) => ({
...item,
currentVersion: '1.0.0',
related: mockAssistantItems
.filter((related) => related.identifier !== item.identifier)
.slice(0, 3),
versions: [
{
createdAt: item.createdAt,
isLatest: true,
isValidated: true,
status: 'published',
version: '1.0.0',
},
],
}));
export const mockMcpDetails = mockMcpItems.map((item) => ({
...item,
author: { name: item.author ?? 'LobeHub', url: 'https://lobehub.com' },
deploymentOptions: [
{
connection: { command: 'npx', type: item.connectionType ?? 'stdio' },
installationMethod: item.installationMethods ?? 'npm',
title: 'E2E recommended deployment',
},
],
overview: {
readme: `# ${item.name}\n\n${item.description}`,
summary: item.description,
},
related: mockMcpItems.filter((related) => related.identifier !== item.identifier).slice(0, 2),
tools: [{ description: 'Fixture tool for E2E tests', name: 'fixtureTool' }],
version: '1.0.0',
versions: [{ isLatest: true, version: '1.0.0' }],
}));
export const mockModelDetails = mockModelItems.map((item) => ({
...item,
providers: mockProviderItems.map((provider) => ({
...provider,
id: provider.identifier,
model: item,
})),
related: mockModelItems.filter((related) => related.identifier !== item.identifier).slice(0, 2),
}));
export const mockProviderDetails = mockProviderItems.map((item) => ({
...item,
models: mockModelItems
.filter((model) => item.models.includes(model.identifier))
.map((model) => ({ ...model, maxOutput: 4096 })),
readme: `# ${item.name}\n\n${item.description}`,
related: mockProviderItems
.filter((related) => related.identifier !== item.identifier)
.slice(0, 2),
}));
+312 -145
View File
@@ -1,179 +1,346 @@
/**
* Mock handlers for Discover/Community API endpoints
* Mock handlers for Discover/Community API endpoints.
*/
import type { Route } from 'playwright';
import type { Request, Route } from 'playwright';
import superjson from 'superjson';
import { type MockHandler, createTrpcResponse } from '../index';
import type { MockHandler } from '../index';
import {
mockAssistantCategories,
mockAssistantDetails,
mockAssistantItems,
mockAssistantList,
mockMcpCategories,
mockMcpDetails,
mockMcpItems,
mockMcpList,
mockModelDetails,
mockModelItems,
mockModelList,
mockProviderDetails,
mockProviderItems,
mockProviderList,
} from './data';
// ============================================
// Helper to parse tRPC batch requests
// ============================================
interface IdentifierEntry {
identifier: string;
lastModified: string;
}
function parseTrpcUrl(url: string): { input?: Record<string, unknown>; procedure: string } {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const SUCCESS_RESPONSE = { success: true };
// Extract procedure name from path like /trpc/lambda.market.getAssistantList
const procedureMatch = pathname.match(/lambda\.market\.(\w+)/);
const procedure = procedureMatch ? procedureMatch[1] : '';
const createTrpcResult = <T>(data: T) => ({
result: {
data: superjson.serialize(data),
},
});
// Parse input from query string
let input: Record<string, unknown> | undefined;
const inputParam = urlObj.searchParams.get('input');
if (inputParam) {
const createTrpcResponse = <T>(data: T): string => JSON.stringify(createTrpcResult(data));
const createTrpcBatchResponse = <T>(data: T[]): string =>
JSON.stringify(data.map((item) => createTrpcResult(item)));
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value);
const getStringInput = (input: unknown, key: string): string | undefined => {
if (!isRecord(input)) return undefined;
const value = input[key];
return typeof value === 'string' ? value : undefined;
};
const getNumberInput = (input: unknown, key: string): number | undefined => {
if (!isRecord(input)) return undefined;
const value = input[key];
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value !== 'string') return undefined;
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue : undefined;
};
const createIdentifiers = (
items: { identifier: string; updatedAt?: string }[],
): IdentifierEntry[] =>
items.map((item) => ({ identifier: item.identifier, lastModified: item.updatedAt ?? '' }));
const unwrapTrpcInput = (input: unknown): unknown => {
if (!isRecord(input)) return input;
if ('json' in input) return input.json;
return input;
};
const parseRequestInput = (request: Request, url: URL): unknown => {
const input = url.searchParams.get('input');
if (input) {
try {
input = JSON.parse(inputParam);
return JSON.parse(input);
} catch {
// Ignore parse errors
return undefined;
}
}
return { input, procedure };
}
try {
return request.postDataJSON();
} catch {
return undefined;
}
};
// ============================================
// Mock Handlers
// ============================================
const getProcedureInputs = (request: Request, url: URL, count: number): unknown[] => {
const rawInput = parseRequestInput(request, url);
const isBatch = url.searchParams.get('batch') === '1' || count > 1;
/**
* Handler for assistant list endpoint
*/
const assistantListHandler: MockHandler = {
if (isBatch && isRecord(rawInput)) {
return Array.from({ length: count }, (_, index) => unwrapTrpcInput(rawInput[String(index)]));
}
return [unwrapTrpcInput(rawInput)];
};
const getProcedures = (url: URL): string[] => {
const marker = '/trpc/lambda/';
const pathname = decodeURIComponent(url.pathname);
const markerIndex = pathname.indexOf(marker);
if (markerIndex === -1) return [];
const procedureSegment = pathname.slice(markerIndex + marker.length);
return procedureSegment.split(',').filter(Boolean);
};
const isMarketProcedure = (procedure: string): boolean => procedure.startsWith('market.');
const matchesText = (value: string | undefined, query: string) =>
value?.toLowerCase().includes(query.toLowerCase()) ?? false;
const paginate = <T>(items: T[], input: unknown, fallbackTotal = items.length) => {
const page = getNumberInput(input, 'page') ?? 1;
const pageSize = getNumberInput(input, 'pageSize') ?? 21;
return {
currentPage: page,
items,
pageSize,
totalCount: Math.max(fallbackTotal, items.length),
totalPages: Math.max(1, Math.ceil(Math.max(fallbackTotal, items.length) / pageSize)),
};
};
const getAssistantList = (input: unknown) => {
const category = getStringInput(input, 'category');
const query = getStringInput(input, 'q');
let items = mockAssistantItems;
if (category && !['all', 'discover'].includes(category)) {
const filtered = items.filter((item) => item.category === category);
if (filtered.length > 0) items = filtered;
}
if (query) {
const filtered = items.filter(
(item) =>
matchesText(item.title, query) ||
matchesText(item.description, query) ||
matchesText(item.identifier, query) ||
matchesText(item.tags?.join(' '), query),
);
if (filtered.length > 0) items = filtered;
}
return { ...mockAssistantList, ...paginate(items, input, 42) };
};
const getMcpList = (input: unknown) => {
const category = getStringInput(input, 'category');
const query = getStringInput(input, 'q');
let items = mockMcpItems;
if (category && !['all', 'discover'].includes(category)) {
const filtered = items.filter((item) => item.category === category);
if (filtered.length > 0) items = filtered;
}
if (query) {
const filtered = items.filter(
(item) =>
matchesText(item.name, query) ||
matchesText(item.description, query) ||
matchesText(item.identifier, query),
);
if (filtered.length > 0) items = filtered;
}
return { ...mockMcpList, ...paginate(items, input), categories: mockMcpList.categories };
};
const getModelList = (input: unknown) => {
const query = getStringInput(input, 'q');
let items = mockModelItems;
if (query) {
const filtered = items.filter(
(item) =>
matchesText(item.displayName, query) ||
matchesText(item.description, query) ||
matchesText(item.identifier, query),
);
if (filtered.length > 0) items = filtered;
}
return { ...mockModelList, ...paginate(items, input) };
};
const getProviderList = (input: unknown) => {
const query = getStringInput(input, 'q');
let items = mockProviderItems;
if (query) {
const filtered = items.filter(
(item) =>
matchesText(item.name, query) ||
matchesText(item.description, query) ||
matchesText(item.identifier, query),
);
if (filtered.length > 0) items = filtered;
}
return { ...mockProviderList, ...paginate(items, input) };
};
const findByIdentifier = <T extends { identifier: string }>(items: T[], input: unknown): T => {
const identifier = getStringInput(input, 'identifier');
return items.find((item) => item.identifier === identifier) ?? items[0];
};
const getMockResponse = (procedure: string, input: unknown): unknown => {
switch (procedure) {
case 'market.getAssistantCategories': {
return mockAssistantCategories;
}
case 'market.getAssistantDetail': {
return findByIdentifier(mockAssistantDetails, input);
}
case 'market.getAssistantIdentifiers': {
return createIdentifiers(mockAssistantItems);
}
case 'market.getAssistantList': {
return getAssistantList(input);
}
case 'market.getMcpCategories': {
return mockMcpCategories;
}
case 'market.getMcpDetail': {
return findByIdentifier(mockMcpDetails, input);
}
case 'market.getMcpList': {
return getMcpList(input);
}
case 'market.getModelCategories': {
return [];
}
case 'market.getModelDetail': {
return findByIdentifier(mockModelDetails, input);
}
case 'market.getModelIdentifiers': {
return createIdentifiers(mockModelItems);
}
case 'market.getModelList': {
return getModelList(input);
}
case 'market.getProviderDetail': {
return findByIdentifier(mockProviderDetails, input);
}
case 'market.getProviderIdentifiers': {
return createIdentifiers(mockProviderItems);
}
case 'market.getProviderList': {
return getProviderList(input);
}
case 'market.registerClientInMarketplace': {
return { clientId: 'e2e-market-client', clientSecret: 'e2e-market-secret' };
}
case 'market.registerM2MToken': {
return SUCCESS_RESPONSE;
}
case 'market.reportAgentEvent':
case 'market.reportAgentInstall':
case 'market.reportCall':
case 'market.reportGroupAgentEvent':
case 'market.reportGroupAgentInstall':
case 'market.reportMcpEvent':
case 'market.reportMcpInstallResult': {
return SUCCESS_RESPONSE;
}
case 'plugin.getPlugins': {
return [];
}
default: {
console.log(` ⚠️ Unhandled mocked lambda endpoint: ${procedure}`);
return SUCCESS_RESPONSE;
}
}
};
const marketHandler: MockHandler = {
handler: async (route: Route) => {
const request = route.request();
const url = new URL(request.url());
const procedures = getProcedures(url);
if (!procedures.some(isMarketProcedure)) {
await route.continue();
return;
}
const inputs = getProcedureInputs(request, url, procedures.length);
// Keep tRPC batch positions intact. Community pages can batch mocked
// market.* calls with normal app calls, such as plugin.getPlugins on the MCP
// detail page; returning only market responses would make the batch client
// read the wrong result for subsequent procedures.
const responses = procedures.map((procedure, index) =>
getMockResponse(procedure, inputs[index]),
);
const isBatch = url.searchParams.get('batch') === '1' || procedures.length > 1;
await route.fulfill({
body: createTrpcResponse(mockAssistantList),
body: isBatch ? createTrpcBatchResponse(responses) : createTrpcResponse(responses[0]),
contentType: 'application/json',
headers: {
'Set-Cookie': 'mp_token_status=active; Path=/; SameSite=Lax',
},
status: 200,
});
},
pattern: '**/trpc/lambda/market.getAssistantList**',
};
/**
* Handler for assistant categories endpoint
*/
const assistantCategoriesHandler: MockHandler = {
handler: async (route: Route) => {
await route.fulfill({
body: createTrpcResponse(mockAssistantCategories),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.getAssistantCategories**',
};
/**
* Handler for model list endpoint
*/
const modelListHandler: MockHandler = {
handler: async (route: Route) => {
await route.fulfill({
body: createTrpcResponse(mockModelList),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.getModelList**',
};
/**
* Handler for provider list endpoint
*/
const providerListHandler: MockHandler = {
handler: async (route: Route) => {
await route.fulfill({
body: createTrpcResponse(mockProviderList),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.getProviderList**',
};
/**
* Handler for MCP list endpoint
*/
const mcpListHandler: MockHandler = {
handler: async (route: Route) => {
await route.fulfill({
body: createTrpcResponse(mockMcpList),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.getMcpList**',
};
/**
* Handler for MCP categories endpoint
*/
const mcpCategoriesHandler: MockHandler = {
handler: async (route: Route) => {
await route.fulfill({
body: createTrpcResponse(mockMcpCategories),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.getMcpCategories**',
};
/**
* Debug handler to log all trpc requests
*/
const trpcDebugHandler: MockHandler = {
handler: async (route: Route) => {
const url = route.request().url();
console.log(` 🔍 TRPC Request: ${url}`);
await route.continue();
},
pattern: '**/trpc/**',
};
/**
* Fallback handler for any unhandled market endpoints
* Returns empty data to prevent hanging requests
*/
const marketFallbackHandler: MockHandler = {
handler: async (route: Route) => {
const url = route.request().url();
const { procedure } = parseTrpcUrl(url);
console.log(` ⚠️ Unhandled market endpoint: ${procedure}`);
// Return empty response to prevent timeout
await route.fulfill({
body: createTrpcResponse({ items: [], pagination: { page: 1, pageSize: 12, total: 0 } }),
contentType: 'application/json',
status: 200,
});
},
pattern: '**/trpc/lambda/market.**',
pattern: '**/trpc/lambda/**',
};
// ============================================
// Export all handlers
// ============================================
export const discoverHandlers: MockHandler[] = [
// Debug handler first to log all requests
trpcDebugHandler,
// Specific handlers (order matters - more specific first)
assistantListHandler,
assistantCategoriesHandler,
modelListHandler,
providerListHandler,
mcpListHandler,
mcpCategoriesHandler,
// Fallback handler (should be last)
marketFallbackHandler,
];
export const discoverHandlers: MockHandler[] = [marketHandler];
+50 -28
View File
@@ -1,12 +1,15 @@
/**
* Type definitions for Discover mock data
* These mirror the actual types from the application
* Type definitions for Discover mock data.
*
* Keep these small and E2E-focused: they only include fields the Community UI
* reads while rendering list and detail pages.
*/
export interface PaginationInfo {
page: number;
export interface ListResponse<T> {
currentPage: number;
items: T[];
pageSize: number;
total: number;
totalCount: number;
totalPages: number;
}
@@ -19,21 +22,25 @@ export interface DiscoverAssistantItem {
avatar: string;
backgroundColor?: string;
category: string;
config?: Record<string, unknown>;
createdAt: string;
description: string;
examples?: Record<string, unknown>[];
identifier: string;
installCount?: number;
knowledgeCount?: number;
pluginCount?: number;
related?: DiscoverAssistantItem[];
summary?: string;
tags?: string[];
title: string;
tokenUsage?: number;
type?: 'agent' | 'agent-group';
updatedAt?: string;
userName?: string;
}
export interface AssistantListResponse {
items: DiscoverAssistantItem[];
pagination: PaginationInfo;
}
export type AssistantListResponse = ListResponse<DiscoverAssistantItem>;
// ============================================
// Model Types
@@ -46,19 +53,17 @@ export interface DiscoverModelItem {
vision?: boolean;
};
contextWindowTokens: number;
createdAt: string;
description: string;
displayName: string;
id: string;
providerId: string;
providerName: string;
identifier: string;
providerCount: number;
providers: string[];
releasedAt?: string;
type: string;
}
export interface ModelListResponse {
items: DiscoverModelItem[];
pagination: PaginationInfo;
}
export type ModelListResponse = ListResponse<DiscoverModelItem>;
// ============================================
// Provider Types
@@ -66,33 +71,50 @@ export interface ModelListResponse {
export interface DiscoverProviderItem {
description: string;
id: string;
logo?: string;
identifier: string;
modelCount: number;
models: string[];
name: string;
url?: string;
}
export interface ProviderListResponse {
items: DiscoverProviderItem[];
pagination: PaginationInfo;
}
export type ProviderListResponse = ListResponse<DiscoverProviderItem>;
// ============================================
// MCP Types
// ============================================
export interface DiscoverMcpItem {
author: string;
avatar: string;
author?: string;
capabilities: {
prompts: boolean;
resources: boolean;
tools: boolean;
};
category: string;
connectionType?: 'http' | 'stdio';
createdAt: string;
description: string;
github?: {
stars?: number;
url: string;
};
icon?: string;
identifier: string;
installationMethods?: string;
installCount?: number;
title: string;
isClaimed?: boolean;
isFeatured?: boolean;
isOfficial?: boolean;
isValidated?: boolean;
manifestUrl: string;
name: string;
promptsCount?: number;
resourcesCount?: number;
toolsCount?: number;
updatedAt: string;
}
export interface McpListResponse {
items: DiscoverMcpItem[];
pagination: PaginationInfo;
export interface McpListResponse extends ListResponse<DiscoverMcpItem> {
categories: string[];
}
+16 -4
View File
@@ -5,6 +5,7 @@
* It uses Playwright's route interception to mock tRPC and REST API calls.
*/
import type { Page, Route } from 'playwright';
import superjson from 'superjson';
import { discoverMocks } from './community';
@@ -124,12 +125,23 @@ export class MockManager {
/**
* Create a JSON response for tRPC endpoints
*/
export function createTrpcResponse<T>(data: T): string {
return JSON.stringify({
export function createTrpcResult<T>(data: T) {
return {
result: {
data,
data: superjson.serialize(data),
},
});
};
}
export function createTrpcResponse<T>(data: T): string {
return JSON.stringify(createTrpcResult(data));
}
/**
* Create a JSON response for batched tRPC endpoints
*/
export function createTrpcBatchResponse<T>(data: T[]): string {
return JSON.stringify(data.map((item) => createTrpcResult(item)));
}
/**
+11 -2
View File
@@ -1,6 +1,7 @@
import { After, AfterAll, Before, BeforeAll, setDefaultTimeout, Status } from '@cucumber/cucumber';
import { chromium, type Cookie } from 'playwright';
import { mockManager } from '../mocks';
import { seedTestUser, TEST_USER } from '../support/seedTestUser';
import { startWebServer, stopWebServer } from '../support/webServer';
import type { CustomWorld } from '../support/world';
@@ -106,8 +107,16 @@ Before(async function (this: CustomWorld, { pickle }) {
);
console.log(`\n📝 Running: ${pickle.name}${testId ? ` (${testId.name.replace('@', '')})` : ''}`);
// Setup API mocks before any page navigation
// await mockManager.setup(this.page);
// Setup Community API mocks before any page navigation. These PR E2E scenarios
// are the user-experience baseline for Community UI flows (list/search/filter/
// detail navigation), not a live marketplace availability check. The live
// marketplace rate-limits anonymous CI traffic, so Community scenarios use
// deterministic fixtures while the rest of the E2E suite keeps real app APIs.
// If we need to validate the real marketplace contract, cover that in a
// separate integration/nightly suite with dedicated credentials and SLA.
if (pickle.tags.some((tag) => tag.name === '@community')) {
await mockManager.setup(this.page);
}
// Set cached session cookies to skip login
if (sessionCookies.length > 0) {
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "مرحبًا، أنا **{{name}}**. جملة واحدة تكفي.\n\nهل ترغب في أن أتناسب مع سير عملك بشكل أفضل؟ انتقل إلى [إعدادات الوكيل]({{url}}) واملأ ملف تعريف الوكيل (يمكنك تعديله في أي وقت).",
"agentDefaultMessageWithSystemRole": "مرحبًا، أنا **{{name}}**. جملة واحدة تكفي — أنت المتحكم.",
"agentDefaultMessageWithoutEdit": "مرحبًا، أنا **{{name}}**. جملة واحدة تكفي — أنت المتحكم.",
"agentDocument.backToChat": "العودة إلى الدردشة",
"agentDocument.linkCopied": "تم نسخ الرابط",
"agentDocument.openAsPage": "افتح كصفحة كاملة",
"agentProfile.files_one": "{{count}} ملف",
"agentProfile.files_other": "{{count}} ملفات",
"agentProfile.knowledgeBases_one": "{{count}} قاعدة معرفة",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "استخراج محتوى رابط الويب",
"followUpPlaceholder": "متابعة. @ لإسناد مهام لوكلاء آخرين.",
"followUpPlaceholderHeterogeneous": "تابع.",
"gatewayMode.title": "وضع البوابة",
"gatewayMode.beta": "تجريبي",
"gatewayMode.cardTitle": "وضع بوابة الوكيل",
"gatewayMode.desc": "قم بتشغيل الوكلاء في السحابة من خلال بوابة الوكلاء الخاصة بـ LobeHub. تستمر المهام في العمل حتى بعد إغلاق الصفحة.",
"group.desc": "ادفع المهمة للأمام مع عدة وكلاء في مساحة مشتركة واحدة.",
"group.memberTooltip": "يوجد {{count}} عضو في المجموعة",
"group.orchestratorThinking": "المنسق يفكر...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "تفويض",
"toolAuth.authorizing": "جارٍ التفويض...",
"toolAuth.hint": "بدون التفويض أو الإعداد، قد لا تعمل المهارات. قد يؤدي ذلك إلى تقييد الوكيل أو حدوث أخطاء.",
"toolAuth.remove": "إزالة",
"toolAuth.signIn": "تسجيل الدخول",
"toolAuth.title": "تفويض المهارات لهذا الوكيل",
"topic.checkOpenNewTopic": "هل تريد بدء موضوع جديد؟",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "المهارات",
"workingPanel.space": "مسافة",
"workingPanel.title": "Working Panel",
"you": "أنت",
"zenMode": "وضع التركيز"
"you": "أنت"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "تم عرض جميع المهام الجارية",
"fleet.backToHome": "العودة إلى الصفحة الرئيسية",
"fleet.closeColumn": "إغلاق العمود",
"fleet.closeIdleColumns": "إغلاق الأعمدة الخاملة",
"fleet.closeIdleColumnsCount": "إغلاق {{count}} من الأعمدة الخاملة",
"fleet.collapseReply": "طي",
"fleet.createTask": "إنشاء مهمة",
"fleet.dragHint": "اسحب لإعادة الترتيب",
"fleet.empty": "لا توجد مهام مفتوحة",
"fleet.emptyDesc": "اختر مهمة جارية على اليسار، أو استخدم + لإضافة عمود.",
"fleet.noRunningTasks": "لا توجد مهام جارية",
"fleet.openInChat": "فتح في الدردشة",
"fleet.pin": "تثبيت العمود",
"fleet.reply": "رد",
"fleet.runningTasks": "المهام الجارية",
"fleet.rows.one": "صف واحد",
"fleet.rows.two": "صفان",
"fleet.runningBoard": "لوحة التشغيل",
"fleet.status.idle": "خامل",
"fleet.status.paused": "متوقف مؤقتًا",
"fleet.status.running": "قيد التشغيل",
"fleet.status.scheduled": "مجدول",
"fleet.tooltip": "عرض جميع الوكلاء جنبًا إلى جنب",
"fleet.unpin": "إلغاء تثبيت العمود",
"gateway.description": "الوصف",
"gateway.descriptionPlaceholder": "اختياري",
"gateway.deviceName": "اسم الجهاز",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "اكتشف MCP",
"navigation.discoverModels": "اكتشف النماذج",
"navigation.discoverProviders": "اكتشف المزودين",
"navigation.document": "مستند",
"navigation.group": "مجموعة",
"navigation.groupChat": "محادثة جماعية",
"navigation.home": "الرئيسية",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "المنتهية الصلاحية",
"credits.packages.tabs.expiredCount": "منتهية الصلاحية ({{count}})",
"credits.packages.title": "حزم الأرصدة الخاصة بي",
"credits.topUp.bestValue.cta": "عرض السنوي النهائي",
"credits.topUp.bestValue.savings": "وفر ${{savings}} على هذا الشراء",
"credits.topUp.bestValue.title": "الخطة السنوية {{plan}} تتيح أقل معدل شحن: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "إلغاء",
"credits.topUp.custom": "مخصص",
"credits.topUp.freeFeeHint": "تشمل تعبئة الخطة المجانية رسوم خدمة بقيمة {{fee}} لكل مليون رصيد.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "معالجة المكافآت: سيتم توزيع الأرصدة خلال ساعة واحدة بعد أن يكمل المدعو الدفع ويجتاز التحقق",
"referral.rules.title": "قواعد البرنامج",
"referral.rules.validInvitation": "دعوة صالحة: يسجل المدعو باستخدام رمز الإحالة الخاص بك، وينفذ إجراءً صالحًا، ويكمل الدفع (اشتراك أو شحن أرصدة)",
"referral.rules.validOperation": "معايير الإجراء الصالح: إرسال رسالة واحدة أو إنشاء صورة واحدة",
"referral.stats.availableBalance": "الرصيد المتاح",
"referral.stats.description": "عرض إحصائيات الإحالة الخاصة بك",
"referral.stats.title": "نظرة عامة على الإحالة",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "مؤرشف",
"management.status.completed": "مكتمل",
"management.status.failed": "فشل",
"management.status.idle": "خامل",
"management.status.paused": "متوقف مؤقتًا",
"management.status.running": "قيد التشغيل",
"management.status.waitingForHuman": "في انتظار الإدخال",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} مواضيع فشلت",
"projectStatus.loading_one": "{{count}} موضوع قيد التحميل",
"projectStatus.loading_other": "{{count}} مواضيع قيد التحميل",
"projectStatus.unread_one": "{{count}} موضوع يحتوي على رد غير مقروء",
"projectStatus.unread_other": "{{count}} مواضيع تحتوي على ردود غير مقروءة",
"projectStatus.waitingForHuman_one": "{{count}} موضوع ينتظر الإدخال",
"projectStatus.waitingForHuman_other": "{{count}} مواضيع تنتظر الإدخال",
"renameModal.description": "يُفضَّل أن يكون قصيرًا وسهل التعرّف.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Здравей, аз съм **{{name}}**. Едно изречение е достатъчно.\n\nИскате да се адаптирам по-добре към вашия работен процес? Отидете в [Настройки на Агента]({{url}}) и попълнете Профила на Агента (можете да го редактирате по всяко време).",
"agentDefaultMessageWithSystemRole": "Здравей, аз съм **{{name}}**. Едно изречение е достатъчно — вие контролирате.",
"agentDefaultMessageWithoutEdit": "Здравей, аз съм **{{name}}**. Едно изречение е достатъчно — вие контролирате.",
"agentDocument.backToChat": "Обратно към чата",
"agentDocument.linkCopied": "Връзката е копирана",
"agentDocument.openAsPage": "Отвори като цяла страница",
"agentProfile.files_one": "{{count}} файл",
"agentProfile.files_other": "{{count}} файла",
"agentProfile.knowledgeBases_one": "{{count}} база знания",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Извличане на съдържание от уеб връзки",
"followUpPlaceholder": "Последващо действие. Използвайте @, за да възлагате задачи на други агенти.",
"followUpPlaceholderHeterogeneous": "Последващ въпрос.",
"gatewayMode.title": "Режим на шлюз",
"gatewayMode.beta": "Бета",
"gatewayMode.cardTitle": "Режим на шлюза за агенти",
"gatewayMode.desc": "Стартирайте агенти в облака чрез шлюза за агенти на LobeHub. Задачите продължават да се изпълняват дори след като затворите страницата.",
"group.desc": "Придвижете задача напред с няколко Агента в едно споделено пространство.",
"group.memberTooltip": "Групата има {{count}} член(а)",
"group.orchestratorThinking": "Оркестраторът мисли...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Упълномощи",
"toolAuth.authorizing": "Упълномощаване...",
"toolAuth.hint": "Без упълномощаване или конфигурация, уменията може да не работят. Това може да ограничи агента или да доведе до грешки.",
"toolAuth.remove": "Премахни",
"toolAuth.signIn": "Вход",
"toolAuth.title": "Упълномощи уменията за този агент",
"topic.checkOpenNewTopic": "Да започнем нова тема?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Умения",
"workingPanel.space": "Пространство",
"workingPanel.title": "Working Panel",
"you": "Вие",
"zenMode": "Режим Зен"
"you": "Вие"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Всички текущи задачи са показани",
"fleet.backToHome": "Обратно към началната страница",
"fleet.closeColumn": "Затваряне на колона",
"fleet.closeIdleColumns": "Затвори неактивни колони",
"fleet.closeIdleColumnsCount": "Затвори {{count}} неактивни колони",
"fleet.collapseReply": "Свий",
"fleet.createTask": "Създаване на задача",
"fleet.dragHint": "Плъзнете, за да пренаредите",
"fleet.empty": "Няма отворени задачи",
"fleet.emptyDesc": "Изберете текуща задача отляво или използвайте +, за да добавите колона.",
"fleet.noRunningTasks": "Няма текущи задачи",
"fleet.openInChat": "Отваряне в чата",
"fleet.pin": "Закачи колона",
"fleet.reply": "Отговор",
"fleet.runningTasks": "Текущи задачи",
"fleet.rows.one": "Един ред",
"fleet.rows.two": "Два реда",
"fleet.runningBoard": "Текуща дъска",
"fleet.status.idle": "Неактивен",
"fleet.status.paused": "Пауза",
"fleet.status.running": "В процес на изпълнение",
"fleet.status.scheduled": "Планирано",
"fleet.tooltip": "Преглед на всички агенти един до друг",
"fleet.unpin": "Откачи колона",
"gateway.description": "Описание",
"gateway.descriptionPlaceholder": "По избор",
"gateway.deviceName": "Име на устройството",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Откриване на MCP",
"navigation.discoverModels": "Откриване на Модели",
"navigation.discoverProviders": "Откриване на Доставчици",
"navigation.document": "Документ",
"navigation.group": "Група",
"navigation.groupChat": "Групов Чат",
"navigation.home": "Начало",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Изтекли",
"credits.packages.tabs.expiredCount": "Изтекли ({{count}})",
"credits.packages.title": "Моите пакети с кредити",
"credits.topUp.bestValue.cta": "Вижте Ultimate годишен",
"credits.topUp.bestValue.savings": "Спестете ${{savings}} при тази покупка",
"credits.topUp.bestValue.title": "{{plan}} годишен отключва най-ниската ставка за зареждане: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Отказ",
"credits.topUp.custom": "По избор",
"credits.topUp.freeFeeHint": "Зарежданията на безплатния план включват такса за услуга от {{fee}} на 1M кредити.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Обработка на наградите: Кредитите ще бъдат разпределени в рамките на 1 час след като поканеният завърши плащане и премине проверка.",
"referral.rules.title": "Правила на програмата",
"referral.rules.validInvitation": "Валидна покана: Поканеният се регистрира с вашия код за препоръка, извършва едно валидно действие и завършва плащане (абонамент или зареждане на кредити).",
"referral.rules.validOperation": "Критерии за валидно действие: Изпращане на съобщение в Chat страницата или генериране на изображение",
"referral.stats.availableBalance": "Налично салдо",
"referral.stats.description": "Вижте статистиката на вашите покани",
"referral.stats.title": "Обзор на поканите",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Архивирани",
"management.status.completed": "Завършени",
"management.status.failed": "Неуспешни",
"management.status.idle": "Неактивен",
"management.status.paused": "Паузирани",
"management.status.running": "В процес",
"management.status.waitingForHuman": "Очаква въвеждане",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} неуспешни теми",
"projectStatus.loading_one": "{{count}} зареждаща се тема",
"projectStatus.loading_other": "{{count}} зареждащи се теми",
"projectStatus.unread_one": "{{count}} тема с непрочетен отговор",
"projectStatus.unread_other": "{{count}} теми с непрочетени отговори",
"projectStatus.waitingForHuman_one": "{{count}} тема, очакваща въвеждане",
"projectStatus.waitingForHuman_other": "{{count}} теми, очакващи въвеждане",
"renameModal.description": "Поддържайте го кратко и лесно за разпознаване.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Hallo, ich bin **{{name}}**. Ein Satz genügt.\n\nMöchten Sie, dass ich besser zu Ihrem Arbeitsablauf passe? Gehen Sie zu [Agenteneinstellungen]({{url}}) und füllen Sie das Agentenprofil aus (Sie können es jederzeit bearbeiten).",
"agentDefaultMessageWithSystemRole": "Hallo, ich bin **{{name}}**. Ein Satz genügt Sie haben die Kontrolle.",
"agentDefaultMessageWithoutEdit": "Hallo, ich bin **{{name}}**. Ein Satz genügt Sie haben die Kontrolle.",
"agentDocument.backToChat": "Zurück zum Chat",
"agentDocument.linkCopied": "Link kopiert",
"agentDocument.openAsPage": "Als vollständige Seite öffnen",
"agentProfile.files_one": "{{count}} Datei",
"agentProfile.files_other": "{{count}} Dateien",
"agentProfile.knowledgeBases_one": "{{count}} Wissensbasis",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Webseiteninhalte extrahieren",
"followUpPlaceholder": "Folgen Sie nach. @, um Aufgaben anderen Agenten zuzuweisen.",
"followUpPlaceholderHeterogeneous": "Weiter ausführen.",
"gatewayMode.title": "Gateway-Modus",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Agent-Gateway-Modus",
"gatewayMode.desc": "Führen Sie Agenten in der Cloud über LobeHubs Agent-Gateway aus. Aufgaben laufen weiter, auch nachdem Sie die Seite geschlossen haben.",
"group.desc": "Bringen Sie eine Aufgabe mit mehreren Agenten in einem gemeinsamen Raum voran.",
"group.memberTooltip": "Es gibt {{count}} Mitglieder in der Gruppe",
"group.orchestratorThinking": "Orchestrator denkt nach...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autorisieren",
"toolAuth.authorizing": "Autorisierung läuft...",
"toolAuth.hint": "Ohne Autorisierung oder Konfiguration funktionieren Skills möglicherweise nicht. Dies kann den Agenten einschränken oder zu Fehlern führen.",
"toolAuth.remove": "Entfernen",
"toolAuth.signIn": "Anmelden",
"toolAuth.title": "Skills für diesen Agenten autorisieren",
"topic.checkOpenNewTopic": "Neues Thema starten?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Fähigkeiten",
"workingPanel.space": "Leerzeichen",
"workingPanel.title": "Working Panel",
"you": "Du",
"zenMode": "Zen-Modus"
"you": "Du"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Alle laufenden Aufgaben werden angezeigt",
"fleet.backToHome": "Zurück zur Startseite",
"fleet.closeColumn": "Spalte schließen",
"fleet.closeIdleColumns": "Leere Spalten schließen",
"fleet.closeIdleColumnsCount": "{{count}} leere Spalten schließen",
"fleet.collapseReply": "Einklappen",
"fleet.createTask": "Aufgabe erstellen",
"fleet.dragHint": "Ziehen, um neu anzuordnen",
"fleet.empty": "Keine offenen Aufgaben",
"fleet.emptyDesc": "Wählen Sie eine laufende Aufgabe links aus oder verwenden Sie +, um eine Spalte hinzuzufügen.",
"fleet.noRunningTasks": "Keine laufenden Aufgaben",
"fleet.openInChat": "Im Chat öffnen",
"fleet.pin": "Spalte anheften",
"fleet.reply": "Antworten",
"fleet.runningTasks": "Laufende Aufgaben",
"fleet.rows.one": "Einzelne Zeile",
"fleet.rows.two": "Zwei Zeilen",
"fleet.runningBoard": "Laufendes Board",
"fleet.status.idle": "Leerlauf",
"fleet.status.paused": "Pausiert",
"fleet.status.running": "Läuft",
"fleet.status.scheduled": "Geplant",
"fleet.tooltip": "Alle Agenten nebeneinander anzeigen",
"fleet.unpin": "Spalte lösen",
"gateway.description": "Beschreibung",
"gateway.descriptionPlaceholder": "Optional",
"gateway.deviceName": "Gerätename",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "MCP entdecken",
"navigation.discoverModels": "Modelle entdecken",
"navigation.discoverProviders": "Anbieter entdecken",
"navigation.document": "Dokument",
"navigation.group": "Gruppe",
"navigation.groupChat": "Gruppen-Chat",
"navigation.home": "Startseite",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Abgelaufen",
"credits.packages.tabs.expiredCount": "Abgelaufen ({{count}})",
"credits.packages.title": "Meine Guthabenpakete",
"credits.topUp.bestValue.cta": "Ultimatives Jahresabo ansehen",
"credits.topUp.bestValue.savings": "Sparen Sie ${{savings}} bei diesem Kauf",
"credits.topUp.bestValue.title": "{{plan}} Jahresabo bietet den niedrigsten Aufladepreis: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Abbrechen",
"credits.topUp.custom": "Benutzerdefiniert",
"credits.topUp.freeFeeHint": "Aufladungen im kostenlosen Tarif beinhalten eine {{fee}} Servicegebühr pro 1M Credits.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Belohnungsverarbeitung: Credits werden innerhalb von 1 Stunde verteilt, nachdem der Eingeladene eine Zahlung abgeschlossen und die Verifizierung bestanden hat.",
"referral.rules.title": "Programmregeln",
"referral.rules.validInvitation": "Gültige Einladung: Der Eingeladene registriert sich mit Ihrem Einladungscode, führt eine gültige Aktion aus und schließt eine Zahlung ab (Abonnement oder Credit-Aufladung).",
"referral.rules.validOperation": "Gültige Aktion: Eine Nachricht senden oder ein Bild generieren",
"referral.stats.availableBalance": "Verfügbares Guthaben",
"referral.stats.description": "Sehen Sie Ihre Empfehlungsstatistiken",
"referral.stats.title": "Empfehlungsübersicht",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Archiviert",
"management.status.completed": "Abgeschlossen",
"management.status.failed": "Fehlgeschlagen",
"management.status.idle": "Leerlauf",
"management.status.paused": "Pausiert",
"management.status.running": "Laufend",
"management.status.waitingForHuman": "Wartet auf Eingabe",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} fehlgeschlagene Themen",
"projectStatus.loading_one": "{{count}} ladendes Thema",
"projectStatus.loading_other": "{{count}} ladende Themen",
"projectStatus.unread_one": "{{count}} Thema mit ungelesener Antwort",
"projectStatus.unread_other": "{{count}} Themen mit ungelesenen Antworten",
"projectStatus.waitingForHuman_one": "{{count}} Thema wartet auf Eingabe",
"projectStatus.waitingForHuman_other": "{{count}} Themen warten auf Eingabe",
"renameModal.description": "Kurz und leicht erkennbar halten.",
+9 -9
View File
@@ -57,7 +57,7 @@
"channel.devWebhookProxyUrl": "HTTPS Tunnel URL",
"channel.devWebhookProxyUrlHint": "Optional. HTTPS tunnel URL for forwarding webhook requests to local dev server.",
"channel.disabled": "Disabled",
"channel.discord.description": "Connect this assistant to Discord server for channel chat and direct messages.",
"channel.discord.description": "Connect this agent to Discord server for channel chat and direct messages.",
"channel.displayToolCalls": "Display Tool Calls",
"channel.displayToolCallsHint": "Show tool call details during AI responses. When disabled, only the final response is displayed for a cleaner experience.",
"channel.dm": "Direct Messages",
@@ -79,7 +79,7 @@
"channel.endpointUrl": "Webhook URL",
"channel.endpointUrlHint": "Please copy this URL and paste it into the <bold>{{fieldName}}</bold> field in the {{name}} Developer Portal.",
"channel.exportConfig": "Export Configuration",
"channel.feishu.description": "Connect this assistant to Feishu for private and group chats.",
"channel.feishu.description": "Connect this agent to Feishu for private and group chats.",
"channel.feishu.webhookMigrationDesc": "WebSocket mode provides real-time event delivery without needing a public callback URL. To migrate, switch the Connection Mode to WebSocket in Advanced Settings. No additional configuration is needed on the Feishu/Lark Open Platform.",
"channel.feishu.webhookMigrationTitle": "Consider migrating to WebSocket mode",
"channel.groupAllowFrom": "Allowed Channels",
@@ -135,7 +135,7 @@
"channel.imessage.bridgeTestDisabledHint": "Enable the bridge service first.",
"channel.imessage.bridgeTestFailed": "BlueBubbles test failed",
"channel.imessage.bridgeTestSuccess": "BlueBubbles connection passed",
"channel.imessage.description": "Connect this assistant to iMessage through the local LobeHub Desktop BlueBubbles bridge.",
"channel.imessage.description": "Connect this agent to iMessage through the local LobeHub Desktop BlueBubbles bridge.",
"channel.imessage.desktopBridge": "Desktop Bridge",
"channel.imessage.desktopDeviceId": "Desktop Device ID",
"channel.imessage.desktopDeviceIdHint": "The LobeHub Desktop device that runs the local BlueBubbles bridge. Find it in Desktop Gateway settings.",
@@ -145,12 +145,12 @@
"channel.importFailed": "Failed to import configuration",
"channel.importInvalidFormat": "Invalid configuration file format",
"channel.importSuccess": "Configuration imported successfully",
"channel.lark.description": "Connect this assistant to Lark for private and group chats.",
"channel.lark.description": "Connect this agent to Lark for private and group chats.",
"channel.line.channelAccessToken": "Channel Access Token",
"channel.line.channelAccessTokenHint": "Long-lived token issued under the Messaging API tab. Token will be encrypted and stored securely.",
"channel.line.channelSecret": "Channel Secret",
"channel.line.channelSecretHint": "From the Basic settings tab. Required — used to verify X-Line-Signature on every inbound webhook.",
"channel.line.description": "Connect this assistant to LINE Messaging API for direct and group chats.",
"channel.line.description": "Connect this agent to LINE Messaging API for direct and group chats.",
"channel.line.destinationUserId": "Destination User ID",
"channel.line.destinationUserIdHint": "The bot's own user ID (`U` + 32 chars) — click \"Fetch from LINE\" below to auto-fill. Not the personal \"Your user ID\" shown in LINE's Basic settings.",
"channel.line.destinationUserIdPlaceholder": "e.g. U1234567890abcdef1234567890abcdef",
@@ -169,7 +169,7 @@
"channel.publicKeyHint": "Optional. Used to verify interaction requests from Discord.",
"channel.publicKeyPlaceholder": "Required for interaction verification",
"channel.qq.appIdHint": "Your QQ Bot App ID from QQ Open Platform",
"channel.qq.description": "Connect this assistant to QQ for group chats and direct messages.",
"channel.qq.description": "Connect this agent to QQ for group chats and direct messages.",
"channel.qq.webhookMigrationDesc": "WebSocket mode provides real-time event delivery and automatic reconnection without needing a callback URL. To migrate, create a new bot on QQ Open Platform without configuring a callback URL, then switch the Connection Mode to WebSocket in Advanced Settings.",
"channel.qq.webhookMigrationTitle": "Consider migrating to WebSocket mode",
"channel.refreshStatus": "Refresh status",
@@ -199,7 +199,7 @@
"channel.slack.appIdHint": "Your Slack App ID from the Slack API dashboard (starts with A).",
"channel.slack.appToken": "App-Level Token",
"channel.slack.appTokenHint": "Required for Socket Mode (WebSocket). Generate an app-level token (xapp-...) under Basic Information in your Slack app settings.",
"channel.slack.description": "Connect this assistant to Slack for channel conversations and direct messages.",
"channel.slack.description": "Connect this agent to Slack for channel conversations and direct messages.",
"channel.slack.webhookMigrationDesc": "Socket Mode provides real-time event delivery via WebSocket without exposing a public HTTP endpoint. To migrate, enable Socket Mode in your Slack app settings, generate an App-Level Token, then switch the Connection Mode to WebSocket in Advanced Settings.",
"channel.slack.webhookMigrationTitle": "Consider migrating to Socket Mode (WebSocket)",
"channel.statusConnected": "Connected",
@@ -208,7 +208,7 @@
"channel.statusFailed": "Failed",
"channel.statusQueued": "Queued",
"channel.statusStarting": "Starting",
"channel.telegram.description": "Connect this assistant to Telegram for private and group chats.",
"channel.telegram.description": "Connect this agent to Telegram for private and group chats.",
"channel.testConnection": "Test Connection",
"channel.testFailed": "Connection test failed",
"channel.testSuccess": "Connection test passed",
@@ -236,7 +236,7 @@
"channel.watchKeywordsAdd": "Add keyword",
"channel.watchKeywordsEmpty": "No keywords added yet — bot only wakes on @mention or DM in subscribed channels.",
"channel.watchKeywordsHint": "A keyword match wakes the bot without an @mention; its instruction is prepended to the user message. Whole-word, case-insensitive.",
"channel.wechat.description": "Connect this assistant to WeChat via iLink Bot for private and group chats.",
"channel.wechat.description": "Connect this agent to WeChat via iLink Bot for private and group chats.",
"channel.wechatBotId": "Bot ID",
"channel.wechatBotIdHint": "Bot identifier assigned after QR code authorization.",
"channel.wechatConnectedInfo": "Connected WeChat Account",
+9 -9
View File
@@ -28,7 +28,7 @@
"apikey.list.columns.status": "Enabled Status",
"apikey.list.title": "API Key List",
"apikey.validation.required": "This field cannot be empty",
"authModal.description": "Your login session has expired. Please sign in again to continue using cloud sync features.",
"authModal.description": "Your sign-in session has expired. Please sign in again to continue using cloud sync features.",
"authModal.later": "Later",
"authModal.signIn": "Sign In Again",
"authModal.signingIn": "Signing in...",
@@ -45,7 +45,7 @@
"betterAuth.errors.emailRequired": "Please enter your email address or username",
"betterAuth.errors.firstNameRequired": "Please enter your first name",
"betterAuth.errors.lastNameRequired": "Please enter your last name",
"betterAuth.errors.loginFailed": "Login failed, please check your email and password",
"betterAuth.errors.loginFailed": "Sign in failed, please check your email and password",
"betterAuth.errors.passwordFormat": "Password must contain both letters and numbers",
"betterAuth.errors.passwordMaxLength": "Password must not exceed 64 characters",
"betterAuth.errors.passwordMinLength": "Password must be at least 8 characters",
@@ -155,13 +155,13 @@
"heatmaps.tooltipTokens": "{{count}} tokens were used on {{date}}",
"heatmaps.totalCount": "A total of {{count}} messages sent in the past year",
"heatmaps.totalCountTokens": "A total of {{count}} tokens used in the past year",
"login": "Log In",
"login": "Sign In",
"loginGuide.f1": "Get free usage",
"loginGuide.f2": "Sync messages across devices",
"loginGuide.f3": "Access a wealth of agents",
"loginGuide.f4": "Explore powerful plugins",
"loginGuide.title": "After logging in, you can:",
"loginOrSignup": "Log In / Sign Up",
"loginGuide.title": "After signing in, you can:",
"loginOrSignup": "Sign In / Sign Up",
"profile.account": "Account",
"profile.authorizations.actions.revoke": "Revoke",
"profile.authorizations.revoke.description": "After revoking, the tool will no longer have access to your data. Re-authorization is required to use it again.",
@@ -186,7 +186,7 @@
"profile.sso.loading": "Loading linked third-party accounts",
"profile.sso.providers": "Connected Accounts",
"profile.sso.unlink.description": "Re-authorization or re-linking is required to sign in with {{provider}} again after unlinking.",
"profile.sso.unlink.forbidden": "You must retain at least one login method.",
"profile.sso.unlink.forbidden": "You must retain at least one sign-in method.",
"profile.sso.unlink.title": "Unlink {{provider}} account?",
"profile.title": "Profile",
"profile.updateAvatar": "Update avatar",
@@ -201,9 +201,9 @@
"profile.usernameRule": "Username can only contain letters, numbers, or underscores",
"profile.usernameTooLong": "Username cannot exceed 64 characters",
"profile.usernameUpdateFailed": "Failed to update username, please try again later",
"signin.subtitle": "Sign up or log in to your {{appName}} account",
"signin.subtitle": "Sign up or sign in to your {{appName}} account",
"signin.title": "Agent teammates that grow with you",
"signout": "Log Out",
"signout": "Sign Out",
"signup": "Sign Up",
"stats.aiheatmaps": "Activity Index",
"stats.assistants": "Agents",
@@ -224,7 +224,7 @@
"stats.loginGuide.f2": "Sync messages across devices",
"stats.loginGuide.f3": "Access a wealth of agents",
"stats.loginGuide.f4": "Explore powerful skills",
"stats.loginGuide.title": "After logging in, you can:",
"stats.loginGuide.title": "After signing in, you can:",
"stats.messages": "Messages",
"stats.modelsRank.left": "Model",
"stats.modelsRank.right": "Messages",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"actions.discord": "Go to Discord for feedback",
"actions.home": "Return to Home",
"actions.retry": "Log in Again",
"actions.retry": "Sign in Again",
"codes.ACCOUNT_ALREADY_LINKED_TO_DIFFERENT_USER": "This account is already linked to another user",
"codes.ACCOUNT_NOT_FOUND": "Account not found",
"codes.CREDENTIAL_ACCOUNT_NOT_FOUND": "Credential account does not exist",
@@ -25,7 +25,7 @@
"codes.PASSWORD_TOO_SHORT": "Password is too short",
"codes.PROVIDER_NOT_FOUND": "Identity provider configuration not found",
"codes.RATE_LIMIT_EXCEEDED": "Too many requests, please try again later",
"codes.SESSION_EXPIRED": "Session has expired, please log in again",
"codes.SESSION_EXPIRED": "Session has expired, please sign in again",
"codes.SOCIAL_ACCOUNT_ALREADY_LINKED": "This social account is already linked to another user",
"codes.TEMPORARY_EMAIL_NOT_ALLOWED": "Temporary email addresses are not supported. Please use a regular email address. Repeated attempts may block this network.",
"codes.UNEXPECTED_ERROR": "An unexpected error occurred, please try again",
+12 -13
View File
@@ -25,8 +25,8 @@
"agentDocument.openAsPage": "Open as full page",
"agentProfile.files_one": "{{count}} file",
"agentProfile.files_other": "{{count}} files",
"agentProfile.knowledgeBases_one": "{{count}} knowledge base",
"agentProfile.knowledgeBases_other": "{{count}} knowledge bases",
"agentProfile.knowledgeBases_one": "{{count}} library",
"agentProfile.knowledgeBases_other": "{{count}} libraries",
"agentProfile.skills_one": "{{count}} skill",
"agentProfile.skills_other": "{{count}} skills",
"agentSignal.receipts.agentSignalLabel": "Agent Signal",
@@ -67,7 +67,7 @@
"claudeCodeInstallGuide.menuNotification.title": "Claude Code CLI not found",
"claudeCodeInstallGuide.reason": "LobeHub could not start Claude Code: {{message}}",
"claudeCodeInstallGuide.title": "Install Claude Code CLI",
"clearCurrentMessages": "Clear current session messages",
"clearCurrentMessages": "Clear current conversation messages",
"cliAuthGuide.actions.openDocs": "Open Sign-in Guide",
"cliAuthGuide.actions.openSystemTools": "Open System Tools",
"cliAuthGuide.afterLogin": "After signing in again or refreshing credentials, retry your message. You can also re-detect in System Tools.",
@@ -110,7 +110,7 @@
"compression.cancelConfirm": "Are you sure you want to uncompress? This will restore the original messages.",
"compression.history": "History",
"compression.summary": "Summary",
"confirmClearCurrentMessages": "You are about to clear the current session messages. Once cleared, they cannot be retrieved. Please confirm your action.",
"confirmClearCurrentMessages": "You are about to clear the current conversation messages. Once cleared, they cannot be retrieved. Please confirm your action.",
"confirmRemoveChatGroupItemAlert": "This Group will be deleted. Group-specific assistants will also be deleted, while external assistants will not be affected.",
"confirmRemoveGroupItemAlert": "You are about to delete this category. After deletion, its agents will be moved to the default list. Please confirm your action.",
"confirmRemoveGroupSuccess": "Group deleted successfully",
@@ -169,8 +169,8 @@
"followUpPlaceholder": "Follow up.",
"followUpPlaceholderHeterogeneous": "Follow up.",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Agent Gateway Mode",
"gatewayMode.desc": "Run agents in the cloud through LobeHub's Agent Gateway. Tasks keep running even after you close the page.",
"gatewayMode.title": "Agent Gateway Mode",
"group.desc": "Move a task forward with multiple Agents in one shared space.",
"group.memberTooltip": "There are {{count}} members in the group",
"group.orchestratorThinking": "Orchestrator is thinking...",
@@ -178,8 +178,8 @@
"group.profile.external": "External",
"group.profile.externalAgentWarning": "This is an external agent. Changes made here will directly modify the original agent configuration.",
"group.profile.groupSettings": "Group Settings",
"group.profile.supervisor": "Supervisor",
"group.profile.supervisorPlaceholder": "The supervisor coordinates different agents. Setting supervisor information here enables more precise workflow coordination.",
"group.profile.supervisor": "Orchestrator",
"group.profile.supervisorPlaceholder": "The Orchestrator coordinates different agents. Setting Orchestrator information here enables more precise workflow coordination.",
"group.removeMember": "Remove Member",
"group.title": "Group",
"groupDescription": "Group description",
@@ -586,7 +586,7 @@
"stt.action": "Voice Input",
"stt.loading": "Recognizing...",
"stt.prettifying": "Polishing...",
"supervisor.label": "Supervisor",
"supervisor.label": "Orchestrator",
"supervisor.todoList.allComplete": "All tasks completed",
"supervisor.todoList.title": "Tasks Completed",
"tab.groupProfile": "Group Profile",
@@ -839,7 +839,7 @@
"tokenDetails.chats": "Chat Messages",
"tokenDetails.historySummary": "History Summary",
"tokenDetails.rest": "Remaining",
"tokenDetails.supervisor": "Group Host",
"tokenDetails.supervisor": "Orchestrator",
"tokenDetails.systemRole": "Role Settings",
"tokenDetails.title": "Context Details",
"tokenDetails.tools": "Skill Settings",
@@ -890,7 +890,7 @@
"topic.defaultTitle": "Untitled Topic",
"topic.openNewTopic": "Open New Topic",
"topic.recent": "Recent Topics",
"topic.saveCurrentMessages": "Save current session as topic",
"topic.saveCurrentMessages": "Save current conversation as topic",
"topic.viewAll": "View All Topics",
"translate.action": "Translate",
"translate.clear": "Clear Translation",
@@ -986,7 +986,7 @@
"workflow.toolDisplayName.saveUserQuestion": "Recorded info",
"workflow.toolDisplayName.search": "Searched the web",
"workflow.toolDisplayName.searchAgent": "Searched agents",
"workflow.toolDisplayName.searchKnowledgeBase": "Searched knowledge base",
"workflow.toolDisplayName.searchKnowledgeBase": "Searched library",
"workflow.toolDisplayName.searchLocalFiles": "Searched files",
"workflow.toolDisplayName.searchSkill": "Searched skills",
"workflow.toolDisplayName.searchUserMemory": "Searched memory",
@@ -1124,6 +1124,5 @@
"workingPanel.skills.title": "Skills",
"workingPanel.space": "Space",
"workingPanel.title": "Working Panel",
"you": "You",
"zenMode": "Zen Mode"
"you": "You"
}
+3 -3
View File
@@ -168,8 +168,8 @@
"cmdk.search.agents": "Agents",
"cmdk.search.assistant": "Agent",
"cmdk.search.assistants": "Agents",
"cmdk.search.chatGroup": "Agent Team",
"cmdk.search.chatGroups": "Agent Teams",
"cmdk.search.chatGroup": "Group",
"cmdk.search.chatGroups": "Groups",
"cmdk.search.communityAgent": "Community Agent",
"cmdk.search.file": "File",
"cmdk.search.files": "Files",
@@ -370,7 +370,7 @@
"navPanel.visible": "Visible",
"new": "New",
"noContent": "No content",
"oauth": "SSO Login",
"oauth": "SSO Sign-in",
"officialSite": "Official Website",
"ok": "OK",
"or": "or",
+1 -1
View File
@@ -101,7 +101,7 @@
"LocalFile.action.open": "Open",
"LocalFile.action.showInFolder": "Show in Folder",
"MaxTokenSlider.unlimited": "Unlimited",
"ModelSelect.featureTag.custom": "Custom model, by default, supports both function calls and visual recognition. Please verify the availability of the above capabilities based on actual situations.",
"ModelSelect.featureTag.custom": "Custom model, by default, supports both tool calls and visual recognition. Please verify the availability of the above capabilities based on actual situations.",
"ModelSelect.featureTag.file": "This model supports file upload for reading and recognition.",
"ModelSelect.featureTag.functionCall": "This model supports tool calls.",
"ModelSelect.featureTag.imageOutput": "This model supports image generation.",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"authResult.failed.desc": "Please try again or switch to a different login method",
"authResult.failed.desc": "Please try again or switch to a different sign-in method",
"authResult.failed.title": "Authorization Failed",
"authResult.success.desc": "Please click the Start button below to continue using LobeHub Desktop",
"authResult.success.title": "Authorization Successful",
+3 -3
View File
@@ -620,7 +620,7 @@
"user.githubUrlInvalid": "Please enter a valid GitHub repository URL",
"user.githubUrlRequired": "Please enter a GitHub repository URL",
"user.login": "Become a Creator",
"user.logout": "Logout",
"user.logout": "Sign out",
"user.myProfile": "My Profile",
"user.noAgents": "This user hasnt published any Agents yet",
"user.noAgents.ownerDescription": "Create your first Agent and share it with the Community.",
@@ -630,11 +630,11 @@
"user.noForkedAgentGroups": "No forked Agent Groups yet",
"user.noForkedAgents": "No forked Agents yet",
"user.noGroups.title": "No Agent Groups yet",
"user.noPlugins": "This user hasn't published any Plugins yet",
"user.noPlugins": "This user hasn't published any Skills yet",
"user.noSkills": "This user hasn't published any Skills yet",
"user.openWorkspacePublicProfile": "Open Public Link",
"user.org.noAgents": "This organization hasnt published any Agents yet",
"user.plugins": "Plugins",
"user.plugins": "Skills",
"user.publishedAgents": "Created Agents",
"user.publishedGroups": "Created Groups",
"user.searchPlaceholder": "Search by name or description...",
+1 -1
View File
@@ -34,7 +34,7 @@
"gateway.title": "Device Gateway",
"navigation.chat": "Chat",
"navigation.discover": "Discover",
"navigation.discoverAssistants": "Discover Assistants",
"navigation.discoverAssistants": "Discover Agents",
"navigation.discoverMcp": "Discover MCP",
"navigation.discoverModels": "Discover Models",
"navigation.discoverProviders": "Discover Providers",
+5 -5
View File
@@ -13,8 +13,8 @@
"import.importConfigFile.title": "Import Failed",
"import.incompatible.description": "This file was exported from a higher version. Please try upgrading to the latest version and then re-importing.",
"import.incompatible.title": "Current application does not support importing this file",
"loginRequired.desc": "You will be redirected to the login page shortly",
"loginRequired.title": "Please log in to use this feature",
"loginRequired.desc": "You will be redirected to the sign-in page shortly",
"loginRequired.title": "Please sign in to use this feature",
"notFound.backHome": "Back to Home",
"notFound.check": "Please check if your URL is correct.",
"notFound.desc": "We couldn't find the page you were looking for.",
@@ -124,9 +124,9 @@
"unlock.comfyui.title": "Verify your {{name}} credentials",
"unlock.confirm": "Confirm and Retry",
"unlock.goToSettings": "Go to Settings",
"unlock.oauth.description": "The administrator has enabled unified login authentication. Click the button below to log in and unlock the application.",
"unlock.oauth.success": "Login successful",
"unlock.oauth.title": "Log in to your account",
"unlock.oauth.description": "The administrator has enabled unified sign-in authentication. Click the button below to sign in and unlock the application.",
"unlock.oauth.success": "Sign-in successful",
"unlock.oauth.title": "Sign in to your account",
"unlock.oauth.welcome": "Welcome!",
"unlock.password.description": "The application encryption has been enabled by the administrator. Enter the application password to unlock the application. The password only needs to be filled in once.",
"unlock.password.placeholder": "Please enter password",
+3 -3
View File
@@ -56,9 +56,9 @@
"library.hierarchy.empty.desc": "Add files or create a folder to get started",
"library.hierarchy.empty.title": "Nothing here yet",
"library.import.action": "Import to workspace…",
"library.import.failed": "Failed to import knowledge base.",
"library.import.success": "Knowledge base imported to {{name}}.",
"library.import.tooltip": "Fork this knowledge base into a workspace. Files are shared by reference; the original stays in your personal space.",
"library.import.failed": "Failed to import library.",
"library.import.success": "Library imported to {{name}}.",
"library.import.tooltip": "Fork this library into a workspace. Files are shared by reference; the original stays in your personal space.",
"library.list.confirmRemoveLibrary": "You are about to delete this library. The files within it will not be deleted but moved to All Files. This action cannot be undone, so please proceed with caution.",
"library.list.copyDescription": "Clone this library and all of its contents into another workspace.",
"library.list.copyFailed": "Failed to copy library",
+1 -3
View File
@@ -38,7 +38,5 @@
"toggleLeftPanel.desc": "Show or hide the left panel",
"toggleLeftPanel.title": "Toggle Left Panel",
"toggleRightPanel.desc": "Show or hide the right panel",
"toggleRightPanel.title": "Toggle Right Panel",
"toggleZenMode.desc": "In focus mode, only display the current conversation and hide other UI elements",
"toggleZenMode.title": "Toggle Focus Mode"
"toggleRightPanel.title": "Toggle Right Panel"
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"features.agentDocumentFloatingChatPanel.desc": "Show the floating chat panel in agent document preview only when this lab feature is enabled.",
"features.agentDocumentFloatingChatPanel.title": "Agent Document Floating Chat Panel",
"features.agentSelfIteration.desc": "Allow the assistant to reflect, build self-awareness, and continuously iterate through ongoing attempts and interactions.",
"features.agentSelfIteration.desc": "Allow the agent to reflect, build self-awareness, and continuously iterate through ongoing attempts and interactions.",
"features.agentSelfIteration.title": "Agent Self-iteration",
"features.assistantMessageGroup.desc": "Group agent messages and their tool call results together for display",
"features.assistantMessageGroup.title": "Agent Message Grouping",
+2 -2
View File
@@ -34,7 +34,7 @@
"errors.authorizationFailed": "Authorization failed, please try again.",
"errors.browserOnly": "The authorization process can only be initiated in a browser.",
"errors.codeConsumed": "The authorization code has already been used. Please try again.",
"errors.codeVerifierMissing": "Invalid authorization session. Please restart the login process.",
"errors.codeVerifierMissing": "Invalid authorization session. Please restart the sign-in process.",
"errors.general": "An error occurred during authorization. Please try again.",
"errors.handoffFailed": "Failed to retrieve authorization result. Please try again.",
"errors.handoffTimeout": "Authorization timed out. Please complete the process in your browser and try again.",
@@ -42,7 +42,7 @@
"errors.openBrowserFailed": "Failed to open the system browser. Please try again.",
"errors.openPopupFailed": "Failed to open authorization popup. Please check your browser's popup blocker settings.",
"errors.popupClosed": "The authorization window was closed before completion.",
"errors.sessionExpired": "Authorization session has expired. Please log in again.",
"errors.sessionExpired": "Authorization session has expired. Please sign in again.",
"errors.stateMismatch": "Authorization state mismatch. Please try again.",
"errors.stateMissing": "Authorization state not found. Please try again.",
"messages.authorizationFailed": "Authorization ran into an issue. Retry, or check if you finished signing in in your browser.",
+5 -5
View File
@@ -10,17 +10,17 @@
"activity.notes": "Notes",
"analysis.action.button": "Request memory analysis",
"analysis.modal.cancel": "Cancel",
"analysis.modal.helper": "By default Lobe AI will analyze all unprocessed chats. It's optional to select a date range to analyze.",
"analysis.modal.helper": "By default Lobe AI will analyze all unprocessed conversations. It's optional to select a date range to analyze.",
"analysis.modal.rangePlaceholder": "No range selected; all conversations will be analyzed.",
"analysis.modal.rangeSelected": "Analyzing chats from {{start}} to {{end}}",
"analysis.modal.rangeSelected": "Analyzing conversations from {{start}} to {{end}}",
"analysis.modal.submit": "Request memory analysis",
"analysis.modal.title": "Analyze chats to generate memories",
"analysis.modal.title": "Analyze conversations to generate memories",
"analysis.range.all": "All conversations",
"analysis.range.end": "Today",
"analysis.range.start": "Beginning",
"analysis.status.errorTitle": "Memory analysis request failed",
"analysis.status.progress": "Processed {{completed}} / {{total}} topics",
"analysis.status.progressUnknown": "Processed {{completed}} topics so far",
"analysis.status.progress": "Processed {{completed}} / {{total}} conversations",
"analysis.status.progressUnknown": "Processed {{completed}} conversations so far",
"analysis.status.tip": "We are processing your conversations to build personal memories. This may take a few minutes.",
"analysis.status.title": "Memory analysis in progress",
"analysis.toast.deduped": "A memory request is already running, continuing progress…",
+4 -2
View File
@@ -88,7 +88,7 @@
"createNewAiProvider.createSuccess": "Creation successful",
"createNewAiProvider.description.placeholder": "Provider description (optional)",
"createNewAiProvider.description.title": "Provider Description",
"createNewAiProvider.id.desc": "Unique identifier for the service provider, which cannot be modified after creation",
"createNewAiProvider.id.desc": "Unique identifier for the provider, which cannot be modified after creation",
"createNewAiProvider.id.duplicate": "Provider ID already exists",
"createNewAiProvider.id.format": "Can only contain numbers, lowercase letters, hyphens (-), and underscores (_) ",
"createNewAiProvider.id.placeholder": "Suggested all lowercase, e.g., openai, cannot be modified after creation",
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "For Claude Opus 4.6; controls effort level (low/medium/high/max).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "For Claude Opus 4.6; toggles adaptive thinking on or off.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "For Claude, DeepSeek and other reasoning models; unlock deeper thinking.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "For GLM-5.2; controls reasoning effort with High and Max levels.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "For GPT-5 series; controls reasoning intensity.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "For GPT-5.1 series; controls reasoning intensity.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "For GPT-5.2 Pro series; controls reasoning intensity.",
@@ -255,7 +256,8 @@
"providerModels.item.modelConfig.files.extra": "The current file upload implementation is just a hack solution, limited to self-experimentation. Please wait for complete file upload capabilities in future implementations.",
"providerModels.item.modelConfig.files.title": "File Upload Support",
"providerModels.item.modelConfig.functionCall.extra": "This configuration will only enable the model's ability to use tools, allowing for the addition of tool-type skills. However, whether the model can truly use the tools depends entirely on the model itself; please test for usability on your own.",
"providerModels.item.modelConfig.functionCall.title": "Support for Tool Usage",
"providerModels.item.modelConfig.functionCall.title": "Support for Tool Calling",
"providerModels.item.modelConfig.id.duplicate": "A model with this ID already exists. Use a different model ID.",
"providerModels.item.modelConfig.id.extra": "This cannot be modified after creation and will be used as the model ID when calling AI",
"providerModels.item.modelConfig.id.placeholder": "Please enter the model ID, e.g., gpt-4o or claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "Model ID",
+3 -3
View File
@@ -44,9 +44,9 @@
"handoff.desc.success": "An attempt has been made to open the desktop application. If it does not open automatically, please switch manually. You can close this browser window later.",
"handoff.title.processing": "Authorization in progress...",
"handoff.title.success": "Authorization completed",
"login.button": "Confirm Login",
"login.description": "The application {{clientName}} is requesting to use your account for login",
"login.title": "Login to {{clientName}}",
"login.button": "Confirm Sign In",
"login.description": "The application {{clientName}} is requesting to use your account for sign-in",
"login.title": "Sign in to {{clientName}}",
"login.userWelcome": "Welcome back, ",
"success.subTitle": "You have successfully authorized the application to access your account. You may now close this page.",
"success.title": "Authorization Successful"
+3 -3
View File
@@ -75,7 +75,7 @@
"builtins.lobe-agent-management.apiName.deleteAgent": "Delete agent",
"builtins.lobe-agent-management.apiName.duplicateAgent": "Duplicate agent",
"builtins.lobe-agent-management.apiName.getAgentDetail": "Get agent detail",
"builtins.lobe-agent-management.apiName.installPlugin": "Install plugin",
"builtins.lobe-agent-management.apiName.installPlugin": "Install Skill",
"builtins.lobe-agent-management.apiName.searchAgent": "Search agents",
"builtins.lobe-agent-management.apiName.updateAgent": "Update agent",
"builtins.lobe-agent-management.apiName.updatePrompt": "Update prompt",
@@ -84,7 +84,7 @@
"builtins.lobe-agent-management.inspector.createAgent.title": "Creating agent:",
"builtins.lobe-agent-management.inspector.duplicateAgent.title": "Duplicating agent:",
"builtins.lobe-agent-management.inspector.getAgentDetail.title": "Getting details:",
"builtins.lobe-agent-management.inspector.installPlugin.title": "Installing plugin:",
"builtins.lobe-agent-management.inspector.installPlugin.title": "Installing Skill:",
"builtins.lobe-agent-management.inspector.searchAgent.all": "Search agents:",
"builtins.lobe-agent-management.inspector.searchAgent.market": "Search market:",
"builtins.lobe-agent-management.inspector.searchAgent.results": "{{count}} results",
@@ -94,7 +94,7 @@
"builtins.lobe-agent-management.render.duplicateAgent.newId": "New Agent ID",
"builtins.lobe-agent-management.render.duplicateAgent.sourceId": "Source Agent ID",
"builtins.lobe-agent-management.render.installPlugin.failed": "Installation failed",
"builtins.lobe-agent-management.render.installPlugin.plugin": "Plugin",
"builtins.lobe-agent-management.render.installPlugin.plugin": "Skill",
"builtins.lobe-agent-management.render.installPlugin.success": "Installed successfully",
"builtins.lobe-agent-management.title": "Agent Manager",
"builtins.lobe-agent.apiName.analyzeVisualMedia": "Analyze visual media",
+15 -15
View File
@@ -267,8 +267,8 @@
"danger.clear.action": "Clear Now",
"danger.clear.confirm": "Clear all chat data? This can't be undone.",
"danger.clear.desc": "Delete all data, including agents, files, messages, and skills. Your account will NOT be deleted.",
"danger.clear.success": "All session messages have been cleared",
"danger.clear.title": "Wipe Data",
"danger.clear.success": "All conversation messages have been cleared",
"danger.clear.title": "Clear Data",
"danger.reset.action": "Reset Now",
"danger.reset.confirm": "Reset all settings?",
"danger.reset.currentVersion": "Current Version",
@@ -348,9 +348,9 @@
"header.global": "Global Settings",
"header.group": "Group Settings",
"header.groupDesc": "Manage group and chat preferences",
"header.session": "Session Settings",
"header.sessionDesc": "Agent Profile and session preferences",
"header.sessionWithName": "Session Settings · {{name}}",
"header.session": "Agent Settings",
"header.sessionDesc": "Agent Profile and chat preferences",
"header.sessionWithName": "Agent Settings · {{name}}",
"header.title": "Settings",
"heterogeneousStatus.account.label": "Account",
"heterogeneousStatus.auth.api": "API",
@@ -432,7 +432,7 @@
"llm.fetcher.latestTime": "Last Updated: {{time}}",
"llm.fetcher.noLatestTime": "No list available yet",
"llm.helpDoc": "Configuration Guide",
"llm.modelList.desc": "Select the models to display in the session. The selected models will be displayed in the model list.",
"llm.modelList.desc": "Select the models to display in conversations. The selected models will be displayed in the model list.",
"llm.modelList.placeholder": "Please select a model from the list",
"llm.modelList.title": "Model List",
"llm.modelList.total": "{{count}} models available in total",
@@ -701,12 +701,12 @@
"settingGroup.scene.options.productive": "Productive",
"settingGroup.scene.title": "Group scenario",
"settingGroup.submit": "Update Group",
"settingGroup.systemPrompt.placeholder": "Please enter the host system prompt",
"settingGroup.systemPrompt.title": "Host System Prompt",
"settingGroup.systemPrompt.placeholder": "Please enter the Orchestrator system prompt",
"settingGroup.systemPrompt.title": "Orchestrator System Prompt",
"settingGroup.title": "Group Information",
"settingGroupChat.allowDM.desc": "When turned off, you can still send direct messages to the agent",
"settingGroupChat.allowDM.title": "Allow Direct Messages from Agent",
"settingGroupChat.enableSupervisor.desc": "Enable the moderator feature to manage Group conversations",
"settingGroupChat.enableSupervisor.desc": "Enable the Orchestrator feature to manage Group conversations",
"settingGroupChat.enableSupervisor.title": "Enable Orchestrator",
"settingGroupChat.maxResponseInRow.desc": "Select how many consecutive messages a member can reply with. Set to 0 to disable this limit.",
"settingGroupChat.maxResponseInRow.title": "Consecutive Reply Count",
@@ -727,9 +727,9 @@
"settingGroupChat.revealDM.desc": "Make private messages sent to other members visible to you.",
"settingGroupChat.revealDM.title": "Show Private Messages",
"settingGroupChat.submit": "Update Settings",
"settingGroupChat.systemPrompt.desc": "Custom system prompt for the group chat host. This may affect the default host behavior.",
"settingGroupChat.systemPrompt.placeholder": "Please enter a custom host system prompt...",
"settingGroupChat.systemPrompt.title": "Host System Prompt",
"settingGroupChat.systemPrompt.desc": "Custom system prompt for the group chat Orchestrator. This may affect the default Orchestrator behavior.",
"settingGroupChat.systemPrompt.placeholder": "Please enter a custom Orchestrator system prompt...",
"settingGroupChat.systemPrompt.title": "Orchestrator System Prompt",
"settingGroupChat.title": "Chat Settings",
"settingGroupMembers.addToGroup": "Add to Group",
"settingGroupMembers.availableAgents": "Available Agents",
@@ -1634,13 +1634,13 @@
"workspace.general.delete.confirm.title": "Delete Workspace",
"workspace.general.delete.confirm.warning.items.agents": "All agents, skills, and their configurations",
"workspace.general.delete.confirm.warning.items.billing": "Subscription, budget settings, and auto top-up",
"workspace.general.delete.confirm.warning.items.conversations": "All sessions, messages, topics, and tasks",
"workspace.general.delete.confirm.warning.items.conversations": "All messages, topics, and tasks",
"workspace.general.delete.confirm.warning.items.files": "Uploaded files, generations, and knowledge base data",
"workspace.general.delete.confirm.warning.items.members": "Members, pending invitations, and audit logs",
"workspace.general.delete.confirm.warning.lead": "The {{name}} workspace will be permanently deleted, along with:",
"workspace.general.delete.confirm.warning.tail": "This cannot be undone. Spend and top-up history will be retained for audit only.",
"workspace.general.delete.cta": "Delete Workspace",
"workspace.general.delete.description": "Permanently delete this workspace and everything inside it — agents, sessions, messages, files, members, and invitations. This action cannot be reversed.",
"workspace.general.delete.description": "Permanently delete this workspace and everything inside it — agents, messages, files, members, and invitations. This action cannot be reversed.",
"workspace.general.delete.failed": "Failed to delete workspace",
"workspace.general.delete.hint": "Cancel any active subscription before deletion. Billing history is kept for audit.",
"workspace.general.delete.notOwner": "Only the workspace owner can delete this workspace.",
@@ -2053,7 +2053,7 @@
"workspaceSetting.group.subscription": "Plans",
"workspaceSetting.storage.comingSoon": "Workspace-scoped data import & export is coming soon.",
"workspaceSetting.storage.danger.clear.desc": "Delete all data in this workspace, including agents, files, messages, and skills. The workspace itself will NOT be deleted.",
"workspaceSetting.storage.danger.clear.title": "Wipe Workspace Data",
"workspaceSetting.storage.danger.clear.title": "Clear Workspace Data",
"workspaceSetting.storage.danger.reset.desc": "Restore all workspace settings to defaults. Workspace data will not be deleted.",
"workspaceSetting.storage.danger.reset.title": "Reset Workspace Settings",
"workspaceSetting.storage.telemetry.desc": "Help us improve {{appName}} with anonymous workspace usage data",
+8 -8
View File
@@ -7,16 +7,16 @@
"actions.confirmRemoveUnstarred": "You are about to delete unstarred topics. This action cannot be undone.",
"actions.copyLink": "Copy Link",
"actions.copyLinkSuccess": "Link copied",
"actions.copySessionId": "Copy Session ID",
"actions.copySessionIdSuccess": "Session ID copied",
"actions.copySessionId": "Copy Topic ID",
"actions.copySessionIdSuccess": "Topic ID copied",
"actions.copyWorkingDirectory": "Copy Working Directory",
"actions.copyWorkingDirectorySuccess": "Working directory copied",
"actions.duplicate": "Duplicate",
"actions.export": "Export Topics",
"actions.favorite": "Favorite",
"actions.import": "Import Conversation",
"actions.import": "Import Topics",
"actions.markCompleted": "Mark as Completed",
"actions.moveToAgent": "Move to another assistant",
"actions.moveToAgent": "Move to another agent",
"actions.openInNewTab": "Open in New Tab",
"actions.openInNewWindow": "Open in a new window",
"actions.removeAll": "Delete All Topics",
@@ -56,7 +56,7 @@
"guide.title": "Topic List",
"importError": "Import Failed",
"importInvalidFormat": "Invalid file format. Please ensure it is a valid JSON file.",
"importLoading": "Importing conversation...",
"importLoading": "Importing topics...",
"importSuccess": "Successfully imported {{count}} messages",
"inPopup.description": "This topic is currently open in a separate window. Continue the conversation there to keep messages in sync.",
"inPopup.focus": "Focus Popup Window",
@@ -81,9 +81,9 @@
"management.bulk.deleteConfirm": "You are about to delete {{count}} topics. This action cannot be undone.",
"management.bulk.deleteTitle": "Delete topics?",
"management.bulk.favorite": "Favorite",
"management.bulk.move": "Move to assistant",
"management.bulk.moveEmpty": "No other assistants",
"management.bulk.moveSearchPlaceholder": "Search assistants…",
"management.bulk.move": "Move to agent",
"management.bulk.moveEmpty": "No other agents",
"management.bulk.moveSearchPlaceholder": "Search agents…",
"management.bulk.selectedCount_one": "{{count}} selected",
"management.bulk.selectedCount_other": "{{count}} selected",
"management.card.noPreview": "No preview available",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Hola, soy **{{name}}**. Una frase es suficiente.\n\n¿Quieres que me adapte mejor a tu flujo de trabajo? Ve a [Configuración del Agente]({{url}}) y completa el Perfil del Agente (puedes editarlo en cualquier momento).",
"agentDefaultMessageWithSystemRole": "Hola, soy **{{name}}**. Una frase es suficiente—tú tienes el control.",
"agentDefaultMessageWithoutEdit": "Hola, soy **{{name}}**. Una frase es suficiente—tú tienes el control.",
"agentDocument.backToChat": "Volver al chat",
"agentDocument.linkCopied": "Enlace copiado",
"agentDocument.openAsPage": "Abrir como página completa",
"agentProfile.files_one": "{{count}} archivo",
"agentProfile.files_other": "{{count}} archivos",
"agentProfile.knowledgeBases_one": "{{count}} base de conocimiento",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Extraer contenido de enlaces web",
"followUpPlaceholder": "Seguimiento. Usa @ para asignar tareas a otros agentes.",
"followUpPlaceholderHeterogeneous": "Continuar.",
"gatewayMode.title": "Modo Gateway",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Modo de Puerta de Enlace del Agente",
"gatewayMode.desc": "Ejecuta agentes en la nube a través de la Puerta de Enlace de Agentes de LobeHub. Las tareas continúan ejecutándose incluso después de cerrar la página.",
"group.desc": "Avanza una tarea con múltiples Agentes en un espacio compartido.",
"group.memberTooltip": "Hay {{count}} miembros en el grupo",
"group.orchestratorThinking": "El Orquestador está pensando...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autorizar",
"toolAuth.authorizing": "Autorizando...",
"toolAuth.hint": "Sin autorización o configuración, las habilidades pueden no funcionar. Esto puede limitar al agente o causar errores.",
"toolAuth.remove": "Eliminar",
"toolAuth.signIn": "Iniciar sesión",
"toolAuth.title": "Autorizar habilidades para este agente",
"topic.checkOpenNewTopic": "¿Iniciar un nuevo tema?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Habilidades",
"workingPanel.space": "Espacio",
"workingPanel.title": "Working Panel",
"you": "Tú",
"zenMode": "Modo Zen"
"you": "Tú"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Se muestran todas las tareas en ejecución",
"fleet.backToHome": "Volver a inicio",
"fleet.closeColumn": "Cerrar columna",
"fleet.closeIdleColumns": "Cerrar columnas inactivas",
"fleet.closeIdleColumnsCount": "Cerrar {{count}} columnas inactivas",
"fleet.collapseReply": "Colapsar",
"fleet.createTask": "Crear tarea",
"fleet.dragHint": "Arrastrar para reordenar",
"fleet.empty": "No hay tareas abiertas",
"fleet.emptyDesc": "Selecciona una tarea en ejecución a la izquierda o usa + para agregar una columna.",
"fleet.noRunningTasks": "No hay tareas en ejecución",
"fleet.openInChat": "Abrir en el chat",
"fleet.pin": "Fijar columna",
"fleet.reply": "Responder",
"fleet.runningTasks": "Tareas en ejecución",
"fleet.rows.one": "Una fila",
"fleet.rows.two": "Dos filas",
"fleet.runningBoard": "Tablero en ejecución",
"fleet.status.idle": "Inactivo",
"fleet.status.paused": "Pausado",
"fleet.status.running": "En ejecución",
"fleet.status.scheduled": "Programado",
"fleet.tooltip": "Ver todos los agentes uno al lado del otro",
"fleet.unpin": "Desfijar columna",
"gateway.description": "Descripción",
"gateway.descriptionPlaceholder": "Opcional",
"gateway.deviceName": "Nombre del Dispositivo",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Descubrir MCP",
"navigation.discoverModels": "Descubrir Modelos",
"navigation.discoverProviders": "Descubrir Proveedores",
"navigation.document": "Documento",
"navigation.group": "Grupo",
"navigation.groupChat": "Chat de Grupo",
"navigation.home": "Inicio",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Expirados",
"credits.packages.tabs.expiredCount": "Expirados ({{count}})",
"credits.packages.title": "Mis Paquetes de Créditos",
"credits.topUp.bestValue.cta": "Ver Ultimate anual",
"credits.topUp.bestValue.savings": "Ahorra ${{savings}} en esta compra",
"credits.topUp.bestValue.title": "{{plan}} anual desbloquea la tarifa de recarga más baja: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Cancelar",
"credits.topUp.custom": "Personalizado",
"credits.topUp.freeFeeHint": "Las recargas del plan gratuito incluyen una tarifa de servicio de {{fee}} por cada 1M de créditos.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Procesamiento de recompensas: Los créditos se distribuirán dentro de 1 hora después de que el invitado complete un pago y pase la verificación",
"referral.rules.title": "Reglas del Programa",
"referral.rules.validInvitation": "Invitación válida: El invitado se registra con tu código de referencia, realiza una acción válida y completa un pago (suscripción o recarga de créditos)",
"referral.rules.validOperation": "Criterios de acción válida: enviar un mensaje en la página de chat o generar una imagen",
"referral.stats.availableBalance": "Saldo Disponible",
"referral.stats.description": "Consulta tus estadísticas de referidos",
"referral.stats.title": "Resumen de Referidos",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Archivado",
"management.status.completed": "Completado",
"management.status.failed": "Fallido",
"management.status.idle": "Inactivo",
"management.status.paused": "Pausado",
"management.status.running": "En ejecución",
"management.status.waitingForHuman": "Esperando entrada",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} temas fallidos",
"projectStatus.loading_one": "{{count}} tema cargando",
"projectStatus.loading_other": "{{count}} temas cargando",
"projectStatus.unread_one": "{{count}} tema con respuesta no leída",
"projectStatus.unread_other": "{{count}} temas con respuestas no leídas",
"projectStatus.waitingForHuman_one": "{{count}} tema esperando entrada",
"projectStatus.waitingForHuman_other": "{{count}} temas esperando entrada",
"renameModal.description": "Mantenlo breve y fácil de reconocer.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "سلام، من **{{name}}** هستم. یک جمله کافی است.\n\nمی‌خواهی بهتر با جریان کاری‌ات هماهنگ شوم؟ به [تنظیمات عامل]({{url}}) برو و نمایه عامل را پر کن (هر زمان می‌توانی ویرایشش کنی).",
"agentDefaultMessageWithSystemRole": "سلام، من **{{name}}** هستم. یک جمله کافی است—کنترل با توست.",
"agentDefaultMessageWithoutEdit": "سلام، من **{{name}}** هستم. یک جمله کافی است—کنترل با توست.",
"agentDocument.backToChat": "بازگشت به چت",
"agentDocument.linkCopied": "لینک کپی شد",
"agentDocument.openAsPage": "باز کردن به صورت صفحه کامل",
"agentProfile.files_one": "{{count}} فایل",
"agentProfile.files_other": "{{count}} فایل",
"agentProfile.knowledgeBases_one": "{{count}} پایگاه‌دانش",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "استخراج محتوای پیوند وب",
"followUpPlaceholder": "پیگیری. برای واگذاری وظیفه به عامل‌های دیگر از @ استفاده کنید.",
"followUpPlaceholderHeterogeneous": "پیگیری.",
"gatewayMode.title": "حالت دروازه",
"gatewayMode.beta": "بتا",
"gatewayMode.cardTitle": "حالت دروازه نماینده",
"gatewayMode.desc": "اجرای نمایندگان در فضای ابری از طریق دروازه نماینده LobeHub. وظایف حتی پس از بستن صفحه ادامه می‌یابند.",
"group.desc": "با چند عامل در یک فضای مشترک، یک وظیفه را پیش ببرید.",
"group.memberTooltip": "{{count}} عضو در گروه وجود دارد",
"group.orchestratorThinking": "هماهنگ‌کننده در حال تفکر است...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "مجازسازی",
"toolAuth.authorizing": "در حال مجازسازی...",
"toolAuth.hint": "بدون مجازسازی یا پیکربندی، مهارت‌ها ممکن است کار نکنند. این می‌تواند باعث محدودیت یا خطا شود.",
"toolAuth.remove": "حذف",
"toolAuth.signIn": "ورود",
"toolAuth.title": "مجازسازی مهارت‌ها برای این نماینده",
"topic.checkOpenNewTopic": "موضوع جدیدی آغاز شود؟",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "مهارت‌ها",
"workingPanel.space": "فضا",
"workingPanel.title": "Working Panel",
"you": "شما",
"zenMode": "حالت تمرکز"
"you": "شما"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "تمام وظایف در حال اجرا نمایش داده شده‌اند",
"fleet.backToHome": "بازگشت به خانه",
"fleet.closeColumn": "بستن ستون",
"fleet.closeIdleColumns": "بستن ستون‌های بیکار",
"fleet.closeIdleColumnsCount": "بستن {{count}} ستون بیکار",
"fleet.collapseReply": "جمع کردن",
"fleet.createTask": "ایجاد وظیفه",
"fleet.dragHint": "برای تغییر ترتیب بکشید",
"fleet.empty": "هیچ وظیفه‌ای باز نیست",
"fleet.emptyDesc": "یک وظیفه در حال اجرا را از سمت چپ انتخاب کنید یا از + برای افزودن ستون استفاده کنید.",
"fleet.noRunningTasks": "هیچ وظیفه‌ای در حال اجرا نیست",
"fleet.openInChat": "باز کردن در چت",
"fleet.pin": "سنجاق کردن ستون",
"fleet.reply": "پاسخ",
"fleet.runningTasks": "وظایف در حال اجرا",
"fleet.rows.one": "یک ردیف",
"fleet.rows.two": "دو ردیف",
"fleet.runningBoard": "تخته در حال اجرا",
"fleet.status.idle": "بیکار",
"fleet.status.paused": "متوقف شده",
"fleet.status.running": "در حال اجرا",
"fleet.status.scheduled": "برنامه‌ریزی شده",
"fleet.tooltip": "مشاهده تمام عوامل در کنار یکدیگر",
"fleet.unpin": "برداشتن سنجاق ستون",
"gateway.description": "توضیحات",
"gateway.descriptionPlaceholder": "اختیاری",
"gateway.deviceName": "نام دستگاه",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "کشف MCP",
"navigation.discoverModels": "کشف مدل‌ها",
"navigation.discoverProviders": "کشف ارائه‌دهندگان",
"navigation.document": "سند",
"navigation.group": "گروه",
"navigation.groupChat": "چت گروهی",
"navigation.home": "خانه",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "منقضی شده",
"credits.packages.tabs.expiredCount": "منقضی شده ({{count}})",
"credits.packages.title": "بسته‌های اعتباری من",
"credits.topUp.bestValue.cta": "مشاهده سالانه نهایی",
"credits.topUp.bestValue.savings": "در این خرید ${{savings}} صرفه‌جویی کنید",
"credits.topUp.bestValue.title": "{{plan}} سالانه کمترین نرخ شارژ را باز می‌کند: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "لغو",
"credits.topUp.custom": "سفارشی",
"credits.topUp.freeFeeHint": "شارژ طرح رایگان شامل هزینه خدمات {{fee}} به ازای هر ۱ میلیون اعتبار است.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "پردازش پاداش: اعتبارها ظرف 1 ساعت پس از تکمیل پرداخت و تأیید توسط دعوت‌شونده توزیع می‌شوند.",
"referral.rules.title": "قوانین برنامه",
"referral.rules.validInvitation": "دعوت معتبر: دعوت‌شونده با کد ارجاع شما ثبت‌نام می‌کند، یک اقدام معتبر انجام می‌دهد و پرداخت را تکمیل می‌کند (اشتراک یا شارژ اعتبار).",
"referral.rules.validOperation": "معیار اقدام معتبر: ارسال یک پیام در صفحه چت یا تولید یک تصویر در صفحه تصویر",
"referral.stats.availableBalance": "موجودی قابل استفاده",
"referral.stats.description": "آمار دعوت‌های خود را مشاهده کنید",
"referral.stats.title": "نمای کلی دعوت‌ها",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "بایگانی‌شده",
"management.status.completed": "تکمیل‌شده",
"management.status.failed": "ناموفق",
"management.status.idle": "بیکار",
"management.status.paused": "متوقف‌شده",
"management.status.running": "در حال اجرا",
"management.status.waitingForHuman": "در انتظار ورودی",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} موضوعات ناموفق",
"projectStatus.loading_one": "{{count}} موضوع در حال بارگذاری",
"projectStatus.loading_other": "{{count}} موضوعات در حال بارگذاری",
"projectStatus.unread_one": "{{count}} موضوع با پاسخ خوانده نشده",
"projectStatus.unread_other": "{{count}} موضوع با پاسخ‌های خوانده نشده",
"projectStatus.waitingForHuman_one": "{{count}} موضوع منتظر ورودی",
"projectStatus.waitingForHuman_other": "{{count}} موضوعات منتظر ورودی",
"renameModal.description": "کوتاه و قابل تشخیص نگه دارید.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Bonjour, je suis **{{name}}**. Une phrase suffit.\n\nVous souhaitez que je madapte mieux à votre flux de travail ? Allez dans [Paramètres de lagent]({{url}}) et complétez le profil de lagent (modifiable à tout moment).",
"agentDefaultMessageWithSystemRole": "Bonjour, je suis **{{name}}**. Une phrase suffit — vous avez le contrôle.",
"agentDefaultMessageWithoutEdit": "Bonjour, je suis **{{name}}**. Une phrase suffit — vous avez le contrôle.",
"agentDocument.backToChat": "Retour au chat",
"agentDocument.linkCopied": "Lien copié",
"agentDocument.openAsPage": "Ouvrir en pleine page",
"agentProfile.files_one": "{{count}} fichier",
"agentProfile.files_other": "{{count}} fichiers",
"agentProfile.knowledgeBases_one": "{{count}} base de connaissances",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Extraire le contenu des liens web",
"followUpPlaceholder": "Donner suite. @ pour attribuer des tâches à dautres agents.",
"followUpPlaceholderHeterogeneous": "Poursuivre.",
"gatewayMode.title": "Mode Passerelle",
"gatewayMode.beta": "Bêta",
"gatewayMode.cardTitle": "Mode Passerelle Agent",
"gatewayMode.desc": "Exécutez des agents dans le cloud via la Passerelle Agent de LobeHub. Les tâches continuent de s'exécuter même après la fermeture de la page.",
"group.desc": "Faites avancer une tâche avec plusieurs agents dans un espace partagé.",
"group.memberTooltip": "Il y a {{count}} membres dans le groupe",
"group.orchestratorThinking": "Lorchestrateur réfléchit...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autoriser",
"toolAuth.authorizing": "Autorisation en cours...",
"toolAuth.hint": "Sans autorisation ou configuration, les compétences peuvent ne pas fonctionner. Cela peut limiter l'agent ou provoquer des erreurs.",
"toolAuth.remove": "Supprimer",
"toolAuth.signIn": "Se connecter",
"toolAuth.title": "Autoriser les compétences pour cet agent",
"topic.checkOpenNewTopic": "Commencer un nouveau sujet ?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Compétences",
"workingPanel.space": "Espace",
"workingPanel.title": "Working Panel",
"you": "Vous",
"zenMode": "Mode Zen"
"you": "Vous"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Toutes les tâches en cours sont affichées",
"fleet.backToHome": "Retour à l'accueil",
"fleet.closeColumn": "Fermer la colonne",
"fleet.closeIdleColumns": "Fermer les colonnes inactives",
"fleet.closeIdleColumnsCount": "Fermer {{count}} colonnes inactives",
"fleet.collapseReply": "Réduire",
"fleet.createTask": "Créer une tâche",
"fleet.dragHint": "Glisser pour réorganiser",
"fleet.empty": "Aucune tâche ouverte",
"fleet.emptyDesc": "Choisissez une tâche en cours à gauche, ou utilisez + pour ajouter une colonne.",
"fleet.noRunningTasks": "Aucune tâche en cours",
"fleet.openInChat": "Ouvrir dans le chat",
"fleet.pin": "Épingler la colonne",
"fleet.reply": "Répondre",
"fleet.runningTasks": "Tâches en cours",
"fleet.rows.one": "Une seule ligne",
"fleet.rows.two": "Deux lignes",
"fleet.runningBoard": "Tableau de bord en cours",
"fleet.status.idle": "Inactif",
"fleet.status.paused": "En pause",
"fleet.status.running": "En cours",
"fleet.status.scheduled": "Planifié",
"fleet.tooltip": "Voir tous les agents côte à côte",
"fleet.unpin": "Désépingler la colonne",
"gateway.description": "Description",
"gateway.descriptionPlaceholder": "Optionnel",
"gateway.deviceName": "Nom de l'appareil",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Découvrir MCP",
"navigation.discoverModels": "Découvrir les Modèles",
"navigation.discoverProviders": "Découvrir les Fournisseurs",
"navigation.document": "Document",
"navigation.group": "Groupe",
"navigation.groupChat": "Chat de Groupe",
"navigation.home": "Accueil",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Expirés",
"credits.packages.tabs.expiredCount": "Expirés ({{count}})",
"credits.packages.title": "Mes forfaits de crédits",
"credits.topUp.bestValue.cta": "Voir l'abonnement annuel Ultimate",
"credits.topUp.bestValue.savings": "Économisez ${{savings}} sur cet achat",
"credits.topUp.bestValue.title": "L'abonnement annuel {{plan}} offre le tarif de recharge le plus bas : ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Annuler",
"credits.topUp.custom": "Personnalisé",
"credits.topUp.freeFeeHint": "Les rechargements du plan gratuit incluent des frais de service de {{fee}} par 1M de crédits.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Traitement des récompenses : Les crédits seront distribués dans l'heure suivant le paiement et la vérification de l'invité.",
"referral.rules.title": "Règles du programme",
"referral.rules.validInvitation": "Invitation valide : L'invité s'inscrit avec votre code de parrainage, effectue une action valide et finalise un paiement (abonnement ou recharge de crédits).",
"referral.rules.validOperation": "Critères daction valide : envoyer un message ou générer une image",
"referral.stats.availableBalance": "Solde disponible",
"referral.stats.description": "Consultez vos statistiques de parrainage",
"referral.stats.title": "Aperçu du parrainage",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Archivé",
"management.status.completed": "Terminé",
"management.status.failed": "Échoué",
"management.status.idle": "Inactif",
"management.status.paused": "En pause",
"management.status.running": "En cours",
"management.status.waitingForHuman": "En attente d'une intervention",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} sujets échoués",
"projectStatus.loading_one": "{{count}} sujet en cours de chargement",
"projectStatus.loading_other": "{{count}} sujets en cours de chargement",
"projectStatus.unread_one": "{{count}} sujet avec une réponse non lue",
"projectStatus.unread_other": "{{count}} sujets avec des réponses non lues",
"projectStatus.waitingForHuman_one": "{{count}} sujet en attente d'entrée",
"projectStatus.waitingForHuman_other": "{{count}} sujets en attente d'entrée",
"renameModal.description": "Gardez-le court et facile à reconnaître.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Ciao, sono **{{name}}**. Una frase è sufficiente.\n\nVuoi che mi adatti meglio al tuo flusso di lavoro? Vai su [Impostazioni Agente]({{url}}) e compila il Profilo Agente (puoi modificarlo in qualsiasi momento).",
"agentDefaultMessageWithSystemRole": "Ciao, sono **{{name}}**. Una frase è sufficiente—sei tu al comando.",
"agentDefaultMessageWithoutEdit": "Ciao, sono **{{name}}**. Una frase è sufficiente—sei tu al comando.",
"agentDocument.backToChat": "Torna alla chat",
"agentDocument.linkCopied": "Link copiato",
"agentDocument.openAsPage": "Apri come pagina intera",
"agentProfile.files_one": "{{count}} file",
"agentProfile.files_other": "{{count}} file",
"agentProfile.knowledgeBases_one": "{{count}} base di conoscenza",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Estrai Contenuto da Link Web",
"followUpPlaceholder": "Follow-up. Usa @ per assegnare attività ad altri agenti.",
"followUpPlaceholderHeterogeneous": "Continua.",
"gatewayMode.title": "Modalità Gateway",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Modalità Gateway Agente",
"gatewayMode.desc": "Esegui agenti nel cloud tramite l'Agent Gateway di LobeHub. Le attività continuano a funzionare anche dopo aver chiuso la pagina.",
"group.desc": "Fai avanzare un'attività con più Agenti in uno spazio condiviso.",
"group.memberTooltip": "Ci sono {{count}} membri nel gruppo",
"group.orchestratorThinking": "L'Orchestratore sta pensando...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autorizza",
"toolAuth.authorizing": "Autorizzazione in corso...",
"toolAuth.hint": "Senza autorizzazione o configurazione, le Skill potrebbero non funzionare. Questo può limitare l'agente o causare errori.",
"toolAuth.remove": "Rimuovi",
"toolAuth.signIn": "Accedi",
"toolAuth.title": "Autorizza le Skill per questo agente",
"topic.checkOpenNewTopic": "Iniziare un nuovo argomento?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Competenze",
"workingPanel.space": "Spazio",
"workingPanel.title": "Working Panel",
"you": "Tu",
"zenMode": "Modalità Zen"
"you": "Tu"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Tutti i compiti in esecuzione sono mostrati",
"fleet.backToHome": "Torna alla home",
"fleet.closeColumn": "Chiudi colonna",
"fleet.closeIdleColumns": "Chiudi colonne inattive",
"fleet.closeIdleColumnsCount": "Chiudi {{count}} colonne inattive",
"fleet.collapseReply": "Comprimi",
"fleet.createTask": "Crea compito",
"fleet.dragHint": "Trascina per riordinare",
"fleet.empty": "Nessun compito aperto",
"fleet.emptyDesc": "Seleziona un compito in esecuzione a sinistra o usa + per aggiungere una colonna.",
"fleet.noRunningTasks": "Nessun compito in esecuzione",
"fleet.openInChat": "Apri nella chat",
"fleet.pin": "Fissa colonna",
"fleet.reply": "Rispondi",
"fleet.runningTasks": "Compiti in esecuzione",
"fleet.rows.one": "Singola riga",
"fleet.rows.two": "Due righe",
"fleet.runningBoard": "Pannello in esecuzione",
"fleet.status.idle": "Inattivo",
"fleet.status.paused": "In pausa",
"fleet.status.running": "In esecuzione",
"fleet.status.scheduled": "Programmato",
"fleet.tooltip": "Visualizza tutti gli agenti fianco a fianco",
"fleet.unpin": "Sblocca colonna",
"gateway.description": "Descrizione",
"gateway.descriptionPlaceholder": "Facoltativo",
"gateway.deviceName": "Nome Dispositivo",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Scopri MCP",
"navigation.discoverModels": "Scopri Modelli",
"navigation.discoverProviders": "Scopri Provider",
"navigation.document": "Documento",
"navigation.group": "Gruppo",
"navigation.groupChat": "Chat di Gruppo",
"navigation.home": "Home",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Scaduti",
"credits.packages.tabs.expiredCount": "Scaduti ({{count}})",
"credits.packages.title": "I Miei Pacchetti Crediti",
"credits.topUp.bestValue.cta": "Visualizza Ultimate annuale",
"credits.topUp.bestValue.savings": "Risparmia ${{savings}} su questo acquisto",
"credits.topUp.bestValue.title": "{{plan}} annuale sblocca la tariffa di ricarica più bassa: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Annulla",
"credits.topUp.custom": "Personalizzato",
"credits.topUp.freeFeeHint": "Le ricariche del piano gratuito includono una commissione di servizio di {{fee}} per 1M di crediti.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Elaborazione delle ricompense: I crediti saranno distribuiti entro 1 ora dopo che l'invitato completa un pagamento e supera la verifica",
"referral.rules.title": "Regole del Programma",
"referral.rules.validInvitation": "Invito valido: L'invitato si registra con il tuo codice di riferimento, esegue un'azione valida e completa un pagamento (abbonamento o ricarica crediti)",
"referral.rules.validOperation": "Criteri di azione valida: invia un messaggio nella pagina Chat o genera un'immagine nella pagina immagini",
"referral.stats.availableBalance": "Saldo Disponibile",
"referral.stats.description": "Visualizza le tue statistiche di invito",
"referral.stats.title": "Panoramica Inviti",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Archiviato",
"management.status.completed": "Completato",
"management.status.failed": "Fallito",
"management.status.idle": "Inattivo",
"management.status.paused": "In pausa",
"management.status.running": "In esecuzione",
"management.status.waitingForHuman": "In attesa di input",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} argomenti falliti",
"projectStatus.loading_one": "{{count}} argomento in caricamento",
"projectStatus.loading_other": "{{count}} argomenti in caricamento",
"projectStatus.unread_one": "{{count}} argomento con risposta non letta",
"projectStatus.unread_other": "{{count}} argomenti con risposte non lette",
"projectStatus.waitingForHuman_one": "{{count}} argomento in attesa di input",
"projectStatus.waitingForHuman_other": "{{count}} argomenti in attesa di input",
"renameModal.description": "Mantienilo breve e facile da riconoscere.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "こんにちは、私は **{{name}}** です。一文から始めましょう。\n\nよりあなたの働き方に合わせるには:[アシスタント設定]({{url}}) でアシスタントプロフィールを補完してください(いつでも変更可能)",
"agentDefaultMessageWithSystemRole": "こんにちは、私は **{{name}}** です。一文から始めましょう—判断はあなたにあります",
"agentDefaultMessageWithoutEdit": "こんにちは、私は **{{name}}** です。一文から始めましょう—判断はあなたにあります",
"agentDocument.backToChat": "チャットに戻る",
"agentDocument.linkCopied": "リンクがコピーされました",
"agentDocument.openAsPage": "全画面で開く",
"agentProfile.files_one": "{{count}} 件のファイル",
"agentProfile.files_other": "{{count}} 件のファイル",
"agentProfile.knowledgeBases_one": "{{count}} 件のナレッジベース",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "ウェブリンクコンテンツの抽出",
"followUpPlaceholder": "フォローアップ。@で他のエージェントにタスクを割り当てできます。",
"followUpPlaceholderHeterogeneous": "フォローアップ。",
"gatewayMode.title": "ゲートウェイモード",
"gatewayMode.beta": "ベータ版",
"gatewayMode.cardTitle": "エージェントゲートウェイモード",
"gatewayMode.desc": "LobeHubのエージェントゲートウェイを通じてクラウドでエージェントを実行します。ページを閉じた後もタスクは実行され続けます。",
"group.desc": "同一の対話空間で、複数のアシスタントが一緒にタスクを推進します",
"group.memberTooltip": "グループに {{count}} 名のメンバーがいます",
"group.orchestratorThinking": "ホストが思考中…",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "承認",
"toolAuth.authorizing": "承認中…",
"toolAuth.hint": "未承認または未設定の場合、関連スキルは使用できません。これによりアシスタントの機能が制限されたりエラーが発生したりする可能性があります",
"toolAuth.remove": "削除",
"toolAuth.signIn": "ログイン",
"toolAuth.title": "アシスタントのスキル承認を完了してください",
"topic.checkOpenNewTopic": "新しいトピックを開きますか?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "スキル",
"workingPanel.space": "スペース",
"workingPanel.title": "Working Panel",
"you": "あなた",
"zenMode": "集中モード"
"you": "あなた"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "すべての実行中のタスクが表示されています",
"fleet.backToHome": "ホームに戻る",
"fleet.closeColumn": "列を閉じる",
"fleet.closeIdleColumns": "アイドル状態の列を閉じる",
"fleet.closeIdleColumnsCount": "{{count}} 個のアイドル状態の列を閉じる",
"fleet.collapseReply": "折りたたむ",
"fleet.createTask": "タスクを作成",
"fleet.dragHint": "ドラッグして並べ替え",
"fleet.empty": "開いているタスクはありません",
"fleet.emptyDesc": "左側の実行中のタスクを選択するか、+を使用して列を追加してください。",
"fleet.noRunningTasks": "実行中のタスクはありません",
"fleet.openInChat": "チャットで開く",
"fleet.pin": "列を固定",
"fleet.reply": "返信",
"fleet.runningTasks": "実行中のタスク",
"fleet.rows.one": "1行",
"fleet.rows.two": "2行",
"fleet.runningBoard": "ランニングボード",
"fleet.status.idle": "待機中",
"fleet.status.paused": "一時停止中",
"fleet.status.running": "実行中",
"fleet.status.scheduled": "スケジュール済み",
"fleet.tooltip": "すべてのエージェントを並べて表示",
"fleet.unpin": "列の固定を解除",
"gateway.description": "説明",
"gateway.descriptionPlaceholder": "任意",
"gateway.deviceName": "デバイス名",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "MCP を発見",
"navigation.discoverModels": "モデルを発見",
"navigation.discoverProviders": "プロバイダーを発見",
"navigation.document": "ドキュメント",
"navigation.group": "グループ",
"navigation.groupChat": "グループチャット",
"navigation.home": "ホーム",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "期限切れ",
"credits.packages.tabs.expiredCount": "期限切れ({{count}}",
"credits.packages.title": "マイクレジットパッケージ",
"credits.topUp.bestValue.cta": "究極の年間プランを見る",
"credits.topUp.bestValue.savings": "この購入で${{savings}}節約",
"credits.topUp.bestValue.title": "{{plan}}年間プランで最安のチャージレートを解放: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "キャンセル",
"credits.topUp.custom": "カスタム",
"credits.topUp.freeFeeHint": "無料プランのチャージには、1Mクレジットごとに{{fee}}のサービス料が含まれます。",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "報酬処理: 招待者が支払いを完了し、認証を通過した後、クレジットは1時間以内に配布されます。",
"referral.rules.title": "プログラムルール",
"referral.rules.validInvitation": "有効な招待: 招待者があなたの招待コードで登録し、有効なアクションを1つ実行し、支払い(サブスクリプションまたはクレジット追加)を完了すること。",
"referral.rules.validOperation": "有効な操作の条件:チャットページで1回メッセージ送信、または画像ページで1枚生成",
"referral.stats.availableBalance": "利用可能残高",
"referral.stats.description": "紹介統計を確認",
"referral.stats.title": "紹介概要",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "アーカイブ済み",
"management.status.completed": "完了済み",
"management.status.failed": "失敗",
"management.status.idle": "アイドル",
"management.status.paused": "一時停止",
"management.status.running": "実行中",
"management.status.waitingForHuman": "入力待ち",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} 件のトピックが失敗しました",
"projectStatus.loading_one": "{{count}} 件のトピックを読み込み中",
"projectStatus.loading_other": "{{count}} 件のトピックを読み込み中",
"projectStatus.unread_one": "{{count}} 件の未読返信があるトピック",
"projectStatus.unread_other": "{{count}} 件の未読返信があるトピック",
"projectStatus.waitingForHuman_one": "{{count}} 件のトピックが入力待ちです",
"projectStatus.waitingForHuman_other": "{{count}} 件のトピックが入力待ちです",
"renameModal.description": "短く、わかりやすい名前にしてください。",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "안녕하세요, 저는 **{{name}}**입니다. 한 문장으로 시작하세요.\n\n더 당신의 업무 방식에 맞추려면: [도우미 설정]({{url}})에서 도우미 프로필을 보완하세요(언제든 변경 가능)",
"agentDefaultMessageWithSystemRole": "안녕하세요, 저는 **{{name}}**입니다. 한 문장으로 시작하세요—결정은 당신에게 있습니다",
"agentDefaultMessageWithoutEdit": "안녕하세요, 저는 **{{name}}**입니다. 한 문장으로 시작하세요—결정은 당신에게 있습니다",
"agentDocument.backToChat": "채팅으로 돌아가기",
"agentDocument.linkCopied": "링크가 복사되었습니다",
"agentDocument.openAsPage": "전체 페이지로 열기",
"agentProfile.files_one": "{{count}}개 파일",
"agentProfile.files_other": "{{count}}개 파일",
"agentProfile.knowledgeBases_one": "{{count}}개 지식 베이스",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "웹 링크 컨텐츠 추출",
"followUpPlaceholder": "후속 작업. 다른 에이전트에게 작업을 할당하려면 @를 사용하세요.",
"followUpPlaceholderHeterogeneous": "후속 메시지를 입력하세요.",
"gatewayMode.title": "게이트웨이 모드",
"gatewayMode.beta": "베타",
"gatewayMode.cardTitle": "에이전트 게이트웨이 모드",
"gatewayMode.desc": "LobeHub의 에이전트 게이트웨이를 통해 클라우드에서 에이전트를 실행하세요. 페이지를 닫아도 작업이 계속 실행됩니다.",
"group.desc": "동일한 대화 공간에서 여러 도우미가 함께 작업을 추진합니다",
"group.memberTooltip": "그룹에 {{count}}명의 구성원이 있습니다",
"group.orchestratorThinking": "호스트가 생각 중…",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "승인",
"toolAuth.authorizing": "승인 중…",
"toolAuth.hint": "승인 또는 설정을 하지 않으면 이 기능들을 정상적으로 사용할 수 없어 도우미 기능 누락이나 오류가 발생할 수 있습니다",
"toolAuth.remove": "제거",
"toolAuth.signIn": "로그인",
"toolAuth.title": "도우미의 기능 승인을 완료하세요",
"topic.checkOpenNewTopic": "새 주제를 열겠습니까?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "스킬",
"workingPanel.space": "공간",
"workingPanel.title": "Working Panel",
"you": "당신",
"zenMode": "집중 모드"
"you": "당신"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "모든 실행 중인 작업이 표시됩니다",
"fleet.backToHome": "홈으로 돌아가기",
"fleet.closeColumn": "열 닫기",
"fleet.closeIdleColumns": "유휴 열 닫기",
"fleet.closeIdleColumnsCount": "{{count}}개의 유휴 열 닫기",
"fleet.collapseReply": "접기",
"fleet.createTask": "작업 생성",
"fleet.dragHint": "끌어서 순서 변경",
"fleet.empty": "열린 작업 없음",
"fleet.emptyDesc": "왼쪽에서 실행 중인 작업을 선택하거나 +를 사용하여 열을 추가하세요.",
"fleet.noRunningTasks": "실행 중인 작업 없음",
"fleet.openInChat": "채팅에서 열기",
"fleet.pin": "열 고정",
"fleet.reply": "답장",
"fleet.runningTasks": "실행 중인 작업",
"fleet.rows.one": "단일 행",
"fleet.rows.two": "두 행",
"fleet.runningBoard": "실행 보드",
"fleet.status.idle": "대기 중",
"fleet.status.paused": "일시 중지됨",
"fleet.status.running": "실행 중",
"fleet.status.scheduled": "예약됨",
"fleet.tooltip": "모든 에이전트를 나란히 보기",
"fleet.unpin": "열 고정 해제",
"gateway.description": "설명",
"gateway.descriptionPlaceholder": "선택 사항",
"gateway.deviceName": "장치 이름",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "MCP 발견",
"navigation.discoverModels": "모델 발견",
"navigation.discoverProviders": "프로바이더 발견",
"navigation.document": "문서",
"navigation.group": "그룹",
"navigation.groupChat": "그룹 채팅",
"navigation.home": "홈",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "만료됨",
"credits.packages.tabs.expiredCount": "만료됨 ({{count}})",
"credits.packages.title": "내 크레딧 패키지",
"credits.topUp.bestValue.cta": "궁극의 연간 보기",
"credits.topUp.bestValue.savings": "이번 구매에서 ${{savings}} 절약",
"credits.topUp.bestValue.title": "{{plan}} 연간 플랜은 최저 충전 요금을 제공합니다: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "취소",
"credits.topUp.custom": "사용자 지정",
"credits.topUp.freeFeeHint": "무료 플랜 충전에는 1M 크레딧당 {{fee}} 서비스 수수료가 포함됩니다.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "보상 처리: 초대받은 사용자가 결제를 완료하고 검증을 통과한 후 1시간 이내에 크레딧이 분배됩니다.",
"referral.rules.title": "프로그램 규칙",
"referral.rules.validInvitation": "유효한 초대: 초대받은 사용자가 귀하의 초대 코드를 사용하여 등록하고, 유효한 작업을 한 번 수행하며, 결제(구독 또는 크레딧 충전)를 완료합니다.",
"referral.rules.validOperation": "유효한 행동 기준: 채팅 페이지에서 메시지 1회 전송 또는 이미지 생성 1회",
"referral.stats.availableBalance": "사용 가능 잔액",
"referral.stats.description": "추천 통계를 확인하세요",
"referral.stats.title": "추천 개요",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "보관됨",
"management.status.completed": "완료됨",
"management.status.failed": "실패",
"management.status.idle": "대기 중",
"management.status.paused": "일시 중지됨",
"management.status.running": "진행 중",
"management.status.waitingForHuman": "입력 대기 중",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}}개의 실패한 주제들",
"projectStatus.loading_one": "{{count}}개의 로딩 중인 주제",
"projectStatus.loading_other": "{{count}}개의 로딩 중인 주제들",
"projectStatus.unread_one": "{{count}}개의 읽지 않은 답글이 있는 주제",
"projectStatus.unread_other": "{{count}}개의 읽지 않은 답글이 있는 주제들",
"projectStatus.waitingForHuman_one": "{{count}}개의 입력 대기 중인 주제",
"projectStatus.waitingForHuman_other": "{{count}}개의 입력 대기 중인 주제들",
"renameModal.description": "간단하고 알아보기 쉽게 설정하세요.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Hoi, ik ben **{{name}}**. Eén zin is genoeg.\n\nWil je dat ik beter aansluit op jouw workflow? Ga naar [Agentinstellingen]({{url}}) en vul het Agentprofiel in (je kunt dit altijd aanpassen).",
"agentDefaultMessageWithSystemRole": "Hoi, ik ben **{{name}}**. Eén zin is genoeg—jij hebt de controle.",
"agentDefaultMessageWithoutEdit": "Hoi, ik ben **{{name}}**. Eén zin is genoeg—jij hebt de controle.",
"agentDocument.backToChat": "Terug naar chat",
"agentDocument.linkCopied": "Link gekopieerd",
"agentDocument.openAsPage": "Openen als volledige pagina",
"agentProfile.files_one": "{{count}} bestand",
"agentProfile.files_other": "{{count}} bestanden",
"agentProfile.knowledgeBases_one": "{{count}} kennisbank",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Webpagina-inhoud ophalen",
"followUpPlaceholder": "Vervolgen. @ om taken toe te wijzen aan andere agenten.",
"followUpPlaceholderHeterogeneous": "Vervolgbericht.",
"gatewayMode.title": "Gateway-modus",
"gatewayMode.beta": "Bèta",
"gatewayMode.cardTitle": "Agent Gateway-modus",
"gatewayMode.desc": "Voer agents uit in de cloud via LobeHub's Agent Gateway. Taken blijven doorgaan, zelfs nadat u de pagina hebt gesloten.",
"group.desc": "Werk samen met meerdere Agents in één gedeelde ruimte.",
"group.memberTooltip": "Er zijn {{count}} leden in de groep",
"group.orchestratorThinking": "Orchestrator is aan het denken...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autoriseren",
"toolAuth.authorizing": "Bezig met autoriseren...",
"toolAuth.hint": "Zonder autorisatie of configuratie werken Skills mogelijk niet. Dit kan de agent beperken of fouten veroorzaken.",
"toolAuth.remove": "Verwijderen",
"toolAuth.signIn": "Inloggen",
"toolAuth.title": "Autoriseer Skills voor deze agent",
"topic.checkOpenNewTopic": "Nieuw onderwerp starten?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Vaardigheden",
"workingPanel.space": "Ruimte",
"workingPanel.title": "Working Panel",
"you": "Jij",
"zenMode": "Zen-modus"
"you": "Jij"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Alle lopende taken worden weergegeven",
"fleet.backToHome": "Terug naar startpagina",
"fleet.closeColumn": "Kolom sluiten",
"fleet.closeIdleColumns": "Sluit ongebruikte kolommen",
"fleet.closeIdleColumnsCount": "Sluit {{count}} ongebruikte kolommen",
"fleet.collapseReply": "Samenvouwen",
"fleet.createTask": "Taak aanmaken",
"fleet.dragHint": "Sleep om te herschikken",
"fleet.empty": "Geen open taken",
"fleet.emptyDesc": "Selecteer een lopende taak aan de linkerkant, of gebruik + om een kolom toe te voegen.",
"fleet.noRunningTasks": "Geen lopende taken",
"fleet.openInChat": "Openen in chat",
"fleet.pin": "Kolom vastzetten",
"fleet.reply": "Antwoorden",
"fleet.runningTasks": "Lopende taken",
"fleet.rows.one": "Enkele rij",
"fleet.rows.two": "Twee rijen",
"fleet.runningBoard": "Lopend Bord",
"fleet.status.idle": "Inactief",
"fleet.status.paused": "Gepauzeerd",
"fleet.status.running": "Bezig",
"fleet.status.scheduled": "Gepland",
"fleet.tooltip": "Bekijk alle agenten naast elkaar",
"fleet.unpin": "Kolom losmaken",
"gateway.description": "Beschrijving",
"gateway.descriptionPlaceholder": "Optioneel",
"gateway.deviceName": "Apparaatnaam",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "MCP Ontdekken",
"navigation.discoverModels": "Modellen Ontdekken",
"navigation.discoverProviders": "Providers Ontdekken",
"navigation.document": "Document",
"navigation.group": "Groep",
"navigation.groupChat": "Groepschat",
"navigation.home": "Startpagina",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Verlopen",
"credits.packages.tabs.expiredCount": "Verlopen ({{count}})",
"credits.packages.title": "Mijn Tegoedpakketten",
"credits.topUp.bestValue.cta": "Bekijk Ultimate jaarlijks",
"credits.topUp.bestValue.savings": "Bespaar ${{savings}} op deze aankoop",
"credits.topUp.bestValue.title": "{{plan}} jaarlijks biedt het laagste opwaardeertarief: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Annuleren",
"credits.topUp.custom": "Aangepast",
"credits.topUp.freeFeeHint": "Gratis plan-opwaarderingen omvatten een servicekosten van {{fee}} per 1M credits.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Beloningsverwerking: Credits worden binnen 1 uur verdeeld nadat de uitgenodigde een betaling heeft voltooid en verificatie heeft doorstaan.",
"referral.rules.title": "Programmaregels",
"referral.rules.validInvitation": "Geldige uitnodiging: De uitgenodigde registreert zich met jouw uitnodigingscode, voert een geldige actie uit en voltooit een betaling (abonnement of creditaanvulling).",
"referral.rules.validOperation": "Geldige actie: één bericht verzenden of één afbeelding genereren",
"referral.stats.availableBalance": "Beschikbaar Saldo",
"referral.stats.description": "Bekijk je verwijzingsstatistieken",
"referral.stats.title": "Verwijzingsoverzicht",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Gearchiveerd",
"management.status.completed": "Voltooid",
"management.status.failed": "Mislukt",
"management.status.idle": "Inactief",
"management.status.paused": "Gepauzeerd",
"management.status.running": "Lopend",
"management.status.waitingForHuman": "Wacht op invoer",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} mislukte onderwerpen",
"projectStatus.loading_one": "{{count}} onderwerp wordt geladen",
"projectStatus.loading_other": "{{count}} onderwerpen worden geladen",
"projectStatus.unread_one": "{{count}} onderwerp met ongelezen reactie",
"projectStatus.unread_other": "{{count}} onderwerpen met ongelezen reacties",
"projectStatus.waitingForHuman_one": "{{count}} onderwerp wacht op invoer",
"projectStatus.waitingForHuman_other": "{{count}} onderwerpen wachten op invoer",
"renameModal.description": "Houd het kort en gemakkelijk herkenbaar.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Cześć, jestem **{{name}}**. Jedno zdanie wystarczy.\n\nChcesz, żebym lepiej dopasował się do Twojego stylu pracy? Przejdź do [Ustawień Agenta]({{url}}) i uzupełnij Profil Agenta (możesz go edytować w każdej chwili).",
"agentDefaultMessageWithSystemRole": "Cześć, jestem **{{name}}**. Jedno zdanie wystarczy — to Ty masz kontrolę.",
"agentDefaultMessageWithoutEdit": "Cześć, jestem **{{name}}**. Jedno zdanie wystarczy — to Ty masz kontrolę.",
"agentDocument.backToChat": "Powrót do czatu",
"agentDocument.linkCopied": "Link skopiowany",
"agentDocument.openAsPage": "Otwórz jako pełną stronę",
"agentProfile.files_one": "{{count}} plik",
"agentProfile.files_other": "{{count}} pliki",
"agentProfile.knowledgeBases_one": "{{count}} baza wiedzy",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Wyodrębnij treść linku",
"followUpPlaceholder": "Kontynuuj. Użyj @, aby przypisać zadania innym agentom.",
"followUpPlaceholderHeterogeneous": "Kontynuuj.",
"gatewayMode.title": "Tryb bramy",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Tryb bramy agenta",
"gatewayMode.desc": "Uruchamiaj agentów w chmurze za pomocą bramy agenta LobeHub. Zadania będą kontynuowane nawet po zamknięciu strony.",
"group.desc": "Pracuj nad zadaniem z wieloma Agentami w jednej wspólnej przestrzeni.",
"group.memberTooltip": "Grupa zawiera {{count}} członków",
"group.orchestratorThinking": "Orkiestrator myśli...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autoryzuj",
"toolAuth.authorizing": "Autoryzowanie...",
"toolAuth.hint": "Bez autoryzacji lub konfiguracji umiejętności mogą nie działać. Może to ograniczyć agenta lub powodować błędy.",
"toolAuth.remove": "Usuń",
"toolAuth.signIn": "Zaloguj się",
"toolAuth.title": "Autoryzuj umiejętności dla tego agenta",
"topic.checkOpenNewTopic": "Rozpocząć nowy temat?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Umiejętności",
"workingPanel.space": "Przestrzeń",
"workingPanel.title": "Working Panel",
"you": "Ty",
"zenMode": "Tryb Zen"
"you": "Ty"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Wszystkie uruchomione zadania są wyświetlane",
"fleet.backToHome": "Powrót do strony głównej",
"fleet.closeColumn": "Zamknij kolumnę",
"fleet.closeIdleColumns": "Zamknij nieaktywne kolumny",
"fleet.closeIdleColumnsCount": "Zamknij {{count}} nieaktywnych kolumn",
"fleet.collapseReply": "Zwiń",
"fleet.createTask": "Utwórz zadanie",
"fleet.dragHint": "Przeciągnij, aby zmienić kolejność",
"fleet.empty": "Brak otwartych zadań",
"fleet.emptyDesc": "Wybierz uruchomione zadanie po lewej stronie lub użyj +, aby dodać kolumnę.",
"fleet.noRunningTasks": "Brak uruchomionych zadań",
"fleet.openInChat": "Otwórz w czacie",
"fleet.pin": "Przypnij kolumnę",
"fleet.reply": "Odpowiedz",
"fleet.runningTasks": "Uruchomione zadania",
"fleet.rows.one": "Pojedynczy wiersz",
"fleet.rows.two": "Dwa wiersze",
"fleet.runningBoard": "Tablica operacyjna",
"fleet.status.idle": "Bezczynny",
"fleet.status.paused": "Wstrzymany",
"fleet.status.running": "Uruchomiony",
"fleet.status.scheduled": "Zaplanowany",
"fleet.tooltip": "Wyświetl wszystkich agentów obok siebie",
"fleet.unpin": "Odepnij kolumnę",
"gateway.description": "Opis",
"gateway.descriptionPlaceholder": "Opcjonalne",
"gateway.deviceName": "Nazwa urządzenia",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Odkryj MCP",
"navigation.discoverModels": "Odkryj Modele",
"navigation.discoverProviders": "Odkryj Dostawców",
"navigation.document": "Dokument",
"navigation.group": "Grupa",
"navigation.groupChat": "Czat Grupowy",
"navigation.home": "Strona główna",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Wygasłe",
"credits.packages.tabs.expiredCount": "Wygasłe ({{count}})",
"credits.packages.title": "Moje pakiety kredytowe",
"credits.topUp.bestValue.cta": "Zobacz Ultimate roczny",
"credits.topUp.bestValue.savings": "Zaoszczędź ${{savings}} na tym zakupie",
"credits.topUp.bestValue.title": "{{plan}} roczny odblokowuje najniższą stawkę doładowania: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Anuluj",
"credits.topUp.custom": "Niestandardowe",
"credits.topUp.freeFeeHint": "Doładowania w darmowym planie obejmują opłatę serwisową w wysokości {{fee}} za każde 1M kredytów.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Przetwarzanie nagrody: Kredyty zostaną przyznane w ciągu 1 godziny po dokonaniu płatności przez zaproszonego i przejściu weryfikacji",
"referral.rules.title": "Zasady Programu",
"referral.rules.validInvitation": "Ważne zaproszenie: Zaproszony rejestruje się z Twoim kodem polecającym, wykonuje jedno ważne działanie i dokonuje płatności (subskrypcja lub doładowanie kredytów)",
"referral.rules.validOperation": "Kryteria ważnej akcji: wysłanie jednej wiadomości na stronie czatu lub wygenerowanie jednego obrazu na stronie obrazów",
"referral.stats.availableBalance": "Dostępne Środki",
"referral.stats.description": "Zobacz swoje statystyki poleceń",
"referral.stats.title": "Podsumowanie Poleceń",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Zarchiwizowane",
"management.status.completed": "Zakończone",
"management.status.failed": "Niepowodzenie",
"management.status.idle": "Bezczynny",
"management.status.paused": "Wstrzymane",
"management.status.running": "W trakcie",
"management.status.waitingForHuman": "Oczekiwanie na dane",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} nieudane tematy",
"projectStatus.loading_one": "{{count}} ładowany temat",
"projectStatus.loading_other": "{{count}} ładowane tematy",
"projectStatus.unread_one": "{{count}} temat z nieprzeczytaną odpowiedzią",
"projectStatus.unread_other": "{{count}} tematy z nieprzeczytanymi odpowiedziami",
"projectStatus.waitingForHuman_one": "{{count}} temat oczekujący na dane",
"projectStatus.waitingForHuman_other": "{{count}} tematy oczekujące na dane",
"renameModal.description": "Utrzymaj nazwę krótką i łatwą do rozpoznania.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Olá, sou **{{name}}**. Uma frase basta.\n\nQuer que eu me adapte melhor ao seu fluxo de trabalho? Vá para [Configurações do Agente]({{url}}) e preencha o Perfil do Agente (você pode editá-lo a qualquer momento).",
"agentDefaultMessageWithSystemRole": "Olá, sou **{{name}}**. Uma frase basta — você está no controle.",
"agentDefaultMessageWithoutEdit": "Olá, sou **{{name}}**. Uma frase basta — você está no controle.",
"agentDocument.backToChat": "Voltar para o chat",
"agentDocument.linkCopied": "Link copiado",
"agentDocument.openAsPage": "Abrir como página inteira",
"agentProfile.files_one": "{{count}} arquivo",
"agentProfile.files_other": "{{count}} arquivos",
"agentProfile.knowledgeBases_one": "{{count}} base de conhecimento",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Extrair Conteúdo de Links da Web",
"followUpPlaceholder": "Acompanhar. Use @ para atribuir tarefas a outros agentes.",
"followUpPlaceholderHeterogeneous": "Continuar.",
"gatewayMode.title": "Modo Gateway",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Modo de Gateway do Agente",
"gatewayMode.desc": "Execute agentes na nuvem através do Gateway do Agente do LobeHub. As tarefas continuam sendo executadas mesmo após você fechar a página.",
"group.desc": "Avance em uma tarefa com vários Agentes em um espaço compartilhado.",
"group.memberTooltip": "Há {{count}} membros no grupo",
"group.orchestratorThinking": "Orquestrador está pensando...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Autorizar",
"toolAuth.authorizing": "Autorizando...",
"toolAuth.hint": "Sem autorização ou configuração, as Habilidades podem não funcionar. Isso pode limitar o Agente ou causar erros.",
"toolAuth.remove": "Remover",
"toolAuth.signIn": "Entrar",
"toolAuth.title": "Autorizar Habilidades para este Agente",
"topic.checkOpenNewTopic": "Iniciar um novo tópico?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Habilidades",
"workingPanel.space": "Espaço",
"workingPanel.title": "Working Panel",
"you": "Você",
"zenMode": "Modo Zen"
"you": "Você"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Todas as tarefas em execução estão exibidas",
"fleet.backToHome": "Voltar para a página inicial",
"fleet.closeColumn": "Fechar coluna",
"fleet.closeIdleColumns": "Fechar colunas inativas",
"fleet.closeIdleColumnsCount": "Fechar {{count}} colunas inativas",
"fleet.collapseReply": "Recolher",
"fleet.createTask": "Criar tarefa",
"fleet.dragHint": "Arraste para reordenar",
"fleet.empty": "Nenhuma tarefa aberta",
"fleet.emptyDesc": "Selecione uma tarefa em execução à esquerda ou use + para adicionar uma coluna.",
"fleet.noRunningTasks": "Nenhuma tarefa em execução",
"fleet.openInChat": "Abrir no chat",
"fleet.pin": "Fixar coluna",
"fleet.reply": "Responder",
"fleet.runningTasks": "Tarefas em execução",
"fleet.rows.one": "Uma linha",
"fleet.rows.two": "Duas linhas",
"fleet.runningBoard": "Painel em execução",
"fleet.status.idle": "Inativo",
"fleet.status.paused": "Pausado",
"fleet.status.running": "Em execução",
"fleet.status.scheduled": "Agendado",
"fleet.tooltip": "Visualizar todos os agentes lado a lado",
"fleet.unpin": "Desafixar coluna",
"gateway.description": "Descrição",
"gateway.descriptionPlaceholder": "Opcional",
"gateway.deviceName": "Nome do Dispositivo",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Descobrir MCP",
"navigation.discoverModels": "Descobrir Modelos",
"navigation.discoverProviders": "Descobrir Provedores",
"navigation.document": "Documento",
"navigation.group": "Grupo",
"navigation.groupChat": "Chat em Grupo",
"navigation.home": "Início",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Expirados",
"credits.packages.tabs.expiredCount": "Expirados ({{count}})",
"credits.packages.title": "Meus Pacotes de Créditos",
"credits.topUp.bestValue.cta": "Ver Ultimate anual",
"credits.topUp.bestValue.savings": "Economize ${{savings}} nesta compra",
"credits.topUp.bestValue.title": "{{plan}} anual desbloqueia a menor taxa de recarga: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "Cancelar",
"credits.topUp.custom": "Personalizado",
"credits.topUp.freeFeeHint": "Recargas do plano gratuito incluem uma taxa de serviço de {{fee}} por 1M de créditos.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Processamento de recompensas: Créditos serão distribuídos dentro de 1 hora após o convidado completar um pagamento e passar pela verificação",
"referral.rules.title": "Regras do Programa",
"referral.rules.validInvitation": "Convite válido: O convidado se registra com seu código de indicação, realiza uma ação válida e completa um pagamento (assinatura ou recarga de créditos)",
"referral.rules.validOperation": "Critérios de ação válida: Enviar uma mensagem na página de chat ou gerar uma imagem",
"referral.stats.availableBalance": "Saldo Disponível",
"referral.stats.description": "Veja suas estatísticas de indicação",
"referral.stats.title": "Visão Geral das Indicações",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Arquivado",
"management.status.completed": "Concluído",
"management.status.failed": "Falhou",
"management.status.idle": "Inativo",
"management.status.paused": "Pausado",
"management.status.running": "Em execução",
"management.status.waitingForHuman": "Aguardando entrada",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} tópicos falharam",
"projectStatus.loading_one": "{{count}} tópico carregando",
"projectStatus.loading_other": "{{count}} tópicos carregando",
"projectStatus.unread_one": "{{count}} tópico com resposta não lida",
"projectStatus.unread_other": "{{count}} tópicos com respostas não lidas",
"projectStatus.waitingForHuman_one": "{{count}} tópico aguardando entrada",
"projectStatus.waitingForHuman_other": "{{count}} tópicos aguardando entrada",
"renameModal.description": "Mantenha curto e fácil de reconhecer.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Привет, я **{{name}}**. Одного предложения достаточно.\n\nХотите, чтобы я лучше соответствовал вашему рабочему процессу? Перейдите в [Настройки агента]({{url}}) и заполните профиль агента (вы можете изменить его в любое время).",
"agentDefaultMessageWithSystemRole": "Привет, я **{{name}}**. Одного предложения достаточно — вы управляете процессом.",
"agentDefaultMessageWithoutEdit": "Привет, я **{{name}}**. Одного предложения достаточно — вы управляете процессом.",
"agentDocument.backToChat": "Вернуться в чат",
"agentDocument.linkCopied": "Ссылка скопирована",
"agentDocument.openAsPage": "Открыть на всю страницу",
"agentProfile.files_one": "{{count}} файл",
"agentProfile.files_other": "{{count}} файлов",
"agentProfile.knowledgeBases_one": "{{count}} база знаний",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Извлекать содержимое веб-ссылок",
"followUpPlaceholder": "Продолжение. Используйте @, чтобы назначать задачи другим агентам.",
"followUpPlaceholderHeterogeneous": "Продолжить.",
"gatewayMode.title": "Режим шлюза",
"gatewayMode.beta": "Бета",
"gatewayMode.cardTitle": "Режим шлюза агента",
"gatewayMode.desc": "Запускайте агентов в облаке через шлюз агентов LobeHub. Задачи продолжают выполняться даже после закрытия страницы.",
"group.desc": "Продвигайте задачу с помощью нескольких агентов в общем пространстве.",
"group.memberTooltip": "В группе {{count}} участников",
"group.orchestratorThinking": "Оркестратор обдумывает...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Авторизовать",
"toolAuth.authorizing": "Авторизация...",
"toolAuth.hint": "Без авторизации или настройки навыки могут не работать. Это может ограничить агента или вызвать ошибки.",
"toolAuth.remove": "Удалить",
"toolAuth.signIn": "Войти",
"toolAuth.title": "Авторизация навыков для этого агента",
"topic.checkOpenNewTopic": "Начать новую тему?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Навыки",
"workingPanel.space": "Пространство",
"workingPanel.title": "Working Panel",
"you": "Вы",
"zenMode": "Режим дзен"
"you": "Вы"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Показаны все выполняемые задачи",
"fleet.backToHome": "Вернуться на главную",
"fleet.closeColumn": "Закрыть колонку",
"fleet.closeIdleColumns": "Закрыть неактивные колонки",
"fleet.closeIdleColumnsCount": "Закрыть {{count}} неактивных колонок",
"fleet.collapseReply": "Свернуть",
"fleet.createTask": "Создать задачу",
"fleet.dragHint": "Перетащите для изменения порядка",
"fleet.empty": "Нет открытых задач",
"fleet.emptyDesc": "Выберите выполняемую задачу слева или используйте +, чтобы добавить колонку.",
"fleet.noRunningTasks": "Нет выполняемых задач",
"fleet.openInChat": "Открыть в чате",
"fleet.pin": "Закрепить колонку",
"fleet.reply": "Ответить",
"fleet.runningTasks": "Выполняемые задачи",
"fleet.rows.one": "Один ряд",
"fleet.rows.two": "Два ряда",
"fleet.runningBoard": "Рабочая панель",
"fleet.status.idle": "Ожидание",
"fleet.status.paused": "Приостановлено",
"fleet.status.running": "Выполняется",
"fleet.status.scheduled": "Запланировано",
"fleet.tooltip": "Просмотреть всех агентов рядом друг с другом",
"fleet.unpin": "Открепить колонку",
"gateway.description": "Описание",
"gateway.descriptionPlaceholder": "Необязательно",
"gateway.deviceName": "Имя устройства",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Открыть MCP",
"navigation.discoverModels": "Открыть Модели",
"navigation.discoverProviders": "Открыть Провайдеров",
"navigation.document": "Документ",
"navigation.group": "Группа",
"navigation.groupChat": "Групповой Чат",
"navigation.home": "Главная",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Истёкшие",
"credits.packages.tabs.expiredCount": "Истёкшие ({{count}})",
"credits.packages.title": "Мои пакеты кредитов",
"credits.topUp.bestValue.cta": "Посмотреть Ultimate годовой",
"credits.topUp.bestValue.savings": "Сэкономьте ${{savings}} на этой покупке",
"credits.topUp.bestValue.title": "{{plan}} годовой предоставляет самую низкую ставку пополнения: ${{price}} / 1М {{creditLabel}}",
"credits.topUp.cancel": "Отмена",
"credits.topUp.custom": "Свой вариант",
"credits.topUp.freeFeeHint": "Пополнения для бесплатного плана включают комиссию за обслуживание в размере {{fee}} за 1M кредитов.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Обработка награды: Кредиты будут распределены в течение 1 часа после того, как приглашенный завершит платеж и пройдет проверку.",
"referral.rules.title": "Правила программы",
"referral.rules.validInvitation": "Действительное приглашение: Приглашенный регистрируется с вашим реферальным кодом, выполняет одно действительное действие и завершает платеж (подписка или пополнение кредитов).",
"referral.rules.validOperation": "Критерии действия: отправка сообщения или генерация изображения",
"referral.stats.availableBalance": "Доступный баланс",
"referral.stats.description": "Просмотр статистики приглашений",
"referral.stats.title": "Обзор приглашений",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Архивированные",
"management.status.completed": "Завершенные",
"management.status.failed": "Неудачные",
"management.status.idle": "Бездействие",
"management.status.paused": "Приостановленные",
"management.status.running": "Выполняются",
"management.status.waitingForHuman": "Ожидание ввода",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} неудачных тем",
"projectStatus.loading_one": "{{count}} загружаемая тема",
"projectStatus.loading_other": "{{count}} загружаемых тем",
"projectStatus.unread_one": "{{count}} тема с непрочитанным ответом",
"projectStatus.unread_other": "{{count}} тем с непрочитанными ответами",
"projectStatus.waitingForHuman_one": "{{count}} тема ожидает ввода",
"projectStatus.waitingForHuman_other": "{{count}} тем ожидают ввода",
"renameModal.description": "Сделайте название коротким и легко узнаваемым.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Merhaba, ben **{{name}}**. Bir cümle yeterli.\n\nİş akışına daha iyi uyum sağlamamı ister misin? [Ajan Ayarları]({{url}}) bölümüne gidip Ajan Profilini doldur (istediğin zaman düzenleyebilirsin).",
"agentDefaultMessageWithSystemRole": "Merhaba, ben **{{name}}**. Bir cümle yeterli—kontrol sende.",
"agentDefaultMessageWithoutEdit": "Merhaba, ben **{{name}}**. Bir cümle yeterli—kontrol sende.",
"agentDocument.backToChat": "Sohbete geri dön",
"agentDocument.linkCopied": "Bağlantı kopyalandı",
"agentDocument.openAsPage": "Tam sayfa olarak aç",
"agentProfile.files_one": "{{count}} dosya",
"agentProfile.files_other": "{{count}} dosya",
"agentProfile.knowledgeBases_one": "{{count}} bilgi tabanı",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Web Sayfası Bağlantı İçeriğini Çıkar",
"followUpPlaceholder": "Takip edin. Diğer ajanlara görev atamak için @ kullanın.",
"followUpPlaceholderHeterogeneous": "Devam edin.",
"gatewayMode.title": "Ağ Geçidi Modu",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Aracı Geçit Modu",
"gatewayMode.desc": "LobeHub'un Aracı Geçidi üzerinden bulutta aracılar çalıştırın. Sayfayı kapattıktan sonra bile görevler çalışmaya devam eder.",
"group.desc": "Birden fazla Ajanla ortak bir alanda görevleri ilerletin.",
"group.memberTooltip": "Grupta {{count}} üye var",
"group.orchestratorThinking": "Yönlendirici düşünüyor...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Yetkilendir",
"toolAuth.authorizing": "Yetkilendiriliyor...",
"toolAuth.hint": "Yetkilendirme veya yapılandırma olmadan, Yetenekler çalışmayabilir. Bu, Ajan'ı sınırlayabilir veya hatalara neden olabilir.",
"toolAuth.remove": "Kaldır",
"toolAuth.signIn": "Giriş Yap",
"toolAuth.title": "Bu Ajan için Yetenekleri Yetkilendir",
"topic.checkOpenNewTopic": "Yeni bir konu başlatılsın mı?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Beceriler",
"workingPanel.space": "Boşluk",
"workingPanel.title": "Working Panel",
"you": "Sen",
"zenMode": "Zen Modu"
"you": "Sen"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Tüm çalışan görevler gösteriliyor",
"fleet.backToHome": "Ana sayfaya dön",
"fleet.closeColumn": "Sütunu kapat",
"fleet.closeIdleColumns": "Boşta olan sütunları kapat",
"fleet.closeIdleColumnsCount": "{{count}} boşta olan sütunu kapat",
"fleet.collapseReply": "Daralt",
"fleet.createTask": "Görev oluştur",
"fleet.dragHint": "Yeniden sıralamak için sürükleyin",
"fleet.empty": "Açık görev yok",
"fleet.emptyDesc": "Soldaki çalışan bir görevi seçin veya sütun eklemek için + kullanın.",
"fleet.noRunningTasks": "Çalışan görev yok",
"fleet.openInChat": "Sohbette aç",
"fleet.pin": "Sütunu sabitle",
"fleet.reply": "Yanıtla",
"fleet.runningTasks": "Çalışan Görevler",
"fleet.rows.one": "Tek satır",
"fleet.rows.two": "İki satır",
"fleet.runningBoard": "Çalışma Panosu",
"fleet.status.idle": "Boşta",
"fleet.status.paused": "Duraklatıldı",
"fleet.status.running": "Çalışıyor",
"fleet.status.scheduled": "Planlandı",
"fleet.tooltip": "Tüm ajanları yan yana görüntüle",
"fleet.unpin": "Sütunun sabitlemesini kaldır",
"gateway.description": "Açıklama",
"gateway.descriptionPlaceholder": "Opsiyonel",
"gateway.deviceName": "Cihaz Adı",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "MCP'yi Keşfet",
"navigation.discoverModels": "Modelleri Keşfet",
"navigation.discoverProviders": "Sağlayıcıları Keşfet",
"navigation.document": "Belge",
"navigation.group": "Grup",
"navigation.groupChat": "Grup Sohbeti",
"navigation.home": "Ana Sayfa",
+3 -1
View File
@@ -89,6 +89,9 @@
"credits.packages.tabs.expired": "Süresi Dolmuş",
"credits.packages.tabs.expiredCount": "Süresi Dolmuş ({{count}})",
"credits.packages.title": "Kredi Paketlerim",
"credits.topUp.bestValue.cta": "En İyi Yıllık Görüntüle",
"credits.topUp.bestValue.savings": "Bu satın alımda ${{savings}} tasarruf edin",
"credits.topUp.bestValue.title": "{{plan}} yıllık en düşük doldurma oranını açar: ${{price}} / 1M {{creditLabel}}",
"credits.topUp.cancel": "İptal Et",
"credits.topUp.custom": "Özel",
"credits.topUp.freeFeeHint": "Ücretsiz plan yüklemelerinde her 1M kredi için {{fee}} hizmet ücreti uygulanır.",
@@ -417,7 +420,6 @@
"referral.rules.rewardDelay": "Ödül işleme: Davet edilen kişi bir ödeme yaptıktan ve doğrulamadan geçtikten sonra krediler 1 saat içinde dağıtılacaktır.",
"referral.rules.title": "Program Kuralları",
"referral.rules.validInvitation": "Geçerli davet: Davet edilen kişi, davet kodunuzla kaydolur, bir geçerli işlem yapar ve bir ödeme (abonelik veya kredi doldurma) tamamlar.",
"referral.rules.validOperation": "Geçerli işlem kriteri: Sohbet sayfasında bir mesaj gönderme veya görsel sayfasında bir görsel oluşturma",
"referral.stats.availableBalance": "Kullanılabilir Bakiye",
"referral.stats.description": "Referans istatistiklerinizi görüntüleyin",
"referral.stats.title": "Referans Genel Bakış",
+3
View File
@@ -143,6 +143,7 @@
"management.status.archived": "Arşivlenmiş",
"management.status.completed": "Tamamlanmış",
"management.status.failed": "Başarısız",
"management.status.idle": "Boşta",
"management.status.paused": "Durduruldu",
"management.status.running": "Çalışıyor",
"management.status.waitingForHuman": "Girdi bekleniyor",
@@ -154,6 +155,8 @@
"projectStatus.failed_other": "{{count}} başarısız konu",
"projectStatus.loading_one": "{{count}} yükleniyor konu",
"projectStatus.loading_other": "{{count}} yükleniyor konu",
"projectStatus.unread_one": "{{count}} konu okunmamış yanıt içeriyor",
"projectStatus.unread_other": "{{count}} konu okunmamış yanıtlar içeriyor",
"projectStatus.waitingForHuman_one": "{{count}} konu yanıt bekliyor",
"projectStatus.waitingForHuman_other": "{{count}} konu yanıt bekliyor",
"renameModal.description": "Kısa ve kolay tanınabilir olsun.",
+8 -3
View File
@@ -20,6 +20,9 @@
"agentDefaultMessage": "Chào bạn, tôi là **{{name}}**. Một câu là đủ.\n\nMuốn tôi phù hợp hơn với quy trình làm việc của bạn? Truy cập [Cài đặt Tác nhân]({{url}}) và điền Hồ sơ Tác nhân (bạn có thể chỉnh sửa bất cứ lúc nào).",
"agentDefaultMessageWithSystemRole": "Chào bạn, tôi là **{{name}}**. Một câu là đủ—bạn là người kiểm soát.",
"agentDefaultMessageWithoutEdit": "Chào bạn, tôi là **{{name}}**. Một câu là đủ—bạn là người kiểm soát.",
"agentDocument.backToChat": "Quay lại trò chuyện",
"agentDocument.linkCopied": "Liên kết đã được sao chép",
"agentDocument.openAsPage": "Mở dưới dạng trang đầy đủ",
"agentProfile.files_one": "{{count}} tệp",
"agentProfile.files_other": "{{count}} tệp",
"agentProfile.knowledgeBases_one": "{{count}} cơ sở tri thức",
@@ -165,7 +168,9 @@
"extendParams.urlContext.title": "Trích xuất nội dung liên kết web",
"followUpPlaceholder": "Theo dõi. Dùng @ để giao nhiệm vụ cho tác vụ khác.",
"followUpPlaceholderHeterogeneous": "Tiếp tục.",
"gatewayMode.title": "Chế độ Gateway",
"gatewayMode.beta": "Beta",
"gatewayMode.cardTitle": "Chế độ Cổng Đại lý",
"gatewayMode.desc": "Chạy các đại lý trên đám mây thông qua Cổng Đại lý của LobeHub. Nhiệm vụ vẫn tiếp tục chạy ngay cả khi bạn đóng trang.",
"group.desc": "Thực hiện nhiệm vụ với nhiều Tác nhân trong một không gian chung.",
"group.memberTooltip": "Có {{count}} thành viên trong nhóm",
"group.orchestratorThinking": "Điều phối viên đang suy nghĩ...",
@@ -877,6 +882,7 @@
"toolAuth.authorize": "Ủy quyền",
"toolAuth.authorizing": "Đang ủy quyền...",
"toolAuth.hint": "Nếu không có ủy quyền hoặc cấu hình, kỹ năng có thể không hoạt động. Điều này có thể giới hạn tác nhân hoặc gây lỗi.",
"toolAuth.remove": "Xóa",
"toolAuth.signIn": "Đăng nhập",
"toolAuth.title": "Ủy quyền kỹ năng cho tác nhân này",
"topic.checkOpenNewTopic": "Bắt đầu chủ đề mới?",
@@ -1118,6 +1124,5 @@
"workingPanel.skills.title": "Kỹ năng",
"workingPanel.space": "Khoảng trống",
"workingPanel.title": "Working Panel",
"you": "Bạn",
"zenMode": "Chế độ tập trung"
"you": "Bạn"
}
+10 -1
View File
@@ -3,18 +3,26 @@
"fleet.allShown": "Tất cả các nhiệm vụ đang chạy đều được hiển thị",
"fleet.backToHome": "Quay về trang chủ",
"fleet.closeColumn": "Đóng cột",
"fleet.closeIdleColumns": "Đóng các cột không hoạt động",
"fleet.closeIdleColumnsCount": "Đóng {{count}} cột không hoạt động",
"fleet.collapseReply": "Thu gọn",
"fleet.createTask": "Tạo nhiệm vụ",
"fleet.dragHint": "Kéo để sắp xếp lại",
"fleet.empty": "Không có nhiệm vụ nào đang mở",
"fleet.emptyDesc": "Chọn một nhiệm vụ đang chạy ở bên trái, hoặc sử dụng + để thêm cột.",
"fleet.noRunningTasks": "Không có nhiệm vụ nào đang chạy",
"fleet.openInChat": "Mở trong trò chuyện",
"fleet.pin": "Ghim cột",
"fleet.reply": "Trả lời",
"fleet.runningTasks": "Nhiệm vụ đang chạy",
"fleet.rows.one": "Một hàng",
"fleet.rows.two": "Hai hàng",
"fleet.runningBoard": "Bảng chạy",
"fleet.status.idle": "Nhàn rỗi",
"fleet.status.paused": "Tạm dừng",
"fleet.status.running": "Đang chạy",
"fleet.status.scheduled": "Đã lên lịch",
"fleet.tooltip": "Xem tất cả các tác nhân cạnh nhau",
"fleet.unpin": "Bỏ ghim cột",
"gateway.description": "Mô tả",
"gateway.descriptionPlaceholder": "Không bắt buộc",
"gateway.deviceName": "Tên thiết bị",
@@ -30,6 +38,7 @@
"navigation.discoverMcp": "Khám phá MCP",
"navigation.discoverModels": "Khám phá Mô hình",
"navigation.discoverProviders": "Khám phá Nhà cung cấp",
"navigation.document": "Tài liệu",
"navigation.group": "Nhóm",
"navigation.groupChat": "Trò chuyện Nhóm",
"navigation.home": "Trang chủ",

Some files were not shown because too many files have changed in this diff Show More