mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 11:40:07 +00:00
Compare commits
427 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e670602f67 | |||
| 3ee17092ab | |||
| db0a650bd5 | |||
| 5678b9fadb | |||
| a99ccf4534 | |||
| 0bd603cc29 | |||
| 6a4c355b54 | |||
| 1a075f3ca5 | |||
| d4b618ed71 | |||
| 6d62e1ec80 | |||
| 3bdd1cbc0f | |||
| 600a20c822 | |||
| bbf20289db | |||
| 9a57fcd600 | |||
| d6bdf406aa | |||
| b98813f0b1 | |||
| dce2925ab9 | |||
| 86f9f1c06c | |||
| 5accb96fec | |||
| 8b83609103 | |||
| 74f4341e8d | |||
| c73416141b | |||
| 9f6084a2fb | |||
| cbd6715dd7 | |||
| d42f8f64c7 | |||
| 5330159fa8 | |||
| 34850e3d1c | |||
| e06a1bd4d9 | |||
| ab09c04bee | |||
| 39af292f6e | |||
| c951cc0afd | |||
| 80ea77ac6b | |||
| 544639ff92 | |||
| 9e58abb37d | |||
| 3b71cb1eee | |||
| 5d3fb27a19 | |||
| f73ce9be9e | |||
| d9e95fbbb7 | |||
| 132ced10a5 | |||
| d6c74efacb | |||
| 6229876ff5 | |||
| 9b0926efdc | |||
| 6faa261e3f | |||
| 03817cd427 | |||
| 3b2767aec9 | |||
| 8b3b8fd218 | |||
| 5d251563b3 | |||
| e6263ebb09 | |||
| 4dd315126b | |||
| 54da37d4bf | |||
| 5912717721 | |||
| 061b91a76e | |||
| 09bf76e10b | |||
| 59a6e3e0ac | |||
| aebe297429 | |||
| b5a6e2d60f | |||
| 11eb5834bb | |||
| e5cd73f7dd | |||
| 4237f7c2b9 | |||
| 54c9900997 | |||
| d786491be1 | |||
| af392959fa | |||
| 5f062a802c | |||
| 619627f100 | |||
| 989a108db9 | |||
| c85fb193f8 | |||
| 18b2564ac5 | |||
| 111aa922d7 | |||
| c06b63330e | |||
| d0771c2dbf | |||
| 3eeeed0c43 | |||
| 24920c93b4 | |||
| 2fc6e855d1 | |||
| 68be44a053 | |||
| 6364027ac2 | |||
| 0875ac4ca2 | |||
| 23a3cf609f | |||
| 40d4f496db | |||
| c47d7285b4 | |||
| e8d64606c8 | |||
| d95582fdcb | |||
| 8e4c481ffb | |||
| 1ebc06b1b4 | |||
| 5f13f37544 | |||
| ded42374d5 | |||
| 07963b3552 | |||
| 754b28382e | |||
| 0c1b19114c | |||
| 09fc1d2d13 | |||
| da34f490c8 | |||
| 85af7b16a2 | |||
| 9f93568fcf | |||
| b7ed1fc744 | |||
| 2713cce61e | |||
| 4e83977e7c | |||
| 07c6ad56ea | |||
| 6fb1a3ba90 | |||
| f04c789043 | |||
| 21b3b2a693 | |||
| ce97ffc0e0 | |||
| f0ebdacc00 | |||
| e864c9e44f | |||
| dc4d26d13f | |||
| aa5cb2953d | |||
| 0fc6f20840 | |||
| b75d81fa4e | |||
| cec6751bda | |||
| 319dfb21d5 | |||
| b3c67430e8 | |||
| 0186a4b01d | |||
| a1d98cb05b | |||
| beba396c8b | |||
| 16f3c0df75 | |||
| e99092e091 | |||
| f4329b81bb | |||
| d9d149de2a | |||
| b72fc88b3f | |||
| d06b0dbbcf | |||
| 31ea03382b | |||
| 2110a87422 | |||
| 2d0dde74c9 | |||
| 0973458529 | |||
| 208b07d417 | |||
| 52983de190 | |||
| 18f5834550 | |||
| a915507938 | |||
| c27bf79e0f | |||
| d59b44c042 | |||
| 7428a58303 | |||
| 0090bd715e | |||
| d7d38d338e | |||
| b6fd29787b | |||
| 0a184130dc | |||
| 706569e61c | |||
| 2cec8062f1 | |||
| 274f95a4a5 | |||
| 604e0cf1c8 | |||
| bf90eef3d4 | |||
| d8d56a6809 | |||
| ca9e1a124c | |||
| ae521e0d61 | |||
| 9179f63449 | |||
| 40271db7a1 | |||
| 9cfd816b50 | |||
| 64052762d1 | |||
| 6f10874617 | |||
| db681ac87d | |||
| f01327a796 | |||
| 9604523c51 | |||
| 0dc5e711bc | |||
| ce75257975 | |||
| a149391b0e | |||
| ace6d34d5f | |||
| 705e8f7090 | |||
| af52fb887a | |||
| 3688e5f3eb | |||
| f61f2ae589 | |||
| 9ac6f5fdc5 | |||
| 2eca77a535 | |||
| 3b5da54660 | |||
| b8ff536d14 | |||
| 4513f8ddd2 | |||
| 1ebd67e00f | |||
| e279bdfb93 | |||
| 515b7b1127 | |||
| 2edc83b79c | |||
| cc3a310717 | |||
| 8a9451a69e | |||
| 84cc9ef336 | |||
| 2e14d9ff18 | |||
| 2082eb2292 | |||
| 6c3ce6195d | |||
| acd29d7e4d | |||
| 3f7f964c1b | |||
| 7b45fc5831 | |||
| f97df5c05e | |||
| e2ce0e5f19 | |||
| 40b1630eec | |||
| cc7d13db30 | |||
| 83df1f4339 | |||
| 42242c0c24 | |||
| 3f8c6c50f3 | |||
| 024f65a47e | |||
| 18f757e925 | |||
| 33b5ac4bc8 | |||
| 55e177f0bd | |||
| b43f78eac5 | |||
| 8cb4096d27 | |||
| 034c68e251 | |||
| 2f4ed1a016 | |||
| 8bafec3d8b | |||
| 6b368c562d | |||
| 72c5d74041 | |||
| 93a6d89b02 | |||
| 59a555ab9e | |||
| 18f6f381c0 | |||
| 5df9feecfb | |||
| 0b8a3a2812 | |||
| ef0d6a3d3a | |||
| cedb98c3bf | |||
| f98e60b679 | |||
| 38cf8f28ac | |||
| 21333da8e7 | |||
| 8293741560 | |||
| bce556cd76 | |||
| 90f49a967f | |||
| a8ca5f78f7 | |||
| d89474a304 | |||
| 5c7775dc9f | |||
| 784128c96b | |||
| ae87e83e22 | |||
| 84c43d9091 | |||
| 6444f27751 | |||
| 4478e72d9c | |||
| 769c8871b7 | |||
| d6d4290b9e | |||
| 7e7ef23fb3 | |||
| eb65c57bfa | |||
| bf7744f93b | |||
| 34183041e4 | |||
| 93dda6bfee | |||
| ab34b9c400 | |||
| ff783b3fd4 | |||
| 977dff7201 | |||
| bedbb00ec4 | |||
| 61a9ae2b22 | |||
| b2f9b2288a | |||
| d9a93c996e | |||
| 69dbf4966d | |||
| 18bb4cb95d | |||
| 4717b8e945 | |||
| acc6efb778 | |||
| c9eeb26bcb | |||
| e9ea0ef92a | |||
| 173c091d54 | |||
| 7c1a52298b | |||
| fb742204e6 | |||
| bed9e7e797 | |||
| b25f669ca6 | |||
| d6316566f7 | |||
| a1520e643e | |||
| 1cc10d3f31 | |||
| fb37fd0ca1 | |||
| 99400b1ac2 | |||
| 7fa6e406d8 | |||
| 0bf2d38089 | |||
| 23a26a567f | |||
| bfced7f8ec | |||
| 5f097faa5f | |||
| 1b51e9deba | |||
| b298ec9c6d | |||
| af471ee9f4 | |||
| 978c7498e5 | |||
| 8181028c09 | |||
| 1b4ec2d1e4 | |||
| af9af15861 | |||
| 9708d90533 | |||
| b85ac86253 | |||
| 0d20305aac | |||
| 761420d6ef | |||
| 2356adb6cc | |||
| f632ed48d2 | |||
| e07605a6fe | |||
| bef203f8ff | |||
| 582aed80ac | |||
| da282c75d6 | |||
| 8fbc4e4a0a | |||
| bf037e333e | |||
| 0ab5195c11 | |||
| 7b779b2e5a | |||
| 06b3ee068d | |||
| fff1e4f10e | |||
| e486e15c64 | |||
| 1e332bec07 | |||
| 206cf8c645 | |||
| 9fc8a59458 | |||
| 067970d532 | |||
| 837bf8c461 | |||
| c0f77deeab | |||
| 392cdef720 | |||
| 1fbb302e25 | |||
| 5f81bcdea6 | |||
| bee3f8e456 | |||
| 1b239e229b | |||
| 749c90c105 | |||
| 9ed52c4135 | |||
| dcd1989faa | |||
| 3438c3de9f | |||
| d25ed06845 | |||
| e186e27d71 | |||
| a1b77bc7ac | |||
| d9a0cc5ca2 | |||
| 100f114e6b | |||
| 269c9a5faf | |||
| 8cfb934afd | |||
| 2fa471bb62 | |||
| 239f3e7154 | |||
| ed36d3b760 | |||
| aaf2045835 | |||
| 110f3c7b1e | |||
| 2ecf47f89b | |||
| 0d5a5f1e37 | |||
| d9f812a026 | |||
| e8ffde951f | |||
| e1e8416583 | |||
| ce1806b88a | |||
| 10fdf24bb4 | |||
| 8d2959f455 | |||
| 2036b75095 | |||
| a7facab0ca | |||
| c3526ac0f3 | |||
| 0c8c8cf83f | |||
| ba96c6d2f6 | |||
| 286c62660c | |||
| 3d286323c5 | |||
| cbf19e1c89 | |||
| d34130e8a6 | |||
| 46e376a0fd | |||
| efb6c9f350 | |||
| 598c840684 | |||
| 9d7975ee1f | |||
| e5a5c772e0 | |||
| 25d4a46550 | |||
| 8e8a2beee4 | |||
| 016c6360aa | |||
| 842cae1ae2 | |||
| 0e2a739e6a | |||
| 214d156e09 | |||
| fbed2713bb | |||
| 6d4d77a854 | |||
| b22d30e13b | |||
| 15b73b9627 | |||
| 89abb0e87c | |||
| 6030b1794d | |||
| 7a50256d04 | |||
| f1df8bd50b | |||
| a4bbd63abc | |||
| f11c7d2033 | |||
| 1751c4a644 | |||
| ceac3493e1 | |||
| cbdd083c8d | |||
| 73bae654e9 | |||
| 5e70f6de14 | |||
| e4734bf1a1 | |||
| f45ab6bc92 | |||
| b38bf6fb2e | |||
| 4eae151235 | |||
| 8a3158d781 | |||
| 9492b58531 | |||
| fc1b56fe0b | |||
| 9cb1b0fbb6 | |||
| f7c7b5ab53 | |||
| 8049af8b24 | |||
| 4d0814b536 | |||
| 4e29a9cb38 | |||
| 52a0333ea2 | |||
| a5dcd83431 | |||
| 5a442b1f84 | |||
| 8ef4e4114e | |||
| 6756812486 | |||
| ca77218968 | |||
| 76f47dfe27 | |||
| 0c770cb837 | |||
| 14002c32e9 | |||
| 5863c17793 | |||
| 7272fb0b19 | |||
| 4c22c8e4af | |||
| 5202895c8d | |||
| 6c2539b105 | |||
| 3db41e0deb | |||
| 5c1729eb4d | |||
| 6e0fdb87cf | |||
| 886e43b7fd | |||
| 8fd062b9af | |||
| 2856152a24 | |||
| cc24f08c34 | |||
| cc6cf4f4ab | |||
| c706e0cf85 | |||
| 44a2846983 | |||
| eddbea1180 | |||
| 52c526a216 | |||
| 5be282b591 | |||
| 4ffd13310d | |||
| 5b69b2c574 | |||
| 247028750d | |||
| d65c169663 | |||
| 102a47ac01 | |||
| ae794c9d17 | |||
| 3f3d2856b4 | |||
| 671ba275fc | |||
| bfd8f3d2bc | |||
| 5b9e3f906d | |||
| 40c748ec9e | |||
| 8c3c8d712f | |||
| 98e385a3d4 | |||
| 8f0e2f27aa | |||
| 55a4d9a7a9 | |||
| abe154d9c0 | |||
| 4a619725c2 | |||
| b4d5faff38 | |||
| db9a99fc72 | |||
| 07ff5acbb6 | |||
| 0b19fdd9bf | |||
| f0493b4cf1 | |||
| b25cf4c4c6 | |||
| 0a56d3cc90 | |||
| 43de4e47f7 | |||
| c0de93a2dd | |||
| b5c7cee5fe | |||
| 4c39157a29 | |||
| 9cf7c0a7de | |||
| 42d7f8cca8 | |||
| ae2f4c29e1 | |||
| 00e504d365 | |||
| 305483dacc | |||
| 04bb8273dc | |||
| 821c95d9d1 | |||
| ab603f95c3 | |||
| 870ac6aa45 | |||
| 216f367300 | |||
| 801c744522 | |||
| e92ea2c206 | |||
| e8e1c65503 | |||
| b6616a832f | |||
| 04020fc804 | |||
| 7519d6f3f1 | |||
| 27deea97cf |
@@ -1,35 +0,0 @@
|
||||
---
|
||||
description: Explain how group chat works in LobeHub (Multi-agent orchestratoin)
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
This rule explains how group chat (multi-agent orchestration) works. Not confused with session group, which is a organization method to manage session.
|
||||
|
||||
## Key points
|
||||
|
||||
- A supervisor will devide who and how will speak next
|
||||
- Each agent will speak just like in single chat (if was asked to speak)
|
||||
- Not coufused with session group
|
||||
|
||||
## Related Files
|
||||
|
||||
- src/store/chat/slices/message/supervisor.ts
|
||||
- src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts
|
||||
- src/prompts/groupChat/index.ts (All prompts here)
|
||||
|
||||
## Snippets
|
||||
|
||||
```tsx
|
||||
// Detect whether in group chat
|
||||
const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
|
||||
|
||||
// Member actions
|
||||
const addAgentsToGroup = useChatGroupStore((s) => s.addAgentsToGroup);
|
||||
const removeAgentFromGroup = useChatGroupStore((s) => s.removeAgentFromGroup);
|
||||
const persistReorder = useChatGroupStore((s) => s.reorderGroupMembers);
|
||||
|
||||
// Get group info
|
||||
const groupConfig = useChatGroupStore(chatGroupSelectors.currentGroupConfig);
|
||||
const currentGroupMemebers = useSessionStore(sessionSelectors.currentGroupAgents);
|
||||
```
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
# 如何添加新的快捷键:开发者指南
|
||||
|
||||
本指南将带您一步步地向 LobeChat 添加一个新的快捷键功能。我们将通过一个完整示例,演示从定义到实现的整个过程。
|
||||
|
||||
## 示例场景
|
||||
|
||||
假设我们要添加一个新的快捷键功能:**快速清空聊天记录**,快捷键为 `Mod+Shift+Backspace`。
|
||||
|
||||
## 步骤 1:更新快捷键常量定义
|
||||
|
||||
首先,在 `src/types/hotkey.ts` 中更新 `HotkeyEnum`:
|
||||
|
||||
```typescript
|
||||
export const HotkeyEnum = {
|
||||
// 已有的快捷键...
|
||||
AddUserMessage: 'addUserMessage',
|
||||
EditMessage: 'editMessage',
|
||||
|
||||
// 新增快捷键
|
||||
ClearChat: 'clearChat', // 添加这一行
|
||||
|
||||
// 其他已有快捷键...
|
||||
} as const;
|
||||
```
|
||||
|
||||
## 步骤 2:注册默认快捷键
|
||||
|
||||
在 `src/const/hotkeys.ts` 中添加快捷键的默认配置:
|
||||
|
||||
```typescript
|
||||
import { KeyMapEnum as Key, combineKeys } from '@lobehub/ui';
|
||||
|
||||
// ...现有代码
|
||||
|
||||
export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
|
||||
// 现有的快捷键配置...
|
||||
|
||||
// 添加新的快捷键配置
|
||||
{
|
||||
group: HotkeyGroupEnum.Conversation, // 归类到会话操作组
|
||||
id: HotkeyEnum.ClearChat,
|
||||
keys: combineKeys([Key.Mod, Key.Shift, Key.Backspace]),
|
||||
scopes: [HotkeyScopeEnum.Chat], // 在聊天作用域下生效
|
||||
},
|
||||
|
||||
// 其他现有快捷键...
|
||||
];
|
||||
```
|
||||
|
||||
## 步骤 3:添加国际化翻译
|
||||
|
||||
在 `src/locales/default/hotkey.ts` 中添加对应的文本描述:
|
||||
|
||||
```typescript
|
||||
import { HotkeyI18nTranslations } from '@/types/hotkey';
|
||||
|
||||
const hotkey: HotkeyI18nTranslations = {
|
||||
// 现有翻译...
|
||||
|
||||
// 添加新快捷键的翻译
|
||||
clearChat: {
|
||||
desc: '清空当前会话的所有消息记录',
|
||||
title: '清空聊天记录',
|
||||
},
|
||||
|
||||
// 其他现有翻译...
|
||||
};
|
||||
|
||||
export default hotkey;
|
||||
```
|
||||
|
||||
如需支持其他语言,还需要在相应的语言文件中添加对应翻译。
|
||||
|
||||
## 步骤 4:创建并注册快捷键 Hook
|
||||
|
||||
在 `src/hooks/useHotkeys/chatScope.ts` 中添加新的 Hook:
|
||||
|
||||
```typescript
|
||||
export const useClearChatHotkey = () => {
|
||||
const clearMessages = useChatStore((s) => s.clearMessages);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useHotkeyById(HotkeyEnum.ClearChat, showConfirm);
|
||||
};
|
||||
|
||||
// 注册聚合
|
||||
|
||||
export const useRegisterChatHotkeys = () => {
|
||||
const { enableScope, disableScope } = useHotkeysContext();
|
||||
|
||||
useOpenChatSettingsHotkey();
|
||||
// ...其他快捷键
|
||||
useClearChatHotkey();
|
||||
|
||||
useEffect(() => {
|
||||
enableScope(HotkeyScopeEnum.Chat);
|
||||
return () => disableScope(HotkeyScopeEnum.Chat);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
## 步骤 5:给相应 UI 元素添加 Tooltip 提示(可选)
|
||||
|
||||
如果有对应的 UI 按钮,可以添加快捷键提示:
|
||||
|
||||
```tsx
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Tooltip } from '@lobehub/ui';
|
||||
import { Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { settingsSelectors } from '@/store/user/selectors';
|
||||
import { HotkeyEnum } from '@/types/hotkey';
|
||||
|
||||
const ClearChatButton = () => {
|
||||
const { t } = useTranslation(['hotkey', 'chat']);
|
||||
const clearChatHotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ClearChat));
|
||||
|
||||
// 获取清空聊天的方法
|
||||
const clearMessages = useChatStore((s) => s.clearMessages);
|
||||
|
||||
return (
|
||||
<Tooltip hotkey={clearChatHotkey} title={t('clearChat.title', { ns: 'hotkey' })}>
|
||||
<Button icon={<DeleteOutlined />} onClick={clearMessages} />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 步骤 6:测试新快捷键
|
||||
|
||||
1. 启动开发服务器
|
||||
2. 打开聊天页面
|
||||
3. 按下设置的快捷键组合(`Cmd+Shift+Backspace` 或 `Ctrl+Shift+Backspace`)
|
||||
4. 确认功能正常工作
|
||||
5. 检查快捷键设置面板中是否正确显示了新快捷键
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **作用域考虑**:根据功能决定快捷键应属于全局作用域还是聊天作用域
|
||||
2. **分组合理**:将快捷键放在合适的功能组中(System/Layout/Conversation)
|
||||
3. **冲突检查**:确保新快捷键不会与现有系统、浏览器或应用快捷键冲突
|
||||
4. **平台适配**:使用 `Key.Mod` 而非硬编码 `Ctrl` 或 `Cmd`,以适配不同平台
|
||||
5. **提供清晰描述**:为快捷键添加明确的标题和描述,帮助用户理解功能
|
||||
|
||||
按照以上步骤,您可以轻松地向系统添加新的快捷键功能,提升用户体验。如有特殊需求,如桌面专属快捷键,可以通过 `isDesktop` 标记进行区分处理。
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
- **快捷键未生效**:检查作用域是否正确,以及是否在 RegisterHotkeys 中调用了对应的 hook
|
||||
- **快捷键设置面板未显示**:确认在 HOTKEYS_REGISTRATION 中正确配置了快捷键
|
||||
- **快捷键冲突**:在 HotkeyInput 组件中可以检测到冲突,用户会看到警告
|
||||
- **功能在某些页面失效**:确认是否注册在了正确的作用域,以及相关页面是否激活了该作用域
|
||||
|
||||
通过这些步骤,您可以确保新添加的快捷键功能稳定、可靠且用户友好。
|
||||
@@ -0,0 +1,138 @@
|
||||
# Recent Data 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
Recent 数据(recentTopics, recentResources, recentPages)存储在 session store 中,可以在应用的任何地方访问。
|
||||
|
||||
## 数据初始化
|
||||
|
||||
在应用顶层(如 `RecentHydration.tsx`)中初始化所有 recent 数据:
|
||||
|
||||
```tsx
|
||||
import { useInitRecentPage } from '@/hooks/useInitRecentPage';
|
||||
import { useInitRecentResource } from '@/hooks/useInitRecentResource';
|
||||
import { useInitRecentTopic } from '@/hooks/useInitRecentTopic';
|
||||
|
||||
const App = () => {
|
||||
// 初始化所有 recent 数据
|
||||
useInitRecentTopic();
|
||||
useInitRecentResource();
|
||||
useInitRecentPage();
|
||||
|
||||
return <YourComponents />;
|
||||
};
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 方式一:直接从 Store 读取(推荐用于多处使用)
|
||||
|
||||
在任何组件中直接访问 store 中的数据:
|
||||
|
||||
```tsx
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { recentSelectors } from '@/store/session/selectors';
|
||||
|
||||
const Component = () => {
|
||||
// 读取数据
|
||||
const recentTopics = useSessionStore(recentSelectors.recentTopics);
|
||||
const isInit = useSessionStore(recentSelectors.isRecentTopicsInit);
|
||||
|
||||
if (!isInit) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{recentTopics.map(topic => (
|
||||
<div key={topic.id}>{topic.title}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 方式二:使用 Hook 返回的数据(用于单一组件)
|
||||
|
||||
```tsx
|
||||
import { useInitRecentTopic } from '@/hooks/useInitRecentTopic';
|
||||
|
||||
const Component = () => {
|
||||
const { data: recentTopics, isLoading } = useInitRecentTopic();
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
|
||||
return <div>{/* 使用 recentTopics */}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## 可用的 Selectors
|
||||
|
||||
### Recent Topics (最近话题)
|
||||
|
||||
```tsx
|
||||
import { recentSelectors } from '@/store/session/selectors';
|
||||
|
||||
// 数据
|
||||
const recentTopics = useSessionStore(recentSelectors.recentTopics);
|
||||
// 类型: RecentTopic[]
|
||||
|
||||
// 初始化状态
|
||||
const isInit = useSessionStore(recentSelectors.isRecentTopicsInit);
|
||||
// 类型: boolean
|
||||
```
|
||||
|
||||
**RecentTopic 类型:**
|
||||
```typescript
|
||||
interface RecentTopic {
|
||||
agent: {
|
||||
avatar: string | null;
|
||||
backgroundColor: string | null;
|
||||
id: string;
|
||||
title: string | null;
|
||||
} | null;
|
||||
id: string;
|
||||
title: string | null;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Recent Resources (最近文件)
|
||||
|
||||
```tsx
|
||||
import { recentSelectors } from '@/store/session/selectors';
|
||||
|
||||
// 数据
|
||||
const recentResources = useSessionStore(recentSelectors.recentResources);
|
||||
// 类型: FileListItem[]
|
||||
|
||||
// 初始化状态
|
||||
const isInit = useSessionStore(recentSelectors.isRecentResourcesInit);
|
||||
// 类型: boolean
|
||||
```
|
||||
|
||||
### Recent Pages (最近页面)
|
||||
|
||||
```tsx
|
||||
import { recentSelectors } from '@/store/session/selectors';
|
||||
|
||||
// 数据
|
||||
const recentPages = useSessionStore(recentSelectors.recentPages);
|
||||
// 类型: any[]
|
||||
|
||||
// 初始化状态
|
||||
const isInit = useSessionStore(recentSelectors.isRecentPagesInit);
|
||||
// 类型: boolean
|
||||
```
|
||||
|
||||
## 特性
|
||||
|
||||
1. **自动登录检测**:只有在用户登录时才会加载数据
|
||||
2. **数据缓存**:数据存储在 store 中,多处使用无需重复加载
|
||||
3. **自动刷新**:使用 SWR,在用户重新聚焦时自动刷新(5分钟间隔)
|
||||
4. **类型安全**:完整的 TypeScript 类型定义
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **初始化位置**:在应用顶层统一初始化所有 recent 数据
|
||||
2. **数据访问**:使用 selectors 从 store 读取数据
|
||||
3. **多处使用**:同一数据在多个组件中使用时,推荐使用方式一(直接从 store 读取)
|
||||
4. **性能优化**:使用 selector 确保只有相关数据变化时才重新渲染
|
||||
@@ -0,0 +1,275 @@
|
||||
# Agent Runtime E2E 测试指南
|
||||
|
||||
本文档描述 Agent Runtime 端到端测试的核心原则和实施方法。
|
||||
|
||||
## 核心原则
|
||||
|
||||
### 1. 最小化 Mock 原则
|
||||
|
||||
E2E 测试的目标是尽可能接近真实运行环境。因此,我们只 Mock **三个外部依赖**:
|
||||
|
||||
| 依赖 | Mock 方式 | 说明 |
|
||||
|------|----------|------|
|
||||
| **Database** | PGLite | 使用 `@lobechat/database/test-utils` 提供的内存数据库 |
|
||||
| **Redis** | InMemoryAgentStateManager | Mock `AgentStateManager` 使用内存实现 |
|
||||
| **Redis** | InMemoryStreamEventManager | Mock `StreamEventManager` 使用内存实现 |
|
||||
|
||||
**不 Mock 的部分:**
|
||||
|
||||
- `model-bank` - 使用真实的模型配置数据
|
||||
- `Mecha` (AgentToolsEngine, ContextEngineering) - 使用真实逻辑
|
||||
- `AgentRuntimeService` - 使用真实逻辑
|
||||
- `AgentRuntimeCoordinator` - 使用真实逻辑
|
||||
|
||||
### 2. 使用 vi.spyOn 而非 vi.mock
|
||||
|
||||
不同测试场景需要不同的 LLM 响应。使用 `vi.spyOn` 可以:
|
||||
|
||||
- 在每个测试中灵活控制返回值
|
||||
- 便于测试不同场景(纯文本、tool calls、错误等)
|
||||
- 避免全局 mock 导致的测试隔离问题
|
||||
|
||||
### 3. 默认模型使用 gpt-5
|
||||
|
||||
- `model-bank` 中肯定有该模型的数据
|
||||
- 避免短期内因模型更新需要修改测试
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 数据库设置
|
||||
|
||||
```typescript
|
||||
import { LobeChatDatabase } from '@lobechat/database';
|
||||
import { getTestDB } from '@lobechat/database/test-utils';
|
||||
|
||||
let testDB: LobeChatDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
testDB = await getTestDB();
|
||||
});
|
||||
```
|
||||
|
||||
### OpenAI Response Mock Helper
|
||||
|
||||
创建一个 helper 函数来生成 OpenAI 格式的流式响应:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 创建 OpenAI 格式的流式响应
|
||||
*/
|
||||
export const createOpenAIStreamResponse = (options: {
|
||||
content?: string;
|
||||
toolCalls?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
arguments: string;
|
||||
}>;
|
||||
finishReason?: 'stop' | 'tool_calls';
|
||||
}) => {
|
||||
const { content, toolCalls, finishReason = 'stop' } = options;
|
||||
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// 发送内容 chunk
|
||||
if (content) {
|
||||
const chunk = {
|
||||
id: 'chatcmpl-mock',
|
||||
object: 'chat.completion.chunk',
|
||||
model: 'gpt-5',
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: { content },
|
||||
finish_reason: null,
|
||||
}],
|
||||
};
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
||||
}
|
||||
|
||||
// 发送 tool_calls chunk
|
||||
if (toolCalls) {
|
||||
for (const tool of toolCalls) {
|
||||
const chunk = {
|
||||
id: 'chatcmpl-mock',
|
||||
object: 'chat.completion.chunk',
|
||||
model: 'gpt-5',
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
id: tool.id,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
arguments: tool.arguments,
|
||||
},
|
||||
}],
|
||||
},
|
||||
finish_reason: null,
|
||||
}],
|
||||
};
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
||||
}
|
||||
}
|
||||
|
||||
// 发送完成 chunk
|
||||
const finishChunk = {
|
||||
id: 'chatcmpl-mock',
|
||||
object: 'chat.completion.chunk',
|
||||
model: 'gpt-5',
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason,
|
||||
}],
|
||||
};
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(finishChunk)}\n\n`));
|
||||
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{ headers: { 'content-type': 'text/event-stream' } },
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 内存状态管理
|
||||
|
||||
使用依赖注入替代 Redis:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
InMemoryAgentStateManager,
|
||||
InMemoryStreamEventManager,
|
||||
} from '@/server/modules/AgentRuntime';
|
||||
import { AgentRuntimeService } from '@/server/services/agentRuntime';
|
||||
|
||||
const stateManager = new InMemoryAgentStateManager();
|
||||
const streamEventManager = new InMemoryStreamEventManager();
|
||||
|
||||
const service = new AgentRuntimeService(serverDB, userId, {
|
||||
coordinatorOptions: {
|
||||
stateManager,
|
||||
streamEventManager,
|
||||
},
|
||||
queueService: null, // 禁用 QStash 队列,使用 executeSync
|
||||
streamEventManager,
|
||||
});
|
||||
```
|
||||
|
||||
### Mock OpenAI API
|
||||
|
||||
在测试中使用 `vi.spyOn` mock fetch:
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// 在测试文件顶部或 beforeEach 中
|
||||
const fetchSpy = vi.spyOn(globalThis, 'fetch');
|
||||
|
||||
// 在具体测试中设置返回值
|
||||
it('should handle text response', async () => {
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
createOpenAIStreamResponse({ content: '杭州今天天气晴朗' })
|
||||
);
|
||||
|
||||
// ... 执行测试
|
||||
});
|
||||
|
||||
it('should handle tool calls', async () => {
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
createOpenAIStreamResponse({
|
||||
toolCalls: [{
|
||||
id: 'call_123',
|
||||
name: 'lobe-web-browsing____search____builtin',
|
||||
arguments: JSON.stringify({ query: '杭州天气' }),
|
||||
}],
|
||||
finishReason: 'tool_calls',
|
||||
})
|
||||
);
|
||||
|
||||
// ... 执行测试
|
||||
});
|
||||
```
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 1. 基本对话测试
|
||||
|
||||
```typescript
|
||||
describe('Basic Chat', () => {
|
||||
it('should complete a simple conversation', async () => {
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
createOpenAIStreamResponse({ content: 'Hello! How can I help you?' })
|
||||
);
|
||||
|
||||
const result = await service.createOperation({
|
||||
agentConfig: { model: 'gpt-5', provider: 'openai' },
|
||||
initialMessages: [{ role: 'user', content: 'Hi' }],
|
||||
// ...
|
||||
});
|
||||
|
||||
const finalState = await service.executeSync(result.operationId);
|
||||
expect(finalState.status).toBe('done');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Tool 调用测试
|
||||
|
||||
```typescript
|
||||
describe('Tool Calls', () => {
|
||||
it('should execute web-browsing tool', async () => {
|
||||
// 第一次调用:LLM 返回 tool_calls
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
createOpenAIStreamResponse({
|
||||
toolCalls: [{
|
||||
id: 'call_123',
|
||||
name: 'lobe-web-browsing____search____builtin',
|
||||
arguments: JSON.stringify({ query: '杭州天气' }),
|
||||
}],
|
||||
finishReason: 'tool_calls',
|
||||
})
|
||||
);
|
||||
|
||||
// 第二次调用:处理 tool 结果后的响应
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
createOpenAIStreamResponse({ content: '根据搜索结果,杭州今天...' })
|
||||
);
|
||||
|
||||
// ... 执行测试
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 错误处理测试
|
||||
|
||||
```typescript
|
||||
describe('Error Handling', () => {
|
||||
it('should handle API errors gracefully', async () => {
|
||||
fetchSpy.mockRejectedValueOnce(new Error('API rate limit exceeded'));
|
||||
|
||||
// ... 执行测试并验证错误处理
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 文件组织
|
||||
|
||||
```
|
||||
src/server/routers/lambda/__tests__/integration/
|
||||
├── setup.ts # 测试设置工具
|
||||
├── aiAgent.integration.test.ts # 现有集成测试
|
||||
├── aiAgent.e2e.test.ts # E2E 测试
|
||||
└── helpers/
|
||||
└── openaiMock.ts # OpenAI mock helper
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **测试隔离**:每个测试后清理 `InMemoryAgentStateManager` 和 `InMemoryStreamEventManager`
|
||||
2. **超时设置**:E2E 测试可能需要更长的超时时间
|
||||
3. **调试**:使用 `DEBUG=lobe-server:*` 环境变量查看详细日志
|
||||
@@ -0,0 +1,85 @@
|
||||
name: Desktop Next Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/desktop/**'
|
||||
- 'scripts/electronWorkflow/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'bun.lockb'
|
||||
- 'src/**'
|
||||
- 'packages/**'
|
||||
- '.github/workflows/desktop-build-electron.yml'
|
||||
|
||||
concurrency:
|
||||
group: desktop-electron-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_VERSION: 24.11.1
|
||||
BUN_VERSION: 1.2.23
|
||||
|
||||
jobs:
|
||||
build-next:
|
||||
name: Build desktop Next bundle
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
UPDATE_CHANNEL: nightly
|
||||
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_PROJECT_ID || 'dummy-desktop-project' }}
|
||||
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_BASE_URL || 'https://analytics.example.com' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-store
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ env.NODE_VERSION }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-${{ env.NODE_VERSION }}-
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: ${{ env.BUN_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --node-linker=hoisted
|
||||
|
||||
- name: Install desktop dependencies
|
||||
run: |
|
||||
cd apps/desktop
|
||||
bun run install-isolated
|
||||
|
||||
- name: Build desktop Next.js bundle
|
||||
run: bun run desktop:build-electron
|
||||
+3
-1
@@ -24,7 +24,7 @@ Desktop.ini
|
||||
.windsurfrules
|
||||
*.code-workspace
|
||||
.vscode/sessions.json
|
||||
|
||||
prd
|
||||
# Temporary files
|
||||
.temp/
|
||||
temp/
|
||||
@@ -116,3 +116,5 @@ CLAUDE.local.md
|
||||
*.xls*
|
||||
|
||||
e2e/reports
|
||||
|
||||
out
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
npm run typecheck
|
||||
npm run type-check
|
||||
npx --no-install lint-staged
|
||||
|
||||
+3
-1
@@ -30,7 +30,9 @@ module.exports = defineConfig({
|
||||
jsonMode: true,
|
||||
},
|
||||
markdown: {
|
||||
reference: '你需要保持 mdx 的组件格式,输出文本不需要在最外层包裹任何代码块语法',
|
||||
reference:
|
||||
'你需要保持 mdx 的组件格式,输出文本不需要在最外层包裹任何代码块语法。以下是一些词汇的固定翻译:\n' +
|
||||
JSON.stringify(require('./glossary.json'), null, 2),
|
||||
entry: ['./README.zh-CN.md', './contributing/**/*.zh-CN.md', './docs/**/*.zh-CN.mdx'],
|
||||
entryLocale: 'zh-CN',
|
||||
outputLocales: ['en-US'],
|
||||
|
||||
@@ -26,6 +26,7 @@ The project follows a well-organized monorepo structure:
|
||||
- `src/` - Main source code
|
||||
- `docs/` - Documentation
|
||||
- `.cursor/rules/` - Development rules and guidelines
|
||||
- PR titles starting with `✨ feat/` or `🐛 fix` will trigger the release workflow upon merge. Only use these prefixes for significant user-facing feature changes or bug fixes
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ read @.cursor/rules/project-structure.mdc
|
||||
- git commit message should prefix with gitmoji
|
||||
- git branch name format example: tj/feat/feature-name
|
||||
- use .github/PULL_REQUEST_TEMPLATE.md to generate pull request description
|
||||
- PR titles starting with `✨ feat/` or `🐛 fix` will trigger the release workflow upon merge. Only use these prefixes for significant user-facing feature changes or bug fixes
|
||||
|
||||
### Package Management
|
||||
|
||||
@@ -44,6 +45,8 @@ see @.cursor/rules/typescript.mdc
|
||||
- wrap the file path in single quotes to avoid shell expansion
|
||||
- Never run `bun run test` etc to run tests, this will run all tests and cost about 10mins
|
||||
- If trying to fix the same test twice, but still failed, stop and ask for help.
|
||||
- **Prefer `vi.spyOn` over `vi.mock`**: When mocking modules or functions, prefer using `vi.spyOn` to mock specific functions rather than `vi.mock` to mock entire modules. This approach is more targeted, easier to maintain, and allows for better control over mock behavior in individual tests.
|
||||
- **Tests must pass type check**: After writing or modifying tests, run `bun run type-check` to ensure there are no type errors. Tests should pass both runtime execution and TypeScript type checking.
|
||||
|
||||
### Typecheck
|
||||
|
||||
@@ -64,6 +67,10 @@ When working with Linear issues:
|
||||
3. **Update issue status** when completing tasks using `mcp__linear-server__update_issue`
|
||||
4. **MUST add completion comment** using `mcp__linear-server__create_comment`
|
||||
|
||||
### Creating Issues
|
||||
|
||||
When creating new Linear issues using `mcp__linear-server__create_issue`, **MUST add the `claude code` label** to indicate the issue was created by Claude Code.
|
||||
|
||||
### Completion Comment (REQUIRED)
|
||||
|
||||
**Every time you complete an issue, you MUST add a comment summarizing the work done.** This is critical for:
|
||||
|
||||
+15
@@ -107,6 +107,19 @@ COPY . .
|
||||
# run build standalone for docker version
|
||||
RUN npm run build:docker
|
||||
|
||||
# Prepare desktop export assets for Electron packaging (if generated)
|
||||
RUN <<'EOF'
|
||||
set -e
|
||||
if [ -d "/app/out" ]; then
|
||||
mkdir -p /app/apps/desktop/dist/next
|
||||
cp -a /app/out/. /app/apps/desktop/dist/next/
|
||||
echo "✅ Copied Next export output into /app/apps/desktop/dist/next"
|
||||
else
|
||||
echo "ℹ️ No Next export output found at /app/out, creating empty directory"
|
||||
mkdir -p /app/apps/desktop/dist/next
|
||||
fi
|
||||
EOF
|
||||
|
||||
## Application image, copy all the files for production
|
||||
FROM busybox:latest AS app
|
||||
|
||||
@@ -115,6 +128,8 @@ COPY --from=base /distroless/ /
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder /app/.next/standalone /app/
|
||||
# Copy Next export output for desktop renderer
|
||||
COPY --from=builder /app/apps/desktop/dist/next /app/apps/desktop/dist/next
|
||||
|
||||
# Copy database migrations
|
||||
COPY --from=builder /app/packages/database/migrations /app/migrations
|
||||
|
||||
@@ -18,6 +18,7 @@ read @.cursor/rules/project-structure.mdc
|
||||
- git commit message should prefix with gitmoji
|
||||
- git branch name format example: tj/feat/feature-name
|
||||
- use .github/PULL_REQUEST_TEMPLATE.md to generate pull request description
|
||||
- PR titles starting with `✨ feat/` or `🐛 fix` will trigger the release workflow upon merge. Only use these prefixes for significant user-facing feature changes or bug fixes
|
||||
|
||||
### Package Management
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ console.log(`🏗️ Building for architecture: ${arch}`);
|
||||
const isNightly = channel === 'nightly';
|
||||
const isBeta = packageJSON.name.includes('beta');
|
||||
|
||||
// Keep only these Electron Framework localization folders (*.lproj)
|
||||
// (aligned with previous Electron Forge build config)
|
||||
const keepLanguages = new Set(['en', 'en_GB', 'en-US', 'en_US']);
|
||||
|
||||
// https://www.electron.build/code-signing-mac#how-to-disable-code-signing-during-the-build-process-on-macos
|
||||
if (!hasAppleCertificate) {
|
||||
// Disable auto discovery to keep electron-builder from searching unavailable signing identities
|
||||
@@ -54,7 +58,7 @@ const config = {
|
||||
*/
|
||||
afterPack: async (context) => {
|
||||
// Only process macOS builds
|
||||
if (context.electronPlatformName !== 'darwin') {
|
||||
if (!['darwin', 'mas'].includes(context.electronPlatformName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,6 +72,36 @@ const config = {
|
||||
);
|
||||
const assetsCarDest = path.join(resourcesPath, 'Assets.car');
|
||||
|
||||
// Remove unused Electron Framework localizations to reduce app size
|
||||
// Equivalent to:
|
||||
// ../../Frameworks/Electron Framework.framework/Versions/A/Resources/*.lproj
|
||||
const frameworkResourcePath = path.join(
|
||||
context.appOutDir,
|
||||
`${context.packager.appInfo.productFilename}.app`,
|
||||
'Contents',
|
||||
'Frameworks',
|
||||
'Electron Framework.framework',
|
||||
'Versions',
|
||||
'A',
|
||||
'Resources',
|
||||
);
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(frameworkResourcePath);
|
||||
await Promise.all(
|
||||
entries.map(async (file) => {
|
||||
if (!file.endsWith('.lproj')) return;
|
||||
|
||||
const lang = file.split('.')[0];
|
||||
if (keepLanguages.has(lang)) return;
|
||||
|
||||
await fs.rm(path.join(frameworkResourcePath, file), { force: true, recursive: true });
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// Non-critical: folder may not exist depending on packaging details
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(assetsCarSource);
|
||||
await fs.copyFile(assetsCarSource, assetsCarDest);
|
||||
@@ -106,6 +140,8 @@ const config = {
|
||||
files: [
|
||||
'dist',
|
||||
'resources',
|
||||
// Ensure Next export assets are packaged
|
||||
'dist/next/**/*',
|
||||
'!resources/locales',
|
||||
'!dist/next/docs',
|
||||
'!dist/next/packages',
|
||||
|
||||
@@ -26,14 +26,16 @@
|
||||
"lint": "eslint --cache ",
|
||||
"start": "electron-vite preview",
|
||||
"test": "vitest --run",
|
||||
"typecheck": "tsgo --noEmit -p tsconfig.json"
|
||||
"typecheck": "tsgo --noEmit -p tsconfig.json",
|
||||
"dev": "electron-vite dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.6.2",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"fetch-socks": "^1.3.2",
|
||||
"get-port-please": "^3.2.0",
|
||||
"pdfjs-dist": "4.10.38"
|
||||
"pdfjs-dist": "4.10.38",
|
||||
"superjson": "^2.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
@@ -41,6 +43,7 @@
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/tsconfig": "^2.0.0",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@lobechat/desktop-bridge": "workspace:*",
|
||||
"@lobechat/electron-client-ipc": "workspace:*",
|
||||
"@lobechat/electron-server-ipc": "workspace:*",
|
||||
"@lobechat/file-loaders": "workspace:*",
|
||||
@@ -51,6 +54,7 @@
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/set-cookie-parser": "^2.4.10",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251210.1",
|
||||
"@modelcontextprotocol/sdk": "^1.24.3",
|
||||
"async-retry": "^1.3.3",
|
||||
"consola": "^3.4.2",
|
||||
"cookie": "^1.1.1",
|
||||
@@ -87,4 +91,4 @@
|
||||
"electron-builder"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,5 @@ packages:
|
||||
- '../../packages/electron-server-ipc'
|
||||
- '../../packages/electron-client-ipc'
|
||||
- '../../packages/file-loaders'
|
||||
- '../../packages/desktop-bridge'
|
||||
- '.'
|
||||
|
||||
@@ -12,7 +12,7 @@ export const appBrowsers = {
|
||||
identifier: 'chat',
|
||||
keepAlive: true,
|
||||
minWidth: 400,
|
||||
path: '/chat',
|
||||
path: '/agent',
|
||||
showOnInit: true,
|
||||
titleBarStyle: 'hidden',
|
||||
vibrancy: 'under-window',
|
||||
@@ -72,7 +72,7 @@ export const windowTemplates = {
|
||||
allowMultipleInstances: true,
|
||||
autoHideMenuBar: true,
|
||||
baseIdentifier: 'chatSingle',
|
||||
basePath: '/chat',
|
||||
basePath: '/agent',
|
||||
height: 600,
|
||||
keepAlive: false, // Multi-instance windows don't need to stay alive
|
||||
minWidth: 400,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { app } from 'electron';
|
||||
import { pathExistsSync } from 'fs-extra';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export const mainDir = join(__dirname);
|
||||
@@ -11,7 +12,12 @@ export const buildDir = join(mainDir, '../../build');
|
||||
|
||||
const appPath = app.getAppPath();
|
||||
|
||||
export const nextStandaloneDir = join(appPath, 'dist', 'next');
|
||||
const nextExportOutDir = join(appPath, 'dist', 'next', 'out');
|
||||
const nextExportDefaultDir = join(appPath, 'dist', 'next');
|
||||
|
||||
export const nextExportDir = pathExistsSync(nextExportOutDir)
|
||||
? nextExportOutDir
|
||||
: nextExportDefaultDir;
|
||||
|
||||
export const userDataDir = app.getPath('userData');
|
||||
|
||||
@@ -19,10 +25,6 @@ export const appStorageDir = join(userDataDir, 'lobehub-storage');
|
||||
|
||||
// ------ Application storage directory ---- //
|
||||
|
||||
// db schema hash
|
||||
export const DB_SCHEMA_HASH_FILENAME = 'lobehub-local-db-schema-hash';
|
||||
// pglite database dir
|
||||
export const LOCAL_DATABASE_DIR = 'lobehub-local-db';
|
||||
// 本地存储文件(模拟 S3)
|
||||
export const FILE_STORAGE_DIR = 'file-storage';
|
||||
// Plugin 安装目录
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const ELECTRON_BE_PROTOCOL_SCHEME = 'lobe-backend';
|
||||
@@ -25,7 +25,7 @@ export const defaultProxySettings: NetworkProxySettings = {
|
||||
* 存储默认值
|
||||
*/
|
||||
export const STORE_DEFAULTS: ElectronMainStore = {
|
||||
dataSyncConfig: { storageMode: 'local' },
|
||||
dataSyncConfig: { storageMode: 'cloud' },
|
||||
encryptedTokens: {},
|
||||
locale: 'auto',
|
||||
networkProxy: defaultProxySettings,
|
||||
|
||||
@@ -563,7 +563,7 @@ export default class AuthCtr extends ControllerModule {
|
||||
// Hash codeVerifier using SHA-256
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(codeVerifier);
|
||||
const digest = await crypto.subtle.digest('SHA-256', data);
|
||||
const digest = await crypto.subtle.digest('SHA-256', data as unknown as NodeJS.BufferSource);
|
||||
|
||||
// Convert hash result to base64url encoding
|
||||
const challenge = Buffer.from(digest)
|
||||
|
||||
@@ -0,0 +1,579 @@
|
||||
import { exec } from 'node:child_process';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
import superjson from 'superjson';
|
||||
|
||||
import FileService from '@/services/fileSrv';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
import { MCPClient } from '../libs/mcp/client';
|
||||
import type { MCPClientParams, ToolCallContent, ToolCallResult } from '../libs/mcp/types';
|
||||
import { ControllerModule, IpcMethod } from './index';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
const logger = createLogger('controllers:McpCtr');
|
||||
|
||||
/**
|
||||
* Desktop-only copy of `@lobechat/types`'s `CheckMcpInstallResult`.
|
||||
*
|
||||
* We intentionally keep it local to avoid pulling the web app's path-alias
|
||||
* expectations (e.g. `@/config/*`) into the desktop `tsgo` typecheck.
|
||||
*/
|
||||
interface CheckMcpInstallResult {
|
||||
allDependenciesMet?: boolean;
|
||||
allOptions?: Array<{
|
||||
allDependenciesMet?: boolean;
|
||||
connection?: {
|
||||
args?: string[];
|
||||
command?: string;
|
||||
installationMethod: string;
|
||||
packageName?: string;
|
||||
repositoryUrl?: string;
|
||||
};
|
||||
isRecommended?: boolean;
|
||||
packageInstalled?: boolean;
|
||||
systemDependencies?: Array<{
|
||||
error?: string;
|
||||
installed: boolean;
|
||||
meetRequirement: boolean;
|
||||
name: string;
|
||||
version?: string;
|
||||
}>;
|
||||
}>;
|
||||
configSchema?: any;
|
||||
connection?: {
|
||||
args?: string[];
|
||||
command?: string;
|
||||
type: 'stdio' | 'http';
|
||||
url?: string;
|
||||
};
|
||||
error?: string;
|
||||
isRecommended?: boolean;
|
||||
needsConfig?: boolean;
|
||||
packageInstalled?: boolean;
|
||||
platform: string;
|
||||
success: boolean;
|
||||
systemDependencies?: Array<{
|
||||
error?: string;
|
||||
installed: boolean;
|
||||
meetRequirement: boolean;
|
||||
name: string;
|
||||
version?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface CustomPluginMetadata {
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface GetStdioMcpServerManifestInput {
|
||||
args?: string[];
|
||||
command: string;
|
||||
env?: Record<string, string>;
|
||||
metadata?: CustomPluginMetadata;
|
||||
name: string;
|
||||
type?: 'stdio';
|
||||
}
|
||||
|
||||
interface GetStreamableMcpServerManifestInput {
|
||||
auth?: {
|
||||
accessToken?: string;
|
||||
token?: string;
|
||||
type: 'none' | 'bearer' | 'oauth2';
|
||||
};
|
||||
headers?: Record<string, string>;
|
||||
identifier: string;
|
||||
metadata?: CustomPluginMetadata;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface CallToolInput {
|
||||
args: any;
|
||||
env: any;
|
||||
params: GetStdioMcpServerManifestInput;
|
||||
toolName: string;
|
||||
}
|
||||
|
||||
interface SuperJSONSerialized<T = unknown> {
|
||||
json: T;
|
||||
meta?: any;
|
||||
}
|
||||
|
||||
const isSuperJSONSerialized = (value: unknown): value is SuperJSONSerialized => {
|
||||
if (!value || typeof value !== 'object') return false;
|
||||
return 'json' in value;
|
||||
};
|
||||
|
||||
const deserializePayload = <T>(payload: unknown): T => {
|
||||
// Keep backward compatibility for older renderer builds that might not serialize yet
|
||||
if (isSuperJSONSerialized(payload)) return superjson.deserialize(payload as any) as T;
|
||||
return payload as T;
|
||||
};
|
||||
|
||||
const serializePayload = <T>(payload: T): SuperJSONSerialized =>
|
||||
superjson.serialize(payload) as any;
|
||||
|
||||
const safeParseToRecord = (value: unknown): Record<string, unknown> => {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value))
|
||||
return value as Record<string, unknown>;
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value) as unknown;
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
||||
return parsed as Record<string, unknown>;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const getFileExtensionFromMimeType = (mimeType: string, fallback: string) => {
|
||||
const [, ext] = mimeType.split('/');
|
||||
return ext || fallback;
|
||||
};
|
||||
|
||||
const todayShard = () => new Date().toISOString().split('T')[0];
|
||||
|
||||
const toMarkdown = async (
|
||||
blocks: ToolCallContent[] | null | undefined,
|
||||
getHTTPURL: (key: string) => Promise<string>,
|
||||
) => {
|
||||
if (!blocks) return '';
|
||||
|
||||
const parts = await Promise.all(
|
||||
blocks.map(async (item) => {
|
||||
switch (item.type) {
|
||||
case 'text': {
|
||||
return item.text;
|
||||
}
|
||||
case 'image': {
|
||||
const url = await getHTTPURL(item.data);
|
||||
return ``;
|
||||
}
|
||||
case 'audio': {
|
||||
const url = await getHTTPURL(item.data);
|
||||
return `<resource type="${item.type}" url="${url}" />`;
|
||||
}
|
||||
case 'resource': {
|
||||
return `<resource type="${item.type}">${JSON.stringify(item.resource)}</resource>}`;
|
||||
}
|
||||
default: {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return parts.filter(Boolean).join('\n\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* MCP Controller (Desktop Main Process)
|
||||
* Implements the same routes as `src/server/routers/desktop/mcp.ts`, but via IPC.
|
||||
*/
|
||||
export default class McpCtr extends ControllerModule {
|
||||
static override readonly groupName = 'mcp';
|
||||
|
||||
private get fileService() {
|
||||
return this.app.getService(FileService);
|
||||
}
|
||||
|
||||
private async createClient(params: MCPClientParams) {
|
||||
const client = new MCPClient(params);
|
||||
await client.initialize();
|
||||
return client;
|
||||
}
|
||||
|
||||
private async processContentBlocks(blocks: ToolCallContent[]): Promise<ToolCallContent[]> {
|
||||
return Promise.all(
|
||||
blocks.map(async (block) => {
|
||||
if (block.type !== 'image' && block.type !== 'audio') return block;
|
||||
|
||||
const ext = getFileExtensionFromMimeType(
|
||||
block.mimeType,
|
||||
block.type === 'image' ? 'png' : 'mp3',
|
||||
);
|
||||
|
||||
const base64 = block.data;
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
const hash = createHash('sha256').update(buffer).digest('hex');
|
||||
const id = randomUUID();
|
||||
const filePath = path.posix.join('mcp', `${block.type}s`, todayShard(), `${id}.${ext}`);
|
||||
|
||||
const { metadata } = await this.fileService.uploadFile({
|
||||
content: base64,
|
||||
filename: `${id}.${ext}`,
|
||||
hash,
|
||||
path: filePath,
|
||||
type: block.mimeType,
|
||||
});
|
||||
|
||||
return { ...block, data: metadata.path };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async getStdioMcpServerManifest(payload: SuperJSONSerialized<GetStdioMcpServerManifestInput>) {
|
||||
const input = deserializePayload<GetStdioMcpServerManifestInput>(payload);
|
||||
const params: MCPClientParams = {
|
||||
args: input.args || [],
|
||||
command: input.command,
|
||||
env: input.env,
|
||||
name: input.name,
|
||||
type: 'stdio',
|
||||
};
|
||||
|
||||
const client = await this.createClient(params);
|
||||
try {
|
||||
const manifest = await client.listManifests();
|
||||
const identifier = input.name;
|
||||
|
||||
const tools = manifest.tools || [];
|
||||
|
||||
return serializePayload({
|
||||
api: tools.map((item) => ({
|
||||
description: item.description,
|
||||
name: item.name,
|
||||
parameters: item.inputSchema as any,
|
||||
})),
|
||||
identifier,
|
||||
meta: {
|
||||
avatar: input.metadata?.avatar || 'MCP_AVATAR',
|
||||
description:
|
||||
input.metadata?.description ||
|
||||
`${identifier} MCP server has ` +
|
||||
Object.entries(manifest)
|
||||
.filter(([key]) => ['tools', 'prompts', 'resources'].includes(key))
|
||||
.map(([key, item]) => `${(item as Array<any>)?.length} ${key}`)
|
||||
.join(','),
|
||||
title: input.metadata?.name || identifier,
|
||||
},
|
||||
...manifest,
|
||||
mcpParams: params,
|
||||
type: 'mcp' as any,
|
||||
});
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async getStreamableMcpServerManifest(
|
||||
payload: SuperJSONSerialized<GetStreamableMcpServerManifestInput>,
|
||||
) {
|
||||
const input = deserializePayload<GetStreamableMcpServerManifestInput>(payload);
|
||||
const params: MCPClientParams = {
|
||||
auth: input.auth,
|
||||
headers: input.headers,
|
||||
name: input.identifier,
|
||||
type: 'http',
|
||||
url: input.url,
|
||||
};
|
||||
|
||||
const client = await this.createClient(params);
|
||||
try {
|
||||
const tools = await client.listTools();
|
||||
const identifier = input.identifier;
|
||||
|
||||
return serializePayload({
|
||||
api: tools.map((item) => ({
|
||||
description: item.description,
|
||||
name: item.name,
|
||||
parameters: item.inputSchema as any,
|
||||
})),
|
||||
identifier,
|
||||
mcpParams: params,
|
||||
meta: {
|
||||
avatar: input.metadata?.avatar || 'MCP_AVATAR',
|
||||
description:
|
||||
input.metadata?.description ||
|
||||
`${identifier} MCP server has ${tools.length} tools, like "${tools[0]?.name}"`,
|
||||
title: identifier,
|
||||
},
|
||||
type: 'mcp' as any,
|
||||
});
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async callTool(payload: SuperJSONSerialized<CallToolInput>) {
|
||||
const input = deserializePayload<CallToolInput>(payload);
|
||||
const params: MCPClientParams = {
|
||||
args: input.params.args || [],
|
||||
command: input.params.command,
|
||||
env: input.env,
|
||||
name: input.params.name,
|
||||
type: 'stdio',
|
||||
};
|
||||
|
||||
const client = await this.createClient(params);
|
||||
try {
|
||||
const args = safeParseToRecord(input.args);
|
||||
|
||||
const raw = (await client.callTool(input.toolName, args)) as ToolCallResult;
|
||||
const processed = raw.isError ? raw.content : await this.processContentBlocks(raw.content);
|
||||
|
||||
const content = await toMarkdown(processed, (key) => this.fileService.getFileHTTPURL(key));
|
||||
|
||||
return serializePayload({
|
||||
content,
|
||||
state: { ...raw, content: processed },
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('callTool failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- MCP Install Check (local system) ----------
|
||||
|
||||
private getInstallInstructions(installInstructions: any) {
|
||||
if (!installInstructions) return undefined;
|
||||
|
||||
let current: string | undefined;
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin': {
|
||||
current = installInstructions.macos;
|
||||
break;
|
||||
}
|
||||
case 'linux': {
|
||||
current = installInstructions.linux_debian || installInstructions.linux;
|
||||
break;
|
||||
}
|
||||
case 'win32': {
|
||||
current = installInstructions.windows;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { current, manual: installInstructions.manual };
|
||||
}
|
||||
|
||||
private async checkSystemDependency(dependency: any) {
|
||||
try {
|
||||
const checkCommand = dependency.checkCommand || `${dependency.name} --version`;
|
||||
const { stdout, stderr } = await execPromise(checkCommand);
|
||||
|
||||
if (stderr && !stdout) {
|
||||
return {
|
||||
error: stderr,
|
||||
installInstructions: this.getInstallInstructions(dependency.installInstructions),
|
||||
installed: false,
|
||||
meetRequirement: false,
|
||||
name: dependency.name,
|
||||
requiredVersion: dependency.requiredVersion,
|
||||
};
|
||||
}
|
||||
|
||||
const output = String(stdout || '').trim();
|
||||
let version = output;
|
||||
|
||||
if (dependency.versionParsingRequired) {
|
||||
const versionMatch = output.match(/[Vv]?(\d+(\.\d+)*)/);
|
||||
if (versionMatch) version = versionMatch[0];
|
||||
}
|
||||
|
||||
let meetRequirement = true;
|
||||
|
||||
if (dependency.requiredVersion) {
|
||||
const currentVersion = String(version).replace(/^[Vv]/, '');
|
||||
const currentNum = Number.parseFloat(currentVersion);
|
||||
|
||||
const requirementMatch = String(dependency.requiredVersion).match(/([<=>]+)?(\d+(\.\d+)*)/);
|
||||
if (requirementMatch) {
|
||||
const [, operator = '=', requiredVersion] = requirementMatch;
|
||||
const requiredNum = Number.parseFloat(requiredVersion);
|
||||
switch (operator) {
|
||||
case '>=': {
|
||||
meetRequirement = currentNum >= requiredNum;
|
||||
break;
|
||||
}
|
||||
case '>': {
|
||||
meetRequirement = currentNum > requiredNum;
|
||||
break;
|
||||
}
|
||||
case '<=': {
|
||||
meetRequirement = currentNum <= requiredNum;
|
||||
break;
|
||||
}
|
||||
case '<': {
|
||||
meetRequirement = currentNum < requiredNum;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
meetRequirement = currentNum === requiredNum;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
installInstructions: this.getInstallInstructions(dependency.installInstructions),
|
||||
installed: true,
|
||||
meetRequirement,
|
||||
name: dependency.name,
|
||||
requiredVersion: dependency.requiredVersion,
|
||||
version,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
installInstructions: this.getInstallInstructions(dependency.installInstructions),
|
||||
installed: false,
|
||||
meetRequirement: false,
|
||||
name: dependency.name,
|
||||
requiredVersion: dependency.requiredVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPackageInstalled(installationMethod: string, details: any) {
|
||||
if (installationMethod === 'npm') {
|
||||
const packageName = details?.packageName;
|
||||
if (!packageName) return { installed: false };
|
||||
|
||||
try {
|
||||
const { stdout } = await execPromise(`npm list -g ${packageName} --depth=0`);
|
||||
if (!stdout.includes('(empty)') && stdout.includes(packageName)) return { installed: true };
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await execPromise(`npx -y ${packageName} --version`);
|
||||
return { installed: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
installed: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (installationMethod === 'python') {
|
||||
const packageName = details?.packageName;
|
||||
if (!packageName) return { installed: false };
|
||||
|
||||
const pythonCommand = details?.pythonCommand || 'python';
|
||||
|
||||
try {
|
||||
const command = `${pythonCommand} -m pip list | grep -i "${packageName}"`;
|
||||
const { stdout } = await execPromise(command);
|
||||
if (stdout.trim() && stdout.toLowerCase().includes(String(packageName).toLowerCase())) {
|
||||
return { installed: true };
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
const importCommand = `${pythonCommand} -c "import ${String(packageName).replace('-', '_')}; print('Package installed')"`;
|
||||
const { stdout } = await execPromise(importCommand);
|
||||
if (stdout.includes('Package installed')) return { installed: true };
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return { installed: false };
|
||||
}
|
||||
|
||||
// manual or unknown
|
||||
return { installed: false };
|
||||
}
|
||||
|
||||
private async checkDeployOption(option: any) {
|
||||
const systemDependenciesResults = [];
|
||||
|
||||
if (Array.isArray(option.systemDependencies) && option.systemDependencies.length > 0) {
|
||||
for (const dep of option.systemDependencies) {
|
||||
systemDependenciesResults.push(await this.checkSystemDependency(dep));
|
||||
}
|
||||
}
|
||||
|
||||
const packageResult = await this.checkPackageInstalled(
|
||||
option.installationMethod,
|
||||
option.installationDetails,
|
||||
);
|
||||
const packageInstalled = Boolean((packageResult as any).installed);
|
||||
|
||||
const allDependenciesMet = systemDependenciesResults.every((dep: any) => dep.meetRequirement);
|
||||
|
||||
const configSchema = option.connection?.configSchema;
|
||||
const needsConfig = Boolean(
|
||||
configSchema &&
|
||||
((Array.isArray(configSchema.required) && configSchema.required.length > 0) ||
|
||||
(configSchema.properties &&
|
||||
Object.values(configSchema.properties).some((prop: any) => prop.required === true))),
|
||||
);
|
||||
|
||||
const connection = option.connection?.url
|
||||
? { ...option.connection, type: 'http' }
|
||||
: { ...option.connection, type: 'stdio' };
|
||||
|
||||
return {
|
||||
allDependenciesMet,
|
||||
configSchema,
|
||||
connection,
|
||||
isRecommended: option.isRecommended,
|
||||
needsConfig,
|
||||
packageInstalled,
|
||||
systemDependencies: systemDependenciesResults,
|
||||
};
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async validMcpServerInstallable(
|
||||
payload: SuperJSONSerialized<{
|
||||
deploymentOptions: any[];
|
||||
}>,
|
||||
) {
|
||||
const input = deserializePayload<{ deploymentOptions: any[] }>(payload);
|
||||
try {
|
||||
const options = input.deploymentOptions || [];
|
||||
const results = [];
|
||||
|
||||
for (const option of options) {
|
||||
results.push(await this.checkDeployOption(option));
|
||||
}
|
||||
|
||||
const recommendedResult = results.find((r: any) => r.isRecommended && r.allDependenciesMet);
|
||||
const firstInstallableResult = results.find((r: any) => r.allDependenciesMet);
|
||||
const bestResult = recommendedResult || firstInstallableResult || results[0];
|
||||
|
||||
const checkResult: CheckMcpInstallResult = {
|
||||
...(bestResult || {}),
|
||||
allOptions: results as any,
|
||||
platform: process.platform,
|
||||
success: true,
|
||||
};
|
||||
|
||||
if (bestResult?.needsConfig) {
|
||||
checkResult.needsConfig = true;
|
||||
checkResult.configSchema = bestResult.configSchema;
|
||||
}
|
||||
|
||||
return serializePayload(checkResult);
|
||||
} catch (error) {
|
||||
return serializePayload({
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error when checking MCP plugin installation status',
|
||||
platform: process.platform,
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,24 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
||||
*/
|
||||
private readonly encryptedTokensKey = 'encryptedTokens';
|
||||
|
||||
/**
|
||||
* Normalize legacy config that used local storageMode.
|
||||
* Local mode has been removed; fall back to cloud.
|
||||
*/
|
||||
private normalizeConfig = (config: DataSyncConfig): DataSyncConfig => {
|
||||
if (config.storageMode !== 'local') return config;
|
||||
|
||||
const nextConfig: DataSyncConfig = {
|
||||
...config,
|
||||
remoteServerUrl: config.remoteServerUrl || OFFICIAL_CLOUD_SERVER,
|
||||
storageMode: 'cloud',
|
||||
};
|
||||
|
||||
this.app.storeManager.set('dataSyncConfig', nextConfig);
|
||||
|
||||
return nextConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get remote server configuration
|
||||
*/
|
||||
@@ -54,12 +72,13 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
||||
const { storeManager } = this.app;
|
||||
|
||||
const config: DataSyncConfig = storeManager.get('dataSyncConfig');
|
||||
const normalized = this.normalizeConfig(config);
|
||||
|
||||
logger.debug(
|
||||
`Remote server config: active=${config.active}, storageMode=${config.storageMode}, url=${config.remoteServerUrl}`,
|
||||
`Remote server config: active=${normalized.active}, storageMode=${normalized.storageMode}, url=${normalized.remoteServerUrl}`,
|
||||
);
|
||||
|
||||
return config;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +92,9 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
||||
const { storeManager } = this.app;
|
||||
const prev: DataSyncConfig = storeManager.get('dataSyncConfig');
|
||||
|
||||
// Save configuration
|
||||
storeManager.set('dataSyncConfig', { ...prev, ...config });
|
||||
// Save configuration with legacy local storage fallback
|
||||
const merged = this.normalizeConfig({ ...prev, ...config });
|
||||
storeManager.set('dataSyncConfig', merged);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -88,7 +108,7 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
||||
const { storeManager } = this.app;
|
||||
|
||||
// Clear instance configuration
|
||||
storeManager.set('dataSyncConfig', { storageMode: 'local' });
|
||||
storeManager.set('dataSyncConfig', { active: false, storageMode: 'cloud' });
|
||||
|
||||
// Clear tokens (if any)
|
||||
await this.clearTokens();
|
||||
@@ -468,7 +488,7 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
||||
}
|
||||
|
||||
async getRemoteServerUrl(config?: DataSyncConfig) {
|
||||
const dataConfig = config ? config : await this.getRemoteServerConfig();
|
||||
const dataConfig = this.normalizeConfig(config ? config : await this.getRemoteServerConfig());
|
||||
|
||||
return dataConfig.storageMode === 'cloud' ? OFFICIAL_CLOUD_SERVER : dataConfig.remoteServerUrl;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
ProxyTRPCRequestParams,
|
||||
ProxyTRPCRequestResult,
|
||||
ProxyTRPCStreamRequestParams,
|
||||
} from '@lobechat/electron-client-ipc';
|
||||
import { ProxyTRPCStreamRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import { IpcMainEvent, WebContents, ipcMain } from 'electron';
|
||||
import { HttpProxyAgent } from 'http-proxy-agent';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
@@ -15,7 +11,7 @@ import { defaultProxySettings } from '@/const/store';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
import RemoteServerConfigCtr from './RemoteServerConfigCtr';
|
||||
import { ControllerModule, IpcMethod } from './index';
|
||||
import { ControllerModule } from './index';
|
||||
|
||||
// Create logger
|
||||
const logger = createLogger('controllers:RemoteServerSyncCtr');
|
||||
@@ -174,129 +170,12 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
||||
});
|
||||
|
||||
if (requestBody) {
|
||||
clientReq.write(Buffer.from(requestBody));
|
||||
clientReq.write(Buffer.from(requestBody as string));
|
||||
}
|
||||
|
||||
clientReq.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual request forwarding to the remote server.
|
||||
* Accepts arguments from IPC and returns response details.
|
||||
*/
|
||||
private async forwardRequest(args: {
|
||||
accessToken: string | null;
|
||||
body?: string | ArrayBuffer;
|
||||
headers: Record<string, string>;
|
||||
method: string;
|
||||
remoteServerUrl: string;
|
||||
urlPath: string; // Pass the base URL
|
||||
}): Promise<{
|
||||
// Node headers type
|
||||
body: Buffer;
|
||||
headers: Record<string, string | string[] | undefined>;
|
||||
status: number;
|
||||
statusText: string; // Return body as Buffer
|
||||
}> {
|
||||
const {
|
||||
urlPath,
|
||||
method,
|
||||
headers: originalHeaders,
|
||||
body: requestBody,
|
||||
accessToken,
|
||||
remoteServerUrl,
|
||||
} = args;
|
||||
|
||||
const pathname = new URL(urlPath, remoteServerUrl).pathname; // Extract pathname from URL
|
||||
const logPrefix = `[ForwardRequest ${method} ${pathname}]`; // Add prefix for easier correlation
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error(`${logPrefix} No access token provided`); // Enhanced log
|
||||
return {
|
||||
body: Buffer.from(''),
|
||||
headers: {},
|
||||
status: 401,
|
||||
statusText: 'Authentication required, missing token',
|
||||
};
|
||||
}
|
||||
|
||||
// 1. Determine target URL and prepare request options
|
||||
const targetUrl = new URL(urlPath, remoteServerUrl); // Combine base URL and path
|
||||
const { requestOptions, requester } = this.createRequester({
|
||||
accessToken,
|
||||
headers: originalHeaders,
|
||||
method,
|
||||
url: targetUrl,
|
||||
});
|
||||
|
||||
// 2. Make the request and capture response
|
||||
return new Promise((resolve) => {
|
||||
const clientReq = requester.request(requestOptions, (clientRes: IncomingMessage) => {
|
||||
const chunks: Buffer[] = [];
|
||||
clientRes.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
clientRes.on('end', () => {
|
||||
const responseBody = Buffer.concat(chunks);
|
||||
resolve({
|
||||
// These are IncomingHttpHeaders
|
||||
body: responseBody,
|
||||
|
||||
headers: clientRes.headers,
|
||||
|
||||
status: clientRes.statusCode || 500,
|
||||
statusText: clientRes.statusMessage || 'Unknown Status',
|
||||
});
|
||||
});
|
||||
|
||||
clientRes.on('error', (error) => {
|
||||
// Error during response streaming
|
||||
logger.error(
|
||||
`${logPrefix} Error reading response stream from ${targetUrl.toString()}:`,
|
||||
error,
|
||||
); // Enhanced log
|
||||
// Rejecting might be better, but we need to resolve the outer promise for proxyTRPCRequest
|
||||
resolve({
|
||||
body: Buffer.from(`Error reading response stream: ${error.message}`),
|
||||
headers: {},
|
||||
|
||||
status: 502,
|
||||
// Bad Gateway
|
||||
statusText: 'Error reading response stream',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
clientReq.on('error', (error) => {
|
||||
logger.error(`${logPrefix} Error forwarding request to ${targetUrl.toString()}:`, error); // Enhanced log
|
||||
// Reject or resolve with error status for the outer promise
|
||||
resolve({
|
||||
body: Buffer.from(`Error forwarding request: ${error.message}`),
|
||||
headers: {},
|
||||
|
||||
status: 502,
|
||||
// Bad Gateway
|
||||
statusText: 'Error forwarding request',
|
||||
});
|
||||
});
|
||||
|
||||
// 3. Send request body if present
|
||||
if (requestBody) {
|
||||
if (typeof requestBody === 'string') {
|
||||
clientReq.write(requestBody, 'utf8'); // Specify encoding for strings
|
||||
} else if (requestBody instanceof ArrayBuffer) {
|
||||
clientReq.write(Buffer.from(requestBody)); // Convert ArrayBuffer to Buffer
|
||||
} else {
|
||||
// Should not happen based on type, but handle defensively
|
||||
logger.warn(`${logPrefix} Unsupported request body type received:`, typeof requestBody); // Enhanced log
|
||||
}
|
||||
}
|
||||
|
||||
clientReq.end(); // Finalize the request
|
||||
});
|
||||
}
|
||||
|
||||
private createRequester({
|
||||
headers,
|
||||
accessToken,
|
||||
@@ -341,144 +220,4 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
||||
const requester = url.protocol === 'https:' ? https : http;
|
||||
return { requestOptions, requester };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 'proxy-trpc-request' IPC call from the renderer process.
|
||||
* This method should be invoked by the ipcMain.handle setup in your main process entry point.
|
||||
*/
|
||||
@IpcMethod()
|
||||
public async proxyTRPCRequest(args: ProxyTRPCRequestParams): Promise<ProxyTRPCRequestResult> {
|
||||
logger.debug('Received proxyTRPCRequest IPC call:', {
|
||||
headers: args.headers,
|
||||
method: args.method,
|
||||
urlPath: args.urlPath, // Log headers too for context
|
||||
});
|
||||
|
||||
const url = new URL(args.urlPath, 'http://a.b');
|
||||
const logPrefix = `[ProxyTRPC ${args.method} ${url.pathname}]`; // Prefix for this specific request
|
||||
|
||||
try {
|
||||
const config = await this.remoteServerConfigCtr.getRemoteServerConfig();
|
||||
if (!config.active || (config.storageMode === 'selfHost' && !config.remoteServerUrl)) {
|
||||
logger.warn(
|
||||
`${logPrefix} Remote server sync not active or configured. Rejecting proxy request.`,
|
||||
); // Enhanced log
|
||||
return {
|
||||
body: Buffer.from('Remote server sync not active or configured').buffer,
|
||||
headers: {},
|
||||
|
||||
status: 503,
|
||||
// Service Unavailable
|
||||
statusText: 'Remote server sync not active or configured', // Return ArrayBuffer
|
||||
};
|
||||
}
|
||||
const remoteServerUrl = await this.remoteServerConfigCtr.getRemoteServerUrl();
|
||||
|
||||
// Get initial token
|
||||
let token = await this.remoteServerConfigCtr.getAccessToken();
|
||||
logger.debug(
|
||||
`${logPrefix} Initial token check: ${token ? 'Token exists' : 'No token found'}`,
|
||||
); // Added log
|
||||
|
||||
logger.info(`${logPrefix} Attempting to forward request...`); // Added log
|
||||
let response = await this.forwardRequest({ ...args, accessToken: token, remoteServerUrl });
|
||||
|
||||
// Handle 401: Refresh token and retry if necessary
|
||||
if (response.status === 401) {
|
||||
logger.info(`${logPrefix} Received 401 from forwarded request. Attempting token refresh.`); // Enhanced log
|
||||
const refreshed = await this.refreshTokenIfNeeded(logPrefix); // Pass prefix for context
|
||||
|
||||
if (refreshed) {
|
||||
const newToken = await this.remoteServerConfigCtr.getAccessToken();
|
||||
if (newToken) {
|
||||
logger.info(`${logPrefix} Token refreshed successfully, retrying the request.`); // Enhanced log
|
||||
response = await this.forwardRequest({
|
||||
...args,
|
||||
accessToken: newToken,
|
||||
remoteServerUrl,
|
||||
});
|
||||
} else {
|
||||
logger.error(
|
||||
`${logPrefix} Token refresh reported success, but failed to retrieve new token. Keeping original 401 response.`,
|
||||
); // Enhanced log
|
||||
// Keep the original 401 response
|
||||
}
|
||||
} else {
|
||||
logger.error(`${logPrefix} Token refresh failed. Keeping original 401 response.`); // Enhanced log
|
||||
// Keep the original 401 response
|
||||
}
|
||||
}
|
||||
|
||||
// Convert headers and body to format defined in IPC event
|
||||
const responseHeaders: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(response.headers)) {
|
||||
if (value !== undefined) {
|
||||
responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the final response, ensuring body is serializable (string or ArrayBuffer)
|
||||
const responseBody = response.body; // Buffer
|
||||
|
||||
// IMPORTANT: Check IPC limits. Large bodies might fail. Consider chunking if needed.
|
||||
// Convert Buffer to ArrayBuffer for IPC
|
||||
const finalBody = responseBody.buffer.slice(
|
||||
responseBody.byteOffset,
|
||||
responseBody.byteOffset + responseBody.byteLength,
|
||||
);
|
||||
|
||||
logger.debug(`${logPrefix} Forwarding successful. Status: ${response.status}`); // Added log
|
||||
return {
|
||||
body: finalBody as ArrayBuffer,
|
||||
headers: responseHeaders,
|
||||
status: response.status,
|
||||
statusText: response.statusText, // Return ArrayBuffer
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${logPrefix} Unhandled error processing proxyTRPCRequest:`, error); // Enhanced log
|
||||
// Ensure a serializable error response is returned
|
||||
return {
|
||||
body: Buffer.from(
|
||||
`Internal Server Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
).buffer,
|
||||
headers: {},
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error during proxy', // Return ArrayBuffer
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to refresh the access token by calling the RemoteServerConfigCtr.
|
||||
* @returns Whether token refresh was successful
|
||||
*/
|
||||
private async refreshTokenIfNeeded(callerLogPrefix: string = '[RefreshToken]'): Promise<boolean> {
|
||||
// Added prefix parameter
|
||||
const logPrefix = `${callerLogPrefix} [RefreshTrigger]`; // Updated prefix
|
||||
logger.debug(`${logPrefix} Entered refreshTokenIfNeeded.`);
|
||||
|
||||
try {
|
||||
logger.info(`${logPrefix} Triggering refreshAccessToken in RemoteServerConfigCtr.`);
|
||||
const result = await this.remoteServerConfigCtr.refreshAccessToken();
|
||||
|
||||
if (result.success) {
|
||||
logger.info(`${logPrefix} refreshAccessToken call completed successfully.`);
|
||||
return true;
|
||||
} else {
|
||||
logger.error(`${logPrefix} refreshAccessToken call failed: ${result.error}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`${logPrefix} Exception occurred while calling refreshAccessToken:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources - No protocol handler to unregister anymore
|
||||
*/
|
||||
destroy() {
|
||||
logger.info('Destroying RemoteServerSyncCtr');
|
||||
// Nothing specific to clean up here regarding request handling now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,19 @@ export default class SystemController extends ControllerModule {
|
||||
|
||||
// Apply visual effects to all browser windows when theme mode changes
|
||||
this.app.browserManager.handleAppThemeChange();
|
||||
// Set app theme mode to the system theme mode
|
||||
|
||||
this.setSystemThemeMode(themeMode);
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async getSystemThemeMode() {
|
||||
return nativeTheme.themeSource;
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async setSystemThemeMode(themeMode: ThemeMode) {
|
||||
nativeTheme.themeSource = themeMode === 'auto' ? 'system' : themeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { DB_SCHEMA_HASH_FILENAME, LOCAL_DATABASE_DIR, userDataDir } from '@/const/dir';
|
||||
|
||||
import { ControllerModule, IpcServerMethod } from './index';
|
||||
|
||||
export default class SystemServerCtr extends ControllerModule {
|
||||
static override readonly groupName = 'system';
|
||||
|
||||
@IpcServerMethod()
|
||||
async getDatabasePath() {
|
||||
return join(this.app.appStoragePath, LOCAL_DATABASE_DIR);
|
||||
}
|
||||
|
||||
@IpcServerMethod()
|
||||
async getDatabaseSchemaHash() {
|
||||
try {
|
||||
return readFileSync(this.DB_SCHEMA_HASH_PATH, 'utf8');
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@IpcServerMethod()
|
||||
async getUserDataPath() {
|
||||
return userDataDir;
|
||||
}
|
||||
|
||||
@IpcServerMethod()
|
||||
async setDatabaseSchemaHash(hash: string) {
|
||||
writeFileSync(this.DB_SCHEMA_HASH_PATH, hash, 'utf8');
|
||||
}
|
||||
|
||||
private get DB_SCHEMA_HASH_PATH() {
|
||||
return join(this.app.appStoragePath, DB_SCHEMA_HASH_FILENAME);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,10 @@ describe('RemoteServerConfigCtr', () => {
|
||||
const result = await controller.clearRemoteServerConfig();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockStoreManager.set).toHaveBeenCalledWith('dataSyncConfig', { storageMode: 'local' });
|
||||
expect(mockStoreManager.set).toHaveBeenCalledWith('dataSyncConfig', {
|
||||
active: false,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
expect(mockStoreManager.delete).toHaveBeenCalledWith('encryptedTokens');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
import { ProxyTRPCRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { App } from '@/core/App';
|
||||
|
||||
import RemoteServerSyncCtr from '../RemoteServerSyncCtr';
|
||||
|
||||
// Mock logger
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock electron
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getAppPath: vi.fn(() => '/mock/app/path'),
|
||||
getPath: vi.fn(() => '/mock/user/data'),
|
||||
},
|
||||
ipcMain: {
|
||||
handle: vi.fn(),
|
||||
on: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock electron-is
|
||||
vi.mock('electron-is', () => ({
|
||||
dev: vi.fn(() => false),
|
||||
linux: vi.fn(() => false),
|
||||
macOS: vi.fn(() => false),
|
||||
windows: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
// Mock http and https modules
|
||||
vi.mock('node:http', () => ({
|
||||
default: {
|
||||
request: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('node:https', () => ({
|
||||
default: {
|
||||
request: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock proxy agents
|
||||
vi.mock('http-proxy-agent', () => ({
|
||||
HttpProxyAgent: vi.fn().mockImplementation(() => ({})),
|
||||
}));
|
||||
|
||||
vi.mock('https-proxy-agent', () => ({
|
||||
HttpsProxyAgent: vi.fn().mockImplementation(() => ({})),
|
||||
}));
|
||||
|
||||
// Mock RemoteServerConfigCtr
|
||||
const mockRemoteServerConfigCtr = {
|
||||
getRemoteServerConfig: vi.fn(),
|
||||
getRemoteServerUrl: vi.fn(),
|
||||
getAccessToken: vi.fn(),
|
||||
refreshAccessToken: vi.fn(),
|
||||
};
|
||||
|
||||
const mockStoreManager = {
|
||||
get: vi.fn().mockReturnValue({
|
||||
enableProxy: false,
|
||||
proxyServer: '',
|
||||
proxyPort: '',
|
||||
proxyType: 'http',
|
||||
}),
|
||||
};
|
||||
|
||||
const mockApp = {
|
||||
getController: vi.fn(() => mockRemoteServerConfigCtr),
|
||||
storeManager: mockStoreManager,
|
||||
} as unknown as App;
|
||||
|
||||
describe('RemoteServerSyncCtr', () => {
|
||||
let controller: RemoteServerSyncCtr;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
controller = new RemoteServerSyncCtr(mockApp);
|
||||
});
|
||||
|
||||
describe('proxyTRPCRequest', () => {
|
||||
const baseParams: ProxyTRPCRequestParams = {
|
||||
urlPath: '/trpc/test.query',
|
||||
method: 'GET',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
};
|
||||
|
||||
it('should return 503 when remote server sync is not active', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: false,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(result.status).toBe(503);
|
||||
expect(result.statusText).toBe('Remote server sync not active or configured');
|
||||
});
|
||||
|
||||
it('should return 503 when selfHost mode without remoteServerUrl', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'selfHost',
|
||||
remoteServerUrl: '',
|
||||
});
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(result.status).toBe(503);
|
||||
expect(result.statusText).toBe('Remote server sync not active or configured');
|
||||
});
|
||||
|
||||
it('should return 401 when no access token is available', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken.mockResolvedValue(null);
|
||||
|
||||
// Mock https.request to simulate the forwardRequest behavior
|
||||
const https = await import('node:https');
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
// Simulate response
|
||||
const mockResponse = {
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required, missing token',
|
||||
headers: {},
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from(''));
|
||||
}
|
||||
if (event === 'end') {
|
||||
handler();
|
||||
}
|
||||
}),
|
||||
};
|
||||
callback(mockResponse);
|
||||
return {
|
||||
on: vi.fn(),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(result.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should forward request successfully when configured properly', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken.mockResolvedValue('valid-token');
|
||||
|
||||
const https = await import('node:https');
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
const mockResponse = {
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from('{"success":true}'));
|
||||
}
|
||||
if (event === 'end') {
|
||||
handler();
|
||||
}
|
||||
}),
|
||||
};
|
||||
callback(mockResponse);
|
||||
return {
|
||||
on: vi.fn(),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.statusText).toBe('OK');
|
||||
});
|
||||
|
||||
it('should retry request after token refresh on 401', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken
|
||||
.mockResolvedValueOnce('expired-token')
|
||||
.mockResolvedValueOnce('new-valid-token');
|
||||
mockRemoteServerConfigCtr.refreshAccessToken.mockResolvedValue({ success: true });
|
||||
|
||||
const https = await import('node:https');
|
||||
let callCount = 0;
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
callCount++;
|
||||
const mockResponse = {
|
||||
statusCode: callCount === 1 ? 401 : 200,
|
||||
statusMessage: callCount === 1 ? 'Unauthorized' : 'OK',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from(callCount === 1 ? '' : '{"success":true}'));
|
||||
}
|
||||
if (event === 'end') {
|
||||
handler();
|
||||
}
|
||||
}),
|
||||
};
|
||||
callback(mockResponse);
|
||||
return {
|
||||
on: vi.fn(),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(mockRemoteServerConfigCtr.refreshAccessToken).toHaveBeenCalled();
|
||||
expect(result.status).toBe(200);
|
||||
});
|
||||
|
||||
it('should keep 401 response when token refresh fails', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken.mockResolvedValue('expired-token');
|
||||
mockRemoteServerConfigCtr.refreshAccessToken.mockResolvedValue({
|
||||
success: false,
|
||||
error: 'Refresh failed',
|
||||
});
|
||||
|
||||
const https = await import('node:https');
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
const mockResponse = {
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
headers: {},
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from(''));
|
||||
}
|
||||
if (event === 'end') {
|
||||
handler();
|
||||
}
|
||||
}),
|
||||
};
|
||||
callback(mockResponse);
|
||||
return {
|
||||
on: vi.fn(),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(mockRemoteServerConfigCtr.refreshAccessToken).toHaveBeenCalled();
|
||||
expect(result.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should handle request error gracefully', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken.mockResolvedValue('valid-token');
|
||||
|
||||
const https = await import('node:https');
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
return {
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'error') {
|
||||
handler(new Error('Network error'));
|
||||
}
|
||||
}),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const result = await controller.proxyTRPCRequest(baseParams);
|
||||
|
||||
expect(result.status).toBe(502);
|
||||
expect(result.statusText).toBe('Error forwarding request');
|
||||
});
|
||||
|
||||
it('should include request body when provided', async () => {
|
||||
mockRemoteServerConfigCtr.getRemoteServerConfig.mockResolvedValue({
|
||||
active: true,
|
||||
storageMode: 'cloud',
|
||||
});
|
||||
mockRemoteServerConfigCtr.getRemoteServerUrl.mockResolvedValue('https://api.example.com');
|
||||
mockRemoteServerConfigCtr.getAccessToken.mockResolvedValue('valid-token');
|
||||
|
||||
const https = await import('node:https');
|
||||
const mockWrite = vi.fn();
|
||||
const mockRequest = vi.fn().mockImplementation((options, callback) => {
|
||||
const mockResponse = {
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK',
|
||||
headers: {},
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from('{"success":true}'));
|
||||
}
|
||||
if (event === 'end') {
|
||||
handler();
|
||||
}
|
||||
}),
|
||||
};
|
||||
callback(mockResponse);
|
||||
return {
|
||||
on: vi.fn(),
|
||||
write: mockWrite,
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mocked(https.default.request).mockImplementation(mockRequest);
|
||||
|
||||
const paramsWithBody: ProxyTRPCRequestParams = {
|
||||
...baseParams,
|
||||
method: 'POST',
|
||||
body: '{"data":"test"}',
|
||||
};
|
||||
|
||||
await controller.proxyTRPCRequest(paramsWithBody);
|
||||
|
||||
expect(mockWrite).toHaveBeenCalledWith('{"data":"test"}', 'utf8');
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterAppReady', () => {
|
||||
it('should register stream:start IPC handler', async () => {
|
||||
const { ipcMain } = await import('electron');
|
||||
|
||||
controller.afterAppReady();
|
||||
|
||||
expect(ipcMain.on).toHaveBeenCalledWith('stream:start', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
it('should clean up resources', () => {
|
||||
// destroy method doesn't throw
|
||||
expect(() => controller.destroy()).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { App } from '@/core/App';
|
||||
|
||||
import SystemServerCtr from '../SystemServerCtr';
|
||||
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('node:fs', () => ({
|
||||
readFileSync: vi.fn(),
|
||||
writeFileSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/const/dir', () => ({
|
||||
DB_SCHEMA_HASH_FILENAME: 'db-schema-hash.txt',
|
||||
LOCAL_DATABASE_DIR: 'database',
|
||||
userDataDir: '/mock/user/data',
|
||||
}));
|
||||
|
||||
const mockApp = {
|
||||
appStoragePath: '/mock/storage',
|
||||
} as unknown as App;
|
||||
|
||||
describe('SystemServerCtr', () => {
|
||||
let controller: SystemServerCtr;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
controller = new SystemServerCtr(mockApp);
|
||||
});
|
||||
|
||||
it('returns database path', async () => {
|
||||
await expect(controller.getDatabasePath()).resolves.toBe('/mock/storage/database');
|
||||
});
|
||||
|
||||
it('reads schema hash when file exists', async () => {
|
||||
const { readFileSync } = await import('node:fs');
|
||||
vi.mocked(readFileSync).mockReturnValue('hash123');
|
||||
|
||||
await expect(controller.getDatabaseSchemaHash()).resolves.toBe('hash123');
|
||||
expect(readFileSync).toHaveBeenCalledWith('/mock/storage/db-schema-hash.txt', 'utf8');
|
||||
});
|
||||
|
||||
it('returns undefined when schema hash file missing', async () => {
|
||||
const { readFileSync } = await import('node:fs');
|
||||
vi.mocked(readFileSync).mockImplementation(() => {
|
||||
throw new Error('missing');
|
||||
});
|
||||
|
||||
await expect(controller.getDatabaseSchemaHash()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns user data path', async () => {
|
||||
await expect(controller.getUserDataPath()).resolves.toBe('/mock/user/data');
|
||||
});
|
||||
|
||||
it('writes schema hash to disk', async () => {
|
||||
const { writeFileSync } = await import('node:fs');
|
||||
|
||||
await controller.setDatabaseSchemaHash('newhash');
|
||||
|
||||
expect(writeFileSync).toHaveBeenCalledWith(
|
||||
'/mock/storage/db-schema-hash.txt',
|
||||
'newhash',
|
||||
'utf8',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import AuthCtr from './AuthCtr';
|
||||
import BrowserWindowsCtr from './BrowserWindowsCtr';
|
||||
import DevtoolsCtr from './DevtoolsCtr';
|
||||
import LocalFileCtr from './LocalFileCtr';
|
||||
import McpCtr from './McpCtr';
|
||||
import McpInstallCtr from './McpInstallCtr';
|
||||
import MenuController from './MenuCtr';
|
||||
import NetworkProxyCtr from './NetworkProxyCtr';
|
||||
@@ -13,7 +14,6 @@ import RemoteServerSyncCtr from './RemoteServerSyncCtr';
|
||||
import ShellCommandCtr from './ShellCommandCtr';
|
||||
import ShortcutController from './ShortcutCtr';
|
||||
import SystemController from './SystemCtr';
|
||||
import SystemServerCtr from './SystemServerCtr';
|
||||
import TrayMenuCtr from './TrayMenuCtr';
|
||||
import UpdaterCtr from './UpdaterCtr';
|
||||
import UploadFileCtr from './UploadFileCtr';
|
||||
@@ -24,6 +24,7 @@ export const controllerIpcConstructors = [
|
||||
BrowserWindowsCtr,
|
||||
DevtoolsCtr,
|
||||
LocalFileCtr,
|
||||
McpCtr,
|
||||
McpInstallCtr,
|
||||
MenuController,
|
||||
NetworkProxyCtr,
|
||||
@@ -43,7 +44,6 @@ type DesktopControllerServices = CreateServicesResult<DesktopControllerIpcConstr
|
||||
export type DesktopIpcServices = MergeIpcService<DesktopControllerServices>;
|
||||
|
||||
export const controllerServerIpcConstructors = [
|
||||
SystemServerCtr,
|
||||
UploadFileServerCtr,
|
||||
] as const satisfies readonly IpcServiceConstructor[];
|
||||
|
||||
|
||||
+205
-100
@@ -1,23 +1,31 @@
|
||||
import {
|
||||
DEFAULT_VARIANTS,
|
||||
LOBE_LOCALE_COOKIE,
|
||||
LOBE_THEME_APPEARANCE,
|
||||
Locales,
|
||||
RouteVariants,
|
||||
} from '@lobechat/desktop-bridge';
|
||||
import { ElectronIPCEventHandler, ElectronIPCServer } from '@lobechat/electron-server-ipc';
|
||||
import { Session, app, protocol } from 'electron';
|
||||
import { app, protocol, session } from 'electron';
|
||||
import { macOS, windows } from 'electron-is';
|
||||
import { pathExistsSync, remove } from 'fs-extra';
|
||||
import { pathExistsSync } from 'fs-extra';
|
||||
import os from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { extname, join } from 'node:path';
|
||||
|
||||
import { name } from '@/../../package.json';
|
||||
import { LOCAL_DATABASE_DIR, buildDir, nextStandaloneDir } from '@/const/dir';
|
||||
import { buildDir, nextExportDir } from '@/const/dir';
|
||||
import { isDev } from '@/const/env';
|
||||
import { ELECTRON_BE_PROTOCOL_SCHEME } from '@/const/protocol';
|
||||
import { IControlModule } from '@/controllers';
|
||||
import { IServiceModule } from '@/services';
|
||||
import { getServerMethodMetadata } from '@/utils/ipc';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
import { CustomRequestHandler, createHandler } from '@/utils/next-electron-rsc';
|
||||
|
||||
import { BrowserManager } from './browser/BrowserManager';
|
||||
import { I18nManager } from './infrastructure/I18nManager';
|
||||
import { IoCContainer } from './infrastructure/IoCContainer';
|
||||
import { ProtocolManager } from './infrastructure/ProtocolManager';
|
||||
import { RendererProtocolManager } from './infrastructure/RendererProtocolManager';
|
||||
import { StaticFileServerManager } from './infrastructure/StaticFileServerManager';
|
||||
import { StoreManager } from './infrastructure/StoreManager';
|
||||
import { UpdaterManager } from './infrastructure/UpdaterManager';
|
||||
@@ -35,8 +43,10 @@ type Class<T> = new (...args: any[]) => T;
|
||||
|
||||
const importAll = (r: any) => Object.values(r).map((v: any) => v.default);
|
||||
|
||||
const devDefaultRendererUrl = 'http://localhost:3015';
|
||||
|
||||
export class App {
|
||||
nextServerUrl = 'http://localhost:3015';
|
||||
rendererLoadedUrl: string;
|
||||
|
||||
browserManager: BrowserManager;
|
||||
menuManager: MenuManager;
|
||||
@@ -47,7 +57,13 @@ export class App {
|
||||
trayManager: TrayManager;
|
||||
staticFileServerManager: StaticFileServerManager;
|
||||
protocolManager: ProtocolManager;
|
||||
rendererProtocolManager: RendererProtocolManager;
|
||||
chromeFlags: string[] = ['OverlayScrollbar', 'FluentOverlayScrollbar', 'FluentScrollbar'];
|
||||
/**
|
||||
* Escape hatch: allow testing static renderer in dev via env
|
||||
*/
|
||||
private readonly rendererStaticOverride =
|
||||
process.env.DESKTOP_RENDERER_STATIC === '1' || process.env.DESKTOP_RENDERER_STATIC === 'true';
|
||||
|
||||
/**
|
||||
* whether app is in quiting
|
||||
@@ -79,6 +95,29 @@ export class App {
|
||||
// Initialize store manager
|
||||
this.storeManager = new StoreManager(this);
|
||||
|
||||
this.rendererProtocolManager = new RendererProtocolManager({
|
||||
getExportMimeType: this.getExportMimeType.bind(this),
|
||||
|
||||
nextExportDir,
|
||||
resolveRendererFilePath: this.resolveRendererFilePath.bind(this),
|
||||
});
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
privileges: {
|
||||
allowServiceWorkers: true,
|
||||
corsEnabled: true,
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
},
|
||||
scheme: ELECTRON_BE_PROTOCOL_SCHEME,
|
||||
},
|
||||
this.rendererProtocolManager.protocolScheme,
|
||||
]);
|
||||
|
||||
// Initialize rendererLoadedUrl from RendererProtocolManager
|
||||
this.rendererLoadedUrl = this.rendererProtocolManager.getRendererUrl();
|
||||
|
||||
// load controllers
|
||||
const controllers: IControlModule[] = importAll(
|
||||
import.meta.glob('@/controllers/*Ctr.ts', { eager: true }),
|
||||
@@ -106,9 +145,9 @@ export class App {
|
||||
this.staticFileServerManager = new StaticFileServerManager(this);
|
||||
this.protocolManager = new ProtocolManager(this);
|
||||
|
||||
// register the schema to interceptor url
|
||||
// it should register before app ready
|
||||
this.registerNextHandler();
|
||||
// Configure renderer loading strategy (dev server vs static export)
|
||||
// should register before app ready
|
||||
this.configureRendererLoader();
|
||||
|
||||
// initialize protocol handlers
|
||||
this.protocolManager.initialize();
|
||||
@@ -130,9 +169,6 @@ export class App {
|
||||
|
||||
this.initDevBranding();
|
||||
|
||||
// Clean up stale database lock file before starting IPC server
|
||||
await this.cleanupDatabaseLock();
|
||||
|
||||
// ==============
|
||||
await this.ipcServer.start();
|
||||
logger.debug('IPC server started');
|
||||
@@ -272,53 +308,6 @@ export class App {
|
||||
shortcutMethodMap: ShortcutMethodMap = new Map();
|
||||
protocolHandlerMap: ProtocolHandlerMap = new Map();
|
||||
|
||||
/**
|
||||
* use in next router interceptor in prod browser render
|
||||
*/
|
||||
nextInterceptor: (params: { session: Session }) => () => void;
|
||||
|
||||
/**
|
||||
* Collection of unregister functions for custom request handlers
|
||||
*/
|
||||
private customHandlerUnregisterFns: Array<() => void> = [];
|
||||
|
||||
/**
|
||||
* Function to register custom request handler
|
||||
*/
|
||||
private registerCustomHandlerFn?: (handler: CustomRequestHandler) => () => void;
|
||||
|
||||
/**
|
||||
* Register custom request handler
|
||||
* @param handler Custom request handler function
|
||||
* @returns Function to unregister the handler
|
||||
*/
|
||||
registerRequestHandler = (handler: CustomRequestHandler): (() => void) => {
|
||||
if (!this.registerCustomHandlerFn) {
|
||||
logger.warn('Custom request handler registration is not available');
|
||||
return () => {};
|
||||
}
|
||||
|
||||
logger.debug('Registering custom request handler');
|
||||
const unregisterFn = this.registerCustomHandlerFn(handler);
|
||||
this.customHandlerUnregisterFns.push(unregisterFn);
|
||||
|
||||
return () => {
|
||||
unregisterFn();
|
||||
const index = this.customHandlerUnregisterFns.indexOf(unregisterFn);
|
||||
if (index !== -1) {
|
||||
this.customHandlerUnregisterFns.splice(index, 1);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister all custom request handlers
|
||||
*/
|
||||
unregisterAllRequestHandlers = () => {
|
||||
this.customHandlerUnregisterFns.forEach((unregister) => unregister());
|
||||
this.customHandlerUnregisterFns = [];
|
||||
};
|
||||
|
||||
private addController = (ControllerClass: IControlModule) => {
|
||||
const controller = new ControllerClass(this);
|
||||
this.controllers.set(ControllerClass, controller);
|
||||
@@ -362,56 +351,173 @@ export class App {
|
||||
}
|
||||
};
|
||||
|
||||
private resolveExportFilePath(pathname: string) {
|
||||
// Normalize by removing leading/trailing slashes so extname works as expected
|
||||
const normalizedPath = decodeURIComponent(pathname).replace(/^\/+/, '').replace(/\/$/, '');
|
||||
|
||||
if (!normalizedPath) return join(nextExportDir, 'index.html');
|
||||
|
||||
const basePath = join(nextExportDir, normalizedPath);
|
||||
const ext = extname(normalizedPath);
|
||||
|
||||
// If the request explicitly includes an extension (e.g. html, ico, txt),
|
||||
// treat it as a direct asset without variant injection.
|
||||
if (ext) {
|
||||
return pathExistsSync(basePath) ? basePath : null;
|
||||
}
|
||||
|
||||
const candidates = [`${basePath}.html`, join(basePath, 'index.html'), basePath];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (pathExistsSync(candidate)) return candidate;
|
||||
}
|
||||
|
||||
const fallback404 = join(nextExportDir, '404.html');
|
||||
if (pathExistsSync(fallback404)) return fallback404;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private getExportMimeType(filePath: string) {
|
||||
const ext = extname(filePath).toLowerCase();
|
||||
|
||||
const map: Record<string, string> = {
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.gif': 'image/gif',
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.ico': 'image/x-icon',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.js': 'application/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.map': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml; charset=utf-8',
|
||||
'.txt': 'text/plain; charset=utf-8',
|
||||
'.webp': 'image/webp',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
};
|
||||
|
||||
return map[ext];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up stale database lock file from previous crashes or abnormal exits
|
||||
* Configure renderer loading strategy for dev/prod
|
||||
*/
|
||||
private cleanupDatabaseLock = async () => {
|
||||
private configureRendererLoader() {
|
||||
if (isDev && !this.rendererStaticOverride) {
|
||||
this.rendererLoadedUrl = devDefaultRendererUrl;
|
||||
this.setupDevRenderer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDev && this.rendererStaticOverride) {
|
||||
logger.warn('Dev mode: DESKTOP_RENDERER_STATIC enabled, using static renderer handler');
|
||||
}
|
||||
|
||||
this.setupProdRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Development: use Next dev server directly
|
||||
*/
|
||||
private setupDevRenderer() {
|
||||
logger.info('Development mode: renderer served from Next dev server, no protocol hook');
|
||||
}
|
||||
|
||||
/**
|
||||
* Production: serve static Next export assets
|
||||
*/
|
||||
private setupProdRenderer() {
|
||||
// Use the URL from RendererProtocolManager
|
||||
this.rendererLoadedUrl = this.rendererProtocolManager.getRendererUrl();
|
||||
this.rendererProtocolManager.registerHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve renderer file path in production by combining variant prefix and pathname.
|
||||
* Falls back to default variant when cookies are missing or invalid.
|
||||
*/
|
||||
private async resolveRendererFilePath(url: URL) {
|
||||
const pathname = url.pathname;
|
||||
const normalizedPathname = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
|
||||
// Static assets should be resolved from root (no variant prefix)
|
||||
if (
|
||||
pathname.startsWith('/_next/') ||
|
||||
pathname.startsWith('/static/') ||
|
||||
pathname === '/favicon.ico' ||
|
||||
pathname === '/manifest.json'
|
||||
) {
|
||||
return this.resolveExportFilePath(pathname);
|
||||
}
|
||||
|
||||
// If the incoming path already contains an extension (like .html or .ico),
|
||||
// treat it as a direct asset lookup to avoid double variant prefixes.
|
||||
if (extname(normalizedPathname)) {
|
||||
return this.resolveExportFilePath(pathname);
|
||||
}
|
||||
|
||||
const variant = await this.getRouteVariantFromCookies();
|
||||
const variantPrefixedPath = `/${variant}${pathname}`;
|
||||
|
||||
// Try variant-specific path first, then default variant as fallback
|
||||
return (
|
||||
this.resolveExportFilePath(variantPrefixedPath) ||
|
||||
this.resolveExportFilePath(`/${this.defaultRouteVariant}${pathname}`) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private readonly defaultRouteVariant = RouteVariants.serializeVariants(DEFAULT_VARIANTS);
|
||||
private readonly localeCookieName = LOBE_LOCALE_COOKIE;
|
||||
private readonly themeCookieName = LOBE_THEME_APPEARANCE;
|
||||
|
||||
/**
|
||||
* Build variant string from Electron session cookies to match Next export structure.
|
||||
* Desktop is always treated as non-mobile (0).
|
||||
*/
|
||||
private async getRouteVariantFromCookies(): Promise<string> {
|
||||
try {
|
||||
const dbPath = join(this.appStoragePath, LOCAL_DATABASE_DIR);
|
||||
const lockPath = `${dbPath}.lock`;
|
||||
const cookies = await session.defaultSession.cookies.get({
|
||||
url: `${this.rendererLoadedUrl}/`,
|
||||
});
|
||||
const locale = cookies.find((c) => c.name === this.localeCookieName)?.value;
|
||||
const themeCookie = cookies.find((c) => c.name === this.themeCookieName)?.value;
|
||||
|
||||
if (pathExistsSync(lockPath)) {
|
||||
logger.info(`Cleaning up stale database lock file: ${lockPath}`);
|
||||
await remove(lockPath);
|
||||
logger.info('Database lock file removed successfully');
|
||||
} else {
|
||||
logger.debug('No database lock file found, skipping cleanup');
|
||||
}
|
||||
const serialized = RouteVariants.serializeVariants(
|
||||
RouteVariants.createVariants({
|
||||
isMobile: false,
|
||||
locale: locale as Locales | undefined,
|
||||
theme: themeCookie === 'dark' || themeCookie === 'light' ? themeCookie : undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
return RouteVariants.serializeVariants(RouteVariants.deserializeVariants(serialized));
|
||||
} catch (error) {
|
||||
logger.error('Failed to cleanup database lock file:', error);
|
||||
// Non-fatal error, allow application to continue
|
||||
logger.warn('Failed to read route variant cookies, using default', error);
|
||||
return this.defaultRouteVariant;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private registerNextHandler() {
|
||||
logger.debug('Registering Next.js handler');
|
||||
const handler = createHandler({
|
||||
debug: true,
|
||||
localhostUrl: this.nextServerUrl,
|
||||
protocol,
|
||||
standaloneDir: nextStandaloneDir,
|
||||
});
|
||||
/**
|
||||
* Build renderer URL with variant prefix injected into the path.
|
||||
* In dev mode (without static override), Next.js dev server handles routing automatically.
|
||||
* In prod or dev with static override, we need to inject variant to match export structure: /[variants]/path
|
||||
*/
|
||||
async buildRendererUrl(path: string): Promise<string> {
|
||||
// Ensure path starts with /
|
||||
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
||||
|
||||
// Log output based on development or production mode
|
||||
if (isDev) {
|
||||
logger.info(
|
||||
`Development mode: Custom request handler enabled, but Next.js interception disabled`,
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Production mode: ${this.nextServerUrl} will be intercepted to ${nextStandaloneDir}`,
|
||||
);
|
||||
// In dev mode without static override, use dev server directly (no variant needed)
|
||||
if (isDev && !this.rendererStaticOverride) {
|
||||
return `${this.rendererLoadedUrl}${cleanPath}`;
|
||||
}
|
||||
|
||||
this.nextInterceptor = handler.createInterceptor;
|
||||
|
||||
// Save custom handler registration function
|
||||
if (handler.registerCustomHandler) {
|
||||
this.registerCustomHandlerFn = handler.registerCustomHandler;
|
||||
logger.debug('Custom request handler registration is available');
|
||||
} else {
|
||||
logger.warn('Custom request handler registration is not available');
|
||||
}
|
||||
// In prod or dev with static override, inject variant for static export structure
|
||||
const variant = await this.getRouteVariantFromCookies();
|
||||
return `${this.rendererLoadedUrl}/${variant}.html${cleanPath}`;
|
||||
}
|
||||
|
||||
private initializeServerIpcEvents() {
|
||||
@@ -445,6 +551,5 @@ export class App {
|
||||
|
||||
// 执行清理操作
|
||||
this.staticFileServerManager.destroy();
|
||||
this.unregisterAllRequestHandlers();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { app } from 'electron';
|
||||
import { pathExistsSync, remove } from 'fs-extra';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { LOCAL_DATABASE_DIR } from '@/const/dir';
|
||||
|
||||
// Import after mocks are set up
|
||||
import { App } from '../App';
|
||||
|
||||
@@ -48,16 +43,6 @@ vi.mock('@/utils/logger', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock fs-extra module
|
||||
vi.mock('fs-extra', async () => {
|
||||
const actual = await vi.importActual('fs-extra');
|
||||
return {
|
||||
...actual,
|
||||
pathExistsSync: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock common/routes
|
||||
vi.mock('~common/routes', () => ({
|
||||
findMatchingRoute: vi.fn(),
|
||||
@@ -80,11 +65,9 @@ vi.mock('@/const/env', () => ({
|
||||
|
||||
vi.mock('@/const/dir', () => ({
|
||||
buildDir: '/mock/build',
|
||||
nextStandaloneDir: '/mock/standalone',
|
||||
LOCAL_DATABASE_DIR: 'lobehub-local-db',
|
||||
nextExportDir: '/mock/export/out',
|
||||
appStorageDir: '/mock/storage/path',
|
||||
userDataDir: '/mock/user/data',
|
||||
DB_SCHEMA_HASH_FILENAME: 'lobehub-local-db-schema-hash',
|
||||
FILE_STORAGE_DIR: 'file-storage',
|
||||
INSTALL_PLUGINS_DIR: 'plugins',
|
||||
LOCAL_STORAGE_URL_PREFIX: '/lobe-desktop-file',
|
||||
@@ -159,118 +142,24 @@ vi.mock('../ui/TrayManager', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/next-electron-rsc', () => ({
|
||||
createHandler: vi.fn(() => ({
|
||||
createInterceptor: vi.fn(),
|
||||
registerCustomHandler: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock controllers and services
|
||||
vi.mock('../../controllers/*Ctr.ts', () => ({}));
|
||||
vi.mock('../../services/*Srv.ts', () => ({}));
|
||||
|
||||
describe('App - Database Lock Cleanup', () => {
|
||||
describe('App', () => {
|
||||
let appInstance: App;
|
||||
let mockLockPath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock glob imports to return empty arrays
|
||||
import.meta.glob = vi.fn(() => ({}));
|
||||
|
||||
mockLockPath = join('/mock/storage/path', LOCAL_DATABASE_DIR) + '.lock';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('bootstrap - database lock cleanup', () => {
|
||||
it('should remove stale lock file if it exists during bootstrap', async () => {
|
||||
// Setup: simulate existing lock file
|
||||
vi.mocked(pathExistsSync).mockReturnValue(true);
|
||||
vi.mocked(remove).mockResolvedValue(undefined);
|
||||
|
||||
// Create app instance
|
||||
appInstance = new App();
|
||||
|
||||
// Call bootstrap which should trigger cleanup
|
||||
await appInstance.bootstrap();
|
||||
|
||||
// Verify: lock file check was called
|
||||
expect(pathExistsSync).toHaveBeenCalledWith(mockLockPath);
|
||||
|
||||
// Verify: lock file was removed
|
||||
expect(remove).toHaveBeenCalledWith(mockLockPath);
|
||||
});
|
||||
|
||||
it('should not attempt to remove lock file if it does not exist', async () => {
|
||||
// Setup: no lock file exists
|
||||
vi.mocked(pathExistsSync).mockReturnValue(false);
|
||||
|
||||
// Create app instance
|
||||
appInstance = new App();
|
||||
|
||||
// Call bootstrap
|
||||
await appInstance.bootstrap();
|
||||
|
||||
// Verify: lock file check was called
|
||||
expect(pathExistsSync).toHaveBeenCalledWith(mockLockPath);
|
||||
|
||||
// Verify: remove was NOT called since file doesn't exist
|
||||
expect(remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should continue bootstrap even if lock cleanup fails', async () => {
|
||||
// Setup: simulate lock file exists but cleanup fails
|
||||
vi.mocked(pathExistsSync).mockReturnValue(true);
|
||||
vi.mocked(remove).mockRejectedValue(new Error('Permission denied'));
|
||||
|
||||
// Create app instance
|
||||
appInstance = new App();
|
||||
|
||||
// Bootstrap should not throw even if cleanup fails
|
||||
await expect(appInstance.bootstrap()).resolves.not.toThrow();
|
||||
|
||||
// Verify: cleanup was attempted
|
||||
expect(pathExistsSync).toHaveBeenCalledWith(mockLockPath);
|
||||
expect(remove).toHaveBeenCalledWith(mockLockPath);
|
||||
});
|
||||
|
||||
it('should clean up lock file before starting IPC server', async () => {
|
||||
// Setup
|
||||
vi.mocked(pathExistsSync).mockReturnValue(true);
|
||||
const callOrder: string[] = [];
|
||||
|
||||
vi.mocked(remove).mockImplementation(async () => {
|
||||
callOrder.push('remove');
|
||||
});
|
||||
|
||||
// Mock IPC server start to track call order
|
||||
const { ElectronIPCServer } = await import('@lobechat/electron-server-ipc');
|
||||
const mockStart = vi.fn().mockImplementation(() => {
|
||||
callOrder.push('ipcServer.start');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
vi.mocked(ElectronIPCServer).mockImplementation(
|
||||
() =>
|
||||
({
|
||||
start: mockStart,
|
||||
}) as any,
|
||||
);
|
||||
|
||||
// Create app instance and bootstrap
|
||||
appInstance = new App();
|
||||
await appInstance.bootstrap();
|
||||
|
||||
// Verify: cleanup happens before IPC server starts
|
||||
expect(callOrder).toEqual(['remove', 'ipcServer.start']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appStoragePath', () => {
|
||||
it('should return storage path from store manager', () => {
|
||||
appInstance = new App();
|
||||
|
||||
@@ -2,14 +2,17 @@ import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-c
|
||||
import {
|
||||
BrowserWindow,
|
||||
BrowserWindowConstructorOptions,
|
||||
session as electronSession,
|
||||
ipcMain,
|
||||
nativeTheme,
|
||||
screen,
|
||||
} from 'electron';
|
||||
import console from 'node:console';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { buildDir, preloadDir, resourcesDir } from '@/const/dir';
|
||||
import { isDev, isMac, isWindows } from '@/const/env';
|
||||
import { ELECTRON_BE_PROTOCOL_SCHEME } from '@/const/protocol';
|
||||
import {
|
||||
BACKGROUND_DARK,
|
||||
BACKGROUND_LIGHT,
|
||||
@@ -18,12 +21,15 @@ import {
|
||||
THEME_CHANGE_DELAY,
|
||||
TITLE_BAR_HEIGHT,
|
||||
} from '@/const/theme';
|
||||
import RemoteServerConfigCtr from '@/controllers/RemoteServerConfigCtr';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
import type { App } from '../App';
|
||||
|
||||
// Create logger
|
||||
const logger = createLogger('core:Browser');
|
||||
// Track sessions that already have protocol handlers installed to avoid duplicates
|
||||
const protocolHandledSessions = new WeakSet<Electron.Session>();
|
||||
|
||||
export interface BrowserWindowOpts extends BrowserWindowConstructorOptions {
|
||||
devTools?: boolean;
|
||||
@@ -41,7 +47,6 @@ export default class Browser {
|
||||
private app: App;
|
||||
private _browserWindow?: BrowserWindow;
|
||||
private themeListenerSetup = false;
|
||||
private stopInterceptHandler;
|
||||
identifier: string;
|
||||
options: BrowserWindowOpts;
|
||||
private readonly windowStateKey: string;
|
||||
@@ -167,11 +172,14 @@ export default class Browser {
|
||||
}
|
||||
|
||||
loadUrl = async (path: string) => {
|
||||
const initUrl = this.app.nextServerUrl + path;
|
||||
const initUrl = await this.app.buildRendererUrl(path);
|
||||
|
||||
console.log('[Browser] initUrl', initUrl);
|
||||
|
||||
try {
|
||||
logger.debug(`[${this.identifier}] Attempting to load URL: ${initUrl}`);
|
||||
await this._browserWindow.loadURL(initUrl);
|
||||
|
||||
logger.debug(`[${this.identifier}] Successfully loaded URL: ${initUrl}`);
|
||||
} catch (error) {
|
||||
logger.error(`[${this.identifier}] Failed to load URL (${initUrl}):`, error);
|
||||
@@ -295,7 +303,6 @@ export default class Browser {
|
||||
*/
|
||||
destroy() {
|
||||
logger.debug(`Destroying window instance: ${this.identifier}`);
|
||||
this.stopInterceptHandler?.();
|
||||
this.cleanupThemeListener();
|
||||
this._browserWindow = undefined;
|
||||
}
|
||||
@@ -354,13 +361,10 @@ export default class Browser {
|
||||
// Apply initial visual effects
|
||||
this.applyVisualEffects();
|
||||
|
||||
logger.debug(`[${this.identifier}] Setting up nextInterceptor.`);
|
||||
this.stopInterceptHandler = this.app.nextInterceptor({
|
||||
session: browserWindow.webContents.session,
|
||||
});
|
||||
|
||||
// Setup CORS bypass for local file server
|
||||
this.setupCORSBypass(browserWindow);
|
||||
// Setup request hook for remote server sync (base URL rewrite + OIDC header)
|
||||
this.setupRemoteServerRequestHook(browserWindow);
|
||||
|
||||
logger.debug(`[${this.identifier}] Initiating placeholder and URL loading sequence.`);
|
||||
this.loadPlaceholder().then(() => {
|
||||
@@ -409,8 +413,7 @@ export default class Browser {
|
||||
} catch (error) {
|
||||
logger.error(`[${this.identifier}] Failed to save window state on quit:`, error);
|
||||
}
|
||||
// Need to clean up intercept handler and theme manager
|
||||
this.stopInterceptHandler?.();
|
||||
// Need to clean up theme manager
|
||||
this.cleanupThemeListener();
|
||||
return;
|
||||
}
|
||||
@@ -445,8 +448,7 @@ export default class Browser {
|
||||
} catch (error) {
|
||||
logger.error(`[${this.identifier}] Failed to save window state on close:`, error);
|
||||
}
|
||||
// Need to clean up intercept handler and theme manager
|
||||
this.stopInterceptHandler?.();
|
||||
// Need to clean up theme manager
|
||||
this.cleanupThemeListener();
|
||||
}
|
||||
});
|
||||
@@ -528,4 +530,114 @@ export default class Browser {
|
||||
|
||||
logger.debug(`[${this.identifier}] CORS bypass setup completed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite tRPC requests to remote server and inject OIDC token via webRequest hooks.
|
||||
* Replaces the previous proxyTRPCRequest IPC forwarding.
|
||||
*/
|
||||
private setupRemoteServerRequestHook(browserWindow: BrowserWindow) {
|
||||
const session = browserWindow.webContents.session;
|
||||
const remoteServerConfigCtr = this.app.getController(RemoteServerConfigCtr);
|
||||
const logPrefix = `[${this.identifier}] RemoteServerRequestHook`;
|
||||
|
||||
// Guard to ensure hooks are registered only once per session
|
||||
const targetSession = session || electronSession.defaultSession;
|
||||
if (!targetSession || protocolHandledSessions.has(targetSession)) return;
|
||||
|
||||
const rewriteUrl = async (rawUrl: string) => {
|
||||
let remoteServerUrl: string | undefined;
|
||||
try {
|
||||
const requestUrl = new URL(rawUrl);
|
||||
|
||||
const config = await remoteServerConfigCtr.getRemoteServerConfig();
|
||||
remoteServerUrl = await remoteServerConfigCtr.getRemoteServerUrl(config);
|
||||
const remoteBase = new URL(remoteServerUrl);
|
||||
if (requestUrl.origin === remoteBase.origin) return;
|
||||
|
||||
const rewrittenUrl = new URL(
|
||||
requestUrl.pathname + requestUrl.search,
|
||||
remoteBase,
|
||||
).toString();
|
||||
logger.debug(`${logPrefix} rewrite ${rawUrl} -> ${rewrittenUrl}`);
|
||||
return rewrittenUrl;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`${logPrefix} rewriteUrl error (rawUrl=${rawUrl}, remoteServerUrl=${remoteServerUrl})`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Transparent rewrite via protocol handlers (no HTTP 302)
|
||||
const registerProtocolHandlers = async () => {
|
||||
const proxyHandler = async (request: Request): Promise<Response | null> => {
|
||||
// lobe-backend://lobe/trpc/xxx -> http://<target_host>/trpc/xxx
|
||||
try {
|
||||
const rewrittenUrl = await rewriteUrl(request.url);
|
||||
if (!rewrittenUrl) return null;
|
||||
|
||||
const headers = new Headers(request.headers);
|
||||
|
||||
const token = await remoteServerConfigCtr.getAccessToken();
|
||||
if (token) headers.set('Oidc-Auth', token);
|
||||
|
||||
const requestInit: RequestInit & { duplex?: 'half' } = {
|
||||
headers,
|
||||
method: request.method,
|
||||
};
|
||||
|
||||
// Only forward body for non-GET/HEAD requests
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
const body = request.body ?? undefined;
|
||||
if (body) {
|
||||
requestInit.body = body;
|
||||
// Node.js (undici) requires `duplex` when sending a streaming body
|
||||
requestInit.duplex = 'half';
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamResponse: Response;
|
||||
try {
|
||||
upstreamResponse = await fetch(rewrittenUrl, requestInit);
|
||||
} catch (error) {
|
||||
logger.error(`${logPrefix} upstream fetch failed: ${rewrittenUrl}`, error);
|
||||
|
||||
return new Response('Upstream fetch failed, target url: ' + rewrittenUrl, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
},
|
||||
status: 502,
|
||||
statusText: 'Bad Gateway',
|
||||
});
|
||||
}
|
||||
const responseHeaders = new Headers(upstreamResponse.headers);
|
||||
|
||||
const allowOrigin = request.headers.get('Origin') || undefined;
|
||||
if (allowOrigin) {
|
||||
responseHeaders.set('Access-Control-Allow-Origin', allowOrigin);
|
||||
responseHeaders.set('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
responseHeaders.set('Access-Control-Allow-Headers', '*');
|
||||
|
||||
responseHeaders.set('X-Src-Url', rewrittenUrl);
|
||||
return new Response(upstreamResponse.body, {
|
||||
headers: responseHeaders,
|
||||
status: upstreamResponse.status,
|
||||
statusText: upstreamResponse.statusText,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`${logPrefix} protocol.handle error:`, error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
targetSession.protocol.handle(ELECTRON_BE_PROTOCOL_SCHEME, proxyHandler);
|
||||
logger.debug(`${logPrefix} protocol handler registered for ${ELECTRON_BE_PROTOCOL_SCHEME}`);
|
||||
};
|
||||
|
||||
registerProtocolHandlers();
|
||||
protocolHandledSessions.add(targetSession);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,11 @@ describe('Browser', () => {
|
||||
let mockApp: AppCore;
|
||||
let mockStoreManagerGet: ReturnType<typeof vi.fn>;
|
||||
let mockStoreManagerSet: ReturnType<typeof vi.fn>;
|
||||
let mockNextInterceptor: ReturnType<typeof vi.fn>;
|
||||
let mockRemoteServerConfigCtr: {
|
||||
getAccessToken: ReturnType<typeof vi.fn>;
|
||||
getRemoteServerConfig: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let autoLoadUrlSpy: ReturnType<typeof vi.spyOn> | undefined;
|
||||
|
||||
const defaultOptions: BrowserWindowOpts = {
|
||||
height: 600,
|
||||
@@ -133,14 +137,34 @@ describe('Browser', () => {
|
||||
// Create mock App
|
||||
mockStoreManagerGet = vi.fn().mockReturnValue(undefined);
|
||||
mockStoreManagerSet = vi.fn();
|
||||
mockNextInterceptor = vi.fn().mockReturnValue(vi.fn());
|
||||
|
||||
// Browser setup now installs protocol handlers that depend on RemoteServerConfigCtr
|
||||
mockRemoteServerConfigCtr = {
|
||||
getAccessToken: vi.fn().mockResolvedValue(null),
|
||||
getRemoteServerConfig: vi.fn().mockResolvedValue({
|
||||
remoteServerUrl: 'http://localhost:3000',
|
||||
}),
|
||||
};
|
||||
|
||||
// Ensure Browser can register protocol handlers on the session
|
||||
(mockBrowserWindow.webContents.session as any).protocol = {
|
||||
handle: vi.fn(),
|
||||
};
|
||||
|
||||
mockApp = {
|
||||
browserManager: {
|
||||
retrieveByIdentifier: vi.fn(),
|
||||
},
|
||||
buildRendererUrl: vi.fn(async (path: string) => {
|
||||
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `http://localhost:3000${cleanPath}`;
|
||||
}),
|
||||
getController: vi.fn((ctr: any) => {
|
||||
// Only the remote server config controller is required in these unit tests
|
||||
if (ctr?.name === 'RemoteServerConfigCtr') return mockRemoteServerConfigCtr;
|
||||
throw new Error(`Unexpected controller requested in Browser tests: ${ctr?.name ?? ctr}`);
|
||||
}),
|
||||
isQuiting: false,
|
||||
nextInterceptor: mockNextInterceptor,
|
||||
nextServerUrl: 'http://localhost:3000',
|
||||
storeManager: {
|
||||
get: mockStoreManagerGet,
|
||||
@@ -149,6 +173,8 @@ describe('Browser', () => {
|
||||
} as unknown as AppCore;
|
||||
|
||||
browser = new Browser(defaultOptions, mockApp);
|
||||
// The constructor triggers an async placeholder->loadUrl chain; stub it to avoid cross-test flakiness.
|
||||
autoLoadUrlSpy = vi.spyOn(browser, 'loadUrl').mockResolvedValue(undefined as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -164,10 +190,6 @@ describe('Browser', () => {
|
||||
it('should create BrowserWindow on construction', () => {
|
||||
expect(MockBrowserWindow).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should setup next interceptor', () => {
|
||||
expect(mockNextInterceptor).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('browserWindow getter', () => {
|
||||
@@ -344,12 +366,14 @@ describe('Browser', () => {
|
||||
|
||||
describe('loadUrl', () => {
|
||||
it('should load full URL successfully', async () => {
|
||||
autoLoadUrlSpy?.mockRestore();
|
||||
await browser.loadUrl('/test-path');
|
||||
|
||||
expect(mockBrowserWindow.loadURL).toHaveBeenCalledWith('http://localhost:3000/test-path');
|
||||
});
|
||||
|
||||
it('should load error page on failure', async () => {
|
||||
autoLoadUrlSpy?.mockRestore();
|
||||
mockBrowserWindow.loadURL.mockRejectedValueOnce(new Error('Load failed'));
|
||||
|
||||
await browser.loadUrl('/test-path');
|
||||
@@ -358,6 +382,7 @@ describe('Browser', () => {
|
||||
});
|
||||
|
||||
it('should setup retry handler on error', async () => {
|
||||
autoLoadUrlSpy?.mockRestore();
|
||||
mockBrowserWindow.loadURL.mockRejectedValueOnce(new Error('Load failed'));
|
||||
|
||||
await browser.loadUrl('/test-path');
|
||||
@@ -367,9 +392,13 @@ describe('Browser', () => {
|
||||
});
|
||||
|
||||
it('should load fallback HTML when error page fails', async () => {
|
||||
autoLoadUrlSpy?.mockRestore();
|
||||
mockBrowserWindow.loadURL.mockRejectedValueOnce(new Error('Load failed'));
|
||||
mockBrowserWindow.loadFile.mockRejectedValueOnce(new Error('Error page failed'));
|
||||
mockBrowserWindow.loadURL.mockResolvedValueOnce(undefined);
|
||||
mockBrowserWindow.loadFile.mockImplementation(async (filePath: string) => {
|
||||
if (filePath === '/mock/resources/error.html') throw new Error('Error page failed');
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await browser.loadUrl('/test-path');
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import { app, protocol } from 'electron';
|
||||
import { pathExistsSync } from 'fs-extra';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { basename, extname } from 'node:path';
|
||||
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
type ResolveRendererFilePath = (url: URL) => Promise<string | null>;
|
||||
type GetExportMimeType = (filePath: string) => string | undefined;
|
||||
|
||||
const RENDERER_PROTOCOL_PRIVILEGES = {
|
||||
allowServiceWorkers: true,
|
||||
corsEnabled: true,
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
} as const;
|
||||
|
||||
interface RendererProtocolManagerOptions {
|
||||
getExportMimeType: GetExportMimeType;
|
||||
host?: string;
|
||||
nextExportDir: string;
|
||||
resolveRendererFilePath: ResolveRendererFilePath;
|
||||
scheme?: string;
|
||||
}
|
||||
|
||||
const RENDERER_DIR = 'next';
|
||||
export class RendererProtocolManager {
|
||||
private readonly scheme: string;
|
||||
private readonly host: string;
|
||||
private readonly nextExportDir: string;
|
||||
private readonly resolveRendererFilePath: ResolveRendererFilePath;
|
||||
private readonly getExportMimeType: GetExportMimeType;
|
||||
private handlerRegistered = false;
|
||||
|
||||
constructor(options: RendererProtocolManagerOptions) {
|
||||
const { nextExportDir, resolveRendererFilePath, getExportMimeType } = options;
|
||||
|
||||
this.scheme = 'app';
|
||||
this.host = RENDERER_DIR;
|
||||
this.nextExportDir = nextExportDir;
|
||||
this.resolveRendererFilePath = resolveRendererFilePath;
|
||||
this.getExportMimeType = getExportMimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full renderer URL with scheme and host
|
||||
*/
|
||||
getRendererUrl(): string {
|
||||
return `${this.scheme}://${this.host}`;
|
||||
}
|
||||
|
||||
get protocolScheme() {
|
||||
return {
|
||||
privileges: RENDERER_PROTOCOL_PRIVILEGES,
|
||||
scheme: this.scheme,
|
||||
};
|
||||
}
|
||||
registerHandler() {
|
||||
if (this.handlerRegistered) return;
|
||||
|
||||
if (!pathExistsSync(this.nextExportDir)) {
|
||||
createLogger('core:RendererProtocolManager').warn(
|
||||
`Next export directory not found, skip static handler: ${this.nextExportDir}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const logger = createLogger('core:RendererProtocolManager');
|
||||
logger.debug(
|
||||
`Registering renderer ${this.scheme}:// handler for production export at host ${this.host}`,
|
||||
);
|
||||
|
||||
const register = () => {
|
||||
if (this.handlerRegistered) return;
|
||||
|
||||
protocol.handle(this.scheme, async (request) => {
|
||||
const url = new URL(request.url);
|
||||
const hostname = url.hostname;
|
||||
const pathname = url.pathname;
|
||||
const isAssetRequest = this.isAssetRequest(pathname);
|
||||
const isExplicit404HtmlRequest = pathname.endsWith('/404.html');
|
||||
|
||||
if (hostname !== this.host) {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
const buildFileResponse = async (targetPath: string) => {
|
||||
const buffer = await readFile(targetPath);
|
||||
const headers = new Headers();
|
||||
const mimeType = this.getExportMimeType(targetPath);
|
||||
|
||||
if (mimeType) headers.set('Content-Type', mimeType);
|
||||
|
||||
return new Response(buffer, { headers });
|
||||
};
|
||||
|
||||
const resolveEntryFilePath = () =>
|
||||
this.resolveRendererFilePath(new URL(`${this.scheme}://${this.host}/`));
|
||||
|
||||
let filePath = await this.resolveRendererFilePath(url);
|
||||
|
||||
// If the resolved file is the export 404 page, treat it as missing so we can
|
||||
// fall back to the entry HTML for SPA routing (unless explicitly requested).
|
||||
if (filePath && this.is404Html(filePath) && !isExplicit404HtmlRequest) {
|
||||
filePath = null;
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
if (isAssetRequest) {
|
||||
return new Response('File Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
// Fallback to entry HTML for unknown routes (SPA-like behavior)
|
||||
filePath = await resolveEntryFilePath();
|
||||
if (!filePath || this.is404Html(filePath)) {
|
||||
return new Response('Render file Not Found', { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await buildFileResponse(filePath);
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
|
||||
if (code === 'ENOENT') {
|
||||
logger.warn(`Export asset missing on disk ${filePath}, falling back`, error);
|
||||
|
||||
if (isAssetRequest) {
|
||||
return new Response('File Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
const fallbackPath = await resolveEntryFilePath();
|
||||
if (!fallbackPath || this.is404Html(fallbackPath)) {
|
||||
return new Response('Render file Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
return await buildFileResponse(fallbackPath);
|
||||
} catch (fallbackError) {
|
||||
logger.error(`Failed to serve fallback entry ${fallbackPath}:`, fallbackError);
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(`Failed to serve export asset ${filePath}:`, error);
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
this.handlerRegistered = true;
|
||||
};
|
||||
|
||||
if (app.isReady()) {
|
||||
register();
|
||||
} else {
|
||||
// protocol.handle needs the default session, which is only available after ready
|
||||
|
||||
app.whenReady().then(register);
|
||||
}
|
||||
}
|
||||
|
||||
private isAssetRequest(pathname: string) {
|
||||
const normalizedPathname = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
const ext = extname(normalizedPathname);
|
||||
|
||||
return (
|
||||
pathname.startsWith('/_next/') ||
|
||||
pathname.startsWith('/static/') ||
|
||||
pathname === '/favicon.ico' ||
|
||||
pathname === '/manifest.json' ||
|
||||
!!ext
|
||||
);
|
||||
}
|
||||
|
||||
private is404Html(filePath: string) {
|
||||
return basename(filePath) === '404.html';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { RendererProtocolManager } from '../RendererProtocolManager';
|
||||
|
||||
const {
|
||||
mockApp,
|
||||
mockPathExistsSync,
|
||||
mockProtocol,
|
||||
mockReadFile,
|
||||
protocolHandlerRef,
|
||||
} = vi.hoisted(() => {
|
||||
const protocolHandlerRef = { current: null as any };
|
||||
|
||||
return {
|
||||
mockApp: {
|
||||
isReady: vi.fn().mockReturnValue(true),
|
||||
whenReady: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
mockPathExistsSync: vi.fn().mockReturnValue(true),
|
||||
mockProtocol: {
|
||||
handle: vi.fn((_scheme: string, handler: any) => {
|
||||
protocolHandlerRef.current = handler;
|
||||
}),
|
||||
},
|
||||
mockReadFile: vi.fn(),
|
||||
protocolHandlerRef,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: mockApp,
|
||||
protocol: mockProtocol,
|
||||
}));
|
||||
|
||||
vi.mock('fs-extra', () => ({
|
||||
pathExistsSync: mockPathExistsSync,
|
||||
}));
|
||||
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
readFile: mockReadFile,
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RendererProtocolManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
protocolHandlerRef.current = null;
|
||||
mockApp.isReady.mockReturnValue(true);
|
||||
mockPathExistsSync.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
protocolHandlerRef.current = null;
|
||||
});
|
||||
|
||||
it('should fall back to entry HTML when resolve returns 404.html for non-asset routes', async () => {
|
||||
const resolveRendererFilePath = vi.fn(async (url: URL) => {
|
||||
if (url.pathname === '/missing') return '/export/404.html';
|
||||
if (url.pathname === '/') return '/export/index.html';
|
||||
return null;
|
||||
});
|
||||
const getExportMimeType = vi.fn(() => 'text/html; charset=utf-8');
|
||||
mockReadFile.mockImplementation(async (path: string) => Buffer.from(`content:${path}`));
|
||||
|
||||
const manager = new RendererProtocolManager({
|
||||
getExportMimeType,
|
||||
nextExportDir: '/export',
|
||||
resolveRendererFilePath,
|
||||
});
|
||||
|
||||
manager.registerHandler();
|
||||
expect(mockProtocol.handle).toHaveBeenCalled();
|
||||
const handler = protocolHandlerRef.current;
|
||||
|
||||
const response = await handler({ url: 'app://next/missing' } as any);
|
||||
const body = await response.text();
|
||||
|
||||
expect(resolveRendererFilePath).toHaveBeenCalledTimes(2);
|
||||
expect(resolveRendererFilePath.mock.calls[0][0].pathname).toBe('/missing');
|
||||
expect(resolveRendererFilePath.mock.calls[1][0].pathname).toBe('/');
|
||||
|
||||
expect(mockReadFile).toHaveBeenCalledWith('/export/index.html');
|
||||
expect(body).toContain('/export/index.html');
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it('should serve 404.html when explicitly requested', async () => {
|
||||
const resolveRendererFilePath = vi.fn(async (url: URL) => {
|
||||
if (url.pathname === '/404.html') return '/export/404.html';
|
||||
if (url.pathname === '/') return '/export/index.html';
|
||||
return null;
|
||||
});
|
||||
const getExportMimeType = vi.fn(() => 'text/html; charset=utf-8');
|
||||
mockReadFile.mockImplementation(async (path: string) => Buffer.from(`content:${path}`));
|
||||
|
||||
const manager = new RendererProtocolManager({
|
||||
getExportMimeType,
|
||||
nextExportDir: '/export',
|
||||
resolveRendererFilePath,
|
||||
});
|
||||
|
||||
manager.registerHandler();
|
||||
const handler = protocolHandlerRef.current;
|
||||
|
||||
const response = await handler({ url: 'app://next/404.html' } as any);
|
||||
|
||||
expect(resolveRendererFilePath).toHaveBeenCalledTimes(1);
|
||||
expect(mockReadFile).toHaveBeenCalledWith('/export/404.html');
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it('should return 404 for missing asset requests without fallback', async () => {
|
||||
const resolveRendererFilePath = vi.fn(async (_url: URL) => null);
|
||||
const getExportMimeType = vi.fn();
|
||||
|
||||
const manager = new RendererProtocolManager({
|
||||
getExportMimeType,
|
||||
nextExportDir: '/export',
|
||||
resolveRendererFilePath,
|
||||
});
|
||||
|
||||
manager.registerHandler();
|
||||
const handler = protocolHandlerRef.current;
|
||||
|
||||
const response = await handler({ url: 'app://next/logo.png' } as any);
|
||||
|
||||
expect(resolveRendererFilePath).toHaveBeenCalledTimes(1);
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import {
|
||||
StdioClientTransport,
|
||||
getDefaultEnvironment,
|
||||
} from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import type { Progress } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
import type { MCPClientParams, McpPrompt, McpResource, McpTool, ToolCallResult } from './types';
|
||||
|
||||
const MCP_TOOL_TIMEOUT = (() => {
|
||||
const val = Number(process.env.MCP_TOOL_TIMEOUT);
|
||||
return Number.isFinite(val) && val > 0 ? val : 60_000;
|
||||
})();
|
||||
|
||||
export class MCPClient {
|
||||
private readonly mcp: Client;
|
||||
|
||||
private transport: Transport;
|
||||
|
||||
constructor(params: MCPClientParams) {
|
||||
this.mcp = new Client({ name: 'lobehub-desktop-mcp-client', version: '1.0.0' });
|
||||
|
||||
switch (params.type) {
|
||||
case 'http': {
|
||||
const headers: Record<string, string> = { ...params.headers };
|
||||
|
||||
if (params.auth) {
|
||||
if (params.auth.type === 'bearer' && params.auth.token) {
|
||||
headers['Authorization'] = `Bearer ${params.auth.token}`;
|
||||
}
|
||||
|
||||
if (params.auth.type === 'oauth2' && params.auth.accessToken) {
|
||||
headers['Authorization'] = `Bearer ${params.auth.accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
this.transport = new StreamableHTTPClientTransport(new URL(params.url), {
|
||||
requestInit: { headers },
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'stdio': {
|
||||
this.transport = new StdioClientTransport({
|
||||
args: params.args,
|
||||
command: params.command,
|
||||
env: {
|
||||
...getDefaultEnvironment(),
|
||||
...params.env,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Exhaustive check
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _never: never = params;
|
||||
throw new Error(`Unsupported MCP connection type: ${(params as any).type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isMethodNotFoundError(error: unknown) {
|
||||
const err = error as any;
|
||||
if (!err) return false;
|
||||
if (err.code === -32601) return true;
|
||||
if (typeof err.message === 'string' && err.message.includes('Method not found')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async initialize(options: { onProgress?: (progress: Progress) => void } = {}) {
|
||||
await this.mcp.connect(this.transport, { onprogress: options.onProgress });
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (typeof (this.mcp as any).disconnect === 'function') {
|
||||
await (this.mcp as any).disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.transport && typeof (this.transport as any).close === 'function') {
|
||||
(this.transport as any).close();
|
||||
}
|
||||
}
|
||||
|
||||
async listTools() {
|
||||
const { tools } = await this.mcp.listTools();
|
||||
return (tools || []) as McpTool[];
|
||||
}
|
||||
|
||||
async listResources() {
|
||||
const { resources } = await this.mcp.listResources();
|
||||
return (resources || []) as McpResource[];
|
||||
}
|
||||
|
||||
async listPrompts() {
|
||||
const { prompts } = await this.mcp.listPrompts();
|
||||
return (prompts || []) as McpPrompt[];
|
||||
}
|
||||
|
||||
async listManifests() {
|
||||
const [tools, prompts, resources] = await Promise.all([
|
||||
this.listTools(),
|
||||
this.listPrompts().catch((error) => {
|
||||
if (this.isMethodNotFoundError(error)) return [] as McpPrompt[];
|
||||
throw error;
|
||||
}),
|
||||
this.listResources().catch((error) => {
|
||||
if (this.isMethodNotFoundError(error)) return [] as McpResource[];
|
||||
throw error;
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
prompts: prompts.length === 0 ? undefined : prompts,
|
||||
resources: resources.length === 0 ? undefined : resources,
|
||||
title: this.mcp.getServerVersion()?.title,
|
||||
tools: tools.length === 0 ? undefined : tools,
|
||||
version: this.mcp.getServerVersion()?.version?.replace('v', ''),
|
||||
};
|
||||
}
|
||||
|
||||
async callTool(toolName: string, args: any): Promise<ToolCallResult> {
|
||||
const result = await this.mcp.callTool({ arguments: args, name: toolName }, undefined, {
|
||||
timeout: MCP_TOOL_TIMEOUT,
|
||||
});
|
||||
return result as ToolCallResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
export interface McpTool {
|
||||
description: string;
|
||||
inputSchema: {
|
||||
[k: string]: unknown;
|
||||
properties?: unknown | null;
|
||||
type: 'object';
|
||||
};
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface McpResource {
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
name: string;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export interface McpPromptArgument {
|
||||
description?: string;
|
||||
name: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface McpPrompt {
|
||||
arguments?: McpPromptArgument[];
|
||||
description?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TextContent {
|
||||
_meta?: any;
|
||||
text: string;
|
||||
type: 'text';
|
||||
}
|
||||
|
||||
export interface ImageContent {
|
||||
_meta?: any;
|
||||
/**
|
||||
* Usually base64 data from MCP server (without data: prefix)
|
||||
*/
|
||||
data: string;
|
||||
mimeType: string;
|
||||
type: 'image';
|
||||
}
|
||||
|
||||
export interface AudioContent {
|
||||
_meta?: any;
|
||||
/**
|
||||
* Usually base64 data from MCP server (without data: prefix)
|
||||
*/
|
||||
data: string;
|
||||
mimeType: string;
|
||||
type: 'audio';
|
||||
}
|
||||
|
||||
export interface ResourceContent {
|
||||
_meta?: any;
|
||||
resource: {
|
||||
_meta?: any;
|
||||
blob?: string;
|
||||
mimeType?: string;
|
||||
text?: string;
|
||||
uri: string;
|
||||
};
|
||||
type: 'resource';
|
||||
}
|
||||
|
||||
export interface ResourceLinkContent {
|
||||
_meta?: any;
|
||||
description?: string;
|
||||
icons?: Array<{
|
||||
mimeType?: string;
|
||||
sizes?: string[];
|
||||
src: string;
|
||||
}>;
|
||||
name: string;
|
||||
title?: string;
|
||||
type: 'resource_link';
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export type ToolCallContent =
|
||||
| TextContent
|
||||
| ImageContent
|
||||
| AudioContent
|
||||
| ResourceContent
|
||||
| ResourceLinkContent;
|
||||
|
||||
export interface ToolCallResult {
|
||||
content: ToolCallContent[];
|
||||
isError?: boolean;
|
||||
structuredContent?: any;
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
accessToken?: string;
|
||||
token?: string;
|
||||
type: 'none' | 'bearer' | 'oauth2';
|
||||
}
|
||||
|
||||
export interface HttpMCPClientParams {
|
||||
auth?: AuthConfig;
|
||||
headers?: Record<string, string>;
|
||||
name: string;
|
||||
type: 'http';
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface StdioMCPClientParams {
|
||||
args: string[];
|
||||
command: string;
|
||||
env?: Record<string, string>;
|
||||
name: string;
|
||||
type: 'stdio';
|
||||
}
|
||||
|
||||
export type MCPClientParams = HttpMCPClientParams | StdioMCPClientParams;
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,17 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
return false;
|
||||
};
|
||||
|
||||
const ensureResultsOrSkipAssertions = (results: unknown[], hint: string) => {
|
||||
if (results.length > 0) return true;
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`⚠️ Spotlight returned 0 results for "${hint}". This usually means indexing is incomplete/disabled. Skipping strict assertions.`,
|
||||
);
|
||||
// Keep a minimal assertion so we still validate the call didn't throw.
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
return false;
|
||||
};
|
||||
|
||||
describe('checkSearchServiceStatus', () => {
|
||||
it('should verify Spotlight is available on macOS', async () => {
|
||||
const isAvailable = await searchService.checkSearchServiceStatus();
|
||||
@@ -40,7 +51,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
|
||||
if (!ensureResults(results, 'package.json search')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'package.json')) return;
|
||||
|
||||
// Should find at least one package.json
|
||||
const packageJson = results.find((r) => r.name === 'package.json');
|
||||
@@ -55,7 +66,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
limit: 10,
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
if (!ensureResults(results, 'README search')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'README')) return;
|
||||
|
||||
// Should contain markdown files
|
||||
const mdFile = results.find((r) => r.type === 'md');
|
||||
@@ -70,7 +81,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
|
||||
if (!ensureResults(results, 'TypeScript file search')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'macOS')) return;
|
||||
|
||||
// Should find the macOS.ts implementation file
|
||||
const macOSFile = results.find((r) => r.name.includes('macOS') && r.type === 'ts');
|
||||
@@ -112,7 +123,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
|
||||
if (!ensureResults(results, 'test file search')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'test.ts')) return;
|
||||
|
||||
// Should find test files (can be in __tests__ directory or co-located with source files)
|
||||
const testFile = results.find((r) => r.name.endsWith('.test.ts'));
|
||||
@@ -230,7 +241,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
|
||||
if (!ensureResults(results, 'file metadata read')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'package.json (metadata)')) return;
|
||||
|
||||
const file = results[0];
|
||||
|
||||
@@ -288,7 +299,7 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
onlyIn: repoRoot,
|
||||
});
|
||||
|
||||
if (!ensureResults(results, 'fuzzy search accuracy')) return;
|
||||
if (!ensureResultsOrSkipAssertions(results, 'LocalFile')) return;
|
||||
|
||||
// Should find LocalFileCtr.ts or similar files
|
||||
const found = results.some(
|
||||
@@ -328,8 +339,8 @@ describe.skipIf(process.platform !== 'darwin')('MacOSSearchServiceImpl Integrati
|
||||
});
|
||||
|
||||
// Both searches should find similar files
|
||||
if (!ensureResults(lowerResults, 'case-insensitive search (lower)')) return;
|
||||
if (!ensureResults(upperResults, 'case-insensitive search (upper)')) return;
|
||||
if (!ensureResultsOrSkipAssertions(lowerResults, 'readme')) return;
|
||||
if (!ensureResultsOrSkipAssertions(upperResults, 'README (case-insensitive)')) return;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
// copy from https://github.com/kirill-konshin/next-electron-rsc
|
||||
import { serialize as serializeCookie } from 'cookie';
|
||||
import { type Protocol, type Session } from 'electron';
|
||||
// @ts-ignore
|
||||
import type { NextConfig } from 'next';
|
||||
// @ts-ignore
|
||||
import type NextNodeServer from 'next/dist/server/next-server';
|
||||
import assert from 'node:assert';
|
||||
import { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { Socket } from 'node:net';
|
||||
import path from 'node:path';
|
||||
import { parse } from 'node:url';
|
||||
import resolve from 'resolve';
|
||||
import { parse as parseCookie, splitCookiesString } from 'set-cookie-parser';
|
||||
|
||||
import { LOCAL_STORAGE_URL_PREFIX } from '@/const/dir';
|
||||
import { isDev } from '@/const/env';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
// 创建日志记录器
|
||||
const logger = createLogger('utils:next-electron-rsc');
|
||||
|
||||
// 定义自定义处理器类型
|
||||
export type CustomRequestHandler = (request: Request) => Promise<Response | null | undefined>;
|
||||
|
||||
export const createRequest = async ({
|
||||
socket,
|
||||
request,
|
||||
session,
|
||||
}: {
|
||||
request: Request;
|
||||
session: Session;
|
||||
socket: Socket;
|
||||
}): Promise<IncomingMessage> => {
|
||||
const req = new IncomingMessage(socket);
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Normal Next.js URL does not contain schema and host/port, otherwise endless loops due to butchering of schema by normalizeRepeatedSlashes in resolve-routes
|
||||
req.url = url.pathname + (url.search || '');
|
||||
req.method = request.method;
|
||||
|
||||
request.headers.forEach((value, key) => {
|
||||
req.headers[key] = value;
|
||||
});
|
||||
|
||||
try {
|
||||
// @see https://github.com/electron/electron/issues/39525#issue-1852825052
|
||||
const cookies = await session.cookies.get({
|
||||
url: request.url,
|
||||
// domain: url.hostname,
|
||||
// path: url.pathname,
|
||||
// `secure: true` Cookies should not be sent via http
|
||||
// secure: url.protocol === 'http:' ? false : undefined,
|
||||
// theoretically not possible to implement sameSite because we don't know the url
|
||||
// of the website that is requesting the resource
|
||||
});
|
||||
|
||||
if (cookies.length) {
|
||||
const cookiesHeader = [];
|
||||
|
||||
for (const cookie of cookies) {
|
||||
const { name, value } = cookie;
|
||||
cookiesHeader.push(serializeCookie(name, value));
|
||||
}
|
||||
|
||||
req.headers.cookie = cookiesHeader.join('; ');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('Failed to parse cookies', { cause: e });
|
||||
}
|
||||
|
||||
if (request.body) {
|
||||
req.push(Buffer.from(await request.arrayBuffer()));
|
||||
}
|
||||
|
||||
req.push(null);
|
||||
req.complete = true;
|
||||
|
||||
return req;
|
||||
};
|
||||
|
||||
export class ReadableServerResponse extends ServerResponse {
|
||||
private responsePromise: Promise<Response>;
|
||||
|
||||
constructor(req: IncomingMessage) {
|
||||
super(req);
|
||||
|
||||
this.responsePromise = new Promise<Response>((resolve) => {
|
||||
const readableStream = new ReadableStream({
|
||||
cancel: () => {},
|
||||
pull: () => {
|
||||
this.emit('drain');
|
||||
},
|
||||
start: (controller) => {
|
||||
let onData;
|
||||
|
||||
this.on(
|
||||
'data',
|
||||
(onData = (chunk) => {
|
||||
controller.enqueue(chunk);
|
||||
}),
|
||||
);
|
||||
|
||||
this.once('end', (chunk) => {
|
||||
controller.enqueue(chunk);
|
||||
controller.close();
|
||||
this.off('data', onData);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.once('writeHead', (statusCode) => {
|
||||
resolve(
|
||||
new Response(readableStream, {
|
||||
headers: this.getHeaders() as any,
|
||||
status: statusCode,
|
||||
statusText: this.statusMessage,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
write(chunk: any, ...args): boolean {
|
||||
this.emit('data', chunk);
|
||||
return super.write(chunk, ...args);
|
||||
}
|
||||
|
||||
end(chunk: any, ...args): this {
|
||||
this.emit('end', chunk);
|
||||
return super.end(chunk, ...args);
|
||||
}
|
||||
|
||||
writeHead(statusCode: number, ...args: any): this {
|
||||
this.emit('writeHead', statusCode);
|
||||
return super.writeHead(statusCode, ...args);
|
||||
}
|
||||
|
||||
getResponse() {
|
||||
return this.responsePromise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://nextjs.org/docs/pages/building-your-application/configuring/custom-server
|
||||
* https://github.com/vercel/next.js/pull/68167/files#diff-d0d8b7158bcb066cdbbeb548a29909fe8dc4e98f682a6d88654b1684e523edac
|
||||
* https://github.com/vercel/next.js/blob/canary/examples/custom-server/server.ts
|
||||
*
|
||||
* @param {string} standaloneDir
|
||||
* @param {string} localhostUrl
|
||||
* @param {import('electron').Protocol} protocol
|
||||
* @param {boolean} debug
|
||||
*/
|
||||
export function createHandler({
|
||||
standaloneDir,
|
||||
localhostUrl,
|
||||
protocol,
|
||||
debug = false,
|
||||
}: {
|
||||
debug?: boolean;
|
||||
localhostUrl: string;
|
||||
protocol: Protocol;
|
||||
standaloneDir: string;
|
||||
}) {
|
||||
assert(standaloneDir, 'standaloneDir is required');
|
||||
assert(protocol, 'protocol is required');
|
||||
|
||||
// 存储自定义请求处理器的数组
|
||||
const customHandlers: CustomRequestHandler[] = [];
|
||||
|
||||
// 注册自定义请求处理器的方法 - 在开发和生产环境中都提供此功能
|
||||
function registerCustomHandler(handler: CustomRequestHandler) {
|
||||
logger.debug('Registering custom request handler');
|
||||
customHandlers.push(handler);
|
||||
return () => {
|
||||
const index = customHandlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
logger.debug('Unregistering custom request handler');
|
||||
customHandlers.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let registerProtocolHandle = false;
|
||||
let interceptorCount = 0; // 追踪活跃的拦截器数量
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
privileges: {
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
},
|
||||
scheme: 'http',
|
||||
},
|
||||
]);
|
||||
logger.debug('Registered HTTP scheme as privileged');
|
||||
|
||||
// 初始化 Next.js 应用(仅在生产环境中使用)
|
||||
let app: NextNodeServer | null = null;
|
||||
let handler: any = null;
|
||||
let preparePromise: Promise<void> | null = null;
|
||||
|
||||
if (!isDev) {
|
||||
logger.info('Initializing Next.js app for production');
|
||||
|
||||
// https://github.com/lobehub/lobe-chat/pull/9851
|
||||
// @ts-ignore
|
||||
// noinspection JSConstantReassignment
|
||||
process.env.NODE_ENV = 'production';
|
||||
const next = require(resolve.sync('next', { basedir: standaloneDir }));
|
||||
|
||||
// @see https://github.com/vercel/next.js/issues/64031#issuecomment-2078708340
|
||||
const config = require(path.join(standaloneDir, '.next', 'required-server-files.json'))
|
||||
.config as NextConfig;
|
||||
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config);
|
||||
|
||||
app = next({ dir: standaloneDir }) as NextNodeServer;
|
||||
|
||||
handler = app.getRequestHandler();
|
||||
preparePromise = app.prepare();
|
||||
} else {
|
||||
logger.debug('Starting in development mode');
|
||||
}
|
||||
|
||||
// 通用的请求处理函数 - 开发和生产环境共用
|
||||
const handleRequest = async (
|
||||
request: Request,
|
||||
session: Session,
|
||||
socket: Socket,
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
// 检查是否是本地文件服务请求,如果是则跳过处理
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname.startsWith(LOCAL_STORAGE_URL_PREFIX + '/')) {
|
||||
if (debug) logger.debug(`Skipping local file service request: ${request.url}`);
|
||||
// 直接使用 fetch 转发请求到本地文件服务
|
||||
return fetch(request);
|
||||
}
|
||||
|
||||
// 先尝试使用自定义处理器处理请求
|
||||
for (const customHandler of customHandlers) {
|
||||
try {
|
||||
const response = await customHandler(request);
|
||||
if (response) {
|
||||
if (debug) logger.debug(`Custom handler processed: ${request.url}`);
|
||||
return response;
|
||||
}
|
||||
} catch (error) {
|
||||
if (debug) logger.error(`Custom handler error: ${error}`);
|
||||
// 继续尝试下一个处理器
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 Node.js 请求对象
|
||||
const req = await createRequest({ request, session, socket });
|
||||
// 创建可读取响应的 Response 对象
|
||||
const res = new ReadableServerResponse(req);
|
||||
|
||||
if (isDev) {
|
||||
// 开发环境:转发请求到开发服务器
|
||||
if (debug) logger.debug(`Forwarding request to dev server: ${request.url}`);
|
||||
|
||||
// 修改 URL 以指向开发服务器
|
||||
const devUrl = new URL(req.url, localhostUrl);
|
||||
|
||||
// 使用 node:http 模块发送请求到开发服务器
|
||||
const http = require('node:http');
|
||||
const devReq = http.request(
|
||||
{
|
||||
headers: req.headers,
|
||||
hostname: devUrl.hostname,
|
||||
method: req.method,
|
||||
path: devUrl.pathname + (devUrl.search || ''),
|
||||
port: devUrl.port,
|
||||
},
|
||||
(devRes) => {
|
||||
// 设置响应状态码和头部
|
||||
res.statusCode = devRes.statusCode;
|
||||
res.statusMessage = devRes.statusMessage;
|
||||
|
||||
// 复制响应头
|
||||
Object.keys(devRes.headers).forEach((key) => {
|
||||
res.setHeader(key, devRes.headers[key]);
|
||||
});
|
||||
|
||||
// 流式传输响应内容
|
||||
devRes.pipe(res);
|
||||
},
|
||||
);
|
||||
|
||||
// 处理错误
|
||||
devReq.on('error', (err) => {
|
||||
if (debug) logger.error(`Error forwarding request: ${err}`);
|
||||
});
|
||||
|
||||
// 传输请求体
|
||||
req.pipe(devReq);
|
||||
} else {
|
||||
// 生产环境:使用 Next.js 处理请求
|
||||
if (debug) logger.debug(`Processing with Next.js handler: ${request.url}`);
|
||||
|
||||
// 确保 Next.js 已准备就绪
|
||||
if (preparePromise) await preparePromise;
|
||||
|
||||
const url = parse(req.url, true);
|
||||
handler(req, res, url);
|
||||
}
|
||||
|
||||
// 获取 Response 对象
|
||||
const response = await res.getResponse();
|
||||
|
||||
// 处理 cookies(两种环境通用处理)
|
||||
try {
|
||||
const cookies = parseCookie(
|
||||
response.headers.getSetCookie().reduce((r, c) => {
|
||||
return [...r, ...splitCookiesString(c)];
|
||||
}, []),
|
||||
);
|
||||
|
||||
for (const cookie of cookies) {
|
||||
let expirationDate: number | undefined;
|
||||
|
||||
if (cookie.expires) {
|
||||
// expires 是 Date 对象,转换为秒级时间戳
|
||||
expirationDate = Math.floor(cookie.expires.getTime() / 1000);
|
||||
} else if (cookie.maxAge) {
|
||||
// maxAge 是秒数,计算过期时间戳
|
||||
expirationDate = Math.floor(Date.now() / 1000) + cookie.maxAge;
|
||||
}
|
||||
|
||||
// 如果都没有,则为 session cookie,不设置 expirationDate
|
||||
|
||||
// 检查是否已过期
|
||||
if (expirationDate && expirationDate < Math.floor(Date.now() / 1000)) {
|
||||
await session.cookies.remove(request.url, cookie.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
await session.cookies.set({
|
||||
domain: cookie.domain,
|
||||
expirationDate,
|
||||
httpOnly: cookie.httpOnly,
|
||||
name: cookie.name,
|
||||
path: cookie.path,
|
||||
secure: cookie.secure,
|
||||
url: request.url,
|
||||
value: cookie.value,
|
||||
} as any);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to set cookies', e);
|
||||
}
|
||||
|
||||
if (debug) logger.debug(`Request processed: ${request.url}, status: ${response.status}`);
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (debug) logger.error(`Error handling request: ${e}`);
|
||||
return new Response(e.message, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
// 创建拦截器函数
|
||||
const createInterceptor = ({ session }: { session: Session }) => {
|
||||
assert(session, 'Session is required');
|
||||
logger.debug(
|
||||
`Creating interceptor with session in ${isDev ? 'development' : 'production'} mode`,
|
||||
);
|
||||
|
||||
const socket = new Socket();
|
||||
interceptorCount++; // 增加拦截器计数
|
||||
|
||||
const closeSocket = () => socket.end();
|
||||
|
||||
process.on('SIGTERM', () => closeSocket);
|
||||
process.on('SIGINT', () => closeSocket);
|
||||
|
||||
if (!registerProtocolHandle) {
|
||||
logger.debug(
|
||||
`Registering HTTP protocol handler in ${isDev ? 'development' : 'production'} mode`,
|
||||
);
|
||||
protocol.handle('http', async (request) => {
|
||||
if (!isDev) {
|
||||
// 检查是否是本地文件服务请求,如果是则允许通过
|
||||
const isLocalhost = request.url.startsWith(localhostUrl);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const isLocalIP =
|
||||
request.url.startsWith('http://127.0.0.1:') ||
|
||||
request.url.startsWith('http://localhost:');
|
||||
const isLocalFileService = url.pathname.startsWith(LOCAL_STORAGE_URL_PREFIX + '/');
|
||||
|
||||
const valid = isLocalhost || (isLocalIP && isLocalFileService);
|
||||
if (!valid) {
|
||||
throw new Error('External HTTP not supported, use HTTPS');
|
||||
}
|
||||
}
|
||||
|
||||
return handleRequest(request, session, socket);
|
||||
});
|
||||
registerProtocolHandle = true;
|
||||
}
|
||||
|
||||
logger.debug(`Active interceptors count: ${interceptorCount}`);
|
||||
|
||||
return function stopIntercept() {
|
||||
interceptorCount--; // 减少拦截器计数
|
||||
logger.debug(`Stopping interceptor, remaining count: ${interceptorCount}`);
|
||||
|
||||
// 只有当没有活跃的拦截器时才取消注册协议处理器
|
||||
if (registerProtocolHandle && interceptorCount === 0) {
|
||||
logger.debug('Unregistering HTTP protocol handler (no active interceptors)');
|
||||
protocol.unhandle('http');
|
||||
registerProtocolHandle = false;
|
||||
}
|
||||
|
||||
process.off('SIGTERM', () => closeSocket);
|
||||
process.off('SIGINT', () => closeSocket);
|
||||
closeSocket();
|
||||
};
|
||||
};
|
||||
|
||||
return { createInterceptor, registerCustomHandler };
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
export const filePathToAppUrl = (filePath: string) => {
|
||||
return `app://lobehub.com${pathToFileURL(filePath).pathname}`;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ProxyTRPCRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import type { StreamInvokeRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Mock electron module
|
||||
@@ -29,7 +29,7 @@ describe('onStreamInvoke', () => {
|
||||
});
|
||||
|
||||
it('should set up stream listeners and send start event', () => {
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: { 'content-type': 'application/json' },
|
||||
method: 'POST',
|
||||
urlPath: '/trpc/lambda/test.endpoint',
|
||||
@@ -77,7 +77,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -105,7 +105,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -137,7 +137,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -178,7 +178,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -220,7 +220,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -254,7 +254,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -289,7 +289,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
body: JSON.stringify({
|
||||
filters: { active: true },
|
||||
query: 'complex query',
|
||||
@@ -316,7 +316,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
@@ -346,7 +346,7 @@ describe('onStreamInvoke', () => {
|
||||
onResponse: vi.fn(),
|
||||
};
|
||||
|
||||
const params: ProxyTRPCRequestParams = {
|
||||
const params: StreamInvokeRequestParams = {
|
||||
headers: {},
|
||||
method: 'GET',
|
||||
urlPath: '/trpc/test',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ProxyTRPCRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import type { StreamInvokeRequestParams } from '@lobechat/electron-client-ipc';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface StreamerCallbacks {
|
||||
* @param callbacks The callbacks to handle stream events.
|
||||
*/
|
||||
export const onStreamInvoke = (
|
||||
params: ProxyTRPCRequestParams,
|
||||
params: StreamInvokeRequestParams,
|
||||
callbacks: StreamerCallbacks,
|
||||
): (() => void) => {
|
||||
const requestId = uuid();
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": false,
|
||||
"target": "ESNext",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"esModuleInterop": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@@ -13,9 +15,21 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/main/*"],
|
||||
"~common/*": ["./src/common/*"]
|
||||
"@/*": [
|
||||
"./src/main/*"
|
||||
],
|
||||
"~common/*": [
|
||||
"./src/common/*"
|
||||
],
|
||||
"*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["src/main/**/*", "src/preload/**/*", "src/common/**/*", "electron-builder.js"]
|
||||
}
|
||||
"include": [
|
||||
"src/main/**/*",
|
||||
"src/preload/**/*",
|
||||
"src/common/**/*",
|
||||
"electron-builder.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: >-
|
||||
LobeHub 2.0 is Here
|
||||
|
||||
description: >-
|
||||
LobeHub 2.0 is here, bringing a new level of AI collaboration and productivity.
|
||||
|
||||
tags:
|
||||
- LobeHub
|
||||
- AI Collaboration
|
||||
- Productivity
|
||||
---
|
||||
|
||||
# LobeHub 2.0 is Here 🎉
|
||||
|
||||
After nearly 10 days of meticulous refinement, LobeChat has fully integrated the DeepSeek R1 model in version v1.49.12, offering users a revolutionary interactive experience in the chain of thought!
|
||||
|
||||
## 🚀 Major Updates
|
||||
|
||||
- 🤯 **Comprehensive Support for DeepSeek R1**: Now fully integrated in both the Community and Cloud versions ([lobechat.com](https://lobechat.com)).
|
||||
- 🧠 **Real-Time Chain of Thought Display**: Transparently presents the AI's reasoning process, making the resolution of complex issues clear and visible.
|
||||
- ⚡️ **Deep Thinking Experience**: Utilizing Chain of Thought technology, it provides more insightful AI conversations.
|
||||
- 💫 **Intuitive Problem Analysis**: Makes the analysis of complex issues clear and easy to understand.
|
||||
|
||||
## 🌟 How to Use
|
||||
|
||||
1. Upgrade to LobeChat v1.49.12 or visit [lobechat.com](https://lobechat.com).
|
||||
2. Select the DeepSeek R1 model in the settings.
|
||||
3. Experience a whole new level of intelligent conversation!
|
||||
|
||||
## 📢 Feedback and Support
|
||||
|
||||
If you encounter any issues while using the application or have suggestions for new features, feel free to engage with us through GitHub Discussions. Let's work together to create a better LobeChat!
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: >-
|
||||
LobeHub 2.0 is Here
|
||||
|
||||
description: >-
|
||||
LobeHub 2.0 is here, bringing a new level of AI collaboration and productivity.
|
||||
|
||||
tags:
|
||||
- LobeHub
|
||||
- AI Collaboration
|
||||
- Productivity
|
||||
---
|
||||
|
||||
# LobeHub 2.0 is Here 🎉
|
||||
|
||||
After nearly 10 days of meticulous refinement, LobeChat has fully integrated the DeepSeek R1 model in version v1.49.12, offering users a revolutionary interactive experience in the chain of thought!
|
||||
|
||||
## 🚀 Major Updates
|
||||
|
||||
- 🤯 **Comprehensive Support for DeepSeek R1**: Now fully integrated in both the Community and Cloud versions ([lobechat.com](https://lobechat.com)).
|
||||
- 🧠 **Real-Time Chain of Thought Display**: Transparently presents the AI's reasoning process, making the resolution of complex issues clear and visible.
|
||||
- ⚡️ **Deep Thinking Experience**: Utilizing Chain of Thought technology, it provides more insightful AI conversations.
|
||||
- 💫 **Intuitive Problem Analysis**: Makes the analysis of complex issues clear and easy to understand.
|
||||
|
||||
## 🌟 How to Use
|
||||
|
||||
1. Upgrade to LobeChat v1.49.12 or visit [lobechat.com](https://lobechat.com).
|
||||
2. Select the DeepSeek R1 model in the settings.
|
||||
3. Experience a whole new level of intelligent conversation!
|
||||
|
||||
## 📢 Feedback and Support
|
||||
|
||||
If you encounter any issues while using the application or have suggestions for new features, feel free to engage with us through GitHub Discussions. Let's work together to create a better LobeChat!
|
||||
@@ -2,6 +2,12 @@
|
||||
"$schema": "https://github.com/lobehub/lobe-chat/blob/main/docs/changelog/schema.json",
|
||||
"cloud": [],
|
||||
"community": [
|
||||
{
|
||||
"image": "https://github.com/user-attachments/assets/5fe4c373-ebd0-42a9-bdca-0ab7e0a2e747",
|
||||
"id": "2025-12-15-V2",
|
||||
"date": "2025-12-15",
|
||||
"versionRange": ["1.47.8", "1.49.12"]
|
||||
},
|
||||
{
|
||||
"image": "https://github.com/user-attachments/assets/5fe4c373-ebd0-42a9-bdca-0ab7e0a2e747",
|
||||
"id": "2025-02-02-deepseek-r1",
|
||||
|
||||
@@ -1089,6 +1089,7 @@ table users {
|
||||
first_name text
|
||||
last_name text
|
||||
full_name text
|
||||
occupation text
|
||||
is_onboarded boolean [default: false]
|
||||
clerk_created_at "timestamp with time zone"
|
||||
email_verified boolean [not null, default: false]
|
||||
@@ -1150,7 +1151,6 @@ table user_memories_contexts {
|
||||
associated_objects jsonb
|
||||
associated_subjects jsonb
|
||||
title text
|
||||
title_vector vector(1024)
|
||||
description text
|
||||
description_vector vector(1024)
|
||||
type varchar(255)
|
||||
@@ -1163,7 +1163,6 @@ table user_memories_contexts {
|
||||
updated_at "timestamp with time zone" [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
title_vector [name: 'user_memories_contexts_title_vector_index']
|
||||
description_vector [name: 'user_memories_contexts_description_vector_index']
|
||||
type [name: 'user_memories_contexts_type_index']
|
||||
user_id [name: 'user_memories_contexts_user_id_index']
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"助手": {
|
||||
"en-US": "Agent"
|
||||
},
|
||||
"文稿": {
|
||||
"en-US": "Page"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { KnipConfig } from 'knip';
|
||||
|
||||
const config: KnipConfig = {
|
||||
entry: ['src/app/**/*.ts{x,}'],
|
||||
ignore: [
|
||||
// Test files
|
||||
'src/**/__tests__/**',
|
||||
'src/**/*.test.ts{x,}',
|
||||
'src/**/*.spec.ts{x,}',
|
||||
// Other directories
|
||||
'packages/**',
|
||||
'e2e/**',
|
||||
'scripts/**',
|
||||
// Config files
|
||||
'*.config.{js,ts,mjs,cjs}',
|
||||
'next-env.d.ts',
|
||||
],
|
||||
ignoreDependencies: [],
|
||||
ignoreExportsUsedInFile: true,
|
||||
project: ['src/**/*.ts{x,}'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
+16
-4
@@ -136,7 +136,7 @@
|
||||
"passwordPlaceholder": "يرجى إدخال كلمة المرور",
|
||||
"signinLink": "تسجيل الدخول الآن",
|
||||
"submit": "تسجيل",
|
||||
"subtitle": "انضم إلى مجتمع LobeChat",
|
||||
"subtitle": "ابدأ مساحة التعاون الخاصة بـ Agents",
|
||||
"success": "تم التسجيل بنجاح! يرجى التحقق من بريدك الإلكتروني لتأكيد الحساب",
|
||||
"title": "إنشاء حساب",
|
||||
"usernamePlaceholder": "يرجى إدخال اسم المستخدم"
|
||||
@@ -144,8 +144,7 @@
|
||||
"verifyEmail": {
|
||||
"backToSignIn": "العودة إلى تسجيل الدخول",
|
||||
"checkSpam": "إذا لم تتلقَ البريد الإلكتروني، يرجى التحقق من مجلد الرسائل غير المرغوب فيها",
|
||||
"descriptionPrefix": "لقد أرسلنا رسالة تحقق إلى",
|
||||
"descriptionSuffix": "",
|
||||
"description": "تم إرسال رسالة تحقق إلى {{email}}",
|
||||
"resend": {
|
||||
"button": "إعادة إرسال رسالة التحقق",
|
||||
"error": "فشل الإرسال، يرجى المحاولة لاحقًا",
|
||||
@@ -159,6 +158,11 @@
|
||||
"prevMonth": "الشهر الماضي",
|
||||
"recent30Days": "آخر 30 يومًا"
|
||||
},
|
||||
"footer": {
|
||||
"agreement": "بالمتابعة، فإنك تؤكد أنك قد قرأت ووافقت على <terms>الشروط والأحكام</terms> و<privacy>سياسة الخصوصية</privacy>",
|
||||
"privacy": "سياسة الخصوصية",
|
||||
"terms": "شروط الخدمة"
|
||||
},
|
||||
"header": {
|
||||
"desc": "إدارة معلومات حسابك.",
|
||||
"title": "الحساب"
|
||||
@@ -194,6 +198,9 @@
|
||||
"email": "عنوان البريد الإلكتروني",
|
||||
"fullName": "الاسم الكامل",
|
||||
"fullNameInputHint": "يرجى إدخال الاسم الكامل الجديد",
|
||||
"occupation": "المهنة",
|
||||
"occupationInputHint": "يرجى إدخال مهنتك",
|
||||
"occupationPlaceholder": "مثال: مهندس برمجيات، مدير منتج، مصمم",
|
||||
"password": "كلمة المرور",
|
||||
"resetPasswordError": "فشل إرسال رابط إعادة تعيين كلمة المرور",
|
||||
"resetPasswordSent": "تم إرسال رابط إعادة تعيين كلمة المرور، يرجى التحقق من بريدك الإلكتروني",
|
||||
@@ -215,6 +222,7 @@
|
||||
"title": "تفاصيل الملف الشخصي",
|
||||
"updateAvatar": "تحديث الصورة الشخصية",
|
||||
"updateFullName": "تحديث الاسم الكامل",
|
||||
"updateOccupation": "تحديث المهنة",
|
||||
"updateUsername": "تحديث اسم المستخدم",
|
||||
"username": "اسم المستخدم",
|
||||
"usernameDuplicate": "اسم المستخدم مستخدم بالفعل",
|
||||
@@ -224,6 +232,10 @@
|
||||
"usernameRule": "اسم المستخدم يجب أن يحتوي فقط على أحرف أو أرقام أو شرطة سفلية",
|
||||
"usernameUpdateFailed": "فشل في تحديث اسم المستخدم، يرجى المحاولة لاحقًا"
|
||||
},
|
||||
"signin": {
|
||||
"subtitle": "سجّل أو قم بتسجيل الدخول إلى حسابك في {{appName}}",
|
||||
"title": "مساحة التعاون الخاصة بك في Agents"
|
||||
},
|
||||
"signout": "تسجيل الخروج",
|
||||
"signup": "الاشتراك",
|
||||
"stats": {
|
||||
@@ -269,7 +281,7 @@
|
||||
},
|
||||
"tab": {
|
||||
"apikey": "إدارة مفاتيح API",
|
||||
"profile": "الملف الشخصي",
|
||||
"profile": "حسابي",
|
||||
"security": "الأمان",
|
||||
"stats": "الإحصائيات",
|
||||
"usage": "إحصاءات الاستخدام"
|
||||
|
||||
+54
-16
@@ -3,6 +3,22 @@
|
||||
"title": "النموذج"
|
||||
},
|
||||
"active": "نشط",
|
||||
"agentBuilder": {
|
||||
"installPlugin": {
|
||||
"authRequired": "يتطلب مكون MCP السحابي تسجيل الدخول والمصادقة",
|
||||
"cancel": "إلغاء",
|
||||
"clickApproveToConnect": "انقر على \"الموافقة\" للاتصال وتفويض هذا التكامل",
|
||||
"connectedAndEnabled": "تم الاتصال والتفعيل",
|
||||
"connectionFailed": "فشل الاتصال",
|
||||
"installFailed": "فشل التثبيت",
|
||||
"installPlugin": "تثبيت المكون الإضافي",
|
||||
"installToEnable": "قم بتثبيت هذا المكون الإضافي لتمكين المساعد",
|
||||
"installedAndEnabled": "تم التثبيت والتفعيل",
|
||||
"requiresAuth": "يتطلب تفويضًا، انقر على \"الموافقة\" للاتصال",
|
||||
"retry": "إعادة المحاولة"
|
||||
},
|
||||
"welcome": "مرحبًا، أنا **Lobe AI**، خبير إعداد مساعدك الشخصي. أخبرني بنوع المساعد الذي تريده، وسأقوم بإعداده لك."
|
||||
},
|
||||
"agentDefaultMessage": "مرحبًا، أنا **{{name}}**، يمكنك بدء المحادثة معي على الفور، أو يمكنك الذهاب إلى [إعدادات المساعد]({{url}}) لإكمال معلوماتي.",
|
||||
"agentDefaultMessageWithSystemRole": "مرحبًا، أنا **{{name}}**، كيف يمكنني مساعدتك؟",
|
||||
"agentDefaultMessageWithoutEdit": "مرحبًا، أنا **{{name}}**، كيف يمكنني مساعدتك؟",
|
||||
@@ -16,19 +32,20 @@
|
||||
},
|
||||
"availableAgents": "المساعدون المتاحون",
|
||||
"backToBottom": "العودة إلى الأسفل",
|
||||
"builtinCopilot": "المساعد المدمج",
|
||||
"chatList": {
|
||||
"expandMessage": "عرض الرسائل",
|
||||
"longMessageDetail": "عرض التفاصيل"
|
||||
},
|
||||
"clearCurrentMessages": "مسح رسائل الجلسة الحالية",
|
||||
"confirmClearCurrentMessages": "سيتم مسح رسائل الجلسة الحالية قريبًا، وبمجرد المسح لن يمكن استعادتها، يرجى تأكيد الإجراء الخاص بك",
|
||||
"confirmRemoveChatGroupItemAlert": "سيتم حذف فريق الوكيل هذا، ولن يتأثر الأعضاء الآخرون. يرجى تأكيد الإجراء.",
|
||||
"confirmRemoveChatGroupItemAlert": "سيتم حذف هذه المجموعة، ولن يتأثر أعضاء الفريق. يرجى تأكيد الإجراء.",
|
||||
"confirmRemoveGroupItemAlert": "سيتم حذف هذه المجموعة قريبًا. بعد الحذف، سيُنتقل المساعدون في هذه المجموعة إلى القائمة الافتراضية. يرجى تأكيد إجراء الحذف.",
|
||||
"confirmRemoveGroupSuccess": "تم حذف فريق الوكلاء بنجاح",
|
||||
"confirmRemoveGroupSuccess": "تم حذف المجموعة بنجاح",
|
||||
"confirmRemoveSessionItemAlert": "سيتم حذف هذا المساعد قريبًا، وبمجرد الحذف لن يمكن استعادته، يرجى تأكيد الإجراء الخاص بك",
|
||||
"confirmRemoveSessionSuccess": "تم حذف المساعد بنجاح",
|
||||
"defaultAgent": "المساعد الافتراضي",
|
||||
"defaultGroupChat": "فريق الوكلاء",
|
||||
"defaultGroupChat": "مجموعة",
|
||||
"defaultList": "القائمة الافتراضية",
|
||||
"defaultSession": "المساعد الافتراضي",
|
||||
"dm": {
|
||||
@@ -44,6 +61,7 @@
|
||||
},
|
||||
"duplicateTitle": "{{title}} نسخة",
|
||||
"emptyAgent": "لا يوجد مساعد",
|
||||
"emptyAgentAction": "إنشاء مساعد",
|
||||
"extendParams": {
|
||||
"disableContextCaching": {
|
||||
"desc": "يمكن تقليل تكلفة توليد محادثة واحدة بنسبة تصل إلى 90%، وزيادة سرعة الاستجابة بمقدار 4 مرات (<1>اعرف المزيد</1>). عند التفعيل، سيتم تعطيل حد عدد الرسائل التاريخية تلقائيًا",
|
||||
@@ -89,8 +107,13 @@
|
||||
},
|
||||
"groupDescription": "وصف الفريق",
|
||||
"groupSidebar": {
|
||||
"agentProfile": {
|
||||
"chat": "الدردشة",
|
||||
"model": "النموذج"
|
||||
},
|
||||
"members": {
|
||||
"addMember": "إضافة عضو",
|
||||
"enableOrchestrator": "تفعيل المنسق",
|
||||
"memberSettings": "إعدادات العضو",
|
||||
"orchestrator": "المُنسق",
|
||||
"orchestratorThinking": "المُنسق يفكر...",
|
||||
@@ -120,7 +143,7 @@
|
||||
"noTemplateMembers": "لا يوجد أعضاء في القالب",
|
||||
"noTemplates": "لا توجد قوالب متاحة",
|
||||
"searchTemplates": "ابحث في القوالب...",
|
||||
"title": "إنشاء فريق وكلاء",
|
||||
"title": "إنشاء مجموعة",
|
||||
"useTemplate": "استخدام القالب"
|
||||
},
|
||||
"hideForYou": "تم إخفاء محتوى الرسائل الخاصة، يرجى تفعيل خيار 【عرض محتوى الرسائل الخاصة】 في الإعدادات للعرض",
|
||||
@@ -154,28 +177,29 @@
|
||||
"knowledgeBase": {
|
||||
"all": "جميع المحتويات",
|
||||
"allFiles": "جميع الملفات",
|
||||
"allKnowledgeBases": "جميع قواعد المعرفة",
|
||||
"disabled": "الوضع الحالي للنشر لا يدعم محادثات قاعدة المعرفة. إذا كنت بحاجة إلى استخدامها، يرجى التبديل إلى نشر قاعدة البيانات على الخادم أو استخدام خدمة {{cloud}}.",
|
||||
"allLibraries": "جميع قواعد البيانات",
|
||||
"disabled": "وضع النشر الحالي لا يدعم المحادثة مع قاعدة البيانات. لاستخدام هذه الميزة، يرجى التبديل إلى نشر قاعدة بيانات على الخادم أو استخدام خدمة {{cloud}}",
|
||||
"library": {
|
||||
"action": {
|
||||
"add": "إضافة",
|
||||
"detail": "تفاصيل",
|
||||
"remove": "إزالة"
|
||||
},
|
||||
"title": "الملفات/قاعدة المعرفة"
|
||||
"title": "الملفات / قاعدة البيانات"
|
||||
},
|
||||
"relativeFilesOrKnowledgeBases": "ملفات/قواعد معرفة مرتبطة",
|
||||
"title": "قاعدة المعرفة",
|
||||
"uploadGuide": "يمكنك عرض الملفات التي تم تحميلها في «قاعدة المعرفة»",
|
||||
"relativeFilesOrLibraries": "الملفات / قواعد البيانات المرتبطة",
|
||||
"title": "قاعدة البيانات",
|
||||
"uploadGuide": "يمكنك عرض الملفات التي تم تحميلها في قسم \"الموارد\"",
|
||||
"viewMore": "عرض المزيد"
|
||||
},
|
||||
"memberSelection": {
|
||||
"addMember": "إضافة عضو",
|
||||
"allMembers": "جميع الأعضاء",
|
||||
"createGroup": "إنشاء فريق وكيل",
|
||||
"createGroup": "إنشاء مجموعة",
|
||||
"noAvailableAgents": "لا يوجد وكلاء متاحون للدعوة",
|
||||
"noSelectedAgents": "لم يتم اختيار أي وكيل بعد",
|
||||
"searchAgents": "ابحث عن وكيل...",
|
||||
"selectedAgents": "تم الاختيار ({{count}})",
|
||||
"setInitialMembers": "اختيار أعضاء الفريق"
|
||||
},
|
||||
"members": "الأعضاء",
|
||||
@@ -245,14 +269,15 @@
|
||||
"senderAssistant": "الوكيل",
|
||||
"senderUser": "أنت"
|
||||
},
|
||||
"newAgent": "مساعد جديد",
|
||||
"newGroupChat": "إنشاء فريق وكلاء جديد",
|
||||
"noAgentsYet": "لا يوجد أعضاء في هذا الفريق بعد. انقر على زر + لدعوة مساعد.",
|
||||
"newAgent": "إنشاء مساعد",
|
||||
"newGroupChat": "إنشاء مجموعة",
|
||||
"newPage": "إنشاء مستند",
|
||||
"noAgentsYet": "لا يوجد أعضاء في هذه المجموعة بعد. انقر على زر + لدعوة مساعد.",
|
||||
"noAvailableAgents": "لا يوجد أعضاء متاحون للدعوة",
|
||||
"noMatchingAgents": "لا يوجد أعضاء مطابقون",
|
||||
"noMembersYet": "لا يوجد أعضاء في هذه المجموعة بعد. انقر على زر + لدعوة المساعدين.",
|
||||
"noSelectedAgents": "لم يتم اختيار أي أعضاء بعد",
|
||||
"openInNewWindow": "افتح الصفحة في نافذة جديدة",
|
||||
"openInNewWindow": "فتح في نافذة مستقلة",
|
||||
"owner": "مالك المجموعة",
|
||||
"pin": "تثبيت",
|
||||
"pinOff": "إلغاء التثبيت",
|
||||
@@ -296,7 +321,7 @@
|
||||
"searchAgentPlaceholder": "مساعد البحث...",
|
||||
"searchAgents": "مساعد البحث...",
|
||||
"selectedAgents": "المساعدون المختارون",
|
||||
"sendPlaceholder": "أدخل محتوى الدردشة...",
|
||||
"sendPlaceholder": "اطرح سؤالًا، أنشئ محتوى، أو ابدأ مهمة، <hotkey><hotkey/>",
|
||||
"sessionGroup": {
|
||||
"config": "إدارة المجموعات",
|
||||
"confirmRemoveGroupAlert": "سيتم حذف هذه المجموعة قريبًا، وبعد الحذف، سيتم نقل مساعدي هذه المجموعة إلى القائمة الافتراضية، يرجى تأكيد إجراءك",
|
||||
@@ -306,11 +331,17 @@
|
||||
"createGroupSuccess": "تم إنشاء المحادثة الجماعية بنجاح",
|
||||
"createSuccess": "تم الإنشاء بنجاح",
|
||||
"creatingAgent": "جاري إنشاء المساعد...",
|
||||
"groupName": "اسم المجموعة",
|
||||
"inputPlaceholder": "الرجاء إدخال اسم المجموعة...",
|
||||
"moveGroup": "نقل إلى مجموعة",
|
||||
"newGroup": "مجموعة جديدة",
|
||||
"noAvailableAgents": "لا يوجد مساعدين متاحين حالياً",
|
||||
"noMatchingAgents": "لم يتم العثور على مساعدين مطابقين",
|
||||
"noSelectedAgents": "يرجى اختيار مساعدين",
|
||||
"rename": "إعادة تسمية المجموعة",
|
||||
"renameSuccess": "تمت إعادة التسمية بنجاح",
|
||||
"searchAgents": "البحث عن مساعدين",
|
||||
"selectedAgents": "المساعدون المختارون ({{count}})",
|
||||
"sortSuccess": "تمت إعادة ترتيب بنجاح",
|
||||
"sorting": "جاري تحديث ترتيب المجموعة...",
|
||||
"tooLong": "يجب أن يكون طول اسم المجموعة بين 1 و 20"
|
||||
@@ -361,6 +392,11 @@
|
||||
"title": "المهام المنجزة"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"groupProfile": "ملف المجموعة",
|
||||
"profile": "ملف المساعد",
|
||||
"search": "بحث"
|
||||
},
|
||||
"thread": {
|
||||
"divider": "موضوع فرعي",
|
||||
"threadMessageCount": "{{messageCount}} رسالة",
|
||||
@@ -413,6 +449,7 @@
|
||||
"checkOpenNewTopic": "هل ترغب في فتح موضوع جديد؟",
|
||||
"checkSaveCurrentMessages": "هل ترغب في حفظ الدردشة الحالية كموضوع؟",
|
||||
"openNewTopic": "فتح موضوع جديد",
|
||||
"recent": "المواضيع الأخيرة",
|
||||
"saveCurrentMessages": "حفظ الجلسة الحالية كموضوع"
|
||||
},
|
||||
"translate": {
|
||||
@@ -424,6 +461,7 @@
|
||||
"clear": "مسح الصوت"
|
||||
},
|
||||
"untitledAgent": "مساعد بدون اسم",
|
||||
"untitledGroup": "مجموعة بدون عنوان",
|
||||
"updateAgent": "تحديث معلومات المساعد",
|
||||
"upload": {
|
||||
"action": {
|
||||
|
||||
+41
-4
@@ -137,14 +137,36 @@
|
||||
"close": "إغلاق",
|
||||
"cmdk": {
|
||||
"about": "حول",
|
||||
"aiModeEmptyState": "أدخل سؤالك في الحقل أعلاه لبدء المحادثة مع الذكاء الاصطناعي",
|
||||
"aiModePlaceholder": "اطرح سؤالاً على الذكاء الاصطناعي...",
|
||||
"communitySupport": "دعم المجتمع",
|
||||
"discover": "استكشاف",
|
||||
"knowledgeBase": "قاعدة المعرفة",
|
||||
"memory": "الذاكرة",
|
||||
"navigate": "التنقل",
|
||||
"newAgent": "إنشاء مساعد جديد",
|
||||
"noResults": "لم يتم العثور على نتائج",
|
||||
"openSettings": "فتح الإعدادات",
|
||||
"painting": "الرسم بالذكاء الاصطناعي",
|
||||
"pages": "المستندات",
|
||||
"painting": "الرسم",
|
||||
"resource": "الموارد",
|
||||
"search": {
|
||||
"agent": "مساعد",
|
||||
"agents": "مساعدون",
|
||||
"assistant": "مساعد الذكاء الاصطناعي",
|
||||
"assistants": "مساعدو الذكاء الاصطناعي",
|
||||
"file": "ملف",
|
||||
"files": "ملفات",
|
||||
"loading": "جارٍ البحث...",
|
||||
"mcp": "خادم MCP",
|
||||
"mcps": "خوادم MCP",
|
||||
"message": "المحادثة",
|
||||
"messages": "المحادثات",
|
||||
"plugin": "الملحق",
|
||||
"plugins": "الملحقات",
|
||||
"searching": "نتائج البحث",
|
||||
"topic": "موضوع",
|
||||
"topics": "مواضيع"
|
||||
},
|
||||
"searchPlaceholder": "أدخل أمرًا أو ابحث...",
|
||||
"settings": "الإعدادات",
|
||||
"starOnGitHub": "قيّمنا على GitHub",
|
||||
@@ -304,6 +326,13 @@
|
||||
"business": "شراكات تجارية",
|
||||
"support": "الدعم عبر البريد الإلكتروني"
|
||||
},
|
||||
"navPanel": {
|
||||
"agent": "المساعد",
|
||||
"displayItems": "عرض العناصر",
|
||||
"library": "المكتبة",
|
||||
"searchAgent": "بحث عن مساعد...",
|
||||
"searchResultEmpty": "لا توجد نتائج بحث"
|
||||
},
|
||||
"new": "جديد",
|
||||
"oauth": "تسجيل الدخول SSO",
|
||||
"officialSite": "الموقع الرسمي",
|
||||
@@ -358,13 +387,21 @@
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"aiImage": "الرسم بالذكاء الاصطناعي",
|
||||
"aiImage": "رسم",
|
||||
"audio": "الصوت",
|
||||
"chat": "الدردشة",
|
||||
"community": "المجتمع",
|
||||
"discover": "اكتشاف",
|
||||
"files": "ملفات",
|
||||
"home": "الصفحة الرئيسية",
|
||||
"knowledgeBase": "قاعدة المعرفة",
|
||||
"me": "أنا",
|
||||
"setting": "الإعدادات"
|
||||
"memory": "الذاكرة",
|
||||
"pages": "المستندات",
|
||||
"resource": "الموارد",
|
||||
"search": "البحث",
|
||||
"setting": "الإعدادات",
|
||||
"video": "الفيديو"
|
||||
},
|
||||
"telemetry": {
|
||||
"allow": "السماح",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"chunkingTooltip": "قم بتقسيم الملف إلى عدة كتل نصية وتحويلها إلى متجهات، يمكن استخدامها في البحث الدلالي والمحادثة حول الملفات",
|
||||
"chunkingUnsupported": "هذا الملف لا يدعم تقسيم الأجزاء",
|
||||
"confirmDelete": "سيتم حذف هذا الملف، ولن يمكن استعادته بعد الحذف، يرجى تأكيد العملية",
|
||||
"confirmDeleteFolder": "سيتم حذف هذا المجلد وجميع محتوياته، ولن يكون بالإمكان استعادته بعد الحذف. يرجى تأكيد العملية.",
|
||||
"confirmDeleteMultiFiles": "سيتم حذف {{count}} ملفًا محددًا، ولن يمكن استعادته بعد الحذف، يرجى تأكيد العملية",
|
||||
"confirmRemoveFromKnowledgeBase": "سيتم إزالة {{count}} ملفًا محددًا من قاعدة المعرفة، لا يزال بإمكانك رؤية الملفات في جميع الملفات، يرجى تأكيد العملية",
|
||||
"copyUrl": "نسخ الرابط",
|
||||
@@ -26,8 +27,19 @@
|
||||
"createChunkingTask": "جارٍ التحضير...",
|
||||
"deleteSuccess": "تم حذف الملف بنجاح",
|
||||
"downloading": "جارٍ تحميل الملف...",
|
||||
"goBack": "العودة إلى الصفحة السابقة",
|
||||
"goForward": "الانتقال إلى الصفحة التالية",
|
||||
"goToParent": "الانتقال إلى المجلد الرئيسي",
|
||||
"moveError": "فشل في نقل الملف",
|
||||
"moveHere": "نقل إلى هنا",
|
||||
"moveSuccess": "تم نقل الملف بنجاح",
|
||||
"moveToFolder": "نقل إلى...",
|
||||
"moveToRoot": "نقل إلى الدليل الجذري",
|
||||
"removeFromKnowledgeBase": "إزالة من قاعدة المعرفة",
|
||||
"removeFromKnowledgeBaseSuccess": "تمت إزالة الملف بنجاح"
|
||||
"removeFromKnowledgeBaseSuccess": "تمت إزالة الملف بنجاح",
|
||||
"rename": "إعادة التسمية",
|
||||
"renameError": "فشل في إعادة التسمية",
|
||||
"renameSuccess": "تمت إعادة التسمية بنجاح"
|
||||
},
|
||||
"bottom": "لقد وصلت إلى النهاية",
|
||||
"config": {
|
||||
@@ -42,6 +54,12 @@
|
||||
"or": "أو",
|
||||
"title": "قم بسحب الملف أو المجلد هنا"
|
||||
},
|
||||
"noFolders": "لا توجد مجلدات حالياً",
|
||||
"sort": {
|
||||
"dateAdded": "تاريخ الإضافة",
|
||||
"name": "الاسم",
|
||||
"size": "الحجم"
|
||||
},
|
||||
"title": {
|
||||
"createdAt": "تاريخ الإنشاء",
|
||||
"size": "الحجم",
|
||||
@@ -164,7 +182,7 @@
|
||||
"OllamaSetupGuide": {
|
||||
"action": {
|
||||
"close": "إغلاق الإشعار",
|
||||
"start": "تم التثبيت والتشغيل، ابدأ المحادثة"
|
||||
"start": "تم التثبيت"
|
||||
},
|
||||
"cors": {
|
||||
"description": "بسبب قيود أمان المتصفح، تحتاج إلى تكوين CORS لـ Ollama لاستخدامه بشكل صحيح.",
|
||||
|
||||
+25
-10
@@ -58,11 +58,12 @@
|
||||
"title": "سجل الإصدارات"
|
||||
}
|
||||
},
|
||||
"downloads": "عدد التنزيلات",
|
||||
"list": "قائمة المساعدين",
|
||||
"marketSource": {
|
||||
"label": "تبديل مصدر السوق",
|
||||
"legacy": "السوق القديم",
|
||||
"new": "السوق الجديد"
|
||||
"label": "تبديل مصدر المجتمع",
|
||||
"legacy": "المجتمع القديم",
|
||||
"new": "المجتمع الجديد"
|
||||
},
|
||||
"more": "المزيد",
|
||||
"plugins": "دمج الإضافات",
|
||||
@@ -85,7 +86,7 @@
|
||||
"subtitle": "المساعد الذي تحاول الوصول إليه تم أرشفته للأسباب التالية المحتملة:",
|
||||
"title": "تم أرشفة المساعد"
|
||||
},
|
||||
"backToMarket": "العودة إلى سوق المساعدين",
|
||||
"backToMarket": "العودة إلى مجتمع المساعد",
|
||||
"deprecated": {
|
||||
"reasons": {
|
||||
"official": "تمت إزالة المساعد من قبل الإدارة لأسباب أمنية أو سياسية",
|
||||
@@ -94,9 +95,9 @@
|
||||
"subtitle": "المساعد الذي تحاول الوصول إليه تم رفضه للأسباب التالية المحتملة:",
|
||||
"title": "تم رفض المساعد"
|
||||
},
|
||||
"support": "إذا واجهت أي مشاكل، يرجى نسخ الرابط وإرساله إلى <1>support@lobehub.com</1> للاستفسار.",
|
||||
"support": "لأي استفسارات، يرجى نسخ الرابط وإرساله إلى <email>support@lobehub.com</email>.",
|
||||
"unpublished": {
|
||||
"subtitle": "المساعد الذي تحاول الوصول إليه قيد المراجعة حاليًا. إذا كان لديك أي استفسار، يرجى نسخ الرابط وإرساله إلى <1>support@lobehub.com</1>.",
|
||||
"subtitle": "المساعد الذي تحاول الوصول إليه يخضع حاليًا لمراجعة الإصدار. إذا كان لديك أي استفسار، يرجى نسخ الرابط وإرساله إلى <email>support@lobehub.com</email>.",
|
||||
"title": "المساعد قيد المراجعة"
|
||||
}
|
||||
},
|
||||
@@ -144,7 +145,7 @@
|
||||
"createGuide": {
|
||||
"func1": {
|
||||
"desc1": "ادخل إلى صفحة إعداد المساعد الذي ترغب في تقديمه من خلال الإعدادات في الزاوية العليا اليمنى من نافذة المحادثة;",
|
||||
"desc2": "انقر على زر التقديم إلى سوق المساعدين في الزاوية العليا اليمنى.",
|
||||
"desc2": "انقر على زر الإرسال إلى مجتمع المساعد في الزاوية اليمنى العليا.",
|
||||
"tag": "الطريقة الأولى",
|
||||
"title": "تقديم عبر LobeChat"
|
||||
},
|
||||
@@ -186,8 +187,10 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"communityAgents": "مساعدو المجتمع",
|
||||
"featuredAssistants": "مساعدون مميزون",
|
||||
"featuredModels": "نماذج مميزة",
|
||||
"featuredPlugins": "الإضافات المميزة",
|
||||
"featuredProviders": "مزودو نماذج مميزون",
|
||||
"featuredTools": "إضافات مميزة",
|
||||
"more": "اكتشف المزيد"
|
||||
@@ -516,7 +519,7 @@
|
||||
"hero": {
|
||||
"desc": "منصة MCP Servers مفتوحة المصدر وقابلة للنشر، تساعد أنظمة الذكاء الاصطناعي على الوصول بسهولة إلى أنظمة الملفات، قواعد البيانات، واجهات برمجة التطبيقات وغيرها من الموارد الحيوية، لتوسيع قدرات الذكاء الاصطناعي الخاصة بك بشكل شامل.",
|
||||
"subTitle": "مفتوح المصدر وجاهز للاستخدام",
|
||||
"title": "سوق MCP مفتوح المصدر للذكاء الاصطناعي"
|
||||
"title": "مجتمع MCP مفتوح المصدر للذكاء الاصطناعي"
|
||||
},
|
||||
"sorts": {
|
||||
"createdAt": "أضيف مؤخراً",
|
||||
@@ -529,7 +532,7 @@
|
||||
"toolsCount": "عدد الأدوات",
|
||||
"updatedAt": "تم التحديث مؤخراً"
|
||||
},
|
||||
"title": "سوق MCP",
|
||||
"title": "مجتمع MCP",
|
||||
"unvalidated": {
|
||||
"desc": "هذا الخادم MCP لم يتم التحقق منه بعد",
|
||||
"title": "غير مُحقق"
|
||||
@@ -690,6 +693,18 @@
|
||||
"home": "الصفحة الرئيسية",
|
||||
"model": "النموذج",
|
||||
"plugin": "الإضافة",
|
||||
"provider": "مزود النموذج"
|
||||
"provider": "مزود النموذج",
|
||||
"user": "المستخدم"
|
||||
},
|
||||
"user": {
|
||||
"agents": "المساعدون",
|
||||
"downloads": "التنزيلات",
|
||||
"editProfile": "تعديل الملف الشخصي",
|
||||
"login": "كن منشئًا",
|
||||
"logout": "تسجيل الخروج",
|
||||
"myProfile": "صفحتي الشخصية",
|
||||
"noAgents": "لم يقم هذا المستخدم بنشر أي مساعدين بعد",
|
||||
"publishedAgents": "المساعدون الذين أنشأتهم",
|
||||
"website": "الموقع الشخصي"
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -9,6 +9,11 @@
|
||||
"on": "إظهار شريط أدوات التنسيق"
|
||||
}
|
||||
},
|
||||
"autoSave": {
|
||||
"latest": "تم تحميل أحدث إصدار",
|
||||
"saved": "تم الحفظ",
|
||||
"saving": "يتم الحفظ تلقائيًا..."
|
||||
},
|
||||
"cancel": "إلغاء",
|
||||
"confirm": "تأكيد",
|
||||
"file": {
|
||||
@@ -20,10 +25,18 @@
|
||||
},
|
||||
"link": {
|
||||
"edit": "تعديل الرابط",
|
||||
"editLinkTitle": "الرابط",
|
||||
"editTextTitle": "العنوان",
|
||||
"open": "فتح الرابط",
|
||||
"placeholder": "أدخل عنوان URL للرابط",
|
||||
"unlink": "إزالة الرابط"
|
||||
},
|
||||
"markdown": {
|
||||
"cancel": "إلغاء",
|
||||
"confirm": "تحويل",
|
||||
"parseMessage": "سيتم تحويل المحتوى إلى تنسيق Markdown، وسيتم استبدال المحتوى الحالي. هل ترغب في المتابعة؟ (سيُغلق تلقائيًا بعد 5 ثوانٍ)",
|
||||
"parseTitle": "تنسيق Markdown"
|
||||
},
|
||||
"math": {
|
||||
"placeholder": "يرجى إدخال معادلة TeX"
|
||||
},
|
||||
@@ -50,13 +63,16 @@
|
||||
"bulletList": "قائمة نقطية",
|
||||
"code": "كود مضمن",
|
||||
"codeblock": "كتلة كود",
|
||||
"image": "صورة",
|
||||
"italic": "مائل",
|
||||
"link": "رابط",
|
||||
"numberList": "قائمة مرقمة",
|
||||
"redo": "إعادة",
|
||||
"strikethrough": "شطب",
|
||||
"table": "جدول",
|
||||
"taskList": "قائمة المهام",
|
||||
"tex": "معادلة TeX",
|
||||
"underline": "تسطير"
|
||||
"underline": "تسطير",
|
||||
"undo": "تراجع"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"PluginSettingsInvalid": "تحتاج هذه الإضافة إلى تكوين صحيح قبل الاستخدام، يرجى التحقق من صحة تكوينك",
|
||||
"ProviderBizError": "طلب خدمة {{provider}} خاطئ، يرجى التحقق من المعلومات التالية أو إعادة المحاولة",
|
||||
"QuotaLimitReached": "عذرًا، لقد تم الوصول إلى الحد الأقصى لاستخدام الرموز (Token) أو عدد الطلبات لهذا المفتاح. يرجى زيادة حصة المفتاح أو المحاولة لاحقًا.",
|
||||
"ServerAgentRuntimeError": "عذرًا، خدمة الوكيل غير متاحة حاليًا. يرجى المحاولة لاحقًا أو التواصل معنا عبر البريد الإلكتروني للحصول على الدعم.",
|
||||
"StreamChunkError": "خطأ في تحليل كتلة الرسالة لطلب التدفق، يرجى التحقق مما إذا كانت واجهة برمجة التطبيقات الحالية تتوافق مع المعايير، أو الاتصال بمزود واجهة برمجة التطبيقات الخاصة بك للاستفسار.",
|
||||
"SubscriptionKeyMismatch": "نعتذر، بسبب عطل عرضي في النظام، فإن استخدام الاشتراك الحالي غير فعال مؤقتًا. يرجى النقر على الزر أدناه لاستعادة الاشتراك، أو مراسلتنا عبر البريد الإلكتروني للحصول على الدعم.",
|
||||
"SubscriptionPlanLimit": "لقد استنفدت نقاط اشتراكك، ولا يمكنك استخدام هذه الميزة. يرجى الترقية إلى خطة أعلى، أو تكوين واجهة برمجة التطبيقات للنموذج المخصص للاستمرار في الاستخدام",
|
||||
@@ -162,6 +163,7 @@
|
||||
"title": "تأكيد معلومات المصادقة الخاصة بـ {{name}}"
|
||||
},
|
||||
"confirm": "تأكيد وإعادة المحاولة",
|
||||
"goToSettings": "الانتقال إلى الإعدادات",
|
||||
"oauth": {
|
||||
"description": "فتح المسؤول توثيق تسجيل الدخول الموحد، انقر فوق الزر أدناه لتسجيل الدخول وفتح التطبيق",
|
||||
"success": "تم تسجيل الدخول بنجاح",
|
||||
|
||||
+51
-15
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"addFolder": "إنشاء مجلد",
|
||||
"addKnowledge": "إضافة معرفة",
|
||||
"addLibrary": "إضافة",
|
||||
"addPage": "إنشاء مستند",
|
||||
"desc": "نظّم معرفتك في العمل، الدراسة والحياة.",
|
||||
"desc": "قم بإدارة مواردك للعمل، الدراسة والحياة.",
|
||||
"detail": {
|
||||
"basic": {
|
||||
"createdAt": "تاريخ الإنشاء",
|
||||
@@ -50,6 +50,12 @@
|
||||
"pin": "تثبيت المستند"
|
||||
},
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"slashCommands": {
|
||||
"bulletedList": "قائمة غير مرتبة",
|
||||
"image": "صورة",
|
||||
"orderedList": "قائمة مرتبة",
|
||||
"todoList": "قائمة المهام"
|
||||
},
|
||||
"titlePlaceholder": "بدون عنوان",
|
||||
"wordCount": "{{wordCount}} كلمة"
|
||||
},
|
||||
@@ -57,16 +63,46 @@
|
||||
"copyContent": "نسخ المحتوى الكامل",
|
||||
"duplicate": "إنشاء نسخة",
|
||||
"empty": "لا توجد مستندات حالياً، انقر على الزر أعلاه لإنشاء أول مستند لك",
|
||||
"filter": {
|
||||
"all": "الكل",
|
||||
"onlyInPages": "فقط في المستندات"
|
||||
},
|
||||
"noResults": "لم يتم العثور على مستندات مطابقة",
|
||||
"pageCount": "إجمالي {{count}} مستند",
|
||||
"selectNote": "اختر مستندًا لبدء التحرير",
|
||||
"title": "المستندات",
|
||||
"untitled": "بدون عنوان"
|
||||
},
|
||||
"empty": "لا توجد ملفات/مجلدات تم تحميلها بعد",
|
||||
"header": {
|
||||
"actions": {
|
||||
"builtInBlockList": {
|
||||
"filtered": "تم تصفية {{ignored}} ملفًا من أصل {{total}} ملف"
|
||||
},
|
||||
"connect": "اتصال...",
|
||||
"gitignore": {
|
||||
"apply": "تطبيق القواعد",
|
||||
"cancel": "تجاهل القواعد",
|
||||
"content": "تم اكتشاف ملف .gitignore (عدد {{count}} من الملفات)، هل ترغب في تطبيق قواعد التجاهل؟",
|
||||
"filtered": "{{ignored}} ملفًا تم تجاهله من أصل {{total}} ملفًا",
|
||||
"title": "تم اكتشاف .gitignore"
|
||||
},
|
||||
"newFolder": "إنشاء مجلد جديد",
|
||||
"newPage": "مستند جديد",
|
||||
"notion": {
|
||||
"error": "فشل في استيراد ملف Notion",
|
||||
"foundFiles": "تم العثور على {{count}} ملف",
|
||||
"importing": "جارٍ استيراد ملفات Notion...",
|
||||
"noMarkdownFiles": "لم يتم العثور على ملفات Markdown في ملف ZIP",
|
||||
"partial": "تم استيراد {{success}} ملفًا بنجاح، وفشل {{failed}} ملفًا",
|
||||
"success": "تم استيراد {{count}} ملفًا بنجاح"
|
||||
},
|
||||
"notionGuide": {
|
||||
"cancel": "إلغاء الاستيراد الآن",
|
||||
"desc": "يرجى أولاً تصدير ملفات Markdown (بصيغة ZIP) من Notion، ثم النقر على متابعة لاختيار ملف الضغط واستيراد جميع الصفحات.",
|
||||
"ok": "اختر ملف ZIP من Notion",
|
||||
"title": "استيراد محتوى Notion"
|
||||
},
|
||||
"uploadFile": "رفع ملف",
|
||||
"uploadFolder": "رفع مجلد"
|
||||
},
|
||||
@@ -91,7 +127,7 @@
|
||||
"quickActions": "إجراءات سريعة",
|
||||
"recentFiles": "الملفات الأخيرة",
|
||||
"recentPages": "الصفحات الأخيرة",
|
||||
"subtitle": "مرحبًا بك في قاعدة المعرفة، ابدأ من هنا لإدارة مستنداتك وملاحظاتك",
|
||||
"subtitle": "مرحبًا بك في مركز الموارد، ابدأ من هنا لإدارة مستنداتك وملفاتك.",
|
||||
"uploadEntries": {
|
||||
"files": {
|
||||
"title": "رفع ملفات"
|
||||
@@ -99,27 +135,27 @@
|
||||
"folder": {
|
||||
"title": "رفع مجلد"
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"title": "قاعدة معرفة جديدة"
|
||||
"library": {
|
||||
"title": "إنشاء مكتبة جديدة"
|
||||
},
|
||||
"newPage": {
|
||||
"title": "إنشاء مستند جديد"
|
||||
}
|
||||
}
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"library": {
|
||||
"list": {
|
||||
"confirmRemoveKnowledgeBase": "سيتم حذف هذه المكتبة المعرفية، ولن يتم حذف الملفات الموجودة بها، بل ستنتقل إلى جميع الملفات. بعد حذف المكتبة المعرفية، لن يمكن استعادتها، يرجى توخي الحذر.",
|
||||
"empty": "انقر على <1>+</1> لبدء إنشاء مكتبة معرفية"
|
||||
"confirmRemoveLibrary": "سيتم حذف هذه المكتبة، لكن الملفات بداخلها لن تُحذف، بل سيتم نقلها إلى جميع الملفات. لا يمكن استعادة المكتبة بعد حذفها، يرجى الحذر.",
|
||||
"empty": "انقر <1>+</1> لبدء إنشاء مكتبة"
|
||||
},
|
||||
"new": "إنشاء مكتبة معرفية جديدة",
|
||||
"title": "المكتبة المعرفية"
|
||||
"new": "مكتبة",
|
||||
"title": "المكتبة"
|
||||
},
|
||||
"menu": {
|
||||
"allFiles": "جميع الملفات",
|
||||
"allPages": "جميع المستندات"
|
||||
},
|
||||
"networkError": "فشل في الحصول على قاعدة المعرفة، يرجى التحقق من اتصال الشبكة ثم إعادة المحاولة",
|
||||
"networkError": "فشل في تحميل المكتبة، يرجى التحقق من اتصال الشبكة والمحاولة مرة أخرى",
|
||||
"notSupportGuide": {
|
||||
"desc": "الوضع الحالي للنشر هو وضع قاعدة بيانات العميل، ولا يمكن استخدام وظيفة إدارة الملفات. يرجى التبديل إلى <1>وضع نشر قاعدة بيانات الخادم</1>، أو استخدام <3>LobeChat Cloud</3> مباشرة.",
|
||||
"features": {
|
||||
@@ -131,9 +167,9 @@
|
||||
"desc": "استخدام نماذج متجهات عالية الأداء لتحويل النصوص إلى متجهات، مما يتيح البحث الدلالي في محتوى الملفات.",
|
||||
"title": "تحويل دلالي إلى متجهات"
|
||||
},
|
||||
"repos": {
|
||||
"desc": "يدعم إنشاء مكتبات معرفية، ويسمح بإضافة أنواع مختلفة من الملفات، لبناء معرفتك في مجالك.",
|
||||
"title": "المكتبة المعرفية"
|
||||
"libraries": {
|
||||
"desc": "يدعم إنشاء مكتبات ويسمح بإضافة أنواع مختلفة من الملفات لبناء مواردك المتخصصة",
|
||||
"title": "المكتبة"
|
||||
}
|
||||
},
|
||||
"title": "الوضع الحالي للنشر لا يدعم إدارة الملفات"
|
||||
@@ -155,7 +191,7 @@
|
||||
"videos": "الفيديوهات",
|
||||
"websites": "المواقع"
|
||||
},
|
||||
"title": "قاعدة المعرفة",
|
||||
"title": "الموارد",
|
||||
"toggleLeftPanel": "إظهار/إخفاء اللوحة الجانبية اليسرى",
|
||||
"uploadDock": {
|
||||
"body": {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"starter": {
|
||||
"createAgent": "إنشاء مساعد",
|
||||
"createGroup": "إنشاء مجموعة",
|
||||
"deepResearch": "بحث معمق",
|
||||
"developing": "قيد التطوير",
|
||||
"image": "رسم",
|
||||
"write": "كتابة"
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,10 @@
|
||||
"desc": "إعادة توليد آخر رسالة",
|
||||
"title": "إعادة توليد الرسالة"
|
||||
},
|
||||
"saveDocument": {
|
||||
"desc": "احفظ جميع التغييرات التي أُجريت على المستند الحالي فورًا",
|
||||
"title": "حفظ المستند"
|
||||
},
|
||||
"saveTopic": {
|
||||
"desc": "حفظ الموضوع الحالي وفتح موضوع جديد",
|
||||
"title": "فتح موضوع جديد"
|
||||
@@ -66,12 +70,12 @@
|
||||
"title": "تبديل المساعد بسرعة"
|
||||
},
|
||||
"toggleLeftPanel": {
|
||||
"desc": "عرض أو إخفاء لوحة المساعد على اليسار",
|
||||
"title": "عرض/إخفاء لوحة المساعد"
|
||||
"desc": "إظهار أو إخفاء اللوحة الجانبية اليسرى",
|
||||
"title": "إظهار/إخفاء اللوحة الجانبية اليسرى"
|
||||
},
|
||||
"toggleRightPanel": {
|
||||
"desc": "عرض أو إخفاء لوحة المواضيع على اليمين",
|
||||
"title": "عرض/إخفاء لوحة الموضوع"
|
||||
"desc": "إظهار أو إخفاء اللوحة الجانبية اليمنى",
|
||||
"title": "إظهار/إخفاء اللوحة الجانبية اليمنى"
|
||||
},
|
||||
"toggleZenMode": {
|
||||
"desc": "في وضع التركيز، عرض المحادثة الحالية فقط، وإخفاء واجهة المستخدم الأخرى",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"addToKnowledgeBase": {
|
||||
"addSuccess": "تم إضافة الملف بنجاح، <1>عرض الآن</1>",
|
||||
"confirm": "إضافة",
|
||||
"error": "فشل في إضافة الملف إلى قاعدة المعرفة",
|
||||
"id": {
|
||||
"placeholder": "يرجى اختيار قاعدة المعرفة لإضافتها",
|
||||
"required": "يرجى اختيار قاعدة المعرفة",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"authorize": {
|
||||
"cancel": "إلغاء",
|
||||
"confirm": "تأكيد التفويض",
|
||||
"description": {
|
||||
"and": "و",
|
||||
"prefix": "بالنقر على تأكيد التفويض، فإنك توافق على",
|
||||
"confirm": "أنشئ ملفك الشخصي",
|
||||
"description": "ملفك الشخصي في المجتمع مستقل عن حساب المستخدم في {{appName}}.",
|
||||
"footer": {
|
||||
"agreement": "بمتابعتك، فإنك تؤكد أنك قد قرأت ووافقت على <terms>الشروط والأحكام</terms> و<privacy>سياسة الخصوصية</privacy>",
|
||||
"privacy": "سياسة الخصوصية",
|
||||
"terms": "شروط الخدمة"
|
||||
},
|
||||
"title": "تأكيد التفويض"
|
||||
"subtitle": "أنشئ ملفًا شخصيًا في المجتمع لتتمكن من تقديم وإدارة معلومات النشر.",
|
||||
"title": "إنشاء ملف المجتمع الشخصي"
|
||||
},
|
||||
"callback": {
|
||||
"buttons": {
|
||||
@@ -51,5 +52,42 @@
|
||||
"submit": "تم التفويض بنجاح! يمكنك الآن نشر المساعد.",
|
||||
"upload": "تم التفويض بنجاح! يمكنك الآن نشر إصدار جديد."
|
||||
}
|
||||
},
|
||||
"profileSetup": {
|
||||
"cancel": "إلغاء",
|
||||
"descriptionEdit": "حدّث معلومات ملفك الشخصي في المجتمع.",
|
||||
"descriptionFirstTime": "قم بإعداد ملفك الشخصي لإكمال إنشاء ملف المجتمع.",
|
||||
"errors": {
|
||||
"notAuthenticated": "يرجى تسجيل الدخول أولاً قبل المتابعة",
|
||||
"updateFailed": "فشل في تحديث الملف الشخصي، يرجى المحاولة مرة أخرى",
|
||||
"usernameTaken": "معرّف المستخدم هذا مستخدم بالفعل، يرجى اختيار معرّف آخر"
|
||||
},
|
||||
"fields": {
|
||||
"description": {
|
||||
"label": "نبذة شخصية",
|
||||
"maxLength": "النبذة الشخصية يجب ألا تتجاوز 200 حرف",
|
||||
"placeholder": "قدّم نفسك بإيجاز..."
|
||||
},
|
||||
"displayName": {
|
||||
"label": "الاسم الظاهر",
|
||||
"maxLength": "الاسم الظاهر يجب ألا يتجاوز 50 حرفًا",
|
||||
"placeholder": "أدخل اسمك الظاهر",
|
||||
"required": "يرجى إدخال الاسم الظاهر"
|
||||
},
|
||||
"userName": {
|
||||
"label": "معرّف المستخدم",
|
||||
"maxLength": "معرّف المستخدم يجب ألا يتجاوز 32 حرفًا",
|
||||
"minLength": "معرّف المستخدم يجب أن لا يقل عن 3 أحرف",
|
||||
"pattern": "يمكن أن يحتوي معرّف المستخدم على أحرف وأرقام وشرطات سفلية وشرطات فقط",
|
||||
"placeholder": "أدخل معرّف المستخدم الخاص بك",
|
||||
"required": "يرجى إدخال معرّف المستخدم",
|
||||
"tooltip": "معرّف المستخدم هو معرفك الفريد وسيُستخدم في رابط صفحتك الشخصية"
|
||||
}
|
||||
},
|
||||
"getStarted": "ابدأ الآن",
|
||||
"save": "حفظ",
|
||||
"success": "تم تحديث الملف الشخصي بنجاح",
|
||||
"titleEdit": "تعديل الملف الشخصي",
|
||||
"titleFirstTime": "أكمل ملفك الشخصي"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"context": {
|
||||
"actions": {
|
||||
"delete": "حذف",
|
||||
"edit": "تعديل"
|
||||
},
|
||||
"defaultType": "سياق",
|
||||
"deleteConfirm": "هل أنت متأكد من أنك تريد حذف هذه الذاكرة السياقية؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"deleteTitle": "حذف الذاكرة السياقية",
|
||||
"description": "الوصف",
|
||||
"empty": "لا توجد ذكريات سياقية حالياً",
|
||||
"impact": "درجة التأثير",
|
||||
"source": "المصدر",
|
||||
"urgency": "درجة الإلحاح"
|
||||
},
|
||||
"empty": {
|
||||
"description": "استخراج الذكريات هو عملية تدريجية، يُرجى التفاعل في مواضيع أكثر لتغذية محتوى الذكريات المستخرجة. حاول إجراء محادثات أعمق مع المساعد لالتقاط وتخزين معلومات ذات قيمة بشكل أفضل.",
|
||||
"search": "لم يتم العثور على ذكريات مطابقة",
|
||||
"title": "لا توجد ذكريات حالياً"
|
||||
},
|
||||
"experience": {
|
||||
"actions": {
|
||||
"delete": "حذف",
|
||||
"edit": "تعديل"
|
||||
},
|
||||
"confidence": "درجة الثقة",
|
||||
"defaultType": "تجربة",
|
||||
"deleteConfirm": "هل أنت متأكد من أنك تريد حذف هذه الذاكرة التجريبية؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"deleteTitle": "حذف الذاكرة التجريبية",
|
||||
"empty": "لا توجد ذكريات تجريبية حالياً",
|
||||
"keyLearning": "التعلم الرئيسي",
|
||||
"situation": "السياق",
|
||||
"source": "المصدر",
|
||||
"steps": {
|
||||
"action": "الإجراء المتخذ",
|
||||
"outcome": "النتيجة المحتملة",
|
||||
"reasoning": "عملية التفكير",
|
||||
"situation": "الخلفية السياقية"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"search": "بحث...",
|
||||
"sort": {
|
||||
"createdAt": "تاريخ الإنشاء",
|
||||
"scoreConfidence": "درجة الثقة",
|
||||
"scoreImpact": "درجة التأثير",
|
||||
"scorePriority": "درجة الأولوية",
|
||||
"scoreUrgency": "درجة الإلحاح"
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"empty": "لا توجد ذاكرة هوية حالياً",
|
||||
"filter": {
|
||||
"search": "ابحث عن دور أو علاقة أو وصف...",
|
||||
"type": {
|
||||
"all": "الكل",
|
||||
"demographic": "التركيبة السكانية",
|
||||
"personal": "شخصي",
|
||||
"professional": "مهني"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"confirmDelete": "تأكيد الحذف",
|
||||
"deleteCancel": "إلغاء",
|
||||
"deleteContent": "هل أنت متأكد من أنك تريد حذف ذاكرة الهوية هذه؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"deleteOk": "حذف",
|
||||
"noResults": "لم يتم العثور على أي ذاكرة هوية مطابقة",
|
||||
"updated": "تم التحديث"
|
||||
},
|
||||
"roleCloud": {
|
||||
"collapse": "إخفاء",
|
||||
"expand": "عرض المزيد"
|
||||
},
|
||||
"view": {
|
||||
"list": "قائمة",
|
||||
"timeline": "الجدول الزمني"
|
||||
}
|
||||
},
|
||||
"loading": "جارٍ التحميل...",
|
||||
"preference": {
|
||||
"actions": {
|
||||
"delete": "حذف",
|
||||
"edit": "تعديل"
|
||||
},
|
||||
"conclusionDirectives": "توجيهات الاستنتاج",
|
||||
"defaultType": "تفضيل",
|
||||
"deleteConfirm": "هل أنت متأكد من أنك تريد حذف هذه الذاكرة التفضيلية؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"deleteTitle": "حذف الذاكرة التفضيلية",
|
||||
"empty": "لا توجد ذكريات تفضيلية حالياً",
|
||||
"priority": "الأولوية",
|
||||
"source": "المصدر",
|
||||
"suggestions": "الاقتراحات"
|
||||
},
|
||||
"tab": {
|
||||
"contexts": "السياقات",
|
||||
"experiences": "التجارب",
|
||||
"home": "الرئيسية",
|
||||
"identities": "الهويات",
|
||||
"preferences": "التفضيلات",
|
||||
"search": "بحث"
|
||||
},
|
||||
"viewMode": {
|
||||
"masonry": "عرض متدرج",
|
||||
"timeline": "الجدول الزمني"
|
||||
}
|
||||
}
|
||||
@@ -10,24 +10,24 @@
|
||||
"discover": {
|
||||
"assistants": {
|
||||
"description": "إنشاء المحتوى، الكتابة، الأسئلة والأجوبة، توليد الصور، توليد الفيديو، توليد الصوت، الوكلاء الذكيون، سير العمل الآلي، تخصيص مساعد الذكاء الاصطناعي / GPTs / OLLaMA الخاص بك",
|
||||
"title": "مساعدات الذكاء الاصطناعي"
|
||||
"title": "مجتمع الوكلاء الذكيين"
|
||||
},
|
||||
"description": "إنشاء المحتوى، الكتابة، الأسئلة والأجوبة، توليد الصور، توليد الفيديو، توليد الصوت، الوكلاء الذكيون، سير العمل الآلي، تطبيقات الذكاء الاصطناعي المخصصة، تخصيص منصة تطبيقات الذكاء الاصطناعي الخاصة بك",
|
||||
"mcp": {
|
||||
"description": "ابحث وقارن واتصل بآلاف خوادم MCP، مما يساعد أنظمة الذكاء الاصطناعي على الوصول بسهولة إلى أنظمة الملفات وقواعد البيانات وواجهات برمجة التطبيقات وغيرها من الموارد الحيوية، لتوسيع قدرات الذكاء الاصطناعي الخاصة بك بشكل شامل",
|
||||
"title": "سوق خوادم MCP"
|
||||
"title": "مجتمع خوادم MCP"
|
||||
},
|
||||
"models": {
|
||||
"description": "استكشاف نماذج الذكاء الاصطناعي الرائجة OpenAI / GPT / Claude 3 / Gemini / Ollama / Azure / DeepSeek",
|
||||
"title": "نماذج الذكاء الاصطناعي"
|
||||
"title": "مجتمع النماذج"
|
||||
},
|
||||
"plugins": {
|
||||
"description": "استكشف توليد الرسوم البيانية، والأبحاث الأكاديمية، وتوليد الصور، وتوليد الفيديو، وتوليد الصوت، وأتمتة سير العمل، ودمج قدرات إضافية غنية لمساعدتك.",
|
||||
"title": "إضافات الذكاء الاصطناعي"
|
||||
"title": "مجتمع الإضافات"
|
||||
},
|
||||
"providers": {
|
||||
"description": "استكشاف مزودي النماذج الرائجة OpenAI / Qwen / Ollama / Anthropic / DeepSeek / Google Gemini / OpenRouter",
|
||||
"title": "مزودو خدمات نماذج الذكاء الاصطناعي"
|
||||
"title": "مجتمع مزودي النماذج"
|
||||
},
|
||||
"search": "بحث",
|
||||
"title": "اكتشاف"
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"plugins": {
|
||||
"description": "البحث، توليد الرسوم البيانية، الأكاديميات، توليد الصور، توليد الفيديو، توليد الصوت، سير العمل الآلي، خصص قدرات ToolCall الخاصة بـ ChatGPT / Claude",
|
||||
"title": "سوق الإضافات"
|
||||
"title": "مجتمع الإضافات"
|
||||
},
|
||||
"welcome": {
|
||||
"description": "{{appName}} يقدم لك أفضل تجربة لاستخدام ChatGPT وClaude وGemini وOLLaMA WebUI",
|
||||
|
||||
@@ -1229,6 +1229,9 @@
|
||||
"deepseek_r1_distill_qwen_32b": {
|
||||
"description": "DeepSeek-R1-Distill-Qwen-32B هو نموذج تم الحصول عليه من Qwen2.5-32B من خلال تقطير المعرفة. يستخدم هذا النموذج 800,000 عينة مختارة تم إنشاؤها بواسطة DeepSeek-R1 للتدريب، ويظهر أداءً ممتازًا في مجالات متعددة مثل الرياضيات، البرمجة، والاستدلال."
|
||||
},
|
||||
"devstral-2:123b": {
|
||||
"description": "نموذج Devstral 2 123B، يتميز بقدرته على استخدام الأدوات لاستكشاف قواعد الشيفرة، وتحرير ملفات متعددة، ودعم وكلاء هندسة البرمجيات."
|
||||
},
|
||||
"doubao-1.5-lite-32k": {
|
||||
"description": "دو باو 1.5 لايت هو نموذج الجيل الجديد الخفيف، مع سرعة استجابة قصوى، حيث يصل الأداء والوقت المستغرق إلى مستوى عالمي."
|
||||
},
|
||||
@@ -1937,6 +1940,15 @@
|
||||
"gpt-5.1-codex-mini": {
|
||||
"description": "GPT-5.1 Codex mini: إصدار مصغر ومنخفض التكلفة من Codex، مُحسَّن لمهام البرمجة القائمة على الوكلاء."
|
||||
},
|
||||
"gpt-5.2": {
|
||||
"description": "GPT-5.2 — النموذج الرائد المخصص للبرمجة وتدفقات العمل الذكية، يتمتع بقدرات أقوى في الاستدلال وسياق طويل المدى."
|
||||
},
|
||||
"gpt-5.2-chat-latest": {
|
||||
"description": "GPT-5.2 Chat: إصدار GPT-5.2 المستخدم في ChatGPT (chat-latest)، لتجربة أحدث التحسينات في المحادثة."
|
||||
},
|
||||
"gpt-5.2-pro": {
|
||||
"description": "GPT-5.2 pro: إصدار أكثر ذكاءً ودقة من GPT-5.2 (لواجهة Responses API فقط)، مثالي للمسائل المعقدة والاستدلال متعدد الجولات الطويل."
|
||||
},
|
||||
"gpt-audio": {
|
||||
"description": "GPT Audio هو نموذج دردشة عام موجه لإدخال وإخراج الصوت، ويدعم استخدام الصوت في واجهة برمجة تطبيقات Chat Completions."
|
||||
},
|
||||
|
||||
+81
-4
@@ -1,5 +1,42 @@
|
||||
{
|
||||
"builtins": {
|
||||
"lobe-agent-builder": {
|
||||
"apiName": {
|
||||
"getAvailableModels": "الحصول على النماذج المتاحة",
|
||||
"getAvailableTools": "الحصول على الأدوات المتاحة",
|
||||
"getConfig": "الحصول على الإعدادات",
|
||||
"getMeta": "الحصول على البيانات الوصفية",
|
||||
"getPrompt": "الحصول على التعليمات النظامية",
|
||||
"searchMarketTools": "البحث في سوق الإضافات",
|
||||
"searchOfficialTools": "البحث عن الأدوات الرسمية",
|
||||
"setModel": "تعيين النموذج",
|
||||
"setOpeningMessage": "تعيين رسالة البداية",
|
||||
"setOpeningQuestions": "تعيين أسئلة البداية",
|
||||
"togglePlugin": "تبديل الإضافة",
|
||||
"updateChatConfig": "تحديث إعدادات المحادثة",
|
||||
"updateConfig": "تحديث الإعدادات",
|
||||
"updateMeta": "تحديث البيانات الوصفية",
|
||||
"updatePrompt": "تحديث التعليمات النظامية"
|
||||
},
|
||||
"title": "منشئ الوكيل"
|
||||
},
|
||||
"lobe-group-management": {
|
||||
"apiName": {
|
||||
"broadcast": "إرسال رسالة للجميع",
|
||||
"createAgent": "إضافة عضو إلى الفريق",
|
||||
"createWorkflow": "تخطيط سير العمل",
|
||||
"executeTask": "تنفيذ المهمة",
|
||||
"getAgentInfo": "الحصول على معلومات العضو",
|
||||
"interrupt": "مقاطعة المهمة",
|
||||
"inviteAgent": "دعوة عضو",
|
||||
"removeAgent": "إزالة عضو",
|
||||
"searchAgent": "البحث عن خبير ذي صلة",
|
||||
"speak": "تعيين عضو للتحدث",
|
||||
"summarize": "تلخيص المحادثة",
|
||||
"vote": "بدء تصويت"
|
||||
},
|
||||
"title": "تنسيق الفريق"
|
||||
},
|
||||
"lobe-knowledge-base": {
|
||||
"apiName": {
|
||||
"readKnowledge": "قراءة محتوى قاعدة المعرفة",
|
||||
@@ -24,6 +61,41 @@
|
||||
},
|
||||
"title": "النظام المحلي"
|
||||
},
|
||||
"lobe-page-agent": {
|
||||
"apiName": {
|
||||
"batchUpdate": "تحديث الدُفعة للعُقد",
|
||||
"compareSnapshots": "مقارنة اللقطات",
|
||||
"convertToList": "تحويل إلى قائمة",
|
||||
"createNode": "إنشاء عقدة",
|
||||
"cropImage": "اقتصاص الصورة",
|
||||
"deleteNode": "حذف العقدة",
|
||||
"deleteSnapshot": "حذف اللقطة",
|
||||
"deleteTableColumn": "حذف عمود الجدول",
|
||||
"deleteTableRow": "حذف صف الجدول",
|
||||
"duplicateNode": "نسخ العقدة",
|
||||
"editTitle": "تحرير عنوان المستند",
|
||||
"indentListItem": "زيادة المسافة البادئة لعنصر القائمة",
|
||||
"initPage": "تهيئة المستند",
|
||||
"insertTableColumn": "إدراج عمود في الجدول",
|
||||
"insertTableRow": "إدراج صف في الجدول",
|
||||
"listSnapshots": "عرض اللقطات",
|
||||
"mergeNodes": "دمج العقد",
|
||||
"moveNode": "نقل العقدة",
|
||||
"outdentListItem": "تقليل المسافة البادئة لعنصر القائمة",
|
||||
"replaceText": "استبدال النص",
|
||||
"resizeImage": "تغيير حجم الصورة",
|
||||
"restoreSnapshot": "استعادة اللقطة",
|
||||
"rotateImage": "تدوير الصورة",
|
||||
"saveSnapshot": "حفظ اللقطة",
|
||||
"setImageAlt": "تعيين النص البديل للصورة",
|
||||
"splitNode": "تقسيم العقدة",
|
||||
"toggleListType": "تبديل نوع القائمة",
|
||||
"unwrapNode": "إلغاء تغليف العقدة",
|
||||
"updateNode": "تحديث العقدة",
|
||||
"wrapNodes": "تغليف العقد"
|
||||
},
|
||||
"title": "المستند"
|
||||
},
|
||||
"lobe-web-browsing": {
|
||||
"apiName": {
|
||||
"crawlMultiPages": "قراءة محتوى عدة صفحات",
|
||||
@@ -338,6 +410,8 @@
|
||||
"installed": "مثبت"
|
||||
},
|
||||
"config": {
|
||||
"addEnv": "إضافة متغير بيئة",
|
||||
"addHeaders": "إضافة رؤوس الطلب",
|
||||
"args": "المعلمات",
|
||||
"command": "الأمر",
|
||||
"env": "متغيرات البيئة",
|
||||
@@ -358,12 +432,15 @@
|
||||
},
|
||||
"title": "تثبيت إضافة مخصصة"
|
||||
},
|
||||
"install": {
|
||||
"title": "معلومات التثبيت"
|
||||
},
|
||||
"marketplace": {
|
||||
"title": "تثبيت إضافات الطرف الثالث",
|
||||
"trustedBy": "مقدم من {{name}}",
|
||||
"unverified": {
|
||||
"title": "إضافات طرف ثالث غير موثوقة",
|
||||
"warning": "هذه الإضافة من سوق طرف ثالث غير موثوق، يرجى التأكد من ثقتك بالمصدر قبل التثبيت."
|
||||
"warning": "هذا المكون الإضافي来自 مجتمع طرف ثالث غير موثوق به. يرجى التأكد من أنك تثق في هذا المصدر قبل التثبيت."
|
||||
},
|
||||
"verified": "موثوقة"
|
||||
},
|
||||
@@ -441,7 +518,7 @@
|
||||
"envConfigDescription": "سيتم تمرير هذه الإعدادات كمتغيرات بيئة عند بدء تشغيل خادم MCP",
|
||||
"httpTypeNotice": "إضافات MCP من نوع HTTP لا تحتاج إلى متغيرات بيئة للتكوين حاليًا",
|
||||
"indexUrl": {
|
||||
"title": "فهرس السوق",
|
||||
"title": "فهرس المجتمع",
|
||||
"tooltip": "لا يدعم التحرير عبر الإنترنت حاليًا، يرجى التكوين عبر متغيرات البيئة عند النشر"
|
||||
},
|
||||
"messages": {
|
||||
@@ -450,14 +527,14 @@
|
||||
"envUpdateFailed": "فشل حفظ متغيرات البيئة",
|
||||
"envUpdateSuccess": "تم حفظ متغيرات البيئة بنجاح"
|
||||
},
|
||||
"modalDesc": "بعد تكوين عنوان سوق الإضافات، يمكنك استخدام سوق إضافات مخصص",
|
||||
"modalDesc": "بعد تكوين عنوان مجتمع المكونات الإضافية، يمكنك استخدام مجتمع مكونات إضافية مخصص",
|
||||
"rules": {
|
||||
"argsRequired": "يرجى إدخال معلمات التشغيل",
|
||||
"commandRequired": "يرجى إدخال أمر التشغيل",
|
||||
"urlRequired": "يرجى إدخال عنوان الخدمة"
|
||||
},
|
||||
"saveSettings": "حفظ الإعدادات",
|
||||
"title": "إعدادات سوق الإضافات"
|
||||
"title": "إعداد مجتمع المكونات الإضافية"
|
||||
},
|
||||
"showInPortal": "يرجى عرض التفاصيل في مساحة العمل",
|
||||
"store": {
|
||||
|
||||
+102
-19
@@ -2,6 +2,7 @@
|
||||
"about": {
|
||||
"title": "حول"
|
||||
},
|
||||
"advancedSettings": "الإعدادات المتقدمة",
|
||||
"agentInfoDescription": {
|
||||
"basic": {
|
||||
"avatar": "الصورة الرمزية",
|
||||
@@ -11,7 +12,6 @@
|
||||
"title": "معلومات المساعد"
|
||||
},
|
||||
"chat": {
|
||||
"displayMode": "وضع العرض",
|
||||
"enableHistoryCount": "تمكين عداد الرسائل السابقة",
|
||||
"historyCount": "عدد الرسائل السابقة",
|
||||
"no": "لا",
|
||||
@@ -76,6 +76,13 @@
|
||||
"title": "إعادة تعيين جميع الإعدادات"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"aiConfig": "إعدادات الذكاء الاصطناعي",
|
||||
"common": "عام",
|
||||
"market": "السوق",
|
||||
"profile": "الحساب",
|
||||
"system": "النظام"
|
||||
},
|
||||
"groupTab": {
|
||||
"chat": "الدردشة",
|
||||
"members": "الأعضاء",
|
||||
@@ -85,7 +92,7 @@
|
||||
"desc": "إعدادات التفضيلات والنماذج.",
|
||||
"global": "إعدادات عامة",
|
||||
"group": "إعدادات الفريق",
|
||||
"groupDesc": "إدارة فريق الوكلاء وتفضيلات المحادثة",
|
||||
"groupDesc": "إدارة المجموعات وتفضيلات الدردشة",
|
||||
"session": "إعدادات الجلسة",
|
||||
"sessionDesc": "إعداد الشخصية وتفضيلات الجلسة.",
|
||||
"sessionWithName": "إعدادات الجلسة · {{name}}",
|
||||
@@ -216,32 +223,82 @@
|
||||
"messages": {
|
||||
"createVersionFailed": "فشل إنشاء الإصدار: {{message}}",
|
||||
"fetchRemoteFailed": "فشل في جلب بيانات المساعد من السوق",
|
||||
"missingIdentifier": "لا يحتوي هذا المساعد على معرف سوق حتى الآن",
|
||||
"notAuthenticated": "يرجى تسجيل الدخول إلى حساب السوق أولاً",
|
||||
"missingIdentifier": "لا يحتوي هذا المساعد على معرف المجتمع حتى الآن",
|
||||
"notAuthenticated": "يرجى تسجيل الدخول إلى حساب المجتمع أولاً",
|
||||
"publishFailed": "فشل النشر: {{message}}"
|
||||
},
|
||||
"submitButton": "نشر",
|
||||
"title": {
|
||||
"submit": "مشاركة في سوق المساعدين",
|
||||
"submit": "مشاركة في مجتمع المساعدين",
|
||||
"upload": "نشر إصدار جديد"
|
||||
}
|
||||
},
|
||||
"resultModal": {
|
||||
"message": "تم إرسال المساعد للمراجعة، وسيتم نشره تلقائيًا بعد الموافقة. انقر على \"عرض في السوق\" لرؤية المساعد المنشور.",
|
||||
"view": "عرض في السوق"
|
||||
"message": "تم إرسال المساعد الذي أنشأته للمراجعة، وسيتم نشره تلقائيًا بعد الموافقة.",
|
||||
"title": "تم الإرسال بنجاح",
|
||||
"view": "عرض في المجتمع"
|
||||
},
|
||||
"submit": {
|
||||
"button": "مشاركة في السوق",
|
||||
"tooltip": "شارك المساعد في سوق المساعدين"
|
||||
"button": "مشاركة في المجتمع",
|
||||
"tooltip": "شارك المساعد في مجتمع المساعدين"
|
||||
},
|
||||
"upload": {
|
||||
"button": "نشر إصدار جديد",
|
||||
"tooltip": "نشر إصدار جديد في سوق المساعدين"
|
||||
"tooltip": "نشر إصدار جديد في مجتمع المساعدين"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"success": "تم التحديث بنجاح"
|
||||
},
|
||||
"myAgents": {
|
||||
"actions": {
|
||||
"cancel": "إلغاء",
|
||||
"confirmDeprecate": "تأكيد الإهمال",
|
||||
"deprecate": "إهمال دائم",
|
||||
"deprecateConfirmContent": "بعد الإهمال، سيتم إزالة هذا المساعد نهائيًا من السوق ولن يكون من الممكن إعادة نشره. هذا الإجراء لا يمكن التراجع عنه، يرجى الحذر.",
|
||||
"deprecateConfirmTitle": "هل تريد تأكيد إهمال المساعد؟",
|
||||
"deprecateError": "فشل في إهمال المساعد",
|
||||
"deprecateLoading": "جارٍ إهمال المساعد...",
|
||||
"deprecateSuccess": "تم إهمال المساعد",
|
||||
"edit": "تحرير المساعد",
|
||||
"publish": "نشر المساعد",
|
||||
"publishError": "فشل في نشر المساعد",
|
||||
"publishLoading": "جارٍ نشر المساعد...",
|
||||
"publishSuccess": "تم نشر المساعد",
|
||||
"unpublish": "إلغاء نشر المساعد",
|
||||
"unpublishError": "فشل في إلغاء نشر المساعد",
|
||||
"unpublishLoading": "جارٍ إلغاء نشر المساعد...",
|
||||
"unpublishSuccess": "تم إلغاء نشر المساعد",
|
||||
"viewDetail": "عرض التفاصيل"
|
||||
},
|
||||
"detail": {
|
||||
"category": "الفئة",
|
||||
"description": "الوصف",
|
||||
"identifier": "المعرّف",
|
||||
"title": "تفاصيل المساعد"
|
||||
},
|
||||
"empty": {
|
||||
"description": "لم تقم بنشر أي مساعد في السوق بعد",
|
||||
"title": "لا يوجد مساعدين منشورين"
|
||||
},
|
||||
"errors": {
|
||||
"editFailed": "فشل في تحرير المساعد، يرجى المحاولة لاحقًا",
|
||||
"fetchFailed": "فشل في جلب تفاصيل المساعد",
|
||||
"notAuthenticated": "يرجى تسجيل الدخول إلى حساب السوق أولاً"
|
||||
},
|
||||
"loginRequired": {
|
||||
"button": "تسجيل الدخول إلى حساب السوق",
|
||||
"description": "يرجى تسجيل الدخول إلى حساب السوق لعرض المساعدين الذين قمت بنشرهم",
|
||||
"title": "تسجيل الدخول مطلوب"
|
||||
},
|
||||
"status": {
|
||||
"archived": "مؤرشف",
|
||||
"deprecated": "مهمل",
|
||||
"published": "منشور",
|
||||
"unpublished": "غير منشور"
|
||||
},
|
||||
"title": "مساعدي المنشور"
|
||||
},
|
||||
"plugin": {
|
||||
"addMCPPlugin": "إضافة مكون MCP",
|
||||
"addTooltip": "إضافة البرنامج المساعد",
|
||||
@@ -259,6 +316,7 @@
|
||||
},
|
||||
"settingAgent": {
|
||||
"avatar": {
|
||||
"sizeExceeded": "تجاوز حجم الصورة الحد الأقصى المسموح به وهو 1 ميغابايت، يرجى اختيار صورة أصغر.",
|
||||
"title": "الصورة الرمزية"
|
||||
},
|
||||
"backgroundColor": {
|
||||
@@ -274,12 +332,12 @@
|
||||
"title": "الاسم"
|
||||
},
|
||||
"prompt": {
|
||||
"placeholder": "الرجاء إدخال كلمة الإشارة للشخصية",
|
||||
"title": "ضبط الشخصية"
|
||||
"placeholder": "أدخل إعدادات المساعد، اضغط / لفتح قائمة الأوامر",
|
||||
"title": "إعدادات المساعد"
|
||||
},
|
||||
"submit": "تحديث معلومات المساعد",
|
||||
"tag": {
|
||||
"desc": "سيتم عرض علامة المساعد في سوق المساعدين",
|
||||
"desc": "سيتم عرض وسم المساعد في مجتمع المساعدين",
|
||||
"placeholder": "الرجاء إدخال العلامة",
|
||||
"title": "العلامة"
|
||||
},
|
||||
@@ -391,10 +449,24 @@
|
||||
}
|
||||
},
|
||||
"settingCommon": {
|
||||
"devMode": {
|
||||
"desc": "عند التفعيل، ستظهر الميزات والخيارات الخاصة بالمطورين",
|
||||
"title": "وضع المطور"
|
||||
},
|
||||
"lang": {
|
||||
"autoMode": "اتباع النظام",
|
||||
"title": "اللغة"
|
||||
},
|
||||
"liteMode": {
|
||||
"desc": "عند التفعيل، سيتم تبسيط الواجهة وإخفاء الميزات المتقدمة",
|
||||
"title": "الوضع المبسط"
|
||||
},
|
||||
"responseLanguage": {
|
||||
"auto": "اتباع إعدادات النظام",
|
||||
"desc": "تحديد اللغة التي يستخدمها الذكاء الاصطناعي في الردود",
|
||||
"placeholder": "اختر لغة الرد",
|
||||
"title": "لغة الرد"
|
||||
},
|
||||
"themeMode": {
|
||||
"auto": "تلقائي",
|
||||
"dark": "داكن",
|
||||
@@ -425,7 +497,7 @@
|
||||
"placeholder": "يرجى إدخال كلمة تلميح نظام المضيف",
|
||||
"title": "كلمة تلميح نظام المضيف"
|
||||
},
|
||||
"title": "معلومات فريق الوكلاء"
|
||||
"title": "معلومات المجموعة"
|
||||
},
|
||||
"settingGroupChat": {
|
||||
"allowDM": {
|
||||
@@ -433,7 +505,7 @@
|
||||
"title": "السماح للمساعد بإرسال رسائل خاصة"
|
||||
},
|
||||
"enableSupervisor": {
|
||||
"desc": "تفعيل وظيفة المشرف لفريق الوكلاء، حيث يدير المشرف سير المحادثة داخل الفريق",
|
||||
"desc": "تفعيل ميزة المشرف على المجموعة، حيث يتولى المشرف إدارة سير المحادثات داخل الفريق",
|
||||
"title": "تفعيل المشرف"
|
||||
},
|
||||
"maxResponseInRow": {
|
||||
@@ -631,6 +703,7 @@
|
||||
"title": "مصدر توليد الكلام"
|
||||
}
|
||||
},
|
||||
"startConversation": "ابدأ المحادثة",
|
||||
"storage": {
|
||||
"actions": {
|
||||
"export": {
|
||||
@@ -663,7 +736,8 @@
|
||||
"identifier": "معرف المساعد (identifier)",
|
||||
"metaMiss": "يرجى استكمال معلومات المساعد قبل التقديم، يجب أن تتضمن الاسم والوصف والعلامة",
|
||||
"placeholder": "الرجاء إدخال معرف المساعد، يجب أن يكون فريدًا، مثل تطوير الويب",
|
||||
"tooltips": "مشاركة في سوق المساعدين"
|
||||
"success": "تم إرسال المساعد بنجاح",
|
||||
"tooltips": "مشاركة في مجتمع المساعدين"
|
||||
},
|
||||
"submitFooter": {
|
||||
"reset": "إعادة تعيين",
|
||||
@@ -758,19 +832,26 @@
|
||||
"tab": {
|
||||
"about": "حول",
|
||||
"agent": "المساعد الافتراضي",
|
||||
"common": "إعدادات عامة",
|
||||
"apikey": "إدارة مفاتيح API",
|
||||
"common": "المظهر",
|
||||
"experiment": "تجربة",
|
||||
"hotkey": "اختصارات لوحة المفاتيح",
|
||||
"image": "الرسم بالذكاء الاصطناعي",
|
||||
"image": "خدمة الرسم",
|
||||
"llm": "نموذج اللغة",
|
||||
"my-agents": "مساعدي المنشور",
|
||||
"profile": "حسابي",
|
||||
"provider": "مزود خدمة الذكاء الاصطناعي",
|
||||
"proxy": "وكيل الشبكة",
|
||||
"security": "الأمان",
|
||||
"stats": "إحصائيات البيانات",
|
||||
"storage": "تخزين البيانات",
|
||||
"sync": "مزامنة السحابة",
|
||||
"system-agent": "مساعد النظام",
|
||||
"tts": "خدمة الكلام"
|
||||
"tts": "خدمة الكلام",
|
||||
"usage": "إحصائيات الاستخدام"
|
||||
},
|
||||
"tools": {
|
||||
"add": "إضافة مكون إضافي",
|
||||
"builtins": {
|
||||
"groupName": "الامتدادات المدمجة"
|
||||
},
|
||||
@@ -796,6 +877,8 @@
|
||||
"tools": "أدوات",
|
||||
"verifyAuth": "لقد أكملت المصادقة"
|
||||
},
|
||||
"notInstalled": "غير مثبت",
|
||||
"notInstalledWarning": "المكون الإضافي غير مثبت حاليًا، وقد يؤثر ذلك على استخدام المساعد",
|
||||
"plugins": {
|
||||
"enabled": "ممكّنة {{num}}",
|
||||
"groupName": "الإضافات",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"actions": {
|
||||
"addNewTopic": "إنشاء موضوع جديد",
|
||||
"autoRename": "إعادة تسمية ذكية",
|
||||
"confirmRemoveAll": "سيتم حذف جميع المواضيع، ولن يمكن استعادتها بعد الحذف، يرجى توخي الحذر.",
|
||||
"confirmRemoveTopic": "سيتم حذف هذا الموضوع، ولن يمكن استعادته بعد الحذف، يرجى توخي الحذر.",
|
||||
"confirmRemoveUnstarred": "سيتم حذف المواضيع غير المفضلة، ولن يمكن استعادتها بعد الحذف، يرجى توخي الحذر.",
|
||||
"duplicate": "إنشاء نسخة",
|
||||
"export": "تصدير الموضوع",
|
||||
"openInNewWindow": "افتح الصفحة في نافذة جديدة",
|
||||
"openInNewWindow": "افتح في نافذة مستقلة",
|
||||
"removeAll": "حذف جميع المواضيع",
|
||||
"removeUnstarred": "حذف المواضيع غير المفضلة"
|
||||
},
|
||||
"defaultTitle": "موضوع افتراضي",
|
||||
"displayItems": "عرض العناصر",
|
||||
"duplicateLoading": "يتم نسخ الموضوع...",
|
||||
"duplicateSuccess": "تم نسخ الموضوع بنجاح",
|
||||
"favorite": "مفضل",
|
||||
@@ -32,6 +34,7 @@
|
||||
"desc": "انقر على زر الإرسال على اليسار لحفظ المحادثة الحالية كموضوع تاريخي وبدء جولة جديدة من المحادثة",
|
||||
"title": "قائمة المواضيع"
|
||||
},
|
||||
"loadMore": "المزيد",
|
||||
"searchPlaceholder": "ابحث عن موضوع...",
|
||||
"searchResultEmpty": "لا توجد نتائج للبحث",
|
||||
"temp": "مؤقت",
|
||||
|
||||
+163
-117
@@ -1,339 +1,344 @@
|
||||
{
|
||||
"guide": {
|
||||
"agents": {
|
||||
"replaceBtn": "تغيير",
|
||||
"title": "إضافة توصيات المساعدين:"
|
||||
"replaceBtn": "تبديل مجموعة",
|
||||
"title": "مساعدون جدد مقترحون:"
|
||||
},
|
||||
"defaultMessage": "أنا مساعدك الذكي الشخصي {{appName}}، كيف يمكنني مساعدتك الآن؟<br />إذا كنت بحاجة إلى مساعد أكثر احترافية أو تخصيصًا، يمكنك النقر على <plus /> لإنشاء مساعد مخصص",
|
||||
"defaultMessageWithoutCreate": "أنا مساعدك الذكي الشخصي {{appName}}، كيف يمكنني مساعدتك الآن؟",
|
||||
"defaultMessage": "أنا مساعدك الذكي الشخصي {{appName}}، كيف يمكنني مساعدتك اليوم؟<br />إذا كنت بحاجة إلى مساعد أكثر تخصصًا أو مخصصًا، يمكنك النقر على <plus /> لإنشاء مساعد مخصص",
|
||||
"defaultMessageWithoutCreate": "أنا مساعدك الذكي الشخصي {{appName}}، كيف يمكنني مساعدتك اليوم؟",
|
||||
"groupActivities": {
|
||||
"analysis": {
|
||||
"codeReview": {
|
||||
"description": "مناقشة تقنية ومراجعة الأقران لتغييرات وتنفيذ الشيفرة البرمجية",
|
||||
"description": "مناقشة تقنية ومراجعة جماعية لتغييرات وتنفيذات الشيفرة",
|
||||
"emoji": "💻",
|
||||
"prompt": "دعنا نراجع بعض الشيفرات معًا. هل يمكنك مساعدتنا في تحليل هذه الشيفرات وتحديد مجالات التحسين؟",
|
||||
"prompt": "دعنا نراجع بعض الشيفرات معًا. هل يمكنك مساعدتنا في تحليلها وتحديد مجالات التحسين؟",
|
||||
"title": "مراجعة الشيفرة"
|
||||
},
|
||||
"investment": {
|
||||
"description": "تحليل السوق، مناقشة استراتيجيات الاستثمار ومشاركة الرؤى المالية",
|
||||
"emoji": "📈",
|
||||
"prompt": "دعنا نحلل السوق معًا. هل يمكنك مساعدتنا في مناقشة استراتيجيات الاستثمار ومشاركة الرؤى المالية؟",
|
||||
"prompt": "دعنا نحلل السوق معًا. هل يمكنك مساعدتنا في مناقشة الاستراتيجيات ومشاركة الرؤى؟",
|
||||
"title": "نادي الاستثمار"
|
||||
},
|
||||
"research": {
|
||||
"description": "استكشاف المفاهيم العلمية، إجراء التجارب ومشاركة الاكتشافات",
|
||||
"emoji": "🔬",
|
||||
"prompt": "دعنا نستكشف العلوم معًا! هل يمكنك مساعدتنا في إجراء التجارب ومشاركة اكتشافاتنا؟",
|
||||
"prompt": "دعنا نستكشف العلوم معًا! هل يمكنك مساعدتنا في إجراء التجارب ومشاركة النتائج؟",
|
||||
"title": "معرض العلوم"
|
||||
},
|
||||
"study": {
|
||||
"description": "اجتماعات تعلم تعاونية، مناقشة المفاهيم وحل المشكلات معًا",
|
||||
"description": "جلسات دراسة تعاونية لمناقشة المفاهيم وحل المشكلات معًا",
|
||||
"emoji": "📚",
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم هذه المفاهيم وحل المشكلات معًا؟",
|
||||
"title": "مجموعة الدراسة"
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم المفاهيم وحل المشكلات؟",
|
||||
"title": "مجموعة دراسة"
|
||||
}
|
||||
},
|
||||
"brainstorm": {
|
||||
"artWorkshop": {
|
||||
"description": "إنشاء، نقد وتقدير أشكال مختلفة من الفن البصري والرقمي",
|
||||
"description": "إنشاء، نقد وتقدير الفنون البصرية والرقمية بمختلف أشكالها",
|
||||
"emoji": "🖼️",
|
||||
"prompt": "دعنا نقيم ورشة عمل فنية! هل يمكنك مساعدتنا في إنشاء، نقد وتقدير أشكال مختلفة من الفن؟",
|
||||
"title": "ورشة العمل الفنية"
|
||||
"prompt": "دعنا نقيم ورشة فنية! هل يمكنك مساعدتنا في الإبداع والنقد وتقدير الفنون؟",
|
||||
"title": "ورشة فنية"
|
||||
},
|
||||
"debate": {
|
||||
"description": "نقاشات ومناظرات منظمة حول مواضيع وقضايا مختلفة",
|
||||
"description": "نقاشات منظمة وجدلية حول مواضيع مختلفة وقضايا راهنة",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "دعنا نجري مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"prompt": "دعنا نقيم مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"title": "نادي المناظرة"
|
||||
},
|
||||
"designReview": {
|
||||
"description": "اجتماعات تعاونية لتقديم ملاحظات على مفاهيم التصميم، النماذج الأولية أو الأعمال الإبداعية",
|
||||
"description": "جلسات تعاونية لتقديم الملاحظات على المفاهيم والنماذج الأولية والأعمال الإبداعية",
|
||||
"emoji": "🎨",
|
||||
"prompt": "نحتاج إلى مراجعة بعض التصاميم. هل يمكنك مساعدتنا في تقديم ملاحظات بناءة على مفاهيم التصميم والنماذج الأولية؟",
|
||||
"prompt": "نحتاج إلى مراجعة بعض التصاميم. هل يمكنك مساعدتنا في تقديم ملاحظات بناءة؟",
|
||||
"title": "مراجعة التصميم"
|
||||
},
|
||||
"ideation": {
|
||||
"description": "توليد أفكار إبداعية وحل المشكلات بشكل تعاوني من وجهات نظر متعددة",
|
||||
"description": "توليد أفكار إبداعية وحلول مبتكرة من خلال التعاون متعدد الزوايا",
|
||||
"emoji": "🧠",
|
||||
"prompt": "دعنا نبدأ جلسة عصف ذهني للمشروع. هل يمكنك مساعدتنا في توليد أفكار وحلول إبداعية؟",
|
||||
"title": "جلسة العصف الذهني"
|
||||
"prompt": "دعنا نبدأ جلسة عصف ذهني للمشروع. هل يمكنك مساعدتنا في توليد الأفكار والحلول؟",
|
||||
"title": "عصف ذهني"
|
||||
}
|
||||
},
|
||||
"game": {
|
||||
"debateClub": {
|
||||
"description": "نقاشات ومناظرات منظمة حول مواضيع وقضايا مختلفة",
|
||||
"description": "نقاشات منظمة وجدلية حول مواضيع مختلفة وقضايا راهنة",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "دعنا نجري مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"prompt": "دعنا نقيم مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"title": "نادي المناظرة"
|
||||
},
|
||||
"gameNight": {
|
||||
"description": "ألعاب تفاعلية ممتعة وأنشطة لبناء الروابط بين الفريق والاستمتاع",
|
||||
"description": "ألعاب وأنشطة تفاعلية ممتعة لبناء روح الفريق والاستمتاع",
|
||||
"emoji": "🎲",
|
||||
"prompt": "ليلة الألعاب بدأت! هل يمكنك مساعدتنا في تنظيم بعض الألعاب التفاعلية الممتعة لبناء الروابط بين الفريق؟",
|
||||
"prompt": "حان وقت ليلة الألعاب! هل يمكنك مساعدتنا في تنظيم ألعاب ممتعة لبناء الفريق؟",
|
||||
"title": "ليلة الألعاب"
|
||||
},
|
||||
"modelUN": {
|
||||
"description": "محاكاة مناظرات الأمم المتحدة والمفاوضات الدبلوماسية حول القضايا العالمية",
|
||||
"emoji": "🌍",
|
||||
"prompt": "دعنا نحاكي مناظرة الأمم المتحدة. هل يمكنك مساعدتنا في إعداد مفاوضات دبلوماسية حول القضايا العالمية؟",
|
||||
"title": "محاكاة الأمم المتحدة"
|
||||
"prompt": "دعنا نحاكي مناظرة في الأمم المتحدة. هل يمكنك مساعدتنا في إعداد مفاوضات دبلوماسية؟",
|
||||
"title": "نموذج الأمم المتحدة"
|
||||
},
|
||||
"werewolf": {
|
||||
"description": "لعبة اجتماعية تعتمد على الاستراتيجية والنقاش لكشف دور الذئب بين اللاعبين",
|
||||
"description": "لعبة استنتاج اجتماعي حيث يحاول اللاعبون كشف المستذئبين من خلال النقاش والاستراتيجية",
|
||||
"emoji": "🐺",
|
||||
"prompt": "دعنا نلعب لعبة الذئب! هل يمكنك مساعدتنا في إعداد القواعد وإدارة هذه اللعبة الاجتماعية الاستنتاجية؟",
|
||||
"title": "لعبة الذئب"
|
||||
"prompt": "دعنا نلعب لعبة المستذئب! هل يمكنك مساعدتنا في إعداد القواعد وإدارة اللعبة؟",
|
||||
"title": "لعبة المستذئب"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"brainstorm": {
|
||||
"description": "توليد أفكار إبداعية وحل المشكلات بشكل تعاوني من وجهات نظر متعددة",
|
||||
"description": "توليد أفكار إبداعية وحلول مبتكرة من خلال التعاون متعدد الزوايا",
|
||||
"emoji": "🧠",
|
||||
"prompt": "دعنا نبدأ جلسة عصف ذهني للمشروع. هل يمكنك مساعدتنا في توليد أفكار وحلول إبداعية؟",
|
||||
"title": "جلسة العصف الذهني"
|
||||
"prompt": "دعنا نبدأ جلسة عصف ذهني للمشروع. هل يمكنك مساعدتنا في توليد الأفكار والحلول؟",
|
||||
"title": "عصف ذهني"
|
||||
},
|
||||
"debate": {
|
||||
"description": "نقاشات ومناظرات منظمة حول مواضيع وقضايا مختلفة",
|
||||
"description": "نقاشات منظمة وجدلية حول مواضيع مختلفة وقضايا راهنة",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "دعنا نجري مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"prompt": "دعنا نقيم مناظرة منظمة. هل يمكنك مساعدتنا في تنظيم نقاش منطقي حول هذا الموضوع؟",
|
||||
"title": "نادي المناظرة"
|
||||
},
|
||||
"languagePractice": {
|
||||
"description": "ممارسة المحادثة وتعلم لغات جديدة مع الناطقين بها",
|
||||
"description": "ممارسة المحادثة وتعلم لغات جديدة مع متحدثين أصليين",
|
||||
"emoji": "🗣️",
|
||||
"prompt": "دعنا نمارس لغة جديدة معًا. هل يمكنك مساعدتنا في تعلم وممارسة التحدث بهذه اللغة؟",
|
||||
"title": "ممارسة اللغة"
|
||||
"prompt": "دعنا نتدرب على لغة جديدة معًا. هل يمكنك مساعدتنا في التعلم والممارسة؟",
|
||||
"title": "تمرين اللغة"
|
||||
},
|
||||
"studyGroup": {
|
||||
"description": "اجتماعات تعلم تعاونية، مناقشة المفاهيم وحل المشكلات معًا",
|
||||
"description": "جلسات دراسة تعاونية لمناقشة المفاهيم وحل المشكلات معًا",
|
||||
"emoji": "📚",
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم هذه المفاهيم وحل المشكلات معًا؟",
|
||||
"title": "مجموعة الدراسة"
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم المفاهيم وحل المشكلات؟",
|
||||
"title": "مجموعة دراسة"
|
||||
}
|
||||
},
|
||||
"planning": {
|
||||
"cookingClass": {
|
||||
"description": "تعلم ومشاركة مهارات الطهي، الوصفات والتقاليد الطهوية",
|
||||
"description": "تعلم ومشاركة مهارات الطبخ والوصفات والتقاليد الغذائية",
|
||||
"emoji": "👨🍳",
|
||||
"prompt": "دعنا نحضر درس طبخ! هل يمكنك مساعدتنا في تعلم وصفات جديدة ومهارات الطهي؟",
|
||||
"prompt": "دعنا نبدأ درس طبخ! هل يمكنك مساعدتنا في تعلم وصفات ومهارات جديدة؟",
|
||||
"title": "صف الطبخ"
|
||||
},
|
||||
"fitnessChallenge": {
|
||||
"description": "تحديد أهداف لياقة جماعية، مشاركة تمارين وتحفيز بعضنا البعض",
|
||||
"description": "تحديد أهداف لياقة جماعية، مشاركة التمارين وتحفيز بعضنا البعض",
|
||||
"emoji": "💪",
|
||||
"prompt": "دعنا نبدأ تحدي اللياقة! هل يمكنك مساعدتنا في تحديد الأهداف وتحفيز بعضنا البعض للحفاظ على الصحة؟",
|
||||
"prompt": "دعنا نبدأ تحدي اللياقة! هل يمكنك مساعدتنا في تحديد الأهداف وتحفيز بعضنا البعض؟",
|
||||
"title": "تحدي اللياقة"
|
||||
},
|
||||
"planningPoker": {
|
||||
"description": "تقنية تقدير مهام المشروع وحجم العمل باستخدام بطاقات التخطيط الرشيق",
|
||||
"description": "تقنية تقدير مرنة باستخدام بطاقات لتقدير مهام المشروع وحجم العمل",
|
||||
"emoji": "🃏",
|
||||
"prompt": "نحن نقوم بلعب البوكر التخطيطي للمشروع. هل يمكنك مساعدتنا في استخدام تقنيات الرشيق لتقدير حجم هذه المهام؟",
|
||||
"title": "بوكر التخطيط"
|
||||
"prompt": "نحن نخطط باستخدام لعبة التخطيط. هل يمكنك مساعدتنا في تقدير المهام باستخدام تقنيات مرنة؟",
|
||||
"title": "تخطيط البوكر"
|
||||
},
|
||||
"travelPlanning": {
|
||||
"description": "تخطيط الرحلات، مشاركة تجارب السفر واكتشاف وجهات جديدة",
|
||||
"description": "تخطيط الرحلات، مشاركة التجارب واكتشاف وجهات جديدة",
|
||||
"emoji": "✈️",
|
||||
"prompt": "دعنا نخطط رحلة معًا! هل يمكنك مساعدتنا في البحث عن الوجهات وتخطيط مسار الرحلة؟",
|
||||
"prompt": "دعنا نخطط لرحلة معًا! هل يمكنك مساعدتنا في البحث عن وجهات وتنظيم الرحلة؟",
|
||||
"title": "تخطيط السفر"
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"codeReview": {
|
||||
"description": "مناقشة تقنية ومراجعة الأقران لتغييرات وتنفيذ الشيفرة البرمجية",
|
||||
"description": "مناقشة تقنية ومراجعة جماعية لتغييرات وتنفيذات الشيفرة",
|
||||
"emoji": "💻",
|
||||
"prompt": "دعنا نراجع بعض الشيفرات معًا. هل يمكنك مساعدتنا في تحليل هذه الشيفرات وتحديد مجالات التحسين؟",
|
||||
"prompt": "دعنا نراجع بعض الشيفرات معًا. هل يمكنك مساعدتنا في تحليلها وتحديد مجالات التحسين؟",
|
||||
"title": "مراجعة الشيفرة"
|
||||
},
|
||||
"designReview": {
|
||||
"description": "اجتماعات تعاونية لتقديم ملاحظات على مفاهيم التصميم، النماذج الأولية أو الأعمال الإبداعية",
|
||||
"description": "جلسات تعاونية لتقديم الملاحظات على المفاهيم والنماذج الأولية والأعمال الإبداعية",
|
||||
"emoji": "🎨",
|
||||
"prompt": "نحتاج إلى مراجعة بعض التصاميم. هل يمكنك مساعدتنا في تقديم ملاحظات بناءة على مفاهيم التصميم والنماذج الأولية؟",
|
||||
"prompt": "نحتاج إلى مراجعة بعض التصاميم. هل يمكنك مساعدتنا في تقديم ملاحظات بناءة؟",
|
||||
"title": "مراجعة التصميم"
|
||||
},
|
||||
"sprintPlanning": {
|
||||
"description": "تقنية تقدير مهام المشروع وحجم العمل باستخدام بطاقات التخطيط الرشيق",
|
||||
"description": "تقنية تقدير مرنة باستخدام بطاقات لتقدير مهام المشروع وحجم العمل",
|
||||
"emoji": "🃏",
|
||||
"prompt": "نحن نقوم بلعب البوكر التخطيطي للمشروع. هل يمكنك مساعدتنا في استخدام تقنيات الرشيق لتقدير حجم هذه المهام؟",
|
||||
"title": "بوكر التخطيط"
|
||||
"prompt": "نحن نخطط باستخدام لعبة التخطيط. هل يمكنك مساعدتنا في تقدير المهام باستخدام تقنيات مرنة؟",
|
||||
"title": "تخطيط البوكر"
|
||||
},
|
||||
"techExchange": {
|
||||
"description": "مناقشة التقنيات الناشئة، الابتكار واتجاهات الصناعة",
|
||||
"description": "مناقشة التقنيات الناشئة والابتكار واتجاهات الصناعة",
|
||||
"emoji": "🚀",
|
||||
"prompt": "دعنا نجري تبادلًا تقنيًا! هل يمكنك مساعدتنا في مناقشة التقنيات الناشئة واتجاهات الصناعة؟",
|
||||
"prompt": "دعنا نبدأ تبادلًا تقنيًا! هل يمكنك مساعدتنا في مناقشة التقنيات والاتجاهات الجديدة؟",
|
||||
"title": "تبادل تقني"
|
||||
}
|
||||
},
|
||||
"title": "توصيات لاستخدام الدردشة الجماعية",
|
||||
"title": "اقتراحات لاستخدام الدردشة الجماعية",
|
||||
"writing": {
|
||||
"bookClub": {
|
||||
"description": "مناقشات وتحليلات أدبية للكتب، القصص والأعمال الأدبية",
|
||||
"description": "مناقشة وتحليل الكتب والقصص والأعمال الأدبية",
|
||||
"emoji": "📖",
|
||||
"prompt": "دعنا نبدأ مناقشة نادي الكتاب. هل يمكنك مساعدتنا في تحليل هذا الكتاب ومناقشة مواضيعه؟",
|
||||
"title": "نادي الكتاب"
|
||||
},
|
||||
"movieClub": {
|
||||
"description": "مشاهدة ومناقشة الأفلام، الوثائقيات والوسائط البصرية معًا",
|
||||
"description": "مشاهدة ومناقشة الأفلام والوثائقيات والوسائط البصرية معًا",
|
||||
"emoji": "🎬",
|
||||
"prompt": "دعنا نبدأ مناقشة نادي الأفلام. هل يمكنك مساعدتنا في تحليل هذا الفيلم ومناقشة مواضيعه؟",
|
||||
"title": "نادي الأفلام"
|
||||
"prompt": "دعنا نبدأ مناقشة نادي السينما. هل يمكنك مساعدتنا في تحليل هذا الفيلم ومناقشة مواضيعه؟",
|
||||
"title": "نادي السينما"
|
||||
},
|
||||
"musicSession": {
|
||||
"description": "جلسات تعاون في تأليف الموسيقى، المشاركة والتقدير",
|
||||
"description": "جلسات تعاونية لإنشاء ومشاركة وتقدير الموسيقى",
|
||||
"emoji": "🎵",
|
||||
"prompt": "دعنا نقم بجلسة ارتجال موسيقية! هل يمكنك مساعدتنا في إنشاء وتقدير الموسيقى معًا؟",
|
||||
"title": "جلسة الموسيقى الارتجالية"
|
||||
"prompt": "دعنا نقيم جلسة موسيقية! هل يمكنك مساعدتنا في الإبداع والاستمتاع بالموسيقى؟",
|
||||
"title": "جلسة موسيقية"
|
||||
},
|
||||
"studyGroup": {
|
||||
"description": "اجتماعات تعلم تعاونية، مناقشة المفاهيم وحل المشكلات معًا",
|
||||
"description": "جلسات دراسة تعاونية لمناقشة المفاهيم وحل المشكلات معًا",
|
||||
"emoji": "📚",
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم هذه المفاهيم وحل المشكلات معًا؟",
|
||||
"title": "مجموعة الدراسة"
|
||||
"prompt": "دعنا نشكل مجموعة دراسة. هل يمكنك مساعدتنا في فهم المفاهيم وحل المشكلات؟",
|
||||
"title": "مجموعة دراسة"
|
||||
}
|
||||
}
|
||||
},
|
||||
"groupMessage": "مرحبًا بك في الدردشة الجماعية! تعاون مع عدة مساعدين من الذكاء الاصطناعي في مساحة محادثة مشتركة.",
|
||||
"groupMessage": "مرحبًا بك في الدردشة الجماعية! تعاون مع عدة مساعدين ذكيين في مساحة محادثة مشتركة.",
|
||||
"groupTemplates": {
|
||||
"analysis": {
|
||||
"description": "رؤى مدفوعة بالبيانات، بحث وتحليل معمق",
|
||||
"description": "رؤى مستندة إلى البيانات وتحليلات معمقة",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "📊",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"plugins": ["steam"],
|
||||
"systemRole": "أنت بارع في معالجة البيانات وتفسيرها، وتكشف عن الأنماط والاتجاهات الكامنة وراء البيانات من خلال الرسوم البيانية والتحليلات الإحصائية.",
|
||||
"systemRole": "أنت بارع في معالجة البيانات وتفسيرها، وتكشف عن الأنماط والاتجاهات من خلال الرسوم البيانية والتحليلات الإحصائية.",
|
||||
"title": "محلل بيانات"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "أنت خبير بحث، مسؤول عن جمع المعلومات والبحث المعمق، قادر على تحليل المشكلات من عدة أبعاد بشكل شامل.",
|
||||
"title": "خبير بحث"
|
||||
"systemRole": "أنت خبير بحثي، مسؤول عن جمع المعلومات وإجراء دراسات معمقة، وتستطيع تحليل القضايا من عدة أبعاد.",
|
||||
"title": "خبير بحثي"
|
||||
},
|
||||
{
|
||||
"avatar": "📈",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "أنت خبير إحصاء، متمكن من مختلف الطرق والنماذج الإحصائية، قادر على استخراج رؤى تجارية قيمة من البيانات.",
|
||||
"title": "خبير إحصاء"
|
||||
"systemRole": "أنت خبير إحصائي، تتقن مختلف الأساليب والنماذج الإحصائية، وتستخلص رؤى تجارية قيّمة من البيانات.",
|
||||
"title": "خبير إحصائي"
|
||||
},
|
||||
{
|
||||
"avatar": "🧮",
|
||||
"backgroundColor": "#F0F8FF",
|
||||
"systemRole": "أنت محلل كمي، متخصص في النمذجة الكمية وتقييم المخاطر، تستخدم الطرق الرياضية لحل المشكلات المعقدة.",
|
||||
"systemRole": "أنت محلل كمي، متخصص في النمذجة الكمية وتقييم المخاطر، وتستخدم الأساليب الرياضية لحل المشكلات المعقدة.",
|
||||
"title": "محلل كمي"
|
||||
}
|
||||
],
|
||||
"title": "فريق التحليل"
|
||||
},
|
||||
"brainstorm": {
|
||||
"description": "تفكير إبداعي متعدد الأبعاد، تحفيز إمكانيات لا محدودة",
|
||||
"description": "تفكير إبداعي متعدد الزوايا لإطلاق إمكانيات لا محدودة",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "🧠",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "أنت مدير إبداعي، ماهر في التحكم في اتجاه الإبداع من منظور شامل، قادر على تحويل المفاهيم المجردة إلى خطط إبداعية قابلة للتنفيذ.",
|
||||
"title": "مدير إبداعي"
|
||||
"systemRole": "أنت مدير إبداعي، بارع في توجيه الرؤية الإبداعية من منظور شامل، وتحويل المفاهيم المجردة إلى أفكار قابلة للتنفيذ.",
|
||||
"title": "المدير الإبداعي"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "أنت خبير ابتكار، مسؤول عن اكتشاف حلول جديدة وأفكار مبتكرة، تجيد التفكير خارج الأطر التقليدية.",
|
||||
"systemRole": "أنت خبير ابتكار، مسؤول عن اكتشاف حلول جديدة وأفكار خارجة عن المألوف، وتجيد التفكير خارج الصندوق.",
|
||||
"title": "خبير ابتكار"
|
||||
},
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "أنت خبير تفكير تصميمي، تفكر من منظور تجربة المستخدم والعرض البصري، تركز على التعبير الإبداعي المرئي.",
|
||||
"title": "خبير تفكير تصميمي"
|
||||
"systemRole": "أنت خبير في التفكير التصميمي، تنظر إلى المشكلات من زاوية تجربة المستخدم والعرض البصري، وتركز على التعبير الإبداعي المرئي.",
|
||||
"title": "خبير التفكير التصميمي"
|
||||
}
|
||||
],
|
||||
"title": "مجموعة العصف الذهني"
|
||||
"title": "فريق العصف الذهني"
|
||||
},
|
||||
"game": {
|
||||
"description": "الاستمتاع بألعاب نصية متعددة اللاعبين مثل لعبة الذئب ومن هو العميل السري",
|
||||
"description": "استمتع بألعاب نصية جماعية مثل لعبة المستذئبين ومن هو الجاسوس",
|
||||
"members": [
|
||||
null,
|
||||
{
|
||||
"avatar": "🧠",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "أنت مضيف ألعاب، بارع في تنظيم الألعاب النصية الجماعية وتوجيه اللاعبين خلال اللعبة.",
|
||||
"title": "مضيف اللعبة"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "أنت ماهر في المشاركة في مختلف ألعاب النص المتعددة اللاعبين، وقادر على اللعب وفقًا لقواعد اللعبة.",
|
||||
"systemRole": "أنت بارع في المشاركة في الألعاب النصية الجماعية، وتلعب وفقًا لقواعد اللعبة.",
|
||||
"title": "لاعب"
|
||||
},
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "أنت ماهر في المشاركة في مختلف ألعاب النص المتعددة اللاعبين، وقادر على اللعب وفقًا لقواعد اللعبة.",
|
||||
"systemRole": "أنت بارع في المشاركة في الألعاب النصية الجماعية، وتلعب وفقًا لقواعد اللعبة.",
|
||||
"title": "لاعب"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "أنت ماهر في المشاركة في مختلف ألعاب النص المتعددة اللاعبين، وقادر على اللعب وفقًا لقواعد اللعبة.",
|
||||
"systemRole": "أنت بارع في المشاركة في الألعاب النصية الجماعية، وتلعب وفقًا لقواعد اللعبة.",
|
||||
"title": "لاعب"
|
||||
}
|
||||
],
|
||||
"title": "صالة الألعاب"
|
||||
"title": "قاعة الألعاب"
|
||||
},
|
||||
"planning": {
|
||||
"description": "التخطيط الاستراتيجي وإدارة المشاريع، تنسيق شامل",
|
||||
"description": "تخطيط استراتيجي وإدارة مشاريع شاملة",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "📋",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "أنت مسؤول عن التخطيط العام للمشروع، مراقبة التقدم وتنسيق الموارد لضمان إتمام المشروع في الوقت المحدد وبجودة عالية.",
|
||||
"title": "طباخ"
|
||||
"systemRole": "أنت مسؤول عن التخطيط العام للمشروع، وضبط الجدول الزمني، وتنسيق الموارد لضمان إنجاز المشروع بجودة عالية وفي الوقت المحدد.",
|
||||
"title": "الطاهي"
|
||||
},
|
||||
{
|
||||
"avatar": "🎯",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "أنت مسؤول عن وضع الخطط الاستراتيجية طويلة الأمد، تحليل فرص السوق، تحديد الأهداف ومسارات التنفيذ.",
|
||||
"title": "خبير شراء المواد"
|
||||
"systemRole": "أنت مسؤول عن وضع الخطط الاستراتيجية طويلة المدى، وتحليل الفرص السوقية، وتحديد الأهداف ومسارات التنفيذ.",
|
||||
"title": "خبير شراء المكونات"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#F0F8FF",
|
||||
"systemRole": "أنت مسؤول عن وضع خطط تنفيذية مفصلة، تنسيق موارد الأقسام المختلفة وضمان قابلية تنفيذ الخطة.",
|
||||
"title": "خبير تطوير الطعام"
|
||||
"systemRole": "أنت مسؤول عن إعداد خطط تنفيذية مفصلة، وتنسيق الموارد بين الأقسام المختلفة لضمان قابلية تنفيذ الخطة.",
|
||||
"title": "خبير تطوير الأطعمة"
|
||||
}
|
||||
],
|
||||
"title": "فريق تطوير الطعام"
|
||||
"title": "فريق تطوير الأطعمة"
|
||||
},
|
||||
"product": {
|
||||
"description": "تصميم وتطوير المنتجات، ابتكار منتجات عالية الجودة",
|
||||
"description": "تصميم وتطوير المنتجات لإنشاء منتجات عالية الجودة",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "أنت مصمم، ماهر في تصميم مختلف أنواع المنتجات، قادر على التصميم وفق متطلبات المنتج.",
|
||||
"systemRole": "أنت مصمم، بارع في تصميم أنواع مختلفة من المنتجات، وتعمل وفقًا لمتطلبات المنتج.",
|
||||
"title": "مصمم"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "أنت مدير منتج، مسؤول عن تخطيط وتصميم وتطوير وصيانة المنتج، لضمان جودة المنتج وتجربة المستخدم.",
|
||||
"systemRole": "أنت مدير منتج، مسؤول عن تخطيط وتصميم وتطوير وصيانة المنتج، وتضمن جودة المنتج وتجربة المستخدم.",
|
||||
"title": "مدير منتج"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑💻",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"systemRole": "أنت مهندس شامل ذو خبرة، ماهر في تطوير مختلف أنواع المنتجات، قادر على التطوير وفق متطلبات المنتج.",
|
||||
"title": "مهندس شامل"
|
||||
"systemRole": "أنت مهندس برمجيات شامل ذو خبرة، بارع في تطوير أنواع مختلفة من المنتجات، وتعمل وفقًا لمتطلبات المنتج.",
|
||||
"title": "مهندس برمجيات شامل"
|
||||
}
|
||||
],
|
||||
"title": "فريق تطوير المنتج"
|
||||
"title": "فريق تطوير المنتجات"
|
||||
},
|
||||
"writing": {
|
||||
"description": "إنشاء المحتوى والتحرير، ابتكار نصوص عالية الجودة",
|
||||
"description": "إنشاء وتحرير المحتوى لصياغة نصوص عالية الجودة",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "✍️",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "أنت ماهر في إنشاء محتوى بأنماط أدبية مختلفة، وقادر على تعديل أسلوب الكتابة حسب المشاهد والجمهور.",
|
||||
"systemRole": "أنت بارع في كتابة أنواع مختلفة من المحتوى، وتستطيع تعديل أسلوب الكتابة حسب السياق والجمهور المستهدف.",
|
||||
"title": "كاتب محتوى"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"systemRole": "أنت محرر، مسؤول عن تدقيق النصوص، تنقيحها وتحسينها، لضمان دقة المحتوى وسلاسته واحترافيته.",
|
||||
"systemRole": "أنت محرر، مسؤول عن تدقيق النصوص وتحريرها وتحسينها، لضمان دقة المحتوى وسلاسته واحترافيته.",
|
||||
"title": "محرر"
|
||||
}
|
||||
],
|
||||
@@ -341,22 +346,63 @@
|
||||
}
|
||||
},
|
||||
"questions": {
|
||||
"moreBtn": "معرفة المزيد",
|
||||
"title": "جرّب أن تسأل:"
|
||||
"moreBtn": "اعرف المزيد",
|
||||
"title": "جرب أن تسأل:"
|
||||
},
|
||||
"welcome": {
|
||||
"afternoon": "مساء الخير",
|
||||
"morning": "صباح الخير",
|
||||
"night": "مساء الخير",
|
||||
"noon": "نهاراً"
|
||||
"noon": "نهارك سعيد"
|
||||
}
|
||||
},
|
||||
"header": "مرحبًا بكم في الاستخدام",
|
||||
"pickAgent": "أو اختيار قالب مساعد من القائمة التالية",
|
||||
"skip": "تخطى الإنشاء",
|
||||
"header": "مرحبًا بك",
|
||||
"pickAgent": "أو اختر من قوالب المساعدين التالية",
|
||||
"skip": "تخطي الإنشاء",
|
||||
"slogan": {
|
||||
"desc1": "قم بتشغيل عقلك الجماعي وأشعل شرارة التفكير. مساعدك الذكي، دائمًا موجود.",
|
||||
"desc2": "أنشئ مساعدك الأول ولنبدأ!",
|
||||
"title": "امنح نفسك عقلاً أذكى"
|
||||
"desc1": "فعّل طاقة العقول، وأطلق شرارة الإبداع. مساعدك الذكي دائمًا هنا.",
|
||||
"desc2": "أنشئ أول مساعد لك، ولنبدأ الرحلة ~",
|
||||
"title": "امنح نفسك عقلًا أكثر ذكاءً"
|
||||
},
|
||||
"welcomeMessages": {
|
||||
"1": "مرحبًا بعودتك 😊",
|
||||
"10": "أقصى إنتاجية الآن~",
|
||||
"11": "في خدمتك!",
|
||||
"12": "شكرًا لانتظارك ☕",
|
||||
"13": "لنبدأ الآن ✅",
|
||||
"14": "هل لديك سؤال جديد؟",
|
||||
"15": "عمل رائع اليوم!",
|
||||
"16": "جاري تحميل الإلهام",
|
||||
"17": "متصل بكامل الطاقة ⚡",
|
||||
"18": "انطلاق! 🚀",
|
||||
"19": "أفكاري تواكبك الآن.",
|
||||
"2": "مرحبًا، أنا هنا",
|
||||
"20": "الإلهام قادم",
|
||||
"21": "بانتظار إشارتك",
|
||||
"22": "وضع الإنتاجية مفعل!",
|
||||
"23": "في وضع الاستعداد",
|
||||
"24": "جاهز للتحدي",
|
||||
"25": "أفكار جديدة قيد التكوين",
|
||||
"26": "الطريق واضح، لننطلق!",
|
||||
"27": "النظام جاهز لمساعدتك 💡",
|
||||
"28": "جاري تحميل مزاج جيد",
|
||||
"29": "تحكم بالإيقاع من الآن 🎵",
|
||||
"3": "أنا جاهز!",
|
||||
"30": "رفع الكفاءة …",
|
||||
"31": "هدف اليوم قيد الإنجاز 🎯",
|
||||
"32": "دع الإلهام يتألق ✨",
|
||||
"33": "تم تحديث المهام",
|
||||
"34": "كل شيء جاهز",
|
||||
"35": "وضع السرعة مفعل",
|
||||
"36": "هيا نبدأ 😎",
|
||||
"37": "أنا هنا بانتظارك",
|
||||
"38": "استمر في الأداء الرائع!",
|
||||
"39": "لا تنسَ أن تأخذ قسطًا من الراحة~ 💤",
|
||||
"4": "سعيد برؤيتك",
|
||||
"5": "هل أنت مستعد للبدء؟",
|
||||
"6": "دعني أساعدك اليوم",
|
||||
"7": "لنواصل التقدم!",
|
||||
"8": "لننجزها معًا 💪",
|
||||
"9": "لنبدأ العمل 🏃♂️"
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -136,7 +136,7 @@
|
||||
"passwordPlaceholder": "Моля, въведете парола",
|
||||
"signinLink": "Влезте сега",
|
||||
"submit": "Регистрация",
|
||||
"subtitle": "Присъединете се към общността на LobeChat",
|
||||
"subtitle": "Стартиране на съвместното пространство на Agents",
|
||||
"success": "Регистрацията е успешна! Моля, проверете имейла си за потвърждение",
|
||||
"title": "Създаване на акаунт",
|
||||
"usernamePlaceholder": "Моля, въведете потребителско име"
|
||||
@@ -144,8 +144,7 @@
|
||||
"verifyEmail": {
|
||||
"backToSignIn": "Обратно към вход",
|
||||
"checkSpam": "Ако не сте получили имейл, моля проверете папката със спам",
|
||||
"descriptionPrefix": "Изпратихме имейл за потвърждение на",
|
||||
"descriptionSuffix": "",
|
||||
"description": "Потвърдителен имейл е изпратен до {{email}}",
|
||||
"resend": {
|
||||
"button": "Изпрати отново имейл за потвърждение",
|
||||
"error": "Изпращането не бе успешно, моля опитайте по-късно",
|
||||
@@ -159,6 +158,11 @@
|
||||
"prevMonth": "Миналия месец",
|
||||
"recent30Days": "Последните 30 дни"
|
||||
},
|
||||
"footer": {
|
||||
"agreement": "Продължавайки, потвърждавате, че сте прочели и се съгласявате с <terms>Условията за ползване</terms> и <privacy>Политиката за поверителност</privacy>",
|
||||
"privacy": "Политика за поверителност",
|
||||
"terms": "Условия за ползване"
|
||||
},
|
||||
"header": {
|
||||
"desc": "Управлявайте информацията за вашия акаунт.",
|
||||
"title": "Акаунт"
|
||||
@@ -194,6 +198,9 @@
|
||||
"email": "Имейл адрес",
|
||||
"fullName": "Пълно име",
|
||||
"fullNameInputHint": "Моля, въведете новото си пълно име",
|
||||
"occupation": "Професия",
|
||||
"occupationInputHint": "Моля, въведете вашата професия",
|
||||
"occupationPlaceholder": "Например: софтуерен инженер, продуктов мениджър, дизайнер",
|
||||
"password": "Парола",
|
||||
"resetPasswordError": "Неуспешно изпращане на линк за нулиране на парола",
|
||||
"resetPasswordSent": "Линк за нулиране на парола е изпратен, моля проверете имейла си",
|
||||
@@ -215,6 +222,7 @@
|
||||
"title": "Детайли на профила",
|
||||
"updateAvatar": "Актуализирай аватара",
|
||||
"updateFullName": "Актуализирай пълното име",
|
||||
"updateOccupation": "Актуализирай професията",
|
||||
"updateUsername": "Актуализиране на потребителско име",
|
||||
"username": "Потребителско име",
|
||||
"usernameDuplicate": "Потребителското име вече е заето",
|
||||
@@ -224,6 +232,10 @@
|
||||
"usernameRule": "Потребителското име може да съдържа само букви, цифри или долна черта",
|
||||
"usernameUpdateFailed": "Неуспешно актуализиране на потребителското име, моля, опитайте отново по-късно"
|
||||
},
|
||||
"signin": {
|
||||
"subtitle": "Регистрирайте се или влезте във вашия {{appName}} акаунт",
|
||||
"title": "Вашето съвместно пространство на Agents"
|
||||
},
|
||||
"signout": "Изход",
|
||||
"signup": "Регистрация",
|
||||
"stats": {
|
||||
@@ -269,7 +281,7 @@
|
||||
},
|
||||
"tab": {
|
||||
"apikey": "Управление на API ключове",
|
||||
"profile": "Профил",
|
||||
"profile": "Моят акаунт",
|
||||
"security": "Сигурност",
|
||||
"stats": "Статистика",
|
||||
"usage": "Статистика за използване"
|
||||
|
||||
+53
-15
@@ -3,6 +3,22 @@
|
||||
"title": "Модел"
|
||||
},
|
||||
"active": "Активен",
|
||||
"agentBuilder": {
|
||||
"installPlugin": {
|
||||
"authRequired": "Облачното MCP разширение изисква удостоверяване",
|
||||
"cancel": "Отказ",
|
||||
"clickApproveToConnect": "Щракнете върху „Одобряване“, за да се свържете и упълномощите тази интеграция",
|
||||
"connectedAndEnabled": "Свързано и активирано",
|
||||
"connectionFailed": "Свързването не бе успешно",
|
||||
"installFailed": "Инсталацията не бе успешна",
|
||||
"installPlugin": "Инсталиране на разширение",
|
||||
"installToEnable": "Инсталирайте това разширение, за да активирате помощника",
|
||||
"installedAndEnabled": "Инсталирано и активирано",
|
||||
"requiresAuth": "Изисква упълномощаване, щракнете върху „Одобряване“, за да се свържете",
|
||||
"retry": "Опитай отново"
|
||||
},
|
||||
"welcome": "Здравей, аз съм **Lobe AI**, твоят експерт по конфигуриране на асистенти. Кажи ми какъв асистент искаш и ще ти помогна да го настроиш."
|
||||
},
|
||||
"agentDefaultMessage": "Здравейте, аз съм **{{name}}**, можете да започнете разговор с мен веднага или да отидете на [Настройки на асистента]({{url}}), за да попълните информацията ми.",
|
||||
"agentDefaultMessageWithSystemRole": "Здравейте, аз съм **{{name}}**. Как мога да ви помогна?",
|
||||
"agentDefaultMessageWithoutEdit": "Здравейте, аз съм **{{name}}**. Как мога да ви помогна?",
|
||||
@@ -16,19 +32,20 @@
|
||||
},
|
||||
"availableAgents": "Налични асистенти",
|
||||
"backToBottom": "Върни се в началото",
|
||||
"builtinCopilot": "Вграден Copilot",
|
||||
"chatList": {
|
||||
"expandMessage": "Разгъни съобщението",
|
||||
"longMessageDetail": "Вижте детайлите"
|
||||
},
|
||||
"clearCurrentMessages": "Изчисти съобщенията от текущата сесия",
|
||||
"confirmClearCurrentMessages": "На път си да изчистиш съобщенията от текущата сесия. След като бъдат изчистени, те не могат да бъдат възстановени. Моля, потвърди действието си.",
|
||||
"confirmRemoveChatGroupItemAlert": "Този екип на Agent ще бъде изтрит. Членовете на екипа няма да бъдат засегнати. Моля, потвърдете действието си.",
|
||||
"confirmRemoveChatGroupItemAlert": "Тази група ще бъде изтрита, но членовете на екипа няма да бъдат засегнати. Моля, потвърдете действието си.",
|
||||
"confirmRemoveGroupItemAlert": "Ще изтриете тази група. След изтриването помощниците ѝ ще бъдат преместени в списъка по подразбиране. Моля, потвърдете действието си.",
|
||||
"confirmRemoveGroupSuccess": "Екипът на агентите беше успешно изтрит",
|
||||
"confirmRemoveGroupSuccess": "Групата беше изтрита успешно",
|
||||
"confirmRemoveSessionItemAlert": "На път си да изтриеш този агент. След като бъде изтрит, той не може да бъде възстановен. Моля, потвърди действието си.",
|
||||
"confirmRemoveSessionSuccess": "Сесията е успешно изтрита",
|
||||
"defaultAgent": "Агент по подразбиране",
|
||||
"defaultGroupChat": "Екип на агентите",
|
||||
"defaultGroupChat": "Група",
|
||||
"defaultList": "Списък по подразбиране",
|
||||
"defaultSession": "Агент по подразбиране",
|
||||
"dm": {
|
||||
@@ -44,6 +61,7 @@
|
||||
},
|
||||
"duplicateTitle": "{{title}} Копие",
|
||||
"emptyAgent": "Няма наличен асистент",
|
||||
"emptyAgentAction": "Създаване на асистент",
|
||||
"extendParams": {
|
||||
"disableContextCaching": {
|
||||
"desc": "Разходите за генериране на единичен разговор могат да бъдат намалени с до 90%, а скоростта на отговорите да се увеличи 4 пъти (<1>Научете повече</1>). При активиране автоматично ще се деактивира ограничението на броя на историческите съобщения",
|
||||
@@ -89,8 +107,13 @@
|
||||
},
|
||||
"groupDescription": "Описание на екипа",
|
||||
"groupSidebar": {
|
||||
"agentProfile": {
|
||||
"chat": "Чат",
|
||||
"model": "Модел"
|
||||
},
|
||||
"members": {
|
||||
"addMember": "Добавяне на член",
|
||||
"enableOrchestrator": "Активирай модератор",
|
||||
"memberSettings": "Настройки на член",
|
||||
"orchestrator": "Водещ",
|
||||
"orchestratorThinking": "Водещият мисли...",
|
||||
@@ -120,7 +143,7 @@
|
||||
"noTemplateMembers": "В шаблона няма членове",
|
||||
"noTemplates": "Няма налични шаблони",
|
||||
"searchTemplates": "Търсене на шаблони...",
|
||||
"title": "Създаване на екип на агентите",
|
||||
"title": "Създаване на група",
|
||||
"useTemplate": "Използвай шаблон"
|
||||
},
|
||||
"hideForYou": "Съдържанието на личните съобщения е скрито. Моля, активирайте „Показване на съдържанието на личните съобщения“ в настройките, за да го видите.",
|
||||
@@ -154,28 +177,29 @@
|
||||
"knowledgeBase": {
|
||||
"all": "Всички съдържания",
|
||||
"allFiles": "Всички файлове",
|
||||
"allKnowledgeBases": "Всички знания",
|
||||
"disabled": "Текущият режим на внедряване не поддържа разговори с база знания. Ако искате да използвате тази функция, моля, превключете на внедряване с база данни на сървъра или използвайте услугата {{cloud}}.",
|
||||
"allLibraries": "Всички ресурси",
|
||||
"disabled": "Текущият режим на внедряване не поддържа диалог с ресурсната база. За да използвате тази функция, моля, преминете към внедряване със сървърна база данни или използвайте услугата {{cloud}}",
|
||||
"library": {
|
||||
"action": {
|
||||
"add": "Добави",
|
||||
"detail": "Детайли",
|
||||
"remove": "Премахни"
|
||||
},
|
||||
"title": "Файлове/База знания"
|
||||
"title": "Файлове/Ресурсна база"
|
||||
},
|
||||
"relativeFilesOrKnowledgeBases": "Свързани файлове/бази знания",
|
||||
"title": "База знания",
|
||||
"uploadGuide": "Качените файлове могат да бъдат прегледани в „База знания“",
|
||||
"relativeFilesOrLibraries": "Свързани файлове/ресурси",
|
||||
"title": "Ресурсна база",
|
||||
"uploadGuide": "Качените файлове могат да бъдат прегледани в раздела „Ресурси“",
|
||||
"viewMore": "Вижте още"
|
||||
},
|
||||
"memberSelection": {
|
||||
"addMember": "Добавяне на член",
|
||||
"allMembers": "Всички членове",
|
||||
"createGroup": "Създаване на екип на Agent",
|
||||
"createGroup": "Създаване на група",
|
||||
"noAvailableAgents": "Няма налични агенти за покана",
|
||||
"noSelectedAgents": "Все още не са избрани агенти",
|
||||
"searchAgents": "Търсене на агент...",
|
||||
"selectedAgents": "Избрани ({{count}})",
|
||||
"setInitialMembers": "Избор на членове на екипа"
|
||||
},
|
||||
"members": "Членове",
|
||||
@@ -245,9 +269,10 @@
|
||||
"senderAssistant": "Агент",
|
||||
"senderUser": "Ти"
|
||||
},
|
||||
"newAgent": "Нов агент",
|
||||
"newGroupChat": "Нов екип на агентите",
|
||||
"noAgentsYet": "Този екип на агентите все още няма членове. Натиснете бутона +, за да поканите асистент.",
|
||||
"newAgent": "Създаване на асистент",
|
||||
"newGroupChat": "Създаване на групов чат",
|
||||
"newPage": "Създаване на документ",
|
||||
"noAgentsYet": "Тази група все още няма членове. Натиснете бутона +, за да поканите асистент.",
|
||||
"noAvailableAgents": "Няма налични членове за покана",
|
||||
"noMatchingAgents": "Няма съвпадащи членове",
|
||||
"noMembersYet": "В тази група все още няма членове. Щракнете върху бутона +, за да поканите асистенти.",
|
||||
@@ -296,7 +321,7 @@
|
||||
"searchAgentPlaceholder": "Търсач на помощ...",
|
||||
"searchAgents": "Асистент за търсене...",
|
||||
"selectedAgents": "Избрани помощници",
|
||||
"sendPlaceholder": "Напиши съобщението си тук...",
|
||||
"sendPlaceholder": "Задайте въпрос, създайте нещо или започнете задача, <hotkey><hotkey/>",
|
||||
"sessionGroup": {
|
||||
"config": "Управление на групи",
|
||||
"confirmRemoveGroupAlert": "Тази група е на път да бъде изтрита. След изтриването, агентите в тази група ще бъдат преместени в списъка по подразбиране. Моля, потвърди действието си.",
|
||||
@@ -306,11 +331,17 @@
|
||||
"createGroupSuccess": "Груповият чат бе създаден успешно",
|
||||
"createSuccess": "Създадена успешно",
|
||||
"creatingAgent": "Създаване на асистент...",
|
||||
"groupName": "Име на групата",
|
||||
"inputPlaceholder": "Моля, въведете име на групата...",
|
||||
"moveGroup": "Премести в група",
|
||||
"newGroup": "Нова група",
|
||||
"noAvailableAgents": "Няма налични асистенти",
|
||||
"noMatchingAgents": "Няма намерени съвпадащи асистенти",
|
||||
"noSelectedAgents": "Моля, изберете асистент",
|
||||
"rename": "Преименувай група",
|
||||
"renameSuccess": "Преименувана успешно",
|
||||
"searchAgents": "Търсене на асистент",
|
||||
"selectedAgents": "Избрани асистенти ({{count}})",
|
||||
"sortSuccess": "Пренареждането е успешно",
|
||||
"sorting": "Актуализиране на подредбата на групата...",
|
||||
"tooLong": "Дължината на името на групата трябва да бъде между 1-20 символа"
|
||||
@@ -361,6 +392,11 @@
|
||||
"title": "Задачите са изпълнени"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"groupProfile": "Профил на групата",
|
||||
"profile": "Профил на асистента",
|
||||
"search": "Търсене"
|
||||
},
|
||||
"thread": {
|
||||
"divider": "Подтема",
|
||||
"threadMessageCount": "{{messageCount}} съобщения",
|
||||
@@ -413,6 +449,7 @@
|
||||
"checkOpenNewTopic": "Да се отвори ли нова тема?",
|
||||
"checkSaveCurrentMessages": "Искате ли да запазите текущата сесия като тема?",
|
||||
"openNewTopic": "Отвори нова тема",
|
||||
"recent": "Последни теми",
|
||||
"saveCurrentMessages": "Запази текущата сесия като тема"
|
||||
},
|
||||
"translate": {
|
||||
@@ -424,6 +461,7 @@
|
||||
"clear": "Изчисти речта"
|
||||
},
|
||||
"untitledAgent": "Безименен асистент",
|
||||
"untitledGroup": "Група без име",
|
||||
"updateAgent": "Актуализирай информацията за агента",
|
||||
"upload": {
|
||||
"action": {
|
||||
|
||||
@@ -137,14 +137,36 @@
|
||||
"close": "Затвори",
|
||||
"cmdk": {
|
||||
"about": "Относно",
|
||||
"aiModeEmptyState": "Въведете въпроса си в полето по-горе, за да започнете разговор с AI",
|
||||
"aiModePlaceholder": "Задайте въпрос на AI...",
|
||||
"communitySupport": "Общностна поддръжка",
|
||||
"discover": "Открий",
|
||||
"knowledgeBase": "База знания",
|
||||
"memory": "Памет",
|
||||
"navigate": "Навигация",
|
||||
"newAgent": "Създай агент",
|
||||
"noResults": "Няма намерени резултати",
|
||||
"openSettings": "Отвори настройките",
|
||||
"painting": "AI Рисуване",
|
||||
"pages": "Документи",
|
||||
"painting": "Рисуване",
|
||||
"resource": "Ресурси",
|
||||
"search": {
|
||||
"agent": "Асистент",
|
||||
"agents": "Асистенти",
|
||||
"assistant": "AI Асистент",
|
||||
"assistants": "AI Асистенти",
|
||||
"file": "Файл",
|
||||
"files": "Файлове",
|
||||
"loading": "Търсене...",
|
||||
"mcp": "MCP Сървър",
|
||||
"mcps": "MCP Сървъри",
|
||||
"message": "Съобщение",
|
||||
"messages": "Съобщения",
|
||||
"plugin": "Приставка",
|
||||
"plugins": "Приставки",
|
||||
"searching": "Резултати от търсенето",
|
||||
"topic": "Тема",
|
||||
"topics": "Теми"
|
||||
},
|
||||
"searchPlaceholder": "Въведете команда или търсене...",
|
||||
"settings": "Настройки",
|
||||
"starOnGitHub": "Дайте ни звезда в GitHub",
|
||||
@@ -304,6 +326,13 @@
|
||||
"business": "Бизнес сътрудничество",
|
||||
"support": "Поддръжка по имейл"
|
||||
},
|
||||
"navPanel": {
|
||||
"agent": "Асистент",
|
||||
"displayItems": "Показване на елементи",
|
||||
"library": "Библиотека",
|
||||
"searchAgent": "Търсене на асистент...",
|
||||
"searchResultEmpty": "Няма намерени резултати"
|
||||
},
|
||||
"new": "Нов",
|
||||
"oauth": "SSO Вход",
|
||||
"officialSite": "Официален сайт",
|
||||
@@ -358,13 +387,21 @@
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"aiImage": "AI рисуване",
|
||||
"aiImage": "Рисуване",
|
||||
"audio": "Аудио",
|
||||
"chat": "Чат",
|
||||
"community": "Общност",
|
||||
"discover": "Открий",
|
||||
"files": "Файлове",
|
||||
"home": "Начало",
|
||||
"knowledgeBase": "База знания",
|
||||
"me": "аз",
|
||||
"setting": "Настройки"
|
||||
"memory": "Памет",
|
||||
"pages": "Документи",
|
||||
"resource": "Ресурси",
|
||||
"search": "Търсене",
|
||||
"setting": "Настройки",
|
||||
"video": "Видео"
|
||||
},
|
||||
"telemetry": {
|
||||
"allow": "Разреши",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"chunkingTooltip": "Разделете файла на множество текстови блокове и ги векторизирайте, за да се използват за семантично търсене и диалог с файла",
|
||||
"chunkingUnsupported": "Този файл не поддържа разделяне на части.",
|
||||
"confirmDelete": "Ще изтриете този файл. След изтриването му няма да може да бъде възстановен. Моля, потвърдете действието си.",
|
||||
"confirmDeleteFolder": "Папката и цялото ѝ съдържание ще бъдат изтрити. След изтриването няма да могат да бъдат възстановени. Моля, потвърдете действието си.",
|
||||
"confirmDeleteMultiFiles": "Ще изтриете избраните {{count}} файла. След изтриването им няма да могат да бъдат възстановени. Моля, потвърдете действието си.",
|
||||
"confirmRemoveFromKnowledgeBase": "Ще премахнете избраните {{count}} файла от базата знания. След премахването им файловете все още могат да бъдат видяни в списъка с всички файлове. Моля, потвърдете действието си.",
|
||||
"copyUrl": "Копирай линк",
|
||||
@@ -26,8 +27,19 @@
|
||||
"createChunkingTask": "Подготовка...",
|
||||
"deleteSuccess": "Файлът е изтрит успешно",
|
||||
"downloading": "Изтегляне на файла...",
|
||||
"goBack": "Назад към предишната страница",
|
||||
"goForward": "Напред към следващата страница",
|
||||
"goToParent": "Влизане в родителската папка",
|
||||
"moveError": "Неуспешно преместване на файла",
|
||||
"moveHere": "Премести тук",
|
||||
"moveSuccess": "Файлът беше преместен успешно",
|
||||
"moveToFolder": "Премести в...",
|
||||
"moveToRoot": "Премести в основната директория",
|
||||
"removeFromKnowledgeBase": "Премахни от базата знания",
|
||||
"removeFromKnowledgeBaseSuccess": "Файлът е премахнат успешно"
|
||||
"removeFromKnowledgeBaseSuccess": "Файлът е премахнат успешно",
|
||||
"rename": "Преименуване",
|
||||
"renameError": "Неуспешно преименуване",
|
||||
"renameSuccess": "Успешно преименуване"
|
||||
},
|
||||
"bottom": "Достигнахте края",
|
||||
"config": {
|
||||
@@ -42,6 +54,12 @@
|
||||
"or": "или",
|
||||
"title": "Плъзнете файл или папка тук"
|
||||
},
|
||||
"noFolders": "Няма налични папки",
|
||||
"sort": {
|
||||
"dateAdded": "Дата на добавяне",
|
||||
"name": "Име",
|
||||
"size": "Размер"
|
||||
},
|
||||
"title": {
|
||||
"createdAt": "Дата на създаване",
|
||||
"size": "Размер",
|
||||
@@ -164,7 +182,7 @@
|
||||
"OllamaSetupGuide": {
|
||||
"action": {
|
||||
"close": "Затвори提示",
|
||||
"start": "Инсталирано и работи, започни разговора"
|
||||
"start": "Инсталирано"
|
||||
},
|
||||
"cors": {
|
||||
"description": "Поради ограниченията на сигурността на браузъра, трябва да конфигурирате крос-домейн достъп за Ollama, за да можете да го използвате нормално.",
|
||||
|
||||
+25
-10
@@ -58,11 +58,12 @@
|
||||
"title": "История на версиите"
|
||||
}
|
||||
},
|
||||
"downloads": "Изтегляния",
|
||||
"list": "Списък с асистенти",
|
||||
"marketSource": {
|
||||
"label": "Превключване на източник на пазара",
|
||||
"legacy": "Стар пазар",
|
||||
"new": "Нов пазар"
|
||||
"label": "Превключване на източника на общността",
|
||||
"legacy": "Стара общност",
|
||||
"new": "Нова общност"
|
||||
},
|
||||
"more": "Още",
|
||||
"plugins": "Интегрирани плъгини",
|
||||
@@ -85,7 +86,7 @@
|
||||
"subtitle": "Асистентът, който се опитвате да достъпите, е архивиран поради една от следните възможни причини:",
|
||||
"title": "Асистентът е архивиран"
|
||||
},
|
||||
"backToMarket": "Обратно към пазара на асистенти",
|
||||
"backToMarket": "Обратно към общността на асистентите",
|
||||
"deprecated": {
|
||||
"reasons": {
|
||||
"official": "Асистентът е премахнат от официалните лица поради проблеми със сигурността/политиката",
|
||||
@@ -94,9 +95,9 @@
|
||||
"subtitle": "Асистентът, който се опитвате да достъпите, е отхвърлен поради една от следните възможни причини:",
|
||||
"title": "Асистентът е отхвърлен"
|
||||
},
|
||||
"support": "Ако имате въпроси, моля копирайте линка и го изпратете на <1>support@lobehub.com</1> за съдействие.",
|
||||
"support": "Ако имате въпроси, моля копирайте линка и го изпратете на <email>support@lobehub.com</email> за консултация.",
|
||||
"unpublished": {
|
||||
"subtitle": "Асистентът, който се опитвате да достъпите, е в процес на преглед. Ако имате въпроси, копирайте линка и го изпратете на <1>support@lobehub.com</1> за съдействие.",
|
||||
"subtitle": "Асистентът, който се опитвате да достъпите, в момента преминава през преглед на версията. Ако имате въпроси, копирайте линка и го изпратете на <email>support@lobehub.com</email> за консултация.",
|
||||
"title": "Асистентът е в процес на преглед"
|
||||
}
|
||||
},
|
||||
@@ -144,7 +145,7 @@
|
||||
"createGuide": {
|
||||
"func1": {
|
||||
"desc1": "Влез в настройките на асистента, който искаш да добавиш, чрез иконата в горния десен ъгъл на прозореца за разговор;",
|
||||
"desc2": "Натисни бутона за добавяне на асистент в горния десен ъгъл.",
|
||||
"desc2": "Кликнете върху бутона в горния десен ъгъл, за да изпратите към общността на асистентите.",
|
||||
"tag": "Метод 1",
|
||||
"title": "Добавяне чрез LobeChat"
|
||||
},
|
||||
@@ -186,8 +187,10 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"communityAgents": "Общностни асистенти",
|
||||
"featuredAssistants": "Препоръчани асистенти",
|
||||
"featuredModels": "Препоръчани модели",
|
||||
"featuredPlugins": "Препоръчани плъгини",
|
||||
"featuredProviders": "Препоръчани доставчици на модели",
|
||||
"featuredTools": "Препоръчани инструменти",
|
||||
"more": "Открий повече"
|
||||
@@ -516,7 +519,7 @@
|
||||
"hero": {
|
||||
"desc": "Отворена и разгръщаема платформа MCP сървъри, която помага на AI системите лесно да имат достъп до файлови системи, бази данни, API и други ключови ресурси, разширявайки вашите AI възможности.",
|
||||
"subTitle": "Отворен код & готово за използване",
|
||||
"title": "Отворен MCP пазар за AI"
|
||||
"title": "Отворена MCP общност, ориентирана към AI"
|
||||
},
|
||||
"sorts": {
|
||||
"createdAt": "Последно добавено",
|
||||
@@ -529,7 +532,7 @@
|
||||
"toolsCount": "Брой инструменти",
|
||||
"updatedAt": "Последна актуализация"
|
||||
},
|
||||
"title": "MCP пазар",
|
||||
"title": "MCP общност",
|
||||
"unvalidated": {
|
||||
"desc": "Този MCP сървър все още не е проверен",
|
||||
"title": "Непроверен"
|
||||
@@ -690,6 +693,18 @@
|
||||
"home": "Начална страница",
|
||||
"model": "Модел",
|
||||
"plugin": "Плъгин",
|
||||
"provider": "Доставчик на модели"
|
||||
"provider": "Доставчик на модели",
|
||||
"user": "Потребител"
|
||||
},
|
||||
"user": {
|
||||
"agents": "Асистенти",
|
||||
"downloads": "Изтегляния",
|
||||
"editProfile": "Редактиране на профил",
|
||||
"login": "Стани създател",
|
||||
"logout": "Изход",
|
||||
"myProfile": "Моят профил",
|
||||
"noAgents": "Този потребител все още не е публикувал асистенти",
|
||||
"publishedAgents": "Създадени асистенти",
|
||||
"website": "Личен уебсайт"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
"on": "Покажи лентата за форматиране"
|
||||
}
|
||||
},
|
||||
"autoSave": {
|
||||
"latest": "Заредена е най-новата версия",
|
||||
"saved": "Запазено",
|
||||
"saving": "Автоматично запазване..."
|
||||
},
|
||||
"cancel": "Отказ",
|
||||
"confirm": "Потвърждение",
|
||||
"file": {
|
||||
@@ -20,10 +25,18 @@
|
||||
},
|
||||
"link": {
|
||||
"edit": "Редактирай връзката",
|
||||
"editLinkTitle": "Връзка",
|
||||
"editTextTitle": "Заглавие",
|
||||
"open": "Отвори връзката",
|
||||
"placeholder": "Въведете URL адрес на връзката",
|
||||
"unlink": "Премахни връзката"
|
||||
},
|
||||
"markdown": {
|
||||
"cancel": "Отказ",
|
||||
"confirm": "Преобразуване",
|
||||
"parseMessage": "Съдържанието ще бъде преобразувано във формат Markdown и съществуващото съдържание ще бъде заменено. Сигурни ли сте? (Автоматично затваряне след 5 секунди)",
|
||||
"parseTitle": "Форматиране в Markdown"
|
||||
},
|
||||
"math": {
|
||||
"placeholder": "Моля, въведете TeX формула"
|
||||
},
|
||||
@@ -50,13 +63,16 @@
|
||||
"bulletList": "Маркиран списък",
|
||||
"code": "Код в реда",
|
||||
"codeblock": "Блок с код",
|
||||
"image": "Изображение",
|
||||
"italic": "Курсив",
|
||||
"link": "Връзка",
|
||||
"numberList": "Номериран списък",
|
||||
"redo": "Повтори",
|
||||
"strikethrough": "Зачеркване",
|
||||
"table": "таблица",
|
||||
"taskList": "Списък със задачи",
|
||||
"tex": "TeX формула",
|
||||
"underline": "Подчертаване"
|
||||
"underline": "Подчертаване",
|
||||
"undo": "Отмени"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"PluginSettingsInvalid": "Този плъгин трябва да бъде конфигуриран правилно, преди да може да се използва. Моля, проверете дали конфигурацията ви е правилна",
|
||||
"ProviderBizError": "Грешка в услугата на {{provider}}, моля проверете следната информация или опитайте отново",
|
||||
"QuotaLimitReached": "Съжаляваме, но текущото използване на токени или броят на заявките е достигнало лимита на квотата за този ключ. Моля, увеличете квотата на ключа или опитайте отново по-късно.",
|
||||
"ServerAgentRuntimeError": "Съжаляваме, в момента услугата Agent не е достъпна. Моля, опитайте отново по-късно или се свържете с нас по имейл за съдействие.",
|
||||
"StreamChunkError": "Грешка при парсирането на съобщение от потокова заявка. Моля, проверете дали текущият API интерфейс отговаря на стандартите или се свържете с вашия доставчик на API за консултация.",
|
||||
"SubscriptionKeyMismatch": "Съжаляваме, но поради случайна системна грешка, текущото използване на абонамента временно е невалидно. Моля, кликнете върху бутона по-долу, за да възстановите абонамента, или се свържете с нас по имейл за поддръжка.",
|
||||
"SubscriptionPlanLimit": "Вашият абонаментен план е изчерпан, не можете да използвате тази функция. Моля, надстройте до по-висок план или конфигурирайте персонализиран модел API, за да продължите да използвате.",
|
||||
@@ -162,6 +163,7 @@
|
||||
"title": "Потвърдете удостоверителната си информация за {{name}}"
|
||||
},
|
||||
"confirm": "Потвърди и опитай отново",
|
||||
"goToSettings": "Отидете в настройките",
|
||||
"oauth": {
|
||||
"description": "Администраторът е активирал унифицирано удостоверяване за вход. Щракнете върху бутона по-долу, за да влезете и отключите приложението.",
|
||||
"success": "Входът е успешен",
|
||||
|
||||
+51
-15
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"addFolder": "Създаване на папка",
|
||||
"addKnowledge": "Добавяне на знание",
|
||||
"addLibrary": "Добавяне",
|
||||
"addPage": "Създаване на документ",
|
||||
"desc": "Управлявайте знанията си за работа, учене и живот.",
|
||||
"desc": "Управлявайте своите ресурси за работа, учене и живот.",
|
||||
"detail": {
|
||||
"basic": {
|
||||
"createdAt": "Дата на създаване",
|
||||
@@ -50,6 +50,12 @@
|
||||
"pin": "Закачане на документа"
|
||||
},
|
||||
"saving": "Запазване...",
|
||||
"slashCommands": {
|
||||
"bulletedList": "Маркиран списък",
|
||||
"image": "Изображение",
|
||||
"orderedList": "Номериран списък",
|
||||
"todoList": "Списък със задачи"
|
||||
},
|
||||
"titlePlaceholder": "Без заглавие",
|
||||
"wordCount": "{{wordCount}} думи"
|
||||
},
|
||||
@@ -57,16 +63,46 @@
|
||||
"copyContent": "Копиране на цялото съдържание",
|
||||
"duplicate": "Създаване на копие",
|
||||
"empty": "Все още няма документи. Щракнете върху бутона по-горе, за да създадете първия си документ.",
|
||||
"filter": {
|
||||
"all": "Всички",
|
||||
"onlyInPages": "Само в документите"
|
||||
},
|
||||
"noResults": "Няма намерени съвпадащи документи",
|
||||
"pageCount": "Общо {{count}} документа",
|
||||
"selectNote": "Изберете документ, за да започнете редактиране",
|
||||
"title": "Документи",
|
||||
"untitled": "Без заглавие"
|
||||
},
|
||||
"empty": "Няма качени файлове/папки",
|
||||
"header": {
|
||||
"actions": {
|
||||
"builtInBlockList": {
|
||||
"filtered": "Филтрирани са {{ignored}} файла (от общо {{total}} файла)"
|
||||
},
|
||||
"connect": "Свързване...",
|
||||
"gitignore": {
|
||||
"apply": "Приложи правилата",
|
||||
"cancel": "Игнорирай правилата",
|
||||
"content": "Открит е файл .gitignore (общо {{count}} файла). Да се приложат ли правилата за игнориране?",
|
||||
"filtered": "Филтрирани са {{ignored}} файла (от общо {{total}} файла)",
|
||||
"title": "Открит е .gitignore файл"
|
||||
},
|
||||
"newFolder": "Нова папка",
|
||||
"newPage": "Създаване на нов документ",
|
||||
"notion": {
|
||||
"error": "Неуспешен импорт на файл от Notion",
|
||||
"foundFiles": "Намерени {{count}} файла",
|
||||
"importing": "Импортиране на файл от Notion...",
|
||||
"noMarkdownFiles": "Не са намерени Markdown файлове в ZIP архива",
|
||||
"partial": "Успешно импортирани {{success}} файла, неуспешни {{failed}}",
|
||||
"success": "Успешно импортирани {{count}} файла"
|
||||
},
|
||||
"notionGuide": {
|
||||
"cancel": "Отказ от импортиране",
|
||||
"desc": "Моля, първо експортирайте Markdown (ZIP) от Notion. След това щракнете върху „Продължи“ и изберете архивния файл, за да импортирате всички страници.",
|
||||
"ok": "Изберете Notion ZIP",
|
||||
"title": "Импортиране на съдържание от Notion"
|
||||
},
|
||||
"uploadFile": "Качване на файл",
|
||||
"uploadFolder": "Качване на папка"
|
||||
},
|
||||
@@ -91,7 +127,7 @@
|
||||
"quickActions": "Бързи действия",
|
||||
"recentFiles": "Скорошни файлове",
|
||||
"recentPages": "Скорошни документи",
|
||||
"subtitle": "Добре дошли в базата знания. Започнете да управлявате вашите документи оттук",
|
||||
"subtitle": "Добре дошли в Центъра за ресурси. Започнете да управлявате вашите документи и файлове оттук.",
|
||||
"uploadEntries": {
|
||||
"files": {
|
||||
"title": "Качване на файлове"
|
||||
@@ -99,27 +135,27 @@
|
||||
"folder": {
|
||||
"title": "Качване на папка"
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"title": "Създай база знания"
|
||||
"library": {
|
||||
"title": "Създаване на нова библиотека"
|
||||
},
|
||||
"newPage": {
|
||||
"title": "Създаване на нов документ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"library": {
|
||||
"list": {
|
||||
"confirmRemoveKnowledgeBase": "Сигурни ли сте, че искате да изтриете тази база знания? Файловете в нея няма да бъдат изтрити, а ще бъдат преместени в общите файлове. След изтриването на базата знания, тя не може да бъде възстановена, моля, действайте внимателно.",
|
||||
"empty": "Кликнете <1>+</1>, за да започнете създаването на база знания"
|
||||
"confirmRemoveLibrary": "Ще бъде изтрита тази библиотека. Файловете в нея няма да бъдат изтрити, а ще бъдат преместени във 'Всички файлове'. След изтриване библиотеката не може да бъде възстановена. Моля, действайте внимателно.",
|
||||
"empty": "Кликнете <1>+</1>, за да създадете библиотека"
|
||||
},
|
||||
"new": "Нова база знания",
|
||||
"title": "База знания"
|
||||
"new": "Библиотека",
|
||||
"title": "Библиотеки"
|
||||
},
|
||||
"menu": {
|
||||
"allFiles": "Всички файлове",
|
||||
"allPages": "Всички документи"
|
||||
},
|
||||
"networkError": "Неуспешно получаване на базата от знания, моля, проверете интернет връзката и опитайте отново",
|
||||
"networkError": "Неуспешно зареждане на библиотеките. Моля, проверете интернет връзката и опитайте отново.",
|
||||
"notSupportGuide": {
|
||||
"desc": "Текущият инстанс е в режим на клиентска база данни и не поддържа функцията за управление на файлове. Моля, превключете на <1>режим на сървърна база данни</1> или използвайте директно <3>LobeChat Cloud</3>",
|
||||
"features": {
|
||||
@@ -131,9 +167,9 @@
|
||||
"desc": "Използва високопроизводителни векторни модели за векторизация на текстови части, позволявайки семантично търсене на съдържанието на файловете",
|
||||
"title": "Семантична векторизация"
|
||||
},
|
||||
"repos": {
|
||||
"desc": "Поддържа създаване на база знания и позволява добавяне на различни типове файлове, за да изградите собствена област на знание",
|
||||
"title": "База знания"
|
||||
"libraries": {
|
||||
"desc": "Позволява създаване на библиотеки и добавяне на различни типове файлове, за да изградите собствена база от знания.",
|
||||
"title": "Библиотеки"
|
||||
}
|
||||
},
|
||||
"title": "Текущият режим на инсталация не поддържа управление на файлове"
|
||||
@@ -155,7 +191,7 @@
|
||||
"videos": "Видеа",
|
||||
"websites": "Уебсайтове"
|
||||
},
|
||||
"title": "База знания",
|
||||
"title": "Ресурси",
|
||||
"toggleLeftPanel": "Показване/скриване на лявия панел",
|
||||
"uploadDock": {
|
||||
"body": {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"starter": {
|
||||
"createAgent": "Създаване на асистент",
|
||||
"createGroup": "Създаване на група",
|
||||
"deepResearch": "Задълбочено проучване",
|
||||
"developing": "В процес на разработка",
|
||||
"image": "Рисуване",
|
||||
"write": "Писане"
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,10 @@
|
||||
"desc": "Прегенерирайте последното съобщение",
|
||||
"title": "Прегенериране на съобщение"
|
||||
},
|
||||
"saveDocument": {
|
||||
"desc": "Запазете незабавно всички промени в текущия документ",
|
||||
"title": "Запазване на документа"
|
||||
},
|
||||
"saveTopic": {
|
||||
"desc": "Запазете текущата тема и отворете нова",
|
||||
"title": "Създаване на нова тема"
|
||||
@@ -66,12 +70,12 @@
|
||||
"title": "Бърза смяна на помощника"
|
||||
},
|
||||
"toggleLeftPanel": {
|
||||
"desc": "Показване или скриване на панела с помощ отляво",
|
||||
"title": "Показване/скриване на панела с помощника"
|
||||
"desc": "Показване или скриване на левия панел",
|
||||
"title": "Показване/Скриване на левия панел"
|
||||
},
|
||||
"toggleRightPanel": {
|
||||
"desc": "Показване или скриване на панела с теми отдясно",
|
||||
"title": "Показване/скриване на панела с теми"
|
||||
"desc": "Показване или скриване на десния панел",
|
||||
"title": "Показване/Скриване на десния панел"
|
||||
},
|
||||
"toggleZenMode": {
|
||||
"desc": "В режим на фокус, показвайте само текущия разговор, скривайки другия интерфейс",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"addToKnowledgeBase": {
|
||||
"addSuccess": "Файлът беше добавен успешно, <1>прегледайте веднага</1>",
|
||||
"confirm": "Добави",
|
||||
"error": "Неуспешно добавяне на файл към базата знания",
|
||||
"id": {
|
||||
"placeholder": "Моля, изберете знание база за добавяне",
|
||||
"required": "Моля, изберете знание база",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"authorize": {
|
||||
"cancel": "Отказ",
|
||||
"confirm": "Разреши използването",
|
||||
"description": {
|
||||
"and": "и",
|
||||
"prefix": "С натискане на „Разреши използването“ се съгласявате с",
|
||||
"privacy": "Политиката за поверителност",
|
||||
"terms": "Условията за ползване"
|
||||
"confirm": "Създай своя профил",
|
||||
"description": "Твоят общностен профил съществува независимо от потребителския акаунт в {{appName}}.",
|
||||
"footer": {
|
||||
"agreement": "Продължавайки, потвърждаваш, че си запознат и приемаш <terms>Условията за ползване</terms> и <privacy>Политиката за поверителност</privacy>",
|
||||
"privacy": "Политика за поверителност",
|
||||
"terms": "Условия за ползване"
|
||||
},
|
||||
"title": "Потвърждаване на оторизацията"
|
||||
"subtitle": "Създай общностен профил, за да подаваш и управляваш информация за публикуване в общността.",
|
||||
"title": "Създаване на общностен профил"
|
||||
},
|
||||
"callback": {
|
||||
"buttons": {
|
||||
@@ -51,5 +52,42 @@
|
||||
"submit": "Упълномощаването е успешно! Вече можете да публикувате помощник.",
|
||||
"upload": "Упълномощаването е успешно! Вече можете да публикувате нова версия."
|
||||
}
|
||||
},
|
||||
"profileSetup": {
|
||||
"cancel": "Отказ",
|
||||
"descriptionEdit": "Актуализирайте информацията във вашия профил в общността.",
|
||||
"descriptionFirstTime": "Настройте своя профил, за да завършите създаването на профила в общността.",
|
||||
"errors": {
|
||||
"notAuthenticated": "Моля, влезте в системата, преди да продължите",
|
||||
"updateFailed": "Неуспешно обновяване на профила. Моля, опитайте отново",
|
||||
"usernameTaken": "Този потребителски ID вече е зает. Моля, изберете друг"
|
||||
},
|
||||
"fields": {
|
||||
"description": {
|
||||
"label": "Лично представяне",
|
||||
"maxLength": "Личното представяне може да бъде до 200 знака",
|
||||
"placeholder": "Разкажете ни малко за себе си..."
|
||||
},
|
||||
"displayName": {
|
||||
"label": "Псевдоним",
|
||||
"maxLength": "Псевдонимът може да бъде до 50 знака",
|
||||
"placeholder": "Въведете вашия псевдоним",
|
||||
"required": "Моля, въведете псевдоним"
|
||||
},
|
||||
"userName": {
|
||||
"label": "Потребителски ID",
|
||||
"maxLength": "Потребителският ID може да бъде до 32 знака",
|
||||
"minLength": "Потребителският ID трябва да бъде поне 3 знака",
|
||||
"pattern": "Потребителският ID може да съдържа само букви, цифри, долни черти и тирета",
|
||||
"placeholder": "Въведете вашия потребителски ID",
|
||||
"required": "Моля, въведете потребителски ID",
|
||||
"tooltip": "Потребителският ID е вашият уникален идентификатор и ще се използва в линка към вашия профил"
|
||||
}
|
||||
},
|
||||
"getStarted": "Започнете",
|
||||
"save": "Запази",
|
||||
"success": "Профилът е обновен успешно",
|
||||
"titleEdit": "Редактиране на профил",
|
||||
"titleFirstTime": "Завършете своя профил"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"context": {
|
||||
"actions": {
|
||||
"delete": "Изтриване",
|
||||
"edit": "Редактиране"
|
||||
},
|
||||
"defaultType": "Контекст",
|
||||
"deleteConfirm": "Сигурни ли сте, че искате да изтриете тази контекстна памет? Това действие не може да бъде отменено.",
|
||||
"deleteTitle": "Изтриване на контекстна памет",
|
||||
"description": "Описание",
|
||||
"empty": "Няма налична контекстна памет",
|
||||
"impact": "Въздействие",
|
||||
"source": "Източник",
|
||||
"urgency": "Спешност"
|
||||
},
|
||||
"empty": {
|
||||
"description": "Извличането на спомени е постепенен процес. Моля, натрупайте повече теми, за да обогатите съдържанието на извлечените спомени. Опитайте се да водите по-задълбочени разговори с асистента, за да уловите и съхраните по-ценна информация.",
|
||||
"search": "Няма намерени съвпадащи спомени",
|
||||
"title": "Няма налични спомени"
|
||||
},
|
||||
"experience": {
|
||||
"actions": {
|
||||
"delete": "Изтриване",
|
||||
"edit": "Редактиране"
|
||||
},
|
||||
"confidence": "Ниво на увереност",
|
||||
"defaultType": "Опит",
|
||||
"deleteConfirm": "Сигурни ли сте, че искате да изтриете тази памет за опит? Това действие не може да бъде отменено.",
|
||||
"deleteTitle": "Изтриване на памет за опит",
|
||||
"empty": "Няма налична памет за опит",
|
||||
"keyLearning": "Ключово знание",
|
||||
"situation": "Контекст",
|
||||
"source": "Източник",
|
||||
"steps": {
|
||||
"action": "Предприето действие",
|
||||
"outcome": "Възможен резултат",
|
||||
"reasoning": "Разсъждение",
|
||||
"situation": "Контекстуален фон"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"search": "Търсене...",
|
||||
"sort": {
|
||||
"createdAt": "Дата на създаване",
|
||||
"scoreConfidence": "Ниво на увереност",
|
||||
"scoreImpact": "Степен на въздействие",
|
||||
"scorePriority": "Приоритет",
|
||||
"scoreUrgency": "Спешност"
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"empty": "Няма запаметени самоличности",
|
||||
"filter": {
|
||||
"search": "Търсене на роля, връзка или описание...",
|
||||
"type": {
|
||||
"all": "Всички",
|
||||
"demographic": "Демографски",
|
||||
"personal": "Лични",
|
||||
"professional": "Професионални"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"confirmDelete": "Потвърдете изтриването",
|
||||
"deleteCancel": "Отказ",
|
||||
"deleteContent": "Сигурни ли сте, че искате да изтриете тази самоличност? Това действие не може да бъде отменено.",
|
||||
"deleteOk": "Изтрий",
|
||||
"noResults": "Няма намерени съвпадащи самоличности",
|
||||
"updated": "Актуализирано"
|
||||
},
|
||||
"roleCloud": {
|
||||
"collapse": "Скрий",
|
||||
"expand": "Покажи още"
|
||||
},
|
||||
"view": {
|
||||
"list": "Списък",
|
||||
"timeline": "Хронология"
|
||||
}
|
||||
},
|
||||
"loading": "Зареждане...",
|
||||
"preference": {
|
||||
"actions": {
|
||||
"delete": "Изтриване",
|
||||
"edit": "Редактиране"
|
||||
},
|
||||
"conclusionDirectives": "Насоки за заключение",
|
||||
"defaultType": "Предпочитание",
|
||||
"deleteConfirm": "Сигурни ли сте, че искате да изтриете тази памет за предпочитание? Това действие не може да бъде отменено.",
|
||||
"deleteTitle": "Изтриване на памет за предпочитание",
|
||||
"empty": "Няма налична памет за предпочитание",
|
||||
"priority": "Приоритет",
|
||||
"source": "Източник",
|
||||
"suggestions": "Препоръки"
|
||||
},
|
||||
"tab": {
|
||||
"contexts": "Контексти",
|
||||
"experiences": "Опит",
|
||||
"home": "Начало",
|
||||
"identities": "Идентичности",
|
||||
"preferences": "Предпочитания",
|
||||
"search": "Търсене"
|
||||
},
|
||||
"viewMode": {
|
||||
"masonry": "Мозайка",
|
||||
"timeline": "Хронология"
|
||||
}
|
||||
}
|
||||
@@ -10,24 +10,24 @@
|
||||
"discover": {
|
||||
"assistants": {
|
||||
"description": "Създаване на съдържание, копиране, въпроси и отговори, генериране на изображения, генериране на видео, генериране на глас, интелигентни агенти, автоматизирани работни потоци, персонализирайте своя собствен AI / GPTs / OLLaMA интелигентен асистент",
|
||||
"title": "AI асистенти"
|
||||
"title": "Общност на интелигентните агенти"
|
||||
},
|
||||
"description": "Създаване на съдържание, копиране, въпроси и отговори, генериране на изображения, генериране на видео, генериране на глас, интелигентни агенти, автоматизирани работни потоци, персонализирани AI приложения, персонализирайте своя собствена AI работна станция",
|
||||
"mcp": {
|
||||
"description": "Търсете, сравнявайте и се свързвайте с хиляди MCP сървъри, които помагат на AI системите лесно да имат достъп до файлови системи, бази данни, API и други ключови ресурси, като по този начин значително разширяват възможностите на вашия AI",
|
||||
"title": "Пазар на MCP сървъри"
|
||||
"title": "Общност на MCP сървърите"
|
||||
},
|
||||
"models": {
|
||||
"description": "Изследвайте основните AI модели OpenAI / GPT / Claude 3 / Gemini / Ollama / Azure / DeepSeek",
|
||||
"title": "AI модели"
|
||||
"title": "Общност на моделите"
|
||||
},
|
||||
"plugins": {
|
||||
"description": "Търсете графични генератори, академични ресурси, генератори на изображения, генератори на видео, генератори на глас и автоматизирани работни потоци, за да интегрирате богати възможности за плъгини във вашия асистент.",
|
||||
"title": "AI плъгини"
|
||||
"title": "Общност на плъгините"
|
||||
},
|
||||
"providers": {
|
||||
"description": "Изследвайте основните доставчици на модели OpenAI / Qwen / Ollama / Anthropic / DeepSeek / Google Gemini / OpenRouter",
|
||||
"title": "Доставчици на AI модели"
|
||||
"title": "Общност на доставчиците на модели"
|
||||
},
|
||||
"search": "Търсене",
|
||||
"title": "Открий"
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"plugins": {
|
||||
"description": "Търсене, генериране на графики, академични изследвания, генериране на изображения, генериране на видео, генериране на глас, автоматизирани работни потоци, персонализирайте ToolCall плъгините на ChatGPT / Claude",
|
||||
"title": "Пазар на плъгини"
|
||||
"title": "Общност на плъгините"
|
||||
},
|
||||
"welcome": {
|
||||
"description": "{{appName}} ви предлага най-доброто изживяване с ChatGPT, Claude, Gemini и OLLaMA WebUI",
|
||||
|
||||
@@ -735,7 +735,7 @@
|
||||
"description": "Opus 4.1 е висок клас модел на Anthropic, оптимизиран за програмиране, сложни логически задачи и продължителни процеси."
|
||||
},
|
||||
"anthropic/claude-opus-4.5": {
|
||||
"description": "Claude Opus 4.5 е водещият модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
"description": "Claude Opus 4.5 е флагманският модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
},
|
||||
"anthropic/claude-sonnet-4": {
|
||||
"description": "Claude Sonnet 4 е хибриден модел за разсъждение от Anthropic, предлагащ комбинирани възможности за мисловни и немисловни задачи."
|
||||
@@ -840,7 +840,7 @@
|
||||
"description": "Claude Opus 4 е най-мощният модел на Anthropic, предназначен за обработка на изключително сложни задачи. Той се отличава с изключителна производителност, интелигентност, плавност и разбиране."
|
||||
},
|
||||
"claude-opus-4-5-20251101": {
|
||||
"description": "Claude Opus 4.5 е водещият модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
"description": "Claude Opus 4.5 е флагманският модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
},
|
||||
"claude-sonnet-4-20250514": {
|
||||
"description": "Claude Sonnet 4 може да генерира почти мигновени отговори или удължено стъпково мислене, като потребителите могат ясно да проследят тези процеси."
|
||||
@@ -1229,6 +1229,9 @@
|
||||
"deepseek_r1_distill_qwen_32b": {
|
||||
"description": "DeepSeek-R1-Distill-Qwen-32B е модел, получен чрез знание на дестилация на Qwen2.5-32B. Този модел е финализиран с 800 000 избрани примера, генерирани от DeepSeek-R1, показвайки изключителна производителност в множество области, включително математика, програмиране и разсъждения."
|
||||
},
|
||||
"devstral-2:123b": {
|
||||
"description": "Devstral 2 123B модел, специализиран в използването на инструменти за изследване на кодови бази, редактиране на множество файлове и подпомагане на агенти за софтуерно инженерство."
|
||||
},
|
||||
"doubao-1.5-lite-32k": {
|
||||
"description": "Doubao-1.5-lite е ново поколение лек модел, с изключителна скорост на отговор, който постига световно ниво както по отношение на ефективността, така и на времето за реакция."
|
||||
},
|
||||
@@ -1695,7 +1698,7 @@
|
||||
"description": "GLM-Zero-Preview притежава мощни способности за сложни разсъждения, показвайки отлични резултати в логическото разсъждение, математиката и програмирането."
|
||||
},
|
||||
"global.anthropic.claude-opus-4-5-20251101-v1:0": {
|
||||
"description": "Claude Opus 4.5 е водещият модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
"description": "Claude Opus 4.5 е флагманският модел на Anthropic, който съчетава изключителен интелект и мащабируема производителност, подходящ за сложни задачи, изискващи най-високо качество на отговорите и способност за разсъждение."
|
||||
},
|
||||
"google/gemini-2.0-flash": {
|
||||
"description": "Gemini 2.0 Flash е високопроизводителен модел за разсъждение от Google, подходящ за разширени мултимодални задачи."
|
||||
@@ -1937,6 +1940,15 @@
|
||||
"gpt-5.1-codex-mini": {
|
||||
"description": "GPT-5.1 Codex mini: по-компактен и икономичен вариант на Codex, оптимизиран за агентски задачи по кодиране."
|
||||
},
|
||||
"gpt-5.2": {
|
||||
"description": "GPT-5.2 — водещ модел за програмиране и агентни работни потоци, предлагащ по-силни способности за разсъждение и работа с дълъг контекст."
|
||||
},
|
||||
"gpt-5.2-chat-latest": {
|
||||
"description": "GPT-5.2 Chat: Вариант на GPT-5.2, използван от ChatGPT (chat-latest), предназначен за най-новите подобрения в диалога."
|
||||
},
|
||||
"gpt-5.2-pro": {
|
||||
"description": "GPT-5.2 pro: По-интелигентна и прецизна версия на GPT-5.2 (само чрез Responses API), подходяща за сложни задачи и дълги многоетапни разсъждения."
|
||||
},
|
||||
"gpt-audio": {
|
||||
"description": "GPT Audio е универсален чат модел, ориентиран към аудио вход и изход, поддържащ използване на аудио I/O в Chat Completions API."
|
||||
},
|
||||
@@ -2211,7 +2223,7 @@
|
||||
"description": "Kimi K2 Instruct, официален модел за извеждане от Kimi, поддържащ дълъг контекст, програмиране, въпроси и отговори и други сценарии."
|
||||
},
|
||||
"kimi-k2-thinking": {
|
||||
"description": "Моделът kimi-k2-thinking, предоставен от Moonshot AI, е мисловен модел с универсални агентни и логически способности. Той е отличен в дълбокото разсъждение и може да използва инструменти на няколко стъпки, за да помага при решаването на различни сложни проблеми."
|
||||
"description": "Моделът kimi-k2-thinking, предоставен от Moonshot AI, е мисловен модел с универсални агентни способности и умения за разсъждение. Той е отличен в дълбокото разсъждение и може да използва инструменти на няколко стъпки, за да помага при решаването на различни сложни проблеми."
|
||||
},
|
||||
"kimi-k2-thinking-turbo": {
|
||||
"description": "Ускорена версия на модела K2 за дълбоко разсъждение, поддържа 256k контекст и предлага скорост на изход от 60–100 токена в секунда при запазване на дълбоките логически способности."
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
{
|
||||
"builtins": {
|
||||
"lobe-agent-builder": {
|
||||
"apiName": {
|
||||
"getAvailableModels": "Извличане на налични модели",
|
||||
"getAvailableTools": "Извличане на налични инструменти",
|
||||
"getConfig": "Извличане на конфигурация",
|
||||
"getMeta": "Извличане на метаданни",
|
||||
"getPrompt": "Извличане на системен подкана",
|
||||
"searchMarketTools": "Търсене в магазина за плъгини",
|
||||
"searchOfficialTools": "Търсене на официални инструменти",
|
||||
"setModel": "Задаване на модел",
|
||||
"setOpeningMessage": "Задаване на начално съобщение",
|
||||
"setOpeningQuestions": "Задаване на начални въпроси",
|
||||
"togglePlugin": "Превключване на плъгин",
|
||||
"updateChatConfig": "Актуализиране на конфигурацията на чата",
|
||||
"updateConfig": "Актуализиране на конфигурацията",
|
||||
"updateMeta": "Актуализиране на метаданните",
|
||||
"updatePrompt": "Актуализиране на системния подкана"
|
||||
},
|
||||
"title": "Създател на агент"
|
||||
},
|
||||
"lobe-group-management": {
|
||||
"apiName": {
|
||||
"broadcast": "Изказване пред всички",
|
||||
"createAgent": "Добавяне на член на екипа",
|
||||
"createWorkflow": "Планиране на работен процес",
|
||||
"executeTask": "Изпълнение на задача",
|
||||
"getAgentInfo": "Получаване на информация за член",
|
||||
"interrupt": "Прекъсване на задача",
|
||||
"inviteAgent": "Покана за членство",
|
||||
"removeAgent": "Премахване на член",
|
||||
"searchAgent": "Търсене на експерт",
|
||||
"speak": "Назначаване на говорител",
|
||||
"summarize": "Обобщаване на разговора",
|
||||
"vote": "Иницииране на гласуване"
|
||||
},
|
||||
"title": "Координиране на екипа"
|
||||
},
|
||||
"lobe-knowledge-base": {
|
||||
"apiName": {
|
||||
"readKnowledge": "Прочети съдържанието на базата знания",
|
||||
@@ -24,6 +61,41 @@
|
||||
},
|
||||
"title": "Локална система"
|
||||
},
|
||||
"lobe-page-agent": {
|
||||
"apiName": {
|
||||
"batchUpdate": "Партидно актуализиране на възли",
|
||||
"compareSnapshots": "Сравняване на моментни снимки",
|
||||
"convertToList": "Преобразуване в списък",
|
||||
"createNode": "Създаване на възел",
|
||||
"cropImage": "Изрязване на изображение",
|
||||
"deleteNode": "Изтриване на възел",
|
||||
"deleteSnapshot": "Изтриване на моментна снимка",
|
||||
"deleteTableColumn": "Изтриване на колона от таблица",
|
||||
"deleteTableRow": "Изтриване на ред от таблица",
|
||||
"duplicateNode": "Дублиране на възел",
|
||||
"editTitle": "Редактиране на заглавието на документа",
|
||||
"indentListItem": "Увеличаване на отстъпа на елемент от списък",
|
||||
"initPage": "Инициализиране на документа",
|
||||
"insertTableColumn": "Вмъкване на колона в таблица",
|
||||
"insertTableRow": "Вмъкване на ред в таблица",
|
||||
"listSnapshots": "Списък с моментни снимки",
|
||||
"mergeNodes": "Сливане на възли",
|
||||
"moveNode": "Преместване на възел",
|
||||
"outdentListItem": "Намаляване на отстъпа на елемент от списък",
|
||||
"replaceText": "Замяна на текст",
|
||||
"resizeImage": "Преоразмеряване на изображение",
|
||||
"restoreSnapshot": "Възстановяване на моментна снимка",
|
||||
"rotateImage": "Завъртане на изображение",
|
||||
"saveSnapshot": "Запазване на моментна снимка",
|
||||
"setImageAlt": "Задаване на алтернативен текст на изображение",
|
||||
"splitNode": "Разделяне на възел",
|
||||
"toggleListType": "Превключване на тип списък",
|
||||
"unwrapNode": "Разопаковане на възел",
|
||||
"updateNode": "Актуализиране на възел",
|
||||
"wrapNodes": "Опаковане на възли"
|
||||
},
|
||||
"title": "Документ"
|
||||
},
|
||||
"lobe-web-browsing": {
|
||||
"apiName": {
|
||||
"crawlMultiPages": "Прочети съдържание от няколко страници",
|
||||
@@ -338,6 +410,8 @@
|
||||
"installed": "Инсталиран"
|
||||
},
|
||||
"config": {
|
||||
"addEnv": "Добавяне на променлива на средата",
|
||||
"addHeaders": "Добавяне на заглавки на заявката",
|
||||
"args": "Параметри",
|
||||
"command": "Команда",
|
||||
"env": "Променливи на средата",
|
||||
@@ -358,12 +432,15 @@
|
||||
},
|
||||
"title": "Инсталиране на персонализиран плъгин"
|
||||
},
|
||||
"install": {
|
||||
"title": "Информация за инсталацията"
|
||||
},
|
||||
"marketplace": {
|
||||
"title": "Инсталиране на трети плъгини",
|
||||
"trustedBy": "Предоставено от {{name}}",
|
||||
"unverified": {
|
||||
"title": "Непроверени трети плъгини",
|
||||
"warning": "Този плъгин идва от непроверен трети пазар, моля уверете се, че имате доверие на източника преди инсталация."
|
||||
"warning": "Този плъгин е от непроверена общност на трета страна. Моля, уверете се, че имате доверие на източника, преди да го инсталирате."
|
||||
},
|
||||
"verified": "Проверен"
|
||||
},
|
||||
@@ -441,7 +518,7 @@
|
||||
"envConfigDescription": "Тези настройки ще бъдат предадени като променливи на средата при стартиране на MCP сървъра",
|
||||
"httpTypeNotice": "HTTP тип MCP плъгини в момента не изискват конфигуриране на променливи на средата",
|
||||
"indexUrl": {
|
||||
"title": "Индекс на пазара",
|
||||
"title": "Индекс на общността",
|
||||
"tooltip": "В момента не се поддържа онлайн редактиране, моля настройте чрез променливи на средата при разгръщане"
|
||||
},
|
||||
"messages": {
|
||||
@@ -450,14 +527,14 @@
|
||||
"envUpdateFailed": "Записът на променливите на средата не бе успешен",
|
||||
"envUpdateSuccess": "Променливите на средата са записани успешно"
|
||||
},
|
||||
"modalDesc": "След конфигуриране на адреса на пазара на плъгини, можете да използвате персонализиран пазар на плъгини",
|
||||
"modalDesc": "След като конфигурирате адреса на общността за плъгини, можете да използвате персонализирана общност за плъгини",
|
||||
"rules": {
|
||||
"argsRequired": "Моля, въведете стартови параметри",
|
||||
"commandRequired": "Моля, въведете команда за стартиране",
|
||||
"urlRequired": "Моля, въведете адрес на услугата"
|
||||
},
|
||||
"saveSettings": "Запази настройките",
|
||||
"title": "Настройки на пазара на плъгини"
|
||||
"title": "Настройки на общността за плъгини"
|
||||
},
|
||||
"showInPortal": "Моля, прегледайте детайлите в работната област",
|
||||
"store": {
|
||||
|
||||
+102
-19
@@ -2,6 +2,7 @@
|
||||
"about": {
|
||||
"title": "Относно"
|
||||
},
|
||||
"advancedSettings": "Разширени настройки",
|
||||
"agentInfoDescription": {
|
||||
"basic": {
|
||||
"avatar": "Аватар",
|
||||
@@ -11,7 +12,6 @@
|
||||
"title": "Информация за асистента"
|
||||
},
|
||||
"chat": {
|
||||
"displayMode": "Режим на показване",
|
||||
"enableHistoryCount": "Разреши броене на историята",
|
||||
"historyCount": "Брой съобщения в историята",
|
||||
"no": "Не",
|
||||
@@ -76,6 +76,13 @@
|
||||
"title": "Нулиране на всички настройки"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"aiConfig": "AI конфигурация",
|
||||
"common": "Общи",
|
||||
"market": "Пазар",
|
||||
"profile": "Акаунт",
|
||||
"system": "Система"
|
||||
},
|
||||
"groupTab": {
|
||||
"chat": "Чат",
|
||||
"members": "Членове",
|
||||
@@ -85,7 +92,7 @@
|
||||
"desc": "Предпочитания и настройки на модела.",
|
||||
"global": "Глобални настройки",
|
||||
"group": "Настройки на екипа",
|
||||
"groupDesc": "Управление на екипа от агенти и предпочитания за чат",
|
||||
"groupDesc": "Управление на групи и предпочитания за чат",
|
||||
"session": "Настройки на сесията",
|
||||
"sessionDesc": "Задаване на роля и предпочитания за сесия.",
|
||||
"sessionWithName": "Настройки на сесията · {{name}}",
|
||||
@@ -216,32 +223,82 @@
|
||||
"messages": {
|
||||
"createVersionFailed": "Неуспешно създаване на версия: {{message}}",
|
||||
"fetchRemoteFailed": "Неуспешно извличане на отдалечени данни за асистента",
|
||||
"missingIdentifier": "Текущият асистент няма идентификатор за пазара",
|
||||
"notAuthenticated": "Моля, влезте в акаунта си в пазара",
|
||||
"missingIdentifier": "Този асистент все още няма идентификатор в общността",
|
||||
"notAuthenticated": "Моля, първо влезте в акаунта си в общността",
|
||||
"publishFailed": "Публикуването не бе успешно: {{message}}"
|
||||
},
|
||||
"submitButton": "Публикувай",
|
||||
"title": {
|
||||
"submit": "Сподели в пазара за асистенти",
|
||||
"submit": "Споделяне в общността на асистенти",
|
||||
"upload": "Публикувай нова версия"
|
||||
}
|
||||
},
|
||||
"resultModal": {
|
||||
"message": "Асистентът е изпратен за преглед. След одобрение ще бъде автоматично публикуван. Кликнете върху „Преглед в пазара“, за да видите публикувания асистент.",
|
||||
"view": "Преглед в пазара"
|
||||
"message": "Вашият асистент е изпратен за преглед. След одобрение ще бъде публикуван автоматично.",
|
||||
"title": "Успешно изпращане",
|
||||
"view": "Виж в общността"
|
||||
},
|
||||
"submit": {
|
||||
"button": "Сподели в пазара",
|
||||
"tooltip": "Сподели асистента в пазара"
|
||||
"button": "Сподели в общността",
|
||||
"tooltip": "Споделете асистента в общността"
|
||||
},
|
||||
"upload": {
|
||||
"button": "Публикувай нова версия",
|
||||
"tooltip": "Публикувай нова версия в пазара за асистенти"
|
||||
"tooltip": "Публикувай нова версия в общността на асистенти"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"success": "Актуализацията беше успешна"
|
||||
},
|
||||
"myAgents": {
|
||||
"actions": {
|
||||
"cancel": "Отказ",
|
||||
"confirmDeprecate": "Потвърди оттегляне",
|
||||
"deprecate": "Постоянно оттегляне",
|
||||
"deprecateConfirmContent": "След оттегляне, този агент ще бъде премахнат от пазара завинаги и няма да може да бъде публикуван отново. Това действие е необратимо, моля, действайте внимателно.",
|
||||
"deprecateConfirmTitle": "Сигурни ли сте, че искате да оттеглите агента?",
|
||||
"deprecateError": "Неуспешно оттегляне на агента",
|
||||
"deprecateLoading": "Оттегляне на агента...",
|
||||
"deprecateSuccess": "Агентът е оттеглен",
|
||||
"edit": "Редактиране на агента",
|
||||
"publish": "Публикуване на агента",
|
||||
"publishError": "Неуспешно публикуване на агента",
|
||||
"publishLoading": "Публикуване на агента...",
|
||||
"publishSuccess": "Агентът е публикуван",
|
||||
"unpublish": "Сваляне на агента",
|
||||
"unpublishError": "Неуспешно сваляне на агента",
|
||||
"unpublishLoading": "Сваляне на агента...",
|
||||
"unpublishSuccess": "Агентът е свален",
|
||||
"viewDetail": "Преглед на подробности"
|
||||
},
|
||||
"detail": {
|
||||
"category": "Категория",
|
||||
"description": "Описание",
|
||||
"identifier": "Идентификатор",
|
||||
"title": "Подробности за агента"
|
||||
},
|
||||
"empty": {
|
||||
"description": "Все още не сте публикували агенти на пазара",
|
||||
"title": "Няма публикувани агенти"
|
||||
},
|
||||
"errors": {
|
||||
"editFailed": "Неуспешно редактиране на агента, моля опитайте отново по-късно",
|
||||
"fetchFailed": "Неуспешно зареждане на подробности за агента",
|
||||
"notAuthenticated": "Моля, влезте в акаунта си за пазара"
|
||||
},
|
||||
"loginRequired": {
|
||||
"button": "Вход в акаунта за пазара",
|
||||
"description": "Моля, влезте в акаунта си за пазара, за да видите публикуваните от вас агенти",
|
||||
"title": "Необходим е вход"
|
||||
},
|
||||
"status": {
|
||||
"archived": "Архивиран",
|
||||
"deprecated": "Оттеглен",
|
||||
"published": "Публикуван",
|
||||
"unpublished": "Непубликуван"
|
||||
},
|
||||
"title": "Моите публикувани агенти"
|
||||
},
|
||||
"plugin": {
|
||||
"addMCPPlugin": "Добавяне на MCP плъгин",
|
||||
"addTooltip": "Персонализиран плъгин",
|
||||
@@ -259,6 +316,7 @@
|
||||
},
|
||||
"settingAgent": {
|
||||
"avatar": {
|
||||
"sizeExceeded": "Размерът на изображението надвишава ограничението от 1MB. Моля, изберете по-малко изображение.",
|
||||
"title": "Аватар"
|
||||
},
|
||||
"backgroundColor": {
|
||||
@@ -274,12 +332,12 @@
|
||||
"title": "Име"
|
||||
},
|
||||
"prompt": {
|
||||
"placeholder": "Въведете дума за подкана за роля",
|
||||
"title": "Настройка на ролята"
|
||||
"placeholder": "Въведете настройките на асистента, натиснете / за отваряне на менюто с команди",
|
||||
"title": "Настройки на асистента"
|
||||
},
|
||||
"submit": "Актуализиране на информацията за асистента",
|
||||
"tag": {
|
||||
"desc": "Етикетите на асистента ще се показват на пазара за асистенти",
|
||||
"desc": "Етикетите на асистента ще се показват в общността на асистенти",
|
||||
"placeholder": "Въведете таг",
|
||||
"title": "Таг"
|
||||
},
|
||||
@@ -391,10 +449,24 @@
|
||||
}
|
||||
},
|
||||
"settingCommon": {
|
||||
"devMode": {
|
||||
"desc": "Когато е активиран, ще се показват функции и опции за разработчици",
|
||||
"title": "Режим за разработчици"
|
||||
},
|
||||
"lang": {
|
||||
"autoMode": "Следвай системата",
|
||||
"title": "Език"
|
||||
},
|
||||
"liteMode": {
|
||||
"desc": "Когато е активиран, интерфейсът ще бъде опростен и разширените функции ще бъдат скрити",
|
||||
"title": "Олекотен режим"
|
||||
},
|
||||
"responseLanguage": {
|
||||
"auto": "Следвай системата",
|
||||
"desc": "Задайте езика, който AI ще използва за отговори",
|
||||
"placeholder": "Изберете език за отговор",
|
||||
"title": "Език на отговора"
|
||||
},
|
||||
"themeMode": {
|
||||
"auto": "Автоматичен",
|
||||
"dark": "Тъмен",
|
||||
@@ -425,7 +497,7 @@
|
||||
"placeholder": "Моля, въведете системно подсещане за водещия",
|
||||
"title": "Системно подсещане за водещия"
|
||||
},
|
||||
"title": "Информация за екипа от агенти"
|
||||
"title": "Информация за групата"
|
||||
},
|
||||
"settingGroupChat": {
|
||||
"allowDM": {
|
||||
@@ -433,7 +505,7 @@
|
||||
"title": "Разреши лични съобщения от асистента"
|
||||
},
|
||||
"enableSupervisor": {
|
||||
"desc": "Активиране на функцията за модератор на екипа от агенти. Модераторът ще управлява хода на разговорите в екипа.",
|
||||
"desc": "Активиране на функцията за модератор на групата, като модераторът ще управлява хода на екипния разговор",
|
||||
"title": "Активирай модератор"
|
||||
},
|
||||
"maxResponseInRow": {
|
||||
@@ -631,6 +703,7 @@
|
||||
"title": "Глас за преобразуване на текст в реч"
|
||||
}
|
||||
},
|
||||
"startConversation": "Започване на разговор",
|
||||
"storage": {
|
||||
"actions": {
|
||||
"export": {
|
||||
@@ -663,7 +736,8 @@
|
||||
"identifier": "Идентификатор на асистента (identifier)",
|
||||
"metaMiss": "Моля, попълнете информацията за агента, преди да го изпратите. Тя трябва да включва име, описание и тагове",
|
||||
"placeholder": "Въведете уникален идентификатор за агента, напр. web-development",
|
||||
"tooltips": "Споделяне на пазара на агенти"
|
||||
"success": "Асистентът е изпратен успешно",
|
||||
"tooltips": "Сподели в общността на асистенти"
|
||||
},
|
||||
"submitFooter": {
|
||||
"reset": "Нулиране",
|
||||
@@ -758,19 +832,26 @@
|
||||
"tab": {
|
||||
"about": "Относно",
|
||||
"agent": "Агент по подразбиране",
|
||||
"common": "Общи настройки",
|
||||
"apikey": "Управление на API ключове",
|
||||
"common": "Външен вид",
|
||||
"experiment": "Експеримент",
|
||||
"hotkey": "Бързи клавиши",
|
||||
"image": "AI рисуване",
|
||||
"image": "Услуга за рисуване",
|
||||
"llm": "Езиков модел",
|
||||
"my-agents": "Моите публикувани агенти",
|
||||
"profile": "Моят акаунт",
|
||||
"provider": "AI доставчик",
|
||||
"proxy": "Мрежов прокси",
|
||||
"security": "Сигурност",
|
||||
"stats": "Статистика",
|
||||
"storage": "Данни за хранилище",
|
||||
"sync": "Синхронизиране в облака",
|
||||
"system-agent": "Системен асистент",
|
||||
"tts": "Текст към реч"
|
||||
"tts": "Текст към реч",
|
||||
"usage": "Използване"
|
||||
},
|
||||
"tools": {
|
||||
"add": "Интегрирай плъгин",
|
||||
"builtins": {
|
||||
"groupName": "Вградени"
|
||||
},
|
||||
@@ -796,6 +877,8 @@
|
||||
"tools": "инструмента",
|
||||
"verifyAuth": "Удостоверяването е завършено"
|
||||
},
|
||||
"notInstalled": "Не е инсталиран",
|
||||
"notInstalledWarning": "Този плъгин не е инсталиран и може да повлияе на работата на асистента",
|
||||
"plugins": {
|
||||
"enabled": "Активирани: {{num}}",
|
||||
"groupName": "Плъгини",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"actions": {
|
||||
"addNewTopic": "Създаване на нова тема",
|
||||
"autoRename": "Автоматично преименуване",
|
||||
"confirmRemoveAll": "Ще бъдат изтрити всички теми. След изтриването им не може да се възстановят. Моля, действайте внимателно.",
|
||||
"confirmRemoveTopic": "Ще бъде изтрита тази тема. След изтриването ѝ не може да се възстанови. Моля, действайте внимателно.",
|
||||
"confirmRemoveUnstarred": "Ще бъдат изтрити темите, които не са запазени. След изтриването им не може да се възстановят. Моля, действайте внимателно.",
|
||||
"duplicate": "Създаване на копие",
|
||||
"export": "Експортиране на темата",
|
||||
"openInNewWindow": "Отвори страницата в нов прозорец",
|
||||
"openInNewWindow": "Отвори в нов прозорец",
|
||||
"removeAll": "Изтриване на всички теми",
|
||||
"removeUnstarred": "Изтриване на незапазените теми"
|
||||
},
|
||||
"defaultTitle": "По подразбиране тема",
|
||||
"displayItems": "Показване на елементи",
|
||||
"duplicateLoading": "Копиране на темата...",
|
||||
"duplicateSuccess": "Темата е копирана успешно",
|
||||
"favorite": "Запазено",
|
||||
@@ -32,6 +34,7 @@
|
||||
"desc": "Кликнете върху бутона отляво за изпращане, за да запазите текущия разговор като историческа тема и да започнете нова сесия.",
|
||||
"title": "Списък с теми"
|
||||
},
|
||||
"loadMore": "Още",
|
||||
"searchPlaceholder": "Търсене на теми...",
|
||||
"searchResultEmpty": "Няма намерени резултати",
|
||||
"temp": "Временен",
|
||||
|
||||
+154
-108
@@ -1,362 +1,408 @@
|
||||
{
|
||||
"guide": {
|
||||
"agents": {
|
||||
"replaceBtn": "Смени",
|
||||
"title": "Препоръчване на нови асистенти:"
|
||||
"replaceBtn": "Смени партидата",
|
||||
"title": "Препоръчани нови асистенти:"
|
||||
},
|
||||
"defaultMessage": "Аз съм вашият личен интелигентен асистент {{appName}}. Как мога да ви помогна сега?<br />Ако имате нужда от по-професионален или персонализиран асистент, можете да кликнете на <plus />, за да създадете персонализиран асистент.",
|
||||
"defaultMessageWithoutCreate": "Аз съм вашият личен интелигентен асистент {{appName}}. Как мога да ви помогна сега?",
|
||||
"defaultMessage": "Аз съм вашият личен интелигентен асистент {{appName}}. С какво мога да ви помогна днес?<br />Ако имате нужда от по-професионален или персонализиран асистент, можете да кликнете <plus /> за да създадете свой собствен.",
|
||||
"defaultMessageWithoutCreate": "Аз съм вашият личен интелигентен асистент {{appName}}. С какво мога да ви помогна днес?",
|
||||
"groupActivities": {
|
||||
"analysis": {
|
||||
"codeReview": {
|
||||
"description": "Техническо обсъждане и преглед на промените в кода и реализацията от колеги",
|
||||
"description": "Технически дискусии и преглед от колеги на промени и реализации в кода",
|
||||
"emoji": "💻",
|
||||
"prompt": "Нека прегледаме някакъв код заедно. Можеш ли да ни помогнеш да анализираме този код и да идентифицираме възможности за подобрение?",
|
||||
"prompt": "Нека прегледаме малко код заедно. Можеш ли да ни помогнеш да го анализираме и да открием възможности за подобрение?",
|
||||
"title": "Преглед на код"
|
||||
},
|
||||
"investment": {
|
||||
"description": "Анализ на пазара, обсъждане на инвестиционни стратегии и споделяне на финансови прозрения",
|
||||
"emoji": "📈",
|
||||
"prompt": "Нека анализираме пазара заедно. Можеш ли да ни помогнеш да обсъдим инвестиционни стратегии и да споделим финансови прозрения?",
|
||||
"prompt": "Нека анализираме пазара заедно. Можеш ли да ни помогнеш да обсъдим стратегии и да споделиш финансови прозрения?",
|
||||
"title": "Инвестиционен клуб"
|
||||
},
|
||||
"research": {
|
||||
"description": "Изследване на научни концепции, провеждане на експерименти и споделяне на открития",
|
||||
"emoji": "🔬",
|
||||
"prompt": "Нека изследваме науката заедно! Можеш ли да ни помогнеш да проведем експерименти и да споделим нашите открития?",
|
||||
"prompt": "Нека изследваме науката заедно! Можеш ли да ни помогнеш с експерименти и да споделим откритията си?",
|
||||
"title": "Научна изложба"
|
||||
},
|
||||
"study": {
|
||||
"description": "Съвместни учебни сесии, обсъждане на концепции и решаване на проблеми заедно",
|
||||
"description": "Съвместни учебни сесии за обсъждане на концепции и решаване на проблеми",
|
||||
"emoji": "📚",
|
||||
"prompt": "Нека сформираме учебна група. Можеш ли да ни помогнеш да разберем тези концепции и да решим проблемите заедно?",
|
||||
"prompt": "Нека създадем учебна група. Можеш ли да ни помогнеш да разберем концепциите и да решим задачите заедно?",
|
||||
"title": "Учебна група"
|
||||
}
|
||||
},
|
||||
"brainstorm": {
|
||||
"artWorkshop": {
|
||||
"description": "Създаване, коментиране и оценяване на различни форми на визуално и дигитално изкуство",
|
||||
"description": "Създаване, коментиране и оценяване на визуално и дигитално изкуство",
|
||||
"emoji": "🖼️",
|
||||
"prompt": "Нека организираме арт работилница! Можеш ли да ни помогнеш да създаваме, коментираме и оценяваме различни форми на изкуство?",
|
||||
"prompt": "Нека организираме арт работилница! Можеш ли да ни помогнеш да създаваме, обсъждаме и оценяваме различни форми на изкуство?",
|
||||
"title": "Арт работилница"
|
||||
},
|
||||
"debate": {
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални въпроси",
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални събития",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "Нека проведем структурирана дискусия. Можеш ли да ни помогнеш да организираме аргументиран дебат по тази тема?",
|
||||
"prompt": "Нека проведем структуриран дебат. Можеш ли да ни помогнеш да организираме аргументирана дискусия по темата?",
|
||||
"title": "Дебатен клуб"
|
||||
},
|
||||
"designReview": {
|
||||
"description": "Съвместни сесии за обратна връзка относно дизайнерски концепции, прототипи или творчески проекти",
|
||||
"description": "Съвместни сесии за обратна връзка по дизайн концепции, прототипи и творчески проекти",
|
||||
"emoji": "🎨",
|
||||
"prompt": "Трябва да прегледаме някои дизайнерски проекти. Можеш ли да ни помогнеш да дадем конструктивна обратна връзка за концепциите и прототипите?",
|
||||
"prompt": "Трябва да прегледаме някои дизайнерски проекти. Можеш ли да ни помогнеш с конструктивна обратна връзка?",
|
||||
"title": "Преглед на дизайн"
|
||||
},
|
||||
"ideation": {
|
||||
"description": "Многоаспектно сътрудничество за генериране на идеи и креативно решаване на проблеми",
|
||||
"description": "Съвместно генериране на идеи и креативно решаване на проблеми от различни гледни точки",
|
||||
"emoji": "🧠",
|
||||
"prompt": "Нека започнем мозъчна атака за проекта. Можеш ли да ни помогнеш да генерираме идеи и решения?",
|
||||
"title": "Мозъчна атака"
|
||||
"prompt": "Нека започнем брейнсторминг за проекта. Можеш ли да ни помогнеш с идеи и решения?",
|
||||
"title": "Брейнсторминг"
|
||||
}
|
||||
},
|
||||
"game": {
|
||||
"debateClub": {
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални въпроси",
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални събития",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "Нека проведем структурирана дискусия. Можеш ли да ни помогнеш да организираме аргументиран дебат по тази тема?",
|
||||
"prompt": "Нека проведем структуриран дебат. Можеш ли да ни помогнеш да организираме аргументирана дискусия по темата?",
|
||||
"title": "Дебатен клуб"
|
||||
},
|
||||
"gameNight": {
|
||||
"description": "Забавни интерактивни игри и дейности за изграждане на екипен дух и забавление",
|
||||
"description": "Забавни интерактивни игри и дейности за изграждане на екип и приятно прекарване",
|
||||
"emoji": "🎲",
|
||||
"prompt": "Време е за игрова вечер! Можеш ли да ни помогнеш да организираме забавни интерактивни игри за изграждане на екипен дух?",
|
||||
"prompt": "Време е за игрова вечер! Можеш ли да ни помогнеш да организираме забавни игри за екипно сплотяване?",
|
||||
"title": "Игрова вечер"
|
||||
},
|
||||
"modelUN": {
|
||||
"description": "Симулация на ООН с дебати и дипломатически преговори по глобални въпроси",
|
||||
"description": "Симулация на дебати в ООН и дипломатически преговори по глобални въпроси",
|
||||
"emoji": "🌍",
|
||||
"prompt": "Нека симулираме дебат в ООН. Можеш ли да ни помогнеш да организираме дипломатически преговори по глобални въпроси?",
|
||||
"title": "Симулация на ООН"
|
||||
"prompt": "Нека симулираме дебат в ООН. Можеш ли да ни помогнеш да организираме дипломатически преговори по глобална тема?",
|
||||
"title": "Модел на ООН"
|
||||
},
|
||||
"werewolf": {
|
||||
"description": "Социална игра за разгадаване на вълци чрез стратегия и дискусии",
|
||||
"description": "Социална логическа игра, в която играчите откриват върколака чрез стратегия и дискусия",
|
||||
"emoji": "🐺",
|
||||
"prompt": "Нека играем Вълк! Можеш ли да ни помогнеш да установим правилата и да водиш тази социална игра за разгадаване?",
|
||||
"title": "Игра Вълк"
|
||||
"prompt": "Нека играем Върколак! Можеш ли да ни помогнеш с правилата и да водиш играта?",
|
||||
"title": "Върколак"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"brainstorm": {
|
||||
"description": "Многоаспектно сътрудничество за генериране на идеи и креативно решаване на проблеми",
|
||||
"description": "Съвместно генериране на идеи и креативно решаване на проблеми от различни гледни точки",
|
||||
"emoji": "🧠",
|
||||
"prompt": "Нека започнем мозъчна атака за проекта. Можеш ли да ни помогнеш да генерираме идеи и решения?",
|
||||
"title": "Мозъчна атака"
|
||||
"prompt": "Нека започнем брейнсторминг за проекта. Можеш ли да ни помогнеш с идеи и решения?",
|
||||
"title": "Брейнсторминг"
|
||||
},
|
||||
"debate": {
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални въпроси",
|
||||
"description": "Структурирани дискусии и дебати по различни теми и актуални събития",
|
||||
"emoji": "⚖️",
|
||||
"prompt": "Нека проведем структурирана дискусия. Можеш ли да ни помогнеш да организираме аргументиран дебат по тази тема?",
|
||||
"prompt": "Нека проведем структуриран дебат. Можеш ли да ни помогнеш да организираме аргументирана дискусия по темата?",
|
||||
"title": "Дебатен клуб"
|
||||
},
|
||||
"languagePractice": {
|
||||
"description": "Практика на говорене и учене на нов език с носители на езика",
|
||||
"description": "Практикуване на говорим език с носители и изучаване на нови езици",
|
||||
"emoji": "🗣️",
|
||||
"prompt": "Нека практикуваме нов език заедно. Можеш ли да ни помогнеш да учим и практикуваме говорене на този език?",
|
||||
"prompt": "Нека упражняваме нов език заедно. Можеш ли да ни помогнеш да учим и практикуваме говоренето?",
|
||||
"title": "Езикова практика"
|
||||
},
|
||||
"studyGroup": {
|
||||
"description": "Съвместни учебни сесии, обсъждане на концепции и решаване на проблеми заедно",
|
||||
"description": "Съвместни учебни сесии за обсъждане на концепции и решаване на проблеми",
|
||||
"emoji": "📚",
|
||||
"prompt": "Нека сформираме учебна група. Можеш ли да ни помогнеш да разберем тези концепции и да решим проблемите заедно?",
|
||||
"prompt": "Нека създадем учебна група. Можеш ли да ни помогнеш да разберем концепциите и да решим задачите заедно?",
|
||||
"title": "Учебна група"
|
||||
}
|
||||
},
|
||||
"planning": {
|
||||
"cookingClass": {
|
||||
"description": "Учене и споделяне на кулинарни умения, рецепти и традиции",
|
||||
"description": "Изучаване и споделяне на кулинарни умения, рецепти и традиции",
|
||||
"emoji": "👨🍳",
|
||||
"prompt": "Нека посетим кулинарен клас! Можеш ли да ни помогнеш да научим нови рецепти и кулинарни техники?",
|
||||
"prompt": "Нека направим кулинарен клас! Можеш ли да ни помогнеш да научим нови рецепти и техники?",
|
||||
"title": "Кулинарен клас"
|
||||
},
|
||||
"fitnessChallenge": {
|
||||
"description": "Поставяне на групови фитнес цели, споделяне на упражнения и взаимна мотивация",
|
||||
"emoji": "💪",
|
||||
"prompt": "Нека започнем фитнес предизвикателство! Можеш ли да ни помогнеш да поставим цели и да се мотивираме взаимно да останем здрави?",
|
||||
"prompt": "Нека започнем фитнес предизвикателство! Можеш ли да ни помогнеш да поставим цели и да се мотивираме взаимно?",
|
||||
"title": "Фитнес предизвикателство"
|
||||
},
|
||||
"planningPoker": {
|
||||
"description": "Аджайл техника за оценка на задачи и обем на работа чрез карти",
|
||||
"description": "Agile техника за оценка на задачи и усилия чрез карти",
|
||||
"emoji": "🃏",
|
||||
"prompt": "Провеждаме планиращ покер за проекта. Можеш ли да ни помогнеш да използваме аджайл техники за оценка на обема на задачите?",
|
||||
"title": "Планиращ покер"
|
||||
"prompt": "Планираме проект с Planning Poker. Можеш ли да ни помогнеш да оценим задачите с agile методи?",
|
||||
"title": "Planning Poker"
|
||||
},
|
||||
"travelPlanning": {
|
||||
"description": "Планиране на пътувания, споделяне на преживявания и откриване на нови дестинации",
|
||||
"emoji": "✈️",
|
||||
"prompt": "Нека планираме пътуване заедно! Можеш ли да ни помогнеш да проучим дестинации и да организираме маршрута?",
|
||||
"prompt": "Нека планираме пътуване заедно! Можеш ли да ни помогнеш с проучване и организация?",
|
||||
"title": "Планиране на пътуване"
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"codeReview": {
|
||||
"description": "Техническо обсъждане и преглед на промените в кода и реализацията от колеги",
|
||||
"description": "Технически дискусии и преглед от колеги на промени и реализации в кода",
|
||||
"emoji": "💻",
|
||||
"prompt": "Нека прегледаме някакъв код заедно. Можеш ли да ни помогнеш да анализираме този код и да идентифицираме възможности за подобрение?",
|
||||
"prompt": "Нека прегледаме малко код заедно. Можеш ли да ни помогнеш да го анализираме и да открием възможности за подобрение?",
|
||||
"title": "Преглед на код"
|
||||
},
|
||||
"designReview": {
|
||||
"description": "Съвместни сесии за обратна връзка относно дизайнерски концепции, прототипи или творчески проекти",
|
||||
"description": "Съвместни сесии за обратна връзка по дизайн концепции, прототипи и творчески проекти",
|
||||
"emoji": "🎨",
|
||||
"prompt": "Трябва да прегледаме някои дизайнерски проекти. Можеш ли да ни помогнеш да дадем конструктивна обратна връзка за концепциите и прототипите?",
|
||||
"prompt": "Трябва да прегледаме някои дизайнерски проекти. Можеш ли да ни помогнеш с конструктивна обратна връзка?",
|
||||
"title": "Преглед на дизайн"
|
||||
},
|
||||
"sprintPlanning": {
|
||||
"description": "Аджайл техника за оценка на задачи и обем на работа чрез карти",
|
||||
"description": "Agile техника за оценка на задачи и усилия чрез карти",
|
||||
"emoji": "🃏",
|
||||
"prompt": "Провеждаме планиращ покер за проекта. Можеш ли да ни помогнеш да използваме аджайл техники за оценка на обема на задачите?",
|
||||
"title": "Планиращ покер"
|
||||
"prompt": "Планираме проект с Planning Poker. Можеш ли да ни помогнеш да оценим задачите с agile методи?",
|
||||
"title": "Planning Poker"
|
||||
},
|
||||
"techExchange": {
|
||||
"description": "Обсъждане на нови технологии, иновации и тенденции в индустрията",
|
||||
"description": "Дискусии за нови технологии, иновации и тенденции в индустрията",
|
||||
"emoji": "🚀",
|
||||
"prompt": "Нека проведем технически обмен! Можеш ли да ни помогнеш да обсъдим нови технологии и тенденции в индустрията?",
|
||||
"title": "Технически обмен"
|
||||
"prompt": "Нека направим технологичен обмен! Можеш ли да ни помогнеш да обсъдим нови технологии и тенденции?",
|
||||
"title": "Технологичен обмен"
|
||||
}
|
||||
},
|
||||
"title": "Препоръки за използване на групов чат",
|
||||
"title": "Препоръки за групов чат",
|
||||
"writing": {
|
||||
"bookClub": {
|
||||
"description": "Литературни дискусии и анализи на книги, истории и литературни произведения",
|
||||
"description": "Литературни дискусии и анализ на книги, истории и произведения",
|
||||
"emoji": "📖",
|
||||
"prompt": "Нека започнем дискусия в книжен клуб. Можеш ли да ни помогнеш да анализираме тази книга и да обсъдим темите ѝ заедно?",
|
||||
"title": "Книжен клуб"
|
||||
"prompt": "Нека започнем дискусия в литературния клуб. Можеш ли да ни помогнеш да анализираме книгата и обсъдим темите ѝ?",
|
||||
"title": "Литературен клуб"
|
||||
},
|
||||
"movieClub": {
|
||||
"description": "Гледане и обсъждане на филми, документални и визуални медии заедно",
|
||||
"description": "Гледане и обсъждане на филми, документални и визуални медии",
|
||||
"emoji": "🎬",
|
||||
"prompt": "Нека започнем дискусия в кино клуб. Можеш ли да ни помогнеш да анализираме този филм и да обсъдим темите му заедно?",
|
||||
"title": "Кино клуб"
|
||||
"prompt": "Нека започнем филмов клуб. Можеш ли да ни помогнеш да анализираме филма и обсъдим темите му?",
|
||||
"title": "Филмов клуб"
|
||||
},
|
||||
"musicSession": {
|
||||
"description": "Съвместно създаване, споделяне и оценяване на музика",
|
||||
"emoji": "🎵",
|
||||
"prompt": "Нека направим музикална импровизация! Можеш ли да ни помогнеш да създаваме и оценяваме музика заедно?",
|
||||
"title": "Музикална импровизация"
|
||||
"prompt": "Нека направим музикална сесия! Можеш ли да ни помогнеш да създаваме и оценяваме музика заедно?",
|
||||
"title": "Музикална сесия"
|
||||
},
|
||||
"studyGroup": {
|
||||
"description": "Съвместни учебни сесии, обсъждане на концепции и решаване на проблеми заедно",
|
||||
"description": "Съвместни учебни сесии за обсъждане на концепции и решаване на проблеми",
|
||||
"emoji": "📚",
|
||||
"prompt": "Нека сформираме учебна група. Можеш ли да ни помогнеш да разберем тези концепции и да решим проблемите заедно?",
|
||||
"prompt": "Нека създадем учебна група. Можеш ли да ни помогнеш да разберем концепциите и да решим задачите заедно?",
|
||||
"title": "Учебна група"
|
||||
}
|
||||
}
|
||||
},
|
||||
"groupMessage": "Добре дошли в груповия чат! Сътрудничете с няколко AI помощника в споделено разговорно пространство.",
|
||||
"groupMessage": "Добре дошли в груповия чат! Сътрудничете с множество AI асистенти в споделено разговорно пространство.",
|
||||
"groupTemplates": {
|
||||
"analysis": {
|
||||
"description": "Данни, водещи до прозрения, задълбочен анализ и изследване",
|
||||
"description": "Инсайти, базирани на данни, задълбочени изследвания и анализи",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "📊",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"plugins": ["steam"],
|
||||
"systemRole": "Вие сте експерт в обработката и тълкуването на данни, разкривайки закономерности и тенденции чрез диаграми и статистически анализи.",
|
||||
"systemRole": "Ти си експерт по данни, който умее да обработва и интерпретира данни чрез графики и статистически анализи.",
|
||||
"title": "Анализатор на данни"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "Ти си изследовател, специализиран в събирането на информация и задълбочени проучвания, способен да анализира проблемите от множество гледни точки.",
|
||||
"systemRole": "Ти си изследовател, който събира информация и провежда задълбочени проучвания от различни гледни точки.",
|
||||
"title": "Изследовател"
|
||||
},
|
||||
{
|
||||
"avatar": "📈",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "Ти си статистик, експерт в различни статистически методи и модели, който извлича ценни бизнес прозрения от данните.",
|
||||
"systemRole": "Ти си статистик, който владее различни статистически методи и модели за извличане на бизнес инсайти.",
|
||||
"title": "Статистик"
|
||||
},
|
||||
{
|
||||
"avatar": "🧮",
|
||||
"backgroundColor": "#F0F8FF",
|
||||
"systemRole": "Ти си количествен анализатор, специализиран в количествено моделиране и оценка на риска, използващ математически методи за решаване на сложни проблеми.",
|
||||
"systemRole": "Ти си количествен анализатор, който използва математически модели за оценка на риска и решаване на сложни проблеми.",
|
||||
"title": "Количествен анализатор"
|
||||
}
|
||||
],
|
||||
"title": "Екип за анализ"
|
||||
"title": "Аналитичен екип"
|
||||
},
|
||||
"brainstorm": {
|
||||
"description": "Многостранно креативно мислене, стимулиране на безкрайни възможности",
|
||||
"description": "Креативно мислене от различни гледни точки, отключващо безкрайни възможности",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "🧠",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "Ти си креативен директор, умел в управлението на творческата посока от макро ниво, способен да превръща абстрактни концепции в конкретни изпълними творчески решения.",
|
||||
"systemRole": "Ти си креативен директор, който ръководи творческия процес и превръща абстрактни идеи в изпълними концепции.",
|
||||
"title": "Креативен директор"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "Ти си експерт по иновации, специализиран в откриването на новаторски решения и пробивни идеи, умеещ да мисли извън установените рамки.",
|
||||
"systemRole": "Ти си иновационен експерт, който открива новаторски решения и мисли извън рамките.",
|
||||
"title": "Експерт по иновации"
|
||||
},
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "Ти си специалист по дизайн мислене, който разглежда проблемите от гледна точка на потребителското изживяване и визуалната презентация, с акцент върху визуалното изразяване на креативността.",
|
||||
"title": "Дизайнер на мислене"
|
||||
"systemRole": "Ти си специалист по дизайн мислене, който се фокусира върху потребителското изживяване и визуалната комуникация.",
|
||||
"title": "Дизайн мислител"
|
||||
}
|
||||
],
|
||||
"title": "Мозъчна атака група"
|
||||
"title": "Брейнсторминг екип"
|
||||
},
|
||||
"game": {
|
||||
"description": "Забавлявай се с различни многопотребителски текстови игри, като Мафия и Кой е предателят",
|
||||
"description": "Играй различни текстови игри с много участници, като Върколак и Шпионинът",
|
||||
"members": [
|
||||
null,
|
||||
{
|
||||
"avatar": "🧠",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "Ти си водещ, който организира и ръководи различни текстови игри с много участници.",
|
||||
"title": "Водещ на игри"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🔬",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "Ти си експерт в различни многопотребителски текстови игри и можеш да играеш според правилата на играта.",
|
||||
"systemRole": "Ти си опитен играч, който участва в текстови игри според правилата.",
|
||||
"title": "Играч"
|
||||
},
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "Ти си експерт в различни многопотребителски текстови игри и можеш да играеш според правилата на играта.",
|
||||
"systemRole": "Ти си опитен играч, който участва в текстови игри според правилата.",
|
||||
"title": "Играч"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "Ти си експерт в различни многопотребителски текстови игри и можеш да играеш според правилата на играта.",
|
||||
"systemRole": "Ти си опитен играч, който участва в текстови игри според правилата.",
|
||||
"title": "Играч"
|
||||
}
|
||||
],
|
||||
"title": "Игрална зала"
|
||||
},
|
||||
"planning": {
|
||||
"description": "Стратегическо планиране и управление на проекти, координиране на цялостната картина",
|
||||
"description": "Стратегическо планиране и управление на проекти",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "📋",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "Отговаряш за цялостното планиране на проекта, контрол на напредъка и координация на ресурсите, за да осигуриш навременно и качествено изпълнение.",
|
||||
"systemRole": "Ти отговаряш за цялостното планиране, управление на срокове и координация на ресурси.",
|
||||
"title": "Главен готвач"
|
||||
},
|
||||
{
|
||||
"avatar": "🎯",
|
||||
"backgroundColor": "#FFF7E8",
|
||||
"systemRole": "Отговаряш за разработване на дългосрочни стратегически планове, анализ на пазарните възможности, поставяне на цели и определяне на пътища за постигането им.",
|
||||
"title": "Експерт по снабдяване с продукти"
|
||||
"systemRole": "Ти разработваш дългосрочни стратегии, анализираш пазара и определяш цели.",
|
||||
"title": "Експерт по доставки"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#F0F8FF",
|
||||
"systemRole": "Отговаряш за изготвяне на детайлни оперативни планове, координация на ресурсите между отделите и осигуряване на изпълнимостта на плана.",
|
||||
"systemRole": "Ти създаваш изпълними планове и координираш ресурсите между отделите.",
|
||||
"title": "Експерт по кулинарни разработки"
|
||||
}
|
||||
],
|
||||
"title": "Екип за кулинарни разработки"
|
||||
"title": "Кулинарен екип"
|
||||
},
|
||||
"product": {
|
||||
"description": "Проектиране и разработка на продукти, създаване на качествени продукти",
|
||||
"description": "Дизайн и разработка на продукти с високо качество",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "🎨",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "Ти си дизайнер, умел в проектирането на различни видове продукти, способен да проектира според изискванията на продукта.",
|
||||
"systemRole": "Ти си дизайнер, който създава продукти според нуждите на потребителите.",
|
||||
"title": "Дизайнер"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑",
|
||||
"backgroundColor": "#E8F5FF",
|
||||
"systemRole": "Ти си продуктов мениджър, отговорен за планирането, проектирането, разработката и поддръжката на продукта, осигурявайки качеството и потребителското изживяване.",
|
||||
"systemRole": "Ти си продуктов мениджър, който планира, проектира и поддържа продукта.",
|
||||
"title": "Продуктов мениджър"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑💻",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"systemRole": "Ти си опитен full-stack инженер, умел в разработката на различни видове продукти, способен да разработва според изискванията на продукта.",
|
||||
"title": "Full-stack инженер"
|
||||
"systemRole": "Ти си опитен full-stack разработчик, който създава продукти според изискванията.",
|
||||
"title": "Full-stack разработчик"
|
||||
}
|
||||
],
|
||||
"title": "Екип за разработка на продукти"
|
||||
"title": "Продуктов екип"
|
||||
},
|
||||
"writing": {
|
||||
"description": "Създаване и редактиране на съдържание, създаване на качествени текстове",
|
||||
"description": "Създаване и редактиране на съдържание с високо качество",
|
||||
"members": [
|
||||
{
|
||||
"avatar": "✍️",
|
||||
"backgroundColor": "#F6E8FF",
|
||||
"systemRole": "Експерт си в създаването на съдържание в различни жанрове и можеш да адаптираш стила на писане според различни ситуации и аудитории.",
|
||||
"title": "Писател на съдържание"
|
||||
"systemRole": "Ти създаваш съдържание в различни стилове и го адаптираш според аудиторията.",
|
||||
"title": "Автор на съдържание"
|
||||
},
|
||||
{
|
||||
"avatar": "🧑🎨",
|
||||
"backgroundColor": "#E8F8F5",
|
||||
"systemRole": "Ти си редактор, отговорен за корекция, полиране и оптимизиране на текстове, осигурявайки точност, плавност и професионализъм на съдържанието.",
|
||||
"systemRole": "Ти си редактор, който проверява, редактира и подобрява текстовете.",
|
||||
"title": "Редактор"
|
||||
}
|
||||
],
|
||||
"title": "Кръг на писателите"
|
||||
"title": "Кръг за писане"
|
||||
}
|
||||
},
|
||||
"questions": {
|
||||
"moreBtn": "Научи повече",
|
||||
"title": "Опитайте да попитате:"
|
||||
"title": "Опитай да попиташ:"
|
||||
},
|
||||
"welcome": {
|
||||
"afternoon": "Добър ден",
|
||||
"afternoon": "Добър следобед",
|
||||
"morning": "Добро утро",
|
||||
"night": "Добър вечер",
|
||||
"night": "Лека нощ",
|
||||
"noon": "Добър ден"
|
||||
}
|
||||
},
|
||||
"header": "Добре дошли",
|
||||
"pickAgent": "Или изберете от следните шаблони на агенти",
|
||||
"pickAgent": "Или изберете от следните шаблони за асистенти",
|
||||
"skip": "Пропусни създаването",
|
||||
"slogan": {
|
||||
"desc1": "Проправяйки път на новата ера на мислене и създаване. Създаден за вас, Супер индивида.",
|
||||
"desc2": "Създайте първия си агент и нека започнем~",
|
||||
"title": "Отключете свръхсилата на мозъка си"
|
||||
"desc1": "Отключете силата на колективния ум. Вашият интелигентен асистент е винаги с вас.",
|
||||
"desc2": "Създайте своя първи асистент и нека започнем~",
|
||||
"title": "Дайте си по-умна глава"
|
||||
},
|
||||
"welcomeMessages": {
|
||||
"1": "Добре дошли отново 😊",
|
||||
"10": "Максимална продуктивност~",
|
||||
"11": "На разположение съм!",
|
||||
"12": "Изчаках ви с нетърпение~☕",
|
||||
"13": "Да започваме ✅",
|
||||
"14": "Донесохте ли нов въпрос?",
|
||||
"15": "Добра работа и днес!",
|
||||
"16": "Зареждам вдъхновение",
|
||||
"17": "Онлайн и зареден ⚡",
|
||||
"18": "Потегляме! 🚀",
|
||||
"19": "Мислите ми вече са в ритъм.",
|
||||
"2": "Здравей, тук съм",
|
||||
"20": "Вдъхновението е на път",
|
||||
"21": "Чакам само вашата команда",
|
||||
"22": "Превключвам на режим ефективност!",
|
||||
"23": "В режим на готовност",
|
||||
"24": "Готов за предизвикателства",
|
||||
"25": "Генерирам нови идеи",
|
||||
"26": "Пътят е ясен, тръгваме!",
|
||||
"27": "Системата е онлайн, готова да помогне 💡",
|
||||
"28": "Зареждам добро настроение",
|
||||
"29": "Поемете контрола от сега 🎵",
|
||||
"3": "Готов съм!",
|
||||
"30": "Увеличавам ефективността …",
|
||||
"31": "Днешната цел е в ход 🎯",
|
||||
"32": "Нека вдъхновението блесне ✨",
|
||||
"33": "Задачите са актуализирани",
|
||||
"34": "Всичко е готово",
|
||||
"35": "Стартирам режим на ускорение",
|
||||
"36": "Да! Да започваме 😎",
|
||||
"37": "Чакам ви да се върнете",
|
||||
"38": "Продължавайте в същия дух!",
|
||||
"39": "Не забравяйте да си починете~ 💤",
|
||||
"4": "Радвам се да ви видя",
|
||||
"5": "Готови ли сте да започнем?",
|
||||
"6": "Оставете днешните задачи на мен",
|
||||
"7": "Продължаваме напред!",
|
||||
"8": "Да дадем всичко от себе си 💪",
|
||||
"9": "Време е за работа 🏃♂️"
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -136,7 +136,7 @@
|
||||
"passwordPlaceholder": "Bitte geben Sie ein Passwort ein",
|
||||
"signinLink": "Jetzt anmelden",
|
||||
"submit": "Registrieren",
|
||||
"subtitle": "Treten Sie der LobeChat-Community bei",
|
||||
"subtitle": "Aktiviere deinen Agents-Arbeitsbereich",
|
||||
"success": "Registrierung erfolgreich! Bitte überprüfen Sie Ihre E-Mail zur Verifizierung",
|
||||
"title": "Konto erstellen",
|
||||
"usernamePlaceholder": "Bitte geben Sie einen Benutzernamen ein"
|
||||
@@ -144,8 +144,7 @@
|
||||
"verifyEmail": {
|
||||
"backToSignIn": "Zurück zur Anmeldung",
|
||||
"checkSpam": "Wenn Sie keine E-Mail erhalten haben, überprüfen Sie bitte Ihren Spam-Ordner",
|
||||
"descriptionPrefix": "Wir haben eine Bestätigungs-E-Mail an",
|
||||
"descriptionSuffix": "gesendet",
|
||||
"description": "Eine Bestätigungs-E-Mail wurde an {{email}} gesendet",
|
||||
"resend": {
|
||||
"button": "Bestätigungs-E-Mail erneut senden",
|
||||
"error": "Senden fehlgeschlagen, bitte versuchen Sie es später erneut",
|
||||
@@ -159,6 +158,11 @@
|
||||
"prevMonth": "Letzter Monat",
|
||||
"recent30Days": "Letzte 30 Tage"
|
||||
},
|
||||
"footer": {
|
||||
"agreement": "Mit dem Fortfahren bestätigst du, dass du die <terms>Nutzungsbedingungen</terms> und die <privacy>Datenschutzerklärung</privacy> gelesen und akzeptiert hast",
|
||||
"privacy": "Datenschutzerklärung",
|
||||
"terms": "Nutzungsbedingungen"
|
||||
},
|
||||
"header": {
|
||||
"desc": "Verwalten Sie Ihre Kontoinformationen.",
|
||||
"title": "Konto"
|
||||
@@ -194,6 +198,9 @@
|
||||
"email": "E-Mail-Adresse",
|
||||
"fullName": "Vollständiger Name",
|
||||
"fullNameInputHint": "Bitte geben Sie einen neuen vollständigen Namen ein",
|
||||
"occupation": "Beruf",
|
||||
"occupationInputHint": "Bitte geben Sie Ihren Beruf ein",
|
||||
"occupationPlaceholder": "Zum Beispiel: Softwareentwickler, Produktmanager, Designer",
|
||||
"password": "Passwort",
|
||||
"resetPasswordError": "Link zum Zurücksetzen des Passworts konnte nicht gesendet werden",
|
||||
"resetPasswordSent": "Link zum Zurücksetzen des Passworts wurde gesendet, bitte überprüfen Sie Ihre E-Mail",
|
||||
@@ -215,6 +222,7 @@
|
||||
"title": "Profilinformationen",
|
||||
"updateAvatar": "Avatar aktualisieren",
|
||||
"updateFullName": "Vollständigen Namen aktualisieren",
|
||||
"updateOccupation": "Beruf aktualisieren",
|
||||
"updateUsername": "Benutzernamen aktualisieren",
|
||||
"username": "Benutzername",
|
||||
"usernameDuplicate": "Benutzername ist bereits vergeben",
|
||||
@@ -224,6 +232,10 @@
|
||||
"usernameRule": "Benutzername darf nur Buchstaben, Zahlen oder Unterstriche enthalten",
|
||||
"usernameUpdateFailed": "Aktualisierung des Benutzernamens fehlgeschlagen, bitte später erneut versuchen"
|
||||
},
|
||||
"signin": {
|
||||
"subtitle": "Registriere dich oder melde dich bei deinem {{appName}}-Konto an",
|
||||
"title": "Dein Agents-Arbeitsbereich"
|
||||
},
|
||||
"signout": "Ausloggen",
|
||||
"signup": "Registrieren",
|
||||
"stats": {
|
||||
@@ -269,7 +281,7 @@
|
||||
},
|
||||
"tab": {
|
||||
"apikey": "API-Schlüssel Verwaltung",
|
||||
"profile": "Profil",
|
||||
"profile": "Mein Konto",
|
||||
"security": "Sicherheit",
|
||||
"stats": "Statistiken",
|
||||
"usage": "Nutzungsstatistik"
|
||||
|
||||
+52
-14
@@ -3,6 +3,22 @@
|
||||
"title": "Modell"
|
||||
},
|
||||
"active": "Aktiv",
|
||||
"agentBuilder": {
|
||||
"installPlugin": {
|
||||
"authRequired": "Das Cloud-MCP-Plugin erfordert eine Anmeldung",
|
||||
"cancel": "Abbrechen",
|
||||
"clickApproveToConnect": "Klicken Sie auf „Genehmigen“, um die Verbindung herzustellen und diese Integration zu autorisieren",
|
||||
"connectedAndEnabled": "Verbunden und aktiviert",
|
||||
"connectionFailed": "Verbindung fehlgeschlagen",
|
||||
"installFailed": "Installation fehlgeschlagen",
|
||||
"installPlugin": "Plugin installieren",
|
||||
"installToEnable": "Installieren Sie dieses Plugin, um den Assistenten zu aktivieren",
|
||||
"installedAndEnabled": "Installiert und aktiviert",
|
||||
"requiresAuth": "Autorisierung erforderlich, klicken Sie auf „Genehmigen“, um die Verbindung herzustellen",
|
||||
"retry": "Erneut versuchen"
|
||||
},
|
||||
"welcome": "Hallo, ich bin **Lobe AI**, dein Experte für die Konfiguration von Assistenten. Sag mir, was für einen Assistenten du dir wünschst, und ich helfe dir, ihn einzurichten."
|
||||
},
|
||||
"agentDefaultMessage": "Hallo, ich bin **{{name}}**. Du kannst sofort mit mir sprechen oder zu den [Assistenteneinstellungen]({{url}}) gehen, um meine Informationen zu vervollständigen.",
|
||||
"agentDefaultMessageWithSystemRole": "Hallo, ich bin **{{name}}**. Wie kann ich Ihnen behilflich sein?",
|
||||
"agentDefaultMessageWithoutEdit": "Hallo, ich bin **{{name}}**. Wie kann ich Ihnen behilflich sein?",
|
||||
@@ -16,19 +32,20 @@
|
||||
},
|
||||
"availableAgents": "Verfügbare Assistenten",
|
||||
"backToBottom": "Zurück zum Ende",
|
||||
"builtinCopilot": "Integrierter Copilot",
|
||||
"chatList": {
|
||||
"expandMessage": "Nachricht anzeigen",
|
||||
"longMessageDetail": "Details anzeigen"
|
||||
},
|
||||
"clearCurrentMessages": "Aktuelle Nachrichten löschen",
|
||||
"confirmClearCurrentMessages": "Möchtest du wirklich die aktuellen Nachrichten löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"confirmRemoveChatGroupItemAlert": "Dieses Agenten-Team wird gelöscht. Die Teammitglieder bleiben davon unberührt. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmRemoveChatGroupItemAlert": "Diese Gruppe wird gelöscht. Teammitglieder bleiben davon unberührt. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmRemoveGroupItemAlert": "Sie sind dabei, diese Gruppe zu löschen. Nach der Löschung werden die Assistenten dieser Gruppe in die Standardliste verschoben. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmRemoveGroupSuccess": "Agent-Team erfolgreich gelöscht",
|
||||
"confirmRemoveGroupSuccess": "Gruppe erfolgreich gelöscht",
|
||||
"confirmRemoveSessionItemAlert": "Möchtest du diesen Assistenten wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"confirmRemoveSessionSuccess": "Hilfe wurde erfolgreich entfernt",
|
||||
"defaultAgent": "Standardassistent",
|
||||
"defaultGroupChat": "Agent-Team",
|
||||
"defaultGroupChat": "Gruppe",
|
||||
"defaultList": "Standardliste",
|
||||
"defaultSession": "Standardassistent",
|
||||
"dm": {
|
||||
@@ -44,6 +61,7 @@
|
||||
},
|
||||
"duplicateTitle": "{{title}} Kopie",
|
||||
"emptyAgent": "Kein Assistent verfügbar",
|
||||
"emptyAgentAction": "Assistent erstellen",
|
||||
"extendParams": {
|
||||
"disableContextCaching": {
|
||||
"desc": "Die Kosten für die Generierung einer einzelnen Konversation können um bis zu 90 % gesenkt werden, die Reaktionsgeschwindigkeit wird um das Vierfache erhöht (<1>Mehr erfahren</1>). Wenn aktiviert, wird die Begrenzung der Anzahl historischer Nachrichten automatisch deaktiviert.",
|
||||
@@ -89,8 +107,13 @@
|
||||
},
|
||||
"groupDescription": "Team-Beschreibung",
|
||||
"groupSidebar": {
|
||||
"agentProfile": {
|
||||
"chat": "Chat",
|
||||
"model": "Modell"
|
||||
},
|
||||
"members": {
|
||||
"addMember": "Mitglied hinzufügen",
|
||||
"enableOrchestrator": "Moderator aktivieren",
|
||||
"memberSettings": "Mitgliedereinstellungen",
|
||||
"orchestrator": "Moderator",
|
||||
"orchestratorThinking": "Der Moderator denkt nach...",
|
||||
@@ -120,7 +143,7 @@
|
||||
"noTemplateMembers": "Keine Mitglieder in der Vorlage",
|
||||
"noTemplates": "Keine Vorlagen verfügbar",
|
||||
"searchTemplates": "Vorlagen durchsuchen...",
|
||||
"title": "Agent-Team erstellen",
|
||||
"title": "Gruppe erstellen",
|
||||
"useTemplate": "Vorlage verwenden"
|
||||
},
|
||||
"hideForYou": "Private Nachrichten sind ausgeblendet. Bitte aktivieren Sie in den Einstellungen „Private Nachrichten anzeigen“, um sie zu sehen.",
|
||||
@@ -154,28 +177,29 @@
|
||||
"knowledgeBase": {
|
||||
"all": "Alle Inhalte",
|
||||
"allFiles": "Alle Dateien",
|
||||
"allKnowledgeBases": "Alle Wissensdatenbanken",
|
||||
"disabled": "Der aktuelle Bereitstellungsmodus unterstützt keine Dialoge mit der Wissensdatenbank. Bitte wechseln Sie zur Bereitstellung mit einer Serverdatenbank oder nutzen Sie den {{cloud}}-Dienst.",
|
||||
"allLibraries": "Alle Bibliotheken",
|
||||
"disabled": "Der aktuelle Bereitstellungsmodus unterstützt keine Bibliotheksdialoge. Um diese Funktion zu nutzen, wechseln Sie bitte zur serverseitigen Datenbankbereitstellung oder verwenden Sie den {{cloud}}-Dienst.",
|
||||
"library": {
|
||||
"action": {
|
||||
"add": "Hinzufügen",
|
||||
"detail": "Details",
|
||||
"remove": "Entfernen"
|
||||
},
|
||||
"title": "Datei/Wissensdatenbank"
|
||||
"title": "Dateien/Bibliothek"
|
||||
},
|
||||
"relativeFilesOrKnowledgeBases": "Verwandte Dateien/Wissensdatenbanken",
|
||||
"relativeFilesOrLibraries": "Verknüpfte Dateien/Bibliotheken",
|
||||
"title": "Wissensdatenbank",
|
||||
"uploadGuide": "Hochgeladene Dateien können in der „Wissensdatenbank“ eingesehen werden.",
|
||||
"uploadGuide": "Hochgeladene Dateien können unter „Ressourcen“ eingesehen werden.",
|
||||
"viewMore": "Mehr anzeigen"
|
||||
},
|
||||
"memberSelection": {
|
||||
"addMember": "Mitglied hinzufügen",
|
||||
"allMembers": "Alle Mitglieder",
|
||||
"createGroup": "Agenten-Team erstellen",
|
||||
"createGroup": "Gruppe erstellen",
|
||||
"noAvailableAgents": "Keine verfügbaren Agents zum Einladen",
|
||||
"noSelectedAgents": "Noch keine Agents ausgewählt",
|
||||
"searchAgents": "Agents suchen...",
|
||||
"selectedAgents": "Ausgewählt ({{count}})",
|
||||
"setInitialMembers": "Teammitglieder auswählen"
|
||||
},
|
||||
"members": "Mitglieder",
|
||||
@@ -245,9 +269,10 @@
|
||||
"senderAssistant": "Agent",
|
||||
"senderUser": "Du"
|
||||
},
|
||||
"newAgent": "Neuer Assistent",
|
||||
"newGroupChat": "Neues Agent-Team",
|
||||
"noAgentsYet": "Dieses Agent-Team hat noch keine Mitglieder. Klicken Sie auf die + Schaltfläche, um Assistenten einzuladen.",
|
||||
"newAgent": "Assistent erstellen",
|
||||
"newGroupChat": "Gruppenchats erstellen",
|
||||
"newPage": "Dokument erstellen",
|
||||
"noAgentsYet": "Diese Gruppe hat noch keine Mitglieder. Klicken Sie auf die Schaltfläche +, um Assistenten einzuladen.",
|
||||
"noAvailableAgents": "Keine Mitglieder verfügbar zum Einladen",
|
||||
"noMatchingAgents": "Keine passenden Mitglieder gefunden",
|
||||
"noMembersYet": "Diese Gruppe hat noch keine Mitglieder. Klicken Sie auf die +-Schaltfläche, um Assistenten einzuladen.",
|
||||
@@ -296,7 +321,7 @@
|
||||
"searchAgentPlaceholder": "Suchassistent...",
|
||||
"searchAgents": "Suchassistent...",
|
||||
"selectedAgents": "Ausgewählte Assistenten",
|
||||
"sendPlaceholder": "Chat-Nachricht eingeben...",
|
||||
"sendPlaceholder": "Fragen stellen, kreativ sein oder eine Aufgabe starten, <hotkey><hotkey/>",
|
||||
"sessionGroup": {
|
||||
"config": "Gruppenkonfiguration",
|
||||
"confirmRemoveGroupAlert": "Die Gruppe wird bald gelöscht. Nach dem Löschen werden die Assistenten in die Standardliste verschoben. Bitte bestätigen Sie Ihre Aktion.",
|
||||
@@ -306,11 +331,17 @@
|
||||
"createGroupSuccess": "Gruppe erfolgreich erstellt",
|
||||
"createSuccess": "Erstellung erfolgreich",
|
||||
"creatingAgent": "Assistent wird erstellt...",
|
||||
"groupName": "Gruppenname",
|
||||
"inputPlaceholder": "Geben Sie den Gruppennamen ein...",
|
||||
"moveGroup": "In Gruppe verschieben",
|
||||
"newGroup": "Neue Gruppe",
|
||||
"noAvailableAgents": "Keine verfügbaren Assistenten",
|
||||
"noMatchingAgents": "Keine passenden Assistenten gefunden",
|
||||
"noSelectedAgents": "Bitte wählen Sie einen Assistenten aus",
|
||||
"rename": "Gruppe umbenennen",
|
||||
"renameSuccess": "Umbenennung erfolgreich",
|
||||
"searchAgents": "Assistenten suchen",
|
||||
"selectedAgents": "Ausgewählte Assistenten ({{count}})",
|
||||
"sortSuccess": "Sortierung erfolgreich aktualisiert",
|
||||
"sorting": "Gruppensortierung wird aktualisiert...",
|
||||
"tooLong": "Gruppenname muss zwischen 1 und 20 Zeichen lang sein"
|
||||
@@ -361,6 +392,11 @@
|
||||
"title": "Aufgaben abgeschlossen"
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"groupProfile": "Gruppenprofil",
|
||||
"profile": "Assistentenprofil",
|
||||
"search": "Suche"
|
||||
},
|
||||
"thread": {
|
||||
"divider": "Unterthema",
|
||||
"threadMessageCount": "{{messageCount}} Nachrichten",
|
||||
@@ -413,6 +449,7 @@
|
||||
"checkOpenNewTopic": "Soll ein neues Thema eröffnet werden?",
|
||||
"checkSaveCurrentMessages": "Möchten Sie die aktuelle Konversation als Thema speichern?",
|
||||
"openNewTopic": "Neues Thema öffnen",
|
||||
"recent": "Neueste Themen",
|
||||
"saveCurrentMessages": "Aktuelle Unterhaltung als Thema speichern"
|
||||
},
|
||||
"translate": {
|
||||
@@ -424,6 +461,7 @@
|
||||
"clear": "Sprachausgabe löschen"
|
||||
},
|
||||
"untitledAgent": "Unbenannter Assistent",
|
||||
"untitledGroup": "Unbenannte Gruppe",
|
||||
"updateAgent": "Assistenteninformationen aktualisieren",
|
||||
"upload": {
|
||||
"action": {
|
||||
|
||||
@@ -137,14 +137,36 @@
|
||||
"close": "Schließen",
|
||||
"cmdk": {
|
||||
"about": "Über",
|
||||
"aiModeEmptyState": "Gib deine Frage oben ein, um mit der KI zu chatten",
|
||||
"aiModePlaceholder": "Stelle der KI eine Frage...",
|
||||
"communitySupport": "Community-Support",
|
||||
"discover": "Entdecken",
|
||||
"knowledgeBase": "Wissensdatenbank",
|
||||
"memory": "Erinnerung",
|
||||
"navigate": "Navigieren",
|
||||
"newAgent": "Neuen Assistenten erstellen",
|
||||
"noResults": "Keine Ergebnisse gefunden",
|
||||
"openSettings": "Einstellungen öffnen",
|
||||
"painting": "KI-Malerei",
|
||||
"pages": "Dokumente",
|
||||
"painting": "Malerei",
|
||||
"resource": "Ressource",
|
||||
"search": {
|
||||
"agent": "Assistent",
|
||||
"agents": "Assistenten",
|
||||
"assistant": "KI-Assistent",
|
||||
"assistants": "KI-Assistenten",
|
||||
"file": "Datei",
|
||||
"files": "Dateien",
|
||||
"loading": "Wird gesucht...",
|
||||
"mcp": "MCP-Server",
|
||||
"mcps": "MCP-Server",
|
||||
"message": "Nachricht",
|
||||
"messages": "Nachrichten",
|
||||
"plugin": "Plugin",
|
||||
"plugins": "Plugins",
|
||||
"searching": "Suchergebnisse",
|
||||
"topic": "Thema",
|
||||
"topics": "Themen"
|
||||
},
|
||||
"searchPlaceholder": "Befehl eingeben oder suchen...",
|
||||
"settings": "Einstellungen",
|
||||
"starOnGitHub": "Gib uns einen Stern auf GitHub",
|
||||
@@ -304,6 +326,13 @@
|
||||
"business": "Geschäftliche Zusammenarbeit",
|
||||
"support": "E-Mail-Support"
|
||||
},
|
||||
"navPanel": {
|
||||
"agent": "Assistent",
|
||||
"displayItems": "Einträge anzeigen",
|
||||
"library": "Bibliothek",
|
||||
"searchAgent": "Assistent durchsuchen...",
|
||||
"searchResultEmpty": "Keine Suchergebnisse gefunden"
|
||||
},
|
||||
"new": "Neu",
|
||||
"oauth": "SSO-Anmeldung",
|
||||
"officialSite": "Offizielle Website",
|
||||
@@ -358,13 +387,21 @@
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"aiImage": "KI-Gemälde",
|
||||
"aiImage": "Zeichnung",
|
||||
"audio": "Audio",
|
||||
"chat": "Chat",
|
||||
"community": "Community",
|
||||
"discover": "Entdecken",
|
||||
"files": "Dateien",
|
||||
"home": "Startseite",
|
||||
"knowledgeBase": "Wissensdatenbank",
|
||||
"me": "Ich",
|
||||
"setting": "Einstellung"
|
||||
"memory": "Erinnerung",
|
||||
"pages": "Dokumente",
|
||||
"resource": "Ressourcen",
|
||||
"search": "Suche",
|
||||
"setting": "Einstellung",
|
||||
"video": "Video"
|
||||
},
|
||||
"telemetry": {
|
||||
"allow": "Erlauben",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"chunkingTooltip": "Teilen Sie die Datei in mehrere Textblöcke und vektorisieren Sie sie, um sie für die semantische Suche und Dateidialoge zu verwenden.",
|
||||
"chunkingUnsupported": "Diese Datei unterstützt kein Chunking.",
|
||||
"confirmDelete": "Die Datei wird gelöscht. Nach dem Löschen kann sie nicht wiederhergestellt werden. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmDeleteFolder": "Dieser Ordner und sein gesamter Inhalt werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmDeleteMultiFiles": "Die ausgewählten {{count}} Dateien werden gelöscht. Nach dem Löschen können sie nicht wiederhergestellt werden. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"confirmRemoveFromKnowledgeBase": "Die ausgewählten {{count}} Dateien werden aus der Wissensdatenbank entfernt. Die Dateien sind weiterhin in allen Dateien sichtbar. Bitte bestätigen Sie Ihre Aktion.",
|
||||
"copyUrl": "Link kopieren",
|
||||
@@ -26,8 +27,19 @@
|
||||
"createChunkingTask": "Wird vorbereitet...",
|
||||
"deleteSuccess": "Datei erfolgreich gelöscht",
|
||||
"downloading": "Datei wird heruntergeladen...",
|
||||
"goBack": "Zurück zur vorherigen Seite",
|
||||
"goForward": "Weiter zur nächsten Seite",
|
||||
"goToParent": "Zum übergeordneten Ordner",
|
||||
"moveError": "Datei konnte nicht verschoben werden",
|
||||
"moveHere": "Hierher verschieben",
|
||||
"moveSuccess": "Datei erfolgreich verschoben",
|
||||
"moveToFolder": "Verschieben nach...",
|
||||
"moveToRoot": "In das Stammverzeichnis verschieben",
|
||||
"removeFromKnowledgeBase": "Aus der Wissensdatenbank entfernen",
|
||||
"removeFromKnowledgeBaseSuccess": "Datei erfolgreich entfernt"
|
||||
"removeFromKnowledgeBaseSuccess": "Datei erfolgreich entfernt",
|
||||
"rename": "Umbenennen",
|
||||
"renameError": "Umbenennen fehlgeschlagen",
|
||||
"renameSuccess": "Erfolgreich umbenannt"
|
||||
},
|
||||
"bottom": "Das Ende ist erreicht",
|
||||
"config": {
|
||||
@@ -42,6 +54,12 @@
|
||||
"or": "oder",
|
||||
"title": "Ziehen Sie Dateien oder Ordner hierher"
|
||||
},
|
||||
"noFolders": "Keine Ordner vorhanden",
|
||||
"sort": {
|
||||
"dateAdded": "Hinzugefügt am",
|
||||
"name": "Name",
|
||||
"size": "Größe"
|
||||
},
|
||||
"title": {
|
||||
"createdAt": "Erstellungsdatum",
|
||||
"size": "Größe",
|
||||
@@ -164,7 +182,7 @@
|
||||
"OllamaSetupGuide": {
|
||||
"action": {
|
||||
"close": "Hinweis schließen",
|
||||
"start": "Installiert und ausgeführt, beginnen Sie das Gespräch"
|
||||
"start": "Installiert"
|
||||
},
|
||||
"cors": {
|
||||
"description": "Aufgrund von Sicherheitsbeschränkungen im Browser müssen Sie CORS für Ollama konfigurieren, um es ordnungsgemäß nutzen zu können.",
|
||||
|
||||
+25
-10
@@ -58,11 +58,12 @@
|
||||
"title": "Versionsverlauf"
|
||||
}
|
||||
},
|
||||
"downloads": "Downloads",
|
||||
"list": "Assistentenliste",
|
||||
"marketSource": {
|
||||
"label": "Marktquelle wechseln",
|
||||
"legacy": "Alter Markt",
|
||||
"new": "Neuer Markt"
|
||||
"label": "Community-Quelle wechseln",
|
||||
"legacy": "Alte Community",
|
||||
"new": "Neue Community"
|
||||
},
|
||||
"more": "Mehr",
|
||||
"plugins": "Integrations-Plugins",
|
||||
@@ -85,7 +86,7 @@
|
||||
"subtitle": "Der aktuell aufgerufene Assistent wurde aus einem der folgenden Gründe archiviert:",
|
||||
"title": "Assistent archiviert"
|
||||
},
|
||||
"backToMarket": "Zurück zum Assistenten-Markt",
|
||||
"backToMarket": "Zurück zur Assistenten-Community",
|
||||
"deprecated": {
|
||||
"reasons": {
|
||||
"official": "Der Assistent wurde aufgrund von Sicherheits- oder politischen Problemen offiziell entfernt.",
|
||||
@@ -94,9 +95,9 @@
|
||||
"subtitle": "Der aktuell aufgerufene Assistent wurde aus einem der folgenden Gründe abgelehnt:",
|
||||
"title": "Assistent abgelehnt"
|
||||
},
|
||||
"support": "Bei Fragen senden Sie bitte den Link an <1>support@lobehub.com</1>.",
|
||||
"support": "Bei Fragen senden Sie bitte den Link an <email>support@lobehub.com</email> zur Beratung.",
|
||||
"unpublished": {
|
||||
"subtitle": "Der aktuell aufgerufene Assistent befindet sich in der Versionsprüfung. Bei Fragen senden Sie bitte den Link an <1>support@lobehub.com</1>.",
|
||||
"subtitle": "Der aktuell aufgerufene Assistent befindet sich in der Versionsprüfung. Bei Fragen senden Sie bitte den Link an <email>support@lobehub.com</email> zur Beratung.",
|
||||
"title": "Assistent in Überprüfung"
|
||||
}
|
||||
},
|
||||
@@ -144,7 +145,7 @@
|
||||
"createGuide": {
|
||||
"func1": {
|
||||
"desc1": "Gehe im Chatfenster über die Einstellungen in der oberen rechten Ecke zur Seite, auf der du deinen Assistenten einreichen möchtest;",
|
||||
"desc2": "Klicke auf die Schaltfläche 'In den Assistentenmarkt einreichen' in der oberen rechten Ecke.",
|
||||
"desc2": "Klicken Sie oben rechts auf die Schaltfläche, um zur Assistenten-Community einzureichen.",
|
||||
"tag": "Methode Eins",
|
||||
"title": "Einreichung über LobeChat"
|
||||
},
|
||||
@@ -186,8 +187,10 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"communityAgents": "Community-Assistenten",
|
||||
"featuredAssistants": "Empfohlene Assistenten",
|
||||
"featuredModels": "Empfohlene Modelle",
|
||||
"featuredPlugins": "Empfohlene Plugins",
|
||||
"featuredProviders": "Empfohlene Modellanbieter",
|
||||
"featuredTools": "Empfohlene Plugins",
|
||||
"more": "Mehr entdecken"
|
||||
@@ -516,7 +519,7 @@
|
||||
"hero": {
|
||||
"desc": "Offene, bereitstellbare MCP-Server-Plattform, die KI-Systemen den einfachen Zugriff auf Dateisysteme, Datenbanken, APIs und andere wichtige Ressourcen ermöglicht und Ihre KI-Fähigkeiten umfassend erweitert.",
|
||||
"subTitle": "Open Source & sofort einsatzbereit",
|
||||
"title": "Open Source MCP-Marktplatz für KI"
|
||||
"title": "Open-Source-MCP-Community für KI"
|
||||
},
|
||||
"sorts": {
|
||||
"createdAt": "Zuletzt hinzugefügt",
|
||||
@@ -529,7 +532,7 @@
|
||||
"toolsCount": "Anzahl der Werkzeuge",
|
||||
"updatedAt": "Zuletzt aktualisiert"
|
||||
},
|
||||
"title": "MCP-Marktplatz",
|
||||
"title": "MCP-Community",
|
||||
"unvalidated": {
|
||||
"desc": "Dieser MCP-Server wurde noch nicht validiert",
|
||||
"title": "Nicht validiert"
|
||||
@@ -690,6 +693,18 @@
|
||||
"home": "Startseite",
|
||||
"model": "Modell",
|
||||
"plugin": "Plugin",
|
||||
"provider": "Modellanbieter"
|
||||
"provider": "Modellanbieter",
|
||||
"user": "Benutzer"
|
||||
},
|
||||
"user": {
|
||||
"agents": "Assistenten",
|
||||
"downloads": "Downloads",
|
||||
"editProfile": "Profil bearbeiten",
|
||||
"login": "Kreator werden",
|
||||
"logout": "Abmelden",
|
||||
"myProfile": "Mein Profil",
|
||||
"noAgents": "Dieser Benutzer hat noch keine Assistenten veröffentlicht",
|
||||
"publishedAgents": "Erstellte Assistenten",
|
||||
"website": "Persönliche Webseite"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
"on": "Formatierungsleiste anzeigen"
|
||||
}
|
||||
},
|
||||
"autoSave": {
|
||||
"latest": "Neueste Version geladen",
|
||||
"saved": "Gespeichert",
|
||||
"saving": "Automatisches Speichern..."
|
||||
},
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Bestätigen",
|
||||
"file": {
|
||||
@@ -20,10 +25,18 @@
|
||||
},
|
||||
"link": {
|
||||
"edit": "Link bearbeiten",
|
||||
"editLinkTitle": "Link",
|
||||
"editTextTitle": "Titel",
|
||||
"open": "Link öffnen",
|
||||
"placeholder": "Link-URL eingeben",
|
||||
"unlink": "Link entfernen"
|
||||
},
|
||||
"markdown": {
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Konvertieren",
|
||||
"parseMessage": "In Markdown-Format konvertieren. Der vorhandene Inhalt wird überschrieben. Möchten Sie fortfahren? (Automatisches Schließen in 5 Sekunden)",
|
||||
"parseTitle": "Markdown formatieren"
|
||||
},
|
||||
"math": {
|
||||
"placeholder": "Bitte TeX-Formel eingeben"
|
||||
},
|
||||
@@ -50,13 +63,16 @@
|
||||
"bulletList": "Ungeordnete Liste",
|
||||
"code": "Inline-Code",
|
||||
"codeblock": "Codeblock",
|
||||
"image": "Bild",
|
||||
"italic": "Kursiv",
|
||||
"link": "Link",
|
||||
"numberList": "Nummerierte Liste",
|
||||
"redo": "Wiederholen",
|
||||
"strikethrough": "Durchgestrichen",
|
||||
"table": "Tabelle",
|
||||
"taskList": "Aufgabenliste",
|
||||
"tex": "TeX-Formel",
|
||||
"underline": "Unterstrichen"
|
||||
"underline": "Unterstrichen",
|
||||
"undo": "Rückgängig"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"PluginSettingsInvalid": "Das Plugin muss korrekt konfiguriert werden, um verwendet werden zu können. Bitte überprüfen Sie Ihre Konfiguration auf Richtigkeit",
|
||||
"ProviderBizError": "Fehler bei der Anforderung des {{provider}}-Dienstes. Bitte überprüfen Sie die folgenden Informationen oder versuchen Sie es erneut.",
|
||||
"QuotaLimitReached": "Es tut uns leid, die Anzahl der Token oder Anfragen hat das Kontingent dieses Schlüssels erreicht. Bitte erhöhen Sie das Kontingent des Schlüssels oder versuchen Sie es später erneut.",
|
||||
"ServerAgentRuntimeError": "Es tut uns leid, der Agent-Dienst ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut oder kontaktieren Sie uns per E-Mail für Unterstützung.",
|
||||
"StreamChunkError": "Fehler beim Parsen des Nachrichtenchunks der Streaming-Anfrage. Bitte überprüfen Sie, ob die aktuelle API-Schnittstelle den Standards entspricht, oder wenden Sie sich an Ihren API-Anbieter.",
|
||||
"SubscriptionKeyMismatch": "Es tut uns leid, aufgrund eines vorübergehenden Systemfehlers ist das aktuelle Abonnement vorübergehend ungültig. Bitte klicken Sie auf die Schaltfläche unten, um das Abonnement wiederherzustellen, oder kontaktieren Sie uns per E-Mail für Unterstützung.",
|
||||
"SubscriptionPlanLimit": "Ihr Abonnementspunktestand ist erschöpft, Sie können diese Funktion nicht nutzen. Bitte upgraden Sie auf einen höheren Plan oder konfigurieren Sie die benutzerdefinierte Modell-API, um weiterhin zu verwenden.",
|
||||
@@ -162,6 +163,7 @@
|
||||
"title": "Bestätige deine {{name}}-Authentifizierungsinformationen"
|
||||
},
|
||||
"confirm": "Bestätigen und erneut versuchen",
|
||||
"goToSettings": "Zu den Einstellungen",
|
||||
"oauth": {
|
||||
"description": "Der Administrator hat die einheitliche Anmeldeauthentifizierung aktiviert. Klicken Sie unten auf die Schaltfläche, um sich anzumelden und die App zu entsperren.",
|
||||
"success": "Anmeldung erfolgreich",
|
||||
|
||||
+51
-15
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"addFolder": "Ordner erstellen",
|
||||
"addKnowledge": "Wissen hinzufügen",
|
||||
"addLibrary": "Hinzufügen",
|
||||
"addPage": "Dokument erstellen",
|
||||
"desc": "Verwalte dein Wissen für Arbeit, Studium und Alltag.",
|
||||
"desc": "Verwalte deine Ressourcen für Arbeit, Studium und Alltag.",
|
||||
"detail": {
|
||||
"basic": {
|
||||
"createdAt": "Erstellungszeit",
|
||||
@@ -50,6 +50,12 @@
|
||||
"pin": "Dokument anheften"
|
||||
},
|
||||
"saving": "Speichern...",
|
||||
"slashCommands": {
|
||||
"bulletedList": "Aufzählungsliste",
|
||||
"image": "Bild",
|
||||
"orderedList": "Nummerierte Liste",
|
||||
"todoList": "Aufgabenliste"
|
||||
},
|
||||
"titlePlaceholder": "Ohne Titel",
|
||||
"wordCount": "{{wordCount}} Wörter"
|
||||
},
|
||||
@@ -57,16 +63,46 @@
|
||||
"copyContent": "Gesamten Inhalt kopieren",
|
||||
"duplicate": "Kopie erstellen",
|
||||
"empty": "Noch keine Dokumente vorhanden. Klicke auf die Schaltfläche oben, um dein erstes Dokument zu erstellen.",
|
||||
"filter": {
|
||||
"all": "Alle",
|
||||
"onlyInPages": "Nur in Dokumenten"
|
||||
},
|
||||
"noResults": "Keine passenden Dokumente gefunden",
|
||||
"pageCount": "Insgesamt {{count}} Dokumente",
|
||||
"selectNote": "Wähle ein Dokument aus, um mit der Bearbeitung zu beginnen",
|
||||
"title": "Dokumente",
|
||||
"untitled": "Ohne Titel"
|
||||
},
|
||||
"empty": "Keine hochgeladenen Dateien/Ordner vorhanden",
|
||||
"header": {
|
||||
"actions": {
|
||||
"builtInBlockList": {
|
||||
"filtered": "{{ignored}} Dateien gefiltert (von insgesamt {{total}} Dateien)"
|
||||
},
|
||||
"connect": "Verbinden...",
|
||||
"gitignore": {
|
||||
"apply": "Regeln anwenden",
|
||||
"cancel": "Regeln ignorieren",
|
||||
"content": ".gitignore-Datei erkannt (insgesamt {{count}} Dateien). Möchten Sie die Ignorierregeln anwenden?",
|
||||
"filtered": "{{ignored}} Dateien wurden gefiltert (von insgesamt {{total}} Dateien)",
|
||||
"title": ".gitignore erkannt"
|
||||
},
|
||||
"newFolder": "Neuen Ordner erstellen",
|
||||
"newPage": "Neues Dokument",
|
||||
"notion": {
|
||||
"error": "Fehler beim Importieren der Notion-Datei",
|
||||
"foundFiles": "{{count}} Dateien gefunden",
|
||||
"importing": "Notion-Dateien werden importiert...",
|
||||
"noMarkdownFiles": "Keine Markdown-Dateien in der ZIP-Datei gefunden",
|
||||
"partial": "{{success}} Dateien erfolgreich importiert, {{failed}} fehlgeschlagen",
|
||||
"success": "{{count}} Dateien erfolgreich importiert"
|
||||
},
|
||||
"notionGuide": {
|
||||
"cancel": "Jetzt nicht importieren",
|
||||
"desc": "Bitte exportiere zunächst Markdown (ZIP) aus Notion. Klicke dann auf „Weiter“, um das ZIP-Archiv auszuwählen und alle Seiten zu importieren.",
|
||||
"ok": "Notion-ZIP auswählen",
|
||||
"title": "Notion-Inhalte importieren"
|
||||
},
|
||||
"uploadFile": "Datei hochladen",
|
||||
"uploadFolder": "Ordner hochladen"
|
||||
},
|
||||
@@ -91,7 +127,7 @@
|
||||
"quickActions": "Schnellaktionen",
|
||||
"recentFiles": "Kürzlich verwendete Dateien",
|
||||
"recentPages": "Kürzlich geöffnete Dokumente",
|
||||
"subtitle": "Willkommen im Wissensspeicher. Beginnen Sie hier mit der Verwaltung Ihrer Dokumente.",
|
||||
"subtitle": "Willkommen im Ressourcen-Center. Beginne hier mit der Verwaltung deiner Dokumente und Dateien.",
|
||||
"uploadEntries": {
|
||||
"files": {
|
||||
"title": "Dateien hochladen"
|
||||
@@ -99,27 +135,27 @@
|
||||
"folder": {
|
||||
"title": "Ordner hochladen"
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"title": "Neue Wissensdatenbank"
|
||||
"library": {
|
||||
"title": "Neue Bibliothek erstellen"
|
||||
},
|
||||
"newPage": {
|
||||
"title": "Neues Dokument erstellen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"library": {
|
||||
"list": {
|
||||
"confirmRemoveKnowledgeBase": "Die Wissensdatenbank wird gelöscht, die darin enthaltenen Dateien werden nicht gelöscht, sondern in den gesamten Dateien verschoben. Nach dem Löschen der Wissensdatenbank kann sie nicht wiederhergestellt werden, bitte vorsichtig vorgehen.",
|
||||
"empty": "Klicken Sie auf <1>+</1>, um eine Wissensdatenbank zu erstellen"
|
||||
"confirmRemoveLibrary": "Diese Bibliothek wird gelöscht. Die darin enthaltenen Dateien bleiben erhalten und werden in 'Alle Dateien' verschoben. Nach dem Löschen kann die Bibliothek nicht wiederhergestellt werden. Bitte gehe vorsichtig vor.",
|
||||
"empty": "Klicke auf <1>+</1>, um eine neue Bibliothek zu erstellen"
|
||||
},
|
||||
"new": "Neue Wissensdatenbank",
|
||||
"title": "Wissensdatenbank"
|
||||
"new": "Bibliothek",
|
||||
"title": "Bibliothek"
|
||||
},
|
||||
"menu": {
|
||||
"allFiles": "Alle Dateien",
|
||||
"allPages": "Alle Dokumente"
|
||||
},
|
||||
"networkError": "Fehler beim Abrufen der Wissensdatenbank. Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut.",
|
||||
"networkError": "Fehler beim Laden der Bibliothek. Bitte überprüfe deine Netzwerkverbindung und versuche es erneut.",
|
||||
"notSupportGuide": {
|
||||
"desc": "Die aktuelle Bereitstellung ist im Client-Datenbankmodus und unterstützt keine Dateiverwaltungsfunktionen. Bitte wechseln Sie zu <1>Server-Datenbank-Bereitstellungsmodus</1> oder verwenden Sie direkt <3>LobeChat Cloud</3>",
|
||||
"features": {
|
||||
@@ -131,9 +167,9 @@
|
||||
"desc": "Verwendet leistungsstarke Vektormodelle zur Vektorisierung von Textteilen, um eine semantische Suche nach Dateiinhalten zu ermöglichen",
|
||||
"title": "Vektor-Semantisierung"
|
||||
},
|
||||
"repos": {
|
||||
"desc": "Unterstützt die Erstellung von Wissensdatenbanken und ermöglicht das Hinzufügen verschiedener Dateitypen, um Ihr Fachwissen aufzubauen",
|
||||
"title": "Wissensdatenbank"
|
||||
"libraries": {
|
||||
"desc": "Erstelle Bibliotheken und füge verschiedene Dateitypen hinzu, um deine persönliche Wissenssammlung aufzubauen.",
|
||||
"title": "Bibliotheken"
|
||||
}
|
||||
},
|
||||
"title": "Der aktuelle Bereitstellungsmodus unterstützt keine Dateiverwaltung"
|
||||
@@ -155,7 +191,7 @@
|
||||
"videos": "Videos",
|
||||
"websites": "Webseiten"
|
||||
},
|
||||
"title": "Wissensdatenbank",
|
||||
"title": "Ressourcen",
|
||||
"toggleLeftPanel": "Seitenleiste ein-/ausblenden",
|
||||
"uploadDock": {
|
||||
"body": {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"starter": {
|
||||
"createAgent": "Assistent erstellen",
|
||||
"createGroup": "Gruppe erstellen",
|
||||
"deepResearch": "Tiefenrecherche",
|
||||
"developing": "In Entwicklung",
|
||||
"image": "Zeichnen",
|
||||
"write": "Schreiben"
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,10 @@
|
||||
"desc": "Die letzte Nachricht neu generieren",
|
||||
"title": "Nachricht neu generieren"
|
||||
},
|
||||
"saveDocument": {
|
||||
"desc": "Alle aktuellen Änderungen im Dokument sofort speichern",
|
||||
"title": "Dokument speichern"
|
||||
},
|
||||
"saveTopic": {
|
||||
"desc": "Das aktuelle Thema speichern und ein neues Thema öffnen",
|
||||
"title": "Neues Thema beginnen"
|
||||
@@ -66,12 +70,12 @@
|
||||
"title": "Schnell zwischen Assistenten wechseln"
|
||||
},
|
||||
"toggleLeftPanel": {
|
||||
"desc": "Linkes Hilfepanel ein- oder ausblenden",
|
||||
"title": "Assistentenpanel ein-/ausblenden"
|
||||
"desc": "Linke Seitenleiste ein- oder ausblenden",
|
||||
"title": "Linke Seitenleiste ein-/ausblenden"
|
||||
},
|
||||
"toggleRightPanel": {
|
||||
"desc": "Rechtes Themenpanel ein- oder ausblenden",
|
||||
"title": "Themenpanel ein-/ausblenden"
|
||||
"desc": "Rechte Seitenleiste ein- oder ausblenden",
|
||||
"title": "Rechte Seitenleiste ein-/ausblenden"
|
||||
},
|
||||
"toggleZenMode": {
|
||||
"desc": "Im Fokusmodus nur die aktuelle Sitzung anzeigen, andere UI ausblenden",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"addToKnowledgeBase": {
|
||||
"addSuccess": "Datei erfolgreich hinzugefügt, <1>jetzt ansehen</1>",
|
||||
"confirm": "Hinzufügen",
|
||||
"error": "Datei konnte nicht zur Wissensdatenbank hinzugefügt werden",
|
||||
"id": {
|
||||
"placeholder": "Bitte wählen Sie die zu hinzuzufügende Wissensdatenbank",
|
||||
"required": "Bitte wählen Sie eine Wissensdatenbank",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"authorize": {
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Autorisieren",
|
||||
"description": {
|
||||
"and": "und",
|
||||
"prefix": "Mit dem Klick auf „Autorisieren“ stimmen Sie den",
|
||||
"privacy": "Datenschutzbestimmungen",
|
||||
"confirm": "Erstelle dein Profil",
|
||||
"description": "Dein Community-Profil ist unabhängig von deinem {{appName}}-Benutzerkonto.",
|
||||
"footer": {
|
||||
"agreement": "Mit dem Fortfahren bestätigst du, dass du die <terms>Nutzungsbedingungen</terms> und die <privacy>Datenschutzerklärung</privacy> gelesen und akzeptiert hast.",
|
||||
"privacy": "Datenschutzerklärung",
|
||||
"terms": "Nutzungsbedingungen"
|
||||
},
|
||||
"title": "Autorisierung bestätigen"
|
||||
"subtitle": "Erstelle ein Community-Profil, um Inhalte einzureichen und deine Einreichungen zu verwalten.",
|
||||
"title": "Community-Profil erstellen"
|
||||
},
|
||||
"callback": {
|
||||
"buttons": {
|
||||
@@ -51,5 +52,42 @@
|
||||
"submit": "Autorisierung erfolgreich! Du kannst jetzt einen Assistenten veröffentlichen.",
|
||||
"upload": "Autorisierung erfolgreich! Du kannst jetzt eine neue Version veröffentlichen."
|
||||
}
|
||||
},
|
||||
"profileSetup": {
|
||||
"cancel": "Abbrechen",
|
||||
"descriptionEdit": "Aktualisiere deine Community-Profilinformationen.",
|
||||
"descriptionFirstTime": "Richte dein Profil ein, um dein Community-Konto zu vervollständigen.",
|
||||
"errors": {
|
||||
"notAuthenticated": "Bitte melde dich an, um fortzufahren.",
|
||||
"updateFailed": "Profilaktualisierung fehlgeschlagen. Bitte versuche es erneut.",
|
||||
"usernameTaken": "Diese Benutzer-ID ist bereits vergeben. Bitte wähle eine andere."
|
||||
},
|
||||
"fields": {
|
||||
"description": {
|
||||
"label": "Über mich",
|
||||
"maxLength": "Die Beschreibung darf maximal 200 Zeichen enthalten.",
|
||||
"placeholder": "Erzähle etwas über dich..."
|
||||
},
|
||||
"displayName": {
|
||||
"label": "Spitzname",
|
||||
"maxLength": "Der Spitzname darf maximal 50 Zeichen enthalten.",
|
||||
"placeholder": "Gib deinen Spitznamen ein",
|
||||
"required": "Bitte gib einen Spitznamen ein"
|
||||
},
|
||||
"userName": {
|
||||
"label": "Benutzer-ID",
|
||||
"maxLength": "Die Benutzer-ID darf maximal 32 Zeichen enthalten.",
|
||||
"minLength": "Die Benutzer-ID muss mindestens 3 Zeichen lang sein.",
|
||||
"pattern": "Die Benutzer-ID darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.",
|
||||
"placeholder": "Gib deine Benutzer-ID ein",
|
||||
"required": "Bitte gib eine Benutzer-ID ein",
|
||||
"tooltip": "Die Benutzer-ID ist dein eindeutiger Identifikator und wird in deinem Profil-Link verwendet."
|
||||
}
|
||||
},
|
||||
"getStarted": "Loslegen",
|
||||
"save": "Speichern",
|
||||
"success": "Profil erfolgreich aktualisiert",
|
||||
"titleEdit": "Profil bearbeiten",
|
||||
"titleFirstTime": "Vervollständige dein Profil"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"context": {
|
||||
"actions": {
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"defaultType": "Kontext",
|
||||
"deleteConfirm": "Möchten Sie diesen Kontextspeicher wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"deleteTitle": "Kontextspeicher löschen",
|
||||
"description": "Beschreibung",
|
||||
"empty": "Keine Kontextspeicher vorhanden",
|
||||
"impact": "Auswirkungsgrad",
|
||||
"source": "Quelle",
|
||||
"urgency": "Dringlichkeit"
|
||||
},
|
||||
"empty": {
|
||||
"description": "Das Abrufen von Erinnerungen ist ein schrittweiser Prozess. Bitte sammeln Sie mehr Themen, um den Inhalt der Erinnerungserfassung zu bereichern. Versuchen Sie, tiefere Gespräche mit dem Assistenten zu führen, um wertvolle Informationen besser erfassen und speichern zu können.",
|
||||
"search": "Keine passenden Erinnerungen gefunden",
|
||||
"title": "Keine Erinnerungen vorhanden"
|
||||
},
|
||||
"experience": {
|
||||
"actions": {
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"confidence": "Vertrauensgrad",
|
||||
"defaultType": "Erfahrung",
|
||||
"deleteConfirm": "Möchten Sie diesen Erfahrungsspeicher wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"deleteTitle": "Erfahrungsspeicher löschen",
|
||||
"empty": "Keine Erfahrungsspeicher vorhanden",
|
||||
"keyLearning": "Zentrale Erkenntnis",
|
||||
"situation": "Kontext",
|
||||
"source": "Quelle",
|
||||
"steps": {
|
||||
"action": "Maßnahme",
|
||||
"outcome": "Mögliches Ergebnis",
|
||||
"reasoning": "Begründung",
|
||||
"situation": "Situationshintergrund"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"search": "Suchen...",
|
||||
"sort": {
|
||||
"createdAt": "Erstellungsdatum",
|
||||
"scoreConfidence": "Vertrauenswürdigkeit",
|
||||
"scoreImpact": "Auswirkung",
|
||||
"scorePriority": "Priorität",
|
||||
"scoreUrgency": "Dringlichkeit"
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"empty": "Keine Identitätserinnerung vorhanden",
|
||||
"filter": {
|
||||
"search": "Suche nach Rolle, Beziehung oder Beschreibung...",
|
||||
"type": {
|
||||
"all": "Alle",
|
||||
"demographic": "Demografisch",
|
||||
"personal": "Persönlich",
|
||||
"professional": "Beruflich"
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"confirmDelete": "Löschen bestätigen",
|
||||
"deleteCancel": "Abbrechen",
|
||||
"deleteContent": "Möchten Sie diese Identitätserinnerung wirklich löschen? Dieser Vorgang kann nicht rückgängig gemacht werden.",
|
||||
"deleteOk": "Löschen",
|
||||
"noResults": "Keine passenden Identitätserinnerungen gefunden",
|
||||
"updated": "Aktualisiert"
|
||||
},
|
||||
"roleCloud": {
|
||||
"collapse": "Weniger anzeigen",
|
||||
"expand": "Mehr anzeigen"
|
||||
},
|
||||
"view": {
|
||||
"list": "Liste",
|
||||
"timeline": "Zeitleiste"
|
||||
}
|
||||
},
|
||||
"loading": "Wird geladen...",
|
||||
"preference": {
|
||||
"actions": {
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"conclusionDirectives": "Schlussfolgerungsanweisungen",
|
||||
"defaultType": "Präferenz",
|
||||
"deleteConfirm": "Möchten Sie diesen Präferenzspeicher wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"deleteTitle": "Präferenzspeicher löschen",
|
||||
"empty": "Keine Präferenzspeicher vorhanden",
|
||||
"priority": "Priorität",
|
||||
"source": "Quelle",
|
||||
"suggestions": "Vorschläge"
|
||||
},
|
||||
"tab": {
|
||||
"contexts": "Kontexte",
|
||||
"experiences": "Erfahrungen",
|
||||
"home": "Startseite",
|
||||
"identities": "Identitäten",
|
||||
"preferences": "Präferenzen",
|
||||
"search": "Suche"
|
||||
},
|
||||
"viewMode": {
|
||||
"masonry": "Kachelansicht",
|
||||
"timeline": "Zeitstrahl"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user