mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(agents): open AGENTS.md editor in a modal, as a settings row, with section reordering
Move the AGENTS.md section editing into a click-to-open modal so the Basic tab no longer expands the full document inline. Present the entry as a settings-style row (title + description on the left, a "manage" button on the right) instead of stacked label/hint/button. Add optional move-up/move-down controls to MemorySection (off by default, so MemoryBrowser is unaffected) and wire reordering in AgentGuideEditor: adjacent sections swap and the whole file is re-saved, with a synthetic preamble pinned to the top.
This commit is contained in:
parent
4f11a444c1
commit
da5aaba0f7
@ -1238,6 +1238,7 @@ export default {
|
||||
},
|
||||
guide: {
|
||||
summary: 'Behavior Guide (AGENTS.md)',
|
||||
open: 'Manage Behavior Guide',
|
||||
desc: "This is the employee's behavior handbook (AGENTS.md): when to write memories, what goes in each file, and which memory tools are available. The Role/Goal/Backstory above are the employee's factory identity; evolving memories like PROFILE/MEMORY live in the Memory menu.",
|
||||
empty: 'This employee has no behavior guide (AGENTS.md) yet.',
|
||||
create: 'Create Behavior Guide',
|
||||
@ -3780,6 +3781,8 @@ export default {
|
||||
editPlaceholder: 'Edit memory content...',
|
||||
confirmed: 'Confirmed',
|
||||
saved: 'Saved',
|
||||
moveUp: 'Move up',
|
||||
moveDown: 'Move down',
|
||||
},
|
||||
facts: {
|
||||
searchPlaceholder: 'Search facts…',
|
||||
|
||||
@ -1130,6 +1130,7 @@ export default {
|
||||
},
|
||||
guide: {
|
||||
summary: '行为指南(AGENTS.md)',
|
||||
open: '管理行为指南',
|
||||
desc: '这里是该员工的行为手册(AGENTS.md):规定何时写记忆、各文件放什么、可用的记忆工具。上方的岗位/目标/背景是员工的出厂身份;PROFILE/MEMORY 等可演化的记忆在「记忆」菜单管理。',
|
||||
empty: '该员工还没有行为指南(AGENTS.md)。',
|
||||
create: '创建行为指南',
|
||||
@ -3872,6 +3873,8 @@ export default {
|
||||
editPlaceholder: '修改记忆内容...',
|
||||
confirmed: '已确认',
|
||||
saved: '已保存',
|
||||
moveUp: '上移',
|
||||
moveDown: '下移',
|
||||
},
|
||||
facts: {
|
||||
searchPlaceholder: '搜索事实…',
|
||||
|
||||
@ -347,8 +347,10 @@
|
||||
<textarea v-model="profileForm.extra" class="form-textarea" rows="4" :placeholder="t('agents.placeholders.extraInstructions')"></textarea>
|
||||
<p class="form-hint">{{ t('agents.fields.extraInstructionsHint') }}</p>
|
||||
<div v-if="editingAgent" class="advanced-guide">
|
||||
<label class="form-label">{{ t('agents.guide.summary') }}</label>
|
||||
<p class="form-hint">{{ t('agents.guide.desc') }}</p>
|
||||
<div class="advanced-guide__info">
|
||||
<span class="advanced-guide__title">{{ t('agents.guide.summary') }}</span>
|
||||
<p class="advanced-guide__desc">{{ t('agents.guide.desc') }}</p>
|
||||
</div>
|
||||
<AgentGuideEditor :agent-id="editingAgent.id" />
|
||||
</div>
|
||||
</details>
|
||||
@ -1965,7 +1967,14 @@ html.dark .seg-count.warn {
|
||||
.advanced-prompt[open] > .advanced-prompt__summary { padding: 0 0 8px; border-bottom: 1px solid var(--mc-border-light); margin-bottom: 8px; }
|
||||
.advanced-prompt[open] > .advanced-prompt__summary::before { transform: rotate(90deg); }
|
||||
.advanced-prompt .form-textarea { width: 100%; box-sizing: border-box; }
|
||||
.advanced-guide { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--mc-border-light); }
|
||||
.advanced-guide {
|
||||
margin-top: 16px; display: flex; align-items: center; gap: 14px;
|
||||
padding: 12px 14px; border-radius: 10px; background: var(--mc-bg-sunken);
|
||||
border: 1px solid var(--mc-border-light);
|
||||
}
|
||||
.advanced-guide__info { flex: 1; min-width: 0; }
|
||||
.advanced-guide__title { display: block; font-size: 13px; font-weight: 600; color: var(--mc-text-primary); }
|
||||
.advanced-guide__desc { margin: 4px 0 0; font-size: 12px; line-height: 1.5; color: var(--mc-text-tertiary); }
|
||||
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 16px 24px; border-top: 1px solid var(--mc-border-light); }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
@ -1,41 +1,60 @@
|
||||
<template>
|
||||
<div class="guide-editor">
|
||||
<div v-if="loading" class="guide-state">…</div>
|
||||
<button type="button" class="guide-open-btn" @click="open">
|
||||
{{ t('agents.guide.open') }}
|
||||
</button>
|
||||
|
||||
<!-- Empty state: AGENTS.md not present or blank -->
|
||||
<div v-else-if="sections.length === 0" class="guide-empty">
|
||||
<p class="guide-empty__text">{{ t('agents.guide.empty') }}</p>
|
||||
<button class="guide-create-btn" :disabled="saving" @click="createGuide">
|
||||
{{ t('agents.guide.create') }}
|
||||
</button>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<div v-if="visible" class="guide-overlay" @click.self="visible = false">
|
||||
<div class="guide-modal">
|
||||
<div class="guide-modal__header">
|
||||
<h3 class="guide-modal__title">{{ t('agents.guide.summary') }}</h3>
|
||||
<button type="button" class="guide-modal__close" @click="visible = false">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Section cards -->
|
||||
<div v-else class="guide-sections">
|
||||
<MemorySection
|
||||
v-for="(sec, idx) in sections"
|
||||
:key="idx"
|
||||
:section="sec"
|
||||
:save-handler="(body: string) => saveSection(idx, body)"
|
||||
/>
|
||||
<div class="guide-modal__body">
|
||||
<div v-if="loading" class="guide-state">…</div>
|
||||
|
||||
<div class="guide-add">
|
||||
<input
|
||||
v-model="newHeading"
|
||||
class="guide-add__input"
|
||||
:placeholder="t('agents.guide.addSectionPlaceholder')"
|
||||
@keyup.enter="addSection"
|
||||
/>
|
||||
<button class="guide-add__btn" :disabled="!newHeading.trim() || saving" @click="addSection">
|
||||
+ {{ t('agents.guide.addSection') }}
|
||||
</button>
|
||||
<!-- Empty state: AGENTS.md not present or blank -->
|
||||
<div v-else-if="sections.length === 0" class="guide-empty">
|
||||
<p class="guide-empty__text">{{ t('agents.guide.empty') }}</p>
|
||||
<button type="button" class="guide-create-btn" :disabled="saving" @click="createGuide">
|
||||
{{ t('agents.guide.create') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Section cards -->
|
||||
<div v-else class="guide-sections">
|
||||
<MemorySection
|
||||
v-for="(sec, idx) in sections"
|
||||
:key="idx"
|
||||
:section="sec"
|
||||
:save-handler="(body: string) => saveSection(idx, body)"
|
||||
:can-move-up="canMoveUp(idx)"
|
||||
:can-move-down="canMoveDown(idx)"
|
||||
@move-up="move(idx, -1)"
|
||||
@move-down="move(idx, 1)"
|
||||
/>
|
||||
|
||||
<div class="guide-add">
|
||||
<input
|
||||
v-model="newHeading"
|
||||
class="guide-add__input"
|
||||
:placeholder="t('agents.guide.addSectionPlaceholder')"
|
||||
@keyup.enter="addSection"
|
||||
/>
|
||||
<button type="button" class="guide-add__btn" :disabled="!newHeading.trim() || saving" @click="addSection">
|
||||
+ {{ t('agents.guide.addSection') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { agentContextApi } from '@/api/index'
|
||||
import { mcToast } from '@/composables/useMcToast'
|
||||
@ -46,12 +65,16 @@ const props = defineProps<{ agentId: string | number }>()
|
||||
const { t } = useI18n()
|
||||
|
||||
const FILE = 'AGENTS.md'
|
||||
const loading = ref(true)
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const sections = ref<MemorySectionData[]>([])
|
||||
const newHeading = ref('')
|
||||
|
||||
onMounted(load)
|
||||
function open() {
|
||||
visible.value = true
|
||||
load()
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@ -109,6 +132,34 @@ async function createGuide() {
|
||||
}
|
||||
}
|
||||
|
||||
// A synthetic preamble (content before the first `## `) is pinned to the top
|
||||
// and never reorders, so the first movable section is index 1 when present.
|
||||
function firstMovable(): number {
|
||||
return sections.value[0]?.synthetic ? 1 : 0
|
||||
}
|
||||
function canMoveUp(idx: number): boolean {
|
||||
return !sections.value[idx]?.synthetic && idx > firstMovable()
|
||||
}
|
||||
function canMoveDown(idx: number): boolean {
|
||||
return !sections.value[idx]?.synthetic && idx < sections.value.length - 1
|
||||
}
|
||||
|
||||
async function move(idx: number, dir: -1 | 1) {
|
||||
const target = idx + dir
|
||||
if (target < firstMovable() || target >= sections.value.length) return
|
||||
saving.value = true
|
||||
try {
|
||||
const next = [...sections.value]
|
||||
;[next[idx], next[target]] = [next[target], next[idx]]
|
||||
await agentContextApi.saveFile(props.agentId, FILE, serialize(next))
|
||||
await load()
|
||||
} catch (e: any) {
|
||||
mcToast.error(e?.message || 'Save failed')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function addSection() {
|
||||
const heading = newHeading.value.trim()
|
||||
if (!heading) return
|
||||
@ -127,7 +178,36 @@ async function addSection() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.guide-editor { margin-top: 8px; }
|
||||
.guide-open-btn {
|
||||
flex-shrink: 0; white-space: nowrap;
|
||||
padding: 7px 14px; border-radius: 8px; font-size: 12px; font-weight: 500;
|
||||
background: var(--mc-bg-elevated); color: var(--mc-text-secondary); border: 1px solid var(--mc-border);
|
||||
cursor: pointer; transition: all 0.12s;
|
||||
}
|
||||
.guide-open-btn:hover { border-color: var(--mc-primary); color: var(--mc-primary); }
|
||||
|
||||
/* Nested modal — sits above the edit-agent modal (z-index 1000). */
|
||||
.guide-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: flex;
|
||||
align-items: center; justify-content: center; z-index: 1100; padding: 20px;
|
||||
}
|
||||
.guide-modal {
|
||||
background: var(--mc-bg-elevated); border: 1px solid var(--mc-border); border-radius: 16px;
|
||||
width: 100%; max-width: 640px; max-height: 85vh; display: flex; flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
|
||||
}
|
||||
.guide-modal__header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 16px 20px; border-bottom: 1px solid var(--mc-border-light);
|
||||
}
|
||||
.guide-modal__title { margin: 0; font-size: 15px; font-weight: 600; color: var(--mc-text-primary); }
|
||||
.guide-modal__close {
|
||||
border: none; background: transparent; font-size: 22px; line-height: 1;
|
||||
color: var(--mc-text-tertiary); cursor: pointer; padding: 0 4px;
|
||||
}
|
||||
.guide-modal__close:hover { color: var(--mc-text-primary); }
|
||||
.guide-modal__body { padding: 16px 20px; overflow-y: auto; }
|
||||
|
||||
.guide-state { font-size: 13px; color: var(--mc-text-tertiary); padding: 8px 0; }
|
||||
.guide-empty {
|
||||
display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
<div class="section-header">
|
||||
<h4 class="section-title">{{ section.heading }}</h4>
|
||||
<div class="section-actions">
|
||||
<button v-if="!editing && canMoveUp" class="section-btn section-btn--move" :title="t('memory.hil.moveUp')" @click="emit('moveUp')">↑</button>
|
||||
<button v-if="!editing && canMoveDown" class="section-btn section-btn--move" :title="t('memory.hil.moveDown')" @click="emit('moveDown')">↓</button>
|
||||
<button v-if="!editing" class="section-btn" :title="t('memory.hil.edit')" @click="startEdit">
|
||||
<MemoryIcon name="edit" :size="12" />
|
||||
</button>
|
||||
@ -41,8 +43,14 @@ const props = defineProps<{
|
||||
section: MemorySectionData
|
||||
/** Persists the edited body — resolves on success, rejects on failure. */
|
||||
saveHandler: (body: string) => Promise<void>
|
||||
/** Show a ↑ button; emits `moveUp`. Off by default (MemoryBrowser doesn't reorder). */
|
||||
canMoveUp?: boolean
|
||||
/** Show a ↓ button; emits `moveDown`. */
|
||||
canMoveDown?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ moveUp: []; moveDown: [] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const editing = ref(false)
|
||||
const draft = ref('')
|
||||
@ -110,6 +118,7 @@ function renderMarkdown(body: string): string {
|
||||
cursor: pointer; transition: all 0.12s;
|
||||
}
|
||||
.section-btn:hover { background: var(--mc-bg-elevated); color: var(--mc-primary); }
|
||||
.section-btn--move { font-size: 14px; line-height: 1; }
|
||||
|
||||
.section-body {
|
||||
margin-top: 8px; font-size: 13px; color: var(--mc-text-secondary); line-height: 1.6;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user