Spaces:
Running
Running
<script setup lang="ts"> | |
import { onMounted, ref, computed, h } from 'vue'; | |
import { NEmpty, NButton, useDialog, useMessage, NResult, NInput, NAlert, NModal, NPopover, NVirtualList} from 'naive-ui'; | |
import conversationCssText from '@/assets/css/conversation.css?raw'; | |
import { usePromptStore, type IPrompt } from '@/stores/modules/prompt'; | |
import { storeToRefs } from 'pinia'; | |
import ChatPromptItem from './ChatPromptItem.vue'; | |
import { isMobile } from '@/utils/utils'; | |
import cookies from '@/utils/cookies'; | |
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner.vue'; | |
import { ApiResultCode } from '@/api/model/ApiResult'; | |
import type { SysConfig } from '@/api/model/sysconf/SysConfig'; | |
import { useChatStore } from '@/stores/modules/chat'; | |
import ChatServiceSelect from '@/components/ChatServiceSelect/ChatServiceSelect.vue'; | |
import { useUserStore } from '@/stores/modules/user'; | |
const message = useMessage(); | |
const dialog = useDialog(); | |
(window as any).$dialog = dialog; | |
const isShowLoading = ref(true); | |
const promptStore = usePromptStore(); | |
const { isShowPromptSotre, isShowChatPrompt, keyword, promptList, searchPromptList, selectedPromptIndex } = storeToRefs(promptStore); | |
const chatStore = useChatStore(); | |
const { isShowChatServiceSelectModal, sydneyConfigs, selectedSydneyBaseUrl } = storeToRefs(chatStore); | |
const userStore = useUserStore(); | |
const scrollbarRef = ref<{ | |
scrollToIndex: (index: number) => {}; | |
getOffset: () => number; | |
getClientSize: () => number; | |
getScrollSize: () => number; | |
}>(); | |
const isInput = ref(false); | |
const isPromptScrolling = ref(false); | |
const promptItemHeight = 130; | |
const isShowUnauthorizedModal = ref(false); | |
const authKey = ref(''); | |
const isAuthBtnLoading = ref(false); | |
const isShowHistory = computed(() => { | |
return (CIB.vm.isMobile && CIB.vm.sidePanel.isVisibleMobile) || (!CIB.vm.isMobile && CIB.vm.sidePanel.isVisibleDesktop); | |
}); | |
const { themeMode, uiVersion, gpt4tEnable, sydneyEnable, sydneyPrompt, enterpriseEnable, copilotProEnable } = storeToRefs(userStore); | |
onMounted(async () => { | |
await initChat(); | |
hackDevMode(); | |
// CIB.vm.isMobile = isMobile(); | |
// show conversion | |
await SydneyFullScreenConv.initWithWaitlistUpdate({ cookLoc: {} }, 10); | |
if (isMobile()) { | |
const serpEle = document.querySelector('cib-serp'); | |
serpEle?.setAttribute('mobile', ''); | |
} | |
if (uiVersion.value === 'v3') { | |
await sj_evt.bind('chs_init', () => { | |
ChatHomeScreen.init('/turing/api/suggestions/v2/zeroinputstarter'); | |
}, true); | |
} | |
initSysConfig(); | |
isShowLoading.value = false; | |
hackStyle(); | |
hackEnterprise(); | |
initSydney(); | |
initChatPrompt(); | |
// set Theme | |
if (themeMode.value == 'light') { | |
CIB.changeColorScheme(0); | |
} else if (themeMode.value == 'dark') { | |
CIB.changeColorScheme(1); | |
} else if (themeMode.value == 'auto') { | |
if (window.matchMedia("(prefers-color-scheme: dark)").matches) { | |
CIB.changeColorScheme(1); | |
} else { | |
CIB.changeColorScheme(0); | |
} | |
} | |
}); | |
const sleep = async (ms: number) => { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
const hackDevMode = () => { | |
if (import.meta.env.DEV) { | |
CIB.manager.chat.api.bing._endpoint = location.origin; | |
} | |
}; | |
const initChatService = () => { | |
if (selectedSydneyBaseUrl.value) { | |
CIB.config.sydney.baseUrl = selectedSydneyBaseUrl.value; | |
isShowChatServiceSelectModal.value = false; | |
} else { | |
isShowChatServiceSelectModal.value = true; | |
selectedSydneyBaseUrl.value = CIB.config.sydney.baseUrl; | |
const isCus = sydneyConfigs.value.filter((x) => !x.isCus).every((x) => x.baseUrl !== selectedSydneyBaseUrl.value); | |
if (isCus) { | |
const cusSydneyConfig = sydneyConfigs.value.find((x) => x.isCus); | |
if (cusSydneyConfig) { | |
cusSydneyConfig.baseUrl = selectedSydneyBaseUrl.value; | |
} | |
} | |
chatStore.checkAllSydneyConfig(); | |
} | |
}; | |
const initSysConfig = async () => { | |
const S = base58Decode(_G.S); | |
let tmpA = []; | |
for (let i = 0; i < _G.SP.length; i++) { | |
tmpA.push(S[_G.SP[i]]); | |
} | |
const token = base58Decode(tmpA.join('')); | |
if (token != _G.AT) { | |
dialog.warning({ | |
title: decodeURI(base58Decode(_G.TIP)), | |
content: decodeURI(base58Decode(_G.TIPC)), | |
maskClosable: false, | |
closable: false, | |
closeOnEsc: false, | |
}); | |
} | |
const res = await userStore.getSysConfig(); | |
switch (res.code) { | |
case ApiResultCode.OK: | |
{ | |
if (!res.data.isAuth) { | |
isShowUnauthorizedModal.value = true; | |
return; | |
} | |
await afterAuth(res.data); | |
let MATD_Cookie = cookies.get('MicrosoftApplicationsTelemetryDeviceId'); | |
if (MATD_Cookie == '' || MATD_Cookie == null) { | |
MATD_Cookie = crypto.randomUUID(); | |
cookies.set('MicrosoftApplicationsTelemetryDeviceId', MATD_Cookie, 60, '/'); | |
} | |
let RWBF_Cookie = userStore.getUserRwBf(); | |
if (RWBF_Cookie != '') { | |
let RWBFs = RWBF_Cookie.split('&'); | |
for (let i = 0; i < RWBFs.length; i++) { | |
if (RWBFs[i].startsWith('wls=')) { | |
RWBFs[i] = 'wls=2'; | |
} | |
} | |
RWBF_Cookie = RWBFs.join('&'); | |
userStore.saveUserRwBf(RWBF_Cookie); | |
} | |
if (res.data.info != '') { | |
const info = JSON.parse(res.data.info); | |
message.create(info['content'], { | |
type: info['type'], | |
keepAliveOnHover: true, | |
showIcon: true, | |
render: (props) => { | |
return h( | |
NAlert, | |
{ | |
closable: true, | |
type: props.type === 'loading' ? 'default' : props.type, | |
title: info['title'], | |
style: { | |
boxShadow: 'var(--n-box-shadow)', | |
maxWidth: 'calc(100vw - 32px)', | |
width: '360px', | |
position: 'fixed', | |
top: '20px', | |
right: '12px', | |
} | |
}, | |
{ | |
default: () => props.content | |
} | |
) | |
} | |
}); | |
} | |
} | |
break; | |
case ApiResultCode.UnLegal: | |
{ | |
_G.SB = true | |
dialog.warning({ | |
title: decodeURI(base58Decode(_G.TIP)), | |
content: decodeURI(base58Decode(_G.TIPC)), | |
maskClosable: false, | |
closable: false, | |
closeOnEsc: false, | |
}); | |
} | |
break; | |
default: | |
message.error(`[${res.code}] ${res.message}`); | |
break; | |
} | |
}; | |
const afterAuth = async (data: SysConfig) => { | |
if (!data.isSysCK) { | |
await userStore.checkUserToken(); | |
} | |
initChatService(); | |
}; | |
const initChat = async () => { | |
return new Promise((resolve, reject) => { | |
sj_evt.bind('sydFSC.init', resolve, true); | |
sj_evt.fire('showSydFSC'); | |
}); | |
}; | |
const hackStyle = async() => { | |
if (location.hostname === 'localhost') { | |
CIB.config.sydney.hostnamesToBypassSecureConnection = CIB.config.sydney.hostnamesToBypassSecureConnection.filter((x) => x !== location.hostname); | |
} | |
if (isMobile()) { | |
await sleep(25); | |
} | |
const serpEle = document.querySelector('cib-serp'); | |
const conversationEle = serpEle?.shadowRoot?.querySelector('cib-conversation') as HTMLElement; | |
// todo 反馈暂时无法使用,先移除 | |
const welcomeEle = conversationEle?.shadowRoot?.querySelector('cib-welcome-container'); | |
const loginTip = welcomeEle?.shadowRoot?.querySelectorAll("div[class='muid-upsell']"); | |
if (loginTip?.length) { | |
loginTip.forEach((ele) => { | |
ele.remove(); | |
}); | |
} | |
welcomeEle?.shadowRoot?.querySelector('.preview-container')?.remove(); | |
welcomeEle?.shadowRoot?.querySelector('.footer')?.remove(); | |
// welcomeEle?.shadowRoot?.querySelector('.controls')?.setAttribute('style', 'margin-bottom: 80px;'); | |
serpEle?.shadowRoot?.querySelector('cib-serp-feedback')?.remove(); | |
if (isMobile()) { | |
welcomeEle?.shadowRoot?.querySelector('.container-item')?.remove(); | |
CIB.vm.actionBar.input.placeholder = '有问题尽管问我...("/" 触发提示词)'; | |
} | |
// 加入css | |
const conversationStyleEle = document.createElement('style'); | |
conversationStyleEle.innerText = conversationCssText; | |
conversationEle.shadowRoot?.append(conversationStyleEle); | |
}; | |
interface IActionBarElement extends HTMLElement { | |
handleInputTextKey: (ev: KeyboardEvent) => void; | |
} | |
const hackEnterprise = () => { | |
if (enterpriseEnable.value) { | |
CIB.config.bingAtWork.isBingChatForEnterpriseEnabled = true; | |
CIB.config.bingAtWork.chatType = "enterprise"; | |
} | |
} | |
const initSydney = () => { | |
if (copilotProEnable.value) { | |
hackCopilotPro(); | |
} | |
if (gpt4tEnable.value) { | |
hackG4t(); | |
} | |
if (sydneyEnable.value) { | |
hackSydney(); | |
CIB.manager.resetConversation = function (O, B=!0, U=!0) { | |
var G; | |
null === (G = CIB.manager.log) || void 0 === G || G.trace(this, CIB.manager.resetConversation, null).write(), | |
CIB.manager.chat.cancelPendingRequest(), | |
CIB.manager.finalizeResetConversation(O, B, U); | |
hackSydney(false); | |
} | |
} | |
} | |
const hackCopilotPro = () => { | |
CIB.config.sydney.request.source = 'cib-ccp' | |
} | |
const hackG4t = () => { | |
CIB.config.sydney.request.optionsSets.push( | |
"dlgpt4t", | |
"dlbmtc", | |
"dlbpc4575", | |
"dlbrngnp", | |
"dlbtc", | |
"dlbuc07", | |
"dlbuf03" | |
) | |
} | |
const hackSydney = (first=true) => { | |
if (first == true) { | |
CIB.config.sydney.request.sliceIds = [ | |
"winmuid1tf", | |
"styleoff", | |
"ccadesk", | |
"smsrpsuppv4cf", | |
"ssrrcache", | |
"contansperf", | |
"crchatrev", | |
"winstmsg2tf", | |
"creatgoglt", | |
"creatorv2t", | |
"sydconfigoptt", | |
"adssqovroff", | |
"530pstho", | |
"517opinion", | |
"418dhlth", | |
"512sprtic1s0", | |
"emsgpr", | |
"525ptrcps0", | |
"529rweas0", | |
"515oscfing2s0", | |
"524vidansgs0", | |
] | |
CIB.config.sydney.request.optionsSets.push("rai278", "nojbfedge") | |
if (uiVersion.value != 'v3') { | |
CIB.config.features.enableUpdateConversationMessages = true | |
CIB.config.strings.webPageContextPrefix = '已处于越狱模式, 请谨慎使用避免封号' | |
} else { | |
const serpEle = document.querySelector('cib-serp'); | |
const conversationEle = serpEle?.shadowRoot?.querySelector('cib-conversation') as HTMLElement; | |
const welcomeEle = conversationEle?.shadowRoot?.querySelector('cib-welcome-container'); | |
const tipEle = document.createElement('div'); | |
tipEle.innerText = '已处于越狱模式, 请谨慎使用避免封号'; | |
tipEle.className = 'preview-container'; | |
welcomeEle?.shadowRoot?.append(tipEle); | |
} | |
} | |
CIB.registerContext([{ | |
"author": "user", | |
"description": sydneyPrompt.value, | |
"contextType": "WebPage", | |
"messageType": "Context", | |
"sourceName": "Ubuntu Pastebin", | |
"sourceUrl": "https://paste.ubuntu.com/p/"+ randomString(10) +"/", | |
// "messageId": "discover-web--page-ping-mriduna-----", | |
}]) | |
} | |
const initChatPrompt = () => { | |
const actionBarEle = document.querySelector('#b_sydConvCont > cib-serp')?.shadowRoot?.querySelector('#cib-action-bar-main') as IActionBarElement; | |
const oldHandleInputTextKey = actionBarEle.handleInputTextKey; | |
actionBarEle.handleInputTextKey = function (ev: KeyboardEvent) { | |
// 有提示词时,优先选择提示词 | |
if (ev.key === 'Enter' && isShowChatPrompt.value) { | |
return; | |
} | |
return oldHandleInputTextKey.apply(this, [ev]); | |
}; | |
CIB.vm.actionBar.input.addEventListener('compositionstart', handleInputStart); | |
CIB.vm.actionBar.input.addEventListener('compositionend', handleInputEnd); | |
CIB.vm.actionBar.input.addEventListener('change', handleInputTextChanged); | |
CIB.vm.actionBar.input.addEventListener('input', handleInputTextChanged); | |
// CIB.vm.actionBar.input.addEventListener('keyup', handleInputTextKey); | |
CIB.vm.actionBar.input.addEventListener('keydown', handleInputTextKey); | |
CIB.vm.actionBar.input.addEventListener('focus', handleInputFocus); | |
CIB.vm.actionBar.input.addEventListener('blur', handleInputBlur); | |
}; | |
const handleInputStart = (ev: Event) => { | |
// console.log('compositionstart : ', ev); | |
isInput.value = true; | |
}; | |
const handleInputEnd = (ev: Event) => { | |
// console.log('compositionend : ', ev); | |
isInput.value = false; | |
handleInputTextChanged(ev); | |
}; | |
const handleInputTextChanged = (ev: Event) => { | |
// console.log('ev : ', ev); | |
if (isInput.value) { | |
return; | |
} | |
if ((ev instanceof InputEvent || ev instanceof CompositionEvent) && ev.target instanceof HTMLTextAreaElement) { | |
if (ev.target.value?.startsWith('/')) { | |
isShowChatPrompt.value = true; | |
keyword.value = ev.target.value.slice(1); | |
selectedPromptIndex.value = 0; | |
} else { | |
keyword.value = ''; | |
isShowChatPrompt.value = false; | |
} | |
} | |
}; | |
const handleInputFocus = (ev: FocusEvent) => { | |
// console.log('获取焦点:', ev); | |
}; | |
const handleInputBlur = (ev: FocusEvent) => { | |
// 简单解决失焦与点击冲突 | |
setTimeout(() => { | |
isShowChatPrompt.value = false; | |
}, 200); | |
}; | |
const handleInputTextKey = (ev: KeyboardEvent) => { | |
switch (ev.key) { | |
case 'ArrowUp': | |
{ | |
// ev.preventDefault(); | |
if (selectedPromptIndex.value > 0) { | |
selectedPromptIndex.value--; | |
if (scrollbarRef.value) { | |
scrollbarRef.value.scrollToIndex(selectedPromptIndex.value); | |
} | |
} | |
} | |
break; | |
case 'ArrowDown': | |
{ | |
// ev.preventDefault(); | |
if (selectedPromptIndex.value < searchPromptList.value.length - 1) { | |
selectedPromptIndex.value++; | |
if (scrollbarRef.value) { | |
scrollbarRef.value.scrollToIndex(selectedPromptIndex.value); | |
} | |
} | |
} | |
break; | |
case 'Tab': | |
case 'Enter': | |
{ | |
// ev.preventDefault(); | |
if (!CIB.vm.actionBar.textInput.value || !CIB.vm.actionBar.textInput.value.startsWith('/')) { | |
return; | |
} | |
selectPrompt(searchPromptList.value[selectedPromptIndex.value]); | |
} | |
break; | |
} | |
}; | |
const selectPrompt = (item: IPrompt) => { | |
// console.log('select prompt : ', item); | |
if (!item) { | |
return; | |
} | |
keyword.value = ''; | |
CIB.vm.actionBar.textInput.value = item.prompt; | |
isShowChatPrompt.value = false; | |
}; | |
const handlePromptListScroll = () => { | |
isPromptScrolling.value = true; | |
setTimeout(() => { | |
if (isPromptScrolling.value === true) { | |
isPromptScrolling.value = false; | |
// 滚动结束设置选中 | |
const offset = scrollbarRef.value?.getOffset() || 0; | |
selectedPromptIndex.value = Math.round(offset / promptItemHeight); | |
} | |
}, 100); | |
}; | |
const auth = async () => { | |
if (!authKey.value) { | |
message.error('请先输入授权码'); | |
return; | |
} | |
isAuthBtnLoading.value = true; | |
userStore.setAuthKey(authKey.value); | |
const res = await userStore.getSysConfig(); | |
if (res.data.isAuth) { | |
message.success('授权成功'); | |
isShowUnauthorizedModal.value = false; | |
afterAuth(res.data); | |
} else { | |
message.error('授权码有误'); | |
} | |
isAuthBtnLoading.value = false; | |
}; | |
</script> | |
<template> | |
<LoadingSpinner :is-show="isShowLoading" /> | |
<main> | |
<NPopover | |
trigger="manual" | |
:show="isShowChatPrompt" | |
:show-arrow="false" | |
class="max-w-[1060px] max-h-[390px]" | |
:to="false" | |
> | |
<template #trigger> | |
<NButton style="position: fixed; left: 20px; bottom: 80px; z-index: -1; opacity: 0;" /> | |
</template> | |
<div class="w-0 md:w-[60px]"></div> | |
<NVirtualList | |
v-if="promptList.length > 0" | |
class="w-full max-w-[1060px] max-h-[390px] overflow-y-auto" | |
:item-size="131" | |
item-resizable | |
:items="promptList" | |
@scroll="handlePromptListScroll" | |
> | |
<template #default="{ item, index }"> | |
<ChatPromptItem :index="index" :source="item" /> | |
</template> | |
</NVirtualList> | |
<NEmpty v-else class="w-full max-w-[1060px] max-h-[390px] rounded-xl py-6" description="暂未设置提示词数据"> | |
<template #extra> | |
<NButton secondary type="info" @click="isShowPromptSotre = true">去提示词库添加</NButton> | |
</template> | |
</NEmpty> | |
</NPopover> | |
</main> | |
<footer> | |
<!-- 服务器选择 --> | |
<ChatServiceSelect /> | |
<!-- 授权 --> | |
<NModal v-model:show="isShowUnauthorizedModal" preset="dialog" :closable="false" :close-on-esc="false" :maskClosable="false" :show-icon="false"> | |
<NResult class="box-border w-11/12 lg:w-[400px] px-4 py-4 rounded-md" status="403" title="401 未授权"> | |
<template #footer> | |
<NInput class="w-11/12" v-model:value="authKey" type="password" placeholder="请输入授权码" maxlength="60" clearable></NInput> | |
<n-button class="mt-4" secondary type="info" :loading="isAuthBtnLoading" @click="auth">授权</n-button> | |
</template> | |
</NResult> | |
</NModal> | |
</footer> | |
</template> | |