This commit is contained in:
shaohuzhang1 2023-12-01 17:36:13 +08:00
commit 9af53c8821
23 changed files with 555 additions and 207 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

@ -0,0 +1,51 @@
<template>
<div>
<el-tooltip effect="dark" content="重新生成" placement="top">
<el-button text @click.stop>
<AppIcon iconName="VideoPlay"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="复制" placement="top">
<el-button text @click="copyClick(item?.answer_text)">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="赞同" placement="top">
<el-button text>
<AppIcon iconName="app-like"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="取消赞同" placement="top">
<el-button text>
<AppIcon iconName="app-like-color"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="反对" placement="top">
<el-button text>
<AppIcon iconName="app-oppose"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="取消反对" placement="top">
<el-button text>
<AppIcon iconName="app-oppose-color"></AppIcon>
</el-button>
</el-tooltip>
</div>
</template>
<script setup lang="ts">
import { copyClick } from '@/utils/clipboard'
defineProps({
data: {
type: Object,
default: () => {}
}
})
const emit = defineEmits(['update:data'])
</script>
<style lang="scss" scoped></style>

View File

@ -64,22 +64,24 @@
:inner_suffix="false"
></MarkdownRenderer>
</el-card>
<el-button
type="primary"
v-if="item.is_stop && !item.write_ed"
@click="startChat(item)"
link
class="mt-8"
>继续</el-button
>
<el-button
type="primary"
v-else-if="!item.write_ed"
@click="stopChat(item)"
link
class="mt-8"
>停止回答</el-button
>
<div class="flex-between mt-8">
<div>
<el-button
type="primary"
v-if="item.is_stop && !item.write_ed"
@click="startChat(item)"
link
>继续</el-button
>
<el-button type="primary" v-else-if="!item.write_ed" @click="stopChat(item)" link
>停止回答</el-button
>
</div>
<!-- <div v-if="item.write_ed && props.appId">
<OperationButton :data="item" />
</div> -->
</div>
</div>
</div>
</template>
@ -107,6 +109,7 @@
</template>
<script setup lang="ts">
import { ref, nextTick, onUpdated, computed } from 'vue'
import OperationButton from './OperationButton.vue'
import applicationApi from '@/api/application'
import { ChatManagement, type chatType } from '@/api/type/application'
import { randomId } from '@/utils/utils'
@ -114,7 +117,8 @@ const props = defineProps({
data: {
type: Object,
default: () => {}
}
},
appId: String
})
const scrollDiv = ref()
@ -125,7 +129,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 +164,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
@ -188,10 +204,12 @@ function chatMessage() {
applicationApi.postChatMessage(chartOpenId.value, problem_text).then(async (response) => {
inputValue.value = ''
const row = chatList.value.find((item) => item.id === id)
if (row) {
ChatManagement.addChatRecord(row, 50, loading)
ChatManagement.write(id)
const reader = response.body.getReader()
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
const { done, value } = await reader.read()
if (done) {
@ -203,11 +221,14 @@ function chatMessage() {
const str = decoder.decode(value, { stream: true })
if (str && str.startsWith('data:')) {
const content = JSON?.parse(str.replace('data:', ''))?.content
if (content) {
ChatManagement.append(id, content)
}
}
} catch (e) {}
} catch (e) {
// console
}
}
}
})

View File

@ -8,7 +8,7 @@
</el-avatar>
</template>
<script setup lang="ts">
import { pinyin } from 'pinyin-pro';
import { pinyin } from 'pinyin-pro'
import { computed } from 'vue'
defineOptions({ name: 'AppAvatar' })
const props = defineProps({
@ -26,37 +26,31 @@ const firstUserName = computed(() => {
return props.name?.substring(0, 1)
})
function getAvatarColour(name: string) {
const charIndex = pinyin.getFullChars(name).charAt(0).toUpperCase().charCodeAt(0) - 65
const getAvatarColour = (name: string) => {
const colours = [
'#ACA9E5',
'#BCC934',
'#B3CFE8',
'#DCDEB5',
'#D65A4A',
'#E0C78B',
'#E59191',
'#E99334',
'#FF6632',
'#F4B7EF',
'#F7D407',
'#F8BB98',
'#2BCBB1',
'#3594F1',
'#486660',
'#4B689F',
'#5976F6',
'#72B1B2',
'#778293',
'#7D6624',
'#82CBB5',
'#837F6A',
'#87B087',
'#9AC0C4',
'#958E55',
'#99E4F2'
'#3370FF',
'#4954E6',
'#F54A45',
'#00B69D',
'#2CA91F',
'#98B600',
'#F80F80',
'#D136D1',
'#F01D94',
'#7F3BF5',
'#8F959E'
]
return colours[charIndex]
let charIndex = name ? pinyin(name).charAt(0).toUpperCase().charCodeAt(0) - 65 : 0
function getColor() {
if (!colours[charIndex]) {
charIndex -= 10
getColor()
}
return colours[charIndex]
}
return getColor()
}
</script>
<style lang="scss" scoped></style>

View File

@ -3,10 +3,11 @@
<div class="card-header">
<slot name="header">
<div class="title flex align-center">
<AppAvatar v-if="!slots.icon && showIcon" class="mr-12" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<slot v-else name="icon"> </slot>
<slot name="icon">
<AppAvatar v-if="showIcon" class="mr-12" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
</slot>
<h4 class="ellipsis-1" style="width: 100%">{{ title }}</h4>
</div>
</slot>

View File

@ -2,18 +2,10 @@
<div class="common-list">
<el-scrollbar>
<ul v-if="data.length > 0">
<li
v-if="slots.prefix"
@click="clickHandle()"
:class="modelValue === undefined || modelValue === null ? 'active' : ''"
class="cursor"
>
<slot name="prefix"> </slot>
</li>
<template v-for="(item, index) in data" :key="index">
<li
@click.prevent="clickHandle(item)"
:class="modelValue === item ? 'active' : ''"
@click.prevent="clickHandle(item, index)"
:class="current === index ? 'active' : ''"
class="cursor"
>
<slot :row="item" :index="index"> </slot>
@ -32,8 +24,6 @@ defineOptions({ name: 'CommonList' })
withDefaults(
defineProps<{
modelValue?: any
data: Array<any>
}>(),
{
@ -41,11 +31,13 @@ withDefaults(
}
)
const emit = defineEmits(['click', 'update:modelValue'])
const emit = defineEmits(['click'])
function clickHandle(row?: any) {
const current = ref(0)
function clickHandle(row: any, index: number) {
current.value = index
emit('click', row)
emit('update:modelValue', row)
}
</script>
<style lang="scss" scoped>

View File

@ -308,5 +308,139 @@ export const iconMap: any = {
)
])
}
}
},
'app-restore': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M3.33333 5.3335V13.3335H10V5.3335H3.33333ZM11.3333 4.66683V14.0742C11.3333 14.4015 11.0548 14.6668 10.7111 14.6668H2.62222C2.27858 14.6668 2 14.4015 2 14.0742V4.59276C2 4.26548 2.27858 4.00016 2.62222 4.00016H10.6667C11.0349 4.00016 11.3333 4.29864 11.3333 4.66683ZM13.8047 1.52876C13.9254 1.6494 14 1.81607 14 2.00016V10.3335C14 10.5176 13.8508 10.6668 13.6667 10.6668H13C12.8159 10.6668 12.6667 10.5176 12.6667 10.3335V2.66683H6.33333C6.14924 2.66683 6 2.51759 6 2.3335V1.66683C6 1.48273 6.14924 1.3335 6.33333 1.3335H13.3333C13.5174 1.3335 13.6841 1.40812 13.8047 1.52876Z',
fill: 'currentColor'
})
]
)
])
}
},
'app-copy': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M3.33333 5.3335V13.3335H10V5.3335H3.33333ZM11.3333 4.66683V14.0742C11.3333 14.4015 11.0548 14.6668 10.7111 14.6668H2.62222C2.27858 14.6668 2 14.4015 2 14.0742V4.59276C2 4.26548 2.27858 4.00016 2.62222 4.00016H10.6667C11.0349 4.00016 11.3333 4.29864 11.3333 4.66683ZM13.8047 1.52876C13.9254 1.6494 14 1.81607 14 2.00016V10.3335C14 10.5176 13.8508 10.6668 13.6667 10.6668H13C12.8159 10.6668 12.6667 10.5176 12.6667 10.3335V2.66683H6.33333C6.14924 2.66683 6 2.51759 6 2.3335V1.66683C6 1.48273 6.14924 1.3335 6.33333 1.3335H13.3333C13.5174 1.3335 13.6841 1.40812 13.8047 1.52876Z',
fill: 'currentColor'
})
]
)
])
}
},
'app-like': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M2.00518 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00004 5.33317 1.98014 14.6874 2.00518 14.6608ZM9.70096 5.28984H12.5717C14.5687 5.28984 15.0274 7.05264 14.5687 8.37353L12.5717 13.6308C12.4029 14.2423 11.8409 14.6665 11.1995 14.6665H3.33882C3.154 14.6665 3.00418 14.5167 3.00418 14.3319V5.62448C3.00418 5.43966 3.154 5.28984 3.33882 5.28984H4.02656C4.24449 5.28984 4.44877 5.18374 4.5741 5.00545L7.35254 1.05296C7.5406 0.753754 8.04824 0.52438 8.5893 0.770777C9.40089 1.14037 10.3724 1.94718 10.3724 3.28394C10.3724 3.78809 10.1486 4.45673 9.70096 5.28984ZM12.5717 6.62841H7.46215L8.52183 4.65626C8.87422 4.00045 9.03388 3.52351 9.03388 3.28394C9.03388 2.89556 8.9524 2.45627 8.25544 2.09612L5.26934 6.34402C5.14401 6.5223 4.93973 6.62841 4.72181 6.62841H4.34275V13.3279H11.1995C11.2411 13.3279 11.2734 13.3035 11.2813 13.2747L11.298 13.2142L13.3098 7.91815C13.5743 7.13902 13.3105 6.62841 12.5717 6.62841Z',
fill: 'currentColor'
})
]
)
])
}
},
'app-like-color': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M2.00497 14.6608H2.00518C2.00511 14.6609 2.00504 14.6609 2.00497 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00006 5.33305 1.98026 14.6344 2.00497 14.6608Z',
fill: '#FFC60A'
}),
h('path', {
d: 'M12.5717 5.28984H9.70096C10.1486 4.45673 10.3724 3.78809 10.3724 3.28394C10.3724 1.94718 9.40089 1.14037 8.5893 0.770777C8.04824 0.52438 7.5406 0.753754 7.35254 1.05296L4.5741 5.00545C4.44877 5.18374 4.24449 5.28984 4.02656 5.28984H3.33882C3.154 5.28984 3.00418 5.43966 3.00418 5.62448V14.3319C3.00418 14.5167 3.154 14.6665 3.33882 14.6665H11.1995C11.8409 14.6665 12.4029 14.2423 12.5717 13.6308L14.5687 8.37353C15.0274 7.05264 14.5687 5.28984 12.5717 5.28984Z',
fill: '#FFC60A'
})
]
)
])
}
},
'app-oppose': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M2.00518 1.28008H0.666616C0.666616 1.33341 0.666504 10.6667 0.666616 10.65H2.00518C1.99984 10.6667 1.99984 1.33341 2.00518 1.28008ZM9.70097 10.6511H12.5717C14.5687 10.6511 15.0274 8.88828 14.5687 7.56739L12.5717 2.3101C12.4029 1.69862 11.8409 1.27441 11.1996 1.27441H3.33883C3.15401 1.27441 3.00418 1.42424 3.00418 1.60906V10.3164C3.00418 10.5013 3.15401 10.6511 3.33883 10.6511H4.02656C4.24449 10.6511 4.44877 10.7572 4.5741 10.9355L7.35254 14.888C7.5406 15.1872 8.04825 15.4165 8.58931 15.1701C9.40089 14.8005 10.3724 13.9937 10.3724 12.657C10.3724 12.1528 10.1486 11.4842 9.70097 10.6511ZM12.5717 9.31251H7.46216L8.52184 11.2847C8.87422 11.9405 9.03388 12.4174 9.03388 12.657C9.03388 13.0454 8.95241 13.4846 8.25545 13.8448L5.26935 9.5969C5.14402 9.41861 4.93974 9.31251 4.72181 9.31251H4.34275V2.61298H11.1996C11.2411 2.61298 11.2734 2.63737 11.2813 2.6662L11.298 2.72673L13.3098 8.02277C13.5743 8.8019 13.3105 9.31251 12.5717 9.31251Z',
fill: 'currentColor'
}),
]
)
])
}
},
'app-oppose-color': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 16 16',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M9.70106 10.7102H12.5718C14.5688 10.7102 15.0275 8.94736 14.5688 7.62647L12.5718 2.36918C12.403 1.7577 11.841 1.3335 11.1996 1.3335H3.33891C3.1541 1.3335 3.00427 1.48332 3.00427 1.66814V10.3755C3.00427 10.5603 3.1541 10.7102 3.33891 10.7102H4.02665C4.24458 10.7102 4.44886 10.8163 4.57419 10.9945L7.35263 14.947C7.54069 15.2462 8.04834 15.4756 8.58939 15.2292C9.40098 14.8596 10.3725 14.0528 10.3725 12.7161C10.3725 12.2119 10.1487 11.5433 9.70106 10.7102Z',
fill: '#F54A45'
}),
h('path', {
d: 'M2.00004 1.3335H0.661473C0.661473 1.3335 0.660982 10.7764 0.661473 10.7035H2.00001C1.99469 10.6868 1.9947 1.38674 2.00004 1.3335Z',
fill: '#F54A45'
})
]
)
])
}
},
}

View File

@ -11,10 +11,19 @@
<template v-for="(item, index) in list" :key="index">
<div :class="item.id === id ? 'dropdown-active' : ''">
<el-dropdown-item :command="item.id">
<div class="flex">
<AppAvatar class="mr-12" shape="square" :size="24">
<div class="flex align-center">
<AppAvatar
v-if="isApplication"
:name="item.name"
pinyinColor
class="mr-12"
shape="square"
:size="24"
/>
<AppAvatar v-else-if="isDataset" class="mr-12" shape="square" :size="24">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<span class="ellipsis-1"> {{ item?.name }}</span>
</div>
</el-dropdown-item>

View File

@ -2,11 +2,7 @@
<div v-if="!menu.meta || !menu.meta.hidden" class="sidebar-item">
<el-menu-item ref="subMenu" :index="menu.path" popper-class="sidebar-popper">
<template #title>
<AppIcon
v-if="menu.meta && menu.meta.icon"
:iconName="menu.meta.icon"
class="sidebar-icon"
/>
<AppIcon v-if="menu.meta && menu.meta.icon" :iconName="menuIcon" class="sidebar-icon" />
<span v-if="menu.meta && menu.meta.title">{{ menu.meta.title }}</span>
</template>
</el-menu-item>
@ -14,11 +10,20 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { type RouteRecordRaw } from 'vue-router'
defineProps<{
const props = defineProps<{
menu: RouteRecordRaw
activeMenu: any
}>()
const menuIcon = computed(() => {
if (props.activeMenu === props.menu.path) {
return props.menu.meta?.iconActive || props.menu?.meta?.icon
} else {
return props.menu?.meta?.icon
}
})
</script>
<style scoped lang="scss">

View File

@ -10,6 +10,7 @@
v-for="(menu, index) in subMenuList"
:key="index"
:menu="menu"
:activeMenu="activeMenu"
>
</sidebar-item>
</el-menu>

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

@ -28,7 +28,8 @@ const applicationRouter = {
path: 'overview',
name: 'AppOverview',
meta: {
icon: 'Document',
icon: 'app-all-menu',
iconActive: 'app-all-menu-active',
title: '概览',
active: 'overview',
parentPath: '/application/:id',

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

@ -4,9 +4,14 @@
<h4 class="title-decoration-1 mb-16">应用信息</h4>
<el-card shadow="never" class="overview-card">
<div class="title flex align-center">
<AppAvatar class="mr-12" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<AppAvatar
v-if="detail?.name"
:name="detail?.name"
pinyinColor
class="mr-12"
shape="square"
:size="32"
/>
<h4 class="ellipsis-1">{{ detail?.name }}</h4>
<div class="ml-8" v-if="detail">
<el-tag v-if="detail?.status" class="success-tag">运行中</el-tag>
@ -24,8 +29,8 @@
{{ shareUrl }}
</span>
<el-button type="primary" text>
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
<el-button type="primary" text @click="copyClick(shareUrl)">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</div>
</el-col>
@ -37,14 +42,14 @@
</span>
<el-button type="primary" text>
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</div>
<div class="mt-4">
<span class="vertical-middle lighter"> API Secret: ************** </span>
<span>
<el-button type="primary" text>
<el-icon style="font-size: 13px"><CopyDocument /></el-icon>
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</span>
<span>
@ -56,12 +61,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"/>
</LayoutContainer>
</template>
<script setup lang="ts">
@ -69,6 +74,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 +82,7 @@ const route = useRoute()
const EmbedDialogRef = ref()
const shareUrl = ref('')
const accessToken = ref('')
const detail = ref<any>(null)
const apiKey = ref<any>(null)
const {
@ -85,10 +92,11 @@ const {
const loading = ref(false)
function openDialog() {
EmbedDialogRef.value.open()
EmbedDialogRef.value.open(accessToken.value)
}
function getAccessToken() {
application.asyncGetAccessToken(id, loading).then((res) => {
accessToken.value = res?.data?.access_token
shareUrl.value = application.location + res?.data?.access_token
})
}

View File

@ -1,6 +1,6 @@
<template>
<el-dialog title="添加关联数据集" v-model="dialogVisible" width="600">
<template #header="{ close, titleId, titleClass }">
<template #header="{ titleId, titleClass }">
<div class="my-header flex">
<h4 :id="titleId" :class="titleClass">添加关联数据集</h4>
<el-button link class="ml-16" @click="refresh">

View File

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

View File

@ -36,6 +36,16 @@
class="application-card cursor"
@click="router.push({ path: `/application/${item.id}/overview` })"
>
<template #icon>
<AppAvatar
v-if="item.name"
:name="item.name"
pinyinColor
class="mr-12"
shape="square"
:size="32"
/>
</template>
<div class="status-tag">
<el-tag v-if="item.status" class="success-tag">运行中</el-tag>
<el-tag v-else class="warning-tag">已停用</el-tag>
@ -44,7 +54,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 +81,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 +100,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 +123,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,45 @@
<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) => {
getProfile()
})
}
function getProfile() {
applicationApi.getProfile(loading).then((res) => {
applicationDetail.value = res.data
})
}
onMounted(() => {
user.changeUserType(2)
getAccessToken(accessToken)
})
</script>
<style lang="scss" scoped>
.chat {

View File

@ -3,33 +3,31 @@
<div class="template-manage flex main-calc-height">
<div class="template-manage__left p-8 border-r">
<h4 class="p-16">供应商</h4>
<common-list
v-model="active_provider"
:data="provider_list"
class="mt-8"
v-loading="loading"
@click="clickListHandle"
>
<template #prefix>
<div class="flex">
<template #default="{ row, index }">
<div class="flex" v-if="index === 0">
<AppIcon
style="height: 24px; width: 24px"
class="mr-8"
:iconName="active_provider ? 'app-all-menu' : 'app-all-menu-active'"
style="height: 20px; width: 20px"
:iconName="active_provider === row ? 'app-all-menu-active' : 'app-all-menu'"
></AppIcon>
<span>全部模型</span>
</div>
</template>
<template #default="{ row }">
<div class="flex">
<span :innerHTML="row.icon" alt="" style="height: 24px; width: 24px" class="mr-8" />
<div class="flex" v-else>
<span :innerHTML="row.icon" alt="" style="height: 20px; width: 20px" class="mr-8" />
<span>{{ row.name }}</span>
</div>
</template>
</common-list>
</div>
<div class="template-manage__right p-24" v-loading="list_model_loading">
<h3 v-if="active_provider">{{ active_provider.name }}</h3>
<h3 v-else>全部模型</h3>
<h3>{{ active_provider?.name }}</h3>
<div class="flex-between mt-8">
<el-button type="primary" @click="openCreateModel(active_provider)">创建模型</el-button>
<el-input
@ -66,6 +64,12 @@ import { splitArray } from '@/utils/common'
import CreateModel from '@/views/template/component/CreateModel.vue'
import SelectProvider from '@/views/template/component/SelectProvider.vue'
const allObj = {
icon: '',
provider: '',
name: '全部模型'
}
const loading = ref<boolean>(false)
const active_provider = ref<Provider>()
@ -81,8 +85,13 @@ const model_split_list = computed(() => {
const createModelRef = ref<InstanceType<typeof CreateModel>>()
const selectProviderRef = ref<InstanceType<typeof SelectProvider>>()
const clickListHandle = (item: Provider) => {
active_provider.value = item
list_model()
}
const openCreateModel = (provider?: Provider) => {
if (provider) {
if (provider && provider.provider) {
createModelRef.value?.open(provider)
} else {
selectProviderRef.value?.open()
@ -90,19 +99,17 @@ const openCreateModel = (provider?: Provider) => {
}
const list_model = () => {
const params = active_provider.value ? { provider: active_provider.value.provider } : {}
const params = active_provider.value?.provider ? { provider: active_provider.value.provider } : {}
ModelApi.getModel({ ...model_search_form.value, ...params }, list_model_loading).then((ok) => {
model_list.value = ok.data
})
}
watch(active_provider, list_model, {
immediate: true
})
onMounted(() => {
ModelApi.getProvider(loading).then((ok) => {
provider_list.value = [...ok.data]
active_provider.value = allObj
provider_list.value = [allObj, ...ok.data]
list_model()
})
})
</script>