mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-15 12:10:16 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ef7e7e84e | |||
| aaa8de0254 | |||
| 644d1b6788 | |||
| c4f7995863 |
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: add-provider-doc
|
||||
description: Guide for adding new AI provider documentation. Use when adding documentation for a new AI provider (like OpenAI, Anthropic, etc.), including usage docs, environment variables, Docker config, and image resources. Triggers on provider documentation tasks.
|
||||
disable-model-invocation: true
|
||||
argument-hint: '[provider-name]'
|
||||
---
|
||||
|
||||
# Adding New AI Provider Documentation
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: add-setting-env
|
||||
description: Guide for adding environment variables to configure user settings. Use when implementing server-side environment variables that control default values for user settings. Triggers on env var configuration or setting default value tasks.
|
||||
disable-model-invocation: true
|
||||
argument-hint: '[setting-name]'
|
||||
---
|
||||
|
||||
# Adding Environment Variable for User Settings
|
||||
|
||||
@@ -19,11 +19,11 @@ A builtin tool is a package the agent runtime can call. It ships **five faces**:
|
||||
|
||||
## Read These First
|
||||
|
||||
| Question | Doc |
|
||||
| ------------------------------------------------------------------------------------ | --------------------------------------------- |
|
||||
| Where do files live? What does each face do? Wiring? | [architecture.md](references/architecture.md) |
|
||||
| How do I name the tool, design APIs, write the manifest, executor, ExecutionRuntime? | [tool-design.md](references/tool-design.md) |
|
||||
| How do I build Inspector / Render / Placeholder / Streaming / Intervention / Portal? | [ui.md](references/ui.md) |
|
||||
| Question | Doc |
|
||||
| ------------------------------------------------------------------------------------ | ---------------------------------- |
|
||||
| Where do files live? What does each face do? Wiring? | [architecture.md](architecture.md) |
|
||||
| How do I name the tool, design APIs, write the manifest, executor, ExecutionRuntime? | [tool-design.md](tool-design.md) |
|
||||
| How do I build Inspector / Render / Placeholder / Streaming / Intervention / Portal? | [ui.md](ui.md) |
|
||||
|
||||
---
|
||||
|
||||
@@ -109,7 +109,7 @@ Before opening the PR:
|
||||
- [ ] Placeholder added if the API has a perceivable execution lag (search, list, crawl).
|
||||
- [ ] Streaming added for APIs that emit incremental output (run command, write file, code execution).
|
||||
- [ ] Intervention added if `humanIntervention` is set in the manifest.
|
||||
- [ ] All registry files updated (see [architecture.md → Registry wiring](references/architecture.md#registry-wiring)).
|
||||
- [ ] All registry files updated (see [architecture.md → Registry wiring](architecture.md#registry-wiring)).
|
||||
- [ ] i18n keys in `src/locales/default/plugin.ts` plus dev seeds in `en-US`/`zh-CN`.
|
||||
- [ ] `bunx vitest run --silent='passed-only' 'packages/builtin-tool-<name>'` passes.
|
||||
- [ ] `bun run type-check` passes.
|
||||
|
||||
+1
-1
@@ -213,7 +213,7 @@ The runtime hands every executor method an optional `BuiltinToolContext` as the
|
||||
| `operationId` | Operation lineage (use for cancellation, tracing) |
|
||||
| `scope` | `'task' \| 'agent' \| …` — toggles default behaviors |
|
||||
| `signal: AbortSignal` | Honor for long-running ops |
|
||||
| `stepContext` | Cross-message runtime state (lobe-agent todos, etc.) |
|
||||
| `stepContext` | Cross-message runtime state (GTD todos, etc.) |
|
||||
| `registerAfterCompletion(cb)` | Defer side-effects past message-update race |
|
||||
| `groupOrchestration` | Group orchestration callbacks |
|
||||
|
||||
@@ -8,7 +8,6 @@ description: >
|
||||
(4) Send interactive cards or stream AI responses to chat platforms.
|
||||
Triggers on "chat sdk", "chat bot", "slack bot", "teams bot", "discord bot", "@chat-adapter",
|
||||
building bots that work across multiple chat platforms.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Chat SDK
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,244 +0,0 @@
|
||||
# Walkthrough: Adding a New Feature End-to-End
|
||||
|
||||
This is a worked example of the canonical 6-step recipe applied to a new entity (`Dataset`), showing a variant of the main skill's pattern: **a list keyed by a parent id** (`datasetMap[benchmarkId]`), useful when the same shape appears under different parents.
|
||||
|
||||
If you only need the canonical (single-array) pattern, the main `SKILL.md` already shows it for `Benchmark`. Read this file when you need the parent-keyed Map variant, or when you want a checklist-style walkthrough.
|
||||
|
||||
## Step 1: Add Service methods
|
||||
|
||||
```typescript
|
||||
class AgentEvalService {
|
||||
async listDatasets(benchmarkId: string) {
|
||||
return lambdaClient.agentEval.listDatasets.query({ benchmarkId });
|
||||
}
|
||||
async getDataset(id: string) {
|
||||
return lambdaClient.agentEval.getDataset.query({ id });
|
||||
}
|
||||
async createDataset(params: CreateDatasetParams) {
|
||||
return lambdaClient.agentEval.createDataset.mutate(params);
|
||||
}
|
||||
// updateDataset / deleteDataset follow the same shape
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Reducer (optimistic updates)
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/dataset/reducer.ts
|
||||
export type DatasetDispatch =
|
||||
| { type: 'addDataset'; value: Dataset }
|
||||
| { type: 'updateDataset'; id: string; value: Partial<Dataset> }
|
||||
| { type: 'deleteDataset'; id: string };
|
||||
|
||||
export const datasetReducer = (state: Dataset[] = [], payload: DatasetDispatch): Dataset[] =>
|
||||
produce(state, (draft) => {
|
||||
switch (payload.type) {
|
||||
case 'addDataset':
|
||||
draft.unshift(payload.value);
|
||||
break;
|
||||
case 'updateDataset': {
|
||||
const i = draft.findIndex((item) => item.id === payload.id);
|
||||
if (i !== -1) draft[i] = { ...draft[i], ...payload.value };
|
||||
break;
|
||||
}
|
||||
case 'deleteDataset': {
|
||||
const i = draft.findIndex((item) => item.id === payload.id);
|
||||
if (i !== -1) draft.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Step 3: Store slice
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/dataset/initialState.ts
|
||||
export interface DatasetData {
|
||||
currentPage: number;
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
items: Dataset[];
|
||||
pageSize: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface DatasetSliceState {
|
||||
// Map keyed by benchmarkId — multiple parent contexts share the slice
|
||||
datasetMap: Record<string, DatasetData>;
|
||||
// Single item for modal display
|
||||
datasetDetail: Dataset | null;
|
||||
isLoadingDatasetDetail: boolean;
|
||||
loadingDatasetIds: string[];
|
||||
}
|
||||
|
||||
export const datasetInitialState: DatasetSliceState = {
|
||||
datasetMap: {},
|
||||
datasetDetail: null,
|
||||
isLoadingDatasetDetail: false,
|
||||
loadingDatasetIds: [],
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/dataset/action.ts
|
||||
const FETCH_DATASETS_KEY = 'FETCH_DATASETS';
|
||||
const FETCH_DATASET_DETAIL_KEY = 'FETCH_DATASET_DETAIL';
|
||||
|
||||
export const createDatasetSlice: StateCreator<EvalStore, any, [], DatasetAction> = (set, get) => ({
|
||||
// Cache key includes benchmarkId so each parent has its own SWR entry
|
||||
useFetchDatasets: (benchmarkId) =>
|
||||
useClientDataSWR(
|
||||
benchmarkId ? [FETCH_DATASETS_KEY, benchmarkId] : null,
|
||||
() => agentEvalService.listDatasets(benchmarkId!),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
set({
|
||||
datasetMap: {
|
||||
...get().datasetMap,
|
||||
[benchmarkId!]: {
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
items: data,
|
||||
pageSize: data.length,
|
||||
total: data.length,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
useFetchDatasetDetail: (id) =>
|
||||
useClientDataSWR(
|
||||
id ? [FETCH_DATASET_DETAIL_KEY, id] : null,
|
||||
() => agentEvalService.getDataset(id!),
|
||||
{
|
||||
onSuccess: (data) => set({ datasetDetail: data, isLoadingDatasetDetail: false }),
|
||||
},
|
||||
),
|
||||
|
||||
refreshDatasets: (benchmarkId) => mutate([FETCH_DATASETS_KEY, benchmarkId]),
|
||||
refreshDatasetDetail: (id) => mutate([FETCH_DATASET_DETAIL_KEY, id]),
|
||||
|
||||
// CREATE with optimistic update — note the temp id pattern
|
||||
createDataset: async (params) => {
|
||||
const tmpId = Date.now().toString();
|
||||
const { benchmarkId } = params;
|
||||
|
||||
get().internal_dispatchDataset(
|
||||
{ type: 'addDataset', value: { ...params, id: tmpId, createdAt: Date.now() } as any },
|
||||
benchmarkId,
|
||||
);
|
||||
get().internal_updateDatasetLoading(tmpId, true);
|
||||
|
||||
try {
|
||||
const result = await agentEvalService.createDataset(params);
|
||||
await get().refreshDatasets(benchmarkId);
|
||||
return result;
|
||||
} finally {
|
||||
get().internal_updateDatasetLoading(tmpId, false);
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE / DELETE follow the same optimistic + refresh pattern as BenchmarkSlice
|
||||
// (see the main SKILL.md)
|
||||
|
||||
// Internal — dispatch reducer scoped to a parent
|
||||
internal_dispatchDataset: (payload, benchmarkId) => {
|
||||
const currentData = get().datasetMap[benchmarkId];
|
||||
const nextItems = datasetReducer(currentData?.items, payload);
|
||||
|
||||
// Skip set when nothing changed — avoids unnecessary re-renders
|
||||
if (isEqual(nextItems, currentData?.items)) return;
|
||||
|
||||
set({
|
||||
datasetMap: {
|
||||
...get().datasetMap,
|
||||
[benchmarkId]: {
|
||||
...currentData,
|
||||
currentPage: currentData?.currentPage ?? 1,
|
||||
hasMore: currentData?.hasMore ?? false,
|
||||
isLoading: false,
|
||||
items: nextItems,
|
||||
pageSize: currentData?.pageSize ?? nextItems.length,
|
||||
total: currentData?.total ?? nextItems.length,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
internal_updateDatasetLoading: (id, loading) => {
|
||||
set((state) => ({
|
||||
loadingDatasetIds: loading
|
||||
? [...state.loadingDatasetIds, id]
|
||||
: state.loadingDatasetIds.filter((i) => i !== id),
|
||||
}));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Step 4: Wire into the store
|
||||
|
||||
```typescript
|
||||
// src/store/eval/store.ts
|
||||
export type EvalStore = EvalStoreState & BenchmarkAction & DatasetAction & RunAction;
|
||||
|
||||
const createStore: StateCreator<EvalStore, [['zustand/devtools', never]]> = (set, get, store) => ({
|
||||
...initialState,
|
||||
...createBenchmarkSlice(set, get, store),
|
||||
...createDatasetSlice(set, get, store),
|
||||
...createRunSlice(set, get, store),
|
||||
});
|
||||
|
||||
// src/store/eval/initialState.ts
|
||||
export const initialState: EvalStoreState = {
|
||||
...benchmarkInitialState,
|
||||
...datasetInitialState,
|
||||
...runInitialState,
|
||||
};
|
||||
```
|
||||
|
||||
## Step 5: Selectors (optional but recommended)
|
||||
|
||||
```typescript
|
||||
export const datasetSelectors = {
|
||||
getDatasetData: (benchmarkId: string) => (s: EvalStore) => s.datasetMap[benchmarkId],
|
||||
getDatasets: (benchmarkId: string) => (s: EvalStore) => s.datasetMap[benchmarkId]?.items ?? [],
|
||||
isLoadingDataset: (id: string) => (s: EvalStore) => s.loadingDatasetIds.includes(id),
|
||||
};
|
||||
```
|
||||
|
||||
## Step 6: Use in component
|
||||
|
||||
```tsx
|
||||
// List scoped to a parent
|
||||
const DatasetList = ({ benchmarkId }: { benchmarkId: string }) => {
|
||||
const useFetchDatasets = useEvalStore((s) => s.useFetchDatasets);
|
||||
const datasets = useEvalStore(datasetSelectors.getDatasets(benchmarkId));
|
||||
const datasetData = useEvalStore(datasetSelectors.getDatasetData(benchmarkId));
|
||||
|
||||
useFetchDatasets(benchmarkId);
|
||||
|
||||
if (datasetData?.isLoading) return <Loading />;
|
||||
return (
|
||||
<div>
|
||||
<h2>Total: {datasetData?.total ?? 0}</h2>
|
||||
<List data={datasets} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Single item for modal — conditional fetching pattern
|
||||
const DatasetImportModal = ({ open, datasetId }: Props) => {
|
||||
const useFetchDatasetDetail = useEvalStore((s) => s.useFetchDatasetDetail);
|
||||
const dataset = useEvalStore((s) => s.datasetDetail);
|
||||
const isLoading = useEvalStore((s) => s.isLoadingDatasetDetail);
|
||||
|
||||
// Only fetch when modal is open AND id present
|
||||
useFetchDatasetDetail(open && datasetId ? datasetId : undefined);
|
||||
|
||||
return <Modal open={open}>{isLoading ? <Loading /> : <div>{dataset?.name}</div>}</Modal>;
|
||||
};
|
||||
```
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: db-migrations
|
||||
description: 'Use when generating or regenerating Drizzle migration files, changing database schema tables or columns, resolving migration sequence conflicts after rebase, reviewing migration SQL for idempotent patterns, or renaming migration files.'
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Database Migrations Guide
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: debug-package
|
||||
description: "Guide for the `debug` npm package and LobeHub log namespaces (lobe-server:*, lobe-desktop:*, lobe-client:*, lobe-*-router:*). Use whenever adding a `debug(...)` logger, picking a namespace for new server/desktop/client/router code, troubleshooting why DEBUG=lobe-* logs don't show up, or when the user asks to 'add logging', 'add a logger', 'instrument this', 'trace this call', 'why isn't my log printing', or mentions `debug(`, `DEBUG=`, `localStorage.debug`, or log format specifiers like %O / %o / %s / %d in a LobeHub codebase."
|
||||
name: debug
|
||||
description: Debug package usage guide. Use when adding debug logging, understanding log namespaces, or implementing debugging features. Triggers on debug logging requests or logging implementation.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: drizzle
|
||||
description: "Drizzle ORM schema authoring and query style for LobeHub (postgres, strict mode). Use when editing anything under `src/database/schemas/`, defining `pgTable` columns/indexes/junction tables, spreading `...timestamps`, generating `createInsertSchema`/`$inferSelect`/`$inferInsert` types, writing `db.select().from(...).leftJoin(...)` queries, or deciding when to split a relational `with:` into two queries. Triggers on `pgTable`, `db.select`, `db.query`, `eq()`/`and()`/`inArray()`, `uniqueIndex`, `primaryKey`, `references({ onDelete })`, 'add a column', 'new table', 'foreign key', 'junction table', 'schema field'. For migration files specifically, see the `db-migrations` skill."
|
||||
user-invocable: false
|
||||
description: Drizzle ORM schema and database guide. Use when working with database schemas (src/database/schemas/*), defining tables, creating migrations, or database model code. Triggers on Drizzle schema definition, database migrations, or ORM usage questions.
|
||||
---
|
||||
|
||||
# Drizzle ORM Schema Style Guide
|
||||
@@ -126,7 +125,11 @@ The relational API generates complex lateral joins with `json_build_array` that
|
||||
|
||||
```typescript
|
||||
// ✅ Good
|
||||
const [result] = await this.db.select().from(agents).where(eq(agents.id, id)).limit(1);
|
||||
const [result] = await this.db
|
||||
.select()
|
||||
.from(agents)
|
||||
.where(eq(agents.id, id))
|
||||
.limit(1);
|
||||
return result;
|
||||
|
||||
// ❌ Bad: relational API
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: hotkey
|
||||
description: "Adding or editing keyboard shortcuts in LobeHub. Use when registering a new hotkey, changing a key combo, scoping a shortcut to chat vs global, or wiring a hotkey hook + tooltip. Covers the 5-step flow: add to `HotkeyEnum` in `src/types/hotkey.ts`, register in `HOTKEYS_REGISTRATION` (`src/const/hotkeys.ts`) with `combineKeys([Key.Mod, …])`, add i18n in `src/locales/default/hotkey.ts`, expose via `useHotkeyById` in `src/hooks/useHotkeys/`, and render `<Tooltip hotkey={…}>`. Triggers on `HotkeyEnum`, `HOTKEYS_REGISTRATION`, `useHotkeyById`, `combineKeys`, `Key.Mod`/`Key.Shift`, 'add a hotkey', 'add a shortcut', '加快捷键', '快捷键', 'Cmd+K', 'keyboard shortcut', 'hotkey scope', 'hotkey conflict'."
|
||||
user-invocable: false
|
||||
description: Guide for adding keyboard shortcuts. Use when implementing new hotkeys, registering shortcuts, or working with keyboard interactions. Triggers on hotkey implementation or keyboard shortcut tasks.
|
||||
---
|
||||
|
||||
# Adding Keyboard Shortcuts Guide
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: i18n
|
||||
description: "LobeHub internationalization with react-i18next. Use when adding any user-facing string in `.tsx`/`.ts` files, creating or renaming a key under `src/locales/default/{namespace}.ts`, deciding the `{feature}.{context}.{action}` flat-key pattern, wiring a new namespace into `src/locales/default/index.ts`, or translating zh-CN/en-US JSON for dev preview. Triggers on `useTranslation`, `t('foo.bar')`, `i18next.t`, `{{variable}}` interpolation, hardcoded UI strings (zh or en) that should be extracted, 'add i18n', '加 i18n key', '翻译', 'locale key', 'namespace', 'pnpm i18n'."
|
||||
user-invocable: false
|
||||
description: Internationalization guide using react-i18next. Use when adding translations, creating i18n keys, or working with localized text in React components (.tsx files). Triggers on translation tasks, locale management, or i18n implementation.
|
||||
---
|
||||
|
||||
# LobeHub Internationalization Guide
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
---
|
||||
name: linear
|
||||
description: "Linear issue management. Use when the user mentions LOBE-xxx issue IDs (e.g. LOBE-4540), says 'linear' / 'linear issue' / 'link linear', or when creating PRs that reference Linear issues. Covers retrieving issues, updating status, adding completion comments, and creating sub-issue trees."
|
||||
user-invocable: false
|
||||
description: "Linear issue management. MUST USE when: (1) user mentions LOBE-xxx issue IDs (e.g. LOBE-4540), (2) user says 'linear', 'linear issue', 'link linear', (3) creating PRs that reference Linear issues. Provides workflows for retrieving issues, updating status, and adding comments."
|
||||
---
|
||||
|
||||
# Linear Issue Management
|
||||
|
||||
Before using Linear workflows, search for `linear` MCP tools. If not found, treat as not installed.
|
||||
|
||||
## PR Creation with Linear Issues
|
||||
## ⚠️ CRITICAL: PR Creation with Linear Issues
|
||||
|
||||
A PR that fixes a Linear issue has **two separate jobs to do**, and both matter:
|
||||
**When creating a PR that references Linear issues (LOBE-xxx), you MUST:**
|
||||
|
||||
1. **`Fixes LOBE-xxx` in the PR body** — Linear watches GitHub for these magic keywords and auto-links the PR and auto-closes the issue on merge. This is the machine-readable side.
|
||||
2. **A completion comment on the Linear issue** — gives the reviewer/PM/teammate landing in Linear a human-readable summary of what changed and why, without forcing them to click through to GitHub and read a diff.
|
||||
1. Create the PR with magic keywords (`Fixes LOBE-xxx`)
|
||||
2. **IMMEDIATELY after PR creation**, add completion comments to ALL referenced Linear issues
|
||||
3. Do NOT consider the task complete until Linear comments are added
|
||||
|
||||
If you only do step 1, Linear watchers (often non-engineers) hit the issue and see no context. So pair PR creation with the Linear comment as part of the same task — finish both before considering the work done.
|
||||
This is NON-NEGOTIABLE. Skipping Linear comments is a workflow violation.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Retrieve issue details** before starting: `mcp__linear-server__get_issue`
|
||||
2. **Read images** — issue descriptions often contain screenshots with critical context (mockups, error states, before/after). Use `mcp__linear-server__extract_images` so you actually see them; reading raw markdown alone misses what the reporter was looking at.
|
||||
3. **Check for sub-issues**: `mcp__linear-server__list_issues` with `parentId` filter
|
||||
4. **Mark as In Progress** at the moment you start planning or implementing — this signals to teammates the issue is owned, so they don't double-pick it up.
|
||||
2. **Read images**: If the issue description contains images, MUST use `mcp__linear-server__extract_images` to read image content for full context
|
||||
3. **Check for sub-issues**: Use `mcp__linear-server__list_issues` with `parentId` filter
|
||||
4. **Mark as In Progress**: When starting to plan or implement an issue, immediately update status to **"In Progress"** via `mcp__linear-server__update_issue`
|
||||
5. **Update issue status** when completing: `mcp__linear-server__update_issue`
|
||||
6. **Add completion comment** (see [format below](#completion-comment-format))
|
||||
6. **Add completion comment** (REQUIRED): `mcp__linear-server__create_comment`
|
||||
|
||||
## Creating Issues
|
||||
|
||||
When creating issues with `mcp__linear-server__create_issue`, add the `claude code` label. Reason: the label is how the team filters/audits AI-generated issues; without it those issues vanish into the general backlog and the team loses visibility into AI contribution patterns.
|
||||
When creating issues with `mcp__linear-server__create_issue`, **MUST add the `claude code` label**.
|
||||
|
||||
## Language
|
||||
|
||||
Match the issue language to the conversation that produced it — if you're discussing in 中文,write the issue in 中文;if discussing in English, write it in English. Reason: the issue is a continuation of the conversation, and forcing a language switch creates translation friction for the collaborator who started the thread.
|
||||
Issue titles, descriptions, and comments **MUST follow the language of the current conversation**, not default to English.
|
||||
|
||||
Specifics:
|
||||
|
||||
- 中文 conversation → 中文 body; technical terms (file paths, identifiers, library names, commands, error messages) stay in English.
|
||||
- English conversation → English body.
|
||||
- Conversation in 中文 → issue body in 中文;technical terms (file paths, identifiers, library names, commands, error messages) stay in English.
|
||||
- Conversation in English → issue body in English.
|
||||
- Code blocks, file paths, and quoted strings always stay in their original form regardless of surrounding language.
|
||||
- This applies equally to **updates** — when editing an existing issue (description **and titles**), preserve the language of the conversation that triggered the edit; don't switch the issue language mid-refactor.
|
||||
- This applies equally to **updates** — when editing an existing issue (description **and titles**), preserve the language of the conversation that triggered the edit; do not switch the issue language during a refactor (Chinese → English or vice versa).
|
||||
|
||||
Rationale: the issue is a continuation of the conversation. Forcing English when the discussion is in Chinese creates translation friction for the collaborator who came from that thread.
|
||||
|
||||
## Creating Sub-issue Trees
|
||||
|
||||
When breaking a parent issue into a tree of sub-issues (e.g., task decomposition for LOBE-xxx), follow these rules — they work around real limitations of the Linear MCP tools.
|
||||
|
||||
### 1. Prefix titles with an ordering index
|
||||
### 1. ALWAYS prefix titles with an ordering index
|
||||
|
||||
The Linear Sub-issues panel orders children by `sortOrder`, which **defaults to newest-first** (most recently created appears on top). Neither parallel nor serial creation produces the intended top-to-bottom reading order, and the MCP `save_issue` tool does **not expose a `sortOrder` parameter** — you can't set order at create time.
|
||||
The Linear Sub-issues panel displays children by `sortOrder`, which **defaults to newest-first** (most recently created appears on top). Neither parallel nor serial creation will produce the intended top-to-bottom reading order, and the MCP `save_issue` tool does **not expose a `sortOrder` parameter** — you cannot set order at create time.
|
||||
|
||||
Workaround: encode execution order in the title itself:
|
||||
**Workaround**: encode execution order in the title itself:
|
||||
|
||||
```plaintext
|
||||
[1] [db] add schema fields
|
||||
@@ -100,7 +100,7 @@ The implementer may open only the sub-issue, not the parent — don't rely on co
|
||||
|
||||
## Completion Comment Format
|
||||
|
||||
Each completed issue gets a comment summarizing the work, so reviewers and future readers don't have to reconstruct it from the PR diff:
|
||||
Every completed issue MUST have a comment summarizing work done:
|
||||
|
||||
```markdown
|
||||
## Changes Summary
|
||||
@@ -116,28 +116,34 @@ Each completed issue gets a comment summarizing the work, so reviewers and futur
|
||||
- ...
|
||||
```
|
||||
|
||||
This gives team visibility, code-review context, and a paper trail for future reference.
|
||||
This is critical for:
|
||||
|
||||
## PR Association
|
||||
- Team visibility
|
||||
- Code review context
|
||||
- Future reference
|
||||
|
||||
When creating PRs for Linear issues, include magic keywords in the PR body:
|
||||
## PR Association (REQUIRED)
|
||||
|
||||
When creating PRs for Linear issues, include magic keywords in PR body:
|
||||
|
||||
- `Fixes LOBE-123`
|
||||
- `Closes LOBE-123`
|
||||
- `Resolves LOBE-123`
|
||||
|
||||
These trigger Linear's auto-link + auto-close on merge.
|
||||
|
||||
## Per-Issue Completion Rule
|
||||
|
||||
When working on multiple issues, close out **each one before starting the next** — don't batch all the Linear updates to the end. Batching is where comments get forgotten and issues stay stuck in "In Progress" days after the PR shipped.
|
||||
|
||||
For each issue:
|
||||
When working on multiple issues, update EACH issue IMMEDIATELY after completing it:
|
||||
|
||||
1. Complete implementation
|
||||
2. Run `bun run type-check`
|
||||
3. Run related tests
|
||||
4. Create PR if needed
|
||||
5. Update status to **"In Review"** (not "Done" — "Done" is for after the PR merges)
|
||||
6. Add the completion comment
|
||||
7. Move to the next issue
|
||||
5. Update status to **"In Review"** (NOT "Done")
|
||||
6. **Add completion comment immediately**
|
||||
7. Move to next issue
|
||||
|
||||
**Note:** Status → "In Review" when PR created. "Done" only after PR merged.
|
||||
|
||||
**❌ Wrong:** Complete all → Create PR → Forget Linear comments
|
||||
|
||||
**✅ Correct:** Complete → Create PR → Add Linear comments → Task done
|
||||
|
||||
@@ -76,9 +76,7 @@ find_project_pids() {
|
||||
port_pid=$(lsof -ti tcp:"$CDP_PORT" -sTCP:LISTEN 2>/dev/null || true)
|
||||
pids="$pids $port_pid"
|
||||
|
||||
# `|| true` because `grep -v '^$'` exits 1 when input has no non-empty
|
||||
# lines, which (with pipefail + set -e) silently kills the caller.
|
||||
echo "$pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true
|
||||
echo "$pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' '
|
||||
}
|
||||
|
||||
# Wait for the CDP HTTP endpoint to respond, with a deadline + early bail-out
|
||||
@@ -148,7 +146,7 @@ do_stop() {
|
||||
for pid in $seed_pids; do
|
||||
all_pids="$all_pids $(expand_descendants "$pid")"
|
||||
done
|
||||
all_pids=$(echo "$all_pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true)
|
||||
all_pids=$(echo "$all_pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
|
||||
|
||||
if [ -z "$all_pids" ]; then
|
||||
echo "[electron-dev] No project Electron/vite processes found."
|
||||
@@ -272,17 +270,10 @@ do_start() {
|
||||
# Launch in a new session (setsid) so the whole process tree shares a PGID
|
||||
# we can later signal in one shot. `setsid bash -c '... exec ...' &` keeps
|
||||
# the bash shell as the session leader; its PID is what we save.
|
||||
# macOS doesn't ship setsid by default — fall back to plain bash; cleanup
|
||||
# still works via `expand_descendants` walking the process tree.
|
||||
local launch_cmd="
|
||||
setsid bash -c "
|
||||
cd '$PROJECT_ROOT/apps/desktop'
|
||||
exec npx electron-vite dev -- --remote-debugging-port=$CDP_PORT
|
||||
"
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
setsid bash -c "$launch_cmd" >> "$ELECTRON_LOG" 2>&1 < /dev/null &
|
||||
else
|
||||
bash -c "$launch_cmd" >> "$ELECTRON_LOG" 2>&1 < /dev/null &
|
||||
fi
|
||||
" >> "$ELECTRON_LOG" 2>&1 < /dev/null &
|
||||
local launcher_pid=$!
|
||||
echo "$launcher_pid" > "$PIDFILE"
|
||||
echo "[electron-dev] Launcher PID (session leader): $launcher_pid"
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
---
|
||||
name: microcopy
|
||||
description: UI copy and microcopy guidelines. Use when writing UI text, buttons, error messages, empty states, onboarding, or any user-facing copy. Triggers on i18n translation, UI text writing, or copy improvement tasks. Supports both Chinese and English.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# LobeHub UI Microcopy Guidelines
|
||||
|
||||
This file is the quick-reference summary. For full prompt-style guidelines with extensive examples (anti-patterns, tone matrices, scenario walk-throughs), load the language-specific reference:
|
||||
|
||||
- **中文文案** — [`references/zh.md`](./references/zh.md)
|
||||
- **English copy** — [`references/en.md`](./references/en.md)
|
||||
|
||||
Brand: **Where Agents Collaborate** - Focus on collaborative agent system, not just "generation".
|
||||
|
||||
## Fixed Terminology
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: modal
|
||||
description: "LobeHub imperative-modal conventions. Use whenever creating, editing, opening, or migrating a modal/dialog/popup — prefer `createModal` / `confirmModal` / `useModalContext` from `@lobehub/ui/base-ui` (headless) over the legacy root `@lobehub/ui` `createModal` (antd Modal props) and over any declarative `open` state + `<Modal />` pattern. Covers required `ModalHost` mounting, the `Content` + `index.tsx` file layout, `content` vs `children` slot, i18n inside `createModal()` (`import { t } from 'i18next'`), and migration notes. Triggers on `createModal`, `confirmModal`, `useModalContext`, `ModalHost`, `antd Modal`, `<Modal open>`, 'open a modal', 'popup', 'dialog', 'confirm dialog', '弹框', '弹窗', '确认框', 'migrate to base-ui'."
|
||||
description: MUST use when creating, editing, or writing modal dialogs or imperative modals. Prefer createModal / useModalContext / confirmModal from @lobehub/ui/base-ui; root @lobehub/ui is legacy (antd Modal). Covers patterns, ModalHost, and migration notes.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: project-overview
|
||||
description: Complete project architecture and structure guide. Use when exploring the codebase, understanding project organization, finding files, or needing comprehensive architectural context. Triggers on architecture questions, directory navigation, or project overview needs.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# LobeHub Project Overview
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: react
|
||||
description: "LobeHub React/SPA component conventions: antd-style with `createStaticStyles` + `cssVar.*` (prefer zero-runtime over `createStyles` + `token`), `@lobehub/ui/base-ui` primitives before `@lobehub/ui` before antd, `Flexbox`/`Center` for layouts, react-router-dom navigation, and the `.desktop.tsx` sync rule. Use when writing or editing any `.tsx` under `src/**`, picking a styling helper, choosing a component (Select/Modal/Drawer/Button/Tooltip), wiring routes in `desktopRouter.config.tsx`/`.desktop.tsx`, or adding a `Link`/`useNavigate` call in the SPA. Triggers on `createStyles`/`createStaticStyles`, `cssVar`, `@lobehub/ui`, `antd-style`, `Flexbox`, `useNavigate`, `react-router-dom`, `Link`, 'new component', 'add a page', 'edit a layout', 'desktopRouter', 'componentMap.desktop'."
|
||||
user-invocable: false
|
||||
description: React component development guide. Use when working with React components (.tsx files), creating UI, using @lobehub/ui components, implementing routing, or building frontend features. Triggers on React component creation, modification, layout implementation, or navigation tasks.
|
||||
---
|
||||
|
||||
# React Component Writing Guide
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: review-checklist
|
||||
description: 'Common recurring mistakes in LobeHub code review — console leftovers, missing return await, hardcoded secrets, hardcoded i18n strings, desktop router pair drift, antd vs @lobehub/ui, non-idempotent migrations, cloud impact red flags. Use as a quick checklist when reviewing PRs, diffs, or branch changes.'
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Review Checklist
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: spa-routes
|
||||
description: MUST use when editing src/routes/ segments, src/spa/router/desktopRouter.config.tsx or desktopRouter.config.desktop.tsx (always change both together), mobileRouter.config.tsx, or when moving UI/logic between routes and src/features/.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# SPA Routes and Features Guide
|
||||
@@ -85,10 +84,10 @@ Each feature should:
|
||||
|
||||
## 3a. Desktop router pair (`desktopRouter.config` × 2)
|
||||
|
||||
| File | Role |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `desktopRouter.config.tsx` | Dynamic imports via `dynamicElement` / `dynamicLayout` — code-splitting; used by `entry.web.tsx` and `entry.desktop.tsx`. |
|
||||
| `desktopRouter.config.desktop.tsx` | Same route tree with **synchronous** imports — kept for Electron / local parity and predictable bundling. |
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `desktopRouter.config.tsx` | Dynamic imports via `dynamicElement` / `dynamicLayout` — code-splitting; used by `entry.web.tsx` and `entry.desktop.tsx`. |
|
||||
| `desktopRouter.config.desktop.tsx` | Same route tree with **synchronous** imports — kept for Electron / local parity and predictable bundling. |
|
||||
|
||||
Anything that changes the tree (new segment, renamed `path`, moved layout, new child route) must be reflected in **both** files in one PR or commit. Remove routes from both when deleting.
|
||||
|
||||
|
||||
@@ -1,91 +1,257 @@
|
||||
---
|
||||
name: store-data-structures
|
||||
description: Zustand store data structure patterns for LobeHub. Covers List vs Detail data structures, Map + Reducer patterns, type definitions, and when to use each pattern. Use when designing store state, choosing data structures, or implementing list/detail pages.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# LobeHub Store Data Structures
|
||||
|
||||
How to structure data in Zustand stores for fast list rendering, multi-detail caching, and ergonomic optimistic updates.
|
||||
This guide covers how to structure data in Zustand stores for optimal performance and user experience.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### ✅ DO
|
||||
|
||||
1. **Separate List and Detail** — different structures for list pages and detail pages
|
||||
2. **Use Map for Details** — cache multiple detail pages with `Record<string, Detail>`
|
||||
3. **Use Array for Lists** — simple arrays for list display
|
||||
4. **Types from `@lobechat/types`** — never use `@lobechat/database` types in stores
|
||||
5. **Distinguish List and Detail types** — List types may have computed UI fields
|
||||
1. **Separate List and Detail** - Use different structures for list pages and detail pages
|
||||
2. **Use Map for Details** - Cache multiple detail pages with `Record<string, Detail>`
|
||||
3. **Use Array for Lists** - Simple arrays for list display
|
||||
4. **Types from @lobechat/types** - Never use `@lobechat/database` types in stores
|
||||
5. **Distinguish List and Detail types** - List types may have computed UI fields
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
1. **Don't use a single detail object** — can't cache multiple pages
|
||||
2. **Don't mix List and Detail types** — they have different purposes
|
||||
3. **Don't use database types** — use types from `@lobechat/types`
|
||||
4. **Don't use Map for lists** — simple arrays are sufficient
|
||||
1. **Don't use single detail object** - Can't cache multiple pages
|
||||
2. **Don't mix List and Detail types** - They have different purposes
|
||||
3. **Don't use database types** - Use types from `@lobechat/types`
|
||||
4. **Don't use Map for lists** - Simple arrays are sufficient
|
||||
|
||||
---
|
||||
|
||||
## Type Definitions
|
||||
|
||||
Each entity gets its own file under `@lobechat/types/`. Each file exports two types:
|
||||
Types should be organized by entity in separate files:
|
||||
|
||||
- **Detail type** — full entity, including heavy fields (rubrics, content, editor state, …)
|
||||
- **List item type** — a **subset** that excludes heavy fields, may add computed UI fields (counts, timestamps formatted for display)
|
||||
```
|
||||
@lobechat/types/src/eval/
|
||||
├── benchmark.ts # Benchmark types
|
||||
├── agentEvalDataset.ts # Dataset types
|
||||
├── agentEvalRun.ts # Run types
|
||||
└── index.ts # Re-exports
|
||||
```
|
||||
|
||||
**Important:** the List type is a **subset**, not an `extends` of Detail. Extending pulls the heavy fields right back in.
|
||||
### Example: Benchmark Types
|
||||
|
||||
> See [`references/types.md`](./references/types.md) for full worked examples (Benchmark, Document) and the heavy-field exclusion checklist.
|
||||
```typescript
|
||||
// packages/types/src/eval/benchmark.ts
|
||||
import type { EvalBenchmarkRubric } from './rubric';
|
||||
|
||||
// ============================================
|
||||
// Detail Type - Full entity (for detail pages)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Full benchmark entity with all fields including heavy data
|
||||
*/
|
||||
export interface AgentEvalBenchmark {
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
isSystem: boolean;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
name: string;
|
||||
referenceUrl?: string | null;
|
||||
rubrics: EvalBenchmarkRubric[]; // Heavy field
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// List Type - Lightweight (for list display)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Lightweight benchmark item - excludes heavy fields
|
||||
* May include computed statistics for UI
|
||||
*/
|
||||
export interface AgentEvalBenchmarkListItem {
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
isSystem: boolean;
|
||||
name: string;
|
||||
// Note: rubrics NOT included (heavy field)
|
||||
|
||||
// Computed statistics for UI display
|
||||
datasetCount?: number;
|
||||
runCount?: number;
|
||||
testCaseCount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Document Types (with heavy content)
|
||||
|
||||
```typescript
|
||||
// packages/types/src/document.ts
|
||||
|
||||
/**
|
||||
* Full document entity - includes heavy content fields
|
||||
*/
|
||||
export interface Document {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
content: string; // Heavy field - full markdown content
|
||||
editorData: any; // Heavy field - editor state
|
||||
metadata?: Record<string, unknown>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight document item - excludes heavy content
|
||||
*/
|
||||
export interface DocumentListItem {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
// Note: content and editorData NOT included
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
// Computed statistics
|
||||
wordCount?: number;
|
||||
lastEditedBy?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
|
||||
- **Detail types** include ALL fields from database (full entity)
|
||||
- **List types** are **subsets** that exclude heavy/large fields
|
||||
- List types may add computed statistics for UI (e.g., `testCaseCount`)
|
||||
- **Each entity gets its own file** (not mixed together)
|
||||
- **All types** exported from `@lobechat/types`, NOT `@lobechat/database`
|
||||
|
||||
**Heavy fields to exclude from List:**
|
||||
|
||||
- Large text content (`content`, `editorData`, `fullDescription`)
|
||||
- Complex objects (`rubrics`, `config`, `metrics`)
|
||||
- Binary data (`image`, `file`)
|
||||
- Large arrays (`messages`, `items`)
|
||||
|
||||
---
|
||||
|
||||
## When to Use Map vs Array
|
||||
|
||||
### Use Map + Reducer — for Detail Data
|
||||
### Use Map + Reducer (for Detail Data)
|
||||
|
||||
✅ Detail page data caching — multiple detail pages cached simultaneously
|
||||
✅ Optimistic updates — update UI before API responds
|
||||
✅ Per-item loading states — track which items are being updated
|
||||
✅ Multi-page navigation — user can switch between details without refetching
|
||||
✅ **Detail page data caching** - Cache multiple detail pages simultaneously
|
||||
✅ **Optimistic updates** - Update UI before API responds
|
||||
✅ **Per-item loading states** - Track which items are being updated
|
||||
✅ **Multiple pages open** - User can navigate between details without refetching
|
||||
|
||||
**Structure:**
|
||||
|
||||
```typescript
|
||||
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
|
||||
```
|
||||
|
||||
Examples: benchmark detail pages, dataset detail pages, user profiles.
|
||||
**Example:** Benchmark detail pages, Dataset detail pages, User profiles
|
||||
|
||||
### Use Simple Array — for List Data
|
||||
### Use Simple Array (for List Data)
|
||||
|
||||
✅ List display — lists, tables, cards
|
||||
✅ Refresh as a whole — entire list refreshes together
|
||||
✅ No per-item updates — no need to mutate individual rows in place
|
||||
✅ Simple data flow — fewer moving parts
|
||||
✅ **List display** - Lists, tables, cards
|
||||
✅ **Read-only or refresh-as-whole** - Entire list refreshes together
|
||||
✅ **No per-item updates** - No need to update individual items
|
||||
✅ **Simple data flow** - Easier to understand and maintain
|
||||
|
||||
**Structure:**
|
||||
|
||||
```typescript
|
||||
benchmarkList: AgentEvalBenchmarkListItem[];
|
||||
benchmarkList: AgentEvalBenchmarkListItem[]
|
||||
```
|
||||
|
||||
Examples: benchmark list, dataset list, user list.
|
||||
**Example:** Benchmark list, Dataset list, User list
|
||||
|
||||
---
|
||||
|
||||
## State Structure Pattern
|
||||
|
||||
### Complete Example
|
||||
|
||||
```typescript
|
||||
// packages/types/src/eval/benchmark.ts
|
||||
import type { EvalBenchmarkRubric } from './rubric';
|
||||
|
||||
/**
|
||||
* Full benchmark entity (for detail pages)
|
||||
*/
|
||||
export interface AgentEvalBenchmark {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
identifier: string;
|
||||
rubrics: EvalBenchmarkRubric[]; // Heavy field
|
||||
metadata?: Record<string, unknown> | null;
|
||||
isSystem: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight benchmark (for list display)
|
||||
* Excludes heavy fields like rubrics
|
||||
*/
|
||||
export interface AgentEvalBenchmarkListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
identifier: string;
|
||||
isSystem: boolean;
|
||||
createdAt: Date;
|
||||
// Note: rubrics excluded
|
||||
|
||||
// Computed statistics
|
||||
testCaseCount?: number;
|
||||
datasetCount?: number;
|
||||
runCount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/benchmark/initialState.ts
|
||||
import type { AgentEvalBenchmark, AgentEvalBenchmarkListItem } from '@lobechat/types';
|
||||
|
||||
export interface BenchmarkSliceState {
|
||||
// List — simple array
|
||||
// ============================================
|
||||
// List Data - Simple Array
|
||||
// ============================================
|
||||
/**
|
||||
* List of benchmarks for list page display
|
||||
* May include computed fields like testCaseCount
|
||||
*/
|
||||
benchmarkList: AgentEvalBenchmarkListItem[];
|
||||
benchmarkListInit: boolean;
|
||||
|
||||
// Detail — map for multi-entity caching
|
||||
// ============================================
|
||||
// Detail Data - Map for Caching
|
||||
// ============================================
|
||||
/**
|
||||
* Map of benchmark details keyed by ID
|
||||
* Caches detail page data for multiple benchmarks
|
||||
* Enables optimistic updates and per-item loading
|
||||
*/
|
||||
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
|
||||
loadingBenchmarkDetailIds: string[]; // per-item loading
|
||||
|
||||
// Mutation states (drive form-level UI)
|
||||
/**
|
||||
* Track which benchmark details are being loaded/updated
|
||||
* For showing spinners on specific items
|
||||
*/
|
||||
loadingBenchmarkDetailIds: string[];
|
||||
|
||||
// ============================================
|
||||
// Mutation States
|
||||
// ============================================
|
||||
isCreatingBenchmark: boolean;
|
||||
isUpdatingBenchmark: boolean;
|
||||
isDeletingBenchmark: boolean;
|
||||
@@ -106,51 +272,180 @@ export const benchmarkInitialState: BenchmarkSliceState = {
|
||||
|
||||
## Reducer Pattern (for Detail Map)
|
||||
|
||||
When the Detail Map needs optimistic updates (i.e. the user edits a row and the UI should reflect it before the server confirms), wire a typed reducer instead of inlining `set` calls. This keeps mutations testable and the dispatch surface small.
|
||||
### Why Use Reducer?
|
||||
|
||||
> See [`references/reducer.md`](./references/reducer.md) for the full discriminated-union action types, the `produce`-based reducer, and the `internal_dispatch*` slice methods that connect them to Zustand.
|
||||
- **Immutable updates** - Immer ensures immutability
|
||||
- **Type-safe actions** - TypeScript discriminated unions
|
||||
- **Testable** - Pure functions easy to test
|
||||
- **Reusable** - Same reducer for optimistic updates and server data
|
||||
|
||||
### Reducer Structure
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/benchmark/reducer.ts
|
||||
import { produce } from 'immer';
|
||||
import type { AgentEvalBenchmark } from '@lobechat/types';
|
||||
|
||||
// ============================================
|
||||
// Action Types
|
||||
// ============================================
|
||||
|
||||
type SetBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'setBenchmarkDetail';
|
||||
value: AgentEvalBenchmark;
|
||||
};
|
||||
|
||||
type UpdateBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'updateBenchmarkDetail';
|
||||
value: Partial<AgentEvalBenchmark>;
|
||||
};
|
||||
|
||||
type DeleteBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'deleteBenchmarkDetail';
|
||||
};
|
||||
|
||||
export type BenchmarkDetailDispatch =
|
||||
| SetBenchmarkDetailAction
|
||||
| UpdateBenchmarkDetailAction
|
||||
| DeleteBenchmarkDetailAction;
|
||||
|
||||
// ============================================
|
||||
// Reducer Function
|
||||
// ============================================
|
||||
|
||||
export const benchmarkDetailReducer = (
|
||||
state: Record<string, AgentEvalBenchmark> = {},
|
||||
payload: BenchmarkDetailDispatch,
|
||||
): Record<string, AgentEvalBenchmark> => {
|
||||
switch (payload.type) {
|
||||
case 'setBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
draft[payload.id] = payload.value;
|
||||
});
|
||||
}
|
||||
|
||||
case 'updateBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
if (draft[payload.id]) {
|
||||
draft[payload.id] = { ...draft[payload.id], ...payload.value };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
case 'deleteBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
delete draft[payload.id];
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Internal Dispatch Methods
|
||||
|
||||
```typescript
|
||||
// In action.ts
|
||||
export interface BenchmarkAction {
|
||||
// ... other methods ...
|
||||
|
||||
// Internal methods - not for direct UI use
|
||||
internal_dispatchBenchmarkDetail: (payload: BenchmarkDetailDispatch) => void;
|
||||
internal_updateBenchmarkDetailLoading: (id: string, loading: boolean) => void;
|
||||
}
|
||||
|
||||
export const createBenchmarkSlice: StateCreator<...> = (set, get) => ({
|
||||
// ... other methods ...
|
||||
|
||||
// Internal - Dispatch to reducer
|
||||
internal_dispatchBenchmarkDetail: (payload) => {
|
||||
const currentMap = get().benchmarkDetailMap;
|
||||
const nextMap = benchmarkDetailReducer(currentMap, payload);
|
||||
|
||||
// Only update if changed
|
||||
if (isEqual(nextMap, currentMap)) return;
|
||||
|
||||
set(
|
||||
{ benchmarkDetailMap: nextMap },
|
||||
false,
|
||||
`dispatchBenchmarkDetail/${payload.type}`,
|
||||
);
|
||||
},
|
||||
|
||||
// Internal - Update loading state
|
||||
internal_updateBenchmarkDetailLoading: (id, loading) => {
|
||||
set(
|
||||
(state) => {
|
||||
if (loading) {
|
||||
return { loadingBenchmarkDetailIds: [...state.loadingBenchmarkDetailIds, id] };
|
||||
}
|
||||
return {
|
||||
loadingBenchmarkDetailIds: state.loadingBenchmarkDetailIds.filter((i) => i !== id),
|
||||
};
|
||||
},
|
||||
false,
|
||||
'updateBenchmarkDetailLoading',
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Structure Comparison
|
||||
|
||||
### ❌ WRONG — Single Detail Object
|
||||
### ❌ WRONG - Single Detail Object
|
||||
|
||||
```typescript
|
||||
interface BenchmarkSliceState {
|
||||
// ❌ Can only cache one detail
|
||||
benchmarkDetail: AgentEvalBenchmark | null;
|
||||
|
||||
// ❌ Global loading state
|
||||
isLoadingBenchmarkDetail: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Problems:
|
||||
**Problems:**
|
||||
|
||||
- Can only cache one detail page at a time
|
||||
- Switching between details forces refetch
|
||||
- Switching between details causes unnecessary refetches
|
||||
- No optimistic updates
|
||||
- No per-item loading states
|
||||
|
||||
### ✅ CORRECT — Separate List and Detail
|
||||
### ✅ CORRECT - Separate List and Detail
|
||||
|
||||
```typescript
|
||||
import type { AgentEvalBenchmark, AgentEvalBenchmarkListItem } from '@lobechat/types';
|
||||
|
||||
interface BenchmarkSliceState {
|
||||
// ✅ List data - simple array
|
||||
benchmarkList: AgentEvalBenchmarkListItem[];
|
||||
benchmarkListInit: boolean;
|
||||
|
||||
// ✅ Detail data - map for caching
|
||||
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
|
||||
|
||||
// ✅ Per-item loading
|
||||
loadingBenchmarkDetailIds: string[];
|
||||
|
||||
// ✅ Mutation states
|
||||
isCreatingBenchmark: boolean;
|
||||
isUpdatingBenchmark: boolean;
|
||||
isDeletingBenchmark: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Benefits:
|
||||
**Benefits:**
|
||||
|
||||
- Cache multiple detail pages
|
||||
- Fast navigation between cached details
|
||||
- Optimistic updates via reducer
|
||||
- Optimistic updates with reducer
|
||||
- Per-item loading states
|
||||
- Clear separation of concerns
|
||||
|
||||
@@ -160,16 +455,22 @@ Benefits:
|
||||
|
||||
### Accessing List Data
|
||||
|
||||
```tsx
|
||||
```typescript
|
||||
const BenchmarkList = () => {
|
||||
// Simple array access
|
||||
const benchmarks = useEvalStore((s) => s.benchmarkList);
|
||||
const isInit = useEvalStore((s) => s.benchmarkListInit);
|
||||
|
||||
if (!isInit) return <Loading />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{benchmarks.map((b) => (
|
||||
<BenchmarkCard key={b.id} name={b.name} testCaseCount={b.testCaseCount} />
|
||||
{benchmarks.map(b => (
|
||||
<BenchmarkCard
|
||||
key={b.id}
|
||||
name={b.name}
|
||||
testCaseCount={b.testCaseCount} // Computed field
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -178,18 +479,22 @@ const BenchmarkList = () => {
|
||||
|
||||
### Accessing Detail Data
|
||||
|
||||
```tsx
|
||||
```typescript
|
||||
const BenchmarkDetail = () => {
|
||||
const { benchmarkId } = useParams<{ benchmarkId: string }>();
|
||||
|
||||
// Get from map
|
||||
const benchmark = useEvalStore((s) =>
|
||||
benchmarkId ? s.benchmarkDetailMap[benchmarkId] : undefined,
|
||||
);
|
||||
|
||||
// Check loading
|
||||
const isLoading = useEvalStore((s) =>
|
||||
benchmarkId ? s.loadingBenchmarkDetailIds.includes(benchmarkId) : false,
|
||||
);
|
||||
|
||||
if (!benchmark) return <Loading />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{benchmark.name}</h1>
|
||||
@@ -205,6 +510,7 @@ const BenchmarkDetail = () => {
|
||||
// src/store/eval/slices/benchmark/selectors.ts
|
||||
export const benchmarkSelectors = {
|
||||
getBenchmarkDetail: (id: string) => (s: EvalStore) => s.benchmarkDetailMap[id],
|
||||
|
||||
isLoadingBenchmarkDetail: (id: string) => (s: EvalStore) =>
|
||||
s.loadingBenchmarkDetailIds.includes(id),
|
||||
};
|
||||
@@ -218,7 +524,7 @@ const isLoading = useEvalStore(benchmarkSelectors.isLoadingBenchmarkDetail(bench
|
||||
|
||||
## Decision Tree
|
||||
|
||||
```text
|
||||
```
|
||||
Need to store data?
|
||||
│
|
||||
├─ Is it a LIST for display?
|
||||
@@ -241,40 +547,43 @@ Need to store data?
|
||||
|
||||
When designing store state structure:
|
||||
|
||||
- [ ] **Organize types by entity** in separate files (e.g. `benchmark.ts`, `agentEvalDataset.ts`)
|
||||
- [ ] **Organize types by entity** in separate files (e.g., `benchmark.ts`, `agentEvalDataset.ts`)
|
||||
- [ ] Create **Detail** type (full entity with all fields including heavy ones)
|
||||
- [ ] Create **ListItem** type:
|
||||
- [ ] Subset of Detail (exclude heavy fields)
|
||||
- [ ] Subset of Detail type (exclude heavy fields)
|
||||
- [ ] May include computed statistics for UI
|
||||
- [ ] **NOT** `extends` Detail
|
||||
- [ ] **NOT** extending Detail type (it's a subset, not extension)
|
||||
- [ ] Use **array** for list data: `xxxList: XxxListItem[]`
|
||||
- [ ] Use **Map** for detail data: `xxxDetailMap: Record<string, Xxx>`
|
||||
- [ ] Per-item loading: `loadingXxxDetailIds: string[]`
|
||||
- [ ] **Reducer** for detail map if optimistic updates needed (see [`references/reducer.md`](./references/reducer.md))
|
||||
- [ ] **Internal dispatch** and **loading** methods
|
||||
- [ ] **Selectors** for clean access (optional but recommended)
|
||||
- [ ] Document in comments which fields are excluded from List and why
|
||||
- [ ] Add per-item loading: `loadingXxxDetailIds: string[]`
|
||||
- [ ] Create **reducer** for detail map if optimistic updates needed
|
||||
- [ ] Add **internal dispatch** and **loading** methods
|
||||
- [ ] Create **selectors** for clean access (optional but recommended)
|
||||
- [ ] Document in comments:
|
||||
- [ ] What fields are excluded from List and why
|
||||
- [ ] What computed fields mean
|
||||
- [ ] What each Map is for
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **File organization** — one entity per file, not mixed
|
||||
2. **List is a subset** — ListItem excludes heavy fields, does not `extends` Detail
|
||||
3. **Clear naming** — `xxxList` for arrays, `xxxDetailMap` for maps
|
||||
4. **Consistent patterns** — all detail maps follow the same shape
|
||||
5. **Type safety** — never use `any`, always use proper types
|
||||
6. **Document exclusions** — comment which fields are excluded and why
|
||||
7. **Selectors** — encapsulate access patterns
|
||||
8. **Loading states** — per-item for details, global for mutations
|
||||
9. **Immutability** — use Immer in reducers
|
||||
1. **File organization** - One entity per file, not mixed together
|
||||
2. **List is subset** - ListItem excludes heavy fields, not extends Detail
|
||||
3. **Clear naming** - `xxxList` for arrays, `xxxDetailMap` for maps
|
||||
4. **Consistent patterns** - All detail maps follow same structure
|
||||
5. **Type safety** - Never use `any`, always use proper types
|
||||
6. **Document exclusions** - Comment which fields are excluded from List and why
|
||||
7. **Selectors** - Encapsulate access patterns
|
||||
8. **Loading states** - Per-item for details, global for lists
|
||||
9. **Immutability** - Use Immer in reducers
|
||||
|
||||
### Common Mistakes to Avoid
|
||||
|
||||
❌ **DON'T extend Detail in List:**
|
||||
|
||||
```typescript
|
||||
// Wrong — pulls heavy fields back in
|
||||
// Wrong - List should not extend Detail
|
||||
export interface BenchmarkListItem extends Benchmark {
|
||||
testCaseCount?: number;
|
||||
}
|
||||
@@ -283,6 +592,7 @@ export interface BenchmarkListItem extends Benchmark {
|
||||
✅ **DO create separate subset:**
|
||||
|
||||
```typescript
|
||||
// Correct - List is a subset with computed fields
|
||||
export interface BenchmarkListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -293,14 +603,14 @@ export interface BenchmarkListItem {
|
||||
|
||||
❌ **DON'T mix entities in one file:**
|
||||
|
||||
```text
|
||||
// Wrong — all entities in agentEvalEntities.ts
|
||||
```typescript
|
||||
// Wrong - all entities in agentEvalEntities.ts
|
||||
```
|
||||
|
||||
✅ **DO separate by entity:**
|
||||
|
||||
```text
|
||||
// Correct — separate files
|
||||
```typescript
|
||||
// Correct - separate files
|
||||
// benchmark.ts
|
||||
// agentEvalDataset.ts
|
||||
// agentEvalRun.ts
|
||||
@@ -310,5 +620,5 @@ export interface BenchmarkListItem {
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `data-fetching` — how to fetch and update this data
|
||||
- `zustand` — general Zustand patterns
|
||||
- `data-fetching` - How to fetch and update this data
|
||||
- `zustand` - General Zustand patterns
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
# Reducer Pattern (for Detail Map)
|
||||
|
||||
## Why Use a Reducer?
|
||||
|
||||
- **Immutable updates** — Immer makes immutability easy
|
||||
- **Type-safe actions** — discriminated union of action types prevents typos
|
||||
- **Testable** — pure function, easy to unit test
|
||||
- **Reusable** — same reducer powers optimistic updates and server-data writes
|
||||
|
||||
## Reducer Structure
|
||||
|
||||
```typescript
|
||||
// src/store/eval/slices/benchmark/reducer.ts
|
||||
import { produce } from 'immer';
|
||||
import type { AgentEvalBenchmark } from '@lobechat/types';
|
||||
|
||||
// Action types — discriminated union
|
||||
type SetBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'setBenchmarkDetail';
|
||||
value: AgentEvalBenchmark;
|
||||
};
|
||||
|
||||
type UpdateBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'updateBenchmarkDetail';
|
||||
value: Partial<AgentEvalBenchmark>;
|
||||
};
|
||||
|
||||
type DeleteBenchmarkDetailAction = {
|
||||
id: string;
|
||||
type: 'deleteBenchmarkDetail';
|
||||
};
|
||||
|
||||
export type BenchmarkDetailDispatch =
|
||||
| SetBenchmarkDetailAction
|
||||
| UpdateBenchmarkDetailAction
|
||||
| DeleteBenchmarkDetailAction;
|
||||
|
||||
export const benchmarkDetailReducer = (
|
||||
state: Record<string, AgentEvalBenchmark> = {},
|
||||
payload: BenchmarkDetailDispatch,
|
||||
): Record<string, AgentEvalBenchmark> => {
|
||||
switch (payload.type) {
|
||||
case 'setBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
draft[payload.id] = payload.value;
|
||||
});
|
||||
}
|
||||
|
||||
case 'updateBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
if (draft[payload.id]) {
|
||||
draft[payload.id] = { ...draft[payload.id], ...payload.value };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
case 'deleteBenchmarkDetail': {
|
||||
return produce(state, (draft) => {
|
||||
delete draft[payload.id];
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Internal Dispatch Methods
|
||||
|
||||
The slice exposes two `internal_*` methods so the reducer and the loading state stay encapsulated behind a stable contract:
|
||||
|
||||
```typescript
|
||||
// In action.ts
|
||||
export interface BenchmarkAction {
|
||||
// ... other methods ...
|
||||
|
||||
// Internal — not for direct UI use
|
||||
internal_dispatchBenchmarkDetail: (payload: BenchmarkDetailDispatch) => void;
|
||||
internal_updateBenchmarkDetailLoading: (id: string, loading: boolean) => void;
|
||||
}
|
||||
|
||||
export const createBenchmarkSlice: StateCreator<...> = (set, get) => ({
|
||||
// ... other methods ...
|
||||
|
||||
// Dispatch to reducer
|
||||
internal_dispatchBenchmarkDetail: (payload) => {
|
||||
const currentMap = get().benchmarkDetailMap;
|
||||
const nextMap = benchmarkDetailReducer(currentMap, payload);
|
||||
|
||||
// Skip set when nothing changed — avoids unnecessary re-renders
|
||||
if (isEqual(nextMap, currentMap)) return;
|
||||
|
||||
set(
|
||||
{ benchmarkDetailMap: nextMap },
|
||||
false,
|
||||
`dispatchBenchmarkDetail/${payload.type}`,
|
||||
);
|
||||
},
|
||||
|
||||
// Update loading state for a specific id
|
||||
internal_updateBenchmarkDetailLoading: (id, loading) => {
|
||||
set(
|
||||
(state) => ({
|
||||
loadingBenchmarkDetailIds: loading
|
||||
? [...state.loadingBenchmarkDetailIds, id]
|
||||
: state.loadingBenchmarkDetailIds.filter((i) => i !== id),
|
||||
}),
|
||||
false,
|
||||
'updateBenchmarkDetailLoading',
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `internal_` prefix is a convention — UI components should call the public mutation methods (e.g. `updateBenchmark`), which in turn call `internal_dispatch*`. This keeps reducer dispatch shapes out of the component layer.
|
||||
@@ -1,101 +0,0 @@
|
||||
# Type Definitions in Detail
|
||||
|
||||
The skill body's Type Definitions section covers the rules; this file holds the full worked examples to keep SKILL.md lean.
|
||||
|
||||
## Organization
|
||||
|
||||
Types should be organized by entity in separate files (not mixed):
|
||||
|
||||
```text
|
||||
@lobechat/types/src/eval/
|
||||
├── benchmark.ts # Benchmark types
|
||||
├── agentEvalDataset.ts # Dataset types
|
||||
├── agentEvalRun.ts # Run types
|
||||
└── index.ts # Re-exports
|
||||
```
|
||||
|
||||
## Example: Benchmark Types
|
||||
|
||||
```typescript
|
||||
// packages/types/src/eval/benchmark.ts
|
||||
import type { EvalBenchmarkRubric } from './rubric';
|
||||
|
||||
/**
|
||||
* Full benchmark entity with all fields including heavy data.
|
||||
*/
|
||||
export interface AgentEvalBenchmark {
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
isSystem: boolean;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
name: string;
|
||||
referenceUrl?: string | null;
|
||||
rubrics: EvalBenchmarkRubric[]; // Heavy field
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight benchmark item — excludes heavy fields, may add computed stats.
|
||||
*/
|
||||
export interface AgentEvalBenchmarkListItem {
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
isSystem: boolean;
|
||||
name: string;
|
||||
// Note: rubrics NOT included (heavy field)
|
||||
|
||||
// Computed statistics for UI display
|
||||
datasetCount?: number;
|
||||
runCount?: number;
|
||||
testCaseCount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Example: Document Types (with heavy content)
|
||||
|
||||
```typescript
|
||||
// packages/types/src/document.ts
|
||||
|
||||
/**
|
||||
* Full document entity — includes heavy content fields.
|
||||
*/
|
||||
export interface Document {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
content: string; // Heavy field — full markdown content
|
||||
editorData: any; // Heavy field — editor state
|
||||
metadata?: Record<string, unknown>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight document item — excludes heavy content.
|
||||
*/
|
||||
export interface DocumentListItem {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
// Note: content and editorData NOT included
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
// Computed statistics
|
||||
wordCount?: number;
|
||||
lastEditedBy?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Heavy Fields to Exclude from List
|
||||
|
||||
- Large text content (`content`, `editorData`, `fullDescription`)
|
||||
- Complex objects (`rubrics`, `config`, `metrics`)
|
||||
- Binary data (`image`, `file`)
|
||||
- Large arrays (`messages`, `items`)
|
||||
|
||||
The reason these belong only on Detail: list pages render many rows, so pulling heavy fields blows up payload size and slows render. Detail pages render one entity, so the full payload is fine.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: testing
|
||||
description: Testing guide using Vitest. Use when writing tests (.test.ts, .test.tsx), fixing failing tests, improving test coverage, or debugging test issues. Triggers on test creation, test debugging, mock setup, or test-related questions.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# LobeHub Testing Guide
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: trpc-router
|
||||
description: TRPC router development guide. Use when creating or modifying TRPC routers (src/server/routers/**), adding procedures, or working with server-side API endpoints. Triggers on TRPC router creation, procedure implementation, or API endpoint tasks.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# TRPC Router Guide
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: typescript
|
||||
description: "TypeScript code style and type-safety guide for LobeHub. Read before writing or editing any `.ts` / `.tsx` / `.mts` — covers `interface` vs `type`, `Record<PropertyKey, unknown>` over `any`/`object`, `as const satisfies`, `@ts-expect-error` over `@ts-ignore`, `import type` (`separate-type-imports`), `async`/`await` + `Promise.all`, `for…of` over indexed `for`, and the no-silent-`.catch(() => fallback)` rule. Also use when reviewing type quality, deciding module augmentation (`declare module`) over `namespace`, or designing extensible types (e.g. `PipelineContext.metadata`). Triggers on any TypeScript file edit, 'fix the type', 'why is this `any`', 'should this be interface or type', 'eslint type-import', 'ts-expect-error'."
|
||||
user-invocable: false
|
||||
description: TypeScript code style and optimization guidelines. MUST READ before writing or modifying any TypeScript code (.ts, .tsx, .mts files). Also use when reviewing code quality or implementing type-safe patterns. Triggers on any TypeScript file edit, code style discussions, or type safety questions.
|
||||
---
|
||||
|
||||
# TypeScript Code Style Guide
|
||||
@@ -29,16 +28,12 @@ user-invocable: false
|
||||
## Imports
|
||||
|
||||
- This project uses `simple-import-sort/imports` and `consistent-type-imports` (`fixStyle: 'separate-type-imports'`)
|
||||
|
||||
- **Separate type imports**: always use `import type { ... }` for type-only imports, NOT `import { type ... }` inline syntax
|
||||
|
||||
- When a file already has `import type { ... }` from a package and you need to add a value import, keep them as **two separate statements**:
|
||||
|
||||
```ts
|
||||
import type { ChatTopicBotContext } from '@lobechat/types';
|
||||
import { RequestTrigger } from '@lobechat/types';
|
||||
```
|
||||
|
||||
- Within each import statement, specifiers are sorted **alphabetically by name**
|
||||
|
||||
## Code Structure
|
||||
@@ -47,7 +42,6 @@ user-invocable: false
|
||||
- Use consistent, descriptive naming; avoid obscure abbreviations
|
||||
- Replace magic numbers/strings with well-named constants
|
||||
- Defer formatting to tooling
|
||||
- Prefer **named exports** over `export default` — keeps refactor renames and IDE auto-import in sync, and avoids the `default` re-naming drift you get with `import Foo from './foo'`. Reserve `export default` for files where the framework requires it (Next.js page/route/layout, React.lazy targets, config files like `vitest.config.ts`)
|
||||
|
||||
## UI and Theming
|
||||
|
||||
@@ -57,6 +51,7 @@ user-invocable: false
|
||||
|
||||
## Performance
|
||||
|
||||
- Prefer `for…of` loops over index-based `for` loops
|
||||
- Reuse existing utils in `packages/utils` or installed npm packages
|
||||
- Query only required columns from database
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+8
-22
@@ -1,20 +1,6 @@
|
||||
# Cloud Project Workflow Configuration
|
||||
|
||||
Cloud-specific workflow configurations and patterns for the lobehub-cloud project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Directory Structure](#directory-structure) — submodule + cloud layout
|
||||
3. [Cloud-Specific Patterns](#cloud-specific-patterns) — cloud-only workflows + re-export pattern
|
||||
4. [TypeScript Path Mappings](#typescript-path-mappings)
|
||||
5. [Workflow Class Location](#workflow-class-location) — cloud-only vs shared
|
||||
6. [Environment Variables](#environment-variables)
|
||||
7. [Best Practices](#best-practices) — decide cloud vs OSS, re-export rules, naming
|
||||
8. [Migration Guide](#migration-guide) — moving workflows from cloud to lobehub
|
||||
9. [Examples](#examples) — `welcome-placeholder`, `agent-eval-run`
|
||||
10. [Troubleshooting](#troubleshooting) — circular imports, 404s, type errors
|
||||
11. [Related Documentation](#related-documentation)
|
||||
This document covers cloud-specific workflow configurations and patterns for the lobehub-cloud project.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -29,7 +15,7 @@ The lobehub-cloud project extends the open-source lobehub codebase with cloud-sp
|
||||
|
||||
### Lobehub Submodule (Open-source)
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub/
|
||||
└── src/
|
||||
├── app/(backend)/api/workflows/
|
||||
@@ -42,7 +28,7 @@ lobehub/
|
||||
|
||||
### Lobehub-cloud (Proprietary)
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub-cloud/
|
||||
└── src/
|
||||
├── app/(backend)/api/workflows/
|
||||
@@ -74,7 +60,7 @@ lobehub-cloud/
|
||||
|
||||
**Structure**:
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub-cloud/src/
|
||||
├── app/(backend)/api/workflows/
|
||||
│ └── feature-name/
|
||||
@@ -176,7 +162,7 @@ This allows cloud to override specific modules while using lobehub defaults.
|
||||
|
||||
Place workflow class in cloud:
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub-cloud/src/server/workflows/featureName/index.ts
|
||||
```
|
||||
|
||||
@@ -184,7 +170,7 @@ lobehub-cloud/src/server/workflows/featureName/index.ts
|
||||
|
||||
Place workflow class in lobehub, re-export in cloud if needed:
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub/src/server/workflows/featureName/index.ts
|
||||
```
|
||||
|
||||
@@ -259,7 +245,7 @@ For shared features:
|
||||
|
||||
Follow consistent naming across lobehub and cloud:
|
||||
|
||||
```text
|
||||
```
|
||||
# Both should use same structure
|
||||
lobehub/src/app/(backend)/api/workflows/feature-name/
|
||||
lobehub-cloud/src/app/(backend)/api/workflows/feature-name/
|
||||
@@ -320,7 +306,7 @@ import { Workflow } from 'lobehub/src/server/workflows/feature';
|
||||
|
||||
**Structure**:
|
||||
|
||||
```text
|
||||
```
|
||||
lobehub-cloud/
|
||||
├── src/app/(backend)/api/workflows/welcome-placeholder/
|
||||
│ ├── process-users/route.ts
|
||||
@@ -1,226 +0,0 @@
|
||||
# Best Practices & Common Pitfalls
|
||||
|
||||
Apply these once your scaffold from `implementation.md` is in place.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Error Handling](#1-error-handling)
|
||||
2. [Logging](#2-logging)
|
||||
3. [Return Values](#3-return-values)
|
||||
4. [flowControl Configuration](#4-flowcontrol-configuration)
|
||||
5. [context.run() Best Practices](#5-contextrun-best-practices)
|
||||
6. [Payload Validation](#6-payload-validation)
|
||||
7. [Database Connection](#7-database-connection)
|
||||
8. [Testing](#8-testing)
|
||||
9. [Common Pitfalls](#common-pitfalls)
|
||||
|
||||
---
|
||||
|
||||
## 1. Error Handling
|
||||
|
||||
```typescript
|
||||
export const { POST } = serve<Payload>(
|
||||
async (context) => {
|
||||
const { itemId } = context.requestPayload ?? {};
|
||||
|
||||
if (!itemId) {
|
||||
return { success: false, error: 'Missing itemId in payload' };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await context.run('step-name', () => doWork(itemId));
|
||||
return { success: true, itemId, result };
|
||||
} catch (error) {
|
||||
console.error('[workflow:error]', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
},
|
||||
{ flowControl: { ... } },
|
||||
);
|
||||
```
|
||||
|
||||
## 2. Logging
|
||||
|
||||
Consistent prefixes make debugging much easier across QStash dashboards and grep:
|
||||
|
||||
```typescript
|
||||
console.log('[{workflow}:{layer}] Starting with payload:', payload);
|
||||
console.log('[{workflow}:{layer}] Processing items:', { count: items.length });
|
||||
console.log('[{workflow}:{layer}] Completed:', result);
|
||||
console.error('[{workflow}:{layer}:error]', error);
|
||||
```
|
||||
|
||||
## 3. Return Values
|
||||
|
||||
Pick the shape that matches the layer's purpose — entry points return statistics, execution layers return per-item results.
|
||||
|
||||
```typescript
|
||||
// Success
|
||||
return { success: true, itemId, result, message: 'Optional success message' };
|
||||
|
||||
// Error
|
||||
return { success: false, error: 'Error description', itemId };
|
||||
|
||||
// Statistics (entry point)
|
||||
return {
|
||||
success: true,
|
||||
totalEligible: 100,
|
||||
toProcess: 80,
|
||||
alreadyProcessed: 20,
|
||||
dryRun: true, // if applicable
|
||||
message: 'Summary message',
|
||||
};
|
||||
```
|
||||
|
||||
## 4. flowControl Configuration
|
||||
|
||||
Tune concurrency by layer — entry points are singletons, execution layers fan out.
|
||||
|
||||
```typescript
|
||||
// Layer 1: Entry — single instance to avoid duplicate processing
|
||||
flowControl: { key: '{workflow}.process', parallelism: 1, ratePerSecond: 1 }
|
||||
|
||||
// Layer 2: Pagination — moderate concurrency
|
||||
flowControl: { key: '{workflow}.paginate', parallelism: 20, ratePerSecond: 5 }
|
||||
|
||||
// Layer 3: Execution — higher concurrency for parallel item work
|
||||
flowControl: { key: '{workflow}.execute', parallelism: 10, ratePerSecond: 5 }
|
||||
```
|
||||
|
||||
**Why these defaults:**
|
||||
|
||||
- **Layer 1** always uses `parallelism: 1` so concurrent triggers don't both start the same batch.
|
||||
- **Layer 2** can fan out widely (10-20) since pagination is cheap.
|
||||
- **Layer 3** caps at 5-10 by default; raise/lower based on external API rate limits.
|
||||
|
||||
## 5. context.run() Best Practices
|
||||
|
||||
- Use descriptive step names with prefixes: `{workflow}:step-name`
|
||||
- Each step should be idempotent (safe to retry)
|
||||
- Don't nest `context.run()` calls — keep them flat
|
||||
- Use unique step names when processing multiple items:
|
||||
|
||||
```typescript
|
||||
// ✅ Unique step names
|
||||
await Promise.all(
|
||||
items.map((item) => context.run(`{workflow}:execute:${item.id}`, () => processItem(item))),
|
||||
);
|
||||
|
||||
// ❌ Same step name — Upstash de-dupes by step name and you'll lose data
|
||||
await Promise.all(items.map((item) => context.run(`{workflow}:execute`, () => processItem(item))));
|
||||
```
|
||||
|
||||
## 6. Payload Validation
|
||||
|
||||
Validate at the top so failures are explicit, not silent `undefined` cascades:
|
||||
|
||||
```typescript
|
||||
export const { POST } = serve<Payload>(
|
||||
async (context) => {
|
||||
const { itemId, configId } = context.requestPayload ?? {};
|
||||
|
||||
if (!itemId) return { success: false, error: 'Missing itemId in payload' };
|
||||
if (!configId) return { success: false, error: 'Missing configId in payload' };
|
||||
|
||||
// Proceed with work...
|
||||
},
|
||||
{ flowControl: { ... } },
|
||||
);
|
||||
```
|
||||
|
||||
## 7. Database Connection
|
||||
|
||||
Get the connection once per workflow — `getServerDB()` is async, repeating it inside each step adds latency:
|
||||
|
||||
```typescript
|
||||
export const { POST } = serve<Payload>(
|
||||
async (context) => {
|
||||
const db = await getServerDB();
|
||||
|
||||
const item = await context.run('get-item', () => itemModel.findById(db, itemId));
|
||||
const result = await context.run('save-result', () => resultModel.create(db, result));
|
||||
},
|
||||
{ flowControl: { ... } },
|
||||
);
|
||||
```
|
||||
|
||||
## 8. Testing
|
||||
|
||||
Integration tests should exercise both the dry-run statistics path and the full execution path:
|
||||
|
||||
```typescript
|
||||
describe('WorkflowName', () => {
|
||||
it('should process items successfully', async () => {
|
||||
const items = await createTestItems();
|
||||
await WorkflowClass.triggerProcessItems({ dryRun: false });
|
||||
await waitForCompletion();
|
||||
const results = await getResults();
|
||||
expect(results).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
it('should support dryRun mode', async () => {
|
||||
const result = await WorkflowClass.triggerProcessItems({ dryRun: true });
|
||||
expect(result).toMatchObject({
|
||||
success: true,
|
||||
dryRun: true,
|
||||
totalEligible: expect.any(Number),
|
||||
toProcess: expect.any(Number),
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### ❌ Reusing `context.run()` step names
|
||||
|
||||
```typescript
|
||||
// Bad — Upstash dedupes by step name
|
||||
await Promise.all(items.map((item) => context.run('process', () => process(item))));
|
||||
|
||||
// Good
|
||||
await Promise.all(items.map((item) => context.run(`process:${item.id}`, () => process(item))));
|
||||
```
|
||||
|
||||
### ❌ Skipping payload validation
|
||||
|
||||
```typescript
|
||||
// Bad — undefined cascades into a confusing failure later
|
||||
const { itemId } = context.requestPayload ?? {};
|
||||
const result = await process(itemId);
|
||||
|
||||
// Good — fail fast with a clear error
|
||||
if (!itemId) return { success: false, error: 'Missing itemId' };
|
||||
```
|
||||
|
||||
### ❌ Skipping the filter step
|
||||
|
||||
```typescript
|
||||
// Bad — duplicates work for items that were already processed
|
||||
const allItems = await getAllItems();
|
||||
await Promise.all(allItems.map((item) => triggerExecute(item)));
|
||||
|
||||
// Good — keeps the pipeline idempotent
|
||||
const allItems = await getAllItems();
|
||||
const itemsNeedingProcessing = await filterExisting(allItems);
|
||||
await Promise.all(itemsNeedingProcessing.map((item) => triggerExecute(item)));
|
||||
```
|
||||
|
||||
### ❌ Inconsistent logging
|
||||
|
||||
```typescript
|
||||
// Bad — different prefixes, mixed formats
|
||||
console.log('Starting workflow');
|
||||
log.info('Processing item:', itemId);
|
||||
console.log(`Done with ${itemId}`);
|
||||
|
||||
// Good — uniform prefix lets you grep by workflow+layer
|
||||
console.log('[workflow:layer] Starting with payload:', payload);
|
||||
console.log('[workflow:layer] Processing item:', { itemId });
|
||||
console.log('[workflow:layer] Completed:', { itemId, result });
|
||||
```
|
||||
@@ -1,91 +0,0 @@
|
||||
# Worked Examples
|
||||
|
||||
Two real workflows already in the codebase that follow this skill's pattern verbatim. Skim them when you want to see the pattern applied to concrete entities.
|
||||
|
||||
## Example 1: Welcome Placeholder
|
||||
|
||||
**Use case:** Generate AI-powered welcome placeholders for users.
|
||||
|
||||
**Structure:**
|
||||
|
||||
- Layer 1: `process-users` — entry point, checks eligible users
|
||||
- Layer 2: `paginate-users` — paginates through active users
|
||||
- Layer 3: `generate-user` — generates placeholders for ONE user
|
||||
|
||||
**Key features:**
|
||||
|
||||
- Filters users who already have cached placeholders in Redis
|
||||
- `paidOnly` flag to scope to subscribed users
|
||||
- `dryRun` mode for statistics
|
||||
- Fan-out for large user batches (`CHUNK_SIZE=20`)
|
||||
|
||||
**Layer 3 shape:**
|
||||
|
||||
```typescript
|
||||
export const { POST } = serve<GenerateUserPlaceholderPayload>(async (context) => {
|
||||
const { userId } = context.requestPayload ?? {};
|
||||
|
||||
const workflow = new WelcomePlaceholderWorkflow(db, userId);
|
||||
const placeholders = await context.run('generate', () => workflow.generate());
|
||||
|
||||
return { success: true, userId, placeholdersCount: placeholders.length };
|
||||
});
|
||||
```
|
||||
|
||||
**Files:**
|
||||
|
||||
- `/api/workflows/welcome-placeholder/process-users/route.ts`
|
||||
- `/api/workflows/welcome-placeholder/paginate-users/route.ts`
|
||||
- `/api/workflows/welcome-placeholder/generate-user/route.ts`
|
||||
- `/server/workflows/welcomePlaceholder/index.ts`
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Agent Welcome
|
||||
|
||||
**Use case:** Generate welcome messages and open questions for AI agents.
|
||||
|
||||
**Structure:**
|
||||
|
||||
- Layer 1: `process-agents` — entry point, checks eligible agents
|
||||
- Layer 2: `paginate-agents` — paginates through active agents
|
||||
- Layer 3: `generate-agent` — generates welcome data for ONE agent
|
||||
|
||||
**Key features:**
|
||||
|
||||
- Filters agents who already have cached data in Redis
|
||||
- `paidOnly` flag for subscribed users' agents only
|
||||
- `dryRun` mode for statistics
|
||||
- Fan-out for large agent batches (`CHUNK_SIZE=20`)
|
||||
|
||||
**Layer 3 shape:**
|
||||
|
||||
```typescript
|
||||
export const { POST } = serve<GenerateAgentWelcomePayload>(async (context) => {
|
||||
const { agentId } = context.requestPayload ?? {};
|
||||
|
||||
const workflow = new AgentWelcomeWorkflow(db, agentId);
|
||||
const data = await context.run('generate', () => workflow.generate());
|
||||
|
||||
return { success: true, agentId, data };
|
||||
});
|
||||
```
|
||||
|
||||
**Files:**
|
||||
|
||||
- `/api/workflows/agent-welcome/process-agents/route.ts`
|
||||
- `/api/workflows/agent-welcome/paginate-agents/route.ts`
|
||||
- `/api/workflows/agent-welcome/generate-agent/route.ts`
|
||||
- `/server/workflows/agentWelcome/index.ts`
|
||||
|
||||
---
|
||||
|
||||
## What's identical, what differs
|
||||
|
||||
Both workflows are the **same pattern** — they only differ in:
|
||||
|
||||
- Entity type (users vs agents)
|
||||
- Business logic (placeholder generation vs welcome generation)
|
||||
- Data source (different database queries)
|
||||
|
||||
Everything else — the 3-layer split, dry-run handling, fan-out, filter-existing, flowControl tuning — is identical. That's the whole point: once you internalize the pattern, adding a new workflow is mostly entity-substitution.
|
||||
@@ -1,333 +0,0 @@
|
||||
# Implementation Patterns
|
||||
|
||||
Full code templates for the 3-layer architecture. Read this when actually writing workflow files.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Workflow Class](#workflow-class) — `src/server/workflows/{workflowName}/index.ts`
|
||||
2. [Layer 1: Entry Point](#layer-1-entry-point-process-) — `process-*` route
|
||||
3. [Layer 2: Pagination](#layer-2-pagination-paginate-) — `paginate-*` route
|
||||
4. [Layer 3: Execution](#layer-3-execution-execute--generate-) — `execute-*` / `generate-*` route
|
||||
|
||||
---
|
||||
|
||||
## Workflow Class
|
||||
|
||||
**Location:** `src/server/workflows/{workflowName}/index.ts`
|
||||
|
||||
```typescript
|
||||
import { Client } from '@upstash/workflow';
|
||||
import debug from 'debug';
|
||||
|
||||
const log = debug('lobe-server:workflows:{workflow-name}');
|
||||
|
||||
// Workflow paths
|
||||
const WORKFLOW_PATHS = {
|
||||
processItems: '/api/workflows/{workflow-name}/process-items',
|
||||
paginateItems: '/api/workflows/{workflow-name}/paginate-items',
|
||||
executeItem: '/api/workflows/{workflow-name}/execute-item',
|
||||
} as const;
|
||||
|
||||
// Payload types
|
||||
export interface ProcessItemsPayload {
|
||||
dryRun?: boolean;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export interface PaginateItemsPayload {
|
||||
cursor?: string;
|
||||
itemIds?: string[]; // For fanout chunks
|
||||
}
|
||||
|
||||
export interface ExecuteItemPayload {
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
const getWorkflowUrl = (path: string): string => {
|
||||
const baseUrl = process.env.APP_URL;
|
||||
if (!baseUrl) throw new Error('APP_URL is required to trigger workflows');
|
||||
return new URL(path, baseUrl).toString();
|
||||
};
|
||||
|
||||
const getWorkflowClient = (): Client => {
|
||||
const token = process.env.QSTASH_TOKEN;
|
||||
if (!token) throw new Error('QSTASH_TOKEN is required to trigger workflows');
|
||||
|
||||
const config: ConstructorParameters<typeof Client>[0] = { token };
|
||||
if (process.env.QSTASH_URL) {
|
||||
(config as Record<string, unknown>).url = process.env.QSTASH_URL;
|
||||
}
|
||||
return new Client(config);
|
||||
};
|
||||
|
||||
export class {WorkflowName}Workflow {
|
||||
private static client: Client;
|
||||
|
||||
private static getClient(): Client {
|
||||
if (!this.client) this.client = getWorkflowClient();
|
||||
return this.client;
|
||||
}
|
||||
|
||||
static triggerProcessItems(payload: ProcessItemsPayload) {
|
||||
const url = getWorkflowUrl(WORKFLOW_PATHS.processItems);
|
||||
log('Triggering process-items workflow');
|
||||
return this.getClient().trigger({ body: payload, url });
|
||||
}
|
||||
|
||||
static triggerPaginateItems(payload: PaginateItemsPayload) {
|
||||
const url = getWorkflowUrl(WORKFLOW_PATHS.paginateItems);
|
||||
log('Triggering paginate-items workflow');
|
||||
return this.getClient().trigger({ body: payload, url });
|
||||
}
|
||||
|
||||
static triggerExecuteItem(payload: ExecuteItemPayload) {
|
||||
const url = getWorkflowUrl(WORKFLOW_PATHS.executeItem);
|
||||
log('Triggering execute-item workflow: %s', payload.itemId);
|
||||
return this.getClient().trigger({ body: payload, url });
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter items that need processing (e.g. check Redis cache, database state).
|
||||
* Return only the ones that actually need work — keeps the pipeline idempotent.
|
||||
*/
|
||||
static async filterItemsNeedingProcessing(itemIds: string[]): Promise<string[]> {
|
||||
if (itemIds.length === 0) return [];
|
||||
// Check existing state and return items that need processing
|
||||
return itemIds;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer 1: Entry Point (process-\*)
|
||||
|
||||
**Purpose:** Validates prerequisites, calculates statistics, supports dry-run mode.
|
||||
|
||||
```typescript
|
||||
import { serve } from '@upstash/workflow/nextjs';
|
||||
import { getServerDB } from '@/database/server';
|
||||
import { WorkflowClass, type ProcessPayload } from '@/server/workflows/{workflowName}';
|
||||
|
||||
export const { POST } = serve<ProcessPayload>(
|
||||
async (context) => {
|
||||
const { dryRun, force } = context.requestPayload ?? {};
|
||||
|
||||
console.log('[{workflow}:process] Starting with payload:', { dryRun, force });
|
||||
|
||||
const allItemIds = await context.run('{workflow}:get-all-items', async () => {
|
||||
const db = await getServerDB();
|
||||
// Query database for eligible items
|
||||
return items.map((item) => item.id);
|
||||
});
|
||||
|
||||
console.log('[{workflow}:process] Total eligible items:', allItemIds.length);
|
||||
|
||||
if (allItemIds.length === 0) {
|
||||
return { success: true, totalEligible: 0, message: 'No eligible items found' };
|
||||
}
|
||||
|
||||
const itemsNeedingProcessing = await context.run('{workflow}:filter-existing', () =>
|
||||
WorkflowClass.filterItemsNeedingProcessing(allItemIds),
|
||||
);
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
totalEligible: allItemIds.length,
|
||||
toProcess: itemsNeedingProcessing.length,
|
||||
alreadyProcessed: allItemIds.length - itemsNeedingProcessing.length,
|
||||
};
|
||||
|
||||
// Dry-run short-circuits before any side effects
|
||||
if (dryRun) {
|
||||
console.log('[{workflow}:process] Dry run mode, returning statistics only');
|
||||
return {
|
||||
...result,
|
||||
dryRun: true,
|
||||
message: `[DryRun] Would process ${itemsNeedingProcessing.length} items`,
|
||||
};
|
||||
}
|
||||
|
||||
if (itemsNeedingProcessing.length === 0) {
|
||||
return { ...result, message: 'All items already processed' };
|
||||
}
|
||||
|
||||
await context.run('{workflow}:trigger-paginate', () => WorkflowClass.triggerPaginateItems({}));
|
||||
|
||||
return {
|
||||
...result,
|
||||
message: `Triggered pagination for ${itemsNeedingProcessing.length} items`,
|
||||
};
|
||||
},
|
||||
{
|
||||
flowControl: {
|
||||
key: '{workflow}.process',
|
||||
parallelism: 1, // single instance — avoids duplicate processing
|
||||
ratePerSecond: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer 2: Pagination (paginate-\*)
|
||||
|
||||
**Purpose:** Handles cursor-based pagination, implements fan-out for large batches.
|
||||
|
||||
```typescript
|
||||
import { serve } from '@upstash/workflow/nextjs';
|
||||
import { chunk } from 'es-toolkit/compat';
|
||||
import { getServerDB } from '@/database/server';
|
||||
import { WorkflowClass, type PaginatePayload } from '@/server/workflows/{workflowName}';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
const CHUNK_SIZE = 20;
|
||||
|
||||
export const { POST } = serve<PaginatePayload>(
|
||||
async (context) => {
|
||||
const { cursor, itemIds: payloadItemIds } = context.requestPayload ?? {};
|
||||
|
||||
console.log('[{workflow}:paginate] Starting:', {
|
||||
cursor,
|
||||
itemIdsCount: payloadItemIds?.length ?? 0,
|
||||
});
|
||||
|
||||
// If specific itemIds were passed in (from a fanout chunk), process them directly
|
||||
if (payloadItemIds && payloadItemIds.length > 0) {
|
||||
await Promise.all(
|
||||
payloadItemIds.map((itemId) =>
|
||||
context.run(`{workflow}:execute:${itemId}`, () =>
|
||||
WorkflowClass.triggerExecuteItem({ itemId }),
|
||||
),
|
||||
),
|
||||
);
|
||||
return { success: true, processedItems: payloadItemIds.length };
|
||||
}
|
||||
|
||||
// Paginate through all items
|
||||
const itemBatch = await context.run('{workflow}:get-batch', async () => {
|
||||
const db = await getServerDB();
|
||||
const items = await db.query(...);
|
||||
if (!items.length) return { ids: [] };
|
||||
const last = items.at(-1);
|
||||
return {
|
||||
ids: items.map((item) => item.id),
|
||||
cursor: last ? last.id : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
const batchItemIds = itemBatch.ids;
|
||||
const nextCursor = 'cursor' in itemBatch ? itemBatch.cursor : undefined;
|
||||
|
||||
if (batchItemIds.length === 0) {
|
||||
return { success: true, message: 'Pagination complete' };
|
||||
}
|
||||
|
||||
const itemIds = await context.run('{workflow}:filter-existing', () =>
|
||||
WorkflowClass.filterItemsNeedingProcessing(batchItemIds),
|
||||
);
|
||||
|
||||
if (itemIds.length > 0) {
|
||||
if (itemIds.length > CHUNK_SIZE) {
|
||||
// Fan out — recursively re-enter pagination with each chunk
|
||||
const chunks = chunk(itemIds, CHUNK_SIZE);
|
||||
console.log('[{workflow}:paginate] Fanout mode:', {
|
||||
chunks: chunks.length,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
chunks.map((ids, idx) =>
|
||||
context.run(`{workflow}:fanout:${idx + 1}/${chunks.length}`, () =>
|
||||
WorkflowClass.triggerPaginateItems({ itemIds: ids }),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Process this page directly
|
||||
await Promise.all(
|
||||
itemIds.map((itemId) =>
|
||||
context.run(`{workflow}:execute:${itemId}`, () =>
|
||||
WorkflowClass.triggerExecuteItem({ itemId }),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tail-call into the next page
|
||||
if (nextCursor) {
|
||||
await context.run('{workflow}:next-page', () =>
|
||||
WorkflowClass.triggerPaginateItems({ cursor: nextCursor }),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
processedItems: itemIds.length,
|
||||
skippedItems: batchItemIds.length - itemIds.length,
|
||||
nextCursor: nextCursor ?? null,
|
||||
};
|
||||
},
|
||||
{
|
||||
flowControl: {
|
||||
key: '{workflow}.paginate',
|
||||
parallelism: 20,
|
||||
ratePerSecond: 5,
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer 3: Execution (execute-\* / generate-\*)
|
||||
|
||||
**Purpose:** Performs the actual business logic for exactly ONE item.
|
||||
|
||||
```typescript
|
||||
import { serve } from '@upstash/workflow/nextjs';
|
||||
import { getServerDB } from '@/database/server';
|
||||
import { WorkflowClass, type ExecutePayload } from '@/server/workflows/{workflowName}';
|
||||
|
||||
export const { POST } = serve<ExecutePayload>(
|
||||
async (context) => {
|
||||
const { itemId } = context.requestPayload ?? {};
|
||||
|
||||
if (!itemId) {
|
||||
return { success: false, error: 'Missing itemId' };
|
||||
}
|
||||
|
||||
const db = await getServerDB();
|
||||
|
||||
const item = await context.run('{workflow}:get-item', async () => {
|
||||
// Query database for item
|
||||
return item;
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
return { success: false, error: 'Item not found' };
|
||||
}
|
||||
|
||||
const result = await context.run('{workflow}:process-item', async () => {
|
||||
const workflow = new WorkflowClass(db, itemId);
|
||||
return workflow.generate(); // or process(), execute(), etc.
|
||||
});
|
||||
|
||||
await context.run('{workflow}:save-result', async () => {
|
||||
const workflow = new WorkflowClass(db, itemId);
|
||||
return workflow.saveToRedis(result); // or saveToDatabase(), etc.
|
||||
});
|
||||
|
||||
return { success: true, itemId, result };
|
||||
},
|
||||
{
|
||||
flowControl: {
|
||||
key: '{workflow}.execute',
|
||||
parallelism: 10,
|
||||
ratePerSecond: 5,
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
name: version-release
|
||||
description: "Version release workflow. Use when the user mentions 'release', 'hotfix', 'version upgrade', 'weekly release', or '发版'/'发布'/'小班车'. This skill is for release process and GitHub Release notes (not docs/changelog page writing)."
|
||||
disable-model-invocation: true
|
||||
argument-hint: '[minor|patch] [version?]'
|
||||
---
|
||||
|
||||
# Version Release Workflow
|
||||
|
||||
This skill is a router. The detailed steps live in `references/`.
|
||||
This skill is a router. The detailed steps live in `reference/`.
|
||||
|
||||
## Scope Boundary (Important)
|
||||
|
||||
@@ -32,12 +30,12 @@ The primary development branch is **canary**. All day-to-day development happens
|
||||
|
||||
Only two release types are used in practice (major releases are extremely rare and can be ignored):
|
||||
|
||||
| Type | Use Case | Frequency | Source Branch | PR Title Format | Version | Reference |
|
||||
| ----- | ---------------------------------------------- | --------------------- | -------------- | ------------------------------------ | ------------- | --------------------------------------- |
|
||||
| Minor | Feature iteration release | \~Every 4 weeks | canary | `🚀 release: v{x.y.0}` | Manually set | `references/minor-release.md` |
|
||||
| Patch | Weekly release / hotfix / model / DB migration | \~Weekly or as needed | canary or main | Custom (e.g. `🚀 release: 20260222`) | Auto patch +1 | `references/patch-release-scenarios.md` |
|
||||
| Type | Use Case | Frequency | Source Branch | PR Title Format | Version | Reference |
|
||||
| ----- | ---------------------------------------------- | --------------------- | -------------- | ------------------------------------ | ------------- | -------------------------------------- |
|
||||
| Minor | Feature iteration release | \~Every 4 weeks | canary | `🚀 release: v{x.y.0}` | Manually set | `reference/minor-release.md` |
|
||||
| Patch | Weekly release / hotfix / model / DB migration | \~Weekly or as needed | canary or main | Custom (e.g. `🚀 release: 20260222`) | Auto patch +1 | `reference/patch-release-scenarios.md` |
|
||||
|
||||
For writing the release-note body (any release type), see `references/release-notes-style.md`.
|
||||
For writing the release-note body (any release type), see `reference/release-notes-style.md`.
|
||||
|
||||
## Auto-Release Trigger Rules (`auto-tag-release.yml`)
|
||||
|
||||
@@ -87,9 +85,9 @@ Before creating the release branch, verify the source branch:
|
||||
|
||||
Pick the right reference and follow it end-to-end:
|
||||
|
||||
- **Minor release** → `references/minor-release.md`
|
||||
- **Patch release** (weekly / hotfix / model launch / DB migration) → `references/patch-release-scenarios.md`
|
||||
- **Writing the PR body / release notes** (any release type) → `references/release-notes-style.md`
|
||||
- **Minor release** → `reference/minor-release.md`
|
||||
- **Patch release** (weekly / hotfix / model launch / DB migration) → `reference/patch-release-scenarios.md`
|
||||
- **Writing the PR body / release notes** (any release type) → `reference/release-notes-style.md`
|
||||
|
||||
### Hard Rules (apply to every release type)
|
||||
|
||||
@@ -97,4 +95,4 @@ Pick the right reference and follow it end-to-end:
|
||||
- **Do NOT** manually create tags — CI handles them.
|
||||
- Minor PR title format is strict (`🚀 release: v{x.y.z}`).
|
||||
- Patch PRs do not need an explicit version number.
|
||||
- Keep release facts accurate; do not invent metrics or availability statements. Release-note inputs (compare base, PR refs, contributor list) **must be derived from `git`** per `references/release-notes-style.md` § Computing Inputs — never from memory or descriptions.
|
||||
- Keep release facts accurate; do not invent metrics or availability statements. Release-note inputs (compare base, PR refs, contributor list) **must be derived from `git`** per `reference/release-notes-style.md` § Computing Inputs — never from memory or descriptions.
|
||||
|
||||
-14
@@ -2,20 +2,6 @@
|
||||
|
||||
Use this guide for **GitHub Release notes** — the body of a release PR that becomes the GitHub Release after merge. Do **not** use it for `docs/changelog/*.mdx` website pages (load `../../docs-changelog/SKILL.md` instead).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Positioning](#positioning) — what this style optimizes for
|
||||
2. [Required Inputs Before Writing](#required-inputs-before-writing)
|
||||
3. [Computing Inputs (Hard Rules — Verify, Never Guess)](#computing-inputs-hard-rules--verify-never-guess) — base ref, PR refs, metrics, authors, pre-publish verification
|
||||
4. [Canonical Structure (Long-Form: Minor / Weekly)](#canonical-structure-long-form-minor--weekly)
|
||||
5. [Variants for Shorter Releases](#variants-for-shorter-releases) — hotfix, DB migration
|
||||
6. [Writing Rules (Hard)](#writing-rules-hard)
|
||||
7. [Style Rules (Long-Form)](#style-rules-long-form)
|
||||
8. [Release Size Heuristics](#release-size-heuristics) — when to use which variant
|
||||
9. [Contributor Ordering](#contributor-ordering)
|
||||
10. [Template](#template) — copy-paste skeleton
|
||||
11. [Quick Checklist](#quick-checklist) — long-form + hotfix
|
||||
|
||||
## Positioning
|
||||
|
||||
This release-note style is:
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: zustand
|
||||
description: "LobeHub Zustand store conventions: public/internal/dispatch action layers, optimistic update pattern, slice composition via `flattenActions`, and class-based action migration. Use whenever working under `src/store/**`, adding a `createXxxSlice`, writing `internal_*` or `internal_dispatch*` actions, designing `messagesMap`/`topicsMap` reducers, refactoring a `StateCreator` object slice into a `XxxActionImpl` class, or debugging stale store reads. Triggers on `useChatStore`/`useUserStore`/`useGlobalStore`, `createStore`, `flattenActions`, `StoreSetter`, `internal_dispatch`, 'add an action', 'zustand selector', 'store slice', 'class action', 'optimistic update'."
|
||||
user-invocable: false
|
||||
description: Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.
|
||||
---
|
||||
|
||||
# LobeHub Zustand State Management
|
||||
|
||||
@@ -2,43 +2,6 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## [Version 2.1.57](https://github.com/lobehub/lobe-chat/compare/v2.1.57-canary.33...v2.1.57)
|
||||
|
||||
<sup>Released on **2026-05-09**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **docker**: replace pnpm init with static package.json in /deps.
|
||||
- **onboarding**: guard skip/mode-switch footer with feature flag, desktop & init checks.
|
||||
- **misc**: hide runtime-only model aliases.
|
||||
|
||||
#### ✨ Features
|
||||
|
||||
- **misc**: set OSS default model to DeepSeek V4 Pro.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **docker**: replace pnpm init with static package.json in /deps, closes [#14576](https://github.com/lobehub/lobe-chat/issues/14576) ([8ed31df](https://github.com/lobehub/lobe-chat/commit/8ed31df))
|
||||
- **onboarding**: guard skip/mode-switch footer with feature flag, desktop & init checks, closes [#14560](https://github.com/lobehub/lobe-chat/issues/14560) ([9756dab](https://github.com/lobehub/lobe-chat/commit/9756dab))
|
||||
- **misc**: hide runtime-only model aliases, closes [#14552](https://github.com/lobehub/lobe-chat/issues/14552) ([2d33322](https://github.com/lobehub/lobe-chat/commit/2d33322))
|
||||
|
||||
#### What's improved
|
||||
|
||||
- **misc**: set OSS default model to DeepSeek V4 Pro, closes [#14555](https://github.com/lobehub/lobe-chat/issues/14555) ([8105fc0](https://github.com/lobehub/lobe-chat/commit/8105fc0))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.56](https://github.com/lobehub/lobe-chat/compare/v2.1.55...v2.1.56)
|
||||
|
||||
<sup>Released on **2026-05-01**</sup>
|
||||
|
||||
@@ -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.15" "User Commands"
|
||||
.TH LH 1 "" "@lobehub/cli 0.0.14" "User Commands"
|
||||
.SH NAME
|
||||
lh \- LobeHub CLI \- manage and connect to LobeHub services
|
||||
.SH SYNOPSIS
|
||||
@@ -68,6 +68,9 @@ Manage agent groups
|
||||
.B bot
|
||||
Manage bot integrations
|
||||
.TP
|
||||
.B cron
|
||||
Manage agent cron jobs
|
||||
.TP
|
||||
.B generate
|
||||
Generate content (text, image, video, speech) Alias: gen.
|
||||
.TP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lobehub/cli",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.14",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"lh": "./dist/index.js",
|
||||
|
||||
@@ -318,7 +318,7 @@ export function registerAgentCommand(program: Command) {
|
||||
}
|
||||
|
||||
// 1. Exec agent to get operationId
|
||||
const input: Record<string, any> = { prompt: options.prompt, trigger: 'cli' };
|
||||
const input: Record<string, any> = { prompt: options.prompt };
|
||||
if (options.agentId) input.agentId = options.agentId;
|
||||
if (deviceId) input.deviceId = deviceId;
|
||||
if (options.slug) input.slug = options.slug;
|
||||
|
||||
@@ -55,7 +55,7 @@ export function registerBriefCommand(program: Command) {
|
||||
typeBadge(b.type, b.priority),
|
||||
truncate(b.title, 40),
|
||||
truncate(b.summary, 50),
|
||||
b.taskId ? pc.dim(b.taskId) : '-',
|
||||
b.taskId ? pc.dim(b.taskId) : b.cronJobId ? pc.dim(b.cronJobId) : '-',
|
||||
b.resolvedAt ? pc.green('resolved') : b.readAt ? pc.dim('read') : 'new',
|
||||
timeAgo(b.createdAt),
|
||||
]);
|
||||
@@ -102,6 +102,7 @@ export function registerBriefCommand(program: Command) {
|
||||
console.log(`${pc.dim('Type:')} ${b.type} ${pc.dim('Created:')} ${timeAgo(b.createdAt)}`);
|
||||
if (b.agentId) console.log(`${pc.dim('Agent:')} ${b.agentId}`);
|
||||
if (b.taskId) console.log(`${pc.dim('Task:')} ${b.taskId}`);
|
||||
if (b.cronJobId) console.log(`${pc.dim('CronJob:')} ${b.cronJobId}`);
|
||||
if (b.topicId) console.log(`${pc.dim('Topic:')} ${b.topicId}`);
|
||||
console.log(`\n${b.summary}`);
|
||||
|
||||
@@ -120,14 +121,14 @@ export function registerBriefCommand(program: Command) {
|
||||
for (const a of actions) {
|
||||
const cmd =
|
||||
a.type === 'comment'
|
||||
? `lh brief resolve ${b.id} --action ${a.key} -m "message"`
|
||||
? `lh brief resolve ${b.id} --action ${a.key} -m "内容"`
|
||||
: `lh brief resolve ${b.id} --action ${a.key}`;
|
||||
console.log(` ${a.label} ${pc.dim(cmd)}`);
|
||||
}
|
||||
} else {
|
||||
console.log(pc.dim('Actions:'));
|
||||
console.log(pc.dim(` lh brief resolve ${b.id} # Approve`));
|
||||
console.log(pc.dim(` lh brief resolve ${b.id} --reply "revision notes" # Request revision`));
|
||||
console.log(pc.dim(` lh brief resolve ${b.id} # 确认通过`));
|
||||
console.log(pc.dim(` lh brief resolve ${b.id} --reply "修改意见" # 反馈修改`));
|
||||
}
|
||||
} else if ((b as any).resolvedComment) {
|
||||
console.log(`${pc.dim('Comment:')} ${(b as any).resolvedComment}`);
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Command } from 'commander';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { registerCronCommand } from './cron';
|
||||
|
||||
const { mockTrpcClient } = vi.hoisted(() => ({
|
||||
mockTrpcClient: {
|
||||
agentCronJob: {
|
||||
batchUpdateStatus: { mutate: vi.fn() },
|
||||
create: { mutate: vi.fn() },
|
||||
delete: { mutate: vi.fn() },
|
||||
findById: { query: vi.fn() },
|
||||
getStats: { query: vi.fn() },
|
||||
list: { query: vi.fn() },
|
||||
resetExecutions: { mutate: vi.fn() },
|
||||
update: { mutate: vi.fn() },
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const { getTrpcClient: mockGetTrpcClient } = vi.hoisted(() => ({
|
||||
getTrpcClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../api/client', () => ({ getTrpcClient: mockGetTrpcClient }));
|
||||
vi.mock('../utils/logger', () => ({
|
||||
log: { debug: vi.fn(), error: vi.fn(), info: vi.fn(), warn: vi.fn() },
|
||||
setVerbose: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('cron command', () => {
|
||||
let exitSpy: ReturnType<typeof vi.spyOn>;
|
||||
let consoleSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
||||
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
mockGetTrpcClient.mockResolvedValue(mockTrpcClient);
|
||||
for (const method of Object.values(mockTrpcClient.agentCronJob)) {
|
||||
for (const fn of Object.values(method)) {
|
||||
(fn as ReturnType<typeof vi.fn>).mockReset();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
exitSpy.mockRestore();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
function createProgram() {
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerCronCommand(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
describe('list', () => {
|
||||
it('should list cron jobs', async () => {
|
||||
mockTrpcClient.agentCronJob.list.query.mockResolvedValue({
|
||||
data: [{ enabled: true, id: 'c1', name: 'Test Job', schedule: '* * * * *' }],
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'list']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.list.query).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should filter by agent-id', async () => {
|
||||
mockTrpcClient.agentCronJob.list.query.mockResolvedValue({ data: [] });
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'list', '--agent-id', 'a1']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.list.query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ agentId: 'a1' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('view', () => {
|
||||
it('should view cron job details', async () => {
|
||||
mockTrpcClient.agentCronJob.findById.query.mockResolvedValue({
|
||||
data: { enabled: true, id: 'c1', name: 'Test', schedule: '* * * * *' },
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'view', 'c1']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.findById.query).toHaveBeenCalledWith({ id: 'c1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a cron job', async () => {
|
||||
mockTrpcClient.agentCronJob.create.mutate.mockResolvedValue({ data: { id: 'c1' } });
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'cron',
|
||||
'create',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'-s',
|
||||
'* * * * *',
|
||||
'-n',
|
||||
'My Job',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.create.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ agentId: 'a1', cronPattern: '* * * * *', name: 'My Job' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete a cron job', async () => {
|
||||
mockTrpcClient.agentCronJob.delete.mutate.mockResolvedValue({});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'delete', 'c1', '--yes']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.delete.mutate).toHaveBeenCalledWith({ id: 'c1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggle', () => {
|
||||
it('should batch enable cron jobs', async () => {
|
||||
mockTrpcClient.agentCronJob.batchUpdateStatus.mutate.mockResolvedValue({
|
||||
data: { updatedCount: 2 },
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'toggle', 'c1', 'c2', '--enable']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.batchUpdateStatus.mutate).toHaveBeenCalledWith({
|
||||
enabled: true,
|
||||
ids: ['c1', 'c2'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset execution count', async () => {
|
||||
mockTrpcClient.agentCronJob.resetExecutions.mutate.mockResolvedValue({});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'reset', 'c1', '--max', '100']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.resetExecutions.mutate).toHaveBeenCalledWith({
|
||||
id: 'c1',
|
||||
newMaxExecutions: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stats', () => {
|
||||
it('should get stats', async () => {
|
||||
mockTrpcClient.agentCronJob.getStats.query.mockResolvedValue({
|
||||
data: { totalJobs: 5, totalExecutions: 100 },
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'cron', 'stats']);
|
||||
|
||||
expect(mockTrpcClient.agentCronJob.getStats.query).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,271 @@
|
||||
import type { Command } from 'commander';
|
||||
import pc from 'picocolors';
|
||||
|
||||
import { getTrpcClient } from '../api/client';
|
||||
import { confirm, outputJson, printTable, timeAgo, truncate } from '../utils/format';
|
||||
import { log } from '../utils/logger';
|
||||
|
||||
export function registerCronCommand(program: Command) {
|
||||
const cron = program.command('cron').description('Manage agent cron jobs');
|
||||
|
||||
// ── list ──────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('list')
|
||||
.description('List cron jobs')
|
||||
.option('--agent-id <id>', 'Filter by agent ID')
|
||||
.option('--enabled', 'Only show enabled jobs')
|
||||
.option('--disabled', 'Only show disabled jobs')
|
||||
.option('-L, --limit <n>', 'Page size', '20')
|
||||
.option('--offset <n>', 'Offset', '0')
|
||||
.option('--json [fields]', 'Output JSON, optionally specify fields (comma-separated)')
|
||||
.action(
|
||||
async (options: {
|
||||
agentId?: string;
|
||||
disabled?: boolean;
|
||||
enabled?: boolean;
|
||||
json?: string | boolean;
|
||||
limit?: string;
|
||||
offset?: string;
|
||||
}) => {
|
||||
const client = await getTrpcClient();
|
||||
|
||||
const input: Record<string, any> = {};
|
||||
if (options.agentId) input.agentId = options.agentId;
|
||||
if (options.enabled) input.enabled = true;
|
||||
if (options.disabled) input.enabled = false;
|
||||
if (options.limit) input.limit = Number.parseInt(options.limit, 10);
|
||||
if (options.offset) input.offset = Number.parseInt(options.offset, 10);
|
||||
|
||||
const result = await client.agentCronJob.list.query(input as any);
|
||||
const items = (result as any).data ?? [];
|
||||
|
||||
if (options.json !== undefined) {
|
||||
const fields = typeof options.json === 'string' ? options.json : undefined;
|
||||
outputJson(items, fields);
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
console.log('No cron jobs found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = items.map((j: any) => [
|
||||
j.id || '',
|
||||
truncate(j.name || '', 30),
|
||||
j.schedule || '',
|
||||
j.enabled ? pc.green('enabled') : pc.dim('disabled'),
|
||||
`${j.executionCount ?? 0}/${j.maxExecutions ?? '∞'}`,
|
||||
j.updatedAt ? timeAgo(j.updatedAt) : '',
|
||||
]);
|
||||
|
||||
printTable(rows, ['ID', 'NAME', 'SCHEDULE', 'STATUS', 'EXECUTIONS', 'UPDATED']);
|
||||
},
|
||||
);
|
||||
|
||||
// ── view ──────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('view <id>')
|
||||
.description('View cron job details')
|
||||
.option('--json [fields]', 'Output JSON')
|
||||
.action(async (id: string, options: { json?: string | boolean }) => {
|
||||
const client = await getTrpcClient();
|
||||
const result = await client.agentCronJob.findById.query({ id });
|
||||
const job = (result as any).data;
|
||||
|
||||
if (options.json !== undefined) {
|
||||
const fields = typeof options.json === 'string' ? options.json : undefined;
|
||||
outputJson(job, fields);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!job) {
|
||||
log.error('Cron job not found.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`${pc.bold('ID:')} ${job.id}`);
|
||||
console.log(`${pc.bold('Name:')} ${job.name || ''}`);
|
||||
console.log(`${pc.bold('Agent ID:')} ${job.agentId || ''}`);
|
||||
console.log(`${pc.bold('Schedule:')} ${job.schedule || ''}`);
|
||||
console.log(
|
||||
`${pc.bold('Status:')} ${job.enabled ? pc.green('enabled') : pc.dim('disabled')}`,
|
||||
);
|
||||
console.log(
|
||||
`${pc.bold('Executions:')} ${job.executionCount ?? 0}/${job.maxExecutions ?? '∞'}`,
|
||||
);
|
||||
if (job.prompt) console.log(`${pc.bold('Prompt:')} ${truncate(job.prompt, 80)}`);
|
||||
if (job.createdAt) console.log(`${pc.bold('Created:')} ${timeAgo(job.createdAt)}`);
|
||||
if (job.updatedAt) console.log(`${pc.bold('Updated:')} ${timeAgo(job.updatedAt)}`);
|
||||
});
|
||||
|
||||
// ── create ────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('create')
|
||||
.description('Create a cron job')
|
||||
.requiredOption('--agent-id <id>', 'Agent ID')
|
||||
.requiredOption('-s, --schedule <cron>', 'Cron schedule expression')
|
||||
.option('-n, --name <name>', 'Job name')
|
||||
.option('-p, --prompt <prompt>', 'Prompt text')
|
||||
.option('--max-executions <n>', 'Maximum number of executions')
|
||||
.option('--json', 'Output JSON')
|
||||
.action(
|
||||
async (options: {
|
||||
agentId: string;
|
||||
json?: boolean;
|
||||
maxExecutions?: string;
|
||||
name?: string;
|
||||
prompt?: string;
|
||||
schedule: string;
|
||||
}) => {
|
||||
const client = await getTrpcClient();
|
||||
|
||||
const input: Record<string, any> = {
|
||||
agentId: options.agentId,
|
||||
cronPattern: options.schedule,
|
||||
};
|
||||
if (options.name) input.name = options.name;
|
||||
if (options.prompt) input.content = options.prompt;
|
||||
if (options.maxExecutions) input.maxExecutions = Number.parseInt(options.maxExecutions, 10);
|
||||
|
||||
const result = await client.agentCronJob.create.mutate(input as any);
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (result as any).data;
|
||||
console.log(`${pc.green('✓')} Created cron job ${pc.bold(data?.id || '')}`);
|
||||
},
|
||||
);
|
||||
|
||||
// ── edit ───────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('edit <id>')
|
||||
.description('Update a cron job')
|
||||
.option('-n, --name <name>', 'Job name')
|
||||
.option('-s, --schedule <cron>', 'Cron schedule expression')
|
||||
.option('-p, --prompt <prompt>', 'Prompt text')
|
||||
.option('--max-executions <n>', 'Maximum number of executions')
|
||||
.option('--enable', 'Enable the job')
|
||||
.option('--disable', 'Disable the job')
|
||||
.action(
|
||||
async (
|
||||
id: string,
|
||||
options: {
|
||||
disable?: boolean;
|
||||
enable?: boolean;
|
||||
maxExecutions?: string;
|
||||
name?: string;
|
||||
prompt?: string;
|
||||
schedule?: string;
|
||||
},
|
||||
) => {
|
||||
const data: Record<string, any> = {};
|
||||
if (options.name) data.name = options.name;
|
||||
if (options.schedule) data.cronPattern = options.schedule;
|
||||
if (options.prompt) data.content = options.prompt;
|
||||
if (options.maxExecutions) data.maxExecutions = Number.parseInt(options.maxExecutions, 10);
|
||||
if (options.enable) data.enabled = true;
|
||||
if (options.disable) data.enabled = false;
|
||||
|
||||
if (Object.keys(data).length === 0) {
|
||||
log.error(
|
||||
'No changes specified. Use --name, --schedule, --prompt, --enable, or --disable.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = await getTrpcClient();
|
||||
await client.agentCronJob.update.mutate({ data, id } as any);
|
||||
console.log(`${pc.green('✓')} Updated cron job ${pc.bold(id)}`);
|
||||
},
|
||||
);
|
||||
|
||||
// ── delete ────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('delete <id>')
|
||||
.description('Delete a cron job')
|
||||
.option('--yes', 'Skip confirmation prompt')
|
||||
.action(async (id: string, options: { yes?: boolean }) => {
|
||||
if (!options.yes) {
|
||||
const confirmed = await confirm('Are you sure you want to delete this cron job?');
|
||||
if (!confirmed) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const client = await getTrpcClient();
|
||||
await client.agentCronJob.delete.mutate({ id });
|
||||
console.log(`${pc.green('✓')} Deleted cron job ${pc.bold(id)}`);
|
||||
});
|
||||
|
||||
// ── toggle ────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('toggle <ids...>')
|
||||
.description('Batch enable or disable cron jobs')
|
||||
.option('--enable', 'Enable the jobs')
|
||||
.option('--disable', 'Disable the jobs')
|
||||
.action(async (ids: string[], options: { disable?: boolean; enable?: boolean }) => {
|
||||
if (!options.enable && !options.disable) {
|
||||
log.error('Specify --enable or --disable.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const enabled = !!options.enable;
|
||||
const client = await getTrpcClient();
|
||||
const result = await client.agentCronJob.batchUpdateStatus.mutate({ enabled, ids });
|
||||
const count = (result as any).data?.updatedCount ?? ids.length;
|
||||
console.log(`${pc.green('✓')} ${enabled ? 'Enabled' : 'Disabled'} ${count} cron job(s)`);
|
||||
});
|
||||
|
||||
// ── reset ─────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('reset <id>')
|
||||
.description('Reset execution count for a cron job')
|
||||
.option('--max <n>', 'Set new max executions')
|
||||
.action(async (id: string, options: { max?: string }) => {
|
||||
const client = await getTrpcClient();
|
||||
|
||||
const input: Record<string, any> = { id };
|
||||
if (options.max) input.newMaxExecutions = Number.parseInt(options.max, 10);
|
||||
|
||||
await client.agentCronJob.resetExecutions.mutate(input as any);
|
||||
console.log(`${pc.green('✓')} Reset execution count for ${pc.bold(id)}`);
|
||||
});
|
||||
|
||||
// ── stats ─────────────────────────────────────────────
|
||||
|
||||
cron
|
||||
.command('stats')
|
||||
.description('Get cron job execution statistics')
|
||||
.option('--json', 'Output JSON')
|
||||
.action(async (options: { json?: boolean }) => {
|
||||
const client = await getTrpcClient();
|
||||
const result = await client.agentCronJob.getStats.query();
|
||||
const stats = (result as any).data;
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(stats, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stats) {
|
||||
console.log('No statistics available.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(stats as Record<string, any>)) {
|
||||
console.log(`${pc.bold(key + ':')} ${value}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -208,7 +208,7 @@ function readAgentProfile(workspacePath: string): AgentProfile {
|
||||
// Try to extract **Emoji:** value (single emoji)
|
||||
const emojiMatch = content.match(/\*{0,2}Emoji:?\*{0,2}\s*(.+)/i);
|
||||
const rawAvatar = emojiMatch ? emojiMatch[1].trim() : undefined;
|
||||
// Filter out placeholder text like (TBD), _(TBD)_, N/A, and Chinese-language equivalents.
|
||||
// Filter out placeholder text like (待定)(Chinese TBD), _(待定)_, (TBD), N/A, etc.
|
||||
const isPlaceholder =
|
||||
rawAvatar && /^[_*((].*[))_*]$|^(?:tbd|todo|n\/?a|none|待定|未定)$/i.test(rawAvatar);
|
||||
const avatar = rawAvatar && !isPlaceholder ? rawAvatar : undefined;
|
||||
|
||||
@@ -145,7 +145,7 @@ export function registerReviewCommands(task: Command) {
|
||||
|
||||
rc.command('add <id>')
|
||||
.description('Add a review rubric')
|
||||
.requiredOption('-n, --name <name>', 'Rubric name (e.g. "Content Accuracy")')
|
||||
.requiredOption('-n, --name <name>', 'Rubric name (e.g. "内容准确性")')
|
||||
.option('--type <type>', 'Rubric type (default: llm-rubric)', 'llm-rubric')
|
||||
.option('-t, --threshold <n>', 'Pass threshold 0-100 (converted to 0-1)')
|
||||
.option('-d, --description <text>', 'Criteria description (for llm-rubric type)')
|
||||
|
||||
@@ -8,6 +8,7 @@ import { registerBotCommand } from './commands/bot';
|
||||
import { registerCompletionCommand } from './commands/completion';
|
||||
import { registerConfigCommand } from './commands/config';
|
||||
import { registerConnectCommand } from './commands/connect';
|
||||
import { registerCronCommand } from './commands/cron';
|
||||
import { registerDeviceCommand } from './commands/device';
|
||||
import { registerDocCommand } from './commands/doc';
|
||||
import { registerEvalCommand } from './commands/eval';
|
||||
@@ -59,6 +60,7 @@ export function createProgram() {
|
||||
registerAgentCommand(program);
|
||||
registerAgentGroupCommand(program);
|
||||
registerBotCommand(program);
|
||||
registerCronCommand(program);
|
||||
registerGenerateCommand(program);
|
||||
registerFileCommand(program);
|
||||
registerHeteroCommand(program);
|
||||
|
||||
@@ -7,18 +7,18 @@ import { entryLocaleJsonFilepath, i18nConfig, localeDir, srcDefaultLocales } fro
|
||||
import { tagWhite, writeJSON } from './utils';
|
||||
|
||||
export const genDefaultLocale = () => {
|
||||
consola.info(`Default locale: ${i18nConfig.entryLocale}...`);
|
||||
consola.info(`默认语言为 ${i18nConfig.entryLocale}...`);
|
||||
|
||||
// Ensure entry locale directory exists
|
||||
const entryLocaleDir = localeDir(i18nConfig.entryLocale);
|
||||
if (!existsSync(entryLocaleDir)) {
|
||||
mkdirSync(entryLocaleDir, { recursive: true });
|
||||
consola.info(`Creating directory: ${entryLocaleDir}`);
|
||||
consola.info(`创建目录:${entryLocaleDir}`);
|
||||
}
|
||||
|
||||
const resources = require(srcDefaultLocales);
|
||||
const data = Object.entries(resources.default);
|
||||
consola.start(`Generating default locale JSON files, found ${data.length} namespaces...`);
|
||||
consola.start(`生成默认语言 JSON 文件,发现 ${data.length} 个命名空间...`);
|
||||
|
||||
for (const [ns, value] of data) {
|
||||
const filepath = entryLocaleJsonFilepath(`${ns}.json`);
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { readJSON, tagWhite, writeJSON } from './utils';
|
||||
|
||||
export const genDiff = () => {
|
||||
consola.start(`Comparing localization files between dev and prod environments...`);
|
||||
consola.start(`对比开发与生产环境中的本地化文件...`);
|
||||
|
||||
const resources = require(srcDefaultLocales);
|
||||
const data = Object.entries(resources.default);
|
||||
@@ -21,7 +21,7 @@ export const genDiff = () => {
|
||||
for (const [ns, devJSON] of data) {
|
||||
const filepath = entryLocaleJsonFilepath(`${ns}.json`);
|
||||
if (!existsSync(filepath)) {
|
||||
consola.info(`File does not exist, skipping: ${filepath}`);
|
||||
consola.info(`文件不存在,跳过:${filepath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const genDiff = () => {
|
||||
}
|
||||
|
||||
if (clearLocals.length > 0) {
|
||||
consola.info('Cleaned up stale entries for the following locales:', clearLocals.join(', '));
|
||||
consola.info('清理了以下语言的过期项目:', clearLocals.join(', '));
|
||||
}
|
||||
consola.success(tagWhite(ns), colors.gray(filepath));
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ const run = async () => {
|
||||
ensureLocalesDirs();
|
||||
|
||||
// Diff analysis
|
||||
split('Diff Analysis');
|
||||
split('差异分析');
|
||||
genDiff();
|
||||
|
||||
// Generate default locale files
|
||||
split('Generate Default Locale Files');
|
||||
split('生成默认语言文件');
|
||||
genDefaultLocale();
|
||||
|
||||
// Generate i18n files
|
||||
split('Generate i18n Files');
|
||||
split('生成国际化文件');
|
||||
};
|
||||
|
||||
run();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { execFile, spawn } from 'node:child_process';
|
||||
import { readFile, rm, stat } from 'node:fs/promises';
|
||||
import { readFile, stat } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
GitBranchListItem,
|
||||
GitCheckoutResult,
|
||||
GitFileDiffStatus,
|
||||
GitFileRevertResult,
|
||||
GitLinkedPullRequestResult,
|
||||
GitPullResult,
|
||||
GitPushResult,
|
||||
@@ -1107,70 +1106,4 @@ export default class GitController extends ControllerModule {
|
||||
return { error: stderr || 'git push failed', success: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a single working-tree change. Mirrors what "Discard changes" does
|
||||
* in GitHub Desktop / VSCode SCM: restore the file to its HEAD state,
|
||||
* dropping any unstaged / staged edits — and physically delete the file
|
||||
* when it doesn't exist at HEAD (untracked or staged-add).
|
||||
*
|
||||
* Branch logic by HEAD presence:
|
||||
* - present at HEAD → `git checkout HEAD -- <file>` (covers modified,
|
||||
* deleted, staged-D — restores both index + worktree from HEAD)
|
||||
* - absent at HEAD → `git rm --cached` (unstage if staged-A; silent
|
||||
* no-op for untracked) + `fs.rm` to delete the file from disk
|
||||
*
|
||||
* filePath is the repo-relative path from `git status` output, the same
|
||||
* shape we hand to the renderer in `GitWorkingTreePatch.filePath`. We
|
||||
* reject absolute paths and `..` traversal so the renderer can't poke
|
||||
* outside the repo even if its payload were tampered with.
|
||||
*/
|
||||
@IpcMethod()
|
||||
async revertGitFile(payload: { filePath: string; path: string }): Promise<GitFileRevertResult> {
|
||||
const { path: dirPath, filePath } = payload;
|
||||
if (!filePath?.trim()) return { error: 'File path is required', success: false };
|
||||
if (path.isAbsolute(filePath) || filePath.split(/[/\\]/).includes('..')) {
|
||||
return { error: `Invalid file path: ${filePath}`, success: false };
|
||||
}
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// Probe HEAD via cat-file -e — exit 0 means the blob exists at HEAD.
|
||||
let existsAtHead: boolean;
|
||||
try {
|
||||
await execFileAsync('git', ['cat-file', '-e', `HEAD:${filePath}`], {
|
||||
cwd: dirPath,
|
||||
timeout: 5000,
|
||||
});
|
||||
existsAtHead = true;
|
||||
} catch {
|
||||
existsAtHead = false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsAtHead) {
|
||||
await execFileAsync('git', ['checkout', 'HEAD', '--', filePath], {
|
||||
cwd: dirPath,
|
||||
timeout: 15_000,
|
||||
});
|
||||
} else {
|
||||
// Unstage if the file is in the index (staged-add). `git rm --cached`
|
||||
// exits non-zero on untracked paths, which is fine — swallow it.
|
||||
try {
|
||||
await execFileAsync('git', ['rm', '--cached', '--quiet', '--', filePath], {
|
||||
cwd: dirPath,
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch {
|
||||
// not staged — fall through to the disk-delete
|
||||
}
|
||||
await rm(path.resolve(dirPath, filePath), { force: true, recursive: false });
|
||||
}
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
const stderr: string = (error?.stderr ?? error?.message ?? '').toString().trim();
|
||||
logger.debug('[revertGitFile] failed', { filePath, stderr });
|
||||
return { error: stderr || 'git revert failed', success: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { unlinkSync } from 'node:fs';
|
||||
import { access, appendFile, mkdir, unlink, writeFile } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import { access, appendFile, mkdir, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import type { Readable, Writable } from 'node:stream';
|
||||
import { finished as streamFinished } from 'node:stream/promises';
|
||||
@@ -16,8 +14,6 @@ import {
|
||||
CODEX_CLI_INSTALL_DOCS_URL,
|
||||
HeterogeneousAgentSessionErrorCode,
|
||||
} from '@lobechat/electron-client-ipc';
|
||||
import type { AskUserBridge } from '@lobechat/heterogeneous-agents/askUser';
|
||||
import { AskUserMcpServer } from '@lobechat/heterogeneous-agents/askUser';
|
||||
import type { AgentContentBlock } from '@lobechat/heterogeneous-agents/spawn';
|
||||
import {
|
||||
AgentStreamPipeline,
|
||||
@@ -103,18 +99,6 @@ interface CancelSessionParams {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
interface SubmitInterventionParams {
|
||||
cancelled?: boolean;
|
||||
/** When set, signals user-cancelled or timeout — the bridge resolves with isError. */
|
||||
cancelReason?: 'timeout' | 'user_cancelled';
|
||||
/** Operation id stamped on the request the renderer is responding to. */
|
||||
operationId: string;
|
||||
/** Structured user answer; ignored when `cancelled` is true. */
|
||||
result?: unknown;
|
||||
/** Correlation key carried on the original `agent_intervention_request`. */
|
||||
toolCallId: string;
|
||||
}
|
||||
|
||||
interface StopSessionParams {
|
||||
sessionId: string;
|
||||
}
|
||||
@@ -166,28 +150,10 @@ interface CliTraceSession {
|
||||
*
|
||||
* Lifecycle: startSession → sendPrompt → (heteroAgentEvent broadcasts) → stopSession
|
||||
*/
|
||||
interface InterventionSlot {
|
||||
bridge: AskUserBridge;
|
||||
/** Resolves once bridge.events() iterator ends (after `cancelAll`). */
|
||||
pumpDone: Promise<void>;
|
||||
/** Path to the per-op temp `mcp.json` we wrote for `--mcp-config`. */
|
||||
tmpConfigPath: string;
|
||||
}
|
||||
|
||||
export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
static override readonly groupName = 'heterogeneousAgent';
|
||||
|
||||
private sessions = new Map<string, AgentSession>();
|
||||
/**
|
||||
* Per-operation AskUserQuestion bridge state. Keyed by `operationId` so the
|
||||
* `submitIntervention` IPC can route an answer to the right pending MCP
|
||||
* handler regardless of which `sessionId` it belongs to (one session can
|
||||
* fire many ops over its lifetime).
|
||||
*/
|
||||
private opIdToIntervention = new Map<string, InterventionSlot>();
|
||||
/** Lazy single MCP server, started on first claude-code prompt. */
|
||||
private askUserMcpServer?: AskUserMcpServer;
|
||||
private askUserMcpStartPromise?: Promise<AskUserMcpServer>;
|
||||
|
||||
private resolveSessionCommand(session: AgentSession): string {
|
||||
const resolvedCommand = session.command.trim();
|
||||
@@ -601,92 +567,6 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── AskUserQuestion MCP server (LOBE-8725) ───
|
||||
|
||||
/**
|
||||
* Lazy single-instance MCP server for CC's AskUserQuestion replacement.
|
||||
* First claude-code prompt triggers `start()`; subsequent prompts reuse
|
||||
* the same listener. Concurrent first-callers de-dupe via the in-flight
|
||||
* promise so we don't bind two ports.
|
||||
*/
|
||||
private async ensureAskUserMcpServerStarted(): Promise<AskUserMcpServer> {
|
||||
if (this.askUserMcpServer) return this.askUserMcpServer;
|
||||
if (!this.askUserMcpStartPromise) {
|
||||
this.askUserMcpStartPromise = (async () => {
|
||||
const server = new AskUserMcpServer();
|
||||
await server.start();
|
||||
this.askUserMcpServer = server;
|
||||
logger.info('AskUserQuestion MCP server started:', server.url);
|
||||
return server;
|
||||
})().catch((err) => {
|
||||
// Reset so a later sendPrompt can retry; surface the error.
|
||||
this.askUserMcpStartPromise = undefined;
|
||||
logger.error('Failed to start AskUserQuestion MCP server:', err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this.askUserMcpStartPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a per-op AskUserQuestion bridge, write its temp `mcp.json`,
|
||||
* and start pumping the bridge's outbound events into the regular
|
||||
* `heteroAgentEvent` broadcast. Caller must invoke the returned cleanup
|
||||
* after the spawn finishes (success, error, or cancel) to remove the
|
||||
* temp file and tear down the bridge.
|
||||
*
|
||||
* Pump errors are logged but never thrown — they don't fail the spawn.
|
||||
*/
|
||||
private async setupInterventionForOp(
|
||||
operationId: string,
|
||||
sessionId: string,
|
||||
): Promise<{ cleanup: () => Promise<void>; tmpConfigPath: string }> {
|
||||
const server = await this.ensureAskUserMcpServerStarted();
|
||||
const bridge = server.registerOperation(operationId);
|
||||
const tmpConfigPath = path.join(os.tmpdir(), `lobe-cc-mcp-${operationId}.json`);
|
||||
|
||||
// `alwaysLoad: true` is the undocumented CC flag that promotes our
|
||||
// server's tool out of the deferred set so the model calls it directly
|
||||
// (no ToolSearch hop). See LOBE-8725 spike notes — falls back to the
|
||||
// 2-hop ToolSearch path if a future CC drops the flag, no breakage.
|
||||
const config = {
|
||||
mcpServers: {
|
||||
lobe_cc: {
|
||||
alwaysLoad: true,
|
||||
type: 'http' as const,
|
||||
url: server.urlForOperation(operationId),
|
||||
},
|
||||
},
|
||||
};
|
||||
await writeFile(tmpConfigPath, JSON.stringify(config), 'utf8');
|
||||
|
||||
// Pump bridge.events() into the `heteroAgentEvent` broadcast. The
|
||||
// iterator only ends after `cancelAll()`, so `pumpDone` resolves at
|
||||
// cleanup time and gates teardown.
|
||||
const pumpDone = (async () => {
|
||||
for await (const event of bridge.events()) {
|
||||
this.broadcast('heteroAgentEvent', { event, sessionId });
|
||||
}
|
||||
})().catch((err) => {
|
||||
logger.warn('AskUserQuestion bridge pump error:', err);
|
||||
});
|
||||
|
||||
this.opIdToIntervention.set(operationId, { bridge, pumpDone, tmpConfigPath });
|
||||
|
||||
const cleanup = async () => {
|
||||
// Unregistering on the server cancels all bridge pendings AND closes
|
||||
// the events iterator (cancelAll fires from within unregisterOperation).
|
||||
this.askUserMcpServer?.unregisterOperation(operationId);
|
||||
await pumpDone;
|
||||
this.opIdToIntervention.delete(operationId);
|
||||
await unlink(tmpConfigPath).catch(() => {
|
||||
/* file may already be gone if app crashed mid-prompt */
|
||||
});
|
||||
};
|
||||
|
||||
return { cleanup, tmpConfigPath };
|
||||
}
|
||||
|
||||
// ─── File cache ───
|
||||
|
||||
private get fileCacheDir(): string {
|
||||
@@ -817,58 +697,32 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
throw new Error(preflightError.message);
|
||||
}
|
||||
|
||||
// Stand up the AskUserQuestion MCP bridge for claude-code prompts BEFORE
|
||||
// building the spawn plan so the driver can wire the temp config path
|
||||
// into `--mcp-config`. Codex / future agents skip this entirely.
|
||||
const intervention =
|
||||
session.agentType === 'claude-code'
|
||||
? await this.setupInterventionForOp(params.operationId, session.sessionId).catch((err) => {
|
||||
logger.warn('Failed to set up AskUserQuestion bridge — proceeding without it:', err);
|
||||
return undefined;
|
||||
})
|
||||
: undefined;
|
||||
|
||||
let spawnPlan;
|
||||
let traceSession;
|
||||
let cwd: string;
|
||||
try {
|
||||
const driver = getHeterogeneousAgentDriver(session.agentType);
|
||||
spawnPlan = await driver.buildSpawnPlan({
|
||||
args: session.args,
|
||||
helpers: {
|
||||
buildClaudeStreamJsonInput: (prompt, imageList) =>
|
||||
this.buildStreamJsonInput(prompt, imageList),
|
||||
resolveCliImagePaths: (imageList) => this.resolveCliImagePaths(imageList),
|
||||
},
|
||||
imageList: params.imageList ?? [],
|
||||
mcpConfigPath: intervention?.tmpConfigPath,
|
||||
prompt: params.prompt,
|
||||
resumeSessionId: session.agentSessionId,
|
||||
});
|
||||
|
||||
// Fall back to the user's Desktop so the process never inherits
|
||||
// the Electron parent's cwd (which is `/` when launched from Finder).
|
||||
cwd = session.cwd || electronApp.getPath('desktop');
|
||||
traceSession = await this.createCliTraceSession({
|
||||
cliArgs: spawnPlan.args,
|
||||
cwd,
|
||||
imageList: params.imageList ?? [],
|
||||
session,
|
||||
stdinPayload: spawnPlan.stdinPayload,
|
||||
});
|
||||
} catch (err) {
|
||||
// We never made it to spawn — the `proc.on('exit')` cleanup path
|
||||
// won't run, so tear the intervention bridge down right here.
|
||||
if (intervention) {
|
||||
await intervention.cleanup().catch((cleanupErr) => {
|
||||
logger.warn('AskUserQuestion cleanup error during pre-spawn failure:', cleanupErr);
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
const driver = getHeterogeneousAgentDriver(session.agentType);
|
||||
const spawnPlan = await driver.buildSpawnPlan({
|
||||
args: session.args,
|
||||
helpers: {
|
||||
buildClaudeStreamJsonInput: (prompt, imageList) =>
|
||||
this.buildStreamJsonInput(prompt, imageList),
|
||||
resolveCliImagePaths: (imageList) => this.resolveCliImagePaths(imageList),
|
||||
},
|
||||
imageList: params.imageList ?? [],
|
||||
prompt: params.prompt,
|
||||
resumeSessionId: session.agentSessionId,
|
||||
});
|
||||
const useStdin = spawnPlan.stdinPayload !== undefined;
|
||||
const cliArgs = spawnPlan.args;
|
||||
|
||||
// Fall back to the user's Desktop so the process never inherits
|
||||
// the Electron parent's cwd (which is `/` when launched from Finder).
|
||||
const cwd = session.cwd || electronApp.getPath('desktop');
|
||||
const traceSession = await this.createCliTraceSession({
|
||||
cliArgs,
|
||||
cwd,
|
||||
imageList: params.imageList ?? [],
|
||||
session,
|
||||
stdinPayload: spawnPlan.stdinPayload,
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
logger.info('Spawning agent:', session.command, cliArgs.join(' '), `(cwd: ${cwd})`);
|
||||
|
||||
@@ -984,15 +838,6 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
void stdoutDrained
|
||||
.then(() => stdoutBroadcastQueue)
|
||||
.finally(async () => {
|
||||
// Tear down the AskUserQuestion bridge / temp `mcp.json` for this
|
||||
// op. Pending MCP handlers get a `session_ended` cancellation so
|
||||
// they return cleanly even if CC was killed mid-tool-call.
|
||||
if (intervention) {
|
||||
await intervention.cleanup().catch((err) => {
|
||||
logger.warn('AskUserQuestion cleanup error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
void this.writeCliTraceJson(traceSession, 'exit.json', {
|
||||
code,
|
||||
finishedAt: new Date().toISOString(),
|
||||
@@ -1127,54 +972,10 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer → main: deliver the user's answer to a pending CC AskUserQuestion
|
||||
* (or signal cancellation). The matching bridge resolves its blocked
|
||||
* `pending()` Promise, the local MCP handler returns to CC, and CC's
|
||||
* `tool_result` flows back through the normal stream pipeline.
|
||||
*
|
||||
* Idempotent — late submissions for already-resolved tool calls are no-ops.
|
||||
* No-op when called for an unknown opId; the bridge may have been cleaned
|
||||
* up already (op finished / cancelled).
|
||||
*/
|
||||
@IpcMethod()
|
||||
async submitIntervention(params: SubmitInterventionParams): Promise<void> {
|
||||
const slot = this.opIdToIntervention.get(params.operationId);
|
||||
if (!slot) {
|
||||
logger.warn('submitIntervention: no active intervention for operationId', params.operationId);
|
||||
return;
|
||||
}
|
||||
slot.bridge.resolve(params.toolCallId, {
|
||||
cancelReason: params.cancelled ? (params.cancelReason ?? 'user_cancelled') : undefined,
|
||||
cancelled: params.cancelled,
|
||||
result: params.result,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously unlink every pending intervention's temp `mcp.json`. The
|
||||
* async exit-handler cleanup loses to Electron's main-process teardown
|
||||
* often enough that we'd leak `lobe-cc-mcp-<opId>.json` files into
|
||||
* `os.tmpdir()` on real shutdowns; sync unlink here is the only reliable
|
||||
* guarantee. Safe to call multiple times.
|
||||
*/
|
||||
private unlinkPendingInterventionConfigsSync = (): void => {
|
||||
for (const [, intervention] of this.opIdToIntervention) {
|
||||
try {
|
||||
unlinkSync(intervention.tmpConfigPath);
|
||||
} catch {
|
||||
/* file may already be gone — fine */
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup on app quit. `before-quit` covers the user-driven Cmd+Q /
|
||||
* `app.quit()` path; SIGTERM / SIGINT cover external kills (test
|
||||
* harnesses, OS shutdown) where Electron's lifecycle events never fire.
|
||||
* Cleanup on app quit.
|
||||
*/
|
||||
afterAppReady() {
|
||||
electronApp.on('before-quit', () => {
|
||||
this.unlinkPendingInterventionConfigsSync();
|
||||
for (const [, session] of this.sessions) {
|
||||
if (session.process && !session.process.killed) {
|
||||
session.cancelledByUs = true;
|
||||
@@ -1182,28 +983,6 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
}
|
||||
}
|
||||
this.sessions.clear();
|
||||
// The exit handlers will tear each per-op intervention down, but if
|
||||
// CC's stdio close races shutdown we'd leave the MCP server bound to
|
||||
// a port. Stopping it here cancels every still-pending bridge with
|
||||
// `session_ended` and closes the listener.
|
||||
void this.askUserMcpServer?.stop().catch((err) => {
|
||||
logger.warn('AskUserQuestion MCP server stop error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
const onSignal = (signal: NodeJS.Signals) => {
|
||||
this.unlinkPendingInterventionConfigsSync();
|
||||
// Defer to Electron's normal quit flow so the rest of the app gets a
|
||||
// chance to tear down. The `before-quit` handler above is idempotent.
|
||||
try {
|
||||
electronApp.quit();
|
||||
} catch {
|
||||
/* during late shutdown app.quit may throw — fine */
|
||||
}
|
||||
// Last-resort exit if Electron is wedged and won't quit on its own.
|
||||
setTimeout(() => process.exit(signal === 'SIGINT' ? 130 : 143), 1000).unref();
|
||||
};
|
||||
process.on('SIGTERM', onSignal);
|
||||
process.on('SIGINT', onSignal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,131 +802,4 @@ describe('HeterogeneousAgentCtr', () => {
|
||||
expect(toolEnds.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('app-quit cleanup of AskUserQuestion temp configs (LOBE-8725)', () => {
|
||||
// The async exit-handler cleanup races Electron's main-process teardown
|
||||
// and used to leak `lobe-cc-mcp-<opId>.json` files in `os.tmpdir()` on
|
||||
// every quit. The controller now unlinks pending intervention temp
|
||||
// configs *synchronously* from `before-quit` AND from process signal
|
||||
// handlers (SIGTERM / SIGINT — `before-quit` doesn't fire on external
|
||||
// kills). These tests exercise both paths against real files.
|
||||
|
||||
/**
|
||||
* Drop a temp `lobe-cc-mcp-<id>.json` and stash it on the controller's
|
||||
* `opIdToIntervention` map under the same key, so the quit hook treats
|
||||
* it like a real pending intervention and tries to unlink it.
|
||||
*/
|
||||
const seedPendingIntervention = async (ctr: HeterogeneousAgentCtr, opId: string) => {
|
||||
const tmpConfigPath = path.join(tmpdir(), `lobe-cc-mcp-test-${opId}.json`);
|
||||
await writeFile(tmpConfigPath, '{"mcpServers":{}}');
|
||||
const slot = {
|
||||
bridge: {} as any,
|
||||
pumpDone: Promise.resolve(),
|
||||
tmpConfigPath,
|
||||
};
|
||||
(ctr as any).opIdToIntervention.set(opId, slot);
|
||||
return tmpConfigPath;
|
||||
};
|
||||
|
||||
const captureRegisteredHandler = (
|
||||
registerSpy: ReturnType<typeof vi.fn> | ReturnType<typeof vi.spyOn>,
|
||||
eventName: string,
|
||||
): (() => void) => {
|
||||
const calls = (registerSpy as any).mock.calls as Array<[string, () => void]>;
|
||||
const match = calls.findLast(([evt]) => evt === eventName);
|
||||
if (!match) throw new Error(`no handler registered for "${eventName}"`);
|
||||
return match[1];
|
||||
};
|
||||
|
||||
it('before-quit synchronously unlinks every pending intervention temp config', async () => {
|
||||
const electron = (await import('electron')) as any;
|
||||
electron.app.on.mockClear();
|
||||
|
||||
const ctr = new HeterogeneousAgentCtr({
|
||||
appStoragePath,
|
||||
storeManager: { get: vi.fn() },
|
||||
} as any);
|
||||
|
||||
const fileA = await seedPendingIntervention(ctr, 'opA');
|
||||
const fileB = await seedPendingIntervention(ctr, 'opB');
|
||||
|
||||
ctr.afterAppReady();
|
||||
const beforeQuit = captureRegisteredHandler(electron.app.on, 'before-quit');
|
||||
beforeQuit();
|
||||
|
||||
await expect(access(fileA)).rejects.toThrow();
|
||||
await expect(access(fileB)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('SIGTERM handler unlinks pending intervention temp configs (external-kill path)', async () => {
|
||||
// External kills (test harness, OS shutdown) skip Electron's lifecycle
|
||||
// events entirely — `before-quit` never fires, so the controller has to
|
||||
// hook the raw process signal too. Stub `process.on` so the handler is
|
||||
// *recorded* but never actually attached to the test runner's process
|
||||
// (otherwise the test leaks a SIGTERM listener that survives the test).
|
||||
// Same for `process.exit` — the controller's fail-safe shouldn't get a
|
||||
// chance to actually exit the worker if its `setTimeout(...).unref()`
|
||||
// ever fires before mockRestore.
|
||||
const electron = (await import('electron')) as any;
|
||||
electron.app.on.mockClear();
|
||||
const processOnSpy = vi.spyOn(process, 'on').mockImplementation(() => process);
|
||||
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
||||
|
||||
const ctr = new HeterogeneousAgentCtr({
|
||||
appStoragePath,
|
||||
storeManager: { get: vi.fn() },
|
||||
} as any);
|
||||
const file = await seedPendingIntervention(ctr, 'opSigterm');
|
||||
|
||||
ctr.afterAppReady();
|
||||
const sigterm = captureRegisteredHandler(processOnSpy, 'SIGTERM');
|
||||
sigterm();
|
||||
|
||||
await expect(access(file)).rejects.toThrow();
|
||||
|
||||
processOnSpy.mockRestore();
|
||||
processExitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('SIGINT handler unlinks pending intervention temp configs (Ctrl-C path)', async () => {
|
||||
const electron = (await import('electron')) as any;
|
||||
electron.app.on.mockClear();
|
||||
const processOnSpy = vi.spyOn(process, 'on').mockImplementation(() => process);
|
||||
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
||||
|
||||
const ctr = new HeterogeneousAgentCtr({
|
||||
appStoragePath,
|
||||
storeManager: { get: vi.fn() },
|
||||
} as any);
|
||||
const file = await seedPendingIntervention(ctr, 'opSigint');
|
||||
|
||||
ctr.afterAppReady();
|
||||
const sigint = captureRegisteredHandler(processOnSpy, 'SIGINT');
|
||||
sigint();
|
||||
|
||||
await expect(access(file)).rejects.toThrow();
|
||||
|
||||
processOnSpy.mockRestore();
|
||||
processExitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('cleanup is idempotent — already-deleted files do not throw', async () => {
|
||||
const electron = (await import('electron')) as any;
|
||||
electron.app.on.mockClear();
|
||||
|
||||
const ctr = new HeterogeneousAgentCtr({
|
||||
appStoragePath,
|
||||
storeManager: { get: vi.fn() },
|
||||
} as any);
|
||||
const file = await seedPendingIntervention(ctr, 'opIdempotent');
|
||||
|
||||
// Pre-delete the file out from under the controller — simulates a
|
||||
// partial cleanup race where the async exit handler beat us to it.
|
||||
await unlink(file);
|
||||
|
||||
ctr.afterAppReady();
|
||||
const beforeQuit = captureRegisteredHandler(electron.app.on, 'before-quit');
|
||||
expect(() => beforeQuit()).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { type App } from '@/core/App';
|
||||
|
||||
import LocalFileCtr from '../LocalFileCtr';
|
||||
|
||||
// Real fs + real @lobechat/file-loaders end-to-end. We only mock the
|
||||
// boundaries we genuinely cannot run in a test process: electron IPC,
|
||||
// execa shell-outs, logger, net fetch.
|
||||
vi.mock('electron', () => ({
|
||||
dialog: { showOpenDialog: vi.fn(), showSaveDialog: vi.fn() },
|
||||
ipcMain: { handle: vi.fn() },
|
||||
shell: { openPath: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('execa', () => ({ execa: vi.fn() }));
|
||||
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/net-fetch', () => ({ netFetch: vi.fn() }));
|
||||
|
||||
vi.mock('@/utils/file-system', () => ({ makeSureDirExist: vi.fn() }));
|
||||
|
||||
const mockApp = {
|
||||
appStoragePath: '/mock/app/storage',
|
||||
getService: vi.fn(),
|
||||
toolDetectorManager: { getBestTool: vi.fn(() => null) },
|
||||
} as unknown as App;
|
||||
|
||||
describe('LocalFileCtr — readFile / readFiles (real fs)', () => {
|
||||
const tmpDir = path.join(os.tmpdir(), 'localfilectr-readfile-test-' + process.pid);
|
||||
let localFileCtr: LocalFileCtr;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
await mkdir(tmpDir, { recursive: true });
|
||||
localFileCtr = new LocalFileCtr(mockApp);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
describe('readFile', () => {
|
||||
it('should read file successfully with default location', async () => {
|
||||
const filePath = path.join(tmpDir, 'test.txt');
|
||||
const content = 'line1\nline2\nline3\nline4\nline5';
|
||||
await writeFile(filePath, content);
|
||||
|
||||
const result = await localFileCtr.readFile({ path: filePath });
|
||||
|
||||
expect(result).toEqual({
|
||||
charCount: 29,
|
||||
content,
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'test.txt',
|
||||
lineCount: 5,
|
||||
loc: [0, 200],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 29,
|
||||
totalLineCount: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should read file with custom location range', async () => {
|
||||
const filePath = path.join(tmpDir, 'range.txt');
|
||||
await writeFile(filePath, 'line1\nline2\nline3\nline4\nline5');
|
||||
|
||||
const result = await localFileCtr.readFile({ loc: [1, 3], path: filePath });
|
||||
|
||||
expect(result).toEqual({
|
||||
charCount: 11,
|
||||
content: 'line2\nline3',
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'range.txt',
|
||||
lineCount: 2,
|
||||
loc: [1, 3],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 29,
|
||||
totalLineCount: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should read full file content when fullContent is true', async () => {
|
||||
const filePath = path.join(tmpDir, 'full.txt');
|
||||
const content = 'line1\nline2\nline3\nline4\nline5';
|
||||
await writeFile(filePath, content);
|
||||
|
||||
const result = await localFileCtr.readFile({ fullContent: true, path: filePath });
|
||||
|
||||
expect(result).toEqual({
|
||||
charCount: 29,
|
||||
content,
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'full.txt',
|
||||
lineCount: 5,
|
||||
loc: [0, 5],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 29,
|
||||
totalLineCount: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle file read error', async () => {
|
||||
const result = await localFileCtr.readFile({
|
||||
path: path.join(tmpDir, 'does-not-exist.txt'),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
charCount: 0,
|
||||
content: expect.stringContaining('Error accessing or processing file'),
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'does-not-exist.txt',
|
||||
lineCount: 0,
|
||||
loc: [0, 0],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 0,
|
||||
totalLineCount: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFiles', () => {
|
||||
it('should read multiple files successfully', async () => {
|
||||
const file1 = path.join(tmpDir, 'a.txt');
|
||||
const file2 = path.join(tmpDir, 'b.txt');
|
||||
await writeFile(file1, 'content a');
|
||||
await writeFile(file2, 'content b');
|
||||
|
||||
const result = await localFileCtr.readFiles({ paths: [file1, file2] });
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
charCount: 9,
|
||||
content: 'content a',
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'a.txt',
|
||||
lineCount: 1,
|
||||
loc: [0, 200],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 9,
|
||||
totalLineCount: 1,
|
||||
},
|
||||
{
|
||||
charCount: 9,
|
||||
content: 'content b',
|
||||
createdTime: expect.any(Date),
|
||||
fileType: 'txt',
|
||||
filename: 'b.txt',
|
||||
lineCount: 1,
|
||||
loc: [0, 200],
|
||||
modifiedTime: expect.any(Date),
|
||||
totalCharCount: 9,
|
||||
totalLineCount: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -106,6 +106,7 @@ const mockApp = {
|
||||
describe('LocalFileCtr', () => {
|
||||
let localFileCtr: LocalFileCtr;
|
||||
let mockShell: any;
|
||||
let mockLoadFile: any;
|
||||
let mockFsPromises: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -113,6 +114,7 @@ describe('LocalFileCtr', () => {
|
||||
|
||||
// Import mocks
|
||||
mockShell = (await import('electron')).shell;
|
||||
mockLoadFile = (await import('@lobechat/file-loaders')).loadFile;
|
||||
mockFsPromises = await import('node:fs/promises');
|
||||
|
||||
localFileCtr = new LocalFileCtr(mockApp);
|
||||
@@ -176,9 +178,91 @@ describe('LocalFileCtr', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// readFile / readFiles e2e tests live in LocalFileCtr.readFile.test.ts so
|
||||
// they exercise real fs + file-loaders without fighting the heavy mocks
|
||||
// this suite needs for execa-driven tools, electron, and the like.
|
||||
describe('readFile', () => {
|
||||
it('should read file successfully with default location', async () => {
|
||||
const mockFileContent = 'line1\nline2\nline3\nline4\nline5';
|
||||
vi.mocked(mockLoadFile).mockResolvedValue({
|
||||
content: mockFileContent,
|
||||
filename: 'test.txt',
|
||||
fileType: 'txt',
|
||||
createdTime: new Date('2024-01-01'),
|
||||
modifiedTime: new Date('2024-01-02'),
|
||||
});
|
||||
|
||||
const result = await localFileCtr.readFile({ path: '/test/file.txt' });
|
||||
|
||||
expect(result.filename).toBe('test.txt');
|
||||
expect(result.fileType).toBe('txt');
|
||||
expect(result.totalLineCount).toBe(5);
|
||||
expect(result.content).toBe(mockFileContent);
|
||||
});
|
||||
|
||||
it('should read file with custom location range', async () => {
|
||||
const mockFileContent = 'line1\nline2\nline3\nline4\nline5';
|
||||
vi.mocked(mockLoadFile).mockResolvedValue({
|
||||
content: mockFileContent,
|
||||
filename: 'test.txt',
|
||||
fileType: 'txt',
|
||||
createdTime: new Date('2024-01-01'),
|
||||
modifiedTime: new Date('2024-01-02'),
|
||||
});
|
||||
|
||||
const result = await localFileCtr.readFile({ path: '/test/file.txt', loc: [1, 3] });
|
||||
|
||||
expect(result.content).toBe('line2\nline3');
|
||||
expect(result.lineCount).toBe(2);
|
||||
expect(result.totalLineCount).toBe(5);
|
||||
});
|
||||
|
||||
it('should read full file content when fullContent is true', async () => {
|
||||
const mockFileContent = 'line1\nline2\nline3\nline4\nline5';
|
||||
vi.mocked(mockLoadFile).mockResolvedValue({
|
||||
content: mockFileContent,
|
||||
filename: 'test.txt',
|
||||
fileType: 'txt',
|
||||
createdTime: new Date('2024-01-01'),
|
||||
modifiedTime: new Date('2024-01-02'),
|
||||
});
|
||||
|
||||
const result = await localFileCtr.readFile({ path: '/test/file.txt', fullContent: true });
|
||||
|
||||
expect(result.content).toBe(mockFileContent);
|
||||
expect(result.lineCount).toBe(5);
|
||||
expect(result.charCount).toBe(mockFileContent.length);
|
||||
expect(result.totalLineCount).toBe(5);
|
||||
expect(result.totalCharCount).toBe(mockFileContent.length);
|
||||
expect(result.loc).toEqual([0, 5]);
|
||||
});
|
||||
|
||||
it('should handle file read error', async () => {
|
||||
vi.mocked(mockLoadFile).mockRejectedValue(new Error('File not found'));
|
||||
|
||||
const result = await localFileCtr.readFile({ path: '/test/missing.txt' });
|
||||
|
||||
expect(result.content).toContain('Error accessing or processing file');
|
||||
expect(result.lineCount).toBe(0);
|
||||
expect(result.charCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFiles', () => {
|
||||
it('should read multiple files successfully', async () => {
|
||||
vi.mocked(mockLoadFile).mockResolvedValue({
|
||||
content: 'file content',
|
||||
filename: 'test.txt',
|
||||
fileType: 'txt',
|
||||
createdTime: new Date('2024-01-01'),
|
||||
modifiedTime: new Date('2024-01-02'),
|
||||
});
|
||||
|
||||
const result = await localFileCtr.readFiles({
|
||||
paths: ['/test/file1.txt', '/test/file2.txt'],
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockLoadFile).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleWriteFile', () => {
|
||||
it('should write file successfully', async () => {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type {
|
||||
HeterogeneousAgentBuildPlanHelpers,
|
||||
HeterogeneousAgentBuildPlanParams,
|
||||
} from '../types';
|
||||
import { claudeCodeDriver } from './claudeCode';
|
||||
|
||||
const stubHelpers: HeterogeneousAgentBuildPlanHelpers = {
|
||||
buildClaudeStreamJsonInput: async () => '{"type":"user","message":{}}\n',
|
||||
resolveCliImagePaths: async () => [],
|
||||
};
|
||||
|
||||
const buildParams = (
|
||||
overrides: Partial<HeterogeneousAgentBuildPlanParams> = {},
|
||||
): HeterogeneousAgentBuildPlanParams => ({
|
||||
args: [],
|
||||
helpers: stubHelpers,
|
||||
imageList: [],
|
||||
prompt: 'hi',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('claudeCodeDriver', () => {
|
||||
it('omits --mcp-config when mcpConfigPath is undefined', async () => {
|
||||
const { args } = await claudeCodeDriver.buildSpawnPlan(buildParams());
|
||||
expect(args).not.toContain('--mcp-config');
|
||||
});
|
||||
|
||||
it('appends --mcp-config <path> when mcpConfigPath is provided', async () => {
|
||||
const { args } = await claudeCodeDriver.buildSpawnPlan(
|
||||
buildParams({ mcpConfigPath: '/tmp/lobe-cc-mcp-op-1.json' }),
|
||||
);
|
||||
const idx = args.indexOf('--mcp-config');
|
||||
expect(idx).toBeGreaterThan(-1);
|
||||
expect(args[idx + 1]).toBe('/tmp/lobe-cc-mcp-op-1.json');
|
||||
});
|
||||
|
||||
it('still pins --disallowedTools AskUserQuestion alongside --mcp-config', async () => {
|
||||
// Even with our local MCP replacement available, CC's built-in stays
|
||||
// disabled — leaving both visible would let the model double-register
|
||||
// the same name and pick the broken one.
|
||||
const { args } = await claudeCodeDriver.buildSpawnPlan(
|
||||
buildParams({ mcpConfigPath: '/tmp/x.json' }),
|
||||
);
|
||||
const disallowedIdx = args.indexOf('--disallowedTools');
|
||||
expect(disallowedIdx).toBeGreaterThan(-1);
|
||||
expect(args[disallowedIdx + 1]).toBe('AskUserQuestion');
|
||||
});
|
||||
|
||||
it('--mcp-config goes before --resume so user --args can still override the resume id', async () => {
|
||||
const { args } = await claudeCodeDriver.buildSpawnPlan(
|
||||
buildParams({ mcpConfigPath: '/tmp/x.json', resumeSessionId: 'cc-prev-1' }),
|
||||
);
|
||||
const mcpIdx = args.indexOf('--mcp-config');
|
||||
const resumeIdx = args.indexOf('--resume');
|
||||
expect(mcpIdx).toBeGreaterThan(-1);
|
||||
expect(resumeIdx).toBeGreaterThan(-1);
|
||||
expect(mcpIdx).toBeLessThan(resumeIdx);
|
||||
expect(args[resumeIdx + 1]).toBe('cc-prev-1');
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,12 @@
|
||||
import { CLAUDE_CODE_BASE_ARGS } from '@lobechat/heterogeneous-agents/spawn';
|
||||
|
||||
import type { HeterogeneousAgentBuildPlanParams, HeterogeneousAgentDriver } from '../types';
|
||||
|
||||
// Desktop runs CC as the user (never root, so bypassPermissions is fine) and
|
||||
// renders the chat bubble live, so it always wants partial deltas. Compose
|
||||
// the shared invariant base args (`@lobechat/heterogeneous-agents/spawn`)
|
||||
// with those caller-specific flags.
|
||||
const DESKTOP_CLAUDE_CODE_ARGS = [
|
||||
...CLAUDE_CODE_BASE_ARGS,
|
||||
const CLAUDE_CODE_BASE_ARGS = [
|
||||
'-p',
|
||||
'--input-format',
|
||||
'stream-json',
|
||||
'--output-format',
|
||||
'stream-json',
|
||||
'--verbose',
|
||||
'--include-partial-messages',
|
||||
'--permission-mode',
|
||||
'bypassPermissions',
|
||||
@@ -18,7 +17,6 @@ export const claudeCodeDriver: HeterogeneousAgentDriver = {
|
||||
args,
|
||||
helpers,
|
||||
imageList,
|
||||
mcpConfigPath,
|
||||
prompt,
|
||||
resumeSessionId,
|
||||
}: HeterogeneousAgentBuildPlanParams) {
|
||||
@@ -26,11 +24,7 @@ export const claudeCodeDriver: HeterogeneousAgentDriver = {
|
||||
|
||||
return {
|
||||
args: [
|
||||
...DESKTOP_CLAUDE_CODE_ARGS,
|
||||
// Wire the controller-managed temp mcp.json (AskUserQuestion server,
|
||||
// see LOBE-8725) when present. Path-based config is required — CC
|
||||
// does not accept inline JSON for `--mcp-config`.
|
||||
...(mcpConfigPath ? ['--mcp-config', mcpConfigPath] : []),
|
||||
...CLAUDE_CODE_BASE_ARGS,
|
||||
...(resumeSessionId ? ['--resume', resumeSessionId] : []),
|
||||
...args,
|
||||
],
|
||||
|
||||
@@ -20,12 +20,6 @@ export interface HeterogeneousAgentBuildPlanParams {
|
||||
args: string[];
|
||||
helpers: HeterogeneousAgentBuildPlanHelpers;
|
||||
imageList: HeterogeneousAgentImageAttachment[];
|
||||
/**
|
||||
* Optional path to an MCP config JSON written by the controller (e.g. for
|
||||
* the local `lobe_cc` AskUserQuestion server). Drivers that recognize the
|
||||
* field append `--mcp-config <path>`; others ignore it.
|
||||
*/
|
||||
mcpConfigPath?: string;
|
||||
prompt: string;
|
||||
resumeSessionId?: string;
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import * as childProcess from 'node:child_process';
|
||||
import * as os from 'node:os';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Mocks must be set up before importing the module under test, because the
|
||||
// module captures `promisify(execFile)` / `promisify(exec)` at import time.
|
||||
vi.mock('node:os', async () => {
|
||||
const actual = await vi.importActual<typeof os>('node:os');
|
||||
return { ...actual, platform: vi.fn(() => actual.platform()) };
|
||||
});
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
exec: vi.fn(),
|
||||
execFile: vi.fn(),
|
||||
}));
|
||||
|
||||
const platformMock = vi.mocked(os.platform);
|
||||
const execFileMock = vi.mocked(childProcess.execFile);
|
||||
const execMock = vi.mocked(childProcess.exec);
|
||||
|
||||
const noErr = null;
|
||||
const callExecFile = (stdout: string, stderr = '') => {
|
||||
execFileMock.mockImplementationOnce(((file: string, args: any, opts: any, cb: any) => {
|
||||
// promisify-wrapped: the callback is always the last positional arg.
|
||||
const callback = typeof opts === 'function' ? opts : cb;
|
||||
callback(noErr, { stdout, stderr });
|
||||
return {} as any;
|
||||
}) as any);
|
||||
};
|
||||
const callExecFileError = (err: Error) => {
|
||||
execFileMock.mockImplementationOnce(((file: string, args: any, opts: any, cb: any) => {
|
||||
const callback = typeof opts === 'function' ? opts : cb;
|
||||
callback(err, { stdout: '', stderr: '' });
|
||||
return {} as any;
|
||||
}) as any);
|
||||
};
|
||||
const callExec = (stdout: string, stderr = '') => {
|
||||
execMock.mockImplementationOnce(((cmd: string, opts: any, cb: any) => {
|
||||
const callback = typeof opts === 'function' ? opts : cb;
|
||||
callback(noErr, { stdout, stderr });
|
||||
return {} as any;
|
||||
}) as any);
|
||||
};
|
||||
|
||||
describe('cliAgentDetectors', () => {
|
||||
beforeEach(() => {
|
||||
execFileMock.mockReset();
|
||||
execMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
describe('on Windows with an npm-installed `claude.cmd` shim', () => {
|
||||
beforeEach(() => {
|
||||
platformMock.mockReturnValue('win32');
|
||||
});
|
||||
|
||||
it('resolves `claude` to the .cmd path via `where`, then runs it through the shell', async () => {
|
||||
// 1) `where claude` → resolves to the .cmd shim under %APPDATA%\npm
|
||||
callExecFile('C:\\Users\\Hanam\\AppData\\Roaming\\npm\\claude.cmd\r\n');
|
||||
// 2) `cmd /c "...\\claude.cmd" --version` → keyword match
|
||||
callExec('1.2.3 (Claude Code)');
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(true);
|
||||
expect(status.path).toBe('C:\\Users\\Hanam\\AppData\\Roaming\\npm\\claude.cmd');
|
||||
expect(status.version).toBe('1.2.3 (Claude Code)');
|
||||
|
||||
// The validation call must go via `exec` (shell), NOT `execFile`, so
|
||||
// cmd.exe can actually interpret the .cmd shim.
|
||||
expect(execMock).toHaveBeenCalledTimes(1);
|
||||
const execCall = execMock.mock.calls[0]!;
|
||||
expect(execCall[0]).toBe('"C:\\Users\\Hanam\\AppData\\Roaming\\npm\\claude.cmd" --version');
|
||||
});
|
||||
|
||||
it('returns unavailable when `where` finds nothing', async () => {
|
||||
callExecFileError(new Error('not found'));
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(false);
|
||||
// We should NOT proceed to invoke anything after a failed resolve.
|
||||
expect(execMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects custom commands containing shell metacharacters', async () => {
|
||||
const { detectHeterogeneousCliCommand } = await import('../cliAgentDetectors');
|
||||
const status = await detectHeterogeneousCliCommand('claude-code', 'claude & calc.exe');
|
||||
|
||||
expect(status.available).toBe(false);
|
||||
expect(execFileMock).not.toHaveBeenCalled();
|
||||
expect(execMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fails detection when version output does not match the expected keyword', async () => {
|
||||
callExecFile('C:\\some\\other\\claude.cmd\r\n');
|
||||
callExec('this is some other binary v1.0');
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(false);
|
||||
});
|
||||
|
||||
it('prefers a .cmd shim when `where` returns multiple PATHEXT matches (codex case)', async () => {
|
||||
// npm drops a Unix shell-script wrapper (extensionless) alongside the
|
||||
// Windows `.cmd` / `.ps1` shims. `where` lists every PATHEXT match;
|
||||
// taking the first line would land us on the unrunnable wrapper.
|
||||
callExecFile(
|
||||
[
|
||||
'C:\\Users\\Hanam\\AppData\\Roaming\\npm\\codex',
|
||||
'C:\\Users\\Hanam\\AppData\\Roaming\\npm\\codex.cmd',
|
||||
'C:\\Users\\Hanam\\AppData\\Roaming\\npm\\codex.ps1',
|
||||
].join('\r\n'),
|
||||
);
|
||||
callExec('codex 0.130.0');
|
||||
|
||||
const { codexDetector } = await import('../cliAgentDetectors');
|
||||
const status = await codexDetector.detect();
|
||||
|
||||
expect(status.available).toBe(true);
|
||||
expect(status.path).toBe('C:\\Users\\Hanam\\AppData\\Roaming\\npm\\codex.cmd');
|
||||
expect(execMock.mock.calls[0]![0]).toBe(
|
||||
'"C:\\Users\\Hanam\\AppData\\Roaming\\npm\\codex.cmd" --version',
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers .exe over .cmd when both are present', async () => {
|
||||
callExecFile(['C:\\tools\\foo.exe', 'C:\\tools\\foo.cmd'].join('\r\n'));
|
||||
callExecFile('claude code 1.0.0');
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(true);
|
||||
expect(status.path).toBe('C:\\tools\\foo.exe');
|
||||
// .exe runs directly via execFile — no shell.
|
||||
expect(execMock).not.toHaveBeenCalled();
|
||||
expect(execFileMock).toHaveBeenCalledTimes(2);
|
||||
expect(execFileMock.mock.calls[1]![0]).toBe('C:\\tools\\foo.exe');
|
||||
});
|
||||
|
||||
it('reports unavailable when `where` only returns unrunnable matches (.ps1 / extensionless)', async () => {
|
||||
callExecFile(
|
||||
[
|
||||
'C:\\Users\\Hanam\\AppData\\Roaming\\npm\\claude',
|
||||
'C:\\Users\\Hanam\\AppData\\Roaming\\npm\\claude.ps1',
|
||||
].join('\r\n'),
|
||||
);
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(false);
|
||||
// Must not attempt to invoke the unrunnable matches.
|
||||
expect(execMock).not.toHaveBeenCalled();
|
||||
expect(execFileMock).toHaveBeenCalledTimes(1); // just `where`
|
||||
});
|
||||
});
|
||||
|
||||
describe('on macOS / Linux with a Unix-style claude binary', () => {
|
||||
beforeEach(() => {
|
||||
platformMock.mockReturnValue('darwin');
|
||||
});
|
||||
|
||||
it('runs the binary directly via execFile (no shell)', async () => {
|
||||
callExecFile('/usr/local/bin/claude\n');
|
||||
callExecFile('1.2.3 (Claude Code)');
|
||||
|
||||
const { claudeCodeDetector } = await import('../cliAgentDetectors');
|
||||
const status = await claudeCodeDetector.detect();
|
||||
|
||||
expect(status.available).toBe(true);
|
||||
expect(status.path).toBe('/usr/local/bin/claude');
|
||||
expect(execMock).not.toHaveBeenCalled();
|
||||
expect(execFileMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,11 @@
|
||||
import { exec, execFile } from 'node:child_process';
|
||||
import { execFile } from 'node:child_process';
|
||||
import { platform } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import type { IToolDetector, ToolStatus } from '@/core/infrastructure/ToolDetectorManager';
|
||||
import { createCommandDetector } from '@/core/infrastructure/ToolDetectorManager';
|
||||
|
||||
const execFilePromise = promisify(execFile);
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
type HeterogeneousCliAgentType = 'claude-code' | 'codex';
|
||||
|
||||
@@ -19,54 +17,17 @@ interface ValidatedDetectorOptions {
|
||||
validateKeywords: string[];
|
||||
}
|
||||
|
||||
const isWindows = () => platform() === 'win32';
|
||||
|
||||
// Reject anything that could break out of the `cmd /c "<path>" --version`
|
||||
// shell line we build for Windows .cmd shims (see `detectValidatedCommand`).
|
||||
// User-supplied custom commands flow through here via `detectHeterogeneousCliCommand`.
|
||||
const WINDOWS_SHELL_METAS = /[&|;<>^`!"]/;
|
||||
|
||||
// Extensions we can actually execute on Windows, in preference order:
|
||||
// `.exe` runs directly via `execFile`, `.cmd` / `.bat` runs via `cmd.exe`.
|
||||
// `.ps1` and extensionless wrappers (npm sometimes drops a Unix shell script
|
||||
// next to the `.cmd` shim) are deliberately excluded — we can't run them.
|
||||
const WINDOWS_RUNNABLE_EXTS = ['.exe', '.cmd', '.bat'] as const;
|
||||
|
||||
const pickWindowsRunnable = (lines: string[]): string | undefined => {
|
||||
for (const ext of WINDOWS_RUNNABLE_EXTS) {
|
||||
const match = lines.find((line) => line.toLowerCase().endsWith(ext));
|
||||
if (match) return match;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const resolveCommandPath = async (command: string): Promise<string | undefined> => {
|
||||
const trimmedCommand = command.trim();
|
||||
if (!trimmedCommand) return;
|
||||
|
||||
if (path.isAbsolute(trimmedCommand) || trimmedCommand.includes(path.sep)) {
|
||||
return trimmedCommand;
|
||||
}
|
||||
|
||||
const whichCommand = isWindows() ? 'where' : 'which';
|
||||
const whichCommand = platform() === 'win32' ? 'where' : 'which';
|
||||
|
||||
try {
|
||||
const { stdout } = await execFilePromise(whichCommand, [trimmedCommand], { timeout: 3000 });
|
||||
const lines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
if (lines.length === 0) return undefined;
|
||||
|
||||
// Windows `where` lists every PATHEXT match (e.g. for `codex` npm ships
|
||||
// a Unix shell wrapper alongside `codex.cmd` and `codex.ps1`). Picking
|
||||
// the first line can land us on something we can't execute, so prefer a
|
||||
// runnable extension and bail otherwise.
|
||||
if (isWindows()) return pickWindowsRunnable(lines);
|
||||
|
||||
return lines[0];
|
||||
return stdout.trim().split(/\r?\n/)[0] || trimmedCommand;
|
||||
} catch {
|
||||
return undefined;
|
||||
return trimmedCommand;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,27 +37,14 @@ const detectValidatedCommand = async (
|
||||
): Promise<ToolStatus> => {
|
||||
const trimmedCommand = command.trim();
|
||||
if (!trimmedCommand) return { available: false };
|
||||
if (isWindows() && WINDOWS_SHELL_METAS.test(trimmedCommand)) return { available: false };
|
||||
|
||||
const { validateFlag = '--version', validateKeywords } = options;
|
||||
|
||||
// Resolve via where/which BEFORE invoking. On Windows this is what discovers
|
||||
// npm-installed shims like `claude.cmd` under %APPDATA%\npm — `execFile`
|
||||
// alone won't apply PATHEXT and can't run .cmd files directly.
|
||||
const resolvedPath = await resolveCommandPath(trimmedCommand);
|
||||
if (!resolvedPath) return { available: false };
|
||||
|
||||
try {
|
||||
const needsShell = isWindows() && /\.(?:cmd|bat)$/i.test(resolvedPath);
|
||||
const { stderr, stdout } = needsShell
|
||||
? await execPromise(`"${resolvedPath}" ${validateFlag}`, {
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
})
|
||||
: await execFilePromise(resolvedPath, [validateFlag], {
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
const { stderr, stdout } = await execFilePromise(trimmedCommand, [validateFlag], {
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
const output = `${stdout}\n${stderr}`.trim();
|
||||
const loweredOutput = output.toLowerCase();
|
||||
|
||||
@@ -106,7 +54,7 @@ const detectValidatedCommand = async (
|
||||
|
||||
return {
|
||||
available: true,
|
||||
path: resolvedPath,
|
||||
path: await resolveCommandPath(trimmedCommand),
|
||||
version: output.split(/\r?\n/)[0],
|
||||
};
|
||||
} catch {
|
||||
|
||||
@@ -345,7 +345,7 @@ export class DeviceGatewayDO extends DurableObject<Env> {
|
||||
const sockets = this.getAuthenticatedSockets();
|
||||
if (sockets.length === 0) {
|
||||
return Response.json(
|
||||
{ content: 'Desktop device offline', error: 'DEVICE_OFFLINE', success: false },
|
||||
{ content: '桌面设备不在线', error: 'DEVICE_OFFLINE', success: false },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
@@ -395,7 +395,7 @@ export class DeviceGatewayDO extends DurableObject<Env> {
|
||||
} catch (err) {
|
||||
return Response.json(
|
||||
{
|
||||
content: `Tool call timed out (${timeout / 1000}s)`,
|
||||
content: `工具调用超时(${timeout / 1000}s)`,
|
||||
error: (err as Error).message,
|
||||
success: false,
|
||||
},
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["hide runtime-only model aliases."],
|
||||
"features": ["set OSS default model to DeepSeek V4 Pro."]
|
||||
},
|
||||
"date": "2026-05-09",
|
||||
"version": "2.1.57"
|
||||
},
|
||||
{
|
||||
"children": {},
|
||||
"date": "2026-05-01",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"https://file.rene.wang/540830955-0fe626a3-0ddc-4f67-b595-3c5b3f1701e0.png": "/blog/assetsa8e504275f2cd891fabecca985998de0.webp",
|
||||
"https://file.rene.wang/Changelog-Seedance.png": "/blog/assetsb2bf4ddf0a45ff887a993c18cb7ab983.webp",
|
||||
"https://file.rene.wang/changlog-04-14.png": "/blog/assets300abe7e259d293da6c5ed4f642a1be6.webp",
|
||||
"https://file.rene.wang/clipboard-1768907980491-9cc0669fc3a38.png": "/blog/assets8be3a46c8f9c5d3b61bc541f44b7f245.webp",
|
||||
"https://file.rene.wang/clipboard-1768908081787-ed9eb1cb78bdb.png": "/blog/assetsab009b79dd794f02aec24b7607f342e8.webp",
|
||||
@@ -54,8 +53,6 @@
|
||||
"https://file.rene.wang/clipboard-1774923001079-89ce6aa271a62.png": "/blog/assets53e6ec9cf72554dbc1f8224fc0550a03.webp",
|
||||
"https://file.rene.wang/clipboard-1775701725582-123f8f8cf73f8.png": "/blog/assets7ea204859aeb5aa9be5810a20ba1669a.webp",
|
||||
"https://file.rene.wang/clipboard-1776909505252-94b051f3ea0a7.png": "/blog/assetsdfda32866c4bc59af0526e52f31d1da2.webp",
|
||||
"https://file.rene.wang/clipboard-1777343750668-9b3dcb0dfff86.png": "/blog/assetsfa267a02f20bc5ba6f1273bcf27b7c9f.webp",
|
||||
"https://file.rene.wang/clipboard-1778331942656-f33b41b2dc439.png": "/blog/assets71fe5959cbc6f0a89243d7262f48fafc.webp",
|
||||
"https://file.rene.wang/lobehub/467951f5-ad65-498d-aea9-fca8f35a4314.png": "/blog/assets907ea775d228958baca38e2dbb65939a.webp",
|
||||
"https://file.rene.wang/lobehub/58d91528-373a-4a42-b520-cf6cb1f8ce1e.png": "/blog/assets7dccdd4df55aede71001da649639437f.webp",
|
||||
"https://file.rene.wang/lobehub/ee700103-3c08-41dc-9ddf-c7705bb7bc6a.png": "/blog/assets196d679bc7071abbf71f2a8566f05aa3.webp",
|
||||
@@ -472,5 +469,6 @@
|
||||
"https://github.com/user-attachments/assets/facdc83c-e789-4649-8060-7f7a10a1b1dd": "/blog/assets05b20e40c03ced0ec8707fed2e8e0f25.webp",
|
||||
"https://github.com/user-attachments/assets/fcdfb9c5-819a-488f-b28d-0857fe861219": "/blog/assets8477415ecec1f37e38ab38ff1217d0a7.webp",
|
||||
"https://github.com/user-attachments/assets/fd60ab55-ead2-4930-ad00-fdf77662f5a0": "/blog/assets276a4e8748e9bd300b30dcd9d0e24980.webp",
|
||||
"https://file.rene.wang/task.png": "/blog/assets4aa1732a45832afc780600e6e329860c.webp"
|
||||
"https://file.rene.wang/clipboard-1777343750668-9b3dcb0dfff86.png": "/blog/assetsfa267a02f20bc5ba6f1273bcf27b7c9f.webp",
|
||||
"https://file.rene.wang/Changelog-Seedance.png": "/blog/assetsb2bf4ddf0a45ff887a993c18cb7ab983.webp"
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
title: Delegate Claude Code and Codex
|
||||
title: 'Delegate Claude Code and Codex'
|
||||
description: >-
|
||||
Delegate Claude Code and Codex from inside LobeHub, with a redesigned home, a
|
||||
Review tab for bulk git diffs, visual understanding, and a wave of new models.
|
||||
Delegate Claude Code and Codex from inside LobeHub, with a redesigned home, a Review tab for bulk git diffs, visual understanding, and a wave of new models.
|
||||
|
||||
|
||||
tags:
|
||||
- Coding agent
|
||||
- Claude Code
|
||||
@@ -13,12 +14,9 @@ tags:
|
||||
|
||||
# Delegate Claude Code and Codex
|
||||
|
||||
Now you can control coding agents in LobeHub. Simply click `Create Agent` and choose your coding agent. This feature is only available on desktop app.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- New: Delegate Claude Code and Codex in LobeHub
|
||||
- Agent-specific topic grouping: switch the topic list to group by agent, with a friendlier empty state
|
||||
- Review tab: a new tab that aggregates bulk git diffs across a tree, \~9× faster on large repos
|
||||
- Local file mention snapshots: drag a file into chat and a snapshot is captured for the model to reason over
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: 在 LobeHub 中调度 Claude Code 与 Codex
|
||||
description: >-
|
||||
在 LobeHub 中直接调度 Claude Code 与 Codex,全新首页、批量 git diff 的 Review
|
||||
标签页、视觉理解工具,以及一批新模型。
|
||||
description: 在 LobeHub 中直接调度 Claude Code 与 Codex,全新首页、批量 git diff 的 Review 标签页、视觉理解工具,以及一批新模型。
|
||||
tags:
|
||||
- 编程 Agent
|
||||
- Claude Code
|
||||
@@ -13,10 +11,6 @@ tags:
|
||||
|
||||
# 在 LobeHub 中调度 Claude Code 与 Codex
|
||||
|
||||
现在你可以在 LobeHub 内使用 Coding Agents。新建助手时选择你最喜欢的 Coding Agent 即可。此功能仅在桌面端可用。
|
||||
|
||||

|
||||
|
||||
## 新功能
|
||||
|
||||
- 新增:在 LobeHub 中调度 Claude Code 与 Codex
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: Agent Tasks GA & Cloud Heterogeneous Agent
|
||||
description: >-
|
||||
Agent Tasks reaches GA with templates, cron, and batch runs; heterogeneous
|
||||
agents now run in the cloud; bot platforms expand to Messenger, Line, and
|
||||
Telegram.
|
||||
tags:
|
||||
- Agent Tasks
|
||||
- Heterogeneous Agent
|
||||
- Bots
|
||||
- Models
|
||||
---
|
||||
|
||||
# Agent Tasks GA & Cloud Heterogeneous Agent
|
||||
|
||||
## Tasks
|
||||
|
||||
Think of Agent Tasks like Linear, but with agents as your teammates. Create tasks the same way you'd file an issue — title, description, optional template — and assign them to an agent instead of a person. The agent picks up the task, executes the work, posts updates in comments, and moves the status forward (todo → in progress → done) as it makes progress.
|
||||
|
||||
Tasks can have subtasks with explicit dependencies, so a parent task can fan out work and the agent will run subtasks in dependency order. Recurring tasks can be wired to a cron schedule, parent assignments can be reshuffled at any time, and every task has its own thread of comments where you and the agent can coordinate.
|
||||
|
||||
Learn more in the [Task guide](/docs/usage/getting-started/task).
|
||||
|
||||
## Features
|
||||
|
||||
- Agent Tasks goes GA: the full task platform with templates, scheduled cron, comment tools, parent reassignment, and dependency-ordered batch subtask runs
|
||||
- Nightly self-review: Agent Signal pipeline runs automatic self-review with skill-aware policies and pushes activity into briefs
|
||||
- Cloud heterogeneous agents: Claude Code and Codex now execute server-side with persistent sessions that survive Vercel replica restarts
|
||||
- `lh hetero exec` CLI: run a standalone heterogeneous agent from the terminal, with multimodal input support across desktop / CLI
|
||||
- Claude Code can now pause and ask you a question mid-execution
|
||||
- Inline agents in chat: `lobeAgents` markdown tag renders agent profile cards, and a newly created agent shows up as a clickable card
|
||||
- Bot platforms expand: Messenger, Line, and Telegram integrations with DM pair policy and per-sender device tool gating
|
||||
- New models: Gemini 3.1 Flash-Lite, SiliconCloud model sync, and DeepSeek V4 Pro as the new OSS default
|
||||
|
||||
## Improvements and fixes
|
||||
|
||||
- Inline document grounding in the KB tool via BM25 search and `docs_*` reads.
|
||||
- Daily Brief redesigned with linkable welcome card and a paired input hint; resolved briefs now show a mute icon.
|
||||
- Long tool-call parameters now wrap instead of truncating; tool execution time formatted as `Xmin Ys`.
|
||||
- Visible divider between queued messages so it's clear which sends are pending.
|
||||
- Copy session ID added to the topic dropdown menu.
|
||||
- Home sidebar collapse state persists across reloads.
|
||||
- Desktop app tray visibility is now a setting.
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
title: Agent 任务系统 GA 与云端异构 Agent
|
||||
description: >-
|
||||
Agent 任务系统正式发布,支持模板、Cron 与批量子任务;异构 Agent 进入云端;Bot 平台新增 Messenger、Line 与
|
||||
Telegram。
|
||||
tags:
|
||||
- Agent 任务
|
||||
- 异构 Agent
|
||||
- Bot
|
||||
- 模型
|
||||
---
|
||||
|
||||
# Agent 任务系统 GA 与云端异构 Agent
|
||||
|
||||
## Agent 任务系统
|
||||
|
||||
Agent 任务系统的体感类似 Linear,但「队友」是 Agent。你像建 Issue 一样创建任务 —— 标题、描述、可选模板 —— 把它分配给 Agent 而不是某个人。Agent 接到任务后会执行工作、在评论中同步进展,并随着推进更新状态(待办 → 进行中 → 已完成)。
|
||||
|
||||
任务支持带显式依赖的子任务,父任务可以拆分工作,Agent 会按依赖顺序运行子任务。周期性任务可以挂接 Cron 计划;父任务的指派可以随时重新调整;每个任务都有自己的评论线,方便你和 Agent 协作沟通。
|
||||
|
||||
详见 [任务使用指南](/docs/usage/getting-started/task)。
|
||||
|
||||
## 新功能
|
||||
|
||||
- Agent 任务系统 GA:完整的任务平台,支持模板、Cron 定时、评论工具、父任务重指派,以及按依赖顺序的批量子任务运行
|
||||
- 夜间自审:Agent Signal 流水线自动运行自审,结合技能感知策略并将活动推送到简报
|
||||
- 云端异构 Agent:Claude Code 与 Codex 在服务端运行,会话持久化可跨 Vercel 副本恢复
|
||||
- `lh hetero exec` CLI:在终端独立运行异构 Agent,桌面端 / CLI 支持多模态输入
|
||||
- AskUserQuestion 工具:Claude Code 可在执行过程中暂停并向你提问
|
||||
- 聊天内联 Agent:`lobeAgents` Markdown 标签渲染 Agent 卡片,新建的 Agent 会以可点击卡片形式出现
|
||||
- Bot 平台扩展:新增 Messenger、Line、Telegram 接入,支持 DM 配对策略与按发送者识别的设备工具网关
|
||||
- 新模型:Gemini 3.1 Flash-Lite、SiliconCloud 模型同步,DeepSeek V4 Pro 成为开源版默认模型
|
||||
|
||||
## 体验优化与修复
|
||||
|
||||
- 知识库工具支持通过 BM25 搜索与 `docs_*` 读取实现内联文档落地。
|
||||
- 每日简报改版:欢迎卡片可链接、输入提示成对出现;已处理的简报展示静音图标。
|
||||
- 工具调用参数过长时自动换行,不再截断;工具执行时间格式化为 `Xmin Ys`。
|
||||
- 排队消息之间新增可见分隔线,方便辨认待发送的内容。
|
||||
- 话题下拉菜单新增「复制会话 ID」操作。
|
||||
- 首页侧边栏的折叠状态在刷新后会保留。
|
||||
- 桌面应用托盘可见性现已纳入设置。
|
||||
@@ -2,14 +2,6 @@
|
||||
"$schema": "https://github.com/lobehub/lobe-chat/blob/main/docs/changelog/schema.json",
|
||||
"cloud": [],
|
||||
"community": [
|
||||
{
|
||||
"image": "/blog/assets4aa1732a45832afc780600e6e329860c.webp",
|
||||
"id": "2026-05-11-agent-tasks-ga",
|
||||
"date": "2026-05-11",
|
||||
"versionRange": [
|
||||
"2.1.57"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsb2bf4ddf0a45ff887a993c18cb7ab983.webp",
|
||||
"id": "2026-05-04-task-scheduler",
|
||||
|
||||
@@ -267,62 +267,6 @@ table agent_eval_test_cases {
|
||||
}
|
||||
}
|
||||
|
||||
table agent_operations {
|
||||
id text [pk, not null]
|
||||
user_id text [not null]
|
||||
agent_id text
|
||||
topic_id text
|
||||
thread_id text
|
||||
task_id text
|
||||
chat_group_id text
|
||||
parent_operation_id text
|
||||
status text [not null]
|
||||
completion_reason text
|
||||
started_at "timestamp with time zone"
|
||||
completed_at "timestamp with time zone"
|
||||
step_count integer
|
||||
max_steps integer
|
||||
force_finish boolean
|
||||
interruption jsonb
|
||||
error jsonb
|
||||
total_cost "numeric(20, 6)"
|
||||
currency text [not null, default: 'USD']
|
||||
total_input_tokens integer
|
||||
total_output_tokens integer
|
||||
total_tokens integer
|
||||
llm_calls integer
|
||||
tool_calls integer
|
||||
human_interventions integer
|
||||
processing_time_ms integer
|
||||
human_waiting_time_ms integer
|
||||
cost jsonb
|
||||
usage jsonb
|
||||
cost_limit jsonb
|
||||
model text
|
||||
provider text
|
||||
model_runtime_config jsonb
|
||||
trigger text
|
||||
app_context jsonb
|
||||
trace_s3_key text
|
||||
metadata jsonb [default: `{}`]
|
||||
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
||||
created_at "timestamp with time zone" [not null, default: `now()`]
|
||||
updated_at "timestamp with time zone" [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
user_id [name: 'agent_operations_user_id_idx']
|
||||
agent_id [name: 'agent_operations_agent_id_idx']
|
||||
topic_id [name: 'agent_operations_topic_id_idx']
|
||||
thread_id [name: 'agent_operations_thread_id_idx']
|
||||
task_id [name: 'agent_operations_task_id_idx']
|
||||
chat_group_id [name: 'agent_operations_chat_group_id_idx']
|
||||
parent_operation_id [name: 'agent_operations_parent_operation_id_idx']
|
||||
status [name: 'agent_operations_status_idx']
|
||||
(user_id, created_at) [name: 'agent_operations_user_id_created_at_idx']
|
||||
metadata [name: 'agent_operations_metadata_idx']
|
||||
}
|
||||
}
|
||||
|
||||
table agent_skills {
|
||||
id text [pk, not null]
|
||||
name text [not null]
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
---
|
||||
title: Task
|
||||
description: >-
|
||||
Learn how to use Tasks in LobeHub to delegate work to agents. Create tasks, assign them to agents, track status, comment for follow-ups, and run tasks one-off or on a recurring schedule.
|
||||
|
||||
|
||||
tags:
|
||||
- Task
|
||||
- Issue Tracker
|
||||
- Agent Assignment
|
||||
- Recurring Task
|
||||
- Workflow
|
||||
---
|
||||
|
||||
# Task
|
||||
|
||||

|
||||
|
||||
**Task** turns a conversation with an agent into trackable work. Instead of chatting in real time and copying results around, you write down what you want, assign it to an agent, and let the agent run it in the background. The agent posts progress, updates the status when it's done, and replies when you leave a comment.
|
||||
|
||||
If you've used Linear or GitHub Issues, the mental model is the same — only the assignee is an agent, and the agent actually does the work.
|
||||
|
||||
## When to Use a Task
|
||||
|
||||
Use a Task when you want an agent to:
|
||||
|
||||
- Do work that takes more than a few minutes to finish.
|
||||
- Run on a schedule (every morning, every Monday, every month).
|
||||
- Report back asynchronously while you focus on something else.
|
||||
- Be re-assigned, commented on, or revisited later with full history preserved.
|
||||
|
||||
For quick, one-shot questions, stay in the regular chat. For anything you'd otherwise track in a todo list or ticket, create a Task.
|
||||
|
||||
## Task Lifecycle
|
||||
|
||||
Every task moves through a small set of statuses:
|
||||
|
||||
| Status | Meaning |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| **Backlog** | Created but not yet picked up by the agent. |
|
||||
| **In Progress** | The agent is actively working on the task. |
|
||||
| **Pending Review** | The agent finished and is waiting for you to verify the result. |
|
||||
| **Done** | You confirmed the result; the task is closed. |
|
||||
| **Canceled** | You closed the task before completion. |
|
||||
|
||||
The agent moves a task from `Backlog` to `In Progress`, then to `Pending Review` when it thinks the work is done. The transition to `Done` is yours to make — see [Reviewing Results](#reviewing-results) below.
|
||||
|
||||
## Creating a Task
|
||||
|
||||
<Steps>
|
||||
### Open the Tasks Panel
|
||||
|
||||
Click **Tasks** in the left sidebar to open the task list for your workspace.
|
||||
|
||||
### Create a New Task
|
||||
|
||||
Click **New Task** in the top right. Give it a clear title — the agent uses the title and description to understand what you want.
|
||||
|
||||
### Write a Description
|
||||
|
||||
Describe the work the same way you'd describe it to a teammate. Include any links, files, or constraints the agent needs. You can paste images and attach Resources just like in a regular chat.
|
||||
|
||||
### Assign an Agent
|
||||
|
||||
Pick an agent from the **Assignee** dropdown. Choose an agent whose capabilities match the task — for example, the **Research Agent** for reading and summarizing, or a custom agent you've built. You can reassign later if the first agent isn't the right fit.
|
||||
|
||||
### Choose a Schedule
|
||||
|
||||
Pick **Run once** for a one-off task, or **Repeat** to put it on a schedule. See [One-off vs. Recurring](#one-off-vs-recurring) below.
|
||||
|
||||
### Submit
|
||||
|
||||
Click **Create**. The task lands in **Backlog**, and the agent picks it up shortly after.
|
||||
|
||||
</Steps>
|
||||
|
||||
<Callout type="info">
|
||||
You can create a Task directly from any chat message — open the message menu and choose **Turn
|
||||
into Task**. The conversation context is carried over automatically.
|
||||
</Callout>
|
||||
|
||||
## Working With the Agent
|
||||
|
||||
While the task is `In Progress`, the agent posts updates inside the task — every step it takes, every tool call, and every intermediate result. You don't have to watch in real time; open the task whenever you want to see where things stand.
|
||||
|
||||
### Reviewing Results
|
||||
|
||||
When the agent thinks it's finished, the task moves to `Pending Review`. Open the task detail page to verify the result. You have two options:
|
||||
|
||||
- **Confirm Complete** — if the result is good, click the **Confirm Complete** button. The task moves to `Done` and closes out.
|
||||
- **Follow up** — if something needs adjustment, leave a comment instead. The agent picks the task back up and continues from where it left off.
|
||||
|
||||
A `Pending Review` task never auto-completes; you stay in control of when work is done.
|
||||
|
||||
### Comments and Follow-ups
|
||||
|
||||
Every task has a comment thread. Use comments to:
|
||||
|
||||
- **Clarify** when the agent asks a question mid-run.
|
||||
- **Course-correct** if the agent is heading in the wrong direction.
|
||||
- **Iterate** at review time — leave a comment like _"Same thing but exclude weekends"_ and the agent reopens the task and tries again.
|
||||
|
||||
The agent reads new comments automatically and follows up. There's no separate "send" — your comment is the instruction.
|
||||
|
||||
<Callout type="info">
|
||||
If the agent is in the middle of a run, your comment is queued until the next checkpoint so it
|
||||
doesn't interrupt mid-step.
|
||||
</Callout>
|
||||
|
||||
### Artifacts
|
||||
|
||||
Pages the agent creates during execution — research notes, summaries, drafts, anything written to your workspace — are listed in the **Artifacts** section of the task detail page. Open, share, or keep editing them directly from there without leaving the task.
|
||||
|
||||
## One-off vs. Recurring
|
||||
|
||||
Tasks support two schedule modes.
|
||||
|
||||
### Run Once
|
||||
|
||||
The default. The agent runs the task immediately, posts a result, and moves it to `Pending Review` for you to confirm. Use this for everything that doesn't need to repeat.
|
||||
|
||||
### Repeat
|
||||
|
||||
Put the task on a schedule and the agent re-runs it automatically. Each run is appended to the same task as a new entry, so you build up a history you can compare across runs.
|
||||
|
||||
Supported intervals:
|
||||
|
||||
- **Hourly** — every _N_ hours.
|
||||
- **Daily** — at a specific time each day.
|
||||
- **Weekly** — on chosen days of the week.
|
||||
- **Monthly** — on a specific day of the month.
|
||||
- **Custom** — any cron expression.
|
||||
|
||||
<Callout type="warning">
|
||||
Recurring tasks consume credits on every run. Check the estimated credit cost shown in the
|
||||
scheduler before saving, and pause the task if you no longer need it.
|
||||
</Callout>
|
||||
|
||||
You can pause, resume, or change the schedule at any time from the task detail page. Pausing keeps history intact; deleting removes the task and its run history.
|
||||
|
||||
## Examples
|
||||
|
||||
A few patterns that work well as Tasks:
|
||||
|
||||
- **Daily market digest** — a Research Agent that summarizes overnight news every weekday at 8 AM.
|
||||
- **Weekly competitor scan** — an agent that visits five competitor sites and flags pricing changes.
|
||||
- **One-off deep research** — a long-running task ("compare these 12 vector databases") you check on later.
|
||||
- **Recurring data pull** — an agent that queries a database and posts the result on Mondays.
|
||||
- **Triage queue** — an inbox-like project where you drop ideas and an agent prepares first-draft answers overnight.
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
title: 任务
|
||||
description: >-
|
||||
了解如何在 LobeHub 中使用任务(Task)将工作委派给 Agent。创建任务、分配给 Agent、跟踪状态、通过评论进行追问,以及一次性运行或按周期重复执行。
|
||||
|
||||
|
||||
tags:
|
||||
- Task
|
||||
- 任务
|
||||
- Issue 跟踪
|
||||
- Agent 分配
|
||||
- 周期任务
|
||||
- 工作流
|
||||
---
|
||||
|
||||
# 任务
|
||||
|
||||

|
||||
|
||||
**任务(Task)** 把你和 Agent 的对话变成可追踪的工作。不必实时聊天再到处复制结果,你可以写下需求、把它指派给一个 Agent,让 Agent 在后台为你完成。Agent 会回报进度、在完成后更新状态,并在你留下评论时继续跟进。
|
||||
|
||||
如果你用过 Linear 或 GitHub Issues,思维模型完全一致 —— 只是这里的执行人是 Agent,并且它会真的把活干完。
|
||||
|
||||
## 什么时候用任务
|
||||
|
||||
当你希望 Agent 做下面这些事情时,就适合开任务:
|
||||
|
||||
- 完成耗时超过几分钟的工作。
|
||||
- 按计划运行(每天早上、每周一、每月一次)。
|
||||
- 异步反馈进度,让你可以同时处理其他事情。
|
||||
- 留下完整历史,便于后续重新分配、评论或回溯。
|
||||
|
||||
对于一次性的、很快能答完的问题,留在普通对话里就好。任何你原本会记在待办列表或工单里的事情,都建议开一个任务。
|
||||
|
||||
## 任务生命周期
|
||||
|
||||
每个任务会在一组简单的状态之间流转:
|
||||
|
||||
| 状态 | 含义 |
|
||||
| ------------------ | ---------------------------------- |
|
||||
| **Backlog** | 已创建,Agent 还没开始处理。 |
|
||||
| **In Progress** | Agent 正在执行该任务。 |
|
||||
| **Pending Review** | Agent 完成了工作,等待你验收结果。 |
|
||||
| **Done** | 你已确认结果,任务关闭。 |
|
||||
| **Canceled** | 在完成前你主动关闭了该任务。 |
|
||||
|
||||
Agent 会自动把任务从 `Backlog` 推进到 `In Progress`,再到 `Pending Review`。任务什么时候变成 `Done`,由你决定 —— 详见下方的 [验收结果](#验收结果)。
|
||||
|
||||
## 创建任务
|
||||
|
||||
<Steps>
|
||||
### 打开任务面板
|
||||
|
||||
点击左侧导航中的 **Tasks**,进入当前工作区的任务列表。
|
||||
|
||||
### 新建任务
|
||||
|
||||
在右上角点击 **New Task**。给任务起一个清晰的标题 —— Agent 会根据标题和描述来理解你的意图。
|
||||
|
||||
### 填写描述
|
||||
|
||||
像给同事派活一样描述工作内容,包含必要的链接、文件或限制条件。你可以像在普通对话里一样粘贴图片、附加 Resource。
|
||||
|
||||
### 指派 Agent
|
||||
|
||||
在 **Assignee** 下拉框中选择合适的 Agent。根据任务挑选能力匹配的 Agent —— 比如 **Research Agent** 适合阅读和总结,或者使用你自己创建的自定义 Agent。后续也可以重新分配。
|
||||
|
||||
### 选择运行方式
|
||||
|
||||
选择 **Run once** 进行一次性运行,或选择 **Repeat** 设置为周期任务。详见下方的 [一次性任务 vs. 周期任务](#一次性任务-vs-周期任务)。
|
||||
|
||||
### 提交
|
||||
|
||||
点击 **Create**。任务会进入 **Backlog** 状态,Agent 很快就会开始执行。
|
||||
|
||||
</Steps>
|
||||
|
||||
<Callout type="info">
|
||||
你也可以从任意聊天消息直接创建任务 —— 打开消息菜单选择 **Turn into Task**,对话上下文会自动带入。
|
||||
</Callout>
|
||||
|
||||
## 与 Agent 协作
|
||||
|
||||
当任务进入 `In Progress` 后,Agent 会在任务内部持续记录进度 —— 每一步动作、每一次工具调用、每一个中间结果。你不需要盯着看,随时打开任务就能看到当前状态。
|
||||
|
||||
### 验收结果
|
||||
|
||||
当 Agent 认为工作完成后,任务会进入 `Pending Review` 状态。打开任务详情页验收结果,你有两种选择:
|
||||
|
||||
- **Confirm Complete** —— 如果结果满意,点击 **Confirm Complete** 按钮。任务进入 `Done` 状态并归档。
|
||||
- **追加评论** —— 如果还需要调整,直接留下评论。Agent 会从上次中断的地方继续推进。
|
||||
|
||||
`Pending Review` 任务不会自动完成 —— 任务是否结束完全由你决定。
|
||||
|
||||
### 评论与追问
|
||||
|
||||
每个任务都有一条评论线索,可以用来:
|
||||
|
||||
- **澄清**:当 Agent 在执行过程中提问时回复它。
|
||||
- **纠偏**:当 Agent 走偏方向时及时拉回。
|
||||
- **迭代**:在验收阶段留一条 _"同样的内容但排除周末"_ 这类评论,Agent 会重新打开任务再跑一次。
|
||||
|
||||
Agent 会自动读取新评论并继续跟进,不需要单独的 "发送" 动作 —— 你的评论就是新的指令。
|
||||
|
||||
<Callout type="info">
|
||||
如果 Agent 正在执行某一步,你的评论会排在下一个检查点处理,避免中途打断它。
|
||||
</Callout>
|
||||
|
||||
### 产出物(Artifacts)
|
||||
|
||||
Agent 在任务执行过程中创建的所有页面 —— 研究笔记、摘要、初稿,以及任何写入工作区的内容 —— 都会列在任务详情页的 **Artifacts** 区域。你可以直接打开、分享或继续编辑,全程不离开任务。
|
||||
|
||||
## 一次性任务 vs. 周期任务
|
||||
|
||||
任务支持两种运行方式。
|
||||
|
||||
### 一次性运行(Run Once)
|
||||
|
||||
默认方式。Agent 立即执行任务、提交结果,然后把任务推进到 `Pending Review` 等待你确认。绝大多数不需要重复的需求都用这种。
|
||||
|
||||
### 周期运行(Repeat)
|
||||
|
||||
把任务设置成定时计划,Agent 会按计划自动重新执行。每次运行的结果都会追加到同一个任务里,形成一份可对比的历史记录。
|
||||
|
||||
支持的周期:
|
||||
|
||||
- **Hourly** —— 每隔 _N_ 小时。
|
||||
- **Daily** —— 每天的指定时间。
|
||||
- **Weekly** —— 每周指定的几天。
|
||||
- **Monthly** —— 每月指定的一天。
|
||||
- **Custom** —— 任意 cron 表达式。
|
||||
|
||||
<Callout type="warning">
|
||||
周期任务每次运行都会消耗积分。保存前请查看调度面板上预估的积分开销;如果不再需要,记得及时暂停任务。
|
||||
</Callout>
|
||||
|
||||
你可以在任务详情页随时暂停、恢复或修改计划。暂停会保留历史;删除会同时清除任务及其所有运行记录。
|
||||
|
||||
## 使用示例
|
||||
|
||||
下面这些场景特别适合做成任务:
|
||||
|
||||
- **每日市场摘要** —— 一个 Research Agent,在每个工作日早上 8 点汇总隔夜资讯。
|
||||
- **每周竞品扫描** —— 一个 Agent 访问 5 个竞品网站并提示定价变化。
|
||||
- **一次性深度研究** —— 一个长跑任务("对比这 12 个向量数据库"),你过一会儿再回来看结果。
|
||||
- **周期数据拉取** —— 一个 Agent 每周一查询数据库并把结果发到任务里。
|
||||
- **想法收件箱** —— 把临时灵感丢进任务列表,让 Agent 在夜里准备好初稿,第二天直接修改。
|
||||
@@ -55,16 +55,3 @@ Feature: 发送消息与流式输出期间的视口滚动行为
|
||||
And 等待流式响应结束
|
||||
And 用户发送一条触发长文输出的消息
|
||||
Then 用户消息应固定在聊天列表顶部
|
||||
|
||||
# Regression guard for the spacer-shrink issue: after streaming has ended,
|
||||
# layout/virtual-list offset corrections can emit scroll events without any
|
||||
# wheel, touch, keyboard, or pointer scroll input. Those synthetic negative
|
||||
# offsets must not be treated as user scroll-up intent.
|
||||
@AGENT-SCROLL-006 @P0 @journey
|
||||
Scenario: 非用户触发的上移不应收缩底部补偿区域
|
||||
Given 用户进入 Lobe AI 对话页面
|
||||
When 用户完成一轮用于垫高列表的长回复对话
|
||||
And 用户发送一条触发短回复的消息并等待回复完成
|
||||
And 记录聊天列表底部补偿区域高度
|
||||
And 模拟非用户触发的聊天列表上移 120 像素
|
||||
Then 聊天列表底部补偿区域高度不应收缩
|
||||
|
||||
@@ -22,7 +22,6 @@ const AT_BOTTOM_EPSILON = 320;
|
||||
const MANUAL_SCROLL_UP_DELTA = 200;
|
||||
|
||||
interface ScrollSnapshot {
|
||||
bottomCompensationHeight: number;
|
||||
clientHeight: number;
|
||||
distanceToBottom: number;
|
||||
scrollHeight: number;
|
||||
@@ -43,18 +42,7 @@ async function getScrollSnapshot(world: CustomWorld): Promise<ScrollSnapshot | n
|
||||
while (el) {
|
||||
const style = window.getComputedStyle(el);
|
||||
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
|
||||
const bottomCompensationHeight = Math.max(
|
||||
0,
|
||||
...Array.from(el.querySelectorAll<HTMLElement>('div[aria-hidden="true"]'))
|
||||
.filter((node) => {
|
||||
const nodeStyle = window.getComputedStyle(node);
|
||||
return nodeStyle.pointerEvents === 'none' && node.offsetWidth > 0;
|
||||
})
|
||||
.map((node) => node.getBoundingClientRect().height),
|
||||
);
|
||||
|
||||
return {
|
||||
bottomCompensationHeight,
|
||||
clientHeight: el.clientHeight,
|
||||
distanceToBottom: el.scrollHeight - el.scrollTop - el.clientHeight,
|
||||
scrollHeight: el.scrollHeight,
|
||||
@@ -67,41 +55,6 @@ async function getScrollSnapshot(world: CustomWorld): Promise<ScrollSnapshot | n
|
||||
});
|
||||
}
|
||||
|
||||
async function sendPrompt(world: CustomWorld, prompt: string, response: string): Promise<void> {
|
||||
llmMockManager.setResponse(prompt, response);
|
||||
|
||||
await world.page.keyboard.type(prompt, { delay: 20 });
|
||||
await world.page.waitForTimeout(200);
|
||||
await world.page.keyboard.press('Enter');
|
||||
}
|
||||
|
||||
async function waitForAssistantMessageToSettle(
|
||||
world: CustomWorld,
|
||||
minLength: number,
|
||||
): Promise<void> {
|
||||
const assistantMessage = world.page
|
||||
.locator('.message-wrapper')
|
||||
.filter({ has: world.page.locator('text=Lobe AI') })
|
||||
.last();
|
||||
|
||||
await expect(assistantMessage).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
let prevLen = 0;
|
||||
let stableTicks = 0;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const len =
|
||||
(await assistantMessage
|
||||
.innerText()
|
||||
.then((t) => t.length)
|
||||
.catch(() => 0)) || 0;
|
||||
if (len > minLength && len === prevLen) stableTicks += 1;
|
||||
else stableTicks = 0;
|
||||
prevLen = len;
|
||||
if (stableTicks >= 3) break;
|
||||
await world.page.waitForTimeout(250);
|
||||
}
|
||||
}
|
||||
|
||||
async function scrollBy(world: CustomWorld, deltaY: number): Promise<void> {
|
||||
await world.page.evaluate((dy) => {
|
||||
const msg = document.querySelector('.message-wrapper');
|
||||
@@ -193,7 +146,11 @@ Given('流式响应被放慢以模拟长文输出', async function (this: Custom
|
||||
|
||||
When('用户发送长文消息并等待回复完成', { timeout: 45_000 }, async function (this: CustomWorld) {
|
||||
const prompt = '请输出一篇很长的文章';
|
||||
await sendPrompt(this, prompt, presetResponses.longScrollArticle);
|
||||
llmMockManager.setResponse(prompt, presetResponses.longScrollArticle);
|
||||
|
||||
await this.page.keyboard.type(prompt, { delay: 20 });
|
||||
await this.page.waitForTimeout(200);
|
||||
await this.page.keyboard.press('Enter');
|
||||
|
||||
// Wait for assistant message to appear and its content to stabilize.
|
||||
const messageWrappers = this.page.locator('.message-wrapper');
|
||||
@@ -208,12 +165,29 @@ When('用户发送长文消息并等待回复完成', { timeout: 45_000 }, async
|
||||
await expect(assistantMessage).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Poll until text has grown past an obvious threshold, then plateaus.
|
||||
await waitForAssistantMessageToSettle(this, 200);
|
||||
let prevLen = 0;
|
||||
let stableTicks = 0;
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const len =
|
||||
(await assistantMessage
|
||||
.innerText()
|
||||
.then((t) => t.length)
|
||||
.catch(() => 0)) || 0;
|
||||
if (len > 200 && len === prevLen) stableTicks += 1;
|
||||
else stableTicks = 0;
|
||||
prevLen = len;
|
||||
if (stableTicks >= 3) break;
|
||||
await this.page.waitForTimeout(250);
|
||||
}
|
||||
});
|
||||
|
||||
When('用户发送一条触发长文输出的消息', async function (this: CustomWorld) {
|
||||
const prompt = '请输出一篇很长的文章';
|
||||
await sendPrompt(this, prompt, presetResponses.longScrollArticle);
|
||||
llmMockManager.setResponse(prompt, presetResponses.longScrollArticle);
|
||||
|
||||
await this.page.keyboard.type(prompt, { delay: 20 });
|
||||
await this.page.waitForTimeout(200);
|
||||
await this.page.keyboard.press('Enter');
|
||||
|
||||
// Wait long enough for pin's smooth scrollToIndex to finish. Virtua drives
|
||||
// the smooth animation via rAF and would otherwise overwrite a manual
|
||||
@@ -221,42 +195,6 @@ When('用户发送一条触发长文输出的消息', async function (this: Cust
|
||||
await this.page.waitForTimeout(1200);
|
||||
});
|
||||
|
||||
When(
|
||||
'用户完成一轮用于垫高列表的长回复对话',
|
||||
{ timeout: 45_000 },
|
||||
async function (this: CustomWorld) {
|
||||
const prompt = '请先输出一篇很长的文章用于垫高列表';
|
||||
await sendPrompt(this, prompt, presetResponses.longScrollArticle);
|
||||
await waitForAssistantMessageToSettle(this, 200);
|
||||
},
|
||||
);
|
||||
|
||||
When(
|
||||
'用户发送一条触发短回复的消息并等待回复完成',
|
||||
{ timeout: 30_000 },
|
||||
async function (this: CustomWorld) {
|
||||
const prompt = '请输出一段短回复用于测试底部补偿区域';
|
||||
await sendPrompt(this, prompt, '这是一个短回复,用于让底部补偿区域保持可见。');
|
||||
await waitForAssistantMessageToSettle(this, 10);
|
||||
await this.page.waitForTimeout(400);
|
||||
},
|
||||
);
|
||||
|
||||
When('记录聊天列表底部补偿区域高度', async function (this: CustomWorld) {
|
||||
const snap = await getScrollSnapshot(this);
|
||||
expect(snap, 'failed to locate scroll container').not.toBeNull();
|
||||
expect(snap!.bottomCompensationHeight).toBeGreaterThan(0);
|
||||
expect(snap!.scrollTop).toBeGreaterThan(120);
|
||||
|
||||
this.testContext.scrollCompensationHeight = snap!.bottomCompensationHeight;
|
||||
this.testContext.scrollHeightBeforeSyntheticOffset = snap!.scrollHeight;
|
||||
});
|
||||
|
||||
When('模拟非用户触发的聊天列表上移 {int} 像素', async function (this: CustomWorld, px: number) {
|
||||
await scrollBy(this, -Math.abs(px));
|
||||
await this.page.waitForTimeout(400);
|
||||
});
|
||||
|
||||
When('用户在流式响应进行中向上滚动 {int} 像素', async function (this: CustomWorld, px: number) {
|
||||
const delta = Math.abs(px) || MANUAL_SCROLL_UP_DELTA;
|
||||
// Mouse wheel over the list, more faithful to real-user interaction than
|
||||
@@ -368,13 +306,3 @@ Then('用户消息应固定在聊天列表顶部', async function (this: CustomW
|
||||
// Pin anchors with `align: 'start'` — tolerate ~150 px of slack for headers.
|
||||
expect(Math.abs(rect!.delta)).toBeLessThanOrEqual(150);
|
||||
});
|
||||
|
||||
Then('聊天列表底部补偿区域高度不应收缩', async function (this: CustomWorld) {
|
||||
const before = this.testContext.scrollCompensationHeight as number | undefined;
|
||||
expect(before, 'missing recorded bottom compensation height').toBeDefined();
|
||||
|
||||
const snap = await getScrollSnapshot(this);
|
||||
expect(snap, 'failed to locate scroll container').not.toBeNull();
|
||||
|
||||
expect(snap!.bottomCompensationHeight).toBeGreaterThanOrEqual(before! - 2);
|
||||
});
|
||||
|
||||
@@ -115,10 +115,6 @@
|
||||
"channel.line.fetchBotInfoMissingToken": "أدخل رمز الوصول للقناة أولاً، ثم انقر على \"Fetch from LINE\".",
|
||||
"channel.line.fetchBotInfoSuccess": "تم جلب معرّف المستخدم الوجهة",
|
||||
"channel.line.webhookManualSetup": "لا يسمح LINE بالتسجيل البرمجي للويب هوك. انسخ هذا الرابط إلى وحدة تحكم مطوري LINE (واجهة برمجة تطبيقات المراسلة → رابط الويب هوك)، انقر على \"تحقق\"، وقم بتمكين \"استخدام الويب هوك\".",
|
||||
"channel.messengerPromo.action": "جرّب Messenger",
|
||||
"channel.messengerPromo.desc": "لا حاجة لإعداد الروبوت. تحدث مع LobeHub على Slack، Discord، Telegram.",
|
||||
"channel.messengerPromo.dismiss": "تجاهل",
|
||||
"channel.messengerPromo.title": "تجاوز الإعداد",
|
||||
"channel.openPlatform": "منصة مفتوحة",
|
||||
"channel.platforms": "المنصات",
|
||||
"channel.publicKey": "المفتاح العام",
|
||||
|
||||
+3
-23
@@ -184,10 +184,6 @@
|
||||
"groupWizard.searchTemplates": "البحث في القوالب...",
|
||||
"groupWizard.title": "إنشاء مجموعة",
|
||||
"groupWizard.useTemplate": "استخدام قالب",
|
||||
"heteroAgent.cloudRepo.multiSelected": "{{count}} مستودعات محددة",
|
||||
"heteroAgent.cloudRepo.noRepos": "لم يتم تكوين أي مستودعات. أضفها في إعدادات الوكيل.",
|
||||
"heteroAgent.cloudRepo.notSet": "لم يتم تحديد أي مستودع",
|
||||
"heteroAgent.cloudRepo.sectionTitle": "المستودعات",
|
||||
"heteroAgent.fullAccess.label": "وصول كامل",
|
||||
"heteroAgent.fullAccess.tooltip": "يعمل Claude Code محليًا مع صلاحية قراءة/كتابة كاملة في دليل العمل. تبديل أوضاع الصلاحيات غير متاح بعد.",
|
||||
"heteroAgent.resumeReset.cwdChanged": "تم تغيير دليل العمل. لا يمكن استئناف جلسة Claude Code السابقة إلا من دليلها الأصلي، لذا بدأت محادثة جديدة.",
|
||||
@@ -314,7 +310,7 @@
|
||||
"openInNewWindow": "فتح في نافذة جديدة",
|
||||
"operation.contextCompression": "السياق طويل جدًا، يتم ضغط السجل...",
|
||||
"operation.execAgentRuntime": "جارٍ تحضير الرد",
|
||||
"operation.execClientSubAgent": "تشغيل الوكيل الفرعي",
|
||||
"operation.execClientTask": "تنفيذ المهمة",
|
||||
"operation.execHeterogeneousAgent": "{{name}} قيد التشغيل",
|
||||
"operation.execServerAgentRuntime": "جاري التشغيل… يمكنك تبديل المهام أو إغلاق الصفحة — ستستمر المهمة بالعمل.",
|
||||
"operation.heterogeneousAgentFallback": "وكيل خارجي",
|
||||
@@ -567,12 +563,8 @@
|
||||
"taskList.contextMenu.copyLink": "نسخ الرابط",
|
||||
"taskList.contextMenu.copyLinkSuccess": "تم نسخ الرابط",
|
||||
"taskList.contextMenu.priority": "الأولوية",
|
||||
"taskList.contextMenu.runNow": "تشغيل الآن",
|
||||
"taskList.contextMenu.status": "الحالة",
|
||||
"taskList.empty": "لا توجد مهام بعد",
|
||||
"taskList.emptyHero.greeting": "ما الذي يجب أن نتعامل معه اليوم؟",
|
||||
"taskList.emptyHero.subtitle": "صف مهمة لوكيلك، أو ابدأ من قالب أدناه.",
|
||||
"taskList.emptyHero.templatesTitle": "قوالب مختارة لك",
|
||||
"taskList.form.grouping": "التجميع",
|
||||
"taskList.form.orderCompletedByRecency": "ترتيب المهام المكتملة حسب الأحدث",
|
||||
"taskList.form.ordering": "الترتيب",
|
||||
@@ -633,10 +625,8 @@
|
||||
"taskSchedule.summary.daily": "يوميًا عند {{time}}",
|
||||
"taskSchedule.summary.disabled": "الأتمتة متوقفة",
|
||||
"taskSchedule.summary.everyNHours": "كل {{count}} ساعات{{minute}}",
|
||||
"taskSchedule.summary.everyNHoursHalfPast": "كل {{count}} ساعة عند الثلاثين دقيقة",
|
||||
"taskSchedule.summary.heartbeat": "يعمل كل {{interval}}",
|
||||
"taskSchedule.summary.hourly": "كل ساعة{{minute}}",
|
||||
"taskSchedule.summary.hourlyHalfPast": "كل ساعة عند الثلاثين دقيقة",
|
||||
"taskSchedule.summary.weekly": "كل {{days}} عند {{time}}",
|
||||
"taskSchedule.tag.add": "تعيين جدول",
|
||||
"taskSchedule.tag.every": "كل {{interval}}",
|
||||
@@ -644,8 +634,6 @@
|
||||
"taskSchedule.tag.schedule": "الجدول · {{schedule}}{{timezone}}",
|
||||
"taskSchedule.time": "الوقت",
|
||||
"taskSchedule.timezone": "المنطقة الزمنية",
|
||||
"taskSchedule.timezoneSearchEmpty": "لا توجد منطقة زمنية مطابقة",
|
||||
"taskSchedule.timezoneSearchPlaceholder": "البحث عن المنطقة الزمنية",
|
||||
"taskSchedule.title": "الجدول",
|
||||
"taskSchedule.unit.hour_one": "{{count}} ساعة",
|
||||
"taskSchedule.unit.hour_other": "{{count}} ساعات",
|
||||
@@ -665,7 +653,6 @@
|
||||
"thread.divider": "موضوع فرعي",
|
||||
"thread.openSubagentThread": "عرض محادثة الوكيل الفرعي كاملة",
|
||||
"thread.subagentBadge": "وكيل فرعي",
|
||||
"thread.subagentReadOnlyHint": "المحادثات مع الوكيل الفرعي للقراءة فقط — يتم التنفيذ بواسطة الوكيل الرئيسي.",
|
||||
"thread.threadMessageCount": "{{messageCount}} رسالة",
|
||||
"thread.title": "موضوع فرعي",
|
||||
"todoProgress.allCompleted": "تم إكمال جميع المهام",
|
||||
@@ -772,8 +759,6 @@
|
||||
"workflow.toolDisplayName.addPreferenceMemory": "الذاكرة المحفوظة",
|
||||
"workflow.toolDisplayName.calculate": "محسوب",
|
||||
"workflow.toolDisplayName.callAgent": "تم استدعاء وكيل",
|
||||
"workflow.toolDisplayName.callSubAgent": "تم إرسال وكيل فرعي",
|
||||
"workflow.toolDisplayName.callSubAgents": "تم إرسال وكلاء فرعيين",
|
||||
"workflow.toolDisplayName.clearTodos": "تم مسح المهام",
|
||||
"workflow.toolDisplayName.copyDocument": "تم نسخ مستند",
|
||||
"workflow.toolDisplayName.crawlMultiPages": "الصفحات التي تم الزحف إليها",
|
||||
@@ -788,6 +773,8 @@
|
||||
"workflow.toolDisplayName.editTitle": "العنوان المُعدَّل",
|
||||
"workflow.toolDisplayName.evaluate": "التعبير المُقيَّم",
|
||||
"workflow.toolDisplayName.execScript": "تم تنفيذ برنامج نصي",
|
||||
"workflow.toolDisplayName.execTask": "تم تنفيذ مهمة",
|
||||
"workflow.toolDisplayName.execTasks": "المهام المنفذة",
|
||||
"workflow.toolDisplayName.execute": "تم تنفيذ العملية الحسابية",
|
||||
"workflow.toolDisplayName.executeCode": "تم تنفيذ الشيفرة",
|
||||
"workflow.toolDisplayName.finishOnboarding": "إنهاء الإعداد التعريفي",
|
||||
@@ -892,13 +879,6 @@
|
||||
"workingPanel.review.mode.unstaged": "غير مُرتب",
|
||||
"workingPanel.review.more": "خيارات إضافية",
|
||||
"workingPanel.review.refresh": "تحديث",
|
||||
"workingPanel.review.revert": "تجاهل التغييرات",
|
||||
"workingPanel.review.revert.confirm.cancel": "إلغاء",
|
||||
"workingPanel.review.revert.confirm.description": "سيتم تجاهل تغييرات شجرة العمل على {{filePath}} نهائيًا. ستُحذف الملفات غير المتعقبة من القرص.",
|
||||
"workingPanel.review.revert.confirm.ok": "تجاهل",
|
||||
"workingPanel.review.revert.confirm.title": "تجاهل التغييرات على هذا الملف؟",
|
||||
"workingPanel.review.revert.failed": "تعذّر تجاهل التغييرات: {{error}}",
|
||||
"workingPanel.review.revert.success": "تم تجاهل التغييرات على {{filePath}}",
|
||||
"workingPanel.review.textDiff.disable": "تعطيل مقارنة النصوص المضمنة",
|
||||
"workingPanel.review.textDiff.enable": "تمكين مقارنة النصوص المضمنة",
|
||||
"workingPanel.review.title": "مراجعة",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"batchDelete": "حذف جماعي",
|
||||
"blog": "مدونة المنتج",
|
||||
"botIntegrationBanner.dismiss": "إغلاق",
|
||||
"botIntegrationBanner.title": "تحدث إلى Lobe AI عبر تطبيقات المراسلة المفضلة لديك",
|
||||
"botIntegrationBanner.title": "إضافة قنوات إلى LobeAI",
|
||||
"branching": "إنشاء موضوع فرعي",
|
||||
"branchingDisable": "ميزة \"الموضوع الفرعي\" غير متاحة في الوضع الحالي. لاستخدام هذه الميزة، يرجى التبديل إلى وضع قاعدة بيانات Postgres/Pglite أو استخدام LobeHub Cloud.",
|
||||
"branchingRequiresSavedTopic": "الموضوع الحالي غير محفوظ، يرجى حفظه أولاً لاستخدام ميزة الموضوع الفرعي",
|
||||
@@ -349,8 +349,6 @@
|
||||
"loading": "جارٍ التحميل...",
|
||||
"mail.business": "تعاون تجاري",
|
||||
"mail.support": "دعم عبر البريد الإلكتروني",
|
||||
"messengerBanner.dismiss": "رفض",
|
||||
"messengerBanner.title": "تحدث إلى Lobe AI عبر تطبيقات المراسلة المفضلة لديك",
|
||||
"more": "المزيد",
|
||||
"navPanel.agent": "الوكيل",
|
||||
"navPanel.customizeSidebar": "تخصيص الشريط الجانبي",
|
||||
|
||||
@@ -40,18 +40,6 @@
|
||||
"modifier.acceptAll": "الاحتفاظ بالجميع",
|
||||
"modifier.reject": "تراجع",
|
||||
"modifier.rejectAll": "تراجع عن الكل",
|
||||
"skillFrontmatter.edit": "تحرير البيانات الوصفية",
|
||||
"skillFrontmatter.empty": "لا توجد بيانات وصفية",
|
||||
"skillFrontmatter.invalid.descriptionInvalid": "يجب أن تكون الوصف نصًا في سطر واحد.",
|
||||
"skillFrontmatter.invalid.descriptionRequired": "الوصف مطلوب.",
|
||||
"skillFrontmatter.invalid.mapping": "يجب أن تكون البيانات الوصفية بتنسيق YAML.",
|
||||
"skillFrontmatter.invalid.nameInvalid": "يجب أن يتكون الاسم من أحرف صغيرة وأرقام وشرطات.",
|
||||
"skillFrontmatter.invalid.nameLocked": "يجب أن يبقى الاسم {{name}}. قم بإعادة تسمية حزمة المهارة بدلاً من ذلك.",
|
||||
"skillFrontmatter.invalid.nameRequired": "الاسم مطلوب.",
|
||||
"skillFrontmatter.invalid.required": "البيانات الوصفية مطلوبة.",
|
||||
"skillFrontmatter.invalid.syntax": "صيغة YAML غير صحيحة.",
|
||||
"skillFrontmatter.saveFailed": "لم يتم حفظ البيانات الوصفية. حاول مرة أخرى أو استمر في التحرير.",
|
||||
"skillFrontmatter.title": "بيانات وصفية للمهارة",
|
||||
"slash.compact": "ضغط السياق",
|
||||
"slash.h1": "عنوان 1",
|
||||
"slash.h2": "عنوان 2",
|
||||
|
||||
@@ -26,11 +26,6 @@
|
||||
"brief.viewRun": "عرض التشغيل",
|
||||
"project.create": "مشروع جديد",
|
||||
"project.deleteConfirm": "سيتم حذف هذا المشروع ولن يمكن استعادته. أكد للمتابعة.",
|
||||
"recommendations.heteroAgent.cta": "أضف الوكيل",
|
||||
"recommendations.heteroAgent.description": "تم اكتشاف واجهة الأوامر {{name}} على هذا الجهاز — أضف وكيل {{name}} للدردشة معه من LobeHub.",
|
||||
"recommendations.heteroAgent.tag": "وكيل البرمجة",
|
||||
"recommendations.heteroAgent.title": "أضف وكيل {{name}}",
|
||||
"recommendations.subtitle": "بعض التوصيات لإعدادك",
|
||||
"starter.createAgent": "إنشاء وكيل",
|
||||
"starter.createGroup": "إنشاء مجموعة",
|
||||
"starter.deepResearch": "بحث معمق",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"messenger.linkModal.notConfigured": "هذا الاتصال غير متاح حاليًا. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"messenger.linkModal.openCta": "افتح في {{platform}}",
|
||||
"messenger.linkModal.scanHint": "أو امسح باستخدام هاتفك لفتح {{platform}}.",
|
||||
"messenger.linkModal.title": "ربط المراسلة",
|
||||
"messenger.list.discord.description": "تحدث مع وكلاء LobeHub الخاصين بك من أي خادم Discord عبر الرسائل الخاصة مع بوت LobeHub.",
|
||||
"messenger.list.slack.description": "تحدث مع وكلاء LobeHub الخاصين بك من أي مساحة عمل Slack عبر الرسائل الخاصة أو @LobeHub.",
|
||||
"messenger.list.telegram.description": "تحدث مع وكلاء LobeHub الخاصين بك في Telegram واختر من يجيب من أي مكان.",
|
||||
@@ -88,8 +89,6 @@
|
||||
"verify.confirm.relink.title": "تم ربط حساب Telegram آخر بالفعل",
|
||||
"verify.confirm.title": "تأكيد الربط",
|
||||
"verify.confirm.workspace": "مساحة العمل: {{workspace}}",
|
||||
"verify.error.alreadyConsumed": "تم استخدام هذا الرابط بالفعل لربط حساب. قم بتسجيل الدخول إلى حساب LobeHub الخاص بك لإدارة الاتصال، أو عد إلى البوت وأرسل /start مرة أخرى لإصدار رابط جديد.",
|
||||
"verify.error.alreadyConsumedTitle": "تم استخدام هذا الرابط بالفعل",
|
||||
"verify.error.alreadyLinkedToOther": "هذا الحساب مرتبط بالفعل بحساب LobeHub مختلف. قم بتسجيل الدخول إلى هذا الحساب أولاً.",
|
||||
"verify.error.expired": "انتهت صلاحية هذا الرابط. يرجى العودة إلى البوت وإرسال /start مرة أخرى.",
|
||||
"verify.error.generic": "حدث خطأ ما. يرجى المحاولة مرة أخرى.",
|
||||
|
||||
@@ -227,7 +227,6 @@
|
||||
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "لسلسلة GPT-5.2 Pro؛ يتحكم في شدة الاستدلال.",
|
||||
"providerModels.item.modelConfig.extendParams.options.gpt5_2ReasoningEffort.hint": "لسلسلة GPT-5.2؛ يتحكم في شدة الاستدلال.",
|
||||
"providerModels.item.modelConfig.extendParams.options.grok4_20ReasoningEffort.hint": "لسلسلة Grok 4.20؛ يتحكم في شدة التفكير. منخفض/متوسط يستخدم 4 وكلاء، عالي/عالي جدًا يستخدم 16 وكيلًا.",
|
||||
"providerModels.item.modelConfig.extendParams.options.grok4_3ReasoningEffort.hint": "لسلسلة Grok 4.3؛ يتحكم في شدة التفكير.",
|
||||
"providerModels.item.modelConfig.extendParams.options.hy3ReasoningEffort.hint": "لنماذج Hy3؛ يتحكم في شدة التفكير. no_think (استجابة فائقة السرعة)، low (تفكير سريع)، و high (تفكير عميق) — لتلبية احتياجات زمن الاستجابة والعمق المختلفة، بدءًا من التفاعلات عالية التردد وحتى المهام الهندسية المعقدة.",
|
||||
"providerModels.item.modelConfig.extendParams.options.imageAspectRatio.hint": "لنماذج توليد الصور من Gemini؛ يتحكم في نسبة العرض إلى الارتفاع للصور المُولدة.",
|
||||
"providerModels.item.modelConfig.extendParams.options.imageAspectRatio2.hint": "لـ Nano Banana 2؛ يتحكم في نسبة العرض إلى الارتفاع للصور المُنشأة (يدعم النسب العريضة جدًا 1:4، 4:1، 1:8، 8:1).",
|
||||
|
||||
+41
-21
@@ -106,6 +106,7 @@
|
||||
"MiniMax-Hailuo-2.3.description": "نموذج جديد لإنشاء الفيديو مع تحسينات شاملة في حركة الجسم، والواقعية الفيزيائية، واتباع التعليمات.",
|
||||
"MiniMax-M1.description": "نموذج استدلال داخلي جديد بسلسلة تفكير تصل إلى 80K ومدخلات حتى 1M، يقدم أداءً مماثلاً لأفضل النماذج العالمية.",
|
||||
"MiniMax-M2-Stable.description": "مصمم لتدفقات العمل البرمجية والوكلاء بكفاءة عالية، مع قدرة تزامن أعلى للاستخدام التجاري.",
|
||||
"MiniMax-M2.1-Lightning.description": "قدرات برمجة متعددة اللغات قوية مع استنتاج أسرع وأكثر كفاءة.",
|
||||
"MiniMax-M2.1-highspeed.description": "قدرات برمجة متعددة اللغات قوية، تجربة برمجة مطورة بشكل شامل. أسرع وأكثر كفاءة.",
|
||||
"MiniMax-M2.1.description": "MiniMax-M2.1 هو نموذج مفتوح المصدر رائد من MiniMax، يركز على حل المهام الواقعية المعقدة. يتميز بقدرات برمجة متعددة اللغات والقدرة على أداء المهام المعقدة كوكلاء ذكي.",
|
||||
"MiniMax-M2.5-highspeed.description": "MiniMax M2.5 Highspeed: نفس أداء M2.5 مع استدلال أسرع.",
|
||||
@@ -114,7 +115,9 @@
|
||||
"MiniMax-M2.7.description": "أول نموذج ذاتي التطور يتميز بأداء رائد في البرمجة والمهام عبر الوكلاء (~60 رمزاً في الثانية).",
|
||||
"MiniMax-M2.description": "MiniMax M2: نموذج الجيل السابق.",
|
||||
"MiniMax-Text-01.description": "MiniMax-01 يقدم انتباهًا خطيًا واسع النطاق يتجاوز Transformers التقليدية، مع 456 مليار معامل و45.9 مليار مفعّلة في كل تمرير. يحقق أداءً من الدرجة الأولى ويدعم حتى 4 ملايين رمز سياقي (32× GPT-4o، 20× Claude-3.5-Sonnet).",
|
||||
"MiniMaxAI/MiniMax-M1-80k.description": "MiniMax-M1 هو نموذج استدلال كبير مفتوح الأوزان مع 456 مليار معلمة إجمالية وحوالي 45.9 مليار نشطة لكل رمز. يدعم سياق 1 مليون بشكل طبيعي ويستخدم Flash Attention لتقليل FLOPs بنسبة 75% على توليد 100 ألف رمز مقارنة بـ DeepSeek R1. مع بنية MoE بالإضافة إلى CISPO وتدريب RL الهجين، يحقق أداءً رائدًا في الاستدلال طويل المدخلات ومهام الهندسة البرمجية الواقعية.",
|
||||
"MiniMaxAI/MiniMax-M2.5.description": "MiniMax-M2.5 هو أحدث نموذج لغة كبير تم تطويره بواسطة MiniMax، تم تدريبه من خلال التعلم المعزز واسع النطاق عبر مئات الآلاف من البيئات المعقدة الواقعية. يتميز بهيكل MoE مع 229 مليار معلمة، ويحقق أداءً رائدًا في الصناعة في مهام مثل البرمجة، استدعاء أدوات الوكلاء، البحث، وسيناريوهات المكتب.",
|
||||
"MiniMaxAI/MiniMax-M2.description": "MiniMax-M2 يعيد تعريف كفاءة الوكلاء. إنه نموذج MoE مضغوط وسريع وفعال من حيث التكلفة مع 230 مليار معلمة إجمالية و10 مليارات معلمة نشطة، مصمم لمهام البرمجة والوكلاء من الدرجة الأولى مع الحفاظ على ذكاء عام قوي. مع 10 مليارات معلمة نشطة فقط، ينافس النماذج الأكبر بكثير، مما يجعله مثاليًا للتطبيقات عالية الكفاءة.",
|
||||
"Moonshot-Kimi-K2-Instruct.description": "يحتوي على 1 تريليون معامل إجماليًا و32 مليار مفعّلة. من بين النماذج غير المفكرة، يتصدر في المعرفة المتقدمة، الرياضيات، والبرمجة، وأقوى في مهام الوكلاء العامة. محسن لأعباء عمل الوكلاء، يمكنه اتخاذ إجراءات وليس فقط الإجابة على الأسئلة. الأفضل للمحادثات العامة الارتجالية وتجارب الوكلاء كنموذج يعمل بردود فعل دون تفكير طويل.",
|
||||
"NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO.description": "Nous Hermes 2 - Mixtral 8x7B-DPO (46.7B) هو نموذج تعليمات عالي الدقة للحسابات المعقدة.",
|
||||
"OmniConsistency.description": "تحسّن OmniConsistency التناسق الأسلوبي والتعميم في مهام تحويل الصور إلى صور من خلال إدخال محولات الانتشار واسعة النطاق (DiTs) وبيانات مزدوجة النمط، مما يمنع تدهور الأسلوب.",
|
||||
@@ -129,6 +132,7 @@
|
||||
"Phi-3.5-vision-instrust.description": "إصدار محدث من نموذج Phi-3-vision.",
|
||||
"Pro/MiniMaxAI/MiniMax-M2.5.description": "MiniMax-M2.5 هو أحدث نموذج لغة كبير تم تطويره بواسطة MiniMax، تم تدريبه من خلال التعلم المعزز واسع النطاق عبر مئات الآلاف من البيئات الواقعية المعقدة. يتميز ببنية MoE مع 229 مليار معلمة، ويحقق أداءً رائدًا في الصناعة في مهام مثل البرمجة، استدعاء أدوات الوكلاء، البحث، وسيناريوهات المكتب.",
|
||||
"Pro/Qwen/Qwen2.5-7B-Instruct.description": "Qwen2.5-7B-Instruct هو جزء من أحدث سلسلة نماذج لغوية كبيرة من Alibaba Cloud. يقدم هذا النموذج ذو 7 مليارات معلمة تحسينات ملحوظة في البرمجة والرياضيات، ويدعم أكثر من 29 لغة، ويعزز اتباع التعليمات، وفهم البيانات المنظمة، وإنتاج المخرجات المنظمة (خصوصًا JSON).",
|
||||
"Pro/THUDM/GLM-4.1V-9B-Thinking.description": "GLM-4.1V-9B-Thinking هو نموذج رؤية-لغة مفتوح المصدر من Zhipu AI ومختبر KEG في جامعة تسينغهوا، مصمم للإدراك متعدد الوسائط المعقد. مبني على GLM-4-9B-0414، ويضيف استدلال سلسلة الأفكار والتعلم المعزز (RL) لتحسين الاستدلال عبر الوسائط والاستقرار بشكل كبير.",
|
||||
"Pro/deepseek-ai/DeepSeek-R1.description": "DeepSeek-R1 هو نموذج استدلال مدفوع بالتعلم المعزز يقلل التكرار ويحسن قابلية القراءة. يستخدم بيانات بداية باردة قبل التعلم المعزز لتعزيز الاستدلال، ويضاهي OpenAI-o1 في مهام الرياضيات، البرمجة، والاستدلال، ويحقق نتائج أفضل من خلال تدريب دقيق.",
|
||||
"Pro/deepseek-ai/DeepSeek-V3.1-Terminus.description": "DeepSeek-V3.1-Terminus هو إصدار محدث من نموذج V3.1، مصمم كنموذج وكيل هجين. يعالج المشكلات التي أبلغ عنها المستخدمون، ويحسن الاستقرار، وتناسق اللغة، ويقلل من الخلط بين الصينية/الإنجليزية والرموز غير الطبيعية. يدمج أوضاع التفكير وغير التفكير مع قوالب محادثة للتبديل المرن. كما يعزز أداء وكلاء الشيفرة والبحث لاستخدام أدوات أكثر موثوقية ومهام متعددة الخطوات.",
|
||||
"Pro/deepseek-ai/DeepSeek-V3.2.description": "DeepSeek-V3.2 هو نموذج يجمع بين الكفاءة الحسابية العالية وأداء التفكير والوكيل الممتاز. يعتمد نهجه على ثلاثة اختراقات تكنولوجية رئيسية: DeepSeek Sparse Attention (DSA)، وهي آلية انتباه فعالة تقلل بشكل كبير من التعقيد الحسابي مع الحفاظ على أداء النموذج، ومُحسنة خصيصًا للسيناريوهات ذات السياق الطويل؛ إطار عمل للتعلم المعزز القابل للتوسع يمكن من خلاله أن ينافس أداء النموذج GPT-5، مع نسخته عالية الحوسبة التي تضاهي Gemini-3.0-Pro في قدرات التفكير؛ وخط أنابيب واسع النطاق لتوليف مهام الوكيل يهدف إلى دمج قدرات التفكير في سيناريوهات استخدام الأدوات، مما يحسن اتباع التعليمات والتعميم في البيئات التفاعلية المعقدة. حقق النموذج أداءً متميزًا في الأولمبياد الدولي للرياضيات (IMO) وأولمبياد المعلوماتية الدولي (IOI) لعام 2025.",
|
||||
@@ -136,12 +140,13 @@
|
||||
"Pro/moonshotai/Kimi-K2-Instruct-0905.description": "Kimi K2-Instruct-0905 هو أحدث وأقوى إصدار من Kimi K2. إنه نموذج MoE من الدرجة الأولى يحتوي على إجمالي 1 تريليون و32 مليار معلمة نشطة. من أبرز ميزاته الذكاء البرمجي القوي مع تحسينات كبيرة في المعايير ومهام الوكلاء الواقعية، بالإضافة إلى تحسينات في جمالية واجهة الشيفرة وسهولة الاستخدام.",
|
||||
"Pro/moonshotai/Kimi-K2-Thinking.description": "Kimi K2 Thinking Turbo هو إصدار Turbo محسّن لسرعة الاستدلال والإنتاجية مع الحفاظ على قدرات التفكير متعدد الخطوات واستخدام الأدوات في K2 Thinking. إنه نموذج MoE يحتوي على حوالي 1 تريليون معلمة إجمالية، ويدعم سياقًا أصليًا بطول 256 ألف رمز، واستدعاء أدوات واسع النطاق ومستقر لسيناريوهات الإنتاج التي تتطلب زمن استجابة وتزامنًا صارمين.",
|
||||
"Pro/moonshotai/Kimi-K2.5.description": "Kimi K2.5 هو نموذج وكيل متعدد الوسائط مفتوح المصدر، مبني على Kimi-K2-Base، ومدرب على حوالي 1.5 تريليون رمز من النصوص والرؤية. يستخدم بنية MoE بعدد إجمالي 1 تريليون مع 32 مليار معلمات نشطة، ويدعم نافذة سياق تصل إلى 256 ألف، مما يدمج الفهم البصري واللغوي بسلاسة.",
|
||||
"Pro/moonshotai/Kimi-K2.6.description": "Kimi K2.6 هو نموذج الوكيل متعدد الوسائط الأصلي مفتوح المصدر من Moonshot AI. مبني على بنية MoE مع 1T إجمالي المعلمات و32B نشطة، يدعم سياق 256K من الرموز. يدعم أكثر من 4000 استدعاء أدوات مع تنفيذ ذاتي مستدام لأكثر من 12 ساعة، تعاون متعدد الوكلاء مع ما يصل إلى 300 وكيل فرعي متوازي، ووضعيات التفكير والاستنتاج الفوري.",
|
||||
"Pro/zai-org/GLM-4.7.description": "GLM-4.7 هو نموذج الجيل الجديد الرائد من Zhipu مع 355B إجمالي المعلمات و32B معلمات نشطة، تم ترقيته بالكامل في الحوار العام، التفكير، وقدرات الوكيل. يعزز GLM-4.7 التفكير المتداخل ويقدم التفكير المحفوظ والتفكير على مستوى الدور.",
|
||||
"Pro/moonshotai/Kimi-K2.6.description": "Kimi K2.6 هو نموذج وكيل متعدد الوسائط مفتوح المصدر من Moonshot AI، يحقق أداءً رائدًا مفتوح المصدر على العديد من المعايير الرئيسية بما في ذلك HLE (مع الأدوات)، SWE-Bench Pro، وBrowseComp. يعتمد النموذج على هيكل MoE مع إجمالي 1T معلمات و32B معلمات نشطة، يدعم نافذة سياق 256K رمز، ويجمع بين قدرات متعددة الوسائط الأصلية.",
|
||||
"Pro/zai-org/GLM-5.1.description": "GLM-5.1 هو نموذج الجيل التالي الرائد المصمم لهندسة الوكلاء، ويستخدم بنية خبراء مختلطة (MoE) بـ 754 مليار معلمة. يعزز قدرات البرمجة بشكل كبير، محققاً نتائج متقدمة على SWE-Bench Pro، ويتفوق بوضوح على سابقه في مقاييس مثل NL2Repo وTerminal-Bench 2.0. مصمم لمهام الوكلاء طويلة الأمد، ويتعامل مع الأسئلة الغامضة بشكل أدق، ويحلل المهام المعقدة، وينفذ التجارب، ويفحص النتائج، ويواصل التحسين عبر مئات الدورات وآلاف استدعاءات الأدوات.",
|
||||
"Pro/zai-org/glm-4.7.description": "GLM-4.7 هو النموذج الرائد الجديد من Zhipu مع 355 مليار معلمة إجمالية و32 مليار معلمة نشطة، تم ترقيته بالكامل في الحوار العام، المنطق، وقدرات الوكلاء. يعزز GLM-4.7 التفكير المتداخل ويقدم التفكير المحفوظ والتفكير على مستوى الدور.",
|
||||
"Pro/zai-org/glm-5.1.description": "GLM-5.1 هو نموذج وكيل رائد من الجيل التالي من Zhipu للهندسة الذكية. يستخدم هيكل Mixture-of-Experts مع 754 مليار معلمة، مع استدعاء أدوات أصلية، إكمال بادئة، دعم FIM، ونافذة سياق 200K لتدفقات العمل طويلة الأمد.",
|
||||
"Pro/zai-org/glm-5.description": "GLM-5 هو نموذج اللغة الكبير من الجيل التالي من Zhipu، يركز على هندسة الأنظمة المعقدة ومهام الوكيل طويلة المدة. تم توسيع معلمات النموذج إلى 744 مليار (40 مليار نشطة) وتدمج DeepSeek Sparse Attention.",
|
||||
"QwQ-32B-Preview.description": "Qwen QwQ هو نموذج بحث تجريبي يركز على تحسين الاستدلال.",
|
||||
"Qwen/QVQ-72B-Preview.description": "QVQ-72B-Preview هو نموذج بحثي من Qwen يركز على الاستدلال البصري، مع قوة في فهم المشاهد المعقدة ومسائل الرياضيات البصرية.",
|
||||
"Qwen/QwQ-32B-Preview.description": "Qwen QwQ هو نموذج بحث تجريبي يركز على تحسين استدلال الذكاء الاصطناعي.",
|
||||
"Qwen/Qwen-Image-Edit-2509.description": "Qwen-Image-Edit-2509 هو أحدث إصدار لتحرير الصور من فريق Qwen. مبني على نموذج Qwen-Image بحجم 20 مليار معلمة، ويمتد من قدرات عرض النصوص القوية إلى تحرير الصور بدقة. يستخدم بنية تحكم مزدوجة، حيث تُرسل المدخلات إلى Qwen2.5-VL للتحكم الدلالي وإلى مشفر VAE للتحكم في المظهر، مما يتيح تحريرًا على مستوى الدلالة والمظهر. يدعم التعديلات المحلية (إضافة/إزالة/تعديل) والتعديلات الدلالية المتقدمة مثل إنشاء الملكية الفكرية ونقل الأسلوب مع الحفاظ على المعنى. يحقق نتائج رائدة في العديد من المعايير.",
|
||||
"Qwen/Qwen-Image.description": "Qwen-Image هو نموذج أساسي لتوليد الصور يحتوي على 20 مليار معلمة من فريق Qwen. يحقق تقدمًا كبيرًا في عرض النصوص المعقدة وتحرير الصور بدقة، خاصة للنصوص الصينية/الإنجليزية عالية الدقة. يدعم تخطيطات متعددة الأسطر والفقرة مع الحفاظ على تناسق الطباعة. بالإضافة إلى عرض النصوص، يدعم مجموعة واسعة من الأساليب من الواقعية إلى الأنمي، والتحرير المتقدم مثل نقل الأسلوب، إضافة/إزالة الكائنات، تحسين التفاصيل، تحرير النصوص، والتحكم في الوضعية، ويهدف إلى أن يكون نموذجًا أساسيًا شاملاً للإبداع البصري.",
|
||||
@@ -223,6 +228,7 @@
|
||||
"THUDM/GLM-4.1V-9B-Thinking.description": "GLM-4.1V-9B-Thinking هو نموذج مفتوح المصدر من Zhipu AI ومختبر Tsinghua KEG، مصمم للإدراك متعدد الوسائط المعقد. يعتمد على GLM-4-9B-0414، ويضيف التفكير المتسلسل والتعلم المعزز لتحسين الاستدلال عبر الوسائط والثبات بشكل كبير.",
|
||||
"THUDM/GLM-Z1-32B-0414.description": "GLM-Z1-32B-0414 هو نموذج استدلال عميق مبني على GLM-4-32B-0414 باستخدام بيانات بدء باردة وتوسيع التعلم المعزز، وتم تدريبه بشكل إضافي على الرياضيات والبرمجة والمنطق. يُظهر تحسنًا كبيرًا في القدرة على حل المسائل الرياضية والمهام المعقدة مقارنة بالنموذج الأساسي.",
|
||||
"THUDM/GLM-Z1-9B-0414.description": "GLM-Z1-9B-0414 هو نموذج GLM صغير يحتوي على 9 مليارات معامل، يحتفظ بقوة المصدر المفتوح ويقدم أداءً مميزًا. يتميز في الاستدلال الرياضي والمهام العامة، ويتفوق على النماذج المفتوحة من نفس الفئة الحجمية.",
|
||||
"Tongyi-Zhiwen/QwenLong-L1-32B.description": "QwenLong-L1-32B هو أول نموذج استدلال طويل السياق (LRM) تم تدريبه باستخدام التعلم المعزز، مُحسن للاستدلال النصي الطويل. يتيح التوسع التدريجي للسياق عبر التعلم المعزز انتقالًا مستقرًا من السياق القصير إلى الطويل. يتفوق على OpenAI-o3-mini وQwen3-235B-A22B في سبعة معايير استدلال وثائق طويلة السياق، منافسًا Claude-3.7-Sonnet-Thinking. يتميز بقوة خاصة في الرياضيات، المنطق، والاستدلال متعدد الخطوات.",
|
||||
"Wan-AI/Wan2.2-I2V-A14B.description": "Wan2.2-I2V-A14B هو أحد أول نماذج إنشاء الفيديو من الصور (I2V) مفتوحة المصدر التي أطلقتها Wan-AI، وهي مبادرة ذكاء اصطناعي تحت مظلة Alibaba، والتي تعتمد على بنية Mixture of Experts (MoE). يركز النموذج على إنشاء تسلسلات فيديو ديناميكية سلسة وطبيعية من خلال دمج الصور الثابتة مع التعليمات النصية. تكمن الابتكارات الأساسية في بنية MoE: حيث يتولى خبير الضوضاء العالية التعامل مع الهيكل العام في المراحل الأولى من إنشاء الفيديو، بينما يقوم خبير الضوضاء المنخفضة بتحسين التفاصيل الدقيقة في المراحل اللاحقة. يحسن هذا التصميم الأداء العام للنموذج دون زيادة تكلفة الاستدلال. مقارنة بالإصدارات السابقة، تم تدريب Wan2.2 على مجموعة بيانات أكبر بكثير، مما أدى إلى تحسينات ملحوظة في فهم الحركة المعقدة، والأنماط الجمالية، والمحتوى الدلالي. ينتج مقاطع فيديو أكثر استقرارًا ويقلل من حركات الكاميرا غير الواقعية.",
|
||||
"Wan-AI/Wan2.2-T2V-A14B.description": "Wan2.2-T2V-A14B هو أول نموذج إنشاء فيديو مفتوح المصدر أطلقته Alibaba يعتمد على بنية Mixture of Experts (MoE). تم تصميم النموذج لمهام تحويل النص إلى فيديو (T2V) وقادر على إنتاج مقاطع فيديو تصل مدتها إلى 5 ثوانٍ بدقة 480P أو 720P. من خلال تقديم بنية MoE، يزيد النموذج بشكل كبير من سعته الإجمالية مع الحفاظ على تكاليف الاستدلال شبه ثابتة. يتضمن خبير الضوضاء العالية الذي يتعامل مع الهيكل العام في المراحل الأولى من الإنشاء، وخبير الضوضاء المنخفضة الذي يحسن التفاصيل الدقيقة في المراحل اللاحقة من الفيديو. بالإضافة إلى ذلك، يدمج Wan2.2 بيانات جمالية منتقاة بعناية، مع تعليقات تفصيلية عبر أبعاد مثل الإضاءة، والتكوين، والألوان. يتيح ذلك إنشاءًا أكثر دقة وقابلية للتحكم في المرئيات بجودة سينمائية. مقارنة بالإصدارات السابقة، تم تدريب النموذج على مجموعة بيانات أكبر، مما أدى إلى تحسينات كبيرة في التعميم في الحركة، والدلالات، والجماليات، وتحسين التعامل مع التأثيرات الديناميكية المعقدة.",
|
||||
"Yi-34B-Chat.description": "Yi-1.5-34B يحتفظ بقدرات اللغة العامة القوية للسلسلة، ويستخدم تدريبًا تدريجيًا على 500 مليار رمز عالي الجودة لتحسين كبير في المنطق الرياضي والبرمجة.",
|
||||
@@ -314,13 +320,13 @@
|
||||
"claude-3-haiku-20240307.description": "Claude 3 Haiku هو أسرع وأصغر نموذج من Anthropic، مصمم لتقديم استجابات شبه فورية بأداء سريع ودقيق.",
|
||||
"claude-3-opus-20240229.description": "Claude 3 Opus هو أقوى نموذج من Anthropic للمهام المعقدة، يتميز بالأداء العالي، الذكاء، الطلاقة، والفهم.",
|
||||
"claude-3-sonnet-20240229.description": "Claude 3 Sonnet يوازن بين الذكاء والسرعة لتلبية احتياجات المؤسسات، ويوفر فائدة عالية بتكلفة أقل ونشر موثوق على نطاق واسع.",
|
||||
"claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 هو النموذج الأسرع والأذكى من Anthropic، يتميز بسرعة البرق وقدرات استدلال موسعة.",
|
||||
"claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 هو أسرع وأذكى نموذج هايكو من Anthropic، يتميز بسرعة البرق وتفكير ممتد.",
|
||||
"claude-haiku-4-5.description": "Claude Haiku 4.5 من Anthropic — نموذج Haiku من الجيل التالي مع تحسينات في التفكير والرؤية.",
|
||||
"claude-haiku-4.5.description": "Claude Haiku 4.5 هو نموذج Haiku الأسرع والأذكى من Anthropic، يتميز بسرعة البرق وقدرات استدلال موسعة.",
|
||||
"claude-opus-4-1-20250805-thinking.description": "Claude Opus 4.1 Thinking هو إصدار متقدم يمكنه عرض عملية تفكيره.",
|
||||
"claude-opus-4-1-20250805.description": "Claude Opus 4.1 هو أحدث وأقوى نموذج من Anthropic للمهام المعقدة للغاية، يتميز بالأداء العالي، الذكاء، الطلاقة، والفهم.",
|
||||
"claude-opus-4-1-20250805.description": "Claude Opus 4.1 هو أحدث وأقوى نموذج من Anthropic للمهام المعقدة للغاية، يتميز بالأداء والذكاء والطلاقة والفهم.",
|
||||
"claude-opus-4-1.description": "Claude Opus 4.1 من Anthropic — نموذج تفكير متميز مع قدرات تحليل عميقة.",
|
||||
"claude-opus-4-20250514.description": "Claude Opus 4 هو النموذج الأكثر قوة من Anthropic للمهام المعقدة للغاية، يتميز بالأداء العالي، الذكاء، الطلاقة، والاستيعاب.",
|
||||
"claude-opus-4-20250514.description": "Claude Opus 4 هو أقوى نموذج من Anthropic للمهام المعقدة للغاية، يتميز بالأداء والذكاء والطلاقة والفهم.",
|
||||
"claude-opus-4-5-20251101.description": "Claude Opus 4.5 هو النموذج الرائد من Anthropic، يجمع بين الذكاء الاستثنائي والأداء القابل للتوسع، مثالي للمهام المعقدة التي تتطلب استجابات عالية الجودة وتفكير متقدم.",
|
||||
"claude-opus-4-5.description": "Claude Opus 4.5 من Anthropic — نموذج رئيسي مع تفكير وبرمجة من الدرجة الأولى.",
|
||||
"claude-opus-4-6.description": "Claude Opus 4.6 من Anthropic — نافذة سياق 1M نموذج رئيسي مع تفكير متقدم.",
|
||||
@@ -329,7 +335,7 @@
|
||||
"claude-opus-4.6-fast.description": "Claude Opus 4.6 هو النموذج الأكثر ذكاءً من Anthropic لبناء الوكلاء والبرمجة.",
|
||||
"claude-opus-4.6.description": "Claude Opus 4.6 هو النموذج الأكثر ذكاءً من Anthropic لبناء الوكلاء والبرمجة.",
|
||||
"claude-sonnet-4-20250514-thinking.description": "Claude Sonnet 4 Thinking يمكنه تقديم استجابات شبه فورية أو تفكير متسلسل مرئي.",
|
||||
"claude-sonnet-4-20250514.description": "Claude Sonnet 4 يمكنه تقديم استجابات شبه فورية أو تفكير ممتد خطوة بخطوة مع عرض العملية.",
|
||||
"claude-sonnet-4-20250514.description": "Claude Sonnet 4 هو النموذج الأكثر ذكاءً من Anthropic حتى الآن، يقدم استجابات شبه فورية أو تفكير خطوة بخطوة ممتد مع تحكم دقيق لمستخدمي API.",
|
||||
"claude-sonnet-4-5-20250929.description": "Claude Sonnet 4.5 هو النموذج الأكثر ذكاءً من Anthropic حتى الآن.",
|
||||
"claude-sonnet-4-5.description": "Claude Sonnet 4.5 من Anthropic — نموذج Sonnet محسّن مع أداء برمجي معزز.",
|
||||
"claude-sonnet-4-6.description": "Claude Sonnet 4.6 من Anthropic — أحدث نموذج Sonnet مع برمجة واستخدام أدوات متفوقة.",
|
||||
@@ -403,7 +409,7 @@
|
||||
"deepseek-ai/deepseek-llm-67b-chat.description": "DeepSeek LLM Chat (67B) هو نموذج مبتكر يوفر فهمًا عميقًا للغة وتفاعلًا ذكيًا.",
|
||||
"deepseek-ai/deepseek-v3.1-terminus.description": "DeepSeek V3.1 هو نموذج تفكير من الجيل التالي يتمتع بقدرات أقوى في التفكير المعقد وسلسلة التفكير لمهام التحليل العميق.",
|
||||
"deepseek-ai/deepseek-v3.2.description": "DeepSeek V3.2 هو نموذج استدلال من الجيل التالي يتميز بقدرات استدلال معقدة وسلسلة التفكير.",
|
||||
"deepseek-chat.description": "نموذج مفتوح المصدر جديد يجمع بين القدرات العامة والبرمجية. يحافظ على حوار النموذج العام وقوة البرمجة للنموذج البرمجي، مع تحسين توافق التفضيلات. كما يحسن DeepSeek-V2.5 الكتابة واتباع التعليمات.",
|
||||
"deepseek-chat.description": "اسم مستعار متوافق لوضع عدم التفكير في DeepSeek V4 Flash. مقرر إيقافه — استخدم DeepSeek V4 Flash بدلاً منه.",
|
||||
"deepseek-coder-33B-instruct.description": "DeepSeek Coder 33B هو نموذج لغة برمجية تم تدريبه على 2 تريليون رمز (87٪ كود، 13٪ نص صيني/إنجليزي). يقدم نافذة سياق 16K ومهام الإكمال في المنتصف، ويوفر إكمال كود على مستوى المشاريع وملء مقاطع الكود.",
|
||||
"deepseek-coder-v2.description": "DeepSeek Coder V2 هو نموذج كود MoE مفتوح المصدر يتميز بأداء قوي في مهام البرمجة، ويضاهي GPT-4 Turbo.",
|
||||
"deepseek-coder-v2:236b.description": "DeepSeek Coder V2 هو نموذج كود MoE مفتوح المصدر يتميز بأداء قوي في مهام البرمجة، ويضاهي GPT-4 Turbo.",
|
||||
@@ -425,7 +431,7 @@
|
||||
"deepseek-r1-fast-online.description": "الإصدار الكامل السريع من DeepSeek R1 مع بحث ويب في الوقت الحقيقي، يجمع بين قدرات بحجم 671B واستجابة أسرع.",
|
||||
"deepseek-r1-online.description": "الإصدار الكامل من DeepSeek R1 مع 671 مليار معلمة وبحث ويب في الوقت الحقيقي، يوفر فهمًا وتوليدًا أقوى.",
|
||||
"deepseek-r1.description": "يستخدم DeepSeek-R1 بيانات البداية الباردة قبل التعلم المعزز ويؤدي أداءً مماثلًا لـ OpenAI-o1 في الرياضيات، والبرمجة، والتفكير.",
|
||||
"deepseek-reasoner.description": "نموذج DeepSeek مخصص لمهام الاستدلال المنطقي المعقدة.",
|
||||
"deepseek-reasoner.description": "اسم مستعار متوافق لوضع التفكير في DeepSeek V4 Flash. مقرر إيقافه — استخدم DeepSeek V4 Flash بدلاً منه.",
|
||||
"deepseek-v2.description": "DeepSeek V2 هو نموذج MoE فعال لمعالجة منخفضة التكلفة.",
|
||||
"deepseek-v2:236b.description": "DeepSeek V2 236B هو نموذج DeepSeek الموجه للبرمجة مع قدرات قوية في توليد الكود.",
|
||||
"deepseek-v3-0324.description": "DeepSeek-V3-0324 هو نموذج MoE يحتوي على 671 مليار معلمة يتميز بقوة في البرمجة، والقدرات التقنية، وفهم السياق، والتعامل مع النصوص الطويلة.",
|
||||
@@ -490,6 +496,8 @@
|
||||
"doubao-seedream-4-0-250828.description": "Seedream 4.0 هو نموذج توليد صور من ByteDance Seed، يدعم إدخال النصوص والصور مع توليد صور عالية الجودة وقابلة للتحكم بدرجة كبيرة. يُولّد الصور من التعليمات النصية.",
|
||||
"doubao-seedream-4-5-251128.description": "Seedream 4.5 هو أحدث نموذج متعدد الوسائط من ByteDance، يدمج قدرات تحويل النص إلى صورة، والصورة إلى صورة، وتوليد الصور بالجملة، مع دمج الفهم العام وقدرات الاستدلال. مقارنة بالإصدار السابق 4.0، يقدم جودة توليد محسّنة بشكل كبير، مع تحسين تناسق التحرير ودمج الصور المتعددة. يوفر تحكمًا أكثر دقة في التفاصيل البصرية، مما يجعل النصوص الصغيرة والوجوه الصغيرة أكثر طبيعية، ويحقق تخطيطًا وألوانًا أكثر انسجامًا، مما يعزز الجماليات العامة.",
|
||||
"doubao-seedream-5-0-260128.description": "Doubao-Seedream-5.0-lite هو أحدث نموذج لتوليد الصور من ByteDance. لأول مرة، يدمج قدرات الاسترجاع عبر الإنترنت، مما يسمح له بتضمين معلومات الويب في الوقت الفعلي وتعزيز حداثة الصور المولدة. كما تم ترقية ذكاء النموذج، مما يمكنه من تفسير التعليمات المعقدة والمحتوى البصري بدقة. بالإضافة إلى ذلك، يقدم تغطية محسّنة للمعرفة العالمية، وتناسقًا مرجعيًا، وجودة توليد في السيناريوهات المهنية، مما يلبي بشكل أفضل احتياجات الإبداع البصري على مستوى المؤسسات.",
|
||||
"dreamina-seedance-2-0-260128.description": "Seedance 2.0 من ByteDance هو أقوى نموذج لإنشاء الفيديو، يدعم إنشاء الفيديو متعدد الوسائط، تحرير الفيديو، تمديد الفيديو، تحويل النص إلى فيديو، وتحويل الصورة إلى فيديو مع صوت متزامن.",
|
||||
"dreamina-seedance-2-0-fast-260128.description": "Seedance 2.0 Fast من ByteDance يقدم نفس القدرات مثل Seedance 2.0 مع سرعات إنشاء أسرع وسعر أكثر تنافسية.",
|
||||
"emohaa.description": "Emohaa هو نموذج للصحة النفسية يتمتع بقدرات استشارية احترافية لمساعدة المستخدمين على فهم المشكلات العاطفية.",
|
||||
"ernie-4.5-0.3b.description": "ERNIE 4.5 0.3B هو نموذج مفتوح المصدر وخفيف الوزن، مصمم للنشر المحلي والمخصص.",
|
||||
"ernie-4.5-8k-preview.description": "ERNIE 4.5 8K Preview هو نموذج معاينة بسياق 8K لتقييم أداء ERNIE 4.5.",
|
||||
@@ -514,7 +522,8 @@
|
||||
"ernie-x1-turbo-32k.description": "ERNIE X1 Turbo 32K هو نموذج تفكير سريع بسياق 32K للاستدلال المعقد والدردشة متعددة الأدوار.",
|
||||
"ernie-x1.1-preview.description": "معاينة ERNIE X1.1 هو نموذج تفكير مخصص للتقييم والاختبار.",
|
||||
"ernie-x1.1.description": "ERNIE X1.1 هو نموذج تفكير تجريبي للتقييم والاختبار.",
|
||||
"fal-ai/bytedance/seedream/v4.description": "Seedream 4.0 هو نموذج توليد الصور من ByteDance Seed، يدعم إدخال النصوص والصور مع توليد صور عالية الجودة وقابلة للتحكم بشكل كبير. يقوم بتوليد الصور من التعليمات النصية.",
|
||||
"fal-ai/bytedance/seedream/v4.5.description": "Seedream 4.5، تم تطويره بواسطة فريق ByteDance Seed، يدعم تحرير الصور المتعددة والتكوين. يتميز بتناسق الموضوع المحسن، اتباع التعليمات بدقة، فهم المنطق المكاني، التعبير الجمالي، تصميم الملصقات والشعارات مع إنشاء نصوص وصور عالية الدقة.",
|
||||
"fal-ai/bytedance/seedream/v4.description": "Seedream 4.0، تم تطويره بواسطة ByteDance Seed، يدعم إدخال النصوص والصور لإنشاء صور عالية الجودة وقابلة للتحكم بناءً على التعليمات.",
|
||||
"fal-ai/flux-kontext/dev.description": "نموذج FLUX.1 يركز على تحرير الصور، ويدعم إدخال النصوص والصور.",
|
||||
"fal-ai/flux-pro/kontext.description": "FLUX.1 Kontext [pro] يقبل النصوص وصور مرجعية كمدخلات، مما يتيح تعديلات محلية مستهدفة وتحولات معقدة في المشهد العام.",
|
||||
"fal-ai/flux/krea.description": "Flux Krea [dev] هو نموذج لتوليد الصور يتميز بميول جمالية نحو صور أكثر واقعية وطبيعية.",
|
||||
@@ -522,8 +531,8 @@
|
||||
"fal-ai/hunyuan-image/v3.description": "نموذج قوي لتوليد الصور متعدد الوسائط أصلي.",
|
||||
"fal-ai/imagen4/preview.description": "نموذج عالي الجودة لتوليد الصور من Google.",
|
||||
"fal-ai/nano-banana.description": "Nano Banana هو أحدث وأسرع وأكثر نماذج Google كفاءةً لتوليد وتحرير الصور من خلال المحادثة.",
|
||||
"fal-ai/qwen-image-edit.description": "نموذج تحرير الصور الاحترافي من فريق Qwen يدعم التعديلات الدلالية والمظهرية، ويحرر النصوص الصينية والإنجليزية بدقة، ويمكّن من تعديلات عالية الجودة مثل نقل الأنماط وتدوير الكائنات.",
|
||||
"fal-ai/qwen-image.description": "نموذج توليد الصور القوي من فريق Qwen يتميز بعرض نصوص صينية مذهلة وأنماط بصرية متنوعة.",
|
||||
"fal-ai/qwen-image-edit.description": "نموذج تحرير الصور الاحترافي من فريق Qwen، يدعم التعديلات الدلالية والمظهرية، تحرير النصوص الدقيقة باللغتين الصينية والإنجليزية، نقل الأنماط، التدوير، والمزيد.",
|
||||
"fal-ai/qwen-image.description": "نموذج قوي لإنشاء الصور من فريق Qwen يتميز بتقديم نصوص صينية قوية وأنماط بصرية متنوعة.",
|
||||
"flux-1-schnell.description": "نموذج تحويل النص إلى صورة يحتوي على 12 مليار معلمة من Black Forest Labs يستخدم تقنيات تقطير الانتشار العدائي الكامن لتوليد صور عالية الجودة في 1-4 خطوات. ينافس البدائل المغلقة ومتاح بموجب ترخيص Apache-2.0 للاستخدام الشخصي والبحثي والتجاري.",
|
||||
"flux-dev.description": "نموذج مفتوح المصدر مخصص لتوليد الصور لأغراض البحث والابتكار غير التجاري، مع تحسينات فعالة.",
|
||||
"flux-kontext-max.description": "توليد وتحرير صور سياقية متقدمة، تجمع بين النصوص والصور لتحقيق نتائج دقيقة ومتسقة.",
|
||||
@@ -562,12 +571,11 @@
|
||||
"gemini-3-flash-preview.description": "Gemini 3 Flash هو أذكى نموذج تم تصميمه للسرعة، يجمع بين الذكاء المتقدم وأساس بحث ممتاز.",
|
||||
"gemini-3-flash.description": "Gemini 3 Flash من Google — نموذج فائق السرعة مع دعم الإدخال متعدد الوسائط.",
|
||||
"gemini-3-pro-image-preview.description": "Gemini 3 Pro Image (Nano Banana Pro) هو نموذج توليد الصور من Google ويدعم المحادثة متعددة الوسائط.",
|
||||
"gemini-3-pro-image-preview:image.description": "Gemini 3 Pro Image (Nano Banana Pro) هو نموذج توليد الصور من Google ويدعم أيضًا الدردشة متعددة الوسائط.",
|
||||
"gemini-3-pro-image-preview:image.description": "Gemini 3 Pro Image (Nano Banana Pro) هو نموذج إنشاء الصور من Google ويدعم أيضًا الدردشة متعددة الوسائط.",
|
||||
"gemini-3-pro-preview.description": "Gemini 3 Pro هو أقوى نموذج من Google للوكيل الذكي والبرمجة الإبداعية، يقدم تفاعلاً أعمق وصورًا أغنى مع استدلال متقدم.",
|
||||
"gemini-3.1-flash-image-preview.description": "Gemini 3.1 Flash Image (Nano Banana 2) يقدم جودة صور احترافية بسرعة فائقة مع دعم الدردشة متعددة الوسائط.",
|
||||
"gemini-3.1-flash-image-preview:image.description": "Gemini 3.1 Flash Image (Nano Banana 2) هو أسرع نموذج توليد صور أصلي من Google مع دعم التفكير، وتوليد الصور الحواري وتحريرها.",
|
||||
"gemini-3.1-flash-image-preview:image.description": "Gemini 3.1 Flash Image (Nano Banana 2) يقدم جودة صور بمستوى احترافي بسرعة Flash مع دعم الدردشة متعددة الوسائط.",
|
||||
"gemini-3.1-flash-lite-preview.description": "Gemini 3.1 Flash-Lite Preview هو النموذج الأكثر كفاءة من حيث التكلفة من Google، مُحسّن للمهام الوكيلة ذات الحجم الكبير، الترجمة، ومعالجة البيانات.",
|
||||
"gemini-3.1-flash-lite.description": "Gemini 3.1 Flash-Lite هو النموذج متعدد الوسائط الأكثر كفاءة من Google، مُحسّن للمهام الوكيلية ذات الحجم الكبير، الترجمة، ومعالجة البيانات.",
|
||||
"gemini-3.1-pro-preview.description": "Gemini 3.1 Pro Preview يحسن من Gemini 3 Pro مع قدرات استدلال محسّنة ويضيف دعم مستوى التفكير المتوسط.",
|
||||
"gemini-3.1-pro.description": "Gemini 3.1 Pro من Google — نموذج متعدد الوسائط متميز مع نافذة سياق 1M.",
|
||||
"gemini-flash-latest.description": "يشير إلى gemini-3-flash-preview",
|
||||
@@ -724,17 +732,21 @@
|
||||
"grok-3-mini.description": "Grok 3 Mini من xAI مع تفكير قوي واستجابات سريعة.",
|
||||
"grok-3.description": "Grok 3 من xAI مع قدرة تفكير قوية.",
|
||||
"grok-4-0709.description": "Grok 4 من xAI بقدرات استدلال قوية.",
|
||||
"grok-4-1-fast-non-reasoning.description": "نموذج متعدد الوسائط متقدم محسّن لاستخدام أدوات الوكلاء عالية الأداء.",
|
||||
"grok-4-1-fast-reasoning.description": "نموذج متعدد الوسائط متقدم محسّن لاستخدام أدوات الوكلاء عالية الأداء.",
|
||||
"grok-4-20-non-reasoning.description": "نموذج غير تفكير للاستخدامات البسيطة.",
|
||||
"grok-4-20-reasoning.description": "نموذج ذكي وسريع للغاية يفكر قبل الرد.",
|
||||
"grok-4-fast-non-reasoning.description": "يسعدنا إطلاق Grok 4 Fast، أحدث تقدم في نماذج الاستدلال منخفضة التكلفة.",
|
||||
"grok-4-fast-reasoning.description": "يسعدنا إطلاق Grok 4 Fast، أحدث تقدم في نماذج الاستدلال منخفضة التكلفة.",
|
||||
"grok-4.20-0309-non-reasoning.description": "نموذج غير تفكير للاستخدامات البسيطة.",
|
||||
"grok-4.20-0309-reasoning.description": "نموذج ذكي وسريع للغاية يفكر قبل الرد.",
|
||||
"grok-4.20-beta-0309-non-reasoning.description": "نسخة غير تفكيرية للاستخدامات البسيطة.",
|
||||
"grok-4.20-beta-0309-reasoning.description": "نموذج ذكي وسريع للغاية يفكر قبل الرد.",
|
||||
"grok-4.20-multi-agent-0309.description": "فريق من 4 أو 16 وكيلًا، يتفوق في حالات الاستخدام البحثية، لا يدعم حاليًا الأدوات على جانب العميل. يدعم فقط أدوات xAI على جانب الخادم (مثل X Search، أدوات البحث على الويب) وأدوات MCP البعيدة.",
|
||||
"grok-4.3.description": "أكثر نموذج لغة كبير يسعى للحقيقة في العالم.",
|
||||
"grok-4.description": "أحدث نموذج Grok الرائد بأداء لا مثيل له في اللغة، الرياضيات، والاستدلال — نموذج شامل حقيقي. يشير حاليًا إلى grok-4-0709؛ نظرًا للموارد المحدودة، فإن سعره مؤقتًا أعلى بنسبة 10% من السعر الرسمي ومن المتوقع أن يعود إلى السعر الرسمي لاحقًا.",
|
||||
"grok-4.description": "أحدث وأقوى نموذج رئيسي لدينا، يتميز في معالجة اللغة الطبيعية، الرياضيات، والتفكير—مثالي لجميع الاستخدامات.",
|
||||
"grok-code-fast-1.description": "يسعدنا إطلاق grok-code-fast-1، نموذج استدلال سريع وفعال من حيث التكلفة يتفوق في البرمجة التلقائية.",
|
||||
"grok-imagine-image-quality.description": "توليد الصور من التعليمات النصية، تحرير الصور الموجودة باستخدام اللغة الطبيعية، أو تحسين الصور بشكل تكراري من خلال محادثات متعددة الأدوار.",
|
||||
"grok-imagine-image-pro.description": "إنشاء صور من مطالبات نصية، تحرير الصور الموجودة باستخدام اللغة الطبيعية، أو تحسين الصور بشكل تكراري من خلال محادثات متعددة الأدوار.",
|
||||
"grok-imagine-image.description": "إنشاء صور من مطالبات نصية، تحرير الصور الموجودة باستخدام اللغة الطبيعية، أو تحسين الصور بشكل تكراري من خلال محادثات متعددة الأدوار.",
|
||||
"grok-imagine-video.description": "إنشاء فيديو متقدم عبر الجودة والتكلفة والكمون.",
|
||||
"groq/compound-mini.description": "Compound-mini هو نظام ذكاء اصطناعي مركب مدعوم بنماذج متاحة علنًا على GroqCloud، يستخدم الأدوات بذكاء وانتقائية للإجابة على استفسارات المستخدمين.",
|
||||
@@ -970,6 +982,7 @@
|
||||
"moonshot-v1-32k.description": "Moonshot V1 32K يدعم 32,768 رمزًا لسياق متوسط الطول، وهو مثالي للوثائق الطويلة والحوارات المعقدة في إنشاء المحتوى، والتقارير، وأنظمة الدردشة.",
|
||||
"moonshot-v1-8k-vision-preview.description": "نماذج Kimi للرؤية (بما في ذلك moonshot-v1-8k-vision-preview/moonshot-v1-32k-vision-preview/moonshot-v1-128k-vision-preview) قادرة على فهم محتوى الصور مثل النصوص، الألوان، وأشكال الكائنات.",
|
||||
"moonshot-v1-8k.description": "Moonshot V1 8K مُحسّن لتوليد النصوص القصيرة بكفاءة عالية، حيث يتعامل مع 8,192 رمزًا للمحادثات القصيرة، والملاحظات، والمحتوى السريع.",
|
||||
"moonshotai/Kimi-Dev-72B.description": "Kimi-Dev-72B هو نموذج برمجة مفتوح المصدر مُحسن باستخدام التعلم المعزز واسع النطاق لإنتاج تصحيحات قوية وجاهزة للإنتاج. يسجل 60.4% على SWE-bench Verified، محققًا رقمًا قياسيًا جديدًا للنماذج المفتوحة في مهام هندسة البرمجيات الآلية مثل إصلاح الأخطاء ومراجعة الكود.",
|
||||
"moonshotai/Kimi-K2-Instruct-0905.description": "Kimi K2-Instruct-0905 هو أحدث وأقوى إصدار من Kimi K2. إنه نموذج MoE من الدرجة الأولى يحتوي على تريليون معلمة إجمالية و32 مليار معلمة نشطة. من أبرز ميزاته الذكاء البرمجي القوي، وتحسينات كبيرة في اختبارات الأداء والمهام الواقعية، بالإضافة إلى تحسينات في جمالية واجهات الاستخدام وسهولة البرمجة الأمامية.",
|
||||
"moonshotai/Kimi-K2-Thinking.description": "Kimi K2 Thinking هو أحدث وأقوى نموذج تفكير مفتوح المصدر. يوسع بشكل كبير عمق التفكير متعدد الخطوات ويحافظ على استخدام الأدوات المستقر عبر 200-300 استدعاء متتالي، محققًا أرقامًا قياسية جديدة في Humanity's Last Exam (HLE)، BrowseComp، ومعايير أخرى. يتفوق في البرمجة، الرياضيات، المنطق، وسيناريوهات الوكيل. يعتمد على بنية MoE مع ~1 تريليون معلمة إجمالية، ويدعم نافذة سياق 256K واستدعاء الأدوات.",
|
||||
"moonshotai/kimi-k2-0711.description": "Kimi K2 0711 هو إصدار موجه من سلسلة Kimi، مناسب للبرمجة عالية الجودة واستخدام الأدوات.",
|
||||
@@ -1131,6 +1144,12 @@
|
||||
"qwen/qwen3-max-preview.description": "Qwen3 Max (نسخة تجريبية) هو إصدار Max المتقدم في الاستدلال ودمج الأدوات.",
|
||||
"qwen/qwen3-max.description": "Qwen3 Max هو النموذج المتقدم في سلسلة Qwen3 للاستدلال متعدد اللغات ودمج الأدوات.",
|
||||
"qwen/qwen3-vl-plus.description": "Qwen3 VL-Plus هو إصدار Qwen3 المحسَّن بالرؤية، مع قدرات أفضل في الاستدلال متعدد الوسائط ومعالجة الفيديو.",
|
||||
"qwen/qwen3.5-122b-a10b.description": "Qwen3.5-122B-A10B هو نموذج لغة كبير متعدد الوسائط تم تطويره بواسطة فريق Qwen، مع إجمالي 122 مليار معلمة و10 مليارات معلمة مفعلة فقط. يستخدم النموذج بنية هجينة فعالة تجمع بين شبكات Gated Delta وMixture-of-Experts (MoE). يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. من خلال تدريب الدمج المبكر، يحقق النموذج قدرات أساسية موحدة للرؤية-اللغة، يدعم النصوص، الصور، وفهم الفيديو. يقدم أداءً ممتازًا عبر معايير متعددة بما في ذلك المعرفة، الاستنتاج، البرمجة، الوكلاء، الفهم البصري، والمهام متعددة اللغات، متفوقًا على GPT-5-mini وQwen3-235B-A22B في عدة مقاييس. يتم تمكين وضع التفكير افتراضيًا، يدعم استدعاء الأدوات، ويغطي 201 لغة ولهجة.",
|
||||
"qwen/qwen3.5-27b.description": "Qwen3.5-27B هو نموذج لغة كبير متعدد الوسائط تم تطويره بواسطة فريق Qwen مع 27 مليار معلمة. يستخدم النموذج بنية هجينة فعالة تجمع بين شبكات Gated Delta وGated Attention. يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. من خلال تدريب الدمج المبكر، يحقق النموذج قدرات أساسية موحدة للرؤية-اللغة، يدعم النصوص، الصور، وفهم الفيديو. يقدم أداءً ممتازًا عبر معايير متعددة بما في ذلك الاستنتاج، البرمجة، الوكلاء، والفهم البصري، متفوقًا على Qwen3-235B-A22B وGPT-5-mini في عدة مقاييس. يتم تمكين وضع التفكير افتراضيًا، يدعم استدعاء الأدوات، ويغطي 201 لغة ولهجة.",
|
||||
"qwen/qwen3.5-35b-a3b.description": "Qwen3.5-35B-A3B هو نموذج لغة كبير متعدد الوسائط تم تطويره بواسطة فريق Qwen، مع إجمالي 35 مليار معلمة و3 مليارات معلمة مفعلة فقط. يستخدم النموذج بنية هجينة فعالة تجمع بين شبكات Gated Delta وMixture-of-Experts (MoE). يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. من خلال تدريب الدمج المبكر، يحقق النموذج قدرات أساسية موحدة للرؤية-اللغة، يدعم النصوص، الصور، وفهم الفيديو. يقدم أداءً ممتازًا عبر معايير متعددة بما في ذلك الاستنتاج، البرمجة، الوكلاء، والفهم البصري. يتم تمكين وضع التفكير افتراضيًا، يدعم استدعاء الأدوات، ويغطي 201 لغة ولهجة.",
|
||||
"qwen/qwen3.5-397b-a17b.description": "Qwen3.5-397B-A17B هو أحدث نموذج رؤية-لغة في سلسلة Qwen، يتميز ببنية Mixture-of-Experts (MoE) مع إجمالي 397 مليار معلمة و17 مليار معلمة مفعلة. يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. يدعم 201 لغة ويوفر قدرات فهم موحدة للرؤية-اللغة، استدعاء الأدوات، ووضع التفكير.",
|
||||
"qwen/qwen3.5-4b.description": "Qwen3.5-4B هو نموذج لغة كبير متعدد الوسائط تم تطويره بواسطة فريق Qwen مع 4 مليارات معلمة، مما يجعله النموذج الأكثر خفة في سلسلة Qwen3.5. يستخدم النموذج بنية هجينة فعالة تجمع بين شبكات Gated Delta وGated Attention. يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. من خلال تدريب الدمج المبكر، يحقق النموذج قدرات أساسية موحدة للرؤية-اللغة، يدعم النصوص، الصور، وفهم الفيديو. يقدم أداءً ممتازًا بين النماذج ذات الحجم المماثل، متفوقًا على GPT-5-Nano وGemini-2.5-Flash-Lite في عدة مقاييس. يتم تمكين وضع التفكير افتراضيًا، يدعم استدعاء الأدوات، ويغطي 201 لغة ولهجة.",
|
||||
"qwen/qwen3.5-9b.description": "Qwen3.5-9B هو نموذج لغة كبير متعدد الوسائط تم تطويره بواسطة فريق Qwen مع 9 مليارات معلمة. كنموذج خفيف الوزن في سلسلة Qwen3.5، يستخدم بنية هجينة فعالة تجمع بين شبكات Gated Delta وGated Attention. يدعم طول سياق 256K، قابل للتمديد إلى حوالي مليون رمز. من خلال تدريب الدمج المبكر، يحقق النموذج قدرات أساسية موحدة للرؤية-اللغة، يدعم النصوص، الصور، وفهم الفيديو. يتم تمكين وضع التفكير افتراضيًا، يدعم استدعاء الأدوات، ويغطي 201 لغة ولهجة.",
|
||||
"qwen2.5-14b-instruct-1m.description": "Qwen2.5 نموذج مفتوح المصدر بسعة 72 مليار معلمة.",
|
||||
"qwen2.5-14b-instruct.description": "Qwen2.5 نموذج مفتوح المصدر بسعة 14 مليار معلمة.",
|
||||
"qwen2.5-32b-instruct.description": "Qwen2.5 نموذج مفتوح المصدر بسعة 32 مليار معلمة.",
|
||||
@@ -1214,6 +1233,8 @@
|
||||
"qwq.description": "QwQ هو نموذج استدلال من عائلة Qwen. مقارنة بالنماذج المضبوطة على التعليمات، يقدم قدرات تفكير واستدلال تعزز الأداء بشكل كبير، خاصة في المشكلات الصعبة. QwQ-32B هو نموذج متوسط الحجم ينافس أفضل نماذج الاستدلال مثل DeepSeek-R1 و o1-mini.",
|
||||
"qwq_32b.description": "نموذج استدلال متوسط الحجم من عائلة Qwen. مقارنة بالنماذج المضبوطة على التعليمات، تعزز قدرات التفكير والاستدلال في QwQ الأداء بشكل كبير، خاصة في المشكلات الصعبة.",
|
||||
"r1-1776.description": "R1-1776 هو إصدار ما بعد التدريب من DeepSeek R1 مصمم لتقديم معلومات واقعية غير خاضعة للرقابة أو التحيز.",
|
||||
"seedance-1-5-pro-251215.description": "Seedance 1.5 Pro من ByteDance يدعم تحويل النص إلى فيديو، تحويل الصورة إلى فيديو (الإطار الأول، الإطار الأول+الأخير)، وإنشاء الصوت المتزامن مع المرئيات.",
|
||||
"seedream-5-0-260128.description": "ByteDance-Seedream-5.0-lite من BytePlus يتميز بإنشاء مدعوم باسترجاع الويب للحصول على معلومات في الوقت الحقيقي، تفسير محسّن للتعليمات المعقدة، وتحسين تناسق المرجع لإنشاء مرئيات احترافية.",
|
||||
"solar-mini-ja.description": "Solar Mini (Ja) يوسع Solar Mini مع تركيز على اللغة اليابانية مع الحفاظ على الأداء القوي والكفاءة في الإنجليزية والكورية.",
|
||||
"solar-mini.description": "Solar Mini هو نموذج لغة مدمج يتفوق على GPT-3.5، يتميز بقدرات متعددة اللغات قوية تدعم الإنجليزية والكورية، ويقدم حلاً فعالاً بصمة صغيرة.",
|
||||
"solar-pro.description": "Solar Pro هو نموذج لغة عالي الذكاء من Upstage، يركز على اتباع التعليمات باستخدام وحدة معالجة رسومات واحدة، مع درجات IFEval تتجاوز 80. حالياً يدعم اللغة الإنجليزية؛ وكان من المقرر إصدار النسخة الكاملة في نوفمبر 2024 مع دعم لغات موسع وسياق أطول.",
|
||||
@@ -1225,9 +1246,7 @@
|
||||
"sophnet/deepseek-v3.2.description": "DeepSeek V3.2 هو نموذج يوازن بين الكفاءة الحسابية العالية وأداء الاستدلال والوكيل الممتاز.",
|
||||
"sora-2-pro.description": "Sora 2 Pro هو نموذجنا الأكثر تقدمًا لتوليد الوسائط، يولد فيديوهات مع صوت متزامن. يمكنه إنشاء مقاطع غنية بالتفاصيل وديناميكية من اللغة الطبيعية أو الصور.",
|
||||
"sora-2.description": "Sora 2 هو نموذجنا الجديد القوي لتوليد الوسائط، يولد فيديوهات مع صوت متزامن. يمكنه إنشاء مقاطع غنية بالتفاصيل وديناميكية من اللغة الطبيعية أو الصور.",
|
||||
"spark-x1.5.description": "تحديثات X1.5: (1) يضيف وضع التفكير الديناميكي الذي يتم التحكم فيه عبر حقل `thinking`; (2) طول سياق أكبر مع 64 ألف إدخال و64 ألف إخراج; (3) يدعم FunctionCall.",
|
||||
"spark-x2-flash.description": "Spark X2-Flash يعتمد على بنية MoE (خليط الخبراء) مع 30 مليار معلمة إجمالية ويدعم نافذة سياق تصل إلى 256 ألف. يدعي تحسينات كبيرة في القدرات الوكالية والبرمجية، وتم تدريبه على مجموعة من معالجات Ascend 910B AI.",
|
||||
"spark-x2.description": "نظرة عامة على قدرات X2: 1. يقدم تعديلًا ديناميكيًا لوضع الاستدلال، يتم التحكم فيه عبر حقل `thinking`. 2. طول سياق موسع: 64 ألف رمز إدخال و128 ألف رمز إخراج. 3. يدعم وظيفة Function Call.",
|
||||
"spark-x.description": "نظرة عامة على قدرات X2: 1. يقدم تعديل ديناميكي لوضع الاستدلال، يتم التحكم فيه عبر الحقل `thinking`. 2. طول سياق موسع: 64K رموز إدخال و128K رموز إخراج. 3. يدعم وظيفة استدعاء الأدوات.",
|
||||
"stable-diffusion-3-medium.description": "أحدث نموذج تحويل النص إلى صورة من Stability AI. هذا الإصدار يحسن جودة الصور، وفهم النص، وتنوع الأساليب بشكل كبير، ويفسر التعليمات الطبيعية المعقدة بدقة أكبر وينتج صورًا أكثر دقة وتنوعًا.",
|
||||
"stable-diffusion-3.5-large-turbo.description": "Stable Diffusion 3.5 Large Turbo يركز على توليد صور عالية الجودة مع قوة ممتازة في إظهار التفاصيل وواقعية المشاهد.",
|
||||
"stable-diffusion-xl-base-1.0.description": "نموذج تحويل نص إلى صورة مفتوح المصدر من Stability AI يتميز بإبداع رائد في توليد الصور. يتمتع بفهم قوي للتعليمات ويدعم تعريف التعليمات العكسية لتوليد دقيق.",
|
||||
@@ -1252,7 +1271,7 @@
|
||||
"step-3.description": "يتمتع هذا النموذج بإدراك بصري قوي واستدلال معقد، ويتعامل بدقة مع فهم المعرفة عبر المجالات، وتحليل الرياضيات والرؤية، ومجموعة واسعة من مهام التحليل البصري اليومية.",
|
||||
"step-image-edit-2.description": "نموذج تحرير خفيف الوزن من أحدث إصدار لـ Stepfun يدعم تحويل النص إلى صورة وتحرير الصور ضمن نموذج واحد. على الرغم من احتوائه على أقل من 6 مليارات معلمة، فإنه يحقق أداءً رائدًا على مستوى حجمه، ينافس النماذج مفتوحة المصدر في نطاق 12B–20B عبر المستويات. تستغرق كل مهمة تحرير فقط 1–2 ثانية، مما يعيد تعريف تجربة تحرير الصور التفاعلي في الوقت الفعلي.",
|
||||
"step-r1-v-mini.description": "نموذج استدلال يتمتع بفهم قوي للصور، يمكنه معالجة الصور والنصوص، ثم توليد نص بعد استدلال عميق. يتفوق في الاستدلال البصري ويقدم أداءً رائدًا في الرياضيات والبرمجة والاستدلال النصي، مع نافذة سياق تصل إلى 100 ألف.",
|
||||
"stepfun-ai/Step-3.5-Flash.description": "Step 3.5 Flash هو النموذج الأساسي المفتوح المصدر الأكثر قوة من StepFun، يستخدم بنية Mixture of Experts (MoE) مع 196B إجمالي المعلمات، فقط 11B معلمات نشطة لكل رمز. يدعم نافذة سياق 256K، ويحقق إنتاجية توليد 100-300 رمز/ثانية من خلال التنبؤ متعدد الرموز (MTP-3). أداء ممتاز في البرمجة ومهام الوكيل، تم التحقق من SWE-bench بنسبة 74.4%.",
|
||||
"stepfun-ai/step3.description": "Step3 هو نموذج استدلال متعدد الوسائط متقدم من StepFun، يعتمد على بنية MoE مع 321 مليار معلمة إجمالية و38 مليار معلمة نشطة. تصميمه الشامل يقلل من تكلفة فك التشفير مع تقديم استدلال رؤية-لغة من الدرجة الأولى. مع تصميم MFA وAFD، يظل فعالًا على كل من المسرعات الرائدة والمنخفضة. يستخدم التدريب المسبق أكثر من 20 تريليون رمز نصي و4 تريليون رمز نصي-صوري عبر العديد من اللغات. يحقق أداءً رائدًا للنماذج المفتوحة في الرياضيات، البرمجة، ومعايير متعددة الوسائط.",
|
||||
"taichu4_vl_2b_nothinking.description": "الإصدار بدون التفكير من نموذج Taichu4.0-VL 2B يتميز باستخدام ذاكرة أقل، تصميم خفيف الوزن، سرعة استجابة سريعة، وقدرات فهم متعددة الوسائط قوية.",
|
||||
"taichu4_vl_32b.description": "الإصدار التفكير من نموذج Taichu4.0-VL 32B مناسب لمهام الفهم والاستدلال متعددة الوسائط المعقدة، ويظهر أداءً رائعًا في الاستدلال الرياضي متعدد الوسائط، قدرات الوكيل متعدد الوسائط، والفهم العام للصور والبصريات.",
|
||||
"taichu4_vl_32b_nothinking.description": "الإصدار بدون التفكير من نموذج Taichu4.0-VL 32B مصمم لفهم النصوص والصور المعقدة وسيناريوهات الإجابة على الأسئلة المعرفية البصرية، ويتفوق في وصف الصور، الإجابة على الأسئلة البصرية، فهم الفيديو، ومهام تحديد المواقع البصرية.",
|
||||
@@ -1349,6 +1368,7 @@
|
||||
"x-ai/grok-4.1-fast.description": "Grok 4.1 Fast هو نموذج عالي الإنتاجية ومنخفض التكلفة من xAI (يدعم نافذة سياق 2 مليون)، مثالي لحالات الاستخدام ذات التزامن العالي والسياق الطويل.",
|
||||
"x-ai/grok-4.description": "Grok 4 هو النموذج الرائد من xAI بقدرات استدلال قوية ودعم متعدد الوسائط.",
|
||||
"x-ai/grok-code-fast-1.description": "Grok Code Fast 1 هو نموذج سريع للبرمجة من xAI بإخراج قابل للقراءة ومناسب للهندسة.",
|
||||
"x1.description": "تحديثات X1.5: (1) يضيف وضع التفكير الديناميكي الذي يتم التحكم فيه عبر الحقل `thinking`; (2) طول سياق أكبر مع 64K إدخال و64K إخراج; (3) يدعم وظيفة استدعاء الأدوات.",
|
||||
"xai/grok-2-vision.description": "Grok 2 Vision يتفوّق في المهام البصرية، ويقدّم أداءً رائدًا في استدلال الرياضيات البصرية (MathVista) وأسئلة المستندات (DocVQA). يتعامل مع المستندات، والمخططات، والرسوم البيانية، ولقطات الشاشة، والصور.",
|
||||
"xai/grok-2.description": "Grok 2 هو نموذج متقدم بأداء رائد في الاستدلال، والدردشة، والبرمجة، ويتفوّق على Claude 3.5 Sonnet وGPT-4 Turbo في تصنيفات LMSYS.",
|
||||
"xai/grok-3-fast.description": "نموذج xAI الرائد يتفوّق في حالات الاستخدام المؤسسية مثل استخراج البيانات، والبرمجة، والتلخيص، مع معرفة عميقة في مجالات مثل المالية، والرعاية الصحية، والقانون، والعلوم. الإصدار السريع يعمل على بنية تحتية أسرع لتقديم استجابات أسرع بتكلفة أعلى لكل رمز.",
|
||||
@@ -1380,7 +1400,7 @@
|
||||
"zai-org/GLM-4.5-Air.description": "GLM-4.5-Air هو نموذج أساسي لتطبيقات الوكلاء يستخدم بنية Mixture-of-Experts. مُحسّن لاستخدام الأدوات، وتصفح الويب، والهندسة البرمجية، وبرمجة الواجهات، ويتكامل مع وكلاء البرمجة مثل Claude Code وRoo Code. يستخدم استدلالًا هجينًا للتعامل مع السيناريوهات المعقدة واليومية.",
|
||||
"zai-org/GLM-4.5V.description": "GLM-4.5V هو أحدث نموذج رؤية من Zhipu AI، مبني على نموذج النص الرائد GLM-4.5-Air (إجمالي 106 مليار، 12 مليار نشط) باستخدام بنية MoE لأداء قوي بتكلفة أقل. يتبع مسار GLM-4.1V-Thinking ويضيف 3D-RoPE لتحسين الاستدلال المكاني ثلاثي الأبعاد. مُحسّن من خلال التدريب المسبق، والتعلم الخاضع للإشراف، والتعلم المعزز، ويتعامل مع الصور، والفيديو، والمستندات الطويلة، ويتصدر النماذج المفتوحة في 41 معيارًا متعدد الوسائط. يتيح وضع التفكير للمستخدمين التوازن بين السرعة والعمق.",
|
||||
"zai-org/GLM-4.6.description": "مقارنة بـ GLM-4.5، يوسّع GLM-4.6 السياق من 128 ألف إلى 200 ألف لمهام الوكلاء المعقدة. يحقق نتائج أعلى في اختبارات البرمجة ويُظهر أداءً أقوى في التطبيقات الواقعية مثل Claude Code وCline وRoo Code وKilo Code، بما في ذلك توليد صفحات الواجهة الأمامية بشكل أفضل. تم تحسين الاستدلال ودعم استخدام الأدوات أثناء التفكير، مما يعزز القدرات العامة. يتكامل بشكل أفضل مع أطر الوكلاء، ويحسّن وكلاء الأدوات/البحث، ويتميز بأسلوب كتابة مفضل بشريًا وطبيعية في تقمص الأدوار.",
|
||||
"zai-org/GLM-4.6V.description": "GLM-4.6V يحقق دقة فهم بصري رائدة على نفس مقياس المعلمات، وهو الأول الذي يدمج قدرة استدعاء الوظائف بشكل أصلي في نماذج الرؤية في بنية النموذج، مما يربط السلسلة من الإدراك البصري إلى العمل القابل للتنفيذ (Action)، ويوفر أساسًا تقنيًا موحدًا للوكلاء متعدد الوسائط في سيناريوهات الأعمال الحقيقية. نافذة السياق البصري توسعت إلى 128K، تدعم معالجة تدفقات الفيديو الطويلة وتحليل الصور المتعددة عالية الدقة.",
|
||||
"zai-org/GLM-4.6V.description": "GLM-4.6V يحقق دقة فهم بصري رائدة بالنسبة لحجم معلماته وهو الأول الذي يدمج قدرات استدعاء الوظائف بشكل طبيعي في بنية نموذج الرؤية، مما يجسر الفجوة بين \"الإدراك البصري\" و\"الإجراءات القابلة للتنفيذ\" ويوفر أساسًا تقنيًا موحدًا للوكلاء متعدد الوسائط في سيناريوهات الأعمال الواقعية. يتم تمديد نافذة السياق البصري إلى 128 ألف، مما يدعم معالجة تدفقات الفيديو الطويلة وتحليل الصور المتعددة عالية الدقة.",
|
||||
"zai/glm-4.5-air.description": "GLM-4.5 وGLM-4.5-Air هما أحدث النماذج الرائدة لدينا لتطبيقات الوكلاء، وكلاهما يستخدم بنية MoE. يحتوي GLM-4.5 على 355 مليار إجمالي و32 مليار نشط لكل تمرير؛ بينما GLM-4.5-Air أنحف بإجمالي 106 مليار و12 مليار نشط.",
|
||||
"zai/glm-4.5.description": "سلسلة GLM-4.5 مصممة للوكلاء. النموذج الرائد GLM-4.5 يجمع بين الاستدلال، والبرمجة، ومهارات الوكلاء مع 355 مليار معلمة إجمالية (32 مليار نشطة) ويقدّم أوضاع تشغيل مزدوجة كنظام استدلال هجين.",
|
||||
"zai/glm-4.5v.description": "GLM-4.5V مبني على GLM-4.5-Air، ويَرِث تقنيات GLM-4.1V-Thinking المثبتة، ويتوسع ببنية MoE قوية بسعة 106 مليار.",
|
||||
|
||||
+22
-23
@@ -69,22 +69,9 @@
|
||||
"builtins.lobe-agent-management.render.installPlugin.plugin": "الملحق",
|
||||
"builtins.lobe-agent-management.render.installPlugin.success": "تم التثبيت بنجاح",
|
||||
"builtins.lobe-agent-management.title": "مدير الوكلاء",
|
||||
"builtins.lobe-agent.apiName.callSubAgent": "استدعاء الوكيل الفرعي",
|
||||
"builtins.lobe-agent.apiName.callSubAgent.completed": "تم إرسال الوكيل الفرعي: ",
|
||||
"builtins.lobe-agent.apiName.callSubAgent.loading": "جارٍ إرسال الوكيل الفرعي: ",
|
||||
"builtins.lobe-agent.apiName.callSubAgents": "استدعاء الوكلاء الفرعيين",
|
||||
"builtins.lobe-agent.apiName.clearTodos": "مسح المهام",
|
||||
"builtins.lobe-agent.apiName.clearTodos.modeAll": "الكل",
|
||||
"builtins.lobe-agent.apiName.clearTodos.modeCompleted": "المكتملة",
|
||||
"builtins.lobe-agent.apiName.clearTodos.result": "مسح <mode>{{mode}}</mode> المهام",
|
||||
"builtins.lobe-agent.apiName.createPlan": "إنشاء خطة",
|
||||
"builtins.lobe-agent.apiName.createPlan.result": "إنشاء خطة: <goal>{{goal}}</goal>",
|
||||
"builtins.lobe-agent.apiName.createTodos": "إنشاء مهام",
|
||||
"builtins.lobe-agent.apiName.updatePlan": "تحديث الخطة",
|
||||
"builtins.lobe-agent.apiName.updatePlan.completed": "مكتمل",
|
||||
"builtins.lobe-agent.apiName.updatePlan.modified": "تم التعديل",
|
||||
"builtins.lobe-agent.apiName.updateTodos": "تحديث المهام",
|
||||
"builtins.lobe-agent.title": "وكيل لوب",
|
||||
"builtins.lobe-agent-marketplace.apiName.showAgentMarketplace": "افتح سوق الوكلاء",
|
||||
"builtins.lobe-agent-marketplace.apiName.submitAgentPick": "إرسال اختيارات الوكلاء",
|
||||
"builtins.lobe-agent-marketplace.title": "سوق الوكلاء",
|
||||
"builtins.lobe-claude-code.agent.instruction": "تعليمات",
|
||||
"builtins.lobe-claude-code.agent.result": "النتيجة",
|
||||
"builtins.lobe-claude-code.todoWrite.allDone": "جميع المهام مكتملة",
|
||||
@@ -152,6 +139,24 @@
|
||||
"builtins.lobe-group-management.inspector.executeAgentTasks.title": "تعيين المهام إلى:",
|
||||
"builtins.lobe-group-management.inspector.speak.title": "المتحدث المحدد:",
|
||||
"builtins.lobe-group-management.title": "منسق المجموعة",
|
||||
"builtins.lobe-gtd.apiName.clearTodos": "مسح المهام",
|
||||
"builtins.lobe-gtd.apiName.clearTodos.modeAll": "الكل",
|
||||
"builtins.lobe-gtd.apiName.clearTodos.modeCompleted": "المكتملة",
|
||||
"builtins.lobe-gtd.apiName.clearTodos.result": "تم مسح المهام <mode>{{mode}}</mode>",
|
||||
"builtins.lobe-gtd.apiName.completeTodos": "إكمال المهام",
|
||||
"builtins.lobe-gtd.apiName.createPlan": "إنشاء خطة",
|
||||
"builtins.lobe-gtd.apiName.createPlan.result": "تم إنشاء الخطة: <goal>{{goal}}</goal>",
|
||||
"builtins.lobe-gtd.apiName.createTodos": "إنشاء مهام",
|
||||
"builtins.lobe-gtd.apiName.execTask": "تنفيذ المهمة",
|
||||
"builtins.lobe-gtd.apiName.execTask.completed": "تم إنشاء المهمة: ",
|
||||
"builtins.lobe-gtd.apiName.execTask.loading": "جارٍ إنشاء المهمة: ",
|
||||
"builtins.lobe-gtd.apiName.execTasks": "تنفيذ المهام",
|
||||
"builtins.lobe-gtd.apiName.removeTodos": "حذف المهام",
|
||||
"builtins.lobe-gtd.apiName.updatePlan": "تحديث الخطة",
|
||||
"builtins.lobe-gtd.apiName.updatePlan.completed": "مكتملة",
|
||||
"builtins.lobe-gtd.apiName.updatePlan.modified": "تم التعديل",
|
||||
"builtins.lobe-gtd.apiName.updateTodos": "تحديث المهام",
|
||||
"builtins.lobe-gtd.title": "أدوات المهام",
|
||||
"builtins.lobe-knowledge-base.apiName.readKnowledge": "قراءة محتوى المكتبة",
|
||||
"builtins.lobe-knowledge-base.apiName.searchKnowledgeBase": "البحث في المكتبة",
|
||||
"builtins.lobe-knowledge-base.inspector.andMoreFiles": "و{{count}} ملفًا آخر",
|
||||
@@ -260,7 +265,6 @@
|
||||
"builtins.lobe-task.apiName.listTasks": "عرض المهام",
|
||||
"builtins.lobe-task.apiName.runTask": "تشغيل مهمة",
|
||||
"builtins.lobe-task.apiName.runTasks": "تشغيل مهام",
|
||||
"builtins.lobe-task.apiName.setTaskSchedule": "تعيين الجدول الزمني",
|
||||
"builtins.lobe-task.apiName.updateTaskComment": "تحديث تعليق",
|
||||
"builtins.lobe-task.apiName.updateTaskStatus": "تحديث الحالة",
|
||||
"builtins.lobe-task.apiName.viewTask": "عرض المهمة",
|
||||
@@ -312,8 +316,6 @@
|
||||
"builtins.lobe-web-onboarding.apiName.finishOnboarding": "إنهاء الإعداد",
|
||||
"builtins.lobe-web-onboarding.apiName.readDocument": "قراءة المستند",
|
||||
"builtins.lobe-web-onboarding.apiName.saveUserQuestion": "حفظ سؤال المستخدم",
|
||||
"builtins.lobe-web-onboarding.apiName.showAgentMarketplace": "تجميع فريق الوكلاء",
|
||||
"builtins.lobe-web-onboarding.apiName.submitAgentPick": "إرسال اختيارات الوكلاء",
|
||||
"builtins.lobe-web-onboarding.apiName.updateDocument": "تحديث المستند",
|
||||
"builtins.lobe-web-onboarding.apiName.writeDocument": "كتابة المستند",
|
||||
"builtins.lobe-web-onboarding.docType.persona": "شخصية المستخدم",
|
||||
@@ -324,9 +326,6 @@
|
||||
"builtins.lobe-web-onboarding.inspector.hunkCount_other": "{{count}} تغييرات",
|
||||
"builtins.lobe-web-onboarding.inspector.interests_one": "{{count}} اهتمام",
|
||||
"builtins.lobe-web-onboarding.inspector.interests_other": "{{count}} اهتمامات",
|
||||
"builtins.lobe-web-onboarding.render.agent": "وكيل",
|
||||
"builtins.lobe-web-onboarding.render.fullName": "الاسم الكامل",
|
||||
"builtins.lobe-web-onboarding.render.interests": "الاهتمامات",
|
||||
"builtins.lobe-web-onboarding.title": "إعداد المستخدم",
|
||||
"builtins.lobe-web-onboarding.updateDocument.hunkMode.delete": "حذف",
|
||||
"builtins.lobe-web-onboarding.updateDocument.hunkMode.deleteLines": "حذف الأسطر",
|
||||
@@ -682,7 +681,7 @@
|
||||
"skillDetail.tools": "الأدوات",
|
||||
"skillDetail.trustWarning": "استخدم الموصلات فقط من المطورين الذين تثق بهم. لا تتحكم LobeHub في الأدوات التي يتيحها المطورون ولا يمكنها التحقق من أنها ستعمل كما هو متوقع أو أنها لن تتغير.",
|
||||
"skillInstallBanner.dismiss": "إغلاق",
|
||||
"skillInstallBanner.title": "اربط تطبيقاتك المفضلة بـ Lobe AI",
|
||||
"skillInstallBanner.title": "أضف المهارات إلى Lobe AI",
|
||||
"store.actions.cancel": "إلغاء",
|
||||
"store.actions.configure": "تهيئة",
|
||||
"store.actions.confirmUninstall": "سيؤدي إلغاء التثبيت إلى مسح إعدادات المهارة. هل ترغب في المتابعة؟",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"jina.description": "تأسست Jina AI في عام 2020، وهي شركة رائدة في مجال البحث الذكي. تشمل تقنياتها نماذج المتجهات، ومعيدو الترتيب، ونماذج لغوية صغيرة لبناء تطبيقات بحث توليدية ومتعددة الوسائط عالية الجودة.",
|
||||
"kimicodingplan.description": "كود Kimi من Moonshot AI يوفر الوصول إلى نماذج Kimi بما في ذلك K2.5 لأداء مهام الترميز.",
|
||||
"lmstudio.description": "LM Studio هو تطبيق سطح مكتب لتطوير وتجربة النماذج اللغوية الكبيرة على جهازك.",
|
||||
"lobehub.description": "يستخدم LobeHub Cloud واجهات برمجة التطبيقات الرسمية للوصول إلى نماذج الذكاء الاصطناعي ويقيس الاستخدام باستخدام أرصدة مرتبطة برموز النماذج.",
|
||||
"longcat.description": "LongCat هو سلسلة من نماذج الذكاء الاصطناعي التوليدية الكبيرة التي تم تطويرها بشكل مستقل بواسطة Meituan. تم تصميمه لتعزيز إنتاجية المؤسسة الداخلية وتمكين التطبيقات المبتكرة من خلال بنية حسابية فعالة وقدرات متعددة الوسائط قوية.",
|
||||
"minimax.description": "تأسست MiniMax في عام 2021، وتبني نماذج ذكاء اصطناعي متعددة الوسائط للأغراض العامة، بما في ذلك نماذج نصية بمليارات المعلمات، ونماذج صوتية وبصرية، بالإضافة إلى تطبيقات مثل Hailuo AI.",
|
||||
"minimaxcodingplan.description": "خطة الرموز MiniMax توفر الوصول إلى نماذج MiniMax بما في ذلك M2.7 لأداء مهام الترميز عبر اشتراك ثابت الرسوم.",
|
||||
|
||||
+2
-21
@@ -291,26 +291,9 @@
|
||||
"heterogeneousStatus.auth.api": "واجهة برمجة التطبيقات",
|
||||
"heterogeneousStatus.auth.label": "طريقة التوثيق",
|
||||
"heterogeneousStatus.auth.subscription": "الاشتراك",
|
||||
"heterogeneousStatus.cloud.githubDesc": "اختر بيانات اعتماد OAuth لـ GitHub للسماح للصندوق الرمل باستنساخ مستودعاتك الخاصة.",
|
||||
"heterogeneousStatus.cloud.githubLabel": "اتصال GitHub",
|
||||
"heterogeneousStatus.cloud.githubNoCreds": "لم يتم العثور على بيانات اعتماد GitHub.",
|
||||
"heterogeneousStatus.cloud.githubPlaceholder": "اختر بيانات اعتماد GitHub...",
|
||||
"heterogeneousStatus.cloud.manageCredentials": "إدارة بيانات الاعتماد →",
|
||||
"heterogeneousStatus.cloud.repoAdd": "إضافة",
|
||||
"heterogeneousStatus.cloud.repoDesc": "أضف المستودعات إلى القائمة. قم بتبديل المستودع النشط من الشريط السفلي في عرض الدردشة.",
|
||||
"heterogeneousStatus.cloud.repoLabel": "المستودعات",
|
||||
"heterogeneousStatus.cloud.repoPlaceholder": "owner/repo أو https://github.com/owner/repo",
|
||||
"heterogeneousStatus.cloud.tabLabel": "السحابة",
|
||||
"heterogeneousStatus.cloud.tokenCancel": "إلغاء",
|
||||
"heterogeneousStatus.cloud.tokenChange": "تغيير",
|
||||
"heterogeneousStatus.cloud.tokenDesc": "رمز OAuth الخاص بـ Claude Code. يتم حفظه بأمان في بيانات الاعتماد بمجرد إرساله. قم بتشغيل `claude setup-token` في الطرفية الخاصة بك لتوليد واحد.",
|
||||
"heterogeneousStatus.cloud.tokenLabel": "رمز Claude Code",
|
||||
"heterogeneousStatus.cloud.tokenPlaceholder": "الصق رمز OAuth الخاص بك هنا",
|
||||
"heterogeneousStatus.cloud.tokenSave": "حفظ",
|
||||
"heterogeneousStatus.command.edit": "تحرير الأمر",
|
||||
"heterogeneousStatus.command.label": "أمر التشغيل",
|
||||
"heterogeneousStatus.command.placeholder": "اسم الأمر أو المسار المطلق",
|
||||
"heterogeneousStatus.desktop.tabLabel": "سطح المكتب",
|
||||
"heterogeneousStatus.detecting": "يتم الآن اكتشاف واجهة سطر الأوامر لـ {{name}}...",
|
||||
"heterogeneousStatus.plan.label": "الخطة",
|
||||
"heterogeneousStatus.redetect": "إعادة الاكتشاف",
|
||||
@@ -492,8 +475,6 @@
|
||||
"plugin.settings.tooltip": "إعدادات المهارة",
|
||||
"plugin.store": "متجر المهارات",
|
||||
"publishToCommunity": "النشر في المجتمع",
|
||||
"serviceModel.modelAssignments.title": "تعيينات النموذج",
|
||||
"serviceModel.optionalFeatures.title": "الميزات الاختيارية",
|
||||
"settingAgent.avatar.sizeExceeded": "يتجاوز حجم الصورة 1 ميجابايت، يرجى اختيار صورة أصغر",
|
||||
"settingAgent.avatar.title": "الصورة الرمزية",
|
||||
"settingAgent.backgroundColor.title": "لون الخلفية",
|
||||
@@ -913,6 +894,8 @@
|
||||
"tools.builtins.lobe-agent-documents.title": "المستندات",
|
||||
"tools.builtins.lobe-agent-management.description": "إنشاء الوكلاء وإدارتهم وتنظيم عملهم",
|
||||
"tools.builtins.lobe-agent-management.title": "إدارة الوكلاء",
|
||||
"tools.builtins.lobe-agent-marketplace.description": "عرض بطاقة سوق الوكلاء المختارة للمستخدمين وتسجيل القوالب التي يختارونها.",
|
||||
"tools.builtins.lobe-agent-marketplace.title": "سوق الوكلاء",
|
||||
"tools.builtins.lobe-artifacts.description": "إنشاء ومعاينة مكونات واجهة المستخدم التفاعلية، وتصوير البيانات، والمخططات، والرسومات بصيغة SVG، وتطبيقات الويب بشكل مباشر. أنشئ محتوى بصريًا غنيًا يمكن للمستخدمين التفاعل معه مباشرة.",
|
||||
"tools.builtins.lobe-artifacts.readme": "أنشئ معاينات حية وتفاعلية لمكونات واجهة المستخدم، وتصوير البيانات، والمخططات، والرسومات بصيغة SVG، وتطبيقات الويب. أنشئ محتوى بصريًا غنيًا يمكن للمستخدمين التفاعل معه مباشرة.",
|
||||
"tools.builtins.lobe-artifacts.title": "القطع الفنية",
|
||||
@@ -1062,8 +1045,6 @@
|
||||
"tools.lobehubSkill.providers.linear.readme": "اجلب قوة Linear مباشرة إلى مساعدك الذكي. أنشئ وحدث المشكلات، وأدر السبرينت، وتتبع تقدم المشاريع، وسهّل سير عمل التطوير — كل ذلك من خلال المحادثة الطبيعية.",
|
||||
"tools.lobehubSkill.providers.microsoft.description": "تقويم Outlook هو أداة جدولة مدمجة ضمن Microsoft Outlook تتيح للمستخدمين إنشاء المواعيد، تنظيم الاجتماعات، وإدارة الوقت والفعاليات بفعالية.",
|
||||
"tools.lobehubSkill.providers.microsoft.readme": "تكامل مع تقويم Outlook لعرض وإنشاء وإدارة فعالياتك بسلاسة. جدْوِل الاجتماعات، وتحقق من التوفر، واضبط التذكيرات، ونظّم وقتك — كل ذلك باستخدام أوامر اللغة الطبيعية.",
|
||||
"tools.lobehubSkill.providers.notion.description": "Notion هو تطبيق تعاوني للإنتاجية وتدوين الملاحظات.",
|
||||
"tools.lobehubSkill.providers.notion.readme": "اتصل بـ Notion للوصول إلى مساحة العمل الخاصة بك وإدارتها. قم بإنشاء الصفحات، والبحث عن المحتوى، وتحديث قواعد البيانات، وتنظيم قاعدة معارفك—كل ذلك من خلال محادثة طبيعية مع مساعدك الذكي.",
|
||||
"tools.lobehubSkill.providers.twitter.description": "X (تويتر سابقًا) هي منصة تواصل اجتماعي لمشاركة التحديثات الفورية، الأخبار، والتفاعل مع جمهورك من خلال المنشورات، الردود، والرسائل المباشرة.",
|
||||
"tools.lobehubSkill.providers.twitter.readme": "اتصل بـ X (تويتر) لنشر التغريدات، وإدارة الجدول الزمني، والتفاعل مع جمهورك. أنشئ المحتوى، وجدول المنشورات، وراقب الإشارات، وابنِ حضورك على وسائل التواصل الاجتماعي من خلال الذكاء الاصطناعي الحواري.",
|
||||
"tools.lobehubSkill.providers.vercel.description": "Vercel هي منصة سحابية للمطورين الأماميين، توفر الاستضافة والدوال الخادمة لنشر التطبيقات بسهولة.",
|
||||
|
||||
@@ -2,270 +2,268 @@
|
||||
"action.connect.button": "اتصل بـ {{provider}}",
|
||||
"action.connect.error": "فشل الاتصال، يرجى المحاولة مرة أخرى.",
|
||||
"action.connect.popupBlocked": "تم حظر نافذة الاتصال المنبثقة. اسمح بالنوافذ المنبثقة في متصفحك للمتابعة.",
|
||||
"action.connect.short": "اتصل",
|
||||
"action.connecting": "في انتظار التفويض...",
|
||||
"action.create.error": "فشل في إنشاء المهمة. يرجى المحاولة مرة أخرى.",
|
||||
"action.create.success": "تمت إضافة المهمة المجدولة. يمكنك العثور عليها في Lobe AI.",
|
||||
"action.createButton": "إضافة مهمة",
|
||||
"action.creating": "جاري الإنشاء...",
|
||||
"action.dismiss.error": "فشل في الإلغاء. يرجى المحاولة مرة أخرى.",
|
||||
"action.dismiss.tooltip": "غير مهتم",
|
||||
"action.refresh.button": "تحديث",
|
||||
"action.optionalConnect.button": "اتصل بـ {{provider}} للحصول على نتائج أكثر ثراءً",
|
||||
"ad-creative-inspiration.description": "كل صباح، قم بمسح إعلانات المنافسين / العلامات التجارية المرجعية (مكتبة إعلانات Meta / Google) — 10 يمكننا تكييفها.",
|
||||
"ad-creative-inspiration.instruction": "كل صباح في الساعة 10:00، قم بمسح الإعلانات الإبداعية الحديثة من منافسيّ والعلامات التجارية المرجعية عبر مكتبة إعلانات Meta وGoogle. اختر 10 تستحق التكيف وشرح السبب.",
|
||||
"ad-creative-inspiration.prompt": "كل صباح في الساعة 10:00، قم بمسح الإعلانات الإبداعية الحديثة من منافسيّ والعلامات التجارية المرجعية عبر مكتبة إعلانات Meta وGoogle. اختر 10 تستحق التكييف ووضح السبب.",
|
||||
"ad-creative-inspiration.title": "إلهام الإعلانات الإبداعية",
|
||||
"aigc-prompt-inspiration.description": "كل صباح، 5 مطالبات مختارة (Midjourney / SD / Flux) مرتبة حسب الأسلوب — جرب واحدة اليوم.",
|
||||
"aigc-prompt-inspiration.instruction": "كل صباح في الساعة 10:00، قدم لي 5 مطالبات مختارة لـ Midjourney أو Stable Diffusion أو Flux، مجمعة حسب الأسلوب. يجب أن تكون كل مطالبة جاهزة للنسخ والتجربة.",
|
||||
"aigc-prompt-inspiration.prompt": "كل صباح في الساعة 10:00، أعطني 5 مطالبات مختارة لـ Midjourney أو Stable Diffusion أو Flux، مرتبة حسب الأسلوب. يجب أن تكون كل مطالبة جاهزة للنسخ والتجربة.",
|
||||
"aigc-prompt-inspiration.title": "إلهام مطالبات AIGC",
|
||||
"arxiv-curated-daily.description": "كل صباح، 5 أوراق جديدة من arXiv في مجال بحثك مع ملخصات من سطر واحد.",
|
||||
"arxiv-curated-daily.instruction": "كل صباح في الساعة 09:00، اختر 5 من أحدث أوراق arXiv في مجال بحثي وقدم لي ملخصًا من سطر واحد لكل منها، حتى أتمكن من تحديد أيها أقرأ بعمق.",
|
||||
"arxiv-curated-daily.prompt": "كل صباح في الساعة 09:00، اختر 5 من أحدث أوراق arXiv في مجال بحثي وقدم لي ملخصًا من سطر واحد لكل منها، حتى أتمكن من تحديد أيها أقرأ بعمق.",
|
||||
"arxiv-curated-daily.title": "اختيارات arXiv اليومية",
|
||||
"bedtime-gratitude.description": "كل ليلة في الساعة 22، اطلب 3 أشياء تشعر بالامتنان لها وشيئًا تعلمته اليوم.",
|
||||
"bedtime-gratitude.instruction": "كل مساء في الساعة 22:00، اطلب مني مشاركة 3 أشياء أنا ممتن لها اليوم وشيء واحد تعلمته. قدم انعكاسًا لطيفًا في فقرة واحدة. إذا كان Notion متصلًا، أضف الإدخال إلى صفحة يومياتي.",
|
||||
"bedtime-gratitude.prompt": "كل مساء في الساعة 22:00، اطلب مني مشاركة 3 أشياء أشعر بالامتنان لها اليوم وشيئًا تعلمته. قدم انعكاسًا لطيفًا من فقرة واحدة. إذا كان Notion متصلًا، أضف الإدخال إلى صفحة يومياتي.",
|
||||
"bedtime-gratitude.title": "امتنان وقت النوم",
|
||||
"brand-collab-weekly.description": "كل يوم اثنين، قم بمسح العلامات التجارية التي تبحث عن منشئي محتوى — طابق حسب التخصص وحجم الجمهور.",
|
||||
"brand-collab-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بمسح العلامات التجارية والنداءات العامة التي تبحث بنشاط عن المبدعين. طابقها مع مجالي وحجم جمهوري. قدم 5 تستحق التقديم.",
|
||||
"brand-collab-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بمسح العلامات التجارية والنداءات العامة التي تبحث بنشاط عن منشئي محتوى. طابق مع تخصصي وحجم جمهوري. أبرز 5 تستحق التقديم.",
|
||||
"brand-collab-weekly.title": "تعاون العلامات التجارية الأسبوعي",
|
||||
"brand-mention-daily.description": "أخبرني بالعلامات التجارية / الكلمات الرئيسية التي يجب تتبعها — كل مساء، حجم الإشارات، المشاعر، الأصوات البارزة.",
|
||||
"brand-mention-daily.instruction": "كل مساء في الساعة 18:00، لخص الإشارات اليومية للعلامات التجارية والكلمات الرئيسية التي أتابعها على X (تويتر): الحجم، المشاعر، الأصوات البارزة. أبلغ عن أي ارتفاعات غير عادية.",
|
||||
"brand-mention-daily.prompt": "كل مساء في الساعة 18:00، لخص إشارات اليوم للعلامات التجارية والكلمات الرئيسية التي أتابعها على X (تويتر): الحجم، المشاعر، الأصوات البارزة. أبلغ عن أي ارتفاعات غير عادية.",
|
||||
"brand-mention-daily.title": "إشارات العلامة التجارية اليومية",
|
||||
"brand-watch-weekly.description": "كل يوم اثنين، تتبع 10 تحديثات للعلامات التجارية الكبرى — تحديث الشعار، الهوية، إعادة تصميم المواقع — مع تحليل.",
|
||||
"brand-watch-weekly.instruction": "كل يوم اثنين في الساعة 10:00، تتبع 10 تحديثات للعلامات التجارية من الشركات التي أتابعها: تحديثات الشعارات، تغييرات الهوية، إعادة تصميم المواقع. أضف تحليلًا من فقرة واحدة لكل منها.",
|
||||
"brand-watch-weekly.prompt": "كل يوم اثنين في الساعة 10:00، تتبع 10 تحديثات للعلامات التجارية من الشركات التي أتابعها: تحديثات الشعار، تغييرات الهوية، إعادة تصميم المواقع. أضف تحليلًا من فقرة واحدة لكل منها.",
|
||||
"brand-watch-weekly.title": "مراقبة العلامات التجارية الأسبوعية",
|
||||
"calendar-conflict-check.description": "كل صباح، افحص اليوم بحثًا عن تعارضات، اجتماعات متتالية، وقت سفر غير كافٍ.",
|
||||
"calendar-conflict-check.instruction": "كل صباح في الساعة 07:30، قم بمسح تقويم اليوم بحثًا عن التعارضات، الاجتماعات المتتالية، أو وقت السفر/الفاصل غير الكافي. اقترح إصلاحات.",
|
||||
"calendar-conflict-check.prompt": "كل صباح في الساعة 07:30، افحص تقويم اليوم بحثًا عن تعارضات، اجتماعات متتالية، أو وقت سفر / احتياطي غير كافٍ. اقترح حلولًا.",
|
||||
"calendar-conflict-check.title": "فحص تعارض التقويم",
|
||||
"card.templateTag": "نموذج",
|
||||
"cashflow-weekly.description": "كل يوم اثنين، ما الذي سيدخل هذا الأسبوع، ما الذي سيخرج، النفقات الكبيرة الأسبوع المقبل.",
|
||||
"cashflow-weekly.instruction": "كل يوم اثنين في الساعة 09:00، راجع التدفق النقدي: المستحقات هذا الأسبوع، المدفوعات المستحقة، والنفقات الكبيرة المقررة للأسبوع المقبل.",
|
||||
"cashflow-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع التدفق النقدي: المستحقات المستحقة هذا الأسبوع، المدفوعات المستحقة، والنفقات الكبيرة المجدولة للأسبوع المقبل.",
|
||||
"cashflow-weekly.title": "التدفق النقدي الأسبوعي",
|
||||
"child-growth-weekly.description": "أخبرني بعمر طفلك — كل يوم اثنين، تركيز التطوير لهذا الأسبوع + أفكار الأنشطة.",
|
||||
"child-growth-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قدم لي مجالات التركيز التنموية المناسبة لعمر طفلي هذا الأسبوع، بالإضافة إلى أفكار لأنشطة الوالدين والطفل وأشياء يجب مراقبتها.",
|
||||
"child-growth-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني مجالات التركيز التنموي المناسبة لعمر طفلي هذا الأسبوع، بالإضافة إلى أفكار الأنشطة بين الوالدين والطفل والأشياء التي يجب مراقبتها.",
|
||||
"child-growth-weekly.title": "نمو الطفل الأسبوعي",
|
||||
"child-study-weekly.description": "أخبرني بما يدرسه طفلك — كل يوم أحد، تقدم هذا الأسبوع + تركيز الأسبوع المقبل.",
|
||||
"child-study-weekly.instruction": "كل يوم أحد في الساعة 20:00، قم بتلخيص تقدم دراسة طفلي هذا الأسبوع وحدد مجالات التركيز للأسبوع المقبل. اقترح أنشطة تدريبية لكل مادة.",
|
||||
"child-study-weekly.prompt": "كل يوم أحد في الساعة 20:00، لخص تقدم دراسة طفلي هذا الأسبوع وحدد مجالات التركيز للأسبوع المقبل. اقترح أنشطة تدريبية لكل موضوع.",
|
||||
"child-study-weekly.title": "دراسة الطفل الأسبوعية",
|
||||
"competitor-creator-tracking.description": "أخبرني بـ 3-5 منشئين لمتابعتهم — كل صباح أتابع ما أنجزوه وما نجح.",
|
||||
"competitor-creator-tracking.instruction": "كل صباح في الساعة 09:00، تتبع 3-5 من المبدعين الذين أتابعهم كمنافسين: ما نشروا، كيف كان أداؤهم، وأفكار يمكنني تكييفها.",
|
||||
"competitor-creator-tracking.prompt": "كل صباح في الساعة 09:00، تابع 3-5 منشئين أتابعهم كمنافسين: ما الذي نشروه، كيف كان أداؤه، وأفكار يمكنني تكييفها.",
|
||||
"competitor-creator-tracking.title": "تتبع منشئي المحتوى المنافسين",
|
||||
"competitor-radar-daily.description": "أخبرني بـ 3-5 منافسين — كل يوم أتابع تحديثات المواقع، الإطلاقات، إشارات التوظيف، النشاط الاجتماعي.",
|
||||
"competitor-radar-daily.instruction": "كل صباح في الساعة 09:00، تتبع 3-5 من منافسيّ: تغييرات المواقع، إطلاق المنتجات، إشارات التوظيف، النشاط الاجتماعي. قدم ما يشير إلى تحركات استراتيجية.",
|
||||
"competitor-radar-daily.prompt": "كل صباح في الساعة 09:00، تابع 3-5 من منافسيّ: تغييرات المواقع، إطلاق المنتجات، إشارات التوظيف، النشاط الاجتماعي. أبرز ما يشير إلى تحركات استراتيجية.",
|
||||
"competitor-radar-daily.title": "رادار المنافسين",
|
||||
"competitor-update-daily.description": "أخبرني بـ 3-5 منافسين — كل يوم أتحقق من سجلات التغيير، الميزات الجديدة وتغييرات المواقع.",
|
||||
"competitor-update-daily.instruction": "كل صباح في الساعة 10:00، راقب 3-5 منتجات منافسة: سجلات التغيير، الميزات الجديدة، تغييرات نصوص المواقع. أبلغ عن أي إشارة تستحق نظرة أعمق.",
|
||||
"competitor-update-daily.prompt": "كل صباح في الساعة 10:00، راقب 3-5 منتجات منافسة: سجلات التغيير، الميزات الجديدة، تغييرات نصوص المواقع. أبلغ عن أي إشارة تستحق نظرة أعمق.",
|
||||
"competitor-update-daily.title": "تحديثات منتجات المنافسين",
|
||||
"content-calendar-weekly.description": "كل ليلة أحد، خطط جدول النشر للأسبوع القادم المكون من 7 أيام بما يتماشى مع الأعياد واللحظات الرائجة.",
|
||||
"content-calendar-weekly.instruction": "كل يوم أحد في الساعة 20:00، خطط جدول النشر لمدة 7 أيام للأسبوع المقبل: قم بمطابقة الفترات مع العطلات القادمة واللحظات الرائجة، واقترح زاوية واحدة لكل فترة. إذا كان Notion متصلًا، قم بصياغة الجدول هناك.",
|
||||
"content-calendar-weekly.prompt": "كل يوم أحد في الساعة 20:00، خطط جدول النشر للأسبوع القادم المكون من 7 أيام: قم بمواءمة الفتحات مع الأعياد القادمة واللحظات الرائجة، واقترح زاوية واحدة لكل فتحة. إذا كان Notion متصلًا، قم بصياغة الجدول هناك.",
|
||||
"content-calendar-weekly.title": "جدول المحتوى الأسبوعي",
|
||||
"contract-expiry-weekly.description": "كل يوم اثنين، العقود التي تنتهي الشهر المقبل (الاشتراكات، الإيجارات، الشراكات).",
|
||||
"contract-expiry-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بإدراج العقود (الاشتراكات، الإيجارات، الشراكات) التي تنتهي صلاحيتها خلال الـ 30 يومًا القادمة. أبلغ عن أيها يجب تجديده وأيها يجب إلغاؤه.",
|
||||
"contract-expiry-weekly.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج العقود (الاشتراكات، الإيجارات، الشراكات) التي تنتهي في الـ 30 يومًا القادمة. حدد أيها يجب تجديده وأيها يجب إلغاؤه.",
|
||||
"contract-expiry-weekly.title": "انتهاء العقود الأسبوعي",
|
||||
"core-metric-daily.description": "أخبرني بالمقاييس التي يجب مراقبتها (DAU، الاحتفاظ، التحويل) — كل صباح أقوم بمزامنة التغيرات.",
|
||||
"core-metric-daily.instruction": "كل صباح في الساعة 09:00، قم بمزامنة التغييرات في مقاييسي الأساسية (DAU، الاحتفاظ، التحويل). قارن مع الأمس ومتوسط الـ 7 أيام.",
|
||||
"core-metric-daily.prompt": "كل صباح في الساعة 09:00، قم بمزامنة التغيرات في مقاييسي الأساسية (DAU، الاحتفاظ، التحويل). قارن مع الأمس ومتوسط الـ 7 أيام.",
|
||||
"core-metric-daily.title": "المقاييس الأساسية اليومية",
|
||||
"cross-platform-engagement-daily.description": "كل صباح، التعليقات، الرسائل المباشرة، الإشارات والمتابعين الجدد عبر جميع المنصات — 30 ثانية.",
|
||||
"cross-platform-engagement-daily.instruction": "كل صباح في الساعة 09:00، قم بتجميع التعليقات، الرسائل المباشرة، الإشارات، والمتابعين الجدد عبر منصاتي. أبرز الـ 5 التي تستحق الرد.",
|
||||
"cross-platform-engagement-daily.prompt": "كل صباح في الساعة 09:00، قم بتجميع التعليقات، الرسائل المباشرة، الإشارات، والمتابعين الجدد عبر منصاتي. أبرز الـ 5 التي تستحق الرد.",
|
||||
"cross-platform-engagement-daily.title": "التفاعل عبر المنصات",
|
||||
"crypto-market-daily.description": "كل صباح، تغيرات 24 ساعة لـ BTC، ETH والرموز التي تتابعها + الأحداث الرئيسية على السلسلة.",
|
||||
"crypto-market-daily.instruction": "كل صباح في الساعة 09:00، قدم لي تغير السعر خلال 24 ساعة لـ BTC، ETH، والرموز التي أتابعها، بالإضافة إلى أهم الأحداث على السلسلة من اليوم الماضي.",
|
||||
"crypto-market-daily.prompt": "كل صباح في الساعة 09:00، أعطني تغيرات الأسعار خلال 24 ساعة لـ BTC، ETH، والرموز التي أتابعها، بالإضافة إلى أهم الأحداث على السلسلة من اليوم الماضي.",
|
||||
"crypto-market-daily.title": "سوق العملات الرقمية اليومية",
|
||||
"daily-design-inspiration.description": "كل صباح، قم بتنسيق 10 أعمال من Dribbble، Behance، Awwwards وPinterest التي تتناسب مع أسلوبك.",
|
||||
"daily-design-inspiration.instruction": "كل صباح في الساعة 09:00، قم بتنسيق 10 أعمال تصميم من Dribbble، Behance، Awwwards، وPinterest التي تتناسب مع أسلوبي، مع ملاحظة قصيرة حول ما يجعل كل واحدة مميزة.",
|
||||
"daily-design-inspiration.prompt": "كل صباح في الساعة 09:00، قم بتنسيق 10 أعمال تصميم من Dribbble، Behance، Awwwards، وPinterest التي تتناسب مع أسلوبي، مع ملاحظة قصيرة حول ما يميز كل منها.",
|
||||
"daily-design-inspiration.title": "إلهام التصميم اليومي",
|
||||
"daily-followup-list.description": "كل صباح، قائمة ذات أولوية للعملاء الذين يجب متابعتهم اليوم، مع سياق آخر تواصل.",
|
||||
"daily-followup-list.instruction": "كل صباح في الساعة 09:00، قم ببناء قائمة متابعة ذات أولوية لليوم من جهات الاتصال الخاصة بي في HubSpot. لكل منها، قم بتلخيص التفاعل الأخير.",
|
||||
"daily-followup-list.prompt": "كل صباح في الساعة 09:00، قم ببناء قائمة متابعة ذات أولوية لليوم من جهات الاتصال الخاصة بي في HubSpot. لكل منها، لخص آخر تفاعل.",
|
||||
"daily-followup-list.title": "قائمة المتابعة اليومية",
|
||||
"daily-learning-bite.description": "كل صباح، قدم قطعة واحدة مدتها 15 دقيقة (مقال، فيديو، أو بودكاست) في مجال تعلمك.",
|
||||
"daily-learning-bite.instruction": "كل صباح في الساعة 07:30، قدم لي قطعة مختارة لمدة 15 دقيقة (مقالة، فيديو، أو بودكاست) في مجال تعلمي، مع خلاصة سريعة.",
|
||||
"daily-learning-bite.prompt": "كل صباح في الساعة 07:30، أحضر لي قطعة واحدة مدتها 15 دقيقة (مقال، فيديو، أو بودكاست) في مجال تعلمي، مع خلاصة سريعة.",
|
||||
"daily-learning-bite.title": "لقمة التعلم اليومية",
|
||||
"daily-topic-pick.description": "كل صباح، قم بمسح أفضل 10 قطع أداءً في مجالك أمس وقم بتحليل الزوايا.",
|
||||
"daily-topic-pick.instruction": "كل صباح في الساعة 09:00، اجمع أفضل 10 قطع محتوى أداءً من مجالي أمس، قم بتحليل زواياها، واختر 1-2 يمكنني نشرها اليوم.",
|
||||
"daily-topic-pick.prompt": "كل صباح في الساعة 09:00، اجمع أفضل 10 قطع محتوى أداءً من مجالي أمس، قم بتحليل زواياها، واختر 1-2 يمكنني نشرها اليوم.",
|
||||
"daily-topic-pick.title": "رادار الموضوعات اليومية",
|
||||
"deal-pipeline-weekly.description": "كل يوم جمعة، كل صفقة في خط الأنابيب: المتحركة، المتوقفة، المتوقع إغلاقها هذا الشهر.",
|
||||
"deal-pipeline-weekly.instruction": "كل يوم جمعة في الساعة 16:00، قم بمراجعة كل صفقة في خط أنابيب HubSpot الخاص بي: ما تحرك هذا الأسبوع، ما توقف، والتوقعات للإغلاق بنجاح بحلول نهاية الشهر.",
|
||||
"deal-pipeline-weekly.prompt": "كل يوم جمعة في الساعة 16:00، راجع كل صفقة في خط الأنابيب الخاص بي في HubSpot: ما الذي تحرك هذا الأسبوع، ما الذي توقف، والإغلاق المتوقع بحلول نهاية الشهر.",
|
||||
"deal-pipeline-weekly.title": "خط الأنابيب الأسبوعي للصفقات",
|
||||
"dependency-security-weekly.description": "كل يوم اثنين، قم بمسح مشاريعك بحثًا عن الثغرات والحزم القديمة مع أولوية الترقية.",
|
||||
"dependency-security-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بمسح مشاريع GitHub الخاصة بي بحثًا عن التبعيات الضعيفة والقديمة. اقترح أولوية الترقية بناءً على الخطورة وخطر التغيير الكبير.",
|
||||
"dependency-security-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بمسح مشاريعي على GitHub بحثًا عن التبعيات الضعيفة والقديمة. اقترح أولوية الترقية بناءً على الخطورة ومخاطر التغييرات الجذرية.",
|
||||
"dependency-security-weekly.title": "فحص أمان التبعيات",
|
||||
"design-trend-weekly.description": "كل يوم اثنين، 3 اتجاهات في واجهات المستخدم / العلامات التجارية / الرسوم التوضيحية مع 5 أمثلة تمثيلية.",
|
||||
"design-trend-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قدم لي 3 اتجاهات ناشئة عبر واجهات المستخدم، العلامات التجارية، والرسوم التوضيحية هذا الأسبوع، مع 5 أمثلة تمثيلية. ساعدني على البقاء على اطلاع.",
|
||||
"design-trend-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني 3 اتجاهات ناشئة عبر واجهات المستخدم، العلامات التجارية، والرسوم التوضيحية هذا الأسبوع، مع 5 أمثلة تمثيلية. ساعدني على البقاء على اطلاع.",
|
||||
"design-trend-weekly.title": "اتجاهات التصميم الأسبوعية",
|
||||
"diet-log-companion.description": "كل مساء، استعرض ما أكلته اليوم — اقتراحات لطيفة، بدون حكم.",
|
||||
"diet-log-companion.instruction": "كل مساء في الساعة 21:00، قم بمراجعة ما أكلته اليوم وقدم اقتراحًا أو اثنين لطيفين وغير حكميين للغد.",
|
||||
"diet-log-companion.prompt": "كل مساء في الساعة 21:00، استعرض معي ما أكلته اليوم وقدم اقتراحًا أو اثنين لطيفين وغير حكميين للغد.",
|
||||
"diet-log-companion.title": "رفيق تسجيل النظام الغذائي",
|
||||
"exhibition-event-weekly.description": "أخبرني بمدينتك — كل يوم اثنين، معارض هذا الأسبوع، العروض، والعروض الحية.",
|
||||
"exhibition-event-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بإدراج المعارض، العروض، وعروض اللايف هاوس لهذا الأسبوع في مدينتي. أضف سياقًا سريعًا للأكثر إثارة.",
|
||||
"exhibition-event-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بإدراج معارض هذا الأسبوع، العروض، والعروض الحية في مدينتي. أضف سياقًا سريعًا للأكثر إثارة.",
|
||||
"exhibition-event-weekly.title": "المعارض والفعاليات",
|
||||
"family-finance-weekly.description": "كل ليلة أحد، تحليل الإنفاق لهذا الأسبوع، إكمال الميزانية، النفقات الكبيرة للأسبوع المقبل.",
|
||||
"family-finance-weekly.instruction": "كل يوم أحد في الساعة 20:00، قم بمراجعة إنفاق الأسرة هذا الأسبوع: تقسيم الفئات من سجل Google Sheets الخاص بي، اكتمال الميزانية، والنفقات الكبيرة المخطط لها الأسبوع المقبل.",
|
||||
"family-finance-weekly.prompt": "كل يوم أحد في الساعة 20:00، راجع إنفاق الأسرة لهذا الأسبوع: تحليل الفئات من سجل Google Sheets الخاص بي، إكمال الميزانية، والنفقات الكبيرة المخطط لها الأسبوع المقبل.",
|
||||
"family-finance-weekly.title": "المالية الأسرية الأسبوعية",
|
||||
"family-task-schedule.description": "كل صباح يوم اثنين، قم بتقسيم المهام، المهمات، توصيلات المدرسة، والفواتير لهذا الأسبوع عبر الأسرة.",
|
||||
"family-task-schedule.instruction": "كل يوم اثنين في الساعة 08:00، قم بصياغة خطة مهام الأسرة لهذا الأسبوع: الأعمال المنزلية، جولات البقالة، توصيلات المدرسة، دفع الفواتير. قم بتعيين مالكين مؤقتين وفترات زمنية. إذا كان Google Calendar متصلًا، اقترح كتل يمكنني إضافتها.",
|
||||
"family-task-schedule.prompt": "كل يوم اثنين في الساعة 08:00، قم بصياغة خطة مهام الأسرة لهذا الأسبوع: الأعمال المنزلية، رحلات البقالة، توصيلات المدرسة، دفع الفواتير. قم بتعيين مالكي المهام والفتحات الزمنية بشكل مبدئي. إذا كان Google Calendar متصلًا، اقترح كتلًا يمكنني إضافتها.",
|
||||
"family-task-schedule.title": "جدول مهام الأسرة",
|
||||
"figma-files-cleanup.description": "كل يوم جمعة، راجع ملفات Figma التي تم تحريرها مؤخرًا — حدد ما يجب أرشفته، وما يجب تسليمه للمطورين.",
|
||||
"figma-files-cleanup.instruction": "كل يوم جمعة في الساعة 17:00، قم بمراجعة ملفات Figma التي تم تحريرها مؤخرًا. أبلغ عن أيها يجب أرشفته، أيها يحتاج إلى تسليم للهندسة، وأيها لا يزال بحاجة إلى تحسين.",
|
||||
"figma-files-cleanup.prompt": "كل يوم جمعة في الساعة 17:00، راجع ملفات Figma التي تم تحريرها مؤخرًا. حدد ما يجب أرشفته، وما يحتاج إلى تسليم للهندسة، وما لا يزال بحاجة إلى تحسين.",
|
||||
"figma-files-cleanup.title": "تنظيف ملفات Figma",
|
||||
"follower-growth-weekly.description": "كل يوم اثنين، تغييرات المتابعين عبر المنصات — أين يجب التركيز، وأين يجب الإصلاح.",
|
||||
"follower-growth-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بمراجعة نمو المتابعين عبر X (تويتر) والمنصات الأخرى الخاصة بي. أبرز أين يجب التركيز وأين ينخفض التفاعل.",
|
||||
"follower-growth-weekly.prompt": "كل يوم اثنين في الساعة 10:00، راجع نمو المتابعين عبر X (تويتر) ومنصاتي الأخرى. أبرز أين يجب التركيز وأين ينخفض التفاعل.",
|
||||
"follower-growth-weekly.title": "نمو المتابعين الأسبوعي",
|
||||
"font-color-weekly.description": "كل يوم أربعاء، 3 أزواج خطوط + 3 لوحات ألوان تستحق الحفظ في مكتبة الإلهام الخاصة بك.",
|
||||
"font-color-weekly.instruction": "كل يوم الأربعاء في الساعة 10:00، قدم لي 3 أزواج خطوط ملحوظة و3 لوحات ألوان تستحق الحفظ. قم بتضمين مكان ترخيص كل خط.",
|
||||
"font-color-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، قدم لي 3 أزواج خطوط ملحوظة و3 لوحات ألوان تستحق الحفظ. قم بتضمين مكان ترخيص كل خط.",
|
||||
"font-color-weekly.title": "الخطوط والألوان الأسبوعية",
|
||||
"friday-wrap-list.description": "كل مساء جمعة: ما لم ينتهِ، ما سيتم شحنه يوم الاثنين، وأول شيء للأسبوع المقبل.",
|
||||
"friday-wrap-list.instruction": "كل يوم جمعة في الساعة 16:00، قم بإدراج: ما لم أنجزه هذا الأسبوع، ما يجب شحنه يوم الاثنين، وأول شيء يجب أن أبدأ به الأسبوع المقبل.",
|
||||
"friday-wrap-list.prompt": "كل يوم جمعة في الساعة 16:00، قم بإدراج: ما لم أنتهِ منه هذا الأسبوع، ما يجب شحنه يوم الاثنين، وأول شيء يجب أن أبدأ به الأسبوع المقبل.",
|
||||
"friday-wrap-list.title": "قائمة اختتام الجمعة",
|
||||
"funding-intel-daily.description": "كل صباح، 3-5 إعلانات تمويل في مجالك: من جمع الأموال، التقييم، من قاد.",
|
||||
"funding-intel-daily.instruction": "كل صباح في الساعة 10:00، قدم لي 3-5 إعلانات تمويل في مجالي من الـ 24 ساعة الماضية: من جمع الأموال، كم، التقييم إذا تم الكشف عنه، المستثمر الرئيسي.",
|
||||
"funding-intel-daily.prompt": "كل صباح في الساعة 10:00، أعطني 3-5 إعلانات تمويل في مجالي من الـ 24 ساعة الماضية: من جمع الأموال، كم، التقييم إذا تم الكشف عنه، المستثمر الرئيسي.",
|
||||
"funding-intel-daily.title": "معلومات التمويل اليومية",
|
||||
"headline-inspiration.description": "كل صباح، 10 قوالب عناوين متطابقة مع العلامة التجارية مستوحاة من النجاحات الأخيرة.",
|
||||
"headline-inspiration.instruction": "كل صباح في الساعة 10:00، قدم لي 10 قوالب عناوين تتناسب مع صوتي، مستخرجة من القطع الفيروسية الأخيرة في مجالي. يجب أن أتمكن من نسخها مباشرة عندما أكون عالقًا.",
|
||||
"headline-inspiration.prompt": "كل صباح في الساعة 10:00، أعطني 10 قوالب عناوين تتطابق مع صوتي، مستوحاة من القطع الفيروسية الأخيرة في مجالي. يجب أن أتمكن من نسخها مباشرة عند الحاجة.",
|
||||
"headline-inspiration.title": "إلهام العناوين",
|
||||
"hot-topic-radar.description": "كل صباح، أبرز 5 موضوعات تزداد سخونة في مجالك — ادخل قبل أن يصبح السوق مشبعًا.",
|
||||
"hot-topic-radar.instruction": "كل صباح في الساعة 10:00، قدم 5 مواضيع في مجالي تزداد سخونة ولكن لم يتم تشبعها بعد، مع ملاحظة من سطر واحد حول سبب كون كل منها يستحق القفز عليه الآن.",
|
||||
"hot-topic-radar.prompt": "كل صباح في الساعة 10:00، أبرز 5 موضوعات في مجالي تزداد سخونة ولكن لم تصل بعد إلى التشبع، مع ملاحظة من سطر واحد حول سبب أهمية كل منها الآن.",
|
||||
"hot-topic-radar.title": "رادار الموضوعات الساخنة",
|
||||
"hubspot-funnel-daily.description": "كل صباح، تتبع تغييرات قمع MQL / SQL / الإغلاق الناجح — حدد أين تتسرب الصفقات.",
|
||||
"hubspot-funnel-daily.instruction": "كل صباح في الساعة 09:00، قم بمراجعة خط أنابيب HubSpot الخاص بي: حركات MQL، SQL، والإغلاق الناجح. أبرز المراحل ذات الانخفاض الكبير مقارنة بالأسبوع السابق.",
|
||||
"hubspot-funnel-daily.prompt": "كل صباح في الساعة 09:00، راجع قمع HubSpot الخاص بي: تحركات MQL، SQL، والإغلاق الناجح. أبرز المراحل ذات التسرب العالي مقارنة بالأسبوع السابق.",
|
||||
"hubspot-funnel-daily.title": "قمع HubSpot اليومي",
|
||||
"industry-morning-brief.description": "كل صباح، لخص 5 عناصر أخبار مهمة، جولات تمويل وتحولات سياسية في مجالك في قراءة مدتها 5 دقائق.",
|
||||
"industry-morning-brief.instruction": "كل صباح في الساعة 08:00، قم بتكثيف 5 عناصر أخبار مهمة، جولات تمويل، وتحولات سياسية من مجالي إلى قراءة لمدة 5 دقائق.",
|
||||
"industry-morning-brief.prompt": "كل صباح في الساعة 08:00، لخص 5 عناصر أخبار مهمة، جولات تمويل، وتحولات سياسية من مجالي في قراءة مدتها 5 دقائق.",
|
||||
"industry-morning-brief.title": "موجز الصباح الصناعي",
|
||||
"industry-research-weekly.description": "كل يوم اثنين، ديناميكيات السوق، التمويل، اللاعبين الجدد وتحولات تنظيمية في قطاعك.",
|
||||
"industry-research-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بتلخيص الأسبوع الماضي في مجالي: ديناميكيات السوق، جولات التمويل، الوافدين الجدد، التحولات التنظيمية. قم بتنسيقها كموجز بحثي.",
|
||||
"industry-research-weekly.prompt": "كل يوم اثنين في الساعة 09:00، لخص الأسبوع الماضي في قطاعي: ديناميكيات السوق، جولات التمويل، الوافدين الجدد، التحولات التنظيمية. قم بتنسيقها كموجز بحث.",
|
||||
"industry-research-weekly.title": "البحث الصناعي الأسبوعي",
|
||||
"invoice-collection-daily.description": "كل صباح، الفواتير المتأخرة، الأيام المتأخرة، من يحتاج إلى بريد متابعة اليوم.",
|
||||
"invoice-collection-daily.instruction": "كل صباح في الساعة 10:00، قم بإدراج الفواتير المتأخرة مع الأيام المتأخرة وجهة الاتصال للمتابعة. قم بصياغة بريد متابعة مهذب لكل منها.",
|
||||
"invoice-collection-daily.prompt": "كل صباح في الساعة 10:00، قم بإدراج الفواتير المتأخرة مع الأيام المتأخرة وجهة الاتصال للمتابعة. قم بصياغة بريد متابعة مهذب لكل منها.",
|
||||
"invoice-collection-daily.title": "تحصيل الفواتير اليومية",
|
||||
"iteration-recap-weekly.description": "كل مساء جمعة، بيانات هذه الدورة: معدل الإكمال، العناصر المتأخرة، الأخطاء الجديدة.",
|
||||
"iteration-recap-weekly.instruction": "كل يوم جمعة في الساعة 17:00، قم بتلخيص تكرار هذا الأسبوع: معدل الإنجاز، العناصر المتأخرة، الأخطاء الجديدة المبلغ عنها. قم بتنسيقها جاهزة للإسقاط في مراجعة يوم الاثنين.",
|
||||
"iteration-recap-weekly.prompt": "كل يوم جمعة في الساعة 17:00، لخص دورة هذا الأسبوع: معدل الإكمال، العناصر المتأخرة، الأخطاء الجديدة المسجلة. قم بتنسيقها جاهزة للإدراج في استعراض يوم الاثنين.",
|
||||
"iteration-recap-weekly.title": "ملخص الدورة الأسبوعية",
|
||||
"key-account-radar.description": "أخبرني بحساباتك الرئيسية — كل يوم أتابع أخبارهم، تمويلهم، تغييراتهم التنفيذية.",
|
||||
"key-account-radar.instruction": "كل صباح في الساعة 09:00، قم بمسح الأخبار عن حساباتي الرئيسية: أخبار الشركات، التمويل، تغييرات التنفيذيين. قدم أي شيء يمكنني استخدامه كمدخل لمحادثة تجديد.",
|
||||
"key-account-radar.prompt": "كل صباح في الساعة 09:00، قم بمسح الأخبار عن حساباتي الرئيسية: أخبار الشركة، التمويل، التغييرات التنفيذية. أبرز أي شيء يمكنني استخدامه كمدخل لمحادثة تجديد.",
|
||||
"key-account-radar.title": "رادار الحسابات الرئيسية",
|
||||
"keyword-tech-feed.description": "أخبرني بالكلمات الرئيسية التقنية التي يجب تتبعها — كل يوم أعيد 5 منشورات وخيوط عالية الجودة.",
|
||||
"keyword-tech-feed.instruction": "كل صباح في الساعة 10:00، قم بجلب 5 منشورات جديدة عالية الجودة، مقالات مدونة، أو أسئلة وأجوبة تتطابق مع الكلمات التقنية الرئيسية التي أتابعها.",
|
||||
"keyword-tech-feed.prompt": "كل صباح في الساعة 10:00، اجلب 5 منشورات جديدة عالية الجودة، مقالات مدونة، أو أسئلة وأجوبة تتطابق مع الكلمات الرئيسية التقنية التي أتابعها.",
|
||||
"keyword-tech-feed.title": "تغذية الكلمات التقنية",
|
||||
"kol-collab-calendar.description": "كل يوم اثنين، قم بمزامنة التعاونات الجارية مع KOL: من المستحق، من المتأخر، الأداء حتى الآن.",
|
||||
"kol-collab-calendar.instruction": "كل يوم اثنين في الساعة 09:00، قم بمراجعة التعاونات مع KOL التي أجريها: من المقرر أن ينشر، من تأخر، وأرقام الأداء للمنشورات المكتملة.",
|
||||
"kol-collab-calendar.prompt": "كل يوم اثنين في الساعة 09:00، راجع التعاونات مع KOL التي أقوم بها: من المستحق النشر، من المتأخر، وأرقام الأداء للمنشورات المكتملة.",
|
||||
"kol-collab-calendar.title": "تقويم التعاون مع KOL",
|
||||
"language-morning-bite.description": "كل صباح، قراءة مدتها 3 دقائق باللغة المستهدفة + 5 بطاقات مفردات. تعلم أثناء تنقلاتك.",
|
||||
"language-morning-bite.instruction": "كل صباح في الساعة 07:30، قدم لي قراءة لمدة 3 دقائق في اللغة المستهدفة بالإضافة إلى 5 بطاقات مفردات (الكلمة، التعريف، جملة المثال).",
|
||||
"language-morning-bite.prompt": "كل صباح في الساعة 07:30، أعطني قراءة مدتها 3 دقائق بلغتي المستهدفة بالإضافة إلى 5 بطاقات مفردات (الكلمة، التعريف، جملة مثال).",
|
||||
"language-morning-bite.title": "لقمة اللغة الصباحية",
|
||||
"linear-sprint-daily.description": "كل صباح، قم بمزامنة تقدم الدورة: العوائق، العناصر المتأخرة، تركيز اليوم — جاهز قبل الاجتماع.",
|
||||
"linear-sprint-daily.instruction": "كل صباح في الساعة 08:30، قم بمزامنة سباق Linear الخاص بي: العوائق، العناصر المتأخرة، ما يجب أن أركز عليه اليوم. قم بتنسيقها كموجز لمدة 5 دقائق قبل الاجتماع.",
|
||||
"linear-sprint-daily.prompt": "كل صباح في الساعة 08:30، قم بمزامنة دورة Linear الخاصة بي: العوائق، العناصر المتأخرة، ما يجب أن أركز عليه اليوم. قم بتنسيقها كموجز مدته 5 دقائق قبل الاجتماع.",
|
||||
"linear-sprint-daily.title": "الدورة اليومية لـ Linear",
|
||||
"macro-economy-weekly.description": "كل صباح يوم اثنين، أسعار الصرف، الفائدة، النفط، الذهب، المؤشرات الرئيسية — السياق قبل المكالمات عبر الحدود.",
|
||||
"macro-economy-weekly.instruction": "كل يوم اثنين في الساعة 08:00، قدم لي لقطة اقتصادية شاملة: أسعار الصرف، أسعار الفائدة، النفط، الذهب، الفضة، مؤشرات الأسهم الرئيسية. أضف ملخصًا من فقرة واحدة \"ما تغير\".",
|
||||
"macro-economy-weekly.prompt": "كل يوم اثنين في الساعة 08:00، أعطني لقطة اقتصادية شاملة: أسعار الصرف، أسعار الفائدة، النفط، الذهب، الفضة، المؤشرات الرئيسية للأسهم. أضف ملخصًا من فقرة واحدة \"ما الذي تغير\".",
|
||||
"macro-economy-weekly.title": "الاقتصاد الكلي الأسبوعي",
|
||||
"marketing-hot-radar.description": "كل صباح، تتبع 5 موضوعات تسويقية تزداد سخونة في مجالك — أيها يجب ركوبه، وأيها يجب تجنبه.",
|
||||
"marketing-hot-radar.instruction": "كل صباح في الساعة 10:00، تتبع 5 مواضيع تسويقية تزداد سخونة في مجالي، أبلغ عن أيها يجب ركوبه وأيها يجب تجنبه، مع سبب من 1-2 جملة.",
|
||||
"marketing-hot-radar.prompt": "كل صباح في الساعة 10:00، تتبع 5 موضوعات تسويقية تزداد سخونة في مجالي، حدد أيها يجب ركوبه وأيها يجب تجنبه، مع سبب من 1-2 جملة.",
|
||||
"marketing-hot-radar.title": "رادار التسويق الساخن",
|
||||
"meeting-brief.description": "كل صباح، قم بإعداد موجز من صفحة واحدة لكل اجتماع اليوم: السياق، الحضور، الملاحظات الأخيرة.",
|
||||
"meeting-brief.instruction": "كل صباح في الساعة 08:30، قم بإنشاء موجز تحضيري من صفحة واحدة لكل اجتماع على تقويمي اليوم: السياق، الحضور، ملاحظات الاجتماع الأخير. اقرأ قبل الدخول.",
|
||||
"meeting-brief.prompt": "كل صباح في الساعة 08:30، قم بإنشاء موجز تحضيري من صفحة واحدة لكل اجتماع في تقويمي اليوم: السياق، الحضور، ملاحظات الاجتماع الأخير. اقرأ قبل الدخول.",
|
||||
"meeting-brief.title": "موجز التحضير للاجتماعات",
|
||||
"monetization-opportunity-weekly.description": "كل يوم أربعاء، قنوات تحقيق الدخل الجديدة ودراسات الحالة للمنشئين: الإعلانات، الدورات، العضويات، التجارة.",
|
||||
"monetization-opportunity-weekly.instruction": "كل يوم الأربعاء في الساعة 10:00، قدم قنوات تحقيق الدخل الجديدة ودراسات الحالة ذات الصلة بالمبدعين في مجالي: الرعايات، المحتوى المدفوع، العضويات، التجارة.",
|
||||
"monetization-opportunity-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، أبرز قنوات تحقيق الدخل الجديدة ودراسات الحالة ذات الصلة بالمنشئين في مجالي: الرعاية، المحتوى المدفوع، العضويات، التجارة.",
|
||||
"monetization-opportunity-weekly.title": "فرص تحقيق الدخل",
|
||||
"morning-brief.description": "كل يوم في الساعة 8: جدول اليوم، عدد الرسائل الإلكترونية المعلقة، المهام، الطقس. اقرأ في الطريق.",
|
||||
"morning-brief.instruction": "كل صباح في الساعة 08:00، أرسل لي: تقويم اليوم، عدد البريد الإلكتروني المعلق، أهم 3 مهام، والطقس. قم بتنسيقها كقراءة لمدة دقيقة واحدة.",
|
||||
"morning-brief.prompt": "كل صباح في الساعة 08:00، أرسل لي: تقويم اليوم، عدد الرسائل الإلكترونية المعلقة، أهم 3 مهام، والطقس. قم بتنسيقها كقراءة مدتها دقيقة واحدة.",
|
||||
"morning-brief.title": "موجز الصباح",
|
||||
"morning-ritual.description": "كل يوم في الساعة 7: الطقس، جدول اليوم، فكرة اليوم، وتذكير بالحركة — بداية لطيفة.",
|
||||
"morning-ritual.instruction": "كل صباح في الساعة 07:00، أرسل لي طقوس صباحية لطيفة: الطقس، جدول اليوم، فكرة قصيرة لليوم، واقتراح حركة صغيرة. إذا كان Google Calendar متصلًا، قم بتثبيت الجدول هناك.",
|
||||
"morning-ritual.prompt": "كل صباح في الساعة 07:00، أرسل لي طقوس صباحية لطيفة: الطقس، جدول اليوم، فكرة قصيرة لليوم، واقتراح حركة صغيرة. إذا كان Google Calendar متصلًا، قم بربط الجدول هناك.",
|
||||
"morning-ritual.title": "الطقوس الصباحية",
|
||||
"must-read-papers-weekly.description": "كل ليلة أحد، 3 أوراق الأكثر استشهادًا / الأكثر نقاشًا هذا الأسبوع كقائمة قراءة عميقة.",
|
||||
"must-read-papers-weekly.instruction": "كل يوم أحد في الساعة 20:00، اختر 3 أوراق من مجالي البحثي التي كانت الأكثر اقتباسًا أو الأكثر مناقشة هذا الأسبوع. قم بتنسيق قائمة قراءة عميقة يمكنني إنهاؤها خلال عطلة نهاية الأسبوع.",
|
||||
"must-read-papers-weekly.prompt": "كل يوم أحد في الساعة 20:00، اختر 3 أوراق من مجالي البحثي التي كانت الأكثر استشهادًا أو الأكثر نقاشًا هذا الأسبوع. قم بتنسيق قائمة قراءة عميقة يمكنني إنهاؤها خلال عطلة نهاية الأسبوع.",
|
||||
"must-read-papers-weekly.title": "الأوراق التي يجب قراءتها أسبوعيًا",
|
||||
"newsletter-aggregator.description": "كل ليلة أحد، قم بدمج النشرات الإخبارية التي اشتركت فيها في ملخص عطلة نهاية الأسبوع.",
|
||||
"newsletter-aggregator.instruction": "كل يوم أحد في الساعة 20:00، قم بمسح صندوق الوارد Gmail الخاص بي بحثًا عن النشرات الإخبارية المستلمة هذا الأسبوع ودمجها في ملخص عطلة نهاية الأسبوع مجمعة حسب الموضوع.",
|
||||
"newsletter-aggregator.prompt": "كل يوم أحد في الساعة 20:00، قم بمسح صندوق الوارد الخاص بي في Gmail بحثًا عن النشرات الإخبارية التي تم استلامها هذا الأسبوع ودمجها في ملخص عطلة نهاية الأسبوع مجمعة حسب الموضوع.",
|
||||
"newsletter-aggregator.title": "مجمّع النشرات الإخبارية",
|
||||
"newsletter-perf-weekly.description": "كل يوم اثنين، معدل الفتح، معدل النقر، واتجاهات إلغاء الاشتراك — حدد ما يجب تحسينه.",
|
||||
"newsletter-perf-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بمراجعة معدل فتح النشرة الإخبارية، معدل النقر، واتجاهات إلغاء الاشتراك من الأسابيع الأربعة الماضية. أبلغ عن أي أجزاء تحتاج إلى تحسين.",
|
||||
"newsletter-perf-weekly.prompt": "كل يوم اثنين في الساعة 10:00، راجع معدل فتح النشرة الإخبارية الخاصة بي، معدل النقر، واتجاهات إلغاء الاشتراك من الأسابيع الأربعة الماضية. حدد أي القطاعات تحتاج إلى تحسين.",
|
||||
"newsletter-perf-weekly.title": "أداء النشرة الإخبارية الأسبوعي",
|
||||
"onboarding-buddy-weekly.description": "كل يوم اثنين، الموظفون الجدد خلال 90 يومًا: التقدم، ملاحظات الزملاء، ما يجب التركيز عليه.",
|
||||
"onboarding-buddy-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بإنشاء تحديث تقدم لكل موظف جديد لا يزال في أول 90 يومًا: المهام المكتملة، ملاحظات الزميل، ما يجب التركيز عليه هذا الأسبوع.",
|
||||
"onboarding-buddy-weekly.prompt": "كل يوم اثنين في الساعة 09:00، قم بإنشاء تحديث تقدم لكل موظف جديد لا يزال في أول 90 يومًا: المهام المكتملة، ملاحظات الزملاء، ما يجب التركيز عليه هذا الأسبوع.",
|
||||
"onboarding-buddy-weekly.title": "تأهيل الموظفين الجدد",
|
||||
"oss-intel-daily.description": "كل صباح، 10 تحديثات تقنية: GitHub Trending، مشاريع مفتوحة المصدر من الشركات الكبرى، إصدارات رئيسية.",
|
||||
"oss-intel-daily.instruction": "كل صباح في الساعة 09:00، قدم لي 10 تحديثات تقنية: GitHub Trending، إصدارات المصادر المفتوحة البارزة من الشركات الكبيرة، والإصدارات الجديدة من المستودعات في مجموعتي.",
|
||||
"oss-intel-daily.prompt": "كل صباح في الساعة 09:00، أعطني 10 تحديثات تقنية: GitHub Trending، إصدارات مفتوحة المصدر البارزة من الشركات الكبرى، والإصدارات الجديدة من المستودعات في تقنيتي.",
|
||||
"oss-intel-daily.title": "معلومات المصادر المفتوحة اليومية",
|
||||
"podcast-new-episodes.description": "أخبرني بالبودكاست الذي اشتركت فيه — كل يوم اثنين، الحلقات الجديدة لهذا الأسبوع + 3 تستحق الاستماع.",
|
||||
"podcast-new-episodes.instruction": "كل يوم اثنين في الساعة 09:00، قم بإدراج الحلقات الجديدة من البودكاست المشترك بها هذا الأسبوع، وقدم التوصيات لأفضل 3 تستحق الاستماع إليها أولاً.",
|
||||
"podcast-new-episodes.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج الحلقات الجديدة من البودكاست الذي اشتركت فيه هذا الأسبوع، وقم بتوصية بأفضل 3 تستحق الاستماع إليها أولاً.",
|
||||
"podcast-new-episodes.title": "الحلقات الجديدة للبودكاست",
|
||||
"portfolio-daily.description": "أخبرني بممتلكاتك — كل إغلاق سوق، تغير اليوم، الأخبار الرئيسية، تحديثات شركات الحيازة.",
|
||||
"portfolio-daily.instruction": "كل يوم في الساعة 16:00 (بعد الإغلاق)، قدم لي تحديث محفظتي: تغير اليوم لكل مركز، أهم الأخبار التي تؤثر على كل حيازة، وأي إعلانات خاصة بالشركة.",
|
||||
"portfolio-daily.prompt": "كل يوم في الساعة 16:00 (بعد الإغلاق)، أعطني تحديث محفظتي: تغير اليوم لكل مركز، أهم الأخبار التي تؤثر على كل حيازة، وأي إعلانات خاصة بالشركة.",
|
||||
"portfolio-daily.title": "المحفظة اليومية",
|
||||
"prd-review-reminder.description": "كل يوم جمعة، قم بإدراج PRDs المستحقة للمراجعة هذا الأسبوع — لا تترك المستندات عالقة في المسودة.",
|
||||
"prd-review-reminder.instruction": "كل يوم جمعة في الساعة 15:00، قم بمراجعة PRDs ووثائق القرار في Notion الخاصة بي التي يجب مراجعتها هذا الأسبوع. أبلغ عن أي شيء لا يزال عالقًا في المسودة.",
|
||||
"prd-review-reminder.prompt": "كل يوم جمعة في الساعة 15:00، راجع PRDs ومستندات القرار في Notion الخاصة بي المستحقة للمراجعة هذا الأسبوع. حدد أي شيء لا يزال عالقًا في المسودة.",
|
||||
"prd-review-reminder.title": "تذكير مراجعة PRD",
|
||||
"pre-market-brief.description": "كل صباح قبل الافتتاح، العناوين الرئيسية للاقتصاد الكلي، الأرباح الرئيسية، الأخبار عن الشركات التي تمتلكها.",
|
||||
"pre-market-brief.instruction": "كل صباح في الساعة 09:00، قدم لي موجز ما قبل السوق: عناوين الأخبار الاقتصادية، الأرباح الرئيسية التي تم إصدارها اليوم، والأخبار عن الشركات في محفظتي.",
|
||||
"pre-market-brief.prompt": "كل صباح في الساعة 09:00، أعطني موجز ما قبل السوق: العناوين الرئيسية للاقتصاد الكلي، الأرباح الرئيسية التي تم إصدارها اليوم، والأخبار عن الشركات في محفظتي.",
|
||||
"pre-market-brief.title": "موجز ما قبل السوق",
|
||||
"precious-metals-daily.description": "كل إغلاق سوق، أسعار الذهب، الفضة، النحاس والنفط مع تغير اليوم — حدد التحركات الكبيرة.",
|
||||
"precious-metals-daily.instruction": "كل يوم في الساعة 16:00 (بعد الإغلاق)، قدم لي أسعار وتغير اليوم لليوم للذهب، الفضة، النحاس، والنفط. أبلغ عن أي حركة تزيد عن 2%.",
|
||||
"precious-metals-daily.prompt": "كل يوم في الساعة 16:00 (بعد الإغلاق)، أعطني أسعار وتغير اليوم لأسعار الذهب، الفضة، النحاس، والنفط. حدد أي تحرك يزيد عن 2%.",
|
||||
"precious-metals-daily.title": "المعادن والطاقة اليومية",
|
||||
"recruit-funnel-daily.description": "كل صباح، المرشحون لكل دور: الطلبات الجديدة، في انتظار المقابلة، في انتظار الملاحظات.",
|
||||
"recruit-funnel-daily.instruction": "كل صباح في الساعة 09:00، قم بتلخيص خط أنابيب التوظيف حسب الدور: الطلبات الجديدة، المرشحين الذين ينتظرون المقابلة، المرشحين الذين ينتظرون الملاحظات. أبلغ عن المحاورين الذين يعيقون.",
|
||||
"recruit-funnel-daily.prompt": "كل صباح في الساعة 09:00، لخص قمع التوظيف حسب الدور: الطلبات الجديدة، المرشحون في انتظار المقابلة، المرشحون في انتظار الملاحظات. حدد المقابلين الذين يعيقون العملية.",
|
||||
"recruit-funnel-daily.title": "قمع التوظيف اليومي",
|
||||
"regulation-watch-weekly.description": "أخبرني بمجالات الامتثال الخاصة بك (البيانات، الضرائب، العمل) — كل يوم اثنين ملخص التغييرات مع التأثير.",
|
||||
"regulation-watch-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بتلخيص التغييرات التنظيمية في مجالات الامتثال التي أتابعها (البيانات، الضرائب، العمل) من الأسبوع الماضي. لكل منها، قم بتقييم التأثير علينا.",
|
||||
"regulation-watch-weekly.prompt": "كل يوم اثنين في الساعة 10:00، لخص التغييرات التنظيمية في مجالات الامتثال التي أتابعها (البيانات، الضرائب، العمل) من الأسبوع الماضي. لكل منها، احكم على التأثير علينا.",
|
||||
"regulation-watch-weekly.title": "مراقبة التنظيم الأسبوعية",
|
||||
"renewal-risk-weekly.description": "كل يوم اثنين، حدد تجديدات هذا الشهر — خاصة الحسابات ذات الاستخدام المتراجع.",
|
||||
"renewal-risk-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بمراجعة عقود HubSpot التي تنتهي صلاحيتها هذا الشهر وأبلغ عن الحسابات ذات الاستخدام المتراجع. اقترح خطة إنقاذ لكل حساب معرض للخطر.",
|
||||
"renewal-risk-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع عقود HubSpot التي تنتهي هذا الشهر وحدد الحسابات ذات الاستخدام المتراجع. اقترح خطة إنقاذ لكل حساب معرض للخطر.",
|
||||
"renewal-risk-weekly.title": "مخاطر التجديد الأسبوعية",
|
||||
"repo-health-weekly.description": "كل يوم اثنين، راجع مستودعاتك: تراكم القضايا، PRs المتوقفة، فشل CI، تنبيهات التبعيات.",
|
||||
"repo-health-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بمراجعة مستودعات GitHub التي أحتفظ بها: تراكم القضايا، PRs المتوقفة، فشل CI، تنبيهات التبعيات. قدم ما يحتاج إلى اهتمام هذا الأسبوع.",
|
||||
"repo-health-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع مستودعات GitHub التي أديرها: تراكم القضايا، PRs المتوقفة، فشل CI، تنبيهات التبعيات. أبرز ما يحتاج إلى الانتباه هذا الأسبوع.",
|
||||
"repo-health-weekly.title": "صحة المستودعات الأسبوعية",
|
||||
"schedule.daily": "كل يوم في {{time}}",
|
||||
"schedule.editableAfterCreateTooltip": "يمكنك تعديل الجدول الزمني بعد إنشاء المهمة.",
|
||||
"schedule.weekly": "كل {{weekday}} في {{time}}",
|
||||
"section.title": "جرب هذه المهام المجدولة",
|
||||
"seo-weekly-report.description": "كل يوم اثنين، حركة الترتيب، الكلمات الرئيسية الناشئة، والصفحات التي تستحق التحديث.",
|
||||
"seo-weekly-report.instruction": "كل يوم اثنين في الساعة 09:00، قدم لي تقرير SEO خفيف الوزن: أفضل المحركات تصنيفًا (صعودًا/هبوطًا)، 5 كلمات رئيسية ناشئة تستحق الاستهداف، و3 صفحات موجودة جاهزة لتحديث المحتوى.",
|
||||
"seo-weekly-report.prompt": "كل يوم اثنين في الساعة 09:00، أعطني تقرير SEO الأسبوعي الخفيف: أهم التحركات في الترتيب (صعودًا / هبوطًا)، 5 كلمات رئيسية ناشئة تستحق الاستهداف، و3 صفحات موجودة جاهزة لتحديث المحتوى.",
|
||||
"seo-weekly-report.title": "تقرير SEO الأسبوعي",
|
||||
"series-update-weekly.description": "أخبرني بما تتابعه — كل أسبوع، تحديثات الحلقات / الفصول وملخصات سريعة.",
|
||||
"series-update-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قدم لي إشعارات التحديث وملخصًا قصيرًا للعروض، الروايات، أو القصص المصورة التي أتابعها.",
|
||||
"series-update-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني إشعارات التحديث وملخصًا قصيرًا للعروض، الروايات، أو القصص المصورة التي أتابعها.",
|
||||
"series-update-weekly.title": "تحديثات السلاسل والكتب الأسبوعية",
|
||||
"standup-brief.description": "كل صباح قبل الاجتماع، اسحب موجز تقدم Linear: تركيز اليوم، العوائق، ما تم إنجازه أمس.",
|
||||
"standup-brief.instruction": "كل صباح في الساعة 08:30، قم بسحب موجز تقدم Linear: تركيز اليوم، العوائق، ما أغلقته أمس. قم بتنسيقها كـ 3 نقاط جاهزة للقراءة بصوت عالٍ في الاجتماع.",
|
||||
"standup-brief.prompt": "كل صباح في الساعة 08:30، اسحب موجز تقدم Linear: تركيز اليوم، العوائق، ما أغلقته أمس. قم بتنسيقها كـ 3 نقاط جاهزة للقراءة بصوت عالٍ في الاجتماع.",
|
||||
"standup-brief.title": "موجز الاجتماع",
|
||||
"sunday-reflection.description": "كل ليلة أحد، استعرض 5 أسئلة: أفضل لحظة، الإحباطات، أهم 3 للأسبوع المقبل.",
|
||||
"sunday-reflection.instruction": "كل يوم أحد في الساعة 21:00، قم بمراجعة 5 مطالبات انعكاسية: الشيء الأكثر إرضاءً هذا الأسبوع، الأكثر إحباطًا، أهم 3 أولويات للأسبوع المقبل، ما تعلمته، ما يجب أن أتخلى عنه.",
|
||||
"sunday-reflection.prompt": "كل يوم أحد في الساعة 21:00، استعرض معي 5 أسئلة للتأمل: أكثر شيء مرضٍ هذا الأسبوع، الأكثر إحباطًا، أهم 3 أولويات للأسبوع المقبل، ما تعلمته، ما يجب أن أتخلى عنه.",
|
||||
"sunday-reflection.title": "تأمل الأحد",
|
||||
"team-status-weekly.description": "كل يوم اثنين، إجازات الفريق، العمل الإضافي، اتجاهات عبء الاجتماعات — تحذير مبكر من الإرهاق.",
|
||||
"team-status-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بمراجعة الأسبوع الماضي للفريق: الإجازات، ساعات العمل الإضافي، عبء الاجتماعات. أبلغ عن أي شخص يتجه نحو الإرهاق.",
|
||||
"team-status-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع الأسبوع الماضي للفريق: الإجازات، ساعات العمل الإضافي، عبء الاجتماعات. حدد أي شخص يتجه نحو الإرهاق.",
|
||||
"team-status-weekly.title": "حالة الفريق الأسبوعية",
|
||||
"tech-trend-weekly.description": "كل يوم اثنين، لخص التحركات الرئيسية في الواجهة الأمامية / الخلفية / الذكاء الاصطناعي: الأوراق، الأطر، التمويل.",
|
||||
"tech-trend-weekly.instruction": "كل يوم اثنين في الساعة 08:00، قم بتلخيص الأسبوع الماضي من تحركات الواجهة الأمامية، الخلفية، والذكاء الاصطناعي: الأوراق البارزة، إصدارات الأطر، جولات التمويل. 10 عناصر مع خلاصة من سطر واحد.",
|
||||
"tech-trend-weekly.prompt": "كل يوم اثنين في الساعة 08:00، لخص الأسبوع الماضي من تحركات الواجهة الأمامية، الخلفية، والذكاء الاصطناعي: الأوراق البارزة، إصدارات الأطر، جولات التمويل. 10 عناصر مع ملخصات من سطر واحد.",
|
||||
"tech-trend-weekly.title": "اتجاهات التقنية الأسبوعية",
|
||||
"travel-inspiration-weekly.description": "كل يوم أربعاء، أسعار الرحلات إلى المدن المستهدفة، سياسة التأشيرات، أفضل نوافذ السفر.",
|
||||
"travel-inspiration-weekly.instruction": "كل يوم الأربعاء في الساعة 10:00، قدم لي تغييرات أسعار الرحلات، تحديثات سياسة التأشيرات، وأفضل نوافذ السفر للمدن في قائمة أمنياتي.",
|
||||
"travel-inspiration-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، أعطني تغييرات أسعار الرحلات، تحديثات سياسة التأشيرات، وأفضل نوافذ السفر للمدن في قائمة أمنياتي.",
|
||||
"travel-inspiration-weekly.title": "إلهام السفر الأسبوعي",
|
||||
"twitter-weekly-recap.description": "كل يوم اثنين، راجع الأسبوع الماضي على X: أفضل نمو، أسوأ تفاعل، ولماذا.",
|
||||
"twitter-weekly-recap.instruction": "كل يوم اثنين في الساعة 10:00، قم بتلخيص نشاطي على X (تويتر) من الأيام السبعة الماضية: التغريدات الأكثر نموًا، التغريدات الأقل تفاعلًا، وفرضية لكل منها. اقترح 3 زوايا لتجربتها هذا الأسبوع.",
|
||||
"twitter-weekly-recap.prompt": "كل يوم اثنين في الساعة 10:00، لخص نشاطي على X (تويتر) من الأيام السبعة الماضية: التغريدات الأكثر نموًا، التغريدات الأقل تفاعلًا، وفرضية لكل منها. اقترح 3 زوايا لتجربتها هذا الأسبوع.",
|
||||
"twitter-weekly-recap.title": "ملخص X (تويتر) الأسبوعي",
|
||||
"user-feedback-daily.description": "كل صباح، قم بتجميع التعليقات من جميع القنوات (المتاجر، الاجتماعية، الدعم) إلى أهم 20 عنصرًا، مرتبة حسب المشاعر والموضوع.",
|
||||
"user-feedback-daily.instruction": "كل صباح في الساعة 09:00، قم بتجميع ملاحظات المستخدم من جميع القنوات (متاجر التطبيقات، وسائل التواصل الاجتماعي، دعم العملاء) في أفضل 20 عنصرًا، مرتبة حسب المشاعر والموضوع.",
|
||||
"user-feedback-daily.prompt": "كل صباح في الساعة 09:00، قم بتجميع تعليقات المستخدمين من جميع القنوات (متاجر التطبيقات، وسائل التواصل الاجتماعي، دعم العملاء) إلى أهم 20 عنصرًا، مرتبة حسب المشاعر والموضوع.",
|
||||
"user-feedback-daily.title": "تعليقات المستخدمين اليومية",
|
||||
"user-interview-schedule.description": "كل يوم اثنين، استعرض مقابلات هذا الأسبوع: من، متى، هل الأسئلة جاهزة.",
|
||||
"user-interview-schedule.instruction": "كل يوم اثنين في الساعة 09:00، قم بإدراج مقابلات المستخدمين المجدولة لهذا الأسبوع: اسم المشارك، الوقت، قائمة التحقق التحضيرية (الأسئلة جاهزة، التسجيل مضبوط).",
|
||||
"user-interview-schedule.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج مقابلات المستخدمين المجدولة لهذا الأسبوع: اسم المشارك، الوقت، قائمة التحقق التحضيرية (الأسئلة جاهزة، الإعداد للتسجيل).",
|
||||
"user-interview-schedule.title": "تحضير مقابلات المستخدمين",
|
||||
"vercel-health-weekly.description": "كل يوم اثنين، راجع الأسبوع الماضي من النشر: معدل النجاح، مدة البناء، الشذوذ في حركة المرور.",
|
||||
"vercel-health-weekly.instruction": "كل يوم اثنين في الساعة 10:00، قم بتلخيص عمليات نشر Vercel الخاصة بي من الأسبوع الماضي: معدل النجاح، مدة البناء، الشذوذ في حركة المرور. أبلغ عن المشكلات المتراكمة.",
|
||||
"vercel-health-weekly.prompt": "كل يوم اثنين في الساعة 10:00، لخص عمليات النشر الخاصة بي على Vercel من الأسبوع الماضي: معدل النجاح، مدة البناء، الشذوذ في حركة المرور. حدد المشكلات المتراكمة.",
|
||||
"vercel-health-weekly.title": "صحة Vercel الأسبوعية",
|
||||
"viral-content-breakdown.description": "كل صباح، قم بتحليل قطعة واحدة فيروسية في مجالك — الزاوية، الخطاف، البنية، النهاية.",
|
||||
"viral-content-breakdown.instruction": "كل صباح في الساعة 10:00، اختر قطعة محتوى فيروسية واحدة من مجالي وقم بتفكيكها: الزاوية، الخطاف الافتتاحي، الهيكل، النهاية. قدم لي قالبًا يمكنني تطبيقه.",
|
||||
"viral-content-breakdown.prompt": "كل صباح في الساعة 10:00، اختر قطعة واحدة من المحتوى الفيروسي من مجالي وقم بتحليلها: الزاوية، الخطاف الافتتاحي، البنية، النهاية. أعطني قالبًا يمكنني تطبيقه.",
|
||||
"viral-content-breakdown.title": "تحليل المحتوى الفيروسي",
|
||||
"watchlist-friday.description": "كل يوم جمعة، 5 إصدارات جديدة ذات تصنيف عالٍ هذا الأسبوع (Douban / IMDb) مع مراجعات من سطر واحد.",
|
||||
"watchlist-friday.instruction": "كل يوم جمعة في الساعة 18:00، اختر 5 إصدارات جديدة ذات تصنيف عالي للأفلام/المسلسلات هذا الأسبوع من Douban وIMDb. أضف مراجعة من سطر واحد لكل منها.",
|
||||
"watchlist-friday.prompt": "كل يوم جمعة في الساعة 18:00، اختر 5 إصدارات جديدة ذات تصنيف عالٍ من الأفلام / المسلسلات هذا الأسبوع من Douban وIMDb. أضف مراجعة من سطر واحد لكل منها.",
|
||||
"watchlist-friday.title": "قائمة المشاهدة الجمعة",
|
||||
"weekly-meeting-brief.description": "كل يوم اثنين، قم بإعداد 3 نقاط نقاش لاجتماع الاستراتيجية الأسبوعي: الاتجاهات، الداخلية، القرارات.",
|
||||
"weekly-meeting-brief.instruction": "كل يوم اثنين في الساعة 08:30، قم بإعداد 3 نقاط نقاش لاجتماع الاستراتيجية لهذا الأسبوع: اتجاهات الصناعة، المقاييس الداخلية التي تستحق الإشارة، والقرارات التي يجب اتخاذها.",
|
||||
"weekly-meeting-brief.prompt": "كل يوم اثنين في الساعة 08:30، قم بإعداد 3 نقاط نقاش لاجتماع الاستراتيجية لهذا الأسبوع: اتجاهات الصناعة، المقاييس الداخلية التي تستحق الإشارة، والقرارات التي يجب اتخاذها.",
|
||||
"weekly-meeting-brief.title": "موجز الاجتماع الأسبوعي",
|
||||
"youtube-channel-weekly.description": "كل يوم اثنين، إحصائيات القناة: المشتركين، أفضل الفيديوهات، الاحتفاظ بالجمهور، الإيرادات.",
|
||||
"youtube-channel-weekly.instruction": "كل يوم اثنين في الساعة 09:00، قم بسحب إحصائيات قناتي على YouTube: تغير المشتركين، أفضل الفيديوهات أداءً، الاحتفاظ بالجمهور، حركة الإيرادات.",
|
||||
"youtube-channel-weekly.prompt": "كل يوم اثنين في الساعة 09:00، اسحب إحصائيات قناتي على YouTube: تغير المشتركين، أفضل الفيديوهات أداءً، الاحتفاظ بالجمهور، حركة الإيرادات.",
|
||||
"youtube-channel-weekly.title": "قناة YouTube الأسبوعية",
|
||||
"youtube-weekly-recap.description": "كل يوم اثنين، اسحب أداء القناة الأسبوع الماضي — المشاهدات، معدل النقر، الاحتفاظ — وحدد موضوعات المتابعة.",
|
||||
"youtube-weekly-recap.instruction": "كل يوم اثنين في الساعة 09:00، قم بسحب أداء قناتي على YouTube للأيام السبعة الماضية: المشاهدات، معدل النقر، منحنيات الاحتفاظ. أبرز أي فيديوهات تستحق متابعة.",
|
||||
"youtube-weekly-recap.prompt": "كل يوم اثنين في الساعة 09:00، اسحب أداء قناتي على YouTube للأيام السبعة الماضية: المشاهدات، معدل النقر، منحنيات الاحتفاظ. أبرز الفيديوهات التي تستحق متابعة.",
|
||||
"youtube-weekly-recap.title": "ملخص YouTube الأسبوعي",
|
||||
"zendesk-ticket-daily.description": "كل صباح، لقطة Zendesk: حجم التراكم، انتهاكات SLA، أهم المشكلات المتكررة.",
|
||||
"zendesk-ticket-daily.instruction": "كل صباح في الساعة 09:00، قدم لي لقطة Zendesk: تراكم التذاكر المفتوحة، انتهاكات SLA، وأهم 3 مشكلات متكررة من الـ 24 ساعة الماضية.",
|
||||
"zendesk-ticket-daily.prompt": "كل صباح في الساعة 09:00، أعطني لقطة Zendesk: تراكم التذاكر المفتوحة، انتهاكات SLA، وأهم 3 مشكلات متكررة من الـ 24 ساعة الماضية.",
|
||||
"zendesk-ticket-daily.title": "تذاكر Zendesk اليومية"
|
||||
}
|
||||
|
||||
+45
-45
@@ -56,51 +56,51 @@
|
||||
"dalle.generating": "جارٍ التوليد...",
|
||||
"dalle.images": "الصور:",
|
||||
"dalle.prompt": "الموجه",
|
||||
"lobe-agent.actions.add": "إضافة",
|
||||
"lobe-agent.actions.clearCompleted": "مسح المكتملة",
|
||||
"lobe-agent.actions.placeholder": "أدخل مهمة للقيام بها...",
|
||||
"lobe-agent.addTodo.placeholder": "أضف مهمة للقيام بها...",
|
||||
"lobe-agent.clearTodos.cleared": "{{count}} عنصر(عناصر) تم مسحها",
|
||||
"lobe-agent.clearTodos.clearedCompleted": "{{count}} عنصر(عناصر) مكتملة تم مسحها",
|
||||
"lobe-agent.clearTodos.clearedCompleted_one": "{{count}} عنصر مكتمل تم مسحه",
|
||||
"lobe-agent.clearTodos.clearedCompleted_other": "{{count}} عناصر مكتملة تم مسحها",
|
||||
"lobe-agent.clearTodos.cleared_one": "{{count}} عنصر تم مسحه",
|
||||
"lobe-agent.clearTodos.cleared_other": "{{count}} عناصر تم مسحها",
|
||||
"lobe-agent.clearTodos.header": "مسح عناصر المهام",
|
||||
"lobe-agent.clearTodos.label": "اختر ما تريد مسحه:",
|
||||
"lobe-agent.clearTodos.noItems": "لا توجد عناصر للمسح",
|
||||
"lobe-agent.clearTodos.option.all": "مسح جميع العناصر (بما في ذلك المعلقة)",
|
||||
"lobe-agent.clearTodos.option.completed": "مسح العناصر المكتملة فقط",
|
||||
"lobe-agent.clearTodos.remaining": "{{count}} عنصر(عناصر) متبقية",
|
||||
"lobe-agent.clearTodos.remaining_one": "{{count}} عنصر متبقي",
|
||||
"lobe-agent.clearTodos.remaining_other": "{{count}} عناصر متبقية",
|
||||
"lobe-agent.completeTodos.completed": "{{count}} عنصر(عناصر) مكتملة",
|
||||
"lobe-agent.completeTodos.completed_one": "{{count}} عنصر مكتمل",
|
||||
"lobe-agent.completeTodos.completed_other": "{{count}} عناصر مكتملة",
|
||||
"lobe-agent.createPlan.context.label": "السياق (اختياري)",
|
||||
"lobe-agent.createPlan.context.placeholder": "الخلفية، القيود، الاعتبارات...",
|
||||
"lobe-agent.createPlan.description.label": "الوصف",
|
||||
"lobe-agent.createPlan.description.placeholder": "ملخص موجز للخطة",
|
||||
"lobe-agent.createPlan.goal.label": "الهدف",
|
||||
"lobe-agent.createPlan.goal.placeholder": "ما الذي تريد تحقيقه؟",
|
||||
"lobe-agent.createTodos.created": "{{count}} عنصر(عناصر) للقيام بها تم إنشاؤها",
|
||||
"lobe-agent.createTodos.created_one": "{{count}} عنصر للقيام به تم إنشاؤه",
|
||||
"lobe-agent.createTodos.created_other": "{{count}} عناصر للقيام بها تم إنشاؤها",
|
||||
"lobe-agent.createTodos.total": "الإجمالي: {{count}} عنصر(عناصر)",
|
||||
"lobe-agent.createTodos.total_one": "الإجمالي: {{count}} عنصر",
|
||||
"lobe-agent.createTodos.total_other": "الإجمالي: {{count}} عناصر",
|
||||
"lobe-agent.removeTodos.removed": "{{count}} عنصر(عناصر) تم إزالتها",
|
||||
"lobe-agent.removeTodos.removed_one": "{{count}} عنصر تم إزالته",
|
||||
"lobe-agent.removeTodos.removed_other": "{{count}} عناصر تم إزالتها",
|
||||
"lobe-agent.status.done": "{{count}} مكتملة",
|
||||
"lobe-agent.status.pending": "{{count}} معلقة",
|
||||
"lobe-agent.todoItem.placeholder": "أدخل مهمة للقيام بها...",
|
||||
"lobe-agent.todoList.empty": "قائمة المهام فارغة",
|
||||
"lobe-agent.todoList.items": "{{count}} عنصر(عناصر)",
|
||||
"lobe-agent.todoList.items_one": "{{count}} عنصر",
|
||||
"lobe-agent.todoList.items_other": "{{count}} عناصر",
|
||||
"lobe-agent.todoList.title": "قائمة المهام",
|
||||
"lobe-agent.updateTodos.updated": "تم تحديث قائمة المهام",
|
||||
"lobe-gtd.actions.add": "إضافة",
|
||||
"lobe-gtd.actions.clearCompleted": "مسح المكتملة",
|
||||
"lobe-gtd.actions.placeholder": "أدخل مهمة...",
|
||||
"lobe-gtd.addTodo.placeholder": "أضف مهمة...",
|
||||
"lobe-gtd.clearTodos.cleared": "تم مسح {{count}} عنصر(عناصر)",
|
||||
"lobe-gtd.clearTodos.clearedCompleted": "تم مسح {{count}} عنصر(عناصر) مكتملة",
|
||||
"lobe-gtd.clearTodos.clearedCompleted_one": "تم مسح عنصر مكتمل واحد",
|
||||
"lobe-gtd.clearTodos.clearedCompleted_other": "تم مسح {{count}} عناصر مكتملة",
|
||||
"lobe-gtd.clearTodos.cleared_one": "تم مسح عنصر واحد",
|
||||
"lobe-gtd.clearTodos.cleared_other": "تم مسح {{count}} عناصر",
|
||||
"lobe-gtd.clearTodos.header": "مسح المهام",
|
||||
"lobe-gtd.clearTodos.label": "اختر ما تريد مسحه:",
|
||||
"lobe-gtd.clearTodos.noItems": "لا توجد عناصر للمسح",
|
||||
"lobe-gtd.clearTodos.option.all": "مسح جميع العناصر (بما في ذلك المعلقة)",
|
||||
"lobe-gtd.clearTodos.option.completed": "مسح العناصر المكتملة فقط",
|
||||
"lobe-gtd.clearTodos.remaining": "{{count}} عنصر(عناصر) متبقية",
|
||||
"lobe-gtd.clearTodos.remaining_one": "عنصر واحد متبقٍ",
|
||||
"lobe-gtd.clearTodos.remaining_other": "{{count}} عناصر متبقية",
|
||||
"lobe-gtd.completeTodos.completed": "تم إكمال {{count}} عنصر(عناصر)",
|
||||
"lobe-gtd.completeTodos.completed_one": "تم إكمال عنصر واحد",
|
||||
"lobe-gtd.completeTodos.completed_other": "تم إكمال {{count}} عناصر",
|
||||
"lobe-gtd.createPlan.context.label": "السياق (اختياري)",
|
||||
"lobe-gtd.createPlan.context.placeholder": "الخلفية، القيود، الاعتبارات...",
|
||||
"lobe-gtd.createPlan.description.label": "الوصف",
|
||||
"lobe-gtd.createPlan.description.placeholder": "ملخص مختصر للخطة",
|
||||
"lobe-gtd.createPlan.goal.label": "الهدف",
|
||||
"lobe-gtd.createPlan.goal.placeholder": "ما الذي تريد تحقيقه؟",
|
||||
"lobe-gtd.createTodos.created": "تم إنشاء {{count}} مهمة",
|
||||
"lobe-gtd.createTodos.created_one": "تم إنشاء مهمة واحدة",
|
||||
"lobe-gtd.createTodos.created_other": "تم إنشاء {{count}} مهام",
|
||||
"lobe-gtd.createTodos.total": "الإجمالي: {{count}} عنصر(عناصر)",
|
||||
"lobe-gtd.createTodos.total_one": "الإجمالي: عنصر واحد",
|
||||
"lobe-gtd.createTodos.total_other": "الإجمالي: {{count}} عناصر",
|
||||
"lobe-gtd.removeTodos.removed": "تمت إزالة {{count}} عنصر(عناصر)",
|
||||
"lobe-gtd.removeTodos.removed_one": "تمت إزالة عنصر واحد",
|
||||
"lobe-gtd.removeTodos.removed_other": "تمت إزالة {{count}} عناصر",
|
||||
"lobe-gtd.status.done": "{{count}} مكتملة",
|
||||
"lobe-gtd.status.pending": "{{count}} معلقة",
|
||||
"lobe-gtd.todoItem.placeholder": "أدخل مهمة...",
|
||||
"lobe-gtd.todoList.empty": "قائمة المهام فارغة",
|
||||
"lobe-gtd.todoList.items": "{{count}} عنصر(عناصر)",
|
||||
"lobe-gtd.todoList.items_one": "عنصر واحد",
|
||||
"lobe-gtd.todoList.items_other": "{{count}} عناصر",
|
||||
"lobe-gtd.todoList.title": "قائمة المهام",
|
||||
"lobe-gtd.updateTodos.updated": "تم تحديث قائمة المهام",
|
||||
"lobe-knowledge-base.readKnowledge.meta.chars": "عدد الأحرف",
|
||||
"lobe-knowledge-base.readKnowledge.meta.lines": "عدد الأسطر",
|
||||
"localFiles.editFile.newString": "استبدال بـ",
|
||||
|
||||
@@ -115,10 +115,6 @@
|
||||
"channel.line.fetchBotInfoMissingToken": "Първо въведете токена за достъп до канала, след това кликнете \"Извличане от LINE\".",
|
||||
"channel.line.fetchBotInfoSuccess": "Потребителското ID на дестинацията е извлечено",
|
||||
"channel.line.webhookManualSetup": "LINE не позволява програмно регистриране на уебхукове. Копирайте този URL в Конзолата за разработчици на LINE (Messaging API → Webhook URL), кликнете \"Проверка\" и активирайте \"Използване на уебхук\".",
|
||||
"channel.messengerPromo.action": "Опитайте Messenger",
|
||||
"channel.messengerPromo.desc": "Без настройка на бот. Чатете с LobeHub в Slack, Discord, Telegram.",
|
||||
"channel.messengerPromo.dismiss": "Отхвърли",
|
||||
"channel.messengerPromo.title": "Пропуснете настройката",
|
||||
"channel.openPlatform": "Отворена платформа",
|
||||
"channel.platforms": "Платформи",
|
||||
"channel.publicKey": "Публичен ключ",
|
||||
|
||||
+3
-23
@@ -184,10 +184,6 @@
|
||||
"groupWizard.searchTemplates": "Търсене на шаблони...",
|
||||
"groupWizard.title": "Създай група",
|
||||
"groupWizard.useTemplate": "Използвай шаблон",
|
||||
"heteroAgent.cloudRepo.multiSelected": "{{count}} хранилища избрани",
|
||||
"heteroAgent.cloudRepo.noRepos": "Няма конфигурирани хранилища. Добавете ги в настройките на агента.",
|
||||
"heteroAgent.cloudRepo.notSet": "Няма избрано хранилище",
|
||||
"heteroAgent.cloudRepo.sectionTitle": "Хранилища",
|
||||
"heteroAgent.fullAccess.label": "Пълен достъп",
|
||||
"heteroAgent.fullAccess.tooltip": "Claude Code работи локално с пълен достъп за четене/запис в работната директория. Превключването на режимите на достъп все още не е налично.",
|
||||
"heteroAgent.resumeReset.cwdChanged": "Работната директория е променена. Предишната сесия на Claude Code може да бъде продължена само от оригиналната ѝ директория, затова е започнат нов разговор.",
|
||||
@@ -314,7 +310,7 @@
|
||||
"openInNewWindow": "Отвори в нов прозорец",
|
||||
"operation.contextCompression": "Контекстът е твърде дълъг, компресиране на историята...",
|
||||
"operation.execAgentRuntime": "Подготвяне на отговор",
|
||||
"operation.execClientSubAgent": "Изпълнение на под-агент",
|
||||
"operation.execClientTask": "Изпълнение на задача",
|
||||
"operation.execHeterogeneousAgent": "{{name}} работи",
|
||||
"operation.execServerAgentRuntime": "Изпълнява се… Можете да превключите задачи или да затворите страницата — задачата ще продължи.",
|
||||
"operation.heterogeneousAgentFallback": "Външен агент",
|
||||
@@ -567,12 +563,8 @@
|
||||
"taskList.contextMenu.copyLink": "Копирай линк",
|
||||
"taskList.contextMenu.copyLinkSuccess": "Линкът е копиран",
|
||||
"taskList.contextMenu.priority": "Приоритет",
|
||||
"taskList.contextMenu.runNow": "Изпълни сега",
|
||||
"taskList.contextMenu.status": "Статус",
|
||||
"taskList.empty": "Все още няма задачи",
|
||||
"taskList.emptyHero.greeting": "С какво да се заемем днес?",
|
||||
"taskList.emptyHero.subtitle": "Опишете задача за вашия агент или започнете с шаблон отдолу.",
|
||||
"taskList.emptyHero.templatesTitle": "Шаблони, избрани за вас",
|
||||
"taskList.form.grouping": "Групиране",
|
||||
"taskList.form.orderCompletedByRecency": "Подреди завършените задачи по актуалност",
|
||||
"taskList.form.ordering": "Подреждане",
|
||||
@@ -633,10 +625,8 @@
|
||||
"taskSchedule.summary.daily": "Всеки ден в {{time}}",
|
||||
"taskSchedule.summary.disabled": "Автоматизацията е изключена",
|
||||
"taskSchedule.summary.everyNHours": "На всеки {{count}} часа{{minute}}",
|
||||
"taskSchedule.summary.everyNHoursHalfPast": "Всеки {{count}} часа в половина",
|
||||
"taskSchedule.summary.heartbeat": "Изпълнява се на всеки {{interval}}",
|
||||
"taskSchedule.summary.hourly": "Всеки час{{minute}}",
|
||||
"taskSchedule.summary.hourlyHalfPast": "Всеки час в половина",
|
||||
"taskSchedule.summary.weekly": "Всяка седмица в {{days}} от {{time}}",
|
||||
"taskSchedule.tag.add": "Задай график",
|
||||
"taskSchedule.tag.every": "всеки {{interval}}",
|
||||
@@ -644,8 +634,6 @@
|
||||
"taskSchedule.tag.schedule": "График · {{schedule}}{{timezone}}",
|
||||
"taskSchedule.time": "Час",
|
||||
"taskSchedule.timezone": "Часова зона",
|
||||
"taskSchedule.timezoneSearchEmpty": "Няма съвпадаща часова зона",
|
||||
"taskSchedule.timezoneSearchPlaceholder": "Търсене на часова зона",
|
||||
"taskSchedule.title": "График",
|
||||
"taskSchedule.unit.hour_one": "{{count}} час",
|
||||
"taskSchedule.unit.hour_other": "{{count}} часа",
|
||||
@@ -665,7 +653,6 @@
|
||||
"thread.divider": "Подтема",
|
||||
"thread.openSubagentThread": "Преглед на пълния разговор със субагента",
|
||||
"thread.subagentBadge": "Субагент",
|
||||
"thread.subagentReadOnlyHint": "Разговорите със SubAgent са само за четене — изпълнението се управлява от основния агент.",
|
||||
"thread.threadMessageCount": "{{messageCount}} съобщения",
|
||||
"thread.title": "Подтема",
|
||||
"todoProgress.allCompleted": "Всички задачи са изпълнени",
|
||||
@@ -772,8 +759,6 @@
|
||||
"workflow.toolDisplayName.addPreferenceMemory": "Запазена памет",
|
||||
"workflow.toolDisplayName.calculate": "Изчислено",
|
||||
"workflow.toolDisplayName.callAgent": "Извикан агент",
|
||||
"workflow.toolDisplayName.callSubAgent": "Изпратен под-агент",
|
||||
"workflow.toolDisplayName.callSubAgents": "Изпратени под-агенти",
|
||||
"workflow.toolDisplayName.clearTodos": "Изчистени задачи",
|
||||
"workflow.toolDisplayName.copyDocument": "Копиран документ",
|
||||
"workflow.toolDisplayName.crawlMultiPages": "Обходени страници",
|
||||
@@ -788,6 +773,8 @@
|
||||
"workflow.toolDisplayName.editTitle": "Редактирано заглавие",
|
||||
"workflow.toolDisplayName.evaluate": "Изчислен израз",
|
||||
"workflow.toolDisplayName.execScript": "Изпълнен скрипт",
|
||||
"workflow.toolDisplayName.execTask": "Изпълнена задача",
|
||||
"workflow.toolDisplayName.execTasks": "Изпълнени задачи",
|
||||
"workflow.toolDisplayName.execute": "Изпълнено изчисление",
|
||||
"workflow.toolDisplayName.executeCode": "Изпълнен код",
|
||||
"workflow.toolDisplayName.finishOnboarding": "Завършване на въвеждането",
|
||||
@@ -892,13 +879,6 @@
|
||||
"workingPanel.review.mode.unstaged": "Неинсценирано",
|
||||
"workingPanel.review.more": "Още опции",
|
||||
"workingPanel.review.refresh": "Обнови",
|
||||
"workingPanel.review.revert": "Отхвърли промените",
|
||||
"workingPanel.review.revert.confirm.cancel": "Отказ",
|
||||
"workingPanel.review.revert.confirm.description": "Промените в работното дърво за {{filePath}} ще бъдат изтрити окончателно. Неследените файлове ще бъдат изтрити от диска.",
|
||||
"workingPanel.review.revert.confirm.ok": "Отхвърли",
|
||||
"workingPanel.review.revert.confirm.title": "Отхвърляне на промените в този файл?",
|
||||
"workingPanel.review.revert.failed": "Неуспешно отхвърляне на промените: {{error}}",
|
||||
"workingPanel.review.revert.success": "Промените в {{filePath}} бяха отхвърлени",
|
||||
"workingPanel.review.textDiff.disable": "Деактивирай вградени текстови разлики",
|
||||
"workingPanel.review.textDiff.enable": "Активирай вградени текстови разлики",
|
||||
"workingPanel.review.title": "Преглед",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"batchDelete": "Групово изтриване",
|
||||
"blog": "Блог на продукта",
|
||||
"botIntegrationBanner.dismiss": "Затвори",
|
||||
"botIntegrationBanner.title": "Говорете с Lobe AI в любимите си приложения за съобщения",
|
||||
"botIntegrationBanner.title": "Добавяне на канали към LobeAI",
|
||||
"branching": "Създай подтема",
|
||||
"branchingDisable": "Функцията „Подтема“ не е налична в текущия режим. За да я използвате, превключете към режим Postgres/Pglite DB или използвайте LobeHub Cloud.",
|
||||
"branchingRequiresSavedTopic": "Текущата тема не е запазена, моля, запазете я, за да използвате функцията за подтема",
|
||||
@@ -349,8 +349,6 @@
|
||||
"loading": "Зареждане...",
|
||||
"mail.business": "Бизнес сътрудничество",
|
||||
"mail.support": "Имейл поддръжка",
|
||||
"messengerBanner.dismiss": "Затвори",
|
||||
"messengerBanner.title": "Говорете с Lobe AI в любимите си приложения за съобщения",
|
||||
"more": "Още",
|
||||
"navPanel.agent": "Агент",
|
||||
"navPanel.customizeSidebar": "Персонализиране на страничната лента",
|
||||
|
||||
@@ -40,18 +40,6 @@
|
||||
"modifier.acceptAll": "Запази всички",
|
||||
"modifier.reject": "Отмени",
|
||||
"modifier.rejectAll": "Отмени всички",
|
||||
"skillFrontmatter.edit": "Редактиране на метаданни",
|
||||
"skillFrontmatter.empty": "Няма метаданни",
|
||||
"skillFrontmatter.invalid.descriptionInvalid": "Описанието трябва да бъде текст на един ред.",
|
||||
"skillFrontmatter.invalid.descriptionRequired": "Описанието е задължително.",
|
||||
"skillFrontmatter.invalid.mapping": "Frontmatter трябва да бъде YAML mapping.",
|
||||
"skillFrontmatter.invalid.nameInvalid": "Името трябва да използва малки букви, цифри и тирета.",
|
||||
"skillFrontmatter.invalid.nameLocked": "Името трябва да остане {{name}}. Преименувайте пакета с умения вместо това.",
|
||||
"skillFrontmatter.invalid.nameRequired": "Името е задължително.",
|
||||
"skillFrontmatter.invalid.required": "Frontmatter е задължително.",
|
||||
"skillFrontmatter.invalid.syntax": "Невалиден YAML синтаксис.",
|
||||
"skillFrontmatter.saveFailed": "Метаданните не бяха запазени. Опитайте отново или продължете с редактирането.",
|
||||
"skillFrontmatter.title": "Метаданни на умение",
|
||||
"slash.compact": "Компресирай контекста",
|
||||
"slash.h1": "Заглавие 1",
|
||||
"slash.h2": "Заглавие 2",
|
||||
|
||||
@@ -26,11 +26,6 @@
|
||||
"brief.viewRun": "Преглед на изпълнението",
|
||||
"project.create": "Нов проект",
|
||||
"project.deleteConfirm": "Този проект ще бъде изтрит и не може да бъде възстановен. Потвърдете, за да продължите.",
|
||||
"recommendations.heteroAgent.cta": "Добавяне на агент",
|
||||
"recommendations.heteroAgent.description": "Открит е CLI на {{name}} на това устройство — добавете агент {{name}}, за да чатите с него от LobeHub.",
|
||||
"recommendations.heteroAgent.tag": "Агент за кодиране",
|
||||
"recommendations.heteroAgent.title": "Добавяне на агент {{name}}",
|
||||
"recommendations.subtitle": "Някои препоръки за вашата настройка",
|
||||
"starter.createAgent": "Създай агент",
|
||||
"starter.createGroup": "Създай група",
|
||||
"starter.deepResearch": "Задълбочено проучване",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"messenger.linkModal.notConfigured": "Тази връзка не е налична в момента. Моля, опитайте отново по-късно.",
|
||||
"messenger.linkModal.openCta": "Отворете в {{platform}}",
|
||||
"messenger.linkModal.scanHint": "Или сканирайте с телефона си, за да отворите {{platform}}.",
|
||||
"messenger.linkModal.title": "Свързване на Messenger",
|
||||
"messenger.list.discord.description": "Чатете с вашите агенти на LobeHub от всеки Discord сървър чрез лично съобщение с бота LobeHub.",
|
||||
"messenger.list.slack.description": "Чатете с вашите агенти на LobeHub от всяко работно пространство в Slack чрез лично съобщение или @LobeHub.",
|
||||
"messenger.list.telegram.description": "Чатете с вашите агенти на LobeHub в Telegram и изберете кой да отговаря от всяко място.",
|
||||
@@ -88,8 +89,6 @@
|
||||
"verify.confirm.relink.title": "Друг Telegram акаунт вече е свързан",
|
||||
"verify.confirm.title": "Потвърдете свързването",
|
||||
"verify.confirm.workspace": "Работно пространство: {{workspace}}",
|
||||
"verify.error.alreadyConsumed": "Този линк вече е използван за свързване на акаунт. Влезте в този LobeHub акаунт, за да управлявате връзката, или се върнете към бота и изпратете /start отново, за да издадете нов линк.",
|
||||
"verify.error.alreadyConsumedTitle": "Този линк вече е използван",
|
||||
"verify.error.alreadyLinkedToOther": "Този акаунт вече е свързан с друг акаунт в LobeHub. Първо влезте в този акаунт.",
|
||||
"verify.error.expired": "Тази връзка е изтекла. Моля, върнете се към бота и изпратете /start отново.",
|
||||
"verify.error.generic": "Нещо се обърка. Моля, опитайте отново.",
|
||||
|
||||
@@ -227,7 +227,6 @@
|
||||
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "За серията GPT-5.2 Pro; контролира интензивността на разсъждението.",
|
||||
"providerModels.item.modelConfig.extendParams.options.gpt5_2ReasoningEffort.hint": "За серията GPT-5.2; контролира интензивността на разсъждението.",
|
||||
"providerModels.item.modelConfig.extendParams.options.grok4_20ReasoningEffort.hint": "За серията Grok 4.20; контролира интензивността на разсъжденията. Ниско/Средно използва 4 агента, Високо/Много високо използва 16 агента.",
|
||||
"providerModels.item.modelConfig.extendParams.options.grok4_3ReasoningEffort.hint": "За серията Grok 4.3; контролира интензивността на разсъжденията.",
|
||||
"providerModels.item.modelConfig.extendParams.options.hy3ReasoningEffort.hint": "За моделите Hy3; контролира интензивността на разсъждението. no_think (свръхбърз отговор), low (бързо разсъждение) и high (дълбоко разсъждение) — за да покрие различни изисквания за латентност и дълбочина, от високочестотни взаимодействия до сложни инженерни задачи.",
|
||||
"providerModels.item.modelConfig.extendParams.options.imageAspectRatio.hint": "За моделите за генериране на изображения Gemini; контролира съотношението на страните на генерираните изображения.",
|
||||
"providerModels.item.modelConfig.extendParams.options.imageAspectRatio2.hint": "За Nano Banana 2; контролира съотношението на страните на генерираните изображения (поддържа изключително широки 1:4, 4:1, 1:8, 8:1).",
|
||||
|
||||
+41
-21
@@ -106,6 +106,7 @@
|
||||
"MiniMax-Hailuo-2.3.description": "Чисто нов модел за видео генериране с цялостни подобрения в движенията на тялото, физическата реалистичност и следването на инструкции.",
|
||||
"MiniMax-M1.description": "Нов вътрешен модел за разсъждение с 80K верига на мисълта и 1M вход, предлагащ производителност, сравнима с водещите глобални модели.",
|
||||
"MiniMax-M2-Stable.description": "Създаден за ефективно програмиране и агентски работни потоци, с по-висока едновременност за търговска употреба.",
|
||||
"MiniMax-M2.1-Lightning.description": "Мощни многоезични програмни възможности с по-бързо и ефективно извеждане.",
|
||||
"MiniMax-M2.1-highspeed.description": "Мощни многоезични програмни възможности, цялостно подобрено програмиране. По-бързо и по-ефективно.",
|
||||
"MiniMax-M2.1.description": "MiniMax-M2.1 е водеща отворена голяма езикова система от MiniMax, фокусирана върху решаването на сложни реални задачи. Основните ѝ предимства са възможностите за програмиране на множество езици и способността да действа като агент за решаване на сложни задачи.",
|
||||
"MiniMax-M2.5-highspeed.description": "MiniMax M2.5 Highspeed: Същата производителност като M2.5, но с по-бързо извеждане.",
|
||||
@@ -114,7 +115,9 @@
|
||||
"MiniMax-M2.7.description": "Първият самоеволюиращ се модел с висок клас производителност при програмиране и агентни задачи (~60 tps).",
|
||||
"MiniMax-M2.description": "MiniMax M2: Модел от предишно поколение.",
|
||||
"MiniMax-Text-01.description": "MiniMax-01 въвежда мащабно линейно внимание отвъд класическите трансформери, с 456B параметри и 45.9B активирани на преминаване. Постига водеща производителност и поддържа до 4M токена контекст (32× GPT-4o, 20× Claude-3.5-Sonnet).",
|
||||
"MiniMaxAI/MiniMax-M1-80k.description": "MiniMax-M1 е модел за хибридно внимание с отворени тегла, съдържащ 456 милиарда общи параметри и ~45.9 милиарда активни на токен. Той поддържа контекст от 1 милион токена и използва Flash Attention за намаляване на FLOPs с 75% при генериране на 100K токена спрямо DeepSeek R1. С архитектура MoE плюс CISPO и обучение с хибридно внимание RL, той постига водещи резултати в задачи за дългосрочно разсъждение и реално софтуерно инженерство.",
|
||||
"MiniMaxAI/MiniMax-M2.5.description": "MiniMax-M2.5 е най-новият голям езиков модел, разработен от MiniMax, обучен чрез мащабно подсилващо обучение в стотици хиляди сложни, реални среди. С архитектура MoE и 229 милиарда параметри, той постига водещи в индустрията резултати в задачи като програмиране, използване на инструменти от агенти, търсене и офис сценарии.",
|
||||
"MiniMaxAI/MiniMax-M2.description": "MiniMax-M2 преосмисля ефективността на агентите. Това е компактен, бърз и икономичен модел MoE с 230 милиарда общи и 10 милиарда активни параметри, създаден за водещи задачи по програмиране и агенти, като същевременно запазва силен общ интелект. Със само 10 милиарда активни параметри, той съперничи на много по-големи модели, което го прави идеален за приложения с висока ефективност.",
|
||||
"Moonshot-Kimi-K2-Instruct.description": "1T общи параметри с 32B активни. Сред немислещите модели е водещ в гранични знания, математика и програмиране, и по-силен в общи агентски задачи. Оптимизиран за агентски натоварвания, може да предприема действия, а не само да отговаря на въпроси. Най-подходящ за импровизационен, общ чат и агентски преживявания като модел на рефлексно ниво без дълго мислене.",
|
||||
"NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO.description": "Nous Hermes 2 - Mixtral 8x7B-DPO (46.7B) е високоточен модел с инструкции за сложни изчисления.",
|
||||
"OmniConsistency.description": "OmniConsistency подобрява стиловата последователност и обобщението при задачи от изображение към изображение чрез въвеждане на мащабни дифузионни трансформери (DiTs) и сдвоени стилизирани данни, избягвайки влошаване на стила.",
|
||||
@@ -129,6 +132,7 @@
|
||||
"Phi-3.5-vision-instrust.description": "Актуализирана версия на модела Phi-3-vision.",
|
||||
"Pro/MiniMaxAI/MiniMax-M2.5.description": "MiniMax-M2.5 е най-новият голям езиков модел, разработен от MiniMax, обучен чрез мащабно обучение с подсилване в стотици хиляди сложни, реални среди. С архитектура MoE и 229 милиарда параметри, той постига водещи резултати в задачи като програмиране, използване на инструменти от агенти, търсене и офис сценарии.",
|
||||
"Pro/Qwen/Qwen2.5-7B-Instruct.description": "Qwen2.5-7B-Instruct е част от най-новата серия LLM на Alibaba Cloud. Моделът с 7B параметри носи значителни подобрения в програмирането и математиката, поддържа над 29 езика и подобрява следването на инструкции, разбирането на структурирани данни и генерирането на структурирани изходи (особено JSON).",
|
||||
"Pro/THUDM/GLM-4.1V-9B-Thinking.description": "GLM-4.1V-9B-Thinking е отворен VLM модел, разработен от Zhipu AI и лабораторията KEG на университета Цинхуа, създаден за сложна мултимодална когниция. Базиран на GLM-4-9B-0414, той добавя верижно разсъждение (chain-of-thought) и обучение чрез подсилване (RL), което значително подобрява между-модалното разсъждение и стабилността.",
|
||||
"Pro/deepseek-ai/DeepSeek-R1.description": "DeepSeek-R1 е модел за разсъждение, базиран на обучение чрез подсилване (RL), който намалява повторенията и подобрява четимостта. Използва cold-start данни преди RL, за да засили разсъждението, съпоставя се с OpenAI-o1 при задачи по математика, код и логика и подобрява общите резултати чрез внимателно обучение.",
|
||||
"Pro/deepseek-ai/DeepSeek-V3.1-Terminus.description": "DeepSeek-V3.1-Terminus е обновен модел от серията V3.1, позициониран като хибриден агентен LLM. Отстранява докладвани от потребители проблеми и подобрява стабилността, езиковата последователност и намалява смесването на китайски/английски и аномални символи. Интегрира режими с и без разсъждение с шаблони за чат за гъвкаво превключване. Подобрява и производителността на Code Agent и Search Agent за по-надеждно използване на инструменти и многoетапни задачи.",
|
||||
"Pro/deepseek-ai/DeepSeek-V3.2.description": "DeepSeek-V3.2 е модел, който съчетава висока изчислителна ефективност с отлично разсъждение и производителност като агент. Подходът му се основава на три ключови технологични пробива: DeepSeek Sparse Attention (DSA), ефективен механизъм за внимание, който значително намалява изчислителната сложност, като същевременно поддържа производителността на модела и е специално оптимизиран за сценарии с дълъг контекст; мащабируема рамка за подсилващо обучение, чрез която производителността на модела може да съперничи на GPT-5, а версията с висока изчислителна мощност съответства на Gemini-3.0-Pro по способности за разсъждение; и мащабна тръбопроводна система за синтез на задачи за агенти, насочена към интегриране на способности за разсъждение в сценарии за използване на инструменти, като по този начин подобрява следването на инструкции и обобщаването в сложни интерактивни среди. Моделът постигна златен медал на Международната математическа олимпиада (IMO) и Международната олимпиада по информатика (IOI) през 2025 г.",
|
||||
@@ -136,12 +140,13 @@
|
||||
"Pro/moonshotai/Kimi-K2-Instruct-0905.description": "Kimi K2-Instruct-0905 е най-новият и най-мощен модел от серията Kimi K2. Това е MoE модел от най-висок клас с 1T общо и 32B активни параметъра. Основните му предимства включват по-силна агентна интелигентност при програмиране с значителни подобрения в бенчмаркове и реални задачи, както и подобрена естетика и използваемост на фронтенд кода.",
|
||||
"Pro/moonshotai/Kimi-K2-Thinking.description": "Kimi K2 Thinking Turbo е ускорен вариант, оптимизиран за скорост на разсъждение и пропускателна способност, като запазва многoетапното разсъждение и използване на инструменти от K2 Thinking. Това е MoE модел с ~1T общи параметри, роден 256K контекст и стабилно мащабируемо извикване на инструменти за производствени сценарии с по-строги изисквания за латентност и едновременност.",
|
||||
"Pro/moonshotai/Kimi-K2.5.description": "Kimi K2.5 е отворен мултимодален агентен модел, базиран на Kimi-K2-Base, обучен върху приблизително 1.5 трилиона смесени визуални и текстови токени. Моделът използва MoE архитектура с общо 1T параметри и 32B активни параметри, поддържа контекстен прозорец от 256K и безпроблемно интегрира визуално и езиково разбиране.",
|
||||
"Pro/moonshotai/Kimi-K2.6.description": "Kimi K2.6 е отворен модел на Moonshot AI за мултимодални агенти. Изграден върху архитектура MoE с 1T общи параметри и 32B активирани, поддържа контекст от 256K токена. Поддържа над 4,000 инструментални извиквания с автономно изпълнение за над 12 часа, сътрудничество между множество агенти с до 300 паралелни под-агенти и режими на мислене и моментално извеждане.",
|
||||
"Pro/zai-org/GLM-4.7.description": "GLM-4.7 е новото поколение флагмански модел на Zhipu с 355B общи параметри и 32B активни параметри, напълно обновен за общи диалози, разсъждения и агентни възможности. GLM-4.7 подобрява преплетеното мислене и въвежда запазено мислене и мислене на ниво завой.",
|
||||
"Pro/moonshotai/Kimi-K2.6.description": "Kimi K2.6 е отворен мултимодален агентен модел от Moonshot AI, който постига водещи резултати на множество основни бенчмаркове, включително HLE (с инструменти), SWE-Bench Pro и BrowseComp. Моделът използва архитектура MoE с общо 1T параметри и 32B активни параметри, поддържа контекстен прозорец от 256K токена и интегрира естествени мултимодални възможности.",
|
||||
"Pro/zai-org/GLM-5.1.description": "GLM-5.1 е следващо поколение флагмански модел, създаден за агентно инженерство, използващ архитектура Mixture of Experts (MoE) с 754B параметъра. Значително подобрява програмните способности, постигайки водещи резултати на SWE-Bench Pro, и превъзхожда предшественика си на NL2Repo и Terminal-Bench 2.0. Създаден за дълги агентни процеси, обработва неясни въпроси с по-добра преценка, разбива сложни задачи, изпълнява експерименти, анализира резултати и оптимизира решенията чрез стотици итерации и хиляди извиквания на инструменти.",
|
||||
"Pro/zai-org/glm-4.7.description": "GLM-4.7 е новото поколение водещ модел на Zhipu с 355 милиарда общи параметри и 32 милиарда активни параметри, напълно обновен за общ диалог, разсъждения и агентни способности. GLM-4.7 подобрява преплетеното мислене и въвежда запазено мислене и мислене на ниво завой.",
|
||||
"Pro/zai-org/glm-5.1.description": "GLM-5.1 е следващото поколение флагмански агентен модел на Zhipu за интелигентно инженерство. Той използва архитектура Mixture-of-Experts с 754B параметри, включваща естествено извикване на инструменти, завършване на префикси, поддръжка на FIM и контекстен прозорец от 200K за дългосрочни работни потоци.",
|
||||
"Pro/zai-org/glm-5.description": "GLM-5 е следващото поколение голям езиков модел на Zhipu, фокусиран върху сложното системно инженерство и задачи на агенти с дълга продължителност. Параметрите на модела са разширени до 744 милиарда (40 милиарда активни) и интегрират DeepSeek Sparse Attention.",
|
||||
"QwQ-32B-Preview.description": "Qwen QwQ е експериментален изследователски модел, фокусиран върху подобряване на разсъждението.",
|
||||
"Qwen/QVQ-72B-Preview.description": "QVQ-72B-Preview е изследователски модел от Qwen, фокусиран върху визуално разсъждение, със силни страни в разбирането на сложни сцени и визуални математически задачи.",
|
||||
"Qwen/QwQ-32B-Preview.description": "Qwen QwQ е експериментален изследователски модел, фокусиран върху подобрено AI разсъждение.",
|
||||
"Qwen/Qwen-Image-Edit-2509.description": "Qwen-Image-Edit-2509 е най-новата версия за редактиране на изображения от екипа на Qwen. Базиран на 20B модела Qwen-Image, той разширява силното текстово рендиране към редактиране на изображения за прецизни текстови промени. Използва двуканална архитектура – входовете се подават към Qwen2.5-VL за семантичен контрол и към VAE енкодер за контрол на външния вид, което позволява редакции както на семантично, така и на визуално ниво. Поддържа локални редакции (добавяне/премахване/промяна) и по-високо ниво на семантични промени като създаване на IP и трансфер на стил, като същевременно запазва смисъла. Постига SOTA резултати в множество бенчмаркове.",
|
||||
"Qwen/Qwen-Image.description": "Qwen-Image е базов модел за генериране на изображения с 20B параметъра от екипа на Qwen. Постига значителен напредък в рендиране на сложен текст и прецизно редактиране на изображения, особено за висококачествен китайски/английски текст. Поддържа многострочни и параграфни оформления с последователна типография. Освен текстово рендиране, поддържа широк спектър от стилове – от фотореалистични до аниме, както и напреднало редактиране като трансфер на стил, добавяне/премахване на обекти, подобряване на детайли, редактиране на текст и контрол на позата, с цел да бъде цялостна основа за визуално творчество.",
|
||||
@@ -223,6 +228,7 @@
|
||||
"THUDM/GLM-4.1V-9B-Thinking.description": "GLM-4.1V-9B-Thinking е модел с отворен код от Zhipu AI и лабораторията KEG на университета Цинхуа, създаден за сложна мултимодална когниция. Построен върху GLM-4-9B-0414, той добавя разсъждения чрез верига от мисли и RL за значително подобряване на кръстомодалното разсъждение и стабилност.",
|
||||
"THUDM/GLM-Z1-32B-0414.description": "GLM-Z1-32B-0414 е модел за дълбоко разсъждение, изграден от GLM-4-32B-0414 с данни за студен старт и разширено подсилено обучение, допълнително обучен върху математика, код и логика. Значително подобрява способността за решаване на сложни задачи спрямо базовия модел.",
|
||||
"THUDM/GLM-Z1-9B-0414.description": "GLM-Z1-9B-0414 е компактен GLM модел с 9 милиарда параметъра, който запазва силните страни на отворения код, като същевременно предлага впечатляващи възможности. Представя се отлично в математическо разсъждение и общи задачи, водещ в своя клас сред отворените модели.",
|
||||
"Tongyi-Zhiwen/QwenLong-L1-32B.description": "QwenLong-L1-32B е първият модел за разсъждение с дълъг контекст (LRM), обучен с RL, оптимизиран за разсъждение върху дълги текстове. Неговото прогресивно разширяване на контекста чрез RL позволява стабилен преход от кратък към дълъг контекст. Той надминава OpenAI-o3-mini и Qwen3-235B-A22B на седем бенчмарка за QA върху документи с дълъг контекст, съперничейки на Claude-3.7-Sonnet-Thinking. Особено силен е в математика, логика и многократни разсъждения.",
|
||||
"Wan-AI/Wan2.2-I2V-A14B.description": "Wan2.2-I2V-A14B е един от първите модели за генериране на видео от изображение (I2V), пуснати с отворен код от Wan-AI, инициатива за изкуствен интелект под Alibaba, който използва архитектура Mixture of Experts (MoE). Моделът се фокусира върху генерирането на плавни и естествени динамични видео последователности чрез комбиниране на статични изображения с текстови подсказки. Основната иновация е в архитектурата MoE: експерт с висок шум отговаря за обработката на грубата структура в ранните етапи на генериране на видеото, докато експерт с нисък шум усъвършенства детайлите в по-късните етапи. Този дизайн подобрява общата производителност на модела, без да увеличава разходите за извеждане. В сравнение с предишни версии, Wan2.2 е обучен върху значително по-голям набор от данни, което води до забележителни подобрения в разбирането на сложни движения, естетически стилове и семантично съдържание. Той произвежда по-стабилни видеа и намалява нереалистичните движения на камерата.",
|
||||
"Wan-AI/Wan2.2-T2V-A14B.description": "Wan2.2-T2V-A14B е първият модел за генериране на видео от текст (T2V), пуснат с отворен код от Alibaba, който използва архитектура Mixture of Experts (MoE). Моделът е предназначен за задачи за генериране на видео от текст и е способен да произвежда видеа с продължителност до 5 секунди при резолюции от 480P или 720P. Чрез въвеждането на архитектурата MoE, моделът значително увеличава общия си капацитет, като същевременно запазва почти непроменени разходите за извеждане. Той включва експерт с висок шум, който обработва глобалната структура в ранните етапи на генериране, и експерт с нисък шум, който усъвършенства детайлите в по-късните етапи на видеото. Освен това Wan2.2 включва внимателно подбрани естетически данни с подробни анотации в измерения като осветление, композиция и цвят. Това позволява по-прецизно и контролируемо генериране на визуализации с кинематографично качество. В сравнение с предишни версии, моделът е обучен върху по-голям набор от данни, което води до значително подобрено обобщение в движенията, семантиката и естетиката, както и по-добро справяне със сложни динамични ефекти.",
|
||||
"Yi-34B-Chat.description": "Yi-1.5-34B запазва силните езикови способности на серията, като използва инкрементално обучение върху 500 милиарда висококачествени токена, за да подобри значително логиката в математиката и програмирането.",
|
||||
@@ -314,13 +320,13 @@
|
||||
"claude-3-haiku-20240307.description": "Claude 3 Haiku е най-бързият и най-компактен модел на Anthropic, проектиран за почти мигновени отговори с бърза и точна производителност.",
|
||||
"claude-3-opus-20240229.description": "Claude 3 Opus е най-мощният модел на Anthropic за силно сложни задачи, отличаващ се с производителност, интелигентност, плавност и разбиране.",
|
||||
"claude-3-sonnet-20240229.description": "Claude 3 Sonnet балансира интелигентност и скорост за корпоративни натоварвания, осигурявайки висока полезност на по-ниска цена и надеждно мащабно внедряване.",
|
||||
"claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 е най-бързият и интелигентен Haiku модел на Anthropic, с мълниеносна скорост и разширено разсъждение.",
|
||||
"claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 е най-бързият и интелигентен Haiku модел на Anthropic, с мълниеносна скорост и разширено мислене.",
|
||||
"claude-haiku-4-5.description": "Claude Haiku 4.5 от Anthropic — ново поколение Haiku с подобрено разсъждение и визия.",
|
||||
"claude-haiku-4.5.description": "Claude Haiku 4.5 е най-бързият и най-умен Haiku модел на Anthropic, с мълниеносна скорост и разширено разсъждение.",
|
||||
"claude-opus-4-1-20250805-thinking.description": "Claude Opus 4.1 Thinking е усъвършенстван вариант, който може да разкрие процеса си на разсъждение.",
|
||||
"claude-opus-4-1-20250805.description": "Claude Opus 4.1 е най-новият и най-способен модел на Anthropic за изключително сложни задачи, отличаващ се с производителност, интелигентност, плавност и разбиране.",
|
||||
"claude-opus-4-1-20250805.description": "Claude Opus 4.1 е най-новият и най-способен модел на Anthropic за изключително сложни задачи, превъзхождащ в производителност, интелигентност, плавност и разбиране.",
|
||||
"claude-opus-4-1.description": "Claude Opus 4.1 от Anthropic — премиум модел за дълбок анализ и разсъждение.",
|
||||
"claude-opus-4-20250514.description": "Claude Opus 4 е най-мощният модел на Anthropic за изключително сложни задачи, отличаващ се с производителност, интелигентност, плавност и разбиране.",
|
||||
"claude-opus-4-20250514.description": "Claude Opus 4 е най-мощният модел на Anthropic за изключително сложни задачи, превъзхождащ в производителност, интелигентност, плавност и разбиране.",
|
||||
"claude-opus-4-5-20251101.description": "Claude Opus 4.5 е флагманският модел на Anthropic, комбиниращ изключителна интелигентност с мащабируема производителност, идеален за сложни задачи, изискващи най-висококачествени отговори и разсъждение.",
|
||||
"claude-opus-4-5.description": "Claude Opus 4.5 от Anthropic — флагмански модел с върхово разсъждение и кодови умения.",
|
||||
"claude-opus-4-6.description": "Claude Opus 4.6 от Anthropic — флагман с 1M контекст и усъвършенствано разсъждение.",
|
||||
@@ -329,8 +335,8 @@
|
||||
"claude-opus-4.6-fast.description": "Claude Opus 4.6 е най-интелигентният модел на Anthropic за създаване на агенти и програмиране.",
|
||||
"claude-opus-4.6.description": "Claude Opus 4.6 е най-интелигентният модел на Anthropic за създаване на агенти и програмиране.",
|
||||
"claude-sonnet-4-20250514-thinking.description": "Claude Sonnet 4 Thinking може да генерира почти мигновени отговори или разширено стъпково мислене с видим процес.",
|
||||
"claude-sonnet-4-20250514.description": "Claude Sonnet 4 може да генерира почти мигновени отговори или разширено стъпка по стъпка мислене с видим процес.",
|
||||
"claude-sonnet-4-5-20250929.description": "Claude Sonnet 4.5 е най-интелигентният модел на Anthropic до момента.",
|
||||
"claude-sonnet-4-20250514.description": "Claude Sonnet 4 е най-интелигентният модел на Anthropic досега, предлагащ почти мигновени отговори или разширено стъпка по стъпка мислене с фино управление за API потребители.",
|
||||
"claude-sonnet-4-5-20250929.description": "Claude Sonnet 4.5 е най-интелигентният модел на Anthropic досега.",
|
||||
"claude-sonnet-4-5.description": "Claude Sonnet 4.5 от Anthropic — подобрен Sonnet с по‑силни кодови способности.",
|
||||
"claude-sonnet-4-6.description": "Claude Sonnet 4.6 от Anthropic — последният Sonnet с превъзходно кодиране и работа с инструменти.",
|
||||
"claude-sonnet-4.5.description": "Claude Sonnet 4.5 е най-интелигентният модел на Anthropic до момента.",
|
||||
@@ -403,7 +409,7 @@
|
||||
"deepseek-ai/deepseek-llm-67b-chat.description": "DeepSeek LLM Chat (67B) е иновативен модел, предлагащ дълбоко езиково разбиране и интеракция.",
|
||||
"deepseek-ai/deepseek-v3.1-terminus.description": "DeepSeek V3.1 е модел за разсъждение от ново поколение с по-силни способности за сложни разсъждения и верига от мисли за задълбочени аналитични задачи.",
|
||||
"deepseek-ai/deepseek-v3.2.description": "DeepSeek V3.2 е модел за разсъждение от следващо поколение с по-силни способности за сложни разсъждения и верига на мисълта.",
|
||||
"deepseek-chat.description": "Нов модел с отворен код, който комбинира общи и кодови способности. Той запазва общия диалогов модел и силното кодиране на кодовия модел, с по-добро съответствие на предпочитанията. DeepSeek-V2.5 също така подобрява писането и следването на инструкции.",
|
||||
"deepseek-chat.description": "Съвместимостен псевдоним за режим без мислене на DeepSeek V4 Flash. Планирано за премахване — използвайте DeepSeek V4 Flash вместо това.",
|
||||
"deepseek-coder-33B-instruct.description": "DeepSeek Coder 33B е езиков модел за програмиране, обучен върху 2 трилиона токени (87% код, 13% китайски/английски текст). Въвежда 16K контекстен прозорец и задачи за попълване в средата, осигурявайки допълване на код на ниво проект и попълване на фрагменти.",
|
||||
"deepseek-coder-v2.description": "DeepSeek Coder V2 е отворен MoE модел за програмиране, който се представя на ниво GPT-4 Turbo.",
|
||||
"deepseek-coder-v2:236b.description": "DeepSeek Coder V2 е отворен MoE модел за програмиране, който се представя на ниво GPT-4 Turbo.",
|
||||
@@ -425,7 +431,7 @@
|
||||
"deepseek-r1-fast-online.description": "Пълна бърза версия на DeepSeek R1 с търсене в реално време в уеб, комбинираща възможности от мащаб 671B и по-бърз отговор.",
|
||||
"deepseek-r1-online.description": "Пълна версия на DeepSeek R1 с 671 милиарда параметъра и търсене в реално време в уеб, предлагаща по-силно разбиране и генериране.",
|
||||
"deepseek-r1.description": "DeepSeek-R1 използва данни от студен старт преди подсиленото обучение и се представя наравно с OpenAI-o1 в математика, програмиране и разсъждение.",
|
||||
"deepseek-reasoner.description": "Модел за разсъждение DeepSeek, фокусиран върху сложни логически задачи.",
|
||||
"deepseek-reasoner.description": "Съвместимостен псевдоним за режим с мислене на DeepSeek V4 Flash. Планирано за премахване — използвайте DeepSeek V4 Flash вместо това.",
|
||||
"deepseek-v2.description": "DeepSeek V2 е ефективен MoE модел за икономична обработка.",
|
||||
"deepseek-v2:236b.description": "DeepSeek V2 236B е модел на DeepSeek, фокусиран върху програмиране, с висока производителност при генериране на код.",
|
||||
"deepseek-v3-0324.description": "DeepSeek-V3-0324 е MoE модел с 671 милиарда параметъра, с изключителни способности в програмиране, технически задачи, разбиране на контекст и обработка на дълги текстове.",
|
||||
@@ -490,6 +496,8 @@
|
||||
"doubao-seedream-4-0-250828.description": "Seedream 4.0 е модел за генериране на изображения от ByteDance Seed, поддържащ вход от текст и изображения с високо контролируемо, висококачествено генериране на изображения. Генерира изображения от текстови подсказки.",
|
||||
"doubao-seedream-4-5-251128.description": "Seedream 4.5 е най-новият мултимодален модел за изображения на ByteDance, интегриращ текст-към-изображение, изображение-към-изображение и групово генериране на изображения, като включва обща логика и способности за разсъждение. В сравнение с предишната версия 4.0, той предлага значително подобрено качество на генериране, с по-добра консистентност при редактиране и мулти-изображение сливане. Осигурява по-прецизен контрол върху визуалните детайли, като произвежда малък текст и малки лица по-естествено, и постига по-хармонично оформление и цветове, подобрявайки цялостната естетика.",
|
||||
"doubao-seedream-5-0-260128.description": "Doubao-Seedream-5.0-lite е най-новият модел за генериране на изображения на ByteDance. За първи път интегрира възможности за онлайн извличане, позволявайки му да включва информация в реално време от уеб и да подобрява актуалността на генерираните изображения. Интелигентността на модела също е подобрена, позволявайки прецизно интерпретиране на сложни инструкции и визуално съдържание. Освен това предлага подобрено глобално покритие на знания, консистентност на референциите и качество на генериране в професионални сценарии, по-добре отговаряйки на нуждите за визуално създаване на корпоративно ниво.",
|
||||
"dreamina-seedance-2-0-260128.description": "Seedance 2.0 от ByteDance е най-мощният модел за видео генериране, поддържащ мултимодално генериране на референтни видеа, редактиране на видеа, разширяване на видеа, текст-към-видео и изображение-към-видео със синхронизиран звук.",
|
||||
"dreamina-seedance-2-0-fast-260128.description": "Seedance 2.0 Fast от ByteDance предлага същите възможности като Seedance 2.0 с по-бързи скорости на генериране на по-конкурентна цена.",
|
||||
"emohaa.description": "Emohaa е модел за психично здраве с професионални консултантски способности, който помага на потребителите да разберат емоционални проблеми.",
|
||||
"ernie-4.5-0.3b.description": "ERNIE 4.5 0.3B е лек модел с отворен код за локално и персонализирано внедряване.",
|
||||
"ernie-4.5-8k-preview.description": "ERNIE 4.5 8K Preview е модел за предварителен преглед с 8K контекст за оценка на ERNIE 4.5.",
|
||||
@@ -514,7 +522,8 @@
|
||||
"ernie-x1-turbo-32k.description": "ERNIE X1 Turbo 32K е бърз мислещ модел с 32K контекст за сложни разсъждения и многозавойни разговори.",
|
||||
"ernie-x1.1-preview.description": "ERNIE X1.1 Preview е предварителен модел за мислене, предназначен за оценка и тестване.",
|
||||
"ernie-x1.1.description": "ERNIE X1.1 е мисловен модел за предварителен преглед за оценка и тестване.",
|
||||
"fal-ai/bytedance/seedream/v4.description": "Seedream 4.0 е модел за генериране на изображения от ByteDance Seed, който поддържа текстови и визуални входове с високо контролируемо и висококачествено генериране на изображения. Той генерира изображения от текстови подсказки.",
|
||||
"fal-ai/bytedance/seedream/v4.5.description": "Seedream 4.5, създаден от екипа на ByteDance Seed, поддържа редактиране и композиция на множество изображения. Характеризира се с подобрена консистентност на обектите, прецизно следване на инструкции, разбиране на пространствена логика, естетическо изразяване, оформление на плакати и дизайн на лога с високопрецизно рендиране на текст-изображение.",
|
||||
"fal-ai/bytedance/seedream/v4.description": "Seedream 4.0, създаден от ByteDance Seed, поддържа текстови и визуални входове за високо контролируемо, висококачествено генериране на изображения от подсказки.",
|
||||
"fal-ai/flux-kontext/dev.description": "FLUX.1 модел, фокусиран върху редактиране на изображения, поддържащ вход от текст и изображения.",
|
||||
"fal-ai/flux-pro/kontext.description": "FLUX.1 Kontext [pro] приема текст и референтни изображения като вход, позволявайки целенасочени локални редакции и сложни глобални трансформации на сцени.",
|
||||
"fal-ai/flux/krea.description": "Flux Krea [dev] е модел за генериране на изображения с естетично предпочитание към по-реалистични и естествени изображения.",
|
||||
@@ -522,8 +531,8 @@
|
||||
"fal-ai/hunyuan-image/v3.description": "Мощен роден мултимодален модел за генериране на изображения.",
|
||||
"fal-ai/imagen4/preview.description": "Модел за висококачествено генериране на изображения от Google.",
|
||||
"fal-ai/nano-banana.description": "Nano Banana е най-новият, най-бърз и най-ефективен роден мултимодален модел на Google, позволяващ генериране и редактиране на изображения чрез разговор.",
|
||||
"fal-ai/qwen-image-edit.description": "Професионален модел за редактиране на изображения от екипа на Qwen, който поддържа семантични и визуални редакции, прецизно редактира китайски и английски текст и позволява висококачествени редакции като прехвърляне на стил и завъртане на обекти.",
|
||||
"fal-ai/qwen-image.description": "Мощен модел за генериране на изображения от екипа на Qwen с впечатляващо рендиране на китайски текст и разнообразни визуални стилове.",
|
||||
"fal-ai/qwen-image-edit.description": "Професионален модел за редактиране на изображения от екипа на Qwen, поддържащ семантични и визуални редакции, прецизно редактиране на текст на китайски/английски, трансфер на стил, завъртане и други.",
|
||||
"fal-ai/qwen-image.description": "Мощен модел за генериране на изображения от екипа на Qwen с висока прецизност при рендиране на китайски текст и разнообразни визуални стилове.",
|
||||
"flux-1-schnell.description": "Модел за преобразуване на текст в изображение с 12 милиарда параметъра от Black Forest Labs, използващ латентна дифузионна дестилация за генериране на висококачествени изображения в 1–4 стъпки. Съперничи на затворени алтернативи и е пуснат под лиценз Apache-2.0 за лична, изследователска и търговска употреба.",
|
||||
"flux-dev.description": "Модел за генериране на изображения с отворен код, оптимизиран за неконкурентни изследвания и иновации.",
|
||||
"flux-kontext-max.description": "Съвременно генериране и редактиране на изображения с контекст, комбиниращо текст и изображения за прецизни и последователни резултати.",
|
||||
@@ -565,9 +574,8 @@
|
||||
"gemini-3-pro-image-preview:image.description": "Gemini 3 Pro Image (Nano Banana Pro) е моделът на Google за генериране на изображения и също така поддържа мултимодален чат.",
|
||||
"gemini-3-pro-preview.description": "Gemini 3 Pro е най-мощният агентен и „vibe-coding“ модел на Google, който предлага по-богати визуализации и по-дълбоко взаимодействие, базирано на съвременно логическо мислене.",
|
||||
"gemini-3.1-flash-image-preview.description": "Gemini 3.1 Flash Image (Nano Banana 2) е най-бързият модел на Google за генериране на изображения с поддръжка на мислене, разговорно генериране и редактиране на изображения.",
|
||||
"gemini-3.1-flash-image-preview:image.description": "Gemini 3.1 Flash Image (Nano Banana 2) е най-бързият собствен модел на Google за генериране на изображения с поддръжка на мислене, разговорно генериране и редактиране на изображения.",
|
||||
"gemini-3.1-flash-image-preview:image.description": "Gemini 3.1 Flash Image (Nano Banana 2) предоставя Pro-качество на изображения с Flash скорост и поддръжка на мултимодален чат.",
|
||||
"gemini-3.1-flash-lite-preview.description": "Gemini 3.1 Flash-Lite Preview е най-икономичният мултимодален модел на Google, оптимизиран за задачи с голям обем, превод и обработка на данни.",
|
||||
"gemini-3.1-flash-lite.description": "Gemini 3.1 Flash-Lite е най-икономичният мултимодален модел на Google, оптимизиран за задачи с голям обем, превод и обработка на данни.",
|
||||
"gemini-3.1-pro-preview.description": "Gemini 3.1 Pro Preview подобрява Gemini 3 Pro с усъвършенствани способности за разсъждение и добавя поддръжка за средно ниво на мислене.",
|
||||
"gemini-3.1-pro.description": "Gemini 3.1 Pro от Google — премиум мултимодален модел с 1M контекст.",
|
||||
"gemini-flash-latest.description": "Посочва gemini-3-flash-preview",
|
||||
@@ -724,17 +732,21 @@
|
||||
"grok-3-mini.description": "Grok 3 Mini от xAI — силно разсъждение и бързи отговори.",
|
||||
"grok-3.description": "Grok 3 от xAI — модел със силни способности за разсъждение.",
|
||||
"grok-4-0709.description": "Grok 4 на xAI с мощни логически способности.",
|
||||
"grok-4-1-fast-non-reasoning.description": "Модерен мултимодален модел, оптимизиран за високоефективна употреба на агентски инструменти.",
|
||||
"grok-4-1-fast-reasoning.description": "Модерен мултимодален модел, оптимизиран за високоефективна употреба на агентски инструменти.",
|
||||
"grok-4-20-non-reasoning.description": "Неразсъждаващ вариант за прости случаи.",
|
||||
"grok-4-20-reasoning.description": "Интелигентен, изключително бърз модел, който разсъждава преди да отговори.",
|
||||
"grok-4-fast-non-reasoning.description": "С гордост представяме Grok 4 Fast – нашият най-нов напредък в икономичните логически модели.",
|
||||
"grok-4-fast-reasoning.description": "С гордост представяме Grok 4 Fast – нашият най-нов напредък в икономичните логически модели.",
|
||||
"grok-4.20-0309-non-reasoning.description": "Неразсъждаващ вариант за прости случаи.",
|
||||
"grok-4.20-0309-reasoning.description": "Интелигентен, изключително бърз модел с разсъждение.",
|
||||
"grok-4.20-beta-0309-non-reasoning.description": "Вариант без мислене за прости случаи на употреба.",
|
||||
"grok-4.20-beta-0309-reasoning.description": "Интелигентен, изключително бърз модел, който разсъждава преди да отговори.",
|
||||
"grok-4.20-multi-agent-0309.description": "Екип от 4 или 16 агента — отличен за проучвания; поддържа само xAI сървърни инструменти.",
|
||||
"grok-4.3.description": "Най-истинно търсещият голям езиков модел в света",
|
||||
"grok-4.description": "Най-новият флагман Grok с ненадмината производителност в езика, математиката и разсъжденията — истински универсален модел. В момента сочи към grok-4-0709; поради ограничени ресурси временно е с 10% по-висока цена от официалната и се очаква да се върне към официалната цена по-късно.",
|
||||
"grok-4.description": "Нашият най-нов и най-силен флагмански модел, превъзхождащ в NLP, математика и разсъждения — идеален универсален инструмент.",
|
||||
"grok-code-fast-1.description": "С гордост представяме grok-code-fast-1 – бърз и икономичен логически модел, който се отличава в агентско програмиране.",
|
||||
"grok-imagine-image-quality.description": "Генерирайте изображения от текстови подсказки, редактирайте съществуващи изображения с естествен език или итеративно усъвършенствайте изображения чрез многократни разговори.",
|
||||
"grok-imagine-image-pro.description": "Генерирайте изображения от текстови подсказки, редактирайте съществуващи изображения с естествен език или итеративно усъвършенствайте изображения чрез многократни разговори.",
|
||||
"grok-imagine-image.description": "Генерирайте изображения от текстови подсказки, редактирайте съществуващи изображения с естествен език или итеративно усъвършенствайте изображения чрез многократни разговори.",
|
||||
"grok-imagine-video.description": "Най-съвременно видео генериране по отношение на качество, цена и латентност.",
|
||||
"groq/compound-mini.description": "Compound-mini е композитна AI система, задвижвана от публично достъпни модели, поддържани в GroqCloud, която интелигентно и селективно използва инструменти за отговаряне на потребителски запитвания.",
|
||||
@@ -970,6 +982,7 @@
|
||||
"moonshot-v1-32k.description": "Moonshot V1 32K поддържа 32 768 токена за средно дълъг контекст, идеален за дълги документи и сложни диалози в създаване на съдържание, отчети и чат системи.",
|
||||
"moonshot-v1-8k-vision-preview.description": "Моделите Kimi vision (включително moonshot-v1-8k-vision-preview/moonshot-v1-32k-vision-preview/moonshot-v1-128k-vision-preview) разбират съдържание на изображения като текст, цветове и форми на обекти.",
|
||||
"moonshot-v1-8k.description": "Moonshot V1 8K е оптимизиран за генериране на кратки текстове с висока ефективност, обработвайки 8 192 токена за кратки чатове, бележки и бързо съдържание.",
|
||||
"moonshotai/Kimi-Dev-72B.description": "Kimi-Dev-72B е модел за програмиране с отворен код, оптимизиран с мащабно RL за създаване на надеждни, готови за производство корекции. Той постига 60.4% на SWE-bench Verified, поставяйки нов рекорд за модели с отворен код в автоматизирани задачи като поправка на грешки и преглед на код.",
|
||||
"moonshotai/Kimi-K2-Instruct-0905.description": "Kimi K2-Instruct-0905 е най-новият и най-мощен модел от серията Kimi K2. Това е MoE модел от най-висок клас с 1T общо и 32B активни параметъра. Основни характеристики включват по-силна агентна интелигентност при програмиране, значителни подобрения в бенчмаркове и реални задачи, както и подобрена естетика и използваемост на фронтенд кода.",
|
||||
"moonshotai/Kimi-K2-Thinking.description": "Kimi K2 Thinking е най-новият и най-мощен модел за мислене с отворен код. Той значително разширява дълбочината на многократното разсъждение и поддържа стабилно използване на инструменти в 200–300 последователни извиквания, поставяйки нови рекорди на Humanity's Last Exam (HLE), BrowseComp и други бенчмаркове. Превъзхожда в кодиране, математика, логика и сценарии с агенти. Изграден на архитектура MoE с ~1 трилион общи параметри, поддържа 256K контекстен прозорец и извикване на инструменти.",
|
||||
"moonshotai/kimi-k2-0711.description": "Kimi K2 0711 е instruct вариант от серията Kimi, подходящ за висококачествен код и използване на инструменти.",
|
||||
@@ -1131,6 +1144,12 @@
|
||||
"qwen/qwen3-max-preview.description": "Qwen3 Max (преглед) е вариантът Max за напреднало разсъждение и интеграция с инструменти.",
|
||||
"qwen/qwen3-max.description": "Qwen3 Max е висококласният модел за разсъждение от серията Qwen3, предназначен за многоезично разсъждение и интеграция с инструменти.",
|
||||
"qwen/qwen3-vl-plus.description": "Qwen3 VL-Plus е подобрен визуален вариант на Qwen3 с усъвършенствано мултимодално разсъждение и обработка на видео.",
|
||||
"qwen/qwen3.5-122b-a10b.description": "Qwen3.5-122B-A10B е роден мултимодален голям езиков модел, разработен от екипа на Qwen, с общо 122 милиарда параметри и само 10 милиарда активни параметри. Моделът използва високо ефективна хибридна архитектура, комбинираща Gated Delta Networks със Sparse Mixture of Experts (MoE). Нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Чрез ранно обучение за сливане моделът постига унифицирани основни способности за визия и език, поддържащи текст, изображения и видео разбиране. Той предоставя отлична производителност в множество бенчмаркове, включително знания, разсъждения, кодиране, агенти, визуално разбиране и многоезични задачи, надминавайки GPT-5-mini и Qwen3-235B-A22B по няколко метрики. Моделът има режим на мислене, активиран по подразбиране, поддържа извикване на инструменти и обхваща 201 езика и диалекта.",
|
||||
"qwen/qwen3.5-27b.description": "Qwen3.5-27B е роден мултимодален голям езиков модел, разработен от екипа на Qwen, с 27 милиарда параметри. Моделът използва високо ефективна хибридна архитектура, комбинираща Gated Delta Networks със Gated Attention. Нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Чрез ранно обучение за сливане моделът постига унифицирани основни способности за визия и език, поддържащи текст, изображения и видео разбиране. Той предоставя отлична производителност в множество бенчмаркове, включително разсъждения, кодиране, агенти и визуално разбиране, надминавайки Qwen3-235B-A22B и GPT-5-mini по няколко метрики. Моделът има режим на мислене, активиран по подразбиране, поддържа извикване на инструменти и обхваща 201 езика и диалекта.",
|
||||
"qwen/qwen3.5-35b-a3b.description": "Qwen3.5-35B-A3B е роден мултимодален голям езиков модел, разработен от екипа на Qwen, с общо 35 милиарда параметри и само 3 милиарда активни параметри. Моделът използва високо ефективна хибридна архитектура, комбинираща Gated Delta Networks със Sparse Mixture of Experts (MoE). Нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Чрез ранно обучение за сливане моделът постига унифицирани основни способности за визия и език, поддържащи текст, изображения и видео разбиране. Той предоставя отлична производителност в множество бенчмаркове, включително разсъждения, кодиране, агенти и визуално разбиране. Моделът има режим на мислене, активиран по подразбиране, поддържа извикване на инструменти и обхваща 201 езика и диалекта.",
|
||||
"qwen/qwen3.5-397b-a17b.description": "Qwen3.5-397B-A17B е най-новият модел за визия и език в серията Qwen, с архитектура Mixture of Experts (MoE) и общо 397 милиарда параметри и 17 милиарда активни параметри. Моделът нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Той поддържа 201 езика и предлага унифицирани способности за разбиране на визия и език, извикване на инструменти и режими на мислене за разсъждение.",
|
||||
"qwen/qwen3.5-4b.description": "Qwen3.5-4B е роден мултимодален голям езиков модел, разработен от екипа на Qwen, с 4 милиарда параметри, което го прави най-леката плътна архитектура в серията Qwen3.5. Моделът използва високо ефективна хибридна архитектура, комбинираща Gated Delta Networks със Gated Attention. Нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Чрез ранно обучение за сливане моделът постига унифицирани основни способности за визия и език, поддържащи текст, изображения и видео разбиране. Той предоставя отлична производителност сред модели от подобен размер, надминавайки GPT-5-Nano и Gemini-2.5-Flash-Lite по няколко метрики. Моделът има режим на мислене, активиран по подразбиране, поддържа извикване на инструменти и обхваща 201 езика и диалекта.",
|
||||
"qwen/qwen3.5-9b.description": "Qwen3.5-9B е роден мултимодален голям езиков модел, разработен от екипа на Qwen, с 9 милиарда параметри. Като лек плътен модел в серията Qwen3.5, той използва високо ефективна хибридна архитектура, комбинираща Gated Delta Networks със Gated Attention. Нативно поддържа дължина на контекста от 256K, разширяема до приблизително 1 милион токени. Чрез ранно обучение за сливане моделът постига унифицирани основни способности за визия и език, поддържащи текст, изображения и видео разбиране. Моделът има режим на мислене, активиран по подразбиране, поддържа извикване на инструменти и обхваща 201 езика и диалекта.",
|
||||
"qwen2.5-14b-instruct-1m.description": "Qwen2.5 отворен код, модел с 72B параметъра.",
|
||||
"qwen2.5-14b-instruct.description": "Qwen2.5 отворен код, модел с 14B параметъра.",
|
||||
"qwen2.5-32b-instruct.description": "Qwen2.5 отворен код, модел с 32B параметъра.",
|
||||
@@ -1214,6 +1233,8 @@
|
||||
"qwq.description": "QwQ е модел за аргументация от семейството на Qwen. В сравнение със стандартните модели, обучени с инструкции, предлага мисловни и логически способности, които значително подобряват ефективността при трудни задачи. QwQ-32B е среден по размер модел, който се конкурира с водещи модели като DeepSeek-R1 и o1-mini.",
|
||||
"qwq_32b.description": "Среден по размер модел за аргументация от семейството на Qwen. В сравнение със стандартните модели, обучени с инструкции, мисловните и логическите способности на QwQ значително подобряват ефективността при трудни задачи.",
|
||||
"r1-1776.description": "R1-1776 е дообучен вариант на DeepSeek R1, създаден да предоставя неконфронтирана, обективна и фактическа информация.",
|
||||
"seedance-1-5-pro-251215.description": "Seedance 1.5 Pro от ByteDance поддържа текст-към-видео, изображение-към-видео (първи кадър, първи+последен кадър) и генериране на звук, синхронизиран с визуализации.",
|
||||
"seedream-5-0-260128.description": "ByteDance-Seedream-5.0-lite от BytePlus предлага генериране, обогатено с уеб търсене за реална информация, подобрена интерпретация на сложни подсказки и подобрена консистентност на референциите за професионално визуално създаване.",
|
||||
"solar-mini-ja.description": "Solar Mini (Ja) разширява Solar Mini с фокус върху японски език, като запазва ефективността и силната производителност на английски и корейски.",
|
||||
"solar-mini.description": "Solar Mini е компактен LLM, който превъзхожда GPT-3.5, с мощни многоезични възможности, поддържащ английски и корейски, и предлага ефективно решение с малък отпечатък.",
|
||||
"solar-pro.description": "Solar Pro е интелигентен LLM от Upstage, фокусиран върху следване на инструкции на един GPU, с IFEval резултати над 80. Понастоящем поддържа английски; пълното издание е планирано за ноември 2024 с разширена езикова поддръжка и по-дълъг контекст.",
|
||||
@@ -1225,9 +1246,7 @@
|
||||
"sophnet/deepseek-v3.2.description": "DeepSeek V3.2 е модел, който балансира висока изчислителна ефективност и отлична производителност за разсъждение и агенти.",
|
||||
"sora-2-pro.description": "Sora 2 Pro е нашият най-съвременен, най-напреднал модел за генериране на медии, генериращ видеа със синхронизиран звук. Той може да създава богато детайлизирани, динамични клипове от естествен език или изображения.",
|
||||
"sora-2.description": "Sora 2 е нашият нов мощен модел за генериране на медии, генериращ видеа със синхронизиран звук. Той може да създава богато детайлизирани, динамични клипове от естествен език или изображения.",
|
||||
"spark-x1.5.description": "Актуализации на X1.5: (1) добавя динамичен режим на мислене, контролиран чрез полето `thinking`; (2) по-голяма дължина на контекста с 64K вход и 64K изход; (3) поддържа FunctionCall.",
|
||||
"spark-x2-flash.description": "Spark X2-Flash използва архитектура MoE (Mixture of Experts) с общо 30 милиарда параметри и поддържа до 256K контекстен прозорец. Твърди се, че предлага значителни подобрения в агентните и кодиращите способности и е обучен на клъстер от AI процесори Ascend 910B.",
|
||||
"spark-x2.description": "Преглед на възможностите на X2: 1. Въвежда динамично регулиране на режима на разсъждение, контролиран чрез полето `thinking`. 2. Разширена дължина на контекста: 64K входни токени и 128K изходни токени. 3. Поддържа функционалност за извикване на функции (Function Call).",
|
||||
"spark-x.description": "Преглед на възможностите на X2: 1. Въвежда динамично регулиране на режима на разсъждение, контролирано чрез полето `thinking`. 2. Разширена дължина на контекста: 64K входни токени и 128K изходни токени. 3. Поддържа функционалност за извикване на функции.",
|
||||
"stable-diffusion-3-medium.description": "Най-новият модел за преобразуване на текст в изображение от Stability AI. Тази версия значително подобрява качеството на изображенията, разбирането на текст и стиловото разнообразие, като по-точно интерпретира сложни естественоезикови заявки и генерира по-прецизни и разнообразни изображения.",
|
||||
"stable-diffusion-3.5-large-turbo.description": "Stable Diffusion 3.5 Large Turbo е фокусиран върху висококачествено генериране на изображения с отлична детайлност и вярност на сцените.",
|
||||
"stable-diffusion-xl-base-1.0.description": "Модел с отворен код за генериране на изображения от текст от Stability AI, предлагащ водещо в индустрията творческо качество. Притежава силно разбиране на инструкции и поддържа обратни дефиниции на подканите за прецизно генериране.",
|
||||
@@ -1252,7 +1271,7 @@
|
||||
"step-3.description": "Този модел притежава силно визуално възприятие и сложна логика, точно обработва междудомейново знание, анализ между математика и визия и широк спектър от ежедневни визуални задачи.",
|
||||
"step-image-edit-2.description": "Лек модел за редактиране от последната итерация на Stepfun, който поддържа както генериране на изображения от текст, така и редактиране на изображения в един модел. Въпреки че има по-малко от 6 милиарда параметри, той постига водещи резултати за своя мащаб, съперничейки на отворени модели в диапазона 12B–20B параметри. Всяка задача за редактиране отнема само 1–2 секунди, преосмисляйки изживяването на редактиране на изображения в реално време.",
|
||||
"step-r1-v-mini.description": "Модел за логическо разсъждение със силно визуално разбиране, който може да обработва изображения и текст, след което да генерира текст след дълбоко разсъждение. Отличава се във визуално разсъждение и предоставя водещи резултати в математика, програмиране и текстово разсъждение, с контекстен прозорец от 100K.",
|
||||
"stepfun-ai/Step-3.5-Flash.description": "Step 3.5 Flash е най-мощният отворен модел на StepFun, използващ разредена архитектура Mixture of Experts (MoE) с 196B общи параметри, само 11B активни параметри на токен. Моделът поддържа контекстен прозорец от 256K, постигайки производителност от 100-300 токена/секунда чрез 3-степенно предсказване на множество токени (MTP-3). Отлична производителност при програмиране и задачи за агенти, SWE-bench Verified достига 74.4%.",
|
||||
"stepfun-ai/step3.description": "Step3 е авангарден модел за мултимодално разсъждение от StepFun, построен върху архитектура MoE с 321 милиарда общи и 38 милиарда активни параметри. Неговият дизайн от край до край минимизира разходите за декодиране, като същевременно осигурява водещо разсъждение за визия и език. С дизайна MFA и AFD, той остава ефективен както на водещи, така и на нискобюджетни ускорители. Предварителното обучение използва над 20 трилиона текстови токени и 4 трилиона токени за изображения-текстове на много езици. Той достига водещи резултати сред модели с отворен код в математика, код и мултимодални бенчмаркове.",
|
||||
"taichu4_vl_2b_nothinking.description": "Версията без мислене на модела Taichu4.0-VL 2B се отличава с по-ниска употреба на памет, лек дизайн, бърза скорост на отговор и силни способности за мултимодално разбиране.",
|
||||
"taichu4_vl_32b.description": "Версията с мислене на модела Taichu4.0-VL 32B е подходяща за сложни задачи за мултимодално разбиране и разсъждение, демонстрирайки изключителна производителност в мултимодално математическо разсъждение, мултимодални способности на агенти и общо разбиране на изображения и визуализации.",
|
||||
"taichu4_vl_32b_nothinking.description": "Версията без мислене на модела Taichu4.0-VL 32B е предназначена за сложни сценарии за разбиране на изображения и текст и визуални въпроси и отговори, превъзхождайки в описания на изображения, визуални въпроси и отговори, разбиране на видео и задачи за визуална локализация.",
|
||||
@@ -1349,6 +1368,7 @@
|
||||
"x-ai/grok-4.1-fast.description": "Grok 4 Fast е високопроизводителен, нискобюджетен модел на xAI (поддържа 2M контекст), идеален за случаи с висока едновременност и дълъг контекст.",
|
||||
"x-ai/grok-4.description": "Grok 4 е водещ логически модел на xAI с мощни логически и мултимодални възможности.",
|
||||
"x-ai/grok-code-fast-1.description": "Grok Code Fast 1 е бърз кодов модел на xAI с четим и удобен за инженеринг изход.",
|
||||
"x1.description": "Актуализации на X1.5: (1) добавя динамичен режим на мислене, контролиран чрез полето `thinking`; (2) по-голяма дължина на контекста с 64K вход и 64K изход; (3) поддържа FunctionCall.",
|
||||
"xai/grok-2-vision.description": "Grok 2 Vision се отличава във визуални задачи, постигайки водещи резултати във визуална математическа логика (MathVista) и въпроси и отговори по документи (DocVQA). Обработва документи, диаграми, графики, екранни снимки и снимки.",
|
||||
"xai/grok-2.description": "Grok 2 е авангарден модел с водеща логика, силен чат, кодиране и логическа ефективност, надминаващ Claude 3.5 Sonnet и GPT-4 Turbo в LMSYS.",
|
||||
"xai/grok-3-fast.description": "Водещият модел на xAI се отличава в корпоративни приложения като извличане на данни, кодиране и обобщаване, с дълбоки познания в области като финанси, здравеопазване, право и наука. Бързият вариант работи върху по-бърза инфраструктура за значително по-бързи отговори при по-висока цена на токен.",
|
||||
@@ -1380,7 +1400,7 @@
|
||||
"zai-org/GLM-4.5-Air.description": "GLM-4.5-Air е базов модел за агентни приложения с архитектура Mixture-of-Experts. Оптимизиран е за използване на инструменти, уеб браузване, софтуерно инженерство и фронтенд програмиране, и се интегрира с кодови агенти като Claude Code и Roo Code. Използва хибридно разсъждение за справяне както със сложни, така и с ежедневни задачи.",
|
||||
"zai-org/GLM-4.5V.description": "GLM-4.5V е най-новият визуален езиков модел (VLM) на Zhipu AI, изграден върху флагманския текстов модел GLM-4.5-Air (106B общо, 12B активни) с MoE архитектура за висока производителност при по-ниска цена. Следва пътя на GLM-4.1V-Thinking и добавя 3D-RoPE за подобрено пространствено разсъждение в 3D. Оптимизиран чрез предварително обучение, SFT и RL, обработва изображения, видео и дълги документи и е сред водещите отворени модели в 41 публични мултимодални бенчмарка. Режимът Thinking позволява на потребителите да балансират между скорост и дълбочина.",
|
||||
"zai-org/GLM-4.6.description": "В сравнение с GLM-4.5, GLM-4.6 разширява контекста от 128K до 200K за по-сложни агентни задачи. Постига по-високи резултати в кодови бенчмаркове и показва по-добра реална производителност в приложения като Claude Code, Cline, Roo Code и Kilo Code, включително по-добро генериране на фронтенд страници. Разсъждението е подобрено и се поддържа използване на инструменти по време на разсъждение, което засилва цялостните възможности. По-добре се интегрира в агентни рамки, подобрява инструментите/търсещите агенти и има по-предпочитан от хора стил на писане и естественост в ролевите сценарии.",
|
||||
"zai-org/GLM-4.6V.description": "GLM-4.6V постига SOTA точност при визуално разбиране при същия мащаб на параметрите и е първият, който нативно интегрира възможност за извикване на функции във визуални модели в архитектурата на модела, свързвайки веригата от визуално възприятие до изпълнимо действие (Action), предоставяйки унифицирана техническа основа за мултимодални агенти в реални бизнес сценарии. Визуалният контекстен прозорец е разширен до 128K, поддържащ обработка на дълги видео потоци и анализ на изображения с висока резолюция.",
|
||||
"zai-org/GLM-4.6V.description": "GLM-4.6V постига водеща точност във визуалното разбиране за своя мащаб на параметрите и е първият, който нативно интегрира възможности за извикване на функции в архитектурата на визуалния модел, преодолявайки разликата между \"визуално възприятие\" и \"изпълними действия\" и предоставяйки унифицирана техническа основа за мултимодални агенти в реални бизнес сценарии. Визуалният контекстен прозорец е разширен до 128 хиляди, поддържайки обработка на дълги видео потоци и анализ на изображения с висока резолюция.",
|
||||
"zai/glm-4.5-air.description": "GLM-4.5 и GLM-4.5-Air са най-новите ни флагмани за агентни приложения, и двата използват MoE. GLM-4.5 има 355B общо и 32B активни параметри на стъпка; GLM-4.5-Air е по-лек с 106B общо и 12B активни.",
|
||||
"zai/glm-4.5.description": "Серията GLM-4.5 е проектирана за агенти. Флагманският GLM-4.5 комбинира разсъждение, програмиране и агентни умения с 355B общи параметри (32B активни) и предлага два режима на работа като хибридна система за разсъждение.",
|
||||
"zai/glm-4.5v.description": "GLM-4.5V надгражда GLM-4.5-Air, наследявайки доказани техники от GLM-4.1V-Thinking и мащабира с мощна MoE архитектура с 106 милиарда параметъра.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user