repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | <!DOCTYPE html> |
| #2 | <html lang="en"> |
| #3 | <head> |
| #4 | <meta charset="UTF-8"> |
| #5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| #6 | <title>MAWD - Solana Trading Agent</title> |
| #7 | <style> |
| #8 | :root { |
| #9 | --bg-primary: #0a0a0f; |
| #10 | --bg-secondary: #12121a; |
| #11 | --bg-tertiary: #1a1a25; |
| #12 | --border-color: #2a2a35; |
| #13 | --text-primary: #e4e4e7; |
| #14 | --text-secondary: #a1a1aa; |
| #15 | --text-muted: #71717a; |
| #16 | --accent-cyan: #06b6d4; |
| #17 | --accent-green: #22c55e; |
| #18 | --accent-yellow: #eab308; |
| #19 | --accent-red: #ef4444; |
| #20 | --accent-purple: #a855f7; |
| #21 | --accent-blue: #3b82f6; |
| #22 | } |
| #23 | |
| #24 | * { |
| #25 | margin: 0; |
| #26 | padding: 0; |
| #27 | box-sizing: border-box; |
| #28 | } |
| #29 | |
| #30 | body { |
| #31 | font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; |
| #32 | background: var(--bg-primary); |
| #33 | color: var(--text-primary); |
| #34 | min-height: 100vh; |
| #35 | display: flex; |
| #36 | flex-direction: column; |
| #37 | } |
| #38 | |
| #39 | /* Header */ |
| #40 | .header { |
| #41 | background: var(--bg-secondary); |
| #42 | border-bottom: 1px solid var(--border-color); |
| #43 | padding: 1rem 1.5rem; |
| #44 | display: flex; |
| #45 | justify-content: space-between; |
| #46 | align-items: center; |
| #47 | } |
| #48 | |
| #49 | .logo { |
| #50 | display: flex; |
| #51 | align-items: center; |
| #52 | gap: 0.75rem; |
| #53 | } |
| #54 | |
| #55 | .logo-icon { |
| #56 | font-size: 1.5rem; |
| #57 | } |
| #58 | |
| #59 | .logo-content { |
| #60 | display: flex; |
| #61 | flex-direction: column; |
| #62 | gap: 0.1rem; |
| #63 | } |
| #64 | |
| #65 | .logo-text { |
| #66 | font-size: 1.25rem; |
| #67 | font-weight: 700; |
| #68 | background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); |
| #69 | -webkit-background-clip: text; |
| #70 | -webkit-text-fill-color: transparent; |
| #71 | } |
| #72 | |
| #73 | .logo-tagline { |
| #74 | font-size: 0.65rem; |
| #75 | font-weight: 500; |
| #76 | color: var(--text-secondary); |
| #77 | letter-spacing: 0.5px; |
| #78 | opacity: 0.8; |
| #79 | } |
| #80 | |
| #81 | .status-bar { |
| #82 | display: flex; |
| #83 | align-items: center; |
| #84 | gap: 1.5rem; |
| #85 | } |
| #86 | |
| #87 | .status-item { |
| #88 | display: flex; |
| #89 | align-items: center; |
| #90 | gap: 0.5rem; |
| #91 | font-size: 0.875rem; |
| #92 | } |
| #93 | |
| #94 | .status-dot { |
| #95 | width: 8px; |
| #96 | height: 8px; |
| #97 | border-radius: 50%; |
| #98 | background: var(--accent-green); |
| #99 | animation: pulse 2s infinite; |
| #100 | } |
| #101 | |
| #102 | .status-dot.disconnected { |
| #103 | background: var(--accent-red); |
| #104 | animation: none; |
| #105 | } |
| #106 | |
| #107 | @keyframes pulse { |
| #108 | 0%, 100% { opacity: 1; } |
| #109 | 50% { opacity: 0.5; } |
| #110 | } |
| #111 | |
| #112 | .wallet-address { |
| #113 | font-size: 0.75rem; |
| #114 | color: var(--text-secondary); |
| #115 | background: var(--bg-tertiary); |
| #116 | padding: 0.25rem 0.75rem; |
| #117 | border-radius: 4px; |
| #118 | font-family: inherit; |
| #119 | } |
| #120 | |
| #121 | .swap-button { |
| #122 | background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); |
| #123 | border: none; |
| #124 | border-radius: 8px; |
| #125 | padding: 0.5rem 1rem; |
| #126 | color: white; |
| #127 | font-weight: 600; |
| #128 | cursor: pointer; |
| #129 | transition: all 0.2s; |
| #130 | font-family: inherit; |
| #131 | font-size: 0.875rem; |
| #132 | } |
| #133 | |
| #134 | .swap-button:hover { |
| #135 | transform: scale(1.05); |
| #136 | box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3); |
| #137 | } |
| #138 | |
| #139 | /* Main Layout */ |
| #140 | .main-container { |
| #141 | flex: 1; |
| #142 | display: flex; |
| #143 | overflow: hidden; |
| #144 | } |
| #145 | |
| #146 | /* Chat Area */ |
| #147 | .chat-panel { |
| #148 | flex: 1; |
| #149 | display: flex; |
| #150 | flex-direction: column; |
| #151 | border-right: 1px solid var(--border-color); |
| #152 | } |
| #153 | |
| #154 | .messages { |
| #155 | flex: 1; |
| #156 | overflow-y: auto; |
| #157 | padding: 1rem; |
| #158 | display: flex; |
| #159 | flex-direction: column; |
| #160 | gap: 1rem; |
| #161 | } |
| #162 | |
| #163 | .message { |
| #164 | max-width: 85%; |
| #165 | animation: fadeIn 0.3s ease; |
| #166 | } |
| #167 | |
| #168 | @keyframes fadeIn { |
| #169 | from { opacity: 0; transform: translateY(10px); } |
| #170 | to { opacity: 1; transform: translateY(0); } |
| #171 | } |
| #172 | |
| #173 | .message.user { |
| #174 | align-self: flex-end; |
| #175 | background: var(--accent-blue); |
| #176 | padding: 0.75rem 1rem; |
| #177 | border-radius: 12px 12px 4px 12px; |
| #178 | } |
| #179 | |
| #180 | .message.assistant { |
| #181 | align-self: flex-start; |
| #182 | background: var(--bg-tertiary); |
| #183 | padding: 0.75rem 1rem; |
| #184 | border-radius: 12px 12px 12px 4px; |
| #185 | border: 1px solid var(--border-color); |
| #186 | } |
| #187 | |
| #188 | .message.system { |
| #189 | align-self: center; |
| #190 | background: var(--bg-secondary); |
| #191 | padding: 0.5rem 1rem; |
| #192 | border-radius: 8px; |
| #193 | font-size: 0.875rem; |
| #194 | color: var(--text-secondary); |
| #195 | } |
| #196 | |
| #197 | .message-content { |
| #198 | white-space: pre-wrap; |
| #199 | word-break: break-word; |
| #200 | line-height: 1.5; |
| #201 | } |
| #202 | |
| #203 | /* Event Cards */ |
| #204 | .event-card { |
| #205 | background: var(--bg-secondary); |
| #206 | border: 1px solid var(--border-color); |
| #207 | border-radius: 8px; |
| #208 | padding: 0.75rem 1rem; |
| #209 | margin: 0.5rem 0; |
| #210 | animation: fadeIn 0.3s ease; |
| #211 | } |
| #212 | |
| #213 | .event-card.thinking { |
| #214 | border-left: 3px solid var(--accent-purple); |
| #215 | } |
| #216 | |
| #217 | .event-card.tool-call { |
| #218 | border-left: 3px solid var(--accent-yellow); |
| #219 | } |
| #220 | |
| #221 | .event-card.tool-result { |
| #222 | border-left: 3px solid var(--accent-green); |
| #223 | } |
| #224 | |
| #225 | .event-card.tool-error { |
| #226 | border-left: 3px solid var(--accent-red); |
| #227 | } |
| #228 | |
| #229 | .event-header { |
| #230 | display: flex; |
| #231 | align-items: center; |
| #232 | gap: 0.5rem; |
| #233 | font-size: 0.75rem; |
| #234 | color: var(--text-muted); |
| #235 | margin-bottom: 0.5rem; |
| #236 | } |
| #237 | |
| #238 | .event-icon { |
| #239 | font-size: 1rem; |
| #240 | } |
| #241 | |
| #242 | .event-content { |
| #243 | font-size: 0.875rem; |
| #244 | color: var(--text-secondary); |
| #245 | overflow-x: auto; |
| #246 | } |
| #247 | |
| #248 | .event-content pre { |
| #249 | margin: 0; |
| #250 | font-family: inherit; |
| #251 | } |
| #252 | |
| #253 | /* Input Area */ |
| #254 | .input-area { |
| #255 | padding: 1rem; |
| #256 | background: var(--bg-secondary); |
| #257 | border-top: 1px solid var(--border-color); |
| #258 | } |
| #259 | |
| #260 | .input-container { |
| #261 | display: flex; |
| #262 | gap: 0.75rem; |
| #263 | align-items: flex-end; |
| #264 | } |
| #265 | |
| #266 | .input-wrapper { |
| #267 | flex: 1; |
| #268 | position: relative; |
| #269 | } |
| #270 | |
| #271 | .chat-input { |
| #272 | width: 100%; |
| #273 | background: var(--bg-tertiary); |
| #274 | border: 1px solid var(--border-color); |
| #275 | border-radius: 8px; |
| #276 | padding: 0.75rem 1rem; |
| #277 | color: var(--text-primary); |
| #278 | font-family: inherit; |
| #279 | font-size: 0.9rem; |
| #280 | resize: none; |
| #281 | min-height: 48px; |
| #282 | max-height: 150px; |
| #283 | } |
| #284 | |
| #285 | .chat-input:focus { |
| #286 | outline: none; |
| #287 | border-color: var(--accent-cyan); |
| #288 | } |
| #289 | |
| #290 | .chat-input::placeholder { |
| #291 | color: var(--text-muted); |
| #292 | } |
| #293 | |
| #294 | .send-btn { |
| #295 | background: var(--accent-cyan); |
| #296 | border: none; |
| #297 | border-radius: 8px; |
| #298 | padding: 0.75rem 1.25rem; |
| #299 | color: var(--bg-primary); |
| #300 | font-weight: 600; |
| #301 | cursor: pointer; |
| #302 | transition: all 0.2s; |
| #303 | display: flex; |
| #304 | align-items: center; |
| #305 | gap: 0.5rem; |
| #306 | } |
| #307 | |
| #308 | .send-btn:hover { |
| #309 | background: #0891b2; |
| #310 | } |
| #311 | |
| #312 | .send-btn:disabled { |
| #313 | opacity: 0.5; |
| #314 | cursor: not-allowed; |
| #315 | } |
| #316 | |
| #317 | /* Activity Panel */ |
| #318 | .activity-panel { |
| #319 | width: 350px; |
| #320 | background: var(--bg-secondary); |
| #321 | display: flex; |
| #322 | flex-direction: column; |
| #323 | overflow: hidden; |
| #324 | } |
| #325 | |
| #326 | .panel-header { |
| #327 | padding: 1rem; |
| #328 | border-bottom: 1px solid var(--border-color); |
| #329 | font-weight: 600; |
| #330 | display: flex; |
| #331 | align-items: center; |
| #332 | gap: 0.5rem; |
| #333 | } |
| #334 | |
| #335 | .activity-feed { |
| #336 | flex: 1; |
| #337 | overflow-y: auto; |
| #338 | padding: 1rem; |
| #339 | } |
| #340 | |
| #341 | .activity-item { |
| #342 | display: flex; |
| #343 | align-items: flex-start; |
| #344 | gap: 0.75rem; |
| #345 | padding: 0.75rem; |
| #346 | background: var(--bg-tertiary); |
| #347 | border-radius: 8px; |
| #348 | margin-bottom: 0.75rem; |
| #349 | font-size: 0.875rem; |
| #350 | animation: fadeIn 0.3s ease; |
| #351 | } |
| #352 | |
| #353 | .activity-icon { |
| #354 | font-size: 1.25rem; |
| #355 | flex-shrink: 0; |
| #356 | } |
| #357 | |
| #358 | .activity-details { |
| #359 | flex: 1; |
| #360 | min-width: 0; |
| #361 | } |
| #362 | |
| #363 | .activity-title { |
| #364 | color: var(--text-primary); |
| #365 | font-weight: 500; |
| #366 | margin-bottom: 0.25rem; |
| #367 | } |
| #368 | |
| #369 | .activity-desc { |
| #370 | color: var(--text-muted); |
| #371 | font-size: 0.75rem; |
| #372 | word-break: break-word; |
| #373 | } |
| #374 | |
| #375 | .activity-time { |
| #376 | color: var(--text-muted); |
| #377 | font-size: 0.7rem; |
| #378 | margin-top: 0.25rem; |
| #379 | } |
| #380 | |
| #381 | /* Quick Actions */ |
| #382 | .quick-actions { |
| #383 | padding: 1rem; |
| #384 | border-top: 1px solid var(--border-color); |
| #385 | } |
| #386 | |
| #387 | .quick-actions-title { |
| #388 | font-size: 0.75rem; |
| #389 | color: var(--text-muted); |
| #390 | margin-bottom: 0.75rem; |
| #391 | } |
| #392 | |
| #393 | .quick-btns { |
| #394 | display: flex; |
| #395 | flex-wrap: wrap; |
| #396 | gap: 0.5rem; |
| #397 | } |
| #398 | |
| #399 | .quick-btn { |
| #400 | background: var(--bg-tertiary); |
| #401 | border: 1px solid var(--border-color); |
| #402 | border-radius: 6px; |
| #403 | padding: 0.5rem 0.75rem; |
| #404 | color: var(--text-secondary); |
| #405 | font-size: 0.75rem; |
| #406 | cursor: pointer; |
| #407 | transition: all 0.2s; |
| #408 | font-family: inherit; |
| #409 | } |
| #410 | |
| #411 | .quick-btn:hover { |
| #412 | border-color: var(--accent-cyan); |
| #413 | color: var(--accent-cyan); |
| #414 | } |
| #415 | |
| #416 | /* Loading States */ |
| #417 | .typing-indicator { |
| #418 | display: flex; |
| #419 | gap: 4px; |
| #420 | padding: 0.75rem 1rem; |
| #421 | align-self: flex-start; |
| #422 | } |
| #423 | |
| #424 | .typing-dot { |
| #425 | width: 8px; |
| #426 | height: 8px; |
| #427 | background: var(--accent-cyan); |
| #428 | border-radius: 50%; |
| #429 | animation: typing 1.4s infinite; |
| #430 | } |
| #431 | |
| #432 | .typing-dot:nth-child(2) { animation-delay: 0.2s; } |
| #433 | .typing-dot:nth-child(3) { animation-delay: 0.4s; } |
| #434 | |
| #435 | @keyframes typing { |
| #436 | 0%, 60%, 100% { transform: translateY(0); } |
| #437 | 30% { transform: translateY(-6px); } |
| #438 | } |
| #439 | |
| #440 | /* Scrollbar */ |
| #441 | ::-webkit-scrollbar { |
| #442 | width: 8px; |
| #443 | } |
| #444 | |
| #445 | ::-webkit-scrollbar-track { |
| #446 | background: var(--bg-primary); |
| #447 | } |
| #448 | |
| #449 | ::-webkit-scrollbar-thumb { |
| #450 | background: var(--border-color); |
| #451 | border-radius: 4px; |
| #452 | } |
| #453 | |
| #454 | ::-webkit-scrollbar-thumb:hover { |
| #455 | background: var(--text-muted); |
| #456 | } |
| #457 | |
| #458 | /* Responsive */ |
| #459 | @media (max-width: 900px) { |
| #460 | .activity-panel { |
| #461 | display: none; |
| #462 | } |
| #463 | } |
| #464 | </style> |
| #465 | </head> |
| #466 | <body> |
| #467 | <header class="header"> |
| #468 | <div class="logo"> |
| #469 | <span class="logo-icon">🦞</span> |
| #470 | <div class="logo-content"> |
| #471 | <span class="logo-text">MAWD</span> |
| #472 | <span class="logo-tagline">Exfoliate, trade, launch, vibe!</span> |
| #473 | </div> |
| #474 | </div> |
| #475 | <div class="status-bar"> |
| #476 | <div class="status-item"> |
| #477 | <span class="status-dot" id="statusDot"></span> |
| #478 | <span id="statusText">Connecting...</span> |
| #479 | </div> |
| #480 | <span class="status-item"> |
| #481 | <span>💰</span> |
| #482 | <span id="balanceDisplay">--</span> SOL |
| #483 | </span> |
| #484 | <span class="status-item" title="Net Worth"> |
| #485 | <span>💎</span> |
| #486 | <span id="networthDisplay">--</span> |
| #487 | </span> |
| #488 | <span class="wallet-address" id="walletAddress">--</span> |
| #489 | <button class="swap-button" id="swapButton" title="Open Jupiter Swap"> |
| #490 | 🔄 Swap |
| #491 | </button> |
| #492 | </div> |
| #493 | </header> |
| #494 | |
| #495 | <main class="main-container"> |
| #496 | <div class="chat-panel"> |
| #497 | <div class="messages" id="messages"> |
| #498 | <div class="message system"> |
| #499 | 🌊 Welcome to MAWD - Your AI-Powered Solana Trading Agent |
| #500 | <br><br> |
| #501 | 💰 Trading & Portfolio | 📊 Analytics & PnL | 📈 OHLCV Charts |
| #502 | <br> |
| #503 | 🔐 Auto Token Analysis | 🐦 Twitter | 🎨 AI Image/Music/Video |
| #504 | <br><br> |
| #505 | 💡 <strong>Just paste any Solana contract address</strong> - I'll automatically analyze it with security data & price charts! |
| #506 | </div> |
| #507 | </div> |
| #508 | |
| #509 | <div class="input-area"> |
| #510 | <div class="input-container"> |
| #511 | <div class="input-wrapper"> |
| #512 | <textarea |
| #513 | class="chat-input" |
| #514 | id="chatInput" |
| #515 | placeholder="Trade, analyze tokens, paste contract addresses for auto-analysis, post tweets, generate content..." |
| #516 | rows="1" |
| #517 | ></textarea> |
| #518 | </div> |
| #519 | <button class="send-btn" id="sendBtn" disabled> |
| #520 | <span>Send</span> |
| #521 | <span>↵</span> |
| #522 | </button> |
| #523 | </div> |
| #524 | </div> |
| #525 | </div> |
| #526 | |
| #527 | <aside class="activity-panel"> |
| #528 | <div class="panel-header"> |
| #529 | <span>⚡</span> |
| #530 | <span>Live Activity</span> |
| #531 | </div> |
| #532 | <div class="activity-feed" id="activityFeed"> |
| #533 | <!-- Activity items appear here --> |
| #534 | </div> |
| #535 | <div class="quick-actions"> |
| #536 | <div class="quick-actions-title">💰 Trading & Portfolio</div> |
| #537 | <div class="quick-btns"> |
| #538 | <button class="quick-btn" data-cmd="Check my wallet balance">💰 Balance</button> |
| #539 | <button class="quick-btn" data-cmd="Show my portfolio with USD values">📊 Portfolio</button> |
| #540 | <button class="quick-btn" data-cmd="What are the top 10 trending tokens?">🔥 Trending</button> |
| #541 | <button class="quick-btn" data-cmd="Search for BONK token">🔍 Search</button> |
| #542 | </div> |
| #543 | |
| #544 | <div class="quick-actions-title" style="margin-top: 1rem;">📈 Analytics & Insights</div> |
| #545 | <div class="quick-btns"> |
| #546 | <button class="quick-btn" data-cmd="What's my net worth?">💎 Net Worth</button> |
| #547 | <button class="quick-btn" data-cmd="Show my PnL for the last 30 days">📊 PnL (30d)</button> |
| #548 | <button class="quick-btn" data-cmd="Show my net worth chart for the last 7 days">📈 Chart (7d)</button> |
| #549 | </div> |
| #550 | |
| #551 | <div class="quick-actions-title" style="margin-top: 1rem;">📈 Charts & Security</div> |
| #552 | <div class="quick-btns"> |
| #553 | <button class="quick-btn" data-cmd="Show me BONK chart with 4 hour candles">📈 Chart (4H)</button> |
| #554 | <button class="quick-btn" data-cmd="DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263">🔍 Auto-Analyze</button> |
| #555 | </div> |
| #556 | |
| #557 | <div class="quick-actions-title" style="margin-top: 1rem;">🐦 Social & Creative</div> |
| #558 | <div class="quick-btns"> |
| #559 | <button class="quick-btn" data-cmd="Tweet about my trading wins">🐦 Tweet</button> |
| #560 | <button class="quick-btn" data-cmd="Generate an image of Solana rocket going to moon">🎨 Image</button> |
| #561 | <button class="quick-btn" data-cmd="Generate a short hype music track">🎵 Music</button> |
| #562 | </div> |
| #563 | |
| #564 | <div class="quick-actions-title" style="margin-top: 1rem;">🏦 CDP Account Management</div> |
| #565 | <div class="quick-btns"> |
| #566 | <button class="quick-btn" data-cmd="Create a new CDP Solana account">➕ New Account</button> |
| #567 | <button class="quick-btn" data-cmd="List all my CDP accounts">📋 List Accounts</button> |
| #568 | <button class="quick-btn" data-cmd="Request faucet for my CDP account">💧 Request Faucet</button> |
| #569 | </div> |
| #570 | |
| #571 | <div class="quick-actions-title" style="margin-top: 1rem;">💰 CoinGecko Market Data</div> |
| #572 | <div class="quick-btns"> |
| #573 | <button class="quick-btn" data-cmd="Get prices for bitcoin, ethereum, and solana">💵 Get Prices</button> |
| #574 | <button class="quick-btn" data-cmd="Show me trending cryptocurrencies">🔥 Trending</button> |
| #575 | <button class="quick-btn" data-cmd="Show global crypto market stats">🌍 Global Stats</button> |
| #576 | </div> |
| #577 | </div> |
| #578 | </aside> |
| #579 | </main> |
| #580 | |
| #581 | <script> |
| #582 | // ============================================================ |
| #583 | // MAWD Frontend - WebSocket Connection & UI |
| #584 | // ============================================================ |
| #585 | |
| #586 | class MAWDClient { |
| #587 | constructor() { |
| #588 | this.ws = null; |
| #589 | this.clientId = this.generateClientId(); |
| #590 | this.isConnected = false; |
| #591 | this.isProcessing = false; |
| #592 | this.reconnectAttempts = 0; |
| #593 | this.maxReconnectAttempts = 5; |
| #594 | |
| #595 | // DOM Elements |
| #596 | this.messagesEl = document.getElementById('messages'); |
| #597 | this.chatInput = document.getElementById('chatInput'); |
| #598 | this.sendBtn = document.getElementById('sendBtn'); |
| #599 | this.statusDot = document.getElementById('statusDot'); |
| #600 | this.statusText = document.getElementById('statusText'); |
| #601 | this.walletAddress = document.getElementById('walletAddress'); |
| #602 | this.balanceDisplay = document.getElementById('balanceDisplay'); |
| #603 | this.networthDisplay = document.getElementById('networthDisplay'); |
| #604 | this.activityFeed = document.getElementById('activityFeed'); |
| #605 | |
| #606 | this.init(); |
| #607 | } |
| #608 | |
| #609 | generateClientId() { |
| #610 | return 'mawd_' + Math.random().toString(36).substr(2, 9); |
| #611 | } |
| #612 | |
| #613 | init() { |
| #614 | this.connect(); |
| #615 | this.setupEventListeners(); |
| #616 | } |
| #617 | |
| #618 | connect() { |
| #619 | const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; |
| #620 | const wsUrl = `${protocol}//${window.location.host}/ws/${this.clientId}`; |
| #621 | |
| #622 | console.log('Connecting to:', wsUrl); |
| #623 | this.ws = new WebSocket(wsUrl); |
| #624 | |
| #625 | this.ws.onopen = () => this.onConnect(); |
| #626 | this.ws.onmessage = (e) => this.onMessage(e); |
| #627 | this.ws.onclose = () => this.onDisconnect(); |
| #628 | this.ws.onerror = (e) => console.error('WebSocket error:', e); |
| #629 | } |
| #630 | |
| #631 | onConnect() { |
| #632 | console.log('Connected to MAWD server'); |
| #633 | this.isConnected = true; |
| #634 | this.reconnectAttempts = 0; |
| #635 | this.statusDot.classList.remove('disconnected'); |
| #636 | this.statusText.textContent = 'Connected'; |
| #637 | this.sendBtn.disabled = false; |
| #638 | |
| #639 | this.addActivity('🟢', 'Connected', 'WebSocket connection established'); |
| #640 | } |
| #641 | |
| #642 | onDisconnect() { |
| #643 | console.log('Disconnected from server'); |
| #644 | this.isConnected = false; |
| #645 | this.statusDot.classList.add('disconnected'); |
| #646 | this.statusText.textContent = 'Disconnected'; |
| #647 | this.sendBtn.disabled = true; |
| #648 | |
| #649 | this.addActivity('🔴', 'Disconnected', 'Connection lost'); |
| #650 | |
| #651 | // Attempt reconnect |
| #652 | if (this.reconnectAttempts < this.maxReconnectAttempts) { |
| #653 | this.reconnectAttempts++; |
| #654 | setTimeout(() => { |
| #655 | console.log(`Reconnect attempt ${this.reconnectAttempts}...`); |
| #656 | this.connect(); |
| #657 | }, 2000 * this.reconnectAttempts); |
| #658 | } |
| #659 | } |
| #660 | |
| #661 | onMessage(event) { |
| #662 | try { |
| #663 | const data = JSON.parse(event.data); |
| #664 | console.log('Event:', data.type, data); |
| #665 | this.handleEvent(data); |
| #666 | } catch (e) { |
| #667 | console.error('Failed to parse message:', e); |
| #668 | } |
| #669 | } |
| #670 | |
| #671 | handleEvent(event) { |
| #672 | switch (event.type) { |
| #673 | case 'connected': |
| #674 | this.handleConnectedEvent(event.data); |
| #675 | break; |
| #676 | case 'user_message': |
| #677 | // Already added when sending |
| #678 | break; |
| #679 | case 'thinking': |
| #680 | this.handleThinkingEvent(event.data); |
| #681 | break; |
| #682 | case 'assistant_message': |
| #683 | this.handleAssistantMessage(event.data); |
| #684 | break; |
| #685 | case 'tool_call': |
| #686 | this.handleToolCall(event.data); |
| #687 | break; |
| #688 | case 'tool_result': |
| #689 | this.handleToolResult(event.data); |
| #690 | break; |
| #691 | case 'step_start': |
| #692 | this.handleStepStart(event.data); |
| #693 | break; |
| #694 | case 'step_complete': |
| #695 | this.handleStepComplete(event.data); |
| #696 | break; |
| #697 | case 'error': |
| #698 | this.handleError(event.data); |
| #699 | break; |
| #700 | case 'pong': |
| #701 | // Heartbeat response |
| #702 | break; |
| #703 | case 'birdeye_price_update': |
| #704 | this.handleBirdeyePriceUpdate(event.data); |
| #705 | break; |
| #706 | case 'birdeye_new_listing': |
| #707 | this.handleBirdeyeNewListing(event.data); |
| #708 | break; |
| #709 | case 'birdeye_large_trade': |
| #710 | this.handleBirdeyeLargeTrade(event.data); |
| #711 | break; |
| #712 | case 'birdeye_wallet_tx': |
| #713 | this.handleBirdeyeWalletTx(event.data); |
| #714 | break; |
| #715 | } |
| #716 | } |
| #717 | |
| #718 | handleConnectedEvent(data) { |
| #719 | if (data.wallet) { |
| #720 | const shortWallet = data.wallet.slice(0, 4) + '...' + data.wallet.slice(-4); |
| #721 | this.walletAddress.textContent = shortWallet; |
| #722 | this.walletAddress.title = data.wallet; |
| #723 | } |
| #724 | this.addActivity('🔗', 'Wallet Connected', `${data.tools} tools available`); |
| #725 | |
| #726 | // Fetch initial balance |
| #727 | this.fetchStatus(); |
| #728 | } |
| #729 | |
| #730 | handleThinkingEvent(data) { |
| #731 | this.removeTypingIndicator(); |
| #732 | const card = this.createEventCard('thinking', '🧠 Thinking', data.content); |
| #733 | this.messagesEl.appendChild(card); |
| #734 | this.scrollToBottom(); |
| #735 | |
| #736 | this.addActivity('🧠', 'Thinking', data.content.slice(0, 50) + '...'); |
| #737 | } |
| #738 | |
| #739 | handleAssistantMessage(data) { |
| #740 | this.removeTypingIndicator(); |
| #741 | if (data.content) { |
| #742 | const msg = document.createElement('div'); |
| #743 | msg.className = 'message assistant'; |
| #744 | msg.innerHTML = `<div class="message-content">${this.formatMessage(data.content)}</div>`; |
| #745 | this.messagesEl.appendChild(msg); |
| #746 | this.scrollToBottom(); |
| #747 | } |
| #748 | this.isProcessing = false; |
| #749 | this.sendBtn.disabled = false; |
| #750 | } |
| #751 | |
| #752 | handleToolCall(data) { |
| #753 | const argsStr = JSON.stringify(data.arguments, null, 2); |
| #754 | const card = this.createEventCard('tool-call', `🔧 ${data.name}`, argsStr); |
| #755 | this.messagesEl.appendChild(card); |
| #756 | this.scrollToBottom(); |
| #757 | |
| #758 | this.addActivity('🔧', data.name, Object.keys(data.arguments).join(', ')); |
| #759 | } |
| #760 | |
| #761 | handleToolResult(data) { |
| #762 | const content = data.success |
| #763 | ? (data.content ? data.content.slice(0, 500) : 'Success') |
| #764 | : `Error: ${data.error}`; |
| #765 | const cardType = data.success ? 'tool-result' : 'tool-error'; |
| #766 | const icon = data.success ? '✅' : '❌'; |
| #767 | |
| #768 | const card = this.createEventCard(cardType, `${icon} ${data.name} Result`, content); |
| #769 | this.messagesEl.appendChild(card); |
| #770 | this.scrollToBottom(); |
| #771 | |
| #772 | this.addActivity(icon, `${data.name}`, data.success ? 'Success' : data.error); |
| #773 | } |
| #774 | |
| #775 | handleStepStart(data) { |
| #776 | this.addTypingIndicator(); |
| #777 | this.addActivity('🔄', `Step ${data.step}/${data.max_steps}`, 'Processing...'); |
| #778 | } |
| #779 | |
| #780 | handleStepComplete(data) { |
| #781 | if (data.final) { |
| #782 | this.isProcessing = false; |
| #783 | this.sendBtn.disabled = false; |
| #784 | this.removeTypingIndicator(); |
| #785 | this.addActivity('✨', 'Complete', `Finished in ${data.step} steps`); |
| #786 | |
| #787 | // Refresh balance after trading |
| #788 | this.fetchStatus(); |
| #789 | } |
| #790 | } |
| #791 | |
| #792 | handleError(data) { |
| #793 | this.removeTypingIndicator(); |
| #794 | const msg = document.createElement('div'); |
| #795 | msg.className = 'message system'; |
| #796 | msg.innerHTML = `❌ Error: ${data.message}`; |
| #797 | this.messagesEl.appendChild(msg); |
| #798 | this.scrollToBottom(); |
| #799 | |
| #800 | this.isProcessing = false; |
| #801 | this.sendBtn.disabled = false; |
| #802 | |
| #803 | this.addActivity('❌', 'Error', data.message); |
| #804 | } |
| #805 | |
| #806 | handleBirdeyePriceUpdate(data) { |
| #807 | const token = data.address ? data.address.slice(0, 8) + '...' : 'Unknown'; |
| #808 | const price = data.price ? `$${data.price}` : 'N/A'; |
| #809 | const change = data.priceChange24h ? `${data.priceChange24h > 0 ? '+' : ''}${data.priceChange24h.toFixed(2)}%` : ''; |
| #810 | |
| #811 | this.addActivity('📊', 'Price Update', `${token} = ${price} ${change}`); |
| #812 | } |
| #813 | |
| #814 | handleBirdeyeNewListing(data) { |
| #815 | const name = data.name || 'Unknown Token'; |
| #816 | const symbol = data.symbol || 'N/A'; |
| #817 | const mint = data.address ? data.address.slice(0, 8) + '...' : ''; |
| #818 | |
| #819 | // Show notification |
| #820 | const notification = document.createElement('div'); |
| #821 | notification.className = 'message system'; |
| #822 | notification.style.background = 'linear-gradient(135deg, rgba(34, 197, 94, 0.1), rgba(6, 182, 212, 0.1))'; |
| #823 | notification.style.border = '1px solid var(--accent-green)'; |
| #824 | notification.innerHTML = ` |
| #825 | <strong>🆕 New Token Listing</strong><br> |
| #826 | ${name} (${symbol})<br> |
| #827 | <span style="font-size: 0.8em; color: var(--text-secondary);">${mint}</span> |
| #828 | `; |
| #829 | this.messagesEl.appendChild(notification); |
| #830 | this.scrollToBottom(); |
| #831 | |
| #832 | this.addActivity('🆕', 'New Listing', `${name} (${symbol})`); |
| #833 | } |
| #834 | |
| #835 | handleBirdeyeLargeTrade(data) { |
| #836 | const token = data.tokenSymbol || 'Unknown'; |
| #837 | const amount = data.amountUSD ? `$${data.amountUSD.toLocaleString()}` : 'N/A'; |
| #838 | const side = data.side || 'trade'; |
| #839 | const sideIcon = side === 'buy' ? '🟢' : '🔴'; |
| #840 | |
| #841 | // Show whale alert |
| #842 | const notification = document.createElement('div'); |
| #843 | notification.className = 'message system'; |
| #844 | notification.style.background = 'linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(239, 68, 68, 0.1))'; |
| #845 | notification.style.border = '1px solid var(--accent-purple)'; |
| #846 | notification.innerHTML = ` |
| #847 | <strong>🐋 Large Trade Alert</strong><br> |
| #848 | ${sideIcon} ${side.toUpperCase()} ${token} - ${amount} |
| #849 | `; |
| #850 | this.messagesEl.appendChild(notification); |
| #851 | this.scrollToBottom(); |
| #852 | |
| #853 | this.addActivity('🐋', 'Whale Trade', `${side.toUpperCase()} ${token} - ${amount}`); |
| #854 | } |
| #855 | |
| #856 | handleBirdeyeWalletTx(data) { |
| #857 | const txType = data.type || 'Transaction'; |
| #858 | const signature = data.signature ? data.signature.slice(0, 8) + '...' : ''; |
| #859 | |
| #860 | this.addActivity('💼', 'Wallet TX', `${txType} ${signature}`); |
| #861 | } |
| #862 | |
| #863 | createEventCard(type, title, content) { |
| #864 | const card = document.createElement('div'); |
| #865 | card.className = `event-card ${type}`; |
| #866 | card.innerHTML = ` |
| #867 | <div class="event-header"> |
| #868 | <span>${title}</span> |
| #869 | </div> |
| #870 | <div class="event-content"><pre>${this.escapeHtml(content)}</pre></div> |
| #871 | `; |
| #872 | return card; |
| #873 | } |
| #874 | |
| #875 | addTypingIndicator() { |
| #876 | if (!document.querySelector('.typing-indicator')) { |
| #877 | const indicator = document.createElement('div'); |
| #878 | indicator.className = 'typing-indicator'; |
| #879 | indicator.innerHTML = ` |
| #880 | <div class="typing-dot"></div> |
| #881 | <div class="typing-dot"></div> |
| #882 | <div class="typing-dot"></div> |
| #883 | `; |
| #884 | this.messagesEl.appendChild(indicator); |
| #885 | this.scrollToBottom(); |
| #886 | } |
| #887 | } |
| #888 | |
| #889 | removeTypingIndicator() { |
| #890 | const indicator = document.querySelector('.typing-indicator'); |
| #891 | if (indicator) indicator.remove(); |
| #892 | } |
| #893 | |
| #894 | addActivity(icon, title, desc) { |
| #895 | const item = document.createElement('div'); |
| #896 | item.className = 'activity-item'; |
| #897 | item.innerHTML = ` |
| #898 | <span class="activity-icon">${icon}</span> |
| #899 | <div class="activity-details"> |
| #900 | <div class="activity-title">${this.escapeHtml(title)}</div> |
| #901 | <div class="activity-desc">${this.escapeHtml(desc)}</div> |
| #902 | <div class="activity-time">${new Date().toLocaleTimeString()}</div> |
| #903 | </div> |
| #904 | `; |
| #905 | this.activityFeed.insertBefore(item, this.activityFeed.firstChild); |
| #906 | |
| #907 | // Keep only last 50 items |
| #908 | while (this.activityFeed.children.length > 50) { |
| #909 | this.activityFeed.removeChild(this.activityFeed.lastChild); |
| #910 | } |
| #911 | } |
| #912 | |
| #913 | sendMessage(text) { |
| #914 | if (!text.trim() || !this.isConnected || this.isProcessing) return; |
| #915 | |
| #916 | this.isProcessing = true; |
| #917 | this.sendBtn.disabled = true; |
| #918 | |
| #919 | // Add user message to UI |
| #920 | const msg = document.createElement('div'); |
| #921 | msg.className = 'message user'; |
| #922 | msg.innerHTML = `<div class="message-content">${this.escapeHtml(text)}</div>`; |
| #923 | this.messagesEl.appendChild(msg); |
| #924 | |
| #925 | this.addTypingIndicator(); |
| #926 | this.scrollToBottom(); |
| #927 | |
| #928 | // Send via WebSocket |
| #929 | this.ws.send(JSON.stringify({ |
| #930 | type: 'chat', |
| #931 | message: text |
| #932 | })); |
| #933 | |
| #934 | this.chatInput.value = ''; |
| #935 | this.autoResizeInput(); |
| #936 | |
| #937 | this.addActivity('💬', 'You', text.slice(0, 40) + (text.length > 40 ? '...' : '')); |
| #938 | } |
| #939 | |
| #940 | async fetchStatus() { |
| #941 | try { |
| #942 | const response = await fetch('/api/status'); |
| #943 | const data = await response.json(); |
| #944 | |
| #945 | if (data.balance !== null && data.balance !== undefined) { |
| #946 | this.balanceDisplay.textContent = data.balance.toFixed(4); |
| #947 | } |
| #948 | } catch (e) { |
| #949 | console.error('Failed to fetch status:', e); |
| #950 | } |
| #951 | } |
| #952 | |
| #953 | formatMessage(text) { |
| #954 | return this.escapeHtml(text).replace(/\n/g, '<br>'); |
| #955 | } |
| #956 | |
| #957 | escapeHtml(text) { |
| #958 | const div = document.createElement('div'); |
| #959 | div.textContent = text; |
| #960 | return div.innerHTML; |
| #961 | } |
| #962 | |
| #963 | scrollToBottom() { |
| #964 | setTimeout(() => { |
| #965 | this.messagesEl.scrollTop = this.messagesEl.scrollHeight; |
| #966 | }, 100); |
| #967 | } |
| #968 | |
| #969 | autoResizeInput() { |
| #970 | this.chatInput.style.height = 'auto'; |
| #971 | this.chatInput.style.height = Math.min(this.chatInput.scrollHeight, 150) + 'px'; |
| #972 | } |
| #973 | |
| #974 | setupEventListeners() { |
| #975 | // Send button |
| #976 | this.sendBtn.addEventListener('click', () => { |
| #977 | this.sendMessage(this.chatInput.value); |
| #978 | }); |
| #979 | |
| #980 | // Enter to send (Shift+Enter for newline) |
| #981 | this.chatInput.addEventListener('keydown', (e) => { |
| #982 | if (e.key === 'Enter' && !e.shiftKey) { |
| #983 | e.preventDefault(); |
| #984 | this.sendMessage(this.chatInput.value); |
| #985 | } |
| #986 | }); |
| #987 | |
| #988 | // Auto-resize input |
| #989 | this.chatInput.addEventListener('input', () => { |
| #990 | this.autoResizeInput(); |
| #991 | }); |
| #992 | |
| #993 | // Quick action buttons |
| #994 | document.querySelectorAll('.quick-btn').forEach(btn => { |
| #995 | btn.addEventListener('click', () => { |
| #996 | const cmd = btn.getAttribute('data-cmd'); |
| #997 | this.chatInput.value = cmd; |
| #998 | this.sendMessage(cmd); |
| #999 | }); |
| #1000 | }); |
| #1001 | |
| #1002 | // Swap button |
| #1003 | const swapBtn = document.getElementById('swapButton'); |
| #1004 | if (swapBtn) { |
| #1005 | swapBtn.addEventListener('click', () => { |
| #1006 | window.open('/swap', 'MAWD Swap', 'width=520,height=720,menubar=no,toolbar=no,location=no,status=no'); |
| #1007 | }); |
| #1008 | } |
| #1009 | |
| #1010 | // Heartbeat |
| #1011 | setInterval(() => { |
| #1012 | if (this.isConnected) { |
| #1013 | this.ws.send(JSON.stringify({ type: 'ping' })); |
| #1014 | } |
| #1015 | }, 30000); |
| #1016 | } |
| #1017 | } |
| #1018 | |
| #1019 | // Initialize client when page loads |
| #1020 | window.addEventListener('DOMContentLoaded', () => { |
| #1021 | new MAWDClient(); |
| #1022 | }); |
| #1023 | </script> |
| #1024 | </body> |
| #1025 | </html> |
| #1026 |