CFO AI
This commit is contained in:
parent
8558d4ae63
commit
b27bf91054
@ -154,6 +154,7 @@ 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,
|
||||
@ -306,7 +307,7 @@ function buildUrl(pathValue, baseUrl) {
|
||||
}
|
||||
|
||||
function resolveStatusPath(aiRequestId, cfg) {
|
||||
const basePath = (cfg.responsesPath || "").replace(/\/+$/, "");
|
||||
const basePath = (cfg.responsesPath || "").replace(new RegExp("/+"), "");
|
||||
if (!basePath) {
|
||||
return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`;
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ passport.use(new MicrosoftStrategy({
|
||||
));
|
||||
|
||||
function socialStrategy(email, profile, provider, done) {
|
||||
db.users.findOrCreate({where: {email, provider}}).then(([user, created]) => {
|
||||
db.users.findOrCreate({where: {email, provider}}).then(([user]) => {
|
||||
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});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,4 @@
|
||||
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
|
||||
@ -156,7 +153,6 @@ 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});
|
||||
|
||||
@ -362,9 +358,6 @@ module.exports = class AlertsDBApi {
|
||||
|
||||
offset = currentPage * limit;
|
||||
|
||||
const orderBy = null;
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
let include = [
|
||||
|
||||
@ -659,7 +652,7 @@ module.exports = class AlertsDBApi {
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['title_text', 'ASC']],
|
||||
order: [['title_text', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
@ -669,5 +662,4 @@ module.exports = class AlertsDBApi {
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
79
backend/src/db/migrations/1772302510315.js
Normal file
79
backend/src/db/migrations/1772302510315.js
Normal file
@ -0,0 +1,79 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
58
backend/src/db/models/funding_opportunities.js
Normal file
58
backend/src/db/models/funding_opportunities.js
Normal file
@ -0,0 +1,58 @@
|
||||
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;
|
||||
};
|
||||
144
backend/src/db/seeders/20260228000000-funding-opportunities.js
Normal file
144
backend/src/db/seeders/20260228000000-funding-opportunities.js
Normal file
@ -0,0 +1,144 @@
|
||||
'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, {});
|
||||
}
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
@ -16,6 +15,7 @@ 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,6 +197,12 @@ 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,
|
||||
@ -226,4 +232,4 @@ db.sequelize.sync().then(function () {
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app;
|
||||
54
backend/src/routes/intelligence.js
Normal file
54
backend/src/routes/intelligence.js
Normal file
@ -0,0 +1,54 @@
|
||||
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;
|
||||
@ -1,3 +1,4 @@
|
||||
const db = require("../db/models");
|
||||
const UsersDBApi = require('../db/api/users');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const ForbiddenError = require('./notifications/errors/forbidden');
|
||||
|
||||
354
backend/src/services/intelligence.js
Normal file
354
backend/src/services/intelligence.js
Normal file
@ -0,0 +1,354 @@
|
||||
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..."
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js'
|
||||
import BaseIcon from './BaseIcon'
|
||||
import AsideMenuList from './AsideMenuList'
|
||||
import { MenuAsideItem } from '../interfaces'
|
||||
import { useAppSelector } from '../stores/hooks'
|
||||
import { useAppSelector, useAppDispatch } from '../stores/hooks'
|
||||
import Link from 'next/link';
|
||||
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import axios from 'axios';
|
||||
|
||||
@ -91,4 +90,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import React, {useEffect, useRef} from 'react'
|
||||
import React, {useEffect, useRef, useState} 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'
|
||||
@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
|
||||
}
|
||||
|
||||
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div>
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { ReactNode, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import React, { ReactNode, useEffect, useState } from 'react'
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||
import menuAside from '../menuAside'
|
||||
@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,11 @@ 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,
|
||||
@ -9,60 +14,12 @@ const menuAside: MenuAsideItem[] = [
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
label: 'Users',
|
||||
href: '/transactions/transactions-list',
|
||||
label: 'Transactions',
|
||||
// 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: '/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'
|
||||
icon: icon.mdiSwapHorizontal ?? icon.mdiTable,
|
||||
permissions: 'READ_TRANSACTIONS'
|
||||
},
|
||||
{
|
||||
href: '/financial_accounts/financial_accounts-list',
|
||||
@ -72,148 +29,65 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: 'mdiBank' in icon ? icon['mdiBank' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_FINANCIAL_ACCOUNTS'
|
||||
},
|
||||
{
|
||||
href: '/vendors/vendors-list',
|
||||
label: 'Vendors',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Financial Health',
|
||||
// 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,
|
||||
icon: icon.mdiGauge ?? icon.mdiTable,
|
||||
permissions: 'READ_METRICS_SNAPSHOTS'
|
||||
},
|
||||
{
|
||||
href: '/anomalies/anomalies-list',
|
||||
label: 'Anomalies',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiAlertOctagon' in icon ? icon['mdiAlertOctagon' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ANOMALIES'
|
||||
},
|
||||
{
|
||||
href: '/alerts/alerts-list',
|
||||
label: 'Alerts',
|
||||
label: 'Alerts & Anomalies',
|
||||
// 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,
|
||||
icon: icon.mdiBellAlert ?? icon.mdiTable,
|
||||
permissions: 'READ_ALERTS'
|
||||
},
|
||||
{
|
||||
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'
|
||||
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: '/profile',
|
||||
label: 'Profile',
|
||||
icon: icon.mdiAccountCircle,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
href: '/api-docs',
|
||||
target: '_blank',
|
||||
label: 'Swagger API',
|
||||
icon: icon.mdiFileCode,
|
||||
permissions: 'READ_API_DOCS'
|
||||
},
|
||||
]
|
||||
|
||||
export default menuAside
|
||||
export default menuAside
|
||||
771
frontend/src/pages/advisor.tsx
Normal file
771
frontend/src/pages/advisor.tsx
Normal file
@ -0,0 +1,771 @@
|
||||
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<Array<{ role: string; content: string }>>([
|
||||
{ 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<any>(null);
|
||||
const [modeling, setModeling] = useState<any>(null);
|
||||
const [opportunities, setOpportunities] = useState<any>(null);
|
||||
const [stewardship, setStewardship] = useState<any>(null);
|
||||
const [risk, setRisk] = useState<any>(null);
|
||||
const [capital, setCapital] = useState<any>(null);
|
||||
const [leadership, setLeadership] = useState<any>(null);
|
||||
const [esg, setEsg] = useState<any>(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 (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('AI Virtual CFO Hub')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={icon.mdiRobotOutline} title="AI Virtual CFO & Advisor" main>
|
||||
<div className="flex gap-2">
|
||||
<BaseButton
|
||||
label="Overview"
|
||||
color={activeTab === 'company' ? 'info' : 'white'}
|
||||
onClick={() => setActiveTab('company')}
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Competitors"
|
||||
color={activeTab === 'competitors' ? 'info' : 'white'}
|
||||
onClick={() => setActiveTab('competitors')}
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Intelligence Hub"
|
||||
color={activeTab === 'intelligence' ? 'info' : 'white'}
|
||||
onClick={() => setActiveTab('intelligence')}
|
||||
icon={icon.mdiNewspaperVariantOutline}
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
{/* Top Summary Row */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6">
|
||||
<CardBox className="border-l-4 border-emerald-500">
|
||||
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Monthly Revenue</p>
|
||||
<h3 className="text-2xl font-bold mt-1">${Number(latestMetrics.mrr || 0).toLocaleString()}</h3>
|
||||
<div className="flex items-center text-xs text-emerald-600 font-semibold mt-1">
|
||||
<BaseIcon path={icon.mdiTrendingUp} size={14} className="mr-1" /> +12.5% vs last month
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="border-l-4 border-blue-500">
|
||||
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Gross Margin</p>
|
||||
<h3 className="text-2xl font-bold mt-1">{latestMetrics.gross_margin_percent || 0}%</h3>
|
||||
<div className="flex items-center text-xs text-gray-400 mt-1">
|
||||
Industry Avg: 75% (SaaS 2026)
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="border-l-4 border-orange-500">
|
||||
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Cash Runway</p>
|
||||
<h3 className="text-2xl font-bold mt-1">{latestMetrics.runway_months || 'N/A'} Months</h3>
|
||||
<div className="flex items-center text-xs text-orange-600 font-semibold mt-1">
|
||||
<BaseIcon path={icon.mdiAlertCircleOutline} size={14} className="mr-1" /> Market shifting to profitability
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="border-l-4 border-purple-500">
|
||||
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Burn Multiplier</p>
|
||||
<h3 className="text-2xl font-bold mt-1">1.2x</h3>
|
||||
<div className="flex items-center text-xs text-blue-600 font-semibold mt-1">
|
||||
Target: < 1.5x
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Main Visuals Column */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{activeTab === 'company' && (
|
||||
<CardBox>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold">Financial Growth Trend</h2>
|
||||
<div className="flex gap-4 text-sm">
|
||||
<span className="flex items-center"><span className="w-3 h-3 bg-emerald-500 rounded-full mr-2"></span> MRR</span>
|
||||
<span className="flex items-center"><span className="w-3 h-3 bg-blue-500 rounded-full mr-2"></span> Net Burn</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-80">
|
||||
<ChartLineSample data={chartData} />
|
||||
</div>
|
||||
</CardBox>
|
||||
)}
|
||||
|
||||
{activeTab === 'competitors' && (
|
||||
<CardBox>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold">Industry Benchmark Comparison</h2>
|
||||
<p className="text-sm text-gray-500 italic">Sector: Finance SaaS</p>
|
||||
</div>
|
||||
<div className="h-80">
|
||||
<ChartLineSample data={competitorData} />
|
||||
</div>
|
||||
<div className="mt-6 grid grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-gray-50 dark:bg-dark-800 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-500">Our Growth Rank</p>
|
||||
<p className="text-2xl font-bold text-emerald-600">Top 25%</p>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-50 dark:bg-dark-800 rounded-lg text-center">
|
||||
<p className="text-sm text-gray-500">Efficiency Score</p>
|
||||
<p className="text-2xl font-bold text-blue-600">88/100</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
)}
|
||||
|
||||
{activeTab === 'intelligence' && (
|
||||
<div className="space-y-6">
|
||||
<CardBox>
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-4 pb-4 border-b gap-4">
|
||||
<div className="flex items-center">
|
||||
<BaseIcon path={icon.mdiFinance} className="mr-2 text-blue-500" />
|
||||
<h2 className="text-xl font-bold text-blue-600 dark:text-blue-400">CFO Intelligence Hub</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 bg-gray-100 dark:bg-dark-900 p-1 rounded-lg mb-6">
|
||||
<BaseButton
|
||||
label="Market News"
|
||||
color={intelSubTab === 'market' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('market')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Stewardship"
|
||||
color={intelSubTab === 'stewardship' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('stewardship')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Risk"
|
||||
color={intelSubTab === 'risk' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('risk')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Capital"
|
||||
color={intelSubTab === 'capital' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('capital')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Modeling"
|
||||
color={intelSubTab === 'modeling' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('modeling')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Strategy"
|
||||
color={intelSubTab === 'strategy' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('strategy')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Leadership"
|
||||
color={intelSubTab === 'leadership' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('leadership')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="ESG"
|
||||
color={intelSubTab === 'esg' ? 'info' : 'white'}
|
||||
onClick={() => setIntelSubTab('esg')}
|
||||
className="border-none shadow-none"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
|
||||
{intelSubTab === 'market' && intelligence && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">Latest Trends</h3>
|
||||
{intelligence.external.trends.map((trend: any, idx: number) => (
|
||||
<div key={idx} className="p-4 bg-gray-50 dark:bg-dark-800 rounded-xl border border-gray-100 dark:border-dark-700">
|
||||
<div className="flex justify-between items-start mb-1">
|
||||
<h4 className="font-bold text-blue-500 text-sm">{trend.title}</h4>
|
||||
<span className={`text-[10px] px-2 py-0.5 rounded font-bold ${trend.impact === 'High' ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600'}`}>{trend.impact} Impact</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 leading-relaxed">{trend.content}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">AI Strategic Analysis</h3>
|
||||
<div className="p-5 bg-blue-600 text-white rounded-2xl shadow-lg relative overflow-hidden">
|
||||
<BaseIcon path={icon.mdiRobot} size={48} className="absolute -right-4 -bottom-4 opacity-10" />
|
||||
<div className="relative z-10 text-sm leading-relaxed whitespace-pre-wrap italic">
|
||||
{intelligence.analysis}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'stewardship' && (
|
||||
<div className="space-y-4">
|
||||
{loadingStewardship ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : stewardship ? (
|
||||
<div className="p-6 bg-slate-50 dark:bg-slate-900/10 rounded-2xl border border-slate-100 dark:border-slate-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiShieldCheckOutline} className="text-slate-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-slate-800 dark:text-slate-400">Financial Stewardship & Compliance</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-slate-900 dark:text-slate-200">
|
||||
{stewardship.stewardship}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Stewardship report unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'risk' && (
|
||||
<div className="space-y-4">
|
||||
{loadingRisk ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : risk ? (
|
||||
<div className="p-6 bg-red-50 dark:bg-red-900/10 rounded-2xl border border-red-100 dark:border-red-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiShieldAlertOutline} className="text-red-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-red-800 dark:text-red-400">Cash Flow & Risk Management</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-red-900 dark:text-red-200">
|
||||
{risk.risk}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Risk analysis unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'capital' && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
{loadingCapital ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : capital ? (
|
||||
<div className="p-6 bg-blue-50 dark:bg-blue-900/10 rounded-2xl border border-blue-100 dark:border-blue-800 h-full">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiAccountGroup} className="text-blue-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-blue-800 dark:text-blue-400">Capital Management Strategy</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-blue-900 dark:text-blue-200">
|
||||
{capital.capital}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Capital strategy unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">Funding Opportunities</h3>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{intelligence?.external?.funding_partners?.map((partner: any, idx: number) => (
|
||||
<div key={idx} className="p-4 border rounded-xl hover:border-blue-500 transition-colors cursor-pointer group" onClick={() => {
|
||||
setChatInput(`Draft an outreach email for ${partner.name} focusing on our MRR of $${Number(latestMetrics.mrr || 0).toLocaleString()}`);
|
||||
}}>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<h4 className="font-bold text-sm group-hover:text-blue-600">{partner.name}</h4>
|
||||
<span className="text-[10px] px-2 py-0.5 bg-gray-100 rounded font-bold text-gray-500">{partner.contact_type}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<span className="text-[10px] text-blue-600 bg-blue-50 px-2 py-0.5 rounded italic">{partner.focus}</span>
|
||||
<span className="text-[10px] text-gray-500 bg-gray-50 px-2 py-0.5 rounded">{partner.stage}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<BaseButton
|
||||
label="Match me with more investors"
|
||||
icon={icon.mdiHandshakeOutline}
|
||||
color="info"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
setChatInput("Search for VC and Angel investors specialized in Fintech and SaaS for a Seed/Series A round in 2026.");
|
||||
handleSendMessage();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'modeling' && (
|
||||
<div className="space-y-4">
|
||||
{loadingModeling ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : modeling ? (
|
||||
<div className="p-6 bg-emerald-50 dark:bg-emerald-900/10 rounded-2xl border border-emerald-100 dark:border-emerald-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiChartBoxOutline} className="text-emerald-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-emerald-800 dark:text-emerald-400">Investment Banking Modeling Pack</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-emerald-900 dark:text-emerald-200">
|
||||
{modeling.model}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Modeling pack unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'strategy' && (
|
||||
<div className="space-y-4">
|
||||
{loadingOpportunities ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : opportunities ? (
|
||||
<div className="p-6 bg-purple-50 dark:bg-purple-900/10 rounded-2xl border border-purple-100 dark:border-purple-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiTrophyOutline} className="text-purple-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-purple-800 dark:text-purple-400">Capitalization & M&A Strategy</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-purple-900 dark:text-purple-200">
|
||||
{opportunities.opportunities}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Strategy report unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'leadership' && (
|
||||
<div className="space-y-4">
|
||||
{loadingLeadership ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : leadership ? (
|
||||
<div className="p-6 bg-amber-50 dark:bg-amber-900/10 rounded-2xl border border-amber-100 dark:border-amber-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiAccountStarOutline} className="text-amber-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-amber-800 dark:text-amber-400">Strategic Leadership & Board Advisory</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-amber-900 dark:text-amber-200">
|
||||
{leadership.leadership}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">Leadership advisory unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intelSubTab === 'esg' && (
|
||||
<div className="space-y-4">
|
||||
{loadingEsg ? (
|
||||
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
|
||||
) : esg ? (
|
||||
<div className="p-6 bg-green-50 dark:bg-green-900/10 rounded-2xl border border-green-100 dark:border-green-800">
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiLeaf} className="text-green-500 mr-2" />
|
||||
<h3 className="font-bold text-lg text-green-800 dark:text-green-400">ESG Strategy & Impact Reporting</h3>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap text-green-900 dark:text-green-200">
|
||||
{esg.esg}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-12 text-gray-500">ESG strategy unavailable.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AI Advisor Chat (Unified for all tabs) */}
|
||||
<CardBox className="flex flex-col h-[450px]">
|
||||
<div className="flex items-center mb-4 pb-4 border-b">
|
||||
<BaseIcon path={icon.mdiChatProcessing} className="mr-2 text-blue-500" />
|
||||
<h2 className="text-lg font-bold">Ask Your AI CFO</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow overflow-y-auto space-y-4 mb-4 p-2 scrollbar-thin scrollbar-thumb-gray-200">
|
||||
{messages.map((m, i) => (
|
||||
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
<div className={`max-w-[85%] p-4 rounded-2xl shadow-sm ${
|
||||
m.role === 'user'
|
||||
? 'bg-blue-600 text-white rounded-tr-none'
|
||||
: 'bg-white dark:bg-dark-800 border border-gray-100 dark:border-dark-700 text-gray-800 dark:text-gray-200 rounded-tl-none'
|
||||
}`}>
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">{m.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{isAskingResponse && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-gray-100 dark:bg-dark-800 p-4 rounded-2xl rounded-tl-none animate-pulse">
|
||||
<div className="flex gap-1">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.2s]"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.4s]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto pt-4 flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
className="flex-grow bg-gray-100 dark:bg-dark-900 border-none rounded-full px-5 py-3 text-sm focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="How can we optimize our R&D tax credits?"
|
||||
value={chatInput}
|
||||
onChange={(e) => setChatInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
|
||||
/>
|
||||
<BaseButton
|
||||
icon={icon.mdiSend}
|
||||
color="info"
|
||||
roundedFull
|
||||
onClick={handleSendMessage}
|
||||
disabled={isAskingResponse || !chatInput.trim()}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
|
||||
{/* Side Column: Alerts & Insights */}
|
||||
<div className="lg:col-span-1 space-y-6">
|
||||
<CardBox isHoverable>
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiLightningBolt} className="mr-2 text-yellow-500" />
|
||||
<h2 className="text-lg font-bold">CFO Strategic Insights</h2>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{alerts.length === 0 ? (
|
||||
<p className="text-gray-500 italic text-sm">Scanning for insights...</p>
|
||||
) : (
|
||||
alerts.slice(0, 3).map((alert: any) => (
|
||||
<div key={alert.id} className="group cursor-pointer" onClick={() => {
|
||||
const prompt = `Act as a CFO: Explain the financial impact of this alert: ${alert.title || alert.description}`;
|
||||
setChatInput(prompt);
|
||||
}}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-[10px] uppercase font-bold text-orange-500 bg-orange-50 px-2 py-0.5 rounded">Action Required</span>
|
||||
<span className="text-[10px] text-gray-400">{new Date(alert.createdAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<p className="text-sm font-bold group-hover:text-blue-500 transition-colors">{alert.title || alert.type}</p>
|
||||
<p className="text-xs text-gray-500 line-clamp-2 mt-1">{alert.description || alert.message}</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox isHoverable>
|
||||
<div className="flex items-center mb-4">
|
||||
<BaseIcon path={icon.mdiShieldCheckOutline} className="mr-2 text-blue-500" />
|
||||
<h2 className="text-lg font-bold">Compliance Status</h2>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-gray-500">Audit Readiness</span>
|
||||
<span className="font-bold text-emerald-600">85%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-100 rounded-full h-1.5">
|
||||
<div className="bg-emerald-500 h-1.5 rounded-full w-[85%]"></div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-gray-500">Tax Compliance</span>
|
||||
<span className={`font-bold text-blue-600`}>100%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-100 rounded-full h-1.5">
|
||||
<div className={`h-1.5 rounded-full bg-blue-500`} style={{ width: `100%` }}></div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg text-xs text-blue-700 dark:text-blue-300">
|
||||
<strong>CFO Note:</strong> You are currently aligned with 2026 SEC AI Disclosure requirements. Next audit scheduled for Q3.
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox>
|
||||
<h2 className="text-lg font-bold mb-4">CFO Strategy Actions</h2>
|
||||
<div className="space-y-3">
|
||||
<BaseButton
|
||||
label="Analyze Risk"
|
||||
icon={icon.mdiShieldAlertOutline}
|
||||
className="w-full text-left"
|
||||
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('risk'); }}
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Fundraising Match"
|
||||
icon={icon.mdiAccountGroup}
|
||||
className="w-full text-left"
|
||||
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('capital'); }}
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="ESG Strategy"
|
||||
icon={icon.mdiLeafOutline}
|
||||
className="w-full text-left"
|
||||
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('esg'); }}
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
label="Audit Checklist"
|
||||
icon={icon.mdiClipboardCheckOutline}
|
||||
className="w-full text-left"
|
||||
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('stewardship'); }}
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AdvisorPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
|
||||
export default AdvisorPage;
|
||||
@ -13,149 +13,114 @@ 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) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-900 text-gray-900 dark:text-gray-100 min-h-screen font-sans">
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('Intelligent Financial Advisor for SMBs')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your CFO.ai app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
{/* Navigation Header */}
|
||||
<header className="flex items-center justify-between px-6 py-4 border-b border-gray-100 dark:border-dark-800">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="bg-blue-600 p-2 rounded-lg">
|
||||
<BaseIcon path={icon.mdiRobotOutline} size={24} className="text-white" />
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
<span className="text-xl font-bold tracking-tight">CFO.ai</span>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="hidden md:flex space-x-8 text-sm font-medium">
|
||||
<a href="#features" className="hover:text-blue-600 transition-colors">Features</a>
|
||||
<a href="#about" className="hover:text-blue-600 transition-colors">About</a>
|
||||
</nav>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link href="/login" className="text-sm font-semibold hover:text-blue-600">Log in</Link>
|
||||
<BaseButton href="/register" label="Get Started" color="info" roundedFull className="px-6" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="py-20 px-6 max-w-7xl mx-auto text-center">
|
||||
<div className="inline-block px-4 py-1.5 mb-6 text-sm font-semibold tracking-wide text-blue-600 uppercase bg-blue-50 rounded-full dark:bg-blue-900/20 dark:text-blue-400">
|
||||
Meet your new Financial Partner
|
||||
</div>
|
||||
<h1 className="text-5xl md:text-7xl font-extrabold mb-8 tracking-tight">
|
||||
The AI CFO for <span className="text-blue-600">SMBs.</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto mb-12 leading-relaxed">
|
||||
CFO.ai interprets your financial data, detects abnormalities, and advises you like a seasoned executive. Stop guessing, start growing.
|
||||
</p>
|
||||
<div className="flex flex-col md:flex-row justify-center items-center space-y-4 md:space-y-0 md:space-x-6">
|
||||
<BaseButton href="/advisor" label="Open Advisor Dashboard" color="info" roundedFull className="px-10 py-4 text-lg" />
|
||||
<Link href="#features" className="text-lg font-semibold flex items-center group hover:text-blue-600 transition-colors">
|
||||
How it works <BaseIcon path={icon.mdiArrowRight} size={20} className="ml-2 transform group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mock Dashboard Preview */}
|
||||
<div className="mt-20 relative rounded-2xl overflow-hidden border border-gray-200 dark:border-dark-700 shadow-2xl max-w-5xl mx-auto">
|
||||
<div className="bg-gray-50 dark:bg-dark-800 p-4 border-b border-gray-200 dark:border-dark-700 flex space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-400"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-400"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-dark-900 p-8 h-[400px] flex items-center justify-center text-gray-400 italic">
|
||||
<div className="text-center">
|
||||
<BaseIcon path={icon.mdiChartTimelineVariant} size={64} className="mx-auto mb-4 opacity-20" />
|
||||
<p>Real-time Financial Insights & AI Advisory</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section id="features" className="py-20 bg-gray-50 dark:bg-dark-800/50">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold mb-4">Everything you need from a CFO</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400">Automated, intelligent, and available 24/7.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
|
||||
<div className="bg-blue-100 dark:bg-blue-900/20 p-3 rounded-xl inline-block mb-6 text-blue-600">
|
||||
<BaseIcon path={icon.mdiGauge} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-3">Real-time Runway</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">Always know exactly how many months of cash you have left based on actual spending.</p>
|
||||
</div>
|
||||
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
|
||||
<div className="bg-emerald-100 dark:bg-emerald-900/20 p-3 rounded-xl inline-block mb-6 text-emerald-600">
|
||||
<BaseIcon path={icon.mdiAlertOctagon} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-3">Anomaly Detection</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">Get notified the moment something unusual happens—unrecognized charges or spikes in burn.</p>
|
||||
</div>
|
||||
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
|
||||
<div className="bg-violet-100 dark:bg-violet-900/20 p-3 rounded-xl inline-block mb-6 text-violet-600">
|
||||
<BaseIcon path={icon.mdiChatProcessing} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-3">Ask Your CFO</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">Chat with an AI that knows your numbers. Ask "Can we afford another hire?" and get a data-backed answer.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12 border-t border-gray-100 dark:border-dark-800 text-center text-gray-500 text-sm">
|
||||
<p>© 2026 {title}. Built for SMBs who want to scale smart.</p>
|
||||
<div className="mt-4 flex justify-center space-x-6">
|
||||
<Link href="/privacy-policy" className="hover:text-blue-600">Privacy Policy</Link>
|
||||
<Link href="/terms-of-use" className="hover:text-blue-600">Terms of Use</Link>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
@ -93,4 +91,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) {
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchView;
|
||||
export default SearchView;
|
||||
Loading…
x
Reference in New Issue
Block a user