feat: 分享对话

This commit is contained in:
wangdan-fit2cloud 2023-11-30 18:50:42 +08:00
parent b71ce75b3a
commit 112e4b568d
13 changed files with 224 additions and 86 deletions

View File

@ -32,6 +32,7 @@
"pinia": "^2.1.6", "pinia": "^2.1.6",
"pinyin-pro": "^3.18.2", "pinyin-pro": "^3.18.2",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
}, },
"devDependencies": { "devDependencies": {

View File

@ -29,32 +29,6 @@ const getApplication: (param: pageRequest) => Promise<Result<any>> = (param) =>
) )
} }
/**
* Id
* @param
* {
"model_id": "string",
"multiple_rounds_dialogue": true,
"dataset_id_list": [
"string"
]
}
*/
const postChatOpen: (data: ApplicationFormType) => Promise<Result<any>> = (data) => {
return post(`${prefix}/chat/open`, data)
}
/**
*
* @param
* chat_id: string
* {
"message": "string",
}
*/
const postChatMessage: (chat_id: string, message: string) => Promise<any> = (chat_id, message) => {
return postStream(`/api/${prefix}/chat_message/${chat_id}`, { message })
}
/** /**
* *
* @param * @param
@ -156,16 +130,85 @@ const getAccessToken: (applicaiton_id: string, loading?: Ref<boolean>) => Promis
return get(`${prefix}/${applicaiton_id}/access_token`, undefined, loading) return get(`${prefix}/${applicaiton_id}/access_token`, undefined, loading)
} }
/**
*
* @param
{
"access_token": "string"
}
*/
const postAppAuthentication: (access_token: string, loading?: Ref<boolean>) => Promise<any> = (
access_token,
loading
) => {
return post(`${prefix}/authentication`, { access_token }, undefined, loading)
}
/**
*
* @param
{
"access_token": "string"
}
*/
const getProfile: (loading?: Ref<boolean>) => Promise<any> = (loading) => {
return get(`${prefix}/profile`, undefined, loading)
}
/**
* Id
* @param
* {
"model_id": "string",
"multiple_rounds_dialogue": true,
"dataset_id_list": [
"string"
]
}
*/
const postChatOpen: (data: ApplicationFormType) => Promise<Result<any>> = (data) => {
return post(`${prefix}/chat/open`, data)
}
/**
* Id
* @param
* {
"model_id": "string",
"multiple_rounds_dialogue": true,
"dataset_id_list": [
"string"
]
}
*/
const getChatOpen: (applicaiton_id: String) => Promise<Result<any>> = (applicaiton_id) => {
return get(`${prefix}/${applicaiton_id}/chat/open`)
}
/**
*
* @param
* chat_id: string
* {
"message": "string",
}
*/
const postChatMessage: (chat_id: string, message: string) => Promise<any> = (chat_id, message) => {
return postStream(`/api/${prefix}/chat_message/${chat_id}`, { message })
}
export default { export default {
getAllAppilcation, getAllAppilcation,
getApplication, getApplication,
postApplication, postApplication,
putApplication, putApplication,
postChatOpen, postChatOpen,
getChatOpen,
postChatMessage, postChatMessage,
delApplication, delApplication,
getApplicationDetail, getApplicationDetail,
getApplicationDataset, getApplicationDataset,
getAPIKey, getAPIKey,
getAccessToken getAccessToken,
postAppAuthentication,
getProfile
} }

View File

@ -107,14 +107,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, nextTick, onUpdated, computed } from 'vue' import { ref, nextTick, onUpdated, computed } from 'vue'
import { useRoute } from 'vue-router'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import { ChatManagement, type chatType } from '@/api/type/application' import { ChatManagement, type chatType } from '@/api/type/application'
import { randomId } from '@/utils/utils' import { randomId } from '@/utils/utils'
const route = useRoute()
const {
params: { accessToken }
} = route as any
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object, type: Object,
default: () => {} default: () => {}
} },
appId: String
}) })
const scrollDiv = ref() const scrollDiv = ref()
@ -125,7 +132,7 @@ const chartOpenId = ref('')
const chatList = ref<chatType[]>([]) const chatList = ref<chatType[]>([])
const isDisabledChart = computed( const isDisabledChart = computed(
() => !(inputValue.value && props.data?.name && props.data?.model_id) () => !(inputValue.value && (props.appId || (props.data?.name && props.data?.model_id)))
) )
function quickProblemHandel(val: string) { function quickProblemHandel(val: string) {
@ -160,15 +167,27 @@ function getChartOpenId() {
dataset_id_list: props.data.dataset_id_list, dataset_id_list: props.data.dataset_id_list,
multiple_rounds_dialogue: props.data.multiple_rounds_dialogue multiple_rounds_dialogue: props.data.multiple_rounds_dialogue
} }
applicationApi if (props.appId) {
.postChatOpen(obj) applicationApi
.then((res) => { .getChatOpen(props.appId)
chartOpenId.value = res.data .then((res) => {
chatMessage() chartOpenId.value = res.data
}) chatMessage()
.catch(() => { })
loading.value = false .catch(() => {
}) loading.value = false
})
} else {
applicationApi
.postChatOpen(obj)
.then((res) => {
chartOpenId.value = res.data
chatMessage()
})
.catch(() => {
loading.value = false
})
}
} }
function chatMessage() { function chatMessage() {
loading.value = true loading.value = true

View File

@ -29,7 +29,7 @@ export default {
app.component(ReadWrite.name, ReadWrite) app.component(ReadWrite.name, ReadWrite)
app.component(TagEllipsis.name, TagEllipsis) app.component(TagEllipsis.name, TagEllipsis)
app.component(CommonList.name, CommonList) app.component(CommonList.name, CommonList)
app.component(dynamicsForm.name, dynamicsForm)
app.component(MarkdownRenderer.name, MarkdownRenderer) app.component(MarkdownRenderer.name, MarkdownRenderer)
app.component(dynamicsForm.name, dynamicsForm)
} }
} }

View File

@ -7,14 +7,13 @@ import {
type RouteRecordRaw, type RouteRecordRaw,
type RouteRecordName type RouteRecordName
} from 'vue-router' } from 'vue-router'
import useStore from '@/stores'; import useStore from '@/stores'
import { routes } from '@/router/routes' import { routes } from '@/router/routes'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: routes routes: routes
}) })
// 路由前置拦截器 // 路由前置拦截器
router.beforeEach( router.beforeEach(
async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
@ -22,8 +21,8 @@ router.beforeEach(
next() next()
return return
} }
const { user } = useStore(); const { user } = useStore()
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password'] const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password', 'Chat']
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) { if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
const token = user.getToken() const token = user.getToken()

View File

@ -45,6 +45,20 @@ const useApplicationStore = defineStore({
reject(error) reject(error)
}) })
}) })
},
async asyncAppAuthentication(token: string, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
applicationApi
.postAppAuthentication(token, loading)
.then((res) => {
localStorage.setItem('accessToken', res.data)
resolve(res)
})
.catch((error) => {
reject(error)
})
})
} }
} }
}) })

View File

@ -3,6 +3,7 @@ import type { User } from '@/api/type/user'
import UserApi from '@/api/user' import UserApi from '@/api/user'
export interface userStateTypes { export interface userStateTypes {
userType: number // 1 系统操作者 2 对话用户
userInfo: User | null userInfo: User | null
token: any token: any
} }
@ -10,6 +11,7 @@ export interface userStateTypes {
const useUserStore = defineStore({ const useUserStore = defineStore({
id: 'user', id: 'user',
state: (): userStateTypes => ({ state: (): userStateTypes => ({
userType: 1,
userInfo: null, userInfo: null,
token: '' token: ''
}), }),
@ -18,7 +20,9 @@ const useUserStore = defineStore({
if (this.token) { if (this.token) {
return this.token return this.token
} }
return localStorage.getItem('token') return this.userType === 1
? localStorage.getItem('token')
: localStorage.getItem('accessToken')
}, },
getPermissions() { getPermissions() {
@ -35,7 +39,9 @@ const useUserStore = defineStore({
return '' return ''
} }
}, },
changeUserType(num: number) {
this.userType = num
},
async profile() { async profile() {
return UserApi.profile().then((ok) => { return UserApi.profile().then((ok) => {
this.userInfo = ok.data this.userInfo = ok.data

15
ui/src/utils/clipboard.ts Normal file
View File

@ -0,0 +1,15 @@
import Clipboard from 'vue-clipboard3'
import { MsgSuccess, MsgError } from '@/utils/message'
/*
*/
export async function copyClick(info: string) {
const { toClipboard } = Clipboard()
try {
await toClipboard(info)
MsgSuccess('复制成功')
} catch (e) {
console.error(e)
MsgError('复制失败')
}
}

View File

@ -49,3 +49,4 @@ export function realatedObject(list: any, val: string | number, attr: string) {
const filterData: any = list.filter((item: any) => item[attr] === val)?.[0] const filterData: any = list.filter((item: any) => item[attr] === val)?.[0]
return filterData || null return filterData || null
} }

View File

@ -24,7 +24,7 @@
{{ shareUrl }} {{ shareUrl }}
</span> </span>
<el-button type="primary" text> <el-button type="primary" text @click="copyClick(shareUrl)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon> <el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button> </el-button>
</div> </div>
@ -56,12 +56,12 @@
</el-col> --> </el-col> -->
</el-row> </el-row>
<div class="mt-16"> <div class="mt-16">
<el-button type="primary"> 演示 </el-button> <el-button type="primary"><a :href="shareUrl" target="_blank">演示</a></el-button>
<el-button @click="openDialog"> 嵌入第三方 </el-button> <el-button @click="openDialog"> 嵌入第三方 </el-button>
</div> </div>
</el-card> </el-card>
</div> </div>
<EmbedDialog ref="EmbedDialogRef" /> <EmbedDialog ref="EmbedDialogRef" :accessToken="accessToken" />
</LayoutContainer> </LayoutContainer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -69,6 +69,7 @@ import { reactive, ref, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import EmbedDialog from './components/EmbedDialog.vue' import EmbedDialog from './components/EmbedDialog.vue'
import { copyClick } from '@/utils/clipboard'
import useStore from '@/stores' import useStore from '@/stores'
const { application } = useStore() const { application } = useStore()
const router = useRouter() const router = useRouter()
@ -76,6 +77,7 @@ const route = useRoute()
const EmbedDialogRef = ref() const EmbedDialogRef = ref()
const shareUrl = ref('') const shareUrl = ref('')
const accessToken = ref('')
const detail = ref<any>(null) const detail = ref<any>(null)
const apiKey = ref<any>(null) const apiKey = ref<any>(null)
const { const {
@ -89,6 +91,7 @@ function openDialog() {
} }
function getAccessToken() { function getAccessToken() {
application.asyncGetAccessToken(id, loading).then((res) => { application.asyncGetAccessToken(id, loading).then((res) => {
accessToken.value = res?.data?.access_token
shareUrl.value = application.location + res?.data?.access_token shareUrl.value = application.location + res?.data?.access_token
}) })
} }

View File

@ -7,7 +7,7 @@
<div class="code border-t p-16"> <div class="code border-t p-16">
<div class="flex-between"> <div class="flex-between">
<span class="bold">复制以下代码进行嵌入</span> <span class="bold">复制以下代码进行嵌入</span>
<el-button text> <el-button text @click="copyClick(source1)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon> <el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button> </el-button>
</div> </div>
@ -23,7 +23,7 @@
<div class="code border-t p-16"> <div class="code border-t p-16">
<div class="flex-between"> <div class="flex-between">
<span class="bold">复制以下代码进行嵌入</span> <span class="bold">复制以下代码进行嵌入</span>
<el-button text> <el-button text @click="copyClick(source2)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon> <el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button> </el-button>
</div> </div>
@ -38,20 +38,20 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { copyClick } from '@/utils/clipboard'
import useStore from '@/stores'
const { application } = useStore()
const props = defineProps({ const props = defineProps({
data: { accessToken: String
type: Array<any>,
default: () => []
}
}) })
const emit = defineEmits(['addData']) const emit = defineEmits(['addData'])
const loading = ref(false) const loading = ref(false)
const dialogVisible = ref<boolean>(false) const dialogVisible = ref<boolean>(false)
const source1 = ref(`<iframe const source1 = ref(`<iframe
src="https://udify.app/chatbot/ASkyzvhN5Z1h6k7g" src="${application.location + props.accessToken}"
style="width: 100%; height: 100%;" style="width: 100%; height: 100%;"
frameborder="0" frameborder="0"
allow="microphone"> allow="microphone">
@ -59,11 +59,11 @@ allow="microphone">
`) `)
const source2 = ref(`<script> window.difyChatbotConfig = { const source2 = ref(`<script> window.difyChatbotConfig = {
token: '85FfbbzTpXzzr40X' token: "${props.accessToken}"
} }
<\/script> <\/script>
<script src="https://udify.app/embed.min.js" <script src="https://udify.app/embed.min.js"
id="85FfbbzTpXzzr40X" id="${props.accessToken}"
defer> defer>
<\/script> <\/script>
`) `)

View File

@ -44,7 +44,7 @@
<template #footer> <template #footer>
<div class="footer-content"> <div class="footer-content">
<el-tooltip effect="dark" content="演示" placement="top"> <el-tooltip effect="dark" content="演示" placement="top">
<el-button text @click.stop> <el-button text @click.stop @click="getAccessToken(item.id)">
<AppIcon iconName="app-view"></AppIcon> <AppIcon iconName="app-view"></AppIcon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
@ -71,7 +71,9 @@
<span>运行中</span> <span>运行中</span>
<!-- <el-switch v-model="item.status" @change="changeState($event, item)" /> --> <!-- <el-switch v-model="item.status" @change="changeState($event, item)" /> -->
</div> </div>
<el-dropdown-item divided @click="deleteApplication(item)">删除</el-dropdown-item> <el-dropdown-item divided @click="deleteApplication(item)"
>删除</el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -88,8 +90,10 @@
import { ref, onMounted, reactive } from 'vue' import { ref, onMounted, reactive } from 'vue'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import type { pageRequest } from '@/api/type/common' import type { pageRequest } from '@/api/type/common'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import useStore from '@/stores'
const { application } = useStore()
const router = useRouter() const router = useRouter()
const loading = ref(false) const loading = ref(false)
@ -109,29 +113,31 @@ function search() {
getList() getList()
} }
function deleteApplication(row: any) { function getAccessToken(id: string) {
MsgConfirm( application.asyncGetAccessToken(id, loading).then((res) => {
`是否删除应用:${row.name} ?`, window.open(application.location + res?.data?.access_token)
`删除后该应用将不再提供服务,请谨慎操作。`, })
{ }
confirmButtonText: '删除',
confirmButtonClass: 'danger' function deleteApplication(row: any) {
} MsgConfirm(`是否删除应用:${row.name} ?`, `删除后该应用将不再提供服务,请谨慎操作。`, {
) confirmButtonText: '删除',
.then(() => { confirmButtonClass: 'danger'
loading.value = true })
applicationApi .then(() => {
.delApplication(row.id) loading.value = true
.then(() => { applicationApi
MsgSuccess('删除成功') .delApplication(row.id)
getList() .then(() => {
}) MsgSuccess('删除成功')
.catch(() => { getList()
loading.value = false })
}) .catch(() => {
}) loading.value = false
.catch(() => {}) })
} })
.catch(() => {})
}
// function changeState(bool: Boolean, row: any) { // function changeState(bool: Boolean, row: any) {
// const obj = { // const obj = {

View File

@ -1,13 +1,44 @@
<template> <template>
<div class="chat"> <div class="chat">
<div class="chat__header"> <div class="chat__header">
<div class="chat-width"><h2 class="ml-24">111</h2></div> <div class="chat-width">
<h2 class="ml-24">{{ applicationDetail?.name }}</h2>
</div>
</div>
<div class="chat__main chat-width" v-loading="loading">
<AiDialog :data="applicationDetail" :appId="applicationDetail?.id"></AiDialog>
</div> </div>
<div class="chat__main chat-width"><AiDialog></AiDialog></div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import AiDialog from '@/components/ai-dialog/index.vue' import AiDialog from '@/components/ai-dialog/index.vue'
import applicationApi from '@/api/application'
import useStore from '@/stores'
const route = useRoute()
const {
params: { accessToken }
} = route as any
const { application, user } = useStore()
const loading = ref(false)
const applicationDetail = ref<any>({})
function getAccessToken(token: string) {
application.asyncAppAuthentication(token, loading).then((res) => {})
}
function getProfile() {
applicationApi.getProfile(loading).then((res) => {
applicationDetail.value = res.data
})
}
onMounted(() => {
user.changeUserType(2)
getAccessToken(accessToken)
getProfile()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat { .chat {