WIP - saving list views

This commit is contained in:
Francisco Gaona
2026-04-10 10:37:11 +02:00
parent a0bdb09c03
commit 12304d5890
15 changed files with 974 additions and 1 deletions

View File

@@ -38,4 +38,12 @@ export class AiAssistantController {
payload,
);
}
@Post('suggest-view-name')
async suggestViewName(
@TenantId() tenantId: string,
@Body() payload: { objectLabel: string; filters: any[]; explanation?: string },
) {
return this.aiAssistantService.suggestViewName(tenantId, payload);
}
}

View File

@@ -688,6 +688,8 @@ export class AiAssistantService {
totalCount: meiliResults.total ?? records.totalCount ?? 0,
strategy: plan.strategy,
explanation: plan.explanation,
filters: [],
sort: null,
};
}
@@ -702,6 +704,8 @@ export class AiAssistantService {
...fallback,
strategy: plan.strategy,
explanation: plan.explanation,
filters: [],
sort: null,
};
}
@@ -730,9 +734,53 @@ export class AiAssistantService {
...filtered,
strategy: plan.strategy,
explanation: plan.explanation,
filters: resolvedFilters,
sort: plan.sort || null,
};
}
/**
* Asks the LLM to suggest a short, human-friendly name for a saved list view
* based on the resolved filters / explanation.
*/
async suggestViewName(
tenantId: string,
payload: { objectLabel: string; filters: AiSearchFilter[]; explanation?: string },
): Promise<{ suggestedName: string }> {
const openAiConfig = await this.getOpenAiConfig(tenantId);
if (!openAiConfig) {
return { suggestedName: `${payload.objectLabel} View` };
}
const model = new ChatOpenAI({
apiKey: openAiConfig.apiKey,
model: this.normalizeChatModel(openAiConfig.model),
temperature: 0.4,
});
const filterSummary = payload.explanation?.trim()
|| payload.filters.map(f => `${f.field} ${f.operator} ${f.value ?? ''}`).join(', ')
|| 'no filters';
try {
const response = await model.invoke([
new SystemMessage(
'You are a CRM assistant. Suggest a very short (25 words), descriptive, and human-friendly name for a saved list view. ' +
'Reply with ONLY the name, no quotes or punctuation.',
),
new HumanMessage(
`Object: ${payload.objectLabel}.\nFilter summary: ${filterSummary}`,
),
]);
const raw = typeof response.content === 'string' ? response.content.trim() : '';
const suggestedName = raw || `${payload.objectLabel} View`;
return { suggestedName };
} catch {
return { suggestedName: `${payload.objectLabel} View` };
}
}
// ============================================
// Planning-Based LangGraph Workflow
// ============================================