mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-20 22:26:05 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26366a9b5b | |||
| 990942fb45 | |||
| ecec2e87e3 | |||
| 7b6978271a | |||
| 28c2e9002a | |||
| b9034ce9c1 | |||
| 2eb7ee824f | |||
| e78949cd23 | |||
| afae236628 | |||
| 8830c6d560 | |||
| f42fc7d65d | |||
| e5e154afcb | |||
| 346812ab88 | |||
| a099749b41 | |||
| fbe8ab3891 | |||
| 2965cbc83a | |||
| fc44aaef38 | |||
| a2b8f4c81a | |||
| 6f9f5643d1 | |||
| e4877436fe | |||
| 04775f66ff | |||
| 9fff5fccf0 | |||
| 5a46c5a971 | |||
| 5722b7159b | |||
| 49a71bed6e | |||
| d5511a6af2 | |||
| e46e81a08a | |||
| 9555e4fda3 | |||
| 729fbc72d5 | |||
| 0e1a55f2f8 | |||
| c1e2d134ed | |||
| 8663991c7c | |||
| 35edca5531 | |||
| 101b9f9973 | |||
| c6a013a1a1 | |||
| 19643ba662 | |||
| 2654c4d31e | |||
| b94aa1da90 | |||
| e896024b68 | |||
| 2835b99d1a | |||
| 47812b2be9 | |||
| 798644414a | |||
| 54bb83f229 | |||
| 65da232c64 | |||
| dacc7798ab | |||
| 9508807da7 | |||
| 6a7eb17cd2 | |||
| c5da34b680 | |||
| 2a37b77482 | |||
| b814cf2611 | |||
| c37817e2d8 | |||
| bbf239705c | |||
| 8a9f42596d | |||
| 29235dc1ed | |||
| e326400dbe | |||
| deeb97ab5b | |||
| d73858ef42 | |||
| 6b9584714d | |||
| b9a4a9093c | |||
| ef5be7e17c | |||
| a4235d3f68 | |||
| fa508f4259 | |||
| 94767fddcb | |||
| 685b17e59e | |||
| 376976849b |
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: agent-signal
|
||||
description: Build or extend LobeHub Agent Signal pipelines for background or quiet agent work driven by event sources, semantic signals, and action handlers. Use when adding a new Agent Signal source, signal or action type, policy, middleware handler, workflow handoff, dedupe or scope behavior, or observability around `src/server/services/agentSignal/**`, `packages/agent-signal`, or `packages/observability-otel/src/modules/agent-signal`.
|
||||
---
|
||||
|
||||
# Agent Signal
|
||||
|
||||
Use this skill to implement event-driven background work for agents without coupling the work to the foreground chat request.
|
||||
|
||||
Agent Signal has one consistent shape:
|
||||
|
||||
`source event` -> `signal interpretation` -> `action execution` -> built-in result signals
|
||||
|
||||
## Start Here
|
||||
|
||||
1. Read `references/architecture.md` to map the package boundary, runtime queue, scope model, and async workflow handoff.
|
||||
2. Read `references/handlers.md` before writing any new policy, source handler, signal handler, or action handler.
|
||||
3. Read `references/observability.md` when you need tracing, metrics, debugging, or workflow snapshot visibility.
|
||||
|
||||
## Use The Right Entry Point
|
||||
|
||||
- Use `emitAgentSignalSourceEvent(...)` when a server-owned producer should execute the pipeline immediately.
|
||||
- Use `executeAgentSignalSourceEvent(...)` when a worker or controlled backend path already owns execution timing and may inject a runtime guard backend.
|
||||
- Use `enqueueAgentSignalSourceEvent(...)` when the caller should return quickly and let Upstash Workflow process the event out-of-band.
|
||||
- Use `emitAgentSignalSourceEventWithStore(...)` for isolated tests or evals that should avoid ambient Redis state.
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/index.ts`
|
||||
- `src/server/workflows/agentSignal/index.ts`
|
||||
- `src/server/workflows/agentSignal/run.ts`
|
||||
|
||||
## Core Model
|
||||
|
||||
- `source`: A normalized fact that happened. Sources come from producers such as runtime lifecycle events, user messages, or bot ingress.
|
||||
- `signal`: A semantic interpretation derived from one source or from another signal. Signals express meaning, routing, or policy state.
|
||||
- `action`: A concrete side effect planned from one signal. Actions do the work.
|
||||
- `policy`: An installable middleware bundle that registers source, signal, and action handlers.
|
||||
- `procedure`: Not a distinct runtime node. Treat "procedure" as the end-to-end flow for one use case: ingress source, matching handlers, planned actions, execution result, and observability.
|
||||
|
||||
Keep the boundaries strict:
|
||||
|
||||
- Add a new `source` when the outside world produced a new event.
|
||||
- Add a new `signal` when the system needs a reusable semantic interpretation.
|
||||
- Add a new `action` when the runtime needs a concrete side effect.
|
||||
- Add or update a `policy` when you are wiring those pieces together.
|
||||
|
||||
## Implementation Workflow
|
||||
|
||||
1. Decide whether the use case is synchronous or quiet background work.
|
||||
2. Define or reuse a source type in `src/server/services/agentSignal/sourceTypes.ts`.
|
||||
3. Define or reuse signal and action types in `src/server/services/agentSignal/policies/types.ts`.
|
||||
4. Implement handlers with `defineSourceHandler`, `defineSignalHandler`, or `defineActionHandler`.
|
||||
5. Bundle handlers with `defineAgentSignalHandlers(...)`.
|
||||
6. Register the policy in `src/server/services/agentSignal/policies/index.ts` and pass it into the runtime factory if needed.
|
||||
7. Add or update ingress code that emits or enqueues the source event.
|
||||
8. Add observability and tests before considering the flow complete.
|
||||
|
||||
## Default Reading Set
|
||||
|
||||
- Shared semantic core:
|
||||
`packages/agent-signal/src/index.ts`
|
||||
`packages/agent-signal/src/base/builders.ts`
|
||||
`packages/agent-signal/src/base/types.ts`
|
||||
- Server-owned runtime and middleware:
|
||||
`src/server/services/agentSignal/runtime/AgentSignalRuntime.ts`
|
||||
`src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
|
||||
`src/server/services/agentSignal/runtime/middleware.ts`
|
||||
`src/server/services/agentSignal/runtime/context.ts`
|
||||
- Existing policy example:
|
||||
`src/server/services/agentSignal/policies/analyzeIntent/index.ts`
|
||||
`src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
|
||||
`src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
|
||||
`src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
|
||||
`src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
|
||||
- Observability:
|
||||
`src/server/services/agentSignal/observability/projector.ts`
|
||||
`src/server/services/agentSignal/observability/traceEvents.ts`
|
||||
`packages/observability-otel/src/modules/agent-signal/index.ts`
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Reuse existing source, signal, and action types before adding new ones.
|
||||
- Keep source handlers focused on interpretation and fan-out, not heavy side effects.
|
||||
- Keep action handlers responsible for side effects, idempotency, and executor-style result reporting.
|
||||
- Use stable ids and idempotency keys when the same source can arrive more than once.
|
||||
- Preserve scope discipline. The runtime uses `scopeKey` to serialize related background work.
|
||||
- Prefer the dedicated shared package types and builders from `@lobechat/agent-signal` for normalized nodes and result contracts.
|
||||
- Add focused tests near the touched runtime, policy, or store module. Existing tests under `src/server/services/agentSignal/**/__tests__` are the reference pattern.
|
||||
|
||||
## References
|
||||
|
||||
- Architecture and boundaries: `references/architecture.md`
|
||||
- Writing handlers and policies: `references/handlers.md`
|
||||
- Observability, metrics, and debugging: `references/observability.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: 'Agent Signal'
|
||||
short_description: 'Build AgentSignal sources, signals, actions, and policies.'
|
||||
default_prompt: 'Use $agent-signal to add a new Agent Signal source, policy, handler, or observability flow.'
|
||||
@@ -0,0 +1,199 @@
|
||||
# Agent Signal Architecture
|
||||
|
||||
## Pipeline
|
||||
|
||||
Use this mental model first:
|
||||
|
||||
```text
|
||||
producer
|
||||
-> emitAgentSignalSourceEvent(...) or enqueueAgentSignalSourceEvent(...)
|
||||
-> emitSourceEvent(...)
|
||||
-> dedupe + scope lock + source normalization
|
||||
-> runtime.emitNormalized(source)
|
||||
-> source handlers
|
||||
-> signal handlers
|
||||
-> action handlers
|
||||
-> built-in result signals
|
||||
-> observability projection + persistence
|
||||
```
|
||||
|
||||
The scheduler is queue-driven, not hard-coded for one policy:
|
||||
|
||||
```text
|
||||
source node
|
||||
-> matching source handlers
|
||||
-> dispatch signals/actions
|
||||
-> matching signal handlers
|
||||
-> dispatch more signals/actions
|
||||
-> matching action handlers
|
||||
-> ExecutorResult
|
||||
-> signal.action.applied | signal.action.skipped | signal.action.failed
|
||||
```
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/index.ts`
|
||||
- `src/server/services/agentSignal/sources/index.ts`
|
||||
- `src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
|
||||
|
||||
## Package Boundaries
|
||||
|
||||
### `packages/agent-signal`
|
||||
|
||||
Treat this as the shared semantic core.
|
||||
|
||||
It provides:
|
||||
|
||||
- base node types: source, signal, action
|
||||
- builders: `createSource`, `createSignal`, `createAction`
|
||||
- built-in result signal types
|
||||
- runtime result contracts such as `RuntimeProcessorResult` and `ExecutorResult`
|
||||
|
||||
Read:
|
||||
|
||||
- `packages/agent-signal/src/base/types.ts`
|
||||
- `packages/agent-signal/src/base/builders.ts`
|
||||
- `packages/agent-signal/src/types/events.ts`
|
||||
- `packages/agent-signal/src/types/builtin.ts`
|
||||
|
||||
### `src/server/services/agentSignal`
|
||||
|
||||
Treat this as the server-owned implementation layer.
|
||||
|
||||
It owns:
|
||||
|
||||
- source catalogs and payload maps
|
||||
- policy-specific signal and action catalogs
|
||||
- middleware registration
|
||||
- runtime scheduling and guard backends
|
||||
- Redis-backed dedupe, waypoint, and policy state
|
||||
- service entrypoints for synchronous and async execution
|
||||
|
||||
### `packages/observability-otel/src/modules/agent-signal`
|
||||
|
||||
Treat this as shared OTEL ownership for Agent Signal metrics and tracer instances.
|
||||
|
||||
## Core Vocabulary
|
||||
|
||||
### Source
|
||||
|
||||
A source is the normalized external fact that started the chain.
|
||||
|
||||
Examples:
|
||||
|
||||
- `agent.user.message`
|
||||
- `runtime.before_step`
|
||||
- `runtime.after_step`
|
||||
- `client.runtime.start`
|
||||
- `bot.message.merged`
|
||||
|
||||
Define source payloads in:
|
||||
|
||||
- `src/server/services/agentSignal/sourceTypes.ts`
|
||||
|
||||
Build normalized sources in:
|
||||
|
||||
- `src/server/services/agentSignal/sources/buildSource.ts`
|
||||
- `packages/agent-signal/src/base/builders.ts`
|
||||
|
||||
### Signal
|
||||
|
||||
A signal is a semantic interpretation. Signals should be reusable and meaning-oriented.
|
||||
|
||||
Examples from `analyzeIntent`:
|
||||
|
||||
- `signal.feedback.satisfaction`
|
||||
- `signal.feedback.domain.memory`
|
||||
- `signal.feedback.domain.prompt`
|
||||
- `signal.feedback.domain.skill`
|
||||
|
||||
Define server-owned signal types in:
|
||||
|
||||
- `src/server/services/agentSignal/policies/types.ts`
|
||||
|
||||
### Action
|
||||
|
||||
An action is a concrete side effect the runtime should execute.
|
||||
|
||||
Example:
|
||||
|
||||
- `action.user-memory.handle`
|
||||
|
||||
Action handlers usually:
|
||||
|
||||
- check idempotency
|
||||
- call tools, models, or services
|
||||
- return `ExecutorResult`
|
||||
|
||||
### Policy
|
||||
|
||||
A policy is an installable bundle of handlers. It is the composition unit that turns the generic runtime into a feature.
|
||||
|
||||
Example:
|
||||
|
||||
- `createAnalyzeIntentPolicy(...)`
|
||||
|
||||
### Procedure
|
||||
|
||||
"Procedure" is not a first-class type in this runtime. Use the word to describe one end-to-end use case:
|
||||
|
||||
1. define ingress source
|
||||
2. emit or enqueue the source
|
||||
3. interpret source into signals
|
||||
4. plan actions from signals
|
||||
5. execute actions
|
||||
6. persist trace and metrics
|
||||
|
||||
When a user asks for "the procedure", document the flow above and point to the exact producer, handlers, and execution entrypoint.
|
||||
|
||||
## Scope, Deduping, And Quiet Background Work
|
||||
|
||||
`scopeKey` is the serialization boundary for related work. It is used for:
|
||||
|
||||
- source dedupe windows
|
||||
- scope locks during source generation
|
||||
- runtime guard state
|
||||
- waypoint persistence for queued processing
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/sources/index.ts`
|
||||
- `src/server/services/agentSignal/runtime/context.ts`
|
||||
- `src/server/services/agentSignal/constants.ts`
|
||||
|
||||
Use `enqueueAgentSignalSourceEvent(...)` when the work should stay quiet and out-of-band. That path:
|
||||
|
||||
1. normalizes the source envelope
|
||||
2. derives or reuses `scopeKey`
|
||||
3. triggers `AgentSignalWorkflow`
|
||||
4. executes later in `runAgentSignalWorkflow`
|
||||
|
||||
This is the preferred path when the UI request should finish immediately and the policy can run in the background.
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/workflows/agentSignal/index.ts`
|
||||
- `src/server/workflows/agentSignal/run.ts`
|
||||
|
||||
## Existing Example: `analyzeIntent`
|
||||
|
||||
Use `analyzeIntent` as the reference chain:
|
||||
|
||||
```text
|
||||
agent.user.message
|
||||
-> feedback satisfaction source handler
|
||||
-> signal.feedback.satisfaction
|
||||
-> feedback domain signal handler
|
||||
-> signal.feedback.domain.*
|
||||
-> feedback action planner
|
||||
-> action.user-memory.handle
|
||||
-> signal.action.applied | skipped | failed
|
||||
```
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/index.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
|
||||
@@ -0,0 +1,228 @@
|
||||
# Writing Handlers And Policies
|
||||
|
||||
## Fluent Registration API
|
||||
|
||||
Use the middleware helpers in `src/server/services/agentSignal/runtime/middleware.ts`.
|
||||
|
||||
They provide:
|
||||
|
||||
- `defineSourceHandler(...)`
|
||||
- `defineSignalHandler(...)`
|
||||
- `defineActionHandler(...)`
|
||||
- `defineAgentSignalHandlers(...)`
|
||||
|
||||
These helpers do two jobs:
|
||||
|
||||
1. keep handler registration terse
|
||||
2. preserve strong typing when `listen` points at concrete source, signal, or action types
|
||||
|
||||
## Handler Shape
|
||||
|
||||
Each handler receives:
|
||||
|
||||
- the current runtime node
|
||||
- `RuntimeProcessorContext`
|
||||
|
||||
The context gives you:
|
||||
|
||||
- `scopeKey`
|
||||
- `now()`
|
||||
- `runtimeState.getGuardState(lane)`
|
||||
- `runtimeState.touchGuardState(lane, now?)`
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/runtime/context.ts`
|
||||
|
||||
## Return Contracts
|
||||
|
||||
Return one of these shapes:
|
||||
|
||||
- `void`: no fan-out, stop at this handler
|
||||
- `{ status: 'dispatch', signals?, actions? }`: continue the chain
|
||||
- `{ status: 'wait', pending? }`: pause for later host coordination
|
||||
- `{ status: 'schedule', nextHop }`: schedule another hop
|
||||
- `{ status: 'conclude', concluded? }`: stop with a terminal runtime result
|
||||
- `ExecutorResult`: only for action handlers that performed a concrete side effect
|
||||
|
||||
Read:
|
||||
|
||||
- `packages/agent-signal/src/base/types.ts`
|
||||
- `src/server/services/agentSignal/runtime/AgentSignalScheduler.ts`
|
||||
|
||||
## Policy Composition Pattern
|
||||
|
||||
Use `defineAgentSignalHandlers([...])` to bundle related handlers into one policy.
|
||||
|
||||
Example from `analyzeIntent`:
|
||||
|
||||
```ts
|
||||
return defineAgentSignalHandlers([
|
||||
createFeedbackSatisfactionJudgeProcessor(...),
|
||||
createFeedbackDomainJudgeSignalHandler(...),
|
||||
createFeedbackActionPlannerSignalHandler(),
|
||||
defineUserMemoryActionHandler(...),
|
||||
]);
|
||||
```
|
||||
|
||||
That bundle is later passed into the runtime via:
|
||||
|
||||
- `createDefaultAgentSignalPolicies(...)`
|
||||
- `createAgentSignalRuntime({ policies })`
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/policies/index.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/index.ts`
|
||||
|
||||
## Source Handler Pattern
|
||||
|
||||
Use a source handler when you are interpreting a producer event into semantic signals.
|
||||
|
||||
Reference:
|
||||
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.ts`
|
||||
|
||||
Pattern:
|
||||
|
||||
```ts
|
||||
return defineSourceHandler(
|
||||
AGENT_SIGNAL_SOURCE_TYPES.agentUserMessage,
|
||||
'agent.user.message:my-handler',
|
||||
async (source, ctx): Promise<RuntimeProcessorResult | void> => {
|
||||
// interpret source payload
|
||||
// optionally use ctx.runtimeState
|
||||
|
||||
return {
|
||||
signals: [
|
||||
/* one or more semantic signals */
|
||||
],
|
||||
status: 'dispatch',
|
||||
};
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Write source handlers when:
|
||||
|
||||
- a raw message, lifecycle event, or bot ingress needs interpretation
|
||||
- the work is still semantic, not side-effectful
|
||||
|
||||
## Signal Handler Pattern
|
||||
|
||||
Use a signal handler when one semantic state should branch into more semantic states or planned actions.
|
||||
|
||||
References:
|
||||
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackDomain.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/feedbackAction.ts`
|
||||
|
||||
Pattern:
|
||||
|
||||
```ts
|
||||
return defineSignalHandler(
|
||||
MY_SIGNAL_TYPE,
|
||||
'signal.my-policy-router',
|
||||
async (signal): Promise<RuntimeProcessorResult | void> => {
|
||||
return {
|
||||
actions: [
|
||||
/* planned work */
|
||||
],
|
||||
status: 'dispatch',
|
||||
};
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Use signal handlers for:
|
||||
|
||||
- routing
|
||||
- fan-out
|
||||
- filtering
|
||||
- conflict resolution
|
||||
- converting interpretation into planned actions
|
||||
|
||||
## Action Handler Pattern
|
||||
|
||||
Use an action handler when the runtime should do actual work.
|
||||
|
||||
Reference:
|
||||
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
|
||||
|
||||
Pattern:
|
||||
|
||||
```ts
|
||||
return defineActionHandler(
|
||||
MY_ACTION_TYPE,
|
||||
'action.my-policy-executor',
|
||||
async (action, ctx): Promise<ExecutorResult> => {
|
||||
// run service/tool/model side effect
|
||||
// check idempotency if needed
|
||||
|
||||
return {
|
||||
actionId: action.actionId,
|
||||
attempt: {
|
||||
completedAt: ctx.now(),
|
||||
current: 1,
|
||||
startedAt,
|
||||
status: 'succeeded',
|
||||
},
|
||||
status: 'applied',
|
||||
};
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Keep these rules:
|
||||
|
||||
- perform idempotency checks here or immediately before side effects
|
||||
- return stable `actionId`
|
||||
- include failure detail in `error`
|
||||
- let the scheduler turn the `ExecutorResult` into built-in result signals
|
||||
|
||||
## Source, Signal, And Action Type Placement
|
||||
|
||||
Use this split:
|
||||
|
||||
- external event payloads:
|
||||
`src/server/services/agentSignal/sourceTypes.ts`
|
||||
- policy-owned signal and action payloads:
|
||||
`src/server/services/agentSignal/policies/types.ts`
|
||||
- normalized shared node contracts:
|
||||
`packages/agent-signal/src/base/types.ts`
|
||||
|
||||
Do not put app-specific signal catalogs into `packages/agent-signal`. That package should stay generic and reusable.
|
||||
|
||||
## Choosing The Right Node
|
||||
|
||||
Choose `source` when:
|
||||
|
||||
- the outside world emitted a new fact
|
||||
|
||||
Choose `signal` when:
|
||||
|
||||
- the system needs semantic meaning that downstream handlers can reuse
|
||||
|
||||
Choose `action` when:
|
||||
|
||||
- the runtime is ready for a concrete side effect
|
||||
|
||||
If a handler both interprets meaning and performs side effects, split it. That keeps chains inspectable and testable.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Prefer focused tests near the touched code.
|
||||
|
||||
Useful references:
|
||||
|
||||
- `src/server/services/agentSignal/runtime/__tests__/AgentSignalRuntime.test.ts`
|
||||
- `src/server/services/agentSignal/__tests__/index.integration.test.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/__tests__/*`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/actions/__tests__/*`
|
||||
|
||||
Test at the smallest level that proves the behavior:
|
||||
|
||||
- handler unit test for one routing rule
|
||||
- runtime test for queue fan-out
|
||||
- integration test for service ingress and observability persistence
|
||||
@@ -0,0 +1,118 @@
|
||||
# Observability And Debugging
|
||||
|
||||
## OTEL Ownership
|
||||
|
||||
Use `packages/observability-otel/src/modules/agent-signal/index.ts` for the shared tracer and metrics.
|
||||
|
||||
Available instruments:
|
||||
|
||||
- `tracer`
|
||||
- `sourceCounter`
|
||||
- `signalCounter`
|
||||
- `actionCounter`
|
||||
- `actionResultCounter`
|
||||
- `chainCounter`
|
||||
- `signalActionTransitionCounter`
|
||||
- `chainDurationHistogram`
|
||||
- `actionDurationHistogram`
|
||||
|
||||
Use this module when you need shared telemetry ownership instead of creating feature-local meters or tracers.
|
||||
|
||||
## Projection Pipeline
|
||||
|
||||
After runtime execution, the service projects one compact observability model from the full chain.
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/observability/projector.ts`
|
||||
- `src/server/services/agentSignal/observability/traceEvents.ts`
|
||||
- `src/server/services/agentSignal/observability/store.ts`
|
||||
|
||||
Projection outputs:
|
||||
|
||||
- a trace envelope with source, signals, actions, results, edges, and handler runs
|
||||
- a compact telemetry record with dominant path, status breakdown, and chain metadata
|
||||
|
||||
This projection is built from:
|
||||
|
||||
- source node
|
||||
- emitted signals
|
||||
- planned actions
|
||||
- executor results
|
||||
|
||||
## How To Inspect A Chain
|
||||
|
||||
Use this order:
|
||||
|
||||
1. Inspect the source type and payload.
|
||||
2. Inspect emitted signals.
|
||||
3. Inspect planned actions.
|
||||
4. Inspect executor results.
|
||||
5. Inspect projected edges and dominant path.
|
||||
|
||||
The helper `toAgentSignalTraceEvents(...)` flattens a chain into compact event records suitable for tracing snapshots.
|
||||
|
||||
## Workflow Snapshot Bridge
|
||||
|
||||
Workflow-triggered runs do not naturally pass through the normal foreground runtime snapshot path, so `runAgentSignalWorkflow` adds a development-only bridge into `.agent-tracing/`.
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/workflows/agentSignal/run.ts`
|
||||
|
||||
Use that path when:
|
||||
|
||||
- the source was enqueued with `enqueueAgentSignalSourceEvent(...)`
|
||||
- you need local trace visibility for quiet background work
|
||||
|
||||
## Common Debug Questions
|
||||
|
||||
### The source emits but nothing happens
|
||||
|
||||
Check:
|
||||
|
||||
- feature gate enabled for the user
|
||||
- source type matches a registered source handler
|
||||
- dedupe or scope lock did not short-circuit generation
|
||||
|
||||
Read:
|
||||
|
||||
- `src/server/services/agentSignal/index.ts`
|
||||
- `src/server/services/agentSignal/sources/index.ts`
|
||||
|
||||
### The signal exists but no action runs
|
||||
|
||||
Check:
|
||||
|
||||
- the signal type has a registered signal handler
|
||||
- the signal handler returns `status: 'dispatch'`
|
||||
- the handler actually returned actions
|
||||
|
||||
### The action runs twice
|
||||
|
||||
Check:
|
||||
|
||||
- source dedupe key stability
|
||||
- action idempotency strategy
|
||||
- scope key stability across retries and workflow handoff
|
||||
|
||||
Reference:
|
||||
|
||||
- `src/server/services/agentSignal/policies/actionIdempotency.ts`
|
||||
- `src/server/services/agentSignal/policies/analyzeIntent/actions/userMemory.ts`
|
||||
|
||||
### Background runs are hard to discover
|
||||
|
||||
Check:
|
||||
|
||||
- workflow snapshot bridge in development
|
||||
- projected telemetry record contents
|
||||
- OTEL counters and histograms in the shared module
|
||||
|
||||
## Minimal Completion Checklist
|
||||
|
||||
- source ingress is testable
|
||||
- handler registration is discoverable from the policy factory
|
||||
- action executor returns structured results
|
||||
- projection includes the new path cleanly
|
||||
- tests cover at least one happy path and one no-op or failure path
|
||||
@@ -166,7 +166,7 @@ Each platform exposes a `PlatformDefinition` registered in `platforms/index.ts`:
|
||||
}
|
||||
```
|
||||
|
||||
`schema` drives both server validation (`mergeWithDefaults`, `extractDefaults`) **and** the auto-generated UI form. Top-level keys `applicationId` / `credentials` / `settings` map to DB columns. Common settings fields live in `platforms/const.ts` (`displayToolCallsField`, `serverIdField`, `userIdField`).
|
||||
`schema` drives both server validation (`mergeWithDefaults`, `extractDefaults`) **and** the auto-generated UI form. Top-level keys `applicationId` / `credentials` / `settings` map to DB columns. Common settings fields live in `platforms/const.ts` (`displayToolCallsField`, `makeServerIdField(platform?)`, `makeUserIdField(platform?)`). The `serverId` / `userId` factories take a platform identifier so the field's hint can render platform-specific "how to find this ID" guidance (Discord Developer Mode, Telegram @userinfobot, etc.); pass no argument to fall back to generic copy.
|
||||
|
||||
Each platform implements `PlatformClient` (see `platforms/types.ts`):
|
||||
|
||||
|
||||
@@ -8,16 +8,20 @@ Generate text, images, videos, speech, and transcriptions.
|
||||
|
||||
```
|
||||
lh generate (alias: gen)
|
||||
├── text <prompt> # Text generation
|
||||
├── image <prompt> # Image generation
|
||||
├── video <prompt> # Video generation
|
||||
├── tts <text> # Text-to-speech
|
||||
├── asr <audioFile> # Audio-to-text (speech recognition)
|
||||
├── download <genId> <taskId> # Wait & download generation result
|
||||
├── status <genId> <taskId> # Check async task status
|
||||
└── list # List generation topics
|
||||
├── text <prompt> # Text generation
|
||||
├── image <prompt> # Image generation
|
||||
├── video <prompt> # Video generation
|
||||
├── tts <text> # Text-to-speech
|
||||
├── asr <audioFile> # Audio-to-text (speech recognition)
|
||||
├── download <generationId> <asyncTaskId> # Wait & download generation result
|
||||
├── status <generationId> <asyncTaskId> # Check async task status
|
||||
└── list # List generation topics
|
||||
```
|
||||
|
||||
> ⚠️ **Important**: `status` and `download` require an `asyncTaskId` (UUID format, e.g.
|
||||
> `7ad0eb13-e9a5-4403-8070-1f7fe95b2f95`), **not** the generation ID (`gen_xxx`).
|
||||
> The asyncTaskId is printed after "→ Task" in the `video` / `image` command output.
|
||||
|
||||
---
|
||||
|
||||
## `lh generate text <prompt>` / `lh gen text <prompt>`
|
||||
@@ -54,7 +58,7 @@ cat README.md | lh gen text "summarize this" --pipe
|
||||
|
||||
## `lh generate image <prompt>` / `lh gen image <prompt>`
|
||||
|
||||
Generate images from text prompt. This is an async operation — the command submits the task and returns a generation ID + task ID for tracking.
|
||||
Generate images from text prompt. This is an async operation — the command submits the task and returns a generation ID + async task ID for tracking.
|
||||
|
||||
**Source**: `apps/cli/src/commands/generate/image.ts`
|
||||
|
||||
@@ -80,17 +84,22 @@ lh gen image "A cute cat" --model dall-e-3 --provider openai --json
|
||||
✓ Image generation started
|
||||
Batch ID: gb_xxx
|
||||
1 image(s) queued
|
||||
Generation gen_xxx → Task <taskId>
|
||||
Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
This is the asyncTaskId — use this for status/download
|
||||
|
||||
Use "lh generate status <generationId> <taskId>" to check progress.
|
||||
Use "lh generate status <generationId> <asyncTaskId>" to check progress.
|
||||
```
|
||||
|
||||
**Typical workflow**:
|
||||
|
||||
```bash
|
||||
# Generate image, then wait & download
|
||||
# 1. Submit generation — note down BOTH IDs from the output
|
||||
lh gen image "A cute cat"
|
||||
lh gen download <generationId> <taskId> -o cat.png
|
||||
# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95
|
||||
|
||||
# 2. Wait & download using generationId + asyncTaskId (the UUID)
|
||||
lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o cat.png
|
||||
```
|
||||
|
||||
---
|
||||
@@ -102,7 +111,7 @@ Generate video from text prompt. This is an async operation.
|
||||
**Source**: `apps/cli/src/commands/generate/video.ts`
|
||||
|
||||
```bash
|
||||
lh gen video "A cat playing piano" -m < model > -p < provider > [options]
|
||||
lh gen video "A cat playing piano" -m <model> -p <provider> [options]
|
||||
```
|
||||
|
||||
| Option | Description | Required |
|
||||
@@ -122,9 +131,26 @@ lh gen video "A cat playing piano" -m < model > -p < provider > [options]
|
||||
```
|
||||
✓ Video generation started
|
||||
Batch ID: gb_xxx
|
||||
Generation gen_xxx → Task <taskId>
|
||||
Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
This is the asyncTaskId — use this for status/download
|
||||
|
||||
Use "lh generate status <generationId> <taskId>" to check progress.
|
||||
Use "lh generate status <generationId> <asyncTaskId>" to check progress.
|
||||
```
|
||||
|
||||
**Typical workflow**:
|
||||
|
||||
```bash
|
||||
# 1. Find available video models for a provider
|
||||
lh model list volcengine --json | grep -i seedance
|
||||
|
||||
# 2. Submit generation — note down BOTH IDs from the output
|
||||
lh gen video "A cat on a runway" -m doubao-seedance-2-0-260128 -p volcengine \
|
||||
--aspect-ratio 9:16 --duration 5 --resolution 1080p
|
||||
# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95
|
||||
|
||||
# 3. Wait & download using generationId + asyncTaskId (the UUID)
|
||||
lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o result.mp4 --timeout 600
|
||||
```
|
||||
|
||||
---
|
||||
@@ -153,15 +179,18 @@ lh gen asr recording.wav [options]
|
||||
|
||||
---
|
||||
|
||||
## `lh generate download <generationId> <taskId>`
|
||||
## `lh generate download <generationId> <asyncTaskId>`
|
||||
|
||||
Wait for an async generation task to complete and download the result file.
|
||||
|
||||
**Source**: `apps/cli/src/commands/generate/index.ts`
|
||||
|
||||
> ⚠️ `<asyncTaskId>` is the UUID printed after "→ Task" in the video/image output.
|
||||
> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error.
|
||||
|
||||
```bash
|
||||
lh gen download <generationId> <taskId> [-o output.png]
|
||||
lh gen download gen_xxx task_xxx -o ~/Desktop/result.mp4 --timeout 600
|
||||
lh gen download <generationId> <asyncTaskId> [-o output.png]
|
||||
lh gen download gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx -o ~/Desktop/result.mp4 --timeout 600
|
||||
```
|
||||
|
||||
| Option | Description | Default |
|
||||
@@ -175,30 +204,21 @@ lh gen download gen_xxx task_xxx -o ~/Desktop/result.mp4 --timeout 600
|
||||
1. Polls `generation.getGenerationStatus` at the specified interval
|
||||
2. Shows live progress: `⋯ Status: processing... (42s)`
|
||||
3. On success: downloads asset URL to local file
|
||||
4. On error: displays error message and exits
|
||||
4. On error / wrong ID: displays a clear message pointing to the correct ID format
|
||||
5. On timeout: suggests using `lh gen status` to check later
|
||||
|
||||
**Typical workflow**:
|
||||
|
||||
```bash
|
||||
# One-shot: generate and download
|
||||
lh gen image "A sunset"
|
||||
# Copy the generation ID and task ID from output
|
||||
lh gen download gen_xxx taskId_xxx -o sunset.png
|
||||
|
||||
# Video (longer timeout)
|
||||
lh gen video "A cat running" -m model -p provider
|
||||
lh gen download gen_xxx taskId_xxx -o cat.mp4 --timeout 600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `lh generate status <generationId> <taskId>`
|
||||
## `lh generate status <generationId> <asyncTaskId>`
|
||||
|
||||
Check the status of an async generation task.
|
||||
|
||||
> ⚠️ `<asyncTaskId>` is the UUID printed after "→ Task" in the video/image output.
|
||||
> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error.
|
||||
|
||||
```bash
|
||||
lh gen status <generationId> <taskId> [--json]
|
||||
lh gen status <generationId> <asyncTaskId> [--json]
|
||||
lh gen status gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
@@ -235,12 +255,17 @@ Image and video generation use an async task pattern:
|
||||
- Triggers async background task (image via `createAsyncCaller`, video via `initModelRuntimeFromDB`)
|
||||
- Returns `{ data: { batch, generations }, success }` with `asyncTaskId` in each generation
|
||||
3. **Poll status** → `generation.getGenerationStatus`
|
||||
- Input: `{ generationId, asyncTaskId }` — both are required, and `asyncTaskId` must be the
|
||||
UUID from the `async_tasks` table, not `gen_xxx`
|
||||
- Returns `{ status, error, generation }` (generation includes asset URLs on success)
|
||||
- Before querying, calls `checkTimeoutTasks` which marks tasks as `error` if they have been
|
||||
`pending` or `processing` for more than ~5 minutes (`ASYNC_TASK_TIMEOUT = 298s`)
|
||||
|
||||
**Server routes**:
|
||||
|
||||
- `src/server/routers/lambda/image/index.ts` — image creation (uses `authedProcedure` + `serverDatabase`)
|
||||
- `src/server/routers/lambda/video/index.ts` — video creation (uses `authedProcedure` + `serverDatabase`)
|
||||
- `src/server/routers/lambda/generation.ts` — status checking
|
||||
- `packages/database/src/models/asyncTask.ts` — `AsyncTaskModel` including `checkTimeoutTasks`
|
||||
|
||||
**Note**: Image/video routes do NOT use the `keyVaults` middleware — they read API keys from the database via `initModelRuntimeFromDB` or `createAsyncCaller`.
|
||||
|
||||
@@ -30,6 +30,17 @@ This is NON-NEGOTIABLE. Skipping Linear comments is a workflow violation.
|
||||
|
||||
When creating issues with `mcp__linear-server__create_issue`, **MUST add the `claude code` label**.
|
||||
|
||||
## Language
|
||||
|
||||
Issue titles, descriptions, and comments **MUST follow the language of the current conversation**, not default to English.
|
||||
|
||||
- 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; 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.
|
||||
|
||||
@@ -117,7 +117,7 @@ it('should handle tool calls', async () => {
|
||||
toolCalls: [
|
||||
{
|
||||
id: 'call_123',
|
||||
name: 'lobe-web-browsing____search____builtin',
|
||||
name: 'lobe-web-browsing____search',
|
||||
arguments: JSON.stringify({ query: 'weather' }),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 🚀 LobeHub v2.1.54 (20260427)
|
||||
|
||||
**Hotfix Scope:** Agent topic-switching regression — stale chat state on agent change
|
||||
|
||||
> Clears residual topic state when navigating between agents and restores blank-canvas behavior on agent switch.
|
||||
|
||||
## 🐛 What's Fixed
|
||||
|
||||
- **Stale topic on agent switch** — Switching from `/agent/agt_A/tpc_X` to `/agent/agt_B` no longer leaves the previous topic's messages on screen, and _Start new topic_ responds again. (#14231)
|
||||
- **Header & sidebar consistency** — Conversation header now shows the active subtopic's title, and the sidebar keeps the parent topic's thread list expanded while a thread is open.
|
||||
|
||||
## ⚙️ Upgrade
|
||||
|
||||
- Self-hosted: pull the new image and restart. No schema or env changes.
|
||||
- Cloud: applied automatically.
|
||||
|
||||
## 👥 Owner
|
||||
|
||||
@{pr-author}
|
||||
|
||||
> **Note for Claude**: Replace `{pr-author}` with the actual PR author. Retrieve via `gh pr view <number> --json author --jq '.author.login'`. Do not hardcode a username.
|
||||
@@ -59,7 +59,10 @@ git push -u origin hotfix/v{version}-{short-hash}
|
||||
|
||||
2. **Create PR to main** with a gitmoji prefix title (e.g. `🐛 fix: description`)
|
||||
|
||||
3. **After merge**: auto-tag-release detects `hotfix/*` branch → auto patch +1.
|
||||
3. **Write a short hotfix changelog** — See `changelog-example/hotfix.md`. Keep it minimal: scope line, 1-3 fix bullets (symptom + fix in one sentence), upgrade note, owner. No long root-cause section — that lives in the commit message.
|
||||
- **Hotfix owner**: Use the actual PR author (retrieve via `gh pr view <number> --json author --jq '.author.login'`), never hardcode a username.
|
||||
|
||||
4. **After merge**: auto-tag-release detects `hotfix/*` branch → auto patch +1.
|
||||
|
||||
### Script
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
## Quick Reference by Name
|
||||
|
||||
- **@arvinxx**: Last resort only, mention for priority:high issues, tool calling, mcp, database
|
||||
- **@arvinxx**: General/uncategorized issues (default assignee), priority:high issues, tool calling, mcp, database
|
||||
- **@canisminor1990**: Design, UI components, editor, markdown rendering
|
||||
- **@tjx666**: Image/video generation, vision, cloud version, documentation, TTS, auth, login/register, database
|
||||
- **@ONLY-yours**: Performance, streaming, settings, general bugs, web platform, marketplace, agent builder, schedule task
|
||||
- **@tjx666**: Model providers and configuration, new model additions, image/video generation, vision, cloud version, documentation, TTS, auth, login/register, database
|
||||
- **@ONLY-yours**: Performance, streaming, settings, web platform, marketplace, agent builder, schedule task
|
||||
- **@Innei**: Knowledge base, files (KB-related), group chat, Electron, desktop client, build system
|
||||
- **@nekomeowww**: Memory, backend, deployment, DevOps, database
|
||||
- **@sudongyuer**: Mobile app (React Native)
|
||||
- **@sxjeru**: Model providers and configuration
|
||||
- **@rdmclin2**: Team workspace, IM and bot integration
|
||||
- **@tcmonster**: Subscription, refund, recharge, business cooperation
|
||||
|
||||
@@ -21,7 +20,7 @@ Quick reference for assigning issues based on labels.
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ---------------- | ------- | -------------------------------------------- |
|
||||
| All `provider:*` | @sxjeru | Model configuration and provider integration |
|
||||
| All `provider:*` | @tjx666 | Model configuration and provider integration |
|
||||
|
||||
### Platform Labels (platform:\*)
|
||||
|
||||
@@ -100,11 +99,10 @@ Quick reference for assigning issues based on labels.
|
||||
|
||||
1. **Specific feature owner** - e.g., `feature:knowledge-base` → @RiverTwilight
|
||||
2. **Platform owner** - e.g., `platform:mobile` → @sudongyuer
|
||||
3. **Provider owner** - e.g., `provider:*` → @sxjeru
|
||||
3. **Provider owner** - e.g., `provider:*` → @tjx666
|
||||
4. **Component owner** - e.g., 💄 Design → @canisminor1990
|
||||
5. **Infrastructure owner** - e.g., `deployment:*` → @nekomeowww
|
||||
6. **General maintainer** - @ONLY-yours for general bugs/issues
|
||||
7. **Last resort** - @arvinxx (only if no clear owner)
|
||||
6. **Default assignee** - @arvinxx for general/uncategorized issues
|
||||
|
||||
### Special Cases
|
||||
|
||||
@@ -121,8 +119,7 @@ Quick reference for assigning issues based on labels.
|
||||
|
||||
**No clear owner:**
|
||||
|
||||
- Assign to @ONLY-yours for general issues
|
||||
- Only mention @arvinxx if critical and truly unclear
|
||||
- Assign to @arvinxx for general issues
|
||||
|
||||
## Comment Templates
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ module.exports = defineConfig({
|
||||
],
|
||||
temperature: 0,
|
||||
saveImmediately: true,
|
||||
modelName: 'gpt-5.1-chat-latest',
|
||||
modelName: 'gpt-4o',
|
||||
experimental: {
|
||||
jsonMode: true,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* Manual E2E coverage for `lh agent space fs` against a real backend.
|
||||
*
|
||||
* Run when:
|
||||
* - A local or remote LobeHub backend is reachable by the CLI
|
||||
* - `AGENT_FS_E2E_AGENT_ID` points at an agent with document access
|
||||
*
|
||||
* Expects:
|
||||
* - The command creates and cleans up a temporary VFS directory
|
||||
* - This suite is skipped unless `AGENT_FS_E2E_AGENT_ID` is set
|
||||
*/
|
||||
const AGENT_ID = process.env.AGENT_FS_E2E_AGENT_ID;
|
||||
const CLI = process.env.LH_CLI_PATH || 'LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts';
|
||||
const TIMEOUT = 30_000;
|
||||
|
||||
function run(args: string): string {
|
||||
return execSync(`${CLI} ${args}`, {
|
||||
encoding: 'utf8',
|
||||
env: { ...process.env, PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}` },
|
||||
timeout: TIMEOUT,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
describe.skipIf(!AGENT_ID)('lh agent space fs unified VFS - manual E2E', () => {
|
||||
const testRoot = `agent:/vfs-cli-e2e-${Date.now()}`;
|
||||
|
||||
it('exercises root, mounted namespaces, writes, copy, move, trash, and cleanup', () => {
|
||||
const root = run(`agent space fs ls --agent-id ${AGENT_ID} agent:/`);
|
||||
expect(root).toContain('lobe/');
|
||||
|
||||
const mountedRoot = run(`agent space fs ls --agent-id ${AGENT_ID} agent:/lobe/skills`);
|
||||
expect(mountedRoot).toContain('builtin/');
|
||||
expect(mountedRoot).toContain('agent/');
|
||||
|
||||
try {
|
||||
expect(run(`agent space fs mkdir --agent-id ${AGENT_ID} --parents ${testRoot}`)).toContain(
|
||||
'created',
|
||||
);
|
||||
expect(
|
||||
run(
|
||||
`agent space fs write --agent-id ${AGENT_ID} --content "# VFS E2E" ${testRoot}/source.md`,
|
||||
),
|
||||
).toContain('created');
|
||||
expect(run(`agent space fs cat --agent-id ${AGENT_ID} ${testRoot}/source.md`)).toContain(
|
||||
'# VFS E2E',
|
||||
);
|
||||
expect(
|
||||
run(`agent space fs cp --agent-id ${AGENT_ID} ${testRoot}/source.md ${testRoot}/copied.md`),
|
||||
).toContain('copied');
|
||||
expect(
|
||||
run(`agent space fs mv --agent-id ${AGENT_ID} ${testRoot}/copied.md ${testRoot}/moved.md`),
|
||||
).toContain('moved');
|
||||
expect(run(`agent space fs rm --agent-id ${AGENT_ID} --yes ${testRoot}/moved.md`)).toContain(
|
||||
'deleted',
|
||||
);
|
||||
expect(run(`agent space fs trash ls --agent-id ${AGENT_ID} ${testRoot}`)).toContain(
|
||||
`${testRoot}/moved.md`,
|
||||
);
|
||||
expect(
|
||||
run(`agent space fs trash restore --agent-id ${AGENT_ID} ${testRoot}/moved.md`),
|
||||
).toContain('restored');
|
||||
} finally {
|
||||
try {
|
||||
run(`agent space fs rm --agent-id ${AGENT_ID} --yes --recursive ${testRoot}`);
|
||||
} catch {
|
||||
// Cleanup is best-effort because earlier assertions may fail before creation.
|
||||
}
|
||||
|
||||
try {
|
||||
run(`agent space fs trash rm --agent-id ${AGENT_ID} --yes --recursive --force ${testRoot}`);
|
||||
} catch {
|
||||
// Cleanup is best-effort because the trash entry may not exist.
|
||||
}
|
||||
}
|
||||
}, 60_000);
|
||||
});
|
||||
@@ -35,6 +35,7 @@
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/ws": "^8.18.1",
|
||||
"commander": "^13.1.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"debug": "^4.4.0",
|
||||
"diff": "^8.0.3",
|
||||
"fast-glob": "^3.3.3",
|
||||
|
||||
@@ -7,12 +7,14 @@ const CLIENT_ID = 'lobehub-cli';
|
||||
* Get a valid access token, refreshing if expired.
|
||||
* Returns null if no credentials or refresh fails.
|
||||
*/
|
||||
export async function getValidToken(): Promise<{ credentials: StoredCredentials } | null> {
|
||||
export async function getValidToken(
|
||||
bufferSeconds = 60,
|
||||
): Promise<{ credentials: StoredCredentials } | null> {
|
||||
const credentials = loadCredentials();
|
||||
if (!credentials) return null;
|
||||
|
||||
// Check if token is still valid (with 60s buffer)
|
||||
if (credentials.expiresAt && Date.now() / 1000 < credentials.expiresAt - 60) {
|
||||
// Check if token is still valid (with configurable buffer)
|
||||
if (credentials.expiresAt && Date.now() / 1000 < credentials.expiresAt - bufferSeconds) {
|
||||
return { credentials };
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,24 @@ const { mockTrpcClient } = vi.hoisted(() => ({
|
||||
updateAgentConfig: { mutate: vi.fn() },
|
||||
updateAgentPinned: { mutate: vi.fn() },
|
||||
},
|
||||
agentDocument: {
|
||||
copyDocumentByPath: { mutate: vi.fn() },
|
||||
deleteDocumentByPath: { mutate: vi.fn() },
|
||||
deleteDocumentPermanentlyByPath: { mutate: vi.fn() },
|
||||
statDocumentByPath: { query: vi.fn() },
|
||||
listDocumentsByPath: { query: vi.fn() },
|
||||
listTrashDocumentsByPath: { query: vi.fn() },
|
||||
mkdirDocumentByPath: { mutate: vi.fn() },
|
||||
readDocumentByPath: { query: vi.fn() },
|
||||
renameDocumentByPath: { mutate: vi.fn() },
|
||||
restoreDocumentFromTrashByPath: { mutate: vi.fn() },
|
||||
writeDocumentByPath: { mutate: vi.fn() },
|
||||
},
|
||||
agentSkills: {
|
||||
createSkill: { mutate: vi.fn() },
|
||||
deleteSkill: { mutate: vi.fn() },
|
||||
updateSkill: { mutate: vi.fn() },
|
||||
},
|
||||
aiAgent: {
|
||||
execAgent: { mutate: vi.fn() },
|
||||
getOperationStatus: { query: vi.fn() },
|
||||
@@ -41,6 +59,11 @@ const { mockStreamAgentEvents } = vi.hoisted(() => ({
|
||||
mockStreamAgentEvents: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockReplayAgentEvents, mockStreamAgentEventsViaWebSocket } = vi.hoisted(() => ({
|
||||
mockReplayAgentEvents: vi.fn(),
|
||||
mockStreamAgentEventsViaWebSocket: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockGetAgentStreamAuthInfo } = vi.hoisted(() => ({
|
||||
mockGetAgentStreamAuthInfo: vi.fn(),
|
||||
}));
|
||||
@@ -49,9 +72,18 @@ const { mockResolveLocalDeviceId } = vi.hoisted(() => ({
|
||||
mockResolveLocalDeviceId: vi.fn(),
|
||||
}));
|
||||
|
||||
const { mockReadStdinText } = vi.hoisted(() => ({
|
||||
mockReadStdinText: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('node:stream/consumers', () => ({ text: mockReadStdinText }));
|
||||
vi.mock('../api/client', () => ({ getTrpcClient: mockGetTrpcClient }));
|
||||
vi.mock('../api/http', () => ({ getAgentStreamAuthInfo: mockGetAgentStreamAuthInfo }));
|
||||
vi.mock('../utils/agentStream', () => ({ streamAgentEvents: mockStreamAgentEvents }));
|
||||
vi.mock('../utils/agentStream', () => ({
|
||||
replayAgentEvents: mockReplayAgentEvents,
|
||||
streamAgentEvents: mockStreamAgentEvents,
|
||||
streamAgentEventsViaWebSocket: mockStreamAgentEventsViaWebSocket,
|
||||
}));
|
||||
vi.mock('../utils/device', () => ({ resolveLocalDeviceId: mockResolveLocalDeviceId }));
|
||||
vi.mock('../utils/logger', () => ({
|
||||
log: { debug: vi.fn(), error: vi.fn(), heartbeat: vi.fn(), info: vi.fn(), warn: vi.fn() },
|
||||
@@ -71,12 +103,26 @@ describe('agent command', () => {
|
||||
serverUrl: 'https://example.com',
|
||||
});
|
||||
mockStreamAgentEvents.mockResolvedValue(undefined);
|
||||
mockReplayAgentEvents.mockReset();
|
||||
mockStreamAgentEventsViaWebSocket.mockReset();
|
||||
mockStreamAgentEventsViaWebSocket.mockResolvedValue(undefined);
|
||||
mockResolveLocalDeviceId.mockReset();
|
||||
mockReadStdinText.mockReset();
|
||||
for (const method of Object.values(mockTrpcClient.agent)) {
|
||||
for (const fn of Object.values(method)) {
|
||||
(fn as ReturnType<typeof vi.fn>).mockReset();
|
||||
}
|
||||
}
|
||||
for (const method of Object.values(mockTrpcClient.agentDocument)) {
|
||||
for (const fn of Object.values(method)) {
|
||||
(fn as ReturnType<typeof vi.fn>).mockReset();
|
||||
}
|
||||
}
|
||||
for (const method of Object.values(mockTrpcClient.agentSkills)) {
|
||||
for (const fn of Object.values(method)) {
|
||||
(fn as ReturnType<typeof vi.fn>).mockReset();
|
||||
}
|
||||
}
|
||||
for (const method of Object.values(mockTrpcClient.aiAgent)) {
|
||||
for (const fn of Object.values(method)) {
|
||||
(fn as ReturnType<typeof vi.fn>).mockReset();
|
||||
@@ -282,7 +328,7 @@ describe('agent command', () => {
|
||||
});
|
||||
|
||||
describe('run', () => {
|
||||
it('should exec agent and connect to SSE stream', async () => {
|
||||
it('should exec agent and connect to the gateway WebSocket stream by default', async () => {
|
||||
mockTrpcClient.aiAgent.execAgent.mutate.mockResolvedValue({
|
||||
operationId: 'op-123',
|
||||
success: true,
|
||||
@@ -304,11 +350,45 @@ describe('agent command', () => {
|
||||
expect(mockTrpcClient.aiAgent.execAgent.mutate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ agentId: 'a1', prompt: 'Hello' }),
|
||||
);
|
||||
expect(mockStreamAgentEventsViaWebSocket).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
gatewayUrl: expect.any(String),
|
||||
json: undefined,
|
||||
operationId: 'op-123',
|
||||
serverUrl: 'https://example.com',
|
||||
token: undefined,
|
||||
tokenType: undefined,
|
||||
verbose: undefined,
|
||||
}),
|
||||
);
|
||||
expect(mockStreamAgentEvents).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fall back to SSE when --sse is provided', async () => {
|
||||
mockTrpcClient.aiAgent.execAgent.mutate.mockResolvedValue({
|
||||
operationId: 'op-sse',
|
||||
success: true,
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'run',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--prompt',
|
||||
'Hello',
|
||||
'--sse',
|
||||
]);
|
||||
|
||||
expect(mockStreamAgentEvents).toHaveBeenCalledWith(
|
||||
'https://example.com/api/agent/stream?operationId=op-123',
|
||||
'https://example.com/api/agent/stream?operationId=op-sse',
|
||||
expect.objectContaining({ 'Oidc-Auth': 'test-token' }),
|
||||
expect.objectContaining({ json: undefined, verbose: undefined }),
|
||||
);
|
||||
expect(mockStreamAgentEventsViaWebSocket).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should support --slug option', async () => {
|
||||
mockTrpcClient.aiAgent.execAgent.mutate.mockResolvedValue({
|
||||
@@ -595,10 +675,8 @@ describe('agent command', () => {
|
||||
'--json',
|
||||
]);
|
||||
|
||||
expect(mockStreamAgentEvents).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(Object),
|
||||
expect.objectContaining({ json: true }),
|
||||
expect(mockStreamAgentEventsViaWebSocket).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ json: true, operationId: 'op-j' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -794,4 +872,540 @@ describe('agent command', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fs', () => {
|
||||
it('should list VFS entries from the unified agent root alias', async () => {
|
||||
mockTrpcClient.agentDocument.listDocumentsByPath.query.mockResolvedValue([
|
||||
{
|
||||
mode: 8,
|
||||
mount: { driver: 'synthetic', source: 'virtual' },
|
||||
name: 'writer',
|
||||
path: './lobe',
|
||||
type: 'directory',
|
||||
},
|
||||
]);
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'ls',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/',
|
||||
'--json',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.listDocumentsByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
cursor: undefined,
|
||||
limit: undefined,
|
||||
path: './',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify(
|
||||
[
|
||||
{
|
||||
mode: 8,
|
||||
mount: { driver: 'synthetic', source: 'virtual' },
|
||||
name: 'writer',
|
||||
path: './lobe',
|
||||
type: 'directory',
|
||||
},
|
||||
],
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass pagination options to VFS ls', async () => {
|
||||
mockTrpcClient.agentDocument.listDocumentsByPath.query.mockResolvedValue([]);
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'ls',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--cursor',
|
||||
'100',
|
||||
'--limit',
|
||||
'25',
|
||||
'agent:/notes',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.listDocumentsByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
cursor: '100',
|
||||
limit: 25,
|
||||
path: './notes',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should print unix-like long listings with ls -la', async () => {
|
||||
mockTrpcClient.agentDocument.listDocumentsByPath.query.mockResolvedValue([
|
||||
{
|
||||
mode: 14,
|
||||
name: '.config',
|
||||
path: './notes/.config',
|
||||
size: 0,
|
||||
type: 'directory',
|
||||
updatedAt: '2026-04-27T07:18:00',
|
||||
},
|
||||
{
|
||||
mode: 6,
|
||||
name: 'SOUL.md',
|
||||
path: './notes/SOUL.md',
|
||||
size: 399,
|
||||
type: 'file',
|
||||
updatedAt: '2026-04-27T07:19:00',
|
||||
},
|
||||
]);
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'ls',
|
||||
'-la',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes',
|
||||
]);
|
||||
|
||||
expect(consoleSpy).toHaveBeenNthCalledWith(1, 'total 1');
|
||||
expect(consoleSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.stringMatching(/^dr-x------ {2}1 agent {2}agent {4}0 --- -- --:-- \.$/),
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.stringMatching(/^dr-x------ {2}1 agent {2}agent {4}0 --- -- --:-- \.\.$/),
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
expect.stringMatching(/^drwx------ {2}1 agent {2}agent {4}0 Apr 27 07:18 \.config\/$/),
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenNthCalledWith(
|
||||
5,
|
||||
expect.stringMatching(/^-rw------- {2}1 agent {2}agent {2}399 Apr 27 07:19 SOUL\.md$/),
|
||||
);
|
||||
});
|
||||
|
||||
it('should expose VFS commands through agent space fs', async () => {
|
||||
mockTrpcClient.agentDocument.listDocumentsByPath.query.mockResolvedValue([]);
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'ls',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.listDocumentsByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
cursor: undefined,
|
||||
limit: undefined,
|
||||
path: './notes',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should collect tree traversal warnings instead of failing the whole tree', async () => {
|
||||
mockTrpcClient.agentDocument.listDocumentsByPath.query
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
mode: 8,
|
||||
name: 'builtin',
|
||||
path: './lobe/skills/builtin',
|
||||
type: 'directory',
|
||||
},
|
||||
])
|
||||
.mockRejectedValueOnce(new Error('Failed to list builtin skills'));
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'tree',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/lobe/skills',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.listDocumentsByPath.query).toHaveBeenNthCalledWith(1, {
|
||||
agentId: 'a1',
|
||||
path: './lobe/skills',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(mockTrpcClient.agentDocument.listDocumentsByPath.query).toHaveBeenNthCalledWith(2, {
|
||||
agentId: 'a1',
|
||||
path: './lobe/skills/builtin',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(log.warn).toHaveBeenCalledWith('./lobe/skills/builtin: Failed to list builtin skills');
|
||||
});
|
||||
|
||||
it('should read SKILL.md when cat targets a skill directory alias', async () => {
|
||||
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
||||
mockTrpcClient.agentDocument.statDocumentByPath.query.mockResolvedValue({
|
||||
content: '# Writer',
|
||||
mode: 2,
|
||||
mount: { driver: 'skills', namespace: 'builtin', source: 'builtin' },
|
||||
name: 'SKILL.md',
|
||||
path: './lobe/skills/builtin/skills/writer/SKILL.md',
|
||||
type: 'file',
|
||||
});
|
||||
mockTrpcClient.agentDocument.readDocumentByPath.query.mockResolvedValue({
|
||||
content: '# Writer',
|
||||
contentType: 'text/markdown',
|
||||
path: './lobe/skills/builtin/skills/writer/SKILL.md',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'cat',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'builtin:/writer',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.statDocumentByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './lobe/skills/builtin/skills/writer/SKILL.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(mockTrpcClient.agentDocument.readDocumentByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './lobe/skills/builtin/skills/writer/SKILL.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(stdoutSpy).toHaveBeenCalledWith('# Writer');
|
||||
stdoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should create a writable skill through touch when the path does not exist', async () => {
|
||||
mockTrpcClient.agentDocument.statDocumentByPath.query.mockRejectedValue({
|
||||
data: { code: 'NOT_FOUND' },
|
||||
});
|
||||
mockTrpcClient.agentDocument.writeDocumentByPath.mutate.mockResolvedValue({
|
||||
path: './lobe/skills/agent/skills/writer/SKILL.md',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'touch',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'skills:/writer',
|
||||
'--content',
|
||||
'# Writer',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.writeDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
content: '# Writer',
|
||||
createMode: 'if-missing',
|
||||
path: './lobe/skills/agent/skills/writer',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should read write content from stdin when no content option is provided', async () => {
|
||||
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY');
|
||||
Object.defineProperty(process.stdin, 'isTTY', { configurable: true, value: false });
|
||||
mockReadStdinText.mockResolvedValue('# Piped Content');
|
||||
mockTrpcClient.agentDocument.statDocumentByPath.query.mockRejectedValue({
|
||||
data: { code: 'NOT_FOUND' },
|
||||
});
|
||||
mockTrpcClient.agentDocument.writeDocumentByPath.mutate.mockResolvedValue({
|
||||
path: './notes/piped.md',
|
||||
});
|
||||
|
||||
try {
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'write',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes/piped.md',
|
||||
]);
|
||||
|
||||
expect(mockReadStdinText).toHaveBeenCalledWith(process.stdin);
|
||||
expect(mockTrpcClient.agentDocument.writeDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
content: '# Piped Content',
|
||||
createMode: 'if-missing',
|
||||
path: './notes/piped.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
} finally {
|
||||
if (stdinDescriptor) {
|
||||
Object.defineProperty(process.stdin, 'isTTY', stdinDescriptor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should create directories through the generic mkdir path API', async () => {
|
||||
mockTrpcClient.agentDocument.mkdirDocumentByPath.mutate.mockResolvedValue({
|
||||
path: './notes/archive',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'mkdir',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--parents',
|
||||
'agent:/notes/archive',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.mkdirDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './notes/archive',
|
||||
recursive: true,
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should stat unified root paths', async () => {
|
||||
mockTrpcClient.agentDocument.statDocumentByPath.query.mockResolvedValue({
|
||||
mode: 8,
|
||||
name: 'lobe',
|
||||
path: './lobe',
|
||||
type: 'directory',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'stat',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/lobe',
|
||||
'--json',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.statDocumentByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './lobe',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy paths through the generic copyDocumentByPath API', async () => {
|
||||
mockTrpcClient.agentDocument.copyDocumentByPath.mutate.mockResolvedValue({
|
||||
path: './notes/published.md',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'cp',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--force',
|
||||
'agent:/notes/draft.md',
|
||||
'agent:/notes/published.md',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.copyDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
force: true,
|
||||
fromPath: './notes/draft.md',
|
||||
toPath: './notes/published.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should rename paths through the generic renameDocumentByPath API', async () => {
|
||||
mockTrpcClient.agentDocument.renameDocumentByPath.mutate.mockResolvedValue({
|
||||
path: './notes/final.md',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'mv',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes/draft.md',
|
||||
'agent:/notes/final.md',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.renameDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
force: undefined,
|
||||
fromPath: './notes/draft.md',
|
||||
toPath: './notes/final.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should soft-delete paths through the generic deleteDocumentByPath API', async () => {
|
||||
mockTrpcClient.agentDocument.deleteDocumentByPath.mutate.mockResolvedValue({});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'rm',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--yes',
|
||||
'--recursive',
|
||||
'agent:/notes',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.deleteDocumentByPath.mutate).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
force: undefined,
|
||||
path: './notes',
|
||||
recursive: true,
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should list trash through the generic trash path API', async () => {
|
||||
mockTrpcClient.agentDocument.listTrashDocumentsByPath.query.mockResolvedValue([
|
||||
{ path: './notes/deleted.md' },
|
||||
]);
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'trash',
|
||||
'ls',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes',
|
||||
]);
|
||||
|
||||
expect(mockTrpcClient.agentDocument.listTrashDocumentsByPath.query).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './notes',
|
||||
topicId: undefined,
|
||||
});
|
||||
expect(consoleSpy).toHaveBeenCalledWith('agent:/notes/deleted.md');
|
||||
});
|
||||
|
||||
it('should restore trash entries through the generic trash restore API', async () => {
|
||||
mockTrpcClient.agentDocument.restoreDocumentFromTrashByPath.mutate.mockResolvedValue({
|
||||
path: './notes/deleted.md',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'trash',
|
||||
'restore',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'agent:/notes/deleted.md',
|
||||
]);
|
||||
|
||||
expect(
|
||||
mockTrpcClient.agentDocument.restoreDocumentFromTrashByPath.mutate,
|
||||
).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
path: './notes/deleted.md',
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should permanently delete trash entries through the generic trash rm API', async () => {
|
||||
mockTrpcClient.agentDocument.deleteDocumentPermanentlyByPath.mutate.mockResolvedValue({});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'agent',
|
||||
'space',
|
||||
'fs',
|
||||
'trash',
|
||||
'rm',
|
||||
'--agent-id',
|
||||
'a1',
|
||||
'--yes',
|
||||
'--force',
|
||||
'agent:/notes/deleted.md',
|
||||
]);
|
||||
|
||||
expect(
|
||||
mockTrpcClient.agentDocument.deleteDocumentPermanentlyByPath.mutate,
|
||||
).toHaveBeenCalledWith({
|
||||
agentId: 'a1',
|
||||
force: true,
|
||||
path: './notes/deleted.md',
|
||||
recursive: undefined,
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,33 +14,12 @@ import {
|
||||
import { resolveLocalDeviceId } from '../utils/device';
|
||||
import { confirm, outputJson, printTable, truncate } from '../utils/format';
|
||||
import { log, setVerbose } from '../utils/logger';
|
||||
|
||||
/**
|
||||
* Resolve an agent identifier (agentId or slug) to a concrete agentId.
|
||||
* When a slug is provided, uses getBuiltinAgent to look up the agent.
|
||||
*/
|
||||
async function resolveAgentId(
|
||||
client: any,
|
||||
opts: { agentId?: string; slug?: string },
|
||||
): Promise<string> {
|
||||
if (opts.agentId) return opts.agentId;
|
||||
|
||||
if (opts.slug) {
|
||||
const agent = await client.agent.getBuiltinAgent.query({ slug: opts.slug });
|
||||
if (!agent) {
|
||||
log.error(`Agent not found for slug: ${opts.slug}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return (agent as any).id || (agent as any).agentId;
|
||||
}
|
||||
|
||||
log.error('Either <agentId> or --slug is required.');
|
||||
process.exit(1);
|
||||
return ''; // unreachable
|
||||
}
|
||||
import { resolveAgentId } from './agent/resolveAgentId';
|
||||
import { registerAgentSpaceFsCommand } from './agent/spaceFs';
|
||||
|
||||
export function registerAgentCommand(program: Command) {
|
||||
const agent = program.command('agent').description('Manage agents');
|
||||
registerAgentSpaceFsCommand(agent);
|
||||
|
||||
// ── list ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { log } from '../../utils/logger';
|
||||
|
||||
interface AgentLookupClient {
|
||||
agent: {
|
||||
getBuiltinAgent: {
|
||||
query: (input: { slug: string }) => Promise<{ agentId?: string; id?: string } | null>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an agent identifier into a concrete agent id.
|
||||
*
|
||||
* Use when:
|
||||
* - A command accepts either a positional agent id or `--slug`.
|
||||
* - Downstream tRPC calls require the concrete agent id.
|
||||
*
|
||||
* Expects:
|
||||
* - `opts.agentId` to win over `opts.slug`.
|
||||
* - `client.agent.getBuiltinAgent` to resolve slugs when needed.
|
||||
*
|
||||
* Returns:
|
||||
* - The resolved agent id, or exits the process after logging a CLI-facing error.
|
||||
*/
|
||||
export async function resolveAgentId(
|
||||
client: AgentLookupClient,
|
||||
opts: { agentId?: string; slug?: string },
|
||||
): Promise<string> {
|
||||
if (opts.agentId) return opts.agentId;
|
||||
|
||||
if (opts.slug) {
|
||||
const agent = await client.agent.getBuiltinAgent.query({ slug: opts.slug });
|
||||
if (!agent) {
|
||||
log.error(`Agent not found for slug: ${opts.slug}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return agent.id || agent.agentId || '';
|
||||
}
|
||||
|
||||
log.error('Either <agentId> or --slug is required.');
|
||||
process.exit(1);
|
||||
return '';
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { text } from 'node:stream/consumers';
|
||||
|
||||
import type { Command } from 'commander';
|
||||
import dayjs from 'dayjs';
|
||||
import pc from 'picocolors';
|
||||
|
||||
import { getTrpcClient } from '../../api/client';
|
||||
import { confirm, outputJson } from '../../utils/format';
|
||||
import { log } from '../../utils/logger';
|
||||
import { resolveAgentId } from './resolveAgentId';
|
||||
|
||||
const SKILL_FILE_NAME = 'SKILL.md';
|
||||
|
||||
const SKILL_NAMESPACE_PREFIXES = {
|
||||
'agent': './lobe/skills/agent/skills',
|
||||
'builtin': './lobe/skills/builtin/skills',
|
||||
'installed-active': './lobe/skills/installed/active/skills',
|
||||
'installed-all': './lobe/skills/installed/all/skills',
|
||||
} as const;
|
||||
|
||||
const FS_PATH_ALIASES = {
|
||||
'agent': './',
|
||||
'builtin': 'builtin',
|
||||
'skills': 'agent',
|
||||
'installed-active': 'installed-active',
|
||||
'installed-all': 'installed-all',
|
||||
} as const;
|
||||
|
||||
type SkillFsNamespace = keyof typeof SKILL_NAMESPACE_PREFIXES;
|
||||
type AgentFsClient = Awaited<ReturnType<typeof getTrpcClient>>;
|
||||
|
||||
interface AgentFsContext {
|
||||
agentId: string;
|
||||
topicId?: string;
|
||||
}
|
||||
|
||||
interface AgentFsNode {
|
||||
content?: string;
|
||||
createdAt?: Date | string;
|
||||
mode?: number;
|
||||
mount?: {
|
||||
driver?: string;
|
||||
namespace?: string;
|
||||
};
|
||||
name: string;
|
||||
path: string;
|
||||
size?: number;
|
||||
type: 'directory' | 'file';
|
||||
updatedAt?: Date | string;
|
||||
}
|
||||
|
||||
interface AgentFsResolvedPath {
|
||||
filePath?: string;
|
||||
namespace?: SkillFsNamespace;
|
||||
path: string;
|
||||
skillName?: string;
|
||||
}
|
||||
|
||||
interface AgentFsOptions {
|
||||
agentId?: string;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
}
|
||||
|
||||
function getTrpcErrorCode(error: unknown): string | undefined {
|
||||
if (!error || typeof error !== 'object') return undefined;
|
||||
|
||||
const value = error as {
|
||||
data?: { code?: string };
|
||||
shape?: { data?: { code?: string } };
|
||||
};
|
||||
|
||||
return value.data?.code ?? value.shape?.data?.code;
|
||||
}
|
||||
|
||||
function exitWithError(message: string): never {
|
||||
log.error(message);
|
||||
process.exit(1);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
function resolveAgentFsPath(input = 'agent:/'): AgentFsResolvedPath {
|
||||
const raw = input.trim();
|
||||
|
||||
const aliasMatch = raw.match(/^([a-z-]+):(\/.*)?$/);
|
||||
|
||||
if (aliasMatch) {
|
||||
const alias = aliasMatch[1] as keyof typeof FS_PATH_ALIASES;
|
||||
const target = FS_PATH_ALIASES[alias];
|
||||
|
||||
if (!target) {
|
||||
exitWithError(
|
||||
`Unknown fs namespace "${aliasMatch[1]}". Use agent, skills, builtin, installed-all, or installed-active.`,
|
||||
);
|
||||
}
|
||||
|
||||
const suffix = aliasMatch[2]?.replace(/^\/+/, '').replace(/\/+$/, '') ?? '';
|
||||
const prefix = target === './' ? './' : SKILL_NAMESPACE_PREFIXES[target as SkillFsNamespace];
|
||||
|
||||
return resolveAgentFsPath(suffix ? `${prefix}/${suffix}` : prefix);
|
||||
}
|
||||
|
||||
if (raw === './' || raw === '.' || raw === '/') {
|
||||
return { path: './' };
|
||||
}
|
||||
|
||||
const match = Object.entries(SKILL_NAMESPACE_PREFIXES).find(([, prefix]) => {
|
||||
return raw === prefix || raw.startsWith(`${prefix}/`);
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
if (!raw.startsWith('./')) {
|
||||
exitWithError(`Invalid fs path "${input}". Use aliases like "agent:/" or a full VFS path.`);
|
||||
}
|
||||
|
||||
const normalizedPath = raw.replaceAll(/\/+/g, '/').replace(/\/+$/, '') || './';
|
||||
return { path: normalizedPath };
|
||||
}
|
||||
|
||||
const [namespace, prefix] = match as [
|
||||
SkillFsNamespace,
|
||||
(typeof SKILL_NAMESPACE_PREFIXES)[SkillFsNamespace],
|
||||
];
|
||||
const relativePath = raw.slice(prefix.length).replace(/^\/+/, '').replace(/\/+$/, '');
|
||||
|
||||
if (
|
||||
relativePath.includes('//') ||
|
||||
relativePath.split('/').some((segment) => segment === '.' || segment === '..')
|
||||
) {
|
||||
exitWithError(`Invalid fs path "${input}"`);
|
||||
}
|
||||
|
||||
if (!relativePath) {
|
||||
return { namespace, path: prefix };
|
||||
}
|
||||
|
||||
const separatorIndex = relativePath.indexOf('/');
|
||||
|
||||
if (separatorIndex < 0) {
|
||||
return {
|
||||
namespace,
|
||||
path: `${prefix}/${relativePath}`,
|
||||
skillName: relativePath,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
filePath: relativePath.slice(separatorIndex + 1),
|
||||
namespace,
|
||||
path: `${prefix}/${relativePath}`,
|
||||
skillName: relativePath.slice(0, separatorIndex),
|
||||
};
|
||||
}
|
||||
|
||||
function requireSkillNamespace(resolved: AgentFsResolvedPath): SkillFsNamespace {
|
||||
if (!resolved.namespace) {
|
||||
exitWithError(`Expected a skill namespace path, but received "${resolved.path}".`);
|
||||
}
|
||||
|
||||
return resolved.namespace;
|
||||
}
|
||||
|
||||
function canonicalSkillFilePath(resolved: AgentFsResolvedPath) {
|
||||
if (!resolved.skillName) {
|
||||
exitWithError('Expected a skill path, but received a namespace root.');
|
||||
}
|
||||
|
||||
if (resolved.filePath && resolved.filePath !== SKILL_FILE_NAME) {
|
||||
exitWithError(`Unsupported writable path "${resolved.path}". Only SKILL.md is mutable.`);
|
||||
}
|
||||
|
||||
return `${SKILL_NAMESPACE_PREFIXES[requireSkillNamespace(resolved)]}/${resolved.skillName}/${SKILL_FILE_NAME}`;
|
||||
}
|
||||
|
||||
function toDisplayPath(path: string) {
|
||||
if (path === './') return 'agent:/';
|
||||
if (path.startsWith('./') && path !== './lobe' && !path.startsWith('./lobe/')) {
|
||||
return `agent:/${path.slice(2)}`;
|
||||
}
|
||||
|
||||
for (const [namespace, prefix] of Object.entries(SKILL_NAMESPACE_PREFIXES) as Array<
|
||||
[SkillFsNamespace, string]
|
||||
>) {
|
||||
const alias = namespace === 'agent' ? 'skills' : namespace;
|
||||
if (path === prefix) return `${alias}:/`;
|
||||
if (path.startsWith(`${prefix}/`)) return `${alias}:/${path.slice(prefix.length + 1)}`;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function isWritableNode(node: { mode?: number }) {
|
||||
return ((node.mode ?? 0) & 4) !== 0;
|
||||
}
|
||||
|
||||
function parseOptionalPositiveInteger(value?: string) {
|
||||
if (value === undefined) return undefined;
|
||||
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
exitWithError(`Expected a positive integer, received "${value}".`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function formatFsNodeName(node: { mode?: number; name: string; type: 'directory' | 'file' }) {
|
||||
const suffix = node.type === 'directory' ? '/' : '';
|
||||
return isWritableNode(node) ? `${node.name}${suffix}` : pc.dim(`${node.name}${suffix}`);
|
||||
}
|
||||
|
||||
function getFsNodeDisplayName(node: Pick<AgentFsNode, 'name' | 'type'>) {
|
||||
if (node.name === '.' || node.name === '..') return node.name;
|
||||
|
||||
return `${node.name}${node.type === 'directory' ? '/' : ''}`;
|
||||
}
|
||||
|
||||
function getParentFsPath(path: string) {
|
||||
if (path === './') return './';
|
||||
|
||||
const segments = path.replace(/^\.\//, '').split('/').filter(Boolean);
|
||||
if (segments.length <= 1) return './';
|
||||
|
||||
return `./${segments.slice(0, -1).join('/')}`;
|
||||
}
|
||||
|
||||
function createSyntheticListingNode(name: '.' | '..', path: string): AgentFsNode {
|
||||
return {
|
||||
mode: 10,
|
||||
name,
|
||||
path,
|
||||
size: 0,
|
||||
type: 'directory',
|
||||
};
|
||||
}
|
||||
|
||||
function formatFsPermissions(node: Pick<AgentFsNode, 'mode' | 'type'>) {
|
||||
const mode = node.mode ?? 0;
|
||||
const canRead = (mode & 2) !== 0 || (mode & 8) !== 0;
|
||||
const canWrite = (mode & 4) !== 0;
|
||||
const canExecute = (mode & 1) !== 0 || (node.type === 'directory' && (mode & 8) !== 0);
|
||||
const owner = `${canRead ? 'r' : '-'}${canWrite ? 'w' : '-'}${canExecute ? 'x' : '-'}`;
|
||||
|
||||
return `${node.type === 'directory' ? 'd' : '-'}${owner}------`;
|
||||
}
|
||||
|
||||
function formatFsLongDate(value?: Date | string) {
|
||||
if (!value) return '--- -- --:--';
|
||||
|
||||
const date = dayjs(value);
|
||||
if (!date.isValid()) return '--- -- --:--';
|
||||
|
||||
return date.format('MMM DD HH:mm');
|
||||
}
|
||||
|
||||
function formatFsLongListing(nodes: AgentFsNode[]) {
|
||||
const sizeWidth = Math.max(1, ...nodes.map((node) => String(node.size ?? 0).length));
|
||||
const totalBlocks = nodes.reduce((total, node) => total + Math.ceil((node.size ?? 0) / 512), 0);
|
||||
const lines = [`total ${totalBlocks}`];
|
||||
|
||||
for (const node of nodes) {
|
||||
const size = String(node.size ?? 0).padStart(sizeWidth, ' ');
|
||||
const mtime = formatFsLongDate(node.updatedAt ?? node.createdAt);
|
||||
lines.push(
|
||||
`${formatFsPermissions(node)} 1 agent agent ${size} ${mtime} ${getFsNodeDisplayName(node)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
async function readFsContentInput(options: { content?: string; contentFile?: string }) {
|
||||
if (options.contentFile) {
|
||||
return readFileSync(options.contentFile, 'utf8');
|
||||
}
|
||||
|
||||
if (options.content !== undefined) return options.content;
|
||||
|
||||
// NOTICE:
|
||||
// CLI write commands should compose with shell pipelines without blocking interactive runs.
|
||||
// Node marks piped stdin with `isTTY === false`, while normal terminals are `true` or undefined in tests.
|
||||
// Remove this branch only if Commander gains first-class stdin option support for these commands.
|
||||
if (process.stdin.isTTY === false) return text(process.stdin);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
async function resolveAgentFsContext(client: AgentFsClient, options: AgentFsOptions) {
|
||||
const agentId = await resolveAgentId(client, options);
|
||||
return { agentId, topicId: options.topicId };
|
||||
}
|
||||
|
||||
async function getFsNode(client: AgentFsClient, context: AgentFsContext, path: string) {
|
||||
try {
|
||||
return (await client.agentDocument.statDocumentByPath.query({
|
||||
agentId: context.agentId,
|
||||
path,
|
||||
topicId: context.topicId,
|
||||
})) as AgentFsNode;
|
||||
} catch (error) {
|
||||
if (getTrpcErrorCode(error) === 'NOT_FOUND') return undefined;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function readFsFile(client: AgentFsClient, context: AgentFsContext, inputPath: string) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
const readPath =
|
||||
resolved.skillName && !resolved.filePath
|
||||
? `${SKILL_NAMESPACE_PREFIXES[requireSkillNamespace(resolved)]}/${resolved.skillName}/${SKILL_FILE_NAME}`
|
||||
: resolved.path;
|
||||
|
||||
const stat = await getFsNode(client, context, readPath);
|
||||
|
||||
if (!stat) {
|
||||
exitWithError(`Path not found: ${inputPath}`);
|
||||
}
|
||||
|
||||
if (stat.type !== 'file') {
|
||||
exitWithError(`Cannot read directory path: ${inputPath}`);
|
||||
}
|
||||
|
||||
const node = (await client.agentDocument.readDocumentByPath.query({
|
||||
agentId: context.agentId,
|
||||
path: readPath,
|
||||
topicId: context.topicId,
|
||||
})) as AgentFsNode;
|
||||
|
||||
return { node, resolved: resolveAgentFsPath(readPath) };
|
||||
}
|
||||
|
||||
async function writeFsFile(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
inputPath: string,
|
||||
content: string,
|
||||
) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
const existing = await getFsNode(
|
||||
client,
|
||||
context,
|
||||
resolved.skillName && !resolved.filePath ? canonicalSkillFilePath(resolved) : resolved.path,
|
||||
);
|
||||
const result = await client.agentDocument.writeDocumentByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
content,
|
||||
createMode: existing ? 'must-exist' : 'if-missing',
|
||||
path: resolved.path,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
|
||||
return {
|
||||
action: existing ? ('updated' as const) : ('created' as const),
|
||||
path: result?.path ?? resolved.path,
|
||||
};
|
||||
}
|
||||
|
||||
async function mkdirFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
inputPath: string,
|
||||
options?: { recursive?: boolean },
|
||||
) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
return client.agentDocument.mkdirDocumentByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
path: resolved.path,
|
||||
recursive: options?.recursive,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
inputPath: string,
|
||||
options?: { force?: boolean; recursive?: boolean },
|
||||
) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
return client.agentDocument.deleteDocumentByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
force: options?.force,
|
||||
path: resolved.path,
|
||||
recursive: options?.recursive,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function copyFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
source: string,
|
||||
destination: string,
|
||||
force?: boolean,
|
||||
) {
|
||||
const sourceResolved = resolveAgentFsPath(source);
|
||||
const destinationResolved = resolveAgentFsPath(destination);
|
||||
|
||||
return client.agentDocument.copyDocumentByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
force,
|
||||
fromPath: sourceResolved.path,
|
||||
toPath: destinationResolved.path,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function renameFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
source: string,
|
||||
destination: string,
|
||||
force?: boolean,
|
||||
) {
|
||||
const sourceResolved = resolveAgentFsPath(source);
|
||||
const destinationResolved = resolveAgentFsPath(destination);
|
||||
|
||||
return client.agentDocument.renameDocumentByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
force,
|
||||
fromPath: sourceResolved.path,
|
||||
toPath: destinationResolved.path,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function listTrashFsPath(client: AgentFsClient, context: AgentFsContext, inputPath?: string) {
|
||||
const resolved = resolveAgentFsPath(inputPath || 'agent:/');
|
||||
|
||||
return (await client.agentDocument.listTrashDocumentsByPath.query({
|
||||
agentId: context.agentId,
|
||||
path: resolved.path,
|
||||
topicId: context.topicId,
|
||||
})) as Array<Pick<AgentFsNode, 'path'>>;
|
||||
}
|
||||
|
||||
async function restoreTrashFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
inputPath: string,
|
||||
) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
return client.agentDocument.restoreDocumentFromTrashByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
path: resolved.path,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteTrashFsPath(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
inputPath: string,
|
||||
options?: { force?: boolean; recursive?: boolean },
|
||||
) {
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
return client.agentDocument.deleteDocumentPermanentlyByPath.mutate({
|
||||
agentId: context.agentId,
|
||||
force: options?.force,
|
||||
path: resolved.path,
|
||||
recursive: options?.recursive,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
}
|
||||
|
||||
async function printFsTree(
|
||||
client: AgentFsClient,
|
||||
context: AgentFsContext,
|
||||
path: string,
|
||||
prefix = '',
|
||||
warnings: string[] = [],
|
||||
) {
|
||||
let nodes: AgentFsNode[];
|
||||
|
||||
try {
|
||||
nodes = (await client.agentDocument.listDocumentsByPath.query({
|
||||
agentId: context.agentId,
|
||||
path,
|
||||
topicId: context.topicId,
|
||||
})) as AgentFsNode[];
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'failed to list path';
|
||||
warnings.push(`${toDisplayPath(path)}: ${message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [index, node] of nodes.entries()) {
|
||||
const last = index === nodes.length - 1;
|
||||
console.log(`${prefix}${last ? '└── ' : '├── '}${formatFsNodeName(node)}`);
|
||||
|
||||
if (node.type === 'directory') {
|
||||
await printFsTree(client, context, node.path, `${prefix}${last ? ' ' : '│ '}`, warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerFsCommands(fsCommand: Command) {
|
||||
fsCommand
|
||||
.command('ls [path]')
|
||||
.description('List VFS entries')
|
||||
.option('-a, --all', 'Include hidden entries')
|
||||
.option('-l, --long', 'Use long listing format')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('--cursor <cursor>', 'Directory pagination cursor')
|
||||
.option('-L, --limit <n>', 'Maximum number of entries')
|
||||
.option('--json [fields]', 'Output JSON, optionally specify fields (comma-separated)')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string | undefined,
|
||||
options: {
|
||||
agentId?: string;
|
||||
all?: boolean;
|
||||
cursor?: string;
|
||||
json?: string | boolean;
|
||||
limit?: string;
|
||||
long?: boolean;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
},
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const resolved = resolveAgentFsPath(inputPath || 'agent:/');
|
||||
|
||||
const nodes = ((await client.agentDocument.listDocumentsByPath.query({
|
||||
agentId: context.agentId,
|
||||
cursor: options.cursor,
|
||||
limit: parseOptionalPositiveInteger(options.limit),
|
||||
path: resolved.path,
|
||||
topicId: context.topicId,
|
||||
})) ?? []) as AgentFsNode[];
|
||||
const filtered = options.all ? nodes : nodes.filter((node) => !node.name.startsWith('.'));
|
||||
|
||||
if (options.json !== undefined) {
|
||||
const fields = typeof options.json === 'string' ? options.json : undefined;
|
||||
outputJson(filtered, fields);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.long) {
|
||||
const longNodes = options.all
|
||||
? [
|
||||
createSyntheticListingNode('.', resolved.path),
|
||||
createSyntheticListingNode('..', getParentFsPath(resolved.path)),
|
||||
...filtered,
|
||||
]
|
||||
: filtered;
|
||||
formatFsLongListing(longNodes).forEach((line) => console.log(line));
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach((node) => console.log(formatFsNodeName(node)));
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('tree [path]')
|
||||
.description('Print a tree view of the VFS')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string | undefined,
|
||||
options: { agentId?: string; slug?: string; topicId?: string },
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const resolved = resolveAgentFsPath(inputPath || 'agent:/');
|
||||
|
||||
console.log(pc.bold(toDisplayPath(resolved.path)));
|
||||
const warnings: string[] = [];
|
||||
await printFsTree(client, context, resolved.path, '', warnings);
|
||||
|
||||
for (const warning of warnings) {
|
||||
log.warn(warning);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('cat <path>')
|
||||
.description('Read a VFS file')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.action(
|
||||
async (inputPath: string, options: { agentId?: string; slug?: string; topicId?: string }) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const { node } = await readFsFile(client, context, inputPath);
|
||||
process.stdout.write(node.content ?? '');
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('stat <path>')
|
||||
.description('Show VFS node metadata')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('--json [fields]', 'Output JSON, optionally specify fields (comma-separated)')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: {
|
||||
agentId?: string;
|
||||
json?: string | boolean;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
},
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const resolved = resolveAgentFsPath(inputPath);
|
||||
|
||||
const node = await getFsNode(client, context, resolved.path);
|
||||
|
||||
if (!node) {
|
||||
exitWithError(`Path not found: ${inputPath}`);
|
||||
}
|
||||
|
||||
if (options.json !== undefined) {
|
||||
const fields = typeof options.json === 'string' ? options.json : undefined;
|
||||
outputJson(node, fields);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(node, null, 2));
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('touch <path>')
|
||||
.description('Create or update a VFS file')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-c, --content <content>', 'File content')
|
||||
.option('-F, --content-file <path>', 'Read content from a local file')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: {
|
||||
agentId?: string;
|
||||
content?: string;
|
||||
contentFile?: string;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
},
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const content = await readFsContentInput(options);
|
||||
const result = await writeFsFile(client, context, inputPath, content);
|
||||
console.log(`${pc.green('✓')} ${result.action} ${pc.bold(toDisplayPath(result.path))}`);
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('write <path>')
|
||||
.description('Write content to a VFS file')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-c, --content <content>', 'File content')
|
||||
.option('-F, --content-file <path>', 'Read content from a local file')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: {
|
||||
agentId?: string;
|
||||
content?: string;
|
||||
contentFile?: string;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
},
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const content = await readFsContentInput(options);
|
||||
const result = await writeFsFile(client, context, inputPath, content);
|
||||
console.log(`${pc.green('✓')} ${result.action} ${pc.bold(toDisplayPath(result.path))}`);
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('mkdir <path>')
|
||||
.description('Create a VFS directory')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-p, --parents', 'Create parent directories as needed')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: { agentId?: string; parents?: boolean; slug?: string; topicId?: string },
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const result = await mkdirFsPath(client, context, inputPath, {
|
||||
recursive: options.parents,
|
||||
});
|
||||
console.log(
|
||||
`${pc.green('✓')} created ${pc.bold(toDisplayPath(result?.path ?? inputPath))}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('rm <path>')
|
||||
.description('Delete a VFS node into trash')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-r, --recursive', 'Recursively delete a directory subtree')
|
||||
.option('-f, --force', 'Forward force semantics to the VFS delete primitive')
|
||||
.option('--yes', 'Skip confirmation prompt')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: {
|
||||
agentId?: string;
|
||||
force?: boolean;
|
||||
recursive?: boolean;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
yes?: boolean;
|
||||
},
|
||||
) => {
|
||||
if (!options.yes) {
|
||||
const confirmed = await confirm(`Delete ${inputPath}?`);
|
||||
if (!confirmed) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
await deleteFsPath(client, context, inputPath, {
|
||||
force: options.force,
|
||||
recursive: options.recursive,
|
||||
});
|
||||
console.log(`${pc.green('✓')} deleted ${pc.bold(inputPath)}`);
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('cp <source> <destination>')
|
||||
.description('Copy a VFS node')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-f, --force', 'Overwrite the destination if it exists')
|
||||
.action(
|
||||
async (
|
||||
source: string,
|
||||
destination: string,
|
||||
options: { agentId?: string; force?: boolean; slug?: string; topicId?: string },
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const result = await copyFsPath(client, context, source, destination, options.force);
|
||||
console.log(
|
||||
`${pc.green('✓')} copied ${pc.bold(source)} → ${pc.bold(toDisplayPath(result?.path ?? destination))}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
fsCommand
|
||||
.command('mv <source> <destination>')
|
||||
.description('Move or rename a VFS node')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-f, --force', 'Overwrite the destination if it exists')
|
||||
.action(
|
||||
async (
|
||||
source: string,
|
||||
destination: string,
|
||||
options: { agentId?: string; force?: boolean; slug?: string; topicId?: string },
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const sourceResolved = resolveAgentFsPath(source);
|
||||
const destinationResolved = resolveAgentFsPath(destination);
|
||||
|
||||
if (sourceResolved.path === destinationResolved.path) {
|
||||
console.log(`${pc.yellow('!')} source and destination are the same.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await renameFsPath(client, context, source, destination, options.force);
|
||||
console.log(
|
||||
`${pc.green('✓')} moved ${pc.bold(source)} → ${pc.bold(toDisplayPath(result?.path ?? destination))}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const trashCommand = fsCommand.command('trash').description('Operate on soft-deleted VFS nodes');
|
||||
|
||||
trashCommand
|
||||
.command('ls [path]')
|
||||
.description('List trashed VFS nodes')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('--json [fields]', 'Output JSON, optionally specify fields (comma-separated)')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string | undefined,
|
||||
options: {
|
||||
agentId?: string;
|
||||
json?: string | boolean;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
},
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const nodes = await listTrashFsPath(client, context, inputPath);
|
||||
|
||||
if (options.json !== undefined) {
|
||||
const fields = typeof options.json === 'string' ? options.json : undefined;
|
||||
outputJson(nodes, fields);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodes.length === 0) {
|
||||
console.log('Trash is empty.');
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.forEach((node) => console.log(toDisplayPath(node.path)));
|
||||
},
|
||||
);
|
||||
|
||||
trashCommand
|
||||
.command('restore <path>')
|
||||
.description('Restore a soft-deleted VFS node')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.action(
|
||||
async (inputPath: string, options: { agentId?: string; slug?: string; topicId?: string }) => {
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
const result = await restoreTrashFsPath(client, context, inputPath);
|
||||
console.log(
|
||||
`${pc.green('✓')} restored ${pc.bold(toDisplayPath(result?.path ?? inputPath))}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
trashCommand
|
||||
.command('rm <path>')
|
||||
.description('Permanently delete a trashed VFS node')
|
||||
.option('-A, --agent-id <id>', 'Agent ID')
|
||||
.option('-s, --slug <slug>', 'Agent slug')
|
||||
.option('-r, --recursive', 'Recursively delete a directory subtree')
|
||||
.option('-f, --force', 'Forward force semantics to the permanent delete primitive')
|
||||
.option('--yes', 'Skip confirmation prompt')
|
||||
.action(
|
||||
async (
|
||||
inputPath: string,
|
||||
options: {
|
||||
agentId?: string;
|
||||
force?: boolean;
|
||||
recursive?: boolean;
|
||||
slug?: string;
|
||||
topicId?: string;
|
||||
yes?: boolean;
|
||||
},
|
||||
) => {
|
||||
if (!options.yes) {
|
||||
const confirmed = await confirm(`Permanently delete ${inputPath}?`);
|
||||
if (!confirmed) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const client = await getTrpcClient();
|
||||
const context = await resolveAgentFsContext(client, options);
|
||||
await deleteTrashFsPath(client, context, inputPath, {
|
||||
force: options.force,
|
||||
recursive: options.recursive,
|
||||
});
|
||||
console.log(`${pc.green('✓')} permanently deleted ${pc.bold(inputPath)}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register agent document VFS commands under `agent space fs`.
|
||||
*
|
||||
* Use when:
|
||||
* - The CLI should expose filesystem-like operations for an agent document space.
|
||||
* - Command registration should stay outside the core `agent` command file.
|
||||
*
|
||||
* Expects:
|
||||
* - `agentCommand` to be the existing `agent` command group.
|
||||
*
|
||||
* Returns:
|
||||
* - Registered Commander subcommands.
|
||||
*/
|
||||
export function registerAgentSpaceFsCommand(agentCommand: Command) {
|
||||
const spaceCommand = agentCommand.command('space').description('Manage agent document space');
|
||||
const fsCommand = spaceCommand.command('fs').description('Operate on the agent document VFS');
|
||||
registerFsCommands(fsCommand);
|
||||
}
|
||||
@@ -7,7 +7,54 @@ import { confirm, outputJson, printBoxTable, printTable, timeAgo } from '../util
|
||||
import { log } from '../utils/logger';
|
||||
import { registerBotMessageCommands } from './botMessage';
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────
|
||||
// ── Access policy helpers ──────────────────────────────
|
||||
|
||||
const DM_POLICIES = ['open', 'allowlist', 'pairing', 'disabled'] as const;
|
||||
const GROUP_POLICIES = ['open', 'allowlist', 'disabled'] as const;
|
||||
type DmPolicy = (typeof DM_POLICIES)[number];
|
||||
type GroupPolicy = (typeof GROUP_POLICIES)[number];
|
||||
|
||||
interface AllowEntry {
|
||||
id: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an allow-list value into `{id, name?}[]`. Mirrors the server-side
|
||||
* back-compat parser — `settings.allowFrom` may be on disk as a comma-separated
|
||||
* string, a bare `string[]`, or the current `{id, name?}[]` shape. The CLI
|
||||
* needs the canonical form before push/filter operations and before sending
|
||||
* back to the server.
|
||||
*/
|
||||
function normalizeAllowList(raw: unknown): AllowEntry[] {
|
||||
if (typeof raw === 'string') {
|
||||
return raw
|
||||
.split(/[\s,]+/)
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
.map((id) => ({ id }));
|
||||
}
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const out: AllowEntry[] = [];
|
||||
for (const entry of raw) {
|
||||
if (typeof entry === 'string') {
|
||||
const id = entry.trim();
|
||||
if (id) out.push({ id });
|
||||
continue;
|
||||
}
|
||||
if (entry && typeof entry === 'object' && 'id' in entry) {
|
||||
const id = (entry as { id?: unknown }).id;
|
||||
if (typeof id !== 'string' || !id.trim()) continue;
|
||||
const name = (entry as { name?: unknown }).name;
|
||||
out.push(
|
||||
typeof name === 'string' && name.trim()
|
||||
? { id: id.trim(), name: name.trim() }
|
||||
: { id: id.trim() },
|
||||
);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function maskValue(val: string): string {
|
||||
if (val.length > 8) return val.slice(0, 4) + '****' + val.slice(-4);
|
||||
@@ -78,6 +125,150 @@ async function resolvePlatform(client: TrpcClient, platformId: string) {
|
||||
return def;
|
||||
}
|
||||
|
||||
// ── Allowlist subcommand factory ────────────────────────
|
||||
|
||||
interface AllowlistGroupOptions {
|
||||
/** Description shown by `lh bot <name> --help`. */
|
||||
description: string;
|
||||
/** Settings field to mutate — `allowFrom` (user IDs) or `groupAllowFrom` (channel IDs). */
|
||||
fieldKey: 'allowFrom' | 'groupAllowFrom';
|
||||
/** Human-friendly description of what the `<id>` arg represents. */
|
||||
idLabel: string;
|
||||
/** Subcommand group name (`allowlist` or `group-allowlist`). */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `list / add / remove / clear` subcommand group around an
|
||||
* array-typed settings field (`allowFrom` or `groupAllowFrom`). All write
|
||||
* paths read existing settings first and merge — passing only a partial
|
||||
* `settings` object to the TRPC `update` would replace the whole JSONB
|
||||
* column and silently drop unrelated fields.
|
||||
*/
|
||||
function registerAllowlistCommand(bot: Command, opts: AllowlistGroupOptions) {
|
||||
const group = bot.command(opts.name).description(opts.description);
|
||||
|
||||
// Read the current entries off a freshly-fetched bot row.
|
||||
const readEntries = (bot: any): AllowEntry[] =>
|
||||
normalizeAllowList((bot.settings as Record<string, unknown> | null)?.[opts.fieldKey]);
|
||||
|
||||
// Build the next settings payload from existing settings + the new entries.
|
||||
const buildPayload = (bot: any, nextEntries: AllowEntry[]) => ({
|
||||
id: bot.id,
|
||||
settings: {
|
||||
...(bot.settings as Record<string, unknown>),
|
||||
[opts.fieldKey]: nextEntries,
|
||||
},
|
||||
});
|
||||
|
||||
group
|
||||
.command('list <botId>')
|
||||
.description(`List ${opts.fieldKey} entries`)
|
||||
.option('--json', 'Output JSON')
|
||||
.action(async (botId: string, options: { json?: boolean }) => {
|
||||
const client = await getTrpcClient();
|
||||
const b = await findBot(client, botId);
|
||||
const entries = readEntries(b);
|
||||
|
||||
if (options.json) {
|
||||
outputJson(entries);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
console.log(`${pc.dim(`No ${opts.fieldKey} entries.`)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(
|
||||
entries.map((e) => [e.id, e.name ?? pc.dim('-')]),
|
||||
['ID', 'NAME'],
|
||||
);
|
||||
});
|
||||
|
||||
group
|
||||
.command('add <botId> <id>')
|
||||
.description(`Add a ${opts.idLabel} to ${opts.fieldKey}`)
|
||||
.option('--name <name>', 'Optional human-friendly label so you can recognise the entry later')
|
||||
.action(async (botId: string, id: string, options: { name?: string }) => {
|
||||
const trimmedId = id.trim();
|
||||
if (!trimmedId) {
|
||||
log.error('ID cannot be empty.');
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await getTrpcClient();
|
||||
const b = await findBot(client, botId);
|
||||
const entries = readEntries(b);
|
||||
|
||||
if (entries.some((e) => e.id === trimmedId)) {
|
||||
log.info(`${trimmedId} is already on the ${opts.fieldKey} list — nothing to do.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedName = options.name?.trim();
|
||||
const next = [
|
||||
...entries,
|
||||
trimmedName ? { id: trimmedId, name: trimmedName } : { id: trimmedId },
|
||||
];
|
||||
|
||||
await client.agentBotProvider.update.mutate(buildPayload(b, next) as any);
|
||||
console.log(
|
||||
`${pc.green('✓')} Added ${pc.bold(trimmedId)}${trimmedName ? ` (${trimmedName})` : ''} to ${opts.fieldKey} (now ${next.length} entr${next.length === 1 ? 'y' : 'ies'})`,
|
||||
);
|
||||
});
|
||||
|
||||
group
|
||||
.command('remove <botId> <id>')
|
||||
.description(`Remove a ${opts.idLabel} from ${opts.fieldKey}`)
|
||||
.action(async (botId: string, id: string) => {
|
||||
const trimmedId = id.trim();
|
||||
const client = await getTrpcClient();
|
||||
const b = await findBot(client, botId);
|
||||
const entries = readEntries(b);
|
||||
const next = entries.filter((e) => e.id !== trimmedId);
|
||||
|
||||
if (next.length === entries.length) {
|
||||
log.info(`${trimmedId} is not on the ${opts.fieldKey} list — nothing to do.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await client.agentBotProvider.update.mutate(buildPayload(b, next) as any);
|
||||
console.log(
|
||||
`${pc.green('✓')} Removed ${pc.bold(trimmedId)} from ${opts.fieldKey} (${next.length} entr${next.length === 1 ? 'y' : 'ies'} left)`,
|
||||
);
|
||||
});
|
||||
|
||||
group
|
||||
.command('clear <botId>')
|
||||
.description(`Clear all entries from ${opts.fieldKey}`)
|
||||
.option('--yes', 'Skip confirmation prompt')
|
||||
.action(async (botId: string, options: { yes?: boolean }) => {
|
||||
const client = await getTrpcClient();
|
||||
const b = await findBot(client, botId);
|
||||
const entries = readEntries(b);
|
||||
|
||||
if (entries.length === 0) {
|
||||
log.info(`${opts.fieldKey} is already empty — nothing to do.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.yes) {
|
||||
const confirmed = await confirm(
|
||||
`Clear all ${entries.length} ${opts.fieldKey} entr${entries.length === 1 ? 'y' : 'ies'} from this bot?`,
|
||||
);
|
||||
if (!confirmed) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await client.agentBotProvider.update.mutate(buildPayload(b, []) as any);
|
||||
console.log(`${pc.green('✓')} Cleared ${opts.fieldKey} on bot ${pc.bold(botId)}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Command Registration ─────────────────────────────────
|
||||
|
||||
export function registerBotCommand(program: Command) {
|
||||
@@ -313,6 +504,16 @@ export function registerBotCommand(program: Command) {
|
||||
.option('--verification-token <token>', 'New verification token')
|
||||
.option('--app-id <appId>', 'New application ID')
|
||||
.option('--platform <platform>', 'New platform')
|
||||
.option(
|
||||
'--dm-policy <policy>',
|
||||
`DM access policy (${DM_POLICIES.join('|')}). 'pairing' requires --user-id.`,
|
||||
)
|
||||
.option('--group-policy <policy>', `Group/channel access policy (${GROUP_POLICIES.join('|')})`)
|
||||
.option(
|
||||
'--user-id <id>',
|
||||
"Owner's platform user ID (required for --dm-policy=pairing; auto-trusts the operator in the global allowlist)",
|
||||
)
|
||||
.option('--server-id <id>', 'Default server / guild / workspace ID for AI tool calls')
|
||||
.action(
|
||||
async (
|
||||
botId: string,
|
||||
@@ -321,11 +522,15 @@ export function registerBotCommand(program: Command) {
|
||||
appSecret?: string;
|
||||
botId?: string;
|
||||
botToken?: string;
|
||||
dmPolicy?: string;
|
||||
encryptKey?: string;
|
||||
groupPolicy?: string;
|
||||
platform?: string;
|
||||
publicKey?: string;
|
||||
secretToken?: string;
|
||||
serverId?: string;
|
||||
signingSecret?: string;
|
||||
userId?: string;
|
||||
verificationToken?: string;
|
||||
webhookProxyUrl?: string;
|
||||
},
|
||||
@@ -342,6 +547,40 @@ export function registerBotCommand(program: Command) {
|
||||
if (options.appId) input.applicationId = options.appId;
|
||||
if (options.platform) input.platform = options.platform;
|
||||
|
||||
// ── Settings (DM / group policy + identity fields) ────────────
|
||||
// Read-modify-write so we don't wipe `allowFrom`, `groupAllowFrom`,
|
||||
// or any other settings field the operator already configured.
|
||||
const settingsPatch: Record<string, unknown> = {};
|
||||
if (options.dmPolicy !== undefined) {
|
||||
if (!(DM_POLICIES as readonly string[]).includes(options.dmPolicy)) {
|
||||
log.error(
|
||||
`Invalid --dm-policy "${options.dmPolicy}". Must be one of: ${DM_POLICIES.join(', ')}`,
|
||||
);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
settingsPatch.dmPolicy = options.dmPolicy as DmPolicy;
|
||||
}
|
||||
if (options.groupPolicy !== undefined) {
|
||||
if (!(GROUP_POLICIES as readonly string[]).includes(options.groupPolicy)) {
|
||||
log.error(
|
||||
`Invalid --group-policy "${options.groupPolicy}". Must be one of: ${GROUP_POLICIES.join(', ')}`,
|
||||
);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
settingsPatch.groupPolicy = options.groupPolicy as GroupPolicy;
|
||||
}
|
||||
if (options.userId !== undefined) settingsPatch.userId = options.userId;
|
||||
if (options.serverId !== undefined) settingsPatch.serverId = options.serverId;
|
||||
|
||||
if (Object.keys(settingsPatch).length > 0) {
|
||||
input.settings = {
|
||||
...(existing.settings as Record<string, unknown>),
|
||||
...settingsPatch,
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(input).length <= 1) {
|
||||
log.error('No changes specified.');
|
||||
process.exit(1);
|
||||
@@ -353,6 +592,22 @@ export function registerBotCommand(program: Command) {
|
||||
},
|
||||
);
|
||||
|
||||
// ── allowlist (DM / group user gate) ──────────────────
|
||||
|
||||
registerAllowlistCommand(bot, {
|
||||
description: 'Manage the global user allowlist (gates DMs and group @mentions)',
|
||||
fieldKey: 'allowFrom',
|
||||
idLabel: 'platform user ID',
|
||||
name: 'allowlist',
|
||||
});
|
||||
|
||||
registerAllowlistCommand(bot, {
|
||||
description: 'Manage the group/channel allowlist (used when groupPolicy=allowlist)',
|
||||
fieldKey: 'groupAllowFrom',
|
||||
idLabel: 'channel / group / thread ID',
|
||||
name: 'group-allowlist',
|
||||
});
|
||||
|
||||
// ── remove ────────────────────────────────────────────
|
||||
|
||||
bot
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Command } from 'commander';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../auth/refresh', () => ({
|
||||
getValidToken: vi.fn().mockResolvedValue({
|
||||
credentials: { accessToken: 'test-token', expiresAt: undefined, refreshToken: 'test-refresh' },
|
||||
}),
|
||||
}));
|
||||
vi.mock('../auth/resolveToken', () => ({
|
||||
resolveToken: vi.fn().mockResolvedValue({
|
||||
serverUrl: 'https://app.lobehub.com',
|
||||
@@ -83,16 +88,21 @@ vi.mock('@lobechat/device-gateway-client', () => ({
|
||||
on: vi.fn().mockImplementation((event: string, handler: (...args: any[]) => any) => {
|
||||
clientEventHandlers[event] = handler;
|
||||
}),
|
||||
reconnect: vi.fn().mockResolvedValue(undefined),
|
||||
sendSystemInfoResponse: vi.fn().mockImplementation((data: any) => {
|
||||
lastSentSystemInfoResponse = data;
|
||||
}),
|
||||
sendToolCallResponse: vi.fn().mockImplementation((data: any) => {
|
||||
lastSentToolResponse = data;
|
||||
}),
|
||||
updateToken: vi.fn(),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line import-x/first
|
||||
import { GatewayClient } from '@lobechat/device-gateway-client';
|
||||
|
||||
// eslint-disable-next-line import-x/first
|
||||
import { resolveToken } from '../auth/resolveToken';
|
||||
// eslint-disable-next-line import-x/first
|
||||
@@ -242,13 +252,33 @@ describe('connect command', () => {
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'connect']);
|
||||
|
||||
clientEventHandlers['auth_failed']?.('invalid token');
|
||||
await clientEventHandlers['auth_failed']?.('invalid token');
|
||||
|
||||
expect(log.error).toHaveBeenCalledWith(expect.stringContaining('Authentication failed'));
|
||||
expect(cleanupAllProcesses).toHaveBeenCalled();
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should retry auth_failed with token refresh when new token available', async () => {
|
||||
vi.mocked(resolveToken).mockResolvedValueOnce({
|
||||
serverUrl: 'https://app.lobehub.com',
|
||||
token: 'refreshed-token',
|
||||
tokenType: 'jwt',
|
||||
userId: 'test-user',
|
||||
});
|
||||
|
||||
const program = createProgram();
|
||||
await program.parseAsync(['node', 'test', 'connect']);
|
||||
|
||||
const mockClient = vi.mocked(GatewayClient).mock.results[0].value;
|
||||
|
||||
await clientEventHandlers['auth_failed']?.('token expired');
|
||||
|
||||
expect(log.info).toHaveBeenCalledWith(expect.stringContaining('Token refreshed'));
|
||||
expect(mockClient.updateToken).toHaveBeenCalledWith('refreshed-token');
|
||||
expect(exitSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle auth_expired', async () => {
|
||||
vi.mocked(resolveToken).mockResolvedValueOnce({
|
||||
serverUrl: 'https://app.lobehub.com',
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import { GatewayClient } from '@lobechat/device-gateway-client';
|
||||
import type { Command } from 'commander';
|
||||
|
||||
import { getValidToken } from '../auth/refresh';
|
||||
import { resolveToken } from '../auth/resolveToken';
|
||||
import { CLI_API_KEY_ENV } from '../constants/auth';
|
||||
import { OFFICIAL_GATEWAY_URL } from '../constants/urls';
|
||||
@@ -284,8 +285,44 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
|
||||
updateStatus('reconnecting');
|
||||
});
|
||||
|
||||
// Handle auth failed
|
||||
client.on('auth_failed', (reason) => {
|
||||
// Proactive token refresh — schedule before JWT expires
|
||||
const startProactiveRefresh = () =>
|
||||
scheduleProactiveRefresh(
|
||||
auth,
|
||||
(refreshed) => {
|
||||
client.updateToken(refreshed.token);
|
||||
auth = refreshed;
|
||||
// Schedule next refresh based on the new token
|
||||
cancelRefreshTimer = startProactiveRefresh();
|
||||
},
|
||||
info,
|
||||
error,
|
||||
);
|
||||
let cancelRefreshTimer = startProactiveRefresh();
|
||||
|
||||
// Handle auth failed — attempt token refresh once before giving up
|
||||
// (e.g., auto-reconnect may send an expired JWT before proactive refresh fires)
|
||||
let authFailedRefreshAttempted = false;
|
||||
client.on('auth_failed', async (reason) => {
|
||||
if (auth.tokenType === 'jwt' && !authFailedRefreshAttempted) {
|
||||
authFailedRefreshAttempted = true;
|
||||
info(`Authentication failed (${reason}). Attempting token refresh...`);
|
||||
try {
|
||||
const refreshed = await resolveToken({});
|
||||
if (refreshed && refreshed.token !== auth.token) {
|
||||
info('Token refreshed successfully. Reconnecting...');
|
||||
client.updateToken(refreshed.token);
|
||||
auth = refreshed;
|
||||
authFailedRefreshAttempted = false;
|
||||
cancelRefreshTimer = startProactiveRefresh();
|
||||
await client.reconnect();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
error(`Authentication failed: ${reason}`);
|
||||
error(
|
||||
`Run 'lh login', or set ${CLI_API_KEY_ENV} and run 'lh login --server <url>' to configure API key authentication.`,
|
||||
@@ -308,8 +345,8 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
|
||||
if (refreshed) {
|
||||
info('Token refreshed successfully. Reconnecting...');
|
||||
client.updateToken(refreshed.token);
|
||||
// Update cached auth so subsequent refreshes use the latest token
|
||||
auth = refreshed;
|
||||
cancelRefreshTimer = startProactiveRefresh();
|
||||
await client.reconnect();
|
||||
return;
|
||||
}
|
||||
@@ -330,6 +367,7 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
|
||||
// Graceful shutdown
|
||||
const cleanup = () => {
|
||||
info('Shutting down...');
|
||||
cancelRefreshTimer?.();
|
||||
cleanupAllProcesses();
|
||||
client.disconnect();
|
||||
removeStatus();
|
||||
@@ -374,6 +412,69 @@ function formatUptime(startedAt: Date): string {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
// How far before expiry to proactively refresh (1 hour)
|
||||
const PROACTIVE_REFRESH_BUFFER = 60 * 60;
|
||||
|
||||
/**
|
||||
* Parse the `exp` claim from a JWT without verifying the signature.
|
||||
*/
|
||||
function parseJwtExp(token: string): number | undefined {
|
||||
try {
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
|
||||
return typeof payload.exp === 'number' ? payload.exp : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a proactive token refresh before the JWT expires.
|
||||
* Returns a cleanup function that cancels the scheduled timer.
|
||||
*/
|
||||
function scheduleProactiveRefresh(
|
||||
auth: { token: string; tokenType: string },
|
||||
onRefreshed: (newAuth: Awaited<ReturnType<typeof resolveToken>>) => void,
|
||||
info: (msg: string) => void,
|
||||
error: (msg: string) => void,
|
||||
): (() => void) | null {
|
||||
if (auth.tokenType !== 'jwt') return null;
|
||||
|
||||
const exp = parseJwtExp(auth.token);
|
||||
if (!exp) return null;
|
||||
|
||||
const refreshAt = (exp - PROACTIVE_REFRESH_BUFFER) * 1000;
|
||||
const delay = refreshAt - Date.now();
|
||||
|
||||
if (delay < 0) {
|
||||
// Already past the refresh window — refresh immediately on next tick
|
||||
void doRefresh();
|
||||
return null;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => void doRefresh(), delay);
|
||||
return () => clearTimeout(timer);
|
||||
|
||||
async function doRefresh() {
|
||||
try {
|
||||
// Use the same buffer so getValidToken actually triggers a refresh
|
||||
const result = await getValidToken(PROACTIVE_REFRESH_BUFFER);
|
||||
if (!result) {
|
||||
error('Proactive token refresh failed — no valid credentials.');
|
||||
return;
|
||||
}
|
||||
|
||||
const refreshed = await resolveToken({});
|
||||
// Only notify if the token actually changed to avoid reschedule loops
|
||||
if (refreshed.token !== auth.token) {
|
||||
info('Proactively refreshed token.');
|
||||
onRefreshed(refreshed);
|
||||
}
|
||||
} catch {
|
||||
error('Proactive token refresh failed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectSystemInfo(): DeviceSystemInfo {
|
||||
const home = os.homedir();
|
||||
const platform = process.platform;
|
||||
|
||||
@@ -9,6 +9,61 @@ import { registerTextCommand } from './text';
|
||||
import { registerTtsCommand } from './tts';
|
||||
import { registerVideoCommand } from './video';
|
||||
|
||||
/**
|
||||
* Parse a tRPC/server error and return a user-friendly message for gen status/download.
|
||||
*
|
||||
* getGenerationStatus throws NOT_FOUND in two distinct cases:
|
||||
* 1. "Async task not found" → asyncTaskId is wrong (user passed gen_xxx instead of UUID)
|
||||
* 2. "Generation not found" → generationId is wrong
|
||||
*
|
||||
* INTERNAL_SERVER_ERROR with a message mentioning "async_tasks" also indicates a bad asyncTaskId
|
||||
* (e.g. the server SQL query fails when a non-UUID is passed).
|
||||
*/
|
||||
function parseGenStatusError(
|
||||
err: any,
|
||||
generationId: string,
|
||||
asyncTaskId: string,
|
||||
command: 'status' | 'download',
|
||||
): string | null {
|
||||
const code = err?.data?.code || err?.shape?.data?.code;
|
||||
const message: string = err?.message || err?.shape?.message || '';
|
||||
|
||||
const isAsyncTaskNotFound =
|
||||
(code === 'NOT_FOUND' && message.includes('Async task not found')) ||
|
||||
(code === 'INTERNAL_SERVER_ERROR' && message.includes('async_tasks'));
|
||||
|
||||
const isGenerationNotFound = code === 'NOT_FOUND' && message.includes('Generation not found');
|
||||
|
||||
if (isAsyncTaskNotFound) {
|
||||
return (
|
||||
`${pc.red('✗')} Async task not found: ${pc.bold(asyncTaskId)}\n` +
|
||||
`\n` +
|
||||
` The second argument must be the ${pc.bold('asyncTaskId')} — the UUID printed after\n` +
|
||||
` "→ Task" in the video/image output, not the generation ID (gen_xxx).\n` +
|
||||
`\n` +
|
||||
` Example output from "lh gen video":\n` +
|
||||
` Generation ${pc.bold('gen_abc123')} → Task ${pc.dim('7ad0eb13-e9a5-4403-8070-1f7fe95b2f95')}\n` +
|
||||
`\n` +
|
||||
` Correct usage:\n` +
|
||||
` ${pc.cyan(`lh gen ${command} gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95`)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (isGenerationNotFound) {
|
||||
return (
|
||||
`${pc.red('✗')} Generation not found: ${pc.bold(generationId)}\n` +
|
||||
`\n` +
|
||||
` The first argument must be the ${pc.bold('generationId')} (gen_xxx) from the\n` +
|
||||
` video/image output.\n` +
|
||||
`\n` +
|
||||
` Correct usage:\n` +
|
||||
` ${pc.cyan(`lh gen ${command} <generationId> <asyncTaskId>`)}`
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function registerGenerateCommand(program: Command) {
|
||||
const generate = program
|
||||
.command('generate')
|
||||
@@ -23,15 +78,26 @@ export function registerGenerateCommand(program: Command) {
|
||||
|
||||
// ── status ──────────────────────────────────────────
|
||||
generate
|
||||
.command('status <generationId> <taskId>')
|
||||
.command('status <generationId> <asyncTaskId>')
|
||||
.description('Check generation task status')
|
||||
.option('--json', 'Output raw JSON')
|
||||
.action(async (generationId: string, taskId: string, options: { json?: boolean }) => {
|
||||
.action(async (generationId: string, asyncTaskId: string, options: { json?: boolean }) => {
|
||||
const client = await getTrpcClient();
|
||||
const result = await client.generation.getGenerationStatus.query({
|
||||
asyncTaskId: taskId,
|
||||
generationId,
|
||||
});
|
||||
|
||||
let result: any;
|
||||
try {
|
||||
result = await client.generation.getGenerationStatus.query({
|
||||
asyncTaskId,
|
||||
generationId,
|
||||
});
|
||||
} catch (err: any) {
|
||||
const msg = parseGenStatusError(err, generationId, asyncTaskId, 'status');
|
||||
if (msg) {
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
@@ -53,7 +119,7 @@ export function registerGenerateCommand(program: Command) {
|
||||
|
||||
// ── download ──────────────────────────────────────────
|
||||
generate
|
||||
.command('download <generationId> <taskId>')
|
||||
.command('download <generationId> <asyncTaskId>')
|
||||
.description('Wait for generation to complete and download the result')
|
||||
.option('-o, --output <path>', 'Output file path (default: auto-detect from asset)')
|
||||
.option('--interval <sec>', 'Polling interval in seconds', '5')
|
||||
@@ -61,7 +127,7 @@ export function registerGenerateCommand(program: Command) {
|
||||
.action(
|
||||
async (
|
||||
generationId: string,
|
||||
taskId: string,
|
||||
asyncTaskId: string,
|
||||
options: { interval?: string; output?: string; timeout?: string },
|
||||
) => {
|
||||
const client = await getTrpcClient();
|
||||
@@ -73,10 +139,20 @@ export function registerGenerateCommand(program: Command) {
|
||||
|
||||
// Poll for completion
|
||||
while (true) {
|
||||
const result = (await client.generation.getGenerationStatus.query({
|
||||
asyncTaskId: taskId,
|
||||
generationId,
|
||||
})) as any;
|
||||
let result: any;
|
||||
try {
|
||||
result = await client.generation.getGenerationStatus.query({
|
||||
asyncTaskId,
|
||||
generationId,
|
||||
});
|
||||
} catch (err: any) {
|
||||
const msg = parseGenStatusError(err, generationId, asyncTaskId, 'download');
|
||||
if (msg) {
|
||||
console.error(`\n${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (result.status === 'success' && result.generation) {
|
||||
const gen = result.generation;
|
||||
@@ -125,7 +201,7 @@ export function registerGenerateCommand(program: Command) {
|
||||
console.log(
|
||||
`${pc.red('✗')} Timed out after ${options.timeout}s. Task still ${result.status}.`,
|
||||
);
|
||||
console.log(pc.dim(`Run "lh gen status ${generationId} ${taskId}" to check later.`));
|
||||
console.log(pc.dim(`Run "lh gen status ${generationId} ${asyncTaskId}" to check later.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"cookie": "^1.1.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"diff": "^8.0.4",
|
||||
"electron": "41.1.0",
|
||||
"electron": "41.3.0",
|
||||
"electron-builder": "^26.8.1",
|
||||
"electron-devtools-installer": "4.0.0",
|
||||
"electron-is": "^3.0.0",
|
||||
|
||||
@@ -725,7 +725,8 @@ export default class HeterogeneousAgentCtr extends ControllerModule {
|
||||
prompt: string,
|
||||
imageList: HeterogeneousAgentImageAttachment[] = [],
|
||||
): Promise<string> {
|
||||
const content: any[] = [{ text: prompt, type: 'text' }];
|
||||
const content: any[] = [];
|
||||
if (prompt && prompt.length > 0) content.push({ text: prompt, type: 'text' });
|
||||
|
||||
for (const image of imageList) {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { constants } from 'node:fs';
|
||||
import { access, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises';
|
||||
import { access, mkdir, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import {
|
||||
@@ -24,6 +24,9 @@ import {
|
||||
type PickFileResult,
|
||||
type PrepareSkillDirectoryParams,
|
||||
type PrepareSkillDirectoryResult,
|
||||
type ProjectFileIndexEntry,
|
||||
type ProjectFileIndexParams,
|
||||
type ProjectFileIndexResult,
|
||||
type RenameLocalFileResult,
|
||||
type ResolveSkillResourcePathParams,
|
||||
type ResolveSkillResourcePathResult,
|
||||
@@ -42,6 +45,7 @@ import {
|
||||
writeLocalFile,
|
||||
} from '@lobechat/local-file-shell';
|
||||
import { dialog, shell } from 'electron';
|
||||
import { execa } from 'execa';
|
||||
import { unzipSync } from 'fflate';
|
||||
|
||||
import { type FileResult, type SearchOptions } from '@/modules/fileSearch';
|
||||
@@ -81,6 +85,50 @@ const resolveNearestExistingRealPath = async (targetPath: string): Promise<strin
|
||||
}
|
||||
};
|
||||
|
||||
const toPosixRelativePath = (filePath: string) => filePath.split(path.sep).join('/');
|
||||
|
||||
const createProjectFileEntry = (
|
||||
root: string,
|
||||
absolutePath: string,
|
||||
isDirectory: boolean,
|
||||
): ProjectFileIndexEntry => {
|
||||
const relativePath = toPosixRelativePath(path.relative(root, absolutePath));
|
||||
|
||||
return {
|
||||
isDirectory,
|
||||
name: path.basename(absolutePath),
|
||||
path: absolutePath,
|
||||
relativePath: isDirectory ? `${relativePath}/` : relativePath,
|
||||
};
|
||||
};
|
||||
|
||||
const collectProjectDirectories = (files: string[], root: string): ProjectFileIndexEntry[] => {
|
||||
const directories = new Set<string>();
|
||||
|
||||
for (const filePath of files) {
|
||||
let current = path.dirname(filePath);
|
||||
while (current && current !== root && current.startsWith(`${root}${path.sep}`)) {
|
||||
if (directories.has(current)) break;
|
||||
directories.add(current);
|
||||
current = path.dirname(current);
|
||||
}
|
||||
}
|
||||
|
||||
return [...directories].map((directory) => createProjectFileEntry(root, directory, true));
|
||||
};
|
||||
|
||||
const createDetectedProjectFileEntry = async (
|
||||
root: string,
|
||||
absolutePath: string,
|
||||
): Promise<ProjectFileIndexEntry> => {
|
||||
try {
|
||||
const stats = await stat(absolutePath);
|
||||
return createProjectFileEntry(root, absolutePath, stats.isDirectory());
|
||||
} catch {
|
||||
return createProjectFileEntry(root, absolutePath, false);
|
||||
}
|
||||
};
|
||||
|
||||
const resolveSafePathRealPrefixes = async (): Promise<string[]> => {
|
||||
const prefixes = new Set<string>(SAFE_PATH_PREFIXES);
|
||||
|
||||
@@ -413,14 +461,127 @@ export default class LocalFileCtr extends ControllerModule {
|
||||
|
||||
// ==================== Search & Find ====================
|
||||
|
||||
@IpcMethod()
|
||||
async getProjectFileIndex(params: ProjectFileIndexParams = {}): Promise<ProjectFileIndexResult> {
|
||||
const requestedScope = params.scope || process.cwd();
|
||||
const startedAt = Date.now();
|
||||
|
||||
try {
|
||||
const rootResult = await execa(
|
||||
'git',
|
||||
['-C', requestedScope, 'rev-parse', '--show-toplevel'],
|
||||
{
|
||||
reject: false,
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
const root = rootResult.exitCode === 0 ? rootResult.stdout.trim() : requestedScope;
|
||||
|
||||
if (rootResult.exitCode === 0) {
|
||||
const [trackedResult, untrackedResult] = await Promise.all([
|
||||
execa(
|
||||
'git',
|
||||
['-C', root, '-c', 'core.quotepath=false', 'ls-files', '--recurse-submodules'],
|
||||
{
|
||||
reject: false,
|
||||
timeout: 10_000,
|
||||
},
|
||||
),
|
||||
execa(
|
||||
'git',
|
||||
[
|
||||
'-C',
|
||||
root,
|
||||
'-c',
|
||||
'core.quotepath=false',
|
||||
'ls-files',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
],
|
||||
{ reject: false, timeout: 10_000 },
|
||||
),
|
||||
]);
|
||||
|
||||
if (trackedResult.exitCode !== 0) {
|
||||
throw new Error(trackedResult.stderr || 'git ls-files failed');
|
||||
}
|
||||
|
||||
const files = [
|
||||
...trackedResult.stdout.split('\n'),
|
||||
...(untrackedResult.exitCode === 0 ? untrackedResult.stdout.split('\n') : []),
|
||||
]
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.map((relativePath) => path.resolve(root, relativePath));
|
||||
|
||||
const seen = new Set<string>();
|
||||
const fileEntries = files
|
||||
.filter((filePath) => {
|
||||
if (seen.has(filePath)) return false;
|
||||
seen.add(filePath);
|
||||
return true;
|
||||
})
|
||||
.map((filePath) => createProjectFileEntry(root, filePath, false));
|
||||
|
||||
const entries = [...collectProjectDirectories(files, root), ...fileEntries];
|
||||
logger.debug('Project file index built from git', {
|
||||
duration: Date.now() - startedAt,
|
||||
entries: entries.length,
|
||||
files: fileEntries.length,
|
||||
requestedScope,
|
||||
root,
|
||||
});
|
||||
|
||||
return {
|
||||
entries,
|
||||
indexedAt: new Date().toISOString(),
|
||||
root,
|
||||
source: 'git',
|
||||
totalCount: entries.length,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug('Git project file index failed, falling back to glob', {
|
||||
error,
|
||||
requestedScope,
|
||||
});
|
||||
}
|
||||
|
||||
const fallback = await this.searchService.glob({ pattern: '**/*', scope: requestedScope });
|
||||
const files = fallback.files.map((filePath) => path.resolve(filePath));
|
||||
const entries = await Promise.all(
|
||||
files.map((filePath) => createDetectedProjectFileEntry(requestedScope, filePath)),
|
||||
);
|
||||
|
||||
logger.debug('Project file index built from glob', {
|
||||
duration: Date.now() - startedAt,
|
||||
entries: entries.length,
|
||||
engine: fallback.engine,
|
||||
requestedScope,
|
||||
});
|
||||
|
||||
return {
|
||||
entries,
|
||||
indexedAt: new Date().toISOString(),
|
||||
root: requestedScope,
|
||||
source: 'glob',
|
||||
totalCount: entries.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle IPC event for local file search
|
||||
*/
|
||||
@IpcMethod()
|
||||
async handleLocalFilesSearch(params: LocalSearchFilesParams): Promise<FileResult[]> {
|
||||
const effectiveDirectory = params.directory ?? params.scope;
|
||||
|
||||
logger.debug('Received file search request:', {
|
||||
directory: params.directory,
|
||||
effectiveDirectory,
|
||||
limit: params.limit,
|
||||
keywords: params.keywords,
|
||||
scope: params.scope,
|
||||
});
|
||||
|
||||
// Build search options from params, mapping directory to onlyIn
|
||||
@@ -436,7 +597,7 @@ export default class LocalFileCtr extends ControllerModule {
|
||||
liveUpdate: params.liveUpdate,
|
||||
modifiedAfter: params.modifiedAfter ? new Date(params.modifiedAfter) : undefined,
|
||||
modifiedBefore: params.modifiedBefore ? new Date(params.modifiedBefore) : undefined,
|
||||
onlyIn: params.directory, // Map directory param to onlyIn option
|
||||
onlyIn: effectiveDirectory,
|
||||
sortBy: params.sortBy,
|
||||
sortDirection: params.sortDirection,
|
||||
};
|
||||
@@ -446,6 +607,14 @@ export default class LocalFileCtr extends ControllerModule {
|
||||
logger.debug('File search completed', {
|
||||
count: results.length,
|
||||
directory: params.directory,
|
||||
effectiveDirectory,
|
||||
results: results.slice(0, 5).map((result) => ({
|
||||
engine: result.engine,
|
||||
isDirectory: result.isDirectory,
|
||||
name: result.name,
|
||||
path: result.path,
|
||||
})),
|
||||
scope: params.scope,
|
||||
});
|
||||
return results;
|
||||
} catch (error) {
|
||||
|
||||
@@ -155,6 +155,9 @@ export default class NotificationCtr extends ControllerModule {
|
||||
const mainWindow = this.app.browserManager.getMainWindow();
|
||||
mainWindow.show();
|
||||
mainWindow.browserWindow.focus();
|
||||
if (params.navigate?.path) {
|
||||
mainWindow.broadcast('navigate', params.navigate);
|
||||
}
|
||||
});
|
||||
|
||||
notification.on('close', () => {
|
||||
|
||||
@@ -185,6 +185,7 @@ describe('HeterogeneousAgentCtr', () => {
|
||||
prompt: string,
|
||||
sessionOverrides: Record<string, any> = {},
|
||||
stdoutLines: string[] = [],
|
||||
sendPromptOverrides: Partial<{ imageList: Array<{ id: string; url: string }> }> = {},
|
||||
) => {
|
||||
const { proc, writes } = createFakeProc({ stdoutLines });
|
||||
nextFakeProc = proc;
|
||||
@@ -198,7 +199,7 @@ describe('HeterogeneousAgentCtr', () => {
|
||||
command: 'claude',
|
||||
...sessionOverrides,
|
||||
});
|
||||
await ctr.sendPrompt({ prompt, sessionId });
|
||||
await ctr.sendPrompt({ prompt, sessionId, ...sendPromptOverrides });
|
||||
|
||||
const { args: cliArgs, command, options } = spawnCalls[0];
|
||||
return { cliArgs, command, ctr, options, sessionId, writes };
|
||||
@@ -261,6 +262,23 @@ describe('HeterogeneousAgentCtr', () => {
|
||||
expect(options.cwd).toBe(explicitCwd);
|
||||
});
|
||||
|
||||
it('omits the empty text block when only images are attached', async () => {
|
||||
const { writes } = await runSendPrompt('', {}, [], {
|
||||
imageList: [{ id: 'image-1', url: 'data:image/png;base64,UE5HX1RFU1Q=' }],
|
||||
});
|
||||
|
||||
expect(writes).toHaveLength(1);
|
||||
const msg = JSON.parse(writes[0].trimEnd());
|
||||
// Anthropic rejects `{ text: '', type: 'text' }` with
|
||||
// "messages: text content blocks must be non-empty".
|
||||
expect(msg.message.content).toEqual([
|
||||
{
|
||||
source: { data: 'UE5HX1RFU1Q=', media_type: 'image/png', type: 'base64' },
|
||||
type: 'image',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('captures the Claude Code session id from stream-json init events', async () => {
|
||||
const { ctr, sessionId } = await runSendPrompt('hello', {}, [
|
||||
`${JSON.stringify({ session_id: 'sess_cc_123', subtype: 'init', type: 'system' })}\n`,
|
||||
|
||||
@@ -5,7 +5,8 @@ import { type App } from '@/core/App';
|
||||
|
||||
import LocalFileCtr from '../LocalFileCtr';
|
||||
|
||||
const { ipcMainHandleMock, fetchMock } = vi.hoisted(() => ({
|
||||
const { execaMock, ipcMainHandleMock, fetchMock } = vi.hoisted(() => ({
|
||||
execaMock: vi.fn(),
|
||||
ipcMainHandleMock: vi.fn(),
|
||||
fetchMock: vi.fn(),
|
||||
}));
|
||||
@@ -14,6 +15,10 @@ vi.mock('@/utils/net-fetch', () => ({
|
||||
netFetch: fetchMock,
|
||||
}));
|
||||
|
||||
vi.mock('execa', () => ({
|
||||
execa: execaMock,
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
@@ -535,6 +540,18 @@ describe('LocalFileCtr', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use scope as the default search directory', async () => {
|
||||
mockSearchService.search.mockResolvedValue([]);
|
||||
|
||||
await localFileCtr.handleLocalFilesSearch({ keywords: 'src', scope: '/workspace/project' });
|
||||
|
||||
expect(mockSearchService.search).toHaveBeenCalledWith('src', {
|
||||
keywords: 'src',
|
||||
limit: 30,
|
||||
onlyIn: '/workspace/project',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array on search error', async () => {
|
||||
mockSearchService.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
@@ -544,6 +561,94 @@ describe('LocalFileCtr', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProjectFileIndex', () => {
|
||||
it('should build a project file index from git files', async () => {
|
||||
execaMock
|
||||
.mockResolvedValueOnce({ exitCode: 0, stdout: '/workspace/project' })
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
stdout: 'src/index.ts\nsrc/components/Button.tsx',
|
||||
})
|
||||
.mockResolvedValueOnce({ exitCode: 0, stdout: 'tmp/local.ts' });
|
||||
|
||||
const result = await localFileCtr.getProjectFileIndex({ scope: '/workspace/project' });
|
||||
|
||||
expect(result.source).toBe('git');
|
||||
expect(result.root).toBe('/workspace/project');
|
||||
expect(result.entries).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
isDirectory: true,
|
||||
path: '/workspace/project/src',
|
||||
relativePath: 'src/',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
isDirectory: false,
|
||||
path: '/workspace/project/src/index.ts',
|
||||
relativePath: 'src/index.ts',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
isDirectory: false,
|
||||
path: '/workspace/project/tmp/local.ts',
|
||||
relativePath: 'tmp/local.ts',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result.totalCount).toBe(result.entries.length);
|
||||
});
|
||||
|
||||
it('should fall back to glob when git indexing fails', async () => {
|
||||
execaMock.mockResolvedValueOnce({ exitCode: 1, stdout: '' });
|
||||
mockSearchService.glob.mockResolvedValue({
|
||||
engine: 'fast-glob',
|
||||
files: ['/workspace/project/src', '/workspace/project/src/index.ts'],
|
||||
success: true,
|
||||
total_files: 2,
|
||||
});
|
||||
vi.mocked(mockFsPromises.stat).mockImplementation(async (filePath: string) => ({
|
||||
isDirectory: () => filePath === '/workspace/project/src',
|
||||
}));
|
||||
|
||||
const result = await localFileCtr.getProjectFileIndex({ scope: '/workspace/project' });
|
||||
|
||||
expect(result.source).toBe('glob');
|
||||
expect(result.entries).toEqual([
|
||||
expect.objectContaining({
|
||||
isDirectory: true,
|
||||
path: '/workspace/project/src',
|
||||
relativePath: 'src/',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
isDirectory: false,
|
||||
path: '/workspace/project/src/index.ts',
|
||||
relativePath: 'src/index.ts',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark glob entries as files when stat fails', async () => {
|
||||
execaMock.mockResolvedValueOnce({ exitCode: 1, stdout: '' });
|
||||
mockSearchService.glob.mockResolvedValue({
|
||||
engine: 'fast-glob',
|
||||
files: ['/workspace/project/src/index.ts'],
|
||||
success: true,
|
||||
total_files: 1,
|
||||
});
|
||||
vi.mocked(mockFsPromises.stat).mockRejectedValue(new Error('missing'));
|
||||
|
||||
const result = await localFileCtr.getProjectFileIndex({ scope: '/workspace/project' });
|
||||
|
||||
expect(result.source).toBe('glob');
|
||||
expect(result.entries).toEqual([
|
||||
expect.objectContaining({
|
||||
isDirectory: false,
|
||||
path: '/workspace/project/src/index.ts',
|
||||
relativePath: 'src/index.ts',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleGlobFiles', () => {
|
||||
it('should glob files successfully', async () => {
|
||||
const mockResult = {
|
||||
|
||||
@@ -2,5 +2,6 @@ export const BRANDING_LOGO_URL = '';
|
||||
export const BRANDING_NAME = 'LobeHub';
|
||||
export const DEFAULT_EMBEDDING_PROVIDER = 'openai';
|
||||
export const DEFAULT_MINI_PROVIDER = 'openai';
|
||||
export const DEFAULT_ONBOARDING_MODEL = 'gemini-3-flash-preview';
|
||||
export const DEFAULT_PROVIDER = 'openai';
|
||||
export const ORG_NAME = 'LobeHub';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"https://file.rene.wang/540830955-0fe626a3-0ddc-4f67-b595-3c5b3f1701e0.png": "/blog/assetsa8e504275f2cd891fabecca985998de0.webp",
|
||||
"https://file.rene.wang/changlog-04-14.png": "/blog/assets300abe7e259d293da6c5ed4f642a1be6.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",
|
||||
"https://file.rene.wang/clipboard-1768908121691-b3517bf882633.png": "/blog/assetsd3cae44cba0d3f57df6440b46246e5e7.webp",
|
||||
@@ -53,7 +52,6 @@
|
||||
"https://file.rene.wang/clipboard-1770266335710-1fec523143aab.png": "/blog/assets636c78daf95c590cd7d80284c68eb6d9.webp",
|
||||
"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-1775701725582-123f8f8cf73f8.png": "/blog/assets7ea204859aeb5aa9be5810a20ba1669a.webp",
|
||||
"https://file.rene.wang/clipboard-1776909505252-94b051f3ea0a7.png": "/blog/assetsdfda32866c4bc59af0526e52f31d1da2.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",
|
||||
@@ -470,5 +468,6 @@
|
||||
"https://github.com/user-attachments/assets/fa8fab19-ace2-4f85-8428-a3a0e28845bb": "/blog/assets/2d678631c55369ba7d753c3ffcb73782.webp",
|
||||
"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://github.com/user-attachments/assets/fd60ab55-ead2-4930-ad00-fdf77662f5a0": "/blog/assets276a4e8748e9bd300b30dcd9d0e24980.webp",
|
||||
"https://file.rene.wang/clipboard-1777343750668-9b3dcb0dfff86.png": "/blog/assetsfa267a02f20bc5ba6f1273bcf27b7c9f.webp"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
title: 'Plugin System: Extend Your Agents with Community Skills'
|
||||
description: LobeHub now supports a plugin ecosystem that lets Agents access real-time information, interact with external services, and handle specialized tasks without leaving the conversation.
|
||||
description: >-
|
||||
LobeHub now supports a plugin ecosystem that lets Agents access real-time
|
||||
information, interact with external services, and handle specialized tasks
|
||||
without leaving the conversation.
|
||||
tags:
|
||||
- LobeHub
|
||||
- Plugins
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '插件系统:用社区技能扩展你的助理'
|
||||
title: 插件系统:用社区技能扩展你的助理
|
||||
description: LobeHub 现已支持插件生态,让助理能够获取实时信息、与外部服务交互,并在对话中处理各种专业任务。
|
||||
tags:
|
||||
- LobeHub
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
title: 'Visual Recognition: Chat With Images, Not Just Text'
|
||||
description: LobeHub now supports multimodal models including GPT-4 Vision, Google Gemini Pro Vision, and GLM-4 Vision. Upload or drag images into conversations and your Agent will understand and respond to visual content.
|
||||
description: >-
|
||||
LobeHub now supports multimodal models including GPT-4 Vision, Google Gemini
|
||||
Pro Vision, and GLM-4 Vision. Upload or drag images into conversations and
|
||||
your Agent will understand and respond to visual content.
|
||||
tags:
|
||||
- Visual Recognition
|
||||
- LobeHub
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: '视觉识别:与图片对话,不只是文字'
|
||||
description: LobeHub 现已支持多模态模型,包括 GPT-4 Vision、Google Gemini Pro Vision 和 GLM-4 Vision。上传或拖拽图片到对话中,助理将理解视觉内容并作出回应。
|
||||
title: 视觉识别:与图片对话,不只是文字
|
||||
description: >-
|
||||
LobeHub 现已支持多模态模型,包括 GPT-4 Vision、Google Gemini Pro Vision 和 GLM-4
|
||||
Vision。上传或拖拽图片到对话中,助理将理解视觉内容并作出回应。
|
||||
tags:
|
||||
- 视觉识别
|
||||
- 多模态交互
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
title: 'Voice Conversations: Talk Naturally With Your Agents'
|
||||
description: LobeHub now supports Text-to-Speech (TTS) and Speech-to-Text (STT), enabling natural voice interactions. Speak with your Agents and hear responses in clear, personalized voices.
|
||||
description: >-
|
||||
LobeHub now supports Text-to-Speech (TTS) and Speech-to-Text (STT), enabling
|
||||
natural voice interactions. Speak with your Agents and hear responses in
|
||||
clear, personalized voices.
|
||||
tags:
|
||||
- TTS
|
||||
- STT
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '语音会话:与你的助理自然对话'
|
||||
title: 语音会话:与你的助理自然对话
|
||||
description: LobeHub 现已支持文字转语音(TTS)和语音转文字(STT),实现自然的语音交互。与助理对话并听到清晰、个性化的语音回复。
|
||||
tags:
|
||||
- TTS
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
title: 'Text-to-Image: Create Visuals Directly in Chat'
|
||||
description: LobeHub now supports text-to-image generation. Invoke DALL-E 3, MidJourney, or Pollinations directly during conversations to turn your ideas into images without leaving the chat.
|
||||
description: >-
|
||||
LobeHub now supports text-to-image generation. Invoke DALL-E 3, MidJourney, or
|
||||
Pollinations directly during conversations to turn your ideas into images
|
||||
without leaving the chat.
|
||||
tags:
|
||||
- Text-to-Image
|
||||
- LobeHub
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: '文生图:在对话中直接创作视觉内容'
|
||||
description: LobeHub 现已支持文本到图片生成。在对话中直接调用 DALL-E 3、MidJourney 或 Pollinations,无需离开聊天界面即可将想法转化为图像。
|
||||
title: 文生图:在对话中直接创作视觉内容
|
||||
description: >-
|
||||
LobeHub 现已支持文本到图片生成。在对话中直接调用 DALL-E 3、MidJourney 或
|
||||
Pollinations,无需离开聊天界面即可将想法转化为图像。
|
||||
tags:
|
||||
- Text to Image
|
||||
- 文生图
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: 灵活适配的认证体系:Clerk 与 Next-Auth 双方案支持
|
||||
description: >-
|
||||
LobeHub 现已支持 Clerk 和 Next-Auth 两种认证方案,让团队可以根据部署模式和安全需求选择最适合的身份验证方式。
|
||||
description: LobeHub 现已支持 Clerk 和 Next-Auth 两种认证方案,让团队可以根据部署模式和安全需求选择最适合的身份验证方式。
|
||||
tags:
|
||||
- 用户管理
|
||||
- 身份验证
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: 本地模型与云端 AI 并行使用
|
||||
description: >-
|
||||
LobeHub v0.127.0 新增 Ollama 支持,让你可以用与云端模型相同的界面运行本地大语言模型。
|
||||
description: LobeHub v0.127.0 新增 Ollama 支持,让你可以用与云端模型相同的界面运行本地大语言模型。
|
||||
tags:
|
||||
- Ollama AI
|
||||
- LobeHub
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: LobeHub 1.0:为持久化、多用户协作而生的新架构
|
||||
description: >-
|
||||
LobeHub 1.0 引入服务端数据库支持和完善的用户管理体系,实现知识库、跨设备同步和团队协作能力。
|
||||
LobeHub Cloud 同步开启 Beta 测试,内置全部新特性。
|
||||
LobeHub 1.0 引入服务端数据库支持和完善的用户管理体系,实现知识库、跨设备同步和团队协作能力。 LobeHub Cloud 同步开启 Beta
|
||||
测试,内置全部新特性。
|
||||
tags:
|
||||
- LobeHub
|
||||
- 服务端数据库
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: LobeHub v1.6:GPT-4o mini 成为默认模型选项
|
||||
description: >-
|
||||
LobeHub v1.6 新增 GPT-4o mini 支持,同时 LobeHub Cloud 将默认模型升级为
|
||||
GPT-4o mini,让开箱即用的对话体验更进一步。
|
||||
LobeHub v1.6 新增 GPT-4o mini 支持,同时 LobeHub Cloud 将默认模型升级为 GPT-4o
|
||||
mini,让开箱即用的对话体验更进一步。
|
||||
tags:
|
||||
- LobeHub
|
||||
- GPT-4o mini
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'LobeHub Enters the Era of Artifacts'
|
||||
title: LobeHub Enters the Era of Artifacts
|
||||
description: >-
|
||||
LobeHub v1.19 brings significant updates, including full feature support for
|
||||
Claude Artifacts, a brand new discovery page design, and support for GitHub
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '重磅更新:LobeHub 迎来 Artifacts 时代'
|
||||
title: 重磅更新:LobeHub 迎来 Artifacts 时代
|
||||
description: >-
|
||||
LobeHub v1.19 带来了重大更新,包括 Claude Artifacts 完整特性支持、全新的发现页面设计,以及 GitHub Models
|
||||
服务商支持,让 AI 助手的能力得到显著提升。
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: Export Conversations as Markdown or OpenAI JSON
|
||||
description: >-
|
||||
LobeHub v1.28.0 adds Markdown and OpenAI-format JSON exports, making it
|
||||
easier to turn conversations into documentation, debugging payloads, or
|
||||
training datasets.
|
||||
LobeHub v1.28.0 adds Markdown and OpenAI-format JSON exports, making it easier
|
||||
to turn conversations into documentation, debugging payloads, or training
|
||||
datasets.
|
||||
tags:
|
||||
- Text Format Export
|
||||
- Markdown Export
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: 支持导出对话为 Markdown 或 OpenAI JSON 格式
|
||||
description: >-
|
||||
LobeHub v1.28.0 新增 Markdown 与 OpenAI 格式 JSON 导出,方便将对话转为文档、
|
||||
调试数据或训练语料。
|
||||
description: LobeHub v1.28.0 新增 Markdown 与 OpenAI 格式 JSON 导出,方便将对话转为文档、 调试数据或训练语料。
|
||||
tags:
|
||||
- 文本格式导出
|
||||
- Markdown 导出
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: 11 月更新 - 新增 4 家模型服务商
|
||||
description: >-
|
||||
LobeHub 新增支持 Gitee AI、InternLM、xAI 和 Cloudflare Workers AI,
|
||||
为团队提供更多模型接入选择。
|
||||
description: LobeHub 新增支持 Gitee AI、InternLM、xAI 和 Cloudflare Workers AI, 为团队提供更多模型接入选择。
|
||||
tags:
|
||||
- LobeHub
|
||||
- AI 模型服务
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: DeepSeek R1 Integration with Chain-of-Thought Transparency
|
||||
description: LobeHub now supports DeepSeek R1 with real-time reasoning display, making complex problem-solving more transparent and easier to follow.
|
||||
description: >-
|
||||
LobeHub now supports DeepSeek R1 with real-time reasoning display, making
|
||||
complex problem-solving more transparent and easier to follow.
|
||||
tags:
|
||||
- LobeHub
|
||||
- DeepSeek
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "50+ New Models and 10+ Providers Added to the Ecosystem"
|
||||
description: LobeHub expands its AI ecosystem with 50+ new models and 10+ providers, making it easier to access diverse AI capabilities without changing your workflow.
|
||||
title: 50+ New Models and 10+ Providers Added to the Ecosystem
|
||||
description: >-
|
||||
LobeHub expands its AI ecosystem with 50+ new models and 10+ providers, making
|
||||
it easier to access diverse AI capabilities without changing your workflow.
|
||||
tags:
|
||||
- LobeHub
|
||||
- Model Providers
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "AI 生态扩展:新增 50+ 模型与 10+ 服务商"
|
||||
title: AI 生态扩展:新增 50+ 模型与 10+ 服务商
|
||||
description: LobeHub 完成史上最大规模 AI 生态扩展,新增 50+ 模型和 10+ 服务商,让你无需改变工作流程即可接入更多 AI 能力。
|
||||
tags:
|
||||
- LobeHub
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Customizable Hotkeys, Data Export, and Provider Expansion"
|
||||
description: LobeHub adds customizable hotkeys, data export functionality, and expands provider support to make daily workflows smoother and more portable.
|
||||
title: 'Customizable Hotkeys, Data Export, and Provider Expansion'
|
||||
description: >-
|
||||
LobeHub adds customizable hotkeys, data export functionality, and expands
|
||||
provider support to make daily workflows smoother and more portable.
|
||||
tags:
|
||||
- LobeHub
|
||||
- Hotkeys
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "快捷键自定义、数据导出与服务商扩展"
|
||||
title: 快捷键自定义、数据导出与服务商扩展
|
||||
description: LobeHub 新增快捷键自定义、数据导出功能,并扩展服务商支持,让日常使用更顺手、数据更可迁移。
|
||||
tags:
|
||||
- LobeHub
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Lobe UI v2 Design System and Desktop App Launch"
|
||||
description: LobeHub launches a refreshed visual design with Lobe UI v2 and officially releases the desktop app for Windows and macOS.
|
||||
title: Lobe UI v2 Design System and Desktop App Launch
|
||||
description: >-
|
||||
LobeHub launches a refreshed visual design with Lobe UI v2 and officially
|
||||
releases the desktop app for Windows and macOS.
|
||||
tags:
|
||||
- Desktop App
|
||||
- LobeHub
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Lobe UI v2 设计系统与桌面端正式发布"
|
||||
title: Lobe UI v2 设计系统与桌面端正式发布
|
||||
description: LobeHub 推出基于 Lobe UI v2 的全新视觉设计,并正式发布 Windows 与 macOS 桌面端应用。
|
||||
tags:
|
||||
- 桌面端
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "Prompt Variables and Claude 4 Reasoning Model Support"
|
||||
description: LobeHub introduces prompt variables for reusable templates and adds full support for Claude 4 reasoning models with web search integration.
|
||||
title: Prompt Variables and Claude 4 Reasoning Model Support
|
||||
description: >-
|
||||
LobeHub introduces prompt variables for reusable templates and adds full
|
||||
support for Claude 4 reasoning models with web search integration.
|
||||
tags:
|
||||
- Prompt Variables
|
||||
- Claude 4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "提示词变量与 Claude 4 推理模型支持"
|
||||
title: 提示词变量与 Claude 4 推理模型支持
|
||||
description: LobeHub 引入提示词变量实现模板复用,并完整支持 Claude 4 推理模型及网页搜索集成。
|
||||
tags:
|
||||
- 提示词变量
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "MCP Marketplace and Search Provider Expansion \U0001F50D"
|
||||
description: >-
|
||||
MCP Marketplace is now live with one-click plugin installation, alongside expanded search providers and new SSO options for easier team access.
|
||||
MCP Marketplace is now live with one-click plugin installation, alongside
|
||||
expanded search providers and new SSO options for easier team access.
|
||||
tags:
|
||||
- MCP Marketplace
|
||||
- Best MCP
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Image Generation, Desktop, and Auth Updates \U0001F3A8"
|
||||
description: >-
|
||||
Generate AI images across multiple providers, connect with expanded identity options, and run desktop workflows with fewer interruptions.
|
||||
Generate AI images across multiple providers, connect with expanded identity
|
||||
options, and run desktop workflows with fewer interruptions.
|
||||
tags:
|
||||
- Image Generation
|
||||
- Desktop App
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 图像生成、桌面端与认证更新 🎨
|
||||
title: "图像生成、桌面端与认证更新 \U0001F3A8"
|
||||
description: 通过多个服务商生成 AI 图像,用更多身份系统完成接入,并在桌面端享受更顺畅的工作流。
|
||||
tags:
|
||||
- 图像生成
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
title: "Gemini Image Generation and Non-Streaming Mode Support \U0001F3A8"
|
||||
description: >-
|
||||
Gemini 2.5 Flash Image generation, non-streaming response mode, and expanded model coverage give you more flexibility in how you generate and receive content.
|
||||
Gemini 2.5 Flash Image generation, non-streaming response mode, and expanded
|
||||
model coverage give you more flexibility in how you generate and receive
|
||||
content.
|
||||
tags:
|
||||
- Gemini
|
||||
- Nano Banana
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "Claude Sonnet 4.5 and Built-in Python Plugin \U0001F40D"
|
||||
description: >-
|
||||
Run Python directly in chat with the new built-in plugin, navigate long conversations faster, and work with Claude Sonnet 4.5 and other new models.
|
||||
Run Python directly in chat with the new built-in plugin, navigate long
|
||||
conversations faster, and work with Claude Sonnet 4.5 and other new models.
|
||||
tags:
|
||||
- Claude Sonnet 4.5
|
||||
- Chain of Thought
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: ComfyUI Integration and Knowledge Base Improvements ⭐
|
||||
description: >-
|
||||
Run ComfyUI visual workflows directly in LobeHub, organize knowledge with waterfall layouts and auto-extraction, and share outputs as PDF.
|
||||
Run ComfyUI visual workflows directly in LobeHub, organize knowledge with
|
||||
waterfall layouts and auto-extraction, and share outputs as PDF.
|
||||
tags:
|
||||
- AI Knowledge Base
|
||||
- Workflow
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: "MCP Cloud Endpoints and Model Library Expansion \U0001F50C"
|
||||
description: >-
|
||||
Connect to managed MCP tools from the marketplace without self-hosting, while new providers and knowledge base pages improve daily workflows.
|
||||
Connect to managed MCP tools from the marketplace without self-hosting, while
|
||||
new providers and knowledge base pages improve daily workflows.
|
||||
tags:
|
||||
- MCP
|
||||
- LobeHub
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Coding Agent — Claude Code & Codex on Desktop
|
||||
description: >-
|
||||
Claude Code and Codex graduate to first-class desktop runtimes, alongside a
|
||||
new Agent Signal runtime and a wave of flagship models.
|
||||
tags:
|
||||
- Heterogeneous Agent
|
||||
- Desktop
|
||||
- Models
|
||||
---
|
||||
|
||||
# Claude Code & Codex on Desktop
|
||||
|
||||
## Features
|
||||
|
||||
- Topic remembers its own scroll position
|
||||
- User message stays pinned to the viewport top with long messages folded, the last user message can be edited and resent inline, and follow-up sends queue cleanly during a concurrent turn.
|
||||
- Delegating 3rd party coding agents such as Claude Code and Codex
|
||||
- Quick chat and capture your screen and ask LobeHun with desktop app
|
||||
- New models: GPT-5.5, DeepSeek V4 Flash and Pro with a reasoning-effort slider, LobeHub-hosted gpt-image-2, Kimi K2.6, MiMo-V2.5 and Pro
|
||||
- New providers: OpenCode Zen and OpenCode Go.
|
||||
|
||||
## Improvements and fixes
|
||||
|
||||
- Disabled markdown streaming on the first assistant block to avoid mid-stream layout shifts.
|
||||
- Conversation no longer repins to the bottom after a manual scroll.
|
||||
- Tool inspectors render correctly for Codex and heterogeneous-agent follow-ups.
|
||||
- FileEditor migrated from antd Modal to base-ui Modal for consistent focus and keyboard behavior.
|
||||
- QStash heartbeat self-reschedules to keep long-running tasks alive.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: 编程 Agent —— Claude Code 与 Codex 进入桌面端
|
||||
description: Claude Code 与 Codex 成为桌面端的一等运行时,全新 Agent Signal 运行时上线,并迎来一批旗舰模型。
|
||||
tags:
|
||||
- 异构 Agent
|
||||
- 桌面端
|
||||
- 模型
|
||||
---
|
||||
|
||||
# Claude Code 与 Codex 进入桌面端
|
||||
|
||||
## 新功能
|
||||
|
||||
- 话题级别记忆滚动位置
|
||||
- 用户消息固定在视口顶部,过长内容自动折叠;最后一条用户消息可直接编辑并重发;并发对话期间的后续发送会顺序排队
|
||||
- 接入 Claude Code、Codex 等第三方编程 Agent
|
||||
- 在桌面端通过 Quick Chat 与屏幕截图直接向 LobeHub 提问
|
||||
- 新模型:GPT-5.5、DeepSeek V4 Flash / Pro(带思考强度滑块)、LobeHub 托管的 gpt-image-2、Kimi K2.6、MiMo-V2.5 与 Pro
|
||||
- 新提供商:OpenCode Zen 与 OpenCode Go
|
||||
|
||||
## 体验优化与修复
|
||||
|
||||
- 第一条助手消息不再启用 Markdown 流式渲染,避免渲染过程中的布局抖动。
|
||||
- 手动滚动后不再重新自动钉住对话底部。
|
||||
- 修复了 Codex 与异构 Agent 后续轮次中工具检查器渲染异常的问题。
|
||||
- FileEditor 从 antd Modal 迁移到 base-ui Modal,焦点与键盘行为更一致。
|
||||
- QStash 心跳支持自我重调度,长任务运行更稳定。
|
||||
+154
-37
@@ -2,225 +2,342 @@
|
||||
"$schema": "https://github.com/lobehub/lobe-chat/blob/main/docs/changelog/schema.json",
|
||||
"cloud": [],
|
||||
"community": [
|
||||
{
|
||||
"image": "/blog/assetsfa267a02f20bc5ba6f1273bcf27b7c9f.webp",
|
||||
"id": "2026-04-27-heterogeneous-agent",
|
||||
"date": "2026-04-27",
|
||||
"versionRange": [
|
||||
"2.1.53"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsdfda32866c4bc59af0526e52f31d1da2.webp",
|
||||
"id": "2026-04-20-daily-brief",
|
||||
"date": "2026-04-20",
|
||||
"versionRange": ["2.1.50", "2.1.52"]
|
||||
"versionRange": [
|
||||
"2.1.50",
|
||||
"2.1.52"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets300abe7e259d293da6c5ed4f642a1be6.webp",
|
||||
"id": "2026-04-13-gateway-sidebar",
|
||||
"date": "2026-04-13",
|
||||
"versionRange": ["2.1.48", "2.1.49"]
|
||||
"versionRange": [
|
||||
"2.1.48",
|
||||
"2.1.49"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets7ea204859aeb5aa9be5810a20ba1669a.webp",
|
||||
"id": "2026-04-06-auto-completion",
|
||||
"date": "2026-04-06",
|
||||
"versionRange": ["2.1.47"]
|
||||
"versionRange": [
|
||||
"2.1.47"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2026-03-30-agent-tasks",
|
||||
"date": "2026-03-30",
|
||||
"versionRange": ["2.1.45", "2.1.46"]
|
||||
"versionRange": [
|
||||
"2.1.45",
|
||||
"2.1.46"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets53e6ec9cf72554dbc1f8224fc0550a03.webp",
|
||||
"id": "2026-03-23-media-memory",
|
||||
"date": "2026-03-23",
|
||||
"versionRange": ["2.1.44"]
|
||||
"versionRange": [
|
||||
"2.1.44"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "https://hub-apac-1.lobeobjects.space/blog/assets/4a68a7644501cb513d08670b102a446e.webp",
|
||||
"id": "2026-03-16-search",
|
||||
"date": "2026-03-16",
|
||||
"versionRange": ["2.1.38", "2.1.43"]
|
||||
"versionRange": [
|
||||
"2.1.38",
|
||||
"2.1.43"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2026-02-08-runtime-auth",
|
||||
"date": "2026-02-08",
|
||||
"versionRange": ["2.1.6", "2.1.26"]
|
||||
"versionRange": [
|
||||
"2.1.6",
|
||||
"2.1.26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsa8e504275f2cd891fabecca985998de0.webp",
|
||||
"id": "2026-01-27-v2",
|
||||
"date": "2026-01-27",
|
||||
"versionRange": ["2.0.1", "2.1.5"]
|
||||
"versionRange": [
|
||||
"2.0.1",
|
||||
"2.1.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets7f3b38c1d76cceb91edb29d6b1eb60db.webp",
|
||||
"id": "2025-12-20-mcp",
|
||||
"date": "2025-12-20",
|
||||
"versionRange": ["1.142.8", "1.143.0"]
|
||||
"versionRange": [
|
||||
"1.142.8",
|
||||
"1.143.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets3a7f0b29839603336e39e923b423409b.webp",
|
||||
"id": "2025-11-08-comfy-ui",
|
||||
"date": "2025-11-08",
|
||||
"versionRange": ["1.133.5", "1.142.8"]
|
||||
"versionRange": [
|
||||
"1.133.5",
|
||||
"1.142.8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets35e6aa692b0c16009c61964279514166.webp",
|
||||
"id": "2025-10-08-python",
|
||||
"date": "2025-10-08",
|
||||
"versionRange": ["1.120.7", "1.133.5"]
|
||||
"versionRange": [
|
||||
"1.120.7",
|
||||
"1.133.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsce5d6dc93676f974be2e162e8ace03f0.webp",
|
||||
"id": "2025-09-08-gemini",
|
||||
"date": "2025-09-08",
|
||||
"versionRange": ["1.109.1", "1.120.7"]
|
||||
"versionRange": [
|
||||
"1.109.1",
|
||||
"1.120.7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsdf48eed9de76b7e37c269b294285f09d.webp",
|
||||
"id": "2025-08-08-image-generation",
|
||||
"date": "2025-08-08",
|
||||
"versionRange": ["1.97.10", "1.109.1"]
|
||||
"versionRange": [
|
||||
"1.97.10",
|
||||
"1.109.1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets902eb746fe2042fc2ea831c71002be72.webp",
|
||||
"id": "2025-07-08-mcp-market",
|
||||
"date": "2025-07-08",
|
||||
"versionRange": ["1.93.3", "1.97.10"]
|
||||
"versionRange": [
|
||||
"1.93.3",
|
||||
"1.97.10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets5cc27b8cae995074da20d4ffe06a1460.webp",
|
||||
"id": "2025-06-08-claude-4",
|
||||
"date": "2025-06-08",
|
||||
"versionRange": ["1.84.27", "1.93.3"]
|
||||
"versionRange": [
|
||||
"1.84.27",
|
||||
"1.93.3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets2a36d86a4eed6e7938dd6e9c684701ed.webp",
|
||||
"id": "2025-05-08-desktop-app",
|
||||
"date": "2025-05-08",
|
||||
"versionRange": ["1.77.17", "1.84.27"]
|
||||
"versionRange": [
|
||||
"1.77.17",
|
||||
"1.84.27"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsc0efdb82443556ae3acefe00099b3f23.webp",
|
||||
"id": "2025-04-06-exports",
|
||||
"date": "2025-04-06",
|
||||
"versionRange": ["1.67.2", "1.77.17"]
|
||||
"versionRange": [
|
||||
"1.67.2",
|
||||
"1.77.17"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetse743f0a47127390dde766a0a790476db.webp",
|
||||
"id": "2025-03-02-new-models",
|
||||
"date": "2025-03-02",
|
||||
"versionRange": ["1.49.13", "1.67.2"]
|
||||
"versionRange": [
|
||||
"1.49.13",
|
||||
"1.67.2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets18168d5fe64ea34905a7e52fd82d0e9d.webp",
|
||||
"id": "2025-02-02-deepseek-r1",
|
||||
"date": "2025-02-02",
|
||||
"versionRange": ["1.47.8", "1.49.12"]
|
||||
"versionRange": [
|
||||
"1.47.8",
|
||||
"1.49.12"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assetsf9ed064fe764cbeff2f46910e7099a91.webp",
|
||||
"id": "2025-01-22-new-ai-provider",
|
||||
"date": "2025-01-22",
|
||||
"versionRange": ["1.43.1", "1.47.7"]
|
||||
"versionRange": [
|
||||
"1.43.1",
|
||||
"1.47.7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets2d409f43b58953ad5396c6beab8a0719.webp",
|
||||
"id": "2025-01-03-user-profile",
|
||||
"date": "2025-01-03",
|
||||
"versionRange": ["1.34.1", "1.43.0"]
|
||||
"versionRange": [
|
||||
"1.34.1",
|
||||
"1.43.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/d9cbfcbef130183bc490d515d8a38aa4.webp",
|
||||
"id": "2024-11-27-forkable-chat",
|
||||
"date": "2024-11-27",
|
||||
"versionRange": ["1.33.1", "1.34.0"]
|
||||
"versionRange": [
|
||||
"1.33.1",
|
||||
"1.34.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/2d678631c55369ba7d753c3ffcb73782.webp",
|
||||
"id": "2024-11-25-november-providers",
|
||||
"date": "2024-11-25",
|
||||
"versionRange": ["1.30.1", "1.33.0"]
|
||||
"versionRange": [
|
||||
"1.30.1",
|
||||
"1.33.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/f10a4b98782e36797c38071eed785c6f.webp",
|
||||
"id": "2024-11-06-share-text-json",
|
||||
"date": "2024-11-06",
|
||||
"versionRange": ["1.26.1", "1.28.0"]
|
||||
"versionRange": [
|
||||
"1.26.1",
|
||||
"1.28.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/944c671604833cd2457445b211ebba33.webp",
|
||||
"id": "2024-10-27-pin-assistant",
|
||||
"date": "2024-10-27",
|
||||
"versionRange": ["1.19.1", "1.26.0"]
|
||||
"versionRange": [
|
||||
"1.19.1",
|
||||
"1.26.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/f6d047a345e47a52592cff916c9a64ce.webp",
|
||||
"id": "2024-09-20-artifacts",
|
||||
"date": "2024-09-20",
|
||||
"versionRange": ["1.17.1", "1.19.0"]
|
||||
"versionRange": [
|
||||
"1.17.1",
|
||||
"1.19.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/d7e57f8e69f97b76b3c2414f3441b6e4.webp",
|
||||
"id": "2024-09-13-openai-o1-models",
|
||||
"date": "2024-09-13",
|
||||
"versionRange": ["1.12.1", "1.17.0"]
|
||||
"versionRange": [
|
||||
"1.12.1",
|
||||
"1.17.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/d6129350de510a62fe87b2d2f0fb9477.webp",
|
||||
"id": "2024-08-21-file-upload-and-knowledge-base",
|
||||
"date": "2024-08-21",
|
||||
"versionRange": ["1.8.1", "1.12.0"]
|
||||
"versionRange": [
|
||||
"1.8.1",
|
||||
"1.12.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/37d85fdfccff9ed56e9c6827faee01c7.webp",
|
||||
"id": "2024-08-02-lobe-chat-database-docker",
|
||||
"date": "2024-08-02",
|
||||
"versionRange": ["1.6.1", "1.8.0"]
|
||||
"versionRange": [
|
||||
"1.6.1",
|
||||
"1.8.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/39d7890f8cbe21e77db8d3c94f7f22e4.webp",
|
||||
"id": "2024-07-19-gpt-4o-mini",
|
||||
"date": "2024-07-19",
|
||||
"versionRange": ["1.0.1", "1.6.0"]
|
||||
"versionRange": [
|
||||
"1.0.1",
|
||||
"1.6.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/eb477e62217f4d1b644eff975c7ac168.webp",
|
||||
"id": "2024-06-19-lobe-chat-v1",
|
||||
"date": "2024-06-19",
|
||||
"versionRange": ["0.147.0", "1.0.0"]
|
||||
"versionRange": [
|
||||
"0.147.0",
|
||||
"1.0.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/8a8d361b4c0cce6da350cc0de65c0ad6.webp",
|
||||
"id": "2024-02-14-ollama",
|
||||
"date": "2024-02-14",
|
||||
"versionRange": ["0.125.1", "0.127.0"]
|
||||
"versionRange": [
|
||||
"0.125.1",
|
||||
"0.127.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/9498087e85f27e692716a63cb3b58d79.webp",
|
||||
"id": "2024-02-08-sso-oauth",
|
||||
"date": "2024-02-08",
|
||||
"versionRange": ["0.118.1", "0.125.0"]
|
||||
"versionRange": [
|
||||
"0.118.1",
|
||||
"0.125.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/603fefbb944bc6761ebdab5956fc0084.webp",
|
||||
"id": "2023-12-22-dalle-3",
|
||||
"date": "2023-12-22",
|
||||
"versionRange": ["0.102.1", "0.118.0"]
|
||||
"versionRange": [
|
||||
"0.102.1",
|
||||
"0.118.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/8d4c2cc0ce8654fa8ac06cc036a7f941.webp",
|
||||
"id": "2023-11-19-tts-stt",
|
||||
"date": "2023-11-19",
|
||||
"versionRange": ["0.101.1", "0.102.0"]
|
||||
"versionRange": [
|
||||
"0.101.1",
|
||||
"0.102.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/d47654360d626f80144cdedb979a3526.webp",
|
||||
"id": "2023-11-14-gpt4-vision",
|
||||
"date": "2023-11-14",
|
||||
"versionRange": ["0.90.0", "0.101.0"]
|
||||
"versionRange": [
|
||||
"0.90.0",
|
||||
"0.101.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets/50b38eac1769ae6f13aef72f3d725eec.webp",
|
||||
"id": "2023-09-09-plugin-system",
|
||||
"date": "2023-09-09",
|
||||
"versionRange": ["0.67.0", "0.72.0"]
|
||||
"versionRange": [
|
||||
"0.67.0",
|
||||
"0.72.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1943,6 +1943,7 @@ table user_memory_persona_documents {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ref: agent_skills.user_id - users.id
|
||||
|
||||
ref: agent_skills.zip_file_hash - global_files.hash_id
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
title: Connect LobeHub to LINE
|
||||
description: >-
|
||||
Learn how to connect a LINE Messaging API bot to your LobeHub agent,
|
||||
enabling your AI assistant to chat with users in LINE direct messages and
|
||||
group conversations.
|
||||
tags:
|
||||
- LINE
|
||||
- Message Channels
|
||||
- Bot Setup
|
||||
- Integration
|
||||
---
|
||||
|
||||
# Connect LobeHub to LINE
|
||||
|
||||
By connecting a LINE channel to your LobeHub agent, users can interact with the AI assistant through LINE direct messages, group chats, and multi-person rooms. The integration uses the official **LINE Messaging API** — there is no third-party broker between LINE and LobeHub.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A LobeHub account with an active subscription
|
||||
- A [LINE Business ID](https://account.line.biz/signup) (sign up with a LINE account or email)
|
||||
- A **LINE Official Account** — every Messaging API channel must be attached to one
|
||||
|
||||
> **Important change (since 2024-09-04):** LINE no longer lets you create a Messaging API channel directly from the LINE Developers Console. You must first create a LINE Official Account and enable the Messaging API on it from LINE Official Account Manager — the channel then appears automatically in the Developers Console.
|
||||
|
||||
## Step 1: Create a LINE Official Account and Enable the Messaging API
|
||||
|
||||
<Steps>
|
||||
### Create a LINE Official Account
|
||||
|
||||
Open [entry.line.biz](https://entry.line.biz/form/entry/unverified) and sign in with your LINE Business ID. Fill in the account name, business category, and region, then submit. Confirm the new account appears in [LINE Official Account Manager](https://manager.line.biz/).
|
||||
|
||||
### Enable the Messaging API in Official Account Manager
|
||||
|
||||
Open the new account → **Settings → Messaging API** → click **Enable Messaging API**. You will be asked to:
|
||||
|
||||
- Register developer information (first-time only).
|
||||
- Pick a **Provider** that will own this channel in the Developers Console — reuse an existing one, or create a fresh one (e.g. "LobeHub").
|
||||
|
||||
> **Heads-up:** the provider assignment is **permanent**. If you manage multiple unrelated services, give each one its own provider.
|
||||
|
||||
### Find the channel in the Developers Console
|
||||
|
||||
Sign in to [LINE Developers Console](https://developers.line.biz/console/) with the same LINE Business ID, open the provider you just chose, and the Messaging API channel will appear automatically.
|
||||
|
||||
### Note the channel identifiers
|
||||
|
||||
Open the **Basic settings** tab and copy the **Channel secret** — LobeHub uses it to verify webhook signatures in Step 4.
|
||||
|
||||
> **Note:** the **"Your user ID"** field on the same tab is **your own** LINE user ID, **not** the bot's destination user ID. Both have the identical `U` + 32 hex format, but LobeHub needs the bot's, which is resolved automatically from the Channel Access Token in Step 4.
|
||||
|
||||
Then open the **Messaging API** tab and note:
|
||||
|
||||
- **Bot basic ID** — the `@xxxx` short ID users will search for.
|
||||
</Steps>
|
||||
|
||||
## Step 2: Issue a Channel Access Token
|
||||
|
||||
<Steps>
|
||||
### Open the Messaging API tab
|
||||
|
||||
Scroll to the bottom of the **Messaging API** tab. You will see a **Channel access token** section.
|
||||
|
||||
### Issue a long-lived token
|
||||
|
||||
Click **Issue** under "Channel access token (long-lived)". Copy the token immediately — LINE only shows it once.
|
||||
|
||||
> **Important:** The Channel Access Token and Channel Secret are sensitive credentials. Never commit them to source control or share them in screenshots.
|
||||
</Steps>
|
||||
|
||||
## Step 3: Disable LINE's Built-in Auto-reply and Greeting
|
||||
|
||||
By default the LINE Official Account Manager auto-replies to user messages and sends a greeting on first contact. These compete with LobeHub's responses, so they must be turned off.
|
||||
|
||||
<Steps>
|
||||
### Open the LINE Official Account Manager
|
||||
|
||||
In the **Messaging API** tab, click the **LINE Official Account Manager** link to open the management UI for the channel's Official Account.
|
||||
|
||||
### Switch the response modes
|
||||
|
||||
Go to **Settings → Messaging API** (or **Response settings**) and set:
|
||||
|
||||
- **Greeting message:** Disabled
|
||||
- **Auto-response messages:** Disabled
|
||||
- **Webhooks:** Enabled
|
||||
|
||||
This leaves your bot to handle every inbound message itself.
|
||||
</Steps>
|
||||
|
||||
## Step 4: Configure LINE in LobeHub
|
||||
|
||||
<Steps>
|
||||
### Open Channel Settings
|
||||
|
||||
In LobeHub, navigate to your agent's settings, then select the **Channels** tab. Click **LINE** from the platform list.
|
||||
|
||||
### Fill in the credentials
|
||||
|
||||
Recommended order — paste the token first so LobeHub can auto-fill the Destination User ID:
|
||||
|
||||
1. **Channel Access Token** — paste the long-lived token issued in Step 2.
|
||||
2. **Destination User ID** — click **Fetch from LINE** next to this field. LobeHub calls `GET /v2/bot/info` with the token you just pasted and fills in the bot's `userId` (33 chars, starts with `U`) for you. You can also type/paste it manually if you already have it.
|
||||
3. **Channel Secret** — paste the Channel secret from the **Basic settings** tab.
|
||||
|
||||
> **Why the auto-fetch?** The LINE Developers Console does **not** display the bot's destination user ID anywhere — `/v2/bot/info` is the only way to read it. The **Fetch from LINE** button removes the manual `curl` step.
|
||||
>
|
||||
> <details>
|
||||
> <summary>Manual alternative (if the button is unavailable)</summary>
|
||||
>
|
||||
> ```bash
|
||||
> curl -H "Authorization: Bearer <YOUR_CHANNEL_ACCESS_TOKEN>" \
|
||||
> https://api.line.me/v2/bot/info
|
||||
> ```
|
||||
>
|
||||
> Copy the `userId` field from the response into the **Destination User ID** field.
|
||||
> </details>
|
||||
|
||||
### Save Configuration
|
||||
|
||||
Click **Save Configuration**. LobeHub will encrypt your credentials, call `GET /v2/bot/info` once to verify the token works and that the bot user ID matches your Destination User ID, and surface a **Webhook URL** for the next step.
|
||||
|
||||
> **Note:** Unlike Telegram, the LINE Messaging API does not allow programmatic webhook registration. LobeHub cannot wire the URL for you — you must paste it in the LINE Developers Console yourself in Step 5.
|
||||
</Steps>
|
||||
|
||||
## Step 5: Wire the Webhook in the LINE Developers Console
|
||||
|
||||
<Steps>
|
||||
### Copy the Webhook URL
|
||||
|
||||
In LobeHub's LINE channel detail page, copy the **Webhook URL** displayed under the credentials section. It looks like `https://app.lobehub.com/api/agent/webhooks/line/<your-destination-user-id>`.
|
||||
|
||||
### Paste it in the LINE Developers Console
|
||||
|
||||
Back in the **Messaging API** tab of your channel:
|
||||
|
||||
- **Webhook URL:** paste the LobeHub Webhook URL.
|
||||
- Click **Update**.
|
||||
- Click **Verify**. LINE sends a signed `POST` with `events: []` to LobeHub, which responds 200 if the Channel Secret matches.
|
||||
- Toggle **Use webhook** to **ON**.
|
||||
</Steps>
|
||||
|
||||
## Step 6: Test the Connection
|
||||
|
||||
<Steps>
|
||||
### Add the bot as a friend
|
||||
|
||||
Open the **Messaging API** tab in the LINE Developers Console and scan the bot's **QR code** with your phone, or search for the **Bot basic ID** (e.g. `@abc1234x`) in LINE.
|
||||
|
||||
### Send a real message
|
||||
|
||||
Send any message to the bot in LINE. Within a few seconds your LobeHub agent should reply.
|
||||
|
||||
### Run Test Connection (optional)
|
||||
|
||||
Click **Test Connection** in LobeHub's channel settings to re-verify the token and the bot identity match. Errors are surfaced with the exact LINE error message.
|
||||
</Steps>
|
||||
|
||||
## Adding the Bot to Group Chats
|
||||
|
||||
To use the bot in LINE group chats or multi-person rooms:
|
||||
|
||||
1. Add the bot as a friend (Step 6).
|
||||
2. Create a group or room and invite the bot, **or** invite the bot to an existing group from the bot's profile screen (`...` → **Invite**).
|
||||
3. Mention the bot or send a message — the bot will reply in the group or room.
|
||||
|
||||
> **Note:** Allowing your bot to join groups and rooms requires enabling **"Allow bot to join group chats"** in the LINE Official Account Manager (**Response settings**). It is off by default.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
| Field | Required | Description |
|
||||
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Destination User ID** | Yes | The bot's user ID (`U` + 32 hex chars). LobeHub auto-fills this via the **Fetch from LINE** button (calls `GET /v2/bot/info` with the access token); the LINE Developers Console UI does not display the value. Used as the bot identity and webhook path segment. |
|
||||
| **Channel Access Token** | Yes | Long-lived token issued from the **Messaging API** tab. Used as the bearer header on every LINE API call. |
|
||||
| **Channel Secret** | Yes | From the **Basic settings** tab. Used to verify `X-Line-Signature` on every inbound webhook delivery. |
|
||||
|
||||
## Feature Notes
|
||||
|
||||
LINE's Messaging API has a few specifics that LobeHub maps as follows:
|
||||
|
||||
- **Markdown** — LINE renders text messages as **plain text** only. LobeHub strips Markdown markup before sending so emphasis / heading / list markers are removed.
|
||||
- **Message editing** — the Messaging API does not support editing sent messages, so LobeHub only sends the **final reply**, not per-step progress edits.
|
||||
- **Typing indicator** — the loading animation is shown in 1:1 user chats only. Group and multi-person room threads silently no-op.
|
||||
- **Reactions** — LINE bots cannot send message reactions today, so the 👀 / ✏️ status reactions used on Discord and Slack are not surfaced.
|
||||
- **Outbound** — LobeHub uses the **push API** (`/v2/bot/message/push`) rather than the reply API, because the reply token expires in \~60s while agent generation can take longer. Push messages count against your channel's monthly quota for paid plans; the free Developer Trial is unlimited.
|
||||
- **Attachments** — inbound images, video, audio, and files are downloaded on demand from the LINE data domain and forwarded to the model. Outbound replies are text-only today.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"Verify" fails in the LINE Developers Console.** The Channel Secret in LobeHub must match the value shown on the LINE Developers Console **Basic settings** tab exactly. Re-paste it, save, and try again.
|
||||
- **`Authentication failed.` on Save / Test Connection.** Your Channel Access Token is invalid or expired. Re-issue the long-lived token in the Messaging API tab and paste the new value into LobeHub.
|
||||
- **`Channel access token belongs to bot Uxxx, not Uyyy`.** The Destination User ID does not match the token. Easiest fix: clear the field and click **Fetch from LINE** to re-pull the correct `userId`. The `Uxxx` shown in the error is also the userId the token actually belongs to — you can paste it in directly. (Manual check: `curl -H "Authorization: Bearer <token>" https://api.line.me/v2/bot/info`.)
|
||||
- **Webhook delivery is rejected with `401 Invalid signature`.** The Channel Secret in LobeHub doesn't match the one in LINE. Update LobeHub with the correct Channel Secret.
|
||||
- **Bot doesn't respond.** Check that:
|
||||
1. **Use webhook** is toggled **ON** in the Messaging API tab.
|
||||
2. **Auto-response messages** and **Greeting message** are disabled in the LINE Official Account Manager.
|
||||
3. The user has added the bot as a friend (LINE will not deliver messages from non-friends).
|
||||
- **Bot doesn't respond in groups.** Make sure **"Allow bot to join group chats"** is enabled in the Official Account Manager's **Response settings**, then re-invite the bot to the group.
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: 将 LobeHub 连接到 LINE
|
||||
description: >-
|
||||
学习如何通过 LINE Messaging API 将 LINE 机器人连接到 LobeHub
|
||||
代理,使您的 AI 助手能够在 LINE 私信和群组对话中与用户互动。
|
||||
tags:
|
||||
- LINE
|
||||
- 消息渠道
|
||||
- 机器人设置
|
||||
- 集成
|
||||
---
|
||||
|
||||
# 将 LobeHub 连接到 LINE
|
||||
|
||||
通过将 LINE 渠道连接到您的 LobeHub 代理,用户可以通过 LINE 私信、群组聊天以及多人聊天室与 AI 助手互动。集成使用官方的 **LINE Messaging API**,LINE 与 LobeHub 之间没有第三方中转。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 一个拥有有效订阅的 LobeHub 账户
|
||||
- 一个 [LINE Business ID](https://account.line.biz/signup)(可用 LINE 账号或邮箱注册)
|
||||
- 一个 **LINE Official Account**(Messaging API channel 必须挂在 Official Account 下)
|
||||
|
||||
> **重要变化(2024-09-04 起):** LINE 已不再允许直接在 LINE Developers Console 中创建 Messaging API channel。必须先建 LINE Official Account,再在 Official Account Manager 中启用 Messaging API,对应的 channel 才会在 Developers Console 中自动出现。
|
||||
|
||||
## 第一步:创建 LINE Official Account 并启用 Messaging API
|
||||
|
||||
<Steps>
|
||||
### 创建 LINE Official Account
|
||||
|
||||
打开 [entry.line.biz](https://entry.line.biz/form/entry/unverified),用 LINE Business ID 登录,按表单填写账号名称、行业、地区等信息提交。完成后到 [LINE Official Account Manager](https://manager.line.biz/) 确认账号已经出现在列表中。
|
||||
|
||||
### 在 Account Manager 中启用 Messaging API
|
||||
|
||||
进入这个 Official Account → **Settings → Messaging API** → 点击 **Enable Messaging API**。系统会让你:
|
||||
|
||||
- 填写开发者信息(首次启用时)。
|
||||
- 选择一个 **Provider**(用来在 Developers Console 中分组 channel)—— 已有的可以复用,没有就新建一个,例如 "LobeHub"。
|
||||
|
||||
> **注意:** Provider 一旦绑定就 **不能修改**。如果同时管理多个不相关业务,建议为每个业务用独立的 Provider。
|
||||
|
||||
### 在 Developers Console 中找到 channel
|
||||
|
||||
用同一个 LINE Business ID 登录 [LINE Developers Console](https://developers.line.biz/console/),选中刚才绑的 Provider,对应的 Messaging API channel 会自动出现。
|
||||
|
||||
### 记录 channel 的关键标识
|
||||
|
||||
打开 **Basic settings** 选项卡,复制 **Channel secret** —— 用于第四步校验 Webhook 签名。
|
||||
|
||||
> **注意:** 同一选项卡里的 **"Your user ID"** 是**你账号自己的** LINE user ID,**不是** bot 的 destination user ID。两者格式完全一样(都以 `U` 开头共 33 位),但 LobeHub 需要的是 bot 的,会在第四步根据 Channel Access Token 自动解析。
|
||||
|
||||
然后切到 **Messaging API** 选项卡,记下:
|
||||
|
||||
- **Bot basic ID** —— 用户搜索机器人时使用的 `@xxxx` 短 ID。
|
||||
</Steps>
|
||||
|
||||
## 第二步:签发 Channel Access Token
|
||||
|
||||
<Steps>
|
||||
### 打开 Messaging API 选项卡
|
||||
|
||||
滚到 **Messaging API** 选项卡底部的 **Channel access token** 区域。
|
||||
|
||||
### 签发长期 Token
|
||||
|
||||
在 "Channel access token (long-lived)" 处点击 **Issue**。立即复制 Token —— LINE 只展示一次。
|
||||
|
||||
> **重要提示:** Channel Access Token 与 Channel Secret 都是敏感凭据,切勿提交到代码仓库或在截图中泄露。
|
||||
</Steps>
|
||||
|
||||
## 第三步:关闭 LINE 自带的自动回复与欢迎语
|
||||
|
||||
LINE Official Account Manager 默认会自动回复用户消息并在第一次接触时发送欢迎语,会与 LobeHub 的回复发生冲突,需要关闭。
|
||||
|
||||
<Steps>
|
||||
### 打开 LINE Official Account Manager
|
||||
|
||||
在 **Messaging API** 选项卡中,点击 **LINE Official Account Manager** 链接进入对应 Official Account 的管理界面。
|
||||
|
||||
### 切换 response 模式
|
||||
|
||||
打开 **设置 → Messaging API**(或 **Response settings**),调整为:
|
||||
|
||||
- **Greeting message:** Disabled
|
||||
- **Auto-response messages:** Disabled
|
||||
- **Webhooks:** Enabled
|
||||
|
||||
这样所有入站消息都会交给你的机器人处理。
|
||||
</Steps>
|
||||
|
||||
## 第四步:在 LobeHub 中配置 LINE
|
||||
|
||||
<Steps>
|
||||
### 打开渠道设置
|
||||
|
||||
在 LobeHub 中,进入您的代理设置,选择 **渠道** 标签。在平台列表中点击 **LINE**。
|
||||
|
||||
### 填写凭据
|
||||
|
||||
推荐顺序 —— 先粘贴 Token,LobeHub 会帮你自动填入 Destination User ID:
|
||||
|
||||
1. **Channel Access Token** —— 粘贴第二步签发的长期 Token。
|
||||
2. **Destination User ID** —— 点击该字段旁的 **从 LINE 获取** 按钮。LobeHub 会用刚才填入的 Token 调用 `GET /v2/bot/info`,自动取出 bot 的 `userId`(以 `U` 开头共 33 位)并填入。如果你已经拿到这个值,也可以手动粘贴。
|
||||
3. **Channel Secret** —— 粘贴 **Basic settings** 选项卡的 Channel secret。
|
||||
|
||||
> **为什么需要自动获取?** LINE Developers Console 界面**不展示** bot 的 destination user ID,唯一的获取方式就是调用 `/v2/bot/info`。**从 LINE 获取** 按钮会把这一步 `curl` 替你做掉。
|
||||
>
|
||||
> <details>
|
||||
> <summary>手动备选方案(按钮不可用时)</summary>
|
||||
>
|
||||
> ```bash
|
||||
> curl -H "Authorization: Bearer <你的 channel access token>" \
|
||||
> https://api.line.me/v2/bot/info
|
||||
> ```
|
||||
>
|
||||
> 把返回 JSON 中的 `userId` 字段复制到 **Destination User ID** 即可。
|
||||
> </details>
|
||||
|
||||
### 保存配置
|
||||
|
||||
点击 **保存配置**。LobeHub 会加密您的凭据,调用一次 `GET /v2/bot/info` 验证 Token 可用、且返回的 bot user ID 与 Destination User ID 一致,并在凭据下方显示 **Webhook URL** 供下一步使用。
|
||||
|
||||
> **注意:** 与 Telegram 不同,LINE Messaging API 不支持程序化注册 Webhook,LobeHub 无法替您在 LINE Developers Console 中填写 URL,需要您在第五步中自行粘贴。
|
||||
</Steps>
|
||||
|
||||
## 第五步:在 LINE Developers Console 配置 Webhook
|
||||
|
||||
<Steps>
|
||||
### 复制 Webhook URL
|
||||
|
||||
在 LobeHub 的 LINE 渠道详情页中,复制凭据区域下方显示的 **Webhook URL**。形如 `https://app.lobehub.com/api/agent/webhooks/line/<your-destination-user-id>`。
|
||||
|
||||
### 粘贴到 LINE Developers Console
|
||||
|
||||
回到 channel 的 **Messaging API** 选项卡:
|
||||
|
||||
- **Webhook URL:** 粘贴 LobeHub 的 Webhook URL。
|
||||
- 点击 **Update**。
|
||||
- 点击 **Verify**。LINE 会向 LobeHub 发送一个签名后的 `POST`(`events: []`),Channel Secret 匹配时 LobeHub 返回 200。
|
||||
- 将 **Use webhook** 切到 **ON**。
|
||||
</Steps>
|
||||
|
||||
## 第六步:测试连接
|
||||
|
||||
<Steps>
|
||||
### 添加机器人为好友
|
||||
|
||||
在 LINE Developers Console 的 **Messaging API** 选项卡,使用手机扫描机器人的 **QR code**;也可以直接在 LINE 中搜索 **Bot basic ID**(例如 `@abc1234x`)。
|
||||
|
||||
### 发送一条真实消息
|
||||
|
||||
在 LINE 里向机器人发送任意消息。几秒内 LobeHub 代理就会回复。
|
||||
|
||||
### 运行测试连接(可选)
|
||||
|
||||
在 LobeHub 渠道设置中点击 **测试连接**,再次校验 Token 与 bot 身份是否匹配,错误信息会透传 LINE 返回的具体内容。
|
||||
</Steps>
|
||||
|
||||
## 在群聊中使用机器人
|
||||
|
||||
要在 LINE 群聊或多人聊天室使用机器人:
|
||||
|
||||
1. 先按第六步将机器人加为好友。
|
||||
2. 创建群组或聊天室并邀请机器人;也可以从机器人个人页(`...` → **Invite**)邀请到已存在的群组。
|
||||
3. 在群里 @ 机器人或直接发送消息,机器人会在群组或聊天室中回复。
|
||||
|
||||
> **注意:** 允许机器人加入群组与聊天室需要在 LINE Official Account Manager 的 **Response settings** 中启用 **"Allow bot to join group chats"**,默认是关闭的。
|
||||
|
||||
## 配置参考
|
||||
|
||||
| 字段 | 是否必需 | 描述 |
|
||||
| ------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Destination User ID** | 是 | 机器人的用户 ID(`U` + 32 位十六进制)。LobeHub 会通过 **从 LINE 获取** 按钮(背后调用 `GET /v2/bot/info`)自动填入;LINE Developers Console 界面不展示这个值。同时作为机器人标识与 Webhook 路径。 |
|
||||
| **Channel Access Token** | 是 | **Messaging API** 选项卡中签发的长期 Token,用作每个 LINE API 调用的 bearer 头。 |
|
||||
| **Channel Secret** | 是 | **Basic settings** 选项卡中的 Channel secret,用于校验每条入站 Webhook 的 `X-Line-Signature`。 |
|
||||
|
||||
## 能力说明
|
||||
|
||||
LINE Messaging API 有一些平台层面的限制,LobeHub 的对接行为如下:
|
||||
|
||||
- **Markdown** —— LINE 文本消息按 **纯文本** 渲染。LobeHub 在发送前会通过 `stripMarkdown` 去掉强调、标题、列表等标记。
|
||||
- **消息编辑** —— Messaging API 不支持编辑已发送消息,因此 LobeHub 只发送 **最终回复**,不会逐步刷新中间进度。
|
||||
- **输入提示动画** —— 仅对 1:1 用户聊天有效,群组与多人聊天室静默 no-op。
|
||||
- **表情反应** —— LINE 机器人当前不能发送消息反应,Discord/Slack 上使用的 👀 / ✏️ 状态反应不会显示。
|
||||
- **出站** —— LobeHub 使用 **push API**(`/v2/bot/message/push`)而非 reply API,因为 reply token 60 秒就过期,而 agent 生成回复可能更慢。Push 消息会消耗付费方案的月度配额,免费的 Developer Trial 实际不限量。
|
||||
- **附件** —— 入站的图片、视频、音频、文件会按需通过 LINE 数据子域下载并送给模型;出站消息当前仅支持文本。
|
||||
|
||||
## 故障排除
|
||||
|
||||
- **LINE Developers Console 的 "Verify" 失败。** LobeHub 中的 Channel Secret 必须与 **Basic settings** 选项卡显示的值完全一致。重新粘贴、保存后再试。
|
||||
- **保存或测试连接时出现 `Authentication failed.`。** Channel Access Token 失效或被撤销。在 Messaging API 选项卡里重新签发长期 Token,并把新值粘贴到 LobeHub。
|
||||
- **`Channel access token belongs to bot Uxxx, not Uyyy`。** Destination User ID 与 Token 不匹配。最简方式:清空字段后点击 **从 LINE 获取**,让 LobeHub 重新拉到正确的 `userId`。错误消息里的 `Uxxx` 也是 Token 实际归属的 bot userId,直接复制粘贴也可以。(手动核对:`curl -H "Authorization: Bearer <token>" https://api.line.me/v2/bot/info`。)
|
||||
- **Webhook 投递返回 `401 Invalid signature`。** LobeHub 中的 Channel Secret 与 LINE 的不一致,更新为正确的 Channel Secret。
|
||||
- **机器人不回复。** 请依次确认:
|
||||
1. Messaging API 选项卡中的 **Use webhook** 已切到 **ON**。
|
||||
2. LINE Official Account Manager 中的 **Auto-response messages** 与 **Greeting message** 都已禁用。
|
||||
3. 用户已经把机器人加为好友(非好友消息 LINE 不会推送)。
|
||||
- **群聊中机器人不回复。** 确认 LINE Official Account Manager 的 **Response settings** 中已启用 **"Allow bot to join group chats"**,然后将机器人重新邀请进群组。
|
||||
@@ -2,8 +2,8 @@
|
||||
title: Channels Overview
|
||||
description: >-
|
||||
Connect your LobeHub agents to external messaging platforms like Discord,
|
||||
Slack, Telegram, QQ, WeChat, Feishu, and Lark, allowing users to interact with
|
||||
AI assistants directly in their favorite chat apps.
|
||||
Slack, Telegram, LINE, QQ, WeChat, Feishu, and Lark, allowing users to
|
||||
interact with AI assistants directly in their favorite chat apps.
|
||||
tags:
|
||||
- Channels
|
||||
- Message Channels
|
||||
@@ -11,6 +11,7 @@ tags:
|
||||
- Discord
|
||||
- Slack
|
||||
- Telegram
|
||||
- LINE
|
||||
- QQ
|
||||
- WeChat
|
||||
- Feishu
|
||||
@@ -32,6 +33,7 @@ Channels allow you to connect your LobeHub agents to external messaging platform
|
||||
| [Discord](/docs/usage/channels/discord) | Connect to Discord servers for channel chat and direct messages |
|
||||
| [Slack](/docs/usage/channels/slack) | Connect to Slack for channel and direct message conversations |
|
||||
| [Telegram](/docs/usage/channels/telegram) | Connect to Telegram for private and group conversations |
|
||||
| [LINE](/docs/usage/channels/line) | Connect to LINE Messaging API for direct and group chats |
|
||||
| [QQ](/docs/usage/channels/qq) | Connect to QQ for group chats and direct messages |
|
||||
| [WeChat (微信)](/docs/usage/channels/wechat) | Connect to WeChat via iLink Bot for private and group chats (requires an active subscription) |
|
||||
| [Feishu (飞书)](/docs/usage/channels/feishu) | Connect to Feishu for team collaboration (Chinese version) |
|
||||
@@ -42,7 +44,7 @@ Channels allow you to connect your LobeHub agents to external messaging platform
|
||||
Each channel integration works by linking a bot account on the target platform to a LobeHub agent. When a user sends a message to the bot, LobeHub processes it through the agent and sends the response back to the same conversation.
|
||||
|
||||
- **Per-agent configuration** — Each agent can have its own set of channel connections, so different agents can serve different platforms or communities.
|
||||
- **Multiple channels simultaneously** — A single agent can be connected to Discord, Slack, Telegram, QQ, WeChat, Feishu, and Lark at the same time. LobeHub routes messages to the correct agent automatically.
|
||||
- **Multiple channels simultaneously** — A single agent can be connected to Discord, Slack, Telegram, LINE, QQ, WeChat, Feishu, and Lark at the same time. LobeHub routes messages to the correct agent automatically.
|
||||
- **Secure credential storage** — All bot tokens and app secrets are encrypted before being stored.
|
||||
|
||||
## Getting Started
|
||||
@@ -52,6 +54,7 @@ Each channel integration works by linking a bot account on the target platform t
|
||||
- [Discord](/docs/usage/channels/discord)
|
||||
- [Slack](/docs/usage/channels/slack)
|
||||
- [Telegram](/docs/usage/channels/telegram)
|
||||
- [LINE](/docs/usage/channels/line)
|
||||
- [QQ](/docs/usage/channels/qq)
|
||||
- [WeChat (微信)](/docs/usage/channels/wechat)
|
||||
- [Feishu (飞书)](/docs/usage/channels/feishu)
|
||||
@@ -63,13 +66,13 @@ If you do not see **WeChat** in the channel list, check that your account has an
|
||||
|
||||
Text messages are supported across all platforms. Some features vary by platform:
|
||||
|
||||
| Feature | Discord | Slack | Telegram | QQ | WeChat | Feishu | Lark |
|
||||
| ---------------------- | ------- | ----- | -------- | --- | ------ | ------- | ------- |
|
||||
| Text messages | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Direct messages | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Group chats | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Reactions | Yes | Yes | Yes | No | No | Partial | Partial |
|
||||
| Image/file attachments | Yes | Yes | Yes | Yes | No | Yes | Yes |
|
||||
| Feature | Discord | Slack | Telegram | LINE | QQ | WeChat | Feishu | Lark |
|
||||
| ---------------------- | ------- | ----- | -------- | ------- | --- | ------ | ------- | ------- |
|
||||
| Text messages | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Direct messages | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Group chats | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Reactions | Yes | Yes | Yes | No | No | No | Partial | Partial |
|
||||
| Image/file attachments | Yes | Yes | Yes | Inbound | Yes | No | Yes | Yes |
|
||||
|
||||
## Allowed Users (global)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: 渠道概览
|
||||
description: >-
|
||||
将 LobeHub 代理连接到外部消息平台,如 Discord、Slack、Telegram、QQ、微信、飞书和
|
||||
将 LobeHub 代理连接到外部消息平台,如 Discord、Slack、Telegram、LINE、QQ、微信、飞书和
|
||||
Lark,让用户可以直接在他们喜欢的聊天应用中与 AI 助手互动。
|
||||
tags:
|
||||
- 渠道
|
||||
@@ -10,6 +10,7 @@ tags:
|
||||
- Discord
|
||||
- Slack
|
||||
- Telegram
|
||||
- LINE
|
||||
- QQ
|
||||
- 微信
|
||||
- 飞书
|
||||
@@ -26,22 +27,23 @@ tags:
|
||||
|
||||
## 支持的平台
|
||||
|
||||
| 平台 | 描述 |
|
||||
| ----------------------------------------- | ---------------------------------- |
|
||||
| [Discord](/docs/usage/channels/discord) | 连接到 Discord 服务器,用于频道聊天和私信 |
|
||||
| [Slack](/docs/usage/channels/slack) | 连接到 Slack,用于频道和私信对话 |
|
||||
| [Telegram](/docs/usage/channels/telegram) | 连接到 Telegram,用于私人和群组对话 |
|
||||
| [QQ](/docs/usage/channels/qq) | 连接到 QQ,用于群聊和私信 |
|
||||
| [微信](/docs/usage/channels/wechat) | 通过 iLink Bot 连接到微信,用于私聊和群聊(需要有效订阅) |
|
||||
| [飞书](/docs/usage/channels/feishu) | 连接到飞书,用于团队协作(中国版) |
|
||||
| [Lark](/docs/usage/channels/lark) | 连接到 Lark,用于团队协作(国际版) |
|
||||
| 平台 | 描述 |
|
||||
| ----------------------------------------- | -------------------------------------- |
|
||||
| [Discord](/docs/usage/channels/discord) | 连接到 Discord 服务器,用于频道聊天和私信 |
|
||||
| [Slack](/docs/usage/channels/slack) | 连接到 Slack,用于频道和私信对话 |
|
||||
| [Telegram](/docs/usage/channels/telegram) | 连接到 Telegram,用于私人和群组对话 |
|
||||
| [LINE](/docs/usage/channels/line) | 通过 LINE Messaging API 连接到 LINE,支持私聊和群聊 |
|
||||
| [QQ](/docs/usage/channels/qq) | 连接到 QQ,用于群聊和私信 |
|
||||
| [微信](/docs/usage/channels/wechat) | 通过 iLink Bot 连接到微信,用于私聊和群聊(需要有效订阅) |
|
||||
| [飞书](/docs/usage/channels/feishu) | 连接到飞书,用于团队协作(中国版) |
|
||||
| [Lark](/docs/usage/channels/lark) | 连接到 Lark,用于团队协作(国际版) |
|
||||
|
||||
## 工作原理
|
||||
|
||||
每个渠道集成都通过将目标平台上的机器人账户与 LobeHub 代理连接来实现。当用户向机器人发送消息时,LobeHub 会通过代理处理消息并将响应发送回同一对话。
|
||||
|
||||
- **按代理配置** — 每个代理可以拥有自己的一组渠道连接,因此不同的代理可以服务于不同的平台或社区。
|
||||
- **同时支持多个渠道** — 单个代理可以同时连接到 Discord、Slack、Telegram、QQ、微信、飞书和 Lark。LobeHub 会自动将消息路由到正确的代理。
|
||||
- **同时支持多个渠道** — 单个代理可以同时连接到 Discord、Slack、Telegram、LINE、QQ、微信、飞书和 Lark。LobeHub 会自动将消息路由到正确的代理。
|
||||
- **安全的凭据存储** — 所有机器人令牌和应用密钥在存储前都会被加密。
|
||||
|
||||
## 快速开始
|
||||
@@ -51,6 +53,7 @@ tags:
|
||||
- [Discord](/docs/usage/channels/discord)
|
||||
- [Slack](/docs/usage/channels/slack)
|
||||
- [Telegram](/docs/usage/channels/telegram)
|
||||
- [LINE](/docs/usage/channels/line)
|
||||
- [QQ](/docs/usage/channels/qq)
|
||||
- [微信](/docs/usage/channels/wechat)
|
||||
- [飞书](/docs/usage/channels/feishu)
|
||||
@@ -62,13 +65,13 @@ tags:
|
||||
|
||||
所有平台均支持文本消息。某些功能因平台而异:
|
||||
|
||||
| 功能 | Discord | Slack | Telegram | QQ | 微信 | 飞书 | Lark |
|
||||
| --------- | ------- | ----- | -------- | -- | -- | ---- | ---- |
|
||||
| 文本消息 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 私人消息 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 群组聊天 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 表情反应 | 是 | 是 | 是 | 否 | 否 | 部分支持 | 部分支持 |
|
||||
| 图片 / 文件附件 | 是 | 是 | 是 | 是 | 否 | 是 | 是 |
|
||||
| 功能 | Discord | Slack | Telegram | LINE | QQ | 微信 | 飞书 | Lark |
|
||||
| --------- | ------- | ----- | -------- | ---- | -- | -- | ---- | ---- |
|
||||
| 文本消息 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 私人消息 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 群组聊天 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||||
| 表情反应 | 是 | 是 | 是 | 否 | 否 | 否 | 部分支持 | 部分支持 |
|
||||
| 图片 / 文件附件 | 是 | 是 | 是 | 仅入站 | 是 | 否 | 是 | 是 |
|
||||
|
||||
## 允许的用户(全局)
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
@journey @home @starter
|
||||
Feature: Home 页面 Starter 快捷创建功能
|
||||
作为用户,我希望在 Home 页面可以通过 Starter 快捷创建 Agent、Group 或文档,并跳转到对应页面
|
||||
|
||||
Background:
|
||||
Given 用户已登录系统
|
||||
|
||||
# ============================================
|
||||
# 创建 Agent 后侧边栏刷新
|
||||
# ============================================
|
||||
|
||||
@HOME-STARTER-AGENT-001 @P0
|
||||
Scenario: 通过 Home 页面创建 Agent 后返回首页侧边栏应显示新创建的 Agent
|
||||
Given 用户在 Home 页面
|
||||
When 用户点击创建 Agent 按钮
|
||||
And 用户在输入框中输入 "E2E Test Agent"
|
||||
And 用户按 Enter 发送
|
||||
Then 页面应该跳转到 Agent 的 profile 页面
|
||||
When 用户返回 Home 页面
|
||||
Then 新创建的 Agent 应该在侧边栏中显示
|
||||
|
||||
# ============================================
|
||||
# 创建 Group 后侧边栏刷新
|
||||
# ============================================
|
||||
|
||||
@HOME-STARTER-GROUP-001 @P0
|
||||
Scenario: 通过 Home 页面创建 Group 后返回首页侧边栏应显示新创建的 Group
|
||||
Given 用户在 Home 页面
|
||||
When 用户点击创建 Group 按钮
|
||||
And 用户在输入框中输入 "E2E Test Group"
|
||||
And 用户按 Enter 发送
|
||||
Then 页面应该跳转到 Group 的 profile 页面
|
||||
When 用户返回 Home 页面
|
||||
Then 新创建的 Group 应该在侧边栏中显示
|
||||
|
||||
# ============================================
|
||||
# 创建文档并跳转到写作页面
|
||||
# ============================================
|
||||
|
||||
@HOME-STARTER-WRITE-001 @P0
|
||||
Scenario: 通过 Home 页面快捷创建文档并跳转到写作页面
|
||||
Given 用户在 Home 页面
|
||||
When 用户点击写作按钮
|
||||
And 用户在输入框中输入 "帮我写一篇关于人工智能的文章"
|
||||
And 用户按 Enter 发送创建文档
|
||||
Then 页面应该跳转到文档编辑页面
|
||||
And Page Agent 应该收到用户的提示词
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Then, When } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { CustomWorld } from '../../support/world';
|
||||
import type { CustomWorld } from '../../support/world';
|
||||
|
||||
// ============================================
|
||||
// When Steps (Actions)
|
||||
@@ -143,7 +143,9 @@ When('I wait for the next page to load', async function (this: CustomWorld) {
|
||||
When('I click on the first assistant card', async function (this: CustomWorld) {
|
||||
await this.page.waitForLoadState('networkidle', { timeout: 30_000 });
|
||||
|
||||
const firstCard = this.page.locator('[data-testid="assistant-item"]').first();
|
||||
const firstCard = this.page
|
||||
.locator('[data-testid="assistant-item"][data-agent-type="agent"]')
|
||||
.first();
|
||||
await firstCard.waitFor({ state: 'visible', timeout: 30_000 });
|
||||
|
||||
// Store the current URL before clicking
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Home Starter Steps
|
||||
*
|
||||
* Step definitions for Home page Starter E2E tests
|
||||
* - Create Agent from Home input
|
||||
* - Create Group from Home input
|
||||
* - Create Document (Write) from Home input
|
||||
* - Verify Agent/Group appears in sidebar after returning to Home
|
||||
* - Verify Document page navigation and Page Agent interaction
|
||||
*/
|
||||
import { Given, Then, When } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { llmMockManager, presetResponses } from '../../mocks/llm';
|
||||
import { type CustomWorld, WAIT_TIMEOUT } from '../../support/world';
|
||||
|
||||
// Store created IDs for verification
|
||||
let createdAgentId: string | null = null;
|
||||
let createdGroupId: string | null = null;
|
||||
let createdDocumentId: string | null = null;
|
||||
|
||||
// ============================================
|
||||
// Given Steps
|
||||
// ============================================
|
||||
|
||||
Given('用户在 Home 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 设置 LLM mock...');
|
||||
// Setup LLM mock before navigation (for agent/group/page builder message)
|
||||
llmMockManager.setResponse('E2E Test Agent', presetResponses.greeting);
|
||||
llmMockManager.setResponse('E2E Test Group', presetResponses.greeting);
|
||||
llmMockManager.setResponse(
|
||||
'帮我写一篇关于人工智能的文章',
|
||||
'好的,我来帮你写一篇关于人工智能的文章。\n\n# 人工智能:改变世界的技术\n\n人工智能(AI)是当今最具变革性的技术之一...',
|
||||
);
|
||||
await llmMockManager.setup(this.page);
|
||||
|
||||
console.log(' 📍 Step: 导航到 Home 页面...');
|
||||
await this.page.goto('/');
|
||||
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
// Reset IDs for each test
|
||||
createdAgentId = null;
|
||||
createdGroupId = null;
|
||||
createdDocumentId = null;
|
||||
|
||||
console.log(' ✅ 已进入 Home 页面');
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// When Steps
|
||||
// ============================================
|
||||
|
||||
When('用户点击创建 Agent 按钮', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击创建 Agent 按钮...');
|
||||
|
||||
// Find the "Create Agent" button by text (supports both English and Chinese)
|
||||
const createAgentButton = this.page
|
||||
.getByRole('button', { name: /create agent|创建智能体/i })
|
||||
.first();
|
||||
|
||||
await expect(createAgentButton).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
await createAgentButton.click();
|
||||
|
||||
// Wait for mode switch animation and ChatInput scroll-into-view to settle
|
||||
await this.page.waitForTimeout(800);
|
||||
|
||||
console.log(' ✅ 已点击创建 Agent 按钮');
|
||||
});
|
||||
|
||||
When('用户点击创建 Group 按钮', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击创建 Group 按钮...');
|
||||
|
||||
// Find the "Create Group" button by text (supports both English and Chinese)
|
||||
const createGroupButton = this.page
|
||||
.getByRole('button', { name: /create group|创建群组/i })
|
||||
.first();
|
||||
|
||||
await expect(createGroupButton).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
await createGroupButton.click();
|
||||
|
||||
// Wait for mode switch animation and ChatInput scroll-into-view to settle
|
||||
await this.page.waitForTimeout(800);
|
||||
|
||||
console.log(' ✅ 已点击创建 Group 按钮');
|
||||
});
|
||||
|
||||
When('用户点击写作按钮', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 点击写作按钮...');
|
||||
|
||||
// Find the "Write" button by text (supports both English and Chinese)
|
||||
const writeButton = this.page.getByRole('button', { name: /write|写作/i }).first();
|
||||
|
||||
await expect(writeButton).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
await writeButton.click();
|
||||
|
||||
// Wait for mode switch animation and ChatInput scroll-into-view to settle
|
||||
await this.page.waitForTimeout(800);
|
||||
|
||||
console.log(' ✅ 已点击写作按钮');
|
||||
});
|
||||
|
||||
When('用户在输入框中输入 {string}', async function (this: CustomWorld, message: string) {
|
||||
console.log(` 📍 Step: 在输入框中输入 "${message}"...`);
|
||||
|
||||
// The chat input is a contenteditable editor, need to click first then type.
|
||||
// Target the contenteditable element INSIDE the ChatInput container directly,
|
||||
// since clicking the container might hit the action bar/footer area instead.
|
||||
const chatInputContainer = this.page.locator('[data-testid="chat-input"]').first();
|
||||
await expect(chatInputContainer).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
|
||||
const editor = chatInputContainer.locator('[contenteditable="true"]').first();
|
||||
await editor.click();
|
||||
await this.page.waitForTimeout(300);
|
||||
await this.page.keyboard.type(message, { delay: 30 });
|
||||
|
||||
console.log(` ✅ 已输入 "${message}"`);
|
||||
});
|
||||
|
||||
When('用户按 Enter 发送', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 按 Enter 发送...');
|
||||
|
||||
// Wait for editor's debounced onChange (100ms default) to sync inputMessage to store.
|
||||
// The send() function reads directly from the editor as a fallback, but this wait
|
||||
// ensures maximum reliability.
|
||||
await this.page.waitForTimeout(200);
|
||||
|
||||
// Listen for navigation to capture the agent/group ID
|
||||
const navigationPromise = this.page.waitForURL(/\/(agent|group)\/.*\/profile/, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await this.page.keyboard.press('Enter');
|
||||
|
||||
// Wait for navigation to profile page
|
||||
await navigationPromise;
|
||||
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
||||
|
||||
// Extract agent/group ID from URL
|
||||
const currentUrl = this.page.url();
|
||||
|
||||
const agentMatch = currentUrl.match(/\/agent\/([^/]+)/);
|
||||
if (agentMatch) {
|
||||
createdAgentId = agentMatch[1];
|
||||
console.log(` 📍 Created agent ID: ${createdAgentId}`);
|
||||
}
|
||||
|
||||
const groupMatch = currentUrl.match(/\/group\/([^/]+)/);
|
||||
if (groupMatch) {
|
||||
createdGroupId = groupMatch[1];
|
||||
console.log(` 📍 Created group ID: ${createdGroupId}`);
|
||||
}
|
||||
|
||||
console.log(' ✅ 已发送消息');
|
||||
});
|
||||
|
||||
When('用户按 Enter 发送创建文档', { timeout: 30_000 }, async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 按 Enter 发送创建文档...');
|
||||
|
||||
// Wait for editor's debounced onChange (100ms default) to sync inputMessage to store
|
||||
await this.page.waitForTimeout(200);
|
||||
|
||||
// Listen for navigation to capture the document ID
|
||||
const navigationPromise = this.page.waitForURL(/\/page\/[^/]+/, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await this.page.keyboard.press('Enter');
|
||||
|
||||
// Wait for navigation to page
|
||||
await navigationPromise;
|
||||
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
||||
|
||||
// Extract document ID from URL
|
||||
const currentUrl = this.page.url();
|
||||
const pageMatch = currentUrl.match(/\/page\/([^/?]+)/);
|
||||
if (pageMatch) {
|
||||
createdDocumentId = pageMatch[1];
|
||||
console.log(` 📍 Created document ID: ${createdDocumentId}`);
|
||||
}
|
||||
|
||||
console.log(' ✅ 已发送并创建文档');
|
||||
});
|
||||
|
||||
When('用户返回 Home 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 返回 Home 页面...');
|
||||
|
||||
await this.page.goto('/');
|
||||
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
console.log(' ✅ 已返回 Home 页面');
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Then Steps
|
||||
// ============================================
|
||||
|
||||
Then('页面应该跳转到 Agent 的 profile 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Agent profile 页面...');
|
||||
|
||||
// Check current URL matches /agent/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/agent\/[^/]+\/profile/);
|
||||
|
||||
console.log(' ✅ 已跳转到 Agent profile 页面');
|
||||
});
|
||||
|
||||
Then('页面应该跳转到 Group 的 profile 页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到 Group profile 页面...');
|
||||
|
||||
// Check current URL matches /group/{id}/profile pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/group\/[^/]+\/profile/);
|
||||
|
||||
console.log(' ✅ 已跳转到 Group profile 页面');
|
||||
});
|
||||
|
||||
Then('新创建的 Agent 应该在侧边栏中显示', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Agent 在侧边栏中显示...');
|
||||
|
||||
// Wait for sidebar to be visible and data to load
|
||||
await this.page.waitForTimeout(1500);
|
||||
|
||||
// Check if the agent appears in sidebar by its link (primary assertion)
|
||||
// This proves that refreshAgentList() was called and the sidebar was updated
|
||||
if (!createdAgentId) {
|
||||
throw new Error('Agent ID was not captured during creation');
|
||||
}
|
||||
|
||||
const agentLink = this.page.locator(`a[href="/agent/${createdAgentId}"]`).first();
|
||||
await expect(agentLink).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
console.log(` ✅ 找到 Agent 链接: /agent/${createdAgentId}`);
|
||||
|
||||
// Get the aria-label or text content to verify it's the correct agent
|
||||
const ariaLabel = await agentLink.getAttribute('aria-label');
|
||||
console.log(` 📍 Agent aria-label: ${ariaLabel}`);
|
||||
|
||||
console.log(' ✅ Agent 已在侧边栏中显示');
|
||||
});
|
||||
|
||||
Then('新创建的 Group 应该在侧边栏中显示', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Group 在侧边栏中显示...');
|
||||
|
||||
// Wait for sidebar to be visible and data to load
|
||||
await this.page.waitForTimeout(1500);
|
||||
|
||||
// Check if the group appears in sidebar by its link (primary assertion)
|
||||
// This proves that refreshAgentList() was called and the sidebar was updated
|
||||
if (!createdGroupId) {
|
||||
throw new Error('Group ID was not captured during creation');
|
||||
}
|
||||
|
||||
const groupLink = this.page.locator(`a[href="/group/${createdGroupId}"]`).first();
|
||||
await expect(groupLink).toBeVisible({ timeout: WAIT_TIMEOUT });
|
||||
console.log(` ✅ 找到 Group 链接: /group/${createdGroupId}`);
|
||||
|
||||
// Get the aria-label or text content to verify it's the correct group
|
||||
const ariaLabel = await groupLink.getAttribute('aria-label');
|
||||
console.log(` 📍 Group aria-label: ${ariaLabel}`);
|
||||
|
||||
console.log(' ✅ Group 已在侧边栏中显示');
|
||||
});
|
||||
|
||||
Then('页面应该跳转到文档编辑页面', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证页面跳转到文档编辑页面...');
|
||||
|
||||
// Check current URL matches /page/{id} pattern
|
||||
const currentUrl = this.page.url();
|
||||
expect(currentUrl).toMatch(/\/page\/[^/?]+/);
|
||||
|
||||
if (!createdDocumentId) {
|
||||
throw new Error('Document ID was not captured during creation');
|
||||
}
|
||||
|
||||
console.log(` ✅ 已跳转到文档编辑页面: /page/${createdDocumentId}`);
|
||||
});
|
||||
|
||||
Then('Page Agent 应该收到用户的提示词', async function (this: CustomWorld) {
|
||||
console.log(' 📍 Step: 验证 Page Agent 收到用户的提示词...');
|
||||
|
||||
// Wait for the page to fully load and Page Agent panel to appear
|
||||
await this.page.waitForTimeout(2000);
|
||||
|
||||
// Look for the user message in the chat panel (Page Agent Copilot)
|
||||
// The message should appear in the chat list
|
||||
const userMessage = this.page.locator('text=帮我写一篇关于人工智能的文章').first();
|
||||
|
||||
// The message might be in the chat panel on the right side
|
||||
const messageVisible = await userMessage.isVisible().catch(() => false);
|
||||
|
||||
if (messageVisible) {
|
||||
console.log(' ✅ 找到用户发送的提示词');
|
||||
} else {
|
||||
// Alternative: check if there's any chat content indicating the message was sent
|
||||
console.log(' ⚠️ 用户消息可能在聊天面板中,但未直接可见');
|
||||
}
|
||||
|
||||
// Verify that the Page Agent responded (mock response should appear)
|
||||
// Wait a bit longer for the mock LLM response
|
||||
await this.page.waitForTimeout(3000);
|
||||
|
||||
// Look for AI response content
|
||||
const aiResponse = this.page.locator('text=人工智能').first();
|
||||
const responseVisible = await aiResponse.isVisible().catch(() => false);
|
||||
|
||||
if (responseVisible) {
|
||||
console.log(' ✅ Page Agent 已响应用户的提示词');
|
||||
} else {
|
||||
console.log(' ⚠️ Page Agent 响应可能正在生成或在其他位置');
|
||||
}
|
||||
|
||||
console.log(' ✅ Page Agent 验证完成');
|
||||
});
|
||||
@@ -29,7 +29,7 @@ async function waitForPageWorkspaceReady(world: CustomWorld): Promise<void> {
|
||||
}
|
||||
|
||||
const readyCandidates = [
|
||||
world.page.locator('button:has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator(':is(button, [role="button"]):has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator('input[placeholder*="Search"], input[placeholder*="搜索"]').first(),
|
||||
world.page.locator('a[href^="/page/"]').first(),
|
||||
];
|
||||
@@ -50,7 +50,7 @@ async function clickNewPageButton(world: CustomWorld): Promise<void> {
|
||||
await waitForPageWorkspaceReady(world);
|
||||
|
||||
const candidates = [
|
||||
world.page.locator('button:has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator(':is(button, [role="button"]):has(svg.lucide-square-pen)').first(),
|
||||
world.page
|
||||
.locator('svg.lucide-square-pen')
|
||||
.first()
|
||||
|
||||
@@ -114,9 +114,11 @@ async function waitForPageWorkspaceReady(world: CustomWorld): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any of these means the page workspace is ready for interactions
|
||||
// Any of these means the page workspace is ready for interactions.
|
||||
// The new-page button is rendered by `@lobehub/ui` ActionIcon as a
|
||||
// `<div role="button">` rather than a native `<button>`, so match either.
|
||||
const readyCandidates = [
|
||||
world.page.locator('button:has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator(':is(button, [role="button"]):has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator('input[placeholder*="Search"], input[placeholder*="搜索"]').first(),
|
||||
world.page.locator('a[href^="/page/"]').first(),
|
||||
];
|
||||
@@ -137,7 +139,7 @@ async function clickNewPageButton(world: CustomWorld): Promise<void> {
|
||||
await waitForPageWorkspaceReady(world);
|
||||
|
||||
const candidates = [
|
||||
world.page.locator('button:has(svg.lucide-square-pen)').first(),
|
||||
world.page.locator(':is(button, [role="button"]):has(svg.lucide-square-pen)').first(),
|
||||
world.page
|
||||
.locator('svg.lucide-square-pen')
|
||||
.first()
|
||||
|
||||
+56
-2
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"channel.allowFrom": "المستخدمون المسموح لهم",
|
||||
"channel.allowFromAdd": "إضافة مستخدم",
|
||||
"channel.allowFromEmpty": "لم تتم إضافة أي مستخدم بعد — يمكن لأي شخص التفاعل مع الروبوت.",
|
||||
"channel.allowFromHint": "فقط المستخدمون المدرجون يمكنهم التفاعل مع الروبوت؛ يتم تضمين 'معرّف المستخدم على المنصة' الخاص بك تلقائيًا.",
|
||||
"channel.allowFromIdLabel": "معرّف المستخدم",
|
||||
"channel.allowFromIdPlaceholder": "معرّف المستخدم على المنصة",
|
||||
"channel.allowFromNameLabel": "ملاحظة",
|
||||
"channel.allowFromNamePlaceholder": "مثال: آليس (تذكيرك)",
|
||||
"channel.allowListRemove": "إزالة",
|
||||
"channel.appSecret": "سر التطبيق",
|
||||
"channel.appSecretHint": "سر التطبيق لتطبيق الروبوت الخاص بك. سيتم تشفيره وتخزينه بأمان.",
|
||||
"channel.appSecretPlaceholder": "الصق سر التطبيق هنا",
|
||||
@@ -14,8 +23,10 @@
|
||||
"channel.charLimitHint": "الحد الأقصى لعدد الأحرف لكل رسالة",
|
||||
"channel.concurrency": "وضع التزامن",
|
||||
"channel.concurrencyDebounce": "إزالة الارتداد",
|
||||
"channel.concurrencyDebounceHint": "معالجة آخر رسالة فقط في الدفعة (يتم تجاهل الرسائل السابقة)",
|
||||
"channel.concurrencyHint": "يقوم الوضع التتابعي بمعالجة الرسائل واحدة تلو الأخرى؛ بينما ينتظر وضع إزالة الارتداد انتهاء دفعة الرسائل قبل المعالجة",
|
||||
"channel.concurrencyQueue": "قائمة الانتظار",
|
||||
"channel.concurrencyQueueHint": "معالجة الرسائل واحدة تلو الأخرى",
|
||||
"channel.connectFailed": "فشل اتصال الروبوت",
|
||||
"channel.connectQueued": "تم وضع اتصال الروبوت في قائمة الانتظار. سيبدأ قريبًا.",
|
||||
"channel.connectStarting": "الروبوت قيد التشغيل. يرجى الانتظار لحظة.",
|
||||
@@ -25,7 +36,9 @@
|
||||
"channel.connectionMode": "وضع الاتصال",
|
||||
"channel.connectionModeHint": "يُفضَّل استخدام WebSocket للروبوتات الجديدة. استخدم Webhook إذا كان روبوتك يحتوي بالفعل على عنوان URL مُعدّ لرد النداء على منصة QQ المفتوحة.",
|
||||
"channel.connectionModeWebSocket": "WebSocket",
|
||||
"channel.connectionModeWebSocketHint": "موصى به للروبوتات الجديدة",
|
||||
"channel.connectionModeWebhook": "Webhook",
|
||||
"channel.connectionModeWebhookHint": "استخدمه إذا كان لدى روبوتك عنوان URL لردّ النداء مُعدّ",
|
||||
"channel.copied": "تم النسخ إلى الحافظة",
|
||||
"channel.copy": "نسخ",
|
||||
"channel.credentials": "بيانات الاعتماد",
|
||||
@@ -45,13 +58,16 @@
|
||||
"channel.displayToolCalls": "عرض استدعاءات الأدوات",
|
||||
"channel.displayToolCallsHint": "عرض تفاصيل استدعاء الأدوات أثناء استجابات الذكاء الاصطناعي. عند التعطيل، يتم عرض الاستجابة النهائية فقط لتجربة أكثر نظافة.",
|
||||
"channel.dm": "الرسائل المباشرة",
|
||||
"channel.dmEnabled": "تمكين الرسائل المباشرة",
|
||||
"channel.dmEnabledHint": "السماح للروبوت بتلقي الرسائل المباشرة والرد عليها",
|
||||
"channel.dmPolicy": "سياسة الرسائل المباشرة",
|
||||
"channel.dmPolicyAllowlist": "القائمة المسموح بها",
|
||||
"channel.dmPolicyAllowlistHint": "فقط المستخدمون المدرجون يمكنهم إرسال رسائل خاصة إلى الروبوت",
|
||||
"channel.dmPolicyDisabled": "معطل",
|
||||
"channel.dmPolicyDisabledHint": "رفض جميع الرسائل الخاصة",
|
||||
"channel.dmPolicyHint": "التحكم في من يمكنه إرسال الرسائل المباشرة إلى الروبوت",
|
||||
"channel.dmPolicyOpen": "مفتوح",
|
||||
"channel.dmPolicyOpenHint": "قبول الرسائل الخاصة من أي شخص",
|
||||
"channel.dmPolicyPairing": "الاقتران",
|
||||
"channel.dmPolicyPairingHint": "يحتاج الغرباء إلى استخدام /approve لإرسال رسالة خاصة",
|
||||
"channel.documentation": "التوثيق",
|
||||
"channel.enabled": "مفعّل",
|
||||
"channel.encryptKey": "مفتاح التشفير",
|
||||
@@ -63,6 +79,22 @@
|
||||
"channel.feishu.description": "قم بتوصيل هذا المساعد بـ Feishu للدردشة الخاصة والجماعية.",
|
||||
"channel.feishu.webhookMigrationDesc": "يوفّر وضع WebSocket تسليمًا فوريًا للأحداث دون الحاجة إلى عنوان URL عام لرد النداء. للانتقال، قم بتغيير وضع الاتصال إلى WebSocket في الإعدادات المتقدمة. لا يلزم أي إعداد إضافي على منصة Feishu/Lark المفتوحة.",
|
||||
"channel.feishu.webhookMigrationTitle": "النظر في الترقية إلى وضع WebSocket",
|
||||
"channel.groupAllowFrom": "القنوات المسموح بها",
|
||||
"channel.groupAllowFromAdd": "إضافة قناة",
|
||||
"channel.groupAllowFromEmpty": "لم تتم إضافة أي قنوات بعد — لن يرد الروبوت في أي مكان.",
|
||||
"channel.groupAllowFromHint": "معرّفات القنوات / المجموعات / الدردشات التي يمكن للروبوت الرد فيها.",
|
||||
"channel.groupAllowFromIdLabel": "معرّف القناة",
|
||||
"channel.groupAllowFromIdPlaceholder": "معرّف القناة / المجموعة / الدردشة",
|
||||
"channel.groupAllowFromNameLabel": "ملاحظة",
|
||||
"channel.groupAllowFromNamePlaceholder": "مثال: #general (تذكيرك)",
|
||||
"channel.groupPolicy": "سياسة المجموعات",
|
||||
"channel.groupPolicyAllowlist": "قائمة السماح",
|
||||
"channel.groupPolicyAllowlistHint": "الرد فقط في القنوات المدرجة",
|
||||
"channel.groupPolicyDisabled": "معطّل",
|
||||
"channel.groupPolicyDisabledHint": "تجاهل جميع رسائل المجموعات",
|
||||
"channel.groupPolicyHint": "أماكن رد الروبوت في المجموعات والقنوات والمواضيع",
|
||||
"channel.groupPolicyOpen": "مفتوح",
|
||||
"channel.groupPolicyOpenHint": "الرد في أي مجموعة أو قناة أو موضوع",
|
||||
"channel.historyLimit": "حد رسائل السجل",
|
||||
"channel.historyLimitHint": "العدد الافتراضي للرسائل التي يتم جلبها عند قراءة سجل القناة",
|
||||
"channel.importConfig": "استيراد التكوين",
|
||||
@@ -70,6 +102,19 @@
|
||||
"channel.importInvalidFormat": "تنسيق ملف التكوين غير صالح",
|
||||
"channel.importSuccess": "تم استيراد التكوين بنجاح",
|
||||
"channel.lark.description": "قم بتوصيل هذا المساعد بـ Lark للدردشة الخاصة والجماعية.",
|
||||
"channel.line.channelAccessToken": "رمز الوصول للقناة",
|
||||
"channel.line.channelAccessTokenHint": "رمز طويل الأمد يتم إصداره ضمن علامة تبويب واجهة برمجة تطبيقات المراسلة. سيتم تشفير الرمز وتخزينه بأمان.",
|
||||
"channel.line.channelSecret": "سر القناة",
|
||||
"channel.line.channelSecretHint": "من علامة تبويب الإعدادات الأساسية. مطلوب - يُستخدم للتحقق من توقيع X-Line-Signature على كل طلب ويب وارد.",
|
||||
"channel.line.description": "قم بتوصيل هذا المساعد بواجهة برمجة تطبيقات المراسلة الخاصة بـ LINE للمحادثات المباشرة والجماعية.",
|
||||
"channel.line.destinationUserId": "معرّف المستخدم الوجهة",
|
||||
"channel.line.destinationUserIdHint": "معرّف المستخدم الوجهة الخاص بالروبوت (يبدأ بـ `U`، إجمالي 33 حرفًا). لا يعرض وحدة تحكم مطوري LINE هذه القيمة. قم بإصدار رمز الوصول للقناة أدناه أولاً، ثم انقر على \"Fetch from LINE\" لملء هذا الحقل تلقائيًا. ملاحظة: \"معرّف المستخدم الخاص بك\" في الإعدادات الأساسية هو معرّف المستخدم الشخصي الخاص بك في LINE، وليس معرّف الروبوت.",
|
||||
"channel.line.destinationUserIdPlaceholder": "مثال: U1234567890abcdef1234567890abcdef",
|
||||
"channel.line.fetchBotInfo": "جلب من LINE",
|
||||
"channel.line.fetchBotInfoFailed": "فشل في جلب معلومات الروبوت",
|
||||
"channel.line.fetchBotInfoMissingToken": "أدخل رمز الوصول للقناة أولاً، ثم انقر على \"Fetch from LINE\".",
|
||||
"channel.line.fetchBotInfoSuccess": "تم جلب معرّف المستخدم الوجهة",
|
||||
"channel.line.webhookManualSetup": "لا يسمح LINE بالتسجيل البرمجي للويب هوك. انسخ هذا الرابط إلى وحدة تحكم مطوري LINE (واجهة برمجة تطبيقات المراسلة → رابط الويب هوك)، انقر على \"تحقق\"، وقم بتمكين \"استخدام الويب هوك\".",
|
||||
"channel.openPlatform": "منصة مفتوحة",
|
||||
"channel.platforms": "المنصات",
|
||||
"channel.publicKey": "المفتاح العام",
|
||||
@@ -93,6 +138,8 @@
|
||||
"channel.secretTokenPlaceholder": "السر الاختياري للتحقق من الويب هوك",
|
||||
"channel.serverId": "معرف الخادم / النقابة الافتراضي",
|
||||
"channel.serverIdHint": "معرف الخادم أو النقابة الافتراضي الخاص بك على هذه المنصة. يستخدمه الذكاء الاصطناعي لإدراج القنوات دون الحاجة للسؤال.",
|
||||
"channel.serverIdHint.discord": "فعّل وضع المطوّر (الإعدادات → متقدم)، ثم انقر بزر الفأرة الأيمن على أيقونة الخادم → انسخ معرّف الخادم.",
|
||||
"channel.serverIdHint.slack": "معرّف مساحة العمل (يبدأ بـ T). اعثر عليه تحت الإعدادات والإدارة → إعدادات مساحة العمل، أو في رابط مساحة العمل.",
|
||||
"channel.settings": "الإعدادات المتقدمة",
|
||||
"channel.settingsResetConfirm": "هل أنت متأكد أنك تريد إعادة تعيين الإعدادات المتقدمة إلى الوضع الافتراضي؟",
|
||||
"channel.settingsResetDefault": "إعادة إلى الوضع الافتراضي",
|
||||
@@ -120,6 +167,13 @@
|
||||
"channel.updateFailed": "فشل في تحديث الحالة",
|
||||
"channel.userId": "معرف المستخدم الخاص بك على المنصة",
|
||||
"channel.userIdHint": "معرف المستخدم الخاص بك على هذه المنصة. يمكن للذكاء الاصطناعي استخدامه لإرسال رسائل مباشرة إليك.",
|
||||
"channel.userIdHint.discord": "فعّل وضع المطوّر (الإعدادات → متقدم)، ثم انقر بزر الفأرة الأيمن على صورتك الشخصية → انسخ معرّف المستخدم.",
|
||||
"channel.userIdHint.feishu": "افتح تطبيقك على منصة Feishu / Lark Open Platform → الأذونات، ثم ابحث عن المعرّف المفتوح الخاص بك.",
|
||||
"channel.userIdHint.qq": "رقم QQ الخاص بك، يظهر في صفحة ملفك الشخصي.",
|
||||
"channel.userIdHint.slack": "افتح ملفك الشخصي في Slack → ⋮ المزيد → انسخ معرّف العضو (يبدأ بـ U).",
|
||||
"channel.userIdHint.telegram": "أرسل أي رسالة إلى @userinfobot على تيليغرام — سيرد عليك بمعرّف المستخدم الرقمي الخاص بك.",
|
||||
"channel.userIdMissingDesc": "بدونه، لا يمكن لأدوات الذكاء الاصطناعي الوصول إليك عبر التذكيرات، كما سيفشل اعتماد الاقتران. قم بإدخاله في الإعدادات المتقدمة.",
|
||||
"channel.userIdMissingTitle": "أضف معرّف المستخدم على منصتك",
|
||||
"channel.validationError": "يرجى ملء معرف التطبيق والرمز",
|
||||
"channel.verificationToken": "رمز التحقق",
|
||||
"channel.verificationTokenHint": "اختياري. يُستخدم للتحقق من مصدر أحداث الويب هوك.",
|
||||
|
||||
+118
-8
@@ -36,7 +36,47 @@
|
||||
"builtinCopilot": "المساعد المدمج",
|
||||
"chatList.expandMessage": "توسيع الرسالة",
|
||||
"chatList.longMessageDetail": "عرض التفاصيل",
|
||||
"claudeCodeInstallGuide.actions.openDocs": "افتح دليل التثبيت",
|
||||
"claudeCodeInstallGuide.actions.openSystemTools": "افتح أدوات النظام",
|
||||
"claudeCodeInstallGuide.afterInstall": "بعد التثبيت، شغّل Claude Code مرة واحدة لتسجيل الدخول، ثم أعد محاولة إرسال رسالتك أو انقر على إعادة الكشف في أدوات النظام.",
|
||||
"claudeCodeInstallGuide.desc": "يتطلب Claude Code وجود واجهة سطر أوامر Claude Code للعمل محليًا. قم بتثبيتها وتأكد من أن أمر `claude` متاح في مسار PATH.",
|
||||
"claudeCodeInstallGuide.installWithBrew": "Homebrew",
|
||||
"claudeCodeInstallGuide.installWithNpm": "التثبيت الموصى به",
|
||||
"claudeCodeInstallGuide.menuNotification.title": "واجهة سطر أوامر Claude Code غير موجودة",
|
||||
"claudeCodeInstallGuide.reason": "تعذر على LobeHub تشغيل Claude Code: {{message}}",
|
||||
"claudeCodeInstallGuide.title": "ثبّت واجهة سطر أوامر Claude Code",
|
||||
"clearCurrentMessages": "مسح رسائل الجلسة الحالية",
|
||||
"cliAuthGuide.actions.openDocs": "افتح دليل تسجيل الدخول",
|
||||
"cliAuthGuide.actions.openSystemTools": "افتح أدوات النظام",
|
||||
"cliAuthGuide.afterLogin": "بعد تسجيل الدخول مرة أخرى أو تحديث بيانات الاعتماد، أعد محاولة إرسال رسالتك. يمكنك أيضًا إعادة الكشف من أدوات النظام.",
|
||||
"cliAuthGuide.desc": "تعذر على {{name}} المتابعة لأن جلسة تسجيل الدخول انتهت أو بيانات الاعتماد غير صالحة.",
|
||||
"cliAuthGuide.errorDetails": "تفاصيل الخطأ",
|
||||
"cliAuthGuide.runCommand": "شغّل هذا في الطرفية",
|
||||
"cliAuthGuide.title": "سجّل الدخول إلى {{name}}",
|
||||
"cliRateLimitGuide.actions.openSystemTools": "افتح أدوات النظام",
|
||||
"cliRateLimitGuide.afterReset": "انتظر حتى وقت إعادة التعيين، ثم أعد محاولة إرسال رسالتك. إذا كنت تستخدم ترخيص API، يمكنك أيضًا التحقق من الحصة والحالة المالية لدى مزود الخدمة.",
|
||||
"cliRateLimitGuide.desc": "لقد وصل {{name}} إلى حد الاستخدام الحالي ولا يمكنه متابعة التشغيل الآن.",
|
||||
"cliRateLimitGuide.limitType": "نافذة الحد",
|
||||
"cliRateLimitGuide.limitTypes.weekCycle": "دورة أسبوعية",
|
||||
"cliRateLimitGuide.relative.day_one": "{{count}} يوم",
|
||||
"cliRateLimitGuide.relative.day_other": "{{count}} أيام",
|
||||
"cliRateLimitGuide.relative.hour_one": "{{count}} ساعة",
|
||||
"cliRateLimitGuide.relative.hour_other": "{{count}} ساعات",
|
||||
"cliRateLimitGuide.relative.minute_one": "{{count}} دقيقة",
|
||||
"cliRateLimitGuide.relative.minute_other": "{{count}} دقائق",
|
||||
"cliRateLimitGuide.relative.soon": "يعاد التعيين قريبًا",
|
||||
"cliRateLimitGuide.resetAt": "يعاد التعيين في",
|
||||
"cliRateLimitGuide.resetInApprox": "يعاد التعيين تقريبًا خلال {{duration}}",
|
||||
"cliRateLimitGuide.title": "تم الوصول إلى حد استخدام {{name}}",
|
||||
"codexInstallGuide.actions.openDocs": "افتح دليل التثبيت",
|
||||
"codexInstallGuide.actions.openSystemTools": "افتح أدوات النظام",
|
||||
"codexInstallGuide.afterInstall": "بعد التثبيت، شغّل Codex مرة واحدة لتسجيل الدخول، ثم أعد محاولة إرسال رسالتك أو انقر على إعادة الكشف في أدوات النظام.",
|
||||
"codexInstallGuide.desc": "يتطلب Codex Agent وجود واجهة سطر أوامر Codex للعمل محليًا. قم بتثبيتها وتأكد من أن أمر codex متاح في مسار PATH.",
|
||||
"codexInstallGuide.installWithBrew": "Homebrew (لنظام macOS)",
|
||||
"codexInstallGuide.installWithNpm": "التثبيت الموصى به",
|
||||
"codexInstallGuide.menuNotification.title": "واجهة سطر أوامر Codex غير موجودة",
|
||||
"codexInstallGuide.reason": "تعذر على LobeHub تشغيل Codex: {{message}}",
|
||||
"codexInstallGuide.title": "ثبّت واجهة سطر أوامر Codex",
|
||||
"compressedHistory": "السجل المضغوط",
|
||||
"compression.cancel": "إلغاء الضغط",
|
||||
"compression.cancelConfirm": "هل أنت متأكد أنك تريد إلغاء الضغط؟ سيؤدي ذلك إلى استعادة الرسائل الأصلية.",
|
||||
@@ -65,6 +105,8 @@
|
||||
"defaultSession": "الوكيل الافتراضي",
|
||||
"desktopNotification.aiReplyCompleted.body": "رد الوكيل جاهز",
|
||||
"desktopNotification.aiReplyCompleted.title": "تم إكمال الرد",
|
||||
"desktopNotification.humanApprovalRequired.body": "أحد الوكلاء يحتاج إلى موافقتك للمتابعة",
|
||||
"desktopNotification.humanApprovalRequired.title": "الموافقة مطلوبة",
|
||||
"dm.placeholder": "ستظهر رسائلك الخاصة مع {{agentTitle}} هنا.",
|
||||
"dm.tooltip": "إرسال رسالة خاصة",
|
||||
"dm.visibleTo": "مرئي فقط لـ {{target}}",
|
||||
@@ -81,7 +123,7 @@
|
||||
"extendParams.effort.title": "الجهد",
|
||||
"extendParams.enableAdaptiveThinking.desc": "اسمح لكلود باتخاذ قرارات ديناميكية حول متى وكم يفكر باستخدام وضع التفكير التكيفي.",
|
||||
"extendParams.enableAdaptiveThinking.title": "تفعيل التفكير التكيفي",
|
||||
"extendParams.enableReasoning.desc": "استنادًا إلى حد آلية التفكير في Claude. <1>اعرف المزيد</1>",
|
||||
"extendParams.enableReasoning.desc": "اسمح للنموذج بالتفكير قبل الإجابة. استخدمه للمهام المعقدة.",
|
||||
"extendParams.enableReasoning.title": "تفعيل التفكير العميق",
|
||||
"extendParams.imageAspectRatio.title": "نسبة أبعاد الصورة",
|
||||
"extendParams.imageResolution.title": "دقة الصورة",
|
||||
@@ -95,6 +137,7 @@
|
||||
"extendParams.urlContext.desc": "عند التفعيل، سيتم تحليل الروابط تلقائيًا لاستخراج محتوى صفحة الويب الفعلي",
|
||||
"extendParams.urlContext.title": "استخراج محتوى رابط الويب",
|
||||
"followUpPlaceholder": "متابعة. @ لإسناد مهام لوكلاء آخرين.",
|
||||
"followUpPlaceholderHeterogeneous": "تابع.",
|
||||
"group.desc": "ادفع المهمة للأمام مع عدة وكلاء في مساحة مشتركة واحدة.",
|
||||
"group.memberTooltip": "يوجد {{count}} عضو في المجموعة",
|
||||
"group.orchestratorThinking": "المنسق يفكر...",
|
||||
@@ -139,6 +182,7 @@
|
||||
"heteroAgent.fullAccess.label": "وصول كامل",
|
||||
"heteroAgent.fullAccess.tooltip": "يعمل Claude Code محليًا مع صلاحية قراءة/كتابة كاملة في دليل العمل. تبديل أوضاع الصلاحيات غير متاح بعد.",
|
||||
"heteroAgent.resumeReset.cwdChanged": "تم تغيير دليل العمل. لا يمكن استئناف جلسة Claude Code السابقة إلا من دليلها الأصلي، لذا بدأت محادثة جديدة.",
|
||||
"heteroAgent.resumeReset.resumeFailed": "تعذر استئناف محادثة Codex المحفوظة بأمان، لذا بدأت محادثة جديدة لهذا الموضوع.",
|
||||
"heteroAgent.switchCwd.cancel": "إلغاء",
|
||||
"heteroAgent.switchCwd.content": "جلسات Claude Code مرتبطة بدليل عمل محدد. سيؤدي التبديل إلى بدء جلسة جديدة لهذا الموضوع — ستبقى رسائل الدردشة، لكن لا يمكن استعادة سياق الجلسة السابقة.",
|
||||
"heteroAgent.switchCwd.ok": "تبديل وبدء جلسة جديدة",
|
||||
@@ -161,6 +205,9 @@
|
||||
"input.stop": "إيقاف",
|
||||
"input.warp": "سطر جديد",
|
||||
"input.warpWithKey": "اضغط <key/> لإدراج فاصل أسطر",
|
||||
"inputQueue.delete": "حذف",
|
||||
"inputQueue.edit": "تحرير",
|
||||
"inputQueue.sendNow": "أرسل الآن (يُوقف التشغيل الحالي)",
|
||||
"intentUnderstanding.title": "جارٍ فهم نيتك...",
|
||||
"inviteMembers": "دعوة الأعضاء",
|
||||
"knowledgeBase.all": "كل المحتوى",
|
||||
@@ -204,6 +251,8 @@
|
||||
"messageAction.interruptedHint": "ماذا يجب أن أفعل بدلاً من ذلك؟",
|
||||
"messageAction.reaction": "إضافة تفاعل",
|
||||
"messageAction.regenerate": "إعادة التوليد",
|
||||
"messageLongCollapse.collapse": "عرض أقل",
|
||||
"messageLongCollapse.expand": "عرض المزيد",
|
||||
"messages.dm.sentTo": "مرئي فقط لـ {{name}}",
|
||||
"messages.dm.title": "رسالة خاصة",
|
||||
"messages.modelCard.credit": "الأرصدة",
|
||||
@@ -245,6 +294,7 @@
|
||||
"minimap.senderUser": "أنت",
|
||||
"newAgent": "إنشاء وكيل",
|
||||
"newClaudeCodeAgent": "إضافة Claude Code",
|
||||
"newCodexAgent": "أضف Codex",
|
||||
"newGroupChat": "إنشاء مجموعة",
|
||||
"newPage": "إنشاء صفحة",
|
||||
"noAgentsYet": "لا يوجد أعضاء في هذه المجموعة بعد. انقر على زر + لدعوة وكلاء.",
|
||||
@@ -424,7 +474,14 @@
|
||||
"taskDetail.activities.fallback.topic": "بدأ موضوعًا",
|
||||
"taskDetail.activitiesEmpty": "لا توجد أي نشاطات بعد",
|
||||
"taskDetail.addSubtask": "إضافة مهمة فرعية",
|
||||
"taskDetail.artifactMenu.delete": "إزالة من المهمة",
|
||||
"taskDetail.artifactMenu.deleteConfirm.content": "لن يظهر هذا المُنتج مرة أخرى في مساحة عمل المهمة.",
|
||||
"taskDetail.artifactMenu.deleteConfirm.ok": "إزالة",
|
||||
"taskDetail.artifactMenu.deleteConfirm.title": "إزالة هذا المُنتج؟",
|
||||
"taskDetail.artifactSize": "{{value}} حرف",
|
||||
"taskDetail.artifacts": "المنتجات",
|
||||
"taskDetail.blockedBy": "محجوب بواسطة {{id}}",
|
||||
"taskDetail.cancelSchedule": "إلغاء الجدولة",
|
||||
"taskDetail.comment.cancel": "إلغاء",
|
||||
"taskDetail.comment.delete": "حذف",
|
||||
"taskDetail.comment.deleteConfirm.content": "سيتم حذف هذا التعليق بشكل دائم.",
|
||||
@@ -439,7 +496,6 @@
|
||||
"taskDetail.instruction": "التعليمات",
|
||||
"taskDetail.instructionPlaceholder": "اضغط لتحرير تعليمات المهمة...",
|
||||
"taskDetail.latestActivity.brief": "الموجز: {{title}}",
|
||||
"taskDetail.latestActivity.briefOnly": "الموجز",
|
||||
"taskDetail.latestActivity.briefWithAction": "{{title}} - {{action}}",
|
||||
"taskDetail.latestActivity.briefWithType": "الموجز ({{type}}): {{title}}",
|
||||
"taskDetail.latestActivity.briefWithTypeOnly": "الموجز ({{type}})",
|
||||
@@ -448,6 +504,7 @@
|
||||
"taskDetail.latestActivity.untitledTopic": "موضوع بدون عنوان",
|
||||
"taskDetail.modelConfig": "تجاوز الإعدادات النموذجية",
|
||||
"taskDetail.navigation": "التنقل",
|
||||
"taskDetail.nextRunCountdown": "التشغيل التالي خلال {{countdown}}",
|
||||
"taskDetail.pauseTask": "إيقاف المهمة مؤقتًا",
|
||||
"taskDetail.priority.high": "عالية",
|
||||
"taskDetail.priority.low": "منخفضة",
|
||||
@@ -465,25 +522,50 @@
|
||||
"taskDetail.status.failed": "فشلت",
|
||||
"taskDetail.status.paused": "متوقفة مؤقتًا",
|
||||
"taskDetail.status.running": "قيد التنفيذ",
|
||||
"taskDetail.status.scheduled": "مجدول",
|
||||
"taskDetail.stopTask": "إيقاف المهمة",
|
||||
"taskDetail.subIssueOf": "فرعية لـ",
|
||||
"taskDetail.subtaskInstructionPlaceholder": "صف المهمة الفرعية...",
|
||||
"taskDetail.subtasks": "المهام الفرعية",
|
||||
"taskDetail.titlePlaceholder": "أدخل عنوان المهمة...",
|
||||
"taskDetail.topicDrawer.untitled": "بلا عنوان",
|
||||
"taskDetail.untitled": "بدون عنوان",
|
||||
"taskDetail.updateFailed": "فشل تحديث المهمة",
|
||||
"taskList.activeTasks": "المهام النشطة",
|
||||
"taskList.all": "جميع المهام",
|
||||
"taskList.assigneeSearch.empty": "لا يوجد وكيل مطابق",
|
||||
"taskList.assigneeSearch.placeholder": "ابحث عن وكيل...",
|
||||
"taskList.breadcrumb.task": "مهمة",
|
||||
"taskList.contextMenu.copyId": "نسخ المعرّف",
|
||||
"taskList.contextMenu.copyIdSuccess": "تم نسخ المعرّف",
|
||||
"taskList.contextMenu.copyLink": "نسخ الرابط",
|
||||
"taskList.contextMenu.copyLinkSuccess": "تم نسخ الرابط",
|
||||
"taskList.contextMenu.priority": "الأولوية",
|
||||
"taskList.contextMenu.status": "الحالة",
|
||||
"taskList.empty": "لا توجد مهام بعد",
|
||||
"taskList.form.grouping": "التجميع",
|
||||
"taskList.form.orderCompletedByRecency": "ترتيب المهام المكتملة حسب الأحدث",
|
||||
"taskList.form.ordering": "الترتيب",
|
||||
"taskList.form.showCompleted": "عرض المكتملة والمُلغاة",
|
||||
"taskList.form.subGrouping": "التجميع الفرعي",
|
||||
"taskList.groupBy.assignee": "المكلّف",
|
||||
"taskList.groupBy.none": "دون تجميع",
|
||||
"taskList.groupBy.priority": "الأولوية",
|
||||
"taskList.groupBy.status": "الحالة",
|
||||
"taskList.hiddenCompleted.count_one": "{{count}} مهمة",
|
||||
"taskList.hiddenCompleted.count_other": "{{count}} مهام",
|
||||
"taskList.hiddenCompleted.show": "عرض",
|
||||
"taskList.hiddenCompleted.suffix": "مخفية بواسطة خيارات العرض",
|
||||
"taskList.kanban.addTask": "إنشاء مهمة",
|
||||
"taskList.kanban.backlog": "قائمة الانتظار",
|
||||
"taskList.kanban.canceled": "ألغيت",
|
||||
"taskList.kanban.done": "منجزة",
|
||||
"taskList.kanban.emptyColumn": "لا توجد مهام",
|
||||
"taskList.kanban.hiddenColumns": "أعمدة مخفية",
|
||||
"taskList.kanban.hideColumn": "إخفاء العمود",
|
||||
"taskList.kanban.needsInput": "بانتظار المراجعة",
|
||||
"taskList.kanban.running": "قيد التنفيذ",
|
||||
"taskList.kanban.showColumn": "إظهار العمود",
|
||||
"taskList.orderBy.assignee": "المكلّف",
|
||||
"taskList.orderBy.createdAt": "تاريخ الإنشاء",
|
||||
"taskList.orderBy.priority": "الأولوية",
|
||||
@@ -492,24 +574,43 @@
|
||||
"taskList.orderBy.updatedAt": "تاريخ التحديث",
|
||||
"taskList.title": "المهام",
|
||||
"taskList.unassigned": "غير مكلّف",
|
||||
"taskList.unassignedHint": "سيقوم Lobe AI بتشغيل هذه المهمة عند عدم تعيين منفذ",
|
||||
"taskList.view.board": "لوحة",
|
||||
"taskList.view.list": "قائمة",
|
||||
"taskList.viewAll": "عرض الكل",
|
||||
"taskSchedule.advancedSettings": "إعدادات متقدمة",
|
||||
"taskSchedule.clear": "مسح",
|
||||
"taskSchedule.continuous": "مستمر",
|
||||
"taskSchedule.enable": "تفعيل الأتمتة",
|
||||
"taskSchedule.every": "كل",
|
||||
"taskSchedule.frequency": "التكرار",
|
||||
"taskSchedule.heading": "الأتمتة",
|
||||
"taskSchedule.hours": "ساعات",
|
||||
"taskSchedule.interval": "متكرّر",
|
||||
"taskSchedule.intervalLabel": "فترة التشغيل",
|
||||
"taskSchedule.intervalSuffix": "كل مرة",
|
||||
"taskSchedule.intervalTab": "متكرّر",
|
||||
"taskSchedule.maxExecutions": "الحد الأقصى لعدد التشغيلات",
|
||||
"taskSchedule.maxExecutionsPlaceholder": "غير محدود",
|
||||
"taskSchedule.minutes": "دقائق",
|
||||
"taskSchedule.nextRun": "التشغيل التالي",
|
||||
"taskSchedule.nextRun.format": "MMM D HH:mm",
|
||||
"taskSchedule.scheduleType.daily": "يومي",
|
||||
"taskSchedule.scheduleType.hourly": "كل ساعة",
|
||||
"taskSchedule.scheduleType.weekly": "أسبوعي",
|
||||
"taskSchedule.scheduler": "الجدولة",
|
||||
"taskSchedule.schedulerNotReady": "ميزة الجدولة ستتوفر قريبًا. استخدم التكرار الآن.",
|
||||
"taskSchedule.schedulerTab": "الجدولة",
|
||||
"taskSchedule.seconds": "ثوانٍ",
|
||||
"taskSchedule.summary.daily": "يوميًا عند {{time}}",
|
||||
"taskSchedule.summary.disabled": "الأتمتة متوقفة",
|
||||
"taskSchedule.summary.everyNHours": "كل {{count}} ساعات{{minute}}",
|
||||
"taskSchedule.summary.heartbeat": "يعمل كل {{interval}}",
|
||||
"taskSchedule.summary.hourly": "كل ساعة{{minute}}",
|
||||
"taskSchedule.summary.weekly": "كل {{days}} عند {{time}}",
|
||||
"taskSchedule.tag.add": "تعيين جدول",
|
||||
"taskSchedule.tag.every": "كل {{interval}}",
|
||||
"taskSchedule.tag.heartbeat": "نبض · {{every}}",
|
||||
"taskSchedule.tag.schedule": "الجدول · {{schedule}}{{timezone}}",
|
||||
"taskSchedule.time": "الوقت",
|
||||
"taskSchedule.timezone": "المنطقة الزمنية",
|
||||
"taskSchedule.title": "الجدول",
|
||||
"taskSchedule.unit.hour_one": "{{count}} ساعة",
|
||||
"taskSchedule.unit.hour_other": "{{count}} ساعات",
|
||||
@@ -517,6 +618,14 @@
|
||||
"taskSchedule.unit.minute_other": "{{count}} دقائق",
|
||||
"taskSchedule.unit.second_one": "{{count}} ثانية",
|
||||
"taskSchedule.unit.second_other": "{{count}} ثوانٍ",
|
||||
"taskSchedule.weekday": "يوم الأسبوع",
|
||||
"taskSchedule.weekdays.fri": "الجمعة",
|
||||
"taskSchedule.weekdays.mon": "الاثنين",
|
||||
"taskSchedule.weekdays.sat": "السبت",
|
||||
"taskSchedule.weekdays.sun": "الأحد",
|
||||
"taskSchedule.weekdays.thu": "الخميس",
|
||||
"taskSchedule.weekdays.tue": "الثلاثاء",
|
||||
"taskSchedule.weekdays.wed": "الأربعاء",
|
||||
"thread.closeSubagentThread": "طي محادثة الوكيل الفرعي",
|
||||
"thread.divider": "موضوع فرعي",
|
||||
"thread.openSubagentThread": "عرض محادثة الوكيل الفرعي كاملة",
|
||||
@@ -605,6 +714,9 @@
|
||||
"viewMode.fullWidth": "العرض الكامل",
|
||||
"viewMode.normal": "قياسي",
|
||||
"viewMode.wideScreen": "شاشة عريضة",
|
||||
"viewSwitcher.chat": "دردشة",
|
||||
"viewSwitcher.page": "صفحة",
|
||||
"viewSwitcher.task": "مهمة",
|
||||
"workflow.awaitingConfirmation": "بانتظار تأكيدك",
|
||||
"workflow.collapse": "طي",
|
||||
"workflow.expandFull": "توسيع كامل",
|
||||
@@ -633,7 +745,6 @@
|
||||
"workflow.toolDisplayName.createTodos": "المهام المُنشأة",
|
||||
"workflow.toolDisplayName.deleteAgent": "تم حذف وكيل",
|
||||
"workflow.toolDisplayName.deleteDocument": "تم حذف مستند",
|
||||
"workflow.toolDisplayName.editDocument": "تم تعديل مستند",
|
||||
"workflow.toolDisplayName.editLocalFile": "تم تعديل ملف",
|
||||
"workflow.toolDisplayName.editTitle": "العنوان المُعدَّل",
|
||||
"workflow.toolDisplayName.evaluate": "التعبير المُقيَّم",
|
||||
@@ -660,13 +771,13 @@
|
||||
"workflow.toolDisplayName.modifyNodes": "الصفحة المُعدَّلة",
|
||||
"workflow.toolDisplayName.moveLocalFiles": "تم نقل الملفات",
|
||||
"workflow.toolDisplayName.readDocument": "قراءة مستند",
|
||||
"workflow.toolDisplayName.readDocumentByFilename": "قراءة مستند",
|
||||
"workflow.toolDisplayName.readKnowledge": "قراءة المعرفة",
|
||||
"workflow.toolDisplayName.readLocalFile": "قراءة ملف",
|
||||
"workflow.toolDisplayName.removeDocument": "تمت إزالة مستند",
|
||||
"workflow.toolDisplayName.removeIdentityMemory": "تمت إزالة الذاكرة",
|
||||
"workflow.toolDisplayName.renameDocument": "أعاد تسمية مستند",
|
||||
"workflow.toolDisplayName.renameLocalFile": "تمت إعادة تسمية ملف",
|
||||
"workflow.toolDisplayName.replaceDocumentContent": "تم استبدال محتوى المستند",
|
||||
"workflow.toolDisplayName.replaceText": "تم استبدال النص",
|
||||
"workflow.toolDisplayName.runCommand": "تم تنفيذ أمر",
|
||||
"workflow.toolDisplayName.search": "تم البحث في الويب",
|
||||
@@ -682,7 +793,6 @@
|
||||
"workflow.toolDisplayName.updateLoadRule": "تم تحديث قاعدة التحميل",
|
||||
"workflow.toolDisplayName.updatePlan": "الخطة المحدَّثة",
|
||||
"workflow.toolDisplayName.updateTodos": "تم تحديث المهام",
|
||||
"workflow.toolDisplayName.upsertDocumentByFilename": "تم تحديث مستند",
|
||||
"workflow.toolDisplayName.writeLocalFile": "تمت كتابة ملف",
|
||||
"workflow.working": "جارٍ العمل...",
|
||||
"workingPanel.agentDocuments": "Agent Documents",
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"back": "رجوع",
|
||||
"batchDelete": "حذف جماعي",
|
||||
"blog": "مدونة المنتج",
|
||||
"botIntegrationBanner.dismiss": "إغلاق",
|
||||
"botIntegrationBanner.title": "إضافة قنوات إلى LobeAI",
|
||||
"branching": "إنشاء موضوع فرعي",
|
||||
"branchingDisable": "ميزة \"الموضوع الفرعي\" غير متاحة في الوضع الحالي. لاستخدام هذه الميزة، يرجى التبديل إلى وضع قاعدة بيانات Postgres/Pglite أو استخدام LobeHub Cloud.",
|
||||
"branchingRequiresSavedTopic": "الموضوع الحالي غير محفوظ، يرجى حفظه أولاً لاستخدام ميزة الموضوع الفرعي",
|
||||
@@ -146,6 +148,7 @@
|
||||
"cmdk.keywords.starGitHub": "جيت هاب نجمة مفضلة إعجاب",
|
||||
"cmdk.keywords.stats": "إحصائيات تحليلات بيانات",
|
||||
"cmdk.keywords.submitIssue": "مشكلة خلل عطل ملاحظات",
|
||||
"cmdk.keywords.tasks": "مهام قائمة_مهام وكيل كانبان",
|
||||
"cmdk.keywords.usage": "الاستخدام الإحصائيات الاستهلاك الحصة",
|
||||
"cmdk.keywords.video": "فيديو،إنشاء،سيدانص،كلينغ",
|
||||
"cmdk.memory": "الذاكرة",
|
||||
@@ -195,6 +198,7 @@
|
||||
"cmdk.settings": "الإعدادات",
|
||||
"cmdk.starOnGitHub": "قيّمنا على GitHub",
|
||||
"cmdk.submitIssue": "إرسال مشكلة",
|
||||
"cmdk.tasks": "المهام",
|
||||
"cmdk.theme": "السمة",
|
||||
"cmdk.themeAuto": "تلقائي",
|
||||
"cmdk.themeCurrent": "الحالي",
|
||||
@@ -266,6 +270,7 @@
|
||||
"footer.title": "هل أعجبك منتجنا؟",
|
||||
"fullscreen": "وضع ملء الشاشة",
|
||||
"generation.hero.taglinePrefix": "ابدأ في الإبداع مع",
|
||||
"generation.promptModeration.blocked": "فشل فحص سياسة المحتوى. يرجى تعديل طلبك.",
|
||||
"getDesktopApp": "احصل على تطبيق سطح المكتب",
|
||||
"historyRange": "نطاق السجل",
|
||||
"home.suggestQuestions": "جرّب هذه الأمثلة",
|
||||
@@ -423,6 +428,7 @@
|
||||
"tab.discover": "اكتشف",
|
||||
"tab.eval": "مختبر التقييم",
|
||||
"tab.files": "الملفات",
|
||||
"tab.generation": "التوليد",
|
||||
"tab.home": "الرئيسية",
|
||||
"tab.image": "صورة",
|
||||
"tab.knowledgeBase": "المكتبة",
|
||||
@@ -433,6 +439,7 @@
|
||||
"tab.resource": "الموارد",
|
||||
"tab.search": "بحث",
|
||||
"tab.setting": "الإعدادات",
|
||||
"tab.tasks": "المهام",
|
||||
"tab.video": "الفيديو",
|
||||
"telemetry.allow": "السماح",
|
||||
"telemetry.deny": "رفض",
|
||||
|
||||
@@ -624,6 +624,8 @@
|
||||
"user.logout": "تسجيل الخروج",
|
||||
"user.myProfile": "ملفي الشخصي",
|
||||
"user.noAgents": "لم ينشر هذا المستخدم أي وكلاء بعد",
|
||||
"user.noAgents.ownerDescription": "أنشئ وكيلك الأول وشاركه مع المجتمع.",
|
||||
"user.noAgents.title": "لا يوجد وكلاء بعد",
|
||||
"user.noFavoriteAgents": "لا يوجد وكلاء محفوظون بعد",
|
||||
"user.noFavoritePlugins": "لا توجد مهارات محفوظة بعد",
|
||||
"user.noForkedAgentGroups": "لا توجد مجموعات وكلاء منسوخة بعد",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"navigation.recentView": "المشاهدات الأخيرة",
|
||||
"navigation.resources": "الموارد",
|
||||
"navigation.settings": "الإعدادات",
|
||||
"navigation.tasks": "المهام",
|
||||
"navigation.unpin": "إلغاء التثبيت",
|
||||
"notification.finishChatGeneration": "اكتمل توليد الرسالة بواسطة الذكاء الاصطناعي",
|
||||
"proxy.auth": "يتطلب المصادقة",
|
||||
|
||||
@@ -111,6 +111,8 @@
|
||||
"response.PluginServerError": "أعاد خادم المهارة خطأ. يرجى التحقق من ملف تعريف المهارة أو إعدادات الخادم.",
|
||||
"response.PluginSettingsInvalid": "تتطلب هذه المهارة إعدادًا صحيحًا قبل الاستخدام. يرجى التحقق من الإعدادات.",
|
||||
"response.ProviderBizError": "حدث خطأ أثناء طلب خدمة {{provider}}، يرجى التحقق أو إعادة المحاولة.",
|
||||
"response.ProviderContentModeration": "فشل التحقق من سياسة المحتوى. عدّل طلبك وحاول مرة أخرى.",
|
||||
"response.ProviderContentModerationWarning": "تم رصد انتهاكات متكررة لسياسة الاستخدام. قد يؤدي أي سوء استخدام إضافي إلى تقييد حسابك.",
|
||||
"response.QuotaLimitReached": "عذرًا، تم الوصول إلى الحد الأقصى لاستخدام الرموز أو عدد الطلبات لهذا المفتاح. يرجى زيادة الحصة أو المحاولة لاحقًا.",
|
||||
"response.QuotaLimitReachedCloud": "خدمة النموذج تحت ضغط كبير حاليًا. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"response.ServerAgentRuntimeError": "عذرًا، خدمة الوكيل غير متوفرة حاليًا. يرجى المحاولة لاحقًا أو التواصل معنا عبر البريد الإلكتروني.",
|
||||
|
||||
@@ -5,12 +5,18 @@
|
||||
"agentSelection.search": "لم يتم العثور على وكلاء مطابقين",
|
||||
"brief.action.acknowledge": "إقرار",
|
||||
"brief.action.approve": "الموافقة",
|
||||
"brief.action.confirmDone": "تأكيد الإنهاء",
|
||||
"brief.action.feedback": "ملاحظات",
|
||||
"brief.action.retry": "إعادة المحاولة",
|
||||
"brief.addFeedback": "مشاركة الملاحظات",
|
||||
"brief.collapse": "عرض أقل",
|
||||
"brief.commentPlaceholder": "شارك ملاحظاتك...",
|
||||
"brief.commentSubmit": "إرسال الملاحظات",
|
||||
"brief.delete": "حذف",
|
||||
"brief.deleteConfirm.content": "سيتم إزالة هذه الموجزة بشكل نهائي.",
|
||||
"brief.deleteConfirm.ok": "حذف",
|
||||
"brief.deleteConfirm.title": "هل تريد حذف هذه الموجزة؟",
|
||||
"brief.editResult": "تعديل",
|
||||
"brief.expandAll": "عرض المزيد",
|
||||
"brief.feedbackSent": "تمت مشاركة الملاحظات",
|
||||
"brief.resolved": "تم وضع علامة كمُعالَج",
|
||||
@@ -21,9 +27,13 @@
|
||||
"starter.createAgent": "إنشاء وكيل",
|
||||
"starter.createGroup": "إنشاء مجموعة",
|
||||
"starter.deepResearch": "بحث معمق",
|
||||
"starter.deepseekV4Pro": "DeepSeek V4 Pro",
|
||||
"starter.deepseekV4ProAlready": "أنت تستخدم بالفعل DeepSeek V4 Pro",
|
||||
"starter.deepseekV4ProSwitched": "تم التبديل إلى DeepSeek V4 Pro",
|
||||
"starter.developing": "قريبًا",
|
||||
"starter.image": "صورة",
|
||||
"starter.imageGeneration": "توليد الصور",
|
||||
"starter.newLabel": "جديد",
|
||||
"starter.videoGeneration": "Seedance 2.0",
|
||||
"starter.write": "كتابة"
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
"deleteLastMessage.title": "حذف الرسالة الأخيرة",
|
||||
"desktop.openSettings.desc": "افتح صفحة إعدادات التطبيق",
|
||||
"desktop.openSettings.title": "إعدادات التطبيق",
|
||||
"desktop.quickChat.desc": "افتح نافذة الدردشة السريعة باستخدام اختصار عام",
|
||||
"desktop.quickChat.title": "الدردشة السريعة",
|
||||
"desktop.quickComposer.desc": "افتح المُنشئ السريع باستخدام اختصار عام",
|
||||
"desktop.quickComposer.title": "المُنشئ السريع",
|
||||
"desktop.showApp.desc": "تبديل ظهور النافذة الرئيسية باستخدام اختصار عام",
|
||||
"desktop.showApp.title": "إظهار/إخفاء النافذة الرئيسية",
|
||||
"editMessage.desc": "ادخل وضع التعديل بالضغط على Alt والنقر المزدوج على الرسالة",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"features.agentSelfIteration.desc": "السماح للمساعد بالتفكير الذاتي، وبناء الوعي الذاتي، والاستمرار في التطوير عبر المحاولات والتفاعلات المتواصلة.",
|
||||
"features.agentSelfIteration.title": "التكرار الذاتي للوكيل",
|
||||
"features.assistantMessageGroup.desc": "تجميع رسائل الوكيل ونتائج استدعاء الأدوات معًا للعرض",
|
||||
"features.assistantMessageGroup.title": "تجميع رسائل الوكيل",
|
||||
"features.gatewayMode.desc": "تنفيذ مهام الوكيل على الخادم عبر بوابة WebSocket بدلًا من التشغيل محليًا، مما يتيح تنفيذًا أسرع ويقلل من استهلاك موارد العميل.",
|
||||
"features.gatewayMode.title": "تنفيذ الوكيل من جانب الخادم (البوابة)",
|
||||
"features.groupChat.desc": "تمكين تنسيق الدردشة الجماعية متعددة الوكلاء.",
|
||||
"features.groupChat.title": "دردشة جماعية (متعددة الوكلاء)",
|
||||
"features.heterogeneousAgent.desc": "تمكين تنفيذ الوكلاء المتغايرين باستخدام Claude Code وCodex CLI وواجهات سطر الأوامر الأخرى الخاصة بالوكلاء الخارجيين. يقوم بإنشاء خيار \"وكيل Claude Code\" في قائمة الوكلاء بالشريط الجانبي.",
|
||||
"features.heterogeneousAgent.title": "وكيل متغاير (Claude Code)",
|
||||
"features.inputMarkdown.desc": "عرض Markdown في منطقة الإدخال في الوقت الفعلي (نص عريض، كتل الشيفرة، جداول، إلخ).",
|
||||
"features.inputMarkdown.title": "عرض Markdown في الإدخال",
|
||||
"title": "المختبرات"
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
"providerModels.item.modelConfig.displayName.title": "اسم عرض النموذج",
|
||||
"providerModels.item.modelConfig.extendParams.extra": "اختر المعلمات الموسعة التي يدعمها النموذج. مرّر المؤشر فوق خيار لمعاينة عناصر التحكم. قد تؤدي التهيئة غير الصحيحة إلى فشل الطلب.",
|
||||
"providerModels.item.modelConfig.extendParams.options.codexMaxReasoningEffort.hint": "لنماذج Codex؛ يتحكم في شدة التفكير.",
|
||||
"providerModels.item.modelConfig.extendParams.options.deepseekV4ReasoningEffort.hint": "لوضع التفكير في DeepSeek V4؛ يتحكم في شدة التفكير. القيمة الافتراضية هي high، بينما max يفعّل أعمق مستويات الاستدلال المستخدمة في مهام الوكلاء المعقّدة.",
|
||||
"providerModels.item.modelConfig.extendParams.options.disableContextCaching.hint": "لنماذج Claude؛ يمكن أن يقلل التكلفة ويسرّع الاستجابات.",
|
||||
"providerModels.item.modelConfig.extendParams.options.effort.hint": "لـ Claude Opus 4.6؛ يتحكم في مستوى الجهد (منخفض/متوسط/عالٍ/أقصى).",
|
||||
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "لـ Claude Opus 4.6؛ يفعّل أو يعطّل التفكير التكيفي.",
|
||||
@@ -226,6 +227,7 @@
|
||||
"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.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).",
|
||||
"providerModels.item.modelConfig.extendParams.options.imageResolution.hint": "لنماذج توليد الصور من Gemini 3؛ يتحكم في دقة الصور المُولدة.",
|
||||
|
||||
+87
-63
@@ -13,6 +13,10 @@
|
||||
"360zhinao3-o1.5.description": "360 Zhinao نموذج الاستدلال من الجيل التالي.",
|
||||
"4.0Ultra.description": "Spark Ultra هو أقوى نموذج في سلسلة Spark، يعزز فهم النصوص وتلخيصها، ويطور البحث عبر الإنترنت. يُعد حلاً شاملاً لزيادة الإنتاجية في بيئة العمل وتقديم إجابات دقيقة، مما يجعله منتجًا ذكيًا رائدًا.",
|
||||
"AnimeSharp.description": "AnimeSharp (المعروف أيضًا باسم \"4x-AnimeSharp\") هو نموذج مفتوح المصدر لتحسين دقة الصور يعتمد على ESRGAN من Kim2091، يركز على تكبير وتحسين صور الأنمي. تم تغيير اسمه من \"4x-TextSharpV1\" في فبراير 2022، وكان مخصصًا أيضًا لصور النصوص ولكنه أصبح محسنًا بشكل كبير لمحتوى الأنمي.",
|
||||
"Baichuan-M2-Plus.description": "نقدم Baichuan-M2، نموذجًا معززًا طبيًا للتفكير، مصممًا لمهام التفكير الطبي الواقعية. نبدأ من الأسئلة الطبية الواقعية ونقوم بتدريب التعلم المعزز بناءً على نظام تحقق واسع النطاق. مع الحفاظ على القدرات العامة للنموذج، حقق نموذج Baichuan-M2 تحسينًا كبيرًا في الفعالية الطبية. يعد Baichuan-M2 أفضل نموذج طبي مفتوح المصدر في العالم حتى الآن. يتفوق على جميع النماذج مفتوحة المصدر، بما في ذلك gpt-oss-120b، وكذلك العديد من النماذج المغلقة المتقدمة على معيار HealthBench. إنه النموذج مفتوح المصدر الأقرب إلى GPT-5 في القدرات الطبية. تظهر ممارستنا أن وجود نظام تحقق قوي أمر حاسم لربط قدرات النموذج بالعالم الواقعي وأن نهج التعلم المعزز الشامل يعزز بشكل أساسي قدرات التفكير الطبي للنموذج. إصدار Baichuan-M2 يدفع حدود التكنولوجيا في مجال الذكاء الاصطناعي الطبي.",
|
||||
"Baichuan-M2.description": "نقدم Baichuan-M2، نموذجًا معززًا طبيًا للتفكير، مصممًا لمهام التفكير الطبي الواقعية. نبدأ من الأسئلة الطبية الواقعية ونقوم بتدريب التعلم المعزز بناءً على نظام تحقق واسع النطاق. مع الحفاظ على القدرات العامة للنموذج، حقق نموذج Baichuan-M2 تحسينًا كبيرًا في الفعالية الطبية. يعد Baichuan-M2 أفضل نموذج طبي مفتوح المصدر في العالم حتى الآن. يتفوق على جميع النماذج مفتوحة المصدر، بما في ذلك gpt-oss-120b، وكذلك العديد من النماذج المغلقة المتقدمة على معيار HealthBench. إنه النموذج مفتوح المصدر الأقرب إلى GPT-5 في القدرات الطبية. تظهر ممارستنا أن وجود نظام تحقق قوي أمر حاسم لربط قدرات النموذج بالعالم الواقعي وأن نهج التعلم المعزز الشامل يعزز بشكل أساسي قدرات التفكير الطبي للنموذج. إصدار Baichuan-M2 يدفع حدود التكنولوجيا في مجال الذكاء الاصطناعي الطبي.",
|
||||
"Baichuan-M3-Plus.description": "نقدم Baichuan-M3، نموذج لغة كبير معزز طبيًا من الجيل الجديد مصمم لدعم المساعدة الطبية على مستوى العيادات. على عكس النهج السابقة التي تركز بشكل أساسي على الإجابة على الأسئلة الثابتة أو لعب الأدوار السطحية، يتم تدريب Baichuan-M3 على نمذجة عملية اتخاذ القرار السريري بشكل صريح، بهدف تحسين الاستخدامية والموثوقية في الممارسة الطبية الواقعية. بدلاً من تقديم إجابات تبدو معقولة أو طرح أسئلة شبيهة بالطبيب أو تقديم توصيات غامضة مثل \"يجب عليك طلب الرعاية الطبية في أقرب وقت ممكن\"، يتم تدريب Baichuan-M3 بشكل صريح على اكتساب المعلومات السريرية الحرجة، وبناء مسارات تفكير طبي متماسكة، وتقييد السلوكيات الميالة للهلوسة بشكل منهجي خلال عملية اتخاذ القرار. يمنح هذا التصميم النموذج قدرات طبية معززة جوهريًا تتماشى مع سير العمل السريري الحقيقي. عبر تقييمات الاستفسارات السريرية، قوة مقاومة الهلوسة الطبية، HealthBench، وHealthBench-Hard، يتفوق Baichuan-M3 على أحدث نموذج رئيسي أصدرته OpenAI، GPT-5.2، مما يرسخ معيارًا جديدًا في نماذج اللغة المعززة طبيًا.",
|
||||
"Baichuan-M3.description": "نقدم Baichuan-M3، نموذج لغة كبير معزز طبيًا من الجيل الجديد مصمم لدعم المساعدة الطبية على مستوى العيادات. على عكس النهج السابقة التي تركز بشكل أساسي على الإجابة على الأسئلة الثابتة أو لعب الأدوار السطحية، يتم تدريب Baichuan-M3 على نمذجة عملية اتخاذ القرار السريري بشكل صريح، بهدف تحسين الاستخدامية والموثوقية في الممارسة الطبية الواقعية. بدلاً من تقديم إجابات تبدو معقولة أو طرح أسئلة شبيهة بالطبيب أو تقديم توصيات غامضة مثل \"يجب عليك طلب الرعاية الطبية في أقرب وقت ممكن\"، يتم تدريب Baichuan-M3 بشكل صريح على اكتساب المعلومات السريرية الحرجة، وبناء مسارات تفكير طبي متماسكة، وتقييد السلوكيات الميالة للهلوسة بشكل منهجي خلال عملية اتخاذ القرار. يمنح هذا التصميم النموذج قدرات طبية معززة جوهريًا تتماشى مع سير العمل السريري الحقيقي. عبر تقييمات الاستفسارات السريرية، قوة مقاومة الهلوسة الطبية، HealthBench، وHealthBench-Hard، يتفوق Baichuan-M3 على أحدث نموذج رئيسي أصدرته OpenAI، GPT-5.2، مما يرسخ معيارًا جديدًا في نماذج اللغة المعززة طبيًا.",
|
||||
"Baichuan2-Turbo.description": "يستخدم تعزيز البحث لربط النموذج بالمعرفة المتخصصة ومعرفة الويب. يدعم تحميل ملفات PDF/Word وإدخال الروابط لاسترجاع شامل وفي الوقت المناسب، مع مخرجات دقيقة واحترافية.",
|
||||
"Baichuan3-Turbo-128k.description": "بفضل نافذة السياق الطويلة جدًا بحجم 128K، تم تحسينه لسيناريوهات المؤسسات عالية التكرار مع مكاسب كبيرة وقيمة قوية. مقارنة بـ Baichuan2، تحسنت قدرات إنشاء المحتوى بنسبة 20%، والإجابة على الأسئلة المعرفية بنسبة 17%، ولعب الأدوار بنسبة 40%. الأداء العام أفضل من GPT-3.5.",
|
||||
"Baichuan3-Turbo.description": "تم تحسينه لسيناريوهات المؤسسات عالية التكرار مع مكاسب كبيرة وقيمة قوية. مقارنة بـ Baichuan2، تحسنت قدرات إنشاء المحتوى بنسبة 20%، والإجابة على الأسئلة المعرفية بنسبة 17%، ولعب الأدوار بنسبة 40%. الأداء العام أفضل من GPT-3.5.",
|
||||
@@ -63,7 +67,6 @@
|
||||
"GLM-5.1.description": "GLM-5.1 هو أحدث نموذج رائد من Zhipu، وهو نسخة محسّنة من GLM-5 مع قدرات هندسية وكيلة محسّنة لأنظمة الهندسة المعقدة والمهام طويلة الأمد.",
|
||||
"GLM-5.description": "GLM-5 هو نموذج الجيل القادم الأساسي من شركة Zhipu، مصمّم خصيصاً لهندسة الوكلاء. يقدّم إنتاجية موثوقة في هندسة الأنظمة المعقدة ومهام الوكلاء طويلة الأمد. في قدرات البرمجة والوكلاء، يحقق GLM-5 أداءً رائداً بين النماذج مفتوحة المصدر. وفي سيناريوهات البرمجة الواقعية، يقترب في تجربة المستخدم من Claude Opus 4.5. يتفوق في هندسة الأنظمة المعقدة ومهام الوكلاء طويلة المدى، مما يجعله نموذجاً أساسياً مثالياً للمساعدين العامين.",
|
||||
"Gryphe/MythoMax-L2-13b.description": "MythoMax-L2 (13B) هو نموذج مبتكر لمجالات متنوعة ومهام معقدة.",
|
||||
"HY-Image-V3.0.description": "قدرات قوية لاستخراج الميزات من الصور الأصلية والحفاظ على التفاصيل، مما يوفر نسيجًا بصريًا أكثر ثراءً وينتج صورًا عالية الدقة ومتقنة ومناسبة للإنتاج.",
|
||||
"HelloMeme.description": "HelloMeme هي أداة ذكاء اصطناعي لإنشاء الميمات، الصور المتحركة (GIFs)، أو مقاطع الفيديو القصيرة من الصور أو الحركات التي تقدمها. لا تتطلب مهارات رسم أو برمجة—فقط صورة مرجعية—لإنتاج محتوى ممتع وجذاب ومتناسق من حيث الأسلوب.",
|
||||
"HiDream-E1-Full.description": "HiDream-E1-Full هو نموذج مفتوح المصدر لتحرير الصور متعدد الوسائط من HiDream.ai، يعتمد على بنية Diffusion Transformer المتقدمة وفهم قوي للغة (مدمج LLaMA 3.1-8B-Instruct). يدعم إنشاء الصور باستخدام اللغة الطبيعية، ونقل الأنماط، والتحرير المحلي، وإعادة الطلاء، مع فهم وتنفيذ ممتازين للنصوص والصور.",
|
||||
"HiDream-I1-Full.description": "HiDream-I1 هو نموذج جديد مفتوح المصدر لإنشاء الصور تم إصداره من قبل HiDream. مع 17 مليار معلمة (Flux يحتوي على 12 مليار)، يمكنه تقديم جودة صور رائدة في الصناعة في ثوانٍ.",
|
||||
@@ -84,10 +87,11 @@
|
||||
"Kwaipilot/KAT-Dev.description": "KAT-Dev (32B) هو نموذج مفتوح المصدر لمهام هندسة البرمجيات. يحقق معدل حل 62.4% على SWE-Bench Verified، ويحتل المرتبة الخامسة بين النماذج المفتوحة. تم تحسينه عبر التدريب الوسيط، SFT، وRL لإكمال الشيفرة، إصلاح الأخطاء، ومراجعة الشيفرة.",
|
||||
"Llama-3.2-11B-Vision-Instruct.description": "استدلال بصري قوي على الصور عالية الدقة، مناسب لتطبيقات الفهم البصري.",
|
||||
"Llama-3.2-90B-Vision-Instruct\t.description": "استدلال بصري متقدم لتطبيقات الفهم البصري المعتمدة على الوكلاء.",
|
||||
"LongCat-2.0-Preview.description": "الميزات الأساسية لـ LongCat-2.0-Preview هي كما يلي: مصمم لسيناريوهات تطوير الوكلاء، مع دعم أصلي لاستخدام الأدوات، التفكير متعدد الخطوات، ومهام السياق الطويل؛ يتفوق في توليد الأكواد، سير العمل الآلي، وتنفيذ التعليمات المعقدة؛ متكامل بعمق مع أدوات الإنتاجية مثل Claude Code، OpenClaw، OpenCode، وKilo Code.",
|
||||
"LongCat-Flash-Chat.description": "تم ترقية نموذج LongCat-Flash-Chat إلى إصدار جديد. يتضمن هذا التحديث تحسينات في قدرات النموذج فقط؛ يظل اسم النموذج وطريقة استدعاء API دون تغيير. بناءً على ميزاته المميزة مثل \"الكفاءة القصوى\" و\"الاستجابة السريعة للغاية\"، يعزز الإصدار الجديد فهم السياق وأداء البرمجة الواقعية: قدرات البرمجة المحسنة بشكل كبير: تم تحسين النموذج بشكل عميق لسيناريوهات المطورين، مما يوفر تحسينات كبيرة في مهام إنشاء الأكواد وتصحيح الأخطاء وشرحها. يُشجع المطورون بشدة على تقييم هذه التحسينات ومقارنتها. دعم سياق طويل للغاية 256K: تضاعف نافذة السياق من الجيل السابق (128K) إلى 256K، مما يتيح معالجة فعالة للوثائق الضخمة والمهام ذات التسلسل الطويل. تحسين شامل للأداء متعدد اللغات: يوفر دعمًا قويًا لتسع لغات، بما في ذلك الإسبانية والفرنسية والعربية والبرتغالية والروسية والإندونيسية. قدرات وكيل أكثر قوة: يظهر النموذج كفاءة أكبر في استدعاء الأدوات المعقدة وتنفيذ المهام متعددة الخطوات.",
|
||||
"LongCat-Flash-Lite.description": "تم إصدار نموذج LongCat-Flash-Lite رسميًا. يعتمد على بنية فعالة من نوع Mixture-of-Experts (MoE)، مع إجمالي 68.5 مليار معلمة وحوالي 3 مليارات معلمة مفعلة. من خلال استخدام جدول تضمين N-gram، يحقق استخدامًا فعالًا للغاية للمعلمات، وتم تحسينه بشكل عميق لكفاءة الاستنتاج وسيناريوهات التطبيقات المحددة. مقارنةً بالنماذج ذات الحجم المماثل، فإن ميزاته الأساسية هي كما يلي: كفاءة استنتاج ممتازة: من خلال الاستفادة من جدول تضمين N-gram لتخفيف عنق الزجاجة في الإدخال والإخراج في بنية MoE، جنبًا إلى جنب مع آليات التخزين المؤقت المخصصة وتحسينات على مستوى النواة، يقلل بشكل كبير من زمن الاستنتاج ويحسن الكفاءة العامة. أداء قوي في الوكيل والبرمجة: يظهر قدرات تنافسية عالية في استدعاء الأدوات ومهام تطوير البرمجيات، مما يوفر أداءً استثنائيًا بالنسبة لحجم النموذج.",
|
||||
"LongCat-Flash-Thinking-2601.description": "تم إصدار نموذج LongCat-Flash-Thinking-2601 رسميًا. كنموذج استنتاج مطور يعتمد على بنية Mixture-of-Experts (MoE)، يتميز بإجمالي 560 مليار معلمة. مع الحفاظ على تنافسية قوية عبر معايير الاستنتاج التقليدية، يعزز بشكل منهجي قدرات الاستنتاج على مستوى الوكيل من خلال التعلم المعزز متعدد البيئات واسع النطاق. مقارنةً بنموذج LongCat-Flash-Thinking، فإن الترقيات الرئيسية هي كما يلي: قوة استثنائية في البيئات المليئة بالضوضاء: من خلال تدريب منهجي بأسلوب المناهج يستهدف الضوضاء وعدم اليقين في البيئات الواقعية، يظهر النموذج أداءً ممتازًا في استدعاء أدوات الوكيل، البحث القائم على الوكيل، والاستنتاج المدمج بالأدوات، مع تحسين كبير في التعميم. قدرات وكيل قوية: من خلال إنشاء رسم بياني يعتمد على أكثر من 60 أداة، وتوسيع التدريب عبر بيئات متعددة واستكشاف واسع النطاق، يحسن النموذج بشكل ملحوظ قدرته على التعميم إلى سيناريوهات واقعية معقدة وخارج التوزيع. وضع التفكير العميق المتقدم: يوسع نطاق الاستنتاج عبر الاستنتاج المتوازي ويعمق القدرة التحليلية من خلال آليات التلخيص والتجريد المدفوعة بالتغذية الراجعة، مما يعالج المشكلات الصعبة للغاية بشكل فعال.",
|
||||
"LongCat-Flash-Thinking.description": "تم إصدار LongCat-Flash-Thinking رسميًا وتم فتح مصدره في نفس الوقت. إنه نموذج استنتاج عميق يمكن استخدامه للمحادثات المجانية داخل LongCat Chat، أو الوصول إليه عبر API بتحديد model=LongCat-Flash-Thinking.",
|
||||
"LongCat-Flash-Thinking.description": "لضمان حصولك على أداء تفكير من الدرجة الأولى، قامت منصة LongCat API بتوحيد وترقية الطلبات إلى نموذج LongCat-Flash-Thinking. سيتم توجيه جميع الطلبات الحالية باستخدام `model=LongCat-Flash-Thinking` تلقائيًا إلى الإصدار الأحدث، LongCat-Flash-Thinking-2601، دون الحاجة إلى تغييرات في الكود.",
|
||||
"M2-her.description": "نموذج حوار نصي مصمم لتقمص الأدوار والمحادثات متعددة الأدوار، مع تخصيص الشخصيات والتعبير العاطفي.",
|
||||
"Meta-Llama-3-3-70B-Instruct.description": "Llama 3.3 70B هو نموذج Transformer متعدد الاستخدامات لمهام المحادثة والتوليد.",
|
||||
"Meta-Llama-3.1-405B-Instruct.description": "نموذج Llama 3.1 مضبوط على التعليمات، محسن للمحادثة متعددة اللغات، ويؤدي بقوة في معايير الصناعة الشائعة بين النماذج المفتوحة والمغلقة.",
|
||||
@@ -103,7 +107,6 @@
|
||||
"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 مع استدلال أسرع.",
|
||||
@@ -301,6 +304,7 @@
|
||||
"baichuan/baichuan2-13b-chat.description": "Baichuan-13B هو نموذج LLM مفتوح المصدر وقابل للاستخدام التجاري يحتوي على 13 مليار معلمة من Baichuan، يحقق نتائج رائدة في فئته على معايير اللغة الصينية والإنجليزية الموثوقة.",
|
||||
"baidu/ERNIE-4.5-300B-A47B.description": "ERNIE-4.5-300B-A47B هو نموذج MoE من Baidu يحتوي على 300 مليار معلمة إجمالية و47 مليار نشطة لكل رمز، يوازن بين الأداء القوي وكفاءة الحوسبة. كنموذج أساسي في سلسلة ERNIE 4.5، يتميز بالفهم والتوليد والاستدلال والبرمجة. يستخدم طريقة تدريب مسبق متعددة الوسائط غير متجانسة مع تدريب مشترك على النصوص والرؤية لتعزيز القدرات، خاصة في اتباع التعليمات والمعرفة العامة.",
|
||||
"baidu/ernie-5.0-thinking-preview.description": "ERNIE 5.0 Thinking Preview هو نموذج ERNIE متعدد الوسائط من الجيل التالي من Baidu، يتميز بفهم متعدد الوسائط قوي، واتباع التعليمات، والإبداع، والأسئلة والأجوبة الواقعية، واستدعاء الأدوات.",
|
||||
"big-pickle.description": "Big Pickle من OpenCode — نموذج مجاني بوزن مفتوح مع قدرات قوية في البرمجة.",
|
||||
"black-forest-labs/flux-1.1-pro.description": "FLUX 1.1 Pro هو إصدار أسرع ومحسّن من FLUX Pro يتميز بجودة صور ممتازة والتزام دقيق بالتعليمات.",
|
||||
"black-forest-labs/flux-dev.description": "FLUX Dev هو الإصدار المخصص للتطوير من FLUX للاستخدام غير التجاري.",
|
||||
"black-forest-labs/flux-pro.description": "FLUX Pro هو النموذج الاحترافي من FLUX لإنتاج صور عالية الجودة.",
|
||||
@@ -316,29 +320,34 @@
|
||||
"claude-2.1.description": "Claude 2 يقدم تحسينات رئيسية للمؤسسات، بما في ذلك سياق 200 ألف رمز، تقليل الهلوسة، دعم التعليمات النظامية، وميزة جديدة: استدعاء الأدوات.",
|
||||
"claude-3-5-haiku-20241022.description": "Claude 3.5 Haiku هو النموذج الأسرع من الجيل التالي من Anthropic. مقارنةً بـ Claude 3 Haiku، فإنه يحسن عبر المهارات ويتفوق على أكبر نموذج سابق Claude 3 Opus في العديد من معايير الذكاء.",
|
||||
"claude-3-5-haiku-latest.description": "Claude 3.5 Haiku يقدم استجابات سريعة للمهام الخفيفة.",
|
||||
"claude-3-5-haiku.description": "Claude Haiku 3.5 من Anthropic — نموذج سريع وفعال من حيث التكلفة مع دعم الرؤية.",
|
||||
"claude-3-7-sonnet-20250219.description": "Claude 3.7 Sonnet هو النموذج الأكثر ذكاءً من Anthropic وأول نموذج استنتاج هجين في السوق. يمكنه إنتاج استجابات شبه فورية أو استنتاجات ممتدة خطوة بخطوة يمكن للمستخدمين رؤيتها. يتميز Sonnet بشكل خاص في البرمجة، علوم البيانات، الرؤية، ومهام الوكيل.",
|
||||
"claude-3-7-sonnet-latest.description": "Claude 3.7 Sonnet هو أحدث وأقوى نموذج من Anthropic للمهام المعقدة، يتميز بالأداء العالي، الذكاء، الطلاقة، والفهم العميق.",
|
||||
"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 هو النموذج الأسرع والأذكى من 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-20250514.description": "يُعد Claude Opus 4 أقوى نماذج 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-5-20251101.description": "Claude Opus 4.5 هو النموذج الرائد من Anthropic، يجمع بين الذكاء الاستثنائي والأداء القابل للتوسع، مثالي للمهام المعقدة التي تتطلب استجابات عالية الجودة وتفكير متقدم.",
|
||||
"claude-opus-4-6.description": "Claude Opus 4.6 هو النموذج الأكثر ذكاءً من Anthropic لبناء الوكلاء والبرمجة.",
|
||||
"claude-opus-4-7.description": "Claude Opus 4.7 هو النموذج الأكثر تقدماً من Anthropic والمتاح للاستخدام العام، والمخصّص للاستدلال المعقد والبرمجة الوكيلية.",
|
||||
"claude-opus-4-5.description": "Claude Opus 4.5 من Anthropic — نموذج رئيسي مع تفكير وبرمجة من الدرجة الأولى.",
|
||||
"claude-opus-4-6.description": "Claude Opus 4.6 من Anthropic — نافذة سياق 1M نموذج رئيسي مع تفكير متقدم.",
|
||||
"claude-opus-4-7.description": "Claude Opus 4.7 من Anthropic — أحدث نموذج Opus مع تفكير وبرمجة متقدمة.",
|
||||
"claude-opus-4.5.description": "Claude Opus 4.5 هو النموذج الرائد من Anthropic، يجمع بين الذكاء الفائق والأداء القابل للتوسع لمهام الاستدلال المعقدة وعالية الجودة.",
|
||||
"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 أكثر نماذج Anthropic ذكاءً حتى الآن، حيث يوفّر استجابات شبه فورية أو تفكيرًا ممتدًا خطوة بخطوة مع تحكّم دقيق ومفصّل لمستخدمي واجهة برمجة التطبيقات (API).",
|
||||
"claude-sonnet-4-20250514.description": "Claude Sonnet 4 يمكنه إنتاج استجابات شبه فورية أو تفكير ممتد خطوة بخطوة مع عملية مرئية.",
|
||||
"claude-sonnet-4-5-20250929.description": "Claude Sonnet 4.5 هو النموذج الأكثر ذكاءً من Anthropic حتى الآن.",
|
||||
"claude-sonnet-4-6.description": "يُعد Claude Sonnet 4.6 أفضل ما تقدمه 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 حتى الآن.",
|
||||
"claude-sonnet-4.6.description": "Claude Sonnet 4.6 هو أفضل مزيج من السرعة والذكاء من Anthropic.",
|
||||
"claude-sonnet-4.description": "Claude Sonnet 4 يمكنه إنتاج استجابات شبه فورية أو استدلال خطوة بخطوة ممتد يمكن للمستخدمين رؤيته. يمكن لمستخدمي API التحكم بدقة في مدة تفكير النموذج.",
|
||||
"claude-sonnet-4.description": "Claude Sonnet 4 من Anthropic — نموذج متوازن مع قدرات قوية في البرمجة والتفكير.",
|
||||
"codegeex-4.description": "CodeGeeX-4 هو مساعد برمجة ذكي يدعم الأسئلة والأجوبة متعددة اللغات وإكمال الشيفرة لزيادة إنتاجية المطورين.",
|
||||
"codegeex4-all-9b.description": "CodeGeeX4-ALL-9B هو نموذج توليد شيفرة متعدد اللغات يدعم الإكمال والتوليد، تفسير الشيفرة، البحث عبر الإنترنت، استدعاء الوظائف، وأسئلة وأجوبة على مستوى المستودع، ويغطي مجموعة واسعة من سيناريوهات تطوير البرمجيات. يُعد من أفضل نماذج الشيفرة تحت 10B.",
|
||||
"codegemma.description": "CodeGemma هو نموذج خفيف الوزن لمهام البرمجة المتنوعة، يتيح التكرار السريع والتكامل السلس.",
|
||||
@@ -409,7 +418,7 @@
|
||||
"deepseek-ai/deepseek-v3.1-terminus.description": "DeepSeek V3.1 هو نموذج تفكير من الجيل التالي يتمتع بقدرات أقوى في التفكير المعقد وسلسلة التفكير لمهام التحليل العميق.",
|
||||
"deepseek-ai/deepseek-v3.1.description": "DeepSeek V3.1 هو نموذج تفكير من الجيل التالي يتمتع بقدرات أقوى في التفكير المعقد وسلسلة التفكير لمهام التحليل العميق.",
|
||||
"deepseek-ai/deepseek-v3.2.description": "DeepSeek V3.2 هو نموذج استدلال من الجيل التالي يتميز بقدرات استدلال معقدة وسلسلة التفكير.",
|
||||
"deepseek-chat.description": "يوازن DeepSeek V3.2 بين قدرات الاستدلال وطول المخرجات لمهام الأسئلة والأجوبة اليومية ومهام الوكلاء. تُظهر الاختبارات العامة أداءً يصل إلى مستوى GPT-5، وهو الأول الذي يدمج التفكير في استخدام الأدوات، مما يجعله رائدًا في تقييمات الوكلاء مفتوحة المصدر.",
|
||||
"deepseek-chat.description": "نموذج مفتوح المصدر جديد يجمع بين القدرات العامة والبرمجية. يحافظ على الحوار العام لنموذج الدردشة وقوة البرمجة لنموذج البرمجة، مع تحسين التوافق مع التفضيلات. DeepSeek-V2.5 يحسن أيضًا الكتابة واتباع التعليمات.",
|
||||
"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.",
|
||||
@@ -432,7 +441,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 V3.2 Thinking هو نموذج استدلال عميق يولّد تسلسلًا منطقيًا للأفكار قبل تقديم المخرجات لتحقيق دقة أعلى، مع نتائج متقدمة في المنافسات وقدرات استدلال تضاهي Gemini-3.0-Pro.",
|
||||
"deepseek-reasoner.description": "اسم مستعار متوافق لوضع التفكير السريع DeepSeek V4. مقرر إيقافه — استخدم 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 مليار معلمة يتميز بقوة في البرمجة، والقدرات التقنية، وفهم السياق، والتعامل مع النصوص الطويلة.",
|
||||
@@ -446,6 +455,8 @@
|
||||
"deepseek-v3.2-thinking.description": "DeepSeek-V3.2 Thinking هو النسخة المخصصة لمهام التفكير من DeepSeek-V3.2.",
|
||||
"deepseek-v3.2.description": "DeepSeek-V3.2 هو أحدث نموذج برمجة من DeepSeek مع قدرات استدلال قوية.",
|
||||
"deepseek-v3.description": "DeepSeek-V3 هو نموذج MoE قوي بإجمالي 671 مليار معلمة و37 مليار معلمة نشطة لكل رمز.",
|
||||
"deepseek-v4-flash.description": "DeepSeek V4 Flash هو العضو الاقتصادي في عائلة V4 مع نافذة سياق 1M وتفكير هجين. وضع التفكير مفعل افتراضيًا ويمكن تبديله عبر معلمة `thinking`؛ وضع غير التفكير محسن لسيناريوهات سير العمل الحساسة للكمون.",
|
||||
"deepseek-v4-pro.description": "DeepSeek V4 Pro هو النموذج الرئيسي لعائلة V4، محسن للتفكير عالي الكثافة، سير العمل الوكيل، والتخطيط طويل الأفق. وضع التفكير مفعل افتراضيًا ويمكن تبديله عبر معلمة `thinking`.",
|
||||
"deepseek-vl2-small.description": "DeepSeek VL2 Small هو إصدار متعدد الوسائط خفيف الوزن للاستخدام في البيئات ذات الموارد المحدودة أو التزامن العالي.",
|
||||
"deepseek-vl2.description": "DeepSeek VL2 هو نموذج متعدد الوسائط لفهم النصوص والصور والإجابة البصرية الدقيقة.",
|
||||
"deepseek/deepseek-chat-v3-0324.description": "DeepSeek V3 هو نموذج MoE يحتوي على 685 مليار معلمة، وهو أحدث إصدار من سلسلة دردشة DeepSeek الرائدة.\n\nيعتمد على [DeepSeek V3](/deepseek/deepseek-chat-v3) ويؤدي أداءً قويًا عبر المهام.",
|
||||
@@ -524,9 +535,11 @@
|
||||
"ernie-4.5-vl-28b-a3b.description": "ERNIE 4.5 VL 28B A3B هو نموذج مفتوح المصدر متعدد الوسائط لفهم الصور والنصوص والاستدلال.",
|
||||
"ernie-5.0-thinking-latest.description": "Wenxin 5.0 Thinking هو نموذج رائد متعدد الوسائط أصلي يدعم النصوص، الصور، الصوت، والفيديو بشكل موحد. يوفر ترقيات شاملة للقدرات في الأسئلة المعقدة، الإبداع، وسيناريوهات الوكلاء.",
|
||||
"ernie-5.0-thinking-preview.description": "معاينة Wenxin 5.0 Thinking هو نموذج رائد متعدد الوسائط أصلي يدعم النصوص، الصور، الصوت، والفيديو بشكل موحد. يوفر ترقيات شاملة للقدرات في الأسئلة المعقدة، الإبداع، وسيناريوهات الوكلاء.",
|
||||
"ernie-5.0.description": "ERNIE 5.0، النموذج الجديد في سلسلة ERNIE، هو نموذج كبير متعدد الوسائط أصلي. يعتمد نهج نمذجة متعدد الوسائط موحد، حيث يقوم بنمذجة النصوص، الصور، الصوت، والفيديو بشكل مشترك لتقديم قدرات متعددة الوسائط شاملة. تم تحسين قدراته الأساسية بشكل كبير، محققًا أداءً قويًا في تقييمات المعايير. يتفوق بشكل خاص في الفهم متعدد الوسائط، اتباع التعليمات، الكتابة الإبداعية، الدقة الواقعية، تخطيط الوكلاء، واستخدام الأدوات.",
|
||||
"ernie-char-8k.description": "ERNIE Character 8K هو نموذج حواري بشخصية مخصصة لبناء شخصيات IP والدردشة طويلة الأمد.",
|
||||
"ernie-char-fiction-8k-preview.description": "معاينة ERNIE Character Fiction 8K هو نموذج لإنشاء الشخصيات والحبكات القصصية، مخصص لتقييم الميزات والاختبار.",
|
||||
"ernie-char-fiction-8k.description": "ERNIE Character Fiction 8K هو نموذج شخصيات للروايات وإنشاء الحبكات، مناسب لتوليد القصص الطويلة.",
|
||||
"ernie-image-turbo.description": "ERNIE-Image هو نموذج نص إلى صورة بـ 8 مليارات معلمة تم تطويره بواسطة Baidu. يحتل المرتبة الأولى في العديد من المعايير، محققًا المركز الأول في SuperCLUE في الصين ويتصدر المسار مفتوح المصدر.",
|
||||
"ernie-irag-edit.description": "ERNIE iRAG Edit هو نموذج لتحرير الصور يدعم المسح، وإعادة الرسم، وتوليد النسخ المتنوعة.",
|
||||
"ernie-lite-pro-128k.description": "ERNIE Lite Pro 128K هو نموذج خفيف الوزن وعالي الأداء للسيناريوهات الحساسة للتكلفة والزمن.",
|
||||
"ernie-novel-8k.description": "ERNIE Novel 8K مصمم خصيصًا للروايات الطويلة وحبكات IP مع سرد متعدد الشخصيات.",
|
||||
@@ -535,8 +548,7 @@
|
||||
"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.5.description": "Seedream 4.5، المطوّر من قبل فريق Seed في ByteDance، يدعم تحرير وتركيب الصور المتعددة. يتميز بتحسين اتساق العناصر، ودقة اتباع التعليمات، وفهم المنطق المكاني، والتعبير الجمالي، وتصميم الملصقات والشعارات مع عرض نصي‑صوري عالي الدقة.",
|
||||
"fal-ai/bytedance/seedream/v4.description": "Seedream 4.0، المطوّر من قبل 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] هو نموذج لتوليد الصور يتميز بميول جمالية نحو صور أكثر واقعية وطبيعية.",
|
||||
@@ -544,8 +556,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": "توليد وتحرير صور سياقية متقدمة، تجمع بين النصوص والصور لتحقيق نتائج دقيقة ومتسقة.",
|
||||
@@ -584,13 +596,15 @@
|
||||
"gemini-2.5-pro-preview-05-06.description": "Gemini 2.5 Pro Preview هو أكثر نماذج Google تقدمًا في الاستدلال، قادر على تحليل الشيفرات والرياضيات ومشاكل العلوم، وتحليل مجموعات البيانات الكبيرة وقواعد الشيفرة والمستندات ضمن سياق طويل.",
|
||||
"gemini-2.5-pro.description": "Gemini 2.5 Pro هو النموذج الرائد من Google في مجال الاستدلال، يدعم السياق الطويل للمهام المعقدة.",
|
||||
"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-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) جودة صور بمستوى احترافي بسرعة Flash مع دعم الدردشة متعددة الوسائط.",
|
||||
"gemini-3.1-flash-image-preview:image.description": "Gemini 3.1 Flash Image (Nano Banana 2) هو أسرع نموذج توليد الصور الأصلي من Google مع دعم التفكير، توليد الصور التفاعلية وتحريرها.",
|
||||
"gemini-3.1-flash-lite-preview.description": "Gemini 3.1 Flash-Lite Preview هو النموذج الأكثر كفاءة من حيث التكلفة من 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 Flash",
|
||||
"gemini-flash-lite-latest.description": "أحدث إصدار من Gemini Flash-Lite",
|
||||
"gemini-pro-latest.description": "أحدث إصدار من Gemini Pro",
|
||||
@@ -645,6 +659,7 @@
|
||||
"global.anthropic.claude-haiku-4-5-20251001-v1:0.description": "Claude Haiku 4.5 هو أسرع وأذكى نموذج Haiku من Anthropic، يتميز بسرعة البرق وقدرة تفكير موسعة.",
|
||||
"global.anthropic.claude-opus-4-5-20251101-v1:0.description": "Claude Opus 4.5 هو النموذج الرائد من Anthropic، يجمع بين ذكاء استثنائي وأداء قابل للتوسع للمهام المعقدة التي تتطلب استجابات واستدلال عالي الجودة.",
|
||||
"global.anthropic.claude-opus-4-6-v1.description": "Claude Opus 4.6 هو النموذج الأكثر ذكاءً من Anthropic لبناء الوكلاء والبرمجة.",
|
||||
"global.anthropic.claude-opus-4-7.description": "Claude Opus 4.7 هو النموذج الأكثر قدرة من Anthropic المتاح بشكل عام للمهام المعقدة والتفكير البرمجي.",
|
||||
"global.anthropic.claude-sonnet-4-5-20250929-v1:0.description": "Claude Sonnet 4.5 هو النموذج الأكثر ذكاءً من Anthropic حتى الآن.",
|
||||
"global.anthropic.claude-sonnet-4-6.description": "Claude Sonnet 4.6 هو أفضل مزيج من السرعة والذكاء من Anthropic.",
|
||||
"google/gemini-2.0-flash-001.description": "Gemini 2.0 Flash يقدم قدرات الجيل التالي، بما في ذلك السرعة الممتازة، استخدام الأدوات المدمجة، التوليد متعدد الوسائط، ونافذة سياق تصل إلى مليون رمز.",
|
||||
@@ -713,46 +728,52 @@
|
||||
"gpt-4o.description": "ChatGPT-4o هو نموذج ديناميكي يتم تحديثه في الوقت الحقيقي، يجمع بين الفهم القوي والقدرة على التوليد لحالات الاستخدام واسعة النطاق مثل دعم العملاء، والتعليم، والدعم الفني.",
|
||||
"gpt-5-chat-latest.description": "نموذج GPT-5 المستخدم في ChatGPT، يجمع بين الفهم القوي والتوليد لتطبيقات المحادثة.",
|
||||
"gpt-5-chat.description": "GPT-5 Chat هو نموذج معاينة محسّن لسيناريوهات المحادثة. يدعم إدخال نصوص وصور، ويُخرج نصًا فقط، ومناسب لتطبيقات الدردشة والذكاء الاصطناعي التفاعلي.",
|
||||
"gpt-5-codex.description": "GPT-5 Codex هو إصدار من GPT-5 محسّن لمهام البرمجة التلقائية في بيئات مشابهة لـ Codex.",
|
||||
"gpt-5-codex.description": "GPT-5 Codex من OpenAI — نموذج متخصص في البرمجة مع دعم استخدام الأدوات.",
|
||||
"gpt-5-mini.description": "إصدار أسرع وأكثر كفاءة من GPT-5 للمهام المحددة جيدًا، يوفر استجابات أسرع مع الحفاظ على الجودة.",
|
||||
"gpt-5-nano.description": "أسرع وأقل تكلفة من إصدارات GPT-5، مثالي للتطبيقات الحساسة للزمن والتكلفة.",
|
||||
"gpt-5-nano.description": "GPT-5 Nano من OpenAI — نموذج خفيف الوزن وفعال من حيث التكلفة.",
|
||||
"gpt-5-pro.description": "GPT-5 pro يستخدم موارد حسابية أكبر للتفكير بعمق وتقديم إجابات أفضل باستمرار.",
|
||||
"gpt-5.1-chat-latest.description": "GPT-5.1 Chat: إصدار ChatGPT من GPT-5.1، مصمم لسيناريوهات المحادثة.",
|
||||
"gpt-5.1-codex-max.description": "GPT-5.1 Codex Max: النموذج الأكثر ذكاءً للبرمجة من OpenAI، محسن للمهام البرمجية طويلة الأفق، يدعم رموز الاستنتاج.",
|
||||
"gpt-5.1-codex-mini.description": "GPT-5.1 Codex mini: إصدار أصغر وأقل تكلفة من Codex، محسّن لمهام البرمجة التلقائية.",
|
||||
"gpt-5.1-codex.description": "GPT-5.1 Codex: إصدار من GPT-5.1 محسّن لمهام البرمجة التلقائية، مناسب لتدفقات العمل المعقدة في واجهة Responses API.",
|
||||
"gpt-5.1.description": "GPT-5.1 — نموذج رائد محسّن للبرمجة والمهام التلقائية مع جهد استدلال قابل للتكوين وسياق أطول.",
|
||||
"gpt-5.1-codex-max.description": "GPT-5.1 Codex Max من OpenAI — نموذج Codex بأقصى قدرات.",
|
||||
"gpt-5.1-codex-mini.description": "GPT-5.1 Codex Mini من OpenAI — نموذج برمجة مضغوط بقدرات قوية.",
|
||||
"gpt-5.1-codex.description": "GPT-5.1 Codex من OpenAI — نموذج يركز على البرمجة مع تحسينات في استخدام الأدوات.",
|
||||
"gpt-5.1.description": "GPT-5.1 من OpenAI — تحسين GPT-5 بدقة تفكير أفضل.",
|
||||
"gpt-5.2-chat-latest.description": "GPT-5.2 Chat هو إصدار ChatGPT المخصص لأحدث تحسينات المحادثة.",
|
||||
"gpt-5.2-codex.description": "GPT-5.2-Codex هو إصدار مطور من GPT-5.2 محسن للمهام البرمجية طويلة الأفق والوكيلة.",
|
||||
"gpt-5.2-codex.description": "GPT-5.2 Codex من OpenAI — متخصص في البرمجة مع تحسين دقة استدعاء الأدوات.",
|
||||
"gpt-5.2-pro.description": "GPT-5.2 Pro: إصدار أكثر ذكاءً ودقة من GPT-5.2 (لواجهة Responses API فقط)، مناسب للمشكلات الصعبة والاستدلال متعدد الأدوار الطويل.",
|
||||
"gpt-5.2.description": "GPT-5.2 هو نموذج رائد لتدفقات العمل البرمجية والتلقائية مع استدلال أقوى وأداء سياقي طويل.",
|
||||
"gpt-5.2.description": "GPT-5.2 من OpenAI — معالجة متعددة الوسائط وترقية التفكير.",
|
||||
"gpt-5.3-chat-latest.description": "GPT-5.3 Chat هو أحدث نموذج ChatGPT المستخدم في ChatGPT مع تحسينات في تجربة المحادثة.",
|
||||
"gpt-5.3-codex.description": "GPT-5.3-Codex هو النموذج الأكثر قدرة على البرمجة الوكيلة حتى الآن، محسن للمهام البرمجية الوكيلة في Codex أو بيئات مشابهة.",
|
||||
"gpt-5.4-mini.description": "GPT-5.4 mini هو أقوى نموذج صغير من OpenAI للبرمجة، واستخدام الحاسوب، والوكلاء الفرعيين.",
|
||||
"gpt-5.4-nano.description": "GPT-5.4 nano هو أرخص نموذج من فئة GPT-5.4 من OpenAI للمهام البسيطة ذات الحجم الكبير.",
|
||||
"gpt-5.4-pro.description": "GPT-5.4 Pro يستخدم المزيد من الحوسبة للتفكير بشكل أعمق وتقديم إجابات أفضل باستمرار، متاح فقط في Responses API.",
|
||||
"gpt-5.4.description": "GPT-5.4 هو النموذج الرائد للعمل المهني المعقد مع أعلى قدرة على الاستنتاج.",
|
||||
"gpt-5.description": "أفضل نموذج لمهام البرمجة والتلقائية عبر المجالات. يحقق GPT-5 قفزات في الدقة والسرعة والاستدلال والوعي بالسياق والتفكير المنظم وحل المشكلات.",
|
||||
"gpt-5.3-codex-spark.description": "GPT-5.3 Codex Spark من OpenAI — نموذج برمجة مضغوط محسن للسرعة.",
|
||||
"gpt-5.3-codex.description": "GPT-5.3 Codex من OpenAI — أحدث Codex مع فهم برمجي معزز.",
|
||||
"gpt-5.4-mini.description": "GPT-5.4 Mini من OpenAI — نموذج فعال يوازن بين التكلفة والأداء.",
|
||||
"gpt-5.4-nano.description": "GPT-5.4 Nano من OpenAI — نموذج خفيف للغاية للمهام ذات الإنتاجية العالية.",
|
||||
"gpt-5.4-pro.description": "GPT-5.4 Pro من OpenAI — النموذج الأكثر قدرة مع أقصى سياق وتفكير.",
|
||||
"gpt-5.4.description": "GPT-5.4 من OpenAI — نموذج الجيل التالي مع نافذة سياق 1M+ وإدخال متعدد الوسائط.",
|
||||
"gpt-5.5-pro.description": "GPT-5.5 Pro يستخدم المزيد من الحوسبة للتفكير بشكل أعمق وتقديم إجابات أفضل باستمرار.",
|
||||
"gpt-5.5.description": "GPT-5.5 هو النموذج الرائد للعمل المهني الأكثر تعقيدًا، البرمجة، والمهام الوكيلية.",
|
||||
"gpt-5.description": "GPT-5 من OpenAI — النموذج الرئيسي مع التفكير المتقدم والإدخال متعدد الوسائط.",
|
||||
"gpt-audio.description": "GPT Audio هو نموذج دردشة عام يدعم الإدخال والإخراج الصوتي، مدعوم في واجهة Chat Completions API.",
|
||||
"gpt-image-1-mini.description": "إصدار منخفض التكلفة من GPT Image 1 يدعم إدخال نصوص وصور وإخراج صور.",
|
||||
"gpt-image-1.5.description": "نموذج GPT Image 1 محسّن بسرعة توليد أسرع 4×، وتحرير أكثر دقة، وتحسين عرض النصوص.",
|
||||
"gpt-image-1.description": "نموذج توليد الصور متعدد الوسائط الأصلي في ChatGPT.",
|
||||
"gpt-image-2.description": "نموذج الصور متعدد الوسائط من الجيل التالي من OpenAI مع التفكير الأصلي، دقة تصل إلى 4K، عرض نصوص شبه مثالي، ودعم متعدد اللغات عالي الدقة.",
|
||||
"gpt-oss-120b.description": "يتطلب الوصول تقديم طلب. GPT-OSS-120B هو نموذج لغة مفتوح المصدر من OpenAI بقدرات قوية في توليد النصوص.",
|
||||
"gpt-oss-20b.description": "يتطلب الوصول تقديم طلب. GPT-OSS-20B هو نموذج لغة متوسط الحجم مفتوح المصدر من OpenAI يتميز بكفاءة في توليد النصوص.",
|
||||
"gpt-oss:120b.description": "GPT-OSS 120B هو نموذج لغة مفتوح المصدر كبير من OpenAI يستخدم تقنيات MXFP4 للكم، ويُعد نموذجًا رائدًا. يتطلب بيئات متعددة وحدات GPU أو محطات عمل عالية الأداء، ويقدم أداءً ممتازًا في الاستدلال المعقد وتوليد الأكواد والمعالجة متعددة اللغات، مع دعم متقدم لاستدعاء الوظائف وتكامل الأدوات.",
|
||||
"gpt-oss:20b.description": "GPT-OSS 20B هو نموذج لغة مفتوح المصدر من OpenAI يستخدم تقنيات MXFP4 للكم، مناسب لوحدات GPU الاستهلاكية عالية الأداء أو أجهزة Mac بمعالجات Apple Silicon. يتميز بأداء قوي في توليد الحوارات والبرمجة ومهام الاستدلال، ويدعم استدعاء الوظائف واستخدام الأدوات.",
|
||||
"gpt-realtime.description": "نموذج عام في الوقت الحقيقي يدعم إدخال وإخراج نصي وصوتي في الوقت الحقيقي، بالإضافة إلى إدخال الصور.",
|
||||
"grok-3-mini.description": "نموذج خفيف الوزن يفكر قبل الرد. سريع وذكي في المهام المنطقية التي لا تتطلب معرفة متخصصة عميقة، مع إمكانية الوصول إلى آثار التفكير الأولية.",
|
||||
"grok-3.description": "نموذج رائد يتفوق في حالات الاستخدام المؤسسية مثل استخراج البيانات، والبرمجة، والتلخيص، مع معرفة متخصصة عميقة في مجالات مثل المالية والرعاية الصحية والقانون والعلوم.",
|
||||
"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-beta-0309-non-reasoning.description": "نسخة غير استدلالية للاستخدامات البسيطة.",
|
||||
"grok-4.20-beta-0309-reasoning.description": "نموذج ذكي وسريع للغاية يقوم بالاستدلال قبل الرد.",
|
||||
"grok-4.20-multi-agent-beta-0309.description": "فريق مكون من 4 أو 16 وكيلًا، يتفوق في حالات الاستخدام البحثية، لا يدعم حاليًا الأدوات على جانب العميل. يدعم فقط أدوات xAI على جانب الخادم (مثل أدوات X Search، وأدوات البحث على الويب) وأدوات MCP البعيدة.",
|
||||
"grok-4.description": "أحدث وأقوى نموذج رائد لدينا، يتفوق في معالجة اللغة الطبيعية والرياضيات والاستدلال — مثالي كأداة شاملة.",
|
||||
"grok-4.20-0309-non-reasoning.description": "نموذج غير تفكير للاستخدامات البسيطة.",
|
||||
"grok-4.20-0309-reasoning.description": "نموذج ذكي وسريع للغاية يفكر قبل الرد.",
|
||||
"grok-4.20-multi-agent-0309.description": "فريق من 4 أو 16 وكيلًا، يتفوق في حالات الاستخدام البحثية، لا يدعم حاليًا الأدوات على جانب العميل. يدعم فقط أدوات xAI على جانب الخادم (مثل X Search، أدوات البحث على الويب) وأدوات MCP البعيدة.",
|
||||
"grok-4.description": "أحدث نموذج رئيسي من Grok مع أداء لا مثيل له في اللغة، الرياضيات، والتفكير — نموذج شامل حقيقي. يشير حاليًا إلى grok-4-0709؛ بسبب الموارد المحدودة، هو مؤقتًا أعلى بنسبة 10% من التسعير الرسمي ومن المتوقع أن يعود إلى السعر الرسمي لاحقًا.",
|
||||
"grok-code-fast-1.description": "يسعدنا إطلاق grok-code-fast-1، نموذج استدلال سريع وفعال من حيث التكلفة يتفوق في البرمجة التلقائية.",
|
||||
"grok-imagine-image-pro.description": "إنشاء صور من مطالبات نصية، تحرير الصور الموجودة باستخدام اللغة الطبيعية، أو تحسين الصور بشكل تكراري من خلال محادثات متعددة الأدوار.",
|
||||
"grok-imagine-image.description": "إنشاء صور من مطالبات نصية، تحرير الصور الموجودة باستخدام اللغة الطبيعية، أو تحسين الصور بشكل تكراري من خلال محادثات متعددة الأدوار.",
|
||||
@@ -760,32 +781,25 @@
|
||||
"groq/compound-mini.description": "Compound-mini هو نظام ذكاء اصطناعي مركب مدعوم بنماذج متاحة علنًا على GroqCloud، يستخدم الأدوات بذكاء وانتقائية للإجابة على استفسارات المستخدمين.",
|
||||
"groq/compound.description": "Compound هو نظام ذكاء اصطناعي مركب مدعوم بعدة نماذج متاحة علنًا على GroqCloud، يستخدم الأدوات بذكاء وانتقائية للإجابة على استفسارات المستخدمين.",
|
||||
"gryphe/mythomax-l2-13b.description": "MythoMax L2 13B هو نموذج لغوي إبداعي وذكي مدمج من عدة نماذج رائدة.",
|
||||
"hunyuan-2.0-instruct-20251111.description": "ميزات الإصدار: تم ترقية قاعدة النموذج من TurboS إلى **Hunyuan 2.0**، مما أدى إلى تحسينات شاملة في القدرات. يعزز بشكل كبير اتباع التعليمات، وفهم النصوص متعددة الأدوار والطويلة، والإبداع الأدبي، ودقة المعرفة، والبرمجة، وقدرات الاستدلال.",
|
||||
"hunyuan-2.0-thinking-20251109.description": "ميزات الإصدار: تم ترقية قاعدة النموذج من TurboS إلى **Hunyuan 2.0**، مما أدى إلى تحسينات شاملة في القدرات. يعزز بشكل كبير قدرة النموذج على اتباع التعليمات المعقدة، وفهم النصوص متعددة الأدوار والطويلة، ومعالجة الأكواد، والعمل كوكيل، وأداء مهام الاستدلال.",
|
||||
"hunyuan-a13b.description": "أول نموذج تفكير هجين من Hunyuan، مطور من hunyuan-standard-256K (بإجمالي 80 مليار، 13 مليار نشطة). يعتمد بشكل افتراضي على التفكير البطيء ويدعم التبديل بين التفكير السريع والبطيء عبر المعلمات أو بإضافة /no_think. تم تحسين القدرات العامة مقارنة بالجيل السابق، خاصة في الرياضيات والعلوم وفهم النصوص الطويلة والمهام المعتمدة على الوكلاء.",
|
||||
"happyhorse-1.0-i2v.description": "HappyHorse-1.0-I2V يدعم توليد الفيديو من النصوص، مما يوفر صورًا ديناميكية عالية الدقة. يمكنه فهم دلالات النصوص بدقة وإنتاج فيديوهات عالية الجودة تكون سلسة، طبيعية، وغنية بالتفاصيل.",
|
||||
"happyhorse-1.0-r2v.description": "HappyHorse-1.0-R2V يدعم توليد الفيديو بناءً على المراجع، مما يوفر استقرارًا أكبر في الموضوع والمشهد. يدعم ما يصل إلى 9 صور مرجعية، يحافظ بدقة على النية الإبداعية، ويوفر قدرة تعبيرية محسنة.",
|
||||
"happyhorse-1.0-t2v.description": "HappyHorse-1.0-T2V يدعم توليد الفيديو من النصوص، مما يوفر صورًا ديناميكية عالية الدقة. يمكنه فهم دلالات النصوص بدقة وإنتاج فيديوهات عالية الجودة تكون سلسة، طبيعية، وغنية بالتفاصيل.",
|
||||
"hunyuan-2.0-instruct-20251111.description": "تم تحسين أساس النموذج بشكل شامل، مع قدرات أساسية أكثر قوة. يحقق أداءً من الدرجة الأولى في المعرفة، الرياضيات، الكتابة، والتفكير. كما يظهر أداءً ممتازًا في اتباع التعليمات، التفاعلات متعددة الأدوار، وفهم السياق الطويل.",
|
||||
"hunyuan-2.0-thinking-20251109.description": "متخصص في المحتوى الإبداعي، التفاعلات متعددة الأدوار، وسيناريوهات اتباع التعليمات العملية. قدرات محسنة بشكل كبير في الرياضيات، البرمجة، والمهام القائمة على الوكلاء.",
|
||||
"hunyuan-code.description": "أحدث نموذج برمجي من Hunyuan مدرب على 200 مليار بيانات أكواد عالية الجودة بالإضافة إلى ستة أشهر من بيانات SFT، مع سياق 8K. يحتل مرتبة قريبة من القمة في معايير البرمجة الآلية وفي تقييمات الخبراء البشريين عبر خمس لغات.",
|
||||
"hunyuan-functioncall.description": "أحدث نموذج MoE FunctionCall من Hunyuan مدرب على بيانات استدعاء الأدوات عالية الجودة، مع نافذة سياق 32K ومعايير رائدة عبر الأبعاد.",
|
||||
"hunyuan-large-longcontext.description": "يتفوق في مهام المستندات الطويلة مثل التلخيص والأسئلة والأجوبة مع التعامل أيضًا مع التوليد العام. قوي في تحليل النصوص الطويلة وتوليد المحتوى المعقد والمفصل.",
|
||||
"hunyuan-large.description": "Hunyuan-large يحتوي على ~389 مليار معلمة إجمالية و ~52 مليار نشطة، وهو أكبر وأقوى نموذج MoE مفتوح في بنية Transformer.",
|
||||
"hunyuan-lite.description": "تمت ترقيته إلى بنية MoE مع إطار سياق 256 ألف، ويتفوق على العديد من النماذج المفتوحة في مجالات معالجة اللغة الطبيعية، الشيفرة، الرياضيات، والمعايير الصناعية.",
|
||||
"hunyuan-lite.description": "تم ترقيته إلى بنية MoE مع نافذة سياق 256K، متفوقًا على العديد من النماذج مفتوحة المصدر عبر معايير NLP، البرمجة، الرياضيات، والمجالات.",
|
||||
"hunyuan-pro.description": "نموذج طويل السياق MoE-32K بعدد تريليون معلمة، يتصدر المعايير، قوي في التعليمات المعقدة والتفكير، الرياضيات المتقدمة، استدعاء الوظائف، ومُحسّن للترجمة متعددة اللغات، والمالية، والقانون، والمجالات الطبية.",
|
||||
"hunyuan-role-latest.description": "للسيناريوهات القائمة على لعب الأدوار، يقدم توافقًا عاليًا مع الشخصية وأسلوب محادثة طبيعي للغاية يشبه الإنسان. يوفر تطويرًا وتقدمًا سرديًا جذابًا، إلى جانب الرفقة العاطفية والإشباع.",
|
||||
"hunyuan-role.description": "أحدث نموذج لعب الأدوار من Hunyuan، تم تحسينه رسميًا ببيانات لعب الأدوار، مما يقدم أداءً أساسيًا أقوى في سيناريوهات لعب الأدوار.",
|
||||
"hunyuan-standard-256K.description": "يستخدم توجيهًا محسنًا لتخفيف توازن الحمل وانهيار الخبراء. يحقق 99.9% في البحث عن الإبرة في كومة القش في السياقات الطويلة. MOE-256K يوسع طول السياق والجودة.",
|
||||
"hunyuan-standard.description": "يستخدم توجيهًا محسنًا لتخفيف توازن الحمل وانهيار الخبراء. يحقق 99.9% في البحث عن الإبرة في كومة القش في السياقات الطويلة. MOE-32K يقدم قيمة قوية مع التعامل مع المدخلات الطويلة.",
|
||||
"hunyuan-t1-20250321.description": "يبني قدرات متوازنة في الفنون والعلوم مع التقاط قوي للمعلومات النصية الطويلة. يدعم الإجابات المنطقية لمشاكل الرياضيات، المنطق، العلوم، وبرمجة الأكواد عبر مستويات الصعوبة.",
|
||||
"hunyuan-t1-20250403.description": "يحسن إنشاء الأكواد على مستوى المشاريع وجودة الكتابة، ويعزز فهم الموضوعات متعددة الأدوار واتباع تعليمات ToB، ويحسن فهم الكلمات، ويقلل من مشكلات الإخراج المختلط بين المبسط/التقليدي والصيني/الإنجليزي.",
|
||||
"hunyuan-t1-20250529.description": "يحسن الكتابة الإبداعية والتأليف، ويعزز البرمجة الأمامية، والرياضيات، والتفكير المنطقي، ويعزز اتباع التعليمات.",
|
||||
"hunyuan-t1-20250711.description": "يحسن بشكل كبير الرياضيات الصعبة، والمنطق، والبرمجة، ويعزز استقرار الإخراج، ويعزز القدرة على النصوص الطويلة.",
|
||||
"hunyuan-t1-latest.description": "يحسن بشكل ملحوظ نموذج التفكير البطيء في الرياضيات الصعبة، والتفكير المعقد، والبرمجة المعقدة، واتباع التعليمات، وجودة الكتابة الإبداعية.",
|
||||
"hunyuan-t1-vision-20250916.description": "أحدث نموذج t1-vision للتفكير العميق مع تحسينات كبيرة في VQA، التثبيت البصري، التعرف البصري على الحروف، الرسوم البيانية، حل المشكلات المصورة، والإبداع المعتمد على الصور، بالإضافة إلى دعم أقوى للإنجليزية واللغات منخفضة الموارد.",
|
||||
"hunyuan-turbo-20241223.description": "يعزز هذا الإصدار توسيع التعليمات لتحسين التعميم، ويحسن بشكل كبير التفكير في الرياضيات/الأكواد/المنطق، ويعزز فهم الكلمات، ويحسن جودة الكتابة.",
|
||||
"hunyuan-turbo-latest.description": "تحسينات عامة في تجربة فهم اللغة الطبيعية، والكتابة، والدردشة، والأسئلة والأجوبة، والترجمة، والمجالات؛ استجابات أكثر إنسانية، توضيح أفضل للنوايا الغامضة، تحسين تحليل الكلمات، جودة إبداعية أعلى وتفاعلية، ومحادثات متعددة الأدوار أقوى.",
|
||||
"hunyuan-standard-256K.description": "يستخدم توجيهًا محسنًا لتخفيف توازن التحميل وانهيار الخبراء. يصل البحث عن النصوص الطويلة \"الإبرة في كومة القش\" إلى 99.9%. MOE-256K يدفع أكثر في الطول والجودة، مما يوسع بشكل كبير طول الإدخال.",
|
||||
"hunyuan-standard.description": "يستخدم توجيهًا محسنًا لتخفيف توازن التحميل وانهيار الخبراء. يصل البحث عن النصوص الطويلة \"الإبرة في كومة القش\" إلى 99.9%. MOE-32K يقدم قيمة أفضل مع توازن الجودة والسعر لإدخالات النصوص الطويلة.",
|
||||
"hunyuan-turbo.description": "نموذج الجيل التالي من Hunyuan LLM بمعمارية MoE جديدة، يوفر تفكيراً أسرع ونتائج أقوى من hunyuan-pro.",
|
||||
"hunyuan-turbos-latest.description": "أحدث نموذج رائد من Hunyuan TurboS مع تفكير أقوى وتجربة شاملة أفضل.",
|
||||
"hunyuan-turbos-longtext-128k-20250325.description": "يتفوق في مهام المستندات الطويلة مثل التلخيص والأسئلة والأجوبة مع التعامل أيضًا مع التوليد العام. قوي في تحليل النصوص الطويلة وتوليد المحتوى المعقد والمفصل.",
|
||||
"hunyuan-turbos-vision-video.description": "ينطبق على سيناريوهات فهم الفيديو. ميزات الإصدار: يعتمد على نموذج فهم الفيديو **Hunyuan Turbos-Vision**، يدعم قدرات فهم الفيديو الأساسية مثل وصف الفيديو والإجابة على أسئلة محتوى الفيديو.",
|
||||
"hunyuan-vision-1.5-instruct.description": "نموذج سريع التفكير لتحويل الصور إلى نص مبني على قاعدة النص TurboS، يظهر تحسينات ملحوظة مقارنة بالإصدار السابق في التعرف الأساسي على الصور واستدلال تحليل الصور.",
|
||||
"hunyuan-vision.description": "أحدث نموذج متعدد الوسائط من Hunyuan يدعم إدخالات الصور + النصوص لتوليد النصوص.",
|
||||
"hy-image-lite.description": "يتبنى ترميز ضغط عالي للغاية لتمكين توليد الصور بسرعة مع الحفاظ على جودة عالية للإخراج. يدعم حالات الاستخدام مثل تحسين صور المنتجات للتجارة الإلكترونية، توليد أصول التصميم للأدوات الإبداعية، وتطوير مشاهد الألعاب التكرارية.",
|
||||
"hy-image-v3.0.description": "استنادًا إلى النموذج الكبير Hunyuan، فهو قادر على التفكير في تخطيط الصور، التكوين، والعمل بالفرشاة، باستخدام المعرفة العالمية لاستنتاج المشاهد البصرية المنطقية. يمكنه أيضًا تفسير الدلالات المعقدة على نطاق آلاف الأحرف، توليد محتوى نصي طويل، رسوم هزلية معقدة، ميمات، وإنتاج رسوم تعليمية حيوية وجذابة.",
|
||||
"hy-video-1.5.description": "يدعم المدخلات متعددة الوسائط بما في ذلك النصوص والصور لتوليد فيديوهات عالية الجودة، مما يمكن من انتقالات المشاهد وتفاعلات متعددة الشخصيات. يبسط سير العمل الإنتاجي ويقلل التكاليف، مما يجعله مناسبًا للإعلانات التجارية، التسويق، والتطبيقات الإبداعية الفردية.",
|
||||
"hy3-preview.description": "Hunyuan Hy3 Preview مصمم لأعباء العمل الوكيلة، يعتمد بنية Mixture-of-Experts (MoE) مع 295 مليار إجمالي المعلمات و21 مليار معلمات مفعلة. يقدم ثلاثة أوضاع داخل نموذج واحد—**no_think** (استجابة فائقة السرعة)، **think_low** (تفكير سريع)، و**think_high** (تفكير عميق)—لتلبية متطلبات الكمون والعمق المتنوعة، من التفاعلات عالية التردد إلى المهام الهندسية المعقدة. يحقق أداءً قريبًا من أحدث ما توصلت إليه التكنولوجيا على معايير البرمجة مثل SWE-bench Verified، ويدعم نافذة سياق 256K لإعادة هيكلة الأكواد عبر الملفات وتحليل الوثائق الطويلة. هذا النموذج مناسب للمطورين الذين يحتاجون إلى إكمال المهام بشكل موثوق مع مراعاة تكلفة الاستدلال.",
|
||||
"image-01-live.description": "نموذج توليد صور بتفاصيل دقيقة، يدعم التحويل من نص إلى صورة وأنماط قابلة للتحكم.",
|
||||
"image-01.description": "نموذج توليد صور جديد بتفاصيل دقيقة، يدعم التحويل من نص إلى صورة ومن صورة إلى صورة.",
|
||||
"imagen-4.0-fast-generate-001.description": "سلسلة نماذج Imagen الجيل الرابع لتحويل النص إلى صورة - النسخة السريعة",
|
||||
@@ -824,7 +838,7 @@
|
||||
"kimi-k2-thinking.description": "Kimi-K2 هو نموذج أساسي ببنية MoE أطلقته Moonshot AI بقدرات فائقة في البرمجة والوكلاء. يحتوي على إجمالي 1T من المعلمات و32B من المعلمات النشطة. في اختبارات الأداء المعيارية في الفئات الرئيسية مثل التفكير العام، والبرمجة، والرياضيات، والوكلاء، يتفوق أداء نموذج K2 على النماذج المفتوحة المصدر الرئيسية الأخرى.",
|
||||
"kimi-k2-turbo-preview.description": "kimi-k2 هو نموذج MoE أساسي يتمتع بقدرات قوية في البرمجة والوكالة (1 تريليون معلمة إجمالية، 32 مليار نشطة)، ويتفوق على النماذج المفتوحة السائدة في اختبارات الاستدلال، البرمجة، الرياضيات، والوكالة.",
|
||||
"kimi-k2.5.description": "Kimi K2.5 هو النموذج الأكثر تنوعًا من Kimi حتى الآن، يتميز ببنية متعددة الوسائط تدعم المدخلات البصرية والنصية، أوضاع \"التفكير\" و\"غير التفكير\"، ومهام المحادثة والوكلاء.",
|
||||
"kimi-k2.6.description": "Kimi K2.6 هو نموذج وكيل متعدد الوسائط ومفتوح المصدر، يعزّز القدرات العملية في البرمجة بعيدة المدى، والتصميم المعتمد على البرمجة، والتنفيذ الذاتي الاستباقي، وتنظيم المهام بأسلوب السرب.",
|
||||
"kimi-k2.6.description": "Kimi K2.6 هو أحدث نموذج وأكثرها قدرة من Kimi، يقدم برمجة طويلة الأفق أقوى، اتباع التعليمات، والتصحيح الذاتي مع دعم النصوص، الصور، والفيديو بالإضافة إلى المهام الوكيلة والدردشة.",
|
||||
"kimi-k2.description": "Kimi-K2 هو نموذج MoE أساسي من Moonshot AI يتمتع بقدرات قوية في البرمجة والوكالة، بإجمالي 1 تريليون معلمة و32 مليار نشطة. يتفوق على النماذج المفتوحة السائدة في اختبارات الاستدلال العام، البرمجة، الرياضيات، ومهام الوكالة.",
|
||||
"kimi-k2:1t.description": "Kimi K2 هو نموذج LLM كبير من نوع MoE من Moonshot AI بإجمالي 1 تريليون معلمة و32 مليار نشطة لكل تمرير أمامي. مُحسّن لقدرات الوكالة بما في ذلك استخدام الأدوات المتقدمة، الاستدلال، وتوليد الشيفرة.",
|
||||
"kling/kling-v3-image-generation.description": "يدعم ما يصل إلى 10 صور مرجعية، مما يتيح لك تثبيت الموضوعات والعناصر ونغمات الألوان لضمان نمط متسق. يجمع بين نقل النمط، الإشارة إلى الصور الشخصية/الشخصيات، دمج الصور المتعددة، والتلوين المحلي للتحكم المرن. يقدم تفاصيل واقعية للصور الشخصية، مع مرئيات عامة دقيقة وغنية بالطبقات، تتميز بألوان وأجواء سينمائية.",
|
||||
@@ -938,10 +952,13 @@
|
||||
"mimo-v2-flash.description": "MiMo-V2-Flash أصبح الآن مفتوح المصدر رسميًا! هذا نموذج MoE (مزيج من الخبراء) مصمم خصيصًا لتحقيق كفاءة استدلال قصوى، مع 309 مليار معلمة إجمالية (15 مليار مفعلة). من خلال الابتكارات في بنية هجينة للانتباه وتسريع الاستدلال متعدد الطبقات MTP، يحتل المرتبة بين أفضل نموذجين مفتوحي المصدر عالميًا عبر العديد من مجموعات قياس أداء الوكلاء. قدراته في البرمجة تتفوق على جميع النماذج مفتوحة المصدر وتنافس النماذج المغلقة الرائدة مثل Claude 4.5 Sonnet، مع تحمل 2.5% فقط من تكلفة الاستدلال وتقديم سرعة توليد أسرع بمقدار 2×—مما يدفع كفاءة استدلال النماذج الكبيرة إلى أقصى حد.",
|
||||
"mimo-v2-omni.description": "MiMo-V2-Omni مصمم خصيصًا للتفاعل والتنفيذ متعدد الوسائط في سيناريوهات العالم الحقيقي. قمنا ببناء أساس كامل الوسائط من الصفر، مدمجين النصوص، والرؤية، والصوت، وموحدين بين \"الإدراك\" و\"التنفيذ\" ضمن بنية واحدة. هذا لا يكسر فقط القيود التقليدية للنماذج التي تركز على الفهم على حساب التنفيذ، ولكنه يمنح النموذج أيضًا قدرات أصلية في الإدراك متعدد الوسائط، واستخدام الأدوات، وتنفيذ الوظائف، وتشغيل واجهات المستخدم الرسومية. يمكن لـ MiMo-V2-Omni التكامل بسلاسة مع أطر الوكلاء الرئيسية، محققًا قفزة من الفهم إلى التحكم مع خفض كبير في عوائق نشر الوكلاء متعدد الوسائط بالكامل.",
|
||||
"mimo-v2-pro.description": "MiMo-V2-Pro مصمم خصيصًا لتدفقات العمل الوكيلة عالية الكثافة في السيناريوهات الواقعية. يتميز بأكثر من تريليون معلمة إجمالية (42 مليار معلمة مفعّلة)، ويتبنى بنية انتباه هجينة مبتكرة، ويدعم طول سياق فائق يصل إلى مليون رمز. يعتمد على نموذج أساسي قوي، نقوم بتوسيع الموارد الحسابية باستمرار عبر نطاق أوسع من سيناريوهات الوكلاء، مما يوسع مساحة العمل الذكية بشكل كبير ويحقق تعميمًا ملحوظًا - من البرمجة إلى تنفيذ المهام الواقعية (\"المخلب\").",
|
||||
"mimo-v2.5-pro.description": "MiMo-V2.5-Pro هو النموذج الرئيسي الأكثر قدرة من Xiaomi حتى الآن، يقدم تحسينات كبيرة في القدرات العامة الوكيلة، الهندسة البرمجية المعقدة، والمهام طويلة الأفق. يحتفظ بالهندسة الهجينة ذات الانتباه النشط 1T الإجمالي / 42B مع نافذة سياق 1M، ويمكنه دعم المهام طويلة الأفق المعقدة التي تمتد لأكثر من ألف استدعاء أداة. الأداء على معايير الوكلاء الصعبة (ClawEval، GDPVal، SWE-bench Pro) قابل للمقارنة مع Claude Opus 4.6.",
|
||||
"mimo-v2.5.description": "MiMo-V2.5 هو نموذج وكيل متعدد الوسائط أصلي يفهم الصور، الفيديو، الصوت، والنصوص في بنية موحدة، مع نافذة سياق 1M. يقدم أداءً وكيلًا على مستوى Pro بتكلفة استدلال أقل بنسبة تقريبية من MiMo-V2.5-Pro، مع تحسين الإدراك متعدد الوسائط مقارنة بـ MiMo-V2-Omni. قدراته الوكيلة المدمجة (التصفح، الفهم، التفكير، التنفيذ) والاستدلال الأسرع تجعله مناسبًا لإطارات العمل الوكيلة الحساسة للكمون ومتعددة الخطوات مثل OpenClaw.",
|
||||
"minicpm-v.description": "MiniCPM-V هو نموذج متعدد الوسائط من الجيل التالي من OpenBMB يتميز بقدرات ممتازة في التعرف البصري للنصوص وفهم الوسائط المتعددة لمجموعة واسعة من الاستخدامات.",
|
||||
"minimax-m2.1.description": "MiniMax-M2.1 هو أحدث إصدار من سلسلة MiniMax، مُحسّن للبرمجة متعددة اللغات والمهام المعقدة الواقعية. كنموذج أصلي للذكاء الاصطناعي، يحقق MiniMax-M2.1 تحسينات كبيرة في الأداء، ودعم أطر الوكلاء، والتكيف مع سيناريوهات متعددة، بهدف مساعدة الأفراد والشركات على تبني نمط حياة وعمل قائم على الذكاء الاصطناعي بسرعة أكبر.",
|
||||
"minimax-m2.5.description": "MiniMax-M2.5 هو نموذج لغة كبير متقدم مصمم للإنتاجية الواقعية ومهام البرمجة.",
|
||||
"minimax-m2.7.description": "MiniMax M2.7 هو نموذج لغة كبير وفعال تم بناؤه خصيصًا للبرمجة وتدفقات العمل الوكيلة.",
|
||||
"minimax-m2.5-free.description": "MiniMax M2.5 Free — نموذج برمجة مجاني مع قدرات تفكير كاملة.",
|
||||
"minimax-m2.5.description": "MiniMax M2.5 — نموذج برمجة فعال مع قدرات تفكير قوية.",
|
||||
"minimax-m2.7.description": "MiniMax M2.7 — أحدث نموذج برمجة من MiniMax مع تحسينات في التفكير واستخدام الأدوات.",
|
||||
"minimax-m2.description": "MiniMax M2 هو نموذج لغوي كبير وفعّال صُمم خصيصًا للبرمجة وسير عمل الوكلاء.",
|
||||
"minimax/minimax-m2.1.description": "MiniMax-M2.1 هو نموذج لغوي كبير وخفيف الوزن ومتطور، مُحسّن للبرمجة وسير عمل الوكلاء وتطوير التطبيقات الحديثة، ويقدم مخرجات أنظف وأكثر إيجازًا واستجابة أسرع.",
|
||||
"minimax/minimax-m2.description": "MiniMax-M2 هو نموذج عالي القيمة يتميز في مهام البرمجة والوكلاء في العديد من سيناريوهات الهندسة.",
|
||||
@@ -1016,6 +1033,7 @@
|
||||
"musesteamer-2.0-turbo-i2v.description": "يدعم توليد فيديو ديناميكي صامت بدقة 720P لمدة 5 ثوانٍ، يتميز بصور بجودة سينمائية، حركات كاميرا معقدة، ومشاعر وأفعال شخصيات واقعية.",
|
||||
"musesteamer-air-i2v.description": "نموذج توليد الفيديو Baidu MuseSteamer Air يقدم أداءً جيدًا في اتساق الموضوع، الواقعية الفيزيائية، تأثيرات حركة الكاميرا، وسرعة التوليد. يدعم توليد فيديو ديناميكي صامت بدقة 720P لمدة 5 ثوانٍ، يقدم صورًا بجودة سينمائية، توليد سريع، وفعالية تكلفة ممتازة.",
|
||||
"musesteamer-air-image.description": "musesteamer-air-image هو نموذج لتوليد الصور تم تطويره بواسطة فريق البحث في Baidu لتقديم أداء استثنائي من حيث التكلفة. يمكنه بسرعة توليد صور واضحة ومتسقة الحركة بناءً على مطالبات المستخدم، مما يحول أوصاف المستخدم بسهولة إلى صور.",
|
||||
"nemotron-3-super-free.description": "Nemotron 3 Super Free من Nvidia — نموذج تفكير مجاني مع دعم قوي للبرمجة.",
|
||||
"nousresearch/hermes-2-pro-llama-3-8b.description": "Hermes 2 Pro Llama 3 8B هو إصدار محدث من Nous Hermes 2 باستخدام أحدث مجموعات البيانات المطورة داخليًا.",
|
||||
"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF.description": "Llama 3.1 Nemotron 70B هو نموذج LLM مخصص من NVIDIA لتحسين الفائدة. يحقق أداءً قويًا في Arena Hard وAlpacaEval 2 LC وGPT-4-Turbo MT-Bench، ويحتل المرتبة الأولى في جميع معايير المحاذاة التلقائية الثلاثة حتى 1 أكتوبر 2024. تم تدريبه من Llama-3.1-70B-Instruct باستخدام RLHF (REINFORCE)، وLlama-3.1-Nemotron-70B-Reward، ومطالبات HelpSteer2-Preference.",
|
||||
"nvidia/llama-3.1-nemotron-51b-instruct.description": "نموذج لغوي مميز يقدم دقة وكفاءة استثنائية.",
|
||||
@@ -1118,6 +1136,7 @@
|
||||
"qwen-coder-turbo-latest.description": "نموذج Qwen للبرمجة.",
|
||||
"qwen-coder-turbo.description": "نموذج Qwen للبرمجة.",
|
||||
"qwen-flash.description": "أسرع وأقل نماذج Qwen تكلفة، مثالي للمهام البسيطة.",
|
||||
"qwen-image-2.0-pro-2026-04-22.description": "نموذج Qwen-Image-2.0 الكامل يدمج توليد الصور وتحريرها في قدرة موحدة. يدعم عرض النصوص بشكل أكثر احترافية مع سعة تعليمات تصل إلى 1k رمز، يقدم قوامًا بصريًا أكثر دقة وواقعية، يمكن من تصوير دقيق للمشاهد الواقعية، ويظهر توافقًا دلاليًا أقوى مع التعليمات. النموذج الكامل يقدم أقوى قدرة عرض نصوص وأعلى مستوى من الواقعية داخل سلسلة 2.0.",
|
||||
"qwen-image-2.0-pro.description": "نموذج الإصدار الكامل من سلسلة Qwen-Image-2.0 يدمج توليد الصور وتحريرها في قدرة موحدة. يدعم تقديم نصوص أكثر احترافية مع قدرة تعليم تصل إلى 1k رمز، ويوفر تفاصيل بصرية أكثر دقة وواقعية، ويُمكّن من تصوير دقيق للمشاهد الواقعية، ويظهر توافقًا أقوى مع التعليمات النصية. يوفر نموذج الإصدار الكامل أقوى قدرة على تقديم النصوص وأعلى مستوى من الواقعية ضمن سلسلة 2.0.",
|
||||
"qwen-image-2.0.description": "نموذج الإصدار المُسرّع من سلسلة Qwen-Image-2.0 يدمج توليد الصور وتحريرها في قدرة موحدة. يدعم تقديم نصوص أكثر احترافية مع قدرة تعليم تصل إلى 1k رمز، ويوفر تفاصيل بصرية أكثر دقة وواقعية، ويُمكّن من تصوير دقيق للمشاهد الواقعية، ويظهر توافقًا أقوى مع التعليمات النصية. يحقق الإصدار المُسرّع التوازن الأمثل بين جودة النموذج والأداء بشكل فعال.",
|
||||
"qwen-image-edit-max.description": "نموذج تحرير الصور Qwen يدعم إدخال صور متعددة وإخراج صور متعددة، مما يمكن من تحرير النصوص داخل الصور بدقة، وإضافة أو إزالة أو نقل الكائنات، وتعديل حركة الموضوع، ونقل أنماط الصور، وتحسين التفاصيل البصرية.",
|
||||
@@ -1240,8 +1259,10 @@
|
||||
"qwen3.5-flash.description": "يعتمد نموذج Qwen3.5 Flash للرؤية واللغة على بنية هجينة تجمع بين آلية الانتباه الخطي وتصميم الخبراء المتعددين (MoE) المتناثر، مما يحقق كفاءة أعلى في الاستدلال. مقارنةً بسلسلة 3، يقدم النموذج تحسينات كبيرة في الأداء النصي البحت والمتعدد الوسائط، كما يوفر استجابات سريعة مع تحقيق توازن بين سرعة الاستدلال والقدرات العامة.",
|
||||
"qwen3.5-omni-flash.description": "Qwen3.5 Omni Flash هو نموذج Qwen كامل الوسائط سريع وفعال من حيث التكلفة يدعم إدخال النصوص والصور والفيديو.",
|
||||
"qwen3.5-omni-plus.description": "Qwen3.5 Omni Plus يدعم إدخال النصوص والصور والفيديو. إنه أحدث نموذج Qwen كامل الوسائط لفهم وتوليد متعدد الوسائط عالي الجودة.",
|
||||
"qwen3.5-plus-2026-04-20.description": "Qwen 3.5 هو نموذج رؤية-لغة Plus أصلي. مقارنة بلقطة فبراير 15، يقدم هذا الإصدار تحسينات كبيرة في قدرات البرمجة الوكيلة وسرعة الاستدلال بشكل ملحوظ. تظل معرفته، تفكيره، وقدراته على السياق الطويل على مستوى عالٍ، مما يلبي متطلبات المهام الوكيلة المعقدة. يناسب البرمجة الوكيلة، سير العمل الإنتاجي، والسيناريوهات ذات الإنتاجية العالية. يتوافق هذا الإصدار مع لقطة أبريل 20، 2026.",
|
||||
"qwen3.5-plus.description": "Qwen3.5 Plus يدعم إدخال النصوص، الصور، والفيديو. أداؤه في المهام النصية البحتة يعادل Qwen3 Max، مع أداء أفضل وتكلفة أقل. قدراته متعددة الوسائط محسنة بشكل كبير مقارنة بسلسلة Qwen3 VL.",
|
||||
"qwen3.5:397b.description": "Qwen3.5 هو نموذج أساسي موحد للرؤية واللغة مع بنية هجينة (Mixture-of-Experts + انتباه خطي)، يقدم قدرات قوية في التفكير متعدد الوسائط، البرمجة، والسياقات الطويلة مع نافذة سياق 256K.",
|
||||
"qwen3.6-27b.description": "سلسلة Qwen 3.6 27B هي نموذج رؤية-لغة كثيف أصلي. مقارنة بالإصدار 3.5-27B، يقدم تحسينات كبيرة في قدرات البرمجة الوكيلة، مع تحسينات إضافية في الأداء STEM وقدرة التفكير. على الجانب البصري، يظهر مكاسب ملحوظة في الذكاء المكاني، تحديد المواقع، والكشف، بينما يتحسن أيضًا بشكل ثابت في فهم الفيديو، OCR الوثائق، وقدرات الوكيل البصري.",
|
||||
"qwen3.6-35b-a3b.description": "يعتمد نموذج Qwen3.6 35B-A3B للرؤية واللغة على بنية هجينة تدمج آلية الانتباه الخطي مع تصميم الخبراء المتعددين (MoE) المتناثر، مما يحقق كفاءة أعلى في الاستدلال. مقارنةً بنموذج 3.5-35B-A3B، يقدم هذا الإصدار تحسينات كبيرة في قدرات البرمجة الوكيلية، والاستدلال الرياضي، واستدلال الأكواد، والذكاء المكاني، إضافة إلى تحديد المواقع واكتشاف الأهداف.",
|
||||
"qwen3.6-flash.description": "يقدم نموذج Qwen3.6 Flash للرؤية واللغة أداءً محسّناً بشكل ملحوظ مقارنةً بإصدار 3.5-Flash. يركز النموذج على تعزيز قدرات البرمجة الوكيلية (متفوقاً بشكل كبير على سابقه في العديد من معايير تقييم الوكلاء البرمجيين)، إضافة إلى تحسين قدرات الاستدلال الرياضي واستدلال الأكواد. وعلى جانب الرؤية، يقدم النموذج تحسينات واضحة في الذكاء المكاني، مع تقدم قوي في تحديد المواقع واكتشاف الأهداف.",
|
||||
"qwen3.6-max-preview.description": "أكبر نموذج مغلق المصدر ضمن سلسلة Qwen3.6. يقدم معرفة أعمق بالعالم، وقدرة أعلى على اتباع التعليمات، وأداءً أقوى في البرمجة الوكيلية للمهام المعقدة. وهو نصي فقط، ويدعم وضع التفكير بشكل افتراضي، إضافة إلى التخزين المؤقت الصريح واستدعاء الدوال.",
|
||||
@@ -1253,8 +1274,6 @@
|
||||
"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 مع دعم لغات موسع وسياق أطول.",
|
||||
@@ -1286,6 +1305,7 @@
|
||||
"step-2-16k.description": "يدعم التفاعلات ذات السياق الكبير للمحادثات المعقدة.",
|
||||
"step-2-mini.description": "مبني على بنية MFA الجديدة، يقدم نتائج مماثلة لـ Step-1 بتكلفة أقل، مع إنتاجية أعلى وزمن استجابة أسرع. يتعامل مع المهام العامة بقدرة قوية على البرمجة.",
|
||||
"step-2x-large.description": "نموذج صور من الجيل الجديد من StepFun يركز على توليد الصور، وينتج صورًا عالية الجودة من التعليمات النصية. يتميز بواقعية أكبر في الملمس وقدرة أقوى على عرض النصوص الصينية/الإنجليزية.",
|
||||
"step-3.5-flash-2603.description": "مبني على Step 3.5 Flash ومحسن لسيناريوهات الوكلاء عالية التردد، يحسن كفاءة الرموز وسرعة الاستدلال مع الحفاظ على قدرات التفكير واستدعاء الأدوات على مستوى النموذج الرئيسي. يدعم أيضًا التبديل إلى وضع التفكير المنخفض لتقليل استهلاك الموارد. بالإضافة إلى ذلك، تم إجراء تحسينات مستهدفة لتعزيز التوافق مع مهام البرمجة وإطارات العمل الوكيلة.",
|
||||
"step-3.5-flash.description": "نموذج التفكير اللغوي الرائد من Stepfun. يتميز بقدرات تفكير من الدرجة الأولى وقدرات تنفيذ سريعة وموثوقة. قادر على تحليل وتخطيط المهام المعقدة، واستدعاء الأدوات بسرعة وموثوقية لأداء المهام، والتعامل مع مختلف المهام المعقدة مثل التفكير المنطقي، الرياضيات، هندسة البرمجيات، والبحث المتعمق.",
|
||||
"step-3.description": "يتمتع هذا النموذج بإدراك بصري قوي واستدلال معقد، ويتعامل بدقة مع فهم المعرفة عبر المجالات، وتحليل الرياضيات والرؤية، ومجموعة واسعة من مهام التحليل البصري اليومية.",
|
||||
"step-r1-v-mini.description": "نموذج استدلال يتمتع بفهم قوي للصور، يمكنه معالجة الصور والنصوص، ثم توليد نص بعد استدلال عميق. يتفوق في الاستدلال البصري ويقدم أداءً رائدًا في الرياضيات والبرمجة والاستدلال النصي، مع نافذة سياق تصل إلى 100 ألف.",
|
||||
@@ -1366,10 +1386,12 @@
|
||||
"wan2.6-r2v.description": "Wanxiang 2.6 المرجع إلى الفيديو يدعم الإشارة إلى شخصيات محددة أو أي كائنات، يحافظ بدقة على الاتساق في المظهر والصوت، ويمكّن الإشارة إلى شخصيات متعددة للأداء المشترك. ملاحظة: عند استخدام الفيديوهات كمرجع، سيتم احتساب الفيديو المدخل ضمن التكلفة. يرجى الرجوع إلى وثائق تسعير النموذج للحصول على التفاصيل.",
|
||||
"wan2.6-t2i.description": "Wanxiang 2.6 T2I يدعم اختيارًا مرنًا لأبعاد الصور ضمن قيود إجمالي مساحة البكسل ونسبة العرض إلى الارتفاع (مثل Wanxiang 2.5).",
|
||||
"wan2.6-t2v.description": "Wanxiang 2.6 يقدم قدرات سرد متعددة اللقطات، ويدعم أيضًا توليد التعليق الصوتي التلقائي والقدرة على دمج ملفات صوتية مخصصة.",
|
||||
"wan2.7-i2v-2026-04-25.description": "Wanxiang 2.7 Image-to-Video يقدم ترقية شاملة في قدرات الأداء. المشاهد الدرامية تتميز بتعبير عاطفي دقيق وطبيعي، بينما تكون المشاهد الحركية مكثفة ومؤثرة. مع انتقالات لقطات أكثر ديناميكية ومحركة بالإيقاع، يحقق أداءً أقوى بشكل عام وسردًا قصصيًا.",
|
||||
"wan2.7-i2v.description": "Wanxiang 2.7 الصورة إلى الفيديو يقدم ترقية شاملة في قدرات الأداء. المشاهد الدرامية تتميز بتعبير عاطفي دقيق وطبيعي، بينما تكون تسلسلات الحركة مكثفة ومؤثرة. مع انتقالات لقطات أكثر ديناميكية وموجهة بالإيقاع، يحقق أداءً أقوى وسردًا قصصيًا.",
|
||||
"wan2.7-image-pro.description": "Wanxiang 2.7 الصورة الإصدار الاحترافي، يدعم إخراج بدقة 4K عالية الوضوح.",
|
||||
"wan2.7-image.description": "Wanxiang 2.7 الصورة، سرعة توليد الصور أسرع.",
|
||||
"wan2.7-r2v.description": "Wanxiang 2.7 المرجع إلى الفيديو يقدم مراجع أكثر استقرارًا للشخصيات، الدعائم، والمشاهد. يدعم ما يصل إلى 5 صور أو فيديوهات مرجعية مختلطة، مع الإشارة إلى نغمة الصوت. مع قدرات أساسية مطورة، يقدم أداءً أقوى وقوة تعبيرية.",
|
||||
"wan2.7-t2v-2026-04-25.description": "Wanxiang 2.7 Text-to-Video يقدم ترقية شاملة في قدرات الأداء. المشاهد الدرامية تتميز بتعبير عاطفي دقيق وطبيعي، بينما تكون المشاهد الحركية مكثفة ومؤثرة. مع انتقالات لقطات أكثر ديناميكية ومحركة بالإيقاع، يحقق أداءً أقوى بشكل عام وسردًا قصصيًا.",
|
||||
"wan2.7-t2v.description": "Wanxiang 2.7 النص إلى الفيديو يقدم ترقية شاملة في قدرات الأداء. المشاهد الدرامية تتميز بتعبير عاطفي دقيق وطبيعي، بينما تكون تسلسلات الحركة مكثفة ومؤثرة. مع انتقالات لقطات أكثر ديناميكية وموجهة بالإيقاع، يحقق أداءً أقوى وسردًا قصصيًا.",
|
||||
"wanx-v1.description": "نموذج تحويل النص إلى صورة الأساسي. يُقابل Tongyi Wanxiang 1.0 General.",
|
||||
"wanx2.0-t2i-turbo.description": "يتفوّق في الصور الشخصية الملمّسة بسرعة معتدلة وتكلفة منخفضة. يُقابل Tongyi Wanxiang 2.0 Speed.",
|
||||
@@ -1408,6 +1430,8 @@
|
||||
"yi-spark.description": "نموذج مدمج وسريع مع قدرات محسّنة في الرياضيات والبرمجة.",
|
||||
"yi-vision-v2.description": "نموذج رؤية للمهام المعقدة مع فهم وتحليل قوي لصور متعددة.",
|
||||
"yi-vision.description": "نموذج رؤية للمهام المعقدة مع فهم وتحليل قوي للصور.",
|
||||
"youtu-vita.description": "VITA هو نموذج فهم متعدد الوسائط يدعم تحليل محتوى الفيديو والصور. يمكن استخدامه لمهام مثل تحليل هيكل الفيديو وكشف الكائنات في الصور.",
|
||||
"yt-video-2.0.description": "يولد فيديوهات متسقة زمنيًا بشكل كبير من الصور، مناسب للتطبيقات المتطلبة مثل الإعلانات، مقاطع الأفلام، وفيديوهات عرض المنتجات.",
|
||||
"z-ai/glm-4.5-air.description": "GLM 4.5 Air هو إصدار خفيف الوزن من GLM 4.5 مخصص للسيناريوهات الحساسة للتكلفة مع الحفاظ على قدرات استدلال قوية.",
|
||||
"z-ai/glm-4.5.description": "GLM 4.5 هو النموذج الرائد من Z.AI باستدلال هجين مُحسّن للهندسة والمهام طويلة السياق.",
|
||||
"z-ai/glm-4.6.description": "GLM 4.6 هو النموذج الرائد من Z.AI مع طول سياق ممتد وقدرات برمجية متقدمة.",
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
{
|
||||
"agent_cron_job_failed": "فشل تنفيذ مهمتك المجدولة \"{{jobName}}\". افتح المهمة لعرض الخطأ الكامل.",
|
||||
"agent_cron_job_failed_insufficient_budget": "تعذّر تشغيل مهمتك المجدولة \"{{jobName}}\" بسبب نفاد رصيد الحساب. يُرجى شحن الرصيد أو ترقية خطتك لاستئناف التشغيل المستقبلي.",
|
||||
"agent_cron_job_failed_insufficient_budget_title": "إيقاف المهمة المجدولة: رصيد غير كافٍ",
|
||||
"agent_cron_job_failed_title": "فشل المهمة المجدولة",
|
||||
"billboard.learnMore": "معرفة المزيد",
|
||||
"billboard.menuLabel": "الإعلانات",
|
||||
"image_generation_completed": "الصورة الخاصة بك \"{{prompt}}\" جاهزة.",
|
||||
"image_generation_completed_title": "اكتملت عملية إنشاء الصورة",
|
||||
"inbox.archiveAll": "أرشفة الكل",
|
||||
"inbox.empty": "لا توجد إشعارات بعد",
|
||||
"inbox.emptyUnread": "لا توجد إشعارات غير مقروءة",
|
||||
"inbox.filterUnread": "عرض غير المقروء فقط",
|
||||
"inbox.markAllRead": "تحديد الكل كمقروء",
|
||||
"inbox.title": "الإشعارات"
|
||||
"inbox.title": "الإشعارات",
|
||||
"video_generation_completed": "الفيديو الخاص بك \"{{prompt}}\" جاهز.",
|
||||
"video_generation_completed_title": "اكتملت عملية إنشاء الفيديو"
|
||||
}
|
||||
|
||||
@@ -87,10 +87,19 @@
|
||||
"finish": "ابدأ الآن",
|
||||
"interests.area.business": "الأعمال والاستراتيجية",
|
||||
"interests.area.coding": "البرمجة والتطوير",
|
||||
"interests.area.creator": "اقتصاد صُنّاع المحتوى",
|
||||
"interests.area.design": "التصميم والإبداع",
|
||||
"interests.area.education": "التعلم والبحث",
|
||||
"interests.area.finance-legal": "المالية والقانون",
|
||||
"interests.area.health": "الصحة والعادات",
|
||||
"interests.area.hobbies": "الهوايات والثقافة",
|
||||
"interests.area.hr": "شؤون الأفراد والموارد البشرية",
|
||||
"interests.area.investing": "الاستثمار والمالية",
|
||||
"interests.area.marketing": "التسويق والترويج",
|
||||
"interests.area.operations": "العمليات والإدارة",
|
||||
"interests.area.other": "مجالات أخرى",
|
||||
"interests.area.parenting": "الأسرة وتربية الأطفال",
|
||||
"interests.area.personal": "الحياة الشخصية",
|
||||
"interests.area.product": "المنتجات والإدارة",
|
||||
"interests.area.sales": "المبيعات وخدمة العملاء",
|
||||
"interests.area.writing": "إنشاء المحتوى",
|
||||
|
||||
+13
-7
@@ -28,15 +28,13 @@
|
||||
"builtins.lobe-agent-builder.title": "خبير بناء الوكلاء",
|
||||
"builtins.lobe-agent-documents.apiName.copyDocument": "نسخ المستند",
|
||||
"builtins.lobe-agent-documents.apiName.createDocument": "إنشاء مستند",
|
||||
"builtins.lobe-agent-documents.apiName.editDocument": "تعديل المستند",
|
||||
"builtins.lobe-agent-documents.apiName.listDocuments": "قائمة المستندات",
|
||||
"builtins.lobe-agent-documents.apiName.patchDocument": "تعديل المستند",
|
||||
"builtins.lobe-agent-documents.apiName.modifyNodes": "تعديل المستند",
|
||||
"builtins.lobe-agent-documents.apiName.readDocument": "قراءة المستند",
|
||||
"builtins.lobe-agent-documents.apiName.readDocumentByFilename": "قراءة المستند حسب اسم الملف",
|
||||
"builtins.lobe-agent-documents.apiName.removeDocument": "إزالة المستند",
|
||||
"builtins.lobe-agent-documents.apiName.renameDocument": "إعادة تسمية المستند",
|
||||
"builtins.lobe-agent-documents.apiName.replaceDocumentContent": "استبدال محتوى المستند",
|
||||
"builtins.lobe-agent-documents.apiName.updateLoadRule": "تحديث قاعدة التحميل",
|
||||
"builtins.lobe-agent-documents.apiName.upsertDocumentByFilename": "إدراج أو تحديث المستند حسب اسم الملف",
|
||||
"builtins.lobe-agent-documents.title": "مستندات الوكيل",
|
||||
"builtins.lobe-agent-management.apiName.callAgent": "وكيل الاتصال",
|
||||
"builtins.lobe-agent-management.apiName.createAgent": "إنشاء وكيل",
|
||||
@@ -438,9 +436,6 @@
|
||||
"loading.plugin": "المهارة قيد التشغيل…",
|
||||
"localSystem.workingDirectory.agentDescription": "دليل العمل الافتراضي لجميع المحادثات مع هذا الوكيل",
|
||||
"localSystem.workingDirectory.agentLevel": "دليل عمل الوكيل",
|
||||
"localSystem.workingDirectory.aheadBehindTooltip": "{{ahead}} للدفع · {{behind}} للسحب ({{upstream}})",
|
||||
"localSystem.workingDirectory.aheadTooltip": "{{count}} عملية دفع إلى {{upstream}}",
|
||||
"localSystem.workingDirectory.behindTooltip": "{{count}} عملية سحب من {{upstream}}",
|
||||
"localSystem.workingDirectory.branchSearchPlaceholder": "البحث عن الفروع",
|
||||
"localSystem.workingDirectory.branchesEmpty": "لا توجد فروع محلية",
|
||||
"localSystem.workingDirectory.branchesHeading": "الفروع",
|
||||
@@ -465,6 +460,17 @@
|
||||
"localSystem.workingDirectory.notSet": "انقر لتعيين دليل العمل",
|
||||
"localSystem.workingDirectory.placeholder": "أدخل مسار الدليل، مثل /Users/name/projects",
|
||||
"localSystem.workingDirectory.prTooltipWithExtra": "{{title}} (+{{count}} من طلبات السحب الإضافية المفتوحة على هذا الفرع)",
|
||||
"localSystem.workingDirectory.pullAction": "انقر لسحب {{count}} عملية تغيير من {{upstream}}",
|
||||
"localSystem.workingDirectory.pullFailed": "فشل السحب",
|
||||
"localSystem.workingDirectory.pullInProgress": "جارٍ السحب…",
|
||||
"localSystem.workingDirectory.pullNoop": "محدّث بالفعل",
|
||||
"localSystem.workingDirectory.pullSuccess": "تم السحب بنجاح",
|
||||
"localSystem.workingDirectory.pushAction": "انقر لدفع {{count}} عملية تغيير إلى {{target}}",
|
||||
"localSystem.workingDirectory.pushActionNew": "انقر لإنشاء الفرع {{target}}",
|
||||
"localSystem.workingDirectory.pushFailed": "فشل الدفع",
|
||||
"localSystem.workingDirectory.pushInProgress": "جارٍ الدفع…",
|
||||
"localSystem.workingDirectory.pushNoop": "كل شيء محدّث",
|
||||
"localSystem.workingDirectory.pushSuccess": "تم الدفع بنجاح",
|
||||
"localSystem.workingDirectory.recent": "حديث",
|
||||
"localSystem.workingDirectory.refreshGitStatus": "تحديث حالة الفرع وطلبات السحب",
|
||||
"localSystem.workingDirectory.removeRecent": "إزالة من الحديث",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"cohere.description": "تقدم Cohere نماذج متعددة اللغات متطورة، واسترجاعًا متقدمًا، ومساحات عمل ذكاء اصطناعي للمؤسسات الحديثة — كل ذلك ضمن منصة آمنة واحدة.",
|
||||
"cometapi.description": "توفر CometAPI الوصول إلى نماذج متقدمة من OpenAI وAnthropic وGoogle وغيرها، مما يتيح للمستخدمين اختيار النموذج والسعر الأنسب لحالات الاستخدام المختلفة.",
|
||||
"comfyui.description": "محرك سير عمل مفتوح المصدر قوي لتوليد الصور والفيديو والصوت، يدعم نماذج مثل SD وFLUX وQwen وHunyuan وWAN مع تحرير قائم على العقد ونشر خاص.",
|
||||
"deepseek.description": "تركز DeepSeek على أبحاث وتطبيقات الذكاء الاصطناعي؛ تتفوق أحدث نماذجها DeepSeek-V3 على نماذج مفتوحة مثل Qwen2.5-72B وLlama-3.1-405B، وتقترب من أداء النماذج المغلقة الرائدة مثل GPT-4o وClaude-3.5-Sonnet.",
|
||||
"deepseek.description": "تركّز DeepSeek على أبحاث الذكاء الاصطناعي وتطبيقاته. وتقدّم أحدث عائلة DeepSeek V4 بإصدارَي Flash وPro مع نافذة سياق تصل إلى مليون نقطة وقدرات تفكير هجينة — مما يجعلها منافسة لأقوى النماذج المغلقة المتقدمة في اختبارات الاستدلال وعمل الوكلاء.",
|
||||
"fal.description": "منصة وسائط توليدية مصممة للمطورين.",
|
||||
"fireworksai.description": "توفر Fireworks AI خدمات نماذج لغوية متقدمة مع دعم استدعاء الوظائف والمعالجة متعددة الوسائط. تم تحسين Firefunction V2 (المبني على Llama-3) لاستدعاء الوظائف والدردشة وتنفيذ التعليمات، بينما يدعم FireLLaVA-13B إدخال الصور والنصوص معًا. تشمل النماذج الأخرى Llama وMixtral.",
|
||||
"giteeai.description": "توفر Gitee AI واجهات برمجة تطبيقات بدون خوادم لخدمات استدلال النماذج اللغوية الكبيرة، جاهزة للاستخدام من قبل المطورين.",
|
||||
@@ -33,7 +33,6 @@
|
||||
"jina.description": "تأسست Jina AI في عام 2020، وهي شركة رائدة في مجال البحث الذكي. تشمل تقنياتها نماذج المتجهات، ومعيدو الترتيب، ونماذج لغوية صغيرة لبناء تطبيقات بحث توليدية ومتعددة الوسائط عالية الجودة.",
|
||||
"kimicodingplan.description": "كود Kimi من Moonshot AI يوفر الوصول إلى نماذج Kimi بما في ذلك K2.5 لأداء مهام الترميز.",
|
||||
"lmstudio.description": "LM Studio هو تطبيق سطح مكتب لتطوير وتجربة النماذج اللغوية الكبيرة على جهازك.",
|
||||
"lobehub.description": "تستخدم LobeHub Cloud واجهات برمجة التطبيقات الرسمية للوصول إلى نماذج الذكاء الاصطناعي، ويتم احتساب الاستخدام عبر أرصدة (Credits) المرتبطة برموز النماذج.",
|
||||
"longcat.description": "LongCat هو سلسلة من نماذج الذكاء الاصطناعي التوليدية الكبيرة التي تم تطويرها بشكل مستقل بواسطة Meituan. تم تصميمه لتعزيز إنتاجية المؤسسة الداخلية وتمكين التطبيقات المبتكرة من خلال بنية حسابية فعالة وقدرات متعددة الوسائط قوية.",
|
||||
"minimax.description": "تأسست MiniMax في عام 2021، وتبني نماذج ذكاء اصطناعي متعددة الوسائط للأغراض العامة، بما في ذلك نماذج نصية بمليارات المعلمات، ونماذج صوتية وبصرية، بالإضافة إلى تطبيقات مثل Hailuo AI.",
|
||||
"minimaxcodingplan.description": "خطة الرموز MiniMax توفر الوصول إلى نماذج MiniMax بما في ذلك M2.7 لأداء مهام الترميز عبر اشتراك ثابت الرسوم.",
|
||||
@@ -47,6 +46,8 @@
|
||||
"ollama.description": "تقدم Ollama نماذج لتوليد الأكواد، والرياضيات، والمعالجة متعددة اللغات، والدردشة، مع دعم للنشر المؤسسي والمحلي.",
|
||||
"ollamacloud.description": "توفر Ollama Cloud استدلالًا مدارًا مع وصول فوري إلى مكتبة نماذج Ollama وواجهات برمجة تطبيقات متوافقة مع OpenAI.",
|
||||
"openai.description": "OpenAI هي مختبر أبحاث رائد في مجال الذكاء الاصطناعي، طورت نماذج GPT التي أحدثت تقدمًا كبيرًا في معالجة اللغة الطبيعية، مع أداء قوي وقيمة عالية في البحث والأعمال والابتكار.",
|
||||
"opencodecodingplan.description": "تقدّم OpenCode Go اشتراكًا بقيمة 10 دولارات شهريًا يوفّر وصولًا موثوقًا إلى نماذج البرمجة المختارة بعناية: GLM وKimi وMiMo وQwen وMiniMax.",
|
||||
"opencodezen.description": "يوفّر OpenCode Zen وصولًا إلى مجموعة من النماذج المختارة من OpenAI وAnthropic وMoonshot وMiniMax وZhipu وQwen وغيرها من خلال مفتاح API واحد.",
|
||||
"openrouter.description": "يوفر OpenRouter الوصول إلى العديد من النماذج المتقدمة من OpenAI وAnthropic وLLaMA وغيرها، مما يتيح للمستخدمين اختيار النموذج والسعر الأنسب لحالتهم.",
|
||||
"perplexity.description": "تقدم Perplexity نماذج دردشة متقدمة، بما في ذلك إصدارات Llama 3.1، للاستخدام عبر الإنترنت وغير المتصل ولمهام معالجة اللغة الطبيعية المعقدة.",
|
||||
"ppio.description": "توفر PPIO واجهات برمجة تطبيقات موثوقة وفعالة من حيث التكلفة للنماذج المفتوحة، بما في ذلك DeepSeek وLlama وQwen وغيرها من النماذج الرائدة.",
|
||||
|
||||
+18
-5
@@ -191,11 +191,6 @@
|
||||
"analytics.telemetry.desc": "ساعدنا في تحسين {{appName}} من خلال بيانات استخدام مجهولة",
|
||||
"analytics.telemetry.title": "إرسال بيانات استخدام مجهولة",
|
||||
"analytics.title": "التحليلات",
|
||||
"ccStatus.account.label": "الحساب",
|
||||
"ccStatus.detecting": "جارٍ اكتشاف Claude Code CLI...",
|
||||
"ccStatus.redetect": "إعادة الاكتشاف",
|
||||
"ccStatus.title": "واجهة سطر أوامر Claude Code",
|
||||
"ccStatus.unavailable": "لم يتم العثور على Claude Code CLI. يُرجى تثبيته أو تهيئته.",
|
||||
"checking": "جارٍ التحقق...",
|
||||
"checkingPermissions": "جارٍ التحقق من الأذونات...",
|
||||
"creds.actions.delete": "حذف",
|
||||
@@ -292,6 +287,17 @@
|
||||
"header.sessionDesc": "ملف تعريف الوكيل وتفضيلات الجلسة",
|
||||
"header.sessionWithName": "إعدادات الجلسة · {{name}}",
|
||||
"header.title": "الإعدادات",
|
||||
"heterogeneousStatus.account.label": "الحساب",
|
||||
"heterogeneousStatus.auth.api": "واجهة برمجة التطبيقات",
|
||||
"heterogeneousStatus.auth.label": "طريقة التوثيق",
|
||||
"heterogeneousStatus.auth.subscription": "الاشتراك",
|
||||
"heterogeneousStatus.command.edit": "تحرير الأمر",
|
||||
"heterogeneousStatus.command.label": "أمر التشغيل",
|
||||
"heterogeneousStatus.command.placeholder": "اسم الأمر أو المسار المطلق",
|
||||
"heterogeneousStatus.detecting": "يتم الآن اكتشاف واجهة سطر الأوامر لـ {{name}}...",
|
||||
"heterogeneousStatus.plan.label": "الخطة",
|
||||
"heterogeneousStatus.redetect": "إعادة الاكتشاف",
|
||||
"heterogeneousStatus.unavailable": "واجهة سطر الأوامر لـ {{name}} غير مُثبتة. يرجى تثبيتها أو إعدادها.",
|
||||
"hotkey.clearBinding": "مسح الربط",
|
||||
"hotkey.conflicts": "تعارض مع اختصارات موجودة",
|
||||
"hotkey.errors.CONFLICT": "تعارض في الاختصار: هذا الاختصار مستخدم بالفعل لوظيفة أخرى",
|
||||
@@ -448,11 +454,18 @@
|
||||
"myAgents.status.published": "منشور",
|
||||
"myAgents.status.unpublished": "غير منشور",
|
||||
"myAgents.title": "وكلائي المنشورون",
|
||||
"notification.category.generation.desc": "إشعارات استكمال الصور ومقاطع الفيديو",
|
||||
"notification.category.generation.title": "الإنشاء",
|
||||
"notification.category.schedule.desc": "إخفاقات وتوقّف المهام المجدولة",
|
||||
"notification.category.schedule.title": "المهام المجدولة",
|
||||
"notification.email.desc": "تلقي إشعارات البريد الإلكتروني عند حدوث أحداث مهمة",
|
||||
"notification.email.title": "إشعارات البريد الإلكتروني",
|
||||
"notification.enabled": "مفعل",
|
||||
"notification.inbox.desc": "عرض الإشعارات في صندوق الوارد داخل التطبيق",
|
||||
"notification.inbox.title": "إشعارات صندوق الوارد",
|
||||
"notification.item.agent_cron_job_failed": "إخفاقات المهام المجدولة",
|
||||
"notification.item.image_generation_completed": "اكتمل إنشاء الصورة",
|
||||
"notification.item.video_generation_completed": "اكتمل إنشاء الفيديو",
|
||||
"notification.title": "قنوات الإشعارات",
|
||||
"plugin.addMCPPlugin": "إضافة MCP",
|
||||
"plugin.addTooltip": "مهارات مخصصة",
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
"table.columns.trigger.enums.cron": "مهمة مجدولة",
|
||||
"table.columns.trigger.enums.eval": "تقييم الأداء",
|
||||
"table.columns.trigger.enums.file_embedding": "تضمين ملف",
|
||||
"table.columns.trigger.enums.image": "توليد الصور",
|
||||
"table.columns.trigger.enums.memory": "استخراج الذاكرة",
|
||||
"table.columns.trigger.enums.semantic_search": "بحث المعرفة",
|
||||
"table.columns.trigger.enums.topic": "ملخص الموضوع",
|
||||
"table.columns.trigger.enums.video": "توليد الفيديو",
|
||||
"table.columns.trigger.title": "المشغل",
|
||||
"table.columns.type.enums.chat": "توليد نصوص",
|
||||
"table.columns.type.enums.embedding": "تضمين",
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
{
|
||||
"action.connect.button": "اتصل بـ {{provider}}",
|
||||
"action.create.error": "فشل في إنشاء المهمة. يرجى المحاولة مرة أخرى.",
|
||||
"action.create.success": "تمت إضافة المهمة المجدولة. يمكنك العثور عليها في Lobe AI.",
|
||||
"action.createButton": "أضف كمهمة مجدولة",
|
||||
"action.creating": "جاري الإنشاء...",
|
||||
"action.dismiss.error": "فشل في الإلغاء. يرجى المحاولة مرة أخرى.",
|
||||
"action.dismiss.tooltip": "غير مهتم",
|
||||
"action.optionalConnect.button": "اتصل بـ {{provider}} للحصول على نتائج أكثر ثراءً",
|
||||
"ad-creative-inspiration.description": "كل صباح، قم بمسح إعلانات المنافسين / العلامات التجارية المرجعية (مكتبة إعلانات 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.prompt": "كل صباح في الساعة 10:00، أعطني 5 مطالبات مختارة لـ Midjourney أو Stable Diffusion أو Flux، مرتبة حسب الأسلوب. يجب أن تكون كل مطالبة جاهزة للنسخ والتجربة.",
|
||||
"aigc-prompt-inspiration.title": "إلهام مطالبات AIGC",
|
||||
"arxiv-curated-daily.description": "كل صباح، 5 أوراق جديدة من arXiv في مجال بحثك مع ملخصات من سطر واحد.",
|
||||
"arxiv-curated-daily.prompt": "كل صباح في الساعة 09:00، اختر 5 من أحدث أوراق arXiv في مجال بحثي وقدم لي ملخصًا من سطر واحد لكل منها، حتى أتمكن من تحديد أيها أقرأ بعمق.",
|
||||
"arxiv-curated-daily.title": "اختيارات arXiv اليومية",
|
||||
"bedtime-gratitude.description": "كل ليلة في الساعة 22، اطلب 3 أشياء تشعر بالامتنان لها وشيئًا تعلمته اليوم.",
|
||||
"bedtime-gratitude.prompt": "كل مساء في الساعة 22:00، اطلب مني مشاركة 3 أشياء أشعر بالامتنان لها اليوم وشيئًا تعلمته. قدم انعكاسًا لطيفًا من فقرة واحدة. إذا كان Notion متصلًا، أضف الإدخال إلى صفحة يومياتي.",
|
||||
"bedtime-gratitude.title": "امتنان وقت النوم",
|
||||
"brand-collab-weekly.description": "كل يوم اثنين، قم بمسح العلامات التجارية التي تبحث عن منشئي محتوى — طابق حسب التخصص وحجم الجمهور.",
|
||||
"brand-collab-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بمسح العلامات التجارية والنداءات العامة التي تبحث بنشاط عن منشئي محتوى. طابق مع تخصصي وحجم جمهوري. أبرز 5 تستحق التقديم.",
|
||||
"brand-collab-weekly.title": "تعاون العلامات التجارية الأسبوعي",
|
||||
"brand-mention-daily.description": "أخبرني بالعلامات التجارية / الكلمات الرئيسية التي يجب تتبعها — كل مساء، حجم الإشارات، المشاعر، الأصوات البارزة.",
|
||||
"brand-mention-daily.prompt": "كل مساء في الساعة 18:00، لخص إشارات اليوم للعلامات التجارية والكلمات الرئيسية التي أتابعها على X (تويتر): الحجم، المشاعر، الأصوات البارزة. أبلغ عن أي ارتفاعات غير عادية.",
|
||||
"brand-mention-daily.title": "إشارات العلامة التجارية اليومية",
|
||||
"brand-watch-weekly.description": "كل يوم اثنين، تتبع 10 تحديثات للعلامات التجارية الكبرى — تحديث الشعار، الهوية، إعادة تصميم المواقع — مع تحليل.",
|
||||
"brand-watch-weekly.prompt": "كل يوم اثنين في الساعة 10:00، تتبع 10 تحديثات للعلامات التجارية من الشركات التي أتابعها: تحديثات الشعار، تغييرات الهوية، إعادة تصميم المواقع. أضف تحليلًا من فقرة واحدة لكل منها.",
|
||||
"brand-watch-weekly.title": "مراقبة العلامات التجارية الأسبوعية",
|
||||
"calendar-conflict-check.description": "كل صباح، افحص اليوم بحثًا عن تعارضات، اجتماعات متتالية، وقت سفر غير كافٍ.",
|
||||
"calendar-conflict-check.prompt": "كل صباح في الساعة 07:30، افحص تقويم اليوم بحثًا عن تعارضات، اجتماعات متتالية، أو وقت سفر / احتياطي غير كافٍ. اقترح حلولًا.",
|
||||
"calendar-conflict-check.title": "فحص تعارض التقويم",
|
||||
"cashflow-weekly.description": "كل يوم اثنين، ما الذي سيدخل هذا الأسبوع، ما الذي سيخرج، النفقات الكبيرة الأسبوع المقبل.",
|
||||
"cashflow-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع التدفق النقدي: المستحقات المستحقة هذا الأسبوع، المدفوعات المستحقة، والنفقات الكبيرة المجدولة للأسبوع المقبل.",
|
||||
"cashflow-weekly.title": "التدفق النقدي الأسبوعي",
|
||||
"child-growth-weekly.description": "أخبرني بعمر طفلك — كل يوم اثنين، تركيز التطوير لهذا الأسبوع + أفكار الأنشطة.",
|
||||
"child-growth-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني مجالات التركيز التنموي المناسبة لعمر طفلي هذا الأسبوع، بالإضافة إلى أفكار الأنشطة بين الوالدين والطفل والأشياء التي يجب مراقبتها.",
|
||||
"child-growth-weekly.title": "نمو الطفل الأسبوعي",
|
||||
"child-study-weekly.description": "أخبرني بما يدرسه طفلك — كل يوم أحد، تقدم هذا الأسبوع + تركيز الأسبوع المقبل.",
|
||||
"child-study-weekly.prompt": "كل يوم أحد في الساعة 20:00، لخص تقدم دراسة طفلي هذا الأسبوع وحدد مجالات التركيز للأسبوع المقبل. اقترح أنشطة تدريبية لكل موضوع.",
|
||||
"child-study-weekly.title": "دراسة الطفل الأسبوعية",
|
||||
"competitor-creator-tracking.description": "أخبرني بـ 3-5 منشئين لمتابعتهم — كل صباح أتابع ما أنجزوه وما نجح.",
|
||||
"competitor-creator-tracking.prompt": "كل صباح في الساعة 09:00، تابع 3-5 منشئين أتابعهم كمنافسين: ما الذي نشروه، كيف كان أداؤه، وأفكار يمكنني تكييفها.",
|
||||
"competitor-creator-tracking.title": "تتبع منشئي المحتوى المنافسين",
|
||||
"competitor-radar-daily.description": "أخبرني بـ 3-5 منافسين — كل يوم أتابع تحديثات المواقع، الإطلاقات، إشارات التوظيف، النشاط الاجتماعي.",
|
||||
"competitor-radar-daily.prompt": "كل صباح في الساعة 09:00، تابع 3-5 من منافسيّ: تغييرات المواقع، إطلاق المنتجات، إشارات التوظيف، النشاط الاجتماعي. أبرز ما يشير إلى تحركات استراتيجية.",
|
||||
"competitor-radar-daily.title": "رادار المنافسين",
|
||||
"competitor-update-daily.description": "أخبرني بـ 3-5 منافسين — كل يوم أتحقق من سجلات التغيير، الميزات الجديدة وتغييرات المواقع.",
|
||||
"competitor-update-daily.prompt": "كل صباح في الساعة 10:00، راقب 3-5 منتجات منافسة: سجلات التغيير، الميزات الجديدة، تغييرات نصوص المواقع. أبلغ عن أي إشارة تستحق نظرة أعمق.",
|
||||
"competitor-update-daily.title": "تحديثات منتجات المنافسين",
|
||||
"content-calendar-weekly.description": "كل ليلة أحد، خطط جدول النشر للأسبوع القادم المكون من 7 أيام بما يتماشى مع الأعياد واللحظات الرائجة.",
|
||||
"content-calendar-weekly.prompt": "كل يوم أحد في الساعة 20:00، خطط جدول النشر للأسبوع القادم المكون من 7 أيام: قم بمواءمة الفتحات مع الأعياد القادمة واللحظات الرائجة، واقترح زاوية واحدة لكل فتحة. إذا كان Notion متصلًا، قم بصياغة الجدول هناك.",
|
||||
"content-calendar-weekly.title": "جدول المحتوى الأسبوعي",
|
||||
"contract-expiry-weekly.description": "كل يوم اثنين، العقود التي تنتهي الشهر المقبل (الاشتراكات، الإيجارات، الشراكات).",
|
||||
"contract-expiry-weekly.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج العقود (الاشتراكات، الإيجارات، الشراكات) التي تنتهي في الـ 30 يومًا القادمة. حدد أيها يجب تجديده وأيها يجب إلغاؤه.",
|
||||
"contract-expiry-weekly.title": "انتهاء العقود الأسبوعي",
|
||||
"core-metric-daily.description": "أخبرني بالمقاييس التي يجب مراقبتها (DAU، الاحتفاظ، التحويل) — كل صباح أقوم بمزامنة التغيرات.",
|
||||
"core-metric-daily.prompt": "كل صباح في الساعة 09:00، قم بمزامنة التغيرات في مقاييسي الأساسية (DAU، الاحتفاظ، التحويل). قارن مع الأمس ومتوسط الـ 7 أيام.",
|
||||
"core-metric-daily.title": "المقاييس الأساسية اليومية",
|
||||
"cross-platform-engagement-daily.description": "كل صباح، التعليقات، الرسائل المباشرة، الإشارات والمتابعين الجدد عبر جميع المنصات — 30 ثانية.",
|
||||
"cross-platform-engagement-daily.prompt": "كل صباح في الساعة 09:00، قم بتجميع التعليقات، الرسائل المباشرة، الإشارات، والمتابعين الجدد عبر منصاتي. أبرز الـ 5 التي تستحق الرد.",
|
||||
"cross-platform-engagement-daily.title": "التفاعل عبر المنصات",
|
||||
"crypto-market-daily.description": "كل صباح، تغيرات 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.prompt": "كل صباح في الساعة 09:00، قم بتنسيق 10 أعمال تصميم من Dribbble، Behance، Awwwards، وPinterest التي تتناسب مع أسلوبي، مع ملاحظة قصيرة حول ما يميز كل منها.",
|
||||
"daily-design-inspiration.title": "إلهام التصميم اليومي",
|
||||
"daily-followup-list.description": "كل صباح، قائمة ذات أولوية للعملاء الذين يجب متابعتهم اليوم، مع سياق آخر تواصل.",
|
||||
"daily-followup-list.prompt": "كل صباح في الساعة 09:00، قم ببناء قائمة متابعة ذات أولوية لليوم من جهات الاتصال الخاصة بي في HubSpot. لكل منها، لخص آخر تفاعل.",
|
||||
"daily-followup-list.title": "قائمة المتابعة اليومية",
|
||||
"daily-learning-bite.description": "كل صباح، قدم قطعة واحدة مدتها 15 دقيقة (مقال، فيديو، أو بودكاست) في مجال تعلمك.",
|
||||
"daily-learning-bite.prompt": "كل صباح في الساعة 07:30، أحضر لي قطعة واحدة مدتها 15 دقيقة (مقال، فيديو، أو بودكاست) في مجال تعلمي، مع خلاصة سريعة.",
|
||||
"daily-learning-bite.title": "لقمة التعلم اليومية",
|
||||
"daily-topic-pick.description": "كل صباح، قم بمسح أفضل 10 قطع أداءً في مجالك أمس وقم بتحليل الزوايا.",
|
||||
"daily-topic-pick.prompt": "كل صباح في الساعة 09:00، اجمع أفضل 10 قطع محتوى أداءً من مجالي أمس، قم بتحليل زواياها، واختر 1-2 يمكنني نشرها اليوم.",
|
||||
"daily-topic-pick.title": "رادار الموضوعات اليومية",
|
||||
"deal-pipeline-weekly.description": "كل يوم جمعة، كل صفقة في خط الأنابيب: المتحركة، المتوقفة، المتوقع إغلاقها هذا الشهر.",
|
||||
"deal-pipeline-weekly.prompt": "كل يوم جمعة في الساعة 16:00، راجع كل صفقة في خط الأنابيب الخاص بي في HubSpot: ما الذي تحرك هذا الأسبوع، ما الذي توقف، والإغلاق المتوقع بحلول نهاية الشهر.",
|
||||
"deal-pipeline-weekly.title": "خط الأنابيب الأسبوعي للصفقات",
|
||||
"dependency-security-weekly.description": "كل يوم اثنين، قم بمسح مشاريعك بحثًا عن الثغرات والحزم القديمة مع أولوية الترقية.",
|
||||
"dependency-security-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بمسح مشاريعي على GitHub بحثًا عن التبعيات الضعيفة والقديمة. اقترح أولوية الترقية بناءً على الخطورة ومخاطر التغييرات الجذرية.",
|
||||
"dependency-security-weekly.title": "فحص أمان التبعيات",
|
||||
"design-trend-weekly.description": "كل يوم اثنين، 3 اتجاهات في واجهات المستخدم / العلامات التجارية / الرسوم التوضيحية مع 5 أمثلة تمثيلية.",
|
||||
"design-trend-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني 3 اتجاهات ناشئة عبر واجهات المستخدم، العلامات التجارية، والرسوم التوضيحية هذا الأسبوع، مع 5 أمثلة تمثيلية. ساعدني على البقاء على اطلاع.",
|
||||
"design-trend-weekly.title": "اتجاهات التصميم الأسبوعية",
|
||||
"diet-log-companion.description": "كل مساء، استعرض ما أكلته اليوم — اقتراحات لطيفة، بدون حكم.",
|
||||
"diet-log-companion.prompt": "كل مساء في الساعة 21:00، استعرض معي ما أكلته اليوم وقدم اقتراحًا أو اثنين لطيفين وغير حكميين للغد.",
|
||||
"diet-log-companion.title": "رفيق تسجيل النظام الغذائي",
|
||||
"exhibition-event-weekly.description": "أخبرني بمدينتك — كل يوم اثنين، معارض هذا الأسبوع، العروض، والعروض الحية.",
|
||||
"exhibition-event-weekly.prompt": "كل يوم اثنين في الساعة 10:00، قم بإدراج معارض هذا الأسبوع، العروض، والعروض الحية في مدينتي. أضف سياقًا سريعًا للأكثر إثارة.",
|
||||
"exhibition-event-weekly.title": "المعارض والفعاليات",
|
||||
"family-finance-weekly.description": "كل ليلة أحد، تحليل الإنفاق لهذا الأسبوع، إكمال الميزانية، النفقات الكبيرة للأسبوع المقبل.",
|
||||
"family-finance-weekly.prompt": "كل يوم أحد في الساعة 20:00، راجع إنفاق الأسرة لهذا الأسبوع: تحليل الفئات من سجل Google Sheets الخاص بي، إكمال الميزانية، والنفقات الكبيرة المخطط لها الأسبوع المقبل.",
|
||||
"family-finance-weekly.title": "المالية الأسرية الأسبوعية",
|
||||
"family-task-schedule.description": "كل صباح يوم اثنين، قم بتقسيم المهام، المهمات، توصيلات المدرسة، والفواتير لهذا الأسبوع عبر الأسرة.",
|
||||
"family-task-schedule.prompt": "كل يوم اثنين في الساعة 08:00، قم بصياغة خطة مهام الأسرة لهذا الأسبوع: الأعمال المنزلية، رحلات البقالة، توصيلات المدرسة، دفع الفواتير. قم بتعيين مالكي المهام والفتحات الزمنية بشكل مبدئي. إذا كان Google Calendar متصلًا، اقترح كتلًا يمكنني إضافتها.",
|
||||
"family-task-schedule.title": "جدول مهام الأسرة",
|
||||
"figma-files-cleanup.description": "كل يوم جمعة، راجع ملفات Figma التي تم تحريرها مؤخرًا — حدد ما يجب أرشفته، وما يجب تسليمه للمطورين.",
|
||||
"figma-files-cleanup.prompt": "كل يوم جمعة في الساعة 17:00، راجع ملفات Figma التي تم تحريرها مؤخرًا. حدد ما يجب أرشفته، وما يحتاج إلى تسليم للهندسة، وما لا يزال بحاجة إلى تحسين.",
|
||||
"figma-files-cleanup.title": "تنظيف ملفات Figma",
|
||||
"follower-growth-weekly.description": "كل يوم اثنين، تغييرات المتابعين عبر المنصات — أين يجب التركيز، وأين يجب الإصلاح.",
|
||||
"follower-growth-weekly.prompt": "كل يوم اثنين في الساعة 10:00، راجع نمو المتابعين عبر X (تويتر) ومنصاتي الأخرى. أبرز أين يجب التركيز وأين ينخفض التفاعل.",
|
||||
"follower-growth-weekly.title": "نمو المتابعين الأسبوعي",
|
||||
"font-color-weekly.description": "كل يوم أربعاء، 3 أزواج خطوط + 3 لوحات ألوان تستحق الحفظ في مكتبة الإلهام الخاصة بك.",
|
||||
"font-color-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، قدم لي 3 أزواج خطوط ملحوظة و3 لوحات ألوان تستحق الحفظ. قم بتضمين مكان ترخيص كل خط.",
|
||||
"font-color-weekly.title": "الخطوط والألوان الأسبوعية",
|
||||
"friday-wrap-list.description": "كل مساء جمعة: ما لم ينتهِ، ما سيتم شحنه يوم الاثنين، وأول شيء للأسبوع المقبل.",
|
||||
"friday-wrap-list.prompt": "كل يوم جمعة في الساعة 16:00، قم بإدراج: ما لم أنتهِ منه هذا الأسبوع، ما يجب شحنه يوم الاثنين، وأول شيء يجب أن أبدأ به الأسبوع المقبل.",
|
||||
"friday-wrap-list.title": "قائمة اختتام الجمعة",
|
||||
"funding-intel-daily.description": "كل صباح، 3-5 إعلانات تمويل في مجالك: من جمع الأموال، التقييم، من قاد.",
|
||||
"funding-intel-daily.prompt": "كل صباح في الساعة 10:00، أعطني 3-5 إعلانات تمويل في مجالي من الـ 24 ساعة الماضية: من جمع الأموال، كم، التقييم إذا تم الكشف عنه، المستثمر الرئيسي.",
|
||||
"funding-intel-daily.title": "معلومات التمويل اليومية",
|
||||
"headline-inspiration.description": "كل صباح، 10 قوالب عناوين متطابقة مع العلامة التجارية مستوحاة من النجاحات الأخيرة.",
|
||||
"headline-inspiration.prompt": "كل صباح في الساعة 10:00، أعطني 10 قوالب عناوين تتطابق مع صوتي، مستوحاة من القطع الفيروسية الأخيرة في مجالي. يجب أن أتمكن من نسخها مباشرة عند الحاجة.",
|
||||
"headline-inspiration.title": "إلهام العناوين",
|
||||
"hot-topic-radar.description": "كل صباح، أبرز 5 موضوعات تزداد سخونة في مجالك — ادخل قبل أن يصبح السوق مشبعًا.",
|
||||
"hot-topic-radar.prompt": "كل صباح في الساعة 10:00، أبرز 5 موضوعات في مجالي تزداد سخونة ولكن لم تصل بعد إلى التشبع، مع ملاحظة من سطر واحد حول سبب أهمية كل منها الآن.",
|
||||
"hot-topic-radar.title": "رادار الموضوعات الساخنة",
|
||||
"hubspot-funnel-daily.description": "كل صباح، تتبع تغييرات قمع 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.prompt": "كل صباح في الساعة 08:00، لخص 5 عناصر أخبار مهمة، جولات تمويل، وتحولات سياسية من مجالي في قراءة مدتها 5 دقائق.",
|
||||
"industry-morning-brief.title": "موجز الصباح الصناعي",
|
||||
"industry-research-weekly.description": "كل يوم اثنين، ديناميكيات السوق، التمويل، اللاعبين الجدد وتحولات تنظيمية في قطاعك.",
|
||||
"industry-research-weekly.prompt": "كل يوم اثنين في الساعة 09:00، لخص الأسبوع الماضي في قطاعي: ديناميكيات السوق، جولات التمويل، الوافدين الجدد، التحولات التنظيمية. قم بتنسيقها كموجز بحث.",
|
||||
"industry-research-weekly.title": "البحث الصناعي الأسبوعي",
|
||||
"invoice-collection-daily.description": "كل صباح، الفواتير المتأخرة، الأيام المتأخرة، من يحتاج إلى بريد متابعة اليوم.",
|
||||
"invoice-collection-daily.prompt": "كل صباح في الساعة 10:00، قم بإدراج الفواتير المتأخرة مع الأيام المتأخرة وجهة الاتصال للمتابعة. قم بصياغة بريد متابعة مهذب لكل منها.",
|
||||
"invoice-collection-daily.title": "تحصيل الفواتير اليومية",
|
||||
"iteration-recap-weekly.description": "كل مساء جمعة، بيانات هذه الدورة: معدل الإكمال، العناصر المتأخرة، الأخطاء الجديدة.",
|
||||
"iteration-recap-weekly.prompt": "كل يوم جمعة في الساعة 17:00، لخص دورة هذا الأسبوع: معدل الإكمال، العناصر المتأخرة، الأخطاء الجديدة المسجلة. قم بتنسيقها جاهزة للإدراج في استعراض يوم الاثنين.",
|
||||
"iteration-recap-weekly.title": "ملخص الدورة الأسبوعية",
|
||||
"key-account-radar.description": "أخبرني بحساباتك الرئيسية — كل يوم أتابع أخبارهم، تمويلهم، تغييراتهم التنفيذية.",
|
||||
"key-account-radar.prompt": "كل صباح في الساعة 09:00، قم بمسح الأخبار عن حساباتي الرئيسية: أخبار الشركة، التمويل، التغييرات التنفيذية. أبرز أي شيء يمكنني استخدامه كمدخل لمحادثة تجديد.",
|
||||
"key-account-radar.title": "رادار الحسابات الرئيسية",
|
||||
"keyword-tech-feed.description": "أخبرني بالكلمات الرئيسية التقنية التي يجب تتبعها — كل يوم أعيد 5 منشورات وخيوط عالية الجودة.",
|
||||
"keyword-tech-feed.prompt": "كل صباح في الساعة 10:00، اجلب 5 منشورات جديدة عالية الجودة، مقالات مدونة، أو أسئلة وأجوبة تتطابق مع الكلمات الرئيسية التقنية التي أتابعها.",
|
||||
"keyword-tech-feed.title": "تغذية الكلمات التقنية",
|
||||
"kol-collab-calendar.description": "كل يوم اثنين، قم بمزامنة التعاونات الجارية مع KOL: من المستحق، من المتأخر، الأداء حتى الآن.",
|
||||
"kol-collab-calendar.prompt": "كل يوم اثنين في الساعة 09:00، راجع التعاونات مع KOL التي أقوم بها: من المستحق النشر، من المتأخر، وأرقام الأداء للمنشورات المكتملة.",
|
||||
"kol-collab-calendar.title": "تقويم التعاون مع KOL",
|
||||
"language-morning-bite.description": "كل صباح، قراءة مدتها 3 دقائق باللغة المستهدفة + 5 بطاقات مفردات. تعلم أثناء تنقلاتك.",
|
||||
"language-morning-bite.prompt": "كل صباح في الساعة 07:30، أعطني قراءة مدتها 3 دقائق بلغتي المستهدفة بالإضافة إلى 5 بطاقات مفردات (الكلمة، التعريف، جملة مثال).",
|
||||
"language-morning-bite.title": "لقمة اللغة الصباحية",
|
||||
"linear-sprint-daily.description": "كل صباح، قم بمزامنة تقدم الدورة: العوائق، العناصر المتأخرة، تركيز اليوم — جاهز قبل الاجتماع.",
|
||||
"linear-sprint-daily.prompt": "كل صباح في الساعة 08:30، قم بمزامنة دورة Linear الخاصة بي: العوائق، العناصر المتأخرة، ما يجب أن أركز عليه اليوم. قم بتنسيقها كموجز مدته 5 دقائق قبل الاجتماع.",
|
||||
"linear-sprint-daily.title": "الدورة اليومية لـ Linear",
|
||||
"macro-economy-weekly.description": "كل صباح يوم اثنين، أسعار الصرف، الفائدة، النفط، الذهب، المؤشرات الرئيسية — السياق قبل المكالمات عبر الحدود.",
|
||||
"macro-economy-weekly.prompt": "كل يوم اثنين في الساعة 08:00، أعطني لقطة اقتصادية شاملة: أسعار الصرف، أسعار الفائدة، النفط، الذهب، الفضة، المؤشرات الرئيسية للأسهم. أضف ملخصًا من فقرة واحدة \"ما الذي تغير\".",
|
||||
"macro-economy-weekly.title": "الاقتصاد الكلي الأسبوعي",
|
||||
"marketing-hot-radar.description": "كل صباح، تتبع 5 موضوعات تسويقية تزداد سخونة في مجالك — أيها يجب ركوبه، وأيها يجب تجنبه.",
|
||||
"marketing-hot-radar.prompt": "كل صباح في الساعة 10:00، تتبع 5 موضوعات تسويقية تزداد سخونة في مجالي، حدد أيها يجب ركوبه وأيها يجب تجنبه، مع سبب من 1-2 جملة.",
|
||||
"marketing-hot-radar.title": "رادار التسويق الساخن",
|
||||
"meeting-brief.description": "كل صباح، قم بإعداد موجز من صفحة واحدة لكل اجتماع اليوم: السياق، الحضور، الملاحظات الأخيرة.",
|
||||
"meeting-brief.prompt": "كل صباح في الساعة 08:30، قم بإنشاء موجز تحضيري من صفحة واحدة لكل اجتماع في تقويمي اليوم: السياق، الحضور، ملاحظات الاجتماع الأخير. اقرأ قبل الدخول.",
|
||||
"meeting-brief.title": "موجز التحضير للاجتماعات",
|
||||
"monetization-opportunity-weekly.description": "كل يوم أربعاء، قنوات تحقيق الدخل الجديدة ودراسات الحالة للمنشئين: الإعلانات، الدورات، العضويات، التجارة.",
|
||||
"monetization-opportunity-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، أبرز قنوات تحقيق الدخل الجديدة ودراسات الحالة ذات الصلة بالمنشئين في مجالي: الرعاية، المحتوى المدفوع، العضويات، التجارة.",
|
||||
"monetization-opportunity-weekly.title": "فرص تحقيق الدخل",
|
||||
"morning-brief.description": "كل يوم في الساعة 8: جدول اليوم، عدد الرسائل الإلكترونية المعلقة، المهام، الطقس. اقرأ في الطريق.",
|
||||
"morning-brief.prompt": "كل صباح في الساعة 08:00، أرسل لي: تقويم اليوم، عدد الرسائل الإلكترونية المعلقة، أهم 3 مهام، والطقس. قم بتنسيقها كقراءة مدتها دقيقة واحدة.",
|
||||
"morning-brief.title": "موجز الصباح",
|
||||
"morning-ritual.description": "كل يوم في الساعة 7: الطقس، جدول اليوم، فكرة اليوم، وتذكير بالحركة — بداية لطيفة.",
|
||||
"morning-ritual.prompt": "كل صباح في الساعة 07:00، أرسل لي طقوس صباحية لطيفة: الطقس، جدول اليوم، فكرة قصيرة لليوم، واقتراح حركة صغيرة. إذا كان Google Calendar متصلًا، قم بربط الجدول هناك.",
|
||||
"morning-ritual.title": "الطقوس الصباحية",
|
||||
"must-read-papers-weekly.description": "كل ليلة أحد، 3 أوراق الأكثر استشهادًا / الأكثر نقاشًا هذا الأسبوع كقائمة قراءة عميقة.",
|
||||
"must-read-papers-weekly.prompt": "كل يوم أحد في الساعة 20:00، اختر 3 أوراق من مجالي البحثي التي كانت الأكثر استشهادًا أو الأكثر نقاشًا هذا الأسبوع. قم بتنسيق قائمة قراءة عميقة يمكنني إنهاؤها خلال عطلة نهاية الأسبوع.",
|
||||
"must-read-papers-weekly.title": "الأوراق التي يجب قراءتها أسبوعيًا",
|
||||
"newsletter-aggregator.description": "كل ليلة أحد، قم بدمج النشرات الإخبارية التي اشتركت فيها في ملخص عطلة نهاية الأسبوع.",
|
||||
"newsletter-aggregator.prompt": "كل يوم أحد في الساعة 20:00، قم بمسح صندوق الوارد الخاص بي في Gmail بحثًا عن النشرات الإخبارية التي تم استلامها هذا الأسبوع ودمجها في ملخص عطلة نهاية الأسبوع مجمعة حسب الموضوع.",
|
||||
"newsletter-aggregator.title": "مجمّع النشرات الإخبارية",
|
||||
"newsletter-perf-weekly.description": "كل يوم اثنين، معدل الفتح، معدل النقر، واتجاهات إلغاء الاشتراك — حدد ما يجب تحسينه.",
|
||||
"newsletter-perf-weekly.prompt": "كل يوم اثنين في الساعة 10:00، راجع معدل فتح النشرة الإخبارية الخاصة بي، معدل النقر، واتجاهات إلغاء الاشتراك من الأسابيع الأربعة الماضية. حدد أي القطاعات تحتاج إلى تحسين.",
|
||||
"newsletter-perf-weekly.title": "أداء النشرة الإخبارية الأسبوعي",
|
||||
"onboarding-buddy-weekly.description": "كل يوم اثنين، الموظفون الجدد خلال 90 يومًا: التقدم، ملاحظات الزملاء، ما يجب التركيز عليه.",
|
||||
"onboarding-buddy-weekly.prompt": "كل يوم اثنين في الساعة 09:00، قم بإنشاء تحديث تقدم لكل موظف جديد لا يزال في أول 90 يومًا: المهام المكتملة، ملاحظات الزملاء، ما يجب التركيز عليه هذا الأسبوع.",
|
||||
"onboarding-buddy-weekly.title": "تأهيل الموظفين الجدد",
|
||||
"oss-intel-daily.description": "كل صباح، 10 تحديثات تقنية: GitHub Trending، مشاريع مفتوحة المصدر من الشركات الكبرى، إصدارات رئيسية.",
|
||||
"oss-intel-daily.prompt": "كل صباح في الساعة 09:00، أعطني 10 تحديثات تقنية: GitHub Trending، إصدارات مفتوحة المصدر البارزة من الشركات الكبرى، والإصدارات الجديدة من المستودعات في تقنيتي.",
|
||||
"oss-intel-daily.title": "معلومات المصادر المفتوحة اليومية",
|
||||
"podcast-new-episodes.description": "أخبرني بالبودكاست الذي اشتركت فيه — كل يوم اثنين، الحلقات الجديدة لهذا الأسبوع + 3 تستحق الاستماع.",
|
||||
"podcast-new-episodes.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج الحلقات الجديدة من البودكاست الذي اشتركت فيه هذا الأسبوع، وقم بتوصية بأفضل 3 تستحق الاستماع إليها أولاً.",
|
||||
"podcast-new-episodes.title": "الحلقات الجديدة للبودكاست",
|
||||
"portfolio-daily.description": "أخبرني بممتلكاتك — كل إغلاق سوق، تغير اليوم، الأخبار الرئيسية، تحديثات شركات الحيازة.",
|
||||
"portfolio-daily.prompt": "كل يوم في الساعة 16:00 (بعد الإغلاق)، أعطني تحديث محفظتي: تغير اليوم لكل مركز، أهم الأخبار التي تؤثر على كل حيازة، وأي إعلانات خاصة بالشركة.",
|
||||
"portfolio-daily.title": "المحفظة اليومية",
|
||||
"prd-review-reminder.description": "كل يوم جمعة، قم بإدراج PRDs المستحقة للمراجعة هذا الأسبوع — لا تترك المستندات عالقة في المسودة.",
|
||||
"prd-review-reminder.prompt": "كل يوم جمعة في الساعة 15:00، راجع PRDs ومستندات القرار في Notion الخاصة بي المستحقة للمراجعة هذا الأسبوع. حدد أي شيء لا يزال عالقًا في المسودة.",
|
||||
"prd-review-reminder.title": "تذكير مراجعة PRD",
|
||||
"pre-market-brief.description": "كل صباح قبل الافتتاح، العناوين الرئيسية للاقتصاد الكلي، الأرباح الرئيسية، الأخبار عن الشركات التي تمتلكها.",
|
||||
"pre-market-brief.prompt": "كل صباح في الساعة 09:00، أعطني موجز ما قبل السوق: العناوين الرئيسية للاقتصاد الكلي، الأرباح الرئيسية التي تم إصدارها اليوم، والأخبار عن الشركات في محفظتي.",
|
||||
"pre-market-brief.title": "موجز ما قبل السوق",
|
||||
"precious-metals-daily.description": "كل إغلاق سوق، أسعار الذهب، الفضة، النحاس والنفط مع تغير اليوم — حدد التحركات الكبيرة.",
|
||||
"precious-metals-daily.prompt": "كل يوم في الساعة 16:00 (بعد الإغلاق)، أعطني أسعار وتغير اليوم لأسعار الذهب، الفضة، النحاس، والنفط. حدد أي تحرك يزيد عن 2%.",
|
||||
"precious-metals-daily.title": "المعادن والطاقة اليومية",
|
||||
"recruit-funnel-daily.description": "كل صباح، المرشحون لكل دور: الطلبات الجديدة، في انتظار المقابلة، في انتظار الملاحظات.",
|
||||
"recruit-funnel-daily.prompt": "كل صباح في الساعة 09:00، لخص قمع التوظيف حسب الدور: الطلبات الجديدة، المرشحون في انتظار المقابلة، المرشحون في انتظار الملاحظات. حدد المقابلين الذين يعيقون العملية.",
|
||||
"recruit-funnel-daily.title": "قمع التوظيف اليومي",
|
||||
"regulation-watch-weekly.description": "أخبرني بمجالات الامتثال الخاصة بك (البيانات، الضرائب، العمل) — كل يوم اثنين ملخص التغييرات مع التأثير.",
|
||||
"regulation-watch-weekly.prompt": "كل يوم اثنين في الساعة 10:00، لخص التغييرات التنظيمية في مجالات الامتثال التي أتابعها (البيانات، الضرائب، العمل) من الأسبوع الماضي. لكل منها، احكم على التأثير علينا.",
|
||||
"regulation-watch-weekly.title": "مراقبة التنظيم الأسبوعية",
|
||||
"renewal-risk-weekly.description": "كل يوم اثنين، حدد تجديدات هذا الشهر — خاصة الحسابات ذات الاستخدام المتراجع.",
|
||||
"renewal-risk-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع عقود HubSpot التي تنتهي هذا الشهر وحدد الحسابات ذات الاستخدام المتراجع. اقترح خطة إنقاذ لكل حساب معرض للخطر.",
|
||||
"renewal-risk-weekly.title": "مخاطر التجديد الأسبوعية",
|
||||
"repo-health-weekly.description": "كل يوم اثنين، راجع مستودعاتك: تراكم القضايا، PRs المتوقفة، فشل CI، تنبيهات التبعيات.",
|
||||
"repo-health-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع مستودعات GitHub التي أديرها: تراكم القضايا، PRs المتوقفة، فشل CI، تنبيهات التبعيات. أبرز ما يحتاج إلى الانتباه هذا الأسبوع.",
|
||||
"repo-health-weekly.title": "صحة المستودعات الأسبوعية",
|
||||
"schedule.daily": "كل يوم في {{time}}",
|
||||
"schedule.weekly": "كل {{weekday}} في {{time}}",
|
||||
"section.title": "جرب هذه المهام المجدولة",
|
||||
"seo-weekly-report.description": "كل يوم اثنين، حركة الترتيب، الكلمات الرئيسية الناشئة، والصفحات التي تستحق التحديث.",
|
||||
"seo-weekly-report.prompt": "كل يوم اثنين في الساعة 09:00، أعطني تقرير SEO الأسبوعي الخفيف: أهم التحركات في الترتيب (صعودًا / هبوطًا)، 5 كلمات رئيسية ناشئة تستحق الاستهداف، و3 صفحات موجودة جاهزة لتحديث المحتوى.",
|
||||
"seo-weekly-report.title": "تقرير SEO الأسبوعي",
|
||||
"series-update-weekly.description": "أخبرني بما تتابعه — كل أسبوع، تحديثات الحلقات / الفصول وملخصات سريعة.",
|
||||
"series-update-weekly.prompt": "كل يوم اثنين في الساعة 09:00، أعطني إشعارات التحديث وملخصًا قصيرًا للعروض، الروايات، أو القصص المصورة التي أتابعها.",
|
||||
"series-update-weekly.title": "تحديثات السلاسل والكتب الأسبوعية",
|
||||
"standup-brief.description": "كل صباح قبل الاجتماع، اسحب موجز تقدم Linear: تركيز اليوم، العوائق، ما تم إنجازه أمس.",
|
||||
"standup-brief.prompt": "كل صباح في الساعة 08:30، اسحب موجز تقدم Linear: تركيز اليوم، العوائق، ما أغلقته أمس. قم بتنسيقها كـ 3 نقاط جاهزة للقراءة بصوت عالٍ في الاجتماع.",
|
||||
"standup-brief.title": "موجز الاجتماع",
|
||||
"sunday-reflection.description": "كل ليلة أحد، استعرض 5 أسئلة: أفضل لحظة، الإحباطات، أهم 3 للأسبوع المقبل.",
|
||||
"sunday-reflection.prompt": "كل يوم أحد في الساعة 21:00، استعرض معي 5 أسئلة للتأمل: أكثر شيء مرضٍ هذا الأسبوع، الأكثر إحباطًا، أهم 3 أولويات للأسبوع المقبل، ما تعلمته، ما يجب أن أتخلى عنه.",
|
||||
"sunday-reflection.title": "تأمل الأحد",
|
||||
"team-status-weekly.description": "كل يوم اثنين، إجازات الفريق، العمل الإضافي، اتجاهات عبء الاجتماعات — تحذير مبكر من الإرهاق.",
|
||||
"team-status-weekly.prompt": "كل يوم اثنين في الساعة 09:00، راجع الأسبوع الماضي للفريق: الإجازات، ساعات العمل الإضافي، عبء الاجتماعات. حدد أي شخص يتجه نحو الإرهاق.",
|
||||
"team-status-weekly.title": "حالة الفريق الأسبوعية",
|
||||
"tech-trend-weekly.description": "كل يوم اثنين، لخص التحركات الرئيسية في الواجهة الأمامية / الخلفية / الذكاء الاصطناعي: الأوراق، الأطر، التمويل.",
|
||||
"tech-trend-weekly.prompt": "كل يوم اثنين في الساعة 08:00، لخص الأسبوع الماضي من تحركات الواجهة الأمامية، الخلفية، والذكاء الاصطناعي: الأوراق البارزة، إصدارات الأطر، جولات التمويل. 10 عناصر مع ملخصات من سطر واحد.",
|
||||
"tech-trend-weekly.title": "اتجاهات التقنية الأسبوعية",
|
||||
"travel-inspiration-weekly.description": "كل يوم أربعاء، أسعار الرحلات إلى المدن المستهدفة، سياسة التأشيرات، أفضل نوافذ السفر.",
|
||||
"travel-inspiration-weekly.prompt": "كل يوم أربعاء في الساعة 10:00، أعطني تغييرات أسعار الرحلات، تحديثات سياسة التأشيرات، وأفضل نوافذ السفر للمدن في قائمة أمنياتي.",
|
||||
"travel-inspiration-weekly.title": "إلهام السفر الأسبوعي",
|
||||
"twitter-weekly-recap.description": "كل يوم اثنين، راجع الأسبوع الماضي على X: أفضل نمو، أسوأ تفاعل، ولماذا.",
|
||||
"twitter-weekly-recap.prompt": "كل يوم اثنين في الساعة 10:00، لخص نشاطي على X (تويتر) من الأيام السبعة الماضية: التغريدات الأكثر نموًا، التغريدات الأقل تفاعلًا، وفرضية لكل منها. اقترح 3 زوايا لتجربتها هذا الأسبوع.",
|
||||
"twitter-weekly-recap.title": "ملخص X (تويتر) الأسبوعي",
|
||||
"user-feedback-daily.description": "كل صباح، قم بتجميع التعليقات من جميع القنوات (المتاجر، الاجتماعية، الدعم) إلى أهم 20 عنصرًا، مرتبة حسب المشاعر والموضوع.",
|
||||
"user-feedback-daily.prompt": "كل صباح في الساعة 09:00، قم بتجميع تعليقات المستخدمين من جميع القنوات (متاجر التطبيقات، وسائل التواصل الاجتماعي، دعم العملاء) إلى أهم 20 عنصرًا، مرتبة حسب المشاعر والموضوع.",
|
||||
"user-feedback-daily.title": "تعليقات المستخدمين اليومية",
|
||||
"user-interview-schedule.description": "كل يوم اثنين، استعرض مقابلات هذا الأسبوع: من، متى، هل الأسئلة جاهزة.",
|
||||
"user-interview-schedule.prompt": "كل يوم اثنين في الساعة 09:00، قم بإدراج مقابلات المستخدمين المجدولة لهذا الأسبوع: اسم المشارك، الوقت، قائمة التحقق التحضيرية (الأسئلة جاهزة، الإعداد للتسجيل).",
|
||||
"user-interview-schedule.title": "تحضير مقابلات المستخدمين",
|
||||
"vercel-health-weekly.description": "كل يوم اثنين، راجع الأسبوع الماضي من النشر: معدل النجاح، مدة البناء، الشذوذ في حركة المرور.",
|
||||
"vercel-health-weekly.prompt": "كل يوم اثنين في الساعة 10:00، لخص عمليات النشر الخاصة بي على Vercel من الأسبوع الماضي: معدل النجاح، مدة البناء، الشذوذ في حركة المرور. حدد المشكلات المتراكمة.",
|
||||
"vercel-health-weekly.title": "صحة Vercel الأسبوعية",
|
||||
"viral-content-breakdown.description": "كل صباح، قم بتحليل قطعة واحدة فيروسية في مجالك — الزاوية، الخطاف، البنية، النهاية.",
|
||||
"viral-content-breakdown.prompt": "كل صباح في الساعة 10:00، اختر قطعة واحدة من المحتوى الفيروسي من مجالي وقم بتحليلها: الزاوية، الخطاف الافتتاحي، البنية، النهاية. أعطني قالبًا يمكنني تطبيقه.",
|
||||
"viral-content-breakdown.title": "تحليل المحتوى الفيروسي",
|
||||
"watchlist-friday.description": "كل يوم جمعة، 5 إصدارات جديدة ذات تصنيف عالٍ هذا الأسبوع (Douban / IMDb) مع مراجعات من سطر واحد.",
|
||||
"watchlist-friday.prompt": "كل يوم جمعة في الساعة 18:00، اختر 5 إصدارات جديدة ذات تصنيف عالٍ من الأفلام / المسلسلات هذا الأسبوع من Douban وIMDb. أضف مراجعة من سطر واحد لكل منها.",
|
||||
"watchlist-friday.title": "قائمة المشاهدة الجمعة",
|
||||
"weekly-meeting-brief.description": "كل يوم اثنين، قم بإعداد 3 نقاط نقاش لاجتماع الاستراتيجية الأسبوعي: الاتجاهات، الداخلية، القرارات.",
|
||||
"weekly-meeting-brief.prompt": "كل يوم اثنين في الساعة 08:30، قم بإعداد 3 نقاط نقاش لاجتماع الاستراتيجية لهذا الأسبوع: اتجاهات الصناعة، المقاييس الداخلية التي تستحق الإشارة، والقرارات التي يجب اتخاذها.",
|
||||
"weekly-meeting-brief.title": "موجز الاجتماع الأسبوعي",
|
||||
"youtube-channel-weekly.description": "كل يوم اثنين، إحصائيات القناة: المشتركين، أفضل الفيديوهات، الاحتفاظ بالجمهور، الإيرادات.",
|
||||
"youtube-channel-weekly.prompt": "كل يوم اثنين في الساعة 09:00، اسحب إحصائيات قناتي على YouTube: تغير المشتركين، أفضل الفيديوهات أداءً، الاحتفاظ بالجمهور، حركة الإيرادات.",
|
||||
"youtube-channel-weekly.title": "قناة YouTube الأسبوعية",
|
||||
"youtube-weekly-recap.description": "كل يوم اثنين، اسحب أداء القناة الأسبوع الماضي — المشاهدات، معدل النقر، الاحتفاظ — وحدد موضوعات المتابعة.",
|
||||
"youtube-weekly-recap.prompt": "كل يوم اثنين في الساعة 09:00، اسحب أداء قناتي على YouTube للأيام السبعة الماضية: المشاهدات، معدل النقر، منحنيات الاحتفاظ. أبرز الفيديوهات التي تستحق متابعة.",
|
||||
"youtube-weekly-recap.title": "ملخص YouTube الأسبوعي",
|
||||
"zendesk-ticket-daily.description": "كل صباح، لقطة Zendesk: حجم التراكم، انتهاكات SLA، أهم المشكلات المتكررة.",
|
||||
"zendesk-ticket-daily.prompt": "كل صباح في الساعة 09:00، أعطني لقطة Zendesk: تراكم التذاكر المفتوحة، انتهاكات SLA، وأهم 3 مشكلات متكررة من الـ 24 ساعة الماضية.",
|
||||
"zendesk-ticket-daily.title": "تذاكر Zendesk اليومية"
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
"renameModal.title": "إعادة تسمية الموضوع",
|
||||
"searchPlaceholder": "ابحث في المواضيع...",
|
||||
"searchResultEmpty": "لم يتم العثور على نتائج.",
|
||||
"taskManager.agent": "وكيل المهام",
|
||||
"taskManager.welcome": "اسألني عن مهامك",
|
||||
"temp": "مؤقت",
|
||||
"title": "الموضوع"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"channel.allowFrom": "Разрешени потребители",
|
||||
"channel.allowFromAdd": "Добавяне на потребител",
|
||||
"channel.allowFromEmpty": "Все още няма добавени потребители — всеки може да взаимодейства с бота.",
|
||||
"channel.allowFromHint": "Само изброените потребители могат да взаимодействат с бота; вашият „Platform User ID“ се добавя автоматично.",
|
||||
"channel.allowFromIdLabel": "Потребителски ID",
|
||||
"channel.allowFromIdPlaceholder": "Платформен потребителски ID",
|
||||
"channel.allowFromNameLabel": "Бележка",
|
||||
"channel.allowFromNamePlaceholder": "напр. Alice (вашата бележка)",
|
||||
"channel.allowListRemove": "Премахване",
|
||||
"channel.appSecret": "Секрет на приложението",
|
||||
"channel.appSecretHint": "Тайният ключ на вашето бот приложение. Той ще бъде криптиран и съхранен сигурно.",
|
||||
"channel.appSecretPlaceholder": "Поставете вашия секрет на приложението тук",
|
||||
@@ -14,8 +23,10 @@
|
||||
"channel.charLimitHint": "Максимален брой символи на съобщение",
|
||||
"channel.concurrency": "Режим на едновременност",
|
||||
"channel.concurrencyDebounce": "Забавяне",
|
||||
"channel.concurrencyDebounceHint": "Обработва само последното съобщение от серия (по-ранните се игнорират)",
|
||||
"channel.concurrencyHint": "Опашка обработва съобщенията едно по едно; Забавяне изчаква завършването на серия от съобщения преди обработка",
|
||||
"channel.concurrencyQueue": "Опашка",
|
||||
"channel.concurrencyQueueHint": "Обработва съобщения едно по едно",
|
||||
"channel.connectFailed": "Свързването на бота не успя",
|
||||
"channel.connectQueued": "Свързването на бота е в опашката. Ще започне скоро.",
|
||||
"channel.connectStarting": "Ботът стартира. Моля, изчакайте момент.",
|
||||
@@ -25,7 +36,9 @@
|
||||
"channel.connectionMode": "Режим на свързване",
|
||||
"channel.connectionModeHint": "WebSocket се препоръчва за нови ботове. Използвайте Webhook, ако вашият бот вече има конфигуриран callback URL в платформата QQ Open Platform.",
|
||||
"channel.connectionModeWebSocket": "WebSocket",
|
||||
"channel.connectionModeWebSocketHint": "Препоръчително за нови ботове",
|
||||
"channel.connectionModeWebhook": "Webhook",
|
||||
"channel.connectionModeWebhookHint": "Използвайте, ако вашият бот има конфигуриран callback URL",
|
||||
"channel.copied": "Копирано в клипборда",
|
||||
"channel.copy": "Копирай",
|
||||
"channel.credentials": "Удостоверения",
|
||||
@@ -45,13 +58,16 @@
|
||||
"channel.displayToolCalls": "Показване на обажданията към инструментите",
|
||||
"channel.displayToolCallsHint": "Показване на детайли за обажданията към инструментите по време на отговорите на ИИ. Когато е изключено, се показва само крайният отговор за по-чисто изживяване.",
|
||||
"channel.dm": "Директни съобщения",
|
||||
"channel.dmEnabled": "Активиране на директни съобщения",
|
||||
"channel.dmEnabledHint": "Позволете на бота да получава и отговаря на директни съобщения",
|
||||
"channel.dmPolicy": "Политика за директни съобщения",
|
||||
"channel.dmPolicyAllowlist": "Списък с позволени",
|
||||
"channel.dmPolicyAllowlistHint": "Само изброените потребители могат да изпращат директни съобщения на бота",
|
||||
"channel.dmPolicyDisabled": "Деактивирано",
|
||||
"channel.dmPolicyDisabledHint": "Отхвърля всички директни съобщения",
|
||||
"channel.dmPolicyHint": "Контролирайте кой може да изпраща директни съобщения до бота",
|
||||
"channel.dmPolicyOpen": "Отворено",
|
||||
"channel.dmPolicyOpenHint": "Приема директни съобщения от всеки",
|
||||
"channel.dmPolicyPairing": "Сдвояване",
|
||||
"channel.dmPolicyPairingHint": "Непознатите трябва да използват /approve, за да изпратят директно съобщение",
|
||||
"channel.documentation": "Документация",
|
||||
"channel.enabled": "Активиран",
|
||||
"channel.encryptKey": "Ключ за криптиране",
|
||||
@@ -63,6 +79,22 @@
|
||||
"channel.feishu.description": "Свържете този асистент с Feishu за лични и групови чатове.",
|
||||
"channel.feishu.webhookMigrationDesc": "Режимът WebSocket осигурява доставяне на събития в реално време без необходимост от публичен callback URL. За миграция превключете режима на свързване към WebSocket в Разширени настройки. Не е необходима допълнителна конфигурация в Feishu/Lark Open Platform.",
|
||||
"channel.feishu.webhookMigrationTitle": "Обмислете миграция към режим WebSocket",
|
||||
"channel.groupAllowFrom": "Разрешени канали",
|
||||
"channel.groupAllowFromAdd": "Добавяне на канал",
|
||||
"channel.groupAllowFromEmpty": "Все още няма добавени канали — ботът няма да отговаря никъде.",
|
||||
"channel.groupAllowFromHint": "ID на канали / групи / чатове, в които ботът може да отговаря.",
|
||||
"channel.groupAllowFromIdLabel": "ID на канал",
|
||||
"channel.groupAllowFromIdPlaceholder": "ID на канал / група / чат",
|
||||
"channel.groupAllowFromNameLabel": "Бележка",
|
||||
"channel.groupAllowFromNamePlaceholder": "напр. #general (вашата бележка)",
|
||||
"channel.groupPolicy": "Групова политика",
|
||||
"channel.groupPolicyAllowlist": "Списък с разрешени",
|
||||
"channel.groupPolicyAllowlistHint": "Отговаря само в изброените канали",
|
||||
"channel.groupPolicyDisabled": "Деактивирано",
|
||||
"channel.groupPolicyDisabledHint": "Игнорира всички групови съобщения",
|
||||
"channel.groupPolicyHint": "Къде ботът отговаря в групи, канали и нишки",
|
||||
"channel.groupPolicyOpen": "Отворено",
|
||||
"channel.groupPolicyOpenHint": "Отговаря във всяка група, канал или нишка",
|
||||
"channel.historyLimit": "Лимит на съобщенията в историята",
|
||||
"channel.historyLimitHint": "По подразбиране брой съобщения за извличане при четене на историята на канала",
|
||||
"channel.importConfig": "Импортиране на конфигурация",
|
||||
@@ -70,6 +102,19 @@
|
||||
"channel.importInvalidFormat": "Невалиден формат на конфигурационния файл",
|
||||
"channel.importSuccess": "Конфигурацията е успешно импортирана",
|
||||
"channel.lark.description": "Свържете този асистент с Lark за лични и групови чатове.",
|
||||
"channel.line.channelAccessToken": "Токен за достъп до канал",
|
||||
"channel.line.channelAccessTokenHint": "Дългосрочен токен, издаден в раздела Messaging API. Токенът ще бъде криптиран и съхраняван сигурно.",
|
||||
"channel.line.channelSecret": "Секрет на канала",
|
||||
"channel.line.channelSecretHint": "От раздела Основни настройки. Задължително — използва се за проверка на X-Line-Signature за всяко входящо уебхук съобщение.",
|
||||
"channel.line.description": "Свържете този асистент с LINE Messaging API за директни и групови чатове.",
|
||||
"channel.line.destinationUserId": "Потребителски ID на дестинацията",
|
||||
"channel.line.destinationUserIdHint": "Потребителското ID на бота (започва с `U`, общо 33 символа). Конзолата за разработчици на LINE НЕ показва тази стойност. Първо издайте токен за достъп до канал по-долу, след това кликнете \"Извличане от LINE\", за да попълните автоматично това поле. Забележка: \"Вашето потребителско ID\" в Основни настройки е вашето лично LINE потребителско ID, а не това на бота.",
|
||||
"channel.line.destinationUserIdPlaceholder": "напр. U1234567890abcdef1234567890abcdef",
|
||||
"channel.line.fetchBotInfo": "Извличане от LINE",
|
||||
"channel.line.fetchBotInfoFailed": "Неуспешно извличане на информация за бота",
|
||||
"channel.line.fetchBotInfoMissingToken": "Първо въведете токена за достъп до канала, след това кликнете \"Извличане от LINE\".",
|
||||
"channel.line.fetchBotInfoSuccess": "Потребителското ID на дестинацията е извлечено",
|
||||
"channel.line.webhookManualSetup": "LINE не позволява програмно регистриране на уебхукове. Копирайте този URL в Конзолата за разработчици на LINE (Messaging API → Webhook URL), кликнете \"Проверка\" и активирайте \"Използване на уебхук\".",
|
||||
"channel.openPlatform": "Отворена платформа",
|
||||
"channel.platforms": "Платформи",
|
||||
"channel.publicKey": "Публичен ключ",
|
||||
@@ -93,6 +138,8 @@
|
||||
"channel.secretTokenPlaceholder": "По избор секрет за проверка на уебхук",
|
||||
"channel.serverId": "ID на сървъра / гилдията по подразбиране",
|
||||
"channel.serverIdHint": "Вашият ID на сървъра или гилдията по подразбиране на тази платформа. AI го използва, за да изброи каналите без да пита.",
|
||||
"channel.serverIdHint.discord": "Включете Developer Mode (Settings → Advanced), след това кликнете с десен бутон върху иконата на сървъра → Copy Server ID.",
|
||||
"channel.serverIdHint.slack": "Workspace ID (започва с T). Намерете го в Settings & administration → Workspace settings или в URL адреса на работното пространство.",
|
||||
"channel.settings": "Разширени настройки",
|
||||
"channel.settingsResetConfirm": "Сигурни ли сте, че искате да върнете разширените настройки към техните стойности по подразбиране?",
|
||||
"channel.settingsResetDefault": "Връщане към стойности по подразбиране",
|
||||
@@ -120,6 +167,13 @@
|
||||
"channel.updateFailed": "Неуспешно актуализиране на статуса",
|
||||
"channel.userId": "Вашият потребителски ID на платформата",
|
||||
"channel.userIdHint": "Вашият потребителски ID на тази платформа. AI може да го използва, за да ви изпраща директни съобщения.",
|
||||
"channel.userIdHint.discord": "Включете Developer Mode (Settings → Advanced), след това кликнете с десен бутон върху своя аватар → Copy User ID.",
|
||||
"channel.userIdHint.feishu": "Отворете вашето приложение в Feishu / Lark Open Platform → Permissions и намерете своя Open ID.",
|
||||
"channel.userIdHint.qq": "Вашият QQ номер, показан на страницата на вашия QQ профил.",
|
||||
"channel.userIdHint.slack": "Отворете своя Slack профил → ⋮ More → Copy member ID (започва с U).",
|
||||
"channel.userIdHint.telegram": "Изпратете произволно съобщение на @userinfobot в Telegram — той ще ви отговори с вашия числов User ID.",
|
||||
"channel.userIdMissingDesc": "Без него AI инструментите не могат да ви изпращат напомняния, а заявките за сдвояване ще се провалят. Попълнете го в Разширени настройки.",
|
||||
"channel.userIdMissingTitle": "Добавете вашия платформен User ID",
|
||||
"channel.validationError": "Моля, попълнете ID на приложението и токен",
|
||||
"channel.verificationToken": "Токен за проверка",
|
||||
"channel.verificationTokenHint": "По избор. Използва се за проверка на източника на събития за уебхук.",
|
||||
|
||||
+118
-8
@@ -36,7 +36,47 @@
|
||||
"builtinCopilot": "Вграден Копилот",
|
||||
"chatList.expandMessage": "Разгъни съобщението",
|
||||
"chatList.longMessageDetail": "Прегледай подробности",
|
||||
"claudeCodeInstallGuide.actions.openDocs": "Отвори ръководството за инсталиране",
|
||||
"claudeCodeInstallGuide.actions.openSystemTools": "Отвори системните инструменти",
|
||||
"claudeCodeInstallGuide.afterInstall": "След инсталиране стартирайте Claude Code веднъж, за да влезете, след което опитайте отново или натиснете „Повторно откриване“ в Системни инструменти.",
|
||||
"claudeCodeInstallGuide.desc": "Claude Code се нуждае от Claude Code CLI, за да работи локално. Инсталирайте го и се уверете, че командата `claude` е достъпна във вашия PATH.",
|
||||
"claudeCodeInstallGuide.installWithBrew": "Homebrew",
|
||||
"claudeCodeInstallGuide.installWithNpm": "Препоръчителна инсталация",
|
||||
"claudeCodeInstallGuide.menuNotification.title": "Claude Code CLI не е намерен",
|
||||
"claudeCodeInstallGuide.reason": "LobeHub не можа да стартира Claude Code: {{message}}",
|
||||
"claudeCodeInstallGuide.title": "Инсталиране на Claude Code CLI",
|
||||
"clearCurrentMessages": "Изчисти съобщенията от текущата сесия",
|
||||
"cliAuthGuide.actions.openDocs": "Отвори ръководството за влизане",
|
||||
"cliAuthGuide.actions.openSystemTools": "Отвори системните инструменти",
|
||||
"cliAuthGuide.afterLogin": "След повторно влизане или обновяване на идентификационните данни опитайте отново. Можете също да извършите повторно откриване в Системни инструменти.",
|
||||
"cliAuthGuide.desc": "{{name}} не можа да продължи, защото сесията за влизане е изтекла или идентификационните данни са невалидни.",
|
||||
"cliAuthGuide.errorDetails": "Подробности за грешката",
|
||||
"cliAuthGuide.runCommand": "Изпълнете това в терминала",
|
||||
"cliAuthGuide.title": "Влезте в {{name}}",
|
||||
"cliRateLimitGuide.actions.openSystemTools": "Отвори системните инструменти",
|
||||
"cliRateLimitGuide.afterReset": "Изчакайте до времето за нулиране, след което опитайте отново. Ако използвате API удостоверяване, проверете квотата и фактурирането при вашия доставчик.",
|
||||
"cliRateLimitGuide.desc": "{{name}} достигна текущия си лимит на употреба и не може да продължи в момента.",
|
||||
"cliRateLimitGuide.limitType": "Период на лимит",
|
||||
"cliRateLimitGuide.limitTypes.weekCycle": "Седмичен цикъл",
|
||||
"cliRateLimitGuide.relative.day_one": "{{count}} ден",
|
||||
"cliRateLimitGuide.relative.day_other": "{{count}} дни",
|
||||
"cliRateLimitGuide.relative.hour_one": "{{count}} час",
|
||||
"cliRateLimitGuide.relative.hour_other": "{{count}} часа",
|
||||
"cliRateLimitGuide.relative.minute_one": "{{count}} минута",
|
||||
"cliRateLimitGuide.relative.minute_other": "{{count}} минути",
|
||||
"cliRateLimitGuide.relative.soon": "Нулира се скоро",
|
||||
"cliRateLimitGuide.resetAt": "Нулира се на",
|
||||
"cliRateLimitGuide.resetInApprox": "Нулира се след около {{duration}}",
|
||||
"cliRateLimitGuide.title": "Достигнат е лимитът за употреба на {{name}}",
|
||||
"codexInstallGuide.actions.openDocs": "Отвори ръководството за инсталиране",
|
||||
"codexInstallGuide.actions.openSystemTools": "Отвори системните инструменти",
|
||||
"codexInstallGuide.afterInstall": "След инсталиране стартирайте Codex веднъж, за да влезете, след което опитайте отново или натиснете „Повторно откриване“ в Системни инструменти.",
|
||||
"codexInstallGuide.desc": "Codex Agent се нуждае от Codex CLI, за да работи локално. Инсталирайте го и се уверете, че командата `codex` е достъпна във вашия PATH.",
|
||||
"codexInstallGuide.installWithBrew": "Homebrew (macOS)",
|
||||
"codexInstallGuide.installWithNpm": "Препоръчителна инсталация",
|
||||
"codexInstallGuide.menuNotification.title": "Codex CLI не е намерен",
|
||||
"codexInstallGuide.reason": "LobeHub не можа да стартира Codex: {{message}}",
|
||||
"codexInstallGuide.title": "Инсталиране на Codex CLI",
|
||||
"compressedHistory": "Сгъната история",
|
||||
"compression.cancel": "Разархивирай",
|
||||
"compression.cancelConfirm": "Сигурни ли сте, че искате да разархивирате? Това ще възстанови оригиналните съобщения.",
|
||||
@@ -65,6 +105,8 @@
|
||||
"defaultSession": "Агент по подразбиране",
|
||||
"desktopNotification.aiReplyCompleted.body": "Отговорът от агента е готов",
|
||||
"desktopNotification.aiReplyCompleted.title": "Отговорът е завършен",
|
||||
"desktopNotification.humanApprovalRequired.body": "Агент има нужда от вашето одобрение, за да продължи",
|
||||
"desktopNotification.humanApprovalRequired.title": "Необходимо е одобрение",
|
||||
"dm.placeholder": "Вашите лични съобщения с {{agentTitle}} ще се появят тук.",
|
||||
"dm.tooltip": "Изпрати лично съобщение",
|
||||
"dm.visibleTo": "Видимо само за {{target}}",
|
||||
@@ -81,7 +123,7 @@
|
||||
"extendParams.effort.title": "Усилие",
|
||||
"extendParams.enableAdaptiveThinking.desc": "Позволете на Claude динамично да решава кога и колко да мисли с режима за адаптивно мислене.",
|
||||
"extendParams.enableAdaptiveThinking.title": "Активирай адаптивно мислене",
|
||||
"extendParams.enableReasoning.desc": "Базирано на ограничението на механизма за мислене на Claude. <1>Научете повече</1>",
|
||||
"extendParams.enableReasoning.desc": "Позволява на модела да разсъждава, преди да отговори. Използвайте за сложни задачи.",
|
||||
"extendParams.enableReasoning.title": "Активирай дълбоко мислене",
|
||||
"extendParams.imageAspectRatio.title": "Съотношение на изображението",
|
||||
"extendParams.imageResolution.title": "Резолюция на изображението",
|
||||
@@ -95,6 +137,7 @@
|
||||
"extendParams.urlContext.desc": "Когато е активирано, уеб връзките ще се анализират автоматично, за да се извлече съдържанието на страницата",
|
||||
"extendParams.urlContext.title": "Извличане на съдържание от уеб връзки",
|
||||
"followUpPlaceholder": "Последващо действие. Използвайте @, за да възлагате задачи на други агенти.",
|
||||
"followUpPlaceholderHeterogeneous": "Последващ въпрос.",
|
||||
"group.desc": "Придвижете задача напред с няколко Агента в едно споделено пространство.",
|
||||
"group.memberTooltip": "Групата има {{count}} член(а)",
|
||||
"group.orchestratorThinking": "Оркестраторът мисли...",
|
||||
@@ -139,6 +182,7 @@
|
||||
"heteroAgent.fullAccess.label": "Пълен достъп",
|
||||
"heteroAgent.fullAccess.tooltip": "Claude Code работи локално с пълен достъп за четене/запис в работната директория. Превключването на режимите на достъп все още не е налично.",
|
||||
"heteroAgent.resumeReset.cwdChanged": "Работната директория е променена. Предишната сесия на Claude Code може да бъде продължена само от оригиналната ѝ директория, затова е започнат нов разговор.",
|
||||
"heteroAgent.resumeReset.resumeFailed": "Запазената нишка на Codex не можа да бъде възстановена безопасно, затова е започнат нов разговор по тази тема.",
|
||||
"heteroAgent.switchCwd.cancel": "Отказ",
|
||||
"heteroAgent.switchCwd.content": "Сесиите на Claude Code са фиксирани към работна директория. Превключването ще започне нова сесия за тази тема — чат съобщенията се запазват, но контекстът на предишната сесия не може да бъде възстановен.",
|
||||
"heteroAgent.switchCwd.ok": "Превключи и започни нова сесия",
|
||||
@@ -161,6 +205,9 @@
|
||||
"input.stop": "Спри",
|
||||
"input.warp": "Нов ред",
|
||||
"input.warpWithKey": "Натиснете <key/>, за да вмъкнете нов ред",
|
||||
"inputQueue.delete": "Изтрий",
|
||||
"inputQueue.edit": "Редактирай",
|
||||
"inputQueue.sendNow": "Изпрати сега (прекъсва текущото изпълнение)",
|
||||
"intentUnderstanding.title": "Разбиране на вашето намерение...",
|
||||
"inviteMembers": "Покани членове",
|
||||
"knowledgeBase.all": "Цялото съдържание",
|
||||
@@ -204,6 +251,8 @@
|
||||
"messageAction.interruptedHint": "Какво трябва да направя вместо това?",
|
||||
"messageAction.reaction": "Добави реакция",
|
||||
"messageAction.regenerate": "Генерирай отново",
|
||||
"messageLongCollapse.collapse": "Покажи по-малко",
|
||||
"messageLongCollapse.expand": "Покажи повече",
|
||||
"messages.dm.sentTo": "Видимо само за {{name}}",
|
||||
"messages.dm.title": "ЛС",
|
||||
"messages.modelCard.credit": "Кредити",
|
||||
@@ -245,6 +294,7 @@
|
||||
"minimap.senderUser": "Вие",
|
||||
"newAgent": "Създай агент",
|
||||
"newClaudeCodeAgent": "Добавяне на Claude Code",
|
||||
"newCodexAgent": "Добави Codex",
|
||||
"newGroupChat": "Създай група",
|
||||
"newPage": "Създай страница",
|
||||
"noAgentsYet": "Тази група все още няма членове. Натиснете бутона +, за да поканите агенти.",
|
||||
@@ -424,7 +474,14 @@
|
||||
"taskDetail.activities.fallback.topic": "започна тема",
|
||||
"taskDetail.activitiesEmpty": "Все още няма дейности",
|
||||
"taskDetail.addSubtask": "Добавяне на подзадача",
|
||||
"taskDetail.artifactMenu.delete": "Премахни от задачата",
|
||||
"taskDetail.artifactMenu.deleteConfirm.content": "Този артефакт повече няма да се появява в работното пространство на задачата.",
|
||||
"taskDetail.artifactMenu.deleteConfirm.ok": "Премахни",
|
||||
"taskDetail.artifactMenu.deleteConfirm.title": "Премахване на този артефакт?",
|
||||
"taskDetail.artifactSize": "{{value}} знака",
|
||||
"taskDetail.artifacts": "Артефакти",
|
||||
"taskDetail.blockedBy": "Блокирано от {{id}}",
|
||||
"taskDetail.cancelSchedule": "Отмени графика",
|
||||
"taskDetail.comment.cancel": "Отказ",
|
||||
"taskDetail.comment.delete": "Изтриване",
|
||||
"taskDetail.comment.deleteConfirm.content": "Този коментар ще бъде изтрит завинаги.",
|
||||
@@ -439,7 +496,6 @@
|
||||
"taskDetail.instruction": "Инструкция",
|
||||
"taskDetail.instructionPlaceholder": "Кликнете, за да редактирате инструкциите за задачата...",
|
||||
"taskDetail.latestActivity.brief": "Кратко: {{title}}",
|
||||
"taskDetail.latestActivity.briefOnly": "Кратко",
|
||||
"taskDetail.latestActivity.briefWithAction": "{{title}} - {{action}}",
|
||||
"taskDetail.latestActivity.briefWithType": "Кратко ({{type}}): {{title}}",
|
||||
"taskDetail.latestActivity.briefWithTypeOnly": "Кратко ({{type}})",
|
||||
@@ -448,6 +504,7 @@
|
||||
"taskDetail.latestActivity.untitledTopic": "Неозаглавена тема",
|
||||
"taskDetail.modelConfig": "Замяна на модела",
|
||||
"taskDetail.navigation": "Навигация",
|
||||
"taskDetail.nextRunCountdown": "Следващо изпълнение след {{countdown}}",
|
||||
"taskDetail.pauseTask": "Пауза на задачата",
|
||||
"taskDetail.priority.high": "Висок",
|
||||
"taskDetail.priority.low": "Нисък",
|
||||
@@ -465,25 +522,50 @@
|
||||
"taskDetail.status.failed": "Неуспешна",
|
||||
"taskDetail.status.paused": "Пауза",
|
||||
"taskDetail.status.running": "В процес",
|
||||
"taskDetail.status.scheduled": "Планирано",
|
||||
"taskDetail.stopTask": "Спиране на задачата",
|
||||
"taskDetail.subIssueOf": "Подзадача към",
|
||||
"taskDetail.subtaskInstructionPlaceholder": "Опишете подзадачата...",
|
||||
"taskDetail.subtasks": "Подзадачи",
|
||||
"taskDetail.titlePlaceholder": "Въведете заглавие на задачата...",
|
||||
"taskDetail.topicDrawer.untitled": "Без заглавие",
|
||||
"taskDetail.untitled": "Без заглавие",
|
||||
"taskDetail.updateFailed": "Неуспешно обновяване на задачата",
|
||||
"taskList.activeTasks": "Активни задачи",
|
||||
"taskList.all": "Всички задачи",
|
||||
"taskList.assigneeSearch.empty": "Няма съвпадащ агент",
|
||||
"taskList.assigneeSearch.placeholder": "Търси агент...",
|
||||
"taskList.breadcrumb.task": "Задача",
|
||||
"taskList.contextMenu.copyId": "Копирай ID",
|
||||
"taskList.contextMenu.copyIdSuccess": "ID копирано",
|
||||
"taskList.contextMenu.copyLink": "Копирай линк",
|
||||
"taskList.contextMenu.copyLinkSuccess": "Линкът е копиран",
|
||||
"taskList.contextMenu.priority": "Приоритет",
|
||||
"taskList.contextMenu.status": "Статус",
|
||||
"taskList.empty": "Все още няма задачи",
|
||||
"taskList.form.grouping": "Групиране",
|
||||
"taskList.form.orderCompletedByRecency": "Подреди завършените задачи по актуалност",
|
||||
"taskList.form.ordering": "Подреждане",
|
||||
"taskList.form.showCompleted": "Покажи завършени и отменени",
|
||||
"taskList.form.subGrouping": "Подгрупиране",
|
||||
"taskList.groupBy.assignee": "Отговорник",
|
||||
"taskList.groupBy.none": "Без групиране",
|
||||
"taskList.groupBy.priority": "Приоритет",
|
||||
"taskList.groupBy.status": "Статус",
|
||||
"taskList.hiddenCompleted.count_one": "{{count}} задача",
|
||||
"taskList.hiddenCompleted.count_other": "{{count}} задачи",
|
||||
"taskList.hiddenCompleted.show": "Покажи",
|
||||
"taskList.hiddenCompleted.suffix": "скрито от настройките за показване",
|
||||
"taskList.kanban.addTask": "Създай задача",
|
||||
"taskList.kanban.backlog": "Изчакване",
|
||||
"taskList.kanban.canceled": "Отменено",
|
||||
"taskList.kanban.done": "Готово",
|
||||
"taskList.kanban.emptyColumn": "Няма задачи",
|
||||
"taskList.kanban.hiddenColumns": "Скрити колони",
|
||||
"taskList.kanban.hideColumn": "Скрий колоната",
|
||||
"taskList.kanban.needsInput": "Изисква преглед",
|
||||
"taskList.kanban.running": "В процес",
|
||||
"taskList.kanban.showColumn": "Покажи колоната",
|
||||
"taskList.orderBy.assignee": "Отговорник",
|
||||
"taskList.orderBy.createdAt": "Дата на създаване",
|
||||
"taskList.orderBy.priority": "Приоритет",
|
||||
@@ -492,24 +574,43 @@
|
||||
"taskList.orderBy.updatedAt": "Дата на обновяване",
|
||||
"taskList.title": "Задачи",
|
||||
"taskList.unassigned": "Без отговорник",
|
||||
"taskList.unassignedHint": "Lobe AI ще изпълнява тази задача, когато няма зададен изпълнител",
|
||||
"taskList.view.board": "Табло",
|
||||
"taskList.view.list": "Списък",
|
||||
"taskList.viewAll": "Преглед на всички",
|
||||
"taskSchedule.advancedSettings": "Разширени настройки",
|
||||
"taskSchedule.clear": "Изчистване",
|
||||
"taskSchedule.continuous": "Непрекъснато",
|
||||
"taskSchedule.enable": "Активиране на автоматизация",
|
||||
"taskSchedule.every": "Всеки",
|
||||
"taskSchedule.frequency": "Честота",
|
||||
"taskSchedule.heading": "Автоматизация",
|
||||
"taskSchedule.hours": "Часа",
|
||||
"taskSchedule.interval": "Повтарящо се",
|
||||
"taskSchedule.intervalLabel": "Интервал на изпълнение",
|
||||
"taskSchedule.intervalSuffix": "всеки път",
|
||||
"taskSchedule.intervalTab": "Повтарящо се",
|
||||
"taskSchedule.maxExecutions": "Макс. изпълнения",
|
||||
"taskSchedule.maxExecutionsPlaceholder": "Неограничено",
|
||||
"taskSchedule.minutes": "Минути",
|
||||
"taskSchedule.nextRun": "Следващо изпълнение",
|
||||
"taskSchedule.nextRun.format": "MMM D HH:mm",
|
||||
"taskSchedule.scheduleType.daily": "Дневно",
|
||||
"taskSchedule.scheduleType.hourly": "На всеки час",
|
||||
"taskSchedule.scheduleType.weekly": "Седмично",
|
||||
"taskSchedule.scheduler": "Планировчик",
|
||||
"taskSchedule.schedulerNotReady": "Планировчикът скоро ще бъде наличен. Засега използвайте Повтарящо се.",
|
||||
"taskSchedule.schedulerTab": "Планировчик",
|
||||
"taskSchedule.seconds": "Секунди",
|
||||
"taskSchedule.summary.daily": "Всеки ден в {{time}}",
|
||||
"taskSchedule.summary.disabled": "Автоматизацията е изключена",
|
||||
"taskSchedule.summary.everyNHours": "На всеки {{count}} часа{{minute}}",
|
||||
"taskSchedule.summary.heartbeat": "Изпълнява се на всеки {{interval}}",
|
||||
"taskSchedule.summary.hourly": "Всеки час{{minute}}",
|
||||
"taskSchedule.summary.weekly": "Всяка седмица в {{days}} от {{time}}",
|
||||
"taskSchedule.tag.add": "Задай график",
|
||||
"taskSchedule.tag.every": "всеки {{interval}}",
|
||||
"taskSchedule.tag.heartbeat": "Ритъм · {{every}}",
|
||||
"taskSchedule.tag.schedule": "График · {{schedule}}{{timezone}}",
|
||||
"taskSchedule.time": "Час",
|
||||
"taskSchedule.timezone": "Часова зона",
|
||||
"taskSchedule.title": "График",
|
||||
"taskSchedule.unit.hour_one": "{{count}} час",
|
||||
"taskSchedule.unit.hour_other": "{{count}} часа",
|
||||
@@ -517,6 +618,14 @@
|
||||
"taskSchedule.unit.minute_other": "{{count}} минути",
|
||||
"taskSchedule.unit.second_one": "{{count}} секунда",
|
||||
"taskSchedule.unit.second_other": "{{count}} секунди",
|
||||
"taskSchedule.weekday": "Ден от седмицата",
|
||||
"taskSchedule.weekdays.fri": "Пет",
|
||||
"taskSchedule.weekdays.mon": "Пон",
|
||||
"taskSchedule.weekdays.sat": "Съб",
|
||||
"taskSchedule.weekdays.sun": "Нед",
|
||||
"taskSchedule.weekdays.thu": "Чет",
|
||||
"taskSchedule.weekdays.tue": "Вто",
|
||||
"taskSchedule.weekdays.wed": "Сря",
|
||||
"thread.closeSubagentThread": "Свиване на разговора със субагента",
|
||||
"thread.divider": "Подтема",
|
||||
"thread.openSubagentThread": "Преглед на пълния разговор със субагента",
|
||||
@@ -605,6 +714,9 @@
|
||||
"viewMode.fullWidth": "Пълна ширина",
|
||||
"viewMode.normal": "Стандартен",
|
||||
"viewMode.wideScreen": "Широкоекранен",
|
||||
"viewSwitcher.chat": "Чат",
|
||||
"viewSwitcher.page": "Страница",
|
||||
"viewSwitcher.task": "Задача",
|
||||
"workflow.awaitingConfirmation": "Очаква се вашето потвърждение",
|
||||
"workflow.collapse": "Свий",
|
||||
"workflow.expandFull": "Разгъни напълно",
|
||||
@@ -633,7 +745,6 @@
|
||||
"workflow.toolDisplayName.createTodos": "Създадени задачи",
|
||||
"workflow.toolDisplayName.deleteAgent": "Изтрит агент",
|
||||
"workflow.toolDisplayName.deleteDocument": "Изтрит документ",
|
||||
"workflow.toolDisplayName.editDocument": "Редактира документ",
|
||||
"workflow.toolDisplayName.editLocalFile": "Редактиран файл",
|
||||
"workflow.toolDisplayName.editTitle": "Редактирано заглавие",
|
||||
"workflow.toolDisplayName.evaluate": "Изчислен израз",
|
||||
@@ -660,13 +771,13 @@
|
||||
"workflow.toolDisplayName.modifyNodes": "Променена страница",
|
||||
"workflow.toolDisplayName.moveLocalFiles": "Преместени файлове",
|
||||
"workflow.toolDisplayName.readDocument": "Прочитане на документ",
|
||||
"workflow.toolDisplayName.readDocumentByFilename": "Прочитане на документ",
|
||||
"workflow.toolDisplayName.readKnowledge": "Преглед на знания",
|
||||
"workflow.toolDisplayName.readLocalFile": "Прочитане на файл",
|
||||
"workflow.toolDisplayName.removeDocument": "Документът е премахнат",
|
||||
"workflow.toolDisplayName.removeIdentityMemory": "Премахната памет",
|
||||
"workflow.toolDisplayName.renameDocument": "Преименува документ",
|
||||
"workflow.toolDisplayName.renameLocalFile": "Преименува файл",
|
||||
"workflow.toolDisplayName.replaceDocumentContent": "Заменено съдържание на документа",
|
||||
"workflow.toolDisplayName.replaceText": "Заменен текст",
|
||||
"workflow.toolDisplayName.runCommand": "Изпълни команда",
|
||||
"workflow.toolDisplayName.search": "Претърси уеба",
|
||||
@@ -682,7 +793,6 @@
|
||||
"workflow.toolDisplayName.updateLoadRule": "Актуализирано правило за натоварване",
|
||||
"workflow.toolDisplayName.updatePlan": "Актуализиран план",
|
||||
"workflow.toolDisplayName.updateTodos": "Актуализирани задачи",
|
||||
"workflow.toolDisplayName.upsertDocumentByFilename": "Актуализира документ",
|
||||
"workflow.toolDisplayName.writeLocalFile": "Записа файл",
|
||||
"workflow.working": "В процес на работа...",
|
||||
"workingPanel.agentDocuments": "Agent Documents",
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"back": "Назад",
|
||||
"batchDelete": "Групово изтриване",
|
||||
"blog": "Блог на продукта",
|
||||
"botIntegrationBanner.dismiss": "Затвори",
|
||||
"botIntegrationBanner.title": "Добавяне на канали към LobeAI",
|
||||
"branching": "Създай подтема",
|
||||
"branchingDisable": "Функцията „Подтема“ не е налична в текущия режим. За да я използвате, превключете към режим Postgres/Pglite DB или използвайте LobeHub Cloud.",
|
||||
"branchingRequiresSavedTopic": "Текущата тема не е запазена, моля, запазете я, за да използвате функцията за подтема",
|
||||
@@ -146,6 +148,7 @@
|
||||
"cmdk.keywords.starGitHub": "github звезда любимо харесване",
|
||||
"cmdk.keywords.stats": "статистики анализи",
|
||||
"cmdk.keywords.submitIssue": "проблем бъг обратна връзка",
|
||||
"cmdk.keywords.tasks": "задачи списък агент канбан",
|
||||
"cmdk.keywords.usage": "използване статистика консумация квота",
|
||||
"cmdk.keywords.video": "видео,генерирай,seedance,kling",
|
||||
"cmdk.memory": "Памет",
|
||||
@@ -195,6 +198,7 @@
|
||||
"cmdk.settings": "Настройки",
|
||||
"cmdk.starOnGitHub": "Дайте звезда в GitHub",
|
||||
"cmdk.submitIssue": "Изпрати проблем",
|
||||
"cmdk.tasks": "Задачи",
|
||||
"cmdk.theme": "Тема",
|
||||
"cmdk.themeAuto": "Автоматично",
|
||||
"cmdk.themeCurrent": "Текуща",
|
||||
@@ -266,6 +270,7 @@
|
||||
"footer.title": "Харесвате ли нашия продукт?",
|
||||
"fullscreen": "Цял екран",
|
||||
"generation.hero.taglinePrefix": "Започнете да създавате с",
|
||||
"generation.promptModeration.blocked": "Проверката на съдържателната политика не беше успешна. Моля, коригирайте подсказката.",
|
||||
"getDesktopApp": "Изтеглете настолното приложение",
|
||||
"historyRange": "Обхват на историята",
|
||||
"home.suggestQuestions": "Опитайте с тези примери",
|
||||
@@ -423,6 +428,7 @@
|
||||
"tab.discover": "Открий",
|
||||
"tab.eval": "Оценителна лаборатория",
|
||||
"tab.files": "Файлове",
|
||||
"tab.generation": "Генериране",
|
||||
"tab.home": "Начало",
|
||||
"tab.image": "Изображение",
|
||||
"tab.knowledgeBase": "Библиотека",
|
||||
@@ -433,6 +439,7 @@
|
||||
"tab.resource": "Ресурси",
|
||||
"tab.search": "Търсене",
|
||||
"tab.setting": "Настройки",
|
||||
"tab.tasks": "Задачи",
|
||||
"tab.video": "Видео",
|
||||
"telemetry.allow": "Разреши",
|
||||
"telemetry.deny": "Откажи",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user