| ... | @@ -439,38 +439,46 @@ export default function ChatView() { | ... | @@ -439,38 +439,46 @@ export default function ChatView() { |
| 439 | </div> | 439 | </div> |
| 440 | | 440 | |
| 441 | {/* Input bar */} | 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 | <form | 443 | <form |
| 444 | onSubmit={onSend} | 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"> | 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 | 448 | {/* Textarea area */} |
| 449 | ref={textareaRef} | 449 | <div className="px-4 pt-4 pb-2"> |
| 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" | 450 | <textarea |
| 451 | rows={1} | 451 | ref={textareaRef} |
| 452 | value={prompt} | 452 | className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none" |
| 453 | onChange={(e) => setPrompt(e.target.value)} | 453 | rows={2} |
| 454 | onKeyDown={onKeyDown} | 454 | value={prompt} |
| 455 | placeholder={ | 455 | onChange={(e) => setPrompt(e.target.value)} |
| 456 | phase === "disconnected" | 456 | onKeyDown={onKeyDown} |
| 457 | ? "Type a message (session auto-connects)..." | 457 | placeholder={ |
| 458 | : "Type a message..." | 458 | phase === "disconnected" |
| 459 | } | 459 | ? "Ask for follow-up changes" |
| 460 | disabled={isSending || isConnecting} | 460 | : "Ask anything..." |
| 461 | /> | 461 | } |
| 462 | <div className="mt-2 flex items-center justify-between gap-2"> | 462 | disabled={isSending || isConnecting} |
| 463 | <div className="flex items-center gap-2"> | 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 | <div className="relative" ref={modelMenuRef}> | 470 | <div className="relative" ref={modelMenuRef}> |
| 465 | <button | 471 | <button |
| 466 | type="button" | 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 | onClick={() => setIsModelMenuOpen((open) => !open)} | 474 | onClick={() => setIsModelMenuOpen((open) => !open)} |
| 469 | > | 475 | > |
| 470 | <span className="max-w-[180px] truncate font-mono"> | 476 | <span className="max-w-[180px] truncate"> |
| 471 | {selectedModel} | 477 | {selectedModel} |
| 472 | </span> | 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 | </button> | 482 | </button> |
| 475 | {isModelMenuOpen && ( | 483 | {isModelMenuOpen && ( |
| 476 | <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"> | 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,14 +514,19 @@ export default function ChatView() { |
| 506 | </div> | 514 | </div> |
| 507 | )} | 515 | )} |
| 508 | </div> | 516 | </div> |
| | 517 | |
| | 518 | {/* Divider */} |
| | 519 | <div className="mx-0.5 h-4 w-px bg-white/[0.08]" /> |
| | 520 | |
| | 521 | {/* Reasoning effort */} |
| 509 | <label | 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 | htmlFor="reasoning-effort" | 524 | htmlFor="reasoning-effort" |
| 512 | > | 525 | > |
| 513 | <span>Reasoning</span> | 526 | <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span> |
| 514 | <select | 527 | <select |
| 515 | id="reasoning-effort" | 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 | value={selectedEffort} | 530 | value={selectedEffort} |
| 518 | onChange={(event) => setSelectedEffort(event.target.value)} | 531 | onChange={(event) => setSelectedEffort(event.target.value)} |
| 519 | > | 532 | > |
| ... | @@ -524,36 +537,51 @@ export default function ChatView() { | ... | @@ -524,36 +537,51 @@ export default function ChatView() { |
| 524 | </option> | 537 | </option> |
| 525 | ))} | 538 | ))} |
| 526 | </select> | 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 | </label> | 543 | </label> |
| 528 | </div> | 544 | </div> |
| 529 | {activeProject && ( | 545 | |
| 530 | <span className="text-[10px] text-[#a0a0a0]/35"> | 546 | {/* Right side: send / stop button */} |
| 531 | {activeProject.name} | 547 | <div className="flex items-center gap-2"> |
| 532 | </span> | 548 | {activeProject && ( |
| 533 | )} | 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 | </div> | 583 | </div> |
| 535 | </div> | 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 | </form> | 585 | </form> |
| 558 | </div> | 586 | </div> |
| 559 | </div> | 587 | </div> |