Skip to content

Commit 8bd0f02

Browse files
committed
feat: enhance task and project retrieval with pagination and dynamic date filtering
1 parent 894a71c commit 8bd0f02

5 files changed

Lines changed: 288 additions & 75 deletions

File tree

backend/src/ai-agent/ask.service.ts

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,47 @@ dotenv.config();
2828

2929
@Injectable()
3030
export class AskService {
31+
private sanitizeToolText(value: string, maxLength = 1000) {
32+
const sanitized = value
33+
.replace(
34+
/data:[^"'()\s>]+;base64,[A-Za-z0-9+/=\r\n]+/g,
35+
"[inline base64 omitted]"
36+
)
37+
.replace(/<img\b[^>]*>/gi, "[image omitted]")
38+
.replace(/\s+/g, " ")
39+
.trim();
40+
41+
return sanitized.length > maxLength
42+
? `${sanitized.slice(0, maxLength)}...`
43+
: sanitized;
44+
}
45+
46+
private compactToolPayload(value: any, depth = 0): any {
47+
if (value === null || value === undefined) return value;
48+
if (typeof value === "string") return this.sanitizeToolText(value);
49+
if (typeof value !== "object") return value;
50+
if (value instanceof Date) return value.toISOString();
51+
if (depth >= 6) return "[nested data omitted]";
52+
53+
if (Array.isArray(value)) {
54+
const limited = value.slice(0, 100);
55+
return limited.map((item) => this.compactToolPayload(item, depth + 1));
56+
}
57+
58+
const omittedKeys = new Set([
59+
"attachments",
60+
"avatarUrl",
61+
"comments",
62+
"phone",
63+
"photo",
64+
]);
65+
return Object.fromEntries(
66+
Object.entries(value)
67+
.filter(([key]) => !omittedKeys.has(key))
68+
.map(([key, item]) => [key, this.compactToolPayload(item, depth + 1)])
69+
);
70+
}
71+
3172
private emitThinking(socket: Socket | undefined, message: string) {
3273
if (socket) {
3374
socket.emit("ai_thinking", { type: "thinking", message });
@@ -94,7 +135,11 @@ export class AskService {
94135
} else if (fn === "getDeployTasks") {
95136
return await getDeployTasks(args?.projectId);
96137
} else if (fn === "getDoneTasks") {
97-
return await getDoneTasks(args?.projectId, args?.dateType);
138+
return await getDoneTasks(
139+
args?.projectId,
140+
args?.dateType,
141+
args?.daysBack
142+
);
98143
} else if (fn === "getUnassignedTasks") {
99144
return await getUnassignedTasks(args?.projectId);
100145
} else if (fn === "getUrgentTasks") {
@@ -126,7 +171,7 @@ export class AskService {
126171
const getMessages = await prisma.chatMessage.findMany({
127172
where: { userId: userId || "" },
128173
orderBy: { createdAt: "desc" },
129-
take: 10,
174+
take: 6,
130175
});
131176

132177
const userMessages = getMessages.reverse();
@@ -146,7 +191,6 @@ export class AskService {
146191
role: "system",
147192
content: `User Selected Project ID: ${data.projectId}`,
148193
},
149-
...data.messages,
150194
];
151195

152196
this.emitThinking(socket, "🧠 Thinking...");
@@ -205,6 +249,7 @@ export class AskService {
205249
// inject cacheDate for certain read-only tools to improve cache key uniqueness
206250
if (fn === "getRepos" || fn === "getContributors") {
207251
args.cacheDate = new Date().toISOString().split("T")[0];
252+
args.resultShapeVersion = 2;
208253
}
209254

210255
// Try cache lookup (aiToolCache stores raw result in 'result' column)
@@ -224,7 +269,7 @@ export class AskService {
224269
let toolResult: any = null;
225270

226271
if (cached && cached.result !== null && cached.result !== undefined) {
227-
toolResult = cached.result;
272+
toolResult = this.compactToolPayload(cached.result);
228273
this.emitToolCall(socket, fn, args);
229274
this.emitToolResult(socket, fn, toolResult);
230275

@@ -237,7 +282,9 @@ export class AskService {
237282
} else {
238283
// Execute actual tool
239284
this.emitToolCall(socket, fn, args);
240-
toolResult = await this.execToolByName(fn, args);
285+
toolResult = this.compactToolPayload(
286+
await this.execToolByName(fn, args)
287+
);
241288
this.emitToolResult(socket, fn, toolResult);
242289

243290
// push tool result into conversation

backend/src/ai-agent/github.service.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ const headers = {
88
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
99
};
1010

11+
const MAX_AI_REPOS = 100;
12+
const MAX_AI_CONTRIBUTORS = 100;
13+
14+
function sanitizeText(value?: string | null, maxLength = 240) {
15+
if (!value) return null;
16+
const sanitized = value
17+
.replace(
18+
/data:[^"'()\s>]+;base64,[A-Za-z0-9+/=\r\n]+/g,
19+
"[inline base64 omitted]"
20+
)
21+
.replace(/<img\b[^>]*>/gi, "[image omitted]")
22+
.replace(/\s+/g, " ")
23+
.trim();
24+
25+
return sanitized.length > maxLength
26+
? `${sanitized.slice(0, maxLength)}...`
27+
: sanitized;
28+
}
29+
1130
export async function getContributors(repo: string, retries = 3) {
1231
const owner = process.env.GITHUB_OWNER;
1332
const url = `https://api.github.com/repos/${owner}/${repo}/stats/contributors`;
@@ -19,14 +38,22 @@ export async function getContributors(repo: string, retries = 3) {
1938
const res = await axios.get(url, { headers });
2039

2140
if (res.status === 200 && Array.isArray(res.data)) {
22-
return res.data.map((c: any) => ({
23-
username: c.author.login,
24-
avatarUrl: c.author.avatar_url,
25-
profileUrl: c.author.html_url,
26-
totalCommits: c.total,
27-
linesAdded: c.weeks.reduce((a: number, w: any) => a + w.a, 0),
28-
linesDeleted: c.weeks.reduce((a: number, w: any) => a + w.d, 0),
29-
}));
41+
const contributors = res.data
42+
.slice(0, MAX_AI_CONTRIBUTORS)
43+
.map((c: any) => ({
44+
username: c.author.login,
45+
profileUrl: c.author.html_url,
46+
totalCommits: c.total,
47+
linesAdded: c.weeks.reduce((a: number, w: any) => a + w.a, 0),
48+
linesDeleted: c.weeks.reduce((a: number, w: any) => a + w.d, 0),
49+
}));
50+
51+
return {
52+
total: res.data.length,
53+
returned: contributors.length,
54+
truncated: res.data.length > contributors.length,
55+
contributors,
56+
};
3057
}
3158

3259
if (res.status === 202) {
@@ -108,16 +135,22 @@ export async function getRepos(retries = 3) {
108135
page++;
109136
}
110137

111-
const repos = allRepos.map((r: any) => ({
138+
const limitedRepos = allRepos.slice(0, MAX_AI_REPOS);
139+
const repos = limitedRepos.map((r: any) => ({
112140
owner: owner,
113141
name: r.name,
114142
fullName: r.full_name,
115-
description: r.description,
143+
description: sanitizeText(r.description),
116144
stars: r.stargazers_count,
117145
forks: r.forks_count,
118146
updatedAt: r.updated_at,
119147
}));
120148

121149
console.log(`Fetched ${repos.length} repos from GitHub`);
122-
return repos;
150+
return {
151+
total: allRepos.length,
152+
returned: repos.length,
153+
truncated: allRepos.length > repos.length,
154+
repos,
155+
};
123156
}

0 commit comments

Comments
 (0)