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", "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

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

View File

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

View File

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

View File

@ -2,18 +2,10 @@
<div class="common-list"> <div class="common-list">
<el-scrollbar> <el-scrollbar>
<ul v-if="data.length > 0"> <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"> <template v-for="(item, index) in data" :key="index">
<li <li
@click.prevent="clickHandle(item)" @click.prevent="clickHandle(item, index)"
:class="modelValue === item ? 'active' : ''" :class="current === index ? 'active' : ''"
class="cursor" class="cursor"
> >
<slot :row="item" :index="index"> </slot> <slot :row="item" :index="index"> </slot>
@ -32,8 +24,6 @@ defineOptions({ name: 'CommonList' })
withDefaults( withDefaults(
defineProps<{ defineProps<{
modelValue?: any
data: Array<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('click', row)
emit('update:modelValue', row)
} }
</script> </script>
<style lang="scss" scoped> <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"> <template v-for="(item, index) in list" :key="index">
<div :class="item.id === id ? 'dropdown-active' : ''"> <div :class="item.id === id ? 'dropdown-active' : ''">
<el-dropdown-item :command="item.id"> <el-dropdown-item :command="item.id">
<div class="flex"> <div class="flex align-center">
<AppAvatar class="mr-12" shape="square" :size="24"> <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="" /> <img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar> </AppAvatar>
<span class="ellipsis-1"> {{ item?.name }}</span> <span class="ellipsis-1"> {{ item?.name }}</span>
</div> </div>
</el-dropdown-item> </el-dropdown-item>

View File

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

View File

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

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

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

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

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

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

View File

@ -7,8 +7,8 @@
<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> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</div> </div>
<div class="mt-8"> <div class="mt-8">
@ -23,8 +23,8 @@
<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> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</div> </div>
<div class="mt-8"> <div class="mt-8">
@ -38,48 +38,45 @@
</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 props = defineProps({ const { application } = useStore()
data: {
type: Array<any>,
default: () => []
}
})
const emit = defineEmits(['addData']) const emit = defineEmits(['addData'])
const loading = ref(false)
const dialogVisible = ref<boolean>(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%;" style="width: 100%; height: 100%;"
frameborder="0" frameborder="0"
allow="microphone"> allow="microphone">
</iframe> </iframe>
`) `
source2.value = `<script> window.difyChatbotConfig = {
const source2 = ref(`<script> window.difyChatbotConfig = { token: "${val}"
token: '85FfbbzTpXzzr40X'
} }
<\/script> <\/script>
<script src="https://udify.app/embed.min.js" <script src="https://udify.app/embed.min.js"
id="85FfbbzTpXzzr40X" id="${val}"
defer> defer>
<\/script> <\/script>
`) `
watch(dialogVisible, (bool) => {
if (!bool) {
loading.value = false
}
})
const open = (checked: any) => {
dialogVisible.value = true dialogVisible.value = true
} }
const submitHandle = () => {
dialogVisible.value = false
}
defineExpose({ open }) defineExpose({ open })
</script> </script>

View File

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

View File

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