@lobehub/chat 0.119.13 → 0.120.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.i18nrc.js +1 -0
  2. package/CHANGELOG.md +50 -0
  3. package/LICENSE +1 -1
  4. package/README.md +24 -26
  5. package/README.zh-CN.md +67 -26
  6. package/docs/Deployment/Common-Error.md +18 -1
  7. package/docs/Deployment/Common-Error.zh-CN.md +18 -0
  8. package/docs/Deployment/Environment-Variable.md +14 -3
  9. package/docs/Deployment/Environment-Variable.zh-CN.md +14 -3
  10. package/docs/Development/Contributing-Guidelines.zh-CN.md +72 -0
  11. package/docs/Development/Internationalization-Implementation.zh-CN.md +117 -0
  12. package/docs/Development/State-Management/Selectors.zh-CN.md +49 -0
  13. package/docs/Development/State-Management/index.zh-CN.md +219 -0
  14. package/docs/Development/index.zh-CN.md +66 -1
  15. package/docs/Home.md +3 -3
  16. package/docs/_Sidebar.md +4 -4
  17. package/locales/ar/chat.json +96 -0
  18. package/locales/ar/common.json +114 -0
  19. package/locales/ar/error.json +72 -0
  20. package/locales/ar/market.json +30 -0
  21. package/locales/ar/migration.json +45 -0
  22. package/locales/ar/plugin.json +166 -0
  23. package/locales/ar/setting.json +306 -0
  24. package/locales/ar/tool.json +9 -0
  25. package/locales/ar/welcome.json +14 -0
  26. package/package.json +10 -5
  27. package/src/app/home/Redirect.tsx +4 -0
  28. package/src/app/layout.tsx +3 -2
  29. package/src/features/Conversation/components/ChatItem/index.tsx +12 -1
  30. package/src/features/Conversation/components/VirtualizedList/index.tsx +4 -1
  31. package/src/locales/create.ts +8 -1
  32. package/src/locales/default/common.ts +1 -0
  33. package/src/locales/resources.ts +5 -0
  34. package/vercel.json +1 -1
  35. package/docs/Development/Data-Store-Selectors.zh-CN.md +0 -68
  36. /package/docs/{Add-New-Locale.md → Development/Add-New-Locale.md} +0 -0
  37. /package/docs/{Add-New-Locale.zh-CN.md → Development/Add-New-Locale.zh-CN.md} +0 -0
  38. /package/docs/Development/{Data-Store-Selectors.md → State-Management/Selectors.md} +0 -0
@@ -0,0 +1,306 @@
1
+ {
2
+ "danger": {
3
+ "clear": {
4
+ "action": "مسح الآن",
5
+ "confirm": "هل تؤكد مسح جميع بيانات المحادثات؟",
6
+ "desc": "سيتم مسح جميع بيانات الجلسة بما في ذلك المساعد والملفات والرسائل والإضافات",
7
+ "success": "تم مسح جميع رسائل الجلسة",
8
+ "title": "مسح جميع رسائل الجلسة"
9
+ },
10
+ "reset": {
11
+ "action": "إعادة تعيين الآن",
12
+ "confirm": "هل تؤكد إعادة تعيين جميع الإعدادات؟",
13
+ "currentVersion": "الإصدار الحالي",
14
+ "desc": "إعادة تعيين جميع عناصر الإعدادات إلى القيم الافتراضية",
15
+ "title": "إعادة تعيين جميع الإعدادات"
16
+ }
17
+ },
18
+ "header": {
19
+ "global": "إعدادات عامة",
20
+ "session": "إعدادات الجلسة",
21
+ "sessionWithName": "إعدادات الجلسة · {{name}}"
22
+ },
23
+ "llm": {
24
+ "AzureOpenAI": {
25
+ "endpoint": {
26
+ "desc": "يمكن العثور على هذه القيمة في قسم 'المفتاح والنقطة النهائية' عند التحقق من الموارد في بوابة Azure",
27
+ "placeholder": "https://docs-test-001.openai.azure.com",
28
+ "title": "عنوان واجهة برمجة التطبيقات لـ Azure"
29
+ },
30
+ "models": {
31
+ "desc": "النماذج المدعومة",
32
+ "title": "قائمة النماذج"
33
+ },
34
+ "title": "إعدادات Azure OpenAI",
35
+ "token": {
36
+ "desc": "يمكن العثور على هذه القيمة في قسم 'المفتاح والنقطة النهائية' عند التحقق من الموارد في بوابة Azure. يمكن استخدام KEY1 أو KEY2",
37
+ "placeholder": "مفتاح واجهة برمجة التطبيقات لـ Azure",
38
+ "title": "مفتاح واجهة برمجة التطبيقات"
39
+ }
40
+ },
41
+ "OpenAI": {
42
+ "azureApiVersion": {
43
+ "desc": "إصدار واجهة برمجة التطبيقات لـ Azure، يتبع تنسيق YYYY-MM-DD، اطلع على [أحدث الإصدارات](https://learn.microsoft.com/zh-cn/azure/ai-services/openai/reference#chat-completions)",
44
+ "fetch": "الحصول على القائمة",
45
+ "title": "إصدار واجهة برمجة التطبيقات لـ Azure"
46
+ },
47
+ "check": {
48
+ "button": "فحص",
49
+ "desc": "اختبار مفتاح واجهة البرمجة التطبيقية وعنوان الوكيل للتأكد من ملئهما بشكل صحيح",
50
+ "pass": "تم الفحص بنجاح",
51
+ "title": "فحص الاتصال"
52
+ },
53
+ "customModelName": {
54
+ "desc": "إضافة نموذج مخصص، يمكن استخدام فاصلة (,) للنماذج المتعددة",
55
+ "placeholder": "نموذج1،نموذج2،نموذج3",
56
+ "title": "اسم النموذج المخصص"
57
+ },
58
+ "endpoint": {
59
+ "desc": "بالإضافة إلى العنوان الافتراضي، يجب أن يتضمن http(s)://",
60
+ "placeholder": "https://api.openai.com/v1",
61
+ "title": "عنوان وكيل الواجهة"
62
+ },
63
+ "models": {
64
+ "count": "يدعم {{count}} نموذج",
65
+ "desc": "النماذج المدعومة",
66
+ "fetch": "الحصول على قائمة النماذج",
67
+ "notSupport": "Azure OpenAI غير مدعوم حالياً لعرض قائمة النماذج",
68
+ "notSupportTip": "يجب عليك التأكد بنفسك من تطابق اسم النموذج مع اسم النشر",
69
+ "refetch": "الحصول على قائمة النماذج مرة أخرى",
70
+ "title": "قائمة النماذج"
71
+ },
72
+ "title": "إعدادات OpenAI",
73
+ "token": {
74
+ "desc": "استخدام مفتاح OpenAI الخاص بك",
75
+ "placeholder": "مفتاح واجهة برمجة التطبيقات لـ OpenAI",
76
+ "title": "مفتاح واجهة برمجة التطبيقات"
77
+ },
78
+ "useAzure": {
79
+ "desc": "استخدام خدمة OpenAI المقدمة من Azure",
80
+ "fetch": "الحصول على القائمة",
81
+ "serverConfig": "المسؤول قام بتكوين خدمة Azure OpenAI على الخادم، ويتم منع التبديل",
82
+ "title": "استخدام Azure OpenAI"
83
+ }
84
+ },
85
+ "waitingForMore": "يتم <1>التخطيط لتوفير</1> المزيد من النماذج، ترقبوا المزيد ✨"
86
+ },
87
+ "plugin": {
88
+ "addTooltip": "إضافة البرنامج المساعد",
89
+ "clearDeprecated": "مسح البرامج المساعدة الغير صالحة",
90
+ "empty": "لا توجد برامج مساعدة مثبتة حاليًا، نرحب بك لزيارة <1>متجر البرامج المساعدة</1> للاستكشاف",
91
+ "installStatus": {
92
+ "deprecated": "تم إلغاء التثبيت"
93
+ },
94
+ "settings": {
95
+ "hint": "يرجى ملء الإعدادات التالية وفقًا للوصف",
96
+ "title": "إعدادات البرنامج المساعد {{id}}",
97
+ "tooltip": "إعدادات البرنامج المساعد"
98
+ },
99
+ "store": "متجر البرامج المساعد"
100
+ },
101
+ "settingAgent": {
102
+ "avatar": {
103
+ "title": "الصورة الرمزية"
104
+ },
105
+ "backgroundColor": {
106
+ "title": "لون الخلفية"
107
+ },
108
+ "description": {
109
+ "placeholder": "الرجاء إدخال وصف المساعد",
110
+ "title": "وصف المساعد"
111
+ },
112
+ "name": {
113
+ "placeholder": "الرجاء إدخال اسم المساعد",
114
+ "title": "الاسم"
115
+ },
116
+ "prompt": {
117
+ "placeholder": "الرجاء إدخال كلمة الإشارة للشخصية",
118
+ "title": "ضبط الشخصية"
119
+ },
120
+ "tag": {
121
+ "placeholder": "الرجاء إدخال العلامة",
122
+ "title": "العلامة"
123
+ },
124
+ "title": "معلومات المساعد"
125
+ },
126
+ "settingChat": {
127
+ "autoCreateTopicThreshold": {
128
+ "desc": "عند تجاوز عدد الرسائل الحالي هذا القيمة، سيتم إنشاء موضوع تلقائيًا",
129
+ "title": "عتبة إنشاء الموضوع التلقائي"
130
+ },
131
+ "chatStyleType": {
132
+ "title": "نوع نافذة الدردشة",
133
+ "type": {
134
+ "chat": "نمط المحادثة",
135
+ "docs": "نمط الوثائق"
136
+ }
137
+ },
138
+ "compressThreshold": {
139
+ "desc": "عندما يتجاوز عدد الرسائل التاريخية غير المضغوطة هذه القيمة، سيتم ضغطها",
140
+ "title": "عتبة ضغط طول الرسائل التاريخية"
141
+ },
142
+ "enableAutoCreateTopic": {
143
+ "desc": "هل يجب إنشاء موضوع تلقائيًا أثناء الدردشة، يسري ذلك فقط في المواضيع المؤقتة",
144
+ "title": "تمكين إنشاء الموضوع تلقائيًا"
145
+ },
146
+ "enableCompressThreshold": {
147
+ "title": "هل تريد تمكين عتبة ضغط طول الرسائل التاريخية"
148
+ },
149
+ "enableHistoryCount": {
150
+ "alias": "غير محدود",
151
+ "limited": "يحتوي فقط على {{number}} رسالة محادثة",
152
+ "title": "تحديد عدد الرسائل التاريخية",
153
+ "unlimited": "غير محدود"
154
+ },
155
+ "historyCount": {
156
+ "desc": "عدد الرسائل التي يتم إرفاقها في كل طلب (تشمل الأسئلة والأجوبة الجديدة. يُحسب كل سؤال وجواب كرسالة واحدة)",
157
+ "title": "عدد الرسائل المرفقة"
158
+ },
159
+ "inputTemplate": {
160
+ "desc": "سيتم ملء أحدث رسالة من المستخدم في هذا القالب",
161
+ "placeholder": "القالب المُعالج مسبقًا {{text}} سيتم استبداله بالمعلومات المُدخلة في الوقت الحقيقي",
162
+ "title": "معالجة مُدخلات المستخدم"
163
+ },
164
+ "title": "إعدادات الدردشة"
165
+ },
166
+ "settingModel": {
167
+ "enableMaxTokens": {
168
+ "title": "تمكين الحد الأقصى للردود"
169
+ },
170
+ "frequencyPenalty": {
171
+ "desc": "كلما زادت القيمة، زاد احتمال تقليل تكرار الكلمات",
172
+ "title": "عقوبة التكرار"
173
+ },
174
+ "maxTokens": {
175
+ "desc": "عدد الرموز الأقصى المستخدمة في التفاعل الواحد",
176
+ "title": "الحد الأقصى للردود"
177
+ },
178
+ "model": {
179
+ "desc": "نموذج ChatGPT",
180
+ "list": {
181
+ "gpt-3.5-turbo": "GPT 3.5",
182
+ "gpt-3.5-turbo-16k": "GPT 3.5 (16K)",
183
+ "gpt-4": "GPT 4",
184
+ "gpt-4-32k": "GPT 4 (32K)"
185
+ },
186
+ "title": "النموذج"
187
+ },
188
+ "presencePenalty": {
189
+ "desc": "كلما زادت القيمة، زاد احتمال التوسع في مواضيع جديدة",
190
+ "title": "جديد الحديث"
191
+ },
192
+ "temperature": {
193
+ "desc": "كلما زادت القيمة، زادت الردود عشوائية أكثر",
194
+ "title": "التباين",
195
+ "titleWithValue": "التباين {{value}}"
196
+ },
197
+ "title": "إعدادات النموذج",
198
+ "topP": {
199
+ "desc": "مشابه للتباين ولكن لا يجب تغييره مع التباين",
200
+ "title": "العينة الأساسية"
201
+ }
202
+ },
203
+ "settingPlugin": {
204
+ "title": "قائمة الإضافات"
205
+ },
206
+ "settingSystem": {
207
+ "accessCode": {
208
+ "desc": "قام المسؤول بتمكين الوصول المشفر",
209
+ "placeholder": "الرجاء إدخال كلمة المرور",
210
+ "title": "كلمة المرور"
211
+ },
212
+ "title": "إعدادات النظام"
213
+ },
214
+ "settingTTS": {
215
+ "openai": {
216
+ "sttModel": "نموذج تحويل النص إلى كلام من OpenAI",
217
+ "ttsModel": "نموذج توليد الكلام من OpenAI"
218
+ },
219
+ "showAllLocaleVoice": {
220
+ "desc": "إذا تم إيقافه، سيتم عرض مصادر الصوت الخاصة باللغة الحالية فقط",
221
+ "title": "عرض جميع مصادر الصوت للغات"
222
+ },
223
+ "stt": "إعدادات التحويل من الصوت إلى نص",
224
+ "sttAutoStop": {
225
+ "desc": "عند الإيقاف، لن يتم إيقاف تحويل الصوت إلى نص تلقائيًا، وسيتطلب الأمر النقر على زر الإيقاف يدويًا",
226
+ "title": "إيقاف تحويل الصوت إلى نص تلقائيًا"
227
+ },
228
+ "sttLocale": {
229
+ "desc": "لغة الصوت المدخلة، يمكن أن يساعد هذا الخيار في زيادة دقة تحويل الصوت إلى نص",
230
+ "title": "لغة تحويل الصوت إلى نص"
231
+ },
232
+ "sttService": {
233
+ "desc": "حيث يكون المتصفح هو خدمة التحويل الصوتي الأصلية",
234
+ "title": "خدمة تحويل الصوت إلى نص"
235
+ },
236
+ "title": "خدمة الصوت",
237
+ "tts": "إعدادات توليد الكلام",
238
+ "ttsService": {
239
+ "desc": "إذا كنت تستخدم خدمة توليد الكلام من OpenAI، يجب التأكد من تمكين خدمة نموذج OpenAI",
240
+ "title": "خدمة توليد الكلام"
241
+ },
242
+ "voice": {
243
+ "desc": "حدد صوتًا للمساعد الحالي، تختلف مصادر الصوت المدعومة بحسب خدمة توليد الكلام",
244
+ "preview": "معاينة الصوت",
245
+ "title": "مصدر توليد الكلام"
246
+ }
247
+ },
248
+ "settingTheme": {
249
+ "avatar": {
250
+ "title": "الصورة الرمزية"
251
+ },
252
+ "fontSize": {
253
+ "desc": "حجم الخط لمحتوى المحادثة",
254
+ "marks": {
255
+ "large": "كبير",
256
+ "normal": "عادي",
257
+ "small": "صغير"
258
+ },
259
+ "title": "حجم الخط"
260
+ },
261
+ "lang": {
262
+ "autoMode": "متابعة النظام",
263
+ "title": "اللغة"
264
+ },
265
+ "neutralColor": {
266
+ "desc": "تخصيص درجات اللون الرمادي للاتجاهات المختلفة",
267
+ "title": "اللون الأحادي"
268
+ },
269
+ "primaryColor": {
270
+ "desc": "تخصيص لون السمة الرئيسي",
271
+ "title": "لون السمة"
272
+ },
273
+ "themeMode": {
274
+ "auto": "تلقائي",
275
+ "dark": "داكن",
276
+ "light": "فاتح",
277
+ "title": "السمة"
278
+ },
279
+ "title": "إعدادات السمة"
280
+ },
281
+ "submitAgentModal": {
282
+ "button": "تقديم المساعد",
283
+ "identifier": "معرف المساعد",
284
+ "metaMiss": "يرجى استكمال معلومات المساعد قبل التقديم، يجب أن تتضمن الاسم والوصف والعلامة",
285
+ "placeholder": "الرجاء إدخال معرف المساعد، يجب أن يكون فريدًا، مثل تطوير الويب",
286
+ "tooltips": "مشاركة في سوق المساعدين"
287
+ },
288
+ "tab": {
289
+ "agent": "المساعد الافتراضي",
290
+ "common": "إعدادات عامة",
291
+ "llm": "نموذج اللغة",
292
+ "tts": "خدمة الكلام"
293
+ },
294
+ "tools": {
295
+ "builtins": {
296
+ "groupName": "الامتدادات المدمجة"
297
+ },
298
+ "plugins": {
299
+ "enabled": "ممكّنة {{num}}",
300
+ "groupName": "الإضافات",
301
+ "noEnabled": "لا توجد إضافات ممكّنة حاليًا",
302
+ "store": "متجر الإضافات"
303
+ },
304
+ "title": "أدوات الامتداد"
305
+ }
306
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "dalle": {
3
+ "autoGenerate": "توليد تلقائي",
4
+ "downloading": "صلاحية روابط الصور المُولَّدة بواسطة DallE3 تدوم ساعة واحدة فقط، يتم تحميل الصور إلى الجهاز المحلي...",
5
+ "generate": "توليد",
6
+ "generating": "جارٍ التوليد...",
7
+ "images": "الصور:"
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "button": {
3
+ "import": "استيراد التكوين",
4
+ "start": "ابدأ الآن"
5
+ },
6
+ "header": "مرحبًا بكم في الاستخدام",
7
+ "pickAgent": "أو اختيار قالب مساعد من القائمة التالية",
8
+ "skip": "تخطى الإنشاء",
9
+ "slogan": {
10
+ "desc1": "قم بتشغيل عقلك الجماعي وأشعل شرارة التفكير. مساعدك الذكي، دائمًا موجود.",
11
+ "desc2": "أنشئ مساعدك الأول ولنبدأ!",
12
+ "title": "امنح نفسك عقلاً أذكى"
13
+ }
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.119.13",
3
+ "version": "0.120.1",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -44,8 +44,11 @@
44
44
  "release": "semantic-release",
45
45
  "start": "next start",
46
46
  "stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
47
- "test": "vitest --passWithNoTests",
48
- "test:coverage": "vitest run --coverage --passWithNoTests",
47
+ "test": "npm run test-temp-fixture-ci && vitest",
48
+ "test-a-local": "vitest",
49
+ "test-temp-fixture-ci": "echo 'please remove this script after new bun@1.0.24 released' && rm -rf node_modules && npx bun@1.0.22 install",
50
+ "test:coverage": "npm run test-temp-fixture-ci && vitest run --coverage",
51
+ "test:coverage-local": "vitest run --coverage",
49
52
  "test:update": "vitest -u",
50
53
  "type-check": "tsc --noEmit",
51
54
  "workflow:docs": "bun scripts/docsWorkflow/index.ts",
@@ -83,7 +86,7 @@
83
86
  "ai": "^2.2.22",
84
87
  "antd": "^5.12.7",
85
88
  "antd-style": "^3",
86
- "brotli-wasm": "^1",
89
+ "brotli-wasm": "^2.0.0",
87
90
  "chroma-js": "^2",
88
91
  "copy-to-clipboard": "^3",
89
92
  "dayjs": "^1",
@@ -117,8 +120,9 @@
117
120
  "remark": "^14",
118
121
  "remark-gfm": "^3",
119
122
  "remark-html": "^15",
123
+ "rtl-detect": "^1.1.2",
120
124
  "semver": "^7",
121
- "sharp": "^0.33",
125
+ "sharp": "0.33.1",
122
126
  "swr": "^2",
123
127
  "systemjs": "^6",
124
128
  "ts-md5": "^1",
@@ -148,6 +152,7 @@
148
152
  "@types/node": "^20",
149
153
  "@types/react": "^18",
150
154
  "@types/react-dom": "^18",
155
+ "@types/rtl-detect": "^1.0.3",
151
156
  "@types/semver": "^7",
152
157
  "@types/systemjs": "^6",
153
158
  "@types/ua-parser-js": "^0.7",
@@ -5,6 +5,8 @@ import { memo, useEffect } from 'react';
5
5
 
6
6
  import { messageService } from '@/services/message';
7
7
  import { sessionService } from '@/services/session';
8
+ import { useGlobalStore } from '@/store/global';
9
+ import { SidebarTabKey } from '@/store/global/initialState';
8
10
  import { useSessionStore } from '@/store/session';
9
11
 
10
12
  const checkHasConversation = async () => {
@@ -19,6 +21,8 @@ const Redirect = memo(() => {
19
21
 
20
22
  useEffect(() => {
21
23
  checkHasConversation().then((hasData) => {
24
+ useGlobalStore.getState().switchSideBar(SidebarTabKey.Chat);
25
+
22
26
  if (hasData) {
23
27
  router.push('/chat');
24
28
 
@@ -1,6 +1,7 @@
1
1
  import { Viewport } from 'next';
2
2
  import { cookies } from 'next/headers';
3
3
  import { PropsWithChildren } from 'react';
4
+ import { isRtlLang } from 'rtl-detect';
4
5
 
5
6
  import Analytics from '@/components/Analytics';
6
7
  import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
@@ -20,9 +21,9 @@ const RootLayout = ({ children }: PropsWithChildren) => {
20
21
  const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR);
21
22
  const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR);
22
23
  const lang = cookieStore.get(LOBE_LOCALE_COOKIE);
23
-
24
+ const direction = isRtlLang(lang?.value || DEFAULT_LANG) ? 'rtl' : 'ltr';
24
25
  return (
25
- <html lang={lang?.value || DEFAULT_LANG} suppressHydrationWarning>
26
+ <html dir={direction} lang={lang?.value || DEFAULT_LANG} suppressHydrationWarning>
26
27
  <body>
27
28
  <StyleRegistry>
28
29
  <Layout
@@ -1,4 +1,5 @@
1
1
  import { type AlertProps, ChatItem } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
2
3
  import isEqual from 'fast-deep-equal';
3
4
  import { ReactNode, memo, useCallback, useMemo, useState } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
@@ -15,6 +16,15 @@ import { renderMessages, useAvatarsClick } from '../../Messages';
15
16
  import ActionsBar from './ActionsBar';
16
17
  import HistoryDivider from './HistoryDivider';
17
18
 
19
+ const useStyles = createStyles(({ css, prefixCls }) => ({
20
+ message: css`
21
+ // prevent the textarea too long
22
+ .${prefixCls}-input {
23
+ max-height: 900px;
24
+ }
25
+ `,
26
+ }));
27
+
18
28
  export interface ChatListItemProps {
19
29
  id: string;
20
30
  index: number;
@@ -22,7 +32,7 @@ export interface ChatListItemProps {
22
32
 
23
33
  const Item = memo<ChatListItemProps>(({ index, id }) => {
24
34
  const { t } = useTranslation('common');
25
-
35
+ const { styles } = useStyles();
26
36
  const [editing, setEditing] = useState(false);
27
37
  const [type = 'chat'] = useSessionStore((s) => {
28
38
  const config = agentSelectors.currentAgentConfig(s);
@@ -111,6 +121,7 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
111
121
  <ChatItem
112
122
  actions={<ActionsBar index={index} setEditing={setEditing} />}
113
123
  avatar={item.meta}
124
+ className={styles.message}
114
125
  editing={editing}
115
126
  error={error}
116
127
  errorMessage={<ErrorMessage data={item} />}
@@ -42,6 +42,9 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
42
42
  }
43
43
  }, [id]);
44
44
 
45
+ // overscan should be 1.5 times the height of the window
46
+ const overscan = typeof window !== 'undefined' ? window.innerHeight * 1.5 : 0;
47
+
45
48
  return chatLoading && data.length === 2 ? null : (
46
49
  <Flexbox height={'100%'}>
47
50
  <Virtuoso
@@ -52,7 +55,7 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
52
55
  followOutput={'auto'}
53
56
  initialTopMostItemIndex={data?.length - 1}
54
57
  itemContent={itemContent}
55
- overscan={70 * 10}
58
+ overscan={overscan}
56
59
  ref={virtuosoRef}
57
60
  />
58
61
  <AutoScroll
@@ -2,6 +2,7 @@ import i18n from 'i18next';
2
2
  import LanguageDetector from 'i18next-browser-languagedetector';
3
3
  import resourcesToBackend from 'i18next-resources-to-backend';
4
4
  import { initReactI18next } from 'react-i18next';
5
+ import { isRtlLang } from 'rtl-detect';
5
6
 
6
7
  import { getClientConfig } from '@/config/client';
7
8
  import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
@@ -23,7 +24,13 @@ export const createI18nNext = (lang?: string) => {
23
24
  return import(`@/../locales/${normalizeLocale(lng)}/${ns}.json`);
24
25
  }),
25
26
  );
26
-
27
+ // Dynamically set HTML direction on language change
28
+ instance.on('languageChanged', (lng) => {
29
+ if (typeof window !== 'undefined') {
30
+ const direction = isRtlLang(lng) ? 'rtl' : 'ltr';
31
+ document.documentElement.dir = direction;
32
+ }
33
+ });
27
34
  return {
28
35
  init: () =>
29
36
  instance.init({
@@ -51,6 +51,7 @@ export default {
51
51
  title: '导入数据',
52
52
  },
53
53
  lang: {
54
+ 'ar': '阿拉伯语',
54
55
  'bn': '孟加拉语',
55
56
  'cs-CZ': '捷克语',
56
57
  'da-DK': '丹麦语',
@@ -1,6 +1,7 @@
1
1
  import resources from './default';
2
2
 
3
3
  export const locales = [
4
+ 'ar',
4
5
  'de-DE',
5
6
  'en-US',
6
7
  'es-ES',
@@ -41,6 +42,10 @@ type LocaleOptions = {
41
42
  }[];
42
43
 
43
44
  export const localeOptions: LocaleOptions = [
45
+ {
46
+ label: 'العربية',
47
+ value: 'ar',
48
+ },
44
49
  {
45
50
  label: '简体中文',
46
51
  value: 'zh-CN',
package/vercel.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "installCommand": "npx bun@1.0.15 install"
2
+ "installCommand": "bun install"
3
3
  }
@@ -1,68 +0,0 @@
1
- # 数据存储取数模块
2
-
3
- selectors 是 LobeChat 数据流研发框架下的取数模块,它的作用是从 store 中以特定特务逻辑取出数据,供组件消费使用。
4
-
5
- 以 `src/store/plugin/selectors.ts` 为例:
6
-
7
- 这个 TypeScript 代码段定义了一个名为 `pluginSelectors` 的对象,该对象包含一系列用于从插件存储状态中检索数据的选择器函数。选择器是一种从 Redux store(或类似的状态管理库)中提取和派生数据的函数。这个特定的例子是为了管理与前端应用程序的插件系统相关的状态。
8
-
9
- 下面是一些关键点的说明:
10
-
11
- - `enabledSchema`: 一个函数,返回一个基于启用插件列表 `enabledPlugins` 过滤后的 `ChatCompletionFunctions` 数组。它将插件标识符作为前缀添加到 API 名称,以确保唯一性,并使用 `uniqBy` 函数从 Lodash 库中删除重复项。
12
- - `onlinePluginStore`: 返回当前在线插件列表。
13
- - `pluginList`: 返回插件列表,包括自定义插件和标准插件。
14
- - `getPluginMetaById`: 根据插件 ID 返回插件元数据。
15
- - `getDevPluginById`: 返回开发中的自定义插件信息。
16
- - `getPluginManifestById`: 根据插件 ID 返回插件清单。
17
- - `getPluginSettingsById`: 根据插件 ID 返回插件设置。
18
- - `getPluginManifestLoadingStatus`: 根据插件 ID 返回插件清单的加载状态(加载中、成功或错误)。
19
- - `isCustomPlugin`: 检查给定 ID 的插件是否为自定义插件。
20
- - `displayPluginList`: 返回一个处理过的插件列表,包括作者、头像、创建时间、描述、首页 URL、标识符和标题。
21
- - `hasPluginUI`: 根据插件 ID 确定插件是否有 UI 组件。
22
-
23
- selectors 是高度模块化和可维护的,它通过将复杂的状态选择逻辑封装在单独的函数中,使得在应用程序的其他部分调用状态数据时,代码更加简洁和直观。此外,由于使用了 TypeScript,每个函数都可以具有明确的输入和输出类型,这有助于提高代码的可靠性和开发效率。
24
-
25
- 以 `displayPluginList` 方法为例。其代码如下:
26
-
27
- ```ts
28
- const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.customPluginList];
29
-
30
- const displayPluginList = (s: PluginStoreState) =>
31
- pluginList(s).map((p) => ({
32
- author: p.author,
33
- avatar: p.meta?.avatar,
34
- createAt: p.createAt,
35
- desc: pluginHelpers.getPluginDesc(p.meta),
36
- homepage: p.homepage,
37
- identifier: p.identifier,
38
- title: pluginHelpers.getPluginTitle(p.meta),
39
- }));
40
- ```
41
-
42
- - `pluginList` 方法:用于从插件状态存储 PluginStoreState 中获取所有插件的列表。 它通过结合两个数组:pluginList 和 customPluginList 来创建一个新的插件列表;
43
- - `displayPluginList` 方法:调用 pluginList 方法来获取合并后的插件列表,转换其中的 title 和 desc 变成显示在 UI 上的文本。
44
-
45
- 在组件中,只需引入便可直接获取最终消费的数据:
46
-
47
- ```tsx | pure
48
- import { usePluginStore } from '@/store/plugin';
49
- import { pluginSelectors } from '@/store/plugin/selectors';
50
-
51
- const Render = ({ plugins }) => {
52
- const list = usePluginStore(pluginSelectors.displayPluginList);
53
-
54
- return <> ... </>;
55
- };
56
- ```
57
-
58
- 这样实现的好处在于:
59
-
60
- 1. **解耦和重用**:通过将选择器独立于组件,我们可以在多个组件之间复用这些选择器而不需要重写取数逻辑。这减少了重复代码,提高了开发效率,并且使得代码库更加干净和易于维护。
61
- 2. **性能优化**:选择器可以用来计算派生数据,这样可以避免在每个组件中重复计算相同的数据。当状态发生变化时,只有依赖于这部分状态的选择器才会重新计算,从而减少不必要的渲染和计算。
62
- 3. **易于测试**:选择器是纯函数,它们仅依赖于传入的参数。这意味着它们可以在隔离的环境中进行测试,无需模拟整个 store 或组件树。
63
- 4. **类型安全**:由于 LobeChat 使用 TypeScript,每个选择器都有明确的输入和输出类型定义。这为开发者提供了自动完成和编译时检查的优势,减少了运行时错误。
64
- 5. **可维护性**:选择器集中了状态的读取逻辑,使得跟踪状态的变化和管理更加直观。如果状态结构发生变化,我们只需要更新相应的选择器,而不是搜索和替换整个代码库中的多个位置。
65
- 6. **可组合性**:选择器可以组合其他选择器,以创建更复杂的选择逻辑。这种模式允许开发者构建一个选择器层次结构,使得状态选择更加灵活和强大。
66
- 7. **简化组件逻辑**:组件不需要知道状态的结构或如何获取和计算需要的数据。组件只需调用选择器即可获取渲染所需的数据,这使得组件逻辑变得更简单和清晰。
67
-
68
- 通过这样的设计,LobeChat 的开发者可以更专注于构建用户界面和业务逻辑,而不必担心数据的获取和处理细节。这种模式也为未来可能的状态结构变更提供了更好的适应性和扩展性。