Browse Source

feat(agent-documents): add delete in drawer + inject memory into system prompt

- AgentDocumentDrawer: delete button in list cards and detail view header
  with confirm dialog, auto-refresh list after delete
- Drawer now re-fetches on every open (not just first time)
- agent-document service: add getMemoryContext(userId) — queries all
  memory-type docs, wraps in <memory> tag
- chat-engine: inject memory context into systemPrompt for logged-in users

Co-authored-by: CodeFree <codefree@chinatelcom.cn>
feat/agent-independence
npmrun 2 months ago
parent
commit
bf88935b8b
  1. 89
      app/components/agent/AgentDocumentDrawer.vue
  2. 28
      server/service/agent-document/index.ts
  3. 19
      server/service/agent/chat-engine.ts

89
app/components/agent/AgentDocumentDrawer.vue

@ -86,6 +86,27 @@ function closeDetail() {
selectedDoc.value = null selectedDoc.value = null
} }
const deleting = ref(false)
async function deleteDocument(doc: AgentDocumentItem | DocDetail) {
if (!confirm(`确定删除「${doc.title}」吗?`)) return
deleting.value = true
try {
await request<{ code: number }>(`/api/agent-documents/${doc.id}`, {
method: 'DELETE',
})
$toast.success('已删除')
documents.value = documents.value.filter((d) => d.id !== doc.id)
if (selectedDoc.value?.id === doc.id) {
selectedDoc.value = null
}
} catch {
$toast.error('删除失败')
} finally {
deleting.value = false
}
}
function onFilterChange(type: 'memory' | 'document' | '') { function onFilterChange(type: 'memory' | 'document' | '') {
typeFilter.value = type typeFilter.value = type
fetchDocuments() fetchDocuments()
@ -100,7 +121,7 @@ function onSearchInput() {
} }
watch(() => props.show, (val) => { watch(() => props.show, (val) => {
if (val && documents.value.length === 0) { if (val) {
fetchDocuments() fetchDocuments()
} }
}) })
@ -177,6 +198,13 @@ watch(() => props.show, (val) => {
{{ item.type === 'document' ? '文档' : '记忆' }} {{ item.type === 'document' ? '文档' : '记忆' }}
</span> </span>
<span class="card-date">{{ new Date(item.updatedAt).toLocaleDateString('zh-CN') }}</span> <span class="card-date">{{ new Date(item.updatedAt).toLocaleDateString('zh-CN') }}</span>
<button
class="card-delete-btn"
:disabled="deleting"
@click.stop="deleteDocument(item)"
>
<Icon name="lucide:trash-2" />
</button>
</div> </div>
<h3 class="card-title">{{ item.title }}</h3> <h3 class="card-title">{{ item.title }}</h3>
<p v-if="item.summary" class="card-summary">{{ item.summary }}</p> <p v-if="item.summary" class="card-summary">{{ item.summary }}</p>
@ -192,6 +220,14 @@ watch(() => props.show, (val) => {
<Icon name="lucide:arrow-left" /> <Icon name="lucide:arrow-left" />
<span>返回</span> <span>返回</span>
</button> </button>
<button
class="delete-btn"
:disabled="deleting"
@click="deleteDocument(selectedDoc)"
>
<Icon name="lucide:trash-2" />
<span>{{ deleting ? '删除中…' : '删除' }}</span>
</button>
<button class="close-btn" @click="emit('close')"> <button class="close-btn" @click="emit('close')">
<Icon name="lucide:x" /> <Icon name="lucide:x" />
</button> </button>
@ -304,6 +340,57 @@ watch(() => props.show, (val) => {
color: var(--color-ink, #141413); color: var(--color-ink, #141413);
} }
.delete-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px;
border: none;
background: none;
border-radius: 8px;
cursor: pointer;
color: var(--color-muted, #6c6a64);
font-size: 13px;
transition: all 0.15s;
margin-left: auto;
}
.delete-btn:hover:not(:disabled) {
background: #fde8e8;
color: #c0392b;
}
.delete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.card-delete-btn {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
color: var(--color-muted, #b8b2a6);
font-size: 14px;
transition: all 0.15s;
margin-left: auto;
}
.card-delete-btn:hover:not(:disabled) {
background: #fde8e8;
color: #c0392b;
}
.card-delete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.drawer-filters { .drawer-filters {
display: flex; display: flex;
align-items: center; align-items: center;

28
server/service/agent-document/index.ts

@ -207,5 +207,33 @@ export async function deleteDocument(
return result.length > 0; return result.length > 0;
} }
// ============ Memory Context ============
export async function getMemoryContext(
userId: number,
): Promise<string | null> {
const rows = await dbGlobal
.select({
title: agentDocuments.title,
content: agentDocuments.content,
})
.from(agentDocuments)
.where(
and(
eq(agentDocuments.userId, userId),
eq(agentDocuments.type, "memory"),
),
)
.orderBy(desc(agentDocuments.updatedAt));
if (rows.length === 0) return null;
const parts = rows.map(
(r) => `## ${r.title}\n${r.content}`,
);
return `<memory>\n${parts.join("\n\n")}\n</memory>`;
}
// ============ Tags Helper ============ // ============ Tags Helper ============
export { deserializeTags, serializeTags }; export { deserializeTags, serializeTags };

19
server/service/agent/chat-engine.ts

@ -14,6 +14,7 @@ import {
updateSession, updateSession,
} from "#server/service/agent/session"; } from "#server/service/agent/session";
import { getAgentToolsForChatByAgentId, getAgentToolsBySlugs, executeAgentTool } from "#server/service/agent-tool"; import { getAgentToolsForChatByAgentId, getAgentToolsBySlugs, executeAgentTool } from "#server/service/agent-tool";
import { getMemoryContext } from "#server/service/agent-document";
import { checkRateLimit, incrementRateLimit } from "#server/service/agent/rate-limit"; import { checkRateLimit, incrementRateLimit } from "#server/service/agent/rate-limit";
import { resolveModelForUser, resolveModelAny, toLanguageModel } from "#server/service/llm/model-resolver"; import { resolveModelForUser, resolveModelAny, toLanguageModel } from "#server/service/llm/model-resolver";
import { generateSessionTitle } from "./title"; import { generateSessionTitle } from "./title";
@ -102,6 +103,12 @@ function buildModelMessages(
}); });
} else { } else {
assistantContent.push({ assistantContent.push({
type: "tool-call",
toolCallId: p.toolCallId,
toolName: p.toolName,
input: p.args ?? {},
});
assistantContent.push({
type: "tool-approval-request", type: "tool-approval-request",
approvalId: p.approvalId, approvalId: p.approvalId,
toolCallId: p.toolCallId, toolCallId: p.toolCallId,
@ -238,7 +245,17 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
const languageModel: LanguageModel = toLanguageModel(resolvedModel); const languageModel: LanguageModel = toLanguageModel(resolvedModel);
const systemPrompt = session.systemPrompt?.trim() || agent.systemPrompt || undefined; const baseSystemPrompt = session.systemPrompt?.trim() || agent.systemPrompt || undefined;
let systemPrompt = baseSystemPrompt;
if (user) {
const memoryContext = await getMemoryContext(user.id);
if (memoryContext) {
systemPrompt = systemPrompt
? `${systemPrompt}\n\n${memoryContext}`
: memoryContext;
}
}
const sessionEnableTools = session.enableTools !== null ? session.enableTools === 1 : agent.enableTools === 1; const sessionEnableTools = session.enableTools !== null ? session.enableTools === 1 : agent.enableTools === 1;
const toolsEnabled = bodyEnableTools !== undefined ? bodyEnableTools : sessionEnableTools; const toolsEnabled = bodyEnableTools !== undefined ? bodyEnableTools : sessionEnableTools;

Loading…
Cancel
Save