@liujitcn/kratos-taro-app-system 0.0.20 → 0.0.21
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.
- package/dist/build.mjs +4 -4
- package/package.json +3 -3
- package/src/api/base/ai_message.ts +8 -5
- package/src/index.ts +17 -0
- package/src/locales/README.md +15 -0
- package/src/locales/en-US.json +91 -0
- package/src/locales/es-ES.json +91 -0
- package/src/locales/fr-FR.json +91 -0
- package/src/locales/ja-JP.json +91 -0
- package/src/locales/ko-KR.json +91 -0
- package/src/locales/zh-CN.json +91 -0
- package/src/locales/zh-TW.json +91 -0
- package/src/pages.ts +4 -4
- package/src/views/pages/my/my.tsx +15 -7
- package/src/views/pagesMember/ai/components/SessionDrawer.tsx +8 -6
- package/src/views/pagesMember/ai/components/WelcomePanel.tsx +9 -7
- package/src/views/pagesMember/ai/index.tsx +120 -52
- package/src/views/pagesMember/profile/profile.tsx +21 -15
- package/src/views/pagesMember/settings/settings.scss +10 -0
- package/src/views/pagesMember/settings/settings.tsx +40 -8
|
@@ -3,8 +3,11 @@ import Taro, { getCurrentPages, useLoad } from '@tarojs/taro'
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
4
|
import {
|
|
5
5
|
formatSrc,
|
|
6
|
+
getCurrentLocale,
|
|
6
7
|
navigateAppRoute,
|
|
8
|
+
t,
|
|
7
9
|
uploadFile,
|
|
10
|
+
useI18n,
|
|
8
11
|
} from '@liujitcn/kratos-taro-app-core'
|
|
9
12
|
import { UniIcon } from '@liujitcn/kratos-taro-app-ui'
|
|
10
13
|
import { defAiMessageService, StreamAiMessageByChunkedRequest } from '../../../api/base/ai_message'
|
|
@@ -50,21 +53,14 @@ type StreamTask = { abort: () => void; aborted: boolean; finished: boolean; succ
|
|
|
50
53
|
type MessageMap = Record<string, ChatMessageItem[]>
|
|
51
54
|
|
|
52
55
|
const AI_TERMINAL = Terminal.TERMINAL_APP
|
|
53
|
-
const THINKING_MESSAGE_CONTENT = '正在回复'
|
|
54
56
|
const LOCAL_USER_MESSAGE_PREFIX = 'ai-user-local'
|
|
55
57
|
const PENDING_MESSAGE_ID = 'pending'
|
|
56
58
|
const MAX_ATTACHMENT_COUNT = 6
|
|
57
59
|
const STARTER_PROMPT_PAGE_SIZE = 4
|
|
58
60
|
|
|
59
|
-
const DEFAULT_SHORTCUTS: AiShortcut[] = [
|
|
60
|
-
{ key: 'summarize', title: '帮我总结一段内容', prompt: '请帮我总结以下内容', action: undefined, required_tools: [], sort: 1, group: '文本助手' },
|
|
61
|
-
{ key: 'rewrite', title: '帮我优化一段文字', prompt: '请帮我优化以下文字', action: undefined, required_tools: [], sort: 2, group: '文本助手' },
|
|
62
|
-
{ key: 'plan', title: '帮我制定一个计划', prompt: '请帮我制定一个清晰的执行计划', action: undefined, required_tools: [], sort: 3, group: '效率助手' },
|
|
63
|
-
{ key: 'ideas', title: '给我一些灵感', prompt: '请围绕这个主题给我一些新想法', action: undefined, required_tools: [], sort: 4, group: '效率助手' },
|
|
64
|
-
]
|
|
65
|
-
|
|
66
61
|
/** AI 助手页面。 */
|
|
67
62
|
export default function AiPage() {
|
|
63
|
+
const { locale } = useI18n()
|
|
68
64
|
const windowInfo = Taro.getWindowInfo() as ReturnType<typeof Taro.getWindowInfo> & {
|
|
69
65
|
safeAreaInsets?: { top?: number; bottom?: number }
|
|
70
66
|
}
|
|
@@ -95,7 +91,7 @@ export default function AiPage() {
|
|
|
95
91
|
const [sessions, setSessionsState] = useState<AiSession[]>([])
|
|
96
92
|
const [messages, setMessagesState] = useState<MessageMap>({})
|
|
97
93
|
const [selectedAttachments, setSelectedAttachments] = useState<AiAttachment[]>([])
|
|
98
|
-
const [starterShortcuts, setStarterShortcuts] = useState<AiShortcut[]>(
|
|
94
|
+
const [starterShortcuts, setStarterShortcuts] = useState<AiShortcut[]>(createDefaultShortcuts)
|
|
99
95
|
|
|
100
96
|
const activeSessionIDRef = useRef('')
|
|
101
97
|
const sessionsRef = useRef<AiSession[]>([])
|
|
@@ -122,15 +118,21 @@ export default function AiPage() {
|
|
|
122
118
|
const starterPromptStart = (starterPromptGroupIndex % starterPromptPageCount) * STARTER_PROMPT_PAGE_SIZE
|
|
123
119
|
const starterPrompts = starterShortcuts.slice(starterPromptStart, starterPromptStart + STARTER_PROMPT_PAGE_SIZE)
|
|
124
120
|
const hour = new Date().getHours()
|
|
125
|
-
const greetingPeriod = hour < 11
|
|
126
|
-
|
|
121
|
+
const greetingPeriod = hour < 11
|
|
122
|
+
? t('system.ai.period.morning')
|
|
123
|
+
: hour < 14
|
|
124
|
+
? t('system.ai.period.noon')
|
|
125
|
+
: hour < 18
|
|
126
|
+
? t('system.ai.period.afternoon')
|
|
127
|
+
: t('system.ai.period.evening')
|
|
128
|
+
const aiGreetingMessage = t('system.ai.greeting', { period: greetingPeriod })
|
|
127
129
|
const composerPlaceholder = isRecording
|
|
128
|
-
? '
|
|
130
|
+
? t('system.ai.recording')
|
|
129
131
|
: uploadingAttachment
|
|
130
|
-
? '
|
|
132
|
+
? t('system.ai.attachmentUploading')
|
|
131
133
|
: hasMessages
|
|
132
|
-
? '
|
|
133
|
-
: '
|
|
134
|
+
? t('system.ai.placeholder.continue')
|
|
135
|
+
: t('system.ai.placeholder.default')
|
|
134
136
|
const isSubmitDisabled =
|
|
135
137
|
uploadingAttachment ||
|
|
136
138
|
currentSessionSending ||
|
|
@@ -276,7 +278,10 @@ export default function AiPage() {
|
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
const createRemoteSession = async () => {
|
|
279
|
-
const response = await defAiSessionService.CreateAiSession({
|
|
281
|
+
const response = await defAiSessionService.CreateAiSession({
|
|
282
|
+
title: t('system.ai.newSession'),
|
|
283
|
+
terminal: AI_TERMINAL,
|
|
284
|
+
})
|
|
280
285
|
const session = response.session ? normalizeSession(response.session) : undefined
|
|
281
286
|
if (!session) return ''
|
|
282
287
|
upsertSession(session)
|
|
@@ -307,7 +312,7 @@ export default function AiPage() {
|
|
|
307
312
|
if (activeSessionIDRef.current === sessionID) scrollChatToBottom()
|
|
308
313
|
} catch (error) {
|
|
309
314
|
if (loadingSessionIDRef.current === sessionID) updateSessionMessages(sessionID, [])
|
|
310
|
-
showError(error, '
|
|
315
|
+
showError(error, t('system.ai.loadMessagesFailed'))
|
|
311
316
|
} finally {
|
|
312
317
|
if (loadingSessionIDRef.current === sessionID) setLoadingSessionID('')
|
|
313
318
|
}
|
|
@@ -322,7 +327,7 @@ export default function AiPage() {
|
|
|
322
327
|
const sessionID = await ensureActiveSession()
|
|
323
328
|
if (sessionID) await loadMessages(sessionID)
|
|
324
329
|
} catch (error) {
|
|
325
|
-
showError(error, '
|
|
330
|
+
showError(error, t('system.ai.loadSessionsFailed'))
|
|
326
331
|
} finally {
|
|
327
332
|
setLoadingSessions(false)
|
|
328
333
|
}
|
|
@@ -339,7 +344,7 @@ export default function AiPage() {
|
|
|
339
344
|
setStarterPromptGroupIndex(0)
|
|
340
345
|
}
|
|
341
346
|
} catch (error) {
|
|
342
|
-
showError(error, '
|
|
347
|
+
showError(error, t('system.ai.loadShortcutsFailed'))
|
|
343
348
|
} finally {
|
|
344
349
|
setLoadingShortcuts(false)
|
|
345
350
|
}
|
|
@@ -368,7 +373,7 @@ export default function AiPage() {
|
|
|
368
373
|
handledByStream = true
|
|
369
374
|
await chunkedTask.promise
|
|
370
375
|
parser.flush()
|
|
371
|
-
if (!task.finished && !task.aborted) throw new Error('
|
|
376
|
+
if (!task.finished && !task.aborted) throw new Error(t('system.ai.responseIncomplete'))
|
|
372
377
|
}
|
|
373
378
|
if (
|
|
374
379
|
process.env.TARO_ENV === 'h5' &&
|
|
@@ -388,15 +393,15 @@ export default function AiPage() {
|
|
|
388
393
|
}
|
|
389
394
|
runningStreamTaskMap.current.set(sessionID, task)
|
|
390
395
|
const response = await defAiMessageService.StreamAiMessage(request, { signal: controller.signal })
|
|
391
|
-
if (!response.body) throw new Error('
|
|
396
|
+
if (!response.body) throw new Error(t('system.ai.streamEmpty'))
|
|
392
397
|
await readAiEventStream(response.body, (event) => handleAiStreamEvent(event, task), controller.signal)
|
|
393
|
-
if (!task.finished && !task.aborted) throw new Error('
|
|
398
|
+
if (!task.finished && !task.aborted) throw new Error(t('system.ai.responseIncomplete'))
|
|
394
399
|
handledByStream = true
|
|
395
400
|
}
|
|
396
401
|
if (!handledByStream) {
|
|
397
402
|
const response = await defAiMessageService.SendAiMessage(request)
|
|
398
403
|
const nextMessages = normalizeNonStreamMessages(response)
|
|
399
|
-
if (!nextMessages.length) throw new Error('
|
|
404
|
+
if (!nextMessages.length) throw new Error(t('system.ai.responseEmpty'))
|
|
400
405
|
updateSessionMessages(sessionID, (current) => replacePendingMessages(current, nextMessages))
|
|
401
406
|
scrollChatToBottom()
|
|
402
407
|
if (response.session) upsertSession(normalizeSession(response.session))
|
|
@@ -407,7 +412,7 @@ export default function AiPage() {
|
|
|
407
412
|
if (task?.aborted) return false
|
|
408
413
|
updateSessionMessages(sessionID, markThinkingMessageFailed)
|
|
409
414
|
scrollChatToBottom()
|
|
410
|
-
showError(error, '
|
|
415
|
+
showError(error, t('system.ai.requestFailed'))
|
|
411
416
|
return false
|
|
412
417
|
} finally {
|
|
413
418
|
if (task && runningStreamTaskMap.current.get(sessionID) === task) {
|
|
@@ -433,6 +438,12 @@ export default function AiPage() {
|
|
|
433
438
|
void ensureSessionsLoaded()
|
|
434
439
|
})
|
|
435
440
|
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
void Taro.setNavigationBarTitle({ title: t('system.ai.chatTitle') })
|
|
443
|
+
setStarterShortcuts(createDefaultShortcuts())
|
|
444
|
+
setStarterPromptGroupIndex(0)
|
|
445
|
+
}, [locale])
|
|
446
|
+
|
|
436
447
|
useEffect(() => {
|
|
437
448
|
const tasks = runningStreamTaskMap.current
|
|
438
449
|
const deltaMap = pendingDeltaMap.current
|
|
@@ -468,16 +479,18 @@ export default function AiPage() {
|
|
|
468
479
|
setSessionKeyword('')
|
|
469
480
|
setShowSessionDrawer(false)
|
|
470
481
|
} catch (error) {
|
|
471
|
-
showError(error, '
|
|
482
|
+
showError(error, t('system.ai.createSessionFailed'))
|
|
472
483
|
}
|
|
473
484
|
}
|
|
474
485
|
|
|
475
486
|
const deleteSession = async (sessionID: string) => {
|
|
476
487
|
const session = sessionsRef.current.find((item) => item.id === sessionID)
|
|
477
488
|
const result = await Taro.showModal({
|
|
478
|
-
title: '
|
|
479
|
-
content:
|
|
480
|
-
|
|
489
|
+
title: t('system.ai.deleteSession'),
|
|
490
|
+
content: t('system.ai.deleteSessionConfirm', {
|
|
491
|
+
title: session?.title || t('system.ai.currentSession'),
|
|
492
|
+
}),
|
|
493
|
+
confirmText: t('common.action.delete'),
|
|
481
494
|
confirmColor: '#cf4444',
|
|
482
495
|
})
|
|
483
496
|
if (!result.confirm) return
|
|
@@ -492,13 +505,13 @@ export default function AiPage() {
|
|
|
492
505
|
await ensureActiveSession()
|
|
493
506
|
}
|
|
494
507
|
} catch (error) {
|
|
495
|
-
showError(error, '
|
|
508
|
+
showError(error, t('system.ai.deleteSessionFailed'))
|
|
496
509
|
}
|
|
497
510
|
}
|
|
498
511
|
|
|
499
512
|
const handleSessionAction = async (session: AiSession) => {
|
|
500
513
|
try {
|
|
501
|
-
const result = await Taro.showActionSheet({ itemList: ['
|
|
514
|
+
const result = await Taro.showActionSheet({ itemList: [t('system.ai.deleteSession')] })
|
|
502
515
|
if (result.tapIndex === 0) await deleteSession(session.id)
|
|
503
516
|
} catch {
|
|
504
517
|
// 用户取消操作无需提示。
|
|
@@ -507,7 +520,7 @@ export default function AiPage() {
|
|
|
507
520
|
|
|
508
521
|
const copyMessage = async (item: ChatMessageItem) => {
|
|
509
522
|
await Taro.setClipboardData({ data: item.content })
|
|
510
|
-
await Taro.showToast({ icon: 'none', title: '
|
|
523
|
+
await Taro.showToast({ icon: 'none', title: t('system.ai.copySuccess') })
|
|
511
524
|
}
|
|
512
525
|
|
|
513
526
|
const deleteMessage = async (item: ChatMessageItem) => {
|
|
@@ -519,7 +532,7 @@ export default function AiPage() {
|
|
|
519
532
|
}
|
|
520
533
|
updateSessionMessages(sessionID, (current) => current.filter((message) => message.messageID !== item.messageID))
|
|
521
534
|
} catch (error) {
|
|
522
|
-
showError(error, '
|
|
535
|
+
showError(error, t('system.ai.deleteMessageFailed'))
|
|
523
536
|
}
|
|
524
537
|
}
|
|
525
538
|
|
|
@@ -532,7 +545,7 @@ export default function AiPage() {
|
|
|
532
545
|
updateSessionMessages(sessionID, normalizeMessageList(response.messages))
|
|
533
546
|
if (response.session) upsertSession(normalizeSession(response.session))
|
|
534
547
|
} catch (error) {
|
|
535
|
-
showError(error, '
|
|
548
|
+
showError(error, t('system.ai.regenerateFailed'))
|
|
536
549
|
} finally {
|
|
537
550
|
setSessionSending(sessionID, false)
|
|
538
551
|
}
|
|
@@ -540,7 +553,9 @@ export default function AiPage() {
|
|
|
540
553
|
|
|
541
554
|
const handleMessageAction = async (item: ChatMessageItem) => {
|
|
542
555
|
try {
|
|
543
|
-
const itemList = item.role === 'ai'
|
|
556
|
+
const itemList = item.role === 'ai'
|
|
557
|
+
? [t('system.ai.action.copy'), t('common.action.delete'), t('system.ai.action.regenerate')]
|
|
558
|
+
: [t('system.ai.action.copy'), t('common.action.delete')]
|
|
544
559
|
const { tapIndex } = await Taro.showActionSheet({ itemList })
|
|
545
560
|
if (tapIndex === 0) await copyMessage(item)
|
|
546
561
|
else if (tapIndex === 1) await deleteMessage(item)
|
|
@@ -560,7 +575,7 @@ export default function AiPage() {
|
|
|
560
575
|
|
|
561
576
|
const handleSend = async () => {
|
|
562
577
|
if (isSubmitDisabled) return
|
|
563
|
-
const text = inputText.trim() || '
|
|
578
|
+
const text = inputText.trim() || t('system.ai.attachmentAnswer')
|
|
564
579
|
const attachments = [...selectedAttachments]
|
|
565
580
|
setInputText('')
|
|
566
581
|
setSelectedAttachments([])
|
|
@@ -570,7 +585,10 @@ export default function AiPage() {
|
|
|
570
585
|
const handleAttachment = async () => {
|
|
571
586
|
if (uploadingAttachment || currentSessionSending) return
|
|
572
587
|
if (selectedAttachments.length >= MAX_ATTACHMENT_COUNT) {
|
|
573
|
-
await Taro.showToast({
|
|
588
|
+
await Taro.showToast({
|
|
589
|
+
icon: 'none',
|
|
590
|
+
title: t('system.ai.attachmentLimit', { count: MAX_ATTACHMENT_COUNT }),
|
|
591
|
+
})
|
|
574
592
|
return
|
|
575
593
|
}
|
|
576
594
|
try {
|
|
@@ -580,7 +598,9 @@ export default function AiPage() {
|
|
|
580
598
|
})
|
|
581
599
|
const files: AttachmentUpload[] = result.tempFilePaths.map((path, index) => ({
|
|
582
600
|
path,
|
|
583
|
-
name:
|
|
601
|
+
name:
|
|
602
|
+
result.tempFiles[index]?.originalFileObj?.name ||
|
|
603
|
+
t('system.ai.imageName', { index: index + 1 }),
|
|
584
604
|
size: Number(result.tempFiles[index]?.size || 0),
|
|
585
605
|
}))
|
|
586
606
|
setUploadingAttachment(true)
|
|
@@ -595,7 +615,7 @@ export default function AiPage() {
|
|
|
595
615
|
setSelectedAttachments((current) => [...current, ...attachments].slice(0, MAX_ATTACHMENT_COUNT))
|
|
596
616
|
} catch (error) {
|
|
597
617
|
const message = error instanceof Error ? error.message : String(error)
|
|
598
|
-
if (!/cancel/i.test(message)) showError(error, '
|
|
618
|
+
if (!/cancel/i.test(message)) showError(error, t('system.ai.uploadFailed'))
|
|
599
619
|
} finally {
|
|
600
620
|
setUploadingAttachment(false)
|
|
601
621
|
}
|
|
@@ -613,7 +633,7 @@ export default function AiPage() {
|
|
|
613
633
|
<Button className='nav-back-button' hoverClass='none' onClick={navigateBack}>
|
|
614
634
|
<UniIcon type='left' size={24} color='#111' />
|
|
615
635
|
</Button>
|
|
616
|
-
<View className='ai-navbar__title'>
|
|
636
|
+
<View className='ai-navbar__title'>{t('system.ai.chatTitle')}</View>
|
|
617
637
|
<Button className='nav-menu-button' hoverClass='none' onClick={() => setShowSessionDrawer((open) => !open)}>
|
|
618
638
|
<UniIcon type='bars' size={24} color='#111' />
|
|
619
639
|
</Button>
|
|
@@ -651,7 +671,7 @@ export default function AiPage() {
|
|
|
651
671
|
onLongPress={() => void handleMessageAction(item)}
|
|
652
672
|
>
|
|
653
673
|
{item.role === 'ai' && item.model ? (
|
|
654
|
-
<View className='reply-meta'><Text className='reply-tag'
|
|
674
|
+
<View className='reply-meta'><Text className='reply-tag'>{t('system.ai.modelReply')}</Text><Text className='reply-model'>{item.model}</Text></View>
|
|
655
675
|
) : null}
|
|
656
676
|
<View className='bubble-content'>{item.content}</View>
|
|
657
677
|
{item.attachments.length ? (
|
|
@@ -662,7 +682,7 @@ export default function AiPage() {
|
|
|
662
682
|
className='attachment-card'
|
|
663
683
|
onClick={() => previewAttachment(attachment, item.attachments)}
|
|
664
684
|
>
|
|
665
|
-
<View className='attachment-icon'>{isImageAttachment(attachment) ? '
|
|
685
|
+
<View className='attachment-icon'>{isImageAttachment(attachment) ? t('system.ai.imageAttachment') : t('system.ai.attachment')}</View>
|
|
666
686
|
<View className='attachment-info'>
|
|
667
687
|
<View className='attachment-name'>{attachment.name}</View>
|
|
668
688
|
<View className='attachment-meta'>{formatAttachmentMeta(attachment)}</View>
|
|
@@ -671,14 +691,14 @@ export default function AiPage() {
|
|
|
671
691
|
))}
|
|
672
692
|
</View>
|
|
673
693
|
) : null}
|
|
674
|
-
{item.tools.length ? <View className='tool-row'
|
|
694
|
+
{item.tools.length ? <View className='tool-row'>{t('system.ai.toolCalled', { tools: formatTools(item.tools) })}</View> : null}
|
|
675
695
|
</View>
|
|
676
696
|
</View>
|
|
677
697
|
))}
|
|
678
698
|
<View id='chat-bottom' className='chat-bottom' />
|
|
679
699
|
</View>
|
|
680
700
|
)}
|
|
681
|
-
{loadingSessionID ? <View className='loading-session'
|
|
701
|
+
{loadingSessionID ? <View className='loading-session'>{t('system.ai.loadMessages')}</View> : null}
|
|
682
702
|
</ScrollView>
|
|
683
703
|
<Composer
|
|
684
704
|
value={inputText}
|
|
@@ -692,7 +712,10 @@ export default function AiPage() {
|
|
|
692
712
|
onAttach={() => void handleAttachment()}
|
|
693
713
|
onRecord={() => {
|
|
694
714
|
setIsRecording((recording) => {
|
|
695
|
-
void Taro.showToast({
|
|
715
|
+
void Taro.showToast({
|
|
716
|
+
icon: 'none',
|
|
717
|
+
title: !recording ? t('system.ai.recognizing') : t('system.ai.speechStopped'),
|
|
718
|
+
})
|
|
696
719
|
return !recording
|
|
697
720
|
})
|
|
698
721
|
}}
|
|
@@ -719,7 +742,7 @@ export default function AiPage() {
|
|
|
719
742
|
function normalizeSession(session?: Partial<AiSession> | null): AiSession {
|
|
720
743
|
return {
|
|
721
744
|
id: String(session?.id ?? ''),
|
|
722
|
-
title: String(session?.title ?? '
|
|
745
|
+
title: String(session?.title ?? t('system.ai.newSession')),
|
|
723
746
|
summary: String(session?.summary ?? ''),
|
|
724
747
|
updated_at: session?.updated_at,
|
|
725
748
|
terminal: Number(session?.terminal ?? AI_TERMINAL),
|
|
@@ -821,7 +844,7 @@ function createThinkingMessage(options?: { sessionID?: string; messageID?: strin
|
|
|
821
844
|
id: streamKey || `ai-thinking-${now}`,
|
|
822
845
|
input_content: undefined,
|
|
823
846
|
output_content: {
|
|
824
|
-
kind: 'text', content:
|
|
847
|
+
kind: 'text', content: t('system.ai.thinking'), reply_source: '', model: '', fallback: false,
|
|
825
848
|
fallback_reason: '', flow: '', step: '', blocks_json: '',
|
|
826
849
|
},
|
|
827
850
|
attachments: [],
|
|
@@ -859,7 +882,7 @@ function appendStreamingDelta(current: ChatMessageItem[], payload: AiStreamPaylo
|
|
|
859
882
|
const streamKey = buildStreamMessageKey(payload.session_id, payload.message_id)
|
|
860
883
|
return current.map((item) => {
|
|
861
884
|
if (item.streamKey !== streamKey || item.role === 'user') return item
|
|
862
|
-
const baseContent = item.content ===
|
|
885
|
+
const baseContent = item.content === t('system.ai.thinking') ? '' : item.content
|
|
863
886
|
return { ...item, content: `${baseContent}${payload.delta}`, status: AiMessageStatus.GENERATING_AMS }
|
|
864
887
|
})
|
|
865
888
|
}
|
|
@@ -870,7 +893,7 @@ function markThinkingMessageFailed(current: ChatMessageItem[]) {
|
|
|
870
893
|
? {
|
|
871
894
|
...item,
|
|
872
895
|
status: AiMessageStatus.FAILED_AMS,
|
|
873
|
-
content: item.role === 'ai' ? '
|
|
896
|
+
content: item.role === 'ai' ? t('system.ai.failedResponse') : item.content,
|
|
874
897
|
}
|
|
875
898
|
: item,
|
|
876
899
|
)
|
|
@@ -880,7 +903,7 @@ function markStreamingError(current: ChatMessageItem[], payload: AiStreamPayload
|
|
|
880
903
|
const streamKey = buildStreamMessageKey(payload.session_id, payload.message_id)
|
|
881
904
|
return current.map((item) =>
|
|
882
905
|
item.localOnly && item.streamKey === streamKey
|
|
883
|
-
? { ...item, status: AiMessageStatus.FAILED_AMS, content: '
|
|
906
|
+
? { ...item, status: AiMessageStatus.FAILED_AMS, content: t('system.ai.failedResponse') }
|
|
884
907
|
: item,
|
|
885
908
|
)
|
|
886
909
|
}
|
|
@@ -908,7 +931,9 @@ function normalizeNonStreamMessages(response: unknown) {
|
|
|
908
931
|
const events = parseAiEventStreamText(response)
|
|
909
932
|
const finishEvent = [...events].reverse().find((item) => item.event === 'finish')
|
|
910
933
|
if (finishEvent) return normalizeMessageList(finishEvent.payload.messages)
|
|
911
|
-
if ([...events].reverse().some((item) => item.event === 'error'))
|
|
934
|
+
if ([...events].reverse().some((item) => item.event === 'error')) {
|
|
935
|
+
throw new Error(t('system.ai.requestFailed'))
|
|
936
|
+
}
|
|
912
937
|
return []
|
|
913
938
|
}
|
|
914
939
|
|
|
@@ -917,7 +942,7 @@ function sortMessages(list: ChatMessageItem[]) {
|
|
|
917
942
|
const difference = resolveTimestamp(left.created_at) - resolveTimestamp(right.created_at)
|
|
918
943
|
if (difference) return difference
|
|
919
944
|
if (left.role !== right.role) return left.role === 'user' ? -1 : 1
|
|
920
|
-
return left.messageID.localeCompare(right.messageID,
|
|
945
|
+
return left.messageID.localeCompare(right.messageID, getCurrentLocale(), { numeric: true })
|
|
921
946
|
})
|
|
922
947
|
}
|
|
923
948
|
|
|
@@ -938,7 +963,9 @@ function isImageAttachment(attachment: AiAttachment) {
|
|
|
938
963
|
}
|
|
939
964
|
|
|
940
965
|
function formatAttachmentMeta(attachment: AiAttachment) {
|
|
941
|
-
return attachment.size
|
|
966
|
+
return attachment.size
|
|
967
|
+
? `${Math.max(1, Math.round(attachment.size / 1024))} KB`
|
|
968
|
+
: t('system.ai.attachment')
|
|
942
969
|
}
|
|
943
970
|
|
|
944
971
|
function formatTools(tools: AiToolCall[]) {
|
|
@@ -949,3 +976,44 @@ function showError(error: unknown, fallback: string) {
|
|
|
949
976
|
const message = error instanceof Error ? error.message : fallback
|
|
950
977
|
void Taro.showToast({ icon: 'none', title: message || fallback })
|
|
951
978
|
}
|
|
979
|
+
|
|
980
|
+
function createDefaultShortcuts(): AiShortcut[] {
|
|
981
|
+
return [
|
|
982
|
+
{
|
|
983
|
+
key: 'summarize',
|
|
984
|
+
title: t('system.ai.prompt.summary'),
|
|
985
|
+
prompt: t('system.ai.prompt.summaryContent'),
|
|
986
|
+
action: undefined,
|
|
987
|
+
required_tools: [],
|
|
988
|
+
sort: 1,
|
|
989
|
+
group: t('system.ai.textAssistant'),
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
key: 'rewrite',
|
|
993
|
+
title: t('system.ai.prompt.optimize'),
|
|
994
|
+
prompt: t('system.ai.prompt.optimizeContent'),
|
|
995
|
+
action: undefined,
|
|
996
|
+
required_tools: [],
|
|
997
|
+
sort: 2,
|
|
998
|
+
group: t('system.ai.textAssistant'),
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
key: 'plan',
|
|
1002
|
+
title: t('system.ai.prompt.plan'),
|
|
1003
|
+
prompt: t('system.ai.prompt.planContent'),
|
|
1004
|
+
action: undefined,
|
|
1005
|
+
required_tools: [],
|
|
1006
|
+
sort: 3,
|
|
1007
|
+
group: t('system.ai.efficiencyAssistant'),
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
key: 'ideas',
|
|
1011
|
+
title: t('system.ai.prompt.idea'),
|
|
1012
|
+
prompt: t('system.ai.prompt.ideaContent'),
|
|
1013
|
+
action: undefined,
|
|
1014
|
+
required_tools: [],
|
|
1015
|
+
sort: 4,
|
|
1016
|
+
group: t('system.ai.efficiencyAssistant'),
|
|
1017
|
+
},
|
|
1018
|
+
]
|
|
1019
|
+
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
View,
|
|
10
10
|
} from '@tarojs/components'
|
|
11
11
|
import Taro, { useLoad } from '@tarojs/taro'
|
|
12
|
-
import { useState } from 'react'
|
|
12
|
+
import { useEffect, useState } from 'react'
|
|
13
13
|
import defaultAvatar from '@liujitcn/kratos-taro-app-core/static/images/avatar.png'
|
|
14
14
|
import navigatorBackground from '@liujitcn/kratos-taro-app-core/static/images/navigator_bg.png'
|
|
15
15
|
import { defAuthService } from '@liujitcn/kratos-taro-app-core/api/system/auth'
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
formatSrc,
|
|
21
21
|
navigateToLogin,
|
|
22
22
|
uploadFile,
|
|
23
|
+
useI18n,
|
|
23
24
|
useUserStore,
|
|
24
25
|
} from '@liujitcn/kratos-taro-app-core'
|
|
25
26
|
import './profile.scss'
|
|
@@ -28,6 +29,7 @@ const IMG_MAX_SIZE = 1024 * 1024
|
|
|
28
29
|
|
|
29
30
|
/** 用户个人资料编辑页。 */
|
|
30
31
|
export default function ProfilePage() {
|
|
32
|
+
const { locale, t } = useI18n()
|
|
31
33
|
const [userInfo, setUserInfo] = useState<UserProfileForm>({
|
|
32
34
|
user_name: '',
|
|
33
35
|
nick_name: '',
|
|
@@ -39,6 +41,10 @@ export default function ProfilePage() {
|
|
|
39
41
|
const ensureAuthenticated = useUserStore((state) => state.ensureAuthenticated)
|
|
40
42
|
const safeTop = Taro.getWindowInfo().safeArea?.top || 0
|
|
41
43
|
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
void Taro.setNavigationBarTitle({ title: t('system.profile.title') })
|
|
46
|
+
}, [locale, t])
|
|
47
|
+
|
|
42
48
|
const requireAuth = () => {
|
|
43
49
|
if (ensureAuthenticated()) return true
|
|
44
50
|
navigateToLogin()
|
|
@@ -66,9 +72,9 @@ export default function ProfilePage() {
|
|
|
66
72
|
await defAuthService.UpdateUserProfile(nextProfile)
|
|
67
73
|
setUserInfo(nextProfile)
|
|
68
74
|
await useUserStore.getState().getUserProfile()
|
|
69
|
-
await Taro.showToast({ icon: 'success', title: '
|
|
75
|
+
await Taro.showToast({ icon: 'success', title: t('system.profile.updateSuccess') })
|
|
70
76
|
} catch {
|
|
71
|
-
await Taro.showToast({ icon: 'error', title: '
|
|
77
|
+
await Taro.showToast({ icon: 'error', title: t('system.profile.avatarUploadFailed') })
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
80
|
|
|
@@ -81,7 +87,7 @@ export default function ProfilePage() {
|
|
|
81
87
|
const file = selected.tempFiles[0]
|
|
82
88
|
const filePath = 'tempFilePath' in file ? file.tempFilePath : file.path
|
|
83
89
|
if (file.size > IMG_MAX_SIZE) {
|
|
84
|
-
await Taro.showToast({ title: '
|
|
90
|
+
await Taro.showToast({ title: t('system.profile.photoLimit'), icon: 'none', duration: 1500 })
|
|
85
91
|
return
|
|
86
92
|
}
|
|
87
93
|
await uploadAvatar(filePath)
|
|
@@ -92,14 +98,14 @@ export default function ProfilePage() {
|
|
|
92
98
|
const response = await defAuthService.BindUserPhone({ code: event.detail.code || '' })
|
|
93
99
|
setUserInfo((current) => ({ ...current, phone: response.phone }))
|
|
94
100
|
await useUserStore.getState().getUserProfile()
|
|
95
|
-
await Taro.showToast({ icon: 'success', title: '
|
|
101
|
+
await Taro.showToast({ icon: 'success', title: t('system.profile.phoneAuthorizationSuccess') })
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
const onSubmit = async () => {
|
|
99
105
|
if (!requireAuth()) return
|
|
100
106
|
await defAuthService.UpdateUserProfile(userInfo)
|
|
101
107
|
await useUserStore.getState().getUserProfile()
|
|
102
|
-
await Taro.showToast({ icon: 'success', title: '
|
|
108
|
+
await Taro.showToast({ icon: 'success', title: t('system.profile.saveSuccess') })
|
|
103
109
|
setTimeout(() => void Taro.navigateBack(), 400)
|
|
104
110
|
}
|
|
105
111
|
|
|
@@ -107,40 +113,40 @@ export default function ProfilePage() {
|
|
|
107
113
|
<View className='profile-viewport' style={{ backgroundImage: `url(${navigatorBackground})` }}>
|
|
108
114
|
<View className='profile-navbar' style={{ paddingTop: `${safeTop}px` }}>
|
|
109
115
|
<View className='profile-back' onClick={() => void Taro.navigateBack()}>‹</View>
|
|
110
|
-
<View className='profile-navbar__title'
|
|
116
|
+
<View className='profile-navbar__title'>{t('system.profile.title')}</View>
|
|
111
117
|
</View>
|
|
112
118
|
<View className='profile-avatar'>
|
|
113
119
|
<View className='profile-avatar__content' onClick={() => void onAvatarChange()}>
|
|
114
120
|
<Image className='profile-avatar__image' src={userInfo.avatar ? formatSrc(userInfo.avatar) : defaultAvatar} mode='aspectFill' />
|
|
115
|
-
<Text className='profile-avatar__text'
|
|
121
|
+
<Text className='profile-avatar__text'>{t('system.profile.avatarChange')}</Text>
|
|
116
122
|
</View>
|
|
117
123
|
</View>
|
|
118
124
|
<View className='profile-form'>
|
|
119
125
|
<View className='profile-form__content'>
|
|
120
126
|
{userInfo.user_name ? (
|
|
121
127
|
<View className='profile-form__item'>
|
|
122
|
-
<Text className='profile-label'
|
|
128
|
+
<Text className='profile-label'>{t('system.profile.account')}</Text>
|
|
123
129
|
<Text className='profile-account profile-placeholder'>{userInfo.user_name}</Text>
|
|
124
130
|
</View>
|
|
125
131
|
) : null}
|
|
126
132
|
{process.env.TARO_ENV === 'weapp' ? (
|
|
127
133
|
<View className='profile-form__item'>
|
|
128
|
-
<Text className='profile-label'
|
|
134
|
+
<Text className='profile-label'>{t('system.profile.mobile')}</Text>
|
|
129
135
|
<View className='profile-input'>
|
|
130
136
|
{userInfo.phone ? (
|
|
131
137
|
<Text className='profile-account'>{userInfo.phone}</Text>
|
|
132
138
|
) : (
|
|
133
|
-
<Button className='profile-auth-button' openType='getPhoneNumber' onGetPhoneNumber={onGetPhoneNumber}
|
|
139
|
+
<Button className='profile-auth-button' openType='getPhoneNumber' onGetPhoneNumber={onGetPhoneNumber}>{t('system.profile.phoneAuthorization')}</Button>
|
|
134
140
|
)}
|
|
135
141
|
</View>
|
|
136
142
|
</View>
|
|
137
143
|
) : null}
|
|
138
144
|
<View className='profile-form__item'>
|
|
139
|
-
<Text className='profile-label'
|
|
140
|
-
<Input className='profile-input' placeholder='
|
|
145
|
+
<Text className='profile-label'>{t('system.profile.nickName')}</Text>
|
|
146
|
+
<Input className='profile-input' placeholder={t('system.profile.nickNamePlaceholder')} value={userInfo.nick_name} onInput={(event) => setUserInfo((current) => ({ ...current, nick_name: event.detail.value }))} />
|
|
141
147
|
</View>
|
|
142
148
|
<View className='profile-form__item'>
|
|
143
|
-
<Text className='profile-label'
|
|
149
|
+
<Text className='profile-label'>{t('system.profile.gender')}</Text>
|
|
144
150
|
<RadioGroup onChange={(event) => setUserInfo((current) => ({ ...current, gender: Number(event.detail.value) }))}>
|
|
145
151
|
{genderList.map((item) => (
|
|
146
152
|
<Label className='profile-radio' key={item.value}>
|
|
@@ -151,7 +157,7 @@ export default function ProfilePage() {
|
|
|
151
157
|
</RadioGroup>
|
|
152
158
|
</View>
|
|
153
159
|
</View>
|
|
154
|
-
<Button className='profile-form__button' onClick={() => void onSubmit()}
|
|
160
|
+
<Button className='profile-form__button' onClick={() => void onSubmit()}>{t('common.action.save')}</Button>
|
|
155
161
|
</View>
|
|
156
162
|
</View>
|
|
157
163
|
)
|
|
@@ -49,6 +49,16 @@
|
|
|
49
49
|
text-align: center;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
.settings-locale {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
padding-right: 42px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.settings-locale__value {
|
|
59
|
+
color: #888;
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
.settings-button {
|
|
53
63
|
margin-bottom: 20px;
|
|
54
64
|
border-radius: 10px;
|