From 77a11ef32e0b1d6c91b180e969c9c44484dffaaf Mon Sep 17 00:00:00 2001 From: Anibal Angulo Date: Sun, 9 Nov 2025 08:35:01 -0600 Subject: [PATCH] add agent context --- backend/app/agents/form_auditor/__init__.py | 14 +- backend/app/agents/form_auditor/models.py | 222 ++++++++++- backend/app/routers/agent.py | 372 ++--------------- backend/app/routers/dataroom.py | 16 +- backend/data/audit_report.json | 1 + backend/data/schemas/schema_103b7090a545.json | 74 ---- docker-compose.yml | 2 + frontend/src/components/AuditReport.tsx | 377 ++++++++++-------- frontend/src/components/ChatExample.tsx | 241 ----------- frontend/src/components/ChatTab.tsx | 146 +++++-- frontend/src/components/DashboardTab.tsx | 41 -- frontend/src/components/DataroomView.tsx | 159 ++++++-- frontend/src/components/WebSearchResults.tsx | 206 ++++++++++ .../appendonlydir/appendonly.aof.1.base.rdb | Bin 0 -> 131 bytes .../appendonlydir/appendonly.aof.1.incr.aof | 302 ++++++++++++++ redis_data/dump.rdb | Bin 500 -> 4172 bytes 16 files changed, 1227 insertions(+), 946 deletions(-) create mode 100644 backend/data/audit_report.json delete mode 100644 backend/data/schemas/schema_103b7090a545.json delete mode 100644 frontend/src/components/ChatExample.tsx create mode 100644 frontend/src/components/WebSearchResults.tsx create mode 100644 redis_data/appendonlydir/appendonly.aof.1.base.rdb create mode 100644 redis_data/appendonlydir/appendonly.aof.1.incr.aof diff --git a/backend/app/agents/form_auditor/__init__.py b/backend/app/agents/form_auditor/__init__.py index 77167c4..ea29f51 100644 --- a/backend/app/agents/form_auditor/__init__.py +++ b/backend/app/agents/form_auditor/__init__.py @@ -11,15 +11,23 @@ from .models import ( async def build_audit_report(payload: dict[str, Any]) -> AuditReport: - extraction_payload = payload.get("extraction") + metadata_raw: Any = None + extraction_payload: Any = None + + if isinstance(payload, dict) and "extraction" in payload: + extraction_payload = payload.get("extraction") + metadata_raw = payload.get("metadata") + else: + extraction_payload = payload + if extraction_payload is None: - raise ValueError("Payload missing 'extraction' key.") + raise ValueError("Payload missing extraction data.") + extraction = ExtractedIrsForm990PfDataSchema.model_validate(extraction_payload) initial_findings = prepare_initial_findings(extraction) metadata: dict[str, Any] = {} - metadata_raw = payload.get("metadata") if isinstance(metadata_raw, dict): metadata = {str(k): v for k, v in metadata_raw.items()} diff --git a/backend/app/agents/form_auditor/models.py b/backend/app/agents/form_auditor/models.py index 2e7d716..800fffd 100644 --- a/backend/app/agents/form_auditor/models.py +++ b/backend/app/agents/form_auditor/models.py @@ -1,9 +1,10 @@ from __future__ import annotations +import re from enum import Enum from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator class Severity(str, Enum): @@ -497,6 +498,214 @@ class TaxCompliancePenalties(BaseModel): ) +_OFFICER_HOURS_PATTERN = re.compile(r"([\d.]+)\s*hrs?/wk", re.IGNORECASE) + + +def _parse_officer_list(entries: list[str] | None) -> list[dict[str, Any]]: + if not entries: + return [] + parsed: list[dict[str, Any]] = [] + for raw in entries: + if not isinstance(raw, str): + continue + parts = [part.strip() for part in raw.split(",")] + name = parts[0] if parts else "" + title = parts[1] if len(parts) > 1 else "" + role = parts[3] if len(parts) > 3 else "" + hours = 0.0 + match = _OFFICER_HOURS_PATTERN.search(raw) + if match: + try: + hours = float(match.group(1)) + except ValueError: + hours = 0.0 + parsed.append( + { + "name": name, + "title_position": title, + "average_hours_per_week": hours, + "related_party_transactions": "", + "former_officer": "", + "governance_role": role, + } + ) + return parsed + + +def _build_program_accomplishments( + descriptions: list[str] | None, +) -> list[dict[str, Any]]: + if not descriptions: + return [] + programs: list[dict[str, Any]] = [] + for idx, description in enumerate(descriptions, start=1): + if not isinstance(description, str): + continue + programs.append( + { + "program_name": f"Program {idx}", + "program_description": description.strip(), + "expenses": 0.0, + "grants": 0.0, + "revenue_generated": 0.0, + "quantitative_outputs": "", + } + ) + return programs + + +def _transform_flat_payload(data: dict[str, Any]) -> dict[str, Any]: + def get_str(key: str) -> str: + value = data.get(key) + if value is None: + return "" + return str(value) + + def get_value(key: str, default: Any = 0) -> Any: + return data.get(key, default) + + transformed: dict[str, Any] = { + "core_organization_metadata": { + "ein": get_str("ein"), + "legal_name": get_str("legal_name"), + "phone_number": get_str("phone_number"), + "website_url": get_str("website_url"), + "return_type": get_str("return_type"), + "amended_return": get_str("amended_return"), + "group_exemption_number": get_str("group_exemption_number"), + "subsection_code": get_str("subsection_code"), + "ruling_date": get_str("ruling_date"), + "accounting_method": get_str("accounting_method"), + "organization_type": get_str("organization_type"), + "year_of_formation": get_str("year_of_formation"), + "incorporation_state": get_str("incorporation_state"), + }, + "revenue_breakdown": { + "total_revenue": get_value("total_revenue"), + "contributions_gifts_grants": get_value("contributions_gifts_grants"), + "program_service_revenue": get_value("program_service_revenue"), + "membership_dues": get_value("membership_dues"), + "investment_income": get_value("investment_income"), + "gains_losses_sales_assets": get_value("gains_losses_sales_assets"), + "rental_income": get_value("rental_income"), + "related_organizations_revenue": get_value("related_organizations_revenue"), + "gaming_revenue": get_value("gaming_revenue"), + "other_revenue": get_value("other_revenue"), + "government_grants": get_value("government_grants"), + "foreign_contributions": get_value("foreign_contributions"), + }, + "expenses_breakdown": { + "total_expenses": get_value("total_expenses"), + "program_services_expenses": get_value("program_services_expenses"), + "management_general_expenses": get_value("management_general_expenses"), + "fundraising_expenses": get_value("fundraising_expenses"), + "grants_us_organizations": get_value("grants_us_organizations"), + "grants_us_individuals": get_value("grants_us_individuals"), + "grants_foreign_organizations": get_value("grants_foreign_organizations"), + "grants_foreign_individuals": get_value("grants_foreign_individuals"), + "compensation_officers": get_value("compensation_officers"), + "compensation_other_staff": get_value("compensation_other_staff"), + "payroll_taxes_benefits": get_value("payroll_taxes_benefits"), + "professional_fees": get_value("professional_fees"), + "office_occupancy_costs": get_value("office_occupancy_costs"), + "information_technology_costs": get_value("information_technology_costs"), + "travel_conference_expenses": get_value("travel_conference_expenses"), + "depreciation_amortization": get_value("depreciation_amortization"), + "insurance": get_value("insurance"), + }, + "balance_sheet": data.get("balance_sheet") or {}, + "officers_directors_trustees_key_employees": _parse_officer_list( + data.get("officers_list") + ), + "governance_management_disclosure": { + "governing_body_size": get_value("governing_body_size"), + "independent_members": get_value("independent_members"), + "financial_statements_reviewed": get_str("financial_statements_reviewed"), + "form_990_provided_to_governing_body": get_str( + "form_990_provided_to_governing_body" + ), + "conflict_of_interest_policy": get_str("conflict_of_interest_policy"), + "whistleblower_policy": get_str("whistleblower_policy"), + "document_retention_policy": get_str("document_retention_policy"), + "ceo_compensation_review_process": get_str( + "ceo_compensation_review_process" + ), + "public_disclosure_practices": get_str("public_disclosure_practices"), + }, + "program_service_accomplishments": _build_program_accomplishments( + data.get("program_accomplishments_list") + ), + "fundraising_grantmaking": { + "total_fundraising_event_revenue": get_value( + "total_fundraising_event_revenue" + ), + "total_fundraising_event_expenses": get_value( + "total_fundraising_event_expenses" + ), + "professional_fundraiser_fees": get_value("professional_fundraiser_fees"), + }, + "functional_operational_data": { + "number_of_employees": get_value("number_of_employees"), + "number_of_volunteers": get_value("number_of_volunteers"), + "occupancy_costs": get_value("occupancy_costs"), + "fundraising_method_descriptions": get_str( + "fundraising_method_descriptions" + ), + "joint_ventures_disregarded_entities": get_str( + "joint_ventures_disregarded_entities" + ), + }, + "compensation_details": { + "base_compensation": get_value("base_compensation"), + "bonus": get_value("bonus"), + "incentive": get_value("incentive"), + "other": get_value("other_compensation", get_value("other", 0)), + "non_fixed_compensation": get_str("non_fixed_compensation"), + "first_class_travel": get_str("first_class_travel"), + "housing_allowance": get_str("housing_allowance"), + "expense_account_usage": get_str("expense_account_usage"), + "supplemental_retirement": get_str("supplemental_retirement"), + }, + "political_lobbying_activities": { + "lobbying_expenditures_direct": get_value("lobbying_expenditures_direct"), + "lobbying_expenditures_grassroots": get_value( + "lobbying_expenditures_grassroots" + ), + "election_501h_status": get_str("election_501h_status"), + "political_campaign_expenditures": get_value( + "political_campaign_expenditures" + ), + "related_organizations_affiliates": get_str( + "related_organizations_affiliates" + ), + }, + "investments_endowment": { + "investment_types": get_str("investment_types"), + "donor_restricted_endowment_values": get_value( + "donor_restricted_endowment_values" + ), + "net_appreciation_depreciation": get_value("net_appreciation_depreciation"), + "related_organization_transactions": get_str( + "related_organization_transactions" + ), + "loans_to_from_related_parties": get_str("loans_to_from_related_parties"), + }, + "tax_compliance_penalties": { + "penalties_excise_taxes_reported": get_str( + "penalties_excise_taxes_reported" + ), + "unrelated_business_income_disclosure": get_str( + "unrelated_business_income_disclosure" + ), + "foreign_bank_account_reporting": get_str("foreign_bank_account_reporting"), + "schedule_o_narrative_explanations": get_str( + "schedule_o_narrative_explanations" + ), + }, + } + return transformed + + class ExtractedIrsForm990PfDataSchema(BaseModel): core_organization_metadata: CoreOrganizationMetadata = Field( ..., @@ -514,7 +723,7 @@ class ExtractedIrsForm990PfDataSchema(BaseModel): title="Expenses Breakdown", ) balance_sheet: dict[str, Any] = Field( - ..., + default_factory=dict, description="Assets, liabilities, and net assets at year end.", title="Balance Sheet Data", ) @@ -566,6 +775,15 @@ class ExtractedIrsForm990PfDataSchema(BaseModel): title="Tax Compliance / Penalties", ) + @model_validator(mode="before") + @classmethod + def _ensure_structure(cls, value: Any) -> Any: + if not isinstance(value, dict): + return value + if "core_organization_metadata" in value: + return value + return _transform_flat_payload(value) + class ValidatorState(BaseModel): extraction: ExtractedIrsForm990PfDataSchema diff --git a/backend/app/routers/agent.py b/backend/app/routers/agent.py index 1b27cf0..b79f2fe 100644 --- a/backend/app/routers/agent.py +++ b/backend/app/routers/agent.py @@ -1,5 +1,9 @@ -from fastapi import APIRouter -from pydantic_ai import Agent +import json +from dataclasses import dataclass +from typing import Annotated, Any + +from fastapi import APIRouter, Header +from pydantic_ai import Agent, RunContext from pydantic_ai.models.openai import OpenAIChatModel from pydantic_ai.providers.azure import AzureProvider from pydantic_ai.ui.vercel_ai import VercelAIAdapter @@ -8,6 +12,7 @@ from starlette.responses import Response from app.agents import form_auditor, web_search from app.core.config import settings +from app.services.extracted_data_service import get_extracted_data_service provider = AzureProvider( azure_endpoint=settings.AZURE_OPENAI_ENDPOINT, @@ -15,347 +20,26 @@ provider = AzureProvider( api_key=settings.AZURE_OPENAI_API_KEY, ) model = OpenAIChatModel(model_name="gpt-4o", provider=provider) -agent = Agent(model=model) + + +@dataclass +class Deps: + extracted_data: dict[str, Any] + + +agent = Agent(model=model, deps_type=Deps) 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 -async def build_audit_report(): +@agent.tool +async def build_audit_report(ctx: RunContext[Deps]): """Calls the audit subagent to get a full audit report of the organization""" + data = ctx.deps.extracted_data + + with open("data/audit_report.json", "w") as f: + json.dump(data, f) + result = await form_auditor.build_audit_report(data) return result.model_dump() @@ -370,5 +54,13 @@ async def search_web_information(query: str, max_results: int = 5): @router.post("/chat") -async def chat(request: Request) -> Response: - return await VercelAIAdapter.dispatch_request(request, agent=agent) +async def chat(request: Request, tema: Annotated[str, Header()]) -> Response: + extracted_data_service = get_extracted_data_service() + + data = await extracted_data_service.get_by_tema(tema) + + extracted_data = [doc.get_extracted_data() for doc in data] + + deps = Deps(extracted_data=extracted_data[0]) + + return await VercelAIAdapter.dispatch_request(request, agent=agent, deps=deps) diff --git a/backend/app/routers/dataroom.py b/backend/app/routers/dataroom.py index dff2e3d..8ef752a 100644 --- a/backend/app/routers/dataroom.py +++ b/backend/app/routers/dataroom.py @@ -14,8 +14,14 @@ logger = logging.getLogger(__name__) class DataroomCreate(BaseModel): name: str - collection: str = "" - storage: str = "" + + @property + def collection(self) -> str: + return self.name.lower().replace(" ", "_") + + @property + def storage(self) -> str: + return self.name.lower().replace(" ", "_") class DataroomInfo(BaseModel): @@ -110,9 +116,9 @@ async def dataroom_info(dataroom_name: str) -> DataroomInfo: if collection_info_response: collection_info = { "vectors_count": collection_info_response.vectors_count, - "indexed_vectors_count": collection_info_response.indexed_vectors_count, - "points_count": collection_info_response.points_count, - "segments_count": collection_info_response.segments_count, + "indexed_vectors_count": collection_info_response.vectors_count, + "points_count": collection_info_response.vectors_count, + "segments_count": collection_info_response.vectors_count, "status": collection_info_response.status, } vector_count = collection_info_response.vectors_count diff --git a/backend/data/audit_report.json b/backend/data/audit_report.json new file mode 100644 index 0000000..3ba85b7 --- /dev/null +++ b/backend/data/audit_report.json @@ -0,0 +1 @@ +{"ein": "31-0329725", "legal_name": "0220 ABBEY CREDIT UNION INC", "phone_number": "(397) 898-7800", "website_url": "www.abbeycu.com", "return_type": "990", "amended_return": "", "group_exemption_number": "", "subsection_code": "501(c)(14)", "ruling_date": "", "accounting_method": "Accrual", "organization_type": "Corporation", "year_of_formation": "1937", "incorporation_state": "OH", "total_revenue": 6146738, "contributions_gifts_grants": 0, "program_service_revenue": 5656278, "membership_dues": 0, "investment_income": 490460, "gains_losses_sales_assets": 0, "rental_income": 0, "related_organizations_revenue": 0, "gaming_revenue": 0, "other_revenue": 0, "government_grants": 0, "foreign_contributions": 0, "total_expenses": 5526970, "program_services_expenses": 386454, "management_general_expenses": 5140516, "fundraising_expenses": 0, "grants_us_organizations": 0, "grants_us_individuals": 0, "grants_foreign_organizations": 0, "grants_foreign_individuals": 0, "compensation_officers": 629393, "compensation_other_staff": 1329887, "payroll_taxes_benefits": 452511, "professional_fees": 96471, "office_occupancy_costs": 292244, "information_technology_costs": 287125, "travel_conference_expenses": 26662, "depreciation_amortization": 236836, "insurance": 39570, "officers_list": ["Lisa Burk, Chief Experience Officer, 40.00 hrs/wk, Officer, $73,286 compensation, $8,931 other compensation", "Eric Stetzel, VP of Business Services, 40.00 hrs/wk, Key employee, $111,064 compensation, $15,979 other compensation", "Dean Pielemeier, CEO, 40.00 hrs/wk, Officer, $212,994 compensation, $20,202 other compensation", "Blanca Criner, Chief Marketing and Business Development Officer, 40.00 hrs/wk, Officer, $121,036 compensation, $4,540 other compensation", "Teri Puthoff, CFO, 40.00 hrs/wk, Officer, $120,962 compensation, $15,754 other compensation", "Michael Thein, Chairman, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Latham Farley, Board Member, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Steve Wilmoth, Treasurer, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Julie Trick, Secretary, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Michele Blake, Board Member, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Cheryl Saunders, Board Member, 1.00 hrs/wk, Individual trustee or director, $0 compensation", "Heather Scaggs-Richardson, Vice Chairman, 1.00 hrs/wk, Individual trustee or director, $0 compensation"], "governing_body_size": 7, "independent_members": 7, "financial_statements_reviewed": "No", "form_990_provided_to_governing_body": "Yes", "conflict_of_interest_policy": "Yes", "whistleblower_policy": "Yes", "document_retention_policy": "Yes", "ceo_compensation_review_process": "Compensation committee, independent compensation consultant, approval by the board or compensation committee", "public_disclosure_practices": "Audited financial statements are handed out each year at the annual meeting. Other documents are not made available to the public unless requested.", "program_accomplishments_list": ["Our mission is to help our members improve their economic well being and quality of life by being competitive convenient and cutting edge.", "Abbey provides checking savings accounts money market accounts certificates of deposit and IRAs. Abbey also offers free atm debit card services wire transfer services mobile banking and insurance related products.", "Abbey provides personal vehicle mortgage and credit card loans to its members."], "total_fundraising_event_revenue": 0, "total_fundraising_event_expenses": 0, "professional_fundraiser_fees": 0, "number_of_employees": 50, "number_of_volunteers": 7, "occupancy_costs": 132399, "fundraising_method_descriptions": "", "joint_ventures_disregarded_entities": "", "base_compensation": 546779, "bonus": 34415, "incentive": 59692, "other_compensation": 24691, "non_fixed_compensation": "", "first_class_travel": "No", "housing_allowance": "No", "expense_account_usage": "No", "supplemental_retirement": "No", "lobbying_expenditures_direct": 0, "lobbying_expenditures_grassroots": 0, "election_501h_status": "", "political_campaign_expenditures": 0, "related_organizations_affiliates": "", "investment_types": "Publicly traded securities", "donor_restricted_endowment_values": 0, "net_appreciation_depreciation": -2190939, "related_organization_transactions": "", "loans_to_from_related_parties": "Lynn Cook (Retired CEO) Life Insurance loan, $1,400,000; Dean Pielemeier (CEO) Life Insurance loan, $1,239,997", "penalties_excise_taxes_reported": "No", "unrelated_business_income_disclosure": "Yes", "foreign_bank_account_reporting": "No", "schedule_o_narrative_explanations": "Membership to Abbey is open to those who live work worship or attend school in Montgomery Miami Shelby Darke or Greene counties. Abbey CU is owned by the people who open a savings account at Abbey CU. Each member has one vote at the annual election of the Board of Directors. Decisions that require approval: Ohio Division of Financial Institutions Pursuant to regulation. CFO prepares the form and CEO reviews before submitting. Approval of Compensation Committee and Board of Directors for CEO compensation. CEO establishes compensation for Key Employees. Audited financial statements are handed out each year at the annual meeting. Other documents are not made available to the public unless requested."} \ No newline at end of file diff --git a/backend/data/schemas/schema_103b7090a545.json b/backend/data/schemas/schema_103b7090a545.json deleted file mode 100644 index 3c0ac4e..0000000 --- a/backend/data/schemas/schema_103b7090a545.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "schema_id": "schema_103b7090a545", - "schema_name": "Testing", - "description": "Informacion de las facturas de taxes (Prueba)", - "fields": [ - { - "name": "employed_id", - "type": "string", - "description": "id number from employed", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "ej_numero", - "type": "integer", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "ej_decimal", - "type": "float", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "ej_booleano", - "type": "boolean", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "ej_list", - "type": "array_string", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "ej_listnum", - "type": "array_integer", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - }, - { - "name": "fecha", - "type": "date", - "description": "ejemplo", - "required": false, - "min_value": null, - "max_value": null, - "pattern": null - } - ], - "created_at": "2025-11-07T17:49:18.193078", - "updated_at": "2025-11-07T22:19:53.434529", - "tema": "ULTA", - "is_global": true -} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2f0df99..283ae4d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,8 @@ services: ports: - 6379:6379 - 8001:8001 + environment: + REDIS_ARGS: --appendonly yes volumes: - ./redis_data:/data restart: unless-stopped diff --git a/frontend/src/components/AuditReport.tsx b/frontend/src/components/AuditReport.tsx index 13fb9cc..9180b2a 100644 --- a/frontend/src/components/AuditReport.tsx +++ b/frontend/src/components/AuditReport.tsx @@ -1,16 +1,15 @@ -import React from "react"; +import React, { useState } from "react"; import { AlertTriangle, CheckCircle, XCircle, FileText, - Building, - Calendar, AlertCircle, - TrendingUp, + ChevronDown, + ChevronRight, Shield, + Info, } from "lucide-react"; -import { cn } from "@/lib/utils"; type Severity = "Pass" | "Warning" | "Error"; @@ -45,42 +44,45 @@ interface AuditReportProps { data: AuditReportData; } -const getSeverityIcon = (severity: Severity) => { +const getSeverityIcon = (severity: Severity, size = "w-4 h-4") => { switch (severity) { case "Pass": - return ; + return ; case "Warning": - return ; + return ; case "Error": - return ; + return ; default: - return ; + 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 getSeverityBadge = (severity: Severity) => { + const colors = { + Pass: "bg-green-100 text-green-800 border-green-200", + Warning: "bg-yellow-100 text-yellow-800 border-yellow-200", + Error: "bg-red-100 text-red-800 border-red-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"; + return ( + + {getSeverityIcon(severity, "w-3 h-3")} + {severity} + + ); }; export const AuditReport: React.FC = ({ data }) => { + const [expandedSections, setExpandedSections] = useState>( + new Set(), + ); + const [showAllFindings, setShowAllFindings] = useState(false); + const { - organisation_ein, organisation_name, + organisation_ein, year, overall_severity, findings, @@ -95,186 +97,227 @@ export const AuditReport: React.FC = ({ data }) => { Error: findings.filter((f) => f.severity === "Error").length, }; + const toggleSection = (section: string) => { + const newExpanded = new Set(expandedSections); + if (newExpanded.has(section)) { + newExpanded.delete(section); + } else { + newExpanded.add(section); + } + setExpandedSections(newExpanded); + }; + + const criticalFindings = findings.filter((f) => f.severity === "Error"); + return (
-
- {/* Header */} -
-
-
-
- + {/* Compact Header */} +
+
+
+ +
+

+ {organisation_name} +

+
+ EIN: {organisation_ein} + {year && {year}}
-
-

- - {organisation_name} -

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

Audit Summary

-
+ {/* Quick Stats */} +
+
+ + Audit Results + +
+ {severityStats.Pass > 0 && (
- - + + {severityStats.Pass} - Passed
+ )} + {severityStats.Warning > 0 && (
- - + + {severityStats.Warning} - Warnings
+ )} + {severityStats.Error > 0 && (
- - + + {severityStats.Error} - Errors +
+ )} +
+
+
+ +
+ {/* Summary */} + {overall_summary && ( +
+
+ +
+

+ Summary +

+

{overall_summary}

-
- - {/* 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}

+ {/* Critical Issues (if any) */} + {criticalFindings.length > 0 && ( +
+

+ + Critical Issues ({criticalFindings.length}) +

+
+ {criticalFindings.slice(0, 2).map((finding, index) => ( +
+ {finding.category}:{" "} + {finding.message}
))} + {criticalFindings.length > 2 && ( + + )}
)} - {/* Detailed Findings */} -
-

- Detailed Findings ({findings.length}) -

-
- {findings.map((finding, index) => ( -
-
-
- {getSeverityIcon(finding.severity)} -
- {finding.category} - - #{finding.check_id} + {/* Sections Overview */} + {sections.length > 0 && ( +
+ + + {expandedSections.has("sections") && ( +
+ {sections.map((section, index) => ( +
+
+ + {section.section} +
+ {getSeverityIcon(section.severity)} + + {Math.round(section.confidence * 100)}% + +
+

{section.summary}

-
- - + ))} +
+ )} +
+ )} + + {/* All Findings */} +
+ + + {expandedSections.has("findings") && ( +
+ {findings.map((finding, index) => ( +
+
+
+ {getSeverityIcon(finding.severity)} +
+ + {finding.category} + + + #{finding.check_id} + +
+
+ {Math.round(finding.confidence * 100)}% confidence
+ +

+ {finding.message} +

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

+ {finding.mitigation} +

+
+ )}
- -

- {finding.message} -

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

{finding.mitigation}

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

- Additional Notes -

-

{notes}

+
+
+ +
+

+ Notes +

+

{notes}

+
+
)}
diff --git a/frontend/src/components/ChatExample.tsx b/frontend/src/components/ChatExample.tsx deleted file mode 100644 index 221fd82..0000000 --- a/frontend/src/components/ChatExample.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { - Conversation, - ConversationContent, - ConversationScrollButton, -} from "@/components/ai-elements/conversation"; -import { Message, MessageContent } from "@/components/ai-elements/message"; -import { - PromptInput, - PromptInputActionAddAttachments, - PromptInputActionMenu, - PromptInputActionMenuContent, - PromptInputActionMenuTrigger, - PromptInputAttachment, - PromptInputAttachments, - PromptInputBody, - PromptInputButton, - PromptInputHeader, - type PromptInputMessage, - PromptInputSelect, - PromptInputSelectContent, - PromptInputSelectItem, - PromptInputSelectTrigger, - PromptInputSelectValue, - PromptInputSubmit, - PromptInputTextarea, - PromptInputFooter, - PromptInputTools, -} from "@/components/ai-elements/prompt-input"; -import { Action, Actions } from "@/components/ai-elements/actions"; -import { Fragment, useState } from "react"; -import { useChat } from "@ai-sdk/react"; -import { Response } from "@/components/ai-elements/response"; -import { CopyIcon, GlobeIcon, RefreshCcwIcon } from "lucide-react"; -import { - Source, - Sources, - SourcesContent, - SourcesTrigger, -} from "@/components/ai-elements/sources"; -import { - Reasoning, - ReasoningContent, - ReasoningTrigger, -} from "@/components/ai-elements/reasoning"; -import { Loader } from "@/components/ai-elements/loader"; -import { DefaultChatTransport } from "ai"; - -const models = [ - { - name: "GPT 4o", - value: "openai/gpt-4o", - }, - { - name: "Deepseek R1", - value: "deepseek/deepseek-r1", - }, -]; - -const ChatBotDemo = () => { - const [input, setInput] = useState(""); - const [model, setModel] = useState(models[0].value); - const [webSearch, setWebSearch] = useState(false); - const { messages, sendMessage, status, regenerate } = useChat({ - transport: new DefaultChatTransport({ - api: "/api/v1/chat", - }), - }); - - const handleSubmit = (message: PromptInputMessage) => { - const hasText = Boolean(message.text); - const hasAttachments = Boolean(message.files?.length); - - if (!(hasText || hasAttachments)) { - return; - } - - sendMessage( - { - text: message.text || "Sent with attachments", - files: message.files, - }, - { - body: { - model: model, - webSearch: webSearch, - }, - }, - ); - setInput(""); - }; - - return ( -
-
- - - {messages.map((message) => ( -
- {message.role === "assistant" && - message.parts.filter((part) => part.type === "source-url") - .length > 0 && ( - - part.type === "source-url", - ).length - } - /> - {message.parts - .filter((part) => part.type === "source-url") - .map((part, i) => ( - - - - ))} - - )} - {message.parts.map((part, i) => { - switch (part.type) { - case "text": - return ( - - - - {part.text} - - - {message.role === "assistant" && - i === messages.length - 1 && ( - - regenerate()} - label="Retry" - > - - - - navigator.clipboard.writeText(part.text) - } - label="Copy" - > - - - - )} - - ); - case "reasoning": - return ( - - - {part.text} - - ); - default: - return null; - } - })} -
- ))} - {status === "submitted" && } -
- -
- - - - - {(attachment) => } - - - - setInput(e.target.value)} - value={input} - /> - - - - - - - - - - setWebSearch(!webSearch)} - > - - Search - - { - setModel(value); - }} - value={model} - > - - - - - {models.map((model) => ( - - {model.name} - - ))} - - - - - - -
-
- ); -}; - -export default ChatBotDemo; diff --git a/frontend/src/components/ChatTab.tsx b/frontend/src/components/ChatTab.tsx index 0cec45a..60cfd12 100644 --- a/frontend/src/components/ChatTab.tsx +++ b/frontend/src/components/ChatTab.tsx @@ -1,8 +1,3 @@ -import { - Conversation, - ConversationContent, - ConversationScrollButton, -} from "@/components/ai-elements/conversation"; import { Message, MessageContent } from "@/components/ai-elements/message"; import { PromptInput, @@ -31,8 +26,10 @@ import { Bot, AlertCircle, PaperclipIcon, + User, } from "lucide-react"; import { AuditReport } from "./AuditReport"; +import { WebSearchResults } from "./WebSearchResults"; import { Loader } from "@/components/ai-elements/loader"; import { DefaultChatTransport } from "ai"; @@ -53,6 +50,9 @@ export function ChatTab({ selectedTema }: ChatTabProps) { } = useChat({ transport: new DefaultChatTransport({ api: "/api/v1/agent/chat", + headers: { + tema: selectedTema || "", + }, }), onError: (error) => { setError(`Error en el chat: ${error.message}`); @@ -103,23 +103,6 @@ export function ChatTab({ selectedTema }: ChatTabProps) { return (
- {/* Chat Header */} -
-
-
- -
-
-

- Chat con {selectedTema} -

-

- Haz preguntas sobre los documentos de este dataroom -

-
-
-
- {/* Chat Content */}
@@ -159,30 +142,60 @@ export function ChatTab({ selectedTema }: ChatTabProps) { case "text": return ( - - - {part.text} - - + {message.role === "user" ? ( +
+
+ + + {part.text} + + +
+
+ +
+
+ ) : ( +
+
+ +
+
+ + + {part.text} + + +
+
+ )} {message.role === "assistant" && i === message.parts.length - 1 && ( - - regenerate()} - label="Regenerar" - disabled={status === "streaming"} - > - - - - navigator.clipboard.writeText(part.text) - } - label="Copiar" - > - - - +
+ + regenerate()} + label="Regenerar" + disabled={status === "streaming"} + > + + + + navigator.clipboard.writeText(part.text) + } + label="Copiar" + > + + + +
)}
); @@ -231,6 +244,51 @@ export function ChatTab({ selectedTema }: ChatTabProps) { default: return null; } + case "tool-search_web_information": + switch (part.state) { + case "input-available": + return ( +
+
+ + Searching the web... + +
+ ); + case "output-available": + return ( +
+
+ +
+
+ ); + case "output-error": + return ( +
+
+ + + Error searching the web + +
+

+ {part.errorText} +

+
+ ); + default: + return null; + } default: return null; } diff --git a/frontend/src/components/DashboardTab.tsx b/frontend/src/components/DashboardTab.tsx index a8507d7..38075eb 100644 --- a/frontend/src/components/DashboardTab.tsx +++ b/frontend/src/components/DashboardTab.tsx @@ -281,47 +281,6 @@ export function DashboardTab({ selectedTema }: DashboardTabProps) {
)} - - {/* Collection Details */} - {dataroomInfo.collection_info && ( -
-

- Detalles de la Colección -

-
-
-
-

- Total Vectores -

-

- {dataroomInfo.collection_info.vectors_count} -

-
-
-

- Vectores Indexados -

-

- {dataroomInfo.collection_info.indexed_vectors_count} -

-
-
-

Puntos

-

- {dataroomInfo.collection_info.points_count} -

-
-
-

Segmentos

-

- {dataroomInfo.collection_info.segments_count} -

-
-
-
-
- )}
); } diff --git a/frontend/src/components/DataroomView.tsx b/frontend/src/components/DataroomView.tsx index 4792ddf..df73bd7 100644 --- a/frontend/src/components/DataroomView.tsx +++ b/frontend/src/components/DataroomView.tsx @@ -1,7 +1,14 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useFileStore } from "@/stores/fileStore"; -import { api } from "@/services/api"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Expand, Minimize2 } from "lucide-react"; import { FilesTab } from "./FilesTab"; import { DashboardTab } from "./DashboardTab"; import { ChatTab } from "./ChatTab"; @@ -11,15 +18,69 @@ interface DataroomViewProps { } export function DataroomView({ onProcessingChange }: DataroomViewProps = {}) { - const { selectedTema, files } = useFileStore(); + const { selectedTema } = useFileStore(); const [processing, setProcessing] = useState(false); + const [fullscreenTab, setFullscreenTab] = useState(null); + const [currentTab, setCurrentTab] = useState("overview"); const handleProcessingChange = (isProcessing: boolean) => { setProcessing(isProcessing); onProcessingChange?.(isProcessing); }; + const openFullscreen = (tabValue: string) => { + setFullscreenTab(tabValue); + }; + + const closeFullscreen = () => { + setFullscreenTab(null); + }; + + const renderTabContent = (tabValue: string, isFullscreen = false) => { + const className = isFullscreen ? "h-[calc(100vh-8rem)] flex flex-col" : ""; + + switch (tabValue) { + case "overview": + return ( +
+ +
+ ); + case "files": + return ( +
+ +
+ ); + case "chat": + return ( +
+ +
+ ); + default: + return null; + } + }; + + const getTabTitle = (tabValue: string) => { + switch (tabValue) { + case "overview": + return "Overview"; + case "files": + return "Files"; + case "chat": + return "Chat"; + default: + return ""; + } + }; + return (
@@ -39,46 +100,86 @@ export function DataroomView({ onProcessingChange }: DataroomViewProps = {}) {
- +
- - +
+ + Overview + + + Files + + + Chat + +
+ +
- + {renderTabContent("overview")} - + {renderTabContent("files")} - + {renderTabContent("chat")}
+ + !open && closeFullscreen()} + > + + + + {selectedTema + ? `${getTabTitle(fullscreenTab || "")} - ${selectedTema}` + : getTabTitle(fullscreenTab || "")} + + + + +
+ {fullscreenTab && renderTabContent(fullscreenTab, true)} +
+
+
); } diff --git a/frontend/src/components/WebSearchResults.tsx b/frontend/src/components/WebSearchResults.tsx new file mode 100644 index 0000000..bc297d7 --- /dev/null +++ b/frontend/src/components/WebSearchResults.tsx @@ -0,0 +1,206 @@ +import React, { useState } from "react"; +import { + Globe, + ExternalLink, + Search, + ChevronDown, + ChevronRight, + Info, + Star, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface SearchResult { + title: string; + url: string; + content: string; + score?: number; +} + +interface WebSearchData { + query: string; + results: SearchResult[]; + summary: string; + total_results: number; +} + +interface WebSearchResultsProps { + data: WebSearchData; +} + +const getScoreColor = (score?: number) => { + if (!score) return "text-gray-500"; + if (score >= 0.8) return "text-green-600"; + if (score >= 0.6) return "text-yellow-600"; + return "text-gray-500"; +}; + +const getScoreStars = (score?: number) => { + if (!score) return 0; + return Math.round(score * 5); +}; + +const truncateContent = (content: string, maxLength: number = 200) => { + if (content.length <= maxLength) return content; + return content.slice(0, maxLength) + "..."; +}; + +export const WebSearchResults: React.FC = ({ data }) => { + const [expandedResults, setExpandedResults] = useState>(new Set()); + const [showAllResults, setShowAllResults] = useState(false); + + const { query, results, summary, total_results } = data; + + const toggleResult = (index: number) => { + const newExpanded = new Set(expandedResults); + if (newExpanded.has(index)) { + newExpanded.delete(index); + } else { + newExpanded.add(index); + } + setExpandedResults(newExpanded); + }; + + const visibleResults = showAllResults ? results : results.slice(0, 3); + + return ( +
+ {/* Header */} +
+
+
+ +
+

Web Search Results

+
+ + "{query}" +
+
+
+
+
{results.length}
+
+ of {total_results} results +
+
+
+
+ +
+ {/* Summary */} + {summary && ( +
+
+ +
+

+ Summary +

+

{summary}

+
+
+
+ )} + + {/* Search Results */} +
+ {visibleResults.map((result, index) => { + const isExpanded = expandedResults.has(index); + const stars = getScoreStars(result.score); + + return ( +
+
+
+
+

+ {result.title} +

+
+ + + {new URL(result.url).hostname} + + {result.score && ( +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ + {Math.round((result.score || 0) * 100)}% + +
+ )} +
+
+
+ +
+ {isExpanded ? result.content : truncateContent(result.content)} +
+ + {result.content.length > 200 && ( + + )} +
+
+ ); + })} +
+ + {/* Show More/Less Button */} + {results.length > 3 && ( +
+ +
+ )} + + {/* No Results */} + {results.length === 0 && ( +
+ +

No results found for "{query}"

+
+ )} +
+
+ ); +}; diff --git a/redis_data/appendonlydir/appendonly.aof.1.base.rdb b/redis_data/appendonlydir/appendonly.aof.1.base.rdb new file mode 100644 index 0000000000000000000000000000000000000000..05630ae4c4ac11dc8bc4b44caebcea6f0015a564 GIT binary patch literal 131 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 N_vmaga~A*i69D;GJs$u7