|
|
@@ -15,9 +15,22 @@ interface Draft {name:string;avatar:string;instructions:string;description?:stri
|
|
|
type Page='overview'|'identity'|'rules'|'usage'|'modelSettings'|'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 emit=defineEmits<{close:[];save:[draft:Draft & {approvalPolicy:WorkspaceApprovalPolicy}];changed:[]}>()
|
|
|
-const draft=reactive({...props.draft,approvalPolicy:props.draft.approvalPolicy??'ask'})
|
|
|
+const draft=reactive({...props.draft,voice:props.draft.voice??'',voiceCustom:props.draft.voiceCustom??'',actBias:props.draft.actBias??'',approvalPolicy:props.draft.approvalPolicy??'ask'})
|
|
|
const page=ref<Page>('overview'),query=ref(''),localError=ref(''),modelDirty=ref(false),modelSaving=ref(false),memoryDirty=ref(false)
|
|
|
const usage=ref<WorkspaceAgentUsage|null>(null),usageLoading=ref(false),usageError=ref('')
|
|
|
+const voiceOptions:ReadonlyArray<{value:NonNullable<Draft['voice']>;label:string;description:string}>=[
|
|
|
+ {value:'',label:'未指定',description:'由模型根据对话自行判断。'},
|
|
|
+ {value:'concise',label:'简洁专业',description:'直接、克制,重点清晰。'},
|
|
|
+ {value:'casual',label:'轻松随和',description:'自然亲切,减少正式感。'},
|
|
|
+ {value:'rigorous',label:'严谨细致',description:'完整说明依据与边界。'},
|
|
|
+ {value:'custom',label:'自定义',description:'使用下方描述的专属语气。'},
|
|
|
+]
|
|
|
+const actionOptions:ReadonlyArray<{value:NonNullable<Draft['actBias']>;label:string;description:string}>=[
|
|
|
+ {value:'',label:'未指定',description:'由模型自行判断。'},
|
|
|
+ {value:'ask_first',label:'先问再做',description:'任务不明确先澄清,确认后执行。'},
|
|
|
+ {value:'ask_key_then_act',label:'问关键问题即行动',description:'最多问 2–3 个关键问题,职责清楚就开工。'},
|
|
|
+ {value:'act_now',label:'直接行动',description:'合理默认即开工,重要分叉才询问。'},
|
|
|
+]
|
|
|
const formatTokens=(value:number)=>value.toLocaleString('zh-CN')
|
|
|
onMounted(async()=>{
|
|
|
usageLoading.value=true
|
|
|
@@ -101,13 +114,16 @@ function submit(){
|
|
|
<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>
|
|
|
+ <fieldset class="persona-field persona-choice-group"><legend>语气</legend>
|
|
|
+ <div class="persona-options">
|
|
|
+ <label v-for="option in voiceOptions" :key="option.value" class="radio-row" :class="{selected:draft.voice===option.value}"><input v-model="draft.voice" type="radio" :value="option.value" :disabled="busy||readOnly"/><span><strong>{{option.label}}</strong><small>{{option.description}}</small></span></label>
|
|
|
+ </div>
|
|
|
+ </fieldset>
|
|
|
<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 class="persona-field persona-choice-group"><legend>行动偏好</legend>
|
|
|
+ <div class="persona-options">
|
|
|
+ <label v-for="option in actionOptions" :key="option.value" class="radio-row" :class="{selected:draft.actBias===option.value}"><input v-model="draft.actBias" type="radio" :value="option.value" :disabled="busy||readOnly"/><span><strong>{{option.label}}</strong><small>{{option.description}}</small></span></label>
|
|
|
+ </div>
|
|
|
</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>
|
|
|
@@ -169,8 +185,18 @@ function submit(){
|
|
|
.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-choice-group{gap:10px;padding:12px;background:color-mix(in srgb,var(--surface-soft) 55%,transparent)}
|
|
|
+.persona-options{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
|
|
|
+.radio-row{display:flex;align-items:flex-start;gap:10px;min-width:0;min-height:66px;padding:11px 12px;box-sizing:border-box;border:1px solid var(--line);border-radius:9px;background:var(--surface);cursor:pointer;transition:border-color 140ms ease,background-color 140ms ease,box-shadow 140ms ease}
|
|
|
+.radio-row:hover:not(:has(input:disabled)){border-color:var(--line-strong);background:var(--surface-raised)}
|
|
|
+.radio-row.selected{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--surface));box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 16%,transparent)}
|
|
|
+.radio-row:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}.radio-row input:focus-visible{outline:none}
|
|
|
+.radio-row:has(input:disabled){cursor:default;opacity:.55}
|
|
|
+.radio-row input{width:16px;height:16px;min-height:16px;margin:2px 0 0;padding:0;accent-color:var(--accent);flex:none}
|
|
|
+.radio-row span{display:grid;gap:4px;min-width:0}
|
|
|
+.radio-row strong{font-size:13px;line-height:1.35;font-weight:600;color:var(--text-primary)}
|
|
|
+.radio-row small{font-size:11.5px;line-height:1.5;color:var(--text-muted)}
|
|
|
.persona-advanced summary{cursor:pointer;font-size:13px;color:var(--text-secondary);margin-bottom:8px}
|
|
|
+@media(max-width:600px){.persona-options{grid-template-columns:1fr}.radio-row{min-height:60px}}
|
|
|
+@media(prefers-reduced-motion:reduce){.radio-row{transition:none}}
|
|
|
</style>
|