Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,27 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# next.js
/.next/
/out/

# production
/build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
66 changes: 66 additions & 0 deletions app/api/ai/agreement-draft/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NextRequest, NextResponse } from "next/server";
import { generateObject } from "ai";
import { z } from "zod";
import { openai } from "@ai-sdk/openai";
import { verifyToken } from "@/lib/auth";

// Real AI provider configuration: OpenAI

const AgreementDraftSchema = z.object({
title: z.string(),
description: z.string(),
amount: z.string(),
asset: z.literal("USDC"),
agreement_type: z.union([z.literal("single"), z.literal("multi")]),
milestones: z.array(
z.object({
description: z.string(),
amount: z.string(),
status: z.literal("pending"),
})
),
metadata: z.object({
generatedByAI: z.literal(true),
riskFlags: z.array(z.string()),
useCase: z.string().optional(),
}),
});

export async function POST(request: NextRequest) {
try {
// Authentication
const authHeader = request.headers.get("authorization");
if (!authHeader?.startsWith("Bearer ")) {
return NextResponse.json({ error: "Missing Authorization header" }, { status: 401 });
}
const token = authHeader.split(" ")[1];
const payload = verifyToken(token);
if (!payload) {
return NextResponse.json({ error: "Invalid JWT token" }, { status: 401 });
}

// Parse user input
const { title, description, amount, milestones, useCase } = await request.json();

// Build prompt for AI
const prompt = `Generate an agreement draft with the following details:\nTitle: ${title}\nDescription: ${description}\nAmount: ${amount}\nUse case: ${useCase ?? "N/A"}\nMilestones: ${JSON.stringify(milestones)}`;

// Generate draft using OpenAI
const { object: aiDraft } = await generateObject({
model: openai("gpt-4o"),
schema: AgreementDraftSchema,
prompt,
});

// Validate draft (schema already validated)
const validatedDraft = AgreementDraftSchema.parse(aiDraft);

return NextResponse.json(validatedDraft);
} catch (error) {
console.error("Error generating agreement draft:", error);
return NextResponse.json(
{ error: "Failed to generate agreement draft" },
{ status: 500 }
);
}
}
32 changes: 31 additions & 1 deletion app/dashboard/business/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ export default function BusinessDashboardPage() {
const [showCustomize, setShowCustomize] = useState(false)
const [notifyEmail, setNotifyEmail] = useState("")
const [signerEmail, setSignerEmail] = useState("")
const [showAIAssistant, setShowAIAssistant] = useState(false)

const dragItem = useRef<number | null>(null)
const dragOverItem = useRef<number | null>(null)
Expand Down Expand Up @@ -543,6 +544,18 @@ export default function BusinessDashboardPage() {
setNotifyEmail(""); setSignerEmail(""); setSelectedWallet(connectedWallets[0].value)
}

const handleUseAIDraft = (draft: any) => {
resetWizard()
setTitle(draft.title)
setDescription(draft.description)
setEscrowType(draft.agreement_type)
setMilestones(draft.milestones.map((m: any) => ({
description: m.description,
amount: m.amount
})))
setStep(2)
}

const agreementUrl = typeof window !== "undefined" ? `${window.location.origin}/dashboard/business` : "https://thalos.app/dashboard/business"
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(agreementUrl)}&bgcolor=0a0a0a&color=f0b400&qzone=3&format=png`

Expand Down Expand Up @@ -1019,7 +1032,13 @@ export default function BusinessDashboardPage() {
{/* Header */}
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-semibold text-white">{t("dashPage.enterpriseAgreements")}</h1>
<Button onClick={() => { setActiveSection("create"); resetWizard() }} className="rounded-full bg-[#3b82f6] px-6 text-sm font-semibold text-white hover:bg-[#2563eb] shadow-[0_4px_16px_rgba(59,130,246,0.25)]">+ {t("dashPage.newAgreement")}</Button>
<div className="flex gap-3">
<Button onClick={() => setShowAIAssistant(true)} className="rounded-full bg-white/10 px-6 text-sm font-semibold text-white hover:bg-white/15 shadow-[0_4px_16px_rgba(255,255,255,0.1)] flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M10 11h4v2h-4z"/><circle cx="12" cy="12" r="10"/><path d="M12 7v2"/></svg>
Create with AI
</Button>
<Button onClick={() => { setActiveSection("create"); resetWizard() }} className="rounded-full bg-[#3b82f6] px-6 text-sm font-semibold text-white hover:bg-[#2563eb] shadow-[0_4px_16px_rgba(59,130,246,0.25)]">+ {t("dashPage.newAgreement")}</Button>
</div>
</div>

{/* Wallet filter — only renders when user has multiple linked wallets */}
Expand Down Expand Up @@ -1843,6 +1862,17 @@ export default function BusinessDashboardPage() {
type="enterprise"
/>

{/* AI Agreement Assistant */}
<AIAgreementAssistant
isOpen={showAIAssistant}
onClose={() => setShowAIAssistant(false)}
onUseDraft={(draft) => {
handleUseAIDraft(draft)
setActiveSection("create")
}}
useCases={useCases}
/>

{/* Agreement Chat - Floating Popup */}
{showAgreementChat && (
<div className="fixed bottom-6 right-6 z-50 w-96 h-[500px] bg-[#0c1220] rounded-2xl border border-white/10 shadow-[0_16px_64px_rgba(0,0,0,0.5)] overflow-hidden animate-in slide-in-from-bottom-4 duration-300">
Expand Down
42 changes: 37 additions & 5 deletions app/dashboard/personal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
import { getProfileByWallet, type Profile } from "@/lib/actions/profile"
import { AgreementsView } from "@/components/agreements/agreements-view"
import { ContactSelector } from "@/components/agreements/contact-selector"
import { AgreementChat } from "@/components/agreements/agreement-chat"
import { AgreementChat } from "@/components/agreements/agreement-chat";
import { AIAgreementAssistant } from "@/components/agreements/ai-agreement-assistant";
import { ProfileEditor } from "@/components/profile/profile-editor"
import { WalletSelector } from "@/components/dashboard/wallet-selector"
import { WalletAgreementsPanel } from "@/components/dashboard/wallet-agreements-panel"
Expand Down Expand Up @@ -520,6 +521,7 @@ const res = await getEscrowsByRole({ role: "approver", address: walletAddress })
const [showCustomize, setShowCustomize] = useState(false)
const [notifyEmail, setNotifyEmail] = useState("")
const [signerEmail, setSignerEmail] = useState("")
const [showAIAssistant, setShowAIAssistant] = useState(false)

const dragItem = useRef<number | null>(null)
const dragOverItem = useRef<number | null>(null)
Expand Down Expand Up @@ -592,6 +594,18 @@ const res = await getEscrowsByRole({ role: "approver", address: walletAddress })
setNotifyEmail(""); setSignerEmail(""); setSelectedWallet(connectedWallets[0].value)
}

const handleUseAIDraft = (draft: any) => {
resetWizard()
setTitle(draft.title)
setDescription(draft.description)
setEscrowType(draft.agreement_type)
setMilestones(draft.milestones.map((m: any) => ({
description: m.description,
amount: m.amount
})))
setStep(2)
}

const agreementUrl = typeof window !== "undefined" ? `${window.location.origin}/dashboard/personal` : "https://thalos.app/dashboard/personal"
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(agreementUrl)}&bgcolor=0a0a0a&color=f0b400&qzone=3&format=png`

Expand Down Expand Up @@ -1108,10 +1122,17 @@ const res = await getEscrowsByRole({ role: "approver", address: walletAddress })
{/* Header */}
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-semibold text-white">{t("dashPage.myAgreements")}</h1>
<Button onClick={() => { setActiveSection("create"); resetWizard() }}
className="rounded-full bg-[#f0b400] px-6 text-sm font-semibold text-background hover:bg-[#d4a000] shadow-[0_4px_16px_rgba(240,180,0,0.25)]">
+ {t("dashPage.newAgreement")}
</Button>
<div className="flex gap-3">
<Button onClick={() => setShowAIAssistant(true)}
className="rounded-full bg-white/10 px-6 text-sm font-semibold text-white hover:bg-white/15 shadow-[0_4px_16px_rgba(255,255,255,0.1)] flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M10 11h4v2h-4z"/><circle cx="12" cy="12" r="10"/><path d="M12 7v2"/></svg>
{t("dashPage.createWithAI")}
</Button>
<Button onClick={() => { setActiveSection("create"); resetWizard() }}
className="rounded-full bg-[#f0b400] px-6 text-sm font-semibold text-background hover:bg-[#d4a000] shadow-[0_4px_16px_rgba(240,180,0,0.25)]">
+ {t("dashPage.newAgreement")}
</Button>
</div>
</div>

{/* Wallet Selector - only shows if user has multiple wallets */}
Expand Down Expand Up @@ -1678,6 +1699,17 @@ const newAgr: Agreement = {
}}
type="personal"
/>

{/* AI Agreement Assistant */}
<AIAgreementAssistant
isOpen={showAIAssistant}
onClose={() => setShowAIAssistant(false)}
onUseDraft={(draft) => {
handleUseAIDraft(draft)
setActiveSection("create")
}}
useCases={useCases}
/>
</div>
);
}
Loading