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",
"pinyin-pro": "^3.18.2",
"vue": "^3.3.4",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.2.4"
},
"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
@ -156,16 +130,85 @@ const getAccessToken: (applicaiton_id: string, loading?: Ref<boolean>) => Promis
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 {
getAllAppilcation,
getApplication,
postApplication,
putApplication,
postChatOpen,
getChatOpen,
postChatMessage,
delApplication,
getApplicationDetail,
getApplicationDataset,
getAPIKey,
getAccessToken
getAccessToken,
postAppAuthentication,
getProfile
}

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@
{{ shareUrl }}
</span>
<el-button type="primary" text>
<el-button type="primary" text @click="copyClick(shareUrl)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button>
</div>
@ -56,12 +56,12 @@
</el-col> -->
</el-row>
<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>
</div>
</el-card>
</div>
<EmbedDialog ref="EmbedDialogRef" />
<EmbedDialog ref="EmbedDialogRef" :accessToken="accessToken" />
</LayoutContainer>
</template>
<script setup lang="ts">
@ -69,6 +69,7 @@ import { reactive, ref, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import applicationApi from '@/api/application'
import EmbedDialog from './components/EmbedDialog.vue'
import { copyClick } from '@/utils/clipboard'
import useStore from '@/stores'
const { application } = useStore()
const router = useRouter()
@ -76,6 +77,7 @@ const route = useRoute()
const EmbedDialogRef = ref()
const shareUrl = ref('')
const accessToken = ref('')
const detail = ref<any>(null)
const apiKey = ref<any>(null)
const {
@ -89,6 +91,7 @@ function openDialog() {
}
function getAccessToken() {
application.asyncGetAccessToken(id, loading).then((res) => {
accessToken.value = 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="flex-between">
<span class="bold">复制以下代码进行嵌入</span>
<el-button text>
<el-button text @click="copyClick(source1)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button>
</div>
@ -23,7 +23,7 @@
<div class="code border-t p-16">
<div class="flex-between">
<span class="bold">复制以下代码进行嵌入</span>
<el-button text>
<el-button text @click="copyClick(source2)">
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
</el-button>
</div>
@ -38,20 +38,20 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { copyClick } from '@/utils/clipboard'
import useStore from '@/stores'
const { application } = useStore()
const props = defineProps({
data: {
type: Array<any>,
default: () => []
}
accessToken: String
})
const emit = defineEmits(['addData'])
const loading = ref(false)
const dialogVisible = ref<boolean>(false)
const source1 = ref(`<iframe
src="https://udify.app/chatbot/ASkyzvhN5Z1h6k7g"
src="${application.location + props.accessToken}"
style="width: 100%; height: 100%;"
frameborder="0"
allow="microphone">
@ -59,11 +59,11 @@ allow="microphone">
`)
const source2 = ref(`<script> window.difyChatbotConfig = {
token: '85FfbbzTpXzzr40X'
token: "${props.accessToken}"
}
<\/script>
<script src="https://udify.app/embed.min.js"
id="85FfbbzTpXzzr40X"
id="${props.accessToken}"
defer>
<\/script>
`)

View File

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

View File

@ -1,13 +1,44 @@
<template>
<div class="chat">
<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 class="chat__main chat-width"><AiDialog></AiDialog></div>
</div>
</template>
<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 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>
<style lang="scss" scoped>
.chat {