// ==UserScript==
// @name ChatGPT 助手
// @author Hmjz100
// @namespace github.com/hmjz100
// @version 1.0.6.1
// @description 《也许同类型中最好用?》系列 - 支持 模型切换器、降智检测、不发送浏览指纹(防降智)以及添加一个可拖动的语音按钮(点击会打开原始 LiveKit Meet)到镜像站。大屏小屏都能用!
// @icon data:image/webp;base64,UklGRnICAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSI8AAAABd6A2kg06/T5EcqGIzkREILWfPrI5RTitte1pXprvniGwKHzbgMMgjMAkHAaB9HhUevJ9U+T/6T6i/wzctm0kde/27hVA1LJgHQAoiCX2Hvw3S62UnAc0rkOEtyHir7C+QPMbSg48lz0vKU1fl0ZUNyHRH8Pf4Tt0/Wb4Ow5/53QI3XzKKRVk8v/IxeB/BgBWUDggvAEAABALAJ0BKiAAIAA+aSqQRaQioZv6rABABoS0gAnAHJxnoP3rMm/kBkKFPL/yvok/1/878zXyp/g/cC/jv9A/x35fdjN7Hf7ANdIJ5yn1lcylRWD9msH+g4lCVG3DwlB27AAA/v/b7x/k//Og4rBakFx39Od09+fJ1x4C+DLOrn57IHc1Tex5U/5cB8l2f3fZBiD+5/7/IowL/9h145ee+E8Tn9Mx//ya2jMrsF0MWumdxn7zRJ7f7BEicBZVfKYEwPy/e9nUZPOkR9QyW1vCwpKLT4HeJVe7ayZ4dcQRfzMR0TeGbvw8U5I+jHciOCqPs43iwvLybOhARP2A/54Jx8B2XKJ3NgHNzVi3W2HkTpisnFaSV2NcPAhURRCHCp9wAwR12+PxPlPateeyuKpswYl1DEzNt/SkXzDPkSq/LyuOSsHuqlcmY8ch6wMlzG7JlrkvSIL3kuB6BS6sByZZXCNqJoa/4vqVhG8NdvpsjyX6hpz5DqxYMDrvqnl1//oedvi9KU2f/8I6YzFjiLYx5bH31D5eTMZBT8kqCpoNVnHwfQz5KqJRnU9pk4JVJRwRO0gt0GsehqDyZAAAAA==
// @license MIT
// @match *://chatgpt.com/*
// @match *://chat.openai.com/*
// @match *://*.oaifree.com/*
// @match *://chat.rawchat.top/*
// @match *://chat.rawchat.cc/*
// @match *://chat.sharedchat.cn/*
// @match *://chat.sharedchat.fun/*
// @match *://chat.chatgptplus.cn/*
// @match *://free.share-ai.top/*
// @match *://go.github.cn.com/*
// @match *://gpt.github.cn.com/*
// @match *://share.github.cn.com/*
// @match *://free.xyhelper.cn/*
// @match *://*.xyhelper.com.cn/*
// @match *://chat.freegpts.org/*
// @match *://go.gptdsb.com/*
// @match *://chat.gptdsb.com/*
// @match *://www.opkfc.com/*
// @match *://chatgpt.dairoot.cn/*
// @match *://web.tu-zi.com/*
// @match *://share.tu-zi.com/*
// @match *://chatgpt.aicnm.cc/*
// @match *://ai.ohsus.me/*
// @match *://share.aivvm.org/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_openInTab
// @grant unsafeWindow
// @run-at document-start
// @require https://unpkg.com/vue@3.4.27/dist/vue.global.prod.js
// @require https://unpkg.com/jquery@3.6.3/dist/jquery.min.js
// ==/UserScript==
(function () {
'use strict';
let interval = setInterval(() => {
if (typeof unsafeWindow.createNoticePopup !== 'function') return;
unsafeWindow.createNoticePopup = function () { };
clearInterval(interval);
}, 1);
let originalRequset = null;
const originalFetch = window.fetch;
unsafeWindow.fetch = async function (url, options) {
url = new URL(url, location.href).href
if (url.includes('sentinel/chat-requirements') && options.method === 'POST') {
let response = await originalFetch(url, options);
let res = await response.clone().text();
try {
res = JSON.parse(res);
} catch (e) {
throw e
}
console.log('Pow 数据: \n', res);
originalRequset = { url, options }
const difficulty = res?.proofofwork?.difficulty ? res?.proofofwork?.difficulty : 'N/A';
updatePow(difficulty);
} else if (url.includes("/backend-api/models") && options?.method === "GET") {
let response = await originalFetch(url, options);
let res = await response.clone().text();
try {
res = JSON.parse(res);
} catch (e) { }
console.log('模型数据: \n', res);
let final = res.models
.map(model => {
const extraModel = extraModels.find(extra => extra.slug === model.slug);
const extraCategories = res.categories && Array.isArray(res.categories)
? res.categories.find(categorie => categorie.default_model === model.slug)
: null;
// 如果不匹配,那就加个默认图标和子标题
model.icon = iconModels["connected"]
model.subTitle = model.title.replace("GPT-", "")
if (extraCategories) {
if (extraCategories.human_category_short_name) model.subTitle = extraCategories.human_category_short_name;
if (extraCategories.short_explainer) model.description = extraCategories.short_explainer;
}
if (extraModel) {
if (extraModel.title) model.title = extraModel.title;
if (extraModel.description) model.description = extraModel.description;
if (extraModel.icon) model.icon = extraModel.icon;
if (extraModel.subTitle) model.subTitle = extraModel.subTitle;
if (extraModel.beta) model.beta = extraModel.beta;
if (extraModel.disabled) model.disabled = extraModel.disabled;
}
if (extraCategories) {
if (extraCategories.icon_filled_src) model.icon.icon_filled_src = extraCategories.icon_filled_src;
if (extraCategories.icon_outline_src) model.icon.icon_outline_src = extraCategories.icon_outline_src;
if (extraCategories.is_beta) model.beta = extraCategories.is_beta;
}
return model;
})
.sort((a, b) => {
// 根据 extraModels 的顺序对匹配的项进行排序
const indexA = extraModels.findIndex(extra => extra.slug === a.slug);
const indexB = extraModels.findIndex(extra => extra.slug === b.slug);
// 如果都匹配 extraModels 中的项,则按 extraModels 顺序排序
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
}
// 如果只有一个匹配,已匹配项排在前面,未匹配项排后面
if (indexA !== -1) return -1; // a 在 b 前
if (indexB !== -1) return 1; // b 在 a 前
return 0; // 如果都没有匹配,不改变顺序
});
models.update(final);
return response
} else if (state.isEnabled && url.endsWith("/backend-api/conversation") && options?.method === "POST") {
let res = { body: JSON.parse(options.body) };
console.log('消息数据: \n', res.body);
res.body.model = state.selectedModelSlug;
options = { ...options, body: JSON.stringify(res.body) };
} else if (url.includes("/backend-api/me") && options?.method === "GET") {
const response = await originalFetch(url, options);
let res = await response.clone().text();
try {
res = JSON.parse(res);
} catch (e) { }
console.log('用户数据: \n', res);
res.name = "语言如歌 - Language is song";
res.picture = "https://cdn.auth0.com/avatars/la.png";
res.email = "LanguageIsSong@100010001.com"
res.phone_number = "100010001"
return new Response(JSON.stringify(res), {
status: response.status,
statusText: response.statusText,
headers: response.headers
})
} else if (url.includes("/backend-api/model_icons") && options?.method === "GET") {
const response = await originalFetch(url, options);
let res = await response.clone().text();
try {
res = JSON.parse(res);
} catch (e) { }
console.log('图标数据: \n', res);
extraModels.forEach((item) => {
if (item.iconSlug && item.icon) {
if (!res.hasOwnProperty(item.iconSlug)) {
res[item.iconSlug] = { ...item.icon };
}
}
})
return new Response(JSON.stringify(res), {
status: response.status,
statusText: response.statusText,
headers: response.headers
})
} else if (url.includes('/ces/v1/') || url.includes('/v1/rgstr') || url.includes('backend-api/lat/r') || url.includes('browser-intake-datadog')) {
// 禁止分析用户浏览数据,以达到“无指纹”的效果。
throw new Error('【ChatGPT 助手】unAnalytics\n已屏蔽数据此数据收集器')
}
let response = await originalFetch(url, options);
return response;
};
// 隐藏原来的按钮
waitForKeyElements('div:not(#ChatGPTAssistant, #ChatGPTVoice-Button, #ChatGPTPow-Button, #immersive-translate-popup) svg.icon[width="25"][height="25"], div#voiceButton svg, #of-custom-floating-ball svg, div > div#livekit', function (element) {
element.parent().hide();
});
waitForKeyElements('div.flex.w-full.gap-2.items-center.justify-center', function (element) {
let text = element.text()
if (text.trim().includes('实时语音')) {
element.parent().hide();
}
})
// 定义不同服务器的配置
let servers = {
"new.oaifree.com": {
apiPath: "/api/voice/link",
apiType: "POST",
url: "wss://webrtc.oaifree.com",
model: new URL(location.href).searchParams.get('model'),
mode: [['标准语音', '高级语音'], ['std', 'adv']],
getToken: data => new URL(data.url).searchParams.get('token'),
getHash: data => new URL(data.url).hash
},
"chat.rawchat.top": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.rawchat.cc": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.sharedchat.cn": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.sharedchat.fun": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.chatgptplus.cn": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"free.share-ai.top": {
apiPath: "/frontend-api/getVoice",
apiType: "GET",
url: data => (data.data.voiceServerUrl || data.data.url),
getToken: data => data.data.token,
getHash: data => data.data.e2ee_key
},
"go.github.cn.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"gpt.github.cn.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"share.github.cn.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"free.xyhelper.cn": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"free.xyhelper.com.cn": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.freegpts.org": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"go.gptdsb.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chat.gptdsb.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"www.opkfc.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"chatgpt.dairoot.cn": {
apiPath: "/api/livekit",
apiType: "GET",
url: data => new URL(data.data).searchParams.get('liveKitUrl'),
getToken: data => new URL(data.data).searchParams.get('token'),
getHash: data => new URL(data.data).hash
},
"web.tu-zi.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
"share.tu-zi.com": {
apiPath: "/backend-api/voice_token",
apiType: "GET",
url: data => data.url,
getToken: data => data.token,
getHash: data => data.e2ee_key
},
};
let html = $(`<div id="ChatGPTAssistant">
<div id="ChatGPTVoice-Button" style="z-index: 114515; top: calc(30% - 34px);">
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#fff" d="M9.5 4C8.67157 4 8 4.67157 8 5.5V18.5C8 19.3284 8.67157 20 9.5 20C10.3284 20 11 19.3284 11 18.5V5.5C11 4.67157 10.3284 4 9.5 4Z"/>
<path fill="#fff" d="M13 8.5C13 7.67157 13.6716 7 14.5 7C15.3284 7 16 7.67157 16 8.5V15.5C16 16.3284 15.3284 17 14.5 17C13.6716 17 13 16.3284 13 15.5V8.5Z"/>
<path fill="#fff" d="M4.5 9C3.67157 9 3 9.67157 3 10.5V13.5C3 14.3284 3.67157 15 4.5 15C5.32843 15 6 14.3284 6 13.5V10.5C6 9.67157 5.32843 9 4.5 9Z"/>
<path fill="#fff" d="M19.5 9C18.6716 9 18 9.67157 18 10.5V13.5C18 14.3284 18.6716 15 19.5 15C20.3284 15 21 14.3284 21 13.5V10.5C21 9.67157 20.3284 9 19.5 9Z"/>
</svg>
<span>语音</span>
</div>
<div id="ChatGPTPow-Button" style="z-index: 114514; top: calc(80% - 34px); background: linear-gradient(140.91deg, #2ecc71 12.61%, #3498db 76.89%);" title="等待站点请求中...">
<svg width="25" height="25" class="icon" fill="none" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" style="transition: all 0.3s ease;">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3498db;stop-opacity:1"></stop>
<stop offset="100%" style="stop-color:#2ecc71;stop-opacity:1"></stop>
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="coloredBlur"></feGaussianBlur>
<feMerge>
<feMergeNode in="coloredBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="icon-group" filter="url(#glow)">
<circle cx="32" cy="32" r="28" fill="url(#gradient)" stroke="#fff" stroke-width="2"></circle>
<circle cx="32" cy="32" r="20" fill="none" stroke="#fff" stroke-width="2" stroke-dasharray="100">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 32 32" to="360 32 32" dur="8s" repeatCount="indefinite"></animateTransform>
</circle>
<circle cx="32" cy="32" r="12" fill="none" stroke="#fff" stroke-width="2">
<animate attributeName="r" values="12;14;12" dur="2s" repeatCount="indefinite"></animate>
</circle>
<circle id="center-dot" cx="32" cy="32" r="4" fill="#fff">
<animate attributeName="r" values="4;6;4" dur="2s" repeatCount="indefinite"></animate>
</circle>
</g>
</svg>
<span>状态</span>
</div>
<style id="ChatGPTAssistant-Style">
div#ChatGPTVoice-Button, div#ChatGPTPow-Button {
border-top-left-radius: 34px;
border-bottom-left-radius: 34px;
background: linear-gradient(140.91deg, #7367F0 12.61%, #574AB8 76.89%);
height: 34px;
width: 80px;
margin: 1px;
display: flex !important;
align-items: center;
position: fixed;
right: -35px;
cursor: pointer;
padding-left: 7px;
opacity: 0.75;
transition: right 0.3s, opacity 0.3s !important;
}
div#ChatGPTVoice-Button:hover,
div#ChatGPTVoice-Button.is-dragging,
div#ChatGPTPow-Button:hover,
div#ChatGPTPow-Button.is-dragging {
right: -5px;
opacity: 1;
}
div#ChatGPTVoice-Button span,
div#ChatGPTPow-Button span {
color:#ffffff;
font-size:15px;
margin-left:3px;
white-space: nowrap;
}
</style>
</div>`)
let button = html.find('#ChatGPTVoice-Button');
let buttonPow = html.find('#ChatGPTPow-Button');
let isDragging = false;
let offsetY = 0;
let dragStartTime;
button.css('z-index', 114514 + 1)
// 从 GM 获取按钮位置
if (GM_getValue('buttonTop')) {
button.css('top', GM_getValue('buttonTop') + 'px');
}
// 点击事件处理
button.on('click touchend', handleVoiceClick);
buttonPow.on('click touchend', getPow);
// 鼠标按下事件
button.on('mousedown touchstart', function (e) {
e.preventDefault();
dragStartTime = Date.now(); // 记录拖动开始时间
offsetY = e.clientY - button.offset().top;
});
// 鼠标移动事件
$(document).on('mousemove touchmove', function (e) {
if (offsetY !== undefined) {
let newTop = e.clientY - offsetY;
const buttonHeight = button.outerHeight();
const windowHeight = $(window).height();
// 限制按钮位置
if (newTop < 0) newTop = 0;
if (newTop + buttonHeight > windowHeight) newTop = windowHeight - buttonHeight;
// 判断是否拖动
if (isDragging || (Date.now() - dragStartTime > 100)) { // 如果已经拖动或拖动时间超过100ms
isDragging = true;
button.addClass('is-dragging');
button.css('top', newTop + 'px');
GM_setValue('buttonTop', newTop);
}
}
});
// 鼠标抬起事件
$(document).on('mouseup touchend', function () {
if (isDragging) {
setTimeout(function () {
isDragging = false;
button.removeClass('is-dragging');
}, 100)
}
offsetY = undefined; // 重置 offsetY
});
setInterval(function () {
if (!$('#ChatGPTAssistant').length) {
$('#ChatGPTAssistant-Style, #ChatGPTVoice-Button, #ChatGPTPow-Button').remove()
let host = location.hostname;
let config = servers[host];
if (!config) button.remove()
$('body').append(html);
}
}, 1000)
// 绑定点击事件到新创建的按钮
async function handleVoiceClick(event) {
if (!event?.currentTarget || isDragging) return;
let element = $(event.currentTarget);
if (element.attr('data-clicked') === 'true') return;
element.attr('data-clicked', 'true');
// 异步获取语音链接
await goVoice(element).catch(function (error) {
alert('获取语音对话(会议)链接错误: \n' + error.message);
console.error(error);
element.removeAttr('data-clicked');
});
};
async function goVoice(element) {
// 获取当前服务器的域名
let host = location.hostname;
// 获取服务器配置
let config = servers[host];
let res, data, url, token, hash;
if (!config) {
throw new Error(`未支持当前站点: ${host}`);
} else {
let extra = {
method: config.apiType,
headers: { 'Content-Type': 'application/json' }
}
if (config.model !== undefined && config.mode !== undefined && config.apiType === 'POST') {
let model = config.model;
let mode = config.mode;
let modeChoice;
if (mode && mode.length) {
let modeOptions = mode[0]
.map((name, index) => `(${index + 1}) ${name}`)
.join(" ");
let userChoice = prompt(`请选择语音模式: (不输入则使用${mode[0][0]})\n${modeOptions}`);
let choiceIndex = parseInt(userChoice) - 1;
if (choiceIndex >= 0 && choiceIndex < mode[1].length) {
modeChoice = mode[1][choiceIndex];
} else if (userChoice === null) {
return element.removeAttr('data-clicked');
} else {
modeChoice = mode[1][0];
}
}
if (!model) {
let userInput = prompt("请输入模型名称: (不输入则使用默认模型)");
if (userInput === null) {
return element.removeAttr('data-clicked');
}
model = userInput;
}
extra.body = JSON.stringify({ model, mode: modeChoice });
}
// 发送请求到语音API
res = await fetch(config.apiPath, extra);
// 解析返回的JSON数据
data = await res.json();
console.log('服务数据: \n', data);
// 检查是否有url或者token,否则抛出错误
function hasUrl(obj) {
if (obj && typeof obj === 'object') {
if ('url' in obj) return true; // 如果当前对象包含 url 属性,返回 true
if ('data' in obj) return true; // 如果当前对象包含 url 属性,返回 true
return Object.values(obj).some(hasUrl); // 递归检查嵌套的对象
}
return false;
}
if (data && !hasUrl(data)) {
throw new Error(data.detail || '语音服务未返回所需数据');
}
// 获取url、token、hash
url = typeof config.url === 'function' ? config.url(data) : config.url;
token = config.getToken ? config.getToken(data) : null;
hash = config.getHash ? config.getHash(data) : null;
}
// 打印日志方便调试
console.log('会议数据: \n', { token, hash, url });
// 检查是否有url或者token,否则抛出错误
if (!url || !token || !hash) throw new Error(data.detail || '语音服务未返回数据');
// 构建 meetUrl
let meetUrl = new URL('https://meet.livekit.io/custom');
if (url) meetUrl.searchParams.set('liveKitUrl', url);
if (token) meetUrl.searchParams.set('token', token);
if (hash) meetUrl.hash = hash;
// 打开新页面
GM_openInTab(meetUrl.href, { active: true, insert: true, setParent: true })
element.removeAttr('data-clicked');
}
async function getPow() {
if (!originalRequset) return;
const { url, options } = originalRequset;
try {
const response = await fetch(url, options);
let res = await response.clone().text();
try {
res = JSON.parse(res);
} catch (e) { }
console.log('Pow 数据: \n', res);
const difficulty = res?.proofofwork?.difficulty ? res?.proofofwork?.difficulty : 'N/A';
updatePow(difficulty);
} catch (error) {
console.error('重新请求失败:', error);
}
}
// 更新难易度指示器
function updatePow(difficulty) {
const result = difficulty === 'N/A' ? {
color: '#888', secondaryColor: '#666', textColor: '#888', level: '未知',
detail: '服务器未提供 PoW 难度值,可能是因为当前站点不是镜像站点\n(即使网站的 UI 界面与官方相似)'
} : (() => {
const hexValue = difficulty.replace('0x', '').replace(/^0+/, '');
const decimalValue = parseInt(hexValue, 16);
const percentage = (100 - Math.min(decimalValue / 0x0FFFFF * 100, 100)).toFixed(2);
const levels = [
{ hexLength: 2, color: '#F44336', secondaryColor: '#d32f2f', level: '困难', detailSuffix: '会员模型及功能可能无法正常使用' },
{ hexLength: 3, color: '#FFC107', secondaryColor: '#ffa000', level: '中等', detailSuffix: '可能影响部分高级功能' },
{ hexLength: 4, color: '#8BC34A', secondaryColor: '#689f38', level: '简单', detailSuffix: '可正常使用 ChatGPT' },
{ hexLength: Infinity, color: '#4CAF50', secondaryColor: '#388e3c', level: '极易', detailSuffix: '可舒适地与 ChatGPT 聊天' }
];
const { color, secondaryColor, level, detailSuffix } = levels.find(({ hexLength }) => hexValue.length <= hexLength);
return { color, secondaryColor, level, detail: `PoW:${difficulty} (${percentage}%)\n${detailSuffix}\n点击刷新状态。` };
})();
// 更新 UI
$('#ChatGPTPow-Button').each(function () {
const $button = $(this);
const $svgStop1 = $button.find('svg defs linearGradient stop[offset="0%"]');
const $svgStop2 = $button.find('svg defs linearGradient stop[offset="100%"]');
$svgStop1.css('stop-color', result.color);
$svgStop2.css('stop-color', result.secondaryColor);
$button.find('span').text(result.level);
$button.attr('title', result.detail);
$button[0].style.background = `linear-gradient(140.91deg, ${result.color} 12.61%, ${result.secondaryColor} 76.89%)`;
});
}
function waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector) {
function findInShadowRoots(root, selector) {
let elements = $(root).find(selector).toArray();
$(root).find('*').each(function () {
let shadowRoot = this.shadowRoot;
if (shadowRoot) {
elements = elements.concat(findInShadowRoots(shadowRoot, selector));
}
});
return elements;
}
var targetElements;
if (iframeSelector) {
targetElements = $(iframeSelector).contents();
} else {
targetElements = $(document);
}
let allElements = findInShadowRoots(targetElements, selectorTxt);
if (allElements.length > 0) {
allElements.forEach(function (element) {
var jThis = $(element);
var uniqueIdentifier = 'alreadyFound';
var alreadyFound = jThis.data(uniqueIdentifier) || false;
if (!alreadyFound) {
var cancelFound = actionFunction(jThis);
if (cancelFound) {
return false;
} else {
jThis.data(uniqueIdentifier, true);
}
}
});
}
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace(/[^\w]/g, "_");
var timeControl = controlObj[controlKey];
if (allElements.length > 0 && bWaitOnce && timeControl) {
clearInterval(timeControl);
delete controlObj[controlKey];
} else {
if (!timeControl) {
timeControl = setInterval(function () {
waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
}, 1000);
controlObj[controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
// ChatGPT 模型切换器
let iconModels = {
"bolt": {
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M12.566 2.11c1.003-1.188 2.93-.252 2.615 1.271L14.227 8h5.697c1.276 0 1.97 1.492 1.146 2.467L11.434 21.89c-1.003 1.19-2.93.253-2.615-1.27L9.772 16H4.076c-1.276 0-1.97-1.492-1.147-2.467L12.565 2.11Z\" clip-rule=\"evenodd\"/></svg>",
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M13.091 4.246 4.682 14H11a1 1 0 0 1 .973 1.23l-1.064 4.524L19.318 10H13a1 1 0 0 1-.973-1.229l1.064-4.525Zm-.848-2.08c1.195-1.386 3.448-.238 3.029 1.544L14.262 8h5.056c1.711 0 2.632 2.01 1.514 3.306l-9.075 10.528c-1.195 1.386-3.448.238-3.029-1.544L9.738 16H4.681c-1.711 0-2.632-2.01-1.514-3.306l9.075-10.527Z\"/></svg>",
},
"connected": {
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M12 7.42a22.323 22.323 0 0 0-2.453 2.127A22.323 22.323 0 0 0 7.42 12a22.32 22.32 0 0 0 2.127 2.453c.807.808 1.636 1.52 2.453 2.128a22.335 22.335 0 0 0 2.453-2.128A22.322 22.322 0 0 0 16.58 12a22.326 22.326 0 0 0-2.127-2.453A22.32 22.32 0 0 0 12 7.42Zm1.751-1.154a24.715 24.715 0 0 1 2.104 1.88 24.722 24.722 0 0 1 1.88 2.103c.316-.55.576-1.085.779-1.59.35-.878.507-1.625.503-2.206-.003-.574-.16-.913-.358-1.111-.199-.199-.537-.356-1.112-.36-.58-.003-1.328.153-2.205.504-.506.203-1.04.464-1.59.78Zm3.983 7.485a24.706 24.706 0 0 1-1.88 2.104 24.727 24.727 0 0 1-2.103 1.88 12.7 12.7 0 0 0 1.59.779c.878.35 1.625.507 2.206.503.574-.003.913-.16 1.111-.358.199-.199.356-.538.36-1.112.003-.58-.154-1.328-.504-2.205a12.688 12.688 0 0 0-.78-1.59ZM12 18.99c.89.57 1.768 1.03 2.605 1.364 1.026.41 2.036.652 2.955.646.925-.006 1.828-.267 2.5-.94.673-.672.934-1.575.94-2.5.006-.919-.236-1.929-.646-2.954A15.688 15.688 0 0 0 18.99 12c.57-.89 1.03-1.768 1.364-2.606.41-1.025.652-2.035.646-2.954-.006-.925-.267-1.828-.94-2.5-.672-.673-1.575-.934-2.5-.94-.919-.006-1.929.235-2.954.646-.838.335-1.716.795-2.606 1.364a15.69 15.69 0 0 0-2.606-1.364C8.37 3.236 7.36 2.994 6.44 3c-.925.006-1.828.267-2.5.94-.673.672-.934 1.575-.94 2.5-.006.919.235 1.929.646 2.955A15.69 15.69 0 0 0 5.01 12c-.57.89-1.03 1.768-1.364 2.605-.41 1.026-.652 2.036-.646 2.955.006.925.267 1.828.94 2.5.672.673 1.575.934 2.5.94.92.006 1.93-.235 2.955-.646A15.697 15.697 0 0 0 12 18.99Zm-1.751-1.255a24.714 24.714 0 0 1-2.104-1.88 24.713 24.713 0 0 1-1.88-2.104c-.315.55-.576 1.085-.779 1.59-.35.878-.507 1.625-.503 2.206.003.574.16.913.359 1.111.198.199.537.356 1.111.36.58.003 1.328-.153 2.205-.504.506-.203 1.04-.463 1.59-.78Zm-3.983-7.486a24.727 24.727 0 0 1 1.88-2.104 24.724 24.724 0 0 1 2.103-1.88 12.696 12.696 0 0 0-1.59-.779c-.878-.35-1.625-.507-2.206-.503-.574.003-.913.16-1.111.359-.199.198-.356.537-.36 1.111-.003.58.153 1.328.504 2.205.203.506.464 1.04.78 1.59Z\" clip-rule=\"evenodd\"/></svg>",
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M12 7.42a22.323 22.323 0 0 0-2.453 2.127A22.323 22.323 0 0 0 7.42 12a22.32 22.32 0 0 0 2.127 2.453c.807.808 1.636 1.52 2.453 2.128a22.335 22.335 0 0 0 2.453-2.128A22.322 22.322 0 0 0 16.58 12a22.326 22.326 0 0 0-2.127-2.453A22.32 22.32 0 0 0 12 7.42Zm1.751-1.154a24.715 24.715 0 0 1 2.104 1.88 24.722 24.722 0 0 1 1.88 2.103c.316-.55.576-1.085.779-1.59.35-.878.507-1.625.503-2.206-.003-.574-.16-.913-.358-1.111-.199-.199-.537-.356-1.112-.36-.58-.003-1.328.153-2.205.504-.506.203-1.04.464-1.59.78Zm3.983 7.485a24.706 24.706 0 0 1-1.88 2.104 24.727 24.727 0 0 1-2.103 1.88 12.7 12.7 0 0 0 1.59.779c.878.35 1.625.507 2.206.503.574-.003.913-.16 1.111-.358.199-.199.356-.538.36-1.112.003-.58-.154-1.328-.504-2.205a12.688 12.688 0 0 0-.78-1.59ZM12 18.99c.89.57 1.768 1.03 2.605 1.364 1.026.41 2.036.652 2.955.646.925-.006 1.828-.267 2.5-.94.673-.672.934-1.575.94-2.5.006-.919-.236-1.929-.646-2.954A15.688 15.688 0 0 0 18.99 12c.57-.89 1.03-1.768 1.364-2.606.41-1.025.652-2.035.646-2.954-.006-.925-.267-1.828-.94-2.5-.672-.673-1.575-.934-2.5-.94-.919-.006-1.929.235-2.954.646-.838.335-1.716.795-2.606 1.364a15.69 15.69 0 0 0-2.606-1.364C8.37 3.236 7.36 2.994 6.44 3c-.925.006-1.828.267-2.5.94-.673.672-.934 1.575-.94 2.5-.006.919.235 1.929.646 2.955A15.69 15.69 0 0 0 5.01 12c-.57.89-1.03 1.768-1.364 2.605-.41 1.026-.652 2.036-.646 2.955.006.925.267 1.828.94 2.5.672.673 1.575.934 2.5.94.92.006 1.93-.235 2.955-.646A15.697 15.697 0 0 0 12 18.99Zm-1.751-1.255a24.714 24.714 0 0 1-2.104-1.88 24.713 24.713 0 0 1-1.88-2.104c-.315.55-.576 1.085-.779 1.59-.35.878-.507 1.625-.503 2.206.003.574.16.913.359 1.111.198.199.537.356 1.111.36.58.003 1.328-.153 2.205-.504.506-.203 1.04-.463 1.59-.78Zm-3.983-7.486a24.727 24.727 0 0 1 1.88-2.104 24.724 24.724 0 0 1 2.103-1.88 12.696 12.696 0 0 0-1.59-.779c-.878-.35-1.625-.507-2.206-.503-.574.003-.913.16-1.111.359-.199.198-.356.537-.36 1.111-.003.58.153 1.328.504 2.205.203.506.464 1.04.78 1.59Z\" clip-rule=\"evenodd\"/></svg>",
},
"star": {
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12.001 1.75c.496 0 .913.373.969.866.306 2.705 1.126 4.66 2.44 6 1.31 1.333 3.223 2.17 5.95 2.412a.976.976 0 0 1-.002 1.945c-2.682.232-4.637 1.067-5.977 2.408-1.34 1.34-2.176 3.295-2.408 5.977a.976.976 0 0 1-1.945.002c-.243-2.727-1.08-4.64-2.412-5.95-1.34-1.314-3.295-2.134-6-2.44a.976.976 0 0 1-.002-1.94c2.75-.317 4.665-1.137 5.972-2.444 1.307-1.307 2.127-3.221 2.444-5.972a.976.976 0 0 1 .971-.864Z\"/></svg>",
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12.001 1.5a1 1 0 0 1 .993.887c.313 2.77 1.153 4.775 2.5 6.146 1.34 1.366 3.3 2.223 6.095 2.47a1 1 0 0 1-.003 1.993c-2.747.238-4.75 1.094-6.123 2.467-1.373 1.374-2.229 3.376-2.467 6.123a1 1 0 0 1-1.992.003c-.248-2.795-1.105-4.754-2.47-6.095-1.372-1.347-3.376-2.187-6.147-2.5a1 1 0 0 1-.002-1.987c2.818-.325 4.779-1.165 6.118-2.504 1.339-1.34 2.179-3.3 2.504-6.118A1 1 0 0 1 12 1.5ZM6.725 11.998c1.234.503 2.309 1.184 3.21 2.069.877.861 1.56 1.888 2.063 3.076.5-1.187 1.18-2.223 2.051-3.094.871-.87 1.907-1.55 3.094-2.05-1.188-.503-2.215-1.187-3.076-2.064-.885-.901-1.566-1.976-2.069-3.21-.505 1.235-1.19 2.3-2.081 3.192-.891.89-1.957 1.576-3.192 2.082Z\"/></svg>",
},
"stars": {
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M19.92.897a.447.447 0 0 0-.89-.001c-.12 1.051-.433 1.773-.922 2.262-.49.49-1.21.801-2.262.923a.447.447 0 0 0 0 .888c1.035.117 1.772.43 2.274.922.499.49.817 1.21.91 2.251a.447.447 0 0 0 .89 0c.09-1.024.407-1.76.91-2.263.502-.502 1.238-.82 2.261-.908a.447.447 0 0 0 .001-.891c-1.04-.093-1.76-.411-2.25-.91-.493-.502-.806-1.24-.923-2.273ZM11.993 3.82a1.15 1.15 0 0 0-2.285-.002c-.312 2.704-1.115 4.559-2.373 5.817-1.258 1.258-3.113 2.06-5.817 2.373a1.15 1.15 0 0 0 .003 2.285c2.658.3 4.555 1.104 5.845 2.37 1.283 1.26 2.1 3.112 2.338 5.789a1.15 1.15 0 0 0 2.292-.003c.227-2.631 1.045-4.525 2.336-5.817 1.292-1.291 3.186-2.109 5.817-2.336a1.15 1.15 0 0 0 .003-2.291c-2.677-.238-4.529-1.056-5.789-2.34-1.266-1.29-2.07-3.186-2.37-5.844Z\"/></svg>",
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M19.898.855a.4.4 0 0 0-.795 0c-.123 1.064-.44 1.802-.943 2.305-.503.503-1.241.82-2.306.943a.4.4 0 0 0 .001.794c1.047.119 1.801.436 2.317.942.512.504.836 1.241.93 2.296a.4.4 0 0 0 .796 0c.09-1.038.413-1.792.93-2.308.515-.516 1.269-.839 2.306-.928a.4.4 0 0 0 .001-.797c-1.055-.094-1.792-.418-2.296-.93-.506-.516-.823-1.27-.941-2.317Z\"/><path fill=\"currentColor\" d=\"M12.001 1.5a1 1 0 0 1 .993.887c.313 2.77 1.153 4.775 2.5 6.146 1.34 1.366 3.3 2.223 6.095 2.47a1 1 0 0 1-.003 1.993c-2.747.238-4.75 1.094-6.123 2.467-1.373 1.374-2.229 3.376-2.467 6.123a1 1 0 0 1-1.992.003c-.248-2.795-1.105-4.754-2.47-6.095-1.372-1.347-3.376-2.187-6.147-2.5a1 1 0 0 1-.002-1.987c2.818-.325 4.779-1.165 6.118-2.504 1.339-1.34 2.179-3.3 2.504-6.118A1 1 0 0 1 12 1.5ZM6.725 11.998c1.234.503 2.309 1.184 3.21 2.069.877.861 1.56 1.888 2.063 3.076.5-1.187 1.18-2.223 2.051-3.094.871-.87 1.907-1.55 3.094-2.05-1.188-.503-2.215-1.187-3.076-2.064-.885-.901-1.566-1.976-2.069-3.21-.505 1.235-1.19 2.3-2.081 3.192-.891.89-1.957 1.576-3.192 2.082Z\"/></svg>",
},
"reasoning_mini": {
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M5.625 11.542C3.264 12 1.75 12.707 1.75 13.5 1.75 14.88 6.34 16 12 16s10.25-1.12 10.25-2.5c0-.793-1.514-1.5-3.875-1.958-3.913-.759-4.477-5.638-5.162-8.814A1.24 1.24 0 0 0 12 1.75a1.24 1.24 0 0 0-1.213.978c-.685 3.176-1.25 8.055-5.162 8.814ZM14.386 17.935a43.306 43.306 0 0 1-4.772 0c.464.986.835 2.09 1.154 3.341A1.28 1.28 0 0 0 12 22.25a1.28 1.28 0 0 0 1.232-.974c.319-1.25.69-2.355 1.154-3.34Z\"/></svg>",
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M14.445 17.935a45.497 45.497 0 0 1-4.89 0c.475.986.856 2.09 1.183 3.341a1.306 1.306 0 0 0 2.524 0c.327-1.25.708-2.355 1.183-3.34ZM4.784 13c.17.06.357.121.564.182 1.613.472 3.965.793 6.652.793 2.687 0 5.039-.321 6.651-.793.208-.06.396-.122.565-.182-.43-.153-.965-.305-1.603-.443-1.532-.329-3.573-1.15-4.548-3.166-.445-.92-.783-1.914-1.065-2.93-.282 1.016-.62 2.01-1.065 2.93-.975 2.016-3.016 2.837-4.548 3.166-.638.138-1.173.29-1.603.443Zm1.157-2.42c1.294-.278 2.551-.883 3.117-2.053.769-1.59 1.187-3.482 1.647-5.567l.051-.232A1.264 1.264 0 0 1 12 1.75c.599 0 1.117.406 1.244.978l.051.232c.46 2.085.878 3.978 1.647 5.567.566 1.17 1.823 1.775 3.117 2.053 2.541.546 4.191 1.427 4.191 2.42 0 1.657-4.59 3-10.25 3S1.75 14.657 1.75 13c0-.993 1.65-1.874 4.19-2.42Z\"/></svg>",
},
"reasoning": {
"icon_filled_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M22.25 4.5a2.75 2.75 0 1 1-5.5 0 2.75 2.75 0 0 1 5.5 0ZM7.615 11.064c-.55.66-1.384.968-2.226 1.131-2.217.43-3.639 1.094-3.639 1.838 0 1.297 4.31 2.348 9.625 2.348 5.316 0 9.625-1.05 9.625-2.348 0-.744-1.422-1.408-3.64-1.838-.841-.163-1.676-.472-2.225-1.13-1.46-1.754-1.977-4.16-2.573-6.928l-.048-.219A1.164 1.164 0 0 0 11.375 3c-.549 0-1.024.382-1.14.918l-.046.219c-.597 2.768-1.115 5.174-2.574 6.927ZM13.615 18.198a40.664 40.664 0 0 1-4.48 0c.435.925.784 1.963 1.084 3.138.135.531.607.914 1.156.914.549 0 1.021-.383 1.156-.914.3-1.175.65-2.212 1.084-3.138Z\"/></svg>",
"icon_outline_src": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M4.924 13.113c.163.06.344.12.544.18 1.554.466 3.82.783 6.41.783 2.59 0 4.856-.317 6.41-.783.2-.06.381-.12.544-.18-.415-.152-.93-.302-1.545-.438-1.476-.324-3.443-1.136-4.382-3.127-.43-.908-.755-1.89-1.027-2.895-.272 1.004-.598 1.987-1.026 2.895-.94 1.99-2.907 2.803-4.383 3.127-.616.136-1.13.286-1.545.438Zm15.018-.576s-.004.007-.018.019c.01-.013.018-.019.018-.019Zm-16.11.019c-.014-.012-.018-.019-.018-.019l.018.019Zm2.207-1.834c1.247-.274 2.459-.872 3.004-2.027.74-1.57 1.143-3.44 1.587-5.5l.05-.23A1.225 1.225 0 0 1 11.878 2c.577 0 1.077.401 1.199.966l.05.23c.443 2.06.845 3.929 1.586 5.499.545 1.155 1.757 1.753 3.004 2.027 2.45.54 4.04 1.41 4.04 2.39 0 1.637-4.424 2.964-9.879 2.964C6.423 16.076 2 14.75 2 13.113c0-.981 1.59-1.851 4.039-2.39ZM14.235 17.988a42.76 42.76 0 0 1-4.713 0c.457.973.825 2.065 1.14 3.3.142.56.639.962 1.216.962s1.074-.403 1.217-.962c.315-1.235.682-2.327 1.14-3.3ZM19.5 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm0 2a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z\"/></svg>",
}
}
let extraModels = [
{
"title": "GPT-4",
"subTitle": "4",
"description": "传统模型,浏览、高级数据分析和 DALL·E 现已集成至内部。",
"slug": "gpt-4",
"iconSlug": "gpt_4",
"tags": ["unofficial", "gpt4"],
"icon": iconModels["star"],
},
{
"title": "GPT-4o mini",
"subTitle": "4o mini",
"description": "精简模型,适合处理日常任务。",
"slug": "gpt-4o-mini",
"iconSlug": "gpt_3.5",
"tags": ["unofficial", "gpt4o", "mini"],
"icon": iconModels["bolt"],
},
{
"title": "GPT-4o",
"subTitle": "4o",
"description": "最新、最高级的模型,适用于大多数任务。",
"slug": "gpt-4o",
"iconSlug": "AG8PqS2q",
"tags": ["unofficial", "gpt4o"],
"icon": iconModels["stars"],
},
{
"beta": true,
"title": "GPT-4o 与 Canvas",
"subTitle": "4o canvas",
"description": "专为协助创作和编码。",
"slug": "gpt-4o-canmore",
"iconSlug": "9fdGgEgJ",
"tags": ["unofficial", "gpt4o", "canvas"],
"icon": iconModels["stars"],
},
{
"title": "o1-mini",
"subTitle": "o1 mini",
"description": "优化后的高级模型,推理速度更快。",
"slug": "o1-mini",
"iconSlug": "o1_mini",
"tags": ["unofficial", "o1", "mini"],
"icon": iconModels["reasoning"],
},
{
"title": "o1",
"subTitle": "o1",
"description": "我们最强大的模型,非常适用于需要创造力和高级推理能力的任务。",
"slug": "o1",
"iconSlug": "o1",
"tags": ["unofficial", "o1"],
"icon": iconModels["reasoning"],
},
{
"title": "o1 pro mode",
"subTitle": "o1 pro",
"description": "更擅长推理的模型,在逻辑与复杂问题上能够更进一步。",
"slug": "o1-pro",
"iconSlug": "o1_pro",
"tags": ["unofficial", "o1", "pro"],
"icon": iconModels["reasoning"],
},
{
"title": "自动",
"subTitle": "自动",
"description": "自动选择合适的模型来满足我的请求。",
"slug": "auto",
"iconSlug": "auto",
"tags": ["unofficial", "auto", "gpt4o", "o1", "mini", "canvas", "pro"],
"icon": iconModels["connected"],
},
{
"disabled": true,
"title": "GPT-3.5",
"subTitle": "3.5",
"description": "我们最快的模型,非常适合多数日常任务。(已弃用)",
"slug": "text-davinci-002-render-sha",
"iconSlug": "gpt_3.5",
"tags": ["unofficial", "gpt3.5"],
"icon": iconModels["bolt"],
},
];
const models = Vue.reactive({
// 保底
all: [...extraModels],
update(newModels) {
this.all = [
...newModels,
...extraModels
]
}
});
function useControllable(controlledValue, onChange, defaultValue) {
let internalValue = Vue.ref(defaultValue == null ? void 0 : defaultValue.value);
let isControlled = Vue.computed(() => controlledValue.value !== void 0);
return [
Vue.computed(() => isControlled.value ? controlledValue.value : internalValue.value),
function (value) {
if (isControlled.value) {
return onChange == null ? void 0 : onChange(value);
} else {
internalValue.value = value;
return onChange == null ? void 0 : onChange(value);
}
}
];
}
let id = 0;
function generateId() {
return ++id;
}
function useId() {
return generateId();
}
function dom(ref2) {
var _a;
if (ref2 == null)
return null;
if (ref2.value == null)
return null;
let el = (_a = ref2.value.$el) != null ? _a : ref2.value;
if (el instanceof Node) {
return el;
}
return null;
}
function match(value, lookup, ...args) {
if (value in lookup) {
let returnValue = lookup[value];
return typeof returnValue === "function" ? returnValue(...args) : returnValue;
}
let error = new Error(
`Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys(
lookup
).map((key) => `"${key}"`).join(", ")}.`
);
if (Error.captureStackTrace)
Error.captureStackTrace(error, match);
throw error;
}
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
class Env {
constructor() {
__publicField(this, "current", this.detect());
__publicField(this, "currentId", 0);
}
set(env2) {
if (this.current === env2)
return;
this.currentId = 0;
this.current = env2;
}
reset() {
this.set(this.detect());
}
nextId() {
return ++this.currentId;
}
get isServer() {
return this.current === "server";
}
get isClient() {
return this.current === "client";
}
detect() {
if (typeof window === "undefined" || typeof document === "undefined") {
return "server";
}
return "client";
}
}
let env = new Env();
function getOwnerDocument(element) {
if (env.isServer)
return null;
if (element instanceof Node)
return element.ownerDocument;
if (element == null ? void 0 : element.hasOwnProperty("value")) {
let domElement = dom(element);
if (domElement)
return domElement.ownerDocument;
}
return document;
}
let focusableSelector = [
"[contentEditable=true]",
"[tabindex]",
"a[href]",
"area[href]",
"button:not([disabled])",
"iframe",
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])"
].map(
(selector) => `${selector}:not([tabindex='-1'])`
).join(",");
var Focus = ((Focus2) => {
Focus2[Focus2["First"] = 1] = "First";
Focus2[Focus2["Previous"] = 2] = "Previous";
Focus2[Focus2["Next"] = 4] = "Next";
Focus2[Focus2["Last"] = 8] = "Last";
Focus2[Focus2["WrapAround"] = 16] = "WrapAround";
Focus2[Focus2["NoScroll"] = 32] = "NoScroll";
return Focus2;
})(Focus || {});
var FocusResult = ((FocusResult2) => {
FocusResult2[FocusResult2["Error"] = 0] = "Error";
FocusResult2[FocusResult2["Overflow"] = 1] = "Overflow";
FocusResult2[FocusResult2["Success"] = 2] = "Success";
FocusResult2[FocusResult2["Underflow"] = 3] = "Underflow";
return FocusResult2;
})(FocusResult || {});
function getFocusableElements(container = document.body) {
if (container == null)
return [];
return Array.from(container.querySelectorAll(focusableSelector)).sort(
// We want to move `:tabindex="0"` to the end of the list, this is what the browser does as well.
(a, z) => Math.sign((a.tabIndex || Number.MAX_SAFE_INTEGER) - (z.tabIndex || Number.MAX_SAFE_INTEGER))
);
}
var FocusableMode = ((FocusableMode2) => {
FocusableMode2[FocusableMode2["Strict"] = 0] = "Strict";
FocusableMode2[FocusableMode2["Loose"] = 1] = "Loose";
return FocusableMode2;
})(FocusableMode || {});
function isFocusableElement(element, mode = 0) {
var _a;
if (element === ((_a = getOwnerDocument(element)) == null ? void 0 : _a.body))
return false;
return match(mode, {
[
0
/* Strict */
]() {
return element.matches(focusableSelector);
},
[
1
/* Loose */
]() {
let next = element;
while (next !== null) {
if (next.matches(focusableSelector))
return true;
next = next.parentElement;
}
return false;
}
});
}
if (typeof window !== "undefined" && typeof document !== "undefined") {
document.addEventListener(
"keydown",
(event) => {
if (event.metaKey || event.altKey || event.ctrlKey) {
return;
}
document.documentElement.dataset.headlessuiFocusVisible = "";
},
true
);
document.addEventListener(
"click",
(event) => {
if (event.detail === 1) {
delete document.documentElement.dataset.headlessuiFocusVisible;
} else if (event.detail === 0) {
document.documentElement.dataset.headlessuiFocusVisible = "";
}
},
true
);
}
let selectableSelector = ["textarea", "input"].join(",");
function isSelectableElement(element) {
var _a, _b;
return (_b = (_a = element == null ? void 0 : element.matches) == null ? void 0 : _a.call(element, selectableSelector)) != null ? _b : false;
}
function sortByDomNode(nodes, resolveKey = (i) => i) {
return nodes.slice().sort((aItem, zItem) => {
let a = resolveKey(aItem);
let z = resolveKey(zItem);
if (a === null || z === null)
return 0;
let position = a.compareDocumentPosition(z);
if (position & Node.DOCUMENT_POSITION_FOLLOWING)
return -1;
if (position & Node.DOCUMENT_POSITION_PRECEDING)
return 1;
return 0;
});
}
function focusIn(container, focus, {
sorted = true,
relativeTo = null,
skipElements = []
} = {}) {
var _a;
let ownerDocument = (_a = Array.isArray(container) ? container.length > 0 ? container[0].ownerDocument : document : container == null ? void 0 : container.ownerDocument) != null ? _a : document;
let elements = Array.isArray(container) ? sorted ? sortByDomNode(container) : container : getFocusableElements(container);
if (skipElements.length > 0 && elements.length > 1) {
elements = elements.filter((x) => !skipElements.includes(x));
}
relativeTo = relativeTo != null ? relativeTo : ownerDocument.activeElement;
let direction = (() => {
if (focus & (1 | 4))
return 1;
if (focus & (2 | 8))
return -1;
throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last");
})();
let startIndex = (() => {
if (focus & 1)
return 0;
if (focus & 2)
return Math.max(0, elements.indexOf(relativeTo)) - 1;
if (focus & 4)
return Math.max(0, elements.indexOf(relativeTo)) + 1;
if (focus & 8)
return elements.length - 1;
throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last");
})();
let focusOptions = focus & 32 ? { preventScroll: true } : {};
let offset = 0;
let total = elements.length;
let next = void 0;
do {
if (offset >= total || offset + total <= 0)
return 0;
let nextIdx = startIndex + offset;
if (focus & 16) {
nextIdx = (nextIdx + total) % total;
} else {
if (nextIdx < 0)
return 3;
if (nextIdx >= total)
return 1;
}
next = elements[nextIdx];
next == null ? void 0 : next.focus(focusOptions);
offset += direction;
} while (next !== ownerDocument.activeElement);
if (focus & (4 | 2) && isSelectableElement(next)) {
next.select();
}
return 2;
}
function isIOS() {
return (
// Check if it is an iPhone
/iPhone/gi.test(window.navigator.platform) || // Check if it is an iPad. iPad reports itself as "MacIntel", but we can check if it is a touch
// screen. Let's hope that Apple doesn't release a touch screen Mac (or maybe this would then
// work as expected 🤔).
/Mac/gi.test(window.navigator.platform) && window.navigator.maxTouchPoints > 0
);
}
function useDocumentEvent(type, listener, options) {
if (env.isServer)
return;
Vue.watchEffect((onInvalidate) => {
document.addEventListener(type, listener, options);
onInvalidate(() => document.removeEventListener(type, listener, options));
});
}
function useWindowEvent(type, listener, options) {
if (env.isServer)
return;
Vue.watchEffect((onInvalidate) => {
window.addEventListener(type, listener, options);
onInvalidate(() => window.removeEventListener(type, listener, options));
});
}
function useOutsideClick(containers, cb, enabled = Vue.computed(() => true)) {
function handleOutsideClick(event, resolveTarget) {
if (!enabled.value)
return;
if (event.defaultPrevented)
return;
let target = resolveTarget(event);
if (target === null) {
return;
}
if (!target.getRootNode().contains(target))
return;
let _containers = function resolve(containers2) {
if (typeof containers2 === "function") {
return resolve(containers2());
}
if (Array.isArray(containers2)) {
return containers2;
}
if (containers2 instanceof Set) {
return containers2;
}
return [containers2];
}(containers);
for (let container of _containers) {
if (container === null)
continue;
let domNode = container instanceof HTMLElement ? container : dom(container);
if (domNode == null ? void 0 : domNode.contains(target)) {
return;
}
if (event.composed && event.composedPath().includes(domNode)) {
return;
}
}
if (
!isFocusableElement(target, FocusableMode.Loose) &&
target.tabIndex !== -1
) {
event.preventDefault();
}
return cb(event, target);
}
let initialClickTarget = Vue.ref(null);
useDocumentEvent(
"pointerdown",
(event) => {
var _a, _b;
if (enabled.value) {
initialClickTarget.value = ((_b = (_a = event.composedPath) == null ? void 0 : _a.call(event)) == null ? void 0 : _b[0]) || event.target;
}
},
true
);
useDocumentEvent(
"mousedown",
(event) => {
var _a, _b;
if (enabled.value) {
initialClickTarget.value = ((_b = (_a = event.composedPath) == null ? void 0 : _a.call(event)) == null ? void 0 : _b[0]) || event.target;
}
},
true
);
useDocumentEvent(
"click",
(event) => {
if (!initialClickTarget.value) {
return;
}
handleOutsideClick(event, () => {
return initialClickTarget.value;
});
initialClickTarget.value = null;
},
true
);
useDocumentEvent(
"touchend",
(event) => {
return handleOutsideClick(event, () => {
if (event.target instanceof HTMLElement) {
return event.target;
}
return null;
});
},
true
);
useWindowEvent(
"blur",
(event) => {
return handleOutsideClick(event, () => {
return window.document.activeElement instanceof HTMLIFrameElement ? window.document.activeElement : null;
});
},
true
);
}
function resolveType(type, as) {
if (type)
return type;
let tag = as != null ? as : "button";
if (typeof tag === "string" && tag.toLowerCase() === "button")
return "button";
return void 0;
}
function useResolveButtonType(data, refElement) {
let type = Vue.ref(resolveType(data.value.type, data.value.as));
Vue.onMounted(() => {
type.value = resolveType(data.value.type, data.value.as);
});
Vue.watchEffect(() => {
var _a;
if (type.value)
return;
if (!dom(refElement))
return;
if (dom(refElement) instanceof HTMLButtonElement && !((_a = dom(refElement)) == null ? void 0 : _a.hasAttribute("type"))) {
type.value = "button";
}
});
return type;
}
var Features$1 = ((Features2) => {
Features2[Features2["None"] = 0] = "None";
Features2[Features2["RenderStrategy"] = 1] = "RenderStrategy";
Features2[Features2["Static"] = 2] = "Static";
return Features2;
})(Features$1 || {});
function render({
visible = true,
features = 0,
ourProps,
theirProps,
...main
}) {
var _a;
let props = mergeProps(theirProps, ourProps);
let mainWithProps = Object.assign(main, { props });
if (visible)
return _render(mainWithProps);
if (features & 2) {
if (props.static)
return _render(mainWithProps);
}
if (features & 1) {
let strategy = ((_a = props.unmount) != null ? _a : true) ? 0 : 1;
return match(strategy, {
// 未挂载
[0]() { return null; },
// 隐藏
[1]() {
return _render({
...main,
props: { ...props, hidden: true, style: { display: "none" } }
});
}
});
}
return _render(mainWithProps);
}
function _render({
props,
attrs,
slots,
slot,
name
}) {
var _a, _b;
let { as, ...incomingProps } = omit(props, ["unmount", "static"]);
let children = (_a = slots.default) == null ? void 0 : _a.call(slots, slot);
let dataAttributes = {};
if (slot) {
let exposeState = false;
let states = [];
for (let [k, v] of Object.entries(slot)) {
if (typeof v === "boolean") {
exposeState = true;
}
if (v === true) {
states.push(k);
}
}
if (exposeState)
dataAttributes[`data-headlessui-state`] = states.join(" ");
}
if (as === "template") {
children = flattenFragments(children != null ? children : []);
if (Object.keys(incomingProps).length > 0 || Object.keys(attrs).length > 0) {
let [firstChild, ...other] = children != null ? children : [];
if (!isValidElement(firstChild) || other.length > 0) {
throw new Error(
[
'Passing props on "template"!',
"",
`The current component <${name} /> is rendering a "template".`,
`However we need to passthrough the following props:`,
Object.keys(incomingProps).concat(Object.keys(attrs)).map((name2) => name2.trim()).filter((current, idx, all) => all.indexOf(current) === idx).sort((a, z) => a.localeCompare(z)).map((line) => ` - ${line}`).join("\n"),
"",
"You can apply a few solutions:",
[
'Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
"Render a single element as the child so that we can forward the props onto that element."
].map((line) => ` - ${line}`).join("\n")
].join("\n")
);
}
let mergedProps = mergeProps((_b = firstChild.props) != null ? _b : {}, incomingProps, dataAttributes);
let cloned = Vue.cloneVNode(firstChild, mergedProps, true);
for (let prop in mergedProps) {
if (prop.startsWith("on")) {
cloned.props || (cloned.props = {});
cloned.props[prop] = mergedProps[prop];
}
}
return cloned;
}
if (Array.isArray(children) && children.length === 1) {
return children[0];
}
return children;
}
return Vue.h(as, Object.assign({}, incomingProps, dataAttributes), {
default: () => children
});
}
function flattenFragments(children) {
return children.flatMap((child) => {
if (child.type === Vue.Fragment) {
return flattenFragments(child.children);
}
return [child];
});
}
function mergeProps(...listOfProps) {
var _a;
if (listOfProps.length === 0)
return {};
if (listOfProps.length === 1)
return listOfProps[0];
let target = {};
let eventHandlers = {};
for (let props of listOfProps) {
for (let prop in props) {
if (prop.startsWith("on") && typeof props[prop] === "function") {
(_a = eventHandlers[prop]) != null ? _a : eventHandlers[prop] = [];
eventHandlers[prop].push(props[prop]);
} else {
target[prop] = props[prop];
}
}
}
if (target.disabled || target["aria-disabled"]) {
return Object.assign(
target,
Object.fromEntries(Object.keys(eventHandlers).map((eventName) => [eventName, void 0]))
);
}
for (let eventName in eventHandlers) {
Object.assign(target, {
[eventName](event, ...args) {
let handlers = eventHandlers[eventName];
for (let handler of handlers) {
if (event instanceof Event && event.defaultPrevented) {
return;
}
handler(event, ...args);
}
}
});
}
return target;
}
function compact(object) {
let clone = Object.assign({}, object);
for (let key in clone) {
if (clone[key] === void 0)
delete clone[key];
}
return clone;
}
function omit(object, keysToOmit = []) {
let clone = Object.assign({}, object);
for (let key of keysToOmit) {
if (key in clone)
delete clone[key];
}
return clone;
}
function isValidElement(input) {
if (input == null)
return false;
if (typeof input.type === "string")
return true;
if (typeof input.type === "object")
return true;
if (typeof input.type === "function")
return true;
return false;
}
var Features = ((Features2) => {
Features2[Features2["None"] = 1] = "None";
Features2[Features2["Focusable"] = 2] = "Focusable";
Features2[Features2["Hidden"] = 4] = "Hidden";
return Features2;
})(Features || {});
let Hidden = Vue.defineComponent({
name: "Hidden",
props: {
as: { type: [Object, String], default: "div" },
features: {
type: Number,
default: 1
}
},
setup(props, { slots, attrs }) {
return () => {
var _a;
let { features, ...theirProps } = props;
let ourProps = {
"aria-hidden": (features & 2) === 2 ? true : (
// @ts-ignore
(_a = theirProps["aria-hidden"]) != null ? _a : void 0
),
hidden: (features & 4) === 4 ? true : void 0,
style: {
position: "fixed",
top: 1,
left: 1,
width: 1,
height: 0,
padding: 0,
margin: -1,
overflow: "hidden",
clip: "rect(0, 0, 0, 0)",
whiteSpace: "nowrap",
borderWidth: "0",
...(features & 4) === 4 && !((features & 2) === 2) && { display: "none" }
}
};
return render({
ourProps,
theirProps,
slot: {},
attrs,
slots,
name: "Hidden"
});
};
}
});
let Context = Symbol("Context");
var State = ((State2) => {
State2[State2["Open"] = 1] = "Open";
State2[State2["Closed"] = 2] = "Closed";
State2[State2["Closing"] = 4] = "Closing";
State2[State2["Opening"] = 8] = "Opening";
return State2;
})(State || {});
function useOpenClosed() {
return Vue.inject(Context, null);
}
function useOpenClosedProvider(value) {
Vue.provide(Context, value);
}
var Keys = ((Keys2) => {
Keys2["Space"] = " ";
Keys2["Enter"] = "Enter";
Keys2["Escape"] = "Escape";
Keys2["Backspace"] = "Backspace";
Keys2["Delete"] = "Delete";
Keys2["ArrowLeft"] = "ArrowLeft";
Keys2["ArrowUp"] = "ArrowUp";
Keys2["ArrowRight"] = "ArrowRight";
Keys2["ArrowDown"] = "ArrowDown";
Keys2["Home"] = "Home";
Keys2["End"] = "End";
Keys2["PageUp"] = "PageUp";
Keys2["PageDown"] = "PageDown";
Keys2["Tab"] = "Tab";
return Keys2;
})(Keys || {});
function attemptSubmit(elementInForm) {
var _a, _b;
let form = (_a = elementInForm == null ? void 0 : elementInForm.form) != null ? _a : elementInForm.closest("form");
if (!form)
return;
for (let element of form.elements) {
if (element === elementInForm)
continue;
if (element.tagName === "INPUT" && element.type === "submit" || element.tagName === "BUTTON" && element.type === "submit" || element.nodeName === "INPUT" && element.type === "image") {
element.click();
return;
}
}
(_b = form.requestSubmit) == null ? void 0 : _b.call(form);
}
function useEventListener(element, type, listener, options) {
if (env.isServer)
return;
Vue.watchEffect((onInvalidate) => {
element = element != null ? element : window;
element.addEventListener(type, listener, options);
onInvalidate(() => element.removeEventListener(type, listener, options));
});
}
var Direction = ((Direction2) => {
Direction2[Direction2["Forwards"] = 0] = "Forwards";
Direction2[Direction2["Backwards"] = 1] = "Backwards";
return Direction2;
})(Direction || {});
function useTabDirection() {
let direction = Vue.ref(0);
useWindowEvent("keydown", (event) => {
if (event.key === "Tab") {
direction.value = event.shiftKey ? 1 : 0;
}
});
return direction;
}
function useRootContainers({
defaultContainers = [],
portals,
mainTreeNodeRef: _mainTreeNodeRef
} = {}) {
let mainTreeNodeRef = Vue.ref(null);
let ownerDocument = getOwnerDocument(mainTreeNodeRef);
function resolveContainers() {
var _a, _b, _c;
let containers = [];
for (let container of defaultContainers) {
if (container === null)
continue;
if (container instanceof HTMLElement) {
containers.push(container);
} else if ("value" in container && container.value instanceof HTMLElement) {
containers.push(container.value);
}
}
if (portals == null ? void 0 : portals.value) {
for (let portal of portals.value) {
containers.push(portal);
}
}
for (let container of (_a = ownerDocument == null ? void 0 : ownerDocument.querySelectorAll("html > *, body > *")) != null ? _a : []) {
if (container === document.body)
continue;
if (container === document.head)
continue;
if (!(container instanceof HTMLElement))
continue;
if (container.id === "headlessui-portal-root")
continue;
if (container.contains(dom(mainTreeNodeRef)))
continue;
if (container.contains((_c = (_b = dom(mainTreeNodeRef)) == null ? void 0 : _b.getRootNode()) == null ? void 0 : _c.host))
continue;
if (containers.some((defaultContainer) => container.contains(defaultContainer)))
continue;
containers.push(container);
}
return containers;
}
return {
resolveContainers,
contains(element) {
return resolveContainers().some((container) => container.contains(element));
},
mainTreeNodeRef,
MainTreeNode() {
if (_mainTreeNodeRef != null)
return null;
return Vue.h(Hidden, { features: Features.Hidden, ref: mainTreeNodeRef });
}
};
}
function useMainTreeNode() {
let mainTreeNodeRef = Vue.ref(null);
return {
mainTreeNodeRef,
MainTreeNode() {
return Vue.h(Hidden, { features: Features.Hidden, ref: mainTreeNodeRef });
}
};
}
let ForcePortalRootContext = Symbol("ForcePortalRootContext");
function usePortalRoot() {
return Vue.inject(ForcePortalRootContext, false);
}
Vue.defineComponent({
name: "ForcePortalRoot",
props: {
as: { type: [Object, String], default: "template" },
force: { type: Boolean, default: false }
},
setup(props, { slots, attrs }) {
Vue.provide(ForcePortalRootContext, props.force);
return () => {
let { force, ...theirProps } = props;
return render({
theirProps,
ourProps: {},
slot: {},
slots,
attrs,
name: "ForcePortalRoot"
});
};
}
});
let DescriptionContext = Symbol("DescriptionContext");
function useDescriptionContext() {
let context = Vue.inject(DescriptionContext, null);
if (context === null) {
throw new Error("Missing parent");
}
return context;
}
function useDescriptions({
slot = Vue.ref({}),
name = "Description",
props = {}
} = {}) {
let descriptionIds = Vue.ref([]);
function register(value) {
descriptionIds.value.push(value);
return () => {
let idx = descriptionIds.value.indexOf(value);
if (idx === -1)
return;
descriptionIds.value.splice(idx, 1);
};
}
Vue.provide(DescriptionContext, { register, slot, name, props });
return Vue.computed(
() => descriptionIds.value.length > 0 ? descriptionIds.value.join(" ") : void 0
);
}
Vue.defineComponent({
name: "Description",
props: {
as: { type: [Object, String], default: "p" },
id: { type: String, default: () => `headlessui-description-${useId()}` }
},
setup(myProps, { attrs, slots }) {
let context = useDescriptionContext();
Vue.onMounted(() => Vue.onUnmounted(context.register(myProps.id)));
return () => {
let { name = "Description", slot = Vue.ref({}), props = {} } = context;
let { id: id2, ...theirProps } = myProps;
let ourProps = {
...Object.entries(props).reduce(
(acc, [key, value]) => Object.assign(acc, { [key]: Vue.unref(value) }),
{}
),
id: id2
};
return render({
ourProps,
theirProps,
slot: slot.value,
attrs,
slots,
name
});
};
}
});
function getPortalRoot(contextElement) {
let ownerDocument = getOwnerDocument(contextElement);
if (!ownerDocument) {
if (contextElement === null) {
return null;
}
throw new Error(
`[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement}`
);
}
let existingRoot = ownerDocument.getElementById("headlessui-portal-root");
if (existingRoot)
return existingRoot;
let root = ownerDocument.createElement("div");
root.setAttribute("id", "headlessui-portal-root");
return ownerDocument.body.appendChild(root);
}
Vue.defineComponent({
name: "Portal",
props: {
as: { type: [Object, String], default: "div" }
},
setup(props, { slots, attrs }) {
let element = Vue.ref(null);
let ownerDocument = Vue.computed(() => getOwnerDocument(element));
let forcePortalRoot = usePortalRoot();
let groupContext = Vue.inject(PortalGroupContext, null);
let myTarget = Vue.ref(
forcePortalRoot === true ? getPortalRoot(element.value) : groupContext == null ? getPortalRoot(element.value) : groupContext.resolveTarget()
);
let ready = Vue.ref(false);
Vue.onMounted(() => {
ready.value = true;
});
Vue.watchEffect(() => {
if (forcePortalRoot)
return;
if (groupContext == null)
return;
myTarget.value = groupContext.resolveTarget();
});
let parent = Vue.inject(PortalParentContext, null);
let didRegister = false;
let instance = Vue.getCurrentInstance();
Vue.watch(element, () => {
if (didRegister)
return;
if (!parent)
return;
let domElement = dom(element);
if (!domElement)
return;
Vue.onUnmounted(parent.register(domElement), instance);
didRegister = true;
});
Vue.onUnmounted(() => {
var _a, _b;
let root = (_a = ownerDocument.value) == null ? void 0 : _a.getElementById("headlessui-portal-root");
if (!root)
return;
if (myTarget.value !== root)
return;
if (myTarget.value.children.length <= 0) {
(_b = myTarget.value.parentElement) == null ? void 0 : _b.removeChild(myTarget.value);
}
});
return () => {
if (!ready.value)
return null;
if (myTarget.value === null)
return null;
let ourProps = {
ref: element,
"data-headlessui-portal": ""
};
return Vue.h(
// @ts-expect-error Children can be an object, but TypeScript is not happy
// with it. Once this is fixed upstream we can remove this assertion.
Vue.Teleport,
{ to: myTarget.value },
render({
ourProps,
theirProps: props,
slot: {},
attrs,
slots,
name: "Portal"
})
);
};
}
});
let PortalParentContext = Symbol("PortalParentContext");
function useNestedPortals() {
let parent = Vue.inject(PortalParentContext, null);
let portals = Vue.ref([]);
function register(portal) {
portals.value.push(portal);
if (parent)
parent.register(portal);
return () => unregister(portal);
}
function unregister(portal) {
let idx = portals.value.indexOf(portal);
if (idx !== -1)
portals.value.splice(idx, 1);
if (parent)
parent.unregister(portal);
}
let api = {
register,
unregister,
portals
};
return [
portals,
Vue.defineComponent({
name: "PortalWrapper",
setup(_, { slots }) {
Vue.provide(PortalParentContext, api);
return () => {
var _a;
return (_a = slots.default) == null ? void 0 : _a.call(slots);
};
}
})
];
}
let PortalGroupContext = Symbol("PortalGroupContext");
Vue.defineComponent({
name: "PortalGroup",
props: {
as: { type: [Object, String], default: "template" },
target: { type: Object, default: null }
},
setup(props, { attrs, slots }) {
let api = Vue.reactive({
resolveTarget() {
return props.target;
}
});
Vue.provide(PortalGroupContext, api);
return () => {
let { target: _, ...theirProps } = props;
return render({
theirProps,
ourProps: {},
slot: {},
attrs,
slots,
name: "PortalGroup"
});
};
}
});
let PopoverContext = Symbol("PopoverContext");
function usePopoverContext(component) {
let context = Vue.inject(PopoverContext, null);
if (context === null) {
let err = new Error(`<${component} /> is missing a parent <${Popover.name} /> component.`);
if (Error.captureStackTrace)
Error.captureStackTrace(err, usePopoverContext);
throw err;
}
return context;
}
let PopoverGroupContext = Symbol("PopoverGroupContext");
function usePopoverGroupContext() {
return Vue.inject(PopoverGroupContext, null);
}
let PopoverPanelContext = Symbol("PopoverPanelContext");
function usePopoverPanelContext() {
return Vue.inject(PopoverPanelContext, null);
}
let Popover = Vue.defineComponent({
name: "Popover",
inheritAttrs: false,
props: {
as: { type: [Object, String], default: "div" }
},
setup(props, { slots, attrs, expose }) {
var _a;
let internalPopoverRef = Vue.ref(null);
expose({ el: internalPopoverRef, $el: internalPopoverRef });
let popoverState = Vue.ref(
1
/* Closed */
);
let button = Vue.ref(null);
let beforePanelSentinel = Vue.ref(null);
let afterPanelSentinel = Vue.ref(null);
let panel = Vue.ref(null);
let ownerDocument = Vue.computed(() => getOwnerDocument(internalPopoverRef));
let isPortalled = Vue.computed(() => {
var _a2, _b;
if (!dom(button))
return false;
if (!dom(panel))
return false;
for (let root2 of document.querySelectorAll("body > *")) {
if (Number(root2 == null ? void 0 : root2.contains(dom(button))) ^ Number(root2 == null ? void 0 : root2.contains(dom(panel)))) {
return true;
}
}
let elements = getFocusableElements();
let buttonIdx = elements.indexOf(dom(button));
let beforeIdx = (buttonIdx + elements.length - 1) % elements.length;
let afterIdx = (buttonIdx + 1) % elements.length;
let beforeElement = elements[beforeIdx];
let afterElement = elements[afterIdx];
if (!((_a2 = dom(panel)) == null ? void 0 : _a2.contains(beforeElement)) && !((_b = dom(panel)) == null ? void 0 : _b.contains(afterElement))) {
return true;
}
return false;
});
let api = {
popoverState,
buttonId: Vue.ref(null),
panelId: Vue.ref(null),
panel,
button,
isPortalled,
beforePanelSentinel,
afterPanelSentinel,
togglePopover() {
popoverState.value = match(popoverState.value, {
[
0
/* Open */
]: 1,
[
1
/* Closed */
]: 0
/* Open */
});
},
closePopover() {
if (popoverState.value === 1)
return;
popoverState.value = 1;
},
close(focusableElement) {
api.closePopover();
let restoreElement = (() => {
if (!focusableElement)
return dom(api.button);
if (focusableElement instanceof HTMLElement)
return focusableElement;
if (focusableElement.value instanceof HTMLElement)
return dom(focusableElement);
return dom(api.button);
})();
restoreElement == null ? void 0 : restoreElement.focus();
}
};
Vue.provide(PopoverContext, api);
useOpenClosedProvider(
Vue.computed(
() => match(popoverState.value, {
[
0
/* Open */
]: State.Open,
[
1
/* Closed */
]: State.Closed
})
)
);
let registerBag = {
buttonId: api.buttonId,
panelId: api.panelId,
close() {
api.closePopover();
}
};
let groupContext = usePopoverGroupContext();
let registerPopover = groupContext == null ? void 0 : groupContext.registerPopover;
let [portals, PortalWrapper] = useNestedPortals();
let root = useRootContainers({
mainTreeNodeRef: groupContext == null ? void 0 : groupContext.mainTreeNodeRef,
portals,
defaultContainers: [button, panel]
});
function isFocusWithinPopoverGroup() {
var _a2, _b, _c, _d;
return (_d = groupContext == null ? void 0 : groupContext.isFocusWithinPopoverGroup()) != null ? _d : ((_a2 = ownerDocument.value) == null ? void 0 : _a2.activeElement) && (((_b = dom(button)) == null ? void 0 : _b.contains(ownerDocument.value.activeElement)) || ((_c = dom(panel)) == null ? void 0 : _c.contains(ownerDocument.value.activeElement)));
}
Vue.watchEffect(() => registerPopover == null ? void 0 : registerPopover(registerBag));
useEventListener(
(_a = ownerDocument.value) == null ? void 0 : _a.defaultView,
"focus",
(event) => {
var _a2, _b;
if (event.target === window)
return;
if (!(event.target instanceof HTMLElement))
return;
if (popoverState.value !== 0)
return;
if (isFocusWithinPopoverGroup())
return;
if (!button)
return;
if (!panel)
return;
if (root.contains(event.target))
return;
if ((_a2 = dom(api.beforePanelSentinel)) == null ? void 0 : _a2.contains(event.target))
return;
if ((_b = dom(api.afterPanelSentinel)) == null ? void 0 : _b.contains(event.target))
return;
api.closePopover();
},
true
);
useOutsideClick(
root.resolveContainers,
(event, target) => {
var _a2;
api.closePopover();
if (!isFocusableElement(target, FocusableMode.Loose)) {
event.preventDefault();
(_a2 = dom(button)) == null ? void 0 : _a2.focus();
}
},
Vue.computed(
() => popoverState.value === 0
/* Open */
)
);
return () => {
let slot = { open: popoverState.value === 0, close: api.close };
return Vue.h(Vue.Fragment, [
Vue.h(
PortalWrapper,
{},
() => render({
theirProps: { ...props, ...attrs },
ourProps: { ref: internalPopoverRef },
slot,
slots,
attrs,
name: "Popover"
})
),
Vue.h(root.MainTreeNode)
]);
};
}
});
let PopoverButton = Vue.defineComponent({
name: "PopoverButton",
props: {
as: { type: [Object, String], default: "button" },
disabled: { type: [Boolean], default: false },
id: { type: String, default: () => `headlessui-popover-button-${useId()}` }
},
inheritAttrs: false,
setup(props, { attrs, slots, expose }) {
let api = usePopoverContext("PopoverButton");
let ownerDocument = Vue.computed(() => getOwnerDocument(api.button));
expose({ el: api.button, $el: api.button });
Vue.onMounted(() => {
api.buttonId.value = props.id;
});
Vue.onUnmounted(() => {
api.buttonId.value = null;
});
let groupContext = usePopoverGroupContext();
let closeOthers = groupContext == null ? void 0 : groupContext.closeOthers;
let panelContext = usePopoverPanelContext();
let isWithinPanel = Vue.computed(
() => panelContext === null ? false : panelContext.value === api.panelId.value
);
let elementRef = Vue.ref(null);
let sentinelId = `headlessui-focus-sentinel-${useId()}`;
if (!isWithinPanel.value) {
Vue.watchEffect(() => {
api.button.value = dom(elementRef);
});
}
let type = useResolveButtonType(
Vue.computed(() => ({ as: props.as, type: attrs.type })),
elementRef
);
function handleKeyDown(event) {
var _a, _b, _c, _d, _e;
if (isWithinPanel.value) {
if (api.popoverState.value === 1)
return;
switch (event.key) {
case Keys.Space:
case Keys.Enter:
event.preventDefault();
(_b = (_a = event.target).click) == null ? void 0 : _b.call(_a);
api.closePopover();
(_c = dom(api.button)) == null ? void 0 : _c.focus();
break;
}
} else {
switch (event.key) {
case Keys.Space:
case Keys.Enter:
event.preventDefault();
event.stopPropagation();
if (api.popoverState.value === 1)
closeOthers == null ? void 0 : closeOthers(api.buttonId.value);
api.togglePopover();
break;
case Keys.Escape:
if (api.popoverState.value !== 0)
return closeOthers == null ? void 0 : closeOthers(api.buttonId.value);
if (!dom(api.button))
return;
if (((_d = ownerDocument.value) == null ? void 0 : _d.activeElement) && !((_e = dom(api.button)) == null ? void 0 : _e.contains(ownerDocument.value.activeElement)))
return;
event.preventDefault();
event.stopPropagation();
api.closePopover();
break;
}
}
}
function handleKeyUp(event) {
if (isWithinPanel.value)
return;
if (event.key === Keys.Space) {
event.preventDefault();
}
}
function handleClick(event) {
var _a, _b;
if (props.disabled)
return;
if (isWithinPanel.value) {
api.closePopover();
(_a = dom(api.button)) == null ? void 0 : _a.focus();
} else {
event.preventDefault();
event.stopPropagation();
if (api.popoverState.value === 1)
closeOthers == null ? void 0 : closeOthers(api.buttonId.value);
api.togglePopover();
(_b = dom(api.button)) == null ? void 0 : _b.focus();
}
}
function handleMouseDown(event) {
event.preventDefault();
event.stopPropagation();
}
let direction = useTabDirection();
function handleFocus() {
let el = dom(api.panel);
if (!el)
return;
function run() {
let result = match(direction.value, {
[Direction.Forwards]: () => focusIn(el, Focus.First),
[Direction.Backwards]: () => focusIn(el, Focus.Last)
});
if (result === FocusResult.Error) {
focusIn(
getFocusableElements().filter((el2) => el2.dataset.headlessuiFocusGuard !== "true"),
match(direction.value, {
[Direction.Forwards]: Focus.Next,
[Direction.Backwards]: Focus.Previous
}),
{ relativeTo: dom(api.button) }
);
}
}
{
run();
}
}
return () => {
let visible = api.popoverState.value === 0;
let slot = { open: visible };
let { id: id2, ...theirProps } = props;
let ourProps = isWithinPanel.value ? {
ref: elementRef,
type: type.value,
onKeydown: handleKeyDown,
onClick: handleClick
} : {
ref: elementRef,
id: id2,
type: type.value,
"aria-expanded": api.popoverState.value === 0,
"aria-controls": dom(api.panel) ? api.panelId.value : void 0,
disabled: props.disabled ? true : void 0,
onKeydown: handleKeyDown,
onKeyup: handleKeyUp,
onClick: handleClick,
onMousedown: handleMouseDown
};
return Vue.h(Vue.Fragment, [
render({
ourProps,
theirProps: { ...attrs, ...theirProps },
slot,
attrs,
slots,
name: "PopoverButton"
}),
visible && !isWithinPanel.value && api.isPortalled.value && Vue.h(Hidden, {
id: sentinelId,
features: Features.Focusable,
"data-headlessui-focus-guard": true,
as: "button",
type: "button",
onFocus: handleFocus
})
]);
};
}
});
Vue.defineComponent({
name: "PopoverOverlay",
props: {
as: { type: [Object, String], default: "div" },
static: { type: Boolean, default: false },
unmount: { type: Boolean, default: true }
},
setup(props, { attrs, slots }) {
let api = usePopoverContext("PopoverOverlay");
let id2 = `headlessui-popover-overlay-${useId()}`;
let usesOpenClosedState = useOpenClosed();
let visible = Vue.computed(() => {
if (usesOpenClosedState !== null) {
return (usesOpenClosedState.value & State.Open) === State.Open;
}
return api.popoverState.value === 0;
});
function handleClick() {
api.closePopover();
}
return () => {
let slot = {
open: api.popoverState.value === 0
/* Open */
};
let ourProps = {
id: id2,
"aria-hidden": true,
onClick: handleClick
};
return render({
ourProps,
theirProps: props,
slot,
attrs,
slots,
features: Features$1.RenderStrategy | Features$1.Static,
visible: visible.value,
name: "PopoverOverlay"
});
};
}
});
const PopoverPanel = Vue.defineComponent({
name: "PopoverPanel",
props: {
as: { type: [Object, String], default: "div" },
static: { type: Boolean, default: false },
unmount: { type: Boolean, default: true },
focus: { type: Boolean, default: false },
id: { type: String, default: () => `headlessui-popover-panel-${useId()}` }
},
inheritAttrs: false,
setup(props, { attrs, slots, expose }) {
let { focus } = props;
let api = usePopoverContext("PopoverPanel");
let ownerDocument = Vue.computed(() => getOwnerDocument(api.panel));
let beforePanelSentinelId = `headlessui-focus-sentinel-before-${useId()}`;
let afterPanelSentinelId = `headlessui-focus-sentinel-after-${useId()}`;
expose({ el: api.panel, $el: api.panel });
Vue.onMounted(() => {
api.panelId.value = props.id;
});
Vue.onUnmounted(() => {
api.panelId.value = null;
});
Vue.provide(PopoverPanelContext, api.panelId);
Vue.watchEffect(() => {
var _a, _b;
if (!focus)
return;
if (api.popoverState.value !== 0)
return;
if (!api.panel)
return;
let activeElement = (_a = ownerDocument.value) == null ? void 0 : _a.activeElement;
if ((_b = dom(api.panel)) == null ? void 0 : _b.contains(activeElement))
return;
focusIn(dom(api.panel), Focus.First);
});
let usesOpenClosedState = useOpenClosed();
let visible = Vue.computed(() => {
if (usesOpenClosedState !== null) {
return (usesOpenClosedState.value & State.Open) === State.Open;
}
return api.popoverState.value === 0;
});
function handleKeyDown(event) {
var _a, _b;
switch (event.key) {
case Keys.Escape:
if (api.popoverState.value !== 0)
return;
if (!dom(api.panel))
return;
if (ownerDocument.value && !((_a = dom(api.panel)) == null ? void 0 : _a.contains(ownerDocument.value.activeElement))) {
return;
}
event.preventDefault();
event.stopPropagation();
api.closePopover();
(_b = dom(api.button)) == null ? void 0 : _b.focus();
break;
}
}
function handleBlur(event) {
var _a, _b, _c, _d, _e;
let el = event.relatedTarget;
if (!el)
return;
if (!dom(api.panel))
return;
if ((_a = dom(api.panel)) == null ? void 0 : _a.contains(el))
return;
api.closePopover();
if (((_c = (_b = dom(api.beforePanelSentinel)) == null ? void 0 : _b.contains) == null ? void 0 : _c.call(_b, el)) || ((_e = (_d = dom(api.afterPanelSentinel)) == null ? void 0 : _d.contains) == null ? void 0 : _e.call(_d, el))) {
el.focus({ preventScroll: true });
}
}
let direction = useTabDirection();
function handleBeforeFocus() {
let el = dom(api.panel);
if (!el)
return;
function run() {
match(direction.value, {
[Direction.Forwards]: () => {
var _a;
let result = focusIn(el, Focus.First);
if (result === FocusResult.Error) {
(_a = dom(api.afterPanelSentinel)) == null ? void 0 : _a.focus();
}
},
[Direction.Backwards]: () => {
var _a;
(_a = dom(api.button)) == null ? void 0 : _a.focus({ preventScroll: true });
}
});
}
{
run();
}
}
function handleAfterFocus() {
let el = dom(api.panel);
if (!el)
return;
function run() {
match(direction.value, {
[Direction.Forwards]: () => {
let button = dom(api.button);
let panel = dom(api.panel);
if (!button)
return;
let elements = getFocusableElements();
let idx = elements.indexOf(button);
let before = elements.slice(0, idx + 1);
let after = elements.slice(idx + 1);
let combined = [...after, ...before];
for (let element of combined.slice()) {
if (element.dataset.headlessuiFocusGuard === "true" || (panel == null ? void 0 : panel.contains(element))) {
let idx2 = combined.indexOf(element);
if (idx2 !== -1)
combined.splice(idx2, 1);
}
}
focusIn(combined, Focus.First, { sorted: false });
},
[Direction.Backwards]: () => {
var _a;
let result = focusIn(el, Focus.Previous);
if (result === FocusResult.Error) {
(_a = dom(api.button)) == null ? void 0 : _a.focus();
}
}
});
}
{
run();
}
}
return () => {
let slot = {
open: api.popoverState.value === 0,
close: api.close
};
let { id: id2, focus: _focus, ...theirProps } = props;
let ourProps = {
ref: api.panel,
id: id2,
onKeydown: handleKeyDown,
onFocusout: focus && api.popoverState.value === 0 ? handleBlur : void 0,
tabIndex: -1
};
return render({
ourProps,
theirProps: { ...attrs, ...theirProps },
attrs,
slot,
slots: {
...slots,
default: (...args) => {
var _a;
return [
Vue.h(Vue.Fragment, [
visible.value && api.isPortalled.value && Vue.h(Hidden, {
id: beforePanelSentinelId,
ref: api.beforePanelSentinel,
features: Features.Focusable,
"data-headlessui-focus-guard": true,
as: "button",
type: "button",
onFocus: handleBeforeFocus
}),
(_a = slots.default) == null ? void 0 : _a.call(slots, ...args),
visible.value && api.isPortalled.value && Vue.h(Hidden, {
id: afterPanelSentinelId,
ref: api.afterPanelSentinel,
features: Features.Focusable,
"data-headlessui-focus-guard": true,
as: "button",
type: "button",
onFocus: handleAfterFocus
})
])
];
}
},
features: Features$1.RenderStrategy | Features$1.Static,
visible: visible.value,
name: "PopoverPanel"
});
};
}
});
Vue.defineComponent({
name: "PopoverGroup",
inheritAttrs: false,
props: {
as: { type: [Object, String], default: "div" }
},
setup(props, { attrs, slots, expose }) {
let groupRef = Vue.ref(null);
let popovers = Vue.shallowRef([]);
let ownerDocument = Vue.computed(() => getOwnerDocument(groupRef));
let root = useMainTreeNode();
expose({ el: groupRef, $el: groupRef });
function unregisterPopover(registerBag) {
let idx = popovers.value.indexOf(registerBag);
if (idx !== -1)
popovers.value.splice(idx, 1);
}
function registerPopover(registerBag) {
popovers.value.push(registerBag);
return () => {
unregisterPopover(registerBag);
};
}
function isFocusWithinPopoverGroup() {
var _a;
let owner = ownerDocument.value;
if (!owner)
return false;
let element = owner.activeElement;
if ((_a = dom(groupRef)) == null ? void 0 : _a.contains(element))
return true;
return popovers.value.some((bag) => {
var _a2, _b;
return ((_a2 = owner.getElementById(bag.buttonId.value)) == null ? void 0 : _a2.contains(element)) || ((_b = owner.getElementById(bag.panelId.value)) == null ? void 0 : _b.contains(element));
});
}
function closeOthers(buttonId) {
for (let popover of popovers.value) {
if (popover.buttonId.value !== buttonId)
popover.close();
}
}
Vue.provide(PopoverGroupContext, {
registerPopover,
unregisterPopover,
isFocusWithinPopoverGroup,
closeOthers,
mainTreeNodeRef: root.mainTreeNodeRef
});
return () => {
let ourProps = { ref: groupRef };
return Vue.h(Vue.Fragment, [
render({
ourProps,
theirProps: { ...props, ...attrs },
slot: {},
attrs,
slots,
name: "PopoverGroup"
}),
Vue.h(root.MainTreeNode)
]);
};
}
});
let LabelContext = Symbol("LabelContext");
function useLabelContext() {
let context = Vue.inject(LabelContext, null);
if (context === null) {
let err = new Error("You used a <Label /> component, but it is not inside a parent.");
if (Error.captureStackTrace)
Error.captureStackTrace(err, useLabelContext);
throw err;
}
return context;
}
function useLabels({
slot = {},
name = "Label",
props = {}
} = {}) {
let labelIds = Vue.ref([]);
function register(value) {
labelIds.value.push(value);
return () => {
let idx = labelIds.value.indexOf(value);
if (idx === -1)
return;
labelIds.value.splice(idx, 1);
};
}
Vue.provide(LabelContext, { register, slot, name, props });
return Vue.computed(() => labelIds.value.length > 0 ? labelIds.value.join(" ") : void 0);
}
Vue.defineComponent({
name: "Label",
props: {
as: { type: [Object, String], default: "label" },
passive: { type: [Boolean], default: false },
id: { type: String, default: () => `headlessui-label-${useId()}` }
},
setup(myProps, { slots, attrs }) {
let context = useLabelContext();
Vue.onMounted(() => Vue.onUnmounted(context.register(myProps.id)));
return () => {
let { name = "Label", slot = {}, props = {} } = context;
let { id: id2, passive, ...theirProps } = myProps;
let ourProps = {
...Object.entries(props).reduce(
(acc, [key, value]) => Object.assign(acc, { [key]: Vue.unref(value) }),
{}
),
id: id2
};
if (passive) {
delete ourProps["onClick"];
delete ourProps["htmlFor"];
delete theirProps["onClick"];
}
return render({
ourProps,
theirProps,
slot,
attrs,
slots,
name
});
};
}
});
let GroupContext = Symbol("GroupContext");
const Switch = Vue.defineComponent({
name: "Switch",
emits: { "update:modelValue": (value) => true },
props: {
as: { type: [Object, String], default: "button" },
modelValue: { type: Boolean, default: void 0 },
defaultChecked: { type: Boolean, optional: true },
form: { type: String, optional: true },
name: { type: String, optional: true },
value: { type: String, optional: true },
id: { type: String, default: () => `headlessui-switch-${useId()}` },
disabled: { type: Boolean, default: false },
tabIndex: { type: Number, default: 0 },
},
inheritAttrs: false,
setup(props, { emit, attrs, slots, expose }) {
const api = Vue.inject(GroupContext, null);
const [checked, onChange] = useControllable(
Vue.computed(() => props.modelValue),
(value) => emit("update:modelValue", value),
Vue.computed(() => props.defaultChecked)
);
const toggle = () => onChange(!checked.value);
const internalSwitchRef = Vue.ref(null);
const switchRef = api ? api.switchRef : internalSwitchRef;
const type = useResolveButtonType(
Vue.computed(() => ({ as: props.as, type: attrs.type })),
switchRef
);
expose({ el: switchRef, $el: switchRef });
function handleClick(event) {
event.preventDefault();
toggle();
}
function handleKeyUp(event) {
if (event.key === Keys.Space) {
event.preventDefault();
toggle();
} else if (event.key === Keys.Enter) {
attemptSubmit(event.currentTarget);
}
}
Vue.onMounted(() => {
const form = Vue.computed(() => dom(switchRef)?.closest("form"));
Vue.watch([form], () => {
if (form.value && props.defaultChecked !== void 0) {
const handle = () => onChange(props.defaultChecked);
form.value.addEventListener("reset", handle);
return () => form.value.removeEventListener("reset", handle);
}
}, { immediate: true });
});
return () => {
const { id, name, value, form, tabIndex, ...otherProps } = props;
const slot = { checked: checked.value };
const ourProps = {
id,
ref: switchRef,
role: "switch",
type: type.value,
tabIndex: tabIndex === -1 ? 0 : tabIndex,
"aria-checked": checked.value,
onClick: handleClick,
onKeyup: handleKeyUp,
"data-state": checked.value ? "checked" : "",
};
return Vue.h(Vue.Fragment, [
name && checked.value !== null ? Vue.h(Hidden, {
features: Features.Hidden,
as: "input",
type: "checkbox",
checked: checked.value,
form,
disabled: otherProps.disabled,
name,
value
}) : null,
render({
ourProps,
theirProps: { ...attrs, ...omit(otherProps, ["modelValue", "defaultChecked"]) },
slot,
attrs,
slots,
name: "Switch",
})
]);
};
}
});
const SwitchGroup = {
__name: "SwitchGroup",
emits: { "update:isSelected": (_value) => true },
props: {
title: {
type: String,
required: true
},
icon: {
type: String,
required: false
},
isSelected: {
type: Boolean,
default: false
}
},
setup(__props, { emit }) {
return (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock("div", {
class: Vue.normalizeClass(["flex items-center m-1.5 p-2.5 text-sm cursor-pointer focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 group relative hover:bg-[#f5f5f5] focus-visible:bg-[#f5f5f5] radix-state-open:bg-[#f5f5f5] dark:hover:bg-token-main-surface-secondary dark:focus-visible:bg-token-main-surface-secondary rounded-md my-0 px-3 mx-2 dark:radix-state-open:bg-token-main-surface-secondary gap-2.5 py-3 !pr-3", { "!opacity-100": __props.isSelected }]),
onClick: () => { emit('update:isSelected', !__props.isSelected); }
}, [
__props.icon ?
Vue.createVNode("div", { class: 'flex items-center justify-center text-token-text-secondary h-5 w-7', innerHTML: Vue.toDisplayString(__props.icon) }, [])
: [],
Vue.createVNode("div", { class: "flex grow items-center justify-between gap-2" }, [
Vue.createVNode("div", { class: "flex items-center gap-3" }, [
Vue.createTextVNode(Vue.toDisplayString(__props.title), 1)
]),
Vue.createVNode(Vue.unref(Switch), {
modelValue: __props.isSelected,
class: 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-token-text-secondary focus-visible:ring-offset-2 focus-visible:radix-state-checked:ring-black focus-visible:dark:ring-token-main-surface-primary focus-visible:dark:radix-state-checked:ring-green-600 cursor-pointer bg-gray-200 radix-state-checked:bg-black dark:border dark:border-token-border-medium dark:bg-transparent relative shrink-0 rounded-full dark:radix-state-checked:border-green-600 dark:radix-state-checked:bg-green-600 h-[20px] w-[32px]'
}, {
default: Vue.withCtx(() => [
Vue.createVNode("span", {
class: "flex items-center justify-center rounded-full transition-transform duration-100 will-change-transform bg-white shadow-[0_1px_2px_rgba(0,0,0,0.45)] h-[16px] w-[16px]",
style: Vue.normalizeStyle(__props.isSelected ? "transform: translateX(14px)" : "transform: translateX(0.125rem)")
}, null, 4)
]),
_: 1
}, 8, ["modelValue", "class"])
])
], 2);
};
}
};
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const DividerItem = _export_sfc({}, [["render", (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock("div", {
role: "separator",
"aria-orientation": "horizontal",
class: "mx-5 my-1 h-px bg-token-border-light"
});
}]]);
const CircleCheckIcon = _export_sfc({}, [["render", (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock("svg", {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
class: "icon-md"
}, [
Vue.createVNode("path", {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
d: "M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.0755 7.93219C16.5272 8.25003 16.6356 8.87383 16.3178 9.32549L11.5678 16.0755C11.3931 16.3237 11.1152 16.4792 10.8123 16.4981C10.5093 16.517 10.2142 16.3973 10.0101 16.1727L7.51006 13.4227C7.13855 13.014 7.16867 12.3816 7.57733 12.0101C7.98598 11.6386 8.61843 11.6687 8.98994 12.0773L10.6504 13.9039L14.6822 8.17451C15 7.72284 15.6238 7.61436 16.0755 7.93219Z",
fill: "currentColor"
}, null, -1)
]);
}]]);
const ModelItem = {
__name: "ModelItem",
props: {
title: {
type: String,
required: true
},
description: {
type: String,
required: false
},
icon: {
type: String,
required: false
},
beta: {
type: String,
required: false
},
disabled: {
type: Boolean,
default: false,
required: false
},
isSelected: {
type: Boolean,
default: false
}
},
setup(__props) {
return (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock("div", {
class: (!__props.disabled ? Vue.normalizeClass(["flex items-center m-1.5 p-2.5 text-sm cursor-pointer focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 group relative hover:bg-[#f5f5f5] focus-visible:bg-[#f5f5f5] radix-state-open:bg-[#f5f5f5] dark:hover:bg-token-main-surface-secondary dark:focus-visible:bg-token-main-surface-secondary rounded-md my-0 px-3 mx-2 dark:radix-state-open:bg-token-main-surface-secondary gap-2.5 py-3 !pr-3", { "!opacity-100": __props.isSelected }]) : 'flex items-center m-1.5 p-2.5 text-sm cursor-pointer focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 group relative hover:bg-[#f5f5f5] focus-visible:bg-[#f5f5f5] radix-state-open:bg-[#f5f5f5] dark:hover:bg-token-main-surface-secondary dark:focus-visible:bg-token-main-surface-secondary rounded-md my-0 px-3 mx-2 dark:radix-state-open:bg-token-main-surface-secondary gap-2.5 py-3 !pr-3 pointer-events-none text-token-text-quaternary')
}, [
Vue.createVNode("div", { class: "flex grow items-center justify-between gap-2" }, [
Vue.createVNode("div", null, [
Vue.createVNode("div", { class: "flex items-center gap-3" }, [
__props.icon ?
Vue.createVNode("span", {
class: 'flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-token-main-surface-secondary',
}, [
Vue.createVNode("div",
{
class: Vue.normalizeClass(['[&_svg]:h-full [&_svg]:w-full h-4 w-4', { "text-token-text-primary": !__props.disabled, "text-gray-300": __props.disabled }]),
innerHTML: Vue.toDisplayString(__props.icon)
}, []
)
])
: [],
Vue.createVNode("div", null, [
__props.beta ?
Vue.createVNode("div", { class: "flex items-center gap-2" }, [
Vue.createVNode("span", null, [
Vue.createTextVNode(Vue.toDisplayString(__props.title), 1)
]),
Vue.createVNode("div", {
class: "border-token-text-quartenary items-center rounded-full border px-1.5 py-0.5 text-[8px] font-semibold leading-3 text-token-text-secondary dark:border-token-border-heavy dark:text-token-text-tertiary"
}, [Vue.createTextVNode('测试版')])
]) :
Vue.createTextVNode(Vue.toDisplayString(__props.title), 1),
__props.description ?
Vue.createVNode("div", { class: Vue.normalizeClass(['text-xs', { "text-token-text-secondary": !__props.disabled }]) }, [
Vue.createTextVNode(Vue.toDisplayString(__props.description), 1)
]) : null,
])
])
]),
(Vue.openBlock(), Vue.createBlock(Vue.resolveDynamicComponent(__props.isSelected ? CircleCheckIcon : []), { class: "icon-md flex-shrink-0" }))
])
], 2);
};
}
};
const ChevronDownIcon = _export_sfc({}, [["render", (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock("svg", {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
class: "icon-md text-token-text-tertiary"
}, [
Vue.createVNode("path", {
d: "M5.29289 9.29289C5.68342 8.90237 6.31658 8.90237 6.70711 9.29289L12 14.5858L17.2929 9.29289C17.6834 8.90237 18.3166 8.90237 18.7071 9.29289C19.0976 9.68342 19.0976 10.3166 18.7071 10.7071L12.7071 16.7071C12.5196 16.8946 12.2652 17 12 17C11.7348 17 11.4804 16.8946 11.2929 16.7071L5.29289 10.7071C4.90237 10.3166 4.90237 9.68342 5.29289 9.29289Z",
fill: "currentColor"
}, null, -1)
]);
}]]);
function getState() {
const defaultState = {
isEnabled: false,
selectedModelSlug: "gpt-4o-mini",
selectedModelName: "4o mini"
};
try {
const savedState = GM_getValue('state');
if (savedState) {
return { ...defaultState, ...JSON.parse(savedState) };
}
} catch (error) { }
GM_setValue('state', JSON.stringify(defaultState))
return defaultState;
}
const state = Vue.reactive(getState());
Vue.watch(state, (newState) => {
GM_setValue('state', JSON.stringify(newState))
});
const _sfc_main = {
__name: "App",
setup(__props) {
const panelButtonRight = Vue.ref();
const stylePopoverPanel = Vue.ref();
const groupedModels = Vue.computed(() => {
const uniqueModels = new Map();
const officialModels = models.all.filter((model) => !model.tags.includes("unofficial"));
officialModels.forEach((model) => {
uniqueModels.set(model.slug, model);
});
const unofficialModels = models.all.filter((model) => model.tags.includes("unofficial"));
unofficialModels.forEach((model) => {
if (!uniqueModels.has(model.slug)) {
uniqueModels.set(model.slug, model);
}
});
let final = [
{
title: '模型',
models: Array.from(uniqueModels.values()).filter((model) => !model.tags.includes("unofficial"))
},
{
title: '隐藏',
models: Array.from(uniqueModels.values()).filter((model) => model.tags.includes("unofficial"))
}
].filter((group) => group.models.length > 0); // 只保留非空分组
return final;
});
const selectModel = (model) => {
state.selectedModelSlug = model.slug;
state.selectedModelName = model.subTitle;
};
return (_ctx, _cache) => {
return Vue.openBlock(), Vue.createBlock(Vue.Fragment, null, [
Vue.createVNode(Vue.unref(Popover), { class: "relative" }, {
default: Vue.withCtx(() => [
Vue.createVNode(Vue.unref(PopoverButton), {
class: "group flex cursor-pointer items-center gap-1 rounded-lg py-1.5 px-3 text-lg font-semibold hover:bg-token-main-surface-secondary radix-state-open:bg-token-main-surface-secondary text-token-text-secondary overflow-hidden whitespace-nowrap",
"aria-label": "模型选择器" + (Vue.unref(state).isEnabled ? (",当前模型为 " + state.selectedModelName) : ""),
onclick: () => {
let buttonRight = panelButtonRight.value.getBoundingClientRect();
let clientWidth = document.documentElement.clientWidth;
stylePopoverPanel.value = {
position: "fixed",
right: `${clientWidth - buttonRight.right}px`,
top: `${buttonRight.bottom + 10}px`,
};
}
}, {
default: Vue.withCtx(() => [
Vue.createVNode(
'div',
{
class: 'text-token-text-secondary',
},
{
default: Vue.withCtx(() => [
Vue.createTextVNode(Vue.unref(state).isEnabled ? state.selectedModelName : "模型库"),
Vue.createVNode('span', { class: 'text-token-text-secondary' })
])
}
),
Vue.createVNode(ChevronDownIcon, { class: "text-token-text-tertiary" })
]),
_: 1
}),
Vue.createVNode("div", {
ref_key: "panelButtonRight",
ref: panelButtonRight,
class: "absolute right-0",
}, null, 512),
(Vue.openBlock(), Vue.createBlock(Vue.Teleport, { to: "body" }, [
Vue.createVNode(Vue.Transition, {
"enter-active-class": "transition ease-out duration-50",
"enter-from-class": "opacity-0 translate-y-1",
"enter-to-class": "opacity-100 translate-y-0",
"leave-active-class": "transition ease-in duration-50",
"leave-from-class": "opacity-100 translate-y-0",
"leave-to-class": "opacity-0 translate-y-1"
}, {
default: Vue.withCtx(() => [
Vue.createVNode(Vue.unref(PopoverPanel), {
style: Vue.normalizeStyle(stylePopoverPanel.value),
class: "z-50 max-w-xs rounded-2xl popover bg-token-main-surface-primary shadow-lg will-change-[opacity,transform] radix-side-bottom:animate-slideUpAndFade radix-side-left:animate-slideRightAndFade radix-side-right:animate-slideLeftAndFade radix-side-top:animate-slideDownAndFade border border-token-border-light py-2 min-w-[340px] max-h-[80vh] overflow-auto",
}, {
default: Vue.withCtx(() => [
Vue.openBlock(), Vue.createBlock(SwitchGroup, {
title: '启用模型切换器',
isSelected: Vue.unref(state).isEnabled,
icon: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0">
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M11.5677 3.5C11.2129 3.5 10.8847 3.68789 10.7051 3.99377L9.89391 5.37524C9.3595 6.28538 8.38603 6.84786 7.3304 6.85645L5.73417 6.86945C5.3794 6.87233 5.0527 7.06288 4.87559 7.3702L4.43693 8.13135C4.2603 8.43784 4.25877 8.81481 4.43291 9.12273L5.22512 10.5235C5.74326 11.4397 5.74326 12.5603 5.22512 13.4765L4.43291 14.8773C4.25877 15.1852 4.2603 15.5622 4.43693 15.8687L4.87559 16.6298C5.0527 16.9371 5.3794 17.1277 5.73417 17.1306L7.33042 17.1436C8.38605 17.1522 9.35952 17.7146 9.89393 18.6248L10.7051 20.0062C10.8847 20.3121 11.2129 20.5 11.5677 20.5H12.4378C12.7926 20.5 13.1208 20.3121 13.3004 20.0062L14.1116 18.6248C14.646 17.7146 15.6195 17.1522 16.6751 17.1436L18.2714 17.1306C18.6262 17.1277 18.9529 16.9371 19.13 16.6298L19.5687 15.8687C19.7453 15.5622 19.7468 15.1852 19.5727 14.8773L18.7805 13.4765C18.2623 12.5603 18.2623 11.4397 18.7805 10.5235L19.5727 9.12273C19.7468 8.81481 19.7453 8.43784 19.5687 8.13135L19.13 7.3702C18.9529 7.06288 18.6262 6.87233 18.2714 6.86945L16.6751 6.85645C15.6195 6.84786 14.646 6.28538 14.1116 5.37524L13.3004 3.99377C13.1208 3.68788 12.7926 3.5 12.4378 3.5H11.5677ZM8.97978 2.98131C9.5186 2.06365 10.5033 1.5 11.5677 1.5H12.4378C13.5022 1.5 14.4869 2.06365 15.0257 2.98131L15.8369 4.36278C16.015 4.66616 16.3395 4.85365 16.6914 4.85652L18.2877 4.86951C19.352 4.87818 20.3321 5.4498 20.8635 6.37177L21.3021 7.13292C21.832 8.05239 21.8366 9.18331 21.3142 10.1071L20.522 11.5078C20.3493 11.8132 20.3493 12.1868 20.522 12.4922L21.3142 13.893C21.8366 14.8167 21.832 15.9476 21.3021 16.8671L20.8635 17.6282C20.3321 18.5502 19.352 19.1218 18.2877 19.1305L16.6914 19.1435C16.3395 19.1464 16.015 19.3339 15.8369 19.6372L15.0257 21.0187C14.4869 21.9363 13.5022 22.5 12.4378 22.5H11.5677C10.5033 22.5 9.5186 21.9363 8.97978 21.0187L8.16863 19.6372C7.99049 19.3339 7.666 19.1464 7.31413 19.1435L5.71789 19.1305C4.65357 19.1218 3.67347 18.5502 3.14213 17.6282L2.70347 16.8671C2.17358 15.9476 2.16899 14.8167 2.6914 13.893L3.48361 12.4922C3.65632 12.1868 3.65632 11.8132 3.48361 11.5078L2.6914 10.1071C2.16899 9.18331 2.17358 8.05239 2.70347 7.13292L3.14213 6.37177C3.67347 5.4498 4.65357 4.87818 5.71789 4.86951L7.31411 4.85652C7.66599 4.85366 7.99048 4.66616 8.16862 4.36278L8.97978 2.98131Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M12.0028 10.5C11.1741 10.5 10.5024 11.1716 10.5024 12C10.5024 12.8284 11.1741 13.5 12.0028 13.5C12.8315 13.5 13.5032 12.8284 13.5032 12C13.5032 11.1716 12.8315 10.5 12.0028 10.5ZM8.50178 12C8.50178 10.067 10.0692 8.5 12.0028 8.5C13.9364 8.5 15.5038 10.067 15.5038 12C15.5038 13.933 13.9364 15.5 12.0028 15.5C10.0692 15.5 8.50178 13.933 8.50178 12Z"/>
</svg>`,
"onUpdate:isSelected": _cache[0] || (_cache[0] = ($event) => Vue.unref(state).isEnabled = $event)
}, null, 8, ["title", "isSelected", "onClick"]),
Vue.createBlock(DividerItem),
(Vue.openBlock(true), Vue.createBlock(Vue.Fragment, null, Vue.renderList(groupedModels.value, (group, index) => {
return Vue.openBlock(), Vue.createBlock(Vue.Fragment, null, [
group.title ?
Vue.createVNode(
'div',
{ class: 'mb-1 flex items-center justify-between px-5 pt-2' },
{
default: Vue.withCtx(() => [
Vue.createVNode('div',
{ class: 'mb-1 flex items-center justify-between' },
{
default: Vue.withCtx(() => [
Vue.createVNode(
'span',
{ class: 'text-sm text-token-text-tertiary' },
group.title
),
])
}),
Vue.createVNode(
'a',
{
href: 'https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4',
target: '_blank',
rel: 'noreferrer',
innerHTML: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-md h-5 w-5 pl-0.5 text-token-text-tertiary"><path d="M13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V12Z" fill="currentColor"></path><path d="M12 9.5C12.6904 9.5 13.25 8.94036 13.25 8.25C13.25 7.55964 12.6904 7 12 7C11.3096 7 10.75 7.55964 10.75 8.25C10.75 8.94036 11.3096 9.5 12 9.5Z" fill="currentColor"></path><path fillRule="evenodd" clipRule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12Z" fill="currentColor"></path></svg>',
}
),
])
}
) : [],
(Vue.openBlock(true), Vue.createBlock(Vue.Fragment, null, Vue.renderList(group.models, (model) => {
return Vue.openBlock(), Vue.createBlock(ModelItem, {
key: model.slug,
title: model.title,
description: model.description,
icon: model.icon.icon_outline_src,
beta: model.beta,
disabled: model.disabled,
isSelected: model.slug === Vue.unref(state).selectedModelSlug,
onClick: ($event) => selectModel(model),
}, null, 8, ["title", "description", "isSelected", "onClick"]);
}), 128)),
(index < groupedModels.value.length - 1)
? [
(Vue.openBlock(), Vue.createBlock(DividerItem, { key: index })),
]
: Vue.createCommentVNode("", true)
], 64);
}), 256)),
Vue.createBlock(DividerItem),
(Vue.createVNode(
'div',
{ class: 'mb-1 flex items-center justify-center px-5 pt-2' },
{
default: Vue.withCtx(() => [
Vue.createVNode(
'svg',
{
width: '24',
height: '24',
viewBox: '0 0 24 24',
fill: 'none',
xmlns: 'http://www.w3.org/2000/svg',
class: 'icon-md h-5 w-5 pl-0.5 text-token-text-tertiary mr-1',
transform: 'scale(1, -1)'
},
{
default: Vue.withCtx(() => [
Vue.createVNode('path', { d: 'M13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V12Z', fill: 'currentColor' }),
Vue.createVNode('path', { d: 'M12 9.5C12.6904 9.5 13.25 8.94036 13.25 8.25C13.25 7.55964 12.6904 7 12 7C11.3096 7 10.75 7.55964 10.75 8.25C10.75 8.94036 11.3096 9.5 12 9.5Z', fill: 'currentColor' }),
Vue.createVNode('path', { fillRule: 'evenodd', clipRule: 'evenodd', d: 'M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12Z', fill: 'currentColor' })
])
}
),
Vue.createVNode(
'span',
{ class: 'text-sm text-token-text-tertiary' },
'非会员选择会员模型会变为调用初始模型'
)
])
}
)),
]),
_: 1
}, 8, ["style"])
]),
_: 1
})
]))
]),
_: 1
}),
], 64);
};
}
};
function mountApp(mountPoint, device) {
const existingVueApp = mountPoint.__vue__;
if (existingVueApp) {
existingVueApp.unmount();
}
const container = $("<div>", {
class: "flex items-center gap-2",
id: "chatgpt-model-switcher"
});
const wrapper = $("<div>", { class: "flex gap-2 pl-1" }).append(container);
if (device === "mobile") {
wrapper.prepend($(mountPoint).children().first().next());
$(mountPoint).prepend(wrapper);
} else {
wrapper.append($(mountPoint).children().last());
$(mountPoint).append(wrapper);
}
let app = Vue.createApp(_sfc_main);
app.mount(container[0]);
}
waitForKeyElements("main div.sticky:first-of-type", function (element) {
console.log(element[0])
mountApp(element[0]);
})
waitForKeyElements("body > div > div > div.sticky", function (element) {
console.log(element[0])
mountApp(element[0], "mobile");
})
// 删除所有聊天记录(镜像站管理专用)
unsafeWindow.deleteAllChats = async function (really) {
try {
let token = unsafeWindow?.__remixContext?.state?.loaderData?.root?.clientBootstrap?.session?.accessToken;
const request = async (url, options = {}) => {
const response = await unsafeWindow.fetch(url, options);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
};
const chatRequest = async () => {
let offset = 0, pageSize = 20, fetchedChats = [];
while (true) {
let chatContent = await request(`/backend-api/conversations?offset=${offset}&limit=${pageSize}&order=updated`, {
method: 'GET',
headers: { 'Authorization': token ? `Bearer ${token}` : '' }
});
if (chatContent?.items?.length) {
fetchedChats = [...fetchedChats, ...chatContent.items];
if (fetchedChats.length >= chatContent.total) break;
offset = fetchedChats.length;
} else {
break;
}
}
return fetchedChats;
};
const removeChats = async (chats) => {
for (let chat of chats) {
let id = chat.id;
while (true) {
try {
let result = await request(`/backend-api/conversation/${id}`, {
method: 'PATCH',
headers: { 'Authorization': token ? `Bearer ${token}` : '' },
body: JSON.stringify({ is_visible: false })
});
if (result.succeed) {
console.log(`成功删除聊天:`, chat);
break; // 成功后退出重试循环,继续下一个聊天
} else {
throw new Error('请求未成功');
}
} catch (error) {
console.error(`请求失败,重试: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, 1000)); // 延时 1 秒后重试
}
}
}
};
let res = await chatRequest();
console.log('对话数据: \n', res);
if (really === true) await removeChats(res);
} catch (error) {
console.error('请求失败:', error);
}
};
})();