Commit 92a4f93e authored by tyyin lan's avatar tyyin lan
parents ad2150ea ace0b833
...@@ -16,6 +16,8 @@ pipeline { ...@@ -16,6 +16,8 @@ pipeline {
pnpm install pnpm install
pnpm run build:sit pnpm run build:sit
cp -r ./dist ./build cp -r ./dist ./build
cd build
ls
''' '''
} }
} }
......
...@@ -4,6 +4,8 @@ FROM nexus3.gsstcloud.com:8092/ddl/ddl-fe-base:latest ...@@ -4,6 +4,8 @@ FROM nexus3.gsstcloud.com:8092/ddl/ddl-fe-base:latest
## Remove default nginx website ## Remove default nginx website
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*
RUn ls
## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder ## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
COPY dist/ /usr/share/nginx/html COPY dist/ /usr/share/nginx/html
......
...@@ -4,7 +4,7 @@ import checker from 'vite-plugin-checker' ...@@ -4,7 +4,7 @@ import checker from 'vite-plugin-checker'
import { visualizer } from 'rollup-plugin-visualizer' import { visualizer } from 'rollup-plugin-visualizer'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/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 VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import UnoCSS from 'unocss/vite' import UnoCSS from 'unocss/vite'
import VueJsx from '@vitejs/plugin-vue-jsx' import VueJsx from '@vitejs/plugin-vue-jsx'
...@@ -20,7 +20,7 @@ export function setupPlugins(isBuild: boolean, envConf: ViteEnv, pathResolve: (d ...@@ -20,7 +20,7 @@ export function setupPlugins(isBuild: boolean, envConf: ViteEnv, pathResolve: (d
], ],
}), }),
Components({ Components({
resolvers: [NaiveUiResolver()], resolvers: [NaiveUiResolver(), VantResolver()],
}), }),
VueI18nPlugin({ VueI18nPlugin({
include: [pathResolve('./src/locales/langs/**')], include: [pathResolve('./src/locales/langs/**')],
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"type-fest": "^4.26.1", "type-fest": "^4.26.1",
"validator": "^13.12.0", "validator": "^13.12.0",
"vant": "^4.9.18",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-i18n": "^9.14.0", "vue-i18n": "^9.14.0",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
...@@ -63,6 +64,8 @@ ...@@ -63,6 +64,8 @@
"@types/validator": "^13.12.2", "@types/validator": "^13.12.2",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.18.0",
"@unocss/eslint-config": "^0.61.9", "@unocss/eslint-config": "^0.61.9",
"@unocss/postcss": "66.1.0-beta.10",
"@unocss/transformer-directives": "66.1.0-beta.10",
"@vitejs/plugin-vue": "^4.6.2", "@vitejs/plugin-vue": "^4.6.2",
"@vitejs/plugin-vue-jsx": "^4.0.1", "@vitejs/plugin-vue-jsx": "^4.0.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
...@@ -77,6 +80,7 @@ ...@@ -77,6 +80,7 @@
"naive-ui": "^2.39.0", "naive-ui": "^2.39.0",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-html": "^1.7.0", "postcss-html": "^1.7.0",
"postcss-mobile-forever": "^5.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6", "prettier-plugin-tailwindcss": "^0.6.6",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
......
This diff is collapsed.
export default { export default {
plugins: { plugins: {
'@unocss/postcss': {},
autoprefixer: {}, autoprefixer: {},
'postcss-mobile-forever': {
appSelector: '#app',
viewportWidth: 375,
desktopWidth: 500,
maxDisplayWidth: 500,
include: [/src\/views\/share\/share-application-mobile/, /src\/views\/share\/components\/mobile\//],
},
}, },
} }
...@@ -42,3 +42,9 @@ export function fetchUserPasswordUpdate<T>(authCode: string, password: string) { ...@@ -42,3 +42,9 @@ export function fetchUserPasswordUpdate<T>(authCode: string, password: string) {
export function fetchGoogleClientId<T>() { export function fetchGoogleClientId<T>() {
return request.post<T>('/googleConfigRest/getClientId.json') 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() ...@@ -29,7 +29,7 @@ const { t } = useI18n()
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
height: 240, height: 240,
width: 500, width: 500,
borderRadius: 6, borderRadius: 10,
btnLoading: false, btnLoading: false,
btnDisabled: false, btnDisabled: false,
cancelBtnText: '', cancelBtnText: '',
......
...@@ -43,6 +43,7 @@ common_module: ...@@ -43,6 +43,7 @@ common_module:
publish_success_message: 'Release success' publish_success_message: 'Release success'
clear_success_message: 'Clear successfully' clear_success_message: 'Clear successfully'
add_success_message: 'New success' add_success_message: 'New success'
reset_success_message: 'Reset successful'
loading: 'Loading' loading: 'Loading'
updating: 'Uploading' updating: 'Uploading'
successful_update: 'Update successfully' successful_update: 'Update successfully'
...@@ -152,6 +153,7 @@ common_module: ...@@ -152,6 +153,7 @@ common_module:
not_certified_yet: 'Not certified yet' not_certified_yet: 'Not certified yet'
authenticated: 'Authenticated' authenticated: 'Authenticated'
cancel_authorization: 'Cancel authorization' cancel_authorization: 'Cancel authorization'
get_code: 'Get Code'
dialogue_module: dialogue_module:
continue_question_message: 'You can keep asking questions' continue_question_message: 'You can keep asking questions'
...@@ -211,6 +213,7 @@ router_title_module: ...@@ -211,6 +213,7 @@ router_title_module:
order_manage: 'Order Management' order_manage: 'Order Management'
data_statistic: 'Data statistic' data_statistic: 'Data statistic'
plugin_center: 'Plugin center' plugin_center: 'Plugin center'
reset_password: 'Reset password'
login_module: login_module:
app_welcome_words: 'Hi, welcome to Model Link' app_welcome_words: 'Hi, welcome to Model Link'
...@@ -227,6 +230,28 @@ login_module: ...@@ -227,6 +230,28 @@ login_module:
successful: 'Obtain success' successful: 'Obtain success'
get_verification_code: 'Get verification code' get_verification_code: 'Get verification code'
other_login_methods: 'Other login methods' 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'
please_review_and_accept_the_agreement: 'Please review and accept agreement'
agree_and_continue: 'Agree & Continue'
disagree: 'Disagree'
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: home_module:
agent_welcome_message: 'Hi, welcome to Model Link' agent_welcome_message: 'Hi, welcome to Model Link'
......
...@@ -43,6 +43,7 @@ common_module: ...@@ -43,6 +43,7 @@ common_module:
publish_success_message: '发布成功' publish_success_message: '发布成功'
clear_success_message: '清空成功' clear_success_message: '清空成功'
add_success_message: '新增成功' add_success_message: '新增成功'
reset_success_message: '重置成功'
loading: '加载中' loading: '加载中'
updating: '更新中' updating: '更新中'
successful_update: '更新成功' successful_update: '更新成功'
...@@ -151,6 +152,8 @@ common_module: ...@@ -151,6 +152,8 @@ common_module:
not_certified_yet: '未认证' not_certified_yet: '未认证'
authenticated: '已认证' authenticated: '已认证'
cancel_authorization: '取消授权' cancel_authorization: '取消授权'
get_code: '获取验证码'
dialogue_module: dialogue_module:
continue_question_message: '你可以继续提问' continue_question_message: '你可以继续提问'
...@@ -210,6 +213,7 @@ router_title_module: ...@@ -210,6 +213,7 @@ router_title_module:
order_manage: '订单管理' order_manage: '订单管理'
data_statistic: '数据统计' data_statistic: '数据统计'
plugin_center: '插件中心' plugin_center: '插件中心'
reset_password: '重置密码'
login_module: login_module:
app_welcome_words: 'Hi, 欢迎使用Model Link' app_welcome_words: 'Hi, 欢迎使用Model Link'
...@@ -226,6 +230,28 @@ login_module: ...@@ -226,6 +230,28 @@ login_module:
successful: '获取成功' successful: '获取成功'
get_verification_code: '获取验证码' get_verification_code: '获取验证码'
other_login_methods: '其他登录方式' 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: '退出登录仍可登录此账号'
please_review_and_accept_the_agreement: '请阅读并同意以下协议'
agree_and_continue: '同意并继续'
disagree: '不同意'
reset_password_module:
reset_login_password: '重置登录密码'
please_enter_your_registered_phone_number: '请输入您注册的手机号,我们将为您重置密码'
please_enter_your_new_password: '请输入您的新密码'
back_to_login: '返回登录'
reset_password: '重置密码'
home_module: home_module:
agent_welcome_message: 'Hi, 欢迎使用Model Link' agent_welcome_message: 'Hi, 欢迎使用Model Link'
......
...@@ -43,6 +43,7 @@ common_module: ...@@ -43,6 +43,7 @@ common_module:
publish_success_message: '發佈成功' publish_success_message: '發佈成功'
clear_success_message: '清空成功' clear_success_message: '清空成功'
add_success_message: '新增成功' add_success_message: '新增成功'
reset_success_message: '重置成功'
loading: '加載中' loading: '加載中'
updating: '更新中' updating: '更新中'
successful_update: '更新成功' successful_update: '更新成功'
...@@ -151,6 +152,7 @@ common_module: ...@@ -151,6 +152,7 @@ common_module:
not_certified_yet: '未認證' not_certified_yet: '未認證'
authenticated: '已認證' authenticated: '已認證'
cancel_authorization: '取消授權' cancel_authorization: '取消授權'
get_code: '獲取驗証碼'
dialogue_module: dialogue_module:
continue_question_message: '你可以繼續提問' continue_question_message: '你可以繼續提問'
...@@ -210,6 +212,7 @@ router_title_module: ...@@ -210,6 +212,7 @@ router_title_module:
order_manage: '訂單管理' order_manage: '訂單管理'
data_statistic: '數據統計' data_statistic: '數據統計'
plugin_center: '插件中心' plugin_center: '插件中心'
reset_password: '重置密碼'
login_module: login_module:
app_welcome_words: 'Hi, 歡迎使用Model Link' app_welcome_words: 'Hi, 歡迎使用Model Link'
...@@ -228,6 +231,28 @@ login_module: ...@@ -228,6 +231,28 @@ login_module:
other_login_methods: '其他登錄方式' other_login_methods: '其他登錄方式'
interrupt_dialogue_prompt: '當前回復尚未完成,是否確定打斷發起新會話?' interrupt_dialogue_prompt: '當前回復尚未完成,是否確定打斷發起新會話?'
interrupt_the_conversation_and_apply_the_history_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: '退出登錄仍可登錄此賬號'
please_review_and_accept_the_agreement: '請閲讀並同意以下協議'
agree_and_continue: '同意並繼續'
disagree: '不同意'
reset_password_module:
reset_login_password: '重置登录密码'
please_enter_your_registered_phone_number: '请输入您注册的手机号,我们将为您重置密码'
please_enter_your_new_password: '请输入您的新密码'
back_to_login: '返回登录'
reset_password: '重置密码'
home_module: home_module:
agent_welcome_message: 'Hi, 歡迎使用Model Link' agent_welcome_message: 'Hi, 歡迎使用Model Link'
......
...@@ -3,7 +3,7 @@ import { useUserStore } from '@/store/modules/user' ...@@ -3,7 +3,7 @@ import { useUserStore } from '@/store/modules/user'
import i18n from '@/locales' import i18n from '@/locales'
/** 路由白名单 */ /** 路由白名单 */
const whitePathList = ['/login'] const whitePathList = ['/login', '/reset-password']
export function createRouterGuards(router: Router) { export function createRouterGuards(router: Router) {
router.beforeEach((to, _from, next) => { router.beforeEach((to, _from, next) => {
......
...@@ -8,7 +8,16 @@ export default [ ...@@ -8,7 +8,16 @@ export default [
rank: 1001, rank: 1001,
title: 'router_title_module.login', 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', // 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> <template>
<div class="loader" /> <div class="loader" />
...@@ -14,16 +20,16 @@ ...@@ -14,16 +20,16 @@
@keyframes l5 { @keyframes l5 {
0% { 0% {
background: #000dff; background: v-bind('activeColor');
box-shadow: box-shadow:
13px 0 #000dff, 13px 0 v-bind('activeColor'),
-13px 0 #0002; -13px 0 #0002;
} }
33% { 33% {
background: #0002; background: #0002;
box-shadow: box-shadow:
13px 0 #000dff, 13px 0 v-bind('activeColor'),
-13px 0 #0002; -13px 0 #0002;
} }
...@@ -31,14 +37,14 @@ ...@@ -31,14 +37,14 @@
background: #0002; background: #0002;
box-shadow: box-shadow:
13px 0 #0002, 13px 0 #0002,
-13px 0 #000dff; -13px 0 v-bind('activeColor');
} }
100% { 100% {
background: #000dff; background: v-bind('activeColor');
box-shadow: box-shadow:
13px 0 #0002, 13px 0 #0002,
-13px 0 #000dff; -13px 0 v-bind('activeColor');
} }
} }
</style> </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="continue-question-title">
{{
type === 'featured'
? t('common_module.recommended_questions') + ':'
: t('common_module.dialogue_module.continue_question_message')
}}
</h3>
<ul class="continue-question-wrapper">
<template v-if="continuousQuestionList.length && promptChangeBtnStatus !== 'loading'">
<li
v-for="questionItem in recommendQuestionList"
:key="questionItem.id"
class="question-item"
@click="handleSelectContinueQuestion(questionItem.content)"
>
{{ questionItem.content }}
</li>
</template>
<template v-else>
<n-skeleton class="question-item-skeleton" width="52%" round />
<n-skeleton class="question-item-skeleton" width="72%" round />
<n-skeleton class="question-item-skeleton" width="70%" round />
</template>
<li class="recommend-question-change-wrapper">
<span
class="recommend-question-change-container"
:class="{
'hover:text-[#096EE0]': promptChangeBtnStatus !== 'loading',
'cursor-not-allowed': promptChangeBtnStatus === 'loading',
'text-[#BDBDBD]': promptChangeBtnStatus === 'loading',
}"
@click="handleRecommendQuestionListUpdate"
>
<i
class="iconfont icon-huanyihuan recommend-question-change-icon"
:class="{ 'group-active:rotate-360': promptChangeBtnStatus !== 'loading' }"
></i>
<span>
{{ promptChangeBtnStatus === 'failed' ? t('common_module.retry') : t('common_module.exchange') }}
</span>
</span>
</li>
</ul>
</template>
<style lang="scss" scoped>
.continue-question-title {
@apply mt-[15px] text-[12px] text-[#999];
}
.continue-question-wrapper {
@apply w-full select-none;
.question-item {
@apply mt-[10px] w-fit cursor-pointer rounded-[10px] border border-[#d4d6d9] bg-[#ffffff80] px-[12px] py-[10px] text-[12px] hover:opacity-80;
}
.question-item-skeleton {
@apply mt-[10px] h-[40px];
}
.recommend-question-change-wrapper {
@apply mt-[10px] pl-[16px] text-[12px];
.recommend-question-change-container {
@apply group cursor-pointer text-[#0B7DFF] transition;
.recommend-question-change-icon {
@apply mr-[2px] inline-block text-[11px] transition-[rotate] duration-150 ease-in-out;
}
}
}
}
</style>
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="message-item-wrapper" :class="[role === 'assistant' ? 'justify-end' : 'justify-start']">
<div class="message-item-container" :class="[role === 'user' ? 'items-end' : 'items-start']">
<!-- 大模型深度思考 -->
<template v-if="role === 'assistant' && isDeepSeekR1">
<div class="deep-seek-title-container">
<div class="deep-seek-name-container" @click="handleShowReasoningContentSwitch">
<span v-if="messageItem.isTextContentLoading" class="deep-seek-text">
{{ t('common_module.deep_thinking', { modelName: agentApplicationConfig.commModelConfig.largeModel }) }}
</span>
<span v-else class="deep-seek-text">
{{ 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="deep-seek-content-container">
<MarkdownRender
ref="markdownRenderRef"
:raw-text-content="
messageItem.reasoningContent
? messageItem.reasoningContent
: t('common_module.dialogue_module.empty_message_content')
"
color="#999"
:font-size="'3.2vw'"
/>
</div>
</n-collapse-transition>
</template>
<!-- 模型内容 -->
<div class="model-content-wrapper">
<div
class="model-content-container"
:class="[
{ 'user-model-content-container': role === 'user' },
{ 'assistant-model-content-container': role === 'assistant' },
]"
>
<img v-show="role === 'user' && messageItem.imageUrl" :src="messageItem.imageUrl" class="upload-image" />
<div v-show="role === 'assistant' && messageItem.pluginName" class="plugin-container">
<div v-show="messageItem.isTextContentLoading" class="plugin-loading" />
<CheckOne v-show="!messageItem.isTextContentLoading" theme="outline" size="16" fill="#40bd23" />
<span class="plugin-name">
{{
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="content-loading">
<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="'3.2vw'"
/>
</p>
<div v-show="isShowVoiceLoading || messageItem.isAnswerResponseLoading" class="answer-response-loading">
<CustomLoading :active-color="'#000DFF'" />
</div>
</div>
<!-- 移动端语音播放 -->
<div v-show="isShowAudioControl" class="audio-control-container">
<div
class="audio-control-icon"
: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"> {{ t('common_module.unplayable') }} </span>
</template>
{{ t('common_module.unplayable_tip') }}
</n-popover>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.message-item-wrapper {
@apply mb-[15px] flex flex-row-reverse last:mb-0;
.message-item-container {
@apply flex w-full flex-col overflow-x-auto;
.deep-seek-title-container {
@apply my-[7px] select-none text-[12px];
.deep-seek-name-container {
@apply inline-flex cursor-pointer;
.deep-seek-text {
@apply mr-[6px];
}
}
}
.deep-seek-content-container {
@apply my-[10px] border-l-[1px] border-solid border-l-[#ccc] px-[13px] py-[8px];
}
.model-content-wrapper {
@apply flex min-w-[80px] max-w-full flex-col;
.model-content-container {
@apply w-full flex-wrap rounded-[10px] border px-[12px] py-[11px];
.upload-image {
@apply max-h-[120px]! mb-[10px] rounded-[10px] object-contain;
}
.plugin-container {
@apply mb-[8px] flex items-center gap-[5px] font-['Microsoft_YaHei_UI'] text-[#999];
.plugin-loading {
@apply bg-px-plugin_loading-gif h-[14px] w-[14px] bg-contain bg-center bg-no-repeat;
}
.plugin-name {
@apply text-[12px] leading-4;
}
}
.content-loading {
@apply py-[6px] pl-[16px];
}
.answer-response-loading {
@apply mb-[5px] mt-[16px] px-[16px];
}
.audio-control-container {
@apply mt-[11px] flex items-center gap-[8px] text-[12px];
.audio-control-icon {
@apply h-[16px] w-[16px] cursor-pointer;
}
}
}
.user-model-content-container {
@apply rounded-tr-none border-[#DCDEFF] bg-[#DCDEFF] text-white;
}
.assistant-model-content-container {
@apply rounded-tl-none border-[#e8e9eb] bg-white text-[#333];
}
}
}
}
</style>
<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="message-list-container" @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="answer-response-loading-text">
{{ 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="back-bottom-btn" @click.stop="clickBackBottom">
<i class="iconfont icon-left back-bottom-btn-icon" />
</div>
</main>
</template>
<style lang="scss" scoped>
.message-list-container {
@apply h-full overflow-y-auto overflow-x-hidden px-[20px];
.answer-response-loading-text {
@apply my-[7px] ml-[4px] text-[12px] text-[#84868c];
}
.back-bottom-btn {
@apply flex-center hover:text-theme-color absolute bottom-[20px] right-[20px] h-[24px] w-[24px] cursor-pointer rounded-full bg-white shadow-[0_0_0_1px_#ededed];
&-icon {
@apply rotate-270 text-[14px];
}
}
}
</style>
<script setup lang="ts">
interface Props {
barBgColor?: string
height?: string
}
withDefaults(defineProps<Props>(), {
barBgColor: '#363636',
height: '12px',
})
</script>
<template>
<div class="music">
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
<div class="bar" />
</div>
</template>
<style lang="scss" scoped>
.music {
display: flex;
align-items: center;
justify-content: space-between;
width: 42px;
height: v-bind(height);
.bar {
width: 2px;
/* stylelint-disable-next-line value-keyword-case */
background: v-bind(barBgColor);
border-radius: 50px;
animation: loader 1.5s ease-in-out infinite;
&:nth-child(1) {
/* background: purple; */
animation-delay: 1s;
}
&:nth-child(2) {
/* background: crimson; */
animation-delay: 0.8s;
}
&:nth-child(3) {
/* background: deeppink; */
animation-delay: 0.6s;
}
&:nth-child(4) {
/* background: orange; */
animation-delay: 0.4s;
}
&:nth-child(5) {
/* background: gold; */
animation-delay: 0.2s;
}
&:nth-child(6) {
/* background: gold; */
animation-delay: 0.2s;
}
&:nth-child(7) {
/* background: gold; */
animation-delay: 0.4s;
}
&:nth-child(8) {
/* background: deeppink; */
animation-delay: 0.6s;
}
&:nth-child(9) {
/* background: crimson; */
animation-delay: 0.8s;
}
&:nth-child(10) {
/* background: purple; */
animation-delay: 1s;
}
}
}
@keyframes loader {
0% {
height: 4px;
}
50% {
height: v-bind(height);
}
100% {
height: 4px;
}
}
</style>
<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="page-header-wrapper">
<div>
<div v-show="isEnableVoice" class="enable-voice-container">
<span class="auto-play">{{ 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="checked-icon"></div>
</template>
</n-switch>
</div>
</div>
<div class="page-header-right-container">
<NButton
v-show="isLogin"
type="primary"
color="#EBECFF"
class="create-agent-btn"
@click="handleToCreateApplication"
>
{{ t('common_module.create_agent_btn_text') }}
</NButton>
<NButton v-show="!isLogin" color="#EBECFF" class="login-btn" @click="handleToLogin">
{{ t('common_module.login_now') }}
</NButton>
<div v-show="isLogin" class="logout-btn">
<Logout theme="outline" size="15" fill="#000DFF" :stroke-width="4" @click="handleToLogout" />
</div>
</div>
</header>
</template>
<style lang="scss" scoped>
.page-header-wrapper {
@apply flex h-[58px] w-full items-center justify-between px-[20px];
.enable-voice-container {
@apply flex items-center gap-[8px];
.auto-play {
@apply text-[12px];
}
.checked-icon {
@apply bg-theme-color h-full w-full rounded-full;
}
}
.page-header-right-container {
@apply flex-center gap-[10px];
.create-agent-btn {
@apply rounded-md! h-[28px]! text-[12px]! min-w-[80px]! text-theme-color!;
}
.login-btn {
@apply rounded-md! h-[28px]! text-[12px]! min-w-[80px]! text-theme-color!;
}
.logout-btn {
@apply flex-center h-[28px] w-[28px] rounded-[5px] bg-[#EBECFF];
}
}
}
</style>
<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="preamble-wrapper">
<div class="avatar-container">
<img :src="agentAvatar" class="agent-avatar" />
</div>
<div class="preamble-container">
<p class="agent-title">
{{ agentApplicationConfig.baseInfo.agentTitle }}
</p>
<div class="flex w-full flex-col items-start justify-center">
<p v-show="agentApplicationConfig.commConfig.preamble" class="preamble">
{{ agentApplicationConfig.commConfig.preamble }}
</p>
<ContinueQuestion
v-model:continuous-question-list="agentApplicationConfig.commConfig.featuredQuestions"
:type="'featured'"
/>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.preamble-wrapper {
@apply flex w-full flex-col px-[20px];
.avatar-container {
@apply mb-[7.5px] mt-[25px] flex w-full justify-center;
.agent-avatar {
@apply rounded-theme h-[40px] w-[40px] object-cover;
}
}
.preamble-container {
@apply flex flex-col items-center justify-center;
.agent-title {
@apply font-family-medium mb-[17px] line-clamp-1 text-[13px] text-[#333];
}
.preamble {
@apply select-none break-all rounded-[10px] rounded-tl-none bg-[#DCDEFF] px-[12.5px] py-[11px] text-[12px];
}
}
}
</style>
...@@ -3,12 +3,14 @@ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue' ...@@ -3,12 +3,14 @@ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Howl } from 'howler' import { Howl } from 'howler'
import { showDialog } from 'vant'
import 'vant/es/dialog/style'
import { useEventListener } from '@vueuse/core' import { useEventListener } from '@vueuse/core'
import type { ValueOf } from 'type-fest' import type { ValueOf } from 'type-fest'
import PageHeader from './components/mobile-page-header.vue' import PageHeader from './components/mobile/page-header.vue'
import Preamble from './components/preamble.vue' import Preamble from './components/mobile/preamble.vue'
import MessageList from './components/message-list.vue' import MessageList from './components/mobile/message-list.vue'
import FooterInput from './components/footer-input.vue' import FooterInput from './components/mobile/footer-input.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config' import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config' import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
...@@ -160,10 +162,10 @@ function handleToLoginPage() { ...@@ -160,10 +162,10 @@ function handleToLoginPage() {
} }
function handleCreateApplicationPage() { function handleCreateApplicationPage() {
window.$dialog.info({ showDialog({
title: t('share_agent_module.create_agent_dialogue_title'), title: t('share_agent_module.create_agent_dialogue_title'),
content: t('share_agent_module.create_agent_dialogue_content'), message: t('share_agent_module.create_agent_dialogue_content'),
positiveText: t('share_agent_module.create_agent_dialogue_positive_text'), confirmButtonText: t('share_agent_module.create_agent_dialogue_positive_text'),
}) })
} }
...@@ -204,18 +206,20 @@ function handleUpdatePageScroll() { ...@@ -204,18 +206,20 @@ function handleUpdatePageScroll() {
} }
function handleClearAllMessage() { function handleClearAllMessage() {
window.$message showDialog({
.ctWarning( title: t('common_module.dialogue_module.clear_message_dialog_title'),
t('common_module.dialogue_module.clear_message_dialog_content'), message: t('common_module.dialogue_module.clear_message_dialog_content'),
t('common_module.dialogue_module.clear_message_dialog_title'), showCancelButton: true,
) cancelButtonText: t('common_module.cancel_btn_text'),
.then(() => { confirmButtonText: t('common_module.confirm_btn_text'),
handleAudioPause() confirmButtonColor: '#F25744',
footerInputRef.value?.blockMessageResponse() }).then(() => {
messageList.value.clear() handleAudioPause()
answerAudioPlaying.value = false footerInputRef.value?.blockMessageResponse()
window.$message.success(t('common_module.clear_success_message')) messageList.value.clear()
}) answerAudioPlaying.value = false
window.$message.success(t('common_module.clear_success_message'))
})
} }
async function handleCreateContinueQuestions(replyTextContent: string) { async function handleCreateContinueQuestions(replyTextContent: string) {
...@@ -355,32 +359,43 @@ function handleExitPage() { ...@@ -355,32 +359,43 @@ function handleExitPage() {
footerInputRef.value?.errorMessageResponse() 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> </script>
<template> <template>
<div v-loading="fullScreenLoading" class="h-full w-full"> <div v-loading="fullScreenLoading" class="share-mobile-container">
<PageHeader <PageHeader
v-model:is-enable-voice="isEnableVoice"
v-model:answer-audio-auto-play="answerAudioAutoPlay"
:agent-title="agentApplicationConfig.baseInfo.agentTitle" :agent-title="agentApplicationConfig.baseInfo.agentTitle"
@to-login="handleToLoginPage" @to-login="handleToLoginPage"
@to-logout="handleToLogoutPage"
@to-create-application="handleCreateApplicationPage" @to-create-application="handleCreateApplicationPage"
@update-auto-playing="handleUpdateAutoPlaying"
/> />
<div class="flex h-[calc(100%-48px)] w-full flex-col bg-[#f2f5f9]"> <div class="main-container">
<div class="mt-5 flex select-none justify-end px-4"> <div v-if="messageList.size === 0" class="w-full flex-1 overflow-auto">
<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">
<Preamble :agent-application-config="agentApplicationConfig" /> <Preamble :agent-application-config="agentApplicationConfig" />
</div> </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"> <div class="relative flex-1 overflow-auto">
<MessageList <MessageList
ref="messageListRef" ref="messageListRef"
...@@ -397,7 +412,7 @@ function handleExitPage() { ...@@ -397,7 +412,7 @@ function handleExitPage() {
</div> </div>
</div> </div>
<div class="footer-operation px-4"> <div class="footer-container">
<FooterInput <FooterInput
ref="footerInputRef" ref="footerInputRef"
v-model:is-answer-response-loading="isAnswerResponseLoading" v-model:is-answer-response-loading="isAnswerResponseLoading"
...@@ -429,9 +444,19 @@ function handleExitPage() { ...@@ -429,9 +444,19 @@ function handleExitPage() {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.footer-operation { .share-mobile-container {
padding-bottom: constant(safe-area-inset-bottom); @apply bg-px-share-h5_bg-png h-full w-full bg-cover bg-no-repeat;
padding-bottom: env(safe-area-inset-bottom);
.main-container {
@apply flex h-[calc(100%-58px)] w-full flex-col;
}
.footer-container {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
@apply bg-white px-[10px] py-[7px];
}
} }
@include custom-scrollbar(4px); @include custom-scrollbar(4px);
......
...@@ -44,6 +44,7 @@ declare namespace I18n { ...@@ -44,6 +44,7 @@ declare namespace I18n {
publish_success_message: string publish_success_message: string
clear_success_message: string clear_success_message: string
add_success_message: string add_success_message: string
reset_success_message: string
loading: string loading: string
updating: string updating: string
successful_update: string successful_update: string
...@@ -151,6 +152,7 @@ declare namespace I18n { ...@@ -151,6 +152,7 @@ declare namespace I18n {
not_certified_yet: string not_certified_yet: string
authenticated: string authenticated: string
cancel_authorization: string cancel_authorization: string
get_code: string
dialogue_module: { dialogue_module: {
continue_question_message: string continue_question_message: string
...@@ -217,6 +219,7 @@ declare namespace I18n { ...@@ -217,6 +219,7 @@ declare namespace I18n {
order_manage: string order_manage: string
data_statistic: string data_statistic: string
plugin_center: string plugin_center: string
reset_password: string
} }
login_module: { login_module: {
...@@ -233,6 +236,29 @@ declare namespace I18n { ...@@ -233,6 +236,29 @@ declare namespace I18n {
login_success: string login_success: string
get_verification_code: string get_verification_code: string
other_login_methods: 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
please_review_and_accept_the_agreement: string
agree_and_continue: string
disagree: 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: { home_module: {
......
import { defineConfig } from 'unocss' import { defineConfig, transformerDirectives, presetUno } from 'unocss'
export default defineConfig({ export default defineConfig({
rules: [ rules: [
...@@ -87,4 +87,6 @@ export default defineConfig({ ...@@ -87,4 +87,6 @@ export default defineConfig({
shortcuts: { shortcuts: {
'flex-center': 'flex items-center justify-center', 'flex-center': 'flex items-center justify-center',
}, },
presets: [presetUno()],
transformers: [transformerDirectives()],
}) })
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