Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@
<template v-if="scrollbarAgentIndex === ''">
<el-scrollbar class="sidebar-agent-container" wrap-class="scrollbar-wrapper">
<div ref="agentElListBox" class="agent-listWrap">
<div v-for="(aitem,aindex) in agentList" :key="aindex" class="agent-cardBox">
<div v-for="(aitem,aindex) in agentList" :key="aindex" class="agent-cardBox"
draggable="true"
@@dragstart="handleAgentDragStart(aitem)"
@@dragend="handleAgentDragEnd">
<div class="agentCard-statebox" :class="[getStatusColor(aitem,'1')]">
{{ getStatusText(aitem,'1') }}
</div>
Expand Down Expand Up @@ -1103,6 +1106,33 @@
</el-tab-pane>
</el-tabs>

@* 拖放覆盖层 - 将智能体拖入组 *@
<div v-if="isDraggingAgent" class="drag-drop-overlay" @@dragover.prevent @@drop.prevent>
<div class="drag-drop-overlay-header">
<i class="fas fa-hand-holding"></i>
将 <strong>{{ draggingAgentData && draggingAgentData.name }}</strong> 拖放到组中
</div>
<div class="drag-drop-overlay-list">
<template v-if="groupList && groupList.length > 0">
<div v-for="group in groupList" :key="group.id"
class="drag-drop-group-item"
:class="{'drag-drop-group-hover': dragOverGroupId === group.id}"
@@dragover.prevent="handleGroupDropZoneOver(group.id)"
@@dragleave="handleGroupDropZoneLeave"
@@drop.prevent="handleAgentDropToGroup(group)">
<i class="fas fa-users" style="margin-right:6px;"></i>
<span class="drag-drop-group-name text-ellipsis">{{ group.name }}</span>
<span class="drag-drop-group-count">{{ group.numberTasks || 0 }} 个任务</span>
</div>
</template>
<div v-else class="drag-drop-empty">
<i class="el-icon-warning-outline"></i>
暂无可用组,请先创建组
</div>
</div>
<div class="drag-drop-overlay-footer">拖到组名称上方释放即可加入</div>
</div>

@* 抽屉 新增|编辑 智能体 540px *@
<el-drawer :visible.sync="visible.drawerAgent" direction="rtl" :with-header="false" :wrapper-closable="false"
:close-on-press-escape="false" size="960px">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Senparc.Ncf.Core.AppServices;
using Senparc.Ncf.Core.Exceptions;
using Senparc.Ncf.Utility;
using Senparc.Ncf.XncfBase;
using Senparc.Xncf.AgentsManager.Domain.Services;
using Senparc.Xncf.AgentsManager.Models.DatabaseModel.Models;
using Senparc.Xncf.AgentsManager.Models.DatabaseModel.Models.Dto;
Expand All @@ -15,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Xncf.AgentsManager.OHS.Local.AppService
Expand Down Expand Up @@ -316,5 +318,138 @@ public async Task<AppResponseBase<string>> RunGroup(ChatGroup_RunGroupRequest re

});
}

/// <summary>
/// 快速组建团队并立即执行任务
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[FunctionRender("快速组建团队并执行任务", "快速选择成员,组建临时团队,并立即执行指定任务", typeof(Register))]
public async Task<StringAppResponse> QuickTeamAndRun(ChatGroup_QuickTeamRequest request)
{
return await this.GetStringResponseAsync(async (response, logger) =>
{
if (!request.Members.SelectedValues.Any())
{
return "至少需要选择一名成员!";
}

if (!int.TryParse(request.Admin.SelectedValues.FirstOrDefault(), out int adminId))
{
return "必须选择一位群主!";
}

if (!int.TryParse(request.EnterAgent.SelectedValues.FirstOrDefault(), out int enterAgentId))
{
return "必须选择一位对接人!";
}

if (string.IsNullOrWhiteSpace(request.Command))
{
return "任务描述不能为空!";
}

// 创建团队名称
var teamName = string.IsNullOrWhiteSpace(request.TeamName)
? $"快速团队-{DateTime.Now:yyyyMMddHHmmss}"
: request.TeamName;

// 创建新的 ChatGroup
var chatGroupDto = new ChatGroupDto(teamName, true, ChatGroupState.Unstart, "通过快速组建功能创建", adminId, enterAgentId);
var chatGroup = new ChatGroup(chatGroupDto);
await _chatGroupService.SaveObjectAsync(chatGroup);
logger.Append($"团队 [{teamName}] 创建成功!");

// 添加成员(合并对接人到成员列表)
var memberIdList = request.Members.SelectedValues.Select(z => int.Parse(z)).ToList();
if (!memberIdList.Contains(enterAgentId))
{
memberIdList.Add(enterAgentId);
}

var memberList = new List<ChatGroupMember>();
foreach (var agentId in memberIdList)
{
var chatGroupMemberDto = new ChatGroupMemberDto(null, chatGroup.Id, agentId);
var member = new ChatGroupMember(chatGroupMemberDto);
member.ResetUID();
memberList.Add(member);
}
await _chatGroupMemeberService.SaveObjectListAsync(memberList);
logger.Append($"已添加 {memberList.Count} 名成员!");

// 确定 AI 模型设置
var aiModelSelected = request.AIModel.SelectedValues.FirstOrDefault();
var aiSetting = Senparc.AI.Config.SenparcAiSetting;
if (!string.IsNullOrEmpty(aiModelSelected) && aiModelSelected != "Default")
{
int.TryParse(aiModelSelected, out int aiModelId);
var aiModel = await _aIModelService.GetObjectAsync(z => z.Id == aiModelId);
if (aiModel == null)
{
throw new NcfExceptionBase($"当前选择的 AI 模型不存在:{aiModelSelected}");
}
var aiModelDto = _aIModelService.Mapper.Map<AIModelDto>(aiModel);
aiSetting = _aIModelService.BuildSenparcAiSetting(aiModelDto);
}

// 执行任务
logger.Append($"开始执行任务:{request.Command}");
var runTask = _chatGroupService.RunChatGroup(logger, chatGroup.Id, request.Command, aiSetting, false);
Task.WaitAll(new[] { (Task)runTask });

return logger.ToString();
});
}

/// <summary>
/// 获取可用的 XNCF 模块列表(用于 AI Chat 自动附加模块功能)
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[FunctionRender("获取可用 XNCF 模块", "列举系统中所有已注册的 XNCF 模块及其可用功能,可用于 AI Chat 自动附加模块功能", typeof(Register))]
public async Task<StringAppResponse> GetAvailableXncfModules(ChatGroup_GetAvailableXncfModulesRequest request)
{
return await this.GetStringResponseAsync(async (response, logger) =>
{
var registers = XncfRegisterManager.RegisterList;
if (!registers.Any())
{
return "当前没有已注册的 XNCF 模块。";
}

var sb = new StringBuilder();
sb.AppendLine($"共发现 {registers.Count} 个 XNCF 模块:");
sb.AppendLine();

foreach (var register in registers)
{
sb.AppendLine($"【{register.Name}】");
sb.AppendLine($" UID: {register.Uid}");
sb.AppendLine($" 版本: {register.Version}");
if (!string.IsNullOrEmpty(register.Description))
{
sb.AppendLine($" 描述: {register.Description}");
}

if (Senparc.Ncf.XncfBase.Register.FunctionRenderCollection.TryGetValue(register.GetType(), out var functionGroup)
&& functionGroup != null && functionGroup.Count > 0)
{
sb.AppendLine($" 可用功能 ({functionGroup.Count} 个):");
foreach (var function in functionGroup)
{
sb.AppendLine($" - {function.Value.FunctionRenderAttribute.Name}: {function.Value.FunctionRenderAttribute.Description}");
}
}
else
{
sb.AppendLine(" 可用功能: 无");
}
sb.AppendLine();
}

return sb.ToString().Replace("\r\n", "<br />").Replace("\n", "<br />");
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,65 @@ public class ChatGroup_RunGroupRequest
/// </summary>
public int ChatMaxRound { get; set; } = ChatGroupService.ChatMaxRound;
}

/// <summary>
/// 快速组建团队并执行任务 Request
/// </summary>
public class ChatGroup_QuickTeamRequest : FunctionAppRequestBase
{
[Required]
[MaxLength(50)]
[Description("团队名称||团队名称(将创建一个临时团队并立即执行任务)")]
public string TeamName { get; set; }

[Required]
[Description("团队成员||选择加入团队的成员")]
public SelectionList Members { get; set; } = new SelectionList(SelectionType.CheckBoxList, new List<SelectionItem>());

[Required]
[Description("群主||群管理员,通常负责协调和管理团队,不参与显式发言")]
public SelectionList Admin { get; set; } = new SelectionList(SelectionType.DropDownList, new List<SelectionItem>());

[Required]
[Description("对接人||对接人,即接受命令的人,通常也是期待返回期望结果的人,会自动加入成员列表")]
public SelectionList EnterAgent { get; set; } = new SelectionList(SelectionType.DropDownList, new List<SelectionItem>());

[Description("AI 模型||请选择运行此程序的外围 AI 模型")]
public SelectionList AIModel { get; set; } = new SelectionList(SelectionType.DropDownList, new List<SelectionItem>());

[Required]
[MaxLength(500)]
[Description("任务描述||说明需要团队协助完成的工作内容")]
public string Command { get; set; }

public override async Task LoadData(IServiceProvider serviceProvider)
{
var agentTemplateService = serviceProvider.GetService<AgentsTemplateService>();
var agentsTemplates = await agentTemplateService.GetFullListAsync(z => z.Enable, z => z.Name, Ncf.Core.Enums.OrderingType.Ascending);

Members.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList();
Admin.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList();
EnterAgent.Items = agentsTemplates.Select(z => new SelectionItem(z.Id.ToString(), z.Name, z.Description)).ToList();

var admin = Admin.Items.FirstOrDefault(z => z.Text == "群主");
if (admin != null)
{
admin.DefaultSelected = true;
}

await BuildXncfRequestHelper.LoadAiModelData(serviceProvider, AIModel);
await base.LoadData(serviceProvider);
}
}

/// <summary>
/// 获取可用 XNCF 模块 Request
/// </summary>
public class ChatGroup_GetAvailableXncfModulesRequest : FunctionAppRequestBase
{
public override async Task LoadData(IServiceProvider serviceProvider)
{
await base.LoadData(serviceProvider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1824,4 +1824,117 @@ height:100%;
.mcp-endpoint-actions {
display: flex;
align-items: center;
}
}
/* 拖放覆盖层样式 - 智能体拖入组 */
.drag-drop-overlay {
position: fixed;
bottom: 30px;
right: 30px;
width: 280px;
max-height: 420px;
background: rgba(255, 255, 255, 0.98);
border: 2px dashed #409EFF;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(64, 158, 255, 0.25);
z-index: 9999;
overflow: hidden;
display: flex;
flex-direction: column;
animation: drag-drop-appear 0.2s ease-out;
}

@keyframes drag-drop-appear {
from { opacity: 0; transform: scale(0.9) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
.drag-drop-overlay {
animation: none;
}
.drag-drop-group-hover {
transform: none;
}
}

.drag-drop-overlay-header {
padding: 12px 14px;
background: linear-gradient(135deg, #ecf5ff, #d9ecff);
border-bottom: 1px solid #b3d8ff;
font-size: 12px;
color: #409EFF;
line-height: 1.5;
}

.drag-drop-overlay-list {
flex: 1;
overflow-y: auto;
padding: 6px;
}

.drag-drop-group-item {
display: flex;
align-items: center;
padding: 9px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
border: 2px solid transparent;
transition: all 0.15s;
margin-bottom: 3px;
color: #333;
}

.drag-drop-group-item:hover,
.drag-drop-group-hover {
background: #ecf5ff;
border-color: #409EFF;
color: #409EFF;
}

.drag-drop-group-hover {
background: #d9ecff;
transform: scale(1.02);
}

.drag-drop-group-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.drag-drop-group-count {
margin-left: 8px;
font-size: 11px;
color: #909399;
white-space: nowrap;
}

.drag-drop-empty {
text-align: center;
color: #909399;
font-size: 13px;
padding: 24px 16px;
}

.drag-drop-overlay-footer {
padding: 8px 14px;
background: #f5f7fa;
border-top: 1px solid #eee;
font-size: 11px;
color: #909399;
text-align: center;
}

/* 智能体卡片拖拽状态 */
.agent-cardBox[draggable="true"] {
cursor: grab;
}

.agent-cardBox[draggable="true"]:active {
cursor: grabbing;
opacity: 0.8;
transform: scale(0.98);
box-shadow: 0 8px 24px rgba(64, 158, 255, 0.3);
}
Loading
Loading