fix: better guidance for the AI

This commit is contained in:
2026-02-26 13:47:07 +01:00
parent 8a50e50f54
commit fde5296be4
2 changed files with 46 additions and 12 deletions

View File

@@ -307,11 +307,11 @@ You can ONLY access information through the tools listed below. Do not claim oth
Available Data Tools:
- get_blog_stats: Get comprehensive blog statistics (total posts, date range, posts per year, tag/category counts, media count). ALWAYS call this first when you need to understand the scope of the data.
- search_posts: Search blog posts using full-text search. Supports category/tag filters and pagination (offset/limit).
- search_posts: Search blog posts using full-text search. Supports category/tag/year/month filters and pagination (offset/limit).
- read_post: Read the full content and metadata of a specific post by ID.
- list_posts: List posts with optional filtering by status, category, or tags. Supports pagination (offset/limit). Returns "total" (global count) and "filteredTotal" (matching filter).
- list_posts: List posts with optional filtering by status, category, tags, year, and month. Supports pagination (offset/limit). Returns "total" (global count) and "filteredTotal" (matching filter). ALWAYS use the year filter when you need posts from a specific year — this is much faster than paginating through all posts.
- get_media: Get information about a specific media file by ID.
- list_media: List media files with optional MIME type filtering. Supports pagination (offset/limit).
- list_media: List media files with optional MIME type, year, month, and tag filtering. Supports pagination (offset/limit). Use year/month filters to narrow efficiently.
- view_image: View an image to analyze its visual content. Use this when you need to describe or analyze what an image looks like.
- update_post_metadata: Update a post's title, excerpt, tags, or categories.
- update_media_metadata: Update a media file's title, alt text, caption, or tags.
@@ -342,12 +342,17 @@ When answering questions:
8. Use render_card with action buttons when presenting items the user might want to navigate to (e.g., posts, media).
9. When comparing data across multiple dimensions (e.g., statistics per year), use render_tabs with embedded charts or tables in each tab.
CRITICAL - Pagination and data volume awareness:
CRITICAL - Efficient data access:
10. This blog may contain thousands or tens of thousands of posts spanning many years. NEVER assume the first page of results represents all data.
11. Always check the "total" and "filteredTotal" fields in list_posts and list_media responses. If total > limit, there are more results available via pagination.
12. When asked to analyze ALL posts (e.g., "show me all posts from 2015"), use pagination (offset/limit) to fetch all pages, or use get_blog_stats first to understand the scope.
12. ALWAYS use year/month filters when working with a specific time period. For example, to get posts from 2015, use list_posts with year=2015 — do NOT paginate through all posts with offsets to find the right year. Year/month filters are executed directly in the database and are much faster.
13. When reporting counts or statistics, always use get_blog_stats or check the total fields rather than counting the items in a single page of results.
14. Never claim there are only N posts when you have only fetched one page. State the total count from the API response.`;
14. Never claim there are only N posts when you have only fetched one page. State the total count from the API response.
CRITICAL - Heatmap and complex visualizations:
15. When building a heatmap, plan the data structure BEFORE fetching data. A heatmap needs series entries (rows) with segments (columns). Decide what the rows and columns represent first, then fetch only the data you need using year/month filters.
16. For tag-based heatmaps, use list_tags first to know which tags exist, then use list_posts with year filter to get posts for the target period, and aggregate tag counts from the results. Do not try to fetch all posts across all years.
17. When building any visualization that requires aggregated data, ALWAYS render the chart as soon as you have enough data. Do not wait to describe what you will do — just do it.`;
}
/**

View File

@@ -14,7 +14,7 @@ import { URL } from 'url';
import { BrowserWindow } from 'electron';
import { ChatEngine } from './ChatEngine';
import { PostEngine, type PostData } from './PostEngine';
import { MediaEngine } from './MediaEngine';
import { MediaEngine, type MediaData } from './MediaEngine';
import { getPostMediaEngine } from './PostMediaEngine';
import { isRenderTool, generateFromToolCall } from '../a2ui/generator';
import type { A2UIServerMessage } from '../a2ui/types';
@@ -742,13 +742,15 @@ export class OpenCodeManager {
return [
{
name: 'search_posts',
description: 'Search blog posts using full-text search. Can filter by category or tags. Returns paginated results with totalMatches count. Use offset to page through results when totalMatches > limit.',
description: 'Search blog posts using full-text search. Can filter by category, tags, year, or month. Returns paginated results with totalMatches count. Use offset to page through results when totalMatches > limit.',
input_schema: {
type: 'object',
properties: {
query: { type: 'string', description: 'The search query text to find in posts' },
category: { type: 'string', description: 'Optional category to filter by (e.g., "article", "picture", "aside", "page")' },
tags: { type: 'array', items: { type: 'string' }, description: 'Optional array of tags to filter by' },
year: { type: 'number', description: 'Filter to posts created in this year (e.g., 2024)' },
month: { type: 'number', description: 'Filter to posts created in this month (1-12). Requires year.' },
limit: { type: 'number', description: 'Maximum number of results to return (default: 10)' },
offset: { type: 'number', description: 'Offset for pagination (default: 0). Use with limit to page through results.' },
},
@@ -768,13 +770,15 @@ export class OpenCodeManager {
},
{
name: 'list_posts',
description: 'List blog posts with optional filtering by status, category, or tags. Returns paginated results. The response includes "total" (global post count in the blog) and "filteredTotal" (count matching current filters). Use offset/limit to page through all results. Always check total to understand the full data volume.',
description: 'List blog posts with optional filtering by status, category, tags, year, or month. Returns paginated results. The response includes "total" (global post count in the blog) and "filteredTotal" (count matching current filters). Use year/month filters to efficiently narrow to a time period instead of paginating through all posts. Use offset/limit to page through filtered results.',
input_schema: {
type: 'object',
properties: {
status: { type: 'string', enum: ['draft', 'published', 'archived'], description: 'Filter by post status' },
category: { type: 'string', description: 'Filter by category' },
tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (posts must have all specified tags)' },
year: { type: 'number', description: 'Filter to posts created in this year (e.g., 2024). Use this to efficiently narrow results by time period.' },
month: { type: 'number', description: 'Filter to posts created in this month (1-12). Requires year.' },
limit: { type: 'number', description: 'Maximum number of results (default: 20)' },
offset: { type: 'number', description: 'Offset for pagination (default: 0)' },
},
@@ -793,11 +797,14 @@ export class OpenCodeManager {
},
{
name: 'list_media',
description: 'List media files in the current project with optional filtering. Returns paginated results with total count. Use offset/limit to page through all results.',
description: 'List media files in the current project with optional filtering by MIME type, year, month, or tags. Returns paginated results with total count. Use year/month filters to efficiently narrow to a time period.',
input_schema: {
type: 'object',
properties: {
mimeTypeFilter: { type: 'string', description: 'Filter by MIME type prefix (e.g., "image/")' },
tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (media must have all specified tags)' },
year: { type: 'number', description: 'Filter to media created in this year (e.g., 2024)' },
month: { type: 'number', description: 'Filter to media created in this month (1-12). Requires year.' },
limit: { type: 'number', description: 'Maximum number of results (default: 20)' },
offset: { type: 'number', description: 'Offset for pagination (default: 0)' },
},
@@ -1136,6 +1143,14 @@ export class OpenCodeManager {
(args.tags as string[]).every(tag => p!.tags.includes(tag))
);
}
if (args.year !== undefined) {
const year = args.year as number;
filteredPosts = filteredPosts.filter(p => p!.createdAt.getFullYear() === year);
}
if (args.month !== undefined && args.year !== undefined) {
const month = (args.month as number) - 1; // Convert 1-indexed to 0-indexed
filteredPosts = filteredPosts.filter(p => p!.createdAt.getMonth() === month);
}
const totalMatches = filteredPosts.length;
const offset = (args.offset as number) || 0;
@@ -1175,10 +1190,12 @@ export class OpenCodeManager {
}
case 'list_posts': {
const filter: { status?: 'draft' | 'published' | 'archived'; tags?: string[]; categories?: string[] } = {};
const filter: { status?: 'draft' | 'published' | 'archived'; tags?: string[]; categories?: string[]; year?: number; month?: number } = {};
if (args.status) filter.status = args.status as 'draft' | 'published' | 'archived';
if (args.tags) filter.tags = args.tags as string[];
if (args.category) filter.categories = [args.category as string];
if (args.year !== undefined) filter.year = args.year as number;
if (args.month !== undefined && args.year !== undefined) filter.month = (args.month as number) - 1; // Convert 1-indexed to 0-indexed
const offset = (args.offset as number) || 0;
const limit = (args.limit as number) || 20;
@@ -1232,7 +1249,19 @@ export class OpenCodeManager {
}
case 'list_media': {
let mediaList = await this.mediaEngine.getAllMedia();
const hasMediaFilter = args.year !== undefined || (args.tags && Array.isArray(args.tags) && (args.tags as string[]).length > 0);
let mediaList: MediaData[];
if (hasMediaFilter) {
const mediaFilter: { year?: number; month?: number; tags?: string[] } = {};
if (args.year !== undefined) mediaFilter.year = args.year as number;
if (args.month !== undefined && args.year !== undefined) mediaFilter.month = (args.month as number) - 1; // Convert 1-indexed to 0-indexed
if (args.tags) mediaFilter.tags = args.tags as string[];
mediaList = await this.mediaEngine.getMediaFiltered(mediaFilter);
} else {
mediaList = await this.mediaEngine.getAllMedia();
}
const totalMedia = mediaList.length;
if (args.mimeTypeFilter) {
mediaList = mediaList.filter(m => m.mimeType.startsWith(args.mimeTypeFilter as string));