You need to sign in or sign up before continuing.
Commit 6d7b65ec authored by nick zheng's avatar nick zheng

Merge branch 'beta' into 'master'

Beta

See merge request !203
parents 2bb401e2 a90eeca3
......@@ -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