diff --git a/src/app/(admin)/admin/projects/page.tsx b/src/app/(admin)/admin/projects/page.tsx index 1baa4d8..da9ac08 100644 --- a/src/app/(admin)/admin/projects/page.tsx +++ b/src/app/(admin)/admin/projects/page.tsx @@ -252,9 +252,16 @@ export default function ProjectsPage() { // AI batch tagging mutation const batchTagProjects = trpc.tag.batchTagProjects.useMutation({ onSuccess: (result) => { - toast.success( - `AI Tagging complete: ${result.processed} tagged, ${result.skipped} skipped, ${result.failed} failed` - ) + if (result.errors && result.errors.length > 0) { + // Show first error if there are any + toast.error(`AI Tagging issue: ${result.errors[0]}`) + } else if (result.processed === 0 && result.skipped === 0 && result.failed === 0) { + toast.info('No projects to tag - all projects in this round already have tags') + } else { + toast.success( + `AI Tagging complete: ${result.processed} tagged, ${result.skipped} skipped, ${result.failed} failed` + ) + } setAiTagDialogOpen(false) setSelectedRoundForTagging('') utils.project.list.invalidate() diff --git a/src/server/services/ai-tagging.ts b/src/server/services/ai-tagging.ts index 14f6940..63c08c0 100644 --- a/src/server/services/ai-tagging.ts +++ b/src/server/services/ai-tagging.ts @@ -101,15 +101,23 @@ async function getTaggingSettings(): Promise<{ const settings = await prisma.systemSettings.findMany({ where: { key: { - in: ['ai_tagging_enabled', 'ai_tagging_max_tags'], + in: ['ai_tagging_enabled', 'ai_tagging_max_tags', 'ai_enabled'], }, }, }) const settingsMap = new Map(settings.map((s) => [s.key, s.value])) + // AI tagging is enabled if: + // 1. ai_tagging_enabled is explicitly 'true', OR + // 2. ai_tagging_enabled is not set but ai_enabled is 'true' (fall back to general AI setting) + const taggingEnabled = settingsMap.get('ai_tagging_enabled') + const aiEnabled = settingsMap.get('ai_enabled') + + const enabled = taggingEnabled === 'true' || (taggingEnabled === undefined && aiEnabled === 'true') + return { - enabled: settingsMap.get('ai_tagging_enabled') === 'true', + enabled, maxTags: parseInt(settingsMap.get('ai_tagging_max_tags') || String(DEFAULT_MAX_TAGS)), } } @@ -409,34 +417,73 @@ export async function batchTagProjects( onProgress?: (processed: number, total: number) => void ): Promise { const settings = await getTaggingSettings() + console.log('[AI Tagging] Settings:', settings) + if (!settings.enabled) { + console.log('[AI Tagging] AI tagging is disabled in settings') return { processed: 0, failed: 0, skipped: 0, - errors: ['AI tagging is disabled'], + errors: ['AI tagging is disabled. Enable it in Settings > AI or set ai_enabled to true.'], results: [], } } - // Get untagged projects in round - const projects = await prisma.project.findMany({ - where: { - roundId, - projectTags: { none: {} }, // Only projects with no tags - }, - include: { - files: { select: { fileType: true } }, - _count: { select: { teamMembers: true, files: true } }, - }, - }) - - if (projects.length === 0) { + // Check if OpenAI is configured + const openai = await getOpenAI() + if (!openai) { + console.log('[AI Tagging] OpenAI is not configured') return { processed: 0, failed: 0, skipped: 0, - errors: [], + errors: ['OpenAI API is not configured. Add your API key in Settings > AI.'], + results: [], + } + } + + // Check if there are any available tags + const availableTags = await getAvailableTags() + console.log(`[AI Tagging] Found ${availableTags.length} available expertise tags`) + if (availableTags.length === 0) { + return { + processed: 0, + failed: 0, + skipped: 0, + errors: ['No expertise tags defined. Create tags in Settings > Tags first.'], + results: [], + } + } + + // Get ALL projects in round to check their tag status + const allProjects = await prisma.project.findMany({ + where: { roundId }, + include: { + files: { select: { fileType: true } }, + _count: { select: { teamMembers: true, files: true } }, + projectTags: { select: { tagId: true } }, + }, + }) + + console.log(`[AI Tagging] Found ${allProjects.length} total projects in round`) + + // Filter to only projects that truly have no tags (empty tags array AND no projectTags) + const untaggedProjects = allProjects.filter(p => + (p.tags.length === 0) && (p.projectTags.length === 0) + ) + + const alreadyTaggedCount = allProjects.length - untaggedProjects.length + console.log(`[AI Tagging] ${untaggedProjects.length} untagged projects, ${alreadyTaggedCount} already have tags`) + + if (untaggedProjects.length === 0) { + return { + processed: 0, + failed: 0, + skipped: alreadyTaggedCount, + errors: alreadyTaggedCount > 0 + ? [] + : ['No projects found in this round'], results: [], } } @@ -446,8 +493,8 @@ export async function batchTagProjects( let failed = 0 const errors: string[] = [] - for (let i = 0; i < projects.length; i++) { - const project = projects[i] + for (let i = 0; i < untaggedProjects.length; i++) { + const project = untaggedProjects[i] try { const result = await tagProject(project.id, userId) results.push(result) @@ -459,7 +506,7 @@ export async function batchTagProjects( // Report progress if (onProgress) { - onProgress(i + 1, projects.length) + onProgress(i + 1, untaggedProjects.length) } }