Browse Source

AI智能体: 移动版

lwx_v12
han2015 2 months ago
parent
commit
8ff9317c4a
  1. 1434
      package-lock.json
  2. 3
      package.json
  3. 5
      src/utils/router/index.ts
  4. 95
      src/views/common/bottom/FloatingBall.vue
  5. 14
      src/views/common/bottom/index.vue
  6. 207
      src/views/doc/agent.vue
  7. 2
      src/views/doc/manage.vue
  8. 8
      src/views/doc/space.vue

1434
package-lock.json

File diff suppressed because it is too large

3
package.json

@ -41,6 +41,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@crazydos/vue-markdown": "^1.1.4",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@nutui/nutui": "^4.0.0", "@nutui/nutui": "^4.0.0",
"@onlyoffice/document-editor-vue": "^1.6.1", "@onlyoffice/document-editor-vue": "^1.6.1",
@ -63,6 +64,8 @@
"pinia": "^2.2.4", "pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.1.1", "pinia-plugin-persistedstate": "^4.1.1",
"quill": "^2.0.3", "quill": "^2.0.3",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sass": "^1.80.3", "sass": "^1.80.3",
"scss": "^0.2.4", "scss": "^0.2.4",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",

5
src/utils/router/index.ts

@ -91,6 +91,11 @@ export const staticRouting : RouteRecordRaw[] = [
component: () => import('@/views/doc/onlyoffice.vue'), component: () => import('@/views/doc/onlyoffice.vue'),
meta: { hidden: true }, meta: { hidden: true },
}, },
{
path: '/agent',
component: () => import('@/views/doc/agent.vue'),
meta: { hidden: true },
},
{ {
path: '/spaces/:spaceid', path: '/spaces/:spaceid',
name: 'spaces', name: 'spaces',

95
src/views/common/bottom/FloatingBall.vue

@ -0,0 +1,95 @@
<!-- FloatingBall.vue -->
<script setup>
import { ref, computed } from 'vue'
const emit = defineEmits(['click'])
const x = ref(window.innerWidth - 60)
const y = ref(window.innerHeight - 150)
const isDragging = ref(false)
const ballStyle = computed(() => ({
position: 'fixed',
left: `${x.value}px`,
top: `${y.value}px`,
zIndex: 99,
userSelect: 'none',
cursor: isDragging.value ? 'grabbing' : 'grab'
}))
const getPoint = e => (e.touches ? e.touches[0] : e)
let startX = 0, startY = 0, initX = 0, initY = 0
const MOVE_THRESHOLD = 5
function startDrag(e) {
const point = getPoint(e)
startX = point.clientX
startY = point.clientY
initX = x.value
initY = y.value
isDragging.value = false
window.addEventListener('mousemove', onMove, { passive: true })
window.addEventListener('mouseup', onUp)
window.addEventListener('touchmove', onMove, { passive: true })
window.addEventListener('touchend', onUp)
e.preventDefault() //
}
function onMove(e) {
const p = getPoint(e)
const dx = p.clientX - startX
const dy = p.clientY - startY
if (!isDragging.value && (Math.abs(dx) > MOVE_THRESHOLD || Math.abs(dy) > MOVE_THRESHOLD)) {
isDragging.value = true
}
if (isDragging.value) {
x.value = Math.max(0, Math.min(window.innerWidth - 50, initX + dx))
y.value = Math.max(0, Math.min(window.innerHeight - 50, initY + dy))
}
}
function onUp() {
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
window.removeEventListener('touchmove', onMove)
window.removeEventListener('touchend', onUp)
if (!isDragging.value) {
emit('click')
}
isDragging.value = false
}
</script>
<template>
<div
class="float-ball"
:style="ballStyle"
@mousedown="startDrag"
@touchstart="startDrag"
@click.stop
>
<slot>AI</slot>
</div>
</template>
<style scoped>
.float-ball {
width: 50px;
height: 50px;
border-radius: 50%;
background: #409eff;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
</style>

14
src/views/common/bottom/index.vue

@ -7,6 +7,9 @@
import { useRoute,useRouter } from 'vue-router' import { useRoute,useRouter } from 'vue-router'
import SvgIcon from '@/components/svgIcon/index.vue' import SvgIcon from '@/components/svgIcon/index.vue'
import FloatingBall from './FloatingBall.vue'
const goTop = () => window.scrollTo({ top: 0, behavior: 'smooth' })
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const footerActive = ref<number>(route.query.fa?route.query.fa:1) const footerActive = ref<number>(route.query.fa?route.query.fa:1)
@ -36,6 +39,7 @@ const openPage = (val:number) => {
router.push({ path: "/"}); router.push({ path: "/"});
} }
} }
</script> </script>
<template> <template>
<div class="contentBetween"> <div class="contentBetween">
@ -59,9 +63,19 @@ const openPage = (val:number) => {
<div class="footSvg"><SvgIcon icon-class="user" :size="20" /></div> <div class="footSvg"><SvgIcon icon-class="user" :size="20" /></div>
<div>我的</div> <div>我的</div>
</div> </div>
<FloatingBall @click="router.push({ path: '/agent',query:{fa:8}});">AI</FloatingBall>
</div> </div>
</template> </template>
<style lang='scss' scoped> <style lang='scss' scoped>
.fixBtn{
x: 100px;
y:300px;
width: 50px;
height: 50px;
position: fixed;
}
.footerBox{ .footerBox{
width: 20%; width: 20%;
text-align: center; text-align: center;

207
src/views/doc/agent.vue

@ -6,14 +6,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
Promotion, Promotion,
Remove Remove,
ArrowLeft,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { doAiTraining,doAiChat,aiChatData,getAiChatList,getAiChat,setAiChat,delAiChat} from "@/api/doc/space" import { userStror } from "@/utils/pinia/stores/modules/userOrders";
import {ElText,ElInput, ButtonInstance} from "element-plus"; import router from "@/utils/router";
import { doAiChat,getAiChatList,getAiChat,setAiChat,delAiChat,getAiagentList} from "@/api/doc/space"
import {ElText,ElInput} from "element-plus";
import { VueMarkdown } from '@crazydos/vue-markdown' import { VueMarkdown } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw' import rehypeRaw from 'rehype-raw'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
const userStore = userStror();
const userid=ref("")
// //
const checkedModel = ref([]) const checkedModel = ref([])
// //
@ -25,15 +30,11 @@ const controller = ref<AbortController | null>(null)
const inputState=ref(true) const inputState=ref(true)
const conversations=ref<chatRecord[]>([]) const conversations=ref<chatRecord[]>([])
const centHoverItem=ref("") const centHoverItem=ref("")
const interact_msg=ref<{ask:boolean,think:string,content:string,docinfo?:any[]}[]>([]) const interact_msg=ref<{ask:boolean,think:string,content:string,docinfo?:any[]}[]>([])
const props = withDefaults(defineProps<{ const agent=ref<{name:string,uuid:string}>({name:"通用AI",uuid:import.meta.env.VITE_DEFAULT_AI_AGENT})
userid:string, const agentList=ref<{name:string,uuid:string}[]>([{name:"通用AI",uuid:import.meta.env.VITE_DEFAULT_AI_AGENT}])
closefunc:()=>void,
agent:{model:boolean,name:string,uuid:string}
}>(),{})
const respMsg=ref("") const respMsg=ref("")
const drawerModel=ref(false)
// //
interface message{ interface message{
@ -78,12 +79,12 @@ async function onSendTextToAI(){
let docinfo:any=[] let docinfo:any=[]
controller.value = new AbortController(); controller.value = new AbortController();
try{ try{
const res= await doAiChat(`${baseURL}/aibot/agents/${props.agent.uuid}/chat`,{ const res= await doAiChat(`${baseURL}/aibot/agents/${agent.value.uuid}/chat`,{
inputs: params, inputs: params,
query:myquestion.value, query:myquestion.value,
response_mode:"streaming", response_mode:"streaming",
conversation_id:conversation.value, conversation_id:conversation.value,
user:atob(props.userid),//base64 user:userid.value,//base64
},controller.value.signal },controller.value.signal
) )
@ -137,8 +138,9 @@ async function onSendTextToAI(){
respMsg.value="" respMsg.value=""
setAiChat({ setAiChat({
"userid":atob(props.userid), "userid":userid.value,
"uuid":conversation.value, "uuid":conversation.value,
"agentuuid":agent.value.uuid,
"brief":interact_msg.value[0].content, "brief":interact_msg.value[0].content,
"content":JSON.stringify(interact_msg.value) "content":JSON.stringify(interact_msg.value)
}) })
@ -147,26 +149,30 @@ async function onSendTextToAI(){
// //
function loadKnownLibList(){ function loadKnownLibList(){
//userid base64 //userid base64
getAiChatList({"userid":atob(props.userid)}).then(resp=>{ getAiChatList({"userid":userid.value}).then(resp=>{
conversations.value=resp.data conversations.value=resp.data
}) })
} }
// //
function showChat(uuid:string){ function showChat(uuid:string){
drawerModel.value=false;
getAiChat({ getAiChat({
"userid":atob(props.userid), "userid":userid.value,
"uuid":uuid "uuid":uuid
}).then(resp=>{ }).then(resp=>{
interact_msg.value = JSON.parse(resp.data.content) interact_msg.value = JSON.parse(resp.data.content)
conversation.value=resp.data.uuid conversation.value=resp.data.uuid
if(resp.data.agentuuid!=""){
agent.value={name:"会话",uuid:resp.data.agentuuid}
}
}) })
} }
// //
function onDelChat(uuid:string){ function onDelChat(uuid:string){
delAiChat({ delAiChat({
"userid":atob(props.userid), "userid":userid.value,
"uuid":uuid "uuid":uuid
}).then(resp=>{ }).then(resp=>{
loadKnownLibList() loadKnownLibList()
@ -182,10 +188,11 @@ function handleMouseLeave(){
} }
function newContext(){ function newContext(){
drawerModel.value=false;
const c =conversations.value.find(c=>c.uuid==conversation.value) const c =conversations.value.find(c=>c.uuid==conversation.value)
if(!c){ if(!c && interact_msg.value.length>0){
conversations.value.push({ conversations.value.push({
"agentuuid":props.agent.uuid, "agentuuid":agent.value.uuid,
"uuid":conversation.value, "uuid":conversation.value,
"brief":interact_msg.value[0].content, "brief":interact_msg.value[0].content,
"messages":interact_msg.value "messages":interact_msg.value
@ -201,109 +208,128 @@ function newContext(){
function resetContext(){ function resetContext(){
interact_msg.value=[] interact_msg.value=[]
conversation.value="" conversation.value=""
props.closefunc()
} }
//ai //ai
function formatRefContent(content:string){ function formatRefContent(content:string){
let result=content.replaceAll(/"/g,'') let result=content.replace(/"/g,'')
result=result.replaceAll(/Unnamed: /g,' ') result=result.replace(/Unnamed: /g,' ')
return result return result
} }
// //
onMounted(() => { onMounted(() => {
if(userStore.userInfoCont == ""){
userStore.getInfo().then(()=>{
userid.value="p0"+userStore.userInfoCont.userId;
})
}else{
userid.value="p0"+userStore.userInfoCont.userId;
}
getAiagentList().then(resp=>{
agentList.value.push(...resp.data)
})
loadKnownLibList() loadKnownLibList()
}); });
</script> </script>
<template> <template>
<el-drawer <el-drawer
:model-value="agent.model" :model-value="drawerModel"
:title="agent.name+' : 知识库'" :title="agent.name+' : 知识库'"
direction="rtl" direction="ltr"
size="80%" size="86%"
@close="resetContext" @close="drawerModel=false;"
:style="{padding:'17px',backgroundColor:'#f3f3f3'}"> >
<div style="display:grid;grid-template-columns:1fr 4fr; width: 100%;height: 100%;"> <div style="overflow-y: auto;padding: 8px;">
<div style="overflow-y: auto;"> <ul>
<ul> <li class="action_menu" @click="newContext">
<li class="action_menu" @click="newContext"> 新建会话
新建会话 </li>
</li> <li class="agent-item" v-for="ag in agentList" @click="agent=ag;newContext()">
<li class="list_item" v-for="item in conversations" @mouseover="handleMouseEnter(item)" @mouseleave="handleMouseLeave()" @click="showChat(item.uuid)"> <el-icon><Star /></el-icon> <span>{{ ag.name }}</span>
<span>{{ item.brief }}</span> </li>
<el-button v-show="centHoverItem == item.uuid" icon="Delete" size="small" circle @click="(e)=>{e.stopPropagation();onDelChat(item.uuid)}"></el-button> <span><el-icon><Clock /></el-icon> </span>
</li> <li class="list_item" v-for="item in conversations" @click="showChat(item.uuid)">
</ul> <span>{{ item.brief }}</span>
</div> <el-button icon="Delete" size="small" circle @click="(e)=>{e.stopPropagation();onDelChat(item.uuid)}"></el-button>
<div style="position: relative;background: white;overflow-y: auto;"> </li>
<div class="reply_area" > </ul>
<template v-for="msg of interact_msg"> </div>
<el-text v-if="msg.ask" class="t_ask" >{{ msg.content }}</el-text> </el-drawer>
<div v-else class="t_resp"> <div class="navBtn">
<el-text style="white-space: pre-line" v-html="msg.think"></el-text> <el-button type="text" :icon="ArrowLeft" @click="router.back()"> 返回</el-button>
<VueMarkdown :markdown="msg.content" :rehype-plugins="[rehypeRaw]" :remark-plugins="[remarkGfm]" ></VueMarkdown> <el-button icon="expand" size="small" @click="drawerModel=true;"></el-button>
<div v-if="msg.docinfo?.length>0" class="doc_ref"> </div>
引用<hr> <div class="app_container">
<el-tooltip v-for="doc in msg.docinfo" placement="top" effect="dark"> <div class="reply_area" >
<template #content> <template v-for="msg of interact_msg">
<div v-html="formatRefContent(doc.content)" /> <el-text v-if="msg.ask" class="t_ask" >{{ msg.content }}</el-text>
</template> <div v-else class="t_resp">
<span>{{doc.document_name}}</span> <el-text style="white-space: pre-line" v-html="msg.think"></el-text>
</el-tooltip> <VueMarkdown :markdown="msg.content" :rehype-plugins="[rehypeRaw]" :remark-plugins="[remarkGfm]" ></VueMarkdown>
</div> <div v-if="msg.docinfo?.length>0" class="doc_ref">
</div> 引用<hr>
<el-tooltip v-for="doc in msg.docinfo" placement="top" effect="dark">
</template> <template #content>
<VueMarkdown :markdown="respMsg" :rehype-plugins="[rehypeRaw]" class="t_resp"></VueMarkdown> <div v-html="formatRefContent(doc.content)" />
</div> </template>
<div class="question_com" :class="{newquestion:conversation!='' || !inputState}"> <span>{{doc.document_name}}</span>
<h1 v-show="conversation =='' && inputState" style="font-size: 56px;margin: 10px;">恒信高科AI平台</h1> </el-tooltip>
<el-checkbox-group v-model="checkedModel" style="display:flex; margin-bottom: 10px;"> </div>
<el-checkbox-button v-for="mod in aimodels" :value="mod.key">
{{ mod.name }}
</el-checkbox-button>
</el-checkbox-group>
<el-input placeholder="问灵犀..." v-model="myquestion" input-style="border-radius: 20px;"
resize="none" :autosize="{minRows: 4}" type="textarea" />
<el-button :style="{display :inputState ? '':'none'}" type="primary" :icon="Promotion" circle @click="onSendTextToAI"/>
<el-button :style="{display :inputState ? 'none':''}" type="primary" :icon="Remove" circle @click="abortFetch"/>
<span>内容由 AI 生成请仔细甄别</span>
</div> </div>
</div>
</template>
<VueMarkdown :markdown="respMsg" :rehype-plugins="[rehypeRaw]" class="t_resp"></VueMarkdown>
</div> </div>
<div class="question_com" :class="{newquestion:conversation!='' || !inputState}">
<h1 v-show="conversation =='' && inputState" style="font-size: 32px;margin: 10px;">恒信高科AI平台</h1>
<el-checkbox-group v-model="checkedModel" style="display:flex; margin-bottom: 10px;">
<el-checkbox-button v-for="mod in aimodels" :value="mod.key">
{{ mod.name }}
</el-checkbox-button>
</el-checkbox-group>
</el-drawer> <el-input placeholder="问灵犀..." v-model="myquestion" input-style="border-radius: 18px;"
resize="none" :autosize="{minRows: 4}" type="textarea" />
<el-button :style="{display :inputState ? '':'none'}" type="primary" :icon="Promotion" circle @click="onSendTextToAI"/>
<el-button :style="{display :inputState ? 'none':''}" type="primary" :icon="Remove" circle @click="abortFetch"/>
<span>内容由 AI 生成请仔细甄别</span>
</div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.app_container { .app_container {
padding: 10px 30px 0px 30px; padding: 10px;
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: white;
}
.navBtn{
margin: 5px;
position: fixed;
} }
.newquestion{ .newquestion{
bottom: 20px; bottom: 20px;
} }
.question_com{ .question_com{
position: fixed; position: fixed;
width: 52%; padding: 0 13px;
margin: 0 50px;
text-align: center; text-align: center;
display: block; display: block;
button{ button{
position: absolute; position: absolute;
bottom: 25px; bottom: 27px;
right: 5px; right: 20px;
} }
} }
.reply_area{ .reply_area{
display: flex; display: flex;
min-height: 20%; min-height: 20%;
flex-direction: column; flex-direction: column;
margin: 15px 15px 110px 0px; margin: 15px 15px 110px 15px;
} }
.t_ask{ .t_ask{
align-self: end; align-self: end;
@ -311,13 +337,11 @@ onMounted(() => {
background-color: rgb(188 211 241); background-color: rgb(188 211 241);
padding: 0 30px; padding: 0 30px;
border-radius:10px; border-radius:10px;
margin: 15px;
} }
.t_resp{ .t_resp{
align-self: start; align-self: start;
line-height: 30px; line-height: 23px;
margin: 20px 33px; font-size: 13px;
font-size: 16px;
color: black; color: black;
} }
.dynamic-width-message-box-byme .el-message-box__message{ .dynamic-width-message-box-byme .el-message-box__message{
@ -330,11 +354,16 @@ onMounted(() => {
border-radius: 8px; border-radius: 8px;
border: 1px solid #c9c6c6; border: 1px solid #c9c6c6;
} }
.agent-item{
padding: 4px 8px;
margin: 3px 14px 3px 0;
border-radius: 8px;
}
.list_item{ .list_item{
display: flex; display: flex;
background-color: #dddddd; background-color: #dddddd;
padding: 10px 8px; padding: 8px;
margin: 3px 14px 3px 0; margin: 3px 14px 3px 10px;
border-radius: 8px; border-radius: 8px;
span{ span{
width: 90%; width: 90%;
@ -351,10 +380,10 @@ onMounted(() => {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
hr{ hr{
width: 90%; width: 70%;
margin: inherit; margin: 11px;
border: none; border: none;
border-top: .5px solid rgb(26 25 25 / 23%); border-top: 0px solid rgb(26 25 25 / 23%);
} }
span{ span{
width: 300px; width: 300px;

2
src/views/doc/manage.vue

@ -614,7 +614,7 @@ onMounted(() => {
.app_container { .app_container {
padding: 10px 15px 0px 15px; padding: 10px 15px 0px 15px;
height: calc(100vh - 65px); height: calc(100vh - 65px);
overflow: hidden; overflow: auto;
position: relative; position: relative;
} }
.nav-header{ .nav-header{

8
src/views/doc/space.vue

@ -46,7 +46,7 @@ const matterList = ref<matterInfo[]>([])
const searchname=ref("") // const searchname=ref("") //
const newdirName=ref("") // const newdirName=ref("") //
const currentHoverRow=ref<matterInfo>({}) //table const currentHoverRow=ref<matterInfo>({}) //table
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) // const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true,permits:{}}]) //
const currentNode=ref<matterInfo>({}) // const currentNode=ref<matterInfo>({}) //
const dynamicVNode = ref<VNode | null>(null) //permission const dynamicVNode = ref<VNode | null>(null) //permission
@ -518,6 +518,7 @@ onMounted(() => {
spaceName.value=c_space.name spaceName.value=c_space.name
owner.value=c_space.userUuid owner.value=c_space.userUuid
breadcrumbList.value[0].permits=c_space.permits
if (c_space.manager) { if (c_space.manager) {
ismanager.value=true ismanager.value=true
@ -577,9 +578,6 @@ onMounted(() => {
<el-button size="small" icon="plus" @click="createDir">新建</el-button> <el-button size="small" icon="plus" @click="createDir">新建</el-button>
</el-col> </el-col>
<el-button size="small" style="margin-left: auto;" @click="()=>currentAgent.model=true">AI助手</el-button>
</el-row> </el-row>
<el-row :gutter="24" style="overflow-y: auto;height: 80%;"> <el-row :gutter="24" style="overflow-y: auto;height: 80%;">
@ -682,7 +680,7 @@ onMounted(() => {
.app_container { .app_container {
padding: 10px 15px 0px 15px; padding: 10px 15px 0px 15px;
height: calc(100vh - 65px); height: calc(100vh - 65px);
overflow: hidden; overflow: auto;
position: relative; position: relative;
} }
.nav-header{ .nav-header{

Loading…
Cancel
Save