Add LICENSE, README, and Docs tab to Mission Control
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const commandFile = path.join(process.cwd(), 'task-history.json')
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
let history = []
|
||||
|
||||
if (fs.existsSync(commandFile)) {
|
||||
history = JSON.parse(fs.readFileSync(commandFile, 'utf-8'))
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const project = searchParams.get('project')
|
||||
|
||||
if (project && project !== 'all') {
|
||||
history = history.filter((h: any) => h.project === project)
|
||||
}
|
||||
|
||||
return NextResponse.json({ history })
|
||||
} catch (error) {
|
||||
console.error('Error reading history:', error)
|
||||
return NextResponse.json({ history: [] })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { task, command, project, action } = body
|
||||
|
||||
let history = []
|
||||
if (fs.existsSync(commandFile)) {
|
||||
history = JSON.parse(fs.readFileSync(commandFile, 'utf-8'))
|
||||
}
|
||||
|
||||
const entry = {
|
||||
id: Date.now().toString(),
|
||||
task,
|
||||
command,
|
||||
project: project || 'sitemente',
|
||||
reply: '',
|
||||
action: action || 'task',
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'pending',
|
||||
notified: false
|
||||
}
|
||||
|
||||
history.push(entry)
|
||||
|
||||
// Keep only last 50 entries
|
||||
if (history.length > 50) {
|
||||
history = history.slice(-50)
|
||||
}
|
||||
|
||||
fs.writeFileSync(commandFile, JSON.stringify(history, null, 2))
|
||||
|
||||
// Send Telegram notification to Horus
|
||||
try {
|
||||
const tgMessage = `📬 *New Task from MC*\n\n*Project:* ${project || 'sitemente'}\n*Task:* ${task}\n*Command:* ${command}\n\n_Reply via /api/command-reply/{id}_`
|
||||
|
||||
// Use message tool to notify (will work if Telegram is configured)
|
||||
const { spawn } = require('child_process')
|
||||
spawn('curl', ['-s', '-X', 'POST',
|
||||
'http://localhost:3000/api/messages/send',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', JSON.stringify({
|
||||
channel: 'telegram',
|
||||
target: '382315644',
|
||||
message: tgMessage
|
||||
})
|
||||
], { detached: true, stdio: 'ignore' })
|
||||
} catch (e) {
|
||||
console.log('Telegram notification skipped')
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, entry })
|
||||
} catch (error) {
|
||||
console.error('Error saving history:', error)
|
||||
return NextResponse.json({ error: 'Failed to save' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const commandFile = path.join(process.cwd(), 'task-history.json')
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const id = searchParams.get('id')
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: 'Missing id' }, { status: 400 })
|
||||
}
|
||||
|
||||
try {
|
||||
let history = []
|
||||
if (fs.existsSync(commandFile)) {
|
||||
history = JSON.parse(fs.readFileSync(commandFile, 'utf-8'))
|
||||
}
|
||||
|
||||
const entry = history.find((h: any) => h.id === id)
|
||||
if (!entry) {
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ entry })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to get' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { id, reply } = body
|
||||
|
||||
if (!id || !reply) {
|
||||
return NextResponse.json({ error: 'Missing id or reply' }, { status: 400 })
|
||||
}
|
||||
|
||||
let history = []
|
||||
if (fs.existsSync(commandFile)) {
|
||||
history = JSON.parse(fs.readFileSync(commandFile, 'utf-8'))
|
||||
}
|
||||
|
||||
const index = history.findIndex((h: any) => h.id === id)
|
||||
if (index === -1) {
|
||||
return NextResponse.json({ error: 'Entry not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
history[index].reply = reply
|
||||
history[index].status = 'replied'
|
||||
history[index].repliedAt = new Date().toISOString()
|
||||
|
||||
fs.writeFileSync(commandFile, JSON.stringify(history, null, 2))
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to save reply' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { command, task, action } = body
|
||||
|
||||
if (!command || !task) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing command or task' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Log the command - in production this would trigger Horus
|
||||
console.log(`[Task Command] Task: ${task}, Command: ${command}, Action: ${action}`)
|
||||
|
||||
// Store command in a file for Horus to pick up
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const commandFile = path.join(process.cwd(), 'pending-commands.json')
|
||||
|
||||
let commands = []
|
||||
if (fs.existsSync(commandFile)) {
|
||||
commands = JSON.parse(fs.readFileSync(commandFile, 'utf-8'))
|
||||
}
|
||||
|
||||
commands.push({
|
||||
id: Date.now().toString(),
|
||||
task,
|
||||
command,
|
||||
action,
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
fs.writeFileSync(commandFile, JSON.stringify(commands, null, 2))
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Command sent for task: ${task}`,
|
||||
commandId: Date.now().toString()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error processing command:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process command' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
status: 'ok',
|
||||
message: 'Command API ready'
|
||||
})
|
||||
}
|
||||
@@ -3,24 +3,53 @@ import { runSiteMenteVoiceTurn } from "../../../../lib/ai/siteMenteAgent";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
// Vapi webhook payload types
|
||||
interface VapiMessage {
|
||||
type: string;
|
||||
role?: string;
|
||||
transcript?: string;
|
||||
}
|
||||
|
||||
interface VapiCall {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface VapiWebhookPayload {
|
||||
message: VapiMessage;
|
||||
call: VapiCall;
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = (await request.json()) as { transcript?: string };
|
||||
|
||||
if (!body.transcript || typeof body.transcript !== "string") {
|
||||
return NextResponse.json(
|
||||
{ error: "transcript is required." },
|
||||
{ status: 400 }
|
||||
);
|
||||
const body = (await request.json()) as VapiWebhookPayload;
|
||||
|
||||
// Extract transcript from Vapi's format
|
||||
const message = body.message;
|
||||
|
||||
// Only process final transcripts
|
||||
if (!message || message.type !== "transcript" || !message.transcript) {
|
||||
// Return empty response for non-transcript messages
|
||||
return NextResponse.json({ results: [] });
|
||||
}
|
||||
|
||||
const transcript = message.transcript;
|
||||
|
||||
// Call MiniMax brain
|
||||
const response = await runSiteMenteVoiceTurn({
|
||||
transcript: body.transcript,
|
||||
transcript: transcript,
|
||||
});
|
||||
|
||||
// Return in Vapi's expected format
|
||||
return NextResponse.json({
|
||||
results: [
|
||||
{
|
||||
result: response.reply,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return NextResponse.json(response);
|
||||
} catch (error) {
|
||||
console.error("[SiteMente][API] Voice route failed", error);
|
||||
console.error("[SiteMente][Vapi] Voice route failed", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to generate voice response." },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const STRIPE_KEY = process.env.STRIPE_SECRET_KEY || "";
|
||||
|
||||
const PLANS: Record<string, { name: string; price: number; currency: string }> = {
|
||||
"starter-setup": { name: "AI Chat Setup", price: 90000, currency: "eur" },
|
||||
"starter-monthly": { name: "AI Chat Monthly", price: 29900, currency: "eur" },
|
||||
"site-setup": { name: "Smart Site Setup", price: 350000, currency: "eur" },
|
||||
"site-monthly": { name: "Smart Site Monthly", price: 74900, currency: "eur" },
|
||||
"growth-setup": { name: "AI Growth Setup", price: 500000, currency: "eur" },
|
||||
"growth-monthly": { name: "AI Growth Monthly", price: 195000, currency: "eur" },
|
||||
"demo-real-estate-essential": { name: "Real Estate Essential", price: 39000, currency: "eur" },
|
||||
"demo-real-estate-profesional": { name: "Real Estate Professional", price: 79000, currency: "eur" },
|
||||
"demo-real-estate-premium": { name: "Real Estate Premium", price: 139000, currency: "eur" },
|
||||
"demo-restaurant-essential": { name: "Restaurant Essential", price: 39000, currency: "eur" },
|
||||
"demo-restaurant-profesional": { name: "Restaurant Professional", price: 79000, currency: "eur" },
|
||||
"demo-restaurant-premium": { name: "Restaurant Premium", price: 139000, currency: "eur" },
|
||||
"demo-clinic-essential": { name: "Clinic Essential", price: 39000, currency: "eur" },
|
||||
"demo-clinic-profesional": { name: "Clinic Professional", price: 79000, currency: "eur" },
|
||||
"demo-clinic-premium": { name: "Clinic Premium", price: 139000, currency: "eur" },
|
||||
"demo-home-services-essential": { name: "Home Services Essential", price: 39000, currency: "eur" },
|
||||
"demo-home-services-profesional": { name: "Home Services Professional", price: 79000, currency: "eur" },
|
||||
"demo-home-services-premium": { name: "Home Services Premium", price: 139000, currency: "eur" },
|
||||
};
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { planId, email, name, planType = "monthly" } = body;
|
||||
|
||||
const origin = request.headers.get("origin") || "http://45.95.42.114:1284";
|
||||
|
||||
let planKey = planId;
|
||||
if (!planId.startsWith("demo-") && !PLANS[planId]) {
|
||||
planKey = `${planId}-${planType}`;
|
||||
}
|
||||
|
||||
const plan = PLANS[planKey];
|
||||
|
||||
if (!plan) {
|
||||
return NextResponse.json({ error: "Invalid plan selected" }, { status: 400 });
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append("payment_method_types[]", "card");
|
||||
params.append("line_items[0][price_data][currency]", plan.currency);
|
||||
params.append("line_items[0][price_data][product_data][name]", plan.name);
|
||||
params.append("line_items[0][price_data][product_data][description]", `SiteMente - ${plan.name}`);
|
||||
params.append("line_items[0][price_data][unit_amount]", String(plan.price));
|
||||
params.append("line_items[0][quantity]", "1");
|
||||
params.append("mode", planType === "monthly" ? "subscription" : "payment");
|
||||
params.append("customer_email", email || "");
|
||||
params.append("metadata[customerName]", name || "");
|
||||
params.append("metadata[planId]", planId);
|
||||
params.append("metadata[planType]", planType);
|
||||
params.append("success_url", `${origin}/success?session_id={CHECKOUT_SESSION_ID}`);
|
||||
params.append("cancel_url", `${origin}/?cancelled=true`);
|
||||
|
||||
if (planType === "monthly") {
|
||||
params.append("line_items[0][price_data][recurring][interval]", "month");
|
||||
}
|
||||
|
||||
const response = await fetch("https://api.stripe.com/v1/checkout/sessions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${STRIPE_KEY}`,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
const session = await response.json();
|
||||
|
||||
if (session.error) {
|
||||
return NextResponse.json({ error: session.error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ sessionId: session.id, url: session.url });
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "Failed to create checkout session";
|
||||
console.error("Stripe error:", message);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const dataFile = path.join(process.cwd(), 'trading-traders.json')
|
||||
|
||||
const defaultTraders = [
|
||||
{
|
||||
id: 'dopetrades',
|
||||
name: 'DopeTrades',
|
||||
status: 'learning',
|
||||
framesAnalyzed: 382,
|
||||
patterns: ['Double Top/Bottom', 'Head & Shoulders', 'Triangles', 'Flags', 'Wedges'],
|
||||
entryRules: [
|
||||
'Identify clear structure (swing highs/lows)',
|
||||
'Wait for retest of level',
|
||||
'Confirm momentum in desired direction',
|
||||
'Higher timeframe alignment',
|
||||
'Entry on break of structure or retest',
|
||||
'Confirmation candle required'
|
||||
],
|
||||
exitRules: [
|
||||
'Stop below recent swing low (long)',
|
||||
'Take profit minimum 2:1',
|
||||
'Scale 50% at 1:1',
|
||||
'Trailing stop after 1:1 achieved',
|
||||
'Never move stop loss further'
|
||||
],
|
||||
indicators: [
|
||||
'9 EMA (short term)',
|
||||
'20 EMA (medium term)',
|
||||
'50 SMA (trend filter)',
|
||||
'RSI 14 (momentum)',
|
||||
'Volume profile'
|
||||
],
|
||||
riskParams: [
|
||||
'Max 2% risk per trade',
|
||||
'Max 3 concurrent trades',
|
||||
'6% daily max loss',
|
||||
'10% weekly max loss',
|
||||
'Stop after 3 losses'
|
||||
],
|
||||
timeframe: 'Multi: 4H/Daily trend, 1H structure, 15min entries',
|
||||
notes: 'Frame analysis: 3% bullish, 7% bearish, 90% neutral. Dark charts confirmed.'
|
||||
}
|
||||
]
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
let traders = defaultTraders
|
||||
if (fs.existsSync(dataFile)) {
|
||||
const saved = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
||||
if (saved.length > 0) {
|
||||
traders = saved
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(dataFile, JSON.stringify(defaultTraders, null, 2))
|
||||
}
|
||||
return NextResponse.json({ traders })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ traders: defaultTraders })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
let traders = defaultTraders
|
||||
|
||||
if (fs.existsSync(dataFile)) {
|
||||
traders = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
||||
}
|
||||
|
||||
traders.push({
|
||||
...body,
|
||||
id: body.name.toLowerCase().replace(/\s+/g, '-'),
|
||||
createdAt: new Date().toISOString()
|
||||
})
|
||||
|
||||
fs.writeFileSync(dataFile, JSON.stringify(traders, null, 2))
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to save' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
const dataFile = path.join(process.cwd(), 'trading-trades.json')
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
let trades = []
|
||||
if (fs.existsSync(dataFile)) {
|
||||
trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
||||
}
|
||||
return NextResponse.json({ trades })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ trades: [] })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
let trades = []
|
||||
|
||||
if (fs.existsSync(dataFile)) {
|
||||
trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
||||
}
|
||||
|
||||
trades.push({
|
||||
...body,
|
||||
id: Date.now().toString(),
|
||||
openedAt: new Date().toISOString()
|
||||
})
|
||||
|
||||
fs.writeFileSync(dataFile, JSON.stringify(trades, null, 2))
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to save' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user