mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 19:45:08 +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: {
|
guide: {
|
||||||
summary: 'Behavior Guide (AGENTS.md)',
|
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.",
|
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.',
|
empty: 'This employee has no behavior guide (AGENTS.md) yet.',
|
||||||
create: 'Create Behavior Guide',
|
create: 'Create Behavior Guide',
|
||||||
@ -3780,6 +3781,8 @@ export default {
|
|||||||
editPlaceholder: 'Edit memory content...',
|
editPlaceholder: 'Edit memory content...',
|
||||||
confirmed: 'Confirmed',
|
confirmed: 'Confirmed',
|
||||||
saved: 'Saved',
|
saved: 'Saved',
|
||||||
|
moveUp: 'Move up',
|
||||||
|
moveDown: 'Move down',
|
||||||
},
|
},
|
||||||
facts: {
|
facts: {
|
||||||
searchPlaceholder: 'Search facts…',
|
searchPlaceholder: 'Search facts…',
|
||||||
|
|||||||
@ -1130,6 +1130,7 @@ export default {
|
|||||||
},
|
},
|
||||||
guide: {
|
guide: {
|
||||||
summary: '行为指南(AGENTS.md)',
|
summary: '行为指南(AGENTS.md)',
|
||||||
|
open: '管理行为指南',
|
||||||
desc: '这里是该员工的行为手册(AGENTS.md):规定何时写记忆、各文件放什么、可用的记忆工具。上方的岗位/目标/背景是员工的出厂身份;PROFILE/MEMORY 等可演化的记忆在「记忆」菜单管理。',
|
desc: '这里是该员工的行为手册(AGENTS.md):规定何时写记忆、各文件放什么、可用的记忆工具。上方的岗位/目标/背景是员工的出厂身份;PROFILE/MEMORY 等可演化的记忆在「记忆」菜单管理。',
|
||||||
empty: '该员工还没有行为指南(AGENTS.md)。',
|
empty: '该员工还没有行为指南(AGENTS.md)。',
|
||||||
create: '创建行为指南',
|
create: '创建行为指南',
|
||||||
@ -3872,6 +3873,8 @@ export default {
|
|||||||
editPlaceholder: '修改记忆内容...',
|
editPlaceholder: '修改记忆内容...',
|
||||||
confirmed: '已确认',
|
confirmed: '已确认',
|
||||||
saved: '已保存',
|
saved: '已保存',
|
||||||
|
moveUp: '上移',
|
||||||
|
moveDown: '下移',
|
||||||
},
|
},
|
||||||
facts: {
|
facts: {
|
||||||
searchPlaceholder: '搜索事实…',
|
searchPlaceholder: '搜索事实…',
|
||||||
|
|||||||
@ -347,8 +347,10 @@
|
|||||||
<textarea v-model="profileForm.extra" class="form-textarea" rows="4" :placeholder="t('agents.placeholders.extraInstructions')"></textarea>
|
<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>
|
<p class="form-hint">{{ t('agents.fields.extraInstructionsHint') }}</p>
|
||||||
<div v-if="editingAgent" class="advanced-guide">
|
<div v-if="editingAgent" class="advanced-guide">
|
||||||
<label class="form-label">{{ t('agents.guide.summary') }}</label>
|
<div class="advanced-guide__info">
|
||||||
<p class="form-hint">{{ t('agents.guide.desc') }}</p>
|
<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" />
|
<AgentGuideEditor :agent-id="editingAgent.id" />
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</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 { 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[open] > .advanced-prompt__summary::before { transform: rotate(90deg); }
|
||||||
.advanced-prompt .form-textarea { width: 100%; box-sizing: border-box; }
|
.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); }
|
.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) {
|
@media (max-width: 900px) {
|
||||||
|
|||||||
@ -1,41 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="guide-editor">
|
<button type="button" class="guide-open-btn" @click="open">
|
||||||
<div v-if="loading" class="guide-state">…</div>
|
{{ t('agents.guide.open') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Empty state: AGENTS.md not present or blank -->
|
<Teleport to="body">
|
||||||
<div v-else-if="sections.length === 0" class="guide-empty">
|
<div v-if="visible" class="guide-overlay" @click.self="visible = false">
|
||||||
<p class="guide-empty__text">{{ t('agents.guide.empty') }}</p>
|
<div class="guide-modal">
|
||||||
<button class="guide-create-btn" :disabled="saving" @click="createGuide">
|
<div class="guide-modal__header">
|
||||||
{{ t('agents.guide.create') }}
|
<h3 class="guide-modal__title">{{ t('agents.guide.summary') }}</h3>
|
||||||
</button>
|
<button type="button" class="guide-modal__close" @click="visible = false">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section cards -->
|
<div class="guide-modal__body">
|
||||||
<div v-else class="guide-sections">
|
<div v-if="loading" class="guide-state">…</div>
|
||||||
<MemorySection
|
|
||||||
v-for="(sec, idx) in sections"
|
|
||||||
:key="idx"
|
|
||||||
:section="sec"
|
|
||||||
:save-handler="(body: string) => saveSection(idx, body)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="guide-add">
|
<!-- Empty state: AGENTS.md not present or blank -->
|
||||||
<input
|
<div v-else-if="sections.length === 0" class="guide-empty">
|
||||||
v-model="newHeading"
|
<p class="guide-empty__text">{{ t('agents.guide.empty') }}</p>
|
||||||
class="guide-add__input"
|
<button type="button" class="guide-create-btn" :disabled="saving" @click="createGuide">
|
||||||
:placeholder="t('agents.guide.addSectionPlaceholder')"
|
{{ t('agents.guide.create') }}
|
||||||
@keyup.enter="addSection"
|
</button>
|
||||||
/>
|
</div>
|
||||||
<button class="guide-add__btn" :disabled="!newHeading.trim() || saving" @click="addSection">
|
|
||||||
+ {{ t('agents.guide.addSection') }}
|
<!-- Section cards -->
|
||||||
</button>
|
<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>
|
</div>
|
||||||
</div>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { agentContextApi } from '@/api/index'
|
import { agentContextApi } from '@/api/index'
|
||||||
import { mcToast } from '@/composables/useMcToast'
|
import { mcToast } from '@/composables/useMcToast'
|
||||||
@ -46,12 +65,16 @@ const props = defineProps<{ agentId: string | number }>()
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const FILE = 'AGENTS.md'
|
const FILE = 'AGENTS.md'
|
||||||
const loading = ref(true)
|
const visible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const sections = ref<MemorySectionData[]>([])
|
const sections = ref<MemorySectionData[]>([])
|
||||||
const newHeading = ref('')
|
const newHeading = ref('')
|
||||||
|
|
||||||
onMounted(load)
|
function open() {
|
||||||
|
visible.value = true
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
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() {
|
async function addSection() {
|
||||||
const heading = newHeading.value.trim()
|
const heading = newHeading.value.trim()
|
||||||
if (!heading) return
|
if (!heading) return
|
||||||
@ -127,7 +178,36 @@ async function addSection() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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-state { font-size: 13px; color: var(--mc-text-tertiary); padding: 8px 0; }
|
||||||
.guide-empty {
|
.guide-empty {
|
||||||
display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
|
display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h4 class="section-title">{{ section.heading }}</h4>
|
<h4 class="section-title">{{ section.heading }}</h4>
|
||||||
<div class="section-actions">
|
<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">
|
<button v-if="!editing" class="section-btn" :title="t('memory.hil.edit')" @click="startEdit">
|
||||||
<MemoryIcon name="edit" :size="12" />
|
<MemoryIcon name="edit" :size="12" />
|
||||||
</button>
|
</button>
|
||||||
@ -41,8 +43,14 @@ const props = defineProps<{
|
|||||||
section: MemorySectionData
|
section: MemorySectionData
|
||||||
/** Persists the edited body — resolves on success, rejects on failure. */
|
/** Persists the edited body — resolves on success, rejects on failure. */
|
||||||
saveHandler: (body: string) => Promise<void>
|
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 { t } = useI18n()
|
||||||
const editing = ref(false)
|
const editing = ref(false)
|
||||||
const draft = ref('')
|
const draft = ref('')
|
||||||
@ -110,6 +118,7 @@ function renderMarkdown(body: string): string {
|
|||||||
cursor: pointer; transition: all 0.12s;
|
cursor: pointer; transition: all 0.12s;
|
||||||
}
|
}
|
||||||
.section-btn:hover { background: var(--mc-bg-elevated); color: var(--mc-primary); }
|
.section-btn:hover { background: var(--mc-bg-elevated); color: var(--mc-primary); }
|
||||||
|
.section-btn--move { font-size: 14px; line-height: 1; }
|
||||||
|
|
||||||
.section-body {
|
.section-body {
|
||||||
margin-top: 8px; font-size: 13px; color: var(--mc-text-secondary); line-height: 1.6;
|
margin-top: 8px; font-size: 13px; color: var(--mc-text-secondary); line-height: 1.6;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user