| ... | ... | @@ -439,38 +439,46 @@ export default function ChatView() { |
| 439 | 439 | </div> |
| 440 | 440 | |
| 441 | 441 | {/* Input bar */} |
| 442 | | <div className="border-t border-white/[0.08] px-5 py-3"> |
| 442 | <div className="px-5 pb-4 pt-2"> |
| 443 | 443 | <form |
| 444 | 444 | onSubmit={onSend} |
| 445 | | className="mx-auto flex max-w-3xl items-end gap-2" |
| 445 | className="mx-auto max-w-3xl" |
| 446 | 446 | > |
| 447 | | <div className="flex-1 rounded-2xl border border-white/[0.1] bg-[#121214] px-3 py-3"> |
| 448 | | <textarea |
| 449 | | ref={textareaRef} |
| 450 | | className="w-full resize-none bg-transparent px-1 pb-1 font-mono text-sm text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:outline-none" |
| 451 | | rows={1} |
| 452 | | value={prompt} |
| 453 | | onChange={(e) => setPrompt(e.target.value)} |
| 454 | | onKeyDown={onKeyDown} |
| 455 | | placeholder={ |
| 456 | | phase === "disconnected" |
| 457 | | ? "Type a message (session auto-connects)..." |
| 458 | | : "Type a message..." |
| 459 | | } |
| 460 | | disabled={isSending || isConnecting} |
| 461 | | /> |
| 462 | | <div className="mt-2 flex items-center justify-between gap-2"> |
| 463 | | <div className="flex items-center gap-2"> |
| 447 | <div className="group rounded-[20px] border border-white/[0.08] bg-[#141416] transition-colors duration-200 focus-within:border-white/[0.16]"> |
| 448 | {/* Textarea area */} |
| 449 | <div className="px-4 pt-4 pb-2"> |
| 450 | <textarea |
| 451 | ref={textareaRef} |
| 452 | className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none" |
| 453 | rows={2} |
| 454 | value={prompt} |
| 455 | onChange={(e) => setPrompt(e.target.value)} |
| 456 | onKeyDown={onKeyDown} |
| 457 | placeholder={ |
| 458 | phase === "disconnected" |
| 459 | ? "Ask for follow-up changes" |
| 460 | : "Ask anything..." |
| 461 | } |
| 462 | disabled={isSending || isConnecting} |
| 463 | /> |
| 464 | </div> |
| 465 | |
| 466 | {/* Bottom toolbar */} |
| 467 | <div className="flex items-center justify-between px-3 pb-3"> |
| 468 | <div className="flex items-center gap-1"> |
| 469 | {/* Model picker */} |
| 464 | 470 | <div className="relative" ref={modelMenuRef}> |
| 465 | 471 | <button |
| 466 | 472 | type="button" |
| 467 | | className="inline-flex items-center gap-2 rounded-full border border-white/[0.1] bg-white/[0.04] px-3 py-1.5 text-xs text-[#d8d8d8] transition-colors duration-150 hover:bg-white/[0.08]" |
| 473 | className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]" |
| 468 | 474 | onClick={() => setIsModelMenuOpen((open) => !open)} |
| 469 | 475 | > |
| 470 | | <span className="max-w-[180px] truncate font-mono"> |
| 476 | <span className="max-w-[180px] truncate"> |
| 471 | 477 | {selectedModel} |
| 472 | 478 | </span> |
| 473 | | <span className="text-[10px] text-[#a0a0a0]/70">▼</span> |
| 479 | <svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="opacity-50"> |
| 480 | <path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" /> |
| 481 | </svg> |
| 474 | 482 | </button> |
| 475 | 483 | {isModelMenuOpen && ( |
| 476 | 484 | <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/[0.1] bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur"> |
| ... | ... | @@ -506,14 +514,19 @@ export default function ChatView() { |
| 506 | 514 | </div> |
| 507 | 515 | )} |
| 508 | 516 | </div> |
| 517 | |
| 518 | {/* Divider */} |
| 519 | <div className="mx-0.5 h-4 w-px bg-white/[0.08]" /> |
| 520 | |
| 521 | {/* Reasoning effort */} |
| 509 | 522 | <label |
| 510 | | className="inline-flex items-center gap-2 rounded-full border border-white/[0.1] bg-white/[0.04] px-3 py-1.5 text-xs text-[#d8d8d8]" |
| 523 | className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]" |
| 511 | 524 | htmlFor="reasoning-effort" |
| 512 | 525 | > |
| 513 | | <span>Reasoning</span> |
| 526 | <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span> |
| 514 | 527 | <select |
| 515 | 528 | id="reasoning-effort" |
| 516 | | className="bg-transparent font-mono text-xs text-[#d8d8d8] outline-none" |
| 529 | className="absolute opacity-0 w-0 h-0" |
| 517 | 530 | value={selectedEffort} |
| 518 | 531 | onChange={(event) => setSelectedEffort(event.target.value)} |
| 519 | 532 | > |
| ... | ... | @@ -524,36 +537,51 @@ export default function ChatView() { |
| 524 | 537 | </option> |
| 525 | 538 | ))} |
| 526 | 539 | </select> |
| 540 | <svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="opacity-50"> |
| 541 | <path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" /> |
| 542 | </svg> |
| 527 | 543 | </label> |
| 528 | 544 | </div> |
| 529 | | {activeProject && ( |
| 530 | | <span className="text-[10px] text-[#a0a0a0]/35"> |
| 531 | | {activeProject.name} |
| 532 | | </span> |
| 533 | | )} |
| 545 | |
| 546 | {/* Right side: send / stop button */} |
| 547 | <div className="flex items-center gap-2"> |
| 548 | {activeProject && ( |
| 549 | <span className="text-[11px] text-[#a0a0a0]/25"> |
| 550 | {activeProject.name} |
| 551 | </span> |
| 552 | )} |
| 553 | {phase === "running" ? ( |
| 554 | <button |
| 555 | type="button" |
| 556 | className="flex h-8 w-8 items-center justify-center rounded-full bg-rose-500/90 text-white transition-all duration-150 hover:bg-rose-500 hover:scale-105" |
| 557 | onClick={() => void onInterrupt()} |
| 558 | aria-label="Stop generation" |
| 559 | > |
| 560 | <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"> |
| 561 | <rect x="2" y="2" width="8" height="8" rx="1.5" /> |
| 562 | </svg> |
| 563 | </button> |
| 564 | ) : ( |
| 565 | <button |
| 566 | type="submit" |
| 567 | className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-[#0c0c0c] transition-all duration-150 hover:bg-white hover:scale-105 disabled:opacity-30 disabled:hover:scale-100" |
| 568 | disabled={isSending || isConnecting || !prompt.trim()} |
| 569 | aria-label={isConnecting ? "Connecting" : isSending ? "Sending" : "Send message"} |
| 570 | > |
| 571 | {isConnecting || isSending ? ( |
| 572 | <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="animate-spin"> |
| 573 | <circle cx="7" cy="7" r="5.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeDasharray="20 12" /> |
| 574 | </svg> |
| 575 | ) : ( |
| 576 | <svg width="14" height="14" viewBox="0 0 14 14" fill="none"> |
| 577 | <path d="M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /> |
| 578 | </svg> |
| 579 | )} |
| 580 | </button> |
| 581 | )} |
| 582 | </div> |
| 534 | 583 | </div> |
| 535 | 584 | </div> |
| 536 | | {phase === "running" ? ( |
| 537 | | <button |
| 538 | | type="button" |
| 539 | | className="shrink-0 rounded-xl bg-rose-600/80 px-4 py-3 text-xs font-medium text-white transition-colors duration-150 hover:bg-rose-600" |
| 540 | | onClick={() => void onInterrupt()} |
| 541 | | > |
| 542 | | Stop |
| 543 | | </button> |
| 544 | | ) : ( |
| 545 | | <button |
| 546 | | type="submit" |
| 547 | | className="shrink-0 rounded-xl bg-white px-4 py-3 text-xs font-medium text-[#0c0c0c] transition-colors duration-150 hover:bg-white/90 disabled:opacity-40" |
| 548 | | disabled={isSending || isConnecting || !prompt.trim()} |
| 549 | | > |
| 550 | | {isConnecting |
| 551 | | ? "Connecting..." |
| 552 | | : isSending |
| 553 | | ? "Sending..." |
| 554 | | : "Send"} |
| 555 | | </button> |
| 556 | | )} |
| 557 | 585 | </form> |
| 558 | 586 | </div> |
| 559 | 587 | </div> |