diff --git a/docker-compose.yml b/docker-compose.yml index 73fd5bf..4f9c7b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - REDIS_URL=redis://redis:6379 - REDIS_DB=0 - SSL_ENABLED=true +# To use Anthropic, uncomment the following lines and comment out the OpenAI lines # - DEFAULT_LLM_PROVIDER=anthropic # - MODEL_NAME=claude-3-5-haiku-latest - DEFAULT_LLM_PROVIDER=openai diff --git a/frontend/src/components/JobMatchAnalysis.tsx b/frontend/src/components/JobMatchAnalysis.tsx index 32cfda0..8253332 100644 --- a/frontend/src/components/JobMatchAnalysis.tsx +++ b/frontend/src/components/JobMatchAnalysis.tsx @@ -15,10 +15,13 @@ import { Button, Paper, SxProps, + Tooltip, + IconButton, } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import ErrorIcon from '@mui/icons-material/Error'; +import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; import PendingIcon from '@mui/icons-material/Pending'; import WarningIcon from '@mui/icons-material/Warning'; import { Candidate, SkillAssessment, SkillStatus } from 'types/types'; @@ -261,7 +264,8 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = const skillMatchHandlers = useMemo(() => { return { onStatus: (status: Types.ChatMessageStatus): void => { - setMatchStatus(status.content.toLowerCase()); + console.log('Skill Match Status:', status.content); + setMatchStatus(status.content); }, }; }, [setMatchStatus]); @@ -305,6 +309,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = const request = await apiClient.candidateMatchForRequirement( candidate.id || '', requirements[i].requirement, + false, skillMatchHandlers ); const result = await request.promise; /* Wait for the streaming result to complete */ @@ -410,6 +415,64 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = return ; }; + const handleAssesmentRegenerate = async (index: number, skill: string): Promise => { + setSkillMatches(prev => { + const updated = [...prev]; + updated[index] = { + ...updated[index], + status: 'pending', + assessment: '', + evidenceDetails: [], + evidenceStrength: 'none', + matchScore: 0, + }; + return updated; + }); + setMatchStatus('Regenerating assessment...'); + const request = apiClient.candidateMatchForRequirement( + candidate.id || '', + skill, + true, + skillMatchHandlers + ); + const result = await request.promise; /* Wait for the streaming result to complete */ + const skillMatch = result.skillAssessment; + setMatchStatus(''); + setSkillMatches(prev => { + const updated = [...prev]; + updated[index] = { + ...skillMatch, + status: 'complete', + matchScore: calculateScore(skillMatch), + domain: updated[index].domain, + }; + return updated; + }); + // Update overall score + setSkillMatches(current => { + const completedMatches = current.filter(match => match.status === 'complete'); + if (completedMatches.length > 0) { + const newOverallScore = + completedMatches.reduce((sum, match) => sum + match.matchScore, 0) / + completedMatches.length; + setOverallScore(Math.round(newOverallScore)); + } + return current; + }); + setAnalysis({ + score: overallScore, + skills: skillMatches, + }); + onAnalysisComplete && + onAnalysisComplete({ + score: overallScore, + skills: skillMatches, + }); + setStartAnalysis(false); + setAnalyzing(false); + setFirstRun(false); + }; + const beginAnalysis = (): void => { initializeRequirements(job); setStartAnalysis(true); @@ -595,6 +658,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = bgcolor: getMatchColor(match.matchScore), color: 'white', minWidth: 90, + pointerEvents: 'none', }} /> ) : match.status === 'waiting' ? ( @@ -605,6 +669,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = bgcolor: 'rgb(189, 173, 85)', color: 'white', minWidth: 90, + pointerEvents: 'none', }} /> ) : match.status === 'pending' ? ( @@ -615,6 +680,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = bgcolor: theme.palette.grey[400], color: 'white', minWidth: 90, + pointerEvents: 'none', }} /> ) : ( @@ -625,6 +691,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = bgcolor: theme.palette.error.main, color: 'white', minWidth: 90, + pointerEvents: 'none', }} /> )} @@ -709,6 +776,19 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = Skill description {match.description} + {match.status === 'complete' && ( + + { + e.stopPropagation(); + handleAssesmentRegenerate(index, match.skill); + }} + > + + + + )} {/* { match.ragResults && match.ragResults.length !== 0 && <> RAG Information diff --git a/frontend/src/components/Message.tsx b/frontend/src/components/Message.tsx index 09067ac..7190138 100644 --- a/frontend/src/components/Message.tsx +++ b/frontend/src/components/Message.tsx @@ -211,11 +211,14 @@ const MessageMeta = (props: MessageMetaProps): JSX.Element => { /* MessageData */ ragResults = [], tools = null, + } = props.metadata || {}; + const { evalCount = 0, evalDuration = 0, promptEvalCount = 0, promptEvalDuration = 0, - } = props.metadata || {}; + } = props.metadata.usage || {}; + const message: ChatMessage | ChatMessageError | ChatMessageStatus = props.messageProps.message; // let llm_submission = '<|system|>\n'; diff --git a/frontend/src/components/ui/JobsView.tsx b/frontend/src/components/ui/JobsView.tsx index 4da9b77..2453804 100644 --- a/frontend/src/components/ui/JobsView.tsx +++ b/frontend/src/components/ui/JobsView.tsx @@ -418,6 +418,7 @@ const JobsView: React.FC = ({ Description + Pay Range {!isMobile && ( <> Owner @@ -499,6 +500,10 @@ const JobsView: React.FC = ({ {truncateDescription(job.summary || job.description || '', 100)} + + {job.payMinimum ? job.payMinimum : 'N/A'}- + {job.payMaximum ? job.payMaximum : 'N/A'} + {!isMobile && ( <> diff --git a/frontend/src/components/ui/TransientChat.tsx b/frontend/src/components/ui/TransientChat.tsx index c88fa00..5931dbe 100644 --- a/frontend/src/components/ui/TransientChat.tsx +++ b/frontend/src/components/ui/TransientChat.tsx @@ -26,10 +26,12 @@ const emptyMetadata: ChatMessageMetaData = { frequencyPenalty: 0, presencePenalty: 0, stopSequences: [], - evalCount: 0, - evalDuration: 0, - promptEvalCount: 0, - promptEvalDuration: 0, + usage: { + evalCount: 0, + evalDuration: 0, + promptEvalCount: 0, + promptEvalDuration: 0, + }, }; const defaultMessage: ChatMessage = { diff --git a/frontend/src/pages/CandidateChatPage.tsx b/frontend/src/pages/CandidateChatPage.tsx index 5316724..0de6257 100644 --- a/frontend/src/pages/CandidateChatPage.tsx +++ b/frontend/src/pages/CandidateChatPage.tsx @@ -32,10 +32,12 @@ const emptyMetadata: ChatMessageMetaData = { frequencyPenalty: 0, presencePenalty: 0, stopSequences: [], - evalCount: 0, - evalDuration: 0, - promptEvalCount: 0, - promptEvalDuration: 0, + usage: { + evalCount: 0, + evalDuration: 0, + promptEvalCount: 0, + promptEvalDuration: 0, + }, }; const defaultMessage: ChatMessage = { diff --git a/frontend/src/pages/GenerateCandidate.tsx b/frontend/src/pages/GenerateCandidate.tsx index 60d2a44..5a6a165 100644 --- a/frontend/src/pages/GenerateCandidate.tsx +++ b/frontend/src/pages/GenerateCandidate.tsx @@ -38,10 +38,12 @@ const emptyMetadata: ChatMessageMetaData = { frequencyPenalty: 0, presencePenalty: 0, stopSequences: [], - evalCount: 0, - evalDuration: 0, - promptEvalCount: 0, - promptEvalDuration: 0, + usage: { + evalCount: 0, + evalDuration: 0, + promptEvalCount: 0, + promptEvalDuration: 0, + }, }; const defaultMessage: ChatMessage = { diff --git a/frontend/src/services/api-client.ts b/frontend/src/services/api-client.ts index 3ffae80..0258ba2 100644 --- a/frontend/src/services/api-client.ts +++ b/frontend/src/services/api-client.ts @@ -1148,9 +1148,10 @@ class ApiClient { candidateMatchForRequirement( candidate_id: string, requirement: string, + regenerate: boolean = false, streamingOptions?: StreamingOptions ): StreamingResponse { - const body = JSON.stringify(requirement); + const body = JSON.stringify({ skill: requirement, regenerate }); streamingOptions = { ...streamingOptions, headers: this.defaultHeaders, diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 6ed432c..803fd51 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -1,6 +1,6 @@ // Generated TypeScript types from Pydantic models // Source: src/backend/models.py -// Generated on: 2025-07-09T20:57:47.233946 +// Generated on: 2025-07-11T20:02:47.037054 // DO NOT EDIT MANUALLY - This file is auto-generated // ============================ @@ -323,10 +323,7 @@ export interface ChatMessageMetaData { stopSequences: Array; ragResults?: Array; llmHistory?: Array; - evalCount: number; - evalDuration: number; - promptEvalCount: number; - promptEvalDuration: number; + usage?: UsageStats; options?: ChatOptions; tools?: Tool; timers?: Record; @@ -416,6 +413,14 @@ export interface ChatQuery { agentOptions?: Record; } +export interface ChatResponse { + content: string; + model: string; + finishReason?: string; + usage?: UsageStats; + usage_dict?: Record; +} + export interface ChatSession { id?: string; userId?: string; @@ -714,6 +719,8 @@ export interface Job { title?: string; summary?: string; company?: string; + payMinimum?: string; + payMaximum?: string; description: string; requirements?: JobRequirements; createdAt?: Date; @@ -1051,6 +1058,11 @@ export interface SkillAssessment { matchScore: number; } +export interface SkillMatchRequest { + skill: string; + regenerate: boolean; +} + export interface SocialLink { platform: "linkedin" | "twitter" | "github" | "dribbble" | "behance" | "website" | "other"; url: string; @@ -1060,6 +1072,7 @@ export interface SystemInfo { installedRAM: string; graphicsCards: Array; CPU: string; + llmBackend: string; llmModel: string; embeddingModel: string; maxContextLength: number; @@ -1083,6 +1096,20 @@ export interface Tunables { enableContext: boolean; } +export interface UsageStats { + promptTokens?: number; + completionTokens?: number; + totalTokens?: number; + promptEvalCount?: number; + promptEvalDuration?: number; + evalCount?: number; + evalDuration?: number; + totalDuration?: number; + tokensPerSecond?: number; + promptTokensPerSecond?: number; + extraStats?: Record; +} + export interface UserActivity { id?: string; userId?: string;