Files
AFFiNE-Mirror/packages/backend/server/src/plugins/copilot/tools/doc-read.ts
Cats Juice 44ef06de36 feat(core): peek doc in ai doc-read tool result (#13424)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Enhanced document read results with clickable cards that open a peek
view of the referenced document.
* Added support for displaying document identifiers in document read
results.

* **Bug Fixes**
* Improved compatibility with older document read results that may lack
a document identifier.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 04:01:07 +00:00

95 lines
2.4 KiB
TypeScript

import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import { DocReader } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import { Models, publicUserSelect } from '../../../models';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
const logger = new Logger('DocReadTool');
export const buildDocContentGetter = (
ac: AccessController,
docReader: DocReader,
models: Models
) => {
const getDoc = async (options: CopilotChatOptions, docId?: string) => {
if (!options?.user || !options?.workspace || !docId) {
return;
}
const canAccess = await ac
.user(options.user)
.workspace(options.workspace)
.doc(docId)
.can('Doc.Read');
if (!canAccess) {
logger.warn(
`User ${options.user} does not have access to doc ${docId} in workspace ${options.workspace}`
);
return;
}
const docMeta = await models.doc.getSnapshot(options.workspace, docId, {
select: {
createdAt: true,
updatedAt: true,
createdByUser: {
select: publicUserSelect,
},
updatedByUser: {
select: publicUserSelect,
},
},
});
if (!docMeta) {
return;
}
const content = await docReader.getDocMarkdown(
options.workspace,
docId,
true
);
if (!content) {
return;
}
return {
docId,
title: content.title,
markdown: content.markdown,
createdAt: docMeta.createdAt,
updatedAt: docMeta.updatedAt,
createdByUser: docMeta.createdByUser,
updatedByUser: docMeta.updatedByUser,
};
};
return getDoc;
};
export const createDocReadTool = (
getDoc: (targetId?: string) => Promise<object | undefined>
) => {
return tool({
description:
'Return the complete text and basic metadata of a single document identified by docId; use this when the user needs the full content of a specific file rather than a search result.',
parameters: z.object({
doc_id: z.string().describe('The target doc to read'),
}),
execute: async ({ doc_id }) => {
try {
const doc = await getDoc(doc_id);
if (!doc) {
return;
}
return { ...doc };
} catch (err: any) {
logger.error(`Failed to read the doc ${doc_id}`, err);
return toolError('Doc Read Failed', err.message);
}
},
});
};