diff --git a/src/components/map-projects/MapProject.jsx b/src/components/map-projects/MapProject.jsx index e426f76..53f18f4 100644 --- a/src/components/map-projects/MapProject.jsx +++ b/src/components/map-projects/MapProject.jsx @@ -122,7 +122,7 @@ import { createLookupSession, LOOKUP_NOT_FOUND } from './lookupSession' import { parseConceptKey } from './conceptKey' import { normalizeLookupConfig } from './lookupConfigFields' import { getDeclaredLocales, getDefaultTargetRepoVersion, getProjectTargetRepoVersion, getSavedTargetRepoVersion, getTargetRepoVersionFromUrl, getTargetRepoVersionId } from './projectTargetRepo' -import { buildBridgeTargetDownloadEntries, buildQualityRowViews, conceptBelongsToTargetRepo, conceptForMapping, findTargetRowView, formatBridgeTargetDownloadEntry, resolveAICandidateID, getScoreDetails, getAIAnalysisCandidateIDs, withCachedConcept, withCandidateNames, buildMatchEntries, restoredNote, withOwnNames, withSavedMatch } from './viewBuilders.js' +import { buildBridgeTargetDownloadEntries, buildQualityRowViews, conceptBelongsToTargetRepo, conceptForMapping, findTargetRowView, formatBridgeTargetDownloadEntry, resolveAICandidateID, getScoreDetails, getAIAnalysisCandidateIDs, getAIIndicatorTooltip, withCachedConcept, withCandidateNames, buildMatchEntries, restoredNote, withOwnNames, withSavedMatch } from './viewBuilders.js' import { formatClockTime, getCapacityWaitLabel, mergeCapacityWaits } from './rowProgress.js' import './MapProject.scss' @@ -1100,6 +1100,7 @@ const MapProject = () => { return { status, latestAnalysis, + primaryCandidateId, isPrimaryMatch: Boolean(targetCode && primaryCandidateId && targetCode === primaryCandidateId), isAlternateMatch: Boolean(targetCode && alternateCandidateIds.includes(targetCode)), } @@ -1203,7 +1204,7 @@ const MapProject = () => { : aiMeta?.latestAnalysis ? - + diff --git a/src/components/map-projects/__tests__/viewHelpers.test.js b/src/components/map-projects/__tests__/viewHelpers.test.js index 2412bfb..85c78a3 100644 --- a/src/components/map-projects/__tests__/viewHelpers.test.js +++ b/src/components/map-projects/__tests__/viewHelpers.test.js @@ -4,12 +4,15 @@ * - conceptForMapping (the tuple -> legacy concept projection at the * onMap / isSelectedForMap boundary) * - resolveAICandidateID (AI Assistant response resolution chain) + * - getAIIndicatorTooltip (the AI icon's tooltip in the Target Code cell) * * Run with: npm test */ import test from 'node:test' import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import i18next from 'i18next' import { conceptBelongsToTargetRepo, @@ -17,7 +20,8 @@ import { conceptForMapping, resolveAICandidateID, getLatestAnalysisEntry, - getAIAnalysisCandidateIDs + getAIAnalysisCandidateIDs, + getAIIndicatorTooltip } from '../viewBuilders.js' const candidatesScore = { recommended: 80, available: 60 } @@ -258,3 +262,29 @@ test('getAIAnalysisCandidateIDs: resolves primary + alternate ids from latest an assert.deepEqual(out.alternateCandidateIds, ['ALT-1', 'ALT-2']) assert.equal(out.latestAnalysis, analysis[1]) }) + +// ---------- getAIIndicatorTooltip (OpenConceptLab/ocl_issues#2934) ---------- + +const translator = async locale => { + const catalog = JSON.parse(readFileSync(new URL(`../../../i18n/locales/${locale}/translations.json`, import.meta.url), 'utf8')) + const i18n = i18next.createInstance() + await i18n.init({lng: locale, resources: {[locale]: {translations: catalog}}, ns: ['translations'], defaultNS: 'translations', interpolation: {escapeValue: false}}) + return i18n.t +} + +test('getAIIndicatorTooltip: orange names the AI\'s pick; the alternate, purple and no-pick cases keep their wording', async () => { + const t = await translator('en') + const tooltip = meta => getAIIndicatorTooltip({isPrimaryMatch: false, isAlternateMatch: false, primaryCandidateId: 'BA00.Z', ...meta}, t) + assert.equal(tooltip({}), 'The AI recommends BA00.Z') + assert.equal(tooltip({isAlternateMatch: true}), 'AI Alternate Candidate Suggestion') + assert.equal(tooltip({isPrimaryMatch: true}), 'AI Recommended') + assert.equal(tooltip({primaryCandidateId: null}), 'AI Analysis') +}) + +test('getAIIndicatorTooltip: every catalog names the recommended code', async () => { + for (const locale of ['en', 'es', 'pt-BR', 'zh']) { + const t = await translator(locale) + const tooltip = getAIIndicatorTooltip({isPrimaryMatch: false, isAlternateMatch: false, primaryCandidateId: 'BA00.Z'}, t) + assert.ok(tooltip.includes('BA00.Z'), `${locale}: ${tooltip}`) + } +}) diff --git a/src/components/map-projects/viewBuilders.js b/src/components/map-projects/viewBuilders.js index af49291..e921b09 100644 --- a/src/components/map-projects/viewBuilders.js +++ b/src/components/map-projects/viewBuilders.js @@ -618,3 +618,11 @@ export const getAIAnalysisCandidateIDs = (analysisProp, conceptCache) => { alternateCandidateIds } } + +// The AI icon's tooltip in the grid's Target Code cell; the orange one names the AI's pick (OpenConceptLab/ocl_issues#2934). +export const getAIIndicatorTooltip = ({isPrimaryMatch, isAlternateMatch, primaryCandidateId}, t) => { + if(isPrimaryMatch) return t('map_project.ai_recommended') + if(isAlternateMatch) return t('map_project.ai_alternate') + if(primaryCandidateId) return t('map_project.ai_recommends_code', {code: primaryCandidateId}) + return t('map_project.ai_analysis') +} diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index d929e49..bf6b226 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -309,6 +309,7 @@ "source": "Source", "ai_recommended": "AI Recommended", "ai_alternate": "AI Alternate Candidate Suggestion", + "ai_recommends_code": "The AI recommends {{code}}", "ai_assistant": "OCL AI Assistant", "ai_prompt_template_unavailable": "OCL AI Assistant prompt template is not available", "view_raw_json": "View Raw JSON", diff --git a/src/i18n/locales/es/translations.json b/src/i18n/locales/es/translations.json index 0cdb020..e69a4e6 100644 --- a/src/i18n/locales/es/translations.json +++ b/src/i18n/locales/es/translations.json @@ -293,6 +293,7 @@ "source": "Fuente", "ai_recommended": "Recomendado por IA", "ai_alternate": "Sugerencia de candidato alternativo de IA", + "ai_recommends_code": "La IA recomienda {{code}}", "ai_assistant": "Asistente de IA de OCL", "ai_prompt_template_unavailable": "La plantilla de prompt del asistente de IA de OCL no está disponible", "view_raw_json": "Ver JSON Sin Procesar", diff --git a/src/i18n/locales/pt-BR/translations.json b/src/i18n/locales/pt-BR/translations.json index 213b0a2..947521b 100644 --- a/src/i18n/locales/pt-BR/translations.json +++ b/src/i18n/locales/pt-BR/translations.json @@ -309,6 +309,7 @@ "source": "Source", "ai_recommended": "Recomendado por IA", "ai_alternate": "Sugestão de Candidato Alternativo por IA", + "ai_recommends_code": "A IA recomenda {{code}}", "ai_assistant": "Assistente de IA do OCL", "ai_prompt_template_unavailable": "O modelo de prompt do Assistente de IA do OCL não está disponível", "view_raw_json": "Ver JSON Bruto", diff --git a/src/i18n/locales/zh/translations.json b/src/i18n/locales/zh/translations.json index ba755cc..9d1b42c 100644 --- a/src/i18n/locales/zh/translations.json +++ b/src/i18n/locales/zh/translations.json @@ -305,6 +305,7 @@ "source": "来源", "ai_recommended": "AI 推荐", "ai_alternate": "AI 备选候选建议", + "ai_recommends_code": "AI 推荐的是 {{code}}", "ai_assistant": "OCL AI 助手", "ai_prompt_template_unavailable": "OCL AI 助手提示模板不可用", "view_raw_json": "查看原始 JSON",