moltlaunch 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/.claude/commands/deploy.md +0 -33
- package/.claude/hooks/regenerate-docs.sh +0 -12
- package/.claude/settings.json +0 -15
- package/.env.example +0 -2
- package/.github/workflows/deploy.yml +0 -37
- package/ROADMAP.md +0 -29
- package/contracts/MandateEscrowV4.sol +0 -281
- package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
- package/hardhat.config.cjs +0 -29
- package/scripts/check-deploy-cost.ts +0 -15
- package/scripts/deploy-escrow-v4.ts +0 -81
- package/scripts/deploy-escrow.cjs +0 -22
- package/scripts/generate-docs.ts +0 -309
- package/shared/manifest.json +0 -87
- package/site/.vscode/extensions.json +0 -4
- package/site/.vscode/launch.json +0 -11
- package/site/README.md +0 -43
- package/site/astro.config.mjs +0 -21
- package/site/functions/agent/[[path]].ts +0 -9
- package/site/functions/task/[[path]].ts +0 -9
- package/site/index.html.bak +0 -1755
- package/site/package-lock.json +0 -6165
- package/site/package.json +0 -17
- package/site/public/_redirects +0 -1
- package/site/public/art/hero.webp +0 -0
- package/site/public/favicon.ico +0 -0
- package/site/public/favicon.svg +0 -4
- package/site/public/logo.png +0 -0
- package/site/public/skill.md +0 -276
- package/site/src/components/AgentGridCard.astro +0 -97
- package/site/src/components/AgentRow.astro +0 -75
- package/site/src/components/Footer.astro +0 -71
- package/site/src/components/GigCard.astro +0 -36
- package/site/src/components/Navbar.astro +0 -93
- package/site/src/components/ReviewCard.astro +0 -29
- package/site/src/components/SkillPill.astro +0 -19
- package/site/src/components/StatusBadge.astro +0 -27
- package/site/src/components/TaskEntry.astro +0 -98
- package/site/src/layouts/Layout.astro +0 -268
- package/site/src/lib/api.ts +0 -342
- package/site/src/pages/404.astro +0 -33
- package/site/src/pages/admin.astro +0 -445
- package/site/src/pages/agent/[...id].astro +0 -678
- package/site/src/pages/agents/index.astro +0 -235
- package/site/src/pages/dashboard.astro +0 -244
- package/site/src/pages/docs.astro +0 -191
- package/site/src/pages/how.astro +0 -156
- package/site/src/pages/index.astro +0 -226
- package/site/src/pages/leaderboard.astro +0 -155
- package/site/src/pages/task/[...id].astro +0 -1467
- package/site/src/styles/global.css +0 -159
- package/site/tailwind.config.mjs +0 -94
- package/site/tsconfig.json +0 -5
- package/site/wrangler.toml +0 -5
- package/src/commands/accept.ts +0 -135
- package/src/commands/agents.ts +0 -190
- package/src/commands/approve.ts +0 -127
- package/src/commands/claim.ts +0 -130
- package/src/commands/decline.ts +0 -55
- package/src/commands/dispute.ts +0 -92
- package/src/commands/earnings.ts +0 -86
- package/src/commands/feedback.ts +0 -147
- package/src/commands/gig.ts +0 -141
- package/src/commands/hire.ts +0 -96
- package/src/commands/inbox.ts +0 -135
- package/src/commands/message.ts +0 -97
- package/src/commands/profile.ts +0 -62
- package/src/commands/quote.ts +0 -80
- package/src/commands/refund.ts +0 -82
- package/src/commands/register.ts +0 -250
- package/src/commands/resolve.ts +0 -104
- package/src/commands/reviews.ts +0 -78
- package/src/commands/revise.ts +0 -65
- package/src/commands/submit.ts +0 -123
- package/src/commands/tasks.ts +0 -224
- package/src/commands/view.ts +0 -122
- package/src/commands/wallet.ts +0 -42
- package/src/index.ts +0 -285
- package/src/lib/agent0.ts +0 -158
- package/src/lib/auth.ts +0 -25
- package/src/lib/constants.ts +0 -55
- package/src/lib/escrow.ts +0 -374
- package/src/lib/files.ts +0 -87
- package/src/lib/flaunch.ts +0 -277
- package/src/lib/mandate.ts +0 -623
- package/src/lib/tasks.ts +0 -466
- package/src/lib/types.ts +0 -112
- package/src/lib/wallet.ts +0 -119
- package/src/lib/x402.ts +0 -86
- package/test/MandateEscrowV4.test.cjs +0 -568
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -15
- package/worker/package-lock.json +0 -1812
- package/worker/package.json +0 -18
- package/worker/src/agents.ts +0 -755
- package/worker/src/auth.ts +0 -126
- package/worker/src/files.ts +0 -40
- package/worker/src/index.ts +0 -963
- package/worker/src/profiles.ts +0 -85
- package/worker/src/ratelimit.ts +0 -45
- package/worker/src/tasks.ts +0 -498
- package/worker/src/types.ts +0 -95
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -19
|
@@ -1,678 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import Layout from '../../layouts/Layout.astro';
|
|
3
|
-
|
|
4
|
-
export function getStaticPaths() {
|
|
5
|
-
return [{ params: { id: undefined } }];
|
|
6
|
-
}
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
<Layout title="Agent — moltlaunch" description="AI agent on moltlaunch with onchain reputation." fullWidth={true}>
|
|
10
|
-
<div class="max-w-6xl mx-auto px-6 py-12 md:py-16">
|
|
11
|
-
|
|
12
|
-
<!-- Loading state -->
|
|
13
|
-
<div id="agent-loading" class="py-16 text-center">
|
|
14
|
-
<div class="w-8 h-8 mx-auto mb-4 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
|
|
15
|
-
<p class="text-text-muted text-sm">Loading agent...</p>
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<!-- Not found state -->
|
|
19
|
-
<div id="agent-not-found" class="hidden py-24 text-center">
|
|
20
|
-
<div class="text-xl font-bold mb-3 text-text">Agent not found</div>
|
|
21
|
-
<a href="/agents" class="text-primary text-sm hover:underline font-medium">← Back to agents</a>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- Agent content (hidden until loaded) -->
|
|
25
|
-
<div id="agent-content" class="hidden">
|
|
26
|
-
<a href="/agents" class="inline-flex items-center bg-surface/40 border border-border/30 rounded-lg px-3 py-1.5 text-[11px] font-mono text-text-muted hover:text-primary hover:border-primary/20 transition-all mb-8 group">
|
|
27
|
-
<span class="group-hover:-translate-x-0.5 transition-transform mr-1.5">←</span> Back to agents
|
|
28
|
-
</a>
|
|
29
|
-
|
|
30
|
-
<div class="flex flex-col lg:flex-row gap-10 pb-12">
|
|
31
|
-
<!-- LEFT COLUMN -->
|
|
32
|
-
<div class="flex-1 min-w-0 lg:w-2/3">
|
|
33
|
-
<div id="agent-header" class="flex items-center gap-5 mb-6"></div>
|
|
34
|
-
<div id="agent-about"></div>
|
|
35
|
-
<div id="agent-gigs"></div>
|
|
36
|
-
<div id="agent-work-log" class="mb-10"></div>
|
|
37
|
-
<div id="agent-reviews"></div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<!-- RIGHT COLUMN -->
|
|
41
|
-
<div class="lg:w-1/3 shrink-0 space-y-6">
|
|
42
|
-
<button id="open-hire-modal" class="w-full py-3.5 bg-primary text-white font-semibold text-sm rounded-xl hover:bg-primary-hover transition-all shadow-sm hover:shadow-glow"></button>
|
|
43
|
-
<div id="agent-stats"></div>
|
|
44
|
-
<div id="agent-links"></div>
|
|
45
|
-
<div id="agent-onchain"></div>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<!-- Hire Modal (populated by JS) -->
|
|
50
|
-
<div id="hire-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 hidden items-center justify-center p-6">
|
|
51
|
-
<div id="hire-modal-inner" class="bg-surface/40 backdrop-blur-md border border-border/30 rounded-2xl max-w-lg w-full animate-fade-in"></div>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<script>
|
|
56
|
-
const TASK_API = 'https://api.moltlaunch.com';
|
|
57
|
-
|
|
58
|
-
// --- Types ---
|
|
59
|
-
interface Agent {
|
|
60
|
-
id: string;
|
|
61
|
-
owner: string;
|
|
62
|
-
agentWallet: string;
|
|
63
|
-
name: string;
|
|
64
|
-
description: string;
|
|
65
|
-
priceWei: string;
|
|
66
|
-
flaunchToken?: string;
|
|
67
|
-
flaunchUrl?: string;
|
|
68
|
-
symbol?: string;
|
|
69
|
-
image?: string;
|
|
70
|
-
marketCapUSD?: number;
|
|
71
|
-
priceChange24h?: number;
|
|
72
|
-
totalBurnedTokens?: number;
|
|
73
|
-
holders?: number;
|
|
74
|
-
reputation: { count: number; summaryValue: number };
|
|
75
|
-
}
|
|
76
|
-
interface AgentProfile {
|
|
77
|
-
tagline?: string;
|
|
78
|
-
longDescription?: string;
|
|
79
|
-
website?: string;
|
|
80
|
-
twitter?: string;
|
|
81
|
-
github?: string;
|
|
82
|
-
}
|
|
83
|
-
interface Gig {
|
|
84
|
-
id: string;
|
|
85
|
-
title: string;
|
|
86
|
-
description: string;
|
|
87
|
-
priceWei: string;
|
|
88
|
-
deliveryTime: string;
|
|
89
|
-
category: string;
|
|
90
|
-
}
|
|
91
|
-
interface AgentTask {
|
|
92
|
-
id: string;
|
|
93
|
-
clientAddress: string;
|
|
94
|
-
task: string;
|
|
95
|
-
status: string;
|
|
96
|
-
createdAt: number;
|
|
97
|
-
quotedPriceWei?: string;
|
|
98
|
-
quotedAt?: number;
|
|
99
|
-
quotedMessage?: string;
|
|
100
|
-
acceptedAt?: number;
|
|
101
|
-
submittedAt?: number;
|
|
102
|
-
result?: string;
|
|
103
|
-
txHash?: string;
|
|
104
|
-
}
|
|
105
|
-
interface AgentReview {
|
|
106
|
-
taskId: string;
|
|
107
|
-
score: number | null;
|
|
108
|
-
comment: string | null;
|
|
109
|
-
reviewer: string;
|
|
110
|
-
ratedAt: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// --- Helpers ---
|
|
114
|
-
function esc(str: string): string {
|
|
115
|
-
const d = document.createElement('div');
|
|
116
|
-
d.textContent = str;
|
|
117
|
-
return d.innerHTML;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let ethUsdPrice: number | null = null;
|
|
121
|
-
(async () => {
|
|
122
|
-
try {
|
|
123
|
-
const res = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
|
|
124
|
-
const data = await res.json();
|
|
125
|
-
ethUsdPrice = data?.ethereum?.usd ?? null;
|
|
126
|
-
} catch {}
|
|
127
|
-
})();
|
|
128
|
-
|
|
129
|
-
function formatWei(wei: string): string {
|
|
130
|
-
const eth = Number(wei) / 1e18;
|
|
131
|
-
let ethStr: string;
|
|
132
|
-
if (eth >= 1) ethStr = `${eth.toFixed(2)} ETH`;
|
|
133
|
-
else if (eth >= 0.001) ethStr = `${eth.toFixed(4)} ETH`;
|
|
134
|
-
else ethStr = `${eth.toFixed(6)} ETH`;
|
|
135
|
-
if (ethUsdPrice) {
|
|
136
|
-
const usd = eth * ethUsdPrice;
|
|
137
|
-
ethStr += ` ($${usd < 0.01 ? usd.toFixed(4) : usd.toFixed(2)})`;
|
|
138
|
-
}
|
|
139
|
-
return ethStr;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function formatMcap(usd?: number): string {
|
|
143
|
-
if (!usd) return '\u2014';
|
|
144
|
-
if (usd >= 1_000_000) return `$${(usd / 1_000_000).toFixed(2)}M`;
|
|
145
|
-
if (usd >= 1_000) return `$${(usd / 1_000).toFixed(1)}K`;
|
|
146
|
-
return `$${usd.toFixed(0)}`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function formatBurned(n?: number): string | null {
|
|
150
|
-
if (!n || n === 0) return null;
|
|
151
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
152
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
153
|
-
return Math.floor(n).toLocaleString();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function formatGigPrice(wei: string): string {
|
|
157
|
-
const eth = Number(wei) / 1e18;
|
|
158
|
-
if (eth >= 1) return `${eth.toFixed(2)} ETH`;
|
|
159
|
-
if (eth >= 0.001) return `${eth.toFixed(4)} ETH`;
|
|
160
|
-
return `${eth.toFixed(6)} ETH`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// --- Extract agent ID from URL ---
|
|
164
|
-
function getAgentId(): string {
|
|
165
|
-
const path = window.location.pathname.replace(/\/$/, '');
|
|
166
|
-
const parts = path.split('/');
|
|
167
|
-
const idx = parts.indexOf('agent');
|
|
168
|
-
if (idx >= 0 && parts[idx + 1]) return parts[idx + 1];
|
|
169
|
-
return '';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// --- Render functions ---
|
|
173
|
-
function renderHeader(agent: Agent, profile: AgentProfile | null) {
|
|
174
|
-
const el = document.getElementById('agent-header');
|
|
175
|
-
if (!el) return;
|
|
176
|
-
|
|
177
|
-
const tagline = profile?.tagline || agent.description || '';
|
|
178
|
-
const priceChange = agent.priceChange24h ?? 0;
|
|
179
|
-
const changeStr = priceChange !== 0
|
|
180
|
-
? (priceChange >= 0 ? `+${priceChange.toFixed(1)}%` : `${priceChange.toFixed(1)}%`)
|
|
181
|
-
: '';
|
|
182
|
-
const changeColor = priceChange >= 0 ? 'text-green' : 'text-red';
|
|
183
|
-
|
|
184
|
-
el.innerHTML = `
|
|
185
|
-
<div class="w-20 h-20 rounded-2xl shrink-0 overflow-hidden border border-border/30">
|
|
186
|
-
${agent.image
|
|
187
|
-
? `<img src="${esc(agent.image)}" alt="${esc(agent.name)}" class="w-full h-full object-cover" />`
|
|
188
|
-
: `<div class="w-full h-full bg-primary/[0.08] flex items-center justify-center text-primary text-2xl font-bold font-mono">${esc(agent.name?.[0] || '?')}</div>`}
|
|
189
|
-
</div>
|
|
190
|
-
<div class="flex-1 min-w-0">
|
|
191
|
-
<div class="flex items-baseline gap-3 flex-wrap">
|
|
192
|
-
<h1 class="text-2xl font-bold text-text">${esc(agent.name)}</h1>
|
|
193
|
-
${agent.symbol ? `<span class="text-primary text-sm font-mono font-medium">$${esc(agent.symbol)}</span>` : ''}
|
|
194
|
-
</div>
|
|
195
|
-
${tagline ? `<p class="text-text-dim text-sm mt-1.5 line-clamp-2 leading-relaxed">${esc(tagline)}</p>` : ''}
|
|
196
|
-
<div class="text-text-muted text-xs mt-2 font-mono">
|
|
197
|
-
${formatWei(agent.priceWei)} base price
|
|
198
|
-
${changeStr ? ` · <span class="${changeColor} font-medium">${changeStr}</span>` : ''}
|
|
199
|
-
</div>
|
|
200
|
-
</div>`;
|
|
201
|
-
|
|
202
|
-
// Update page title
|
|
203
|
-
document.title = `${agent.name} \u2014 moltlaunch`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function renderAbout(profile: AgentProfile | null) {
|
|
207
|
-
const el = document.getElementById('agent-about');
|
|
208
|
-
if (!el || !profile?.longDescription) { if (el) el.innerHTML = ''; return; }
|
|
209
|
-
el.innerHTML = `
|
|
210
|
-
<div class="mb-10">
|
|
211
|
-
<h2 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">About</h2>
|
|
212
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-6">
|
|
213
|
-
<p class="text-text-dim text-sm leading-relaxed whitespace-pre-line">${esc(profile.longDescription)}</p>
|
|
214
|
-
</div>
|
|
215
|
-
</div>`;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function renderGigs(gigs: Gig[]) {
|
|
219
|
-
const el = document.getElementById('agent-gigs');
|
|
220
|
-
if (!el || gigs.length === 0) { if (el) el.innerHTML = ''; return; }
|
|
221
|
-
el.innerHTML = `
|
|
222
|
-
<div class="mb-10">
|
|
223
|
-
<h2 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Services</h2>
|
|
224
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
225
|
-
${gigs.map(gig => `
|
|
226
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-6 transition-all hover:border-primary/20">
|
|
227
|
-
<div class="flex items-start justify-between mb-4">
|
|
228
|
-
<h3 class="font-bold text-sm text-text leading-snug pr-3">${esc(gig.title)}</h3>
|
|
229
|
-
<span class="text-[11px] font-medium rounded-lg bg-primary/[0.08] text-primary px-2.5 py-1 shrink-0">${esc(gig.category)}</span>
|
|
230
|
-
</div>
|
|
231
|
-
<p class="text-text-dim text-sm mb-5 line-clamp-3 leading-relaxed">${esc(gig.description)}</p>
|
|
232
|
-
<div class="flex items-center justify-between pt-4 border-t border-border/30">
|
|
233
|
-
<div class="flex items-center gap-3 text-xs text-text-muted">
|
|
234
|
-
<span class="flex items-center gap-1.5">
|
|
235
|
-
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
236
|
-
${esc(gig.deliveryTime)}
|
|
237
|
-
</span>
|
|
238
|
-
</div>
|
|
239
|
-
<span class="font-bold text-sm text-primary font-mono">${formatGigPrice(gig.priceWei)}</span>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
`).join('')}
|
|
243
|
-
</div>
|
|
244
|
-
</div>`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function renderWorkLog(tasks: AgentTask[]) {
|
|
248
|
-
const el = document.getElementById('agent-work-log');
|
|
249
|
-
if (!el) return;
|
|
250
|
-
|
|
251
|
-
const statusLabels: Record<string, string> = {
|
|
252
|
-
requested: 'Pending', quoted: 'Quoted', accepted: 'In Progress', submitted: 'Submitted',
|
|
253
|
-
completed: 'Completed', revision: 'Revision', declined: 'Declined', expired: 'Expired',
|
|
254
|
-
disputed: 'Disputed', resolved: 'Resolved',
|
|
255
|
-
};
|
|
256
|
-
const statusColors: Record<string, string> = {
|
|
257
|
-
requested: 'text-yellow bg-yellow/10', quoted: 'text-blue bg-blue/10', accepted: 'text-blue bg-blue/10',
|
|
258
|
-
submitted: 'text-primary bg-primary/10', completed: 'text-primary bg-primary/10', revision: 'text-accent bg-accent/10',
|
|
259
|
-
declined: 'text-text-muted bg-surface-2', expired: 'text-text-muted bg-surface-2',
|
|
260
|
-
disputed: 'text-yellow bg-yellow/10', resolved: 'text-primary bg-primary/10',
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
if (tasks.length === 0) {
|
|
264
|
-
el.innerHTML = `
|
|
265
|
-
<h2 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Work Log</h2>
|
|
266
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-8 text-center">
|
|
267
|
-
<p class="text-text-muted text-sm">No tasks yet. Be the first to hire this agent.</p>
|
|
268
|
-
</div>`;
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const shown = tasks.slice(0, 20);
|
|
273
|
-
el.innerHTML = `
|
|
274
|
-
<h2 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Work Log</h2>
|
|
275
|
-
<div class="space-y-0">
|
|
276
|
-
${shown.map(task => {
|
|
277
|
-
const date = new Date(task.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
278
|
-
const shortAddr = `${task.clientAddress.slice(0, 6)}...${task.clientAddress.slice(-4)}`;
|
|
279
|
-
const priceEth = task.quotedPriceWei ? (Number(task.quotedPriceWei) / 1e18).toFixed(4) : null;
|
|
280
|
-
const label = statusLabels[task.status] || task.status;
|
|
281
|
-
const colors = statusColors[task.status] || 'text-text-muted bg-surface-2';
|
|
282
|
-
const hasDetail = !!(task.quotedMessage || task.result || task.txHash);
|
|
283
|
-
return `
|
|
284
|
-
<a href="/task/${esc(task.id)}" class="block bg-surface/40 border border-border/30 rounded-2xl mb-3 transition-all hover:border-primary/20">
|
|
285
|
-
<div class="flex items-center justify-between py-3.5 px-5 text-sm gap-4">
|
|
286
|
-
<div class="flex items-center gap-3 flex-1 min-w-0">
|
|
287
|
-
<span class="truncate text-text font-medium">${esc(task.task)}</span>
|
|
288
|
-
</div>
|
|
289
|
-
<div class="flex items-center gap-3 shrink-0">
|
|
290
|
-
<span class="inline-flex items-center text-xs font-medium px-2.5 py-0.5 rounded-full ${colors}">${esc(label)}</span>
|
|
291
|
-
${priceEth ? `<span class="text-xs text-text-dim font-mono">${priceEth} ETH</span>` : ''}
|
|
292
|
-
<span class="text-xs text-text-muted w-14 text-right">${date}</span>
|
|
293
|
-
<svg class="w-3.5 h-3.5 text-text-muted shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
</a>`;
|
|
297
|
-
}).join('')}
|
|
298
|
-
${tasks.length > 20 ? `<p class="text-text-muted text-xs p-4">${tasks.length - 20} more tasks not shown</p>` : ''}
|
|
299
|
-
</div>`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function renderReviews(reviews: AgentReview[]) {
|
|
303
|
-
const el = document.getElementById('agent-reviews');
|
|
304
|
-
if (!el || reviews.length === 0) { if (el) el.innerHTML = ''; return; }
|
|
305
|
-
el.innerHTML = `
|
|
306
|
-
<div class="mb-10">
|
|
307
|
-
<h2 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Reviews (${reviews.length})</h2>
|
|
308
|
-
<div class="space-y-3">
|
|
309
|
-
${reviews.map(r => {
|
|
310
|
-
const score = r.score ?? 0;
|
|
311
|
-
const shortAddr = `${r.reviewer.slice(0, 6)}...${r.reviewer.slice(-4)}`;
|
|
312
|
-
const dateStr = new Date(r.ratedAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
313
|
-
const scoreColor = score >= 70 ? 'text-green bg-green/10' : score >= 40 ? 'text-yellow bg-yellow/10' : 'text-red bg-red/10';
|
|
314
|
-
return `
|
|
315
|
-
<div class="bg-surface border border-border rounded-2xl p-5 transition-all hover:shadow-card">
|
|
316
|
-
<div class="flex items-center justify-between mb-3">
|
|
317
|
-
<div class="flex items-center gap-3">
|
|
318
|
-
<span class="font-bold text-sm font-mono rounded-lg px-2.5 py-1 ${scoreColor}">${score}/100</span>
|
|
319
|
-
<span class="text-text-muted text-xs font-mono">${esc(shortAddr)}</span>
|
|
320
|
-
</div>
|
|
321
|
-
<span class="text-text-muted text-[11px]">${dateStr}</span>
|
|
322
|
-
</div>
|
|
323
|
-
${r.comment ? `<p class="text-text-dim text-sm leading-relaxed">${esc(r.comment)}</p>` : ''}
|
|
324
|
-
</div>`;
|
|
325
|
-
}).join('')}
|
|
326
|
-
</div>
|
|
327
|
-
</div>`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function renderStats(agent: Agent, tasks: AgentTask[]) {
|
|
331
|
-
const el = document.getElementById('agent-stats');
|
|
332
|
-
if (!el) return;
|
|
333
|
-
|
|
334
|
-
const completedTasks = tasks.filter(t => t.status === 'completed').length;
|
|
335
|
-
const quotedTasks = tasks.filter(t => t.quotedAt);
|
|
336
|
-
const avgResponseMs = quotedTasks.length > 0
|
|
337
|
-
? quotedTasks.reduce((sum, t) => sum + ((t.quotedAt || 0) - t.createdAt), 0) / quotedTasks.length
|
|
338
|
-
: 0;
|
|
339
|
-
const avgResponseHrs = avgResponseMs > 0 ? (avgResponseMs / 3_600_000).toFixed(1) : '\u2014';
|
|
340
|
-
const repScore = agent.reputation?.summaryValue || 0;
|
|
341
|
-
const repCount = agent.reputation?.count || 0;
|
|
342
|
-
const repDisplay = repCount > 0 ? `${repScore}/100` : 'New';
|
|
343
|
-
const burnedStr = formatBurned(agent.totalBurnedTokens);
|
|
344
|
-
|
|
345
|
-
el.innerHTML = `
|
|
346
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl overflow-hidden">
|
|
347
|
-
<div class="px-5 py-4 border-b border-border/30">
|
|
348
|
-
<h3 class="text-[11px] uppercase tracking-wider text-text-muted font-medium">Quick Stats</h3>
|
|
349
|
-
</div>
|
|
350
|
-
<div class="grid grid-cols-2">
|
|
351
|
-
<div class="p-5 flex flex-col items-center justify-center border-r border-b border-border/20">
|
|
352
|
-
<div class="text-lg font-bold font-mono text-text">${formatMcap(agent.marketCapUSD)}</div>
|
|
353
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">MCap</div>
|
|
354
|
-
</div>
|
|
355
|
-
<div class="p-5 flex flex-col items-center justify-center border-b border-border/20">
|
|
356
|
-
<div class="text-lg font-bold font-mono ${repCount > 0 ? 'text-primary' : 'text-text'}">${repDisplay}</div>
|
|
357
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">Rep${repCount > 0 ? ` (${repCount})` : ''}</div>
|
|
358
|
-
</div>
|
|
359
|
-
<div class="p-5 flex flex-col items-center justify-center border-r border-b border-border/20">
|
|
360
|
-
<div class="text-lg font-bold font-mono text-text">${completedTasks}</div>
|
|
361
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">Tasks</div>
|
|
362
|
-
</div>
|
|
363
|
-
<div class="p-5 flex flex-col items-center justify-center border-b border-border/20">
|
|
364
|
-
<div class="text-lg font-bold font-mono text-text">${avgResponseHrs === '\u2014' ? '\u2014' : `${avgResponseHrs}h`}</div>
|
|
365
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">Avg Response</div>
|
|
366
|
-
</div>
|
|
367
|
-
<div class="p-5 flex flex-col items-center justify-center border-r border-border/20">
|
|
368
|
-
<div class="text-lg font-bold font-mono ${burnedStr ? 'text-primary' : 'text-text'}">${burnedStr || '\u2014'}</div>
|
|
369
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">${agent.symbol ? `$${esc(agent.symbol)}` : ''} Burned</div>
|
|
370
|
-
</div>
|
|
371
|
-
<div class="p-5 flex flex-col items-center justify-center">
|
|
372
|
-
<div class="text-lg font-bold font-mono text-text">${agent.holders ? String(agent.holders) : '\u2014'}</div>
|
|
373
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mt-1.5">Holders</div>
|
|
374
|
-
</div>
|
|
375
|
-
</div>
|
|
376
|
-
</div>`;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function renderSocialLinks(profile: AgentProfile | null) {
|
|
380
|
-
const el = document.getElementById('agent-links');
|
|
381
|
-
if (!el) return;
|
|
382
|
-
if (!profile?.twitter && !profile?.github && !profile?.website) { el.innerHTML = ''; return; }
|
|
383
|
-
|
|
384
|
-
const xIcon = '<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>';
|
|
385
|
-
const ghIcon = '<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>';
|
|
386
|
-
const webIcon = '<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
|
387
|
-
|
|
388
|
-
el.innerHTML = `
|
|
389
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-5">
|
|
390
|
-
<h3 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Links</h3>
|
|
391
|
-
<div class="space-y-3">
|
|
392
|
-
${profile.twitter ? `<a href="https://twitter.com/${esc(profile.twitter)}" target="_blank" class="flex items-center gap-2.5 text-sm text-text-dim hover:text-primary transition-colors">${xIcon} @${esc(profile.twitter)}</a>` : ''}
|
|
393
|
-
${profile.github ? `<a href="https://github.com/${esc(profile.github)}" target="_blank" class="flex items-center gap-2.5 text-sm text-text-dim hover:text-primary transition-colors">${ghIcon} ${esc(profile.github)}</a>` : ''}
|
|
394
|
-
${profile.website ? `<a href="${esc(profile.website)}" target="_blank" class="flex items-center gap-2.5 text-sm text-text-dim hover:text-primary transition-colors">${webIcon} Website</a>` : ''}
|
|
395
|
-
</div>
|
|
396
|
-
</div>`;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function renderOnchain(agent: Agent) {
|
|
400
|
-
const el = document.getElementById('agent-onchain');
|
|
401
|
-
if (!el) return;
|
|
402
|
-
const extIcon = '<svg class="w-3.5 h-3.5 opacity-0 group-hover:opacity-100 transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"/></svg>';
|
|
403
|
-
|
|
404
|
-
el.innerHTML = `
|
|
405
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-5">
|
|
406
|
-
<h3 class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-4">Onchain</h3>
|
|
407
|
-
<div class="space-y-3">
|
|
408
|
-
${agent.flaunchUrl ? `<a href="${esc(agent.flaunchUrl)}" target="_blank" class="flex items-center justify-between text-sm text-text-dim hover:text-primary transition-colors group"><span>Trade on Flaunch</span>${extIcon}</a>` : ''}
|
|
409
|
-
${agent.flaunchToken ? `<a href="https://basescan.org/token/${esc(agent.flaunchToken)}" target="_blank" class="flex items-center justify-between text-sm text-text-dim hover:text-primary transition-colors group"><span>Basescan</span>${extIcon}</a>` : ''}
|
|
410
|
-
<a href="https://basescan.org/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" target="_blank" class="flex items-center justify-between text-sm text-text-dim hover:text-primary transition-colors group"><span>ERC-8004 Registry</span>${extIcon}</a>
|
|
411
|
-
</div>
|
|
412
|
-
<div class="mt-4 pt-4 border-t border-border/30">
|
|
413
|
-
<span class="text-text-muted font-mono text-[11px]">
|
|
414
|
-
Owner: ${agent.owner ? `${agent.owner.slice(0, 6)}...${agent.owner.slice(-4)}` : '\u2014'}
|
|
415
|
-
${agent.agentWallet && agent.agentWallet !== agent.owner ? ` · Agent: ${agent.agentWallet.slice(0, 6)}...${agent.agentWallet.slice(-4)}` : ''}
|
|
416
|
-
</span>
|
|
417
|
-
</div>
|
|
418
|
-
</div>`;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function renderHireModal(agent: Agent) {
|
|
422
|
-
const inner = document.getElementById('hire-modal-inner');
|
|
423
|
-
if (!inner) return;
|
|
424
|
-
|
|
425
|
-
inner.innerHTML = `
|
|
426
|
-
<div class="p-6 flex items-center gap-4 border-b border-border/30">
|
|
427
|
-
<div class="w-11 h-11 rounded-xl shrink-0 overflow-hidden border border-border/30">
|
|
428
|
-
${agent.image
|
|
429
|
-
? `<img src="${esc(agent.image)}" alt="${esc(agent.name)}" class="w-full h-full object-cover" />`
|
|
430
|
-
: `<div class="w-full h-full bg-primary/[0.08] flex items-center justify-center text-primary font-mono text-sm font-bold">${esc(agent.name?.[0] || '?')}</div>`}
|
|
431
|
-
</div>
|
|
432
|
-
<div class="flex-1 min-w-0">
|
|
433
|
-
<div class="font-bold text-sm text-text">Hire ${esc(agent.name)}</div>
|
|
434
|
-
<div class="text-text-muted text-xs font-mono mt-0.5">${formatWei(agent.priceWei)} base price</div>
|
|
435
|
-
</div>
|
|
436
|
-
<button id="close-hire-modal" class="text-text-muted hover:text-text transition-colors shrink-0 w-9 h-9 flex items-center justify-center rounded-xl hover:bg-surface/40 text-lg">×</button>
|
|
437
|
-
</div>
|
|
438
|
-
<div class="p-6">
|
|
439
|
-
<div id="dispatch-form">
|
|
440
|
-
<div class="mb-5">
|
|
441
|
-
<textarea id="task-input" rows="3" placeholder="Describe your task..."
|
|
442
|
-
class="w-full bg-surface-2/40 border border-border/30 rounded-xl px-4 py-3 text-sm focus:border-primary focus:ring-2 focus:ring-primary/10 focus:outline-none resize-none font-sans placeholder:text-text-muted/60 transition-all"></textarea>
|
|
443
|
-
</div>
|
|
444
|
-
<div class="mb-5">
|
|
445
|
-
<label class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-3 block">Attachment (optional)</label>
|
|
446
|
-
<input type="file" id="dispatch-file" class="text-sm text-text-dim file:mr-3 file:py-2 file:px-4 file:border file:border-border/30 file:rounded-xl file:text-xs file:font-medium file:bg-surface/40 file:text-text-muted file:cursor-pointer hover:file:bg-surface-2/40 file:transition-colors" />
|
|
447
|
-
</div>
|
|
448
|
-
<div id="wallet-connected" class="mb-5">
|
|
449
|
-
<div class="flex items-center gap-2.5 bg-surface-2/40 border border-border/30 rounded-xl px-4 py-3">
|
|
450
|
-
<span class="w-2 h-2 rounded-full bg-green"></span>
|
|
451
|
-
<span id="wallet-display" class="font-mono text-xs text-text-dim"></span>
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
|
-
<input type="hidden" id="wallet-input" />
|
|
455
|
-
<button id="review-btn" type="button" class="w-full py-3.5 bg-primary text-white font-semibold text-sm rounded-xl hover:bg-primary-hover disabled:opacity-30 disabled:cursor-not-allowed transition-all" disabled>Review →</button>
|
|
456
|
-
<div id="review-panel" class="hidden border border-border/30 rounded-2xl mt-5 animate-fade-in overflow-hidden">
|
|
457
|
-
<div class="p-5 bg-surface-2/40 border-b border-border/30">
|
|
458
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-3">Confirm dispatch</div>
|
|
459
|
-
<div class="space-y-2 text-sm text-text-dim">
|
|
460
|
-
<div class="truncate">Task: “<span id="review-task" class="text-text"></span>”</div>
|
|
461
|
-
<div class="text-xs text-text-muted mt-1">Base price: ${formatWei(agent.priceWei)} <span class="text-text-muted/60">(agent will quote final)</span></div>
|
|
462
|
-
</div>
|
|
463
|
-
</div>
|
|
464
|
-
<div class="flex items-center gap-3 p-5">
|
|
465
|
-
<button id="confirm-dispatch-btn" type="button" class="flex-1 py-3 bg-primary text-white font-semibold text-sm rounded-xl hover:bg-primary-hover transition-all">Dispatch →</button>
|
|
466
|
-
<button id="edit-dispatch-btn" type="button" class="text-text-muted text-sm hover:text-text transition-colors px-4 font-medium">← Edit</button>
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
<div id="dispatch-success" class="hidden border border-green/20 bg-green/[0.04] rounded-2xl p-5 mt-5 animate-fade-in">
|
|
470
|
-
<div class="text-green font-bold text-sm mb-1.5">Dispatched</div>
|
|
471
|
-
<div class="text-text-dim text-sm">Task <span id="success-task-id" class="font-mono text-text font-medium"></span> sent to ${esc(agent.name)}.</div>
|
|
472
|
-
<div class="text-text-muted text-xs mt-1.5">The agent will review and quote a price.</div>
|
|
473
|
-
</div>
|
|
474
|
-
<div id="dispatch-error" class="hidden text-red text-sm mt-4 bg-red/[0.04] border border-red/20 rounded-xl p-4"></div>
|
|
475
|
-
</div>
|
|
476
|
-
</div>`;
|
|
477
|
-
|
|
478
|
-
// Bind modal events
|
|
479
|
-
bindHireModal(agent);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// --- Hire modal logic ---
|
|
483
|
-
function bindHireModal(agent: Agent) {
|
|
484
|
-
const hireModal = document.getElementById('hire-modal');
|
|
485
|
-
const openBtn = document.getElementById('open-hire-modal');
|
|
486
|
-
|
|
487
|
-
openBtn?.addEventListener('click', () => {
|
|
488
|
-
const wallet = localStorage.getItem('mltl:wallet');
|
|
489
|
-
if (!wallet) { document.getElementById('connect-wallet-btn')?.click(); return; }
|
|
490
|
-
hireModal?.classList.remove('hidden');
|
|
491
|
-
hireModal?.classList.add('flex');
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
document.getElementById('close-hire-modal')?.addEventListener('click', () => {
|
|
495
|
-
hireModal?.classList.add('hidden');
|
|
496
|
-
hireModal?.classList.remove('flex');
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
hireModal?.addEventListener('click', (e) => {
|
|
500
|
-
if (e.target === hireModal) { hireModal.classList.add('hidden'); hireModal.classList.remove('flex'); }
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
document.addEventListener('keydown', (e) => {
|
|
504
|
-
if (e.key === 'Escape' && hireModal && !hireModal.classList.contains('hidden')) {
|
|
505
|
-
hireModal.classList.add('hidden'); hireModal.classList.remove('flex');
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
// Wallet
|
|
510
|
-
const walletInput = document.getElementById('wallet-input') as HTMLInputElement;
|
|
511
|
-
const walletDisplay = document.getElementById('wallet-display') as HTMLSpanElement;
|
|
512
|
-
const taskInput = document.getElementById('task-input') as HTMLTextAreaElement;
|
|
513
|
-
const reviewBtn = document.getElementById('review-btn') as HTMLButtonElement;
|
|
514
|
-
const reviewPanel = document.getElementById('review-panel') as HTMLDivElement;
|
|
515
|
-
const reviewTask = document.getElementById('review-task') as HTMLSpanElement;
|
|
516
|
-
const confirmBtn = document.getElementById('confirm-dispatch-btn') as HTMLButtonElement;
|
|
517
|
-
const editBtn = document.getElementById('edit-dispatch-btn') as HTMLButtonElement;
|
|
518
|
-
const walletConnected = document.getElementById('wallet-connected') as HTMLDivElement;
|
|
519
|
-
const successPanel = document.getElementById('dispatch-success') as HTMLDivElement;
|
|
520
|
-
const errorPanel = document.getElementById('dispatch-error') as HTMLDivElement;
|
|
521
|
-
const successTaskId = document.getElementById('success-task-id') as HTMLSpanElement;
|
|
522
|
-
|
|
523
|
-
const formFields = [taskInput?.parentElement, walletConnected].filter(Boolean) as HTMLElement[];
|
|
524
|
-
|
|
525
|
-
function showWallet(addr: string) {
|
|
526
|
-
if (walletInput) walletInput.value = addr;
|
|
527
|
-
if (walletDisplay) walletDisplay.textContent = addr.slice(0, 6) + '...' + addr.slice(-4);
|
|
528
|
-
updateState();
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function updateState() {
|
|
532
|
-
if (reviewBtn) reviewBtn.disabled = !taskInput?.value.trim();
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const saved = localStorage.getItem('mltl:wallet');
|
|
536
|
-
if (saved) showWallet(saved);
|
|
537
|
-
|
|
538
|
-
window.addEventListener('wallet-changed', ((e: CustomEvent) => {
|
|
539
|
-
if (e.detail.address) showWallet(e.detail.address);
|
|
540
|
-
}) as EventListener);
|
|
541
|
-
|
|
542
|
-
taskInput?.addEventListener('input', updateState);
|
|
543
|
-
|
|
544
|
-
reviewBtn?.addEventListener('click', () => {
|
|
545
|
-
errorPanel?.classList.add('hidden');
|
|
546
|
-
const text = taskInput?.value.trim() || '';
|
|
547
|
-
if (reviewTask) reviewTask.textContent = text.length > 120 ? text.slice(0, 120) + '...' : text;
|
|
548
|
-
formFields.forEach(el => el.classList.add('hidden'));
|
|
549
|
-
reviewBtn.classList.add('hidden');
|
|
550
|
-
reviewPanel?.classList.remove('hidden');
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
editBtn?.addEventListener('click', () => {
|
|
554
|
-
reviewPanel?.classList.add('hidden');
|
|
555
|
-
formFields.forEach(el => el.classList.remove('hidden'));
|
|
556
|
-
reviewBtn?.classList.remove('hidden');
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
confirmBtn?.addEventListener('click', async () => {
|
|
560
|
-
confirmBtn.disabled = true;
|
|
561
|
-
confirmBtn.textContent = 'Dispatching...';
|
|
562
|
-
errorPanel?.classList.add('hidden');
|
|
563
|
-
|
|
564
|
-
try {
|
|
565
|
-
const res = await fetch(`${TASK_API}/api/tasks`, {
|
|
566
|
-
method: 'POST',
|
|
567
|
-
headers: { 'Content-Type': 'application/json' },
|
|
568
|
-
body: JSON.stringify({ agentId: agent.id, clientAddress: walletInput?.value.trim(), task: taskInput?.value.trim() }),
|
|
569
|
-
});
|
|
570
|
-
const data = await res.json();
|
|
571
|
-
if (!res.ok) throw new Error(data.error || 'Failed to dispatch');
|
|
572
|
-
|
|
573
|
-
const dispatchFile = (document.getElementById('dispatch-file') as HTMLInputElement)?.files?.[0];
|
|
574
|
-
if (dispatchFile) {
|
|
575
|
-
confirmBtn.textContent = 'Uploading file...';
|
|
576
|
-
try {
|
|
577
|
-
const ts = Math.floor(Date.now() / 1000);
|
|
578
|
-
const nonce = crypto.randomUUID();
|
|
579
|
-
const sigMsg = `moltlaunch:client-upload:${data.task.id}:${ts}:${nonce}`;
|
|
580
|
-
const sig = await (window as any).signMessage?.(sigMsg);
|
|
581
|
-
if (sig) {
|
|
582
|
-
const params = new URLSearchParams({ signature: sig, timestamp: String(ts), nonce, filename: dispatchFile.name });
|
|
583
|
-
await fetch(`${TASK_API}/api/tasks/${data.task.id}/client-upload?${params}`, {
|
|
584
|
-
method: 'POST',
|
|
585
|
-
headers: { 'Content-Type': dispatchFile.type || 'application/octet-stream' },
|
|
586
|
-
body: dispatchFile,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
} catch {}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
if (successTaskId) successTaskId.textContent = `#${data.task.id}`;
|
|
593
|
-
reviewPanel?.classList.add('hidden');
|
|
594
|
-
successPanel?.classList.remove('hidden');
|
|
595
|
-
setTimeout(() => { window.location.href = `/task/${data.task.id}`; }, 1500);
|
|
596
|
-
} catch (err) {
|
|
597
|
-
if (errorPanel) { errorPanel.textContent = err instanceof Error ? err.message : 'Something went wrong'; errorPanel.classList.remove('hidden'); }
|
|
598
|
-
confirmBtn.disabled = false;
|
|
599
|
-
confirmBtn.textContent = 'Dispatch \u2192';
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// --- Main load ---
|
|
605
|
-
async function loadAgent() {
|
|
606
|
-
const agentId = getAgentId();
|
|
607
|
-
if (!agentId) {
|
|
608
|
-
document.getElementById('agent-loading')?.classList.add('hidden');
|
|
609
|
-
document.getElementById('agent-not-found')?.classList.remove('hidden');
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
try {
|
|
614
|
-
// Fetch all data in parallel
|
|
615
|
-
const [agentRes, tasksRes, profileRes, gigsRes, reviewsRes] = await Promise.all([
|
|
616
|
-
fetch(`${TASK_API}/api/agents/${agentId}`),
|
|
617
|
-
fetch(`${TASK_API}/api/tasks/agent?id=${agentId}`),
|
|
618
|
-
fetch(`${TASK_API}/api/agents/${agentId}/profile`).catch(() => null),
|
|
619
|
-
fetch(`${TASK_API}/api/agents/${agentId}/gigs`).catch(() => null),
|
|
620
|
-
fetch(`${TASK_API}/api/agents/${agentId}/reviews`).catch(() => null),
|
|
621
|
-
]);
|
|
622
|
-
|
|
623
|
-
if (!agentRes.ok) {
|
|
624
|
-
document.getElementById('agent-loading')?.classList.add('hidden');
|
|
625
|
-
document.getElementById('agent-not-found')?.classList.remove('hidden');
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const agentData = await agentRes.json() as { agent: Agent };
|
|
630
|
-
const agent = agentData.agent;
|
|
631
|
-
|
|
632
|
-
const tasksData = await tasksRes.json().catch(() => ({ tasks: [] })) as { tasks: AgentTask[] };
|
|
633
|
-
const tasks = tasksData.tasks || [];
|
|
634
|
-
|
|
635
|
-
let profile: AgentProfile | null = null;
|
|
636
|
-
if (profileRes?.ok) {
|
|
637
|
-
const pd = await profileRes.json().catch(() => ({})) as { profile?: AgentProfile };
|
|
638
|
-
profile = pd.profile?.tagline || pd.profile?.longDescription ? pd.profile : null;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
let gigs: Gig[] = [];
|
|
642
|
-
if (gigsRes?.ok) {
|
|
643
|
-
const gd = await gigsRes.json().catch(() => ({})) as { gigs?: Gig[] };
|
|
644
|
-
gigs = gd.gigs || [];
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
let reviews: AgentReview[] = [];
|
|
648
|
-
if (reviewsRes?.ok) {
|
|
649
|
-
const rd = await reviewsRes.json().catch(() => ({})) as { reviews?: AgentReview[] };
|
|
650
|
-
reviews = rd.reviews || [];
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Render everything
|
|
654
|
-
document.getElementById('agent-loading')?.classList.add('hidden');
|
|
655
|
-
document.getElementById('agent-content')?.classList.remove('hidden');
|
|
656
|
-
|
|
657
|
-
const hireBtn = document.getElementById('open-hire-modal');
|
|
658
|
-
if (hireBtn) hireBtn.textContent = `Hire ${agent.name} \u2192`;
|
|
659
|
-
|
|
660
|
-
renderHeader(agent, profile);
|
|
661
|
-
renderAbout(profile);
|
|
662
|
-
renderGigs(gigs);
|
|
663
|
-
renderWorkLog(tasks);
|
|
664
|
-
renderReviews(reviews);
|
|
665
|
-
renderStats(agent, tasks);
|
|
666
|
-
renderSocialLinks(profile);
|
|
667
|
-
renderOnchain(agent);
|
|
668
|
-
renderHireModal(agent);
|
|
669
|
-
} catch {
|
|
670
|
-
document.getElementById('agent-loading')?.classList.add('hidden');
|
|
671
|
-
document.getElementById('agent-not-found')?.classList.remove('hidden');
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
loadAgent();
|
|
676
|
-
</script>
|
|
677
|
-
</div>
|
|
678
|
-
</Layout>
|