|
35 | 35 | // Waiting loader state |
36 | 36 | let requestPending = false; |
37 | 37 | let waitingEl = null; |
| 38 | + let currentAbortController = null; |
| 39 | + |
| 40 | + // Abort current request helper |
| 41 | + function abortCurrentRequest(){ |
| 42 | + if(currentAbortController){ |
| 43 | + currentAbortController.abort(); |
| 44 | + currentAbortController = null; |
| 45 | + } |
| 46 | + requestPending = false; |
| 47 | + hideWaiting(); |
| 48 | + } |
38 | 49 |
|
39 | 50 | // LocalStorage helpers |
40 | 51 | function loadConfigs(){ |
|
664 | 675 |
|
665 | 676 | // 切换厂商 |
666 | 677 | function setActiveVendor(vendor){ |
| 678 | + abortCurrentRequest(); |
667 | 679 | currentVendor = vendor; |
668 | 680 | $$('#vendorType .seg-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.vendor === vendor)); |
669 | 681 | renderTestButtons(vendor); |
|
672 | 684 |
|
673 | 685 | // 切换测试场景 |
674 | 686 | function setActiveScenario(scenario){ |
| 687 | + abortCurrentRequest(); |
675 | 688 | $$('#testType .seg-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.scenario === scenario)); |
676 | 689 | // 查找对应的默认输入 |
677 | 690 | const tests = vendorTests[currentVendor] || []; |
|
702 | 715 |
|
703 | 716 | // Test function call flow (multiple scenarios) |
704 | 717 | testBtn.addEventListener('click', async () => { |
| 718 | + abortCurrentRequest(); |
705 | 719 | const apiUrl = apiUrlEl.value.trim(); |
706 | 720 | const apiKey = apiKeyEl.value.trim(); |
707 | 721 | const model = (modelEl.value || SYSTEM_DEFAULTS.model).trim(); |
|
711 | 725 | // 发起新请求前自动清空历史记录 |
712 | 726 | clearResults(); |
713 | 727 |
|
| 728 | + currentAbortController = new AbortController(); |
| 729 | + const signal = currentAbortController.signal; |
| 730 | + |
714 | 731 | const scenario = testTypeWrap.querySelector('.seg-btn.active')?.dataset.scenario || 'openai_tools'; |
715 | 732 | const endpoint = buildEndpoint(apiUrl); |
716 | 733 | const geminiEndpoint = buildGeminiEndpoint(apiUrl, model, apiKey); |
|
744 | 761 | addMessage('user', '消息 #1', requestBody1.messages[0]); |
745 | 762 |
|
746 | 763 | const t1Start = Date.now(); |
747 | | - const r1 = await fetchAndParse(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(requestBody1) }); |
| 764 | + const r1 = await fetchAndParse(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(requestBody1), signal }); |
748 | 765 | const data1 = ensureJsonOrThrow(r1); |
749 | 766 | const t1Duration = Date.now() - t1Start; |
750 | 767 | addBlock('响应 #1', data1, t1Duration); |
|
776 | 793 | const requestBody2 = { model, messages: [ requestBody1.messages[0], assistantMsg, toolMessage ] }; |
777 | 794 | const t2Start = Date.now(); |
778 | 795 | addBlock('请求 #2', requestBody2); |
779 | | - const r2 = await fetchAndParse(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(requestBody2) }); |
| 796 | + const r2 = await fetchAndParse(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(requestBody2), signal }); |
780 | 797 | const data2 = ensureJsonOrThrow(r2); |
781 | 798 | const t2Duration = Date.now() - t2Start; |
782 | 799 | addBlock('响应 #2', data2, t2Duration); |
|
803 | 820 | const aR1 = await fetchAndParse(anthropicEndpoint, { |
804 | 821 | method: 'POST', |
805 | 822 | headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, |
806 | | - body: JSON.stringify(aReq1) |
| 823 | + body: JSON.stringify(aReq1), |
| 824 | + signal |
807 | 825 | }); |
808 | 826 | const aData1 = ensureJsonOrThrow(aR1); |
809 | 827 | const aT1Duration = Date.now() - aT1Start; |
|
841 | 859 | const aR2 = await fetchAndParse(anthropicEndpoint, { |
842 | 860 | method: 'POST', |
843 | 861 | headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, |
844 | | - body: JSON.stringify(aReq2) |
| 862 | + body: JSON.stringify(aReq2), |
| 863 | + signal |
845 | 864 | }); |
846 | 865 | const aData2 = ensureJsonOrThrow(aR2); |
847 | 866 | const aT2Duration = Date.now() - aT2Start; |
|
865 | 884 | addBlock('请求 #1', gReq1); |
866 | 885 | addMessage('user', '消息 #1', { role: 'user', parts: [{ text: userText }] }); |
867 | 886 | const gT1Start = Date.now(); |
868 | | - const gR1 = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq1) }); |
| 887 | + const gR1 = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq1), signal }); |
869 | 888 | const gData1 = ensureJsonOrThrow(gR1); |
870 | 889 | const gT1Duration = Date.now() - gT1Start; |
871 | 890 | addBlock('响应 #1', gData1, gT1Duration); |
|
902 | 921 | }; |
903 | 922 | const gT2Start = Date.now(); |
904 | 923 | addBlock('请求 #2', gReq2); |
905 | | - const gR2 = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq2) }); |
| 924 | + const gR2 = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq2), signal }); |
906 | 925 | const gData2 = ensureJsonOrThrow(gR2); |
907 | 926 | const gT2Duration = Date.now() - gT2Start; |
908 | 927 | addBlock('响应 #2', gData2, gT2Duration); |
|
934 | 953 | const rtR1 = await fetchAndParse(responsesEndpoint, { |
935 | 954 | method: 'POST', |
936 | 955 | headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, |
937 | | - body: JSON.stringify(rtReq1) |
| 956 | + body: JSON.stringify(rtReq1), |
| 957 | + signal |
938 | 958 | }); |
939 | 959 | const rtData1 = ensureJsonOrThrow(rtR1); |
940 | 960 | const rtT1Duration = Date.now() - rtT1Start; |
|
982 | 1002 | const rtR2 = await fetchAndParse(responsesEndpoint, { |
983 | 1003 | method: 'POST', |
984 | 1004 | headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, |
985 | | - body: JSON.stringify(rtReq2) |
| 1005 | + body: JSON.stringify(rtReq2), |
| 1006 | + signal |
986 | 1007 | }); |
987 | 1008 | const rtData2 = ensureJsonOrThrow(rtR2); |
988 | 1009 | const rtT2Duration = Date.now() - rtT2Start; |
|
1012 | 1033 | const rR = await fetchAndParse(responsesEndpoint, { |
1013 | 1034 | method: 'POST', |
1014 | 1035 | headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, |
1015 | | - body: JSON.stringify(rReq) |
| 1036 | + body: JSON.stringify(rReq), |
| 1037 | + signal |
1016 | 1038 | }); |
1017 | 1039 | const rData = ensureJsonOrThrow(rR); |
1018 | 1040 | const rTDuration = Date.now() - rTStart; |
|
1050 | 1072 | addBlock('请求 #1', gReq); |
1051 | 1073 | addMessage('user', '消息', gReq.contents[0]); |
1052 | 1074 | const gsTStart = Date.now(); |
1053 | | - const gR = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq) }); |
| 1075 | + const gR = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq), signal }); |
1054 | 1076 | const gData = ensureJsonOrThrow(gR); |
1055 | 1077 | const gsTDuration = Date.now() - gsTStart; |
1056 | 1078 | addBlock('响应 #1', gData, gsTDuration); |
|
1073 | 1095 | addBlock('请求 #1', gReq); |
1074 | 1096 | addMessage('user', '消息', gReq.contents[0]); |
1075 | 1097 | const guTStart = Date.now(); |
1076 | | - const gR = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq) }); |
| 1098 | + const gR = await fetchAndParse(geminiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gReq), signal }); |
1077 | 1099 | const gData = ensureJsonOrThrow(gR); |
1078 | 1100 | const guTDuration = Date.now() - guTStart; |
1079 | 1101 | addBlock('响应 #1', gData, guTDuration); |
|
1090 | 1112 | } |
1091 | 1113 |
|
1092 | 1114 | }catch(err){ |
| 1115 | + if(err.name === 'AbortError'){ |
| 1116 | + console.log('Request aborted by user'); |
| 1117 | + return; |
| 1118 | + } |
1093 | 1119 | console.error(err); |
1094 | 1120 | // 清空顶部简要错误,改为在时间线内展示红色错误块 |
1095 | 1121 | errorMessage.textContent = ''; |
|
0 commit comments