From c9a63e129df20cd0c34b0f2016bb4a450176dc1a Mon Sep 17 00:00:00 2001 From: Anibal Angulo Date: Fri, 7 Nov 2025 11:19:43 -0600 Subject: [PATCH] initial agent --- ai-elements-tools.md | 384 ++++++++++++++ backend/app/routers/agent.py | 670 ++++++++++++------------ docker-compose.yml | 3 +- frontend/src/components/AuditReport.tsx | 283 ++++++++++ frontend/src/components/ChatTab.tsx | 289 +++++----- redis_data/dump.rdb | Bin 0 -> 500 bytes 6 files changed, 1169 insertions(+), 460 deletions(-) create mode 100644 ai-elements-tools.md create mode 100644 frontend/src/components/AuditReport.tsx create mode 100644 redis_data/dump.rdb diff --git a/ai-elements-tools.md b/ai-elements-tools.md new file mode 100644 index 0000000..c30fdc4 --- /dev/null +++ b/ai-elements-tools.md @@ -0,0 +1,384 @@ +# Generative User Interfaces + +Generative user interfaces (generative UI) is the process of allowing a large language model (LLM) to go beyond text and "generate UI". This creates a more engaging and AI-native experience for users. + + + +At the core of generative UI are [ tools ](/docs/ai-sdk-core/tools-and-tool-calling), which are functions you provide to the model to perform specialized tasks like getting the weather in a location. The model can decide when and how to use these tools based on the context of the conversation. + +Generative UI is the process of connecting the results of a tool call to a React component. Here's how it works: + +1. You provide the model with a prompt or conversation history, along with a set of tools. +2. Based on the context, the model may decide to call a tool. +3. If a tool is called, it will execute and return data. +4. This data can then be passed to a React component for rendering. + +By passing the tool results to React components, you can create a generative UI experience that's more engaging and adaptive to your needs. + +## Build a Generative UI Chat Interface + +Let's create a chat interface that handles text-based conversations and incorporates dynamic UI elements based on model responses. + +### Basic Chat Implementation + +Start with a basic chat implementation using the `useChat` hook: + +```tsx filename="app/page.tsx" +'use client'; + +import { useChat } from '@ai-sdk/react'; +import { useState } from 'react'; + +export default function Page() { + const [input, setInput] = useState(''); + const { messages, sendMessage } = useChat(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + sendMessage({ text: input }); + setInput(''); + }; + + return ( +
+ {messages.map(message => ( +
+
{message.role === 'user' ? 'User: ' : 'AI: '}
+
+ {message.parts.map((part, index) => { + if (part.type === 'text') { + return {part.text}; + } + return null; + })} +
+
+ ))} + +
+ setInput(e.target.value)} + placeholder="Type a message..." + /> + +
+
+ ); +} +``` + +To handle the chat requests and model responses, set up an API route: + +```ts filename="app/api/chat/route.ts" +import { openai } from '@ai-sdk/openai'; +import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai'; + +export async function POST(request: Request) { + const { messages }: { messages: UIMessage[] } = await request.json(); + + const result = streamText({ + model: openai('gpt-4o'), + system: 'You are a friendly assistant!', + messages: convertToModelMessages(messages), + stopWhen: stepCountIs(5), + }); + + return result.toUIMessageStreamResponse(); +} +``` + +This API route uses the `streamText` function to process chat messages and stream the model's responses back to the client. + +### Create a Tool + +Before enhancing your chat interface with dynamic UI elements, you need to create a tool and corresponding React component. A tool will allow the model to perform a specific action, such as fetching weather information. + +Create a new file called `ai/tools.ts` with the following content: + +```ts filename="ai/tools.ts" +import { tool as createTool } from 'ai'; +import { z } from 'zod'; + +export const weatherTool = createTool({ + description: 'Display the weather for a location', + inputSchema: z.object({ + location: z.string().describe('The location to get the weather for'), + }), + execute: async function ({ location }) { + await new Promise(resolve => setTimeout(resolve, 2000)); + return { weather: 'Sunny', temperature: 75, location }; + }, +}); + +export const tools = { + displayWeather: weatherTool, +}; +``` + +In this file, you've created a tool called `weatherTool`. This tool simulates fetching weather information for a given location. This tool will return simulated data after a 2-second delay. In a real-world application, you would replace this simulation with an actual API call to a weather service. + +### Update the API Route + +Update the API route to include the tool you've defined: + +```ts filename="app/api/chat/route.ts" highlight="3,8,14" +import { openai } from '@ai-sdk/openai'; +import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai'; +import { tools } from '@/ai/tools'; + +export async function POST(request: Request) { + const { messages }: { messages: UIMessage[] } = await request.json(); + + const result = streamText({ + model: openai('gpt-4o'), + system: 'You are a friendly assistant!', + messages: convertToModelMessages(messages), + stopWhen: stepCountIs(5), + tools, + }); + + return result.toUIMessageStreamResponse(); +} +``` + +Now that you've defined the tool and added it to your `streamText` call, let's build a React component to display the weather information it returns. + +### Create UI Components + +Create a new file called `components/weather.tsx`: + +```tsx filename="components/weather.tsx" +type WeatherProps = { + temperature: number; + weather: string; + location: string; +}; + +export const Weather = ({ temperature, weather, location }: WeatherProps) => { + return ( +
+

Current Weather for {location}

+

Condition: {weather}

+

Temperature: {temperature}°C

+
+ ); +}; +``` + +This component will display the weather information for a given location. It takes three props: `temperature`, `weather`, and `location` (exactly what the `weatherTool` returns). + +### Render the Weather Component + +Now that you have your tool and corresponding React component, let's integrate them into your chat interface. You'll render the Weather component when the model calls the weather tool. + +To check if the model has called a tool, you can check the `parts` array of the UIMessage object for tool-specific parts. In AI SDK 5.0, tool parts use typed naming: `tool-${toolName}` instead of generic types. + +Update your `page.tsx` file: + +```tsx filename="app/page.tsx" highlight="4,9,14-15,19-46" +'use client'; + +import { useChat } from '@ai-sdk/react'; +import { useState } from 'react'; +import { Weather } from '@/components/weather'; + +export default function Page() { + const [input, setInput] = useState(''); + const { messages, sendMessage } = useChat(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + sendMessage({ text: input }); + setInput(''); + }; + + return ( +
+ {messages.map(message => ( +
+
{message.role === 'user' ? 'User: ' : 'AI: '}
+
+ {message.parts.map((part, index) => { + if (part.type === 'text') { + return {part.text}; + } + + if (part.type === 'tool-displayWeather') { + switch (part.state) { + case 'input-available': + return
Loading weather...
; + case 'output-available': + return ( +
+ +
+ ); + case 'output-error': + return
Error: {part.errorText}
; + default: + return null; + } + } + + return null; + })} +
+
+ ))} + +
+ setInput(e.target.value)} + placeholder="Type a message..." + /> + +
+
+ ); +} +``` + +In this updated code snippet, you: + +1. Use manual input state management with `useState` instead of the built-in `input` and `handleInputChange`. +2. Use `sendMessage` instead of `handleSubmit` to send messages. +3. Check the `parts` array of each message for different content types. +4. Handle tool parts with type `tool-displayWeather` and their different states (`input-available`, `output-available`, `output-error`). + +This approach allows you to dynamically render UI components based on the model's responses, creating a more interactive and context-aware chat experience. + +## Expanding Your Generative UI Application + +You can enhance your chat application by adding more tools and components, creating a richer and more versatile user experience. Here's how you can expand your application: + +### Adding More Tools + +To add more tools, simply define them in your `ai/tools.ts` file: + +```ts +// Add a new stock tool +export const stockTool = createTool({ + description: 'Get price for a stock', + inputSchema: z.object({ + symbol: z.string().describe('The stock symbol to get the price for'), + }), + execute: async function ({ symbol }) { + // Simulated API call + await new Promise(resolve => setTimeout(resolve, 2000)); + return { symbol, price: 100 }; + }, +}); + +// Update the tools object +export const tools = { + displayWeather: weatherTool, + getStockPrice: stockTool, +}; +``` + +Now, create a new file called `components/stock.tsx`: + +```tsx +type StockProps = { + price: number; + symbol: string; +}; + +export const Stock = ({ price, symbol }: StockProps) => { + return ( +
+

Stock Information

+

Symbol: {symbol}

+

Price: ${price}

+
+ ); +}; +``` + +Finally, update your `page.tsx` file to include the new Stock component: + +```tsx +'use client'; + +import { useChat } from '@ai-sdk/react'; +import { useState } from 'react'; +import { Weather } from '@/components/weather'; +import { Stock } from '@/components/stock'; + +export default function Page() { + const [input, setInput] = useState(''); + const { messages, sendMessage } = useChat(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + sendMessage({ text: input }); + setInput(''); + }; + + return ( +
+ {messages.map(message => ( +
+
{message.role}
+
+ {message.parts.map((part, index) => { + if (part.type === 'text') { + return {part.text}; + } + + if (part.type === 'tool-displayWeather') { + switch (part.state) { + case 'input-available': + return
Loading weather...
; + case 'output-available': + return ( +
+ +
+ ); + case 'output-error': + return
Error: {part.errorText}
; + default: + return null; + } + } + + if (part.type === 'tool-getStockPrice') { + switch (part.state) { + case 'input-available': + return
Loading stock price...
; + case 'output-available': + return ( +
+ +
+ ); + case 'output-error': + return
Error: {part.errorText}
; + default: + return null; + } + } + + return null; + })} +
+
+ ))} + +
+ setInput(e.target.value)} + /> + +
+
+ ); +} +``` + +By following this pattern, you can continue to add more tools and components, expanding the capabilities of your Generative UI application. diff --git a/backend/app/routers/agent.py b/backend/app/routers/agent.py index d38b5ff..0d24e83 100644 --- a/backend/app/routers/agent.py +++ b/backend/app/routers/agent.py @@ -19,346 +19,346 @@ agent = Agent(model=model) router = APIRouter(prefix="/api/v1/agent", tags=["Agent"]) +data = { + "extraction": { + "core_organization_metadata": { + "ein": "84-2674654", + "legal_name": "07 IN HEAVEN MEMORIAL SCHOLARSHIP", + "phone_number": "(262) 215-0300", + "website_url": "", + "return_type": "990-PF", + "amended_return": "No", + "group_exemption_number": "", + "subsection_code": "501(c)(3)", + "ruling_date": "", + "accounting_method": "Cash", + "organization_type": "corporation", + "year_of_formation": "", + "incorporation_state": "WI", + }, + "revenue_breakdown": { + "total_revenue": 5227, + "contributions_gifts_grants": 5227, + "program_service_revenue": 0, + "membership_dues": 0, + "investment_income": 0, + "gains_losses_sales_assets": 0, + "rental_income": 0, + "related_organizations_revenue": 0, + "gaming_revenue": 0, + "other_revenue": 0, + "government_grants": 0, + "foreign_contributions": 0, + }, + "expenses_breakdown": { + "total_expenses": 2104, + "program_services_expenses": 0, + "management_general_expenses": 0, + "fundraising_expenses": 2104, + "grants_us_organizations": 0, + "grants_us_individuals": 0, + "grants_foreign_organizations": 0, + "grants_foreign_individuals": 0, + "compensation_officers": 0, + "compensation_other_staff": 0, + "payroll_taxes_benefits": 0, + "professional_fees": 0, + "office_occupancy_costs": 0, + "information_technology_costs": 0, + "travel_conference_expenses": 0, + "depreciation_amortization": 0, + "insurance": 0, + }, + "balance_sheet": {}, + "officers_directors_trustees_key_employees": [ + { + "name": "REBECCA TERPSTRA", + "title_position": "PRESIDENT", + "average_hours_per_week": 0.1, + "related_party_transactions": "", + "former_officer": "", + "governance_role": "", + }, + { + "name": "ROBERT GUZMAN", + "title_position": "VICE PRESDEINT", + "average_hours_per_week": 0.1, + "related_party_transactions": "", + "former_officer": "", + "governance_role": "", + }, + { + "name": "ANDREA VALENTI", + "title_position": "TREASURER", + "average_hours_per_week": 0.1, + "related_party_transactions": "", + "former_officer": "", + "governance_role": "", + }, + { + "name": "BETHANY WALSH", + "title_position": "SECRETARY", + "average_hours_per_week": 0.1, + "related_party_transactions": "", + "former_officer": "", + "governance_role": "", + }, + ], + "governance_management_disclosure": { + "governing_body_size": 4, + "independent_members": 4, + "financial_statements_reviewed": "", + "form_990_provided_to_governing_body": "", + "conflict_of_interest_policy": "", + "whistleblower_policy": "", + "document_retention_policy": "", + "ceo_compensation_review_process": "", + "public_disclosure_practices": "Yes", + }, + "program_service_accomplishments": [], + "fundraising_grantmaking": { + "total_fundraising_event_revenue": 0, + "total_fundraising_event_expenses": 2104, + "professional_fundraiser_fees": 0, + }, + "functional_operational_data": { + "number_of_employees": 0, + "number_of_volunteers": 0, + "occupancy_costs": 0, + "fundraising_method_descriptions": "", + "joint_ventures_disregarded_entities": "", + }, + "compensation_details": { + "base_compensation": 0, + "bonus": 0, + "incentive": 0, + "other": 0, + "non_fixed_compensation": "", + "first_class_travel": "", + "housing_allowance": "", + "expense_account_usage": "", + "supplemental_retirement": "", + }, + "political_lobbying_activities": { + "lobbying_expenditures_direct": 0, + "lobbying_expenditures_grassroots": 0, + "election_501h_status": "", + "political_campaign_expenditures": 0, + "related_organizations_affiliates": "", + }, + "investments_endowment": { + "investment_types": "", + "donor_restricted_endowment_values": 0, + "net_appreciation_depreciation": 0, + "related_organization_transactions": "", + "loans_to_from_related_parties": "", + }, + "tax_compliance_penalties": { + "penalties_excise_taxes_reported": "No", + "unrelated_business_income_disclosure": "No", + "foreign_bank_account_reporting": "No", + "schedule_o_narrative_explanations": "", + }, + }, + "extraction_metadata": { + "core_organization_metadata": { + "ein": {"value": "84-2674654", "references": ["0-7"]}, + "legal_name": { + "value": "07 IN HEAVEN MEMORIAL SCHOLARSHIP", + "references": ["0-6"], + }, + "phone_number": {"value": "(262) 215-0300", "references": ["0-a"]}, + "website_url": {"value": "", "references": []}, + "return_type": { + "value": "990-PF", + "references": ["4ade8ed0-bce7-4bd5-bd8d-190e3e4be95b"], + }, + "amended_return": { + "value": "No", + "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], + }, + "group_exemption_number": {"value": "", "references": []}, + "subsection_code": { + "value": "501(c)(3)", + "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], + }, + "ruling_date": {"value": "", "references": []}, + "accounting_method": {"value": "Cash", "references": ["0-d"]}, + "organization_type": { + "value": "corporation", + "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], + }, + "year_of_formation": {"value": "", "references": []}, + "incorporation_state": { + "value": "WI", + "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], + }, + }, + "revenue_breakdown": { + "total_revenue": {"value": 5227, "references": ["0-1z"]}, + "contributions_gifts_grants": {"value": 5227, "references": ["0-m"]}, + "program_service_revenue": {"value": 0, "references": []}, + "membership_dues": {"value": 0, "references": []}, + "investment_income": {"value": 0, "references": []}, + "gains_losses_sales_assets": {"value": 0, "references": []}, + "rental_income": {"value": 0, "references": []}, + "related_organizations_revenue": {"value": 0, "references": []}, + "gaming_revenue": {"value": 0, "references": []}, + "other_revenue": {"value": 0, "references": []}, + "government_grants": {"value": 0, "references": []}, + "foreign_contributions": {"value": 0, "references": []}, + }, + "expenses_breakdown": { + "total_expenses": {"value": 2104, "references": ["0-2S"]}, + "program_services_expenses": {"value": 0, "references": []}, + "management_general_expenses": {"value": 0, "references": []}, + "fundraising_expenses": {"value": 2104, "references": ["13-d"]}, + "grants_us_organizations": {"value": 0, "references": []}, + "grants_us_individuals": {"value": 0, "references": []}, + "grants_foreign_organizations": {"value": 0, "references": []}, + "grants_foreign_individuals": {"value": 0, "references": []}, + "compensation_officers": { + "value": 0, + "references": ["5-1q", "5-1w", "5-1C", "5-1I"], + }, + "compensation_other_staff": {"value": 0, "references": []}, + "payroll_taxes_benefits": {"value": 0, "references": []}, + "professional_fees": {"value": 0, "references": []}, + "office_occupancy_costs": {"value": 0, "references": []}, + "information_technology_costs": {"value": 0, "references": []}, + "travel_conference_expenses": {"value": 0, "references": []}, + "depreciation_amortization": {"value": 0, "references": []}, + "insurance": {"value": 0, "references": []}, + }, + "balance_sheet": {}, + "officers_directors_trustees_key_employees": [ + { + "name": {"value": "REBECCA TERPSTRA", "references": ["5-1o"]}, + "title_position": {"value": "PRESIDENT", "references": ["5-1p"]}, + "average_hours_per_week": {"value": 0.1, "references": ["5-1p"]}, + "related_party_transactions": {"value": "", "references": []}, + "former_officer": {"value": "", "references": []}, + "governance_role": {"value": "", "references": []}, + }, + { + "name": {"value": "ROBERT GUZMAN", "references": ["5-1u"]}, + "title_position": { + "value": "VICE PRESDEINT", + "references": ["5-1v"], + }, + "average_hours_per_week": {"value": 0.1, "references": ["5-1v"]}, + "related_party_transactions": {"value": "", "references": []}, + "former_officer": {"value": "", "references": []}, + "governance_role": {"value": "", "references": []}, + }, + { + "name": {"value": "ANDREA VALENTI", "references": ["5-1A"]}, + "title_position": {"value": "TREASURER", "references": ["5-1B"]}, + "average_hours_per_week": {"value": 0.1, "references": ["5-1B"]}, + "related_party_transactions": {"value": "", "references": []}, + "former_officer": {"value": "", "references": []}, + "governance_role": {"value": "", "references": []}, + }, + { + "name": {"value": "BETHANY WALSH", "references": ["5-1G"]}, + "title_position": {"value": "SECRETARY", "references": ["5-1H"]}, + "average_hours_per_week": {"value": 0.1, "references": ["5-1H"]}, + "related_party_transactions": {"value": "", "references": []}, + "former_officer": {"value": "", "references": []}, + "governance_role": {"value": "", "references": []}, + }, + ], + "governance_management_disclosure": { + "governing_body_size": { + "value": 4, + "references": ["5-1o", "5-1u", "5-1A", "5-1G"], + }, + "independent_members": { + "value": 4, + "references": ["5-1o", "5-1u", "5-1A", "5-1G"], + }, + "financial_statements_reviewed": {"value": "", "references": []}, + "form_990_provided_to_governing_body": {"value": "", "references": []}, + "conflict_of_interest_policy": {"value": "", "references": []}, + "whistleblower_policy": {"value": "", "references": []}, + "document_retention_policy": {"value": "", "references": []}, + "ceo_compensation_review_process": {"value": "", "references": []}, + "public_disclosure_practices": {"value": "Yes", "references": ["4-g"]}, + }, + "program_service_accomplishments": [], + "fundraising_grantmaking": { + "total_fundraising_event_revenue": {"value": 0, "references": []}, + "total_fundraising_event_expenses": { + "value": 2104, + "references": ["13-d"], + }, + "professional_fundraiser_fees": {"value": 0, "references": []}, + }, + "functional_operational_data": { + "number_of_employees": {"value": 0, "references": []}, + "number_of_volunteers": {"value": 0, "references": []}, + "occupancy_costs": {"value": 0, "references": []}, + "fundraising_method_descriptions": {"value": "", "references": []}, + "joint_ventures_disregarded_entities": {"value": "", "references": []}, + }, + "compensation_details": { + "base_compensation": {"value": 0, "references": ["5-1q", "5-1w"]}, + "bonus": {"value": 0, "references": []}, + "incentive": {"value": 0, "references": []}, + "other": {"value": 0, "references": []}, + "non_fixed_compensation": {"value": "", "references": []}, + "first_class_travel": {"value": "", "references": []}, + "housing_allowance": {"value": "", "references": []}, + "expense_account_usage": {"value": "", "references": []}, + "supplemental_retirement": {"value": "", "references": []}, + }, + "political_lobbying_activities": { + "lobbying_expenditures_direct": {"value": 0, "references": []}, + "lobbying_expenditures_grassroots": {"value": 0, "references": []}, + "election_501h_status": {"value": "", "references": []}, + "political_campaign_expenditures": {"value": 0, "references": []}, + "related_organizations_affiliates": {"value": "", "references": []}, + }, + "investments_endowment": { + "investment_types": {"value": "", "references": []}, + "donor_restricted_endowment_values": {"value": 0, "references": []}, + "net_appreciation_depreciation": {"value": 0, "references": []}, + "related_organization_transactions": {"value": "", "references": []}, + "loans_to_from_related_parties": {"value": "", "references": []}, + }, + "tax_compliance_penalties": { + "penalties_excise_taxes_reported": { + "value": "No", + "references": ["3-I"], + }, + "unrelated_business_income_disclosure": { + "value": "No", + "references": ["3-Y"], + }, + "foreign_bank_account_reporting": { + "value": "No", + "references": ["4-H"], + }, + "schedule_o_narrative_explanations": {"value": "", "references": []}, + }, + }, + "metadata": { + "filename": "markdown.md", + "org_id": None, + "duration_ms": 16656, + "credit_usage": 27.2, + "job_id": "nnmr8lcxtykk5ll5wodjtrnn6", + "version": "extract-20250930", + }, +} -@agent.tool_plain() + +@agent.tool_plain async def build_audit_report(): """Calls the audit subagent to get a full audit report of the organization""" - data = { - "extraction": { - "core_organization_metadata": { - "ein": "84-2674654", - "legal_name": "07 IN HEAVEN MEMORIAL SCHOLARSHIP", - "phone_number": "(262) 215-0300", - "website_url": "", - "return_type": "990-PF", - "amended_return": "No", - "group_exemption_number": "", - "subsection_code": "501(c)(3)", - "ruling_date": "", - "accounting_method": "Cash", - "organization_type": "corporation", - "year_of_formation": "", - "incorporation_state": "WI", - }, - "revenue_breakdown": { - "total_revenue": 5227, - "contributions_gifts_grants": 5227, - "program_service_revenue": 0, - "membership_dues": 0, - "investment_income": 0, - "gains_losses_sales_assets": 0, - "rental_income": 0, - "related_organizations_revenue": 0, - "gaming_revenue": 0, - "other_revenue": 0, - "government_grants": 0, - "foreign_contributions": 0, - }, - "expenses_breakdown": { - "total_expenses": 2104, - "program_services_expenses": 0, - "management_general_expenses": 0, - "fundraising_expenses": 2104, - "grants_us_organizations": 0, - "grants_us_individuals": 0, - "grants_foreign_organizations": 0, - "grants_foreign_individuals": 0, - "compensation_officers": 0, - "compensation_other_staff": 0, - "payroll_taxes_benefits": 0, - "professional_fees": 0, - "office_occupancy_costs": 0, - "information_technology_costs": 0, - "travel_conference_expenses": 0, - "depreciation_amortization": 0, - "insurance": 0, - }, - "balance_sheet": {}, - "officers_directors_trustees_key_employees": [ - { - "name": "REBECCA TERPSTRA", - "title_position": "PRESIDENT", - "average_hours_per_week": 0.1, - "related_party_transactions": "", - "former_officer": "", - "governance_role": "", - }, - { - "name": "ROBERT GUZMAN", - "title_position": "VICE PRESDEINT", - "average_hours_per_week": 0.1, - "related_party_transactions": "", - "former_officer": "", - "governance_role": "", - }, - { - "name": "ANDREA VALENTI", - "title_position": "TREASURER", - "average_hours_per_week": 0.1, - "related_party_transactions": "", - "former_officer": "", - "governance_role": "", - }, - { - "name": "BETHANY WALSH", - "title_position": "SECRETARY", - "average_hours_per_week": 0.1, - "related_party_transactions": "", - "former_officer": "", - "governance_role": "", - }, - ], - "governance_management_disclosure": { - "governing_body_size": 4, - "independent_members": 4, - "financial_statements_reviewed": "", - "form_990_provided_to_governing_body": "", - "conflict_of_interest_policy": "", - "whistleblower_policy": "", - "document_retention_policy": "", - "ceo_compensation_review_process": "", - "public_disclosure_practices": "Yes", - }, - "program_service_accomplishments": [], - "fundraising_grantmaking": { - "total_fundraising_event_revenue": 0, - "total_fundraising_event_expenses": 2104, - "professional_fundraiser_fees": 0, - }, - "functional_operational_data": { - "number_of_employees": 0, - "number_of_volunteers": 0, - "occupancy_costs": 0, - "fundraising_method_descriptions": "", - "joint_ventures_disregarded_entities": "", - }, - "compensation_details": { - "base_compensation": 0, - "bonus": 0, - "incentive": 0, - "other": 0, - "non_fixed_compensation": "", - "first_class_travel": "", - "housing_allowance": "", - "expense_account_usage": "", - "supplemental_retirement": "", - }, - "political_lobbying_activities": { - "lobbying_expenditures_direct": 0, - "lobbying_expenditures_grassroots": 0, - "election_501h_status": "", - "political_campaign_expenditures": 0, - "related_organizations_affiliates": "", - }, - "investments_endowment": { - "investment_types": "", - "donor_restricted_endowment_values": 0, - "net_appreciation_depreciation": 0, - "related_organization_transactions": "", - "loans_to_from_related_parties": "", - }, - "tax_compliance_penalties": { - "penalties_excise_taxes_reported": "No", - "unrelated_business_income_disclosure": "No", - "foreign_bank_account_reporting": "No", - "schedule_o_narrative_explanations": "", - }, - }, - "extraction_metadata": { - "core_organization_metadata": { - "ein": {"value": "84-2674654", "references": ["0-7"]}, - "legal_name": { - "value": "07 IN HEAVEN MEMORIAL SCHOLARSHIP", - "references": ["0-6"], - }, - "phone_number": {"value": "(262) 215-0300", "references": ["0-a"]}, - "website_url": {"value": "", "references": []}, - "return_type": { - "value": "990-PF", - "references": ["4ade8ed0-bce7-4bd5-bd8d-190e3e4be95b"], - }, - "amended_return": { - "value": "No", - "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], - }, - "group_exemption_number": {"value": "", "references": []}, - "subsection_code": { - "value": "501(c)(3)", - "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], - }, - "ruling_date": {"value": "", "references": []}, - "accounting_method": {"value": "Cash", "references": ["0-d"]}, - "organization_type": { - "value": "corporation", - "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], - }, - "year_of_formation": {"value": "", "references": []}, - "incorporation_state": { - "value": "WI", - "references": ["4ac9edc4-e9bb-430f-b4c4-a42bf4c04b28"], - }, - }, - "revenue_breakdown": { - "total_revenue": {"value": 5227, "references": ["0-1z"]}, - "contributions_gifts_grants": {"value": 5227, "references": ["0-m"]}, - "program_service_revenue": {"value": 0, "references": []}, - "membership_dues": {"value": 0, "references": []}, - "investment_income": {"value": 0, "references": []}, - "gains_losses_sales_assets": {"value": 0, "references": []}, - "rental_income": {"value": 0, "references": []}, - "related_organizations_revenue": {"value": 0, "references": []}, - "gaming_revenue": {"value": 0, "references": []}, - "other_revenue": {"value": 0, "references": []}, - "government_grants": {"value": 0, "references": []}, - "foreign_contributions": {"value": 0, "references": []}, - }, - "expenses_breakdown": { - "total_expenses": {"value": 2104, "references": ["0-2S"]}, - "program_services_expenses": {"value": 0, "references": []}, - "management_general_expenses": {"value": 0, "references": []}, - "fundraising_expenses": {"value": 2104, "references": ["13-d"]}, - "grants_us_organizations": {"value": 0, "references": []}, - "grants_us_individuals": {"value": 0, "references": []}, - "grants_foreign_organizations": {"value": 0, "references": []}, - "grants_foreign_individuals": {"value": 0, "references": []}, - "compensation_officers": { - "value": 0, - "references": ["5-1q", "5-1w", "5-1C", "5-1I"], - }, - "compensation_other_staff": {"value": 0, "references": []}, - "payroll_taxes_benefits": {"value": 0, "references": []}, - "professional_fees": {"value": 0, "references": []}, - "office_occupancy_costs": {"value": 0, "references": []}, - "information_technology_costs": {"value": 0, "references": []}, - "travel_conference_expenses": {"value": 0, "references": []}, - "depreciation_amortization": {"value": 0, "references": []}, - "insurance": {"value": 0, "references": []}, - }, - "balance_sheet": {}, - "officers_directors_trustees_key_employees": [ - { - "name": {"value": "REBECCA TERPSTRA", "references": ["5-1o"]}, - "title_position": {"value": "PRESIDENT", "references": ["5-1p"]}, - "average_hours_per_week": {"value": 0.1, "references": ["5-1p"]}, - "related_party_transactions": {"value": "", "references": []}, - "former_officer": {"value": "", "references": []}, - "governance_role": {"value": "", "references": []}, - }, - { - "name": {"value": "ROBERT GUZMAN", "references": ["5-1u"]}, - "title_position": { - "value": "VICE PRESDEINT", - "references": ["5-1v"], - }, - "average_hours_per_week": {"value": 0.1, "references": ["5-1v"]}, - "related_party_transactions": {"value": "", "references": []}, - "former_officer": {"value": "", "references": []}, - "governance_role": {"value": "", "references": []}, - }, - { - "name": {"value": "ANDREA VALENTI", "references": ["5-1A"]}, - "title_position": {"value": "TREASURER", "references": ["5-1B"]}, - "average_hours_per_week": {"value": 0.1, "references": ["5-1B"]}, - "related_party_transactions": {"value": "", "references": []}, - "former_officer": {"value": "", "references": []}, - "governance_role": {"value": "", "references": []}, - }, - { - "name": {"value": "BETHANY WALSH", "references": ["5-1G"]}, - "title_position": {"value": "SECRETARY", "references": ["5-1H"]}, - "average_hours_per_week": {"value": 0.1, "references": ["5-1H"]}, - "related_party_transactions": {"value": "", "references": []}, - "former_officer": {"value": "", "references": []}, - "governance_role": {"value": "", "references": []}, - }, - ], - "governance_management_disclosure": { - "governing_body_size": { - "value": 4, - "references": ["5-1o", "5-1u", "5-1A", "5-1G"], - }, - "independent_members": { - "value": 4, - "references": ["5-1o", "5-1u", "5-1A", "5-1G"], - }, - "financial_statements_reviewed": {"value": "", "references": []}, - "form_990_provided_to_governing_body": {"value": "", "references": []}, - "conflict_of_interest_policy": {"value": "", "references": []}, - "whistleblower_policy": {"value": "", "references": []}, - "document_retention_policy": {"value": "", "references": []}, - "ceo_compensation_review_process": {"value": "", "references": []}, - "public_disclosure_practices": {"value": "Yes", "references": ["4-g"]}, - }, - "program_service_accomplishments": [], - "fundraising_grantmaking": { - "total_fundraising_event_revenue": {"value": 0, "references": []}, - "total_fundraising_event_expenses": { - "value": 2104, - "references": ["13-d"], - }, - "professional_fundraiser_fees": {"value": 0, "references": []}, - }, - "functional_operational_data": { - "number_of_employees": {"value": 0, "references": []}, - "number_of_volunteers": {"value": 0, "references": []}, - "occupancy_costs": {"value": 0, "references": []}, - "fundraising_method_descriptions": {"value": "", "references": []}, - "joint_ventures_disregarded_entities": {"value": "", "references": []}, - }, - "compensation_details": { - "base_compensation": {"value": 0, "references": ["5-1q", "5-1w"]}, - "bonus": {"value": 0, "references": []}, - "incentive": {"value": 0, "references": []}, - "other": {"value": 0, "references": []}, - "non_fixed_compensation": {"value": "", "references": []}, - "first_class_travel": {"value": "", "references": []}, - "housing_allowance": {"value": "", "references": []}, - "expense_account_usage": {"value": "", "references": []}, - "supplemental_retirement": {"value": "", "references": []}, - }, - "political_lobbying_activities": { - "lobbying_expenditures_direct": {"value": 0, "references": []}, - "lobbying_expenditures_grassroots": {"value": 0, "references": []}, - "election_501h_status": {"value": "", "references": []}, - "political_campaign_expenditures": {"value": 0, "references": []}, - "related_organizations_affiliates": {"value": "", "references": []}, - }, - "investments_endowment": { - "investment_types": {"value": "", "references": []}, - "donor_restricted_endowment_values": {"value": 0, "references": []}, - "net_appreciation_depreciation": {"value": 0, "references": []}, - "related_organization_transactions": {"value": "", "references": []}, - "loans_to_from_related_parties": {"value": "", "references": []}, - }, - "tax_compliance_penalties": { - "penalties_excise_taxes_reported": { - "value": "No", - "references": ["3-I"], - }, - "unrelated_business_income_disclosure": { - "value": "No", - "references": ["3-Y"], - }, - "foreign_bank_account_reporting": { - "value": "No", - "references": ["4-H"], - }, - "schedule_o_narrative_explanations": {"value": "", "references": []}, - }, - }, - "metadata": { - "filename": "markdown.md", - "org_id": None, - "duration_ms": 16656, - "credit_usage": 27.2, - "job_id": "nnmr8lcxtykk5ll5wodjtrnn6", - "version": "extract-20250930", - }, - } - result = await form_auditor.build_audit_report(data) - return result.model_dump_json() + return result.model_dump() @router.post("/chat") diff --git a/docker-compose.yml b/docker-compose.yml index ed2506e..84a4c08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,11 +27,12 @@ services: db: image: redis/redis-stack:latest + command: redis-server --appendonly yes ports: - 6379:6379 - 8001:8001 volumes: - - redis_data:/data # Persistent Redis data + - ./redis_data:/data restart: unless-stopped networks: - app-network diff --git a/frontend/src/components/AuditReport.tsx b/frontend/src/components/AuditReport.tsx new file mode 100644 index 0000000..13fb9cc --- /dev/null +++ b/frontend/src/components/AuditReport.tsx @@ -0,0 +1,283 @@ +import React from "react"; +import { + AlertTriangle, + CheckCircle, + XCircle, + FileText, + Building, + Calendar, + AlertCircle, + TrendingUp, + Shield, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +type Severity = "Pass" | "Warning" | "Error"; + +interface AuditFinding { + check_id: string; + category: string; + severity: Severity; + message: string; + mitigation?: string; + confidence: number; +} + +interface AuditSectionSummary { + section: string; + severity: Severity; + summary: string; + confidence: number; +} + +interface AuditReportData { + organisation_ein: string; + organisation_name: string; + year?: number; + overall_severity: Severity; + findings: AuditFinding[]; + sections: AuditSectionSummary[]; + overall_summary?: string; + notes?: string; +} + +interface AuditReportProps { + data: AuditReportData; +} + +const getSeverityIcon = (severity: Severity) => { + switch (severity) { + case "Pass": + return ; + case "Warning": + return ; + case "Error": + return ; + default: + return ; + } +}; + +const getSeverityColor = (severity: Severity) => { + switch (severity) { + case "Pass": + return "text-green-700 bg-green-50 border-green-200"; + case "Warning": + return "text-yellow-700 bg-yellow-50 border-yellow-200"; + case "Error": + return "text-red-700 bg-red-50 border-red-200"; + default: + return "text-gray-700 bg-gray-50 border-gray-200"; + } +}; + +const getConfidenceColor = (confidence: number) => { + if (confidence >= 0.8) return "text-green-600"; + if (confidence >= 0.6) return "text-yellow-600"; + return "text-red-600"; +}; + +export const AuditReport: React.FC = ({ data }) => { + const { + organisation_ein, + organisation_name, + year, + overall_severity, + findings, + sections, + overall_summary, + notes, + } = data; + + const severityStats = { + Pass: findings.filter((f) => f.severity === "Pass").length, + Warning: findings.filter((f) => f.severity === "Warning").length, + Error: findings.filter((f) => f.severity === "Error").length, + }; + + return ( +
+
+ {/* Header */} +
+
+
+
+ +
+
+

+ + {organisation_name} +

+
+ + + EIN: {organisation_ein} + + {year && ( + + + {year} + + )} +
+
+
+ + {/* Overall Status */} +
+ {getSeverityIcon(overall_severity)} + {overall_severity} +
+
+
+ + {/* Statistics Bar */} +
+
+

Audit Summary

+
+
+ + + {severityStats.Pass} + + Passed +
+
+ + + {severityStats.Warning} + + Warnings +
+
+ + + {severityStats.Error} + + Errors +
+
+
+
+ + {/* Overall Summary */} + {overall_summary && ( +
+

+ Overall Assessment +

+

{overall_summary}

+
+ )} + + {/* Section Summaries */} + {sections.length > 0 && ( +
+

+ Section Analysis +

+
+ {sections.map((section, index) => ( +
+
+

{section.section}

+
+ {getSeverityIcon(section.severity)} + + {Math.round(section.confidence * 100)}% + +
+
+

{section.summary}

+
+ ))} +
+
+ )} + + {/* Detailed Findings */} +
+

+ Detailed Findings ({findings.length}) +

+
+ {findings.map((finding, index) => ( +
+
+
+ {getSeverityIcon(finding.severity)} +
+ {finding.category} + + #{finding.check_id} + +
+
+
+ + + {Math.round(finding.confidence * 100)}% confidence + +
+
+ +

+ {finding.message} +

+ + {finding.mitigation && ( +
+
+ Recommended Action: +
+

{finding.mitigation}

+
+ )} +
+ ))} +
+
+ + {/* Notes */} + {notes && ( +
+

+ Additional Notes +

+

{notes}

+
+ )} +
+
+ ); +}; diff --git a/frontend/src/components/ChatTab.tsx b/frontend/src/components/ChatTab.tsx index 2c9d531..0cec45a 100644 --- a/frontend/src/components/ChatTab.tsx +++ b/frontend/src/components/ChatTab.tsx @@ -32,6 +32,7 @@ import { AlertCircle, PaperclipIcon, } from "lucide-react"; +import { AuditReport } from "./AuditReport"; import { Loader } from "@/components/ai-elements/loader"; import { DefaultChatTransport } from "ai"; @@ -101,9 +102,9 @@ export function ChatTab({ selectedTema }: ChatTabProps) { } return ( -
+
{/* Chat Header */} -
+
@@ -120,138 +121,178 @@ export function ChatTab({ selectedTema }: ChatTabProps) {
{/* Chat Content */} -
- - -
- {/* Welcome Message */} - {messages.length === 0 && ( -
-
- -
-
-

- ¡Hola! Soy tu asistente de IA para el dataroom{" "} - {selectedTema}. Puedes hacerme preguntas - sobre los documentos almacenados aquí. -

-
-
- )} +
+
+ {/* Welcome Message */} + {messages.length === 0 && ( +
+
+ +
+
+

+ ¡Hola! Soy tu asistente de IA para el dataroom{" "} + {selectedTema}. Puedes hacerme preguntas + sobre los documentos almacenados aquí. +

+
+
+ )} - {/* Error Message */} - {error && ( -
-
- -
-
-

{error}

-
-
- )} + {/* Error Message */} + {error && ( +
+
+ +
+
+

{error}

+
+
+ )} - {/* Chat Messages */} - {messages.map((message) => ( -
- {message.parts.map((part, i) => { - switch (part.type) { - case "text": + {/* Chat Messages */} + {messages.map((message) => ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return ( + + + + {part.text} + + + {message.role === "assistant" && + i === message.parts.length - 1 && ( + + regenerate()} + label="Regenerar" + disabled={status === "streaming"} + > + + + + navigator.clipboard.writeText(part.text) + } + label="Copiar" + > + + + + )} + + ); + case "tool-build_audit_report": + switch (part.state) { + case "input-available": return ( - - - - {part.text} - - - {message.role === "assistant" && - i === message.parts.length - 1 && ( - - regenerate()} - label="Regenerar" - disabled={status === "streaming"} - > - - - - navigator.clipboard.writeText(part.text) - } - label="Copiar" - > - - - - )} - +
+
+ + Generando reporte de auditoría... + +
+ ); + case "output-available": + return ( +
+
+ +
+
+ ); + case "output-error": + return ( +
+
+ + + Error generando reporte de auditoría + +
+

+ {part.errorText} +

+
); default: return null; } - })} -
- ))} - {status === "streaming" && } - {status === "loading" && } -
- - - - - {/* Chat Input */} -
- - - - {(attachment) => } - - - - setInput(e.target.value)} - value={input} - placeholder={`Pregunta algo sobre ${selectedTema}...`} - disabled={status === "streaming" || status === "loading"} - className="min-h-[60px] resize-none border-0 focus:ring-0 transition-all duration-200 text-base px-4 py-3 bg-white rounded-xl" - /> - - - - - - - - - - - - - - - + })} +
+ ))} + {status === "streaming" && } + {status === "loading" && }
+ + {/* Chat Input */} +
+ + + + {(attachment) => } + + + + setInput(e.target.value)} + value={input} + placeholder={`Pregunta algo sobre ${selectedTema}...`} + disabled={status === "streaming" || status === "loading"} + className="min-h-[60px] resize-none border-0 focus:ring-0 transition-all duration-200 text-base px-4 py-3 bg-white rounded-xl" + /> + + + + + + + + + + + + + + + +
); } diff --git a/redis_data/dump.rdb b/redis_data/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..254444c6d49fa2a70d068472e8ca178178afe9a9 GIT binary patch literal 500 zcmWG?b@2=~FfcUw#aWb^l3A=uO-d|IJ;3n2apUpaQ`5IxU}Rzha_a6sntOfY1#u=I zmsQ0ov7kUNH$Npcr&uo~u_Uo5KR;K`1;_{jGORN5Qc^1zm>L*37=nSSnG3QRm>8Hi zfVlBL6fm)|GS;xNdN>AqFaVXv<2Mkb9Sm6*z~F;D!mK|MObiVF7=AM`2w31Z#lX

nP!=6m}G2b snUZFdWMFP=VrgKKl45F>Y+#gPl$@GqiWC`4K=1zV(b;6?EdK8&0ML)AwEzGB literal 0 HcmV?d00001