Commit 6a91a552 authored by nick zheng's avatar nick zheng

feat: 移动端登录&&重置密码&&应用分享页UI调整

parent 774da38e
......@@ -4,7 +4,7 @@ import checker from 'vite-plugin-checker'
import { visualizer } from 'rollup-plugin-visualizer'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import { NaiveUiResolver, VantResolver } from 'unplugin-vue-components/resolvers'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import UnoCSS from 'unocss/vite'
import VueJsx from '@vitejs/plugin-vue-jsx'
......@@ -20,7 +20,7 @@ export function setupPlugins(isBuild: boolean, envConf: ViteEnv, pathResolve: (d
],
}),
Components({
resolvers: [NaiveUiResolver()],
resolvers: [NaiveUiResolver(), VantResolver()],
}),
VueI18nPlugin({
include: [pathResolve('./src/locales/langs/**')],
......
......@@ -44,6 +44,7 @@
"spark-md5": "^3.0.2",
"type-fest": "^4.26.1",
"validator": "^13.12.0",
"vant": "^4.9.18",
"vue": "^3.5.13",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.5"
......
......@@ -92,6 +92,9 @@ importers:
validator:
specifier: ^13.12.0
version: 13.12.0
vant:
specifier: ^4.9.18
version: 4.9.18(vue@3.5.13(typescript@5.6.2))
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.6.2)
......@@ -1249,6 +1252,14 @@ packages:
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0
'@vant/popperjs@1.3.0':
resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
'@vant/use@1.6.0':
resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
peerDependencies:
vue: ^3.0.0
'@vitejs/plugin-vue-jsx@4.0.1':
resolution: {integrity: sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==}
engines: {node: ^18.0.0 || >=20.0.0}
......@@ -3284,6 +3295,11 @@ packages:
resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==}
engines: {node: '>= 0.10'}
vant@4.9.18:
resolution: {integrity: sha512-1bmWv/G0xz45btPqSasgv4TUdTqCneyFfnrQJ3xgerGvfTC5aP/rpO4wJb5FItCZjaSw5+9FNBj2Tz6n9TLXYA==}
peerDependencies:
vue: ^3.0.0
vdirs@0.1.8:
resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
peerDependencies:
......@@ -4530,6 +4546,12 @@ snapshots:
- rollup
- supports-color
'@vant/popperjs@1.3.0': {}
'@vant/use@1.6.0(vue@3.5.13(typescript@5.6.2))':
dependencies:
vue: 3.5.13(typescript@5.6.2)
'@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)(terser@5.39.0))(vue@3.5.13(typescript@5.6.2))':
dependencies:
'@babel/core': 7.25.2
......@@ -6514,7 +6536,7 @@ snapshots:
estree-walker: 3.0.3
fast-glob: 3.3.2
local-pkg: 0.5.0
magic-string: 0.30.11
magic-string: 0.30.17
mlly: 1.7.1
pathe: 1.1.2
pkg-types: 1.2.0
......@@ -6603,7 +6625,7 @@ snapshots:
dependencies:
browserslist: 4.23.3
escalade: 3.2.0
picocolors: 1.1.0
picocolors: 1.1.1
uri-js@4.4.1:
dependencies:
......@@ -6613,6 +6635,13 @@ snapshots:
validator@13.12.0: {}
vant@4.9.18(vue@3.5.13(typescript@5.6.2)):
dependencies:
'@vant/popperjs': 1.3.0
'@vant/use': 1.6.0(vue@3.5.13(typescript@5.6.2))
'@vue/shared': 3.5.13
vue: 3.5.13(typescript@5.6.2)
vdirs@0.1.8(vue@3.5.13(typescript@5.6.2)):
dependencies:
evtd: 0.2.4
......
......@@ -42,3 +42,9 @@ export function fetchUserPasswordUpdate<T>(authCode: string, password: string) {
export function fetchGoogleClientId<T>() {
return request.post<T>('/googleConfigRest/getClientId.json')
}
export function fetchForgetMemberPassword<T>(account: string, authCode: string, password: string) {
return request.post<T>('/bizMemberInfoRest/forgetMemberPassword.json', null, {
params: { account, authCode, password },
})
}
......@@ -29,7 +29,7 @@ const { t } = useI18n()
const props = withDefaults(defineProps<Props>(), {
height: 240,
width: 500,
borderRadius: 6,
borderRadius: 10,
btnLoading: false,
btnDisabled: false,
cancelBtnText: '',
......
......@@ -43,6 +43,7 @@ common_module:
publish_success_message: 'Release success'
clear_success_message: 'Clear successfully'
add_success_message: 'New success'
reset_success_message: 'Reset successful'
loading: 'Loading'
updating: 'Uploading'
successful_update: 'Update successfully'
......@@ -152,6 +153,7 @@ common_module:
not_certified_yet: 'Not certified yet'
authenticated: 'Authenticated'
cancel_authorization: 'Cancel authorization'
get_code: 'Get Code'
dialogue_module:
continue_question_message: 'You can keep asking questions'
......@@ -211,6 +213,7 @@ router_title_module:
order_manage: 'Order Management'
data_statistic: 'Data statistic'
plugin_center: 'Plugin center'
reset_password: 'Reset password'
login_module:
app_welcome_words: 'Hi, welcome to Model Link'
......@@ -227,6 +230,25 @@ login_module:
successful: 'Obtain success'
get_verification_code: 'Get verification code'
other_login_methods: 'Other login methods'
mobile_welcome_words: 'Hello, welcome to Model Link!'
sign_in_with_mobile_number: 'Sign in with mobile number'
verification_code_login: 'Verification code login'
password_login: 'Password login'
email_login: 'Email login'
forgot_password: 'Forgot password?'
agreement_prefix: 'I have read and agree to the'
agreement_terms: 'User Agreement'
agreement_privacy: 'Privacy Policy'
agreement_and: 'and'
confirm_to_logout: 'Confirm to logout?'
you_can_login_again_after_logout: 'You can login again after logout'
reset_password_module:
reset_login_password: 'Reset login password'
please_enter_your_registered_phone_number: "Please enter your registered phone number. We'll reset your password for you"
please_enter_your_new_password: 'Please enter your new password'
back_to_login: 'Back to login'
reset_password: 'Reset Password'
home_module:
agent_welcome_message: 'Hi, welcome to Model Link'
......
......@@ -43,6 +43,7 @@ common_module:
publish_success_message: '发布成功'
clear_success_message: '清空成功'
add_success_message: '新增成功'
reset_success_message: '重置成功'
loading: '加载中'
updating: '更新中'
successful_update: '更新成功'
......@@ -151,6 +152,8 @@ common_module:
not_certified_yet: '未认证'
authenticated: '已认证'
cancel_authorization: '取消授权'
get_code: '获取验证码'
dialogue_module:
continue_question_message: '你可以继续提问'
......@@ -210,6 +213,7 @@ router_title_module:
order_manage: '订单管理'
data_statistic: '数据统计'
plugin_center: '插件中心'
reset_password: '重置密码'
login_module:
app_welcome_words: 'Hi, 欢迎使用Model Link'
......@@ -226,6 +230,25 @@ login_module:
successful: '获取成功'
get_verification_code: '获取验证码'
other_login_methods: '其他登录方式'
mobile_welcome_words: '您好,欢迎使用Model Link!'
sign_in_with_mobile_number: '推荐使用手机号码登录'
verification_code_login: '验证码登录'
password_login: '密码登录'
email_login: '邮箱登录'
forgot_password: '忘记密码?'
agreement_prefix: '我已阅读并同意'
agreement_terms: '用户协议'
agreement_privacy: '隐私政策'
agreement_and: '与'
confirm_to_logout: '确认要退出登录吗?'
you_can_login_again_after_logout: '退出登录仍可登录此账号'
reset_password_module:
reset_login_password: '重置登录密码'
please_enter_your_registered_phone_number: '请输入您注册的手机号,我们将为您重置密码'
please_enter_your_new_password: '请输入您的新密码'
back_to_login: '返回登录'
reset_password: '重置密码'
home_module:
agent_welcome_message: 'Hi, 欢迎使用Model Link'
......
......@@ -43,6 +43,7 @@ common_module:
publish_success_message: '發佈成功'
clear_success_message: '清空成功'
add_success_message: '新增成功'
reset_success_message: '重置成功'
loading: '加載中'
updating: '更新中'
successful_update: '更新成功'
......@@ -151,6 +152,7 @@ common_module:
not_certified_yet: '未認證'
authenticated: '已認證'
cancel_authorization: '取消授權'
get_code: '獲取驗証碼'
dialogue_module:
continue_question_message: '你可以繼續提問'
......@@ -210,6 +212,7 @@ router_title_module:
order_manage: '訂單管理'
data_statistic: '數據統計'
plugin_center: '插件中心'
reset_password: '重置密碼'
login_module:
app_welcome_words: 'Hi, 歡迎使用Model Link'
......@@ -228,6 +231,25 @@ login_module:
other_login_methods: '其他登錄方式'
interrupt_dialogue_prompt: '當前回復尚未完成,是否確定打斷發起新會話?'
interrupt_the_conversation_and_apply_the_history_prompt: '當前回復尚未完成,是否確定打斷對話應用其它記錄?'
mobile_welcome_words: '您好,歡迎使用Model Link!'
sign_in_with_mobile_number: '推薦使用手機號碼登錄'
verification_code_login: '驗證碼登錄'
password_login: '密碼登錄'
email_login: '郵箱登錄'
forgot_password: '忘記密碼?'
agreement_prefix: '我已閲讀並同意'
agreement_terms: '用户協議'
agreement_privacy: '隱私政策'
agreement_and: '與'
confirm_to_logout: '確認要退出登錄嗎?'
you_can_login_again_after_logout: '退出登錄仍可登錄此賬號'
reset_password_module:
reset_login_password: '重置登录密码'
please_enter_your_registered_phone_number: '请输入您注册的手机号,我们将为您重置密码'
please_enter_your_new_password: '请输入您的新密码'
back_to_login: '返回登录'
reset_password: '重置密码'
home_module:
agent_welcome_message: 'Hi, 歡迎使用Model Link'
......
......@@ -3,7 +3,7 @@ import { useUserStore } from '@/store/modules/user'
import i18n from '@/locales'
/** 路由白名单 */
const whitePathList = ['/login']
const whitePathList = ['/login', '/reset-password']
export function createRouterGuards(router: Router) {
router.beforeEach((to, _from, next) => {
......
......@@ -8,7 +8,16 @@ export default [
rank: 1001,
title: 'router_title_module.login',
},
component: () => import('@/views/login/login.vue'),
component: () => import('@/views/login/index.vue'),
},
{
path: '/reset-password',
name: 'ResetPassword',
meta: {
rank: 1001,
title: 'router_title_module.reset_password',
},
component: () => import('@/views/reset-password/reset-password.vue'),
},
// {
// path: '/404',
......
This diff is collapsed.
<script setup lang="ts">
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import H5Login from './h5-login.vue'
import PCLogin from './login.vue'
const { isMobile } = useLayoutConfig()
</script>
<template>
<component :is="isMobile ? H5Login : PCLogin"></component>
</template>
This diff is collapsed.
<script setup lang="ts"></script>
<script setup lang="ts">
interface Props {
activeColor?: string
}
const { activeColor = '#000dff' } = defineProps<Props>()
</script>
<template>
<div class="loader" />
......@@ -14,16 +20,16 @@
@keyframes l5 {
0% {
background: #000dff;
background: v-bind('activeColor');
box-shadow:
13px 0 #000dff,
13px 0 v-bind('activeColor'),
-13px 0 #0002;
}
33% {
background: #0002;
box-shadow:
13px 0 #000dff,
13px 0 v-bind('activeColor'),
-13px 0 #0002;
}
......@@ -31,14 +37,14 @@
background: #0002;
box-shadow:
13px 0 #0002,
-13px 0 #000dff;
-13px 0 v-bind('activeColor');
}
100% {
background: #000dff;
background: v-bind('activeColor');
box-shadow:
13px 0 #0002,
-13px 0 #000dff;
-13px 0 v-bind('activeColor');
}
}
</style>
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
interface Props {
agentTitle: string
}
const { t } = useI18n()
defineProps<Props>()
const emit = defineEmits<{
toCreateApplication: []
toLogin: []
}>()
const userStore = useUserStore()
const isLogin = computed(() => {
return userStore.isLogin
})
function handleToCreateApplication() {
emit('toCreateApplication')
}
function handleToLogin() {
emit('toLogin')
}
</script>
<template>
<header class="flex h-[48px] w-full items-center justify-between border-b border-[#e8e9eb] bg-white px-4">
<div class="bg-px-logo-png bg-contain! h-[24px] w-[100px] bg-center bg-no-repeat" />
<div>
<NButton
v-show="isLogin"
type="primary"
class="rounded-md! h-[32px]! text-xs! min-w-[80px]!"
@click="handleToCreateApplication"
>
{{ t('common_module.create_agent_btn_text') }}
</NButton>
<NButton v-show="!isLogin" type="primary" class="rounded-md! h-[32px]! text-xs! w-[80px]!" @click="handleToLogin">
<span class="text-xs"> {{ t('common_module.login_now') }}</span>
</NButton>
</div>
</header>
</template>
<script setup lang="ts">
import { debounce } from 'lodash-es'
import { nanoid } from 'nanoid'
import { computed, inject, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { Emitter } from 'mitt'
import { fetchRecommendQuestionList } from '@/apis/home-agent'
interface Props {
type: 'continuous' | 'featured'
}
defineProps<Props>()
const { t } = useI18n()
const emitter = inject<Emitter<MittEvents>>('emitter')
const continuousQuestionList = defineModel<string[]>('continuousQuestionList', { required: true })
const promptChangeBtnStatus = ref<'normal' | 'loading' | 'failed'>('normal')
const recommendQuestionList = computed(() => {
if (continuousQuestionList.value.length === 0) {
return []
}
return continuousQuestionList.value.map((item) => {
return {
id: nanoid(),
content: item,
}
})
})
function getRecommendQuestionList() {
return fetchRecommendQuestionList<string[]>().then((res) => {
continuousQuestionList.value = res.data
})
}
const handleRecommendQuestionListUpdate = debounce(
() => {
if (promptChangeBtnStatus.value === 'loading') {
return
}
promptChangeBtnStatus.value = 'loading'
getRecommendQuestionList()
.then(() => {
promptChangeBtnStatus.value = 'normal'
})
.catch(() => {
promptChangeBtnStatus.value = 'failed'
})
},
700,
{ leading: true, trailing: false },
)
function handleSelectContinueQuestion(continueQuestion: string) {
emitter?.emit('selectQuestion', continueQuestion)
}
</script>
<template>
<h3 class="mt-[15px] text-[12px] text-[#999]">
{{
type === 'featured'
? t('common_module.recommended_questions') + ':'
: t('common_module.dialogue_module.continue_question_message')
}}
</h3>
<ul class="w-full select-none">
<template v-if="continuousQuestionList.length && promptChangeBtnStatus !== 'loading'">
<li
v-for="questionItem in recommendQuestionList"
:key="questionItem.id"
class="mt-[10px] w-fit cursor-pointer rounded-[10px] border border-[#d4d6d9] bg-[#ffffff80] px-[12px] py-[10px] text-[12px] hover:opacity-80"
@click="handleSelectContinueQuestion(questionItem.content)"
>
{{ questionItem.content }}
</li>
</template>
<template v-else>
<n-skeleton :class="'mt-[10px]'" height="41px" width="52%" round />
<n-skeleton :class="'mt-[10px]'" height="41px" width="72%" round />
<n-skeleton :class="'mt-[10px]'" height="41px" width="70%" round />
</template>
<li class="mt-[10px] pl-[16px] text-[12px]">
<span
class="group cursor-pointer text-[#0B7DFF] transition"
:class="{
'hover:text-[#096EE0]': promptChangeBtnStatus !== 'loading',
'cursor-not-allowed': promptChangeBtnStatus === 'loading',
'text-[#BDBDBD]': promptChangeBtnStatus === 'loading',
}"
@click="handleRecommendQuestionListUpdate"
>
<i
class="iconfont icon-huanyihuan mr-[2px] inline-block text-[11px] transition-[rotate] duration-150 ease-in-out"
:class="{ 'group-active:rotate-360': promptChangeBtnStatus !== 'loading' }"
></i>
<span>
{{ promptChangeBtnStatus === 'failed' ? t('common_module.retry') : t('common_module.exchange') }}
</span>
</span>
</li>
</ul>
</template>
This diff is collapsed.
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { CheckOne, Down } from '@icon-park/vue-next'
import CustomLoading from '../custom-loading.vue'
import MusicWavesLoading from '../music-waves-loading.vue'
import MarkdownRender from '@/components/markdown-render/markdown-render.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
interface Props {
role: 'user' | 'assistant'
messageItem: ConversationMessageItem
agentApplicationConfig: PersonalAppConfigState
}
const { t } = useI18n()
const props = defineProps<Props>()
const emit = defineEmits<{
audioPlay: []
audioPause: []
}>()
const markdownRenderRef = useTemplateRef<InstanceType<typeof MarkdownRender>>('markdownRenderRef')
const isShowReasoningContent = ref(true)
const timbreEnabled = computed(() => {
return !!props.agentApplicationConfig.voiceConfig.timbreId
})
const isShowAudioControl = computed(() => {
return props.role === 'assistant' && !props.messageItem.isVoiceLoading && timbreEnabled.value
})
const isPlayableAudio = computed(() => {
return isShowAudioControl.value && !!props.messageItem.voiceFragmentUrlList.length
})
const isShowVoiceLoading = computed(() => {
return (
props.role === 'assistant' &&
(props.messageItem.isAnswerResponseLoading || (props.messageItem.isVoiceLoading && timbreEnabled.value))
)
})
const isDeepSeekR1 = computed(() => {
return props.agentApplicationConfig.commModelConfig.largeModel === 'DeepSeek'
})
function handleAudioControl() {
if (!isPlayableAudio.value) {
return
}
if (props.messageItem.isVoicePlaying) {
emit('audioPause')
} else {
emit('audioPlay')
}
}
function handleShowReasoningContentSwitch() {
isShowReasoningContent.value = !isShowReasoningContent.value
}
</script>
<template>
<div
class="mb-[15px] flex flex-row-reverse last:mb-0"
:class="[role === 'assistant' ? 'justify-end' : 'justify-start']"
>
<div class="flex w-full flex-col overflow-x-auto" :class="[role === 'user' ? 'items-end' : 'items-start']">
<!-- 大模型深度思考 -->
<template v-if="role === 'assistant' && isDeepSeekR1">
<div class="my-[7px] select-none text-[12px]">
<div class="inline-flex cursor-pointer" @click="handleShowReasoningContentSwitch">
<span v-if="messageItem.isTextContentLoading" class="mr-[6px]">
{{ t('common_module.deep_thinking', { modelName: agentApplicationConfig.commModelConfig.largeModel }) }}
</span>
<span v-else class="mr-[6px]">
{{ t('common_module.have_thought_deeply') }}
</span>
<Down
theme="outline"
:size="18"
fill="#333"
:stroke-width="3"
class="transition-[rotate] duration-100 ease-linear"
:class="{ '-rotate-180': isShowReasoningContent }"
/>
</div>
</div>
<n-collapse-transition :show="isShowReasoningContent">
<div
v-if="messageItem.reasoningContent"
class="my-[10px] border-l-[1px] border-solid border-l-[#ccc] px-[13px] py-[8px]"
>
<MarkdownRender
ref="markdownRenderRef"
:raw-text-content="
messageItem.reasoningContent
? messageItem.reasoningContent
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
:font-size="'12px'"
/>
</div>
</n-collapse-transition>
</template>
<!-- 模型内容 -->
<div class="flex min-w-[80px] max-w-full flex-col">
<div
class="w-full flex-wrap rounded-xl border px-4 px-[12px] py-[11px]"
:class="[
{ 'rounded-tr-none border-[#DCDEFF] bg-[#DCDEFF] text-white': role === 'user' },
{ 'rounded-tl-none border-[#e8e9eb] bg-white text-[#333]': role === 'assistant' },
]"
>
<img
v-show="role === 'user' && messageItem.imageUrl"
:src="messageItem.imageUrl"
class="max-h-[120px]! mb-[11px] rounded-[10px] object-contain"
/>
<div
v-show="role === 'assistant' && messageItem.pluginName"
class="mb-[8px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999]"
>
<div
v-show="messageItem.isTextContentLoading"
class="bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat"
/>
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" :class="'14'" fill="#40bd23" />
<span class="text-[12px] leading-5">
{{
messageItem.isTextContentLoading
? t('common_module.plugin_in_progress', { pluginName: messageItem.pluginName })
: t('common_module.plugin_executed_successfully', { pluginName: messageItem.pluginName })
}}
</span>
</div>
<div v-if="messageItem.isTextContentLoading" class="py-1.5 pl-4">
<CustomLoading :active-color="'#000DFF'" />
</div>
<div v-else>
<p class="break-all">
<MarkdownRender
ref="markdownRenderRef"
:raw-text-content="
messageItem.isEmptyContent
? t('common_module.dialogue_module.empty_message_content')
: messageItem.textContent
"
:color="'#333'"
:font-size="'12px'"
/>
</p>
<div v-show="isShowVoiceLoading || messageItem.isAnswerResponseLoading" class="mb-[5px] mt-4 px-4">
<CustomLoading :active-color="'#000DFF'" />
</div>
</div>
<!-- 移动端语音播放 -->
<div v-show="isShowAudioControl" class="mt-[13px] flex items-center gap-2">
<div
class="h-[18px] w-[18px] cursor-pointer"
:class="messageItem.isVoicePlaying ? 'bg-svg-pause' : 'bg-svg-play'"
@click="handleAudioControl"
/>
<MusicWavesLoading v-show="messageItem.isVoicePlaying && isPlayableAudio" bar-bg-color="#333" />
<n-popover style="max-width: 310px">
<template #trigger>
<span v-show="!isPlayableAudio" class="text-[12px]"> {{ t('common_module.unplayable') }} </span>
</template>
{{ t('common_module.unplayable_tip') }}
</n-popover>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useScroll } from '@/composables/useScroll'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useBackBottom } from '@/composables/useBackBottom'
import MessageItem from './message-item.vue'
import ContinueQuestion from './continue-question.vue'
interface Props {
messageList: Map<string, ConversationMessageItem>
agentApplicationConfig: PersonalAppConfigState
continuousQuestionStatus: 'default' | 'close'
isAnswerResponseLoading: boolean
createContinueQuestionsException: boolean
isAnswerResponseInterrupt?: boolean
}
const props = defineProps<Props>()
defineEmits<{
audioPlay: [messageItem: ConversationMessageItem]
audioPause: []
}>()
const { t } = useI18n()
const { scrollRef, scrollToBottom } = useScroll()
const { visible, clickBackBottom, throttleScrollContainer } = useBackBottom(scrollRef, scrollToBottom)
const continuousQuestionList = defineModel<string[]>('continuousQuestionList', { required: true })
const isShowContinueQuestion = computed(() => {
return (
props.continuousQuestionStatus === 'default' &&
props.messageList.size > 1 &&
!props.isAnswerResponseLoading &&
!props.createContinueQuestionsException &&
!props.isAnswerResponseInterrupt
)
})
watch(
() => props.messageList.size,
() => {
clickBackBottom()
},
)
defineExpose({
scrollToBottom: handleScrollToBottom,
})
function handleScrollToBottom() {
nextTick(() => {
!visible.value && scrollToBottom()
})
}
</script>
<template>
<main ref="scrollRef" class="h-full overflow-y-auto overflow-x-hidden px-5" @scroll="throttleScrollContainer">
<div>
<MessageItem
v-for="[key, messageItem] in messageList"
:key="key"
:role="messageItem.role"
:message-item="messageItem"
:agent-application-config="agentApplicationConfig"
@audio-play="() => $emit('audioPlay', messageItem)"
@audio-pause="() => $emit('audioPause')"
/>
</div>
<p v-show="isAnswerResponseLoading" class="my-[7px] ml-1 text-xs text-[#84868c]">
{{ t('common_module.dialogue_module.do_not_exit_page') }}
</p>
<div v-show="isShowContinueQuestion">
<ContinueQuestion v-model:continuous-question-list="continuousQuestionList" :type="'continuous'" />
</div>
<div
v-show="visible"
class="flex-center hover:text-theme-color absolute bottom-5 right-5 h-6 w-6 cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed]"
@click.stop="clickBackBottom"
>
<i class="iconfont icon-left rotate-270 text-sm" />
</div>
</main>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'
import { computed, CSSProperties } from 'vue'
import { useI18n } from 'vue-i18n'
import { Logout } from '@icon-park/vue-next'
interface Props {
agentTitle: string
}
const { t } = useI18n()
defineProps<Props>()
const emit = defineEmits<{
toCreateApplication: []
toLogin: []
toLogout: []
updateAutoPlaying: [value: boolean]
}>()
const userStore = useUserStore()
const isEnableVoice = defineModel<boolean>('isEnableVoice', { required: true })
const answerAudioAutoPlay = defineModel<boolean>('answerAudioAutoPlay', { required: true })
const railStyle = ({ focused, checked }: { focused: boolean; checked: boolean }) => {
const style: CSSProperties = {}
if (checked) {
style.background = '#CFD1FF'
} else {
if (focused) {
style.boxShadow = '0 0 0 2px #ddd'
}
}
return style
}
const isLogin = computed(() => {
return userStore.isLogin
})
function handleToCreateApplication() {
emit('toCreateApplication')
}
function handleToLogin() {
emit('toLogin')
}
function handleToLogout() {
emit('toLogout')
}
</script>
<template>
<header class="flex h-[58px] w-full items-center justify-between px-[20px]">
<div>
<div v-show="isEnableVoice" class="flex items-center gap-2">
<span class="text-[12px]">{{ t('common_module.voice_auto_play') }}</span>
<n-switch
v-model:value="answerAudioAutoPlay"
size="small"
:rail-style="railStyle"
@update:value="(isAutoPlay: boolean) => emit('updateAutoPlaying', isAutoPlay)"
>
<template #checked> {{ t('common_module.open') }} </template>
<template #unchecked> {{ t('common_module.close') }} </template>
<template #checked-icon>
<div class="bg-theme-color h-full w-full rounded-full"></div>
</template>
</n-switch>
</div>
</div>
<div class="flex-center gap-[10px]">
<NButton
v-show="isLogin"
type="primary"
color="#EBECFF"
class="rounded-md! h-[28px]! text-[12px]! min-w-[80px]! text-theme-color!"
@click="handleToCreateApplication"
>
{{ t('common_module.create_agent_btn_text') }}
</NButton>
<NButton
v-show="!isLogin"
color="#EBECFF"
class="rounded-md! h-[28]! text-[12px]! min-w-[80px]! text-theme-color!"
@click="handleToLogin"
>
<span class="text-xs"> {{ t('common_module.login_now') }}</span>
</NButton>
<div v-show="isLogin" class="flex-center h-[28px] w-[28px] rounded-[5px] bg-[#EBECFF]">
<Logout theme="outline" size="15" fill="#000DFF" :stroke-width="4" @click="handleToLogout" />
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import ContinueQuestion from './continue-question.vue'
interface Props {
agentApplicationConfig: PersonalAppConfigState
}
const props = defineProps<Props>()
const agentApplicationConfig = computed(() => props.agentApplicationConfig)
const agentAvatar = computed(() => {
return (
agentApplicationConfig.value.baseInfo.agentAvatar ||
'https://gsst-poe-sit.gz.bcebos.com/data/20240911/1726041369632.webp'
)
})
</script>
<template>
<div class="flex w-full flex-1 flex-col px-[20px]">
<div class="mb-[7.5px] mt-[25px] flex w-full justify-center">
<img :src="agentAvatar" class="rounded-theme h-[40px] w-[40px] object-cover" />
</div>
<div class="flex flex-col items-center justify-center">
<p class="font-family-medium mb-[17px] line-clamp-1 text-[13px] text-[#333]">
{{ agentApplicationConfig.baseInfo.agentTitle }}
</p>
<div class="flex w-full flex-col items-start justify-center">
<p
v-show="agentApplicationConfig.commConfig.preamble"
class="mb-0 select-none break-all rounded-[10px] rounded-tl-none bg-[#DCDEFF] px-[12.5px] py-[11px] text-[12px]"
>
{{ agentApplicationConfig.commConfig.preamble }}
</p>
<ContinueQuestion
v-model:continuous-question-list="agentApplicationConfig.commConfig.featuredQuestions"
:type="'featured'"
/>
</div>
</div>
</div>
</template>
......@@ -3,12 +3,14 @@ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Howl } from 'howler'
import { showDialog } from 'vant'
import 'vant/es/dialog/style'
import { useEventListener } from '@vueuse/core'
import type { ValueOf } from 'type-fest'
import PageHeader from './components/mobile-page-header.vue'
import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue'
import PageHeader from './components/mobile/page-header.vue'
import Preamble from './components/mobile/preamble.vue'
import MessageList from './components/mobile/message-list.vue'
import FooterInput from './components/mobile/footer-input.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useUserStore } from '@/store/modules/user'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
......@@ -160,10 +162,10 @@ function handleToLoginPage() {
}
function handleCreateApplicationPage() {
window.$dialog.info({
showDialog({
title: t('share_agent_module.create_agent_dialogue_title'),
content: t('share_agent_module.create_agent_dialogue_content'),
positiveText: t('share_agent_module.create_agent_dialogue_positive_text'),
message: t('share_agent_module.create_agent_dialogue_content'),
confirmButtonText: t('share_agent_module.create_agent_dialogue_positive_text'),
})
}
......@@ -204,12 +206,14 @@ function handleUpdatePageScroll() {
}
function handleClearAllMessage() {
window.$message
.ctWarning(
t('common_module.dialogue_module.clear_message_dialog_content'),
t('common_module.dialogue_module.clear_message_dialog_title'),
)
.then(() => {
showDialog({
title: t('common_module.dialogue_module.clear_message_dialog_title'),
message: t('common_module.dialogue_module.clear_message_dialog_content'),
showCancelButton: true,
cancelButtonText: t('common_module.cancel_btn_text'),
confirmButtonText: t('common_module.confirm_btn_text'),
confirmButtonColor: '#F25744',
}).then(() => {
handleAudioPause()
footerInputRef.value?.blockMessageResponse()
messageList.value.clear()
......@@ -355,32 +359,43 @@ function handleExitPage() {
footerInputRef.value?.errorMessageResponse()
}
}
function handleToLogoutPage() {
showDialog({
title: t('login_module.confirm_to_logout'),
message: t('login_module.you_can_login_again_after_logout'),
showCancelButton: true,
cancelButtonText: t('common_module.cancel_btn_text'),
confirmButtonText: t('common_module.logout'),
confirmButtonColor: '#F25744',
}).then(() => {
userStore.logout()
handleAudioPause()
footerInputRef.value?.blockMessageResponse()
messageList.value.clear()
answerAudioPlaying.value = false
})
}
</script>
<template>
<div v-loading="fullScreenLoading" class="h-full w-full">
<div v-loading="fullScreenLoading" class="bg-px-share-h5_bg-png h-full w-full bg-cover bg-no-repeat">
<PageHeader
v-model:is-enable-voice="isEnableVoice"
v-model:answer-audio-auto-play="answerAudioAutoPlay"
:agent-title="agentApplicationConfig.baseInfo.agentTitle"
@to-login="handleToLoginPage"
@to-logout="handleToLogoutPage"
@to-create-application="handleCreateApplicationPage"
@update-auto-playing="handleUpdateAutoPlaying"
/>
<div class="flex h-[calc(100%-48px)] w-full flex-col bg-[#f2f5f9]">
<div class="mt-5 flex select-none justify-end px-4">
<div v-show="isEnableVoice" class="flex items-center gap-2">
<span>{{ t('common_module.voice_auto_play') }}</span>
<n-switch v-model:value="answerAudioAutoPlay" size="small" @update:value="handleUpdateAutoPlaying">
<template #checked> {{ t('common_module.open') }} </template>
<template #unchecked> {{ t('common_module.close') }} </template>
</n-switch>
</div>
</div>
<div v-if="messageList.size === 0" class="w-full flex-1 overflow-auto px-4">
<div class="flex h-[calc(100%-58px)] w-full flex-col">
<div v-if="messageList.size === 0" class="w-full flex-1 overflow-auto">
<Preamble :agent-application-config="agentApplicationConfig" />
</div>
<div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden pt-5">
<div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden">
<div class="relative flex-1 overflow-auto">
<MessageList
ref="messageListRef"
......@@ -391,13 +406,14 @@ function handleExitPage() {
:is-answer-response-loading="isAnswerResponseLoading"
:create-continue-questions-exception="createContinueQuestionsException"
:is-answer-response-interrupt="isAnswerResponseInterrupt"
class="pt-5"
@audio-play="handleAudioPlay"
@audio-pause="handleAudioPause"
/>
</div>
</div>
<div class="footer-operation px-4">
<div class="footer-operation bg-white px-4">
<FooterInput
ref="footerInputRef"
v-model:is-answer-response-loading="isAnswerResponseLoading"
......
......@@ -44,6 +44,7 @@ declare namespace I18n {
publish_success_message: string
clear_success_message: string
add_success_message: string
reset_success_message: string
loading: string
updating: string
successful_update: string
......@@ -151,6 +152,7 @@ declare namespace I18n {
not_certified_yet: string
authenticated: string
cancel_authorization: string
get_code: string
dialogue_module: {
continue_question_message: string
......@@ -217,6 +219,7 @@ declare namespace I18n {
order_manage: string
data_statistic: string
plugin_center: string
reset_password: string
}
login_module: {
......@@ -233,6 +236,26 @@ declare namespace I18n {
login_success: string
get_verification_code: string
other_login_methods: string
mobile_welcome_words: string
sign_in_with_mobile_number: string
verification_code_login: string
password_login: string
email_login: string
forgot_password: string
agreement_prefix: string
agreement_terms: string
agreement_privacy: string
agreement_and: string
confirm_to_logout: string
you_can_login_again_after_logout: string
}
reset_password_module: {
reset_login_password: string
please_enter_your_registered_phone_number: string
please_enter_your_new_password: string
back_to_login: string
reset_password: string
}
home_module: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment