feat:单个agent配置各自的apiurl、apimodel、apikey
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from openai import OpenAI, AsyncOpenAI
|
|
||||||
|
import httpx
|
||||||
|
from openai import OpenAI, AsyncOpenAI, max_retries
|
||||||
import yaml
|
import yaml
|
||||||
from termcolor import colored
|
from termcolor import colored
|
||||||
import os
|
import os
|
||||||
@@ -21,6 +23,9 @@ OPENAI_API_BASE = os.getenv("OPENAI_API_BASE") or yaml_data.get(
|
|||||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or yaml_data.get(
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or yaml_data.get(
|
||||||
"OPENAI_API_KEY", ""
|
"OPENAI_API_KEY", ""
|
||||||
)
|
)
|
||||||
|
OPENAI_API_MODEL = os.getenv("OPENAI_API_MODEL") or yaml_data.get(
|
||||||
|
"OPENAI_API_MODEL", ""
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize OpenAI clients
|
# Initialize OpenAI clients
|
||||||
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_BASE)
|
||||||
@@ -41,8 +46,11 @@ MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY") or yaml_data.get(
|
|||||||
|
|
||||||
# for LLM completion
|
# for LLM completion
|
||||||
def LLM_Completion(
|
def LLM_Completion(
|
||||||
messages: list[dict], stream: bool = True, useGroq: bool = True
|
messages: list[dict], stream: bool = True, useGroq: bool = True,model_config: dict = None
|
||||||
) -> str:
|
) -> str:
|
||||||
|
if model_config:
|
||||||
|
print_colored(f"Using model config: {model_config}", "blue")
|
||||||
|
return _call_with_custom_config(messages,stream,model_config)
|
||||||
if not useGroq or not FAST_DESIGN_MODE:
|
if not useGroq or not FAST_DESIGN_MODE:
|
||||||
force_gpt4 = True
|
force_gpt4 = True
|
||||||
useGroq = False
|
useGroq = False
|
||||||
@@ -75,6 +83,82 @@ def LLM_Completion(
|
|||||||
return _chat_completion(messages=messages)
|
return _chat_completion(messages=messages)
|
||||||
|
|
||||||
|
|
||||||
|
def _call_with_custom_config(messages: list[dict], stream: bool, model_config: dict) ->str:
|
||||||
|
"使用自定义配置调用API"
|
||||||
|
api_url = model_config.get("apiUrl", OPENAI_API_BASE)
|
||||||
|
api_key = model_config.get("apiKey", OPENAI_API_KEY)
|
||||||
|
api_model = model_config.get("apiModel", OPENAI_API_MODEL)
|
||||||
|
|
||||||
|
temp_client = OpenAI(api_key=api_key, base_url=api_url)
|
||||||
|
temp_async_client = AsyncOpenAI(api_key=api_key, base_url=api_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if stream:
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError as ex:
|
||||||
|
if "There is no current event loop in thread" in str(ex):
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
return loop.run_until_complete(
|
||||||
|
_achat_completion_stream_custom(messages=messages, temp_async_client=temp_async_client, api_model=api_model)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
response = temp_client.chat.completions.create(
|
||||||
|
messages=messages,
|
||||||
|
model=api_model,
|
||||||
|
temperature=0.3,
|
||||||
|
max_tokens=4096,
|
||||||
|
timeout=180
|
||||||
|
|
||||||
|
)
|
||||||
|
full_reply_content = response.choices[0].message.content
|
||||||
|
print(colored(full_reply_content, "blue", "on_white"), end="")
|
||||||
|
return full_reply_content
|
||||||
|
except Exception as e:
|
||||||
|
print_colored(f"Custom API error for model {api_model} :{str(e)}","red")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def _achat_completion_stream_custom(messages:list[dict], temp_async_client, api_model: str ) -> str:
|
||||||
|
max_retries=3
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
response = await temp_async_client.chat.completions.create(
|
||||||
|
messages=messages,
|
||||||
|
model=api_model,
|
||||||
|
temperature=0.3,
|
||||||
|
max_tokens=4096,
|
||||||
|
stream=True,
|
||||||
|
timeout=180
|
||||||
|
)
|
||||||
|
|
||||||
|
collected_chunks = []
|
||||||
|
collected_messages = []
|
||||||
|
async for chunk in response:
|
||||||
|
collected_chunks.append(chunk)
|
||||||
|
choices = chunk.choices
|
||||||
|
if len(choices) > 0:
|
||||||
|
chunk_message = chunk.choices[0].delta
|
||||||
|
collected_messages.append(chunk_message)
|
||||||
|
if chunk_message.content:
|
||||||
|
print(colored(chunk_message.content, "blue", "on_white"), end="")
|
||||||
|
print()
|
||||||
|
full_reply_content = "".join(
|
||||||
|
[m.content or "" for m in collected_messages if m is not None]
|
||||||
|
)
|
||||||
|
return full_reply_content
|
||||||
|
except httpx.RemoteProtocolError as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
wait_time = (attempt + 1) *2
|
||||||
|
print_colored(f"⚠️ Stream connection interrupted (attempt {attempt+1}/{max_retries}). Retrying in {wait_time}s...", text_color="yellow")
|
||||||
|
await asyncio.sleep(wait_time)
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print_colored(f"Custom API stream error for model {api_model} :{str(e)}","red")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
async def _achat_completion_stream_groq(messages: list[dict]) -> str:
|
||||||
from groq import AsyncGroq
|
from groq import AsyncGroq
|
||||||
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
|
groq_client = AsyncGroq(api_key=GROQ_API_KEY)
|
||||||
@@ -144,7 +228,7 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
max_tokens=4096,
|
max_tokens=4096,
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
timeout=30,
|
timeout=600,
|
||||||
model="gpt-3.5-turbo-16k",
|
model="gpt-3.5-turbo-16k",
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
@@ -172,16 +256,15 @@ async def _achat_completion_stream_gpt35(messages: list[dict]) -> str:
|
|||||||
return full_reply_content
|
return full_reply_content
|
||||||
|
|
||||||
|
|
||||||
async def _achat_completion_json(messages: list[dict]) -> str:
|
def _achat_completion_json(messages: list[dict] ) -> str:
|
||||||
max_attempts = 5
|
max_attempts = 5
|
||||||
|
|
||||||
for attempt in range(max_attempts):
|
for attempt in range(max_attempts):
|
||||||
try:
|
try:
|
||||||
response = await async_client.chat.completions.create(
|
response = async_client.chat.completions.create(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
max_tokens=4096,
|
max_tokens=4096,
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
timeout=30,
|
timeout=600,
|
||||||
model=MODEL,
|
model=MODEL,
|
||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
)
|
)
|
||||||
@@ -245,7 +328,7 @@ def _cons_kwargs(messages: list[dict]) -> dict:
|
|||||||
"messages": messages,
|
"messages": messages,
|
||||||
"max_tokens": 2000,
|
"max_tokens": 2000,
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"timeout": 60,
|
"timeout": 600,
|
||||||
}
|
}
|
||||||
kwargs_mode = {"model": MODEL}
|
kwargs_mode = {"model": MODEL}
|
||||||
kwargs.update(kwargs_mode)
|
kwargs.update(kwargs_mode)
|
||||||
|
|||||||
@@ -81,16 +81,33 @@ class BaseAction():
|
|||||||
action_Record += PROMPT_TEMPLATE_ACTION_RECORD.format(AgentName = actionInfo["AgentName"], Action_Description = actionInfo["AgentName"], Action_Result = actionInfo["Action_Result"], Important_Mark = Important_Mark)
|
action_Record += PROMPT_TEMPLATE_ACTION_RECORD.format(AgentName = actionInfo["AgentName"], Action_Description = actionInfo["AgentName"], Action_Result = actionInfo["Action_Result"], Important_Mark = Important_Mark)
|
||||||
|
|
||||||
# Handle missing agent profiles gracefully
|
# Handle missing agent profiles gracefully
|
||||||
|
model_config = None
|
||||||
if agentName not in AgentProfile_Dict:
|
if agentName not in AgentProfile_Dict:
|
||||||
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
|
print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow")
|
||||||
agentProfile = f"AI Agent named {agentName}"
|
agentProfile = f"AI Agent named {agentName}"
|
||||||
else:
|
else:
|
||||||
agentProfile = AgentProfile_Dict[agentName]
|
# agentProfile = AgentProfile_Dict[agentName]
|
||||||
|
agent_config = AgentProfile_Dict[agentName]
|
||||||
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(agentName = agentName, agentProfile = agentProfile, General_Goal = General_Goal, Current_Task_Description = TaskDescription, Input_Objects = inputObject_Record, History_Action = action_Record, Action_Description = self.info["Description"], Action_Custom_Note = self.Action_Custom_Note)
|
agentProfile = agent_config.get("profile",f"AI Agent named {agentName}")
|
||||||
|
if agent_config.get("useCustomAPI",False):
|
||||||
|
model_config = {
|
||||||
|
"apiModel":agent_config.get("apiModel"),
|
||||||
|
"apiUrl":agent_config.get("apiUrl"),
|
||||||
|
"apiKey":agent_config.get("apiKey"),
|
||||||
|
}
|
||||||
|
prompt = PROMPT_TEMPLATE_TAKE_ACTION_BASE.format(
|
||||||
|
agentName = agentName,
|
||||||
|
agentProfile = agentProfile,
|
||||||
|
General_Goal = General_Goal,
|
||||||
|
Current_Task_Description = TaskDescription,
|
||||||
|
Input_Objects = inputObject_Record,
|
||||||
|
History_Action = action_Record,
|
||||||
|
Action_Description = self.info["Description"],
|
||||||
|
Action_Custom_Note = self.Action_Custom_Note
|
||||||
|
)
|
||||||
print_colored(text = prompt, text_color="red")
|
print_colored(text = prompt, text_color="red")
|
||||||
messages = [{"role":"system", "content": prompt}]
|
messages = [{"role":"system", "content": prompt}]
|
||||||
ActionResult = LLM_Completion(messages,True,False)
|
ActionResult = LLM_Completion(messages,True,False,model_config=model_config)
|
||||||
ActionInfo_with_Result = copy.deepcopy(self.info)
|
ActionInfo_with_Result = copy.deepcopy(self.info)
|
||||||
ActionInfo_with_Result["Action_Result"] = ActionResult
|
ActionInfo_with_Result["Action_Result"] = ActionResult
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
## config for default LLM
|
## config for default LLM
|
||||||
OPENAI_API_BASE: ""
|
OPENAI_API_BASE: "https://ai.gitee.com/v1"
|
||||||
OPENAI_API_KEY: ""
|
OPENAI_API_KEY: "HYCNGM39GGFNSB1F8MBBMI9QYJR3P1CRSYS2PV1A"
|
||||||
OPENAI_API_MODEL: "gpt-4-turbo-preview"
|
OPENAI_API_MODEL: "DeepSeek-V3"
|
||||||
|
|
||||||
## config for fast mode
|
## config for fast mode
|
||||||
FAST_DESIGN_MODE: True
|
FAST_DESIGN_MODE: False
|
||||||
GROQ_API_KEY: ""
|
GROQ_API_KEY: ""
|
||||||
MISTRAL_API_KEY: ""
|
MISTRAL_API_KEY: ""
|
||||||
|
|
||||||
|
|||||||
@@ -271,13 +271,29 @@ def Handle_saveRequestCashe():
|
|||||||
|
|
||||||
@app.route("/setAgents", methods=["POST"])
|
@app.route("/setAgents", methods=["POST"])
|
||||||
def set_agents():
|
def set_agents():
|
||||||
global AgentBoard, AgentProfile_Dict
|
global AgentBoard, AgentProfile_Dict,yaml_data
|
||||||
AgentBoard = request.json
|
AgentBoard = request.json
|
||||||
AgentProfile_Dict = {}
|
AgentProfile_Dict = {}
|
||||||
for item in AgentBoard:
|
for item in AgentBoard:
|
||||||
name = item["Name"]
|
name = item["Name"]
|
||||||
profile = item["Profile"]
|
if all(item.get(field) for field in ["apiUrl","apiKey","apiModel"]):
|
||||||
AgentProfile_Dict[name] = profile
|
agent_config = {
|
||||||
|
"profile": item["Profile"],
|
||||||
|
"apiUrl": item["apiUrl"],
|
||||||
|
"apiKey": item["apiKey"],
|
||||||
|
"apiModel": item["apiModel"],
|
||||||
|
"useCustomAPI":True
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
agent_config = {
|
||||||
|
"profile": item["Profile"],
|
||||||
|
"apiUrl": yaml_data.get("OPENAI_API_BASE"),
|
||||||
|
"apiKey": yaml_data.get("OPENAI_API_KEY"),
|
||||||
|
"apiModel": yaml_data.get("OPENAI_API_MODEL"),
|
||||||
|
"useCustomAPI":False
|
||||||
|
}
|
||||||
|
AgentProfile_Dict[name] = agent_config
|
||||||
|
|
||||||
return jsonify({"code": 200, "content": "set agentboard successfully"})
|
return jsonify({"code": 200, "content": "set agentboard successfully"})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ onMounted(async () => {
|
|||||||
const res = await readConfig<Agent[]>('agent.json')
|
const res = await readConfig<Agent[]>('agent.json')
|
||||||
agentsStore.setAgents(res)
|
agentsStore.setAgents(res)
|
||||||
}
|
}
|
||||||
await api.setAgents(agentsStore.agents.map(item => pick(item, ['Name', 'Profile'])))
|
await api.setAgents(
|
||||||
|
agentsStore.agents.map(item => pick(item, ['Name', 'Profile', 'apiUrl', 'apiKey', 'apiModel']))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 上传agent文件
|
// 上传agent文件
|
||||||
@@ -118,8 +120,6 @@ const readFileContent = (file: File) => {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('🔄 开始处理智能体数据...')
|
|
||||||
// 修改发送到后端的数据
|
// 修改发送到后端的数据
|
||||||
const processedAgents = validAgents.map(agent => ({
|
const processedAgents = validAgents.map(agent => ({
|
||||||
Name: agent.Name,
|
Name: agent.Name,
|
||||||
@@ -131,35 +131,9 @@ const readFileContent = (file: File) => {
|
|||||||
apiModel: agent.apiModel
|
apiModel: agent.apiModel
|
||||||
}))
|
}))
|
||||||
|
|
||||||
console.log(
|
|
||||||
'📤 发送到后端的智能体数据:',
|
|
||||||
processedAgents.map(a => ({
|
|
||||||
Name: a.Name,
|
|
||||||
apiUrl: a.apiUrl,
|
|
||||||
apiKey: a.apiKey ? '***' + a.apiKey.slice(-4) : '未设置',
|
|
||||||
apiModel: a.apiModel
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
// 调用API
|
|
||||||
api
|
|
||||||
.setAgents(processedAgents)
|
|
||||||
.then(response => {
|
|
||||||
console.log('✅ 后端API调用成功,响应:', response)
|
|
||||||
ElMessage.success('智能体上传成功')
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('❌ 后端API调用失败:', error)
|
|
||||||
ElMessage.error('智能体上传失败')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新store
|
|
||||||
console.log('💾 更新智能体store...')
|
|
||||||
agentsStore.setAgents(processedAgents)
|
agentsStore.setAgents(processedAgents)
|
||||||
console.log('✅ 智能体store更新完成')
|
|
||||||
|
|
||||||
// 调用API
|
// 调用API
|
||||||
console.log('🌐 开始调用后端API...')
|
|
||||||
api
|
api
|
||||||
.setAgents(processedAgents)
|
.setAgents(processedAgents)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -1,861 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, onUnmounted, ref } from 'vue'
|
|
||||||
import { throttle } from 'lodash'
|
|
||||||
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
|
|
||||||
|
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
|
||||||
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
|
||||||
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
|
||||||
import variables from '@/styles/variables.module.scss'
|
|
||||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
|
||||||
import api from '@/api'
|
|
||||||
import ProcessCard from '../TaskProcess/ProcessCard.vue'
|
|
||||||
import ExecutePlan from './ExecutePlan.vue'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'refreshLine'): void
|
|
||||||
(el: 'setCurrentTask', task: IRawStepTask): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const agentsStore = useAgentsStore()
|
|
||||||
const drawerVisible = ref(false)
|
|
||||||
const collaborationProcess = computed(() => {
|
|
||||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
|
||||||
})
|
|
||||||
|
|
||||||
// 编辑逻辑
|
|
||||||
const editMode = ref(false) //全局编辑开关
|
|
||||||
const editMap = reactive<Record<string, boolean>>({}) //行级编辑状态
|
|
||||||
const editBuffer = reactive<Record<string, string | undefined>>({}) //临时输入
|
|
||||||
|
|
||||||
function getProcessDescription(stepId: string, processId: string) {
|
|
||||||
const step = collaborationProcess.value.find(s => s.Id === stepId)
|
|
||||||
if (step) {
|
|
||||||
const process = step.TaskProcess.find(p => p.ID === processId)
|
|
||||||
return process?.Description || ''
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
Object.keys(editMap).forEach(key => {
|
|
||||||
if (editMap[key]) {
|
|
||||||
const [stepId, processId] = key.split('-')
|
|
||||||
const value = editBuffer[key]
|
|
||||||
// 确保 value 是字符串类型
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
// @ts-ignore - TypeScript 无法正确推断类型,但运行时是安全的
|
|
||||||
handleSaveEdit(stepId, processId, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
editMode.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOpenEdit(stepId: string, processId: string) {
|
|
||||||
if (!editMode.value) return
|
|
||||||
const key = `${stepId}-${processId}`
|
|
||||||
editMap[key] = true
|
|
||||||
editBuffer[key] = getProcessDescription(stepId, processId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSaveEdit(stepId: string, processId: string, value: string) {
|
|
||||||
const key = `${stepId}-${processId}`
|
|
||||||
const step = collaborationProcess.value.find(s => s.Id === stepId)
|
|
||||||
if (step) {
|
|
||||||
const process = step.TaskProcess.find(p => p.ID === processId)
|
|
||||||
if (process) {
|
|
||||||
process.Description = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editMap[key] = false
|
|
||||||
ElMessage.success('已保存(前端内存)')
|
|
||||||
}
|
|
||||||
const jsplumb = new Jsplumb('task-results-main', {
|
|
||||||
connector: {
|
|
||||||
type: BezierConnector.type,
|
|
||||||
options: { curviness: 30, stub: 10 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 操作折叠面板时要实时的刷新连线
|
|
||||||
let timer: ReturnType<typeof setInterval> | null = null
|
|
||||||
function handleCollapse() {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer)
|
|
||||||
}
|
|
||||||
timer = setInterval(() => {
|
|
||||||
jsplumb.repaintEverything()
|
|
||||||
emit('refreshLine')
|
|
||||||
}, 1) as ReturnType<typeof setInterval>
|
|
||||||
|
|
||||||
// 默认三秒后已经完全打开
|
|
||||||
const timer1 = setTimeout(() => {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer)
|
|
||||||
timer = null
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer)
|
|
||||||
}
|
|
||||||
if (timer1) {
|
|
||||||
clearInterval(timer1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建内部连线
|
|
||||||
function createInternalLine(id?: string) {
|
|
||||||
const arr: ConnectArg[] = []
|
|
||||||
jsplumb.reset()
|
|
||||||
collaborationProcess.value.forEach(item => {
|
|
||||||
// 创建左侧流程与产出的连线
|
|
||||||
arr.push({
|
|
||||||
sourceId: `task-results-${item.Id}-0`,
|
|
||||||
targetId: `task-results-${item.Id}-1`,
|
|
||||||
anchor: [AnchorLocations.Left, AnchorLocations.Left]
|
|
||||||
})
|
|
||||||
collaborationProcess.value.forEach(jitem => {
|
|
||||||
// 创建左侧产出与上一步流程的连线
|
|
||||||
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
|
|
||||||
arr.push({
|
|
||||||
sourceId: `task-results-${jitem.Id}-1`,
|
|
||||||
targetId: `task-results-${item.Id}-0`,
|
|
||||||
anchor: [AnchorLocations.Left, AnchorLocations.Left],
|
|
||||||
config: {
|
|
||||||
type: 'output'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 创建右侧任务程序与InputObject字段的连线
|
|
||||||
jitem.TaskProcess.forEach(i => {
|
|
||||||
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
|
|
||||||
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
|
|
||||||
const sourceId = `task-results-${item.Id}-1`
|
|
||||||
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
|
|
||||||
arr.push({
|
|
||||||
sourceId,
|
|
||||||
targetId,
|
|
||||||
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
|
||||||
config: {
|
|
||||||
stops: [
|
|
||||||
[0, color],
|
|
||||||
[1, color]
|
|
||||||
],
|
|
||||||
transparent: targetId !== id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 创建右侧TaskProcess内部连线
|
|
||||||
item.TaskProcess?.forEach(i => {
|
|
||||||
if (!i.ImportantInput?.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.TaskProcess?.forEach(i2 => {
|
|
||||||
if (i.ImportantInput.includes(`ActionResult:${i2.ID}`)) {
|
|
||||||
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
|
|
||||||
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
|
|
||||||
const targetId = `task-results-${item.Id}-0-${i.ID}`
|
|
||||||
arr.push({
|
|
||||||
sourceId,
|
|
||||||
targetId,
|
|
||||||
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
|
||||||
config: {
|
|
||||||
stops: [
|
|
||||||
[0, color],
|
|
||||||
[1, color]
|
|
||||||
],
|
|
||||||
transparent: targetId !== id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
jsplumb.connects(arr)
|
|
||||||
jsplumb.repaintEverything()
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
// 额外产物编辑状态
|
|
||||||
const editingOutputId = ref<string | null>(null)
|
|
||||||
const editingOutputContent = ref('')
|
|
||||||
|
|
||||||
// 额外产物内容存储
|
|
||||||
const additionalOutputContents = ref<Record<string, string>>({})
|
|
||||||
|
|
||||||
async function handleRun() {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
|
|
||||||
agentsStore.setExecutePlan(d)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看任务过程
|
|
||||||
async function handleTaskProcess() {
|
|
||||||
drawerVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始编辑额外产物内容
|
|
||||||
function startOutputEditing(output: string) {
|
|
||||||
editingOutputId.value = output
|
|
||||||
editingOutputContent.value = getAdditionalOutputContent(output) || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存额外产物内容
|
|
||||||
function saveOutputEditing() {
|
|
||||||
if (editingOutputId.value && editingOutputContent.value.trim()) {
|
|
||||||
additionalOutputContents.value[editingOutputId.value] = editingOutputContent.value.trim()
|
|
||||||
}
|
|
||||||
editingOutputId.value = null
|
|
||||||
editingOutputContent.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消编辑额外产物内容
|
|
||||||
function cancelOutputEditing() {
|
|
||||||
editingOutputId.value = null
|
|
||||||
editingOutputContent.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取额外产物内容
|
|
||||||
function getAdditionalOutputContent(output: string) {
|
|
||||||
return additionalOutputContents.value[output] || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理额外产物的键盘事件
|
|
||||||
function handleOutputKeydown(event: KeyboardEvent) {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
saveOutputEditing()
|
|
||||||
} else if (event.key === 'Escape') {
|
|
||||||
editingOutputId.value = null
|
|
||||||
editingOutputContent.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加滚动状态标识
|
|
||||||
const isScrolling = ref(false)
|
|
||||||
let scrollTimer: ReturnType<typeof setTimeout> | null = null
|
|
||||||
|
|
||||||
// 修改滚动处理函数
|
|
||||||
function handleScroll() {
|
|
||||||
isScrolling.value = true
|
|
||||||
emit('refreshLine')
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (scrollTimer) {
|
|
||||||
clearTimeout(scrollTimer)
|
|
||||||
}
|
|
||||||
jsplumb.repaintEverything()
|
|
||||||
|
|
||||||
// 设置滚动结束检测
|
|
||||||
scrollTimer = setTimeout(() => {
|
|
||||||
isScrolling.value = false
|
|
||||||
}, 300) as ReturnType<typeof setTimeout>
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改鼠标事件处理函数
|
|
||||||
const handleMouseEnter = throttle(id => {
|
|
||||||
if (!isScrolling.value) {
|
|
||||||
createInternalLine(id)
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
const handleMouseLeave = throttle(() => {
|
|
||||||
if (!isScrolling.value) {
|
|
||||||
createInternalLine()
|
|
||||||
}
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
jsplumb.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 按钮交互状态管理 ==========
|
|
||||||
const buttonHoverState = ref(null) // null | 'process' | 'execute'
|
|
||||||
|
|
||||||
const handleProcessMouseEnter = () => {
|
|
||||||
buttonHoverState.value = 'process'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleExecuteMouseEnter = () => {
|
|
||||||
if (agentsStore.agentRawPlan.data) {
|
|
||||||
buttonHoverState.value = 'execute'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleButtonMouseLeave = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
buttonHoverState.value = null
|
|
||||||
}, 150)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算按钮类名
|
|
||||||
const processBtnClass = computed(() => {
|
|
||||||
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
|
|
||||||
})
|
|
||||||
|
|
||||||
const executeBtnClass = computed(() => {
|
|
||||||
// 鼠标悬停在过程按钮上时,执行按钮变圆形
|
|
||||||
if (buttonHoverState.value === 'process') {
|
|
||||||
return 'circle'
|
|
||||||
}
|
|
||||||
// 其他情况:如果有任务数据就显示椭圆形,否则显示圆形
|
|
||||||
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 计算按钮是否显示文字
|
|
||||||
const showProcessText = computed(() => {
|
|
||||||
return buttonHoverState.value === 'process'
|
|
||||||
})
|
|
||||||
|
|
||||||
const showExecuteText = computed(() => {
|
|
||||||
// 鼠标悬停在过程按钮上时,执行按钮不显示文字
|
|
||||||
if (buttonHoverState.value === 'process') return false
|
|
||||||
// 其他情况:如果有任务数据就显示文字,否则不显示
|
|
||||||
return agentsStore.agentRawPlan.data
|
|
||||||
})
|
|
||||||
|
|
||||||
// 计算按钮标题
|
|
||||||
const processBtnTitle = computed(() => {
|
|
||||||
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
|
|
||||||
})
|
|
||||||
|
|
||||||
const executeBtnTitle = computed(() => {
|
|
||||||
return showExecuteText.value ? '任务执行' : '点击运行'
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
createInternalLine,
|
|
||||||
clear
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="h-full flex flex-col relative"
|
|
||||||
id="task-results"
|
|
||||||
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
|
||||||
>
|
|
||||||
<!-- 标题与执行按钮 -->
|
|
||||||
<div class="text-[18px] font-bold mb-[18px] flex justify-between items-center px-[20px]">
|
|
||||||
<span class="text-[var(--color-text-title-header)]">执行结果</span>
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-[14px] task-button-group"
|
|
||||||
@mouseleave="handleButtonMouseLeave"
|
|
||||||
>
|
|
||||||
<!-- 任务过程按钮 -->
|
|
||||||
<el-button
|
|
||||||
:class="processBtnClass"
|
|
||||||
:color="variables.tertiary"
|
|
||||||
:title="processBtnTitle"
|
|
||||||
@mouseenter="handleProcessMouseEnter"
|
|
||||||
@click="handleTaskProcess"
|
|
||||||
>
|
|
||||||
<svg-icon icon-class="process" />
|
|
||||||
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
|
||||||
</el-button>
|
|
||||||
|
|
||||||
<!-- 任务执行按钮 -->
|
|
||||||
<el-popover
|
|
||||||
:disabled="Boolean(agentsStore.agentRawPlan.data)"
|
|
||||||
title="请先输入要执行的任务"
|
|
||||||
:visible="showPopover"
|
|
||||||
@hide="showPopover = false"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<el-button
|
|
||||||
:class="executeBtnClass"
|
|
||||||
:color="variables.tertiary"
|
|
||||||
:title="executeBtnTitle"
|
|
||||||
:disabled="!agentsStore.agentRawPlan.data"
|
|
||||||
@mouseenter="handleExecuteMouseEnter"
|
|
||||||
@click="handleRun"
|
|
||||||
>
|
|
||||||
<svg-icon icon-class="action" />
|
|
||||||
<span v-if="showExecuteText" class="btn-text">任务执行</span>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
|
|
||||||
<el-drawer
|
|
||||||
v-model="drawerVisible"
|
|
||||||
title="任务过程"
|
|
||||||
direction="rtl"
|
|
||||||
size="30%"
|
|
||||||
:destroy-on-close="false"
|
|
||||||
>
|
|
||||||
<!-- 头部工具栏 -->
|
|
||||||
<template #header>
|
|
||||||
<div class="drawer-header">
|
|
||||||
<span class="title">任务过程</span>
|
|
||||||
<!-- <el-button v-if="!editMode" text icon="Edit" @click="editMode = true" />
|
|
||||||
<el-button v-else text icon="Check" @click="save" /> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<el-scrollbar height="calc(100vh - 120px)">
|
|
||||||
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
|
|
||||||
<div v-else class="process-list">
|
|
||||||
<!-- 使用ProcessCard组件显示每个AgentSelection -->
|
|
||||||
<ProcessCard
|
|
||||||
v-for="step in collaborationProcess"
|
|
||||||
:key="step.Id"
|
|
||||||
:step="step"
|
|
||||||
@open-edit="handleOpenEdit"
|
|
||||||
@save-edit="handleSaveEdit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-scrollbar>
|
|
||||||
</el-drawer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div
|
|
||||||
v-loading="agentsStore.agentRawPlan.loading"
|
|
||||||
class="flex-1 overflow-auto relative"
|
|
||||||
@scroll="handleScroll"
|
|
||||||
>
|
|
||||||
<div id="task-results-main" class="px-[40px] relative">
|
|
||||||
<!-- 原有的流程和产物 -->
|
|
||||||
<div v-for="item in collaborationProcess" :key="item.Id" class="card-item">
|
|
||||||
<el-card
|
|
||||||
class="card-item w-full relative"
|
|
||||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
|
||||||
:shadow="true"
|
|
||||||
:id="`task-results-${item.Id}-0`"
|
|
||||||
@click="emit('setCurrentTask', item)"
|
|
||||||
>
|
|
||||||
<div class="text-[18px] mb-[15px]">{{ item.StepName }}</div>
|
|
||||||
<!-- 折叠面板 -->
|
|
||||||
<el-collapse @change="handleCollapse">
|
|
||||||
<el-collapse-item
|
|
||||||
v-for="item1 in item.TaskProcess"
|
|
||||||
:key="`task-results-${item.Id}-${item1.ID}`"
|
|
||||||
:name="`task-results-${item.Id}-${item1.ID}`"
|
|
||||||
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
|
|
||||||
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
>
|
|
||||||
<template v-if="loading" #icon>
|
|
||||||
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
|
||||||
<span></span>
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-[15px] rounded-[20px]"
|
|
||||||
:class="{
|
|
||||||
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
|
|
||||||
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- 右侧链接点 -->
|
|
||||||
<div
|
|
||||||
class="absolute right-0 top-1/2 transform -translate-y-1/2"
|
|
||||||
:id="`task-results-${item.Id}-0-${item1.ID}`"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="w-[41px] h-[41px] rounded-full flex items-center justify-center"
|
|
||||||
:style="{ background: getAgentMapIcon(item1.AgentName).color }"
|
|
||||||
>
|
|
||||||
<svg-icon
|
|
||||||
:icon-class="getAgentMapIcon(item1.AgentName).icon"
|
|
||||||
color="#fff"
|
|
||||||
size="24px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="text-[16px]">
|
|
||||||
<span>{{ item1.AgentName }}: </span>
|
|
||||||
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
|
|
||||||
{{ getActionTypeDisplay(item1.ActionType)?.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<ExecutePlan
|
|
||||||
:action-id="item1.ID"
|
|
||||||
:node-id="item.StepName"
|
|
||||||
:execute-plans="agentsStore.executePlan"
|
|
||||||
/>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card
|
|
||||||
class="card-item w-full relative output-object-card"
|
|
||||||
:shadow="true"
|
|
||||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
|
||||||
:id="`task-results-${item.Id}-1`"
|
|
||||||
@click="emit('setCurrentTask', item)"
|
|
||||||
>
|
|
||||||
<!-- <div class="text-[18px]">{{ item.OutputObject }}</div>-->
|
|
||||||
<el-collapse @change="handleCollapse">
|
|
||||||
<el-collapse-item
|
|
||||||
class="output-object"
|
|
||||||
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
|
|
||||||
>
|
|
||||||
<template v-if="loading" #icon>
|
|
||||||
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
|
||||||
<span></span>
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
<div class="text-[18px]">{{ item.OutputObject }}</div>
|
|
||||||
</template>
|
|
||||||
<ExecutePlan
|
|
||||||
:node-id="item.OutputObject"
|
|
||||||
:execute-plans="agentsStore.executePlan"
|
|
||||||
/>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 额外产物的编辑卡片 -->
|
|
||||||
<div
|
|
||||||
v-for="(output, index) in agentsStore.additionalOutputs"
|
|
||||||
:key="`additional-output-${index}`"
|
|
||||||
class="card-item"
|
|
||||||
>
|
|
||||||
<!-- 空的流程卡片位置 -->
|
|
||||||
<div class="w-full"></div>
|
|
||||||
|
|
||||||
<!-- 额外产物的编辑卡片 -->
|
|
||||||
<el-card
|
|
||||||
class="card-item w-full relative output-object-card additional-output-card"
|
|
||||||
:shadow="false"
|
|
||||||
:id="`additional-output-results-${index}`"
|
|
||||||
>
|
|
||||||
<!-- 产物名称行 -->
|
|
||||||
<div class="text-[18px] mb-3">
|
|
||||||
{{ output }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 编辑区域行 -->
|
|
||||||
<div class="additional-output-editor">
|
|
||||||
<div v-if="editingOutputId === output" class="w-full">
|
|
||||||
<!-- 编辑状态:输入框 + 按钮 -->
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<el-input
|
|
||||||
v-model="editingOutputContent"
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
|
||||||
placeholder="请输入产物内容"
|
|
||||||
@keydown="handleOutputKeydown"
|
|
||||||
class="output-editor"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<div class="flex justify-end gap-2">
|
|
||||||
<el-button @click="saveOutputEditing" type="primary" size="small" class="px-3">
|
|
||||||
√
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="cancelOutputEditing" size="small" class="px-3">
|
|
||||||
×
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="w-full">
|
|
||||||
<!-- 非编辑状态:折叠区域 + 编辑按钮 -->
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between p-3 bg-[var(--color-bg-quinary)] rounded-[8px]"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="text-[14px] text-[var(--color-text-secondary)] output-content-display"
|
|
||||||
>
|
|
||||||
{{ getAdditionalOutputContent(output) || '暂无内容,点击编辑' }}
|
|
||||||
</div>
|
|
||||||
<el-button
|
|
||||||
@click="startOutputEditing(output)"
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
class="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<svg-icon icon-class="action" size="12px" />
|
|
||||||
<span>编辑</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
#task-results.is-running {
|
|
||||||
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
|
|
||||||
}
|
|
||||||
#task-results {
|
|
||||||
:deep(.el-collapse) {
|
|
||||||
border: none;
|
|
||||||
border-radius: 20px;
|
|
||||||
.el-collapse-item + .el-collapse-item {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-collapse-item__header {
|
|
||||||
border: none;
|
|
||||||
background: var(--color-bg-detail-list-run);
|
|
||||||
min-height: 41px;
|
|
||||||
line-height: 41px;
|
|
||||||
border-radius: 20px;
|
|
||||||
transition: border-radius 1ms;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.el-collapse-item__title {
|
|
||||||
background: var(--color-bg-detail-list);
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 900;
|
|
||||||
background: var(--color-bg-icon-rotate);
|
|
||||||
border-radius: 50px;
|
|
||||||
color: #d8d8d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-object {
|
|
||||||
.el-collapse-item__header {
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
.el-collapse-item__title {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-collapse-item__wrap {
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
.card-item {
|
|
||||||
background: var(--color-bg-detail);
|
|
||||||
padding: 25px;
|
|
||||||
padding-top: 10px;
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-collapse-item__wrap {
|
|
||||||
border: none;
|
|
||||||
background: var(--color-bg-detail-list);
|
|
||||||
border-bottom-left-radius: 20px;
|
|
||||||
border-bottom-right-radius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-card) {
|
|
||||||
.el-card__body {
|
|
||||||
padding-right: 40px;
|
|
||||||
background-color: var(--color-bg-detail);
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-card-bg-result-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-object-card {
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-card {
|
|
||||||
background: linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
|
|
||||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-item + .card-item {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additional-output-card {
|
|
||||||
border: 1px dashed #dcdfe6;
|
|
||||||
opacity: 0.9;
|
|
||||||
box-shadow: var(--color-agent-list-hover-shadow);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #409eff;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑区域样式调整
|
|
||||||
.el-collapse {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
.el-collapse-item {
|
|
||||||
.el-collapse-item__header {
|
|
||||||
background: var(--color-bg-detail);
|
|
||||||
min-height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
.el-collapse-item__title {
|
|
||||||
background: transparent;
|
|
||||||
font-size: 14px;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-collapse-item__wrap {
|
|
||||||
background: var(--color-bg-detail);
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 额外产物编辑区域样式
|
|
||||||
.additional-output-editor {
|
|
||||||
.output-editor {
|
|
||||||
:deep(.el-textarea__inner) {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-bg-detail);
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
resize: none;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-content-display {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
line-height: 1.5;
|
|
||||||
min-height: 20px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
border-color: #409eff;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑按钮样式
|
|
||||||
.el-button {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&.el-button--small {
|
|
||||||
padding: 4px 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 新增:按钮交互样式 ==========
|
|
||||||
.task-button-group {
|
|
||||||
.el-button {
|
|
||||||
display: inline-flex !important;
|
|
||||||
align-items: center !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
white-space: nowrap !important;
|
|
||||||
border: none !important;
|
|
||||||
color: var(--color-text-primary) !important;
|
|
||||||
position: relative;
|
|
||||||
background-color: var(--color-bg-tertiary);
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-2px) !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
||||||
filter: brightness(1.1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
filter: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 圆形状态
|
|
||||||
.circle {
|
|
||||||
width: 40px !important;
|
|
||||||
height: 40px !important;
|
|
||||||
min-width: 40px !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
border-radius: 50% !important;
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 椭圆形状态
|
|
||||||
.ellipse {
|
|
||||||
height: 40px !important;
|
|
||||||
border-radius: 20px !important;
|
|
||||||
padding: 0 16px !important;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
display: inline-block !important;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 4px;
|
|
||||||
opacity: 1;
|
|
||||||
animation: fadeIn 0.3s ease forwards;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 4px;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.3s ease forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-5px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
import type {
|
||||||
|
AxiosError,
|
||||||
|
AxiosInstance,
|
||||||
|
AxiosRequestConfig,
|
||||||
|
AxiosResponse,
|
||||||
|
InternalAxiosRequestConfig,
|
||||||
|
} from 'axios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
@@ -16,7 +22,6 @@ export interface AxiosResponseData {
|
|||||||
export function initService() {
|
export function initService() {
|
||||||
service = axios.create({
|
service = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
timeout: 50000,
|
|
||||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||||
paramsSerializer: (params) => {
|
paramsSerializer: (params) => {
|
||||||
return qs.stringify(params)
|
return qs.stringify(params)
|
||||||
|
|||||||
Reference in New Issue
Block a user