({
initialThreadHasMore,
initialThreadMessages,
initialThreadSettings,
initialThreadSummary,
threadId,
}: CodexThreadPaneProps)
| 640 | } |
| 641 | |
| 642 | export function CodexThreadPane({ |
| 643 | initialThreadHasMore, |
| 644 | initialThreadMessages, |
| 645 | initialThreadSettings, |
| 646 | initialThreadSummary, |
| 647 | threadId, |
| 648 | }: CodexThreadPaneProps) { |
| 649 | const router = useRouter(); |
| 650 | const { |
| 651 | isMobileViewport, |
| 652 | refreshThreads, |
| 653 | setPreferredLaunchModelSettings, |
| 654 | setPreferredLaunchPermissionsMode, |
| 655 | sidebarOpen, |
| 656 | threads, |
| 657 | toggleSidebar, |
| 658 | } = useCodexWorkspace(); |
| 659 | const [menuOpen, setMenuOpen] = useState(false); |
| 660 | const [permissionsMenuOpen, setPermissionsMenuOpen] = useState(false); |
| 661 | const [modelMenuOpen, setModelMenuOpen] = useState(false); |
| 662 | const [updatingMeta, setUpdatingMeta] = useState(false); |
| 663 | const [deletingThread, setDeletingThread] = useState(false); |
| 664 | const [updatingPermissions, setUpdatingPermissions] = useState(false); |
| 665 | const [updatingModelSettings, setUpdatingModelSettings] = useState(false); |
| 666 | const [loadingModelOptions, setLoadingModelOptions] = useState(false); |
| 667 | const [permissionsMode, setPermissionsMode] = useState<PermissionsMode>( |
| 668 | initialThreadSettings?.permissionsMode ?? 'default-permissions', |
| 669 | ); |
| 670 | const [modelOptions, setModelOptions] = useState<ThreadModelOptions | null>(null); |
| 671 | const [composerText, setComposerText] = useState(''); |
| 672 | const [sendingReply, setSendingReply] = useState(false); |
| 673 | const [replyError, setReplyError] = useState<string | null>(null); |
| 674 | const [threadMessages, setThreadMessages] = useState<WebCodexThreadMessage[]>(initialThreadMessages); |
| 675 | const [threadHasMore, setThreadHasMore] = useState(initialThreadHasMore); |
| 676 | const menuRef = useRef<HTMLDivElement | null>(null); |
| 677 | const permissionsMenuRef = useRef<HTMLDivElement | null>(null); |
| 678 | const modelMenuRef = useRef<HTMLDivElement | null>(null); |
| 679 | const composerInputRef = useRef<HTMLTextAreaElement | null>(null); |
| 680 | const replyStreamRef = useRef<EventSource | null>(null); |
| 681 | const autoStartedDraftThreadRef = useRef<string | null>(null); |
| 682 | const [pendingThreadSummary, setPendingThreadSummary] = useState<WebCodexThreadSummary | null>(() => ( |
| 683 | typeof window === 'undefined' ? null : getPendingCreatedThread(threadId) |
| 684 | )); |
| 685 | const activeThread = threads.find((entry) => entry.threadId === threadId) |
| 686 | ?? pendingThreadSummary |
| 687 | ?? initialThreadSummary |
| 688 | ?? null; |
| 689 | const threadDisplayTitle = activeThread?.title ?? '新聊天'; |
| 690 | const threadDisplayMeta = activeThread?.cwd |
| 691 | ? `${activeThread.cwd} · ${activeThread.updatedAtLabel}` |
| 692 | : '新的对话已准备就绪'; |
| 693 | const threadViewportVisible = !isMobileViewport || !sidebarOpen; |
| 694 | |
| 695 | function closeReplyStream() { |
| 696 | replyStreamRef.current?.close(); |
| 697 | replyStreamRef.current = null; |
| 698 | } |
| 699 |
nothing calls this directly
no test coverage detected