(rawText: string)
| 982 | } |
| 983 | |
| 984 | async function submitReplyText(rawText: string) { |
| 985 | if (sendingReply) { |
| 986 | return; |
| 987 | } |
| 988 | const text = rawText.trim(); |
| 989 | if (!text) { |
| 990 | return; |
| 991 | } |
| 992 | |
| 993 | const timestamp = new Date().toISOString(); |
| 994 | const userMessageId = createLocalMessageId('local-user'); |
| 995 | const assistantDraftId = createLocalMessageId('local-assistant'); |
| 996 | const optimisticUserMessage: WebCodexThreadMessage = { |
| 997 | id: userMessageId, |
| 998 | role: 'user', |
| 999 | source: 'local', |
| 1000 | text, |
| 1001 | timestamp, |
| 1002 | }; |
| 1003 | |
| 1004 | setThreadMessages((current) => [ |
| 1005 | ...current.filter((message) => message.source !== 'stream'), |
| 1006 | optimisticUserMessage, |
| 1007 | buildStreamAssistantMessage(assistantDraftId, timestamp, '', '', 'queued'), |
| 1008 | ]); |
| 1009 | setComposerText(''); |
| 1010 | setSendingReply(true); |
| 1011 | setReplyError(null); |
| 1012 | requestAnimationFrame(() => { |
| 1013 | composerInputRef.current?.focus(); |
| 1014 | }); |
| 1015 | |
| 1016 | try { |
| 1017 | const response = await fetch(`/api/codex-threads/${encodeURIComponent(threadId)}/reply`, { |
| 1018 | method: 'POST', |
| 1019 | headers: { 'Content-Type': 'application/json' }, |
| 1020 | body: JSON.stringify({ text }), |
| 1021 | }); |
| 1022 | const payload = await response.json().catch(() => null) as ThreadReplyResponse | null; |
| 1023 | if (!response.ok || !payload?.ok) { |
| 1024 | const errorMessage = (payload?.error && String(payload.error).trim()) || '发送失败'; |
| 1025 | replaceStreamFailureMessage(assistantDraftId, timestamp, errorMessage); |
| 1026 | setReplyError(errorMessage); |
| 1027 | setSendingReply(false); |
| 1028 | return; |
| 1029 | } |
| 1030 | const runId = typeof payload.runId === 'string' ? payload.runId.trim() : ''; |
| 1031 | if (!runId) { |
| 1032 | replaceStreamFailureMessage(assistantDraftId, timestamp, '回复启动失败。'); |
| 1033 | setReplyError('回复启动失败。'); |
| 1034 | setSendingReply(false); |
| 1035 | return; |
| 1036 | } |
| 1037 | startReplyStream(runId, assistantDraftId, timestamp); |
| 1038 | } catch (error) { |
| 1039 | const errorMessage = error instanceof Error ? error.message : '发送失败'; |
| 1040 | replaceStreamFailureMessage(assistantDraftId, timestamp, errorMessage); |
| 1041 | setReplyError(errorMessage); |
no test coverage detected