Job analysis in flight
This commit is contained in:
parent
05c53653ed
commit
cb97cabfc3
@ -39,6 +39,8 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { BackstoryPageProps } from 'components/BackstoryTab';
|
import { BackstoryPageProps } from 'components/BackstoryTab';
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
||||||
|
import { CandidateInfo } from 'components/CandidateInfo';
|
||||||
|
import { ComingSoon } from 'components/ui/ComingSoon';
|
||||||
|
|
||||||
// Main component
|
// Main component
|
||||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||||
@ -93,32 +95,14 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
}, [selectedCandidate, activeStep]);
|
}, [selectedCandidate, activeStep]);
|
||||||
|
|
||||||
// Steps in our process
|
// Steps in our process
|
||||||
const steps = selectedCandidate === null ? [
|
const steps = [
|
||||||
{ index: 0, label: 'Select Candidate', icon: <PersonIcon /> },
|
|
||||||
{ index: 1, label: 'Job Description', icon: <WorkIcon /> },
|
{ index: 1, label: 'Job Description', icon: <WorkIcon /> },
|
||||||
{ index: 2, label: 'View Analysis', icon: <AssessmentIcon /> }
|
{ index: 2, label: 'AI Analysis', icon: <WorkIcon /> },
|
||||||
] : [
|
{ index: 3, label: 'Generated Resume', icon: <AssessmentIcon /> }
|
||||||
{ index: 1, label: 'Job Description', icon: <WorkIcon /> },
|
|
||||||
{ index: 2, label: 'View Analysis', icon: <AssessmentIcon /> }
|
|
||||||
];
|
];
|
||||||
|
if (!selectedCandidate) {
|
||||||
// Mock handlers for our analysis APIs
|
steps.unshift({ index: 0, label: 'Select Candidate', icon: <PersonIcon /> })
|
||||||
const fetchRequirements = async (): Promise<string[]> => {
|
}
|
||||||
// Simulates extracting requirements from the job description
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
// This would normally parse the job description to extract requirements
|
|
||||||
const mockRequirements = [
|
|
||||||
"5+ years of React development experience",
|
|
||||||
"Strong TypeScript skills",
|
|
||||||
"Experience with RESTful APIs",
|
|
||||||
"Knowledge of state management solutions (Redux, Context API)",
|
|
||||||
"Experience with CI/CD pipelines",
|
|
||||||
"Cloud platform experience (AWS, Azure, GCP)"
|
|
||||||
];
|
|
||||||
|
|
||||||
return mockRequirements;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchMatchForRequirement = async (requirement: string): Promise<any> => {
|
const fetchMatchForRequirement = async (requirement: string): Promise<any> => {
|
||||||
// Create different mock responses based on the requirement
|
// Create different mock responses based on the requirement
|
||||||
@ -245,10 +229,12 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeStep === 1 && (/*(extraInfo && !jobTitle) || */!jobDescription)) {
|
if (activeStep === 1) {
|
||||||
setError('Please provide both job title and description before continuing.');
|
if ((/*(extraInfo && !jobTitle) || */!jobDescription)) {
|
||||||
|
setError('Please provide job description before continuing.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (activeStep === 2) {
|
if (activeStep === 2) {
|
||||||
setAnalysisStarted(true);
|
setAnalysisStarted(true);
|
||||||
@ -433,15 +419,19 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
{selectedCandidate && (
|
{selectedCandidate && (
|
||||||
<JobMatchAnalysis
|
<JobMatchAnalysis
|
||||||
jobTitle={jobTitle}
|
job={{ title: jobTitle, description: jobDescription }}
|
||||||
candidateName={selectedCandidate.fullName}
|
candidate={selectedCandidate}
|
||||||
fetchRequirements={fetchRequirements}
|
|
||||||
fetchMatchForRequirement={fetchMatchForRequirement}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderResume = () => (
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
{selectedCandidate && <ComingSoon>Resume Builder</ComingSoon>}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
// If no user is logged in, show message
|
// If no user is logged in, show message
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
@ -464,6 +454,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
Candidate Analysis
|
Candidate Analysis
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{selectedCandidate && <CandidateInfo variant="small" candidate={selectedCandidate} />}
|
||||||
<Typography variant="subtitle1" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle1" color="text.secondary" gutterBottom>
|
||||||
Match candidates to job requirements with AI-powered analysis
|
Match candidates to job requirements with AI-powered analysis
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -496,6 +487,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
{activeStep === 0 && renderCandidateSelection()}
|
{activeStep === 0 && renderCandidateSelection()}
|
||||||
{activeStep === 1 && renderJobDescription()}
|
{activeStep === 1 && renderJobDescription()}
|
||||||
{activeStep === 2 && renderAnalysis()}
|
{activeStep === 2 && renderAnalysis()}
|
||||||
|
{activeStep === 3 && renderResume()}
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Generated TypeScript types from Pydantic models
|
// Generated TypeScript types from Pydantic models
|
||||||
// Source: src/backend/models.py
|
// Source: src/backend/models.py
|
||||||
// Generated on: 2025-06-03T18:51:32.304683
|
// Generated on: 2025-06-03T23:59:28.355326
|
||||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
// DO NOT EDIT MANUALLY - This file is auto-generated
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
@ -13,13 +13,13 @@ export type ActivityType = "login" | "search" | "view_job" | "apply_job" | "mess
|
|||||||
|
|
||||||
export type ApplicationStatus = "applied" | "reviewing" | "interview" | "offer" | "rejected" | "accepted" | "withdrawn";
|
export type ApplicationStatus = "applied" | "reviewing" | "interview" | "offer" | "rejected" | "accepted" | "withdrawn";
|
||||||
|
|
||||||
export type ChatContextType = "job_search" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_image" | "rag_search";
|
export type ChatContextType = "job_search" | "job_requirements" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_image" | "rag_search";
|
||||||
|
|
||||||
export type ChatMessageType = "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
export type ChatMessageType = "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
||||||
|
|
||||||
export type ChatSenderType = "user" | "assistant" | "system";
|
export type ChatSenderType = "user" | "assistant" | "system";
|
||||||
|
|
||||||
export type ChatStatusType = "initializing" | "streaming" | "done" | "error";
|
export type ChatStatusType = "initializing" | "streaming" | "status" | "done" | "error";
|
||||||
|
|
||||||
export type ColorBlindMode = "protanopia" | "deuteranopia" | "tritanopia" | "none";
|
export type ColorBlindMode = "protanopia" | "deuteranopia" | "tritanopia" | "none";
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ export interface Certification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatContext {
|
export interface ChatContext {
|
||||||
type: "job_search" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_image" | "rag_search";
|
type: "job_search" | "job_requirements" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_image" | "rag_search";
|
||||||
relatedEntityId?: string;
|
relatedEntityId?: string;
|
||||||
relatedEntityType?: "job" | "candidate" | "employer";
|
relatedEntityType?: "job" | "candidate" | "employer";
|
||||||
additionalContext?: Record<string, any>;
|
additionalContext?: Record<string, any>;
|
||||||
@ -282,10 +282,10 @@ export interface ChatMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: "initializing" | "streaming" | "done" | "error";
|
status: "initializing" | "streaming" | "status" | "done" | "error";
|
||||||
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
||||||
sender: "user" | "assistant" | "system";
|
sender: "user" | "assistant" | "system";
|
||||||
timestamp: Date;
|
timestamp?: Date;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
content: string;
|
content: string;
|
||||||
metadata?: ChatMessageMetaData;
|
metadata?: ChatMessageMetaData;
|
||||||
@ -295,16 +295,16 @@ export interface ChatMessageBase {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: "initializing" | "streaming" | "done" | "error";
|
status: "initializing" | "streaming" | "status" | "done" | "error";
|
||||||
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
||||||
sender: "user" | "assistant" | "system";
|
sender: "user" | "assistant" | "system";
|
||||||
timestamp: Date;
|
timestamp?: Date;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatMessageMetaData {
|
export interface ChatMessageMetaData {
|
||||||
model: "qwen2.5";
|
model: "qwen2.5" | "flux-schnell";
|
||||||
temperature: number;
|
temperature: number;
|
||||||
maxTokens: number;
|
maxTokens: number;
|
||||||
topP: number;
|
topP: number;
|
||||||
@ -326,10 +326,10 @@ export interface ChatMessageRagSearch {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: "done";
|
status: "initializing" | "streaming" | "status" | "done" | "error";
|
||||||
type: "rag_result";
|
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
||||||
sender: "user";
|
sender: "user" | "assistant" | "system";
|
||||||
timestamp: Date;
|
timestamp?: Date;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
content: string;
|
content: string;
|
||||||
dimensions: number;
|
dimensions: number;
|
||||||
@ -339,10 +339,10 @@ export interface ChatMessageUser {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: "done";
|
status: "initializing" | "streaming" | "status" | "done" | "error";
|
||||||
type: "user";
|
type: "error" | "generating" | "info" | "preparing" | "processing" | "heartbeat" | "response" | "searching" | "rag_result" | "system" | "thinking" | "tooling" | "user";
|
||||||
sender: "user";
|
sender: "user" | "assistant" | "system";
|
||||||
timestamp: Date;
|
timestamp?: Date;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
@ -857,6 +857,233 @@ export interface WorkExperience {
|
|||||||
achievements?: Array<string>;
|
achievements?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// Default Objects
|
||||||
|
// ============================
|
||||||
|
|
||||||
|
// These objects contain the default values from your Pydantic models
|
||||||
|
// Use them to initialize objects with sensible defaults:
|
||||||
|
// const message: ChatMessage = { ...DefaultChatMessage, sessionId: '123', content: 'Hello' };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for BaseUser
|
||||||
|
* Fields with defaults: isAdmin
|
||||||
|
*/
|
||||||
|
export const DefaultBaseUser: Partial<BaseUser> = {
|
||||||
|
isAdmin: False
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for BaseUserWithType
|
||||||
|
* Fields with defaults: isAdmin
|
||||||
|
*/
|
||||||
|
export const DefaultBaseUserWithType: Partial<BaseUserWithType> = {
|
||||||
|
isAdmin: False
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for Candidate
|
||||||
|
* Fields with defaults: isAdmin, userType, ragContentSize
|
||||||
|
*/
|
||||||
|
export const DefaultCandidate: Partial<Candidate> = {
|
||||||
|
isAdmin: False,
|
||||||
|
userType: "candidate",
|
||||||
|
ragContentSize: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for CandidateAI
|
||||||
|
* Fields with defaults: isAdmin, userType, ragContentSize, isAI
|
||||||
|
*/
|
||||||
|
export const DefaultCandidateAI: Partial<CandidateAI> = {
|
||||||
|
isAdmin: False,
|
||||||
|
userType: "candidate",
|
||||||
|
ragContentSize: 0,
|
||||||
|
isAI: True
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatContext
|
||||||
|
* Fields with defaults: additionalContext
|
||||||
|
*/
|
||||||
|
export const DefaultChatContext: Partial<ChatContext> = {
|
||||||
|
additionalContext: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatMessage
|
||||||
|
* Fields with defaults: status, type, sender, content
|
||||||
|
*/
|
||||||
|
export const DefaultChatMessage: Partial<ChatMessage> = {
|
||||||
|
status: "initializing",
|
||||||
|
type: "preparing",
|
||||||
|
sender: "system",
|
||||||
|
content: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatMessageBase
|
||||||
|
* Fields with defaults: status, type, sender, content
|
||||||
|
*/
|
||||||
|
export const DefaultChatMessageBase: Partial<ChatMessageBase> = {
|
||||||
|
status: "initializing",
|
||||||
|
type: "preparing",
|
||||||
|
sender: "system",
|
||||||
|
content: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatMessageMetaData
|
||||||
|
* Fields with defaults: model, temperature, maxTokens, topP, evalCount, evalDuration, promptEvalCount, promptEvalDuration
|
||||||
|
*/
|
||||||
|
export const DefaultChatMessageMetaData: Partial<ChatMessageMetaData> = {
|
||||||
|
model: "qwen2.5",
|
||||||
|
temperature: 0.7,
|
||||||
|
maxTokens: 8092,
|
||||||
|
topP: 1,
|
||||||
|
evalCount: 0,
|
||||||
|
evalDuration: 0,
|
||||||
|
promptEvalCount: 0,
|
||||||
|
promptEvalDuration: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatMessageRagSearch
|
||||||
|
* Fields with defaults: status, type, sender, content, dimensions
|
||||||
|
*/
|
||||||
|
export const DefaultChatMessageRagSearch: Partial<ChatMessageRagSearch> = {
|
||||||
|
status: "done",
|
||||||
|
type: "rag_result",
|
||||||
|
sender: "user",
|
||||||
|
content: "",
|
||||||
|
dimensions: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatMessageUser
|
||||||
|
* Fields with defaults: status, type, sender, content
|
||||||
|
*/
|
||||||
|
export const DefaultChatMessageUser: Partial<ChatMessageUser> = {
|
||||||
|
status: "done",
|
||||||
|
type: "user",
|
||||||
|
sender: "user",
|
||||||
|
content: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatOptions
|
||||||
|
* Fields with defaults: seed, temperature
|
||||||
|
*/
|
||||||
|
export const DefaultChatOptions: Partial<ChatOptions> = {
|
||||||
|
seed: 8911,
|
||||||
|
temperature: 0.7
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChatSession
|
||||||
|
* Fields with defaults: isArchived
|
||||||
|
*/
|
||||||
|
export const DefaultChatSession: Partial<ChatSession> = {
|
||||||
|
isArchived: False
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ChromaDBGetResponse
|
||||||
|
* Fields with defaults: ids, embeddings, documents, metadatas, distances, name, size, dimensions, query
|
||||||
|
*/
|
||||||
|
export const DefaultChromaDBGetResponse: Partial<ChromaDBGetResponse> = {
|
||||||
|
ids: [],
|
||||||
|
embeddings: [],
|
||||||
|
documents: [],
|
||||||
|
metadatas: [],
|
||||||
|
distances: [],
|
||||||
|
name: "",
|
||||||
|
size: 0,
|
||||||
|
dimensions: 3,
|
||||||
|
query: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for Document
|
||||||
|
* Fields with defaults: includeInRAG, ragChunks
|
||||||
|
*/
|
||||||
|
export const DefaultDocument: Partial<Document> = {
|
||||||
|
includeInRAG: True,
|
||||||
|
ragChunks: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for Employer
|
||||||
|
* Fields with defaults: isAdmin, userType
|
||||||
|
*/
|
||||||
|
export const DefaultEmployer: Partial<Employer> = {
|
||||||
|
isAdmin: False,
|
||||||
|
userType: "employer"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for Job
|
||||||
|
* Fields with defaults: views, applicationCount
|
||||||
|
*/
|
||||||
|
export const DefaultJob: Partial<Job> = {
|
||||||
|
views: 0,
|
||||||
|
applicationCount: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for LLMMessage
|
||||||
|
* Fields with defaults: role, content, toolCalls
|
||||||
|
*/
|
||||||
|
export const DefaultLLMMessage: Partial<LLMMessage> = {
|
||||||
|
role: "",
|
||||||
|
content: "",
|
||||||
|
toolCalls: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for MFAVerifyRequest
|
||||||
|
* Fields with defaults: rememberDevice
|
||||||
|
*/
|
||||||
|
export const DefaultMFAVerifyRequest: Partial<MFAVerifyRequest> = {
|
||||||
|
rememberDevice: False
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for PaginatedRequest
|
||||||
|
* Fields with defaults: page, limit
|
||||||
|
*/
|
||||||
|
export const DefaultPaginatedRequest: Partial<PaginatedRequest> = {
|
||||||
|
page: 1,
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for RagEntry
|
||||||
|
* Fields with defaults: description, enabled
|
||||||
|
*/
|
||||||
|
export const DefaultRagEntry: Partial<RagEntry> = {
|
||||||
|
description: "",
|
||||||
|
enabled: True
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for SearchQuery
|
||||||
|
* Fields with defaults: page, limit
|
||||||
|
*/
|
||||||
|
export const DefaultSearchQuery: Partial<SearchQuery> = {
|
||||||
|
page: 1,
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for Tunables
|
||||||
|
* Fields with defaults: enableRAG, enableTools, enableContext
|
||||||
|
*/
|
||||||
|
export const DefaultTunables: Partial<Tunables> = {
|
||||||
|
enableRAG: True,
|
||||||
|
enableTools: True,
|
||||||
|
enableContext: True
|
||||||
|
};
|
||||||
// ============================
|
// ============================
|
||||||
// Date Conversion Functions
|
// Date Conversion Functions
|
||||||
// ============================
|
// ============================
|
||||||
@ -1017,7 +1244,7 @@ export function convertChatMessageFromApi(data: any): ChatMessage {
|
|||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
// Convert timestamp from ISO string to Date
|
// Convert timestamp from ISO string to Date
|
||||||
timestamp: new Date(data.timestamp),
|
timestamp: data.timestamp ? new Date(data.timestamp) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1030,7 +1257,7 @@ export function convertChatMessageBaseFromApi(data: any): ChatMessageBase {
|
|||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
// Convert timestamp from ISO string to Date
|
// Convert timestamp from ISO string to Date
|
||||||
timestamp: new Date(data.timestamp),
|
timestamp: data.timestamp ? new Date(data.timestamp) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1043,7 +1270,7 @@ export function convertChatMessageRagSearchFromApi(data: any): ChatMessageRagSea
|
|||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
// Convert timestamp from ISO string to Date
|
// Convert timestamp from ISO string to Date
|
||||||
timestamp: new Date(data.timestamp),
|
timestamp: data.timestamp ? new Date(data.timestamp) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1056,7 +1283,7 @@ export function convertChatMessageUserFromApi(data: any): ChatMessageUser {
|
|||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
// Convert timestamp from ISO string to Date
|
// Convert timestamp from ISO string to Date
|
||||||
timestamp: new Date(data.timestamp),
|
timestamp: data.timestamp ? new Date(data.timestamp) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -138,10 +138,14 @@ def is_date_type(python_type: Any) -> bool:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_default_enum_value(field_info: Any, debug: bool = False) -> Optional[Any]:
|
def get_field_default_value(field_info: Any, debug: bool = False) -> tuple[bool, Any]:
|
||||||
"""Extract the specific enum value from a field's default, if it exists"""
|
"""Extract the default value from a field, if it exists
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (has_default, default_value)
|
||||||
|
"""
|
||||||
if not hasattr(field_info, 'default'):
|
if not hasattr(field_info, 'default'):
|
||||||
return None
|
return False, None
|
||||||
|
|
||||||
default_val = field_info.default
|
default_val = field_info.default
|
||||||
|
|
||||||
@ -152,7 +156,7 @@ def get_default_enum_value(field_info: Any, debug: bool = False) -> Optional[Any
|
|||||||
if default_val is ... or default_val is None:
|
if default_val is ... or default_val is None:
|
||||||
if debug:
|
if debug:
|
||||||
print(f" └─ Default is undefined marker")
|
print(f" └─ Default is undefined marker")
|
||||||
return None
|
return False, None
|
||||||
|
|
||||||
# Check for Pydantic's internal "PydanticUndefined" or similar markers
|
# Check for Pydantic's internal "PydanticUndefined" or similar markers
|
||||||
default_str = str(default_val)
|
default_str = str(default_val)
|
||||||
@ -173,17 +177,72 @@ def get_default_enum_value(field_info: Any, debug: bool = False) -> Optional[Any
|
|||||||
if is_undefined_marker:
|
if is_undefined_marker:
|
||||||
if debug:
|
if debug:
|
||||||
print(f" └─ Default is undefined marker pattern")
|
print(f" └─ Default is undefined marker pattern")
|
||||||
return None
|
return False, None
|
||||||
|
|
||||||
# Check if it's an enum instance
|
# We have a real default value
|
||||||
|
if debug:
|
||||||
|
print(f" └─ Has real default value: {repr(default_val)}")
|
||||||
|
return True, default_val
|
||||||
|
|
||||||
|
def convert_default_to_typescript(default_val: Any, debug: bool = False) -> str:
|
||||||
|
"""Convert a Python default value to TypeScript literal"""
|
||||||
|
if debug:
|
||||||
|
print(f" 🔄 Converting default: {repr(default_val)} (type: {type(default_val)})")
|
||||||
|
|
||||||
|
# Handle None
|
||||||
|
if default_val is None:
|
||||||
|
return "undefined"
|
||||||
|
|
||||||
|
# Handle Enum instances
|
||||||
if isinstance(default_val, Enum):
|
if isinstance(default_val, Enum):
|
||||||
if debug:
|
return f'"{default_val.value}"'
|
||||||
print(f" └─ Default is enum instance: {default_val.value}")
|
|
||||||
return default_val
|
|
||||||
|
|
||||||
|
# Handle basic types
|
||||||
|
if isinstance(default_val, str):
|
||||||
|
# Escape quotes and special characters
|
||||||
|
escaped = default_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
|
return f'"{escaped}"'
|
||||||
|
elif isinstance(default_val, (int, float)):
|
||||||
|
return str(default_val)
|
||||||
|
elif isinstance(default_val, bool):
|
||||||
|
return "true" if default_val else "false"
|
||||||
|
elif isinstance(default_val, list):
|
||||||
|
if not default_val: # Empty list
|
||||||
|
return "[]"
|
||||||
|
# For non-empty lists, convert each item
|
||||||
|
items = [convert_default_to_typescript(item, debug) for item in default_val]
|
||||||
|
return f"[{', '.join(items)}]"
|
||||||
|
elif isinstance(default_val, dict):
|
||||||
|
if not default_val: # Empty dict
|
||||||
|
return "{}"
|
||||||
|
# For non-empty dicts, convert each key-value pair
|
||||||
|
items = []
|
||||||
|
for key, value in default_val.items():
|
||||||
|
key_str = f'"{key}"' if isinstance(key, str) else str(key)
|
||||||
|
value_str = convert_default_to_typescript(value, debug)
|
||||||
|
items.append(f"{key_str}: {value_str}")
|
||||||
|
return f"{{{', '.join(items)}}}"
|
||||||
|
elif isinstance(default_val, datetime):
|
||||||
|
# Convert datetime to ISO string, then wrap in new Date()
|
||||||
|
iso_string = default_val.isoformat()
|
||||||
|
return f'new Date("{iso_string}")'
|
||||||
|
|
||||||
|
# For other types, try to convert to string
|
||||||
if debug:
|
if debug:
|
||||||
print(f" └─ Default is not an enum instance")
|
print(f" ⚠️ Unknown default type, converting to string: {type(default_val)}")
|
||||||
return None
|
|
||||||
|
# Try to convert to a reasonable TypeScript representation
|
||||||
|
try:
|
||||||
|
if hasattr(default_val, '__dict__'):
|
||||||
|
# It's an object, try to serialize its properties
|
||||||
|
return "{}" # Fallback to empty object for complex types
|
||||||
|
else:
|
||||||
|
# Try string conversion
|
||||||
|
str_val = str(default_val)
|
||||||
|
escaped = str_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
|
return f'"{escaped}"'
|
||||||
|
except:
|
||||||
|
return "undefined"
|
||||||
|
|
||||||
def python_type_to_typescript(python_type: Any, field_info: Any = None, debug: bool = False) -> str:
|
def python_type_to_typescript(python_type: Any, field_info: Any = None, debug: bool = False) -> str:
|
||||||
"""Convert a Python type to TypeScript type string, considering field defaults"""
|
"""Convert a Python type to TypeScript type string, considering field defaults"""
|
||||||
@ -198,13 +257,9 @@ def python_type_to_typescript(python_type: Any, field_info: Any = None, debug: b
|
|||||||
if debug and original_type != python_type:
|
if debug and original_type != python_type:
|
||||||
print(f" 🔄 Unwrapped: {original_type} -> {python_type}")
|
print(f" 🔄 Unwrapped: {original_type} -> {python_type}")
|
||||||
|
|
||||||
# Check if this field has a specific enum default value
|
# REMOVED: The problematic enum default checking that returns only the default value
|
||||||
if field_info:
|
# This was causing the issue where enum fields would only show the default value
|
||||||
default_enum = get_default_enum_value(field_info, debug)
|
# instead of all possible enum values
|
||||||
if default_enum is not None:
|
|
||||||
if debug:
|
|
||||||
print(f" 🎯 Field has specific enum default: {default_enum.value}")
|
|
||||||
return f'"{default_enum.value}"'
|
|
||||||
|
|
||||||
# Handle None/null
|
# Handle None/null
|
||||||
if python_type is type(None):
|
if python_type is type(None):
|
||||||
@ -268,9 +323,12 @@ def python_type_to_typescript(python_type: Any, field_info: Any = None, debug: b
|
|||||||
literal_values.append(str(arg))
|
literal_values.append(str(arg))
|
||||||
return " | ".join(literal_values)
|
return " | ".join(literal_values)
|
||||||
|
|
||||||
# Handle Enum types
|
# Handle Enum types - THIS IS THE CORRECT BEHAVIOR
|
||||||
|
# Return all possible enum values, not just the default
|
||||||
if isinstance(python_type, type) and issubclass(python_type, Enum):
|
if isinstance(python_type, type) and issubclass(python_type, Enum):
|
||||||
enum_values = [f'"{v.value}"' for v in python_type]
|
enum_values = [f'"{v.value}"' for v in python_type]
|
||||||
|
if debug:
|
||||||
|
print(f" 🎯 Enum type detected: {python_type.__name__} with values: {enum_values}")
|
||||||
return " | ".join(enum_values)
|
return " | ".join(enum_values)
|
||||||
|
|
||||||
# Handle individual enum instances
|
# Handle individual enum instances
|
||||||
@ -375,18 +433,12 @@ def is_field_optional(field_info: Any, field_type: Any, debug: bool = False) ->
|
|||||||
print(f" └─ RESULT: Required (default is undefined marker)")
|
print(f" └─ RESULT: Required (default is undefined marker)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Special case: if field has a specific default value (like enum), it's required
|
# FIXED: Fields with actual default values (including enums) should be REQUIRED
|
||||||
# because it will always have a value, just not optional for the consumer
|
|
||||||
if isinstance(default_val, Enum):
|
|
||||||
if debug:
|
|
||||||
print(f" └─ RESULT: Required (has specific enum default: {default_val.value})")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# FIXED: Fields with actual default values (like [], "", 0) should be REQUIRED
|
|
||||||
# because they will always have a value (either provided or the default)
|
# because they will always have a value (either provided or the default)
|
||||||
|
# This applies to enum fields with defaults as well
|
||||||
if debug:
|
if debug:
|
||||||
print(f" └─ RESULT: Required (has actual default value - field will always have a value)")
|
print(f" └─ RESULT: Required (has actual default value - field will always have a value)")
|
||||||
return False # Changed from True to False
|
return False
|
||||||
else:
|
else:
|
||||||
if debug:
|
if debug:
|
||||||
print(f" └─ No default attribute found")
|
print(f" └─ No default attribute found")
|
||||||
@ -420,6 +472,7 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
interface_name = model_class.__name__
|
interface_name = model_class.__name__
|
||||||
properties = []
|
properties = []
|
||||||
date_fields = [] # Track date fields for conversion functions
|
date_fields = [] # Track date fields for conversion functions
|
||||||
|
default_fields = [] # Track fields with default values for default object generation
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
print(f" 🔍 Processing model: {interface_name}")
|
print(f" 🔍 Processing model: {interface_name}")
|
||||||
@ -445,6 +498,17 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
if debug:
|
if debug:
|
||||||
print(f" Raw type: {field_type}")
|
print(f" Raw type: {field_type}")
|
||||||
|
|
||||||
|
# Check for default values
|
||||||
|
has_default, default_value = get_field_default_value(field_info, debug)
|
||||||
|
if has_default:
|
||||||
|
ts_default = convert_default_to_typescript(default_value, debug)
|
||||||
|
default_fields.append({
|
||||||
|
'name': ts_name,
|
||||||
|
'value': ts_default
|
||||||
|
})
|
||||||
|
if debug:
|
||||||
|
print(f" 🎯 Default value: {repr(default_value)} -> {ts_default}")
|
||||||
|
|
||||||
# Check if this is a date field
|
# Check if this is a date field
|
||||||
is_date = is_date_type(field_type)
|
is_date = is_date_type(field_type)
|
||||||
if debug:
|
if debug:
|
||||||
@ -461,7 +525,7 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()):
|
elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()):
|
||||||
print(f" ⚠️ Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}")
|
print(f" ⚠️ Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}")
|
||||||
|
|
||||||
# Pass field_info to the type converter for default enum handling
|
# Pass field_info to the type converter (but now it won't override enum types)
|
||||||
ts_type = python_type_to_typescript(field_type, field_info, debug)
|
ts_type = python_type_to_typescript(field_type, field_info, debug)
|
||||||
|
|
||||||
# Check if optional
|
# Check if optional
|
||||||
@ -497,6 +561,17 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
if debug:
|
if debug:
|
||||||
print(f" Raw type: {field_type}")
|
print(f" Raw type: {field_type}")
|
||||||
|
|
||||||
|
# Check for default values
|
||||||
|
has_default, default_value = get_field_default_value(field_info, debug)
|
||||||
|
if has_default:
|
||||||
|
ts_default = convert_default_to_typescript(default_value, debug)
|
||||||
|
default_fields.append({
|
||||||
|
'name': ts_name,
|
||||||
|
'value': ts_default
|
||||||
|
})
|
||||||
|
if debug:
|
||||||
|
print(f" 🎯 Default value: {repr(default_value)} -> {ts_default}")
|
||||||
|
|
||||||
# Check if this is a date field
|
# Check if this is a date field
|
||||||
is_date = is_date_type(field_type)
|
is_date = is_date_type(field_type)
|
||||||
if debug:
|
if debug:
|
||||||
@ -513,7 +588,7 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()):
|
elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()):
|
||||||
print(f" ⚠️ Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}")
|
print(f" ⚠️ Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}")
|
||||||
|
|
||||||
# Pass field_info to the type converter for default enum handling
|
# Pass field_info to the type converter (but now it won't override enum types)
|
||||||
ts_type = python_type_to_typescript(field_type, field_info, debug)
|
ts_type = python_type_to_typescript(field_type, field_info, debug)
|
||||||
|
|
||||||
# For Pydantic v1, check required and default
|
# For Pydantic v1, check required and default
|
||||||
@ -534,7 +609,8 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]:
|
|||||||
return {
|
return {
|
||||||
'name': interface_name,
|
'name': interface_name,
|
||||||
'properties': properties,
|
'properties': properties,
|
||||||
'date_fields': date_fields
|
'date_fields': date_fields,
|
||||||
|
'default_fields': default_fields
|
||||||
}
|
}
|
||||||
|
|
||||||
def process_enum(enum_class) -> Dict[str, Any]:
|
def process_enum(enum_class) -> Dict[str, Any]:
|
||||||
@ -548,6 +624,159 @@ def process_enum(enum_class) -> Dict[str, Any]:
|
|||||||
'values': " | ".join(values)
|
'values': " | ".join(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def generate_default_objects(interfaces: List[Dict[str, Any]]) -> str:
|
||||||
|
"""Generate TypeScript default objects for models with default values"""
|
||||||
|
default_objects = []
|
||||||
|
|
||||||
|
for interface in interfaces:
|
||||||
|
interface_name = interface['name']
|
||||||
|
default_fields = interface.get('default_fields', [])
|
||||||
|
|
||||||
|
if not default_fields:
|
||||||
|
continue # Skip interfaces without default values
|
||||||
|
|
||||||
|
object_name = f"Default{interface_name}"
|
||||||
|
|
||||||
|
# Generate default object
|
||||||
|
obj_lines = [
|
||||||
|
f"/**",
|
||||||
|
f" * Default values for {interface_name}",
|
||||||
|
f" * Fields with defaults: {', '.join([f['name'] for f in default_fields])}",
|
||||||
|
f" */",
|
||||||
|
f"export const {object_name}: Partial<{interface_name}> = {{"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add default field values
|
||||||
|
for i, default_field in enumerate(default_fields):
|
||||||
|
field_name = default_field['name']
|
||||||
|
field_value = default_field['value']
|
||||||
|
|
||||||
|
# Add comma for all but the last field
|
||||||
|
comma = "," if i < len(default_fields) - 1 else ""
|
||||||
|
obj_lines.append(f" {field_name}: {field_value}{comma}")
|
||||||
|
|
||||||
|
obj_lines.append("};")
|
||||||
|
obj_lines.append("") # Empty line after each object
|
||||||
|
|
||||||
|
default_objects.append('\n'.join(obj_lines))
|
||||||
|
|
||||||
|
if not default_objects:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Generate the default objects section
|
||||||
|
result = [
|
||||||
|
"// ============================",
|
||||||
|
"// Default Objects",
|
||||||
|
"// ============================",
|
||||||
|
"",
|
||||||
|
"// These objects contain the default values from your Pydantic models",
|
||||||
|
"// Use them to initialize objects with sensible defaults:",
|
||||||
|
"// const message: ChatMessage = { ...DefaultChatMessage, sessionId: '123', content: 'Hello' };",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
result.extend(default_objects)
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
"""Generate TypeScript conversion functions for models with date fields"""
|
||||||
|
conversion_functions = []
|
||||||
|
|
||||||
|
for interface in interfaces:
|
||||||
|
interface_name = interface['name']
|
||||||
|
date_fields = interface.get('date_fields', [])
|
||||||
|
|
||||||
|
if not date_fields:
|
||||||
|
continue # Skip interfaces without date fields
|
||||||
|
|
||||||
|
function_name = f"convert{interface_name}FromApi"
|
||||||
|
|
||||||
|
# Generate function
|
||||||
|
func_lines = [
|
||||||
|
f"/**",
|
||||||
|
f" * Convert {interface_name} from API response, parsing date fields",
|
||||||
|
f" * Date fields: {', '.join([f['name'] for f in date_fields])}",
|
||||||
|
f" */",
|
||||||
|
f"export function {function_name}(data: any): {interface_name} {{",
|
||||||
|
f" if (!data) return data;",
|
||||||
|
f" ",
|
||||||
|
f" return {{",
|
||||||
|
f" ...data,"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add date field conversions with validation
|
||||||
|
for date_field in date_fields:
|
||||||
|
field_name = date_field['name']
|
||||||
|
is_optional = date_field['optional']
|
||||||
|
|
||||||
|
# Add a comment for clarity
|
||||||
|
func_lines.append(f" // Convert {field_name} from ISO string to Date")
|
||||||
|
|
||||||
|
if is_optional:
|
||||||
|
func_lines.append(f" {field_name}: data.{field_name} ? new Date(data.{field_name}) : undefined,")
|
||||||
|
else:
|
||||||
|
func_lines.append(f" {field_name}: new Date(data.{field_name}),")
|
||||||
|
|
||||||
|
func_lines.extend([
|
||||||
|
f" }};",
|
||||||
|
f"}}"
|
||||||
|
])
|
||||||
|
|
||||||
|
conversion_functions.append('\n'.join(func_lines))
|
||||||
|
|
||||||
|
if not conversion_functions:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Generate the conversion functions section
|
||||||
|
result = [
|
||||||
|
"// ============================",
|
||||||
|
"// Date Conversion Functions",
|
||||||
|
"// ============================",
|
||||||
|
"",
|
||||||
|
"// These functions convert API responses to properly typed objects",
|
||||||
|
"// with Date objects instead of ISO date strings",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
result.extend(conversion_functions)
|
||||||
|
result.append("")
|
||||||
|
|
||||||
|
# Generate a generic converter function
|
||||||
|
models_with_dates = [interface['name'] for interface in interfaces if interface.get('date_fields')]
|
||||||
|
|
||||||
|
if models_with_dates:
|
||||||
|
result.extend([
|
||||||
|
"/**",
|
||||||
|
" * Generic converter that automatically selects the right conversion function",
|
||||||
|
" * based on the model type",
|
||||||
|
" */",
|
||||||
|
"export function convertFromApi<T>(data: any, modelType: string): T {",
|
||||||
|
" if (!data) return data;",
|
||||||
|
" ",
|
||||||
|
" switch (modelType) {"
|
||||||
|
])
|
||||||
|
|
||||||
|
for model_name in models_with_dates:
|
||||||
|
result.append(f" case '{model_name}':")
|
||||||
|
result.append(f" return convert{model_name}FromApi(data) as T;")
|
||||||
|
|
||||||
|
result.extend([
|
||||||
|
" default:",
|
||||||
|
" return data as T;",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"/**",
|
||||||
|
" * Convert array of items using the appropriate converter",
|
||||||
|
" */",
|
||||||
|
"export function convertArrayFromApi<T>(data: any[], modelType: string): T[] {",
|
||||||
|
" if (!data || !Array.isArray(data)) return data;",
|
||||||
|
" return data.map(item => convertFromApi<T>(item, modelType));",
|
||||||
|
"}",
|
||||||
|
""
|
||||||
|
])
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str:
|
def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str:
|
||||||
"""Generate TypeScript conversion functions for models with date fields"""
|
"""Generate TypeScript conversion functions for models with date fields"""
|
||||||
conversion_functions = []
|
conversion_functions = []
|
||||||
@ -706,8 +935,10 @@ def generate_typescript_interfaces(source_file: str, debug: bool = False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
total_date_fields = sum(len(interface.get('date_fields', [])) for interface in interfaces)
|
total_date_fields = sum(len(interface.get('date_fields', [])) for interface in interfaces)
|
||||||
|
total_default_fields = sum(len(interface.get('default_fields', [])) for interface in interfaces)
|
||||||
print(f"\n📊 Found {len(interfaces)} interfaces and {len(enums)} enums")
|
print(f"\n📊 Found {len(interfaces)} interfaces and {len(enums)} enums")
|
||||||
print(f"🗓️ Found {total_date_fields} date fields across all models")
|
print(f"🗓️ Found {total_date_fields} date fields across all models")
|
||||||
|
print(f"🎯 Found {total_default_fields} fields with default values across all models")
|
||||||
|
|
||||||
# Generate TypeScript content
|
# Generate TypeScript content
|
||||||
ts_content = f"""// Generated TypeScript types from Pydantic models
|
ts_content = f"""// Generated TypeScript types from Pydantic models
|
||||||
@ -741,6 +972,11 @@ def generate_typescript_interfaces(source_file: str, debug: bool = False):
|
|||||||
|
|
||||||
ts_content += "}\n\n"
|
ts_content += "}\n\n"
|
||||||
|
|
||||||
|
# Add default objects
|
||||||
|
default_objects = generate_default_objects(interfaces)
|
||||||
|
if default_objects:
|
||||||
|
ts_content += default_objects
|
||||||
|
|
||||||
# Add conversion functions
|
# Add conversion functions
|
||||||
conversion_functions = generate_conversion_functions(interfaces)
|
conversion_functions = generate_conversion_functions(interfaces)
|
||||||
if conversion_functions:
|
if conversion_functions:
|
||||||
@ -780,7 +1016,7 @@ def compile_typescript(ts_file: str) -> bool:
|
|||||||
def main():
|
def main():
|
||||||
"""Main function with command line argument parsing"""
|
"""Main function with command line argument parsing"""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Generate TypeScript types from Pydantic models with date conversion functions and proper enum default handling',
|
description='Generate TypeScript types from Pydantic models with date conversion functions, default objects, and proper enum handling',
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog="""
|
epilog="""
|
||||||
Examples:
|
Examples:
|
||||||
@ -795,8 +1031,12 @@ Generated conversion functions can be used like:
|
|||||||
const candidate = convertCandidateFromApi(apiResponse);
|
const candidate = convertCandidateFromApi(apiResponse);
|
||||||
const jobs = convertArrayFromApi<Job>(apiResponse, 'Job');
|
const jobs = convertArrayFromApi<Job>(apiResponse, 'Job');
|
||||||
|
|
||||||
Enum defaults are now properly handled:
|
Generated default objects can be used like:
|
||||||
status: ChatStatusType = ChatStatusType.DONE -> status: "done"
|
const message: ChatMessage = { ...DefaultChatMessage, sessionId: '123', content: 'Hello' };
|
||||||
|
const overrideMessage: ChatMessage = { ...DefaultChatMessage, status: 'error' };
|
||||||
|
|
||||||
|
Enum fields now properly support all enum values:
|
||||||
|
status: ChatStatusType = ChatStatusType.DONE -> status: "pending" | "processing" | "done" | "error"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -833,12 +1073,12 @@ Enum defaults are now properly handled:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--version', '-v',
|
'--version', '-v',
|
||||||
action='version',
|
action='version',
|
||||||
version='TypeScript Generator 3.1 (with Enum Default Handling)'
|
version='TypeScript Generator 3.3 (with Default Objects and Fixed Enum Handling)'
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
print("🚀 Enhanced TypeScript Type Generator with Enum Default Handling")
|
print("🚀 Enhanced TypeScript Type Generator with Default Objects and Fixed Enum Handling")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print(f"📁 Source file: {args.source}")
|
print(f"📁 Source file: {args.source}")
|
||||||
print(f"📁 Output file: {args.output}")
|
print(f"📁 Output file: {args.output}")
|
||||||
@ -883,27 +1123,37 @@ Enum defaults are now properly handled:
|
|||||||
|
|
||||||
# Count conversion functions and provide detailed feedback
|
# Count conversion functions and provide detailed feedback
|
||||||
conversion_count = ts_content.count('export function convert') - ts_content.count('convertFromApi') - ts_content.count('convertArrayFromApi')
|
conversion_count = ts_content.count('export function convert') - ts_content.count('convertFromApi') - ts_content.count('convertArrayFromApi')
|
||||||
enum_specific_count = ts_content.count(': "') - ts_content.count('export type')
|
default_objects_count = ts_content.count('export const Default')
|
||||||
|
enum_union_count = ts_content.count(' | ')
|
||||||
|
|
||||||
if conversion_count > 0:
|
if conversion_count > 0:
|
||||||
print(f"🗓️ Generated {conversion_count} date conversion functions")
|
print(f"🗓️ Generated {conversion_count} date conversion functions")
|
||||||
if enum_specific_count > 0:
|
if default_objects_count > 0:
|
||||||
print(f"🎯 Generated {enum_specific_count} specific enum default types")
|
print(f"🎯 Generated {default_objects_count} default objects")
|
||||||
|
if enum_union_count > 0:
|
||||||
|
print(f"🔗 Generated {enum_union_count} union types (including proper enum types)")
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
# Show which models have date conversion
|
# Show which models have date conversion
|
||||||
models_with_dates = []
|
models_with_dates = []
|
||||||
|
models_with_defaults = []
|
||||||
for line in ts_content.split('\n'):
|
for line in ts_content.split('\n'):
|
||||||
if line.startswith('export function convert') and 'FromApi' in line and 'convertFromApi' not in line:
|
if line.startswith('export function convert') and 'FromApi' in line and 'convertFromApi' not in line:
|
||||||
model_name = line.split('convert')[1].split('FromApi')[0]
|
model_name = line.split('convert')[1].split('FromApi')[0]
|
||||||
models_with_dates.append(model_name)
|
models_with_dates.append(model_name)
|
||||||
|
elif line.startswith('export const Default'):
|
||||||
|
model_name = line.split('export const Default')[1].split(':')[0]
|
||||||
|
models_with_defaults.append(model_name)
|
||||||
|
|
||||||
if models_with_dates:
|
if models_with_dates:
|
||||||
print(f" Models with date conversion: {', '.join(models_with_dates)}")
|
print(f" Models with date conversion: {', '.join(models_with_dates)}")
|
||||||
|
if models_with_defaults:
|
||||||
|
print(f" Models with default objects: {', '.join(models_with_defaults)}")
|
||||||
|
|
||||||
# Provide troubleshooting info if debug mode
|
# Provide troubleshooting info if debug mode
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print(f"\n🐛 Debug mode was enabled. If you see incorrect type conversions:")
|
print(f"\n🐛 Debug mode was enabled. If you see incorrect type conversions:")
|
||||||
print(f" 1. Check the debug output above for '🎯 Field has specific enum default' lines")
|
print(f" 1. Look for '🎯 Enum type detected' lines to verify enum handling")
|
||||||
print(f" 2. Look for '📅 Date type check' lines for date handling")
|
print(f" 2. Look for '📅 Date type check' lines for date handling")
|
||||||
print(f" 3. Look for '⚠️' warnings about fallback types")
|
print(f" 3. Look for '⚠️' warnings about fallback types")
|
||||||
print(f" 4. Verify your Pydantic model field types and defaults are correct")
|
print(f" 4. Verify your Pydantic model field types and defaults are correct")
|
||||||
@ -924,18 +1174,23 @@ Enum defaults are now properly handled:
|
|||||||
print(f"✅ File size: {file_size} characters")
|
print(f"✅ File size: {file_size} characters")
|
||||||
if conversion_count > 0:
|
if conversion_count > 0:
|
||||||
print(f"✅ Date conversion functions: {conversion_count}")
|
print(f"✅ Date conversion functions: {conversion_count}")
|
||||||
if enum_specific_count > 0:
|
if default_objects_count > 0:
|
||||||
print(f"✅ Specific enum default types: {enum_specific_count}")
|
print(f"✅ Default objects: {default_objects_count}")
|
||||||
|
if enum_union_count > 0:
|
||||||
|
print(f"✅ Union types (proper enum support): {enum_union_count}")
|
||||||
if not args.skip_test:
|
if not args.skip_test:
|
||||||
print("✅ Model validation passed")
|
print("✅ Model validation passed")
|
||||||
if not args.skip_compile:
|
if not args.skip_compile:
|
||||||
print("✅ TypeScript syntax validated")
|
print("✅ TypeScript syntax validated")
|
||||||
|
|
||||||
print(f"\n💡 Usage in your TypeScript project:")
|
print(f"\n💡 Usage in your TypeScript project:")
|
||||||
print(f" import {{ Candidate, Employer, Job, convertCandidateFromApi }} from './{Path(args.output).stem}';")
|
print(f" import {{ ChatMessage, ChatStatusType, DefaultChatMessage, convertChatMessageFromApi }} from './{Path(args.output).stem}';")
|
||||||
|
print(f" const message: ChatMessage = {{ ...DefaultChatMessage, sessionId: '123', content: 'Hello' }};")
|
||||||
if conversion_count > 0:
|
if conversion_count > 0:
|
||||||
print(f" const candidate = convertCandidateFromApi(apiResponse);")
|
print(f" const message = convertChatMessageFromApi(apiResponse);")
|
||||||
print(f" const jobs = convertArrayFromApi<Job>(apiResponse, 'Job');")
|
print(f" const messages = convertArrayFromApi<ChatMessage>(apiResponse, 'ChatMessage');")
|
||||||
|
if default_objects_count > 0:
|
||||||
|
print(f" const overrideMessage: ChatMessage = {{ ...DefaultChatMessage, status: 'error' }};")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ class ChatStatusType(str, Enum):
|
|||||||
|
|
||||||
class ChatContextType(str, Enum):
|
class ChatContextType(str, Enum):
|
||||||
JOB_SEARCH = "job_search"
|
JOB_SEARCH = "job_search"
|
||||||
|
JOB_REQUIREMENTS = "job_requirements"
|
||||||
CANDIDATE_CHAT = "candidate_chat"
|
CANDIDATE_CHAT = "candidate_chat"
|
||||||
INTERVIEW_PREP = "interview_prep"
|
INTERVIEW_PREP = "interview_prep"
|
||||||
RESUME_REVIEW = "resume_review"
|
RESUME_REVIEW = "resume_review"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user