@liujitcn/kratos-uni-app-system 0.0.59 → 0.0.61
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/index.mjs +15 -5
- package/package.json +2 -2
- package/src/api/base/v1/ai_message.ts +3 -2
- package/src/index.ts +1 -1
- package/src/notification.ts +13 -4
- package/src/views/pagesMember/ai/index.vue +37 -8
- package/src/views/pagesMember/message/detail.vue +1 -1
- package/src/views/pagesMember/message/index.vue +7 -1
- package/src/views/pagesMember/profile/profile.vue +35 -20
package/dist/index.mjs
CHANGED
|
@@ -3,9 +3,14 @@ import { registerUserStoreExtension } from "@liujitcn/kratos-uni-app-core/stores
|
|
|
3
3
|
|
|
4
4
|
// src/notification.ts
|
|
5
5
|
import { ref } from "vue";
|
|
6
|
+
import { getLocaleRequestHeaders } from "@liujitcn/kratos-uni-app-core";
|
|
6
7
|
import { setAppMenuBadge } from "@liujitcn/kratos-uni-app-core/navigation";
|
|
7
8
|
import { hasValidToken } from "@liujitcn/kratos-uni-app-core/utils/auth";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getRequestAccessToken,
|
|
11
|
+
siteBaseURL,
|
|
12
|
+
sourceClient
|
|
13
|
+
} from "@liujitcn/kratos-uni-app-core/utils/http";
|
|
9
14
|
|
|
10
15
|
// src/api/base/v1/notification.ts
|
|
11
16
|
import { http } from "@liujitcn/kratos-uni-app-core/utils/http";
|
|
@@ -172,9 +177,13 @@ function startNotificationSse() {
|
|
|
172
177
|
const token = await getRequestAccessToken("optional");
|
|
173
178
|
if (!token || controller.signal.aborted)
|
|
174
179
|
return;
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const response = await fetch(`${siteBaseURL}/events/base.notification`, {
|
|
181
|
+
headers: {
|
|
182
|
+
Accept: "text/event-stream",
|
|
183
|
+
Authorization: token,
|
|
184
|
+
"source-client": sourceClient,
|
|
185
|
+
...getLocaleRequestHeaders()
|
|
186
|
+
},
|
|
178
187
|
signal: controller.signal
|
|
179
188
|
});
|
|
180
189
|
if (response.status === 401 || response.status === 403)
|
|
@@ -730,5 +739,6 @@ var src_default = systemModule;
|
|
|
730
739
|
export {
|
|
731
740
|
src_default as default,
|
|
732
741
|
pauseNotificationPolling,
|
|
733
|
-
resumeNotificationPolling
|
|
742
|
+
resumeNotificationPolling,
|
|
743
|
+
startNotificationPolling
|
|
734
744
|
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git@github.com:liujitcn/kratos-admin.git"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.0.
|
|
10
|
+
"version": "0.0.61",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@dcloudio/uni-app": "3.0.0-5010520260709002",
|
|
36
36
|
"@dcloudio/uni-ui": "^1.4.28",
|
|
37
37
|
"vue": "^3.4.21",
|
|
38
|
-
"@liujitcn/kratos-uni-app-core": "^0.0.
|
|
38
|
+
"@liujitcn/kratos-uni-app-core": "^0.0.61"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"esbuild": "^0.20.2",
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
handleAuthExpired,
|
|
4
4
|
http,
|
|
5
5
|
requestBaseURL,
|
|
6
|
+
sourceClient,
|
|
6
7
|
} from '@liujitcn/kratos-uni-app-core/utils/http'
|
|
7
8
|
import type { ListAiMessageRequest, ListAiMessageResponse } from '../../../rpc/base/v1/ai_session'
|
|
8
9
|
import type {
|
|
@@ -74,7 +75,7 @@ export async function SendAiMessageStream(
|
|
|
74
75
|
const headers: Record<string, string> = {
|
|
75
76
|
Accept: 'text/event-stream',
|
|
76
77
|
'Content-Type': 'application/json;charset=utf-8',
|
|
77
|
-
'source-client':
|
|
78
|
+
'source-client': sourceClient,
|
|
78
79
|
...getLocaleRequestHeaders(),
|
|
79
80
|
}
|
|
80
81
|
if (accessToken) {
|
|
@@ -128,7 +129,7 @@ export function StreamAiMessageByChunkedRequest(
|
|
|
128
129
|
header: {
|
|
129
130
|
Accept: 'text/event-stream',
|
|
130
131
|
'Content-Type': 'application/json;charset=utf-8',
|
|
131
|
-
'source-client':
|
|
132
|
+
'source-client': sourceClient,
|
|
132
133
|
...getLocaleRequestHeaders(),
|
|
133
134
|
...(accessToken ? { Authorization: accessToken } : {}),
|
|
134
135
|
},
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,6 @@ registerUserStoreExtension({
|
|
|
14
14
|
onSilentLogout: stopNotificationPolling,
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
export { pauseNotificationPolling, resumeNotificationPolling }
|
|
17
|
+
export { pauseNotificationPolling, resumeNotificationPolling, startNotificationPolling }
|
|
18
18
|
|
|
19
19
|
export default systemModule
|
package/src/notification.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
|
+
import { getLocaleRequestHeaders } from '@liujitcn/kratos-uni-app-core'
|
|
2
3
|
import { setAppMenuBadge } from '@liujitcn/kratos-uni-app-core/navigation'
|
|
3
4
|
import { hasValidToken } from '@liujitcn/kratos-uni-app-core/utils/auth'
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getRequestAccessToken,
|
|
7
|
+
siteBaseURL,
|
|
8
|
+
sourceClient,
|
|
9
|
+
} from '@liujitcn/kratos-uni-app-core/utils/http'
|
|
5
10
|
import { defNotificationService } from './api/base/v1/notification'
|
|
6
11
|
|
|
7
12
|
/** System 模块共享的站内信未读数。 */
|
|
@@ -70,9 +75,13 @@ function startNotificationSse(): () => void {
|
|
|
70
75
|
try {
|
|
71
76
|
const token = await getRequestAccessToken('optional')
|
|
72
77
|
if (!token || controller.signal.aborted) return
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
const response = await fetch(`${siteBaseURL}/events/base.notification`, {
|
|
79
|
+
headers: {
|
|
80
|
+
Accept: 'text/event-stream',
|
|
81
|
+
Authorization: token,
|
|
82
|
+
'source-client': sourceClient,
|
|
83
|
+
...getLocaleRequestHeaders(),
|
|
84
|
+
},
|
|
76
85
|
signal: controller.signal,
|
|
77
86
|
})
|
|
78
87
|
if (response.status === 401 || response.status === 403) return
|
|
@@ -484,6 +484,8 @@ async function sendAiPayload(payload: { text: string; attachments: AiAttachment[
|
|
|
484
484
|
if (!sessionID || isSessionSending(sessionID)) {
|
|
485
485
|
return false
|
|
486
486
|
}
|
|
487
|
+
// 发送前等待进行中的历史加载结束,避免本地消息与历史加载竞态。
|
|
488
|
+
await waitForMessagesLoaded(sessionID)
|
|
487
489
|
|
|
488
490
|
const localUserMessage = createLocalUserMessage(payload)
|
|
489
491
|
const thinkingMessage = createThinkingMessage({ sessionID })
|
|
@@ -798,10 +800,7 @@ async function ensureSessionsLoaded() {
|
|
|
798
800
|
terminal: AI_TERMINAL,
|
|
799
801
|
})
|
|
800
802
|
sessions.value = normalizeSessionList(response.sessions)
|
|
801
|
-
|
|
802
|
-
if (sessionID) {
|
|
803
|
-
await loadMessages(sessionID)
|
|
804
|
-
}
|
|
803
|
+
await ensureActiveSession()
|
|
805
804
|
} catch (error) {
|
|
806
805
|
showError(error, t('system.ai.load_sessions_failed'))
|
|
807
806
|
} finally {
|
|
@@ -814,8 +813,12 @@ async function ensureActiveSession() {
|
|
|
814
813
|
return activeSessionID.value
|
|
815
814
|
}
|
|
816
815
|
if (sessions.value.length > 0) {
|
|
817
|
-
|
|
818
|
-
|
|
816
|
+
const sessionID = sessions.value[0].id
|
|
817
|
+
activeSessionID.value = sessionID
|
|
818
|
+
if (!messages.value[sessionID]?.length) {
|
|
819
|
+
await loadMessages(sessionID)
|
|
820
|
+
}
|
|
821
|
+
return sessionID
|
|
819
822
|
}
|
|
820
823
|
const sessionID = await createRemoteSession()
|
|
821
824
|
if (sessionID) {
|
|
@@ -849,13 +852,14 @@ async function loadMessages(sessionID: string) {
|
|
|
849
852
|
if (loadingSessionID.value !== sessionID) {
|
|
850
853
|
return
|
|
851
854
|
}
|
|
852
|
-
|
|
855
|
+
const loaded = normalizeMessageList(response.messages)
|
|
856
|
+
messages.value[sessionID] = mergeWithLocalOnlyStreaming(messages.value[sessionID] ?? [], loaded)
|
|
853
857
|
if (activeSessionID.value === sessionID) {
|
|
854
858
|
scrollChatToBottom()
|
|
855
859
|
}
|
|
856
860
|
} catch (error) {
|
|
857
861
|
if (loadingSessionID.value === sessionID) {
|
|
858
|
-
messages.value[sessionID] = []
|
|
862
|
+
messages.value[sessionID] = mergeWithLocalOnlyStreaming(messages.value[sessionID] ?? [], [])
|
|
859
863
|
}
|
|
860
864
|
showError(error, t('system.ai.load_messages_failed'))
|
|
861
865
|
} finally {
|
|
@@ -865,6 +869,31 @@ async function loadMessages(sessionID: string) {
|
|
|
865
869
|
}
|
|
866
870
|
}
|
|
867
871
|
|
|
872
|
+
/** 合并历史加载结果与仍在流式中的本地消息,避免加载覆盖进行中的对话。 */
|
|
873
|
+
function mergeWithLocalOnlyStreaming(current: ChatMessageItem[], loaded: ChatMessageItem[]) {
|
|
874
|
+
const localStreaming = current.filter(
|
|
875
|
+
(item) => item.localOnly && item.status === AiMessageStatus.AI_MESSAGE_STATUS_GENERATING,
|
|
876
|
+
)
|
|
877
|
+
if (!localStreaming.length) {
|
|
878
|
+
return loaded
|
|
879
|
+
}
|
|
880
|
+
const messageMap = new Map<string, ChatMessageItem>()
|
|
881
|
+
for (const item of loaded) {
|
|
882
|
+
messageMap.set(item.key, item)
|
|
883
|
+
}
|
|
884
|
+
for (const item of localStreaming) {
|
|
885
|
+
messageMap.set(item.key, item)
|
|
886
|
+
}
|
|
887
|
+
return sortMessages(Array.from(messageMap.values()))
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/** 等待指定会话的历史加载完成,避免发送消息与加载竞态。 */
|
|
891
|
+
async function waitForMessagesLoaded(sessionID: string) {
|
|
892
|
+
while (loadingSessionID.value === sessionID) {
|
|
893
|
+
await new Promise((resolve) => setTimeout(resolve, 16))
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
868
897
|
function normalizeSession(session?: Partial<AiSession> | null): AiSession {
|
|
869
898
|
return {
|
|
870
899
|
id: String(session?.id ?? ''),
|
|
@@ -74,7 +74,7 @@ function openAction() {
|
|
|
74
74
|
{{ t('common.action.view') }}
|
|
75
75
|
</button>
|
|
76
76
|
</view>
|
|
77
|
-
<view v-else class="empty">{{ t('
|
|
77
|
+
<view v-else class="empty">{{ t('core.status.loading') }}</view>
|
|
78
78
|
</template>
|
|
79
79
|
|
|
80
80
|
<style scoped lang="scss">
|
|
@@ -15,6 +15,7 @@ const finished = ref(false)
|
|
|
15
15
|
const selectedView = ref(NotificationView.NOTIFICATION_VIEW_INBOX)
|
|
16
16
|
const categoryId = ref<number>()
|
|
17
17
|
const categoryOptions = ref<NotificationCategory[]>([])
|
|
18
|
+
let requestSeq = 0
|
|
18
19
|
const viewOptions = [
|
|
19
20
|
{ value: NotificationView.NOTIFICATION_VIEW_INBOX, key: 'system.notification.view.inbox' },
|
|
20
21
|
{ value: NotificationView.NOTIFICATION_VIEW_UNREAD, key: 'system.notification.view.unread' },
|
|
@@ -35,9 +36,12 @@ async function loadCategories() {
|
|
|
35
36
|
|
|
36
37
|
/** 刷新站内信列表。 */
|
|
37
38
|
async function refresh() {
|
|
39
|
+
requestSeq++
|
|
38
40
|
cursorId.value = 0
|
|
39
41
|
finished.value = false
|
|
40
42
|
items.value = []
|
|
43
|
+
// 先重置 loading,再发起新请求,避免清空列表后 loadMore 因旧 loading 直接返回。
|
|
44
|
+
loading.value = false
|
|
41
45
|
await loadMore()
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -81,6 +85,7 @@ async function deleteItem(item: Notification) {
|
|
|
81
85
|
/** 加载下一页站内信。 */
|
|
82
86
|
async function loadMore() {
|
|
83
87
|
if (loading.value || finished.value) return
|
|
88
|
+
const seq = requestSeq
|
|
84
89
|
loading.value = true
|
|
85
90
|
try {
|
|
86
91
|
const result = await defNotificationService.PageNotification({
|
|
@@ -91,11 +96,12 @@ async function loadMore() {
|
|
|
91
96
|
page_num: 1,
|
|
92
97
|
page_size: 20,
|
|
93
98
|
})
|
|
99
|
+
if (seq !== requestSeq) return
|
|
94
100
|
items.value.push(...result.notifications)
|
|
95
101
|
finished.value = !result.has_more
|
|
96
102
|
cursorId.value = result.next_cursor_id
|
|
97
103
|
} finally {
|
|
98
|
-
loading.value = false
|
|
104
|
+
if (seq === requestSeq) loading.value = false
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -3,7 +3,7 @@ import { defAuthService } from '@liujitcn/kratos-uni-app-core/api/system/app/v1/
|
|
|
3
3
|
import { useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
|
|
4
4
|
import type { UserProfileForm } from '@liujitcn/kratos-uni-app-core/rpc/system/app/v1/auth'
|
|
5
5
|
import { onLoad } from '@dcloudio/uni-app'
|
|
6
|
-
import { computed, ref } from 'vue'
|
|
6
|
+
import { computed, onBeforeUnmount, ref } from 'vue'
|
|
7
7
|
import type { BaseDictForm_DictItem } from '@liujitcn/kratos-uni-app-core/rpc/system/app/v1/base_dict'
|
|
8
8
|
import { BaseUserIDType } from '@liujitcn/kratos-uni-app-core/rpc/system/common/v1/common'
|
|
9
9
|
import { defBaseDictService } from '@liujitcn/kratos-uni-app-core/api/system/app/v1/base_dict'
|
|
@@ -21,6 +21,13 @@ const { t } = useI18n()
|
|
|
21
21
|
const { safeAreaInsets } = uni.getSystemInfoSync()
|
|
22
22
|
|
|
23
23
|
const imgMaxSize = ref(1024 * 1024)
|
|
24
|
+
const submitting = ref(false)
|
|
25
|
+
let navigateBackTimer: ReturnType<typeof setTimeout> | undefined
|
|
26
|
+
|
|
27
|
+
onBeforeUnmount(() => {
|
|
28
|
+
if (navigateBackTimer) clearTimeout(navigateBackTimer)
|
|
29
|
+
navigateBackTimer = undefined
|
|
30
|
+
})
|
|
24
31
|
|
|
25
32
|
// 获取个人信息,修改个人信息需提供初始值
|
|
26
33
|
const userInfo = ref({} as UserProfileForm)
|
|
@@ -196,6 +203,7 @@ const onGetPhoneNumber: UniHelper.ButtonOnGetphonenumber = async (e) => {
|
|
|
196
203
|
|
|
197
204
|
// 点击保存提交表单
|
|
198
205
|
const onSubmit = async () => {
|
|
206
|
+
if (submitting.value) return
|
|
199
207
|
if (!userStore.ensureAuthenticated()) {
|
|
200
208
|
navigateToLogin()
|
|
201
209
|
return
|
|
@@ -210,25 +218,32 @@ const onSubmit = async () => {
|
|
|
210
218
|
return
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
uni.
|
|
231
|
-
|
|
221
|
+
submitting.value = true
|
|
222
|
+
try {
|
|
223
|
+
const { nick_name, gender, email, id_type, id_code } = userInfo.value
|
|
224
|
+
await defAuthService.UpdateUserProfile({
|
|
225
|
+
user_profile: {
|
|
226
|
+
nick_name: nick_name,
|
|
227
|
+
gender: gender,
|
|
228
|
+
avatar: userInfo.value.avatar,
|
|
229
|
+
phone: userInfo.value.phone,
|
|
230
|
+
user_name: userInfo.value.user_name,
|
|
231
|
+
email,
|
|
232
|
+
id_type,
|
|
233
|
+
id_code,
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
// 更新Store昵称
|
|
237
|
+
syncUserStoreProfile(userInfo.value)
|
|
238
|
+
await uni.showToast({ icon: 'success', title: t('system.profile.save_success') })
|
|
239
|
+
if (navigateBackTimer) clearTimeout(navigateBackTimer)
|
|
240
|
+
navigateBackTimer = setTimeout(() => {
|
|
241
|
+
navigateBackTimer = undefined
|
|
242
|
+
uni.navigateBack()
|
|
243
|
+
}, 400)
|
|
244
|
+
} finally {
|
|
245
|
+
submitting.value = false
|
|
246
|
+
}
|
|
232
247
|
}
|
|
233
248
|
</script>
|
|
234
249
|
|