mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-02-05 01:55:29 +08:00
AIGC应用平台+知识库模块
This commit is contained in:
@ -0,0 +1,5 @@
|
||||
{
|
||||
"prompt": "# 角色\n你是一个犀利的电影解说员,可以使用尖锐幽默的语言,向用户讲解电影剧情、介绍最新上映的电影,还可以用普通人都可以理解的语言讲解电影相关知识。\n\n## 技能\n### 技能 1: 推荐最新上映的电影\n1. 当用户请你推荐最新电影时,需要先了解用户喜欢哪种类型片。如果你已经知道了,请跳过这一步,在询问时可以用“请问您喜欢什么类型的电影呢亲”。\n2. 如果你并不知道用户所说的电影,可以使用 工具搜索电影,了解电影类型。\n3. 根据用户的电影偏好,推荐几部正在上映和即将上映的电影,在推荐开头可以说“好的亲,以下是为您推荐的电影”。\n===回复示例===\n - \uD83C\uDFAC 电影名: <电影名>\n - \uD83D\uDD50 上映时间: <电影在中国大陆的上映的日期>\n - \uD83D\uDCA1 电影简介: <100字总结这部电影的剧情摘要>\n===示例结束===\n\n### 技能 2: 介绍电影\n1. 当用户说介绍某一部电影,请使用工具 搜索电影介绍的链接,在收到需求时可以回应“好嘞亲,马上为您查找相关电影介绍”。\n2. 如果此时获取的信息不够全面,可以继续使用 工具 打开搜索结果中的相关链接,以了解电影详情。\n3. 根据搜索和浏览结果,生成电影介绍\n### 技能 3: 介绍电影概念\n- 你可以使用数据集中的知识,调用 知识库 搜索相关知识,并向用户介绍基础概念,介绍前可以说“亲,下面为您介绍一下这个电影概念”。\n- 使用用户熟悉的电影,举一个实际的场景解释概念\n\n## 限制:\n- 只讨论与电影有关的内容,拒绝回答与电影无关的话题,拒绝时可以说“不好意思亲,这边只讨论电影相关话题哦”。\n- 所输出的内容必须按照给定的格式进行组织,不能偏离框架要求,在表述中合理运用常用语。\n- 总结部分不能超过 100 字。\n- 只会输出知识库中已有内容, 不在知识库中的书籍, 通过 工具去了解。\n- 请使用 Markdown 的 ^^ 形式说明引用来源。”",
|
||||
"prologue": "嘿,亲!我对电影那可是门儿清,能给你带来超棒的电影体验。",
|
||||
"presetQuestion": [{"key": 1,"descr": "有啥好看的动作片推荐不?"},{"key": 2,"descr":"介绍下《流浪地球 3》呗。"},{"key": 3,"descr":"啥是电影蒙太奇呀?"}]
|
||||
}
|
||||
@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<div class="flex header">
|
||||
<a-input
|
||||
@pressEnter="loadFlowData"
|
||||
class="header-search"
|
||||
size="small"
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入流程名称,回车搜索"
|
||||
></a-input>
|
||||
</div>
|
||||
<a-row :span="24">
|
||||
<a-col :span="12" v-for="item in flowList" @click="handleSelect(item)">
|
||||
<a-card :style="item.id === flowId ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
|
||||
<div style="display: flex; width: 100%;align-items:center">
|
||||
<img :src="getImage(item.icon)" class="flow-icon"/>
|
||||
<div style="display: grid;margin-left: 5px;align-items: center">
|
||||
<span class="checkbox-name ellipsis">{{ item.name }}</span>
|
||||
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
|
||||
<span class="tag-input">输入</span>
|
||||
<div v-for="(metaItem, index) in item.metadata">
|
||||
<a-tag color="rgba(87,104,161,0.08)" class="tags-meadata">
|
||||
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-desc mt-10">
|
||||
{{ item.descr || '暂无描述' }}
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div v-if="flowId" class="use-select">
|
||||
已选择 <span class="ellipsis" style="max-width: 150px">{{flowData.name}}</span>
|
||||
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="flowList.length > 0"
|
||||
:current="pageNo"
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:total="total"
|
||||
:showQuickJumper="true"
|
||||
:showSizeChanger="true"
|
||||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
/>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModal, useModalInner } from '@/components/Modal';
|
||||
import { Pagination } from 'ant-design-vue';
|
||||
import { list } from '@/views/super/airag/aiknowledge/AiKnowledgeBase.api';
|
||||
import knowledge from '/@/views/super/airag/aiknowledge/icon/knowledge.png';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { getFileAccessHttpUrl } from "@/utils/common/compUtils";
|
||||
import defaultFlowImg from "@/assets/images/ai/aiflow.png";
|
||||
|
||||
export default {
|
||||
name: 'AiAppAddFlowModal',
|
||||
components: {
|
||||
Pagination,
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('选择流程');
|
||||
//应用类型
|
||||
const flowId = ref<any>([]);
|
||||
//流程数据
|
||||
const flowList = ref<any>({});
|
||||
//选中的数据
|
||||
const flowData = ref<any>({})
|
||||
//当前页数
|
||||
const pageNo = ref<number>(1);
|
||||
//每页条数
|
||||
const pageSize = ref<number>(10);
|
||||
//总条数
|
||||
const total = ref<number>(0);
|
||||
//搜索文本
|
||||
const searchText = ref<string>('');
|
||||
//可选择的页数
|
||||
const pageSizeOptions = ref<any>(['10', '20', '30']);
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
flowId.value = data.flowId ? cloneDeep(data.flowId) : '';
|
||||
flowData.value = data.flowData ? cloneDeep(data.flowData) : {};
|
||||
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
|
||||
loadFlowData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
emit('success',{ flowId: flowId.value, flowData: flowData.value });
|
||||
handleCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
//复选框选中事件
|
||||
const handleSelect = (item) => {
|
||||
if(flowId.value === item.id){
|
||||
flowId.value = "";
|
||||
flowData.value = null;
|
||||
return;
|
||||
}
|
||||
flowId.value = item.id;
|
||||
flowData.value = item;
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载知识库
|
||||
*/
|
||||
function loadFlowData() {
|
||||
let params = {
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
name: searchText.value,
|
||||
status:'enable'
|
||||
};
|
||||
pageApi.list(params).then((res) =>{
|
||||
if(res){
|
||||
for (const data of res.records) {
|
||||
data.metadata = getMetadata(data.metadata);
|
||||
}
|
||||
flowList.value = res.records;
|
||||
total.value = res.total;
|
||||
} else {
|
||||
flowList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页改变事件
|
||||
* @param page
|
||||
* @param current
|
||||
*/
|
||||
function handlePageChange(page, current) {
|
||||
pageNo.value = page;
|
||||
pageSize.value = current;
|
||||
loadFlowData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空选中状态
|
||||
*/
|
||||
function handleClearClick() {
|
||||
flowId.value = "";
|
||||
flowData.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图标
|
||||
*/
|
||||
function getImage(icon) {
|
||||
return icon ? getFileAccessHttpUrl(icon) : defaultFlowImg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入输出参入
|
||||
*
|
||||
* @param metadata
|
||||
*/
|
||||
function getMetadata(metadata) {
|
||||
if (!metadata) {
|
||||
return [];
|
||||
}
|
||||
let parse = JSON.parse(metadata);
|
||||
let inputsArr = parse['inputs'];
|
||||
return [...inputsArr];
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
flowList,
|
||||
flowId,
|
||||
handleSelect,
|
||||
pageNo,
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
total,
|
||||
handlePageChange,
|
||||
knowledge,
|
||||
searchText,
|
||||
loadFlowData,
|
||||
handleClearClick,
|
||||
flowData,
|
||||
getImage,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
color: #646a73;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
.header-search {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.type-title {
|
||||
color: #1d2025;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.type-desc {
|
||||
color: #8f959e;
|
||||
font-weight: 400;
|
||||
}
|
||||
.list-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 260px;
|
||||
}
|
||||
.checkbox-card {
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.checkbox-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #354052;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
align-content: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: grid;
|
||||
}
|
||||
.use-select {
|
||||
color: #646a73;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
}
|
||||
.ellipsis {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.flow-icon{
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
:deep(.ant-card .ant-card-body){
|
||||
padding:16px !important;
|
||||
}
|
||||
.header-create-by{
|
||||
font-size: 12px;
|
||||
color: #646a73;
|
||||
}
|
||||
.text-desc {
|
||||
width: 100%;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
font-size: 12px;
|
||||
color: #676F83;
|
||||
}
|
||||
.mt-10{
|
||||
margin-top: 10px;
|
||||
}
|
||||
.flex{
|
||||
display: flex;
|
||||
}
|
||||
.text-status{
|
||||
font-size: 12px;
|
||||
color: #676F83;
|
||||
}
|
||||
.tag-text {
|
||||
display: flow;
|
||||
max-width: 48px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
color: rgba(15, 21, 40,0.82);
|
||||
}
|
||||
.tag-input{
|
||||
align-self: center;
|
||||
color: rgba(55,67,106,0.7);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
margin-right: 6px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tags-meadata{
|
||||
padding-inline: 2px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<div class="flex header">
|
||||
<span>所选知识库必须使用相同的 Embedding 模型</span>
|
||||
<a-input
|
||||
@pressEnter="loadKnowledgeData"
|
||||
class="header-search"
|
||||
size="small"
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入知识库名称,回车搜索"
|
||||
></a-input>
|
||||
</div>
|
||||
<a-row :span="24">
|
||||
<a-col :span="12" v-for="item in appKnowledgeOption" @click="handleSelect(item)">
|
||||
<a-card :style="item.checked ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
|
||||
<div style="display: flex; width: 100%; justify-content: space-between">
|
||||
<div>
|
||||
<img class="checkbox-img" :src="knowledge" />
|
||||
<span class="checkbox-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker"> </a-checkbox>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div v-if="knowledgeIds.length > 0" class="use-select">
|
||||
已选择 {{ knowledgeIds.length }} 知识库
|
||||
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="appKnowledgeOption.length > 0"
|
||||
:current="pageNo"
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:total="total"
|
||||
:showQuickJumper="true"
|
||||
:showSizeChanger="true"
|
||||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
/>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModal, useModalInner } from '@/components/Modal';
|
||||
import { Pagination } from 'ant-design-vue';
|
||||
import { list } from '@/views/super/airag/aiknowledge/AiKnowledgeBase.api';
|
||||
import knowledge from '/@/views/super/airag/aiknowledge/icon/knowledge.png';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export default {
|
||||
name: 'AiAppAddKnowledgeModal',
|
||||
components: {
|
||||
Pagination,
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('添加关联知识库');
|
||||
|
||||
//app知识库
|
||||
const appKnowledgeOption = ref<any>([]);
|
||||
//应用类型
|
||||
const knowledgeIds = ref<any>([]);
|
||||
//应用数据
|
||||
const knowledgeData = ref<any>([]);
|
||||
//当前页数
|
||||
const pageNo = ref<number>(1);
|
||||
//每页条数
|
||||
const pageSize = ref<number>(10);
|
||||
//总条数
|
||||
const total = ref<number>(0);
|
||||
//搜索文本
|
||||
const searchText = ref<string>('');
|
||||
//可选择的页数
|
||||
const pageSizeOptions = ref<any>(['10', '20', '30']);
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds.split(',')) : [];
|
||||
knowledgeData.value = data.knowledgeDataList ? cloneDeep(data.knowledgeDataList) : [];
|
||||
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
|
||||
loadKnowledgeData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
emit('success', knowledgeIds.value, knowledgeData.value);
|
||||
handleCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
//复选框选中事件
|
||||
const handleSelect = (item) => {
|
||||
let id = item.id;
|
||||
const target = appKnowledgeOption.value.find((item) => item.id === id);
|
||||
if (target) {
|
||||
target.checked = !target.checked;
|
||||
}
|
||||
//存放选中的知识库的id
|
||||
if (knowledgeIds.value.length == 0) {
|
||||
knowledgeIds.value.push(id);
|
||||
knowledgeData.value.push(item);
|
||||
return;
|
||||
}
|
||||
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
|
||||
if (findIndex === -1) {
|
||||
knowledgeIds.value.push(id);
|
||||
knowledgeData.value.push(item);
|
||||
} else {
|
||||
knowledgeIds.value.splice(findIndex, 1);
|
||||
knowledgeData.value.splice(findIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载知识库
|
||||
*/
|
||||
function loadKnowledgeData() {
|
||||
let params = {
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
name: searchText.value,
|
||||
};
|
||||
list(params).then((res) => {
|
||||
if (res.success) {
|
||||
if (knowledgeIds.value.length > 0) {
|
||||
for (const item of res.result.records) {
|
||||
if (knowledgeIds.value.includes(item.id)) {
|
||||
item.checked = true;
|
||||
}
|
||||
}
|
||||
appKnowledgeOption.value = res.result.records;
|
||||
} else {
|
||||
appKnowledgeOption.value = res.result.records;
|
||||
}
|
||||
total.value = res.result.total;
|
||||
} else {
|
||||
appKnowledgeOption.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页改变事件
|
||||
* @param page
|
||||
* @param current
|
||||
*/
|
||||
function handlePageChange(page, current) {
|
||||
pageNo.value = page;
|
||||
pageSize.value = current;
|
||||
loadKnowledgeData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空选中状态
|
||||
*/
|
||||
function handleClearClick() {
|
||||
knowledgeIds.value = [];
|
||||
knowledgeData.value = [];
|
||||
appKnowledgeOption.value.forEach((item) => {
|
||||
item.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
appKnowledgeOption,
|
||||
knowledgeIds,
|
||||
handleSelect,
|
||||
pageNo,
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
total,
|
||||
handlePageChange,
|
||||
knowledge,
|
||||
searchText,
|
||||
loadKnowledgeData,
|
||||
handleClearClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
color: #646a73;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
.header-search {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.type-title {
|
||||
color: #1d2025;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.type-desc {
|
||||
color: #8f959e;
|
||||
font-weight: 400;
|
||||
}
|
||||
.list-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 260px;
|
||||
}
|
||||
.checkbox-card {
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.checkbox-img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.checkbox-name {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.use-select {
|
||||
color: #646a73;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="1000px" @ok="handleOk" @cancel="handleCancel" okText="替换" wrapClassName='ai-rag-generate-prompt-modal'>
|
||||
<div class="prompt">
|
||||
<div class="prompt-left">
|
||||
<div class="prompt-left-title">提示词生成器</div>
|
||||
<div class="prompt-left-desc">提示词生成器使用配置的模型来优化提示词,以获得更高的质量和更好的结构。请写出清晰详细的说明。</div>
|
||||
<a-divider></a-divider>
|
||||
<div class="prompt-left-try">
|
||||
<div class="prompt-left-try-title">试一试</div>
|
||||
</div>
|
||||
<div class="instructions">
|
||||
<div class="instructions-content" v-for="item in instructionsList" @click="instructionsClick(item.value)">
|
||||
<Icon :icon="item.icon" size="14" color="#676f83"></Icon>
|
||||
<div class="instructions-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prompt-left-textarea">
|
||||
<div class="command">指令</div>
|
||||
<a-textarea v-model:value="prompt" :autoSize="{ minRows: 8, maxRows: 8 }"></a-textarea>
|
||||
</div>
|
||||
<a-button @click="generatedPrompt" class="prompt-left-btn" type="primary" :loading="loading">
|
||||
<span style="align-items: center; display: flex" v-if="!loading">
|
||||
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"
|
||||
></path>
|
||||
</svg>
|
||||
<span style="margin-left: 4px">生成</span>
|
||||
</span>
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="prompt-right">
|
||||
<div v-if="!loading && !content">
|
||||
<svg width="6em" height="6em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"
|
||||
></path>
|
||||
</svg>
|
||||
<div>在左侧描述您的用例,</div>
|
||||
<div>编排预览将在此处显示。</div>
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<a-spin :spinning="loading" tip="为您编排应用程序中…"></a-spin>
|
||||
</div>
|
||||
<div v-if="content">
|
||||
<a-textarea v-model:value="content" :autoSize="{ minRows: 18, maxRows: 18 }"></a-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModalInner } from '@/components/Modal';
|
||||
import { promptGenerate } from '@/views/super/airag/aiapp/AiApp.api';
|
||||
|
||||
export default {
|
||||
name: 'AiAppGeneratedPrompt',
|
||||
components: {
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['ok', 'register'],
|
||||
setup(props, { emit }) {
|
||||
//提示词
|
||||
const prompt = ref<string>('');
|
||||
//加载
|
||||
const loading = ref<boolean>(false);
|
||||
//显示文本
|
||||
const content = ref<string>('');
|
||||
//指令提示词
|
||||
const instructionsList = ref<any>([
|
||||
{ name: 'python代码助手', value: 'python', icon: 'ant-design:code-outlined' },
|
||||
{ name: '翻译器', value: 'translator', icon: 'ant-design:translation-outlined' },
|
||||
{ name: '会议助手', value: 'meeting', icon: 'ant-design:team-outlined' },
|
||||
{ name: '润色文章', value: 'article', icon: 'ant-design:profile-outlined' },
|
||||
{ name: 'sql生成器', value: 'sql', icon: 'ant-design:console-sql-outlined' },
|
||||
{ name: '旅行规划师', value: 'travel', icon: 'ant-design:car-outlined' },
|
||||
{ name: 'linux专家', value: 'linux', icon: 'ant-design:fund-projection-screen-outlined' },
|
||||
{ name: '内容提炼器', value: 'content', icon: 'ant-design:read-outlined' },
|
||||
]);
|
||||
//指令
|
||||
const tip = ref<any>({
|
||||
python: '你是一个python专家,可以帮助用户编写和纠错代码。',
|
||||
translator: '一个可以将多种语言翻译为中文的翻译器。',
|
||||
meeting: '将会议内容提炼总结,包括讨论主题、关键要点和待办事项。',
|
||||
article: '用高超的编辑技巧改进我的文章。',
|
||||
sql: '根据用户的描述,生成sql语句,要支持引导用户提供表结构',
|
||||
travel: '你是一个旅行规划师,擅长帮助用户轻松规划他们的旅行',
|
||||
linux: '你是一个linux专家,擅长解决各种linux相关的问题。',
|
||||
content: '你是一个阅读理解大师,可以阅读用户提供的文章,并提炼主要内容输出给用户。',
|
||||
});
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
content.value = '';
|
||||
loading.value = false;
|
||||
prompt.value = '';
|
||||
setModalProps({ height: 500 });
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
emit('ok', content.value);
|
||||
handleCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成
|
||||
*/
|
||||
function generatedPrompt() {
|
||||
content.value = '';
|
||||
loading.value = true;
|
||||
promptGenerate({ prompt: prompt.value })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
content.value = res.result;
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令点击事件
|
||||
*/
|
||||
function instructionsClick(value) {
|
||||
prompt.value = tip.value[value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
prompt,
|
||||
generatedPrompt,
|
||||
instructionsList,
|
||||
loading,
|
||||
instructionsClick,
|
||||
content,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.prompt {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.prompt-left {
|
||||
width: 50%;
|
||||
padding: 20px;
|
||||
border-right: 1px solid #10182814;
|
||||
.prompt-left-title {
|
||||
background: linear-gradient(92deg, #2250f2 -29.55%, #0ebcf3 75.22%);
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
line-height: 28px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
.prompt-left-desc {
|
||||
color: #676f83;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.prompt-left-try {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.prompt-left-try-title {
|
||||
color: #676f83;
|
||||
line-height: 18px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.prompt-left-textarea {
|
||||
margin-top: 25px;
|
||||
.command {
|
||||
color: #101828;
|
||||
line-height: 15px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
.prompt-left-btn {
|
||||
width: 80px;
|
||||
margin-top: 10px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.prompt-right {
|
||||
padding: 20px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
svg {
|
||||
color: #676f83;
|
||||
}
|
||||
}
|
||||
.instructions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.instructions-content {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-radius: 5px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.instructions-name {
|
||||
color: #354052;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 2px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
:deep(.ant-divider-horizontal) {
|
||||
margin: 12px 0;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.ai-rag-generate-prompt-modal {
|
||||
.jeecg-modal-content > .scroll-container {
|
||||
padding: 0;
|
||||
|
||||
& > .scrollbar__wrap {
|
||||
overflow: hidden;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="800px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<BasicForm @register="registerForm">
|
||||
<template #typeSlot="{ model, field }">
|
||||
<a-radio-group v-model:value="type" style="display: flex">
|
||||
<a-card
|
||||
v-for="item in appTypeOption"
|
||||
style="margin-right: 10px; cursor: pointer; width: 100%"
|
||||
@click="handleTypeClick(item.value)"
|
||||
:style="type === item.value ? { borderColor: '#3370ff' } : {}"
|
||||
>
|
||||
<a-radio :value="item.value">
|
||||
<div class="type-title">{{ item.title }}</div>
|
||||
<div class="type-desc">{{ item.desc }}</div>
|
||||
</a-radio>
|
||||
</a-card>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModal, useModalInner } from '@/components/Modal';
|
||||
|
||||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { useForm } from '@/components/Form';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { formSchema } from '../AiApp.data';
|
||||
import { initDictOptions } from '@/utils/dict';
|
||||
import { saveApp } from '@/views/super/airag/aiapp/AiApp.api';
|
||||
|
||||
export default {
|
||||
name: 'AiAppModal',
|
||||
components: {
|
||||
BasicForm,
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('创建应用');
|
||||
|
||||
//保存或修改
|
||||
const isUpdate = ref<boolean>(false);
|
||||
|
||||
//app类型
|
||||
const appTypeOption = ref<any>([]);
|
||||
//应用类型
|
||||
const type = ref<string>('chatSimple');
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
layout: 'vertical',
|
||||
wrapperCol: { span: 24 },
|
||||
});
|
||||
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
type.value = 'chatSimple';
|
||||
//update-begin---author:wangshuai---date:2025-03-11---for: 【QQYUN-11324】8.修改弹窗head---
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-11---for:【QQYUN-11324】8.修改弹窗head---
|
||||
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
values.type = type.value;
|
||||
let result = await saveApp(values);
|
||||
if (result) {
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//update-begin---author:wangshuai---date:2025-03-11---for: 【QQYUN-11324】8.修改弹窗head---
|
||||
if(isUpdate.value){
|
||||
//刷新列表
|
||||
emit('success', values);
|
||||
}else{
|
||||
//刷新列表
|
||||
emit('success', result);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-11---for: 【QQYUN-11324】8.修改弹窗head---
|
||||
}
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
//初始化AI应用类型
|
||||
initAppTypeOption();
|
||||
|
||||
function initAppTypeOption() {
|
||||
initDictOptions('ai_app_type').then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
for (const datum of data) {
|
||||
if (datum.value === 'chatSimple') {
|
||||
datum['desc'] = '适合新手创建小助手';
|
||||
} else if (datum.value === 'chatFLow') {
|
||||
datum['desc'] = '适合高级用户自定义小助手的工作流';
|
||||
}
|
||||
}
|
||||
}
|
||||
appTypeOption.value = data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用类型点击事件
|
||||
*/
|
||||
function handleTypeClick(val) {
|
||||
type.value = val;
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
registerForm,
|
||||
title,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
appTypeOption,
|
||||
type,
|
||||
handleTypeClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.type-title {
|
||||
color: #1d2025;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.type-desc {
|
||||
color: #8f959e;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,95 @@
|
||||
<!--手动录入text-->
|
||||
<template>
|
||||
<BasicModal title="参数设置" destroyOnClose @register="registerModal" :canFullscreen="false" width="560px" @ok="handleOk" @cancel="handleCancel">
|
||||
<AiModelSeniorForm ref="aiModelSeniorFormRef" :type="type"></AiModelSeniorForm>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModalInner } from '@/components/Modal';
|
||||
|
||||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { MarkdownViewer } from '@/components/Markdown';
|
||||
import AiModelSeniorForm from '/@/views/super/airag/aimodel/components/AiModelSeniorForm.vue';
|
||||
|
||||
export default {
|
||||
name: 'AiAppParamsSettingModal',
|
||||
components: {
|
||||
MarkdownViewer,
|
||||
BasicForm,
|
||||
BasicModal,
|
||||
AiModelSeniorForm,
|
||||
},
|
||||
emits: ['ok', 'register'],
|
||||
setup(props, { emit }) {
|
||||
let aiModelSeniorFormRef = ref()
|
||||
//类型
|
||||
const type = ref<string>('');
|
||||
//注册modal
|
||||
const [registerModal, { closeModal }] = useModalInner(async (data) => {
|
||||
type.value = data.type;
|
||||
if(data.type === 'model'){
|
||||
if(!data.metadata.hasOwnProperty("temperature") ){
|
||||
data.metadata['temperature'] = 0.7;
|
||||
}
|
||||
}else{
|
||||
if(!data.metadata.hasOwnProperty("topNumber") ){
|
||||
data.metadata['topNumber'] = 4;
|
||||
}
|
||||
if(!data.metadata.hasOwnProperty("similarity") ){
|
||||
data.metadata['similarity'] = 0.76;
|
||||
}
|
||||
}
|
||||
setTimeout(()=>{
|
||||
aiModelSeniorFormRef.value.setModalParams(data.metadata);
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* 弹窗点击事件
|
||||
*/
|
||||
function handleOk() {
|
||||
let emitChange = aiModelSeniorFormRef.value.emitChange();
|
||||
emit('ok',emitChange);
|
||||
handleCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗关闭事件
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
type,
|
||||
aiModelSeniorFormRef,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.content {
|
||||
margin-top: 20px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.title-tag {
|
||||
color: #477dee;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="800px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<BasicForm @register="registerForm"></BasicForm>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModalInner } from '@/components/Modal';
|
||||
|
||||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { useForm } from '@/components/Form';
|
||||
import { quickCommandFormSchema} from '../AiApp.data';
|
||||
|
||||
export default {
|
||||
name: 'AiAppQuickCommandModal',
|
||||
components: {
|
||||
BasicForm,
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['ok', 'update-ok', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('添加指令');
|
||||
|
||||
//保存或修改
|
||||
const isUpdate = ref<boolean>(false);
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
|
||||
schemas: quickCommandFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
layout: 'vertical',
|
||||
wrapperCol: { span: 24 },
|
||||
});
|
||||
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
setModalProps({ minHeight: 200, bodyStyle: { padding: '10px' } });
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
if(isUpdate.value){
|
||||
emit('update-ok',values);
|
||||
}else{
|
||||
emit('ok', values);
|
||||
}
|
||||
handleCancel();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
registerForm,
|
||||
title,
|
||||
handleOk,
|
||||
handleCancel
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.type-title {
|
||||
color: #1d2025;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.type-desc {
|
||||
color: #8f959e;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" :width="width" :title="title" :footer="null">
|
||||
<!-- 嵌入表单 -->
|
||||
<div v-if="type === 'menu'">
|
||||
<a-form layout="vertical" :model="appData">
|
||||
<a-form-item label="菜单名称">
|
||||
<a-input v-model:value="appData.name" readonly/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单地址">
|
||||
<a-input v-model:value="appData.menu" readonly/>
|
||||
</a-form-item>
|
||||
<a-form-item style="text-align:right">
|
||||
<a-button @click.prevent="copyMenu">复制菜单</a-button>
|
||||
<a-button type="primary" style="margin-left: 10px" @click="copySql">复制SQL</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<!-- 嵌入网站 -->
|
||||
<div v-else-if="type === 'web'" class="web">
|
||||
|
||||
<div style="display: flex;margin: 0 auto">
|
||||
<div :class="activeKey===1?'active':''" class="web-img" @click="handleImageClick(1)">
|
||||
<img src="../img/webEmbedded.png" />
|
||||
</div>
|
||||
<div style="margin-left: 10px" :class="activeKey===2?'active':''" class="web-img" @click="handleImageClick(2)">
|
||||
<img src="../img/iconWebEmbedded.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-title" v-if="activeKey === 1">
|
||||
将以下 iframe 嵌入到你的网站中的目标位置
|
||||
</div>
|
||||
<div class="web-title" v-else>
|
||||
将以下 script 添加到网页的body区域中
|
||||
</div>
|
||||
<div class="web-code" v-if="activeKey === 1">
|
||||
<div class="web-code-title">
|
||||
<div class="web-code-desc">
|
||||
html
|
||||
</div>
|
||||
<Icon class="pointer" icon="ant-design:copy-outlined" @click="copyIframe(1)"></Icon>
|
||||
</div>
|
||||
<div class="web-code-iframe">
|
||||
<pre> {{getIframeText(1)}} </pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="web-code" v-if="activeKey === 2">
|
||||
<div class="web-code-title">
|
||||
<div class="web-code-desc">
|
||||
html
|
||||
</div>
|
||||
<Icon class="pointer" icon="ant-design:copy-outlined" @click="copyIframe(2)"></Icon>
|
||||
</div>
|
||||
<div class="web-code-iframe">
|
||||
<pre> {{getIframeText(2)}} </pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModalInner } from '@/components/Modal';
|
||||
|
||||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { buildUUID } from '@/utils/uuid';
|
||||
import { copyTextToClipboard } from '@/hooks/web/useCopyToClipboard';
|
||||
import { isDevMode } from '/@/utils/env';
|
||||
|
||||
export default {
|
||||
name: 'AiAppSendModal',
|
||||
components: {
|
||||
BasicForm,
|
||||
BasicModal,
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
//标题
|
||||
const title = ref<string>('嵌入网站');
|
||||
const $message = useMessage();
|
||||
//类型
|
||||
const type = ref<string>('web');
|
||||
//应用信息
|
||||
const appData = ref<any>({});
|
||||
//弹窗宽度
|
||||
const width = ref<string>("800px");
|
||||
//选中的key
|
||||
const activeKey = ref<number>(1);
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
type.value = data.type;
|
||||
appData.value = data.data;
|
||||
appData.value.menu = "/ai/chat/"+ data.data.id
|
||||
activeKey.value = 1;
|
||||
let minHeight = 220;
|
||||
if(data.type === 'web'){
|
||||
title.value = '嵌入网站';
|
||||
width.value = '640px';
|
||||
minHeight = 500
|
||||
}else{
|
||||
title.value = '配置菜单';
|
||||
width.value = '500px';
|
||||
}
|
||||
setModalProps({ height: minHeight, bodyStyle: { padding: '10px' } });
|
||||
});
|
||||
|
||||
/**
|
||||
* 复制菜单
|
||||
*/
|
||||
function copyMenu() {
|
||||
copyText(appData.value.menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制sql
|
||||
*/
|
||||
function copySql() {
|
||||
const insertMenuSql = `INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
|
||||
VALUES ('${buildUUID()}', NULL, '${appData.value.name}', '${appData.value.menu}', '1', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', null, NULL, NULL, 0)`;
|
||||
copyText(insertMenuSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前文本
|
||||
*/
|
||||
function getIframeText(value) {
|
||||
let locationUrl = document.location.protocol +"//" + window.location.host;
|
||||
//update-begin---author:wangshuai---date:2025-03-20---for:【QQYUN-11649】【AI】应用嵌入,支持一个小图标点击出聊天---
|
||||
if(value === 1){
|
||||
return '<iframe\n' +
|
||||
' src="'+locationUrl+'/ai/app/chat/'+appData.value.id+'"\n' +
|
||||
' style="width: 100%; height: 100%;">\n' +
|
||||
'</iframe>';
|
||||
}else{
|
||||
//update-begin---author:wangshuai---date:2025-03-28---for:【QQYUN-11649】应用嵌入,支持一个小图标点击出聊天---
|
||||
let path = "/src/views/super/airag/aiapp/chat/js/chat.js"
|
||||
if(!isDevMode()){
|
||||
path = "/chat/chat.js";
|
||||
}
|
||||
let text ='<script src=' + locationUrl + path +' id="e7e007dd52f67fe36365eff636bbffbd">'+'<'+'/script>';
|
||||
text += '\n <'+'script>\n';
|
||||
text += ' createAiChat({\n' +
|
||||
' appId:"'+ appData.value.id +'",\n';
|
||||
text += ' // 支持top-left左上, top-right右上, bottom-left左下, bottom-right右下\n';
|
||||
text += ' iconPosition:"bottom-right"\n';
|
||||
text += ' })\n';
|
||||
text += ' <'+'/script>';
|
||||
return text;
|
||||
//update-end---author:wangshuai---date:2025-03-28---for:【QQYUN-11649】应用嵌入,支持一个小图标点击出聊天---
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-20---for:【QQYUN-11649】【AI】应用嵌入,支持一个小图标点击出聊天---
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制iframe
|
||||
*/
|
||||
function copyIframe(value) {
|
||||
copyText(getIframeText(value));
|
||||
}
|
||||
|
||||
// 复制文本到剪贴板
|
||||
function copyText(text: string) {
|
||||
const success = copyTextToClipboard(text);
|
||||
if (success) {
|
||||
$message.createMessage.success('复制成功!');
|
||||
} else {
|
||||
$message.createMessage.error('复制失败!');
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片点击事件
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
function handleImageClick(value) {
|
||||
activeKey.value = value;
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
type,
|
||||
appData,
|
||||
copySql,
|
||||
copyMenu,
|
||||
width,
|
||||
copyIframe,
|
||||
getIframeText,
|
||||
activeKey,
|
||||
handleImageClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
color: #1d2025;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
color: #8f959e;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.web{
|
||||
padding: 0 10px;
|
||||
}
|
||||
.web-title{
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
}
|
||||
.web-img{
|
||||
border-width: 1.5px;
|
||||
width: 240px;
|
||||
margin-top: 20px;
|
||||
border-radius: 6px;
|
||||
img{
|
||||
border-radius: 6px;
|
||||
width: 240px;
|
||||
height: 150px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.active{
|
||||
border-color: rgb(41 112 255);
|
||||
}
|
||||
.web-code{
|
||||
border-width: 1.5px;
|
||||
margin-top: 20px;
|
||||
background-color: #f9fafb;
|
||||
border-color: #10182814;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
.web-code-title{
|
||||
width: 100%;
|
||||
padding:10px;
|
||||
background-color: #f2f4f7;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.web-code-desc{
|
||||
color: #354052;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
}
|
||||
.web-code-iframe{
|
||||
padding: 15px;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: #354052;
|
||||
}
|
||||
}
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user