|
@@ -0,0 +1,846 @@
|
|
|
|
|
+<script>
|
|
|
|
|
+import Prompt from '!!raw-loader!./system/prompt.md'
|
|
|
|
|
+const systemPlugin = require.context('./system/plugin', true, /\.vue$/)
|
|
|
|
|
+const otherPlugin = require.context('./plugin', true, /\.vue$/)
|
|
|
|
|
+const components = {}
|
|
|
|
|
+otherPlugin.keys().forEach(key => {
|
|
|
|
|
+ initComponent(otherPlugin(key))
|
|
|
|
|
+})
|
|
|
|
|
+systemPlugin.keys().forEach(key => {
|
|
|
|
|
+ initComponent(systemPlugin(key))
|
|
|
|
|
+})
|
|
|
|
|
+function initComponent(plugin) {
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ if (data.type === 'comp') {
|
|
|
|
|
+ components[plugin.default.options.name] = plugin.default
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+import Vue from 'vue'
|
|
|
|
|
+import { GoogleGenAI, Type } from '@google/genai'
|
|
|
|
|
+export default Vue.extend({
|
|
|
|
|
+ name: 'Index',
|
|
|
|
|
+ components: components,
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ AiKey: '',
|
|
|
|
|
+ AiModel: '',
|
|
|
|
|
+ aiConfig: {},
|
|
|
|
|
+ showText: false,
|
|
|
|
|
+ showMessage: false,
|
|
|
|
|
+ showMenu: false,
|
|
|
|
|
+ text: '',
|
|
|
|
|
+ message: '',
|
|
|
|
|
+ timer: null,
|
|
|
|
|
+ target: null,
|
|
|
|
|
+ inProcess: false,
|
|
|
|
|
+ aiState: '0',
|
|
|
|
|
+ workLength: 0,
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ aiContext: [],
|
|
|
|
|
+ tool_name: '',
|
|
|
|
|
+ tool_input: '',
|
|
|
|
|
+ tool_data: {},
|
|
|
|
|
+ toolList: [],
|
|
|
|
|
+ taskTollList: [],
|
|
|
|
|
+ pluginList: [],
|
|
|
|
|
+ componentList: [],
|
|
|
|
|
+ pageList: [],
|
|
|
|
|
+ taskList: [],
|
|
|
|
|
+ prompt: Prompt,
|
|
|
|
|
+ aboutList: [
|
|
|
|
|
+ '优化一下首页的SEO数据',
|
|
|
|
|
+ '翻译一下关于我们页面',
|
|
|
|
|
+ '站内有多少种产品类型'
|
|
|
|
|
+ ],
|
|
|
|
|
+ aboutText: '',
|
|
|
|
|
+ guideData: {
|
|
|
|
|
+ step: 0,
|
|
|
|
|
+ apiKey: '',
|
|
|
|
|
+ model: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.importPlugin()
|
|
|
|
|
+ this.getConfig()
|
|
|
|
|
+ this.workStart()
|
|
|
|
|
+ this.scrollText()
|
|
|
|
|
+ this.$bus.$on('sentAi', (tool_data, tool_role) => {
|
|
|
|
|
+ this.sentAi(tool_data, tool_role)
|
|
|
|
|
+ })
|
|
|
|
|
+ this.$bus.$on('runWork', (workData) => {
|
|
|
|
|
+ this.runWork(workData)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ scrollText() {
|
|
|
|
|
+ let index = 0
|
|
|
|
|
+ this.aboutText = this.aboutList[index]
|
|
|
|
|
+ setInterval(() => {
|
|
|
|
|
+ if (index === this.aboutList.length) {
|
|
|
|
|
+ index = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ this.aboutText = this.aboutList[index]
|
|
|
|
|
+ index += 1
|
|
|
|
|
+ }, 4000)
|
|
|
|
|
+ },
|
|
|
|
|
+ importPlugin() {
|
|
|
|
|
+ systemPlugin.keys().forEach(key => {
|
|
|
|
|
+ const plugin = systemPlugin(key)
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ if (data.type === 'page') {
|
|
|
|
|
+ this.importAsPage(plugin)
|
|
|
|
|
+ } else if (data.type === 'tool') {
|
|
|
|
|
+ this.importAsTool(plugin)
|
|
|
|
|
+ } else if (data.type === 'task') {
|
|
|
|
|
+ this.importAsTask(plugin)
|
|
|
|
|
+ } else if (data.type === 'comp') {
|
|
|
|
|
+ this.importAsComp(plugin)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log(plugin.default.options.name + '类型未知,无法加载')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ otherPlugin.keys().forEach(key => {
|
|
|
|
|
+ const plugin = otherPlugin(key)
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ if (data.type === 'page') {
|
|
|
|
|
+ this.importAsPage(plugin)
|
|
|
|
|
+ } else if (data.type === 'tool') {
|
|
|
|
|
+ this.importAsTool(plugin)
|
|
|
|
|
+ } else if (data.type === 'task') {
|
|
|
|
|
+ this.importAsTask(plugin)
|
|
|
|
|
+ } else if (data.type === 'comp') {
|
|
|
|
|
+ this.importAsComp(plugin)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log(plugin.default.options.name + '类型未知,无法加载')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ importAsComp(plugin) {
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ this.componentList.push(plugin.default.options.name)
|
|
|
|
|
+ this.prompt += data.prompt
|
|
|
|
|
+ console.log('已从' + plugin.default.options.name + '中加载组件')
|
|
|
|
|
+ },
|
|
|
|
|
+ importAsPage(plugin) {
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ const routeConfig = data.router
|
|
|
|
|
+ this.pageList.push({
|
|
|
|
|
+ name: routeConfig.name,
|
|
|
|
|
+ title: routeConfig.meta.title
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!routeConfig.component) {
|
|
|
|
|
+ routeConfig.component = plugin.default
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.parent) {
|
|
|
|
|
+ this.$router.addRoute(data.parent, routeConfig)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$router.addRoute(routeConfig)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.prompt += data.prompt
|
|
|
|
|
+ console.log('已加载' + data.router.meta.title + '页面')
|
|
|
|
|
+ },
|
|
|
|
|
+ importAsTool(plugin) {
|
|
|
|
|
+ const tools = plugin.default.options.methods
|
|
|
|
|
+ for (const key in tools) {
|
|
|
|
|
+ this.toolList.push({
|
|
|
|
|
+ name: key,
|
|
|
|
|
+ tool: tools[key]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ this.prompt += data.prompt
|
|
|
|
|
+ console.log('已从' + plugin.default.options.name + '中加载' + Object.keys(tools).length + '项能力')
|
|
|
|
|
+ },
|
|
|
|
|
+ importAsTask(plugin) {
|
|
|
|
|
+ const tools = plugin.default.options.methods
|
|
|
|
|
+ for (const key in tools) {
|
|
|
|
|
+ this.taskTollList.push({
|
|
|
|
|
+ name: key,
|
|
|
|
|
+ tool: tools[key]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ const data = plugin.default.options.data()
|
|
|
|
|
+ this.prompt += data.prompt
|
|
|
|
|
+ console.log('已从' + plugin.default.options.name + '中加载' + Object.keys(tools).length + '项技能')
|
|
|
|
|
+ },
|
|
|
|
|
+ workStart() {
|
|
|
|
|
+ setInterval(() => {
|
|
|
|
|
+ const that = this
|
|
|
|
|
+ this.workLength = this.taskList.length
|
|
|
|
|
+ if (this.taskList.length && this.taskList[0].states === 'wait') {
|
|
|
|
|
+ if (this.retryCount > this.aiConfig.maxRetry) {
|
|
|
|
|
+ this.postMessage('已达失败重试上限,请检查AI配置以及额度')
|
|
|
|
|
+ this.taskList.splice(0, 0)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.taskList[0].states = 'running'
|
|
|
|
|
+ this.taskTollList.forEach(tool => {
|
|
|
|
|
+ if (tool.name === this.taskList[0].type) {
|
|
|
|
|
+ tool.tool(this.taskList[0].data, that)
|
|
|
|
|
+ .then(result => {
|
|
|
|
|
+ this.retryCount = 0
|
|
|
|
|
+ this.postMessage(this.taskList[0].data.title + '任务结束')
|
|
|
|
|
+ this.taskList.shift()
|
|
|
|
|
+ }).catch(err => {
|
|
|
|
|
+ this.retryCount += 1
|
|
|
|
|
+ this.postMessage(this.taskList[0].data.title + '任务失败')
|
|
|
|
|
+ const task = this.taskList.shift()
|
|
|
|
|
+ if (task.count) {
|
|
|
|
|
+ if (task.count > this.aiConfig.retryOne) {
|
|
|
|
|
+ this.postMessage(this.taskList[0].data.title + '已达失败重试上限,任务移除')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ task.count += 1
|
|
|
|
|
+ task.states = 'wait'
|
|
|
|
|
+ this.taskList.push(task)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 100)
|
|
|
|
|
+ },
|
|
|
|
|
+ textInput(e) {
|
|
|
|
|
+ this.text = this.target.innerText
|
|
|
|
|
+ },
|
|
|
|
|
+ sent(e) {
|
|
|
|
|
+ if (this.inProcess && this.aiState === '2') {
|
|
|
|
|
+ this.resetContext()
|
|
|
|
|
+ } else if (!this.inProcess) {
|
|
|
|
|
+ this.inProcess = true
|
|
|
|
|
+ this.sentAi()
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ guide(tool_data) {
|
|
|
|
|
+ console.log(tool_data)
|
|
|
|
|
+ if (tool_data) {
|
|
|
|
|
+ if (
|
|
|
|
|
+ tool_data.text === '用户反馈先前的操作或者数据存在错误,请询问是何处错误' ||
|
|
|
|
|
+ tool_data.text === '用户反馈需要结束对话,若有未完成的工作确认是否放弃工作,然后结束对话') {
|
|
|
|
|
+ this.inProcess = true
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: '在开始使用前,需要先进行相关配置',
|
|
|
|
|
+ tool_name: 'initSelect',
|
|
|
|
|
+ finish: false,
|
|
|
|
|
+ data: JSON.stringify([
|
|
|
|
|
+ { label: '继续', value: 'go' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+ this.guideData.step = 0
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.guideData.step === 0) {
|
|
|
|
|
+ this.guideData.step = 1
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: '请输入Gemini apiKey。可以前往https://aistudio.google.com/apikey获取',
|
|
|
|
|
+ tool_name: 'initInput',
|
|
|
|
|
+ finish: false,
|
|
|
|
|
+ data: ''
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.guideData.step === 1) {
|
|
|
|
|
+ if (tool_data.text.length < 10) {
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: 'apiKey似乎不对。可以前往https://aistudio.google.com/apikey获取',
|
|
|
|
|
+ tool_name: 'initInput',
|
|
|
|
|
+ finish: false,
|
|
|
|
|
+ data: ''
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.guideData.apiKey = tool_data.text
|
|
|
|
|
+ this.guideData.step = 2
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: '你想使用哪个Ai模型?',
|
|
|
|
|
+ tool_name: 'initSelect',
|
|
|
|
|
+ data: JSON.stringify([
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Gemini 2.5 Pro',
|
|
|
|
|
+ value: 'gemini-2.5-pro'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Gemini 2.5 Flash',
|
|
|
|
|
+ value: 'gemini-2.5-flash'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Gemini 2.5 Flash Lite',
|
|
|
|
|
+ value: 'gemini-2.5-flash-lite'
|
|
|
|
|
+ }
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.guideData.step === 2) {
|
|
|
|
|
+ this.guideData.step = 3
|
|
|
|
|
+ this.guideData.model = tool_data.value
|
|
|
|
|
+ const AiConfig = {}
|
|
|
|
|
+ AiConfig.setting = {
|
|
|
|
|
+ maxRetry: 20,
|
|
|
|
|
+ retryOne: 5,
|
|
|
|
|
+ language: '',
|
|
|
|
|
+ debug: false
|
|
|
|
|
+ }
|
|
|
|
|
+ AiConfig.Google_Ai = {
|
|
|
|
|
+ apiKey: this.guideData.apiKey,
|
|
|
|
|
+ model: this.guideData.model
|
|
|
|
|
+ }
|
|
|
|
|
+ AiConfig.active = 'Google_Ai'
|
|
|
|
|
+ localStorage.setItem('aiConfig', JSON.stringify(AiConfig))
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: '一切就绪,手动刷新页面后就可以使用自由聊天功能了。',
|
|
|
|
|
+ tool_name: 'initSelect',
|
|
|
|
|
+ finish: false,
|
|
|
|
|
+ data: JSON.stringify([
|
|
|
|
|
+ { label: '刷新页面', value: 'go' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.guideData.step === 3) {
|
|
|
|
|
+ location.reload()
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.inProcess = true
|
|
|
|
|
+ this.parseAiData({
|
|
|
|
|
+ context: '在开始使用前,需要先进行相关配置',
|
|
|
|
|
+ tool_name: 'initSelect',
|
|
|
|
|
+ finish: false,
|
|
|
|
|
+ data: JSON.stringify([
|
|
|
|
|
+ { label: '继续', value: 'go' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ sentAi(tool_data, tool_role) {
|
|
|
|
|
+ if (this.AiKey === '') {
|
|
|
|
|
+ this.guide(tool_data)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const ai = new GoogleGenAI({
|
|
|
|
|
+ apiKey: this.AiKey
|
|
|
|
|
+ })
|
|
|
|
|
+ let role = 'user'
|
|
|
|
|
+ if (tool_role) { role = tool_role }
|
|
|
|
|
+ const model = this.AiModel
|
|
|
|
|
+ const config = {
|
|
|
|
|
+ thinkingConfig: {
|
|
|
|
|
+ thinkingBudget: -1
|
|
|
|
|
+ },
|
|
|
|
|
+ responseMimeType: 'application/json',
|
|
|
|
|
+ responseSchema: {
|
|
|
|
|
+ type: Type.OBJECT,
|
|
|
|
|
+ required: ['context', 'data', 'finish'],
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ context: {
|
|
|
|
|
+ type: Type.STRING
|
|
|
|
|
+ },
|
|
|
|
|
+ tool_name: {
|
|
|
|
|
+ type: Type.STRING
|
|
|
|
|
+ },
|
|
|
|
|
+ data: {
|
|
|
|
|
+ type: Type.STRING
|
|
|
|
|
+ },
|
|
|
|
|
+ finish: {
|
|
|
|
|
+ type: Type.BOOLEAN
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.aiContext.length === 0) {
|
|
|
|
|
+ this.aiContext.push({
|
|
|
|
|
+ role: 'model',
|
|
|
|
|
+ parts: [{
|
|
|
|
|
+ text: this.prompt + Prompt
|
|
|
|
|
+ }]
|
|
|
|
|
+ })
|
|
|
|
|
+ this.aiContext.push({
|
|
|
|
|
+ role: role,
|
|
|
|
|
+ parts: [{
|
|
|
|
|
+ text: this.text
|
|
|
|
|
+ }]
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.aiContext.push({
|
|
|
|
|
+ role: role,
|
|
|
|
|
+ parts: [{
|
|
|
|
|
+ text: '工具调用成功,数据为' + JSON.stringify(tool_data)
|
|
|
|
|
+ }]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ this.aiState = '0'
|
|
|
|
|
+ this.tool_name = ''
|
|
|
|
|
+ this.tool_input = ''
|
|
|
|
|
+ this.tool_data = ''
|
|
|
|
|
+ ai.models.generateContent({
|
|
|
|
|
+ model: model,
|
|
|
|
|
+ config: config,
|
|
|
|
|
+ contents: this.aiContext
|
|
|
|
|
+ }).then(response => {
|
|
|
|
|
+ this.parseAiData(JSON.parse(response.text))
|
|
|
|
|
+ }).catch(error => {
|
|
|
|
|
+ const errMessage = JSON.parse(error.message.split(' . ')[1])
|
|
|
|
|
+ this.aiState = '2'
|
|
|
|
|
+ this.target.innerText = '呼哟!出错了,把这些内容给到开发说不定有用:' + errMessage.error.message
|
|
|
|
|
+ console.log(errMessage)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ parseAiData(data) {
|
|
|
|
|
+ this.target.innerText = data.context
|
|
|
|
|
+ this.aiContext.push({
|
|
|
|
|
+ role: 'model',
|
|
|
|
|
+ parts: [{
|
|
|
|
|
+ text: JSON.stringify(data)
|
|
|
|
|
+ }]
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!data.tool_name && !data.finish) {
|
|
|
|
|
+ this.sentAi('若会话未完成,必须调用一个工具', 'user')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.aiState = '1'
|
|
|
|
|
+ this.useTool(data.tool_name ? data.tool_name : '', data.data ? JSON.parse(data.data) : {})
|
|
|
|
|
+ if (data.finish) {
|
|
|
|
|
+ this.aiState = '2'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 任务
|
|
|
|
|
+ runWork(workData) {
|
|
|
|
|
+ for (let i = 0; i < this.taskList.length; i++) {
|
|
|
|
|
+ if (this.taskList[i].type === workData.type && this.taskList[i].data.id === workData.data.id) {
|
|
|
|
|
+ this.postMessage('已有相同的任务,请勿重复添加')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.taskList.push({
|
|
|
|
|
+ ...workData,
|
|
|
|
|
+ states: 'wait'
|
|
|
|
|
+ })
|
|
|
|
|
+ console.log(workData)
|
|
|
|
|
+ this.postMessage(workData.data.title + '加入队列')
|
|
|
|
|
+ },
|
|
|
|
|
+ postMessage(message) {
|
|
|
|
|
+ if (this.timer) { clearTimeout(this.timer) }
|
|
|
|
|
+ this.message = message
|
|
|
|
|
+ this.showMessage = true
|
|
|
|
|
+ this.timer = setTimeout(() => {
|
|
|
|
|
+ this.showMessage = false
|
|
|
|
|
+ }, 2000)
|
|
|
|
|
+ },
|
|
|
|
|
+ useTool(toolName, data) {
|
|
|
|
|
+ this.tool_name = toolName
|
|
|
|
|
+ this.aiState = '1'
|
|
|
|
|
+ this.toolList.forEach((item) => {
|
|
|
|
|
+ if (toolName === item.name) {
|
|
|
|
|
+ item.tool(data)
|
|
|
|
|
+ .then(res => {
|
|
|
|
|
+ this.sentAi(res.data)
|
|
|
|
|
+ console.log(res)
|
|
|
|
|
+ }).catch(err => {
|
|
|
|
|
+ console.log(err)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ this.componentList.forEach((item) => {
|
|
|
|
|
+ if (toolName === item) {
|
|
|
|
|
+ this.tool_name = item
|
|
|
|
|
+ this.tool_data = data
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ gotoUrl(pageName) {
|
|
|
|
|
+ this.$router.push({
|
|
|
|
|
+ name: pageName
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ resetContext() {
|
|
|
|
|
+ this.aiContext = this.aiContext.slice(0, 0)
|
|
|
|
|
+ this.aiState = '1'
|
|
|
|
|
+ this.tool_name = ''
|
|
|
|
|
+ this.tool_input = ''
|
|
|
|
|
+ this.tool_data = {}
|
|
|
|
|
+ this.inProcess = false
|
|
|
|
|
+ this.target.innerText = ''
|
|
|
|
|
+ this.text = ''
|
|
|
|
|
+ },
|
|
|
|
|
+ getConfig() {
|
|
|
|
|
+ this.target = document.getElementById('ai-input')
|
|
|
|
|
+ const config = JSON.parse(localStorage.getItem('aiConfig')) || {}
|
|
|
|
|
+ this.aiConfig = config.setting
|
|
|
|
|
+ this.AiKey = config.active ? config[config.active].apiKey : ''
|
|
|
|
|
+ this.AiModel = config.active ? config[config.active].model : ''
|
|
|
|
|
+ if (!config.active) {
|
|
|
|
|
+ this.guide()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div :class="['ai-ball',showText?'':'hide',showMessage?'':'message-hide',workLength?'working':'',inProcess?'process':'']">
|
|
|
|
|
+ <div class="ai-inner" @click="showText=!showText">
|
|
|
|
|
+ <div class="eye" />
|
|
|
|
|
+ <div class="eye" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div :class="['page-menu',showMenu?'show':'']">
|
|
|
|
|
+ <div class="page-list">
|
|
|
|
|
+ <div v-for="page in pageList" class="list-item" @click="gotoUrl(page.name);showMenu=false">{{ page.title }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div /><div />
|
|
|
|
|
+ <div class="button" @click="showMenu=!showMenu">
|
|
|
|
|
+ <div class="line" />
|
|
|
|
|
+ <div class="line" />
|
|
|
|
|
+ <div class="line" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-box">
|
|
|
|
|
+ <div id="ai-input" class="input" :contenteditable="!inProcess" @input="textInput" @keydown.ctrl.enter="sent" />
|
|
|
|
|
+ <div :class="['send',inProcess?aiState==='2'?'el-icon-check':'el-icon-loading':'el-icon-top']" @click="sent" />
|
|
|
|
|
+ <div v-for="comp in componentList" :class="['tool',tool_name===comp?'':'hide']">
|
|
|
|
|
+ <component
|
|
|
|
|
+ :is="comp"
|
|
|
|
|
+ :tool_name.sync="tool_name"
|
|
|
|
|
+ :tool_input.sync="tool_input"
|
|
|
|
|
+ :tool_data.sync="tool_data"
|
|
|
|
|
+ @runWork="runWork"
|
|
|
|
|
+ @sentAi="sentAi"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="state">
|
|
|
|
|
+ <span v-if="aiState === '0'">AI助理思考中...</span>
|
|
|
|
|
+ <span v-if="aiState === '1'">工具调用中...</span>
|
|
|
|
|
+ <span v-if="aiState === '2'">对话已结束,请重新开始</span>
|
|
|
|
|
+ <span v-if="inProcess&&aiState==='1'" style="margin-left: auto;cursor: pointer" @click="sentAi({text:'用户反馈先前的操作或者数据存在错误,请询问是何处错误'},'model')">有误</span>
|
|
|
|
|
+ <span v-if="inProcess&&aiState==='1'" style="cursor: pointer" @click="sentAi({text:'用户反馈需要结束对话,若有未完成的工作确认是否放弃工作,然后结束对话'},'model')">结束</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-show="!inProcess && text===''" class="about-text">
|
|
|
|
|
+ <span>{{ aboutText }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message">
|
|
|
|
|
+ <div class="inner">{{ message }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="counter">
|
|
|
|
|
+ {{ workLength }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+ .ai-ball{
|
|
|
|
|
+ filter: drop-shadow( 0 0 8px #4F46E522);
|
|
|
|
|
+ width: 70px;
|
|
|
|
|
+ height: 70px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #DBEAFE;
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+ right: 40px;
|
|
|
|
|
+ bottom: 120px;
|
|
|
|
|
+ .page-menu{
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ background: #DBEAFE;
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ border-radius: 22px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 60px;
|
|
|
|
|
+ right: 8px;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-rows: 0 44px;
|
|
|
|
|
+ grid-template-columns: 0 44px;
|
|
|
|
|
+ .page-list{
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ .list-item{
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ border-radius: 36px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ padding: 6px 16px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ color: #2b2677;
|
|
|
|
|
+ &:hover{
|
|
|
|
|
+ background: #b4cbec;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .button{
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ grid-gap: 4px;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ &:hover{
|
|
|
|
|
+ background: #b4cbec;
|
|
|
|
|
+ }
|
|
|
|
|
+ .line{
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ transform-origin: 100% 50%;
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 2px;
|
|
|
|
|
+ background: #4F46E5;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ &.show{
|
|
|
|
|
+ width: 244px;
|
|
|
|
|
+ height: 344px;
|
|
|
|
|
+ grid-template-rows: 300px 44px;
|
|
|
|
|
+ grid-template-columns: 200px 44px;
|
|
|
|
|
+ .page-list{
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .button{
|
|
|
|
|
+ grid-gap: 0;
|
|
|
|
|
+ .line{
|
|
|
|
|
+ &:first-child{
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ transform: translateY(2px) translateX(2px) rotate(40deg);
|
|
|
|
|
+ }
|
|
|
|
|
+ &:last-child{
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ transform: translateY(-2px) translateX(2px) rotate(-40deg);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .counter{
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ padding: 0 7px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ right: -12px;
|
|
|
|
|
+ bottom: -2px;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ background: #4F46E5;
|
|
|
|
|
+ scale: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .message{
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ width: 1000px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: -42px;
|
|
|
|
|
+ right: 60px;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ .inner{
|
|
|
|
|
+ float: right;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ text-align: end;
|
|
|
|
|
+ line-height: 36px;
|
|
|
|
|
+ padding: 0 20px;
|
|
|
|
|
+ color: #2b2677;
|
|
|
|
|
+ background: #DBEAFE;
|
|
|
|
|
+ width: fit-content;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .text-box{
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ border-radius: 26px;
|
|
|
|
|
+ width: 500px;
|
|
|
|
|
+ min-height: 50px;
|
|
|
|
|
+ background: #DBEAFE;
|
|
|
|
|
+ right: 86px;
|
|
|
|
|
+ bottom: 8px;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+ grid-gap: 0 10px;
|
|
|
|
|
+ .about-text{
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ padding: 0 24px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ color: gray;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .tool{
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ grid-template-rows: 1fr;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ padding: 10px 10px;
|
|
|
|
|
+ &.hide{
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ grid-template-rows: 0fr;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .state{
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ grid-gap: 6px;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ margin: 0 12px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ color: #2b2677
|
|
|
|
|
+ }
|
|
|
|
|
+ .send{
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background: #4F46E5;
|
|
|
|
|
+ width: 36px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ line-height: 36px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ &:hover{
|
|
|
|
|
+ scale: 1.08;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .input{
|
|
|
|
|
+ color: #2b2677;
|
|
|
|
|
+ margin: 6px 12px;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: fit-content;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .ai-inner{
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border-bottom-right-radius: 2px;
|
|
|
|
|
+ background: #4F46E5;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-evenly;
|
|
|
|
|
+ @keyframes eyes {
|
|
|
|
|
+ 0%{
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ 4%{
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ 8%{
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ 12%{
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ 16%{
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ 20%{
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ &:hover{
|
|
|
|
|
+ transform: rotate(6deg) scale(1.04);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .eye{
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ width: 6px;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ animation: 5s eyes infinite;
|
|
|
|
|
+ transition-duration: 300ms;
|
|
|
|
|
+ transform: translateX(-60%);
|
|
|
|
|
+ }
|
|
|
|
|
+ &.hide{
|
|
|
|
|
+ .page-menu{
|
|
|
|
|
+ bottom: 80px;
|
|
|
|
|
+ right: 4px;
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ pointer-events: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+ .message{
|
|
|
|
|
+ bottom: 12px;
|
|
|
|
|
+ right: 86px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .text-box{
|
|
|
|
|
+ right: 70px;
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ .ai-inner{
|
|
|
|
|
+ .eye{
|
|
|
|
|
+ transform: translateX(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ &.message-hide{
|
|
|
|
|
+ .message{
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ &.working{
|
|
|
|
|
+ .counter{
|
|
|
|
|
+ scale: 1;
|
|
|
|
|
+ animation: working 1s infinite;
|
|
|
|
|
+ }
|
|
|
|
|
+ @keyframes working {
|
|
|
|
|
+ from{
|
|
|
|
|
+ box-shadow: 0 0 0 0 #4F46E5ff;
|
|
|
|
|
+ }
|
|
|
|
|
+ to{
|
|
|
|
|
+ box-shadow: 0 0 0 6px #4F46E500;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ &.process{
|
|
|
|
|
+ .text-box{
|
|
|
|
|
+ .input{
|
|
|
|
|
+ max-height: 600px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ overflow-y: scroll;
|
|
|
|
|
+ }
|
|
|
|
|
+ .tool{
|
|
|
|
|
+ .init-select{
|
|
|
|
|
+ overflow-y: scroll;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .send{
|
|
|
|
|
+ background: #DBEAFE;
|
|
|
|
|
+ color: #4F46E5;
|
|
|
|
|
+ &:hover{
|
|
|
|
|
+ scale: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .state{
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|