发布 v1.10 #5
@@ -1,24 +1,45 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import baseFormSource from './components/AgentBaseForm.vue?raw';
|
import builtinToolsFormSource from './components/AgentBuiltinToolsForm.vue?raw';
|
||||||
import inspectorSource from './components/AgentInspectorPanel.vue?raw';
|
import inspectorSource from './components/AgentInspectorPanel.vue?raw';
|
||||||
|
import tryoutPanelSource from './components/AgentTryoutPanel.vue?raw';
|
||||||
|
|
||||||
describe('agent Studio responsive layout contract', () => {
|
describe('agent Studio responsive layout contract', () => {
|
||||||
it('keeps the inspector usable at the 768px breakpoint', () => {
|
it('keeps the inspector usable at the 768px breakpoint', () => {
|
||||||
expect(inspectorSource).toContain('@media (max-width: 900px)');
|
expect(inspectorSource).toContain('@media (max-width: 900px)');
|
||||||
expect(inspectorSource).toMatch(
|
expect(inspectorSource).toMatch(
|
||||||
/@media \(max-width: 900px\)[\s\S]*?left: var\(--space-4\);[\s\S]*?width: auto;/,
|
/@media \(max-width: 900px\)[\s\S]*?inset:[\s\S]*?var\(--space-4\)[\s\S]*?96px;/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps the inspector and builtin tool controls usable at 375px', () => {
|
it('keeps the inspector and builtin tool controls usable at 375px', () => {
|
||||||
expect(inspectorSource).toMatch(
|
expect(inspectorSource).toMatch(
|
||||||
/@media \(max-width: 480px\)[\s\S]*?right: var\(--space-2\);[\s\S]*?left: var\(--space-2\);/,
|
/@media \(max-width: 480px\)[\s\S]*?inset:[\s\S]*?var\(--space-2\)[\s\S]*?96px;/,
|
||||||
);
|
);
|
||||||
expect(baseFormSource).toMatch(
|
expect(builtinToolsFormSource).toMatch(
|
||||||
/@media \(max-width: 480px\)[\s\S]*?--agent-tool-approval-column: 72px;/,
|
/@media \(max-width: 480px\)[\s\S]*?--agent-tool-approval-column: 72px;/,
|
||||||
);
|
);
|
||||||
expect(baseFormSource).toContain('white-space: normal;');
|
expect(builtinToolsFormSource).toContain('white-space: normal;');
|
||||||
expect(baseFormSource).toContain('-webkit-line-clamp: 2;');
|
expect(builtinToolsFormSource).toContain('-webkit-line-clamp: 2;');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports a centered animated tryout surface without remounting it', () => {
|
||||||
|
expect(inspectorSource).toContain('label="内置工具"');
|
||||||
|
expect(inspectorSource).toContain(':expanded="tryoutExpanded"');
|
||||||
|
expect(inspectorSource).toContain("'is-tryout-expanded'");
|
||||||
|
expect(inspectorSource).toContain('--motion-duration-medium');
|
||||||
|
expect(inspectorSource).toContain(
|
||||||
|
'@media (prefers-reduced-motion: reduce)',
|
||||||
|
);
|
||||||
|
expect(tryoutPanelSource).toContain(':aria-pressed="Boolean(expanded)"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps clear disabled throughout active output and async cleanup', () => {
|
||||||
|
expect(tryoutPanelSource).toContain(
|
||||||
|
':disabled="loading || approvalLoading || clearing"',
|
||||||
|
);
|
||||||
|
expect(tryoutPanelSource).toContain(
|
||||||
|
'if (loading.value || approvalLoading.value || clearing.value)',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/* eslint-disable vue/no-mutating-props */
|
/* eslint-disable vue/no-mutating-props */
|
||||||
import type { AgentBuiltinToolKey } from '../builtin-tools';
|
|
||||||
import type { AgentInfo, AgentOption, AgentToolBinding } from '../types';
|
import type { AgentInfo, AgentOption, AgentToolBinding } from '../types';
|
||||||
|
|
||||||
import { InfoFilled } from '@element-plus/icons-vue';
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
ElIcon,
|
ElIcon,
|
||||||
ElInput,
|
ElInput,
|
||||||
ElInputNumber,
|
ElInputNumber,
|
||||||
ElMessageBox,
|
|
||||||
ElOption,
|
ElOption,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
@@ -21,7 +19,6 @@ import { resolveAgentCompressionTokenThreshold } from '../compression-threshold'
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
agent: AgentInfo;
|
agent: AgentInfo;
|
||||||
canDisableShellApproval: boolean;
|
|
||||||
categories: AgentOption[];
|
categories: AgentOption[];
|
||||||
models: AgentOption[];
|
models: AgentOption[];
|
||||||
toolBindings: AgentToolBinding[];
|
toolBindings: AgentToolBinding[];
|
||||||
@@ -35,92 +32,6 @@ const visibilityScopeOptions = [
|
|||||||
{ label: '公开', value: 'PUBLIC' },
|
{ label: '公开', value: 'PUBLIC' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const builtinToolOptions: Array<{
|
|
||||||
description: string;
|
|
||||||
key: AgentBuiltinToolKey;
|
|
||||||
label: string;
|
|
||||||
}> = [
|
|
||||||
{ description: '读取工作区内的 UTF-8 文本', key: 'read', label: '读取文件' },
|
|
||||||
{ description: '在工作区内写入或插入文本', key: 'write', label: '写入文件' },
|
|
||||||
{
|
|
||||||
description: '以补丁方式安全修改工作区文件',
|
|
||||||
key: 'patch',
|
|
||||||
label: '补丁修改',
|
|
||||||
},
|
|
||||||
{ description: '执行平台白名单中的受控命令', key: 'shell', label: 'Shell' },
|
|
||||||
{
|
|
||||||
description: '将工作区文件发布为可下载产物',
|
|
||||||
key: 'artifactPublish',
|
|
||||||
label: '发布产物',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function builtinTool(key: AgentBuiltinToolKey) {
|
|
||||||
return props.agent.executionConfigJson!.builtinTools![key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBuiltinToolEnabledChange(key: AgentBuiltinToolKey) {
|
|
||||||
if (key === 'shell' && !props.canDisableShellApproval) {
|
|
||||||
builtinTool('shell').approvalRequired = true;
|
|
||||||
delete props.agent.executionConfigJson!.builtinTools!
|
|
||||||
.shellApprovalRiskConfirmed;
|
|
||||||
}
|
|
||||||
emit('change');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmUnsafeShellExecution() {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'关闭后,命令和脚本将以后端服务权限在宿主环境运行,当前没有沙箱隔离。确认继续?',
|
|
||||||
'关闭 Shell 调用确认',
|
|
||||||
{
|
|
||||||
cancelButtonText: '保持开启',
|
|
||||||
confirmButtonText: '确认关闭',
|
|
||||||
type: 'warning',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
props.agent.executionConfigJson!.builtinTools!.shellApprovalRiskConfirmed = true;
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmBuiltinToolEnabledChange(key: AgentBuiltinToolKey) {
|
|
||||||
if (
|
|
||||||
key !== 'shell' ||
|
|
||||||
builtinTool('shell').enabled ||
|
|
||||||
builtinTool('shell').approvalRequired
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!props.canDisableShellApproval) {
|
|
||||||
builtinTool('shell').approvalRequired = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return confirmUnsafeShellExecution();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmShellApprovalChange() {
|
|
||||||
if (!props.canDisableShellApproval) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!builtinTool('shell').approvalRequired) {
|
|
||||||
delete props.agent.executionConfigJson!.builtinTools!
|
|
||||||
.shellApprovalRiskConfirmed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return confirmUnsafeShellExecution();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleApprovalChange(key: AgentBuiltinToolKey, value: unknown) {
|
|
||||||
if (key === 'shell' && value === true) {
|
|
||||||
delete props.agent.executionConfigJson!.builtinTools!
|
|
||||||
.shellApprovalRiskConfirmed;
|
|
||||||
}
|
|
||||||
emit('change');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleModelChange(modelId: AgentInfo['modelId']) {
|
function handleModelChange(modelId: AgentInfo['modelId']) {
|
||||||
const selectedModel = props.models.find(
|
const selectedModel = props.models.find(
|
||||||
(model) => model.value === String(modelId ?? ''),
|
(model) => model.value === String(modelId ?? ''),
|
||||||
@@ -268,66 +179,6 @@ function handleModelChange(modelId: AgentInfo['modelId']) {
|
|||||||
@input="emit('change')"
|
@input="emit('change')"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<section
|
|
||||||
class="agent-form__builtin-tools"
|
|
||||||
aria-labelledby="builtin-tools-title"
|
|
||||||
>
|
|
||||||
<div class="agent-form__section-head">
|
|
||||||
<div>
|
|
||||||
<div id="builtin-tools-title" class="agent-form__section-title">
|
|
||||||
内置工具
|
|
||||||
</div>
|
|
||||||
<div class="agent-form__section-description">
|
|
||||||
为当前智能体配置工作区能力
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="agent-form__tool-columns" aria-hidden="true">
|
|
||||||
<span>启用</span>
|
|
||||||
<span>调用前确认</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="agent-form__tool-list">
|
|
||||||
<div
|
|
||||||
v-for="item in builtinToolOptions"
|
|
||||||
:key="item.key"
|
|
||||||
class="agent-form__tool-row"
|
|
||||||
>
|
|
||||||
<div class="agent-form__tool-copy">
|
|
||||||
<div class="agent-form__tool-name">{{ item.label }}</div>
|
|
||||||
<div class="agent-form__tool-description">
|
|
||||||
{{ item.description }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ElSwitch
|
|
||||||
v-model="builtinTool(item.key).enabled"
|
|
||||||
:aria-label="`启用${item.label}`"
|
|
||||||
:before-change="() => confirmBuiltinToolEnabledChange(item.key)"
|
|
||||||
@change="handleBuiltinToolEnabledChange(item.key)"
|
|
||||||
/>
|
|
||||||
<ElTooltip
|
|
||||||
:disabled="item.key !== 'shell' || canDisableShellApproval"
|
|
||||||
content="Shell 调用确认仅平台超级管理员可以关闭"
|
|
||||||
effect="light"
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<span class="agent-form__tool-approval">
|
|
||||||
<ElSwitch
|
|
||||||
v-model="builtinTool(item.key).approvalRequired"
|
|
||||||
:aria-label="`${item.label}调用前确认`"
|
|
||||||
:before-change="
|
|
||||||
item.key === 'shell' ? confirmShellApprovalChange : undefined
|
|
||||||
"
|
|
||||||
:disabled="
|
|
||||||
!builtinTool(item.key).enabled ||
|
|
||||||
(item.key === 'shell' && !canDisableShellApproval)
|
|
||||||
"
|
|
||||||
@change="handleApprovalChange(item.key, $event)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</ElTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div class="agent-form__grid">
|
<div class="agent-form__grid">
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<template #label>
|
<template #label>
|
||||||
@@ -396,89 +247,6 @@ function handleModelChange(modelId: AgentInfo['modelId']) {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-form__builtin-tools {
|
|
||||||
--agent-tool-approval-column: 96px;
|
|
||||||
--agent-tool-enabled-column: 48px;
|
|
||||||
|
|
||||||
margin-bottom: var(--space-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__section-head {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns:
|
|
||||||
minmax(0, 1fr)
|
|
||||||
calc(var(--agent-tool-enabled-column) + var(--agent-tool-approval-column));
|
|
||||||
gap: var(--space-3);
|
|
||||||
align-items: end;
|
|
||||||
padding-bottom: var(--space-2);
|
|
||||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 22px;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__section-description,
|
|
||||||
.agent-form__tool-description,
|
|
||||||
.agent-form__tool-columns {
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-columns {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: var(--agent-tool-enabled-column) var(
|
|
||||||
--agent-tool-approval-column
|
|
||||||
);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns:
|
|
||||||
minmax(0, 1fr) var(--agent-tool-enabled-column)
|
|
||||||
var(--agent-tool-approval-column);
|
|
||||||
gap: var(--space-3);
|
|
||||||
align-items: center;
|
|
||||||
min-height: 56px;
|
|
||||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-copy {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-description {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-row > :deep(.el-switch),
|
|
||||||
.agent-form__tool-approval {
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-approval {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__label {
|
.agent-form__label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -510,26 +278,5 @@ function handleModelChange(modelId: AgentInfo['modelId']) {
|
|||||||
.agent-form {
|
.agent-form {
|
||||||
padding: var(--space-3);
|
padding: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-form__builtin-tools {
|
|
||||||
--agent-tool-approval-column: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__section-head,
|
|
||||||
.agent-form__tool-row {
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-row {
|
|
||||||
min-height: calc(var(--space-8) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-form__tool-description {
|
|
||||||
display: -webkit-box;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AgentBuiltinToolKey } from '../builtin-tools';
|
||||||
|
import type { AgentInfo } from '../types';
|
||||||
|
|
||||||
|
import { ElForm, ElMessageBox, ElSwitch, ElTooltip } from 'element-plus';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
agent: AgentInfo;
|
||||||
|
canDisableShellApproval: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{ change: [] }>();
|
||||||
|
|
||||||
|
const builtinToolOptions: Array<{
|
||||||
|
description: string;
|
||||||
|
key: AgentBuiltinToolKey;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{ description: '读取工作区内的 UTF-8 文本', key: 'read', label: '读取文件' },
|
||||||
|
{ description: '在工作区内写入或插入文本', key: 'write', label: '写入文件' },
|
||||||
|
{
|
||||||
|
description: '以补丁方式安全修改工作区文件',
|
||||||
|
key: 'patch',
|
||||||
|
label: '补丁修改',
|
||||||
|
},
|
||||||
|
{ description: '执行平台白名单中的受控命令', key: 'shell', label: 'Shell' },
|
||||||
|
{
|
||||||
|
description: '将工作区文件发布为可下载产物',
|
||||||
|
key: 'artifactPublish',
|
||||||
|
label: '发布产物',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function builtinTool(key: AgentBuiltinToolKey) {
|
||||||
|
return props.agent.executionConfigJson!.builtinTools![key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBuiltinToolEnabledChange(key: AgentBuiltinToolKey) {
|
||||||
|
if (key === 'shell' && !props.canDisableShellApproval) {
|
||||||
|
builtinTool('shell').approvalRequired = true;
|
||||||
|
delete props.agent.executionConfigJson!.builtinTools!
|
||||||
|
.shellApprovalRiskConfirmed;
|
||||||
|
}
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmUnsafeShellExecution() {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'关闭后,命令和脚本将以后端服务权限在宿主环境运行,当前没有沙箱隔离。确认继续?',
|
||||||
|
'关闭 Shell 调用确认',
|
||||||
|
{
|
||||||
|
cancelButtonText: '保持开启',
|
||||||
|
confirmButtonText: '确认关闭',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
props.agent.executionConfigJson!.builtinTools!.shellApprovalRiskConfirmed = true;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmBuiltinToolEnabledChange(key: AgentBuiltinToolKey) {
|
||||||
|
if (
|
||||||
|
key !== 'shell' ||
|
||||||
|
builtinTool('shell').enabled ||
|
||||||
|
builtinTool('shell').approvalRequired
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!props.canDisableShellApproval) {
|
||||||
|
builtinTool('shell').approvalRequired = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return confirmUnsafeShellExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmShellApprovalChange() {
|
||||||
|
if (!props.canDisableShellApproval) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!builtinTool('shell').approvalRequired) {
|
||||||
|
delete props.agent.executionConfigJson!.builtinTools!
|
||||||
|
.shellApprovalRiskConfirmed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return confirmUnsafeShellExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleApprovalChange(key: AgentBuiltinToolKey, value: unknown) {
|
||||||
|
if (key === 'shell' && value === true) {
|
||||||
|
delete props.agent.executionConfigJson!.builtinTools!
|
||||||
|
.shellApprovalRiskConfirmed;
|
||||||
|
}
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ElForm class="agent-builtin-tools">
|
||||||
|
<section aria-labelledby="builtin-tools-description">
|
||||||
|
<div class="agent-builtin-tools__head">
|
||||||
|
<div
|
||||||
|
id="builtin-tools-description"
|
||||||
|
class="agent-builtin-tools__description"
|
||||||
|
>
|
||||||
|
为当前智能体配置工作区能力
|
||||||
|
</div>
|
||||||
|
<div class="agent-builtin-tools__columns" aria-hidden="true">
|
||||||
|
<span>启用</span>
|
||||||
|
<span>调用前确认</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agent-builtin-tools__list">
|
||||||
|
<div
|
||||||
|
v-for="item in builtinToolOptions"
|
||||||
|
:key="item.key"
|
||||||
|
class="agent-builtin-tools__row"
|
||||||
|
>
|
||||||
|
<div class="agent-builtin-tools__copy">
|
||||||
|
<div class="agent-builtin-tools__name">{{ item.label }}</div>
|
||||||
|
<div class="agent-builtin-tools__description">
|
||||||
|
{{ item.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ElSwitch
|
||||||
|
v-model="builtinTool(item.key).enabled"
|
||||||
|
:aria-label="`启用${item.label}`"
|
||||||
|
:before-change="() => confirmBuiltinToolEnabledChange(item.key)"
|
||||||
|
@change="handleBuiltinToolEnabledChange(item.key)"
|
||||||
|
/>
|
||||||
|
<ElTooltip
|
||||||
|
:disabled="item.key !== 'shell' || canDisableShellApproval"
|
||||||
|
content="Shell 调用确认仅平台超级管理员可以关闭"
|
||||||
|
effect="light"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span class="agent-builtin-tools__approval">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="builtinTool(item.key).approvalRequired"
|
||||||
|
:aria-label="`${item.label}调用前确认`"
|
||||||
|
:before-change="
|
||||||
|
item.key === 'shell' ? confirmShellApprovalChange : undefined
|
||||||
|
"
|
||||||
|
:disabled="
|
||||||
|
!builtinTool(item.key).enabled ||
|
||||||
|
(item.key === 'shell' && !canDisableShellApproval)
|
||||||
|
"
|
||||||
|
@change="handleApprovalChange(item.key, $event)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.agent-builtin-tools {
|
||||||
|
--agent-tool-approval-column: 96px;
|
||||||
|
--agent-tool-enabled-column: 48px;
|
||||||
|
|
||||||
|
padding: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__head {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
minmax(0, 1fr)
|
||||||
|
calc(var(--agent-tool-enabled-column) + var(--agent-tool-approval-column));
|
||||||
|
gap: var(--space-3);
|
||||||
|
align-items: end;
|
||||||
|
padding-bottom: var(--space-2);
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__description,
|
||||||
|
.agent-builtin-tools__columns {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--agent-tool-enabled-column) var(
|
||||||
|
--agent-tool-approval-column
|
||||||
|
);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
minmax(0, 1fr) var(--agent-tool-enabled-column)
|
||||||
|
var(--agent-tool-approval-column);
|
||||||
|
gap: var(--space-3);
|
||||||
|
align-items: center;
|
||||||
|
min-height: 56px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__copy {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__copy .agent-builtin-tools__description {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__row > :deep(.el-switch),
|
||||||
|
.agent-builtin-tools__approval {
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__approval {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.agent-builtin-tools {
|
||||||
|
--agent-tool-approval-column: 72px;
|
||||||
|
|
||||||
|
padding: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__head,
|
||||||
|
.agent-builtin-tools__row {
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__row {
|
||||||
|
min-height: calc(var(--space-8) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-builtin-tools__copy .agent-builtin-tools__description {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -44,11 +44,13 @@ const emit = defineEmits<{
|
|||||||
selectIssue: [nodeId: string];
|
selectIssue: [nodeId: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const loadAgentBuiltinToolsForm = () => import('./AgentBuiltinToolsForm.vue');
|
||||||
const loadAgentInteractionForm = () => import('./AgentInteractionForm.vue');
|
const loadAgentInteractionForm = () => import('./AgentInteractionForm.vue');
|
||||||
const loadAgentKnowledgeForm = () => import('./AgentKnowledgeForm.vue');
|
const loadAgentKnowledgeForm = () => import('./AgentKnowledgeForm.vue');
|
||||||
const loadAgentSkillInspector = () => import('./AgentSkillInspector.vue');
|
const loadAgentSkillInspector = () => import('./AgentSkillInspector.vue');
|
||||||
const loadAgentToolForm = () => import('./AgentToolForm.vue');
|
const loadAgentToolForm = () => import('./AgentToolForm.vue');
|
||||||
const loadAgentTryoutPanel = () => import('./AgentTryoutPanel.vue');
|
const loadAgentTryoutPanel = () => import('./AgentTryoutPanel.vue');
|
||||||
|
const AgentBuiltinToolsForm = defineAsyncComponent(loadAgentBuiltinToolsForm);
|
||||||
const AgentInteractionForm = defineAsyncComponent(loadAgentInteractionForm);
|
const AgentInteractionForm = defineAsyncComponent(loadAgentInteractionForm);
|
||||||
const AgentKnowledgeForm = defineAsyncComponent(loadAgentKnowledgeForm);
|
const AgentKnowledgeForm = defineAsyncComponent(loadAgentKnowledgeForm);
|
||||||
const AgentSkillInspector = defineAsyncComponent(loadAgentSkillInspector);
|
const AgentSkillInspector = defineAsyncComponent(loadAgentSkillInspector);
|
||||||
@@ -65,8 +67,9 @@ const selectedKnowledge = computed(() => {
|
|||||||
return props.state.knowledgeBindings.find((item) => item.localId === localId);
|
return props.state.knowledgeBindings.find((item) => item.localId === localId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeBaseTab = ref<'basic' | 'interaction'>('basic');
|
const activeBaseTab = ref<'basic' | 'builtinTools' | 'interaction'>('basic');
|
||||||
const interactionForm = ref<AgentInteractionFormExpose>();
|
const interactionForm = ref<AgentInteractionFormExpose>();
|
||||||
|
const tryoutExpanded = ref(false);
|
||||||
const selectedModel = computed(() =>
|
const selectedModel = computed(() =>
|
||||||
props.models.find((item) => item.value === String(props.state.agent.modelId)),
|
props.models.find((item) => item.value === String(props.state.agent.modelId)),
|
||||||
);
|
);
|
||||||
@@ -96,6 +99,19 @@ function handleMoveSkill(skillId: number | string | undefined, offset: -1 | 1) {
|
|||||||
emit('moveSkill', skillId, offset);
|
emit('moveSkill', skillId, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCloseTryout() {
|
||||||
|
tryoutExpanded.value = false;
|
||||||
|
emit('closeTryout');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key !== 'Escape' || !tryoutExpanded.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
tryoutExpanded.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.issues,
|
() => props.issues,
|
||||||
(issues) => {
|
(issues) => {
|
||||||
@@ -105,6 +121,16 @@ watch(
|
|||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.state.panelMode,
|
||||||
|
(panelMode) => {
|
||||||
|
if (panelMode !== 'tryout') {
|
||||||
|
tryoutExpanded.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
);
|
||||||
|
|
||||||
const selectedTool = computed(() => {
|
const selectedTool = computed(() => {
|
||||||
if (!props.state.selectedNodeId.startsWith('tool:')) return;
|
if (!props.state.selectedNodeId.startsWith('tool:')) return;
|
||||||
const localId = props.state.selectedNodeId.slice('tool:'.length);
|
const localId = props.state.selectedNodeId.slice('tool:'.length);
|
||||||
@@ -141,8 +167,10 @@ let prefetchHandle: number | undefined;
|
|||||||
let prefetchTimer: number | undefined;
|
let prefetchTimer: number | undefined;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
window.addEventListener('keydown', handleWindowKeydown);
|
||||||
const prefetchHiddenPanels = () => {
|
const prefetchHiddenPanels = () => {
|
||||||
void Promise.allSettled([
|
void Promise.allSettled([
|
||||||
|
loadAgentBuiltinToolsForm(),
|
||||||
loadAgentInteractionForm(),
|
loadAgentInteractionForm(),
|
||||||
loadAgentKnowledgeForm(),
|
loadAgentKnowledgeForm(),
|
||||||
loadAgentSkillInspector(),
|
loadAgentSkillInspector(),
|
||||||
@@ -160,6 +188,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', handleWindowKeydown);
|
||||||
if (prefetchHandle !== undefined) {
|
if (prefetchHandle !== undefined) {
|
||||||
window.cancelIdleCallback(prefetchHandle);
|
window.cancelIdleCallback(prefetchHandle);
|
||||||
}
|
}
|
||||||
@@ -170,7 +199,13 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside class="agent-inspector">
|
<aside
|
||||||
|
class="agent-inspector"
|
||||||
|
:class="{
|
||||||
|
'is-tryout': state.panelMode === 'tryout',
|
||||||
|
'is-tryout-expanded': state.panelMode === 'tryout' && tryoutExpanded,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<template v-if="state.panelMode === 'tryout'">
|
<template v-if="state.panelMode === 'tryout'">
|
||||||
<AgentTryoutPanel
|
<AgentTryoutPanel
|
||||||
:agent="state.agent"
|
:agent="state.agent"
|
||||||
@@ -178,7 +213,9 @@ onBeforeUnmount(() => {
|
|||||||
:skill-bindings="state.skillBindings"
|
:skill-bindings="state.skillBindings"
|
||||||
:tool-bindings="state.toolBindings"
|
:tool-bindings="state.toolBindings"
|
||||||
:knowledge-bindings="state.knowledgeBindings"
|
:knowledge-bindings="state.knowledgeBindings"
|
||||||
@close="emit('closeTryout')"
|
:expanded="tryoutExpanded"
|
||||||
|
@close="handleCloseTryout"
|
||||||
|
@update:expanded="tryoutExpanded = $event"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -221,7 +258,6 @@ onBeforeUnmount(() => {
|
|||||||
<ElTabPane label="基础设置" name="basic">
|
<ElTabPane label="基础设置" name="basic">
|
||||||
<AgentBaseForm
|
<AgentBaseForm
|
||||||
:agent="state.agent"
|
:agent="state.agent"
|
||||||
:can-disable-shell-approval="canDisableShellApproval"
|
|
||||||
:categories="categories"
|
:categories="categories"
|
||||||
:models="models"
|
:models="models"
|
||||||
:tool-bindings="state.toolBindings"
|
:tool-bindings="state.toolBindings"
|
||||||
@@ -235,6 +271,13 @@ onBeforeUnmount(() => {
|
|||||||
@change="emit('change')"
|
@change="emit('change')"
|
||||||
/>
|
/>
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
|
<ElTabPane label="内置工具" name="builtinTools">
|
||||||
|
<AgentBuiltinToolsForm
|
||||||
|
:agent="state.agent"
|
||||||
|
:can-disable-shell-approval="canDisableShellApproval"
|
||||||
|
@change="emit('change')"
|
||||||
|
/>
|
||||||
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
</template>
|
</template>
|
||||||
<AgentKnowledgeForm
|
<AgentKnowledgeForm
|
||||||
@@ -267,14 +310,16 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.agent-inspector {
|
.agent-inspector {
|
||||||
|
--agent-inspector-expanded-width: 960px;
|
||||||
|
--agent-inspector-width: min(420px, calc(100vw - 320px));
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--space-6);
|
inset: var(--space-6) var(--space-6) 96px
|
||||||
right: var(--space-6);
|
calc(100% - var(--space-6) - var(--agent-inspector-width));
|
||||||
bottom: 96px;
|
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: min(420px, calc(100vw - 320px));
|
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
@@ -285,6 +330,27 @@ onBeforeUnmount(() => {
|
|||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-inspector.is-tryout {
|
||||||
|
transition:
|
||||||
|
top var(--motion-duration-medium) var(--motion-ease-standard),
|
||||||
|
right var(--motion-duration-medium) var(--motion-ease-standard),
|
||||||
|
bottom var(--motion-duration-medium) var(--motion-ease-standard),
|
||||||
|
left var(--motion-duration-medium) var(--motion-ease-standard),
|
||||||
|
border-radius var(--motion-duration-medium) var(--motion-ease-standard),
|
||||||
|
box-shadow var(--motion-duration-medium) var(--motion-ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-inspector.is-tryout-expanded {
|
||||||
|
inset: var(--space-6)
|
||||||
|
max(
|
||||||
|
var(--space-6),
|
||||||
|
calc((100% - var(--agent-inspector-expanded-width)) / 2)
|
||||||
|
);
|
||||||
|
z-index: 40;
|
||||||
|
border-radius: var(--radius-panel);
|
||||||
|
box-shadow: var(--shadow-float);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-inspector__header {
|
.agent-inspector__header {
|
||||||
padding: var(--space-4);
|
padding: var(--space-4);
|
||||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
@@ -346,17 +412,23 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.agent-inspector {
|
.agent-inspector {
|
||||||
top: calc(var(--space-8) + var(--space-8) + var(--space-2));
|
inset: calc(var(--space-8) + var(--space-8) + var(--space-2)) var(--space-4)
|
||||||
right: var(--space-4);
|
96px;
|
||||||
left: var(--space-4);
|
}
|
||||||
width: auto;
|
|
||||||
|
.agent-inspector.is-tryout-expanded {
|
||||||
|
inset: var(--space-4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.agent-inspector {
|
.agent-inspector {
|
||||||
right: var(--space-2);
|
inset: calc(var(--space-8) + var(--space-8) + var(--space-2)) var(--space-2)
|
||||||
left: var(--space-2);
|
96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-inspector.is-tryout-expanded {
|
||||||
|
inset: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-inspector__header {
|
.agent-inspector__header {
|
||||||
@@ -367,4 +439,10 @@ onBeforeUnmount(() => {
|
|||||||
padding: var(--space-3) var(--space-3) 0;
|
padding: var(--space-3) var(--space-3) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.agent-inspector.is-tryout {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
import { BrushCleaning } from '@easyflow/icons';
|
import { BrushCleaning } from '@easyflow/icons';
|
||||||
import { copyTextToClipboard } from '@easyflow/utils';
|
import { copyTextToClipboard } from '@easyflow/utils';
|
||||||
|
|
||||||
|
import { FullScreen } from '@element-plus/icons-vue';
|
||||||
import { ElButton, ElMessage } from 'element-plus';
|
import { ElButton, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import AiChatPanel from '#/components/ai-chat/AiChatPanel.vue';
|
import AiChatPanel from '#/components/ai-chat/AiChatPanel.vue';
|
||||||
@@ -41,13 +42,17 @@ import AgentWelcomeState from './AgentWelcomeState.vue';
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
agent: AgentInfo;
|
agent: AgentInfo;
|
||||||
|
expanded?: boolean;
|
||||||
imageEnabled?: boolean;
|
imageEnabled?: boolean;
|
||||||
knowledgeBindings: AgentKnowledgeBinding[];
|
knowledgeBindings: AgentKnowledgeBinding[];
|
||||||
skillBindings: AgentSkillBinding[];
|
skillBindings: AgentSkillBinding[];
|
||||||
toolBindings: AgentToolBinding[];
|
toolBindings: AgentToolBinding[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{ close: [] }>();
|
const emit = defineEmits<{
|
||||||
|
close: [];
|
||||||
|
'update:expanded': [value: boolean];
|
||||||
|
}>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
@@ -63,6 +68,7 @@ const {
|
|||||||
stop,
|
stop,
|
||||||
} = useAgentTryoutStream();
|
} = useAgentTryoutStream();
|
||||||
const approvalLoading = ref(false);
|
const approvalLoading = ref(false);
|
||||||
|
const clearing = ref(false);
|
||||||
const composer = useAgentComposerDraft('DRAFT');
|
const composer = useAgentComposerDraft('DRAFT');
|
||||||
const loadDraftAgentArtifact = createAgentArtifactLoader(() => ({
|
const loadDraftAgentArtifact = createAgentArtifactLoader(() => ({
|
||||||
agentId: String(props.agent.id || ''),
|
agentId: String(props.agent.id || ''),
|
||||||
@@ -225,6 +231,10 @@ function handleSelectNextVariant(item: ChatTimelineMessageItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleClearSession() {
|
async function handleClearSession() {
|
||||||
|
if (loading.value || approvalLoading.value || clearing.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearing.value = true;
|
||||||
try {
|
try {
|
||||||
await clearDraftSession();
|
await clearDraftSession();
|
||||||
await composer.clear();
|
await composer.clear();
|
||||||
@@ -232,6 +242,8 @@ async function handleClearSession() {
|
|||||||
ElMessage.success('已清理会话');
|
ElMessage.success('已清理会话');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(error instanceof Error ? error.message : '清理会话失败');
|
ElMessage.error(error instanceof Error ? error.message : '清理会话失败');
|
||||||
|
} finally {
|
||||||
|
clearing.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,13 +408,23 @@ async function handleReject(payload: ChatTimelineToolApprovalPayload) {
|
|||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
>
|
>
|
||||||
<template #headerActions>
|
<template #headerActions>
|
||||||
|
<ElButton
|
||||||
|
:icon="FullScreen"
|
||||||
|
circle
|
||||||
|
text
|
||||||
|
:aria-label="expanded ? '收起试运行' : '展开试运行'"
|
||||||
|
:aria-pressed="Boolean(expanded)"
|
||||||
|
:title="expanded ? '收起试运行' : '展开试运行'"
|
||||||
|
@click="emit('update:expanded', !expanded)"
|
||||||
|
/>
|
||||||
<ElButton
|
<ElButton
|
||||||
:icon="BrushCleaning"
|
:icon="BrushCleaning"
|
||||||
circle
|
circle
|
||||||
text
|
text
|
||||||
:disabled="approvalLoading"
|
:disabled="loading || approvalLoading || clearing"
|
||||||
|
:loading="clearing"
|
||||||
aria-label="清理会话"
|
aria-label="清理会话"
|
||||||
title="清理会话"
|
:title="loading ? '输出结束后可清理会话' : '清理会话'"
|
||||||
@click="handleClearSession"
|
@click="handleClearSession"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user