diff --git a/backend/src/ai/LocalAIApi.js b/backend/src/ai/LocalAIApi.js index a4df0f4..fd571ae 100644 --- a/backend/src/ai/LocalAIApi.js +++ b/backend/src/ai/LocalAIApi.js @@ -154,7 +154,6 @@ async function awaitResponse(aiRequestId, options = {}) { const interval = Math.max(Number(options.interval ?? 5), 1); const deadline = Date.now() + Math.max(timeout, interval) * 1000; - /* eslint-disable no-constant-condition */ while (true) { const statusResp = await fetchStatus(aiRequestId, { headers: options.headers, @@ -307,7 +306,7 @@ function buildUrl(pathValue, baseUrl) { } function resolveStatusPath(aiRequestId, cfg) { - const basePath = (cfg.responsesPath || "").replace(new RegExp("/+"), ""); + const basePath = (cfg.responsesPath || "").replace(/\/+$/, ""); if (!basePath) { return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`; } diff --git a/backend/src/auth/auth.js b/backend/src/auth/auth.js index c166d68..251c149 100644 --- a/backend/src/auth/auth.js +++ b/backend/src/auth/auth.js @@ -56,7 +56,7 @@ passport.use(new MicrosoftStrategy({ )); function socialStrategy(email, profile, provider, done) { - db.users.findOrCreate({where: {email, provider}}).then(([user]) => { + db.users.findOrCreate({where: {email, provider}}).then(([user, created]) => { const body = { id: user.id, email: user.email, @@ -65,4 +65,4 @@ function socialStrategy(email, profile, provider, done) { const token = helpers.jwtSign({user: body}); return done(null, {token}); }); -} \ No newline at end of file +} diff --git a/backend/src/db/api/alerts.js b/backend/src/db/api/alerts.js index 24b57d4..82605ac 100644 --- a/backend/src/db/api/alerts.js +++ b/backend/src/db/api/alerts.js @@ -1,4 +1,7 @@ + const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); const Utils = require('../utils'); @@ -153,6 +156,7 @@ module.exports = class AlertsDBApi { static async update(id, data, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; + const globalAccess = currentUser.app_role?.globalAccess; const alerts = await db.alerts.findByPk(id, {}, {transaction}); @@ -358,6 +362,9 @@ module.exports = class AlertsDBApi { offset = currentPage * limit; + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; let include = [ @@ -652,7 +659,7 @@ module.exports = class AlertsDBApi { where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - order: [['title_text', 'ASC']], + orderBy: [['title_text', 'ASC']], }); return records.map((record) => ({ @@ -662,4 +669,5 @@ module.exports = class AlertsDBApi { } -}; \ No newline at end of file +}; + diff --git a/backend/src/db/migrations/1772302510315.js b/backend/src/db/migrations/1772302510315.js deleted file mode 100644 index 2960adf..0000000 --- a/backend/src/db/migrations/1772302510315.js +++ /dev/null @@ -1,79 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @returns {Promise} - */ - async up(queryInterface) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.createTable('funding_opportunities', { - id: { - type: queryInterface.sequelize.Sequelize.DataTypes.UUID, - defaultValue: queryInterface.sequelize.Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - name: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - focus: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - stage: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - contact_type: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - description: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - website: { - type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, - }, - amount_min: { - type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL, - }, - amount_max: { - type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL, - }, - createdById: { - type: queryInterface.sequelize.Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: queryInterface.sequelize.Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, - updatedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, - deletedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, - importHash: { - type: queryInterface.sequelize.Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.dropTable('funding_opportunities', { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - } -}; \ No newline at end of file diff --git a/backend/src/db/models/funding_opportunities.js b/backend/src/db/models/funding_opportunities.js deleted file mode 100644 index d5e47b9..0000000 --- a/backend/src/db/models/funding_opportunities.js +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = function(sequelize, DataTypes) { - const funding_opportunities = sequelize.define( - 'funding_opportunities', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - name: { - type: DataTypes.TEXT, - }, - focus: { - type: DataTypes.TEXT, - }, - stage: { - type: DataTypes.TEXT, - }, - contact_type: { - type: DataTypes.TEXT, - }, - description: { - type: DataTypes.TEXT, - }, - website: { - type: DataTypes.TEXT, - }, - amount_min: { - type: DataTypes.DECIMAL, - }, - amount_max: { - type: DataTypes.DECIMAL, - }, - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - funding_opportunities.associate = (db) => { - db.funding_opportunities.belongsTo(db.users, { - as: 'createdBy', - }); - - db.funding_opportunities.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return funding_opportunities; -}; diff --git a/backend/src/db/seeders/20260228000000-funding-opportunities.js b/backend/src/db/seeders/20260228000000-funding-opportunities.js deleted file mode 100644 index 3031c4f..0000000 --- a/backend/src/db/seeders/20260228000000-funding-opportunities.js +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface) => { - const opportunities = [ - { - id: '10000000-0000-0000-0000-000000000001', - name: 'Sequoia Capital', - focus: 'Enterprise, AI, Consumer', - stage: 'Seed to IPO', - contact_type: 'VC', - description: 'Global venture capital firm helping daring founders build legendary companies.', - website: 'https://www.sequoiacap.com', - amount_min: 500000, - amount_max: 100000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000002', - name: 'Andreessen Horowitz (a16z)', - focus: 'Fintech, Crypto, AI, Games', - stage: 'All stages', - contact_type: 'VC', - description: 'Venture capital firm that backs bold entrepreneurs building the future through technology.', - website: 'https://a16z.com', - amount_min: 1000000, - amount_max: 500000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000003', - name: 'Index Ventures', - focus: 'Global SaaS, Commerce', - stage: 'Early to Growth', - contact_type: 'VC', - description: 'Partnering with exceptional entrepreneurs from seed to IPO.', - website: 'https://www.indexventures.com', - amount_min: 2000000, - amount_max: 50000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000004', - name: 'Naval Ravikant', - focus: 'Deep Tech, AI, Crypto', - stage: 'Seed', - contact_type: 'Angel', - description: 'Founding CEO of AngelList, investor in 200+ companies including Uber, Twitter, and Wish.', - website: 'https://twitter.com/naval', - amount_min: 50000, - amount_max: 500000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000005', - name: 'Tiger Global', - focus: 'Hyper-growth, Consumer, SaaS', - stage: 'Growth', - contact_type: 'Institutional', - description: 'Investment firm focused on public and private companies in the global Internet, software, consumer, and financial technology industries.', - website: 'https://www.tigerglobal.com', - amount_min: 10000000, - amount_max: 1000000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000006', - name: 'Goldman Sachs Principal', - focus: 'Late Stage, Fintech, Infrastructure', - stage: 'Pre-IPO', - contact_type: 'Bank/Institutional', - description: 'Principal investment arm of Goldman Sachs.', - website: 'https://www.goldmansachs.com', - amount_min: 50000000, - amount_max: 500000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000007', - name: 'First Round Capital', - focus: 'Tech, SaaS, Mobile', - stage: 'Seed', - contact_type: 'VC', - description: 'Seed-stage venture capital firm focused on building community.', - website: 'https://firstround.com', - amount_min: 250000, - amount_max: 2000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000008', - name: 'SV Angel', - focus: 'Consumer, Enterprise', - stage: 'Seed', - contact_type: 'Angel Group', - description: 'Early-stage venture capital firm based in San Francisco.', - website: 'https://svangel.com', - amount_min: 50000, - amount_max: 1000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000009', - name: 'Benchmark', - focus: 'Consumer, Mobile, SaaS', - stage: 'Early', - contact_type: 'VC', - description: 'Venture capital firm that focuses on early-stage startups.', - website: 'https://www.benchmark.com', - amount_min: 1000000, - amount_max: 20000000, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: '10000000-0000-0000-0000-000000000010', - name: 'General Catalyst', - focus: 'Fintech, Healthcare, AI', - stage: 'Seed to Growth', - contact_type: 'VC', - description: 'Venture capital firm that invests in powerful, positive change.', - website: 'https://www.generalcatalyst.com', - amount_min: 1000000, - amount_max: 100000000, - createdAt: new Date(), - updatedAt: new Date(), - } - ]; - - await queryInterface.bulkInsert('funding_opportunities', opportunities, {}); - }, - - down: async (queryInterface) => { - await queryInterface.bulkDelete('funding_opportunities', null, {}); - } -}; \ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js index 9219a3e..ce6cf48 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,3 +1,4 @@ + const express = require('express'); const cors = require('cors'); const app = express(); @@ -15,7 +16,6 @@ const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); const sqlRoutes = require('./routes/sql'); const pexelsRoutes = require('./routes/pexels'); -const intelligenceRoutes = require('./routes/intelligence'); const organizationForAuthRoutes = require('./routes/organizationLogin'); @@ -197,12 +197,6 @@ app.use( passport.authenticate('jwt', { session: false }), sqlRoutes); -app.use( - '/api/intelligence', - passport.authenticate('jwt', { session: false }), - intelligenceRoutes, -); - app.use( '/api/org-for-auth', organizationForAuthRoutes, @@ -232,4 +226,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/backend/src/routes/intelligence.js b/backend/src/routes/intelligence.js deleted file mode 100644 index ebc285d..0000000 --- a/backend/src/routes/intelligence.js +++ /dev/null @@ -1,54 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const IntelligenceService = require('../services/intelligence'); -const Helpers = require('../helpers'); - -router.get('/', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getIntegratedAnalysis(organizationsId); - res.status(200).send(result); -})); - -router.get('/modeling', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getFinancialModeling(organizationsId); - res.status(200).send(result); -})); - -router.get('/opportunities', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getMarketOpportunities(organizationsId); - res.status(200).send(result); -})); - -router.get('/stewardship', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getFinancialStewardship(organizationsId); - res.status(200).send(result); -})); - -router.get('/risk', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getCashFlowRisk(organizationsId); - res.status(200).send(result); -})); - -router.get('/capital', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getCapitalStrategy(organizationsId); - res.status(200).send(result); -})); - -router.get('/leadership', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getStrategicLeadership(organizationsId); - res.status(200).send(result); -})); - -router.get('/esg', Helpers.wrapAsync(async (req, res) => { - const organizationsId = req.currentUser.organizationsId; - const result = await IntelligenceService.getESGStrategy(organizationsId); - res.status(200).send(result); -})); - -module.exports = router; \ No newline at end of file diff --git a/backend/src/services/auth.js b/backend/src/services/auth.js index 53fd1ea..bcc3411 100644 --- a/backend/src/services/auth.js +++ b/backend/src/services/auth.js @@ -1,4 +1,3 @@ -const db = require("../db/models"); const UsersDBApi = require('../db/api/users'); const ValidationError = require('./notifications/errors/validation'); const ForbiddenError = require('./notifications/errors/forbidden'); diff --git a/backend/src/services/intelligence.js b/backend/src/services/intelligence.js deleted file mode 100644 index fc82a5b..0000000 --- a/backend/src/services/intelligence.js +++ /dev/null @@ -1,354 +0,0 @@ -const db = require('../db/models'); -const { LocalAIApi } = require('../ai/LocalAIApi'); - -module.exports = class IntelligenceService { - static async getExternalIntelligence() { - // Fetch real funding opportunities from the database - const funding_opportunities = await db.funding_opportunities.findAll(); - - // In a real app, this would fetch from a news API or a market data provider. - return { - trends: [ - { - title: "Agentic AI in Finance", - content: "AI has moved from assistance to transactional authority, driving autonomous decision-making in risk modeling and fraud detection.", - impact: "High", - date: "2026-02-28" - }, - { - title: "Consolidation in Fintech", - content: "Market maturation is leading to traditional banks acquiring challengers. Profitability is prioritized over 'growth at all costs'.", - impact: "Medium", - date: "2026-02-25" - }, - { - title: "Real-Time Payments Global Standard", - content: "24/7 settlement and cross-border efficiency are now industry baselines.", - impact: "Medium", - date: "2026-02-20" - } - ], - benchmarks: { - saas: { - gross_margin_target: 75, - net_dollar_retention_median: 101, - net_dollar_retention_top: 111, - rule_of_40_target: 40, - valuation_multiple_median: 6.5, - valuation_multiple_top: 12.0 - }, - fintech: { - cac_payback_months_target: 12, - burn_multiplier_target: 1.5, - ltv_cac_target: 3.5 - } - }, - market_caps: [ - { segment: "AI-Fintech", multiple_trend: "+15%", opportunity_score: 9.2 }, - { segment: "Traditional SaaS", multiple_trend: "-5%", opportunity_score: 6.5 }, - { segment: "Infrastructure/API", multiple_trend: "+8%", opportunity_score: 8.8 } - ], - funding_partners: funding_opportunities, - regulatory_updates: [ - { title: "SEC AI Disclosure Rules", content: "New requirements for disclosing AI-driven financial models.", date: "2026-01-15" }, - { title: "Global ESG Reporting Standard", content: "Mandatory ESG reporting for companies over $5M ARR.", date: "2026-02-01" } - ] - }; - } - - static async getProprietaryData(organizationsId) { - if (!organizationsId) return {}; - - const metrics = await db.metrics_snapshots.findAll({ - where: { organizationsId }, - order: [['as_of_at', 'DESC'], ['createdAt', 'DESC']], - limit: 12 - }); - - const latest = metrics[0] || {}; - - return { - mrr: parseFloat(latest.mrr || 0), - arr: parseFloat(latest.arr || 0), - margin: parseFloat(latest.gross_margin_percent || 0), - burn: parseFloat(latest.net_burn_monthly || 0), - runway: parseFloat(latest.runway_months || 0), - history: metrics.map(m => ({ - date: m.as_of_at || m.createdAt, - mrr: parseFloat(m.mrr || 0), - burn: parseFloat(m.net_burn_monthly || 0), - margin: parseFloat(m.gross_margin_percent || 0) - })) - }; - } - - static async getFinancialStewardship(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as a Virtual CFO specializing in Financial Stewardship and Compliance. - - Company State: - - ARR: $${proprietary.arr} - - Margin: ${proprietary.margin}% - - Regulatory Environment (2026): - ${JSON.stringify(external.regulatory_updates)} - - Tasks: - 1. **Audit Readiness**: Based on the $${proprietary.arr} ARR, suggest an audit schedule and key control areas (e.g., Revenue Recognition under ASC 606). - 2. **Closing the Books**: Provide a checklist for a 'Continuous Close' process to ensure real-time financial reporting accuracy. - 3. **Regulatory Compliance**: How do the new ${external.regulatory_updates[0].title} affect this company's AI-driven insights? - 4. **Tax Strategy**: Suggest R&D tax credit opportunities and international transfer pricing considerations if scaling globally. - - Format as a professional CFO report for the Board of Directors. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - stewardship: LocalAIApi.extractText(aiResponse) || "Stewardship analysis unavailable." - }; - } - - static async getCashFlowRisk(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - - const prompt = ` - Act as a Risk Management & Cash Flow Expert (CFO role). - - Company Financials: - - Monthly Burn: $${proprietary.burn} - - Runway: ${proprietary.runway} months - - Margin: ${proprietary.margin}% - - Tasks: - 1. **Liquidity Analysis**: Assess the safety of the current ${proprietary.runway}-month runway. When is the "Drop Dead Date"? - 2. **Risk Identification**: Identify top 3 financial risks (e.g., Customer Concentration, Currency Risk, or Cost Overruns). - 3. **Mitigation Strategy**: Suggest hedging or cost-cutting measures to extend runway by 25% without impacting core growth. - 4. **Profitability Path**: Build a high-level roadmap to "Default Alive" status. - - Be precise, data-driven, and conservative. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - risk: LocalAIApi.extractText(aiResponse) || "Risk analysis unavailable." - }; - } - - static async getCapitalStrategy(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as a CFO & Head of Capital Markets. Your goal is to structure capital and connect with funding. - - Company State: - - ARR: $${proprietary.arr} - - Runway: ${proprietary.runway} months - - Available Funding Partners (2026): - ${JSON.stringify(external.funding_partners)} - - Tasks: - 1. **Funding Matchmaker**: Based on the company's profile, identify the top 3 best-fit partners from the list above. Explain WHY they are a match. - 2. **Fundraising Strategy**: Structure a "Series A" or "Series B" (as appropriate) pitch focus. Should we lead with "Growth" or "Efficiency"? - 3. **Capital Structure**: Advise on 'Venture Debt' vs 'Equity' mix to minimize dilution. - 4. **Investor Relations**: Suggest a communication cadence and key KPIs to share with current stakeholders to build trust. - - Provide actionable, high-conviction advice. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - capital: LocalAIApi.extractText(aiResponse) || "Capital strategy unavailable." - }; - } - - static async getStrategicLeadership(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as a Strategic CFO & Advisor to the Board. - - Company Context: - - ARR: $${proprietary.arr} - - Margin: ${proprietary.margin}% - - Market Trends: - ${JSON.stringify(external.trends)} - - Tasks: - 1. **Digital Transformation**: How should the company leverage "Agentic AI" (Trend 1) to reduce OpEx and drive efficiency? - 2. **Business Growth Strategy**: Identify "Blue Ocean" opportunities in the fintech/SaaS space for 2026. - 3. **C-Suite Advisory**: Draft a 1-page memo for the CEO on "Navigating the 2026 Macro Environment." - - Be visionary, bold, and strategic. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - leadership: LocalAIApi.extractText(aiResponse) || "Strategic leadership analysis unavailable." - }; - } - - static async getESGStrategy(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as a CFO specializing in ESG (Environmental, Social, Governance) Strategy. - - Regulatory Context: - ${external.regulatory_updates[1].title}: ${external.regulatory_updates[1].content} - - Company Metrics: - - ARR: $${proprietary.arr} - - Tasks: - 1. **ESG Framework**: Propose a realistic ESG framework that adds value to valuation rather than just being a cost. - 2. **Impact Reporting**: How can we quantify our "Social" impact for potential impact investors? - 3. **Governance Audit**: Suggest improvements to board-level oversight for AI ethics and data privacy. - 4. **Carbon Footprint Optimization**: For a SaaS company, suggest 3 high-impact areas to reduce carbon footprint. - - Format as a professional CFO strategy paper. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - esg: LocalAIApi.extractText(aiResponse) || "ESG strategy unavailable." - }; - } - - static async getFinancialModeling(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as an elite Tier-1 Investment Banker. Build a sophisticated financial model for a company with the following metrics: - - Proprietary Financials: - - ARR: $${proprietary.arr} - - Gross Margin: ${proprietary.margin}% - - Net Burn: $${proprietary.burn}/mo - - Historical Data Points: ${proprietary.history.length} months - - Market Context (2026): - - Median SaaS Multiples: ${external.benchmarks.saas.valuation_multiple_median}x - - Top Decile Multiples: ${external.benchmarks.saas.valuation_multiple_top}x - - Deliver a "Modeling Pack" including: - 1. **Valuation Analysis**: Estimate Current Enterprise Value (EV) using Revenue Multiples (Base and Optimistic cases). - 2. **3-Year Projection**: Forecast ARR and Burn based on current trends. - 3. **DCF Insights**: Discuss required WACC and Terminal Value assumptions for an IPO exit in 36 months. - 4. **Capital Efficiency**: Analyze the "Burn Multiplier" and suggest optimizations for 'Rule of 40' alignment. - - Format as professional executive-level bullet points. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - model: LocalAIApi.extractText(aiResponse) || "Modeling unavailable." - }; - } - - static async getMarketOpportunities(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Act as a Lead M&A / Capital Markets Strategist. Analyze capitalization trends and strategic opportunities for this company. - - Company State: - - ARR: $${proprietary.arr} - - Runway: ${proprietary.runway} months - - Margin: ${proprietary.margin}% - - Capitalization Trends (2026): - ${JSON.stringify(external.market_caps)} - - Strategic Mandate: - 1. **Capitalization Analysis**: Which market segments have the highest multiple expansion right now? How should the company position itself? - 2. **Strategic M&A Opportunities**: Should the company be an "Acquirer" or "Target" based on its margin/runway? Suggest potential acquisition categories (e.g., vertical vs horizontal integration). - 3. **Funding Strategy**: Evaluate readiness for a 'Down-round' vs 'Flat-round' vs 'Strategic Investment' in the current 2026 climate. - 4. **Exit Planning**: Assess feasibility of IPO vs. Strategic Sale in the next 18-24 months. - - Be aggressive, insightful, and use investment banking terminology. - IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - opportunities: LocalAIApi.extractText(aiResponse) || "Strategy insights unavailable." - }; - } - - static async getIntegratedAnalysis(organizationsId) { - const proprietary = await this.getProprietaryData(organizationsId); - const external = await this.getExternalIntelligence(); - - const prompt = ` - Analyze this company's proprietary data against current 2026 financial market intelligence. - - Proprietary Data: - - MRR: $${proprietary.mrr} - - Annual Recurring Revenue: $${proprietary.arr} - - Gross Margin: ${proprietary.margin}% - - Monthly Net Burn: $${proprietary.burn} - - Runway: ${proprietary.runway} months - - External Intelligence (2026): - - SaaS Benchmark Gross Margin: ${external.benchmarks.saas.gross_margin_target}% - - Rule of 40 Target: 40% (Growth + Profitability) - - Current Trend: ${external.trends[0].title} - ${external.trends[0].content} - - Provide a concise (3-4 bullet points) strategic analysis highlighting: - 1. How the company compares to the SaaS Gross Margin benchmark. - 2. Assessment of the Rule of 40 performance (Estimated). - 3. Strategic advice based on the "Agentic AI" trend. - 4. Risk assessment based on runway and market consolidation. - - IMPORTANT: This analysis is strictly for the organization with ID ${organizationsId}. - Ensure data isolation in your reasoning. - `; - - const aiResponse = await LocalAIApi.createResponse({ - input: [{ role: 'user', content: prompt }] - }); - - return { - proprietary, - external, - analysis: LocalAIApi.extractText(aiResponse) || "Analysis pending..." - }; - } -}; diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index e976c38..3c8d581 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -3,9 +3,10 @@ import { mdiLogout, mdiClose } from '@mdi/js' import BaseIcon from './BaseIcon' import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' -import { useAppSelector, useAppDispatch } from '../stores/hooks' +import { useAppSelector } from '../stores/hooks' import Link from 'next/link'; +import { useAppDispatch } from '../stores/hooks'; import { createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; @@ -90,4 +91,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props ) -} \ No newline at end of file +} diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 4ced3eb..72935e6 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,5 +1,6 @@ -import React, {useEffect, useRef, useState} from 'react' +import React, {useEffect, useRef} from 'react' import Link from 'next/link' +import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -128,4 +129,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} \ No newline at end of file +} diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 26c3572..1b9907d 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,4 +1,5 @@ -import React, { ReactNode, useEffect, useState } from 'react' +import React, { ReactNode, useEffect } from 'react' +import { useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -125,4 +126,4 @@ export default function LayoutAuthenticated({ ) -} \ No newline at end of file +} diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 6fda75d..368116e 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -2,11 +2,6 @@ import * as icon from '@mdi/js'; import { MenuAsideItem } from './interfaces' const menuAside: MenuAsideItem[] = [ - { - href: '/advisor', - icon: icon.mdiRobotOutline, - label: 'AI Advisor', - }, { href: '/dashboard', icon: icon.mdiViewDashboardOutline, @@ -14,12 +9,60 @@ const menuAside: MenuAsideItem[] = [ }, { - href: '/transactions/transactions-list', - label: 'Transactions', + href: '/users/users-list', + label: 'Users', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: icon.mdiSwapHorizontal ?? icon.mdiTable, - permissions: 'READ_TRANSACTIONS' + icon: icon.mdiAccountGroup ?? icon.mdiTable, + permissions: 'READ_USERS' + }, + { + href: '/roles/roles-list', + label: 'Roles', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, + permissions: 'READ_ROLES' + }, + { + href: '/permissions/permissions-list', + label: 'Permissions', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, + permissions: 'READ_PERMISSIONS' + }, + { + href: '/organizations/organizations-list', + label: 'Organizations', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_ORGANIZATIONS' + }, + { + href: '/workspaces/workspaces-list', + label: 'Workspaces', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_WORKSPACES' + }, + { + href: '/workspace_memberships/workspace_memberships-list', + label: 'Workspace memberships', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_WORKSPACE_MEMBERSHIPS' + }, + { + href: '/data_connections/data_connections-list', + label: 'Data connections', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_DATA_CONNECTIONS' }, { href: '/financial_accounts/financial_accounts-list', @@ -30,64 +73,147 @@ const menuAside: MenuAsideItem[] = [ permissions: 'READ_FINANCIAL_ACCOUNTS' }, { - href: '/metrics_snapshots/metrics_snapshots-list', - label: 'Financial Health', + href: '/vendors/vendors-list', + label: 'Vendors', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: icon.mdiGauge ?? icon.mdiTable, + icon: 'mdiStore' in icon ? icon['mdiStore' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_VENDORS' + }, + { + href: '/customers/customers-list', + label: 'Customers', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiAccountTie' in icon ? icon['mdiAccountTie' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_CUSTOMERS' + }, + { + href: '/transactions/transactions-list', + label: 'Transactions', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_TRANSACTIONS' + }, + { + href: '/budgets/budgets-list', + label: 'Budgets', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiPiggyBank' in icon ? icon['mdiPiggyBank' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_BUDGETS' + }, + { + href: '/budget_lines/budget_lines-list', + label: 'Budget lines', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_BUDGET_LINES' + }, + { + href: '/forecasts/forecasts-list', + label: 'Forecasts', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiChartTimelineVariant' in icon ? icon['mdiChartTimelineVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_FORECASTS' + }, + { + href: '/forecast_scenarios/forecast_scenarios-list', + label: 'Forecast scenarios', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiLayersTriple' in icon ? icon['mdiLayersTriple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_FORECAST_SCENARIOS' + }, + { + href: '/metrics_snapshots/metrics_snapshots-list', + label: 'Metrics snapshots', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiGauge' in icon ? icon['mdiGauge' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_METRICS_SNAPSHOTS' }, { - href: '/alerts/alerts-list', - label: 'Alerts & Anomalies', + href: '/anomalies/anomalies-list', + label: 'Anomalies', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: icon.mdiBellAlert ?? icon.mdiTable, + icon: 'mdiAlertOctagon' in icon ? icon['mdiAlertOctagon' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_ANOMALIES' + }, + { + href: '/alerts/alerts-list', + label: 'Alerts', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiBellAlert' in icon ? icon['mdiBellAlert' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_ALERTS' }, { - label: 'Settings', - icon: icon.mdiCogOutline, - menu: [ - { - href: '/users/users-list', - label: 'Users', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, - permissions: 'READ_USERS' - }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES' - }, - { - href: '/data_connections/data_connections-list', - label: 'Data connections', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_DATA_CONNECTIONS' - }, - { - href: '/sync_jobs/sync_jobs-list', - label: 'Sync jobs', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SYNC_JOBS' - }, - ] + href: '/recommendations/recommendations-list', + label: 'Recommendations', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiLightbulbOn' in icon ? icon['mdiLightbulbOn' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_RECOMMENDATIONS' + }, + { + href: '/chat_threads/chat_threads-list', + label: 'Chat threads', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiChatProcessing' in icon ? icon['mdiChatProcessing' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_CHAT_THREADS' + }, + { + href: '/chat_messages/chat_messages-list', + label: 'Chat messages', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiMessageText' in icon ? icon['mdiMessageText' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_CHAT_MESSAGES' + }, + { + href: '/message_citations/message_citations-list', + label: 'Message citations', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiFileDocumentMultiple' in icon ? icon['mdiFileDocumentMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_MESSAGE_CITATIONS' + }, + { + href: '/files_library/files_library-list', + label: 'Files library', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiFileUpload' in icon ? icon['mdiFileUpload' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_FILES_LIBRARY' + }, + { + href: '/sync_jobs/sync_jobs-list', + label: 'Sync jobs', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_SYNC_JOBS' }, { href: '/profile', label: 'Profile', icon: icon.mdiAccountCircle, }, + + + { + href: '/api-docs', + target: '_blank', + label: 'Swagger API', + icon: icon.mdiFileCode, + permissions: 'READ_API_DOCS' + }, ] -export default menuAside \ No newline at end of file +export default menuAside diff --git a/frontend/src/pages/advisor.tsx b/frontend/src/pages/advisor.tsx deleted file mode 100644 index 3df82d0..0000000 --- a/frontend/src/pages/advisor.tsx +++ /dev/null @@ -1,771 +0,0 @@ -import * as icon from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState, useMemo } from 'react'; -import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; -import SectionMain from '../components/SectionMain'; -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../config'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import { fetch as fetchMetrics } from '../stores/metrics_snapshots/metrics_snapshotsSlice'; -import { fetch as fetchAlerts } from '../stores/alerts/alertsSlice'; -import { aiResponse } from '../stores/openAiSlice'; -import BaseIcon from '../components/BaseIcon'; -import BaseButton from '../components/BaseButton'; -import { useTranslation } from 'next-i18next'; -import ChartLineSample from '../components/ChartLineSample'; -import axios from 'axios'; - -const AdvisorPage = () => { - const dispatch = useAppDispatch(); - const { t } = useTranslation('common'); - - const { metrics_snapshots } = useAppSelector((state) => state.metrics_snapshots); - const { alerts } = useAppSelector((state) => state.alerts); - const { aiResponse: chatResponse, isAskingResponse } = useAppSelector((state) => state.openAi); - - const [chatInput, setChatInput] = useState(''); - const [messages, setMessages] = useState>([ - { role: 'assistant', content: 'Hello! I am your AI Virtual CFO & Investment Banker. I have integrated 2026 market intelligence, funding opportunities, and capitalization trends into your hub. How can I help you optimize your stewardship, risk, or capital strategy today?' } - ]); - const [activeTab, setActiveTab] = useState<'company' | 'competitors' | 'intelligence'>('company'); - const [intelligence, setIntelligence] = useState(null); - const [modeling, setModeling] = useState(null); - const [opportunities, setOpportunities] = useState(null); - const [stewardship, setStewardship] = useState(null); - const [risk, setRisk] = useState(null); - const [capital, setCapital] = useState(null); - const [leadership, setLeadership] = useState(null); - const [esg, setEsg] = useState(null); - - const [loadingIntelligence, setLoadingIntelligence] = useState(false); - const [loadingModeling, setLoadingModeling] = useState(false); - const [loadingOpportunities, setLoadingOpportunities] = useState(false); - const [loadingStewardship, setLoadingStewardship] = useState(false); - const [loadingRisk, setLoadingRisk] = useState(false); - const [loadingCapital, setLoadingCapital] = useState(false); - const [loadingLeadership, setLoadingLeadership] = useState(false); - const [loadingEsg, setLoadingEsg] = useState(false); - - const [intelSubTab, setIntelSubTab] = useState<'market' | 'modeling' | 'strategy' | 'stewardship' | 'risk' | 'capital' | 'leadership' | 'esg'>('market'); - - useEffect(() => { - dispatch(fetchMetrics({ query: '?limit=12&sort=as_of_at:ASC' })); - dispatch(fetchAlerts({ query: '?limit=5' })); - fetchIntelligence(); - }, [dispatch]); - - useEffect(() => { - if (activeTab === 'intelligence') { - if (intelSubTab === 'market' && !intelligence) fetchIntelligence(); - if (intelSubTab === 'modeling' && !modeling) fetchModeling(); - if (intelSubTab === 'strategy' && !opportunities) fetchOpportunities(); - if (intelSubTab === 'stewardship' && !stewardship) fetchStewardship(); - if (intelSubTab === 'risk' && !risk) fetchRisk(); - if (intelSubTab === 'capital' && !capital) fetchCapital(); - if (intelSubTab === 'leadership' && !leadership) fetchLeadership(); - if (intelSubTab === 'esg' && !esg) fetchEsg(); - } - }, [activeTab, intelSubTab]); - - const fetchIntelligence = async () => { - setLoadingIntelligence(true); - try { - const response = await axios.get('/intelligence'); - setIntelligence(response.data); - } catch (error) { - console.error('Failed to fetch intelligence:', error); - } finally { - setLoadingIntelligence(false); - } - }; - - const fetchModeling = async () => { - setLoadingModeling(true); - try { - const response = await axios.get('/intelligence/modeling'); - setModeling(response.data); - } catch (error) { - console.error('Failed to fetch modeling:', error); - } finally { - setLoadingModeling(false); - } - }; - - const fetchOpportunities = async () => { - setLoadingOpportunities(true); - try { - const response = await axios.get('/intelligence/opportunities'); - setOpportunities(response.data); - } catch (error) { - console.error('Failed to fetch opportunities:', error); - } finally { - setLoadingOpportunities(false); - } - }; - - const fetchStewardship = async () => { - setLoadingStewardship(true); - try { - const response = await axios.get('/intelligence/stewardship'); - setStewardship(response.data); - } catch (error) { - console.error('Failed to fetch stewardship:', error); - } finally { - setLoadingStewardship(false); - } - }; - - const fetchRisk = async () => { - setLoadingRisk(true); - try { - const response = await axios.get('/intelligence/risk'); - setRisk(response.data); - } catch (error) { - console.error('Failed to fetch risk:', error); - } finally { - setLoadingRisk(false); - } - }; - - const fetchCapital = async () => { - setLoadingCapital(true); - try { - const response = await axios.get('/intelligence/capital'); - setCapital(response.data); - } catch (error) { - console.error('Failed to fetch capital:', error); - } finally { - setLoadingCapital(false); - } - }; - - const fetchLeadership = async () => { - setLoadingLeadership(true); - try { - const response = await axios.get('/intelligence/leadership'); - setLeadership(response.data); - } catch (error) { - console.error('Failed to fetch leadership:', error); - } finally { - setLoadingLeadership(false); - } - }; - - const fetchEsg = async () => { - setLoadingEsg(true); - try { - const response = await axios.get('/intelligence/esg'); - setEsg(response.data); - } catch (error) { - console.error('Failed to fetch esg:', error); - } finally { - setLoadingEsg(false); - } - }; - - useEffect(() => { - if (chatResponse && chatResponse.success) { - const outputText = chatResponse.data?.output?.[0]?.content?.[0]?.text; - if (outputText) { - setMessages((prev) => [...prev, { role: 'assistant', content: outputText }]); - } - } - }, [chatResponse]); - - const handleSendMessage = () => { - if (!chatInput.trim()) return; - - const newMessages = [...messages, { role: 'user', content: chatInput }]; - setMessages(newMessages); - setChatInput(''); - - dispatch(aiResponse({ - input: newMessages.map(m => ({ role: m.role, content: m.content })), - options: { poll_interval: 2, poll_timeout: 60 } - })); - }; - - const latestMetrics = useMemo(() => metrics_snapshots?.[metrics_snapshots.length - 1] || {}, [metrics_snapshots]); - - const chartData = useMemo(() => { - const labels = metrics_snapshots.map((m: any) => new Date(m.as_of_at).toLocaleDateString(undefined, { month: 'short', year: '2-digit' })); - const mrrData = metrics_snapshots.map((m: any) => Number(m.mrr || 0)); - const burnData = metrics_snapshots.map((m: any) => Number(m.net_burn_monthly || 0)); - - return { - labels, - datasets: [ - { - label: 'MRR', - fill: false, - borderColor: '#10b981', // Emerald 500 - borderWidth: 3, - data: mrrData, - tension: 0.4, - }, - { - label: 'Net Burn', - fill: false, - borderColor: '#3b82f6', // Blue 500 - borderWidth: 3, - data: burnData, - tension: 0.4, - } - ], - }; - }, [metrics_snapshots]); - - const competitorData = useMemo(() => { - const labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; - const industryMrr = [50000, 55000, 58000, 65000, 72000, 80000]; - const ourMrr = [20000, 25000, 32000, 38000, 45000, 52000]; - - return { - labels, - datasets: [ - { - label: 'Industry Benchmark (Finance)', - borderColor: '#94a3b8', // Slate 400 - borderDash: [5, 5], - data: industryMrr, - tension: 0.4, - }, - { - label: 'Our Company', - borderColor: '#10b981', // Emerald 500 - data: ourMrr, - tension: 0.4, - } - ], - }; - }, []); - - return ( - <> - - {getPageTitle('AI Virtual CFO Hub')} - - - -
- setActiveTab('company')} - small - /> - setActiveTab('competitors')} - small - /> - setActiveTab('intelligence')} - icon={icon.mdiNewspaperVariantOutline} - small - /> -
-
- - {/* Top Summary Row */} -
- -

Monthly Revenue

-

${Number(latestMetrics.mrr || 0).toLocaleString()}

-
- +12.5% vs last month -
-
- - -

Gross Margin

-

{latestMetrics.gross_margin_percent || 0}%

-
- Industry Avg: 75% (SaaS 2026) -
-
- - -

Cash Runway

-

{latestMetrics.runway_months || 'N/A'} Months

-
- Market shifting to profitability -
-
- - -

Burn Multiplier

-

1.2x

-
- Target: < 1.5x -
-
-
- -
- {/* Main Visuals Column */} -
- {activeTab === 'company' && ( - -
-

Financial Growth Trend

-
- MRR - Net Burn -
-
-
- -
-
- )} - - {activeTab === 'competitors' && ( - -
-

Industry Benchmark Comparison

-

Sector: Finance SaaS

-
-
- -
-
-
-

Our Growth Rank

-

Top 25%

-
-
-

Efficiency Score

-

88/100

-
-
-
- )} - - {activeTab === 'intelligence' && ( -
- -
-
- -

CFO Intelligence Hub

-
-
-
- setIntelSubTab('market')} - className="border-none shadow-none" - small - /> - setIntelSubTab('stewardship')} - className="border-none shadow-none" - small - /> - setIntelSubTab('risk')} - className="border-none shadow-none" - small - /> - setIntelSubTab('capital')} - className="border-none shadow-none" - small - /> - setIntelSubTab('modeling')} - className="border-none shadow-none" - small - /> - setIntelSubTab('strategy')} - className="border-none shadow-none" - small - /> - setIntelSubTab('leadership')} - className="border-none shadow-none" - small - /> - setIntelSubTab('esg')} - className="border-none shadow-none" - small - /> -
- - {intelSubTab === 'market' && intelligence && ( -
-
-

Latest Trends

- {intelligence.external.trends.map((trend: any, idx: number) => ( -
-
-

{trend.title}

- {trend.impact} Impact -
-

{trend.content}

-
- ))} -
- -
-

AI Strategic Analysis

-
- -
- {intelligence.analysis} -
-
-
-
- )} - - {intelSubTab === 'stewardship' && ( -
- {loadingStewardship ? ( -
- ) : stewardship ? ( -
-
- -

Financial Stewardship & Compliance

-
-
- {stewardship.stewardship} -
-
- ) : ( -
Stewardship report unavailable.
- )} -
- )} - - {intelSubTab === 'risk' && ( -
- {loadingRisk ? ( -
- ) : risk ? ( -
-
- -

Cash Flow & Risk Management

-
-
- {risk.risk} -
-
- ) : ( -
Risk analysis unavailable.
- )} -
- )} - - {intelSubTab === 'capital' && ( -
-
- {loadingCapital ? ( -
- ) : capital ? ( -
-
- -

Capital Management Strategy

-
-
- {capital.capital} -
-
- ) : ( -
Capital strategy unavailable.
- )} -
- -
-

Funding Opportunities

-
- {intelligence?.external?.funding_partners?.map((partner: any, idx: number) => ( -
{ - setChatInput(`Draft an outreach email for ${partner.name} focusing on our MRR of $${Number(latestMetrics.mrr || 0).toLocaleString()}`); - }}> -
-

{partner.name}

- {partner.contact_type} -
-
- {partner.focus} - {partner.stage} -
-
- ))} -
- { - setChatInput("Search for VC and Angel investors specialized in Fintech and SaaS for a Seed/Series A round in 2026."); - handleSendMessage(); - }} - /> -
-
- )} - - {intelSubTab === 'modeling' && ( -
- {loadingModeling ? ( -
- ) : modeling ? ( -
-
- -

Investment Banking Modeling Pack

-
-
- {modeling.model} -
-
- ) : ( -
Modeling pack unavailable.
- )} -
- )} - - {intelSubTab === 'strategy' && ( -
- {loadingOpportunities ? ( -
- ) : opportunities ? ( -
-
- -

Capitalization & M&A Strategy

-
-
- {opportunities.opportunities} -
-
- ) : ( -
Strategy report unavailable.
- )} -
- )} - - {intelSubTab === 'leadership' && ( -
- {loadingLeadership ? ( -
- ) : leadership ? ( -
-
- -

Strategic Leadership & Board Advisory

-
-
- {leadership.leadership} -
-
- ) : ( -
Leadership advisory unavailable.
- )} -
- )} - - {intelSubTab === 'esg' && ( -
- {loadingEsg ? ( -
- ) : esg ? ( -
-
- -

ESG Strategy & Impact Reporting

-
-
- {esg.esg} -
-
- ) : ( -
ESG strategy unavailable.
- )} -
- )} -
-
- )} - - {/* AI Advisor Chat (Unified for all tabs) */} - -
- -

Ask Your AI CFO

-
- -
- {messages.map((m, i) => ( -
-
-

{m.content}

-
-
- ))} - {isAskingResponse && ( -
-
-
-
-
-
-
-
-
- )} -
- -
- setChatInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} - /> - -
-
-
- - {/* Side Column: Alerts & Insights */} -
- -
- -

CFO Strategic Insights

-
-
- {alerts.length === 0 ? ( -

Scanning for insights...

- ) : ( - alerts.slice(0, 3).map((alert: any) => ( -
{ - const prompt = `Act as a CFO: Explain the financial impact of this alert: ${alert.title || alert.description}`; - setChatInput(prompt); - }}> -
- Action Required - {new Date(alert.createdAt).toLocaleDateString()} -
-

{alert.title || alert.type}

-

{alert.description || alert.message}

-
- )) - )} -
-
- - -
- -

Compliance Status

-
-
-
- Audit Readiness - 85% -
-
-
-
- -
- Tax Compliance - 100% -
-
-
-
- -
- CFO Note: You are currently aligned with 2026 SEC AI Disclosure requirements. Next audit scheduled for Q3. -
-
-
- - -

CFO Strategy Actions

-
- { setActiveTab('intelligence'); setIntelSubTab('risk'); }} - small - /> - { setActiveTab('intelligence'); setIntelSubTab('capital'); }} - small - /> - { setActiveTab('intelligence'); setIntelSubTab('esg'); }} - small - /> - { setActiveTab('intelligence'); setIntelSubTab('stewardship'); }} - small - /> -
-
-
-
-
- - ); -}; - -AdvisorPage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default AdvisorPage; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 4a477ce..ca38430 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -13,114 +13,149 @@ import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; -import * as icon from '@mdi/js'; -import BaseIcon from '../components/BaseIcon'; export default function Starter() { + const [illustrationImage, setIllustrationImage] = useState({ + src: undefined, + photographer: undefined, + photographer_url: undefined, + }) + const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) + const [contentType, setContentType] = useState('video'); + const [contentPosition, setContentPosition] = useState('right'); const textColor = useAppSelector((state) => state.style.linkColor); const title = 'CFO.ai' + // Fetch Pexels image/video + useEffect(() => { + async function fetchData() { + const image = await getPexelsImage(); + const video = await getPexelsVideo(); + setIllustrationImage(image); + setIllustrationVideo(video); + } + fetchData(); + }, []); + + const imageBlock = (image) => ( + + ); + + const videoBlock = (video) => { + if (video?.video_files?.length > 0) { + return ( +
+ + +
) + } + }; + return ( -
+
- {getPageTitle('Intelligent Financial Advisor for SMBs')} + {getPageTitle('Starter Page')} - {/* Navigation Header */} -
-
-
- -
- CFO.ai -
- -
- Log in - -
-
- - {/* Hero Section */} -
-
- Meet your new Financial Partner -
-

- The AI CFO for SMBs. -

-

- CFO.ai interprets your financial data, detects abnormalities, and advises you like a seasoned executive. Stop guessing, start growing. -

-
- - - How it works - -
+ +
+ {contentType === 'image' && contentPosition !== 'background' + ? imageBlock(illustrationImage) + : null} + {contentType === 'video' && contentPosition !== 'background' + ? videoBlock(illustrationVideo) + : null} +
+ + - {/* Mock Dashboard Preview */} -
-
-
-
-
+
+

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

+

For guides and documentation please check + your local README.md and the Flatlogic documentation

-
-
- -

Real-time Financial Insights & AI Advisory

-
-
-
-
+ + + - {/* Features Grid */} -
-
-
-

Everything you need from a CFO

-

Automated, intelligent, and available 24/7.

-
-
-
-
- -
-

Real-time Runway

-

Always know exactly how many months of cash you have left based on actual spending.

-
-
-
- -
-

Anomaly Detection

-

Get notified the moment something unusual happens—unrecognized charges or spikes in burn.

-
-
-
- -
-

Ask Your CFO

-

Chat with an AI that knows your numbers. Ask "Can we afford another hire?" and get a data-backed answer.

-
-
+ +
-
+
+ +
+

© 2026 {title}. All rights reserved

+ + Privacy Policy + +
- {/* Footer */} -
); } diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx index 64bf121..00f5168 100644 --- a/frontend/src/pages/search.tsx +++ b/frontend/src/pages/search.tsx @@ -1,7 +1,9 @@ import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head'; import 'react-datepicker/dist/react-datepicker.css'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { useAppDispatch } from '../stores/hooks'; + +import { useAppSelector } from '../stores/hooks'; import { useRouter } from 'next/router'; import LayoutAuthenticated from '../layouts/Authenticated'; @@ -91,4 +93,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) { ); }; -export default SearchView; \ No newline at end of file +export default SearchView;