feat: 工作流编排

This commit is contained in:
wangdan-fit2cloud 2024-06-04 18:53:43 +08:00
parent 7f269dc4d0
commit b45a82c519
29 changed files with 363 additions and 297 deletions

View File

@ -1,7 +1,7 @@
<template>
<el-card shadow="never" class="card-add">
<div class="flex-center">
<AppIcon iconName="Plus" class="add-icon p-8" />
<AppIcon iconName="Plus" class="add-icon p-8 border-r-4 layout-bg" />
<span>{{ title }}</span>
</div>
</el-card>
@ -30,9 +30,7 @@ defineProps({
.add-icon {
font-size: 14px;
border-radius: 4px;
border: 1px solid var(--app-border-color-dark);
background: var(--app-layout-bg-color);
margin-right: 12px;
}
&:hover {

View File

@ -21,7 +21,7 @@
</el-card>
<el-card shadow="never" class="card-add box-card" @click="add_card">
<div class="flex-center">
<AppIcon iconName="Plus" class="add-icon p-8" />
<AppIcon iconName="Plus" class="add-icon layout-bg p-8 border-r-4" />
<span>{{ add_msg }}</span>
</div>
</el-card>
@ -133,9 +133,7 @@ defineExpose({
.add-icon {
font-size: 14px;
border-radius: 4px;
border: 1px solid var(--app-border-color-dark);
background: var(--app-layout-bg-color);
margin-right: 12px;
}
&:hover {

View File

@ -1,5 +1,5 @@
<template>
<div class="content-container">
<div class="content-container border-r-4">
<div class="content-container__header flex align-center w-full" v-if="slots.header || header">
<slot name="backButton">
<back-button :to="backTo" v-if="showBack"></back-button>
@ -41,7 +41,6 @@ const showBack = computed(() => {
}
.content-container__main {
background-color: var(--app-view-bg-color);
border-radius: 4px;
box-sizing: border-box;
min-width: 700px;
}

View File

@ -1,9 +1,9 @@
<template>
<div>
<el-button-group>
<el-button type="plain" size="small" @click="$_zoomIn">放大</el-button>
<el-button type="plain" size="small" @click="$_zoomOut">缩小</el-button>
<el-button type="plain" size="small" @click="$_reset">还原(大小&定位)</el-button>
<el-button size="small" @click="$_zoomIn">放大</el-button>
<el-button size="small" @click="$_zoomOut">缩小</el-button>
<el-button size="small" @click="$_reset">还原(大小&定位)</el-button>
</el-button-group>
</div>
</template>

View File

@ -1,8 +1,10 @@
import Components from '@/components'
import ElementPlus from 'element-plus'
import * as ElementPlusIcons from '@element-plus/icons-vue'
import { HtmlNode, HtmlNodeModel, BaseEdge } from '@logicflow/core'
import { createApp, h } from 'vue'
import directives from '@/directives'
import i18n from '@/locales'
class AppNode extends HtmlNode {
isMounted
@ -24,6 +26,10 @@ class AppNode extends HtmlNode {
this.app.use(ElementPlus)
this.app.use(Components)
this.app.use(directives)
this.app.use(i18n)
for (const [key, component] of Object.entries(ElementPlusIcons)) {
this.app.component(key, component)
}
}
setHtml(rootEl: HTMLElement) {
@ -31,7 +37,7 @@ class AppNode extends HtmlNode {
this.isMounted = true
const node = document.createElement('div')
rootEl.appendChild(node)
this.app.mount(node)
this.app?.mount(node)
} else {
if (this.r && this.r.component) {
this.r.component.props.properties = this.props.model.getProperties()

View File

@ -3,6 +3,10 @@ import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
class CustomEdge2 extends BezierEdge {}
class CustomEdgeModel2 extends BezierEdgeModel {
getArrowStyle() {
return { offet: 0 }
}
getEdgeStyle() {
const style = super.getEdgeStyle()

File diff suppressed because one or more lines are too long

View File

@ -1,115 +0,0 @@
import LogicFlow from '@logicflow/core'
import { shapeList } from './data'
type ShapeItem = {
type?: string
text?: string
icon?: string
label?: string
className?: string
disabled?: boolean
properties?: Record<string, any>
callback?: (lf: LogicFlow, container: HTMLElement) => void
}
class AppMenu {
lf: LogicFlow
shapeList: ShapeItem[]
panelEl?: HTMLDivElement
static pluginName = 'AppMenu'
domContainer?: HTMLElement
constructor({ lf }: { lf: LogicFlow }) {
this.lf = lf
this.lf.setPatternItems = (shapeList: Array<ShapeItem>) => {
this.setPatternItems(shapeList)
}
this.shapeList = shapeList
this.panelEl = undefined
this.domContainer = undefined
}
render(lf: LogicFlow, domContainer: HTMLElement) {
this.destroy()
if (!this.shapeList || this.shapeList.length === 0) {
// 首次render后失败后后续调用setPatternItems支持渲染
this.domContainer = domContainer
return
}
this.panelEl = document.createElement('div')
this.panelEl.className = 'lf-dndpanel'
this.shapeList.forEach((shapeItem) => {
this.panelEl?.appendChild(this.createDndItem(shapeItem))
})
domContainer.appendChild(this.panelEl)
this.domContainer = domContainer
}
destroy() {
if (this.domContainer && this.panelEl && this.domContainer.contains(this.panelEl)) {
this.domContainer.removeChild(this.panelEl)
}
}
setPatternItems(shapeList: Array<ShapeItem>) {
this.shapeList = shapeList
// 支持渲染后重新设置拖拽面板
if (this.domContainer) {
this.render(this.lf, this.domContainer)
}
}
private createDndItem(shapeItem: ShapeItem): HTMLElement {
const el = document.createElement('div')
el.className = shapeItem.className ? `lf-dnd-item ${shapeItem.className}` : 'lf-dnd-item'
const shape = document.createElement('div')
shape.className = 'lf-dnd-shape'
if (shapeItem.icon) {
shape.style.backgroundImage = `url(${shapeItem.icon})`
}
el.appendChild(shape)
if (shapeItem.label) {
const text = document.createElement('div')
text.innerText = shapeItem.label
text.className = 'lf-dnd-text'
el.appendChild(text)
}
if (shapeItem.disabled) {
el.classList.add('disabled')
// 保留callback的执行可用于界面提示当前shapeItem的禁用状态
el.onmousedown = () => {
if (shapeItem.callback && this.domContainer) {
shapeItem.callback(this.lf, this.domContainer)
}
}
return el
}
el.onmousedown = () => {
if (shapeItem.type) {
this.lf.dnd.startDrag({
type: shapeItem.type,
properties: shapeItem.properties
})
}
if (shapeItem.callback && this.domContainer) {
shapeItem.callback(this.lf, this.domContainer)
}
}
el.ondblclick = (e) => {
this.lf.graphModel.eventCenter.emit('dnd:panel-dbclick', {
e,
data: shapeItem
})
}
el.onclick = (e) => {
this.lf.graphModel.eventCenter.emit('dnd:panel-click', {
e,
data: shapeItem
})
}
el.oncontextmenu = (e) => {
this.lf.graphModel.eventCenter.emit('dnd:panel-contextmenu', {
e,
data: shapeItem
})
}
return el
}
}
export { AppMenu }

View File

@ -8,7 +8,7 @@
</div>
<div><slot></slot></div>
</div>
<div class="input-container" v-resize="resetInputContainer">
<!-- <div class="input-container" v-resize="resetInputContainer">
<div v-for="(item, index) in nodeModel.properties.input" :key="index" class="step-field">
<span>{{ item.key }}</span>
</div>
@ -21,7 +21,7 @@
>
<span>{{ item.key }}</span>
</div>
</div>
</div> -->
</div>
</div>
</template>

View File

@ -11,8 +11,6 @@ import LogicFlow from '@logicflow/core'
import { ref, onMounted } from 'vue'
import AppEdge from './common/edge/index'
import Control from './common/NodeControl.vue'
import { AppMenu } from './common/menu/index'
import '@logicflow/extension/lib/style/index.css'
import '@logicflow/core/dist/style/index.css'
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
@ -29,7 +27,6 @@ type ShapeItem = {
properties?: Record<string, any>
callback?: (lf: LogicFlow, container: HTMLElement) => void
}
// LogicFlow.use(AppMenu)
const graphData = {
nodes: [
@ -63,6 +60,19 @@ const graphData = {
output: [{ key: '输出' }]
// node_data: { model: 'shanghai', name: '222222' }
}
},
{
id: '34902d3d-a3ff-497f-b8e1-0c34a44d7dd4',
type: 'search-dataset-node',
x: 500,
y: 250,
properties: {
height: 200,
stepName: '开始',
// input: [{ key: '' }],
output: [{ key: '输出' }]
// node_data: { model: 'shanghai', name: '222222' }
}
}
]
// edges: [
@ -108,6 +118,7 @@ onMounted(() => {
const container: any = document.querySelector('#container')
if (container) {
lf.value = new LogicFlow({
textEdit: false,
background: {
backgroundColor: '#f5f6f7'
},

View File

@ -69,7 +69,7 @@ const validate = () => {
}
onMounted(() => {
props.nodeModel.validate = validate
set(props.nodeModel, 'validate', validate)
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,12 +1,12 @@
import ChatNodeVue from './index.vue'
import BaseNodeVue from './index.vue'
import { AppNode, AppNodeModel } from '@/components/workflow/common/app-node/index'
class ChatNode extends AppNode {
class BaseNode extends AppNode {
constructor(props: any) {
super(props, ChatNodeVue)
super(props, BaseNodeVue)
}
}
export default {
type: 'base-node',
model: AppNodeModel,
view: ChatNode
view: BaseNode
}

View File

@ -1,5 +1,5 @@
<template>
<NodeContainer :nodeModel="nodeModel" style="width: 320px">
<NodeContainer :nodeModel="nodeModel">
<el-form
@submit.prevent
:model="chat_data"
@ -7,7 +7,7 @@
require-asterisk-position="right"
class="mb-24"
label-width="auto"
ref="aiChatNodeFormRef"
ref="baseNodeFormRef"
>
<el-form-item
label="应用名称"
@ -76,10 +76,10 @@ const chat_data = computed({
const handleFocus = () => {
props.nodeModel.isSelected = false
}
const aiChatNodeFormRef = ref<FormInstance>()
const baseNodeFormRef = ref<FormInstance>()
const validate = () => {
aiChatNodeFormRef.value?.validate()
baseNodeFormRef.value?.validate()
}
onMounted(() => {

View File

@ -1,51 +1,186 @@
<template>
<NodeContainer :nodeModel="nodeModel">
<el-form
:model="chat_data"
label-position="top"
require-asterisk-position="right"
class="mb-24"
label-width="auto"
ref="aiChatNodeFormRef"
>
<el-form-item label="知识库">
<el-select v-model="chat_data.model" placeholder="请选择模型">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="提示词">
<el-input v-model="chat_data.name" />
</el-form-item>
</el-form>
<h5 class="title-decoration-1 mb-8">节点设置</h5>
<el-card shadow="never" class="card-never">
<el-form
:model="form_data"
label-position="top"
require-asterisk-position="right"
label-width="auto"
ref="DatasetNodeFormRef"
>
<el-form-item label="选择知识库">
<template #label>
<div class="flex-between">
<span>选择知识库</span>
<el-button type="primary" link @click="openDatasetDialog">
<el-icon><Plus /></el-icon>
</el-button>
</div>
</template>
<div class="w-full">
<el-text type="info" v-if="form_data.dataset_id_list?.length === 0">
关联的知识库展示在这里
</el-text>
<template v-for="(item, index) in form_data.dataset_id_list" :key="index" v-else>
<div class="flex-between border border-r-4 white-bg mb-4" style="padding: 5px 8px">
<div class="flex align-center" style="line-height: 20px">
<AppAvatar
v-if="relatedObject(datasetList, item, 'id')?.type === '1'"
class="mr-8 avatar-purple"
shape="square"
:size="20"
>
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
</AppAvatar>
<AppAvatar v-else class="mr-8" shape="square" :size="20">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<div class="ellipsis">
{{ relatedObject(datasetList, item, 'id')?.name }}
</div>
</div>
<el-button text @click="removeDataset(item)">
<el-icon><Close /></el-icon>
</el-button>
</div>
</template>
</div>
</el-form-item>
<el-form-item label="检索参数">
<template #label>
<div class="flex-between">
<span>检索参数</span>
<el-button type="primary" link @click="openParamSettingDialog">
<el-icon><Setting /></el-icon>
</el-button>
</div>
</template>
<div class="w-full">
<el-row>
<el-col :span="12" class="color-secondary lighter">检索模式</el-col>
<el-col :span="12" class="lighter">
{{ form_data.dataset_setting.search_mode }}</el-col
>
<el-col :span="12" class="color-secondary lighter"> 相似度高于</el-col>
<el-col :span="12" class="lighter">
{{ form_data.dataset_setting.similarity }}</el-col
>
<el-col :span="12" class="color-secondary lighter"> 引用分段 Top</el-col>
<el-col :span="12" class="lighter"> {{ form_data.dataset_setting.top_n }}</el-col>
<el-col :span="12" class="color-secondary lighter"> 最大引用字符数</el-col>
<el-col :span="12" class="lighter">
{{ form_data.dataset_setting.max_paragraph_char_number }}</el-col
>
</el-row>
</div>
</el-form-item>
</el-form>
</el-card>
<h5 class="title-decoration-1 mb-8 mt-8">参数输出</h5>
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">检索结果 {data}</div>
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">满足直接回答的分段内容 {paragraph}</div>
<ParamSettingDialog ref="ParamSettingDialogRef" @refresh="refreshParam" />
<AddDatasetDialog
ref="AddDatasetDialogRef"
@addData="addDataset"
:data="datasetList"
@refresh="refresh"
:loading="datasetLoading"
/>
</NodeContainer>
</template>
<script setup lang="ts">
import { set } from 'lodash'
import { app } from '@/main'
import NodeContainer from '@/components/workflow/common/node-container/index.vue'
import AddDatasetDialog from '@/views/application/components/AddDatasetDialog.vue'
import ParamSettingDialog from '@/views/application/components/ParamSettingDialog.vue'
import type { FormInstance } from 'element-plus'
import { ref, computed, onMounted } from 'vue'
import { relatedObject } from '@/utils/utils'
import useStore from '@/stores'
const { dataset, application, user } = useStore()
const {
params: { id }
} = app.config.globalProperties.$route as any
const chat_data = computed({
const props = defineProps<{ nodeModel: any }>()
const form = {
dataset_id_list: [],
dataset_setting: {
top_n: 3,
similarity: 0.6,
max_paragraph_char_number: 5000,
search_mode: 'embedding'
}
}
const form_data = computed({
get: () => {
if (props.nodeModel.properties.node_data) {
return props.nodeModel.properties.node_data
} else {
set(props.nodeModel.properties, 'node_data', form)
}
return {}
return props.nodeModel.properties.node_data
},
set: (value) => {
props.nodeModel.properties.node_data = value
set(props.nodeModel.properties, 'node_data', value)
}
})
const props = defineProps<{ nodeModel: any }>()
const aiChatNodeFormRef = ref<FormInstance>()
const DatasetNodeFormRef = ref<FormInstance>()
const ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()
const AddDatasetDialogRef = ref<InstanceType<typeof AddDatasetDialog>>()
const datasetList = ref<any>([])
const datasetLoading = ref(false)
function refreshParam(data: any) {
set(props.nodeModel.properties.node_data, 'dataset_setting', data)
}
const openParamSettingDialog = () => {
ParamSettingDialogRef.value?.open(form_data.value.dataset_setting, 'workflow')
}
function removeDataset(id: any) {
const list = props.nodeModel.properties.node_data.dataset_id_list.filter((v) => v !== id)
set(props.nodeModel.properties.node_data, 'dataset_id_list', list)
}
function addDataset(val: Array<string>) {
set(props.nodeModel.properties.node_data, 'dataset_id_list', val)
}
function openDatasetDialog() {
AddDatasetDialogRef.value.open(form_data.value.dataset_id_list)
}
function getDataset() {
if (id) {
application.asyncGetApplicationDataset(id, datasetLoading).then((res: any) => {
datasetList.value = res.data
})
} else {
dataset.asyncGetAllDataset(datasetLoading).then((res: any) => {
datasetList.value = res.data?.filter((v: any) => v.user_id === user.userInfo?.id)
})
}
}
function refresh() {
getDataset()
}
const validate = () => {
aiChatNodeFormRef.value?.validate()
DatasetNodeFormRef.value?.validate()
}
onMounted(() => {
props.nodeModel.validate = validate
getDataset()
set(props.nodeModel, 'validate', validate)
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,16 +1,15 @@
<template>
<NodeContainer :nodeModel="nodeModel" class="start-node" style="width: 320px">
<NodeContainer :nodeModel="nodeModel" class="start-node">
<h5 class="title-decoration-1 mb-8">全局变量</h5>
<div class="text-bg p-8-12 mb-8">当前时 {time}</div>
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">当前时 {time}</div>
<h5 class="title-decoration-1 mb-8">参数输出</h5>
<div class="text-bg p-8-12 mb-8">用户问题 {question}</div>
<div class="border-r-4 p-8-12 mb-8 layout-bg lighter">用户问题 {question}</div>
</NodeContainer>
</template>
<script setup lang="ts">
import NodeContainer from '@/components/workflow/common/node-container/index.vue'
import type { FormInstance } from 'element-plus'
import { ref, computed, onMounted } from 'vue'
import { MdEditor } from 'md-editor-v3'
// const chat_data = computed({
// get: () => {
@ -44,11 +43,4 @@ onMounted(() => {
props.nodeModel.validate = validate
})
</script>
<style lang="scss" scoped>
.start-node {
.text-bg {
background: var(--app-layout-bg-color);
border-radius: 4px;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="aboutDialogVisible" class="about-dialog">
<el-dialog v-model="aboutDialogVisible" class="about-dialog border-r-4">
<template #header="{ titleId, titleClass }">
<div class="flex-center">
<div class="logo mr-4"></div>
@ -57,7 +57,6 @@ defineExpose({ open })
<style lang="scss" scope>
.about-dialog {
padding: 0 0 24px 0;
border-radius: 4px;
width: 600px;
font-weight: 400;
.el-dialog__header {

View File

@ -9,7 +9,7 @@ import directives from '@/directives'
import App from './App.vue'
import router from '@/router'
import Components from '@/components'
import i18n from './locales';
import i18n from './locales'
const app = createApp(App)
app.use(store)
app.use(directives)
@ -24,6 +24,7 @@ app.use(ElementPlus, {
app.use(theme)
app.use(router)
app.use(i18n);
app.use(i18n)
app.use(Components)
app.mount('#app')
export { app }

View File

@ -273,6 +273,9 @@ h5 {
.border-b-light {
border-bottom: 1px solid var(--el-border-color-lighter);
}
.border-r-4 {
border-radius: 4px;
}
.cursor {
cursor: pointer;
@ -460,6 +463,14 @@ h5 {
color: var(--app-text-color-secondary);
}
.layout-bg {
background: var(--app-layout-bg-color);
}
.white-bg {
background: #ffffff;
}
.app-warning-icon {
font-size: 16px;
color: var(--app-text-color-secondary);

View File

@ -1,6 +1,8 @@
<template>
<el-dialog title="API Key" v-model="dialogVisible" width="800">
<el-button type="primary" class="mb-16" @click="createApiKey"> {{$t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey')}} </el-button>
<el-button type="primary" class="mb-16" @click="createApiKey">
{{ $t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey') }}
</el-button>
<el-table :data="apiKey" class="mb-16" :loading="loading">
<el-table-column prop="secret_key" label="API Key">
<template #default="{ row }">
@ -12,28 +14,47 @@
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')" width="60">
<el-table-column
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')"
width="60"
>
<template #default="{ row }">
<div @click.stop>
<el-switch size="small" v-model="row.is_active" @change="changeState($event, row)" />
</div>
</template>
</el-table-column>
<el-table-column prop="name" :label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')" width="170">
<el-table-column
prop="name"
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')"
width="170"
>
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column :label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')" align="left" width="80">
<el-table-column
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')"
align="left"
width="80"
>
<template #default="{ row }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')" placement="top">
<el-tooltip
effect="dark"
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')"
placement="top"
>
<el-button type="primary" text @click.stop="settingApiKey(row)">
<el-icon><Setting /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')" placement="top">
<el-tooltip
effect="dark"
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')"
placement="top"
>
<el-button type="primary" text @click="deleteApiKey(row)">
<el-icon><Delete /></el-icon>
</el-button>
@ -82,7 +103,7 @@ function deleteApiKey(row: any) {
t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),
{
confirmButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.confirmDelete'),
cancelButtonText:t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
confirmButtonClass: 'danger'
}
)
@ -99,7 +120,9 @@ function changeState(bool: Boolean, row: any) {
const obj = {
is_active: bool
}
const str = bool ? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess') : t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
const str = bool
? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess')
: t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
overviewApi.putAPIKey(id as string, row.id, obj, loading).then((res) => {
MsgSuccess(str)
getApiKeyList()
@ -129,15 +152,4 @@ function refresh() {
defineExpose({ open })
</script>
<style lang="scss" scope>
.embed-dialog {
.code {
color: var(--app-text-color) !important;
background: var(--app-layout-bg-color);
font-weight: 400;
font-size: 13px;
white-space: pre;
height: 180px;
}
}
</style>
<style lang="scss" scope></style>

View File

@ -5,7 +5,7 @@
<div class="border">
<p class="title p-16 bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.embedDialogTitle')}}</p>
<img src="@/assets/window1.png" alt="" class="ml-8" />
<div class="code border-t p-16">
<div class="code layout-bg border-t p-16">
<div class="flex-between">
<span class="bold">{{$t('views.applicationOverview.appInfo.EmbedDialog.fullscreenModeTitle')}}</span>
<el-button text @click="copyClick(source1)">
@ -91,7 +91,7 @@ defineExpose({ open })
.code {
color: var(--app-text-color) !important;
background: var(--app-layout-bg-color);
font-weight: 400;
font-size: 13px;
white-space: pre;

View File

@ -1,10 +1,18 @@
<template>
<el-dialog :title="$t('views.applicationOverview.appInfo.LimitDialog.dialogTitle')" v-model="dialogVisible">
<el-dialog
:title="$t('views.applicationOverview.appInfo.LimitDialog.dialogTitle')"
v-model="dialogVisible"
>
<el-form label-position="top" ref="limitFormRef" :model="form">
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.showSourceLabel')" @click.prevent>
<el-form-item
:label="$t('views.applicationOverview.appInfo.LimitDialog.showSourceLabel')"
@click.prevent
>
<el-switch size="small" v-model="form.show_source"></el-switch>
</el-form-item>
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')">
<el-form-item
:label="$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')"
>
<el-input-number
v-model="form.access_num"
:min="0"
@ -12,9 +20,14 @@
controls-position="right"
step-strictly
/>
<span class="ml-4">{{$t('views.applicationOverview.appInfo.LimitDialog.timesDays')}}</span>
<span class="ml-4">{{
$t('views.applicationOverview.appInfo.LimitDialog.timesDays')
}}</span>
</el-form-item>
<el-form-item :label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')" @click.prevent>
<el-form-item
:label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')"
@click.prevent
>
<el-switch size="small" v-model="form.white_active"></el-switch>
</el-form-item>
<el-form-item>
@ -28,9 +41,11 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false">{{$t('views.applicationOverview.appInfo.LimitDialog.cancelButtonText')}} </el-button>
<el-button @click.prevent="dialogVisible = false"
>{{ $t('views.applicationOverview.appInfo.LimitDialog.cancelButtonText') }}
</el-button>
<el-button type="primary" @click="submit(limitFormRef)" :loading="loading">
{{$t('views.applicationOverview.appInfo.LimitDialog.saveButtonText')}}
{{ $t('views.applicationOverview.appInfo.LimitDialog.saveButtonText') }}
</el-button>
</span>
</template>
@ -103,15 +118,4 @@ const submit = async (formEl: FormInstance | undefined) => {
defineExpose({ open })
</script>
<style lang="scss" scope>
.embed-dialog {
.code {
color: var(--app-text-color) !important;
background: var(--app-layout-bg-color);
font-weight: 400;
font-size: 13px;
white-space: pre;
height: 180px;
}
}
</style>
<style lang="scss" scope></style>

View File

@ -15,7 +15,7 @@
</div>
<!-- 下拉框 -->
<el-collapse-transition>
<div v-show="showPopover" class="workflow-dropdown-menu border">
<div v-show="showPopover" class="workflow-dropdown-menu border border-r-4">
<h5 class="title">基础组件</h5>
<template v-for="(item, index) in shapeList" :key="index">
<div class="workflow-dropdown-item cursor flex p-8-12" @mousedown="onmousedown(item)">
@ -37,16 +37,6 @@
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
import Workflow from '@/components/workflow/index.vue'
import { shapeList, iconComponent } from '@/components/workflow/menu-data'
type ShapeItem = {
type?: string
text?: string
icon?: string
label?: string
className?: string
disabled?: boolean
properties?: Record<string, any>
callback?: (lf: LogicFlow, container: HTMLElement) => void
}
const workflowRef = ref()
@ -56,7 +46,7 @@ function clickoutside() {
showPopover.value = false
}
function onmousedown(item: ShapeItem) {
function onmousedown(item: any) {
workflowRef.value?.onmousedown(item)
}
@ -86,7 +76,6 @@ onBeforeUnmount(() => {})
width: 240px;
box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);
background: #ffffff;
border-radius: 4px;
.title {
padding: 8px 12px 4px;

View File

@ -206,7 +206,7 @@
v-for="(item, index) in applicationForm.dataset_id_list"
:key="index"
>
<el-card class="relate-dataset-card" shadow="never">
<el-card class="relate-dataset-card border-r-4" shadow="never">
<div class="flex-between">
<div class="flex align-center">
<AppAvatar
@ -218,7 +218,7 @@
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
</AppAvatar>
<AppAvatar v-else class="mr-12" shape="square" :size="32">
<AppAvatar v-else class="mr-8" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
<div class="ellipsis">
@ -525,7 +525,6 @@ onMounted(() => {
.create-application {
.relate-dataset-card {
color: var(--app-text-color);
border-radius: 4px;
}
.dialog-bg {
border-radius: 8px;

View File

@ -1,10 +1,18 @@
<template>
<el-dialog :title="$t('views.application.applicationForm.dialogues.addDataset')" v-model="dialogVisible" width="600">
<el-dialog
:title="$t('views.application.applicationForm.dialogues.addDataset')"
v-model="dialogVisible"
width="600"
append-to-body
>
<template #header="{ titleId, titleClass }">
<div class="my-header flex">
<h4 :id="titleId" :class="titleClass">{{$t('views.application.applicationForm.dialogues.addDataset')}}</h4>
<h4 :id="titleId" :class="titleClass">
{{ $t('views.application.applicationForm.dialogues.addDataset') }}
</h4>
<el-button link class="ml-16" @click="refresh">
<el-icon class="mr-4"><Refresh /></el-icon>{{$t('views.application.applicationForm.dialogues.refresh')}}
<el-icon class="mr-4"><Refresh /></el-icon
>{{ $t('views.application.applicationForm.dialogues.refresh') }}
</el-button>
</div>
</template>
@ -19,8 +27,12 @@
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{$t('views.application.applicationForm.buttons.cancel')}} </el-button>
<el-button type="primary" @click="submitHandle"> {{$t('views.application.applicationForm.buttons.confirm')}} </el-button>
<el-button @click.prevent="dialogVisible = false">
{{ $t('views.application.applicationForm.buttons.cancel') }}
</el-button>
<el-button type="primary" @click="submitHandle">
{{ $t('views.application.applicationForm.buttons.confirm') }}
</el-button>
</span>
</template>
</el-dialog>

View File

@ -5,12 +5,15 @@
class="param-dialog"
v-model="dialogVisible"
style="width: 550px"
append-to-body
>
<div class="dialog-max-height">
<el-scrollbar always>
<div class="p-16">
<el-form label-position="top" ref="paramFormRef" :model="form">
<el-form-item :label="$t('views.application.applicationForm.dialogues.selectSearchMode')">
<el-form-item
:label="$t('views.application.applicationForm.dialogues.selectSearchMode')"
>
<el-radio-group v-model="form.search_mode" class="card__radio" @change="changeHandle">
<el-card
shadow="never"
@ -18,8 +21,12 @@
:class="form.search_mode === 'embedding' ? 'active' : ''"
>
<el-radio value="embedding" size="large">
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.vectorSearch')}}</p>
<el-text type="info">{{$t('views.application.applicationForm.dialogues.vectorSearchTooltip')}}</el-text>
<p class="mb-4">
{{ $t('views.application.applicationForm.dialogues.vectorSearch') }}
</p>
<el-text type="info">{{
$t('views.application.applicationForm.dialogues.vectorSearchTooltip')
}}</el-text>
</el-radio>
</el-card>
<el-card
@ -28,16 +35,22 @@
:class="form.search_mode === 'keywords' ? 'active' : ''"
>
<el-radio value="keywords" size="large">
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.fullTextSearch')}}</p>
<el-text type="info">{{$t('views.application.applicationForm.dialogues.fullTextSearchTooltip')}}</el-text>
<p class="mb-4">
{{ $t('views.application.applicationForm.dialogues.fullTextSearch') }}
</p>
<el-text type="info">{{
$t('views.application.applicationForm.dialogues.fullTextSearchTooltip')
}}</el-text>
</el-radio>
</el-card>
<el-card shadow="never" :class="form.search_mode === 'blend' ? 'active' : ''">
<el-radio value="blend" size="large">
<p class="mb-4">{{$t('views.application.applicationForm.dialogues.hybridSearch')}}</p>
<el-text type="info"
>{{$t('views.application.applicationForm.dialogues.hybridSearchTooltip')}}</el-text
>
<p class="mb-4">
{{ $t('views.application.applicationForm.dialogues.hybridSearch') }}
</p>
<el-text type="info">{{
$t('views.application.applicationForm.dialogues.hybridSearchTooltip')
}}</el-text>
</el-radio>
</el-card>
</el-radio-group>
@ -47,7 +60,9 @@
<el-form-item>
<template #label>
<div class="flex align-center">
<span class="mr-4">{{$t('views.application.applicationForm.dialogues.similarityThreshold')}}</span>
<span class="mr-4">{{
$t('views.application.applicationForm.dialogues.similarityThreshold')
}}</span>
<el-tooltip effect="dark" content="相似度越高相关性越强。" placement="right">
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
@ -65,7 +80,9 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('views.application.applicationForm.dialogues.topReferences')">
<el-form-item
:label="$t('views.application.applicationForm.dialogues.topReferences')"
>
<el-input-number
v-model="form.top_n"
:min="1"
@ -87,7 +104,10 @@
class="custom-slider"
/>
</el-form-item>
<el-form-item :label="$t('views.application.applicationForm.dialogues.noReferencesAction')">
<el-form-item
v-if="!isWorkflow"
:label="$t('views.application.applicationForm.dialogues.noReferencesAction')"
>
<el-form
label-position="top"
ref="noReferencesformRef"
@ -102,7 +122,9 @@
>
<div>
<el-radio value="ai_questioning">
<p>{{$t('views.application.applicationForm.dialogues.continueQuestioning')}}</p>
<p>
{{ $t('views.application.applicationForm.dialogues.continueQuestioning') }}
</p>
<el-form-item
v-if="form.no_references_setting.status === 'ai_questioning'"
:label="$t('views.application.applicationForm.form.prompt.label')"
@ -120,7 +142,7 @@
</div>
<div class="mt-8">
<el-radio value="designated_answer">
<p>{{$t('views.application.applicationForm.dialogues.provideAnswer')}}</p>
<p>{{ $t('views.application.applicationForm.dialogues.provideAnswer') }}</p>
<el-form-item
v-if="form.no_references_setting.status === 'designated_answer'"
prop="designated_answer"
@ -144,9 +166,11 @@
</div>
<template #footer>
<span class="dialog-footer p-16">
<el-button @click.prevent="dialogVisible = false">{{$t('views.application.applicationForm.buttons.cancel')}}</el-button>
<el-button @click.prevent="dialogVisible = false">{{
$t('views.application.applicationForm.buttons.cancel')
}}</el-button>
<el-button type="primary" @click="submit(noReferencesformRef)" :loading="loading">
{{$t('views.application.applicationForm.buttons.save')}}
{{ $t('views.application.applicationForm.buttons.save') }}
</el-button>
</span>
</template>
@ -164,8 +188,8 @@ const noReferencesformRef = ref()
const defaultValue = {
ai_questioning: '{question}',
// @ts-ignore
designated_answer:t('views.application.applicationForm.dialogues.designated_answer')
// @ts-ignore
designated_answer: t('views.application.applicationForm.dialogues.designated_answer')
}
const form = ref<any>({
@ -185,13 +209,27 @@ const noReferencesform = ref<any>({
})
const noReferencesRules = reactive<FormRules<any>>({
ai_questioning: [{ required: true, message: t('views.application.applicationForm.dialogues.promptPlaceholder'), trigger: 'blur' }],
designated_answer: [{ required: true, message: t('views.application.applicationForm.dialogues.concentPlaceholder'), trigger: 'blur' }]
ai_questioning: [
{
required: true,
message: t('views.application.applicationForm.dialogues.promptPlaceholder'),
trigger: 'blur'
}
],
designated_answer: [
{
required: true,
message: t('views.application.applicationForm.dialogues.concentPlaceholder'),
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const isWorkflow = ref(false)
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
@ -212,7 +250,8 @@ watch(dialogVisible, (bool) => {
}
})
const open = (data: any) => {
const open = (data: any, type?: string) => {
isWorkflow.value = type === 'workflow'
form.value = { ...form.value, ...cloneDeep(data) }
noReferencesform.value[form.value.no_references_setting.status] =
form.value.no_references_setting.value

View File

@ -1,5 +1,5 @@
<template>
<div class="chat" v-loading="loading">
<div class="chat layout-bg" v-loading="loading">
<div class="chat__header">
<div class="chat-width">
<h2 class="ml-24">{{ applicationDetail?.name }}</h2>
@ -60,7 +60,6 @@ onMounted(() => {
</script>
<style lang="scss">
.chat {
background-color: var(--app-layout-bg-color);
overflow: hidden;
&__header {
background: var(--app-header-bg-color);

View File

@ -1,5 +1,5 @@
<template>
<div class="chat-embed" v-loading="loading">
<div class="chat-embed layout-bg" v-loading="loading">
<div class="chat-embed__header">
<div class="chat-width">
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
@ -200,7 +200,6 @@ onMounted(() => {
</script>
<style lang="scss">
.chat-embed {
background-color: var(--app-layout-bg-color);
overflow: hidden;
&__header {
background: var(--app-header-bg-color);

View File

@ -1,5 +1,5 @@
<template>
<div class="chat-pc" :class="classObj" v-loading="loading">
<div class="chat-pc layout-bg" :class="classObj" v-loading="loading">
<div class="chat-pc__header">
<h4 class="ml-24">{{ applicationDetail?.name }}</h4>
</div>
@ -292,7 +292,6 @@ onMounted(() => {
</script>
<style lang="scss">
.chat-pc {
background-color: var(--app-layout-bg-color);
overflow: hidden;
&__header {

View File

@ -37,7 +37,7 @@
shadow="hover"
:title="item.title || '-'"
:description="item.content"
class="document-card cursor"
class="document-card layout-bg layout-bg cursor "
:class="item.is_active ? '' : 'disabled'"
:showIcon="false"
@click="editParagraph(item)"
@ -362,7 +362,6 @@ onMounted(() => {})
}
.document-card {
height: 210px;
background: var(--app-layout-bg-color);
border: 1px solid var(--app-layout-bg-color);
&:hover {
background: #ffffff;