|
@@ -10,7 +10,7 @@ import {apiRequest} from '@/api/client'
|
|
|
import type {WorkspaceAgent, WorkspaceAgentUsage, WorkspaceApprovalPolicy, WorkspaceConversation, WorkspaceSource} from '@shared/workspace'
|
|
import type {WorkspaceAgent, WorkspaceAgentUsage, WorkspaceApprovalPolicy, WorkspaceConversation, WorkspaceSource} from '@shared/workspace'
|
|
|
import type {Profile} from '@shared/types'
|
|
import type {Profile} from '@shared/types'
|
|
|
|
|
|
|
|
-interface Draft {name:string;avatar:string;instructions:string;source:string;canManageTeam:boolean;canCollaborate:boolean;memoryEnabled:boolean;approvalPolicy?:WorkspaceApprovalPolicy}
|
|
|
|
|
|
|
+interface Draft {name:string;avatar:string;instructions:string;job?:string;antiJobs?:string[];voice?:''|'concise'|'casual'|'rigorous'|'custom';voiceCustom?:string;actBias?:''|'ask_first'|'ask_key_then_act'|'act_now';source:string;canManageTeam:boolean;canCollaborate:boolean;memoryEnabled:boolean;approvalPolicy?:WorkspaceApprovalPolicy}
|
|
|
type Page='overview'|'identity'|'rules'|'usage'|'models'|'memory'|'permissions'
|
|
type Page='overview'|'identity'|'rules'|'usage'|'models'|'memory'|'permissions'
|
|
|
const props=defineProps<{agent:WorkspaceAgent;draft:Draft;sources:WorkspaceSource[];profiles:Profile[];agents:WorkspaceAgent[];conversations:WorkspaceConversation[];conversationId?:string;isAdmin?:boolean;knowledgeEnabled?:boolean;busy?:boolean;error?:string}>()
|
|
const props=defineProps<{agent:WorkspaceAgent;draft:Draft;sources:WorkspaceSource[];profiles:Profile[];agents:WorkspaceAgent[];conversations:WorkspaceConversation[];conversationId?:string;isAdmin?:boolean;knowledgeEnabled?:boolean;busy?:boolean;error?:string}>()
|
|
|
const emit=defineEmits<{close:[];save:[draft:Draft & {approvalPolicy:WorkspaceApprovalPolicy}];changed:[]}>()
|
|
const emit=defineEmits<{close:[];save:[draft:Draft & {approvalPolicy:WorkspaceApprovalPolicy}];changed:[]}>()
|
|
@@ -25,7 +25,14 @@ onMounted(async()=>{
|
|
|
finally{usageLoading.value=false}
|
|
finally{usageLoading.value=false}
|
|
|
})
|
|
})
|
|
|
const readOnly=computed(()=>!!props.agent.remoteAgentId)
|
|
const readOnly=computed(()=>!!props.agent.remoteAgentId)
|
|
|
-const snapshot=()=>({name:draft.name,avatar:draft.avatar,instructions:draft.instructions,source:draft.source,canManageTeam:draft.canManageTeam,canCollaborate:draft.canCollaborate,memoryEnabled:draft.memoryEnabled,approvalPolicy:draft.approvalPolicy})
|
|
|
|
|
|
|
+const antiDraft=ref('')
|
|
|
|
|
+function addAntiJob(){
|
|
|
|
|
+ const value=antiDraft.value.trim()
|
|
|
|
|
+ if(value&&!(draft.antiJobs??[]).includes(value)&&(draft.antiJobs??[]).length<8)draft.antiJobs=[...(draft.antiJobs??[]),value]
|
|
|
|
|
+ antiDraft.value=''
|
|
|
|
|
+}
|
|
|
|
|
+function removeAntiJob(value:string){draft.antiJobs=(draft.antiJobs??[]).filter(item=>item!==value)}
|
|
|
|
|
+const snapshot=()=>({name:draft.name,avatar:draft.avatar,instructions:draft.instructions,job:draft.job,antiJobs:draft.antiJobs,voice:draft.voice,voiceCustom:draft.voiceCustom,actBias:draft.actBias,source:draft.source,canManageTeam:draft.canManageTeam,canCollaborate:draft.canCollaborate,memoryEnabled:draft.memoryEnabled,approvalPolicy:draft.approvalPolicy})
|
|
|
const baseline=JSON.stringify(snapshot())
|
|
const baseline=JSON.stringify(snapshot())
|
|
|
const dirty=computed(()=>JSON.stringify(snapshot())!==baseline)
|
|
const dirty=computed(()=>JSON.stringify(snapshot())!==baseline)
|
|
|
const sourceKey=(source:Pick<WorkspaceSource,'nodeId'|'profile'>)=>JSON.stringify([source.nodeId,source.profile])
|
|
const sourceKey=(source:Pick<WorkspaceSource,'nodeId'|'profile'>)=>JSON.stringify([source.nodeId,source.profile])
|
|
@@ -85,7 +92,22 @@ function submit(){
|
|
|
<section class="profile-card"><h4>运行与协作</h4><dl><div><dt>基础机器人</dt><dd>{{sourceLabel}} / {{sourceProfile}}</dd></div><div><dt>执行环境</dt><dd>{{environment}}</dd></div><div><dt>Token 用量</dt><dd>{{usageLoading?'读取中…':usage?`今日 ${formatTokens(usage.todayUsage.total)} · 本月 ${formatTokens(usage.monthUsage.total)}`:'暂无统计'}}</dd></div><div><dt>Bot 协作</dt><dd>{{draft.canCollaborate?'已允许':'未开启'}}</dd></div><div><dt>组建团队</dt><dd>{{draft.canManageTeam?'已允许':'未开启'}}</dd></div></dl><button type="button" @click="selectPage('usage')">查看 Token 用量</button></section>
|
|
<section class="profile-card"><h4>运行与协作</h4><dl><div><dt>基础机器人</dt><dd>{{sourceLabel}} / {{sourceProfile}}</dd></div><div><dt>执行环境</dt><dd>{{environment}}</dd></div><div><dt>Token 用量</dt><dd>{{usageLoading?'读取中…':usage?`今日 ${formatTokens(usage.todayUsage.total)} · 本月 ${formatTokens(usage.monthUsage.total)}`:'暂无统计'}}</dd></div><div><dt>Bot 协作</dt><dd>{{draft.canCollaborate?'已允许':'未开启'}}</dd></div><div><dt>组建团队</dt><dd>{{draft.canManageTeam?'已允许':'未开启'}}</dd></div></dl><button type="button" @click="selectPage('usage')">查看 Token 用量</button></section>
|
|
|
</template>
|
|
</template>
|
|
|
<form v-else-if="page==='identity'" class="profile-form" @submit.prevent="submit"><label>名称<input v-model="draft.name" required maxlength="100" :readonly="readOnly" :disabled="busy"/></label><AgentIdentityPanel v-if="!readOnly" :profile="avatarProfile" embedded :show-name="false" :show-default-model="false" :show-actions="false" :busy="busy" @avatar-change="draft.avatar=$event"/><AgentAvatar v-else :name="draft.name" :avatar="draft.avatar" :size="96"/></form>
|
|
<form v-else-if="page==='identity'" class="profile-form" @submit.prevent="submit"><label>名称<input v-model="draft.name" required maxlength="100" :readonly="readOnly" :disabled="busy"/></label><AgentIdentityPanel v-if="!readOnly" :profile="avatarProfile" embedded :show-name="false" :show-default-model="false" :show-actions="false" :busy="busy" @avatar-change="draft.avatar=$event"/><AgentAvatar v-else :name="draft.name" :avatar="draft.avatar" :size="96"/></form>
|
|
|
- <form v-else-if="page==='rules'" class="profile-form" @submit.prevent="submit"><label>角色提示词与规则<textarea v-model="draft.instructions" rows="12" maxlength="24000" :readonly="readOnly" :disabled="busy" placeholder="描述这个 Bot 的职责、回答方式和工作规则。"/></label><p class="profile-note">这些规则属于当前 Bot,会在它的单聊和群聊中使用。</p></form>
|
|
|
|
|
|
|
+ <form v-else-if="page==='rules'" class="profile-form" @submit.prevent="submit">
|
|
|
|
|
+ <label>一句话职责<input v-model="draft.job" maxlength="120" :readonly="readOnly" :disabled="busy" placeholder="这个 Bot 只做的一件事,例如:盯家庭邮箱和日程,表单、账单、RSVP 不漏"/></label>
|
|
|
|
|
+ <fieldset class="persona-field"><legend>反任务(明确不做的事)</legend>
|
|
|
|
|
+ <ul v-if="draft.antiJobs?.length" class="anti-list"><li v-for="item in draft.antiJobs" :key="item"><span>{{ item }}</span><button type="button" :disabled="busy||readOnly" @click="removeAntiJob(item)" :aria-label="`移除 ${item}`"><AppIcon name="close" :size="12"/></button></li></ul>
|
|
|
|
|
+ <div class="anti-input"><input v-model="antiDraft" maxlength="120" :readonly="readOnly" :disabled="busy||(draft.antiJobs?.length??0)>=8" placeholder="例如:绝不直接发送邮件或提交表单" @keydown.enter.prevent="addAntiJob"/><button type="button" :disabled="busy||readOnly||!antiDraft.trim()" @click="addAntiJob">添加</button></div>
|
|
|
|
|
+ </fieldset>
|
|
|
|
|
+ <label>语气<select v-model="draft.voice" :disabled="busy||readOnly"><option value="">未指定</option><option value="concise">简洁专业</option><option value="casual">轻松随和</option><option value="rigorous">严谨细致</option><option value="custom">自定义</option></select></label>
|
|
|
|
|
+ <label v-if="draft.voice==='custom'">自定义语气<textarea v-model="draft.voiceCustom" rows="2" maxlength="200" :readonly="readOnly" :disabled="busy" placeholder="用一句话描述语气,例如:随意一点,像疯狂科学家,多用短句。"/></label>
|
|
|
|
|
+ <fieldset class="persona-field"><legend>行动偏好</legend>
|
|
|
|
|
+ <label class="radio-row"><input v-model="draft.actBias" type="radio" value="" :disabled="busy||readOnly"/><span>未指定<small>由模型自行判断。</small></span></label>
|
|
|
|
|
+ <label class="radio-row"><input v-model="draft.actBias" type="radio" value="ask_first" :disabled="busy||readOnly"/><span>先问再做<small>任务不明确先澄清,确认后执行。</small></span></label>
|
|
|
|
|
+ <label class="radio-row"><input v-model="draft.actBias" type="radio" value="ask_key_then_act" :disabled="busy||readOnly"/><span>问关键问题即行动<small>最多问 2–3 个关键问题,职责清楚就开工。</small></span></label>
|
|
|
|
|
+ <label class="radio-row"><input v-model="draft.actBias" type="radio" value="act_now" :disabled="busy||readOnly"/><span>直接行动<small>合理默认即开工,重要分叉才询问。</small></span></label>
|
|
|
|
|
+ </fieldset>
|
|
|
|
|
+ <details class="persona-advanced"><summary>高级:完整提示词</summary><label>角色提示词与规则<textarea v-model="draft.instructions" rows="10" maxlength="24000" :readonly="readOnly" :disabled="busy" placeholder="描述这个 Bot 的职责、回答方式和工作规则。"/></label><p class="profile-note">结构化规范会自动插入在完整提示词之前;这些规则属于当前 Bot,会在它的单聊和群聊中使用。</p></details>
|
|
|
|
|
+ </form>
|
|
|
<template v-else-if="page==='usage'">
|
|
<template v-else-if="page==='usage'">
|
|
|
<p v-if="usageError" class="profile-note">{{usageError}}</p>
|
|
<p v-if="usageError" class="profile-note">{{usageError}}</p>
|
|
|
<section v-if="usage" class="profile-card">
|
|
<section v-if="usage" class="profile-card">
|
|
@@ -135,4 +157,15 @@ function submit(){
|
|
|
.bot-profile__sidebar{min-width:0}@media(max-width:600px){.bot-profile{grid-template-columns:minmax(0,1fr)}}
|
|
.bot-profile__sidebar{min-width:0}@media(max-width:600px){.bot-profile{grid-template-columns:minmax(0,1fr)}}
|
|
|
.usage-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}.usage-stat{display:grid;gap:6px;padding:14px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.usage-stat small{color:var(--text-muted);font-size:11px}.usage-stat strong{font-size:20px;font-weight:650;font-variant-numeric:tabular-nums}.usage-stat span{color:var(--text-secondary);font-size:11px}.usage-table{width:100%;border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums}.usage-table th{color:var(--text-muted);font-weight:500;text-align:right;padding:6px 4px;border-bottom:1px solid var(--line)}.usage-table th:first-child,.usage-table td:first-child{text-align:left}.usage-table td{padding:7px 4px;border-bottom:1px solid var(--line);text-align:right}.usage-table td:first-child{color:var(--text-secondary)}
|
|
.usage-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}.usage-stat{display:grid;gap:6px;padding:14px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.usage-stat small{color:var(--text-muted);font-size:11px}.usage-stat strong{font-size:20px;font-weight:650;font-variant-numeric:tabular-nums}.usage-stat span{color:var(--text-secondary);font-size:11px}.usage-table{width:100%;border-collapse:collapse;font-size:12px;font-variant-numeric:tabular-nums}.usage-table th{color:var(--text-muted);font-weight:500;text-align:right;padding:6px 4px;border-bottom:1px solid var(--line)}.usage-table th:first-child,.usage-table td:first-child{text-align:left}.usage-table td{padding:7px 4px;border-bottom:1px solid var(--line);text-align:right}.usage-table td:first-child{color:var(--text-secondary)}
|
|
|
.profile-source .profile-source__picker{position:relative;min-height:48px;padding:6px 8px;margin:0;border:1px solid var(--line);border-radius:8px;background:var(--settings-panel);box-sizing:border-box}.profile-source__identity{flex:1}.profile-source__picker>select{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer}.profile-source__picker:focus-within{outline:2px solid var(--accent);outline-offset:2px}@media(max-width:600px){.profile-source{display:block}.profile-source .profile-source__picker{max-width:100%}}
|
|
.profile-source .profile-source__picker{position:relative;min-height:48px;padding:6px 8px;margin:0;border:1px solid var(--line);border-radius:8px;background:var(--settings-panel);box-sizing:border-box}.profile-source__identity{flex:1}.profile-source__picker>select{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer}.profile-source__picker:focus-within{outline:2px solid var(--accent);outline-offset:2px}@media(max-width:600px){.profile-source{display:block}.profile-source .profile-source__picker{max-width:100%}}
|
|
|
|
|
+.persona-field{display:grid;gap:8px;border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:0}
|
|
|
|
|
+.persona-field legend{font-size:12.5px;color:var(--text-secondary);padding:0 4px}
|
|
|
|
|
+.anti-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
|
|
|
|
|
+.anti-list li{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:13px;padding:6px 8px;border:1px solid var(--line);border-radius:8px;background:var(--surface-soft)}
|
|
|
|
|
+.anti-list li button{border:0;background:transparent;min-height:24px;padding:2px;cursor:pointer;color:var(--text-muted)}
|
|
|
|
|
+.anti-input{display:flex;gap:8px}
|
|
|
|
|
+.anti-input input{flex:1}
|
|
|
|
|
+.radio-row{display:flex;align-items:flex-start;gap:8px}
|
|
|
|
|
+.radio-row input{width:16px;height:16px;margin-top:3px;accent-color:var(--accent);flex:none}
|
|
|
|
|
+.radio-row span{display:grid;gap:2px}
|
|
|
|
|
+.persona-advanced summary{cursor:pointer;font-size:13px;color:var(--text-secondary);margin-bottom:8px}
|
|
|
</style>
|
|
</style>
|