Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions app/next-client-app/api/recommendations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@

import request from "@/lib/api/request";
import {
unisonBaseUrl,
unisonApiKey,
recommendationService,
recommendationServiceBaseUrl,
recommendationServiceApiKey,
recommendationServiceName,
} from "@/constants";

export const getConceptRecommendationsUnison = async (
// Unison can query by concept name, or concept code (exact match).
// The latter is used first in searching, then the former.
export const getRecommendations = async (
queryValue: string,
domainId: string
): Promise<UnisonConceptResponse> => {
): Promise<RecommendationServiceResponse> => {
try {
if (recommendationService === "unison") {
const endpoint = `${queryValue}?apiKey=${unisonApiKey}&domain=${domainId}`;
return await request<UnisonConceptResponse>(endpoint, {
baseUrl: unisonBaseUrl,
// Unison recommendation service
// Unison can query by concept name, or concept code (exact match).
// The latter is used first in searching, then the former.
if (recommendationServiceName === "unison") {
const endpoint = `${queryValue}?apiKey=${recommendationServiceApiKey}&domain=${domainId}`;
return await request<RecommendationServiceResponse>(endpoint, {
baseUrl: recommendationServiceBaseUrl,
headers: {
Accept: "application/json",
},
});
}
// TODO: Implement Lettuce recommendation service
else if (recommendationService === "lettuce") {
console.log("Lettuce recommendation service");
// Lettuce recommendation service
if (recommendationServiceName === "lettuce") {
const endpoint = `${queryValue}?domain=${domainId}`;
return await request<RecommendationServiceResponse>(endpoint, {
baseUrl: recommendationServiceBaseUrl,
headers: {
Accept: "application/json",
Authorization: `Bearer ${recommendationServiceApiKey}`,
},
authMode: "apiKey",
});
}

// Other recommendation services not supported
throw new Error("Recommendation service not supported");
} catch (error) {
console.error("Error fetching recommendations:", error);
Expand Down
7 changes: 5 additions & 2 deletions app/next-client-app/components/core/Tooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
TooltipTrigger,
} from "@/components/ui/tooltip";

import { InfoIcon } from "lucide-react";
Expand All @@ -22,7 +22,10 @@ export function Tooltips({
<TooltipTrigger asChild>
<InfoIcon className="ml-1 h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent className="max-w-96 text-center" side={side}>
<TooltipContent
className="max-w-96 text-center whitespace-pre-wrap"
side={side}
>
<p>
{content}
{link && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { useState } from "react";
import { Button } from "../ui/button";
import { Sparkles, Loader2 } from "lucide-react";
import AISuggestionDialog from "./ai-suggestions-dialog";
import { getConceptRecommendationsUnison } from "@/api/recommendations";
import { getRecommendations } from "@/api/recommendations";
import { addConcept } from "@/api/concepts";
import { toast } from "sonner";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { domains } from "@/constants/domains";
import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
Expand All @@ -20,7 +20,7 @@ export function AISuggestionsButton({
value,
tableId,
rowId,
contentType
contentType,
}: {
value: string;
tableId: string;
Expand All @@ -29,7 +29,8 @@ export function AISuggestionsButton({
}) {
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [suggestions, setSuggestions] = useState<UnisonConceptItem[]>([]);
const [suggestions, setSuggestions] = useState<RecommendationItem[]>([]);
const [metadata, setMetadata] = useState<RecommendationMetadata | null>(null);
const [domainId, setDomainId] = useState<string>("");

// Fetches AI suggestions from the API
Expand All @@ -42,14 +43,16 @@ export function AISuggestionsButton({
setIsLoading(true);

try {
// Call the getConceptRecommendations function
const recommendations: UnisonConceptResponse =
await getConceptRecommendationsUnison(value, domainId);
const recommendations: RecommendationServiceResponse =
await getRecommendations(value, domainId);
// Filter to get only unique concept IDs
const uniqueRecommendations = recommendations.items.filter(
(item, index, array) =>
array.findIndex((i) => i.conceptId === item.conceptId) === index
);
if (recommendations.metadata) {
setMetadata(recommendations.metadata);
}

setSuggestions(uniqueRecommendations);
setIsOpen(true);
Expand Down Expand Up @@ -136,6 +139,7 @@ export function AISuggestionsButton({
rowId={rowId}
domainId={domainId}
contentType={contentType}
metadata={metadata}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { InfoItem } from "../core/InfoItem";
interface AISuggestionDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
suggestions: UnisonConceptItem[];
suggestions: RecommendationItem[];
onApplySuggestion: (data: {
concept: number;
object_id: number;
Expand All @@ -23,6 +23,7 @@ interface AISuggestionDialogProps {
rowId: number;
domainId: string;
contentType: string;
metadata?: RecommendationMetadata | null;
}

// Main dialog component combining all parts
Expand All @@ -36,6 +37,7 @@ export default function AISuggestionDialog({
rowId,
domainId,
contentType,
metadata,
}: AISuggestionDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
Expand Down Expand Up @@ -66,6 +68,16 @@ export default function AISuggestionDialog({
}
className="py-1 md:py-0 md:px-3"
/>
{metadata && (
<InfoItem
label="Pipeline"
value={
metadata.pipeline.charAt(0).toUpperCase() +
metadata.pipeline.slice(1)
}
className="py-1 md:py-0 md:px-3"
/>
)}
</div>
</DialogHeader>

Expand Down
15 changes: 11 additions & 4 deletions app/next-client-app/components/recommendations/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export const columns = (
table_id: string;
}) => void,
contentType: string
): ColumnDef<UnisonConceptItem>[] => [
): ColumnDef<RecommendationItem>[] => [
{
id: "Concept Name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Concept Name" />
),
cell: ({ row }) => {
const { conceptName } = row.original;
return <div className="w-[500px]">{conceptName}</div>;
return <div className="w-[500px] whitespace-pre-wrap">{conceptName}</div>;
},
enableSorting: false,
enableHiding: true,
Expand Down Expand Up @@ -63,13 +63,20 @@ export const columns = (
id: "Accuracy",
header: ({ column }) => (
<div className="text-center w-full">
<DataTableColumnHeader column={column} title="Accuracy/Confidence" />
<DataTableColumnHeader column={column} title="Accuracy/Score" />
</div>
),
enableSorting: false,
enableHiding: true,
cell: ({ row }) => {
const { accuracy, explanation } = row.original;
const { accuracy, explanation, scores } = row.original;
if (scores) {
return (
<div className="text-center w-full flex items-center justify-center gap-1">
{((1 - (scores?.["vector-search"] || 0)) * 100).toFixed(2)}%
</div>
);
}
return (
<div className="text-center w-full flex items-center justify-center gap-1">
{accuracy}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { domains } from "@/constants/domains";
import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
Expand All @@ -20,7 +20,7 @@ export function StoredRecommendationsButton({
tableId,
rowId,
contentType,
mappingRecommendations
mappingRecommendations,
}: {
value: string;
tableId: string;
Expand All @@ -31,7 +31,7 @@ export function StoredRecommendationsButton({
mappingRecommendations: MappingRecommendation[];
}) {
const [isOpen, setIsOpen] = useState(false);
const [suggestions, setSuggestions] = useState<UnisonConceptItem[]>([]);
const [suggestions, setSuggestions] = useState<RecommendationItem[]>([]);
const [domainId, setDomainId] = useState<string>("");

// Handle click to show stored recommendations
Expand All @@ -57,7 +57,7 @@ export function StoredRecommendationsButton({
// Transform mapping recommendations to expected format
const transformMappingRecommendations = (
recommendations: MappingRecommendation[]
): UnisonConceptItem[] => {
): RecommendationItem[] => {
return recommendations.map((rec) => ({
accuracy: rec.score ?? null,
conceptId: rec.concept.concept_id,
Expand All @@ -68,7 +68,7 @@ export function StoredRecommendationsButton({
conceptClass: rec.concept.concept_class_id ?? "Unknown",
explanation: rec.score
? `Pre-computed recommendation from ${rec.tool_name} (score: ${rec.score})`
: `Pre-computed recommendation from ${rec.tool_name} (no score)`
: `Pre-computed recommendation from ${rec.tool_name} (no score)`,
}));
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { InfoItem } from "../core/InfoItem";
interface RecommendationsDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
suggestions: UnisonConceptItem[];
suggestions: RecommendationItem[];
onApplySuggestion: (data: {
concept: number;
object_id: number;
Expand All @@ -36,7 +36,7 @@ export default function RecommendationsDialog({
rowId,
domainId,
contentType,
source
source,
}: RecommendationsDialogProps) {
const getSourceLabel = () => {
return source === "v3"
Expand Down
10 changes: 5 additions & 5 deletions app/next-client-app/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { MAX_FILE_SIZE_BYTES } from "./config";

export const apiUrl = process.env.BACKEND_URL;

export const recommendationService = process.env.RECOMMENDATION_SERVICE;
export const recommendationServiceBaseUrl =
process.env.RECOMMENDATION_SERVICE_BASE_URL;

export const unisonBaseUrl = process.env.UNISON_BASE_URL;

export const unisonApiKey = process.env.UNISON_API_KEY;
export const recommendationServiceApiKey =
process.env.RECOMMENDATION_SERVICE_API_KEY;

export const enableReuseTriggerOption = env(
"NEXT_PUBLIC_ENABLE_REUSE_TRIGGER_OPTION"
Expand All @@ -22,7 +22,7 @@ export const enableStoredRecommendation = env(
);

export const recommendationServiceName = env(
"NEXT_PUBLIC_RECOMMENDATION_SERVICE_NAME"
"NEXT_PUBLIC_RECOMMENDATION_SERVICE"
);

// Re-export MAX_FILE_SIZE_BYTES from config.js
Expand Down
4 changes: 2 additions & 2 deletions app/next-client-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions app/next-client-app/types/recommendation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface UnisonConceptItem {
interface RecommendationItem {
accuracy: number | null;
conceptId: number;
conceptName: string;
Expand All @@ -7,11 +7,21 @@ interface UnisonConceptItem {
domain: string;
conceptClass: string;
explanation: string;
standardConcept?: string;
scores?: { "vector-search": number };
}

interface UnisonConceptResponse {
items: UnisonConceptItem[];
count: number;
interface RecommendationMetadata {
assistant: string;
version: string;
pipeline: string;
info: string | null;
}

interface RecommendationServiceResponse {
items: RecommendationItem[];
count?: number;
metadata?: RecommendationMetadata;
}

interface MappingRecommendation {
Expand All @@ -23,4 +33,4 @@ interface MappingRecommendation {
tool_name: string;
tool_version: string;
created_at: Date;
}
}
7 changes: 3 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,9 @@ services:
- WATCHPACK_POLLING=true
- NEXT_PUBLIC_ENABLE_REUSE_TRIGGER_OPTION=true
- NEXT_PUBLIC_ENABLE_AI_RECOMMENDATION=true
- NEXT_PUBLIC_RECOMMENDATION_SERVICE_NAME=unison
- RECOMMENDATION_SERVICE=unison
- UNISON_API_KEY=unison-api-key
- UNISON_BASE_URL=https://api.hyperunison.com/api/public/suggester/generate
- NEXT_PUBLIC_RECOMMENDATION_SERVICE=unison
- RECOMMENDATION_SERVICE_BASE_URL=https://api.hyperunison.com/api/public/suggester/generate
- RECOMMENDATION_SERVICE_API_KEY=unison-api-key
- NEXT_PUBLIC_BODY_SIZE_LIMIT=31457280
volumes:
- ./app/next-client-app:/app
Expand Down