diff --git a/.env b/.env index 2538e06..9dc1de9 100644 --- a/.env +++ b/.env @@ -3,5 +3,5 @@ PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb PUBLIC_OJ_URL=http://localhost:8000 PUBLIC_CODE_URL=http://localhost:3000 PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc -PUBLIC_SIGNALING_URL=wss://signaling.xuyue.cc -PUBLIC_WS_URL=wss://oj.xuyue.cc/ws \ No newline at end of file +PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 +PUBLIC_WS_URL=ws://10.13.114.114:81/ws \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 0392757..baf7621 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,7 +2,24 @@ import { darkTheme, dateZhCN, zhCN } from "naive-ui" import "normalize.css" import "./index.css" +import { useConfigStore } from "shared/store/config" +import { useConfigUpdate } from "shared/composables/configUpdate" +import { useMaxKB } from "shared/composables/maxkb" +import { useUserStore } from "shared/store/user" + const isDark = useDark() +const configStore = useConfigStore() +const userStore = useUserStore() + +// 初始化配置和实时更新 +onMounted(() => { + configStore.getConfig() + userStore.getMyProfile() +}) + +// 使用配置更新和 MaxKB 功能 +useConfigUpdate() +useMaxKB() // 延迟加载 highlight.js,避免阻塞首屏 const hljsInstance = ref(null) diff --git a/src/admin/setting/config.vue b/src/admin/setting/config.vue index a193090..a85d342 100644 --- a/src/admin/setting/config.vue +++ b/src/admin/setting/config.vue @@ -2,6 +2,8 @@ import { NButton, NTag } from "naive-ui" import { parseTime } from "utils/functions" import { Server } from "utils/types" +import { useConfigStore } from "shared/store/config" +import { useConfigWebSocket } from "shared/composables/websocket" import { deleteJudgeServer, editWebsite, @@ -17,6 +19,8 @@ interface Testcase { } const message = useMessage() +const configStore = useConfigStore() +const { updateConfig } = useConfigWebSocket() const testcaseColumns: DataTableColumn[] = [ { title: "测试用例 ID", key: "id" }, @@ -106,6 +110,7 @@ const websiteConfig = reactive({ allow_register: true, submission_list_show_all: true, class_list: [], + enable_maxkb: true, }) async function getWebsiteConfig() { @@ -117,12 +122,18 @@ async function getWebsiteConfig() { websiteConfig.allow_register = res.data.allow_register websiteConfig.submission_list_show_all = res.data.submission_list_show_all websiteConfig.class_list = res.data.class_list + websiteConfig.enable_maxkb = res.data.enable_maxkb } async function saveWebsiteConfig() { await editWebsite(websiteConfig) message.success("网站配置保存成功") getWebsiteConfig() + configStore.getConfig() + + // 通过 WebSocket 广播配置变化,实现实时切换 + updateConfig('enable_maxkb', websiteConfig.enable_maxkb) + updateConfig('submission_list_show_all', websiteConfig.submission_list_show_all) } async function deleteTestcase(id?: string) { @@ -198,6 +209,10 @@ onMounted(() => { 显示所有提交 + + 启用右下角 AI 入口 + + diff --git a/src/shared/components/Header.vue b/src/shared/components/Header.vue index 9508686..4d4c020 100644 --- a/src/shared/components/Header.vue +++ b/src/shared/components/Header.vue @@ -83,11 +83,6 @@ function renderIcon(icon: string) { return () => h(Icon, { icon, width: 24, height: 24 }) } -onMounted(() => { - userStore.getMyProfile() - configStore.getConfig() -}) - const menus = computed(() => [ { label: () => h(RouterLink, { to: "/learn/01" }, { default: () => "自学" }), diff --git a/src/shared/composables/configUpdate.ts b/src/shared/composables/configUpdate.ts new file mode 100644 index 0000000..4923a25 --- /dev/null +++ b/src/shared/composables/configUpdate.ts @@ -0,0 +1,44 @@ +import { useConfigStore } from "shared/store/config" +import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket" + +export function useConfigUpdate() { + const configStore = useConfigStore() + + // 处理 WebSocket 配置更新 + const handleConfigUpdate = (data: ConfigUpdate) => { + console.log("收到配置更新:", data) + + // 更新全局配置 - 使用响应式方式 + if (data.key in configStore.config) { + // 直接修改 ref 的值来触发响应式更新 + (configStore.config as any)[data.key] = data.value + console.log(`配置 ${data.key} 已更新为:`, data.value) + } + } + + // 初始化 WebSocket - handler 会在 onMounted 时自动添加 + const { connect, status } = useConfigWebSocket(handleConfigUpdate) + + // 连接 WebSocket + onMounted(() => { + console.log("配置更新 WebSocket 正在连接...") + connect() + + // 测试连接 - 发送心跳包 + setTimeout(() => { + if (status.value === "connected") { + console.log("配置更新 WebSocket 连接成功,发送测试消息...") + // 这里可以发送一个测试消息 + } + }, 2000) + }) + + // 监听连接状态 + watch(status, (newStatus) => { + console.log("配置更新 WebSocket 状态:", newStatus) + }) + + return { + connect + } +} diff --git a/src/shared/composables/maxkb.ts b/src/shared/composables/maxkb.ts new file mode 100644 index 0000000..41ccc5a --- /dev/null +++ b/src/shared/composables/maxkb.ts @@ -0,0 +1,89 @@ +import { useConfigStore } from "shared/store/config" +import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket" + +export function useMaxKB() { + const configStore = useConfigStore() + const isLoaded = ref(false) + + // 处理 WebSocket 配置更新 - 只处理 MaxKB 相关 + const handleConfigUpdate = (data: ConfigUpdate) => { + console.log("MaxKB 收到配置更新:", data) + if (data.key === 'enable_maxkb') { + if (data.value) { + loadMaxKBScript() + } else { + removeMaxKBScript() + } + } + } + + // 初始化 WebSocket + const { connect } = useConfigWebSocket(handleConfigUpdate) + + const loadMaxKBScript = () => { + const { enable_maxkb } = configStore.config + + if (!enable_maxkb) { + return + } + + const existingScript = document.querySelector(`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`) + if (existingScript) { + isLoaded.value = true + return + } + + // 创建并插入脚本标签 + const script = document.createElement("script") + script.src = import.meta.env.PUBLIC_MAXKB_URL + script.async = true + script.defer = true + + script.onload = () => { + isLoaded.value = true + console.log("MaxKB script loaded successfully") + } + + script.onerror = () => { + console.error("Failed to load MaxKB script") + } + + document.head.appendChild(script) + } + + const removeMaxKBScript = () => { + isLoaded.value = false + const maxkbChatButton = document.getElementById("maxkb-chat-button") + const maxkbChatContainer = document.getElementById("maxkb-chat-container") + if (maxkbChatButton) { + maxkbChatButton.remove() + } + if (maxkbChatContainer) { + maxkbChatContainer.remove() + } + } + + // 连接 WebSocket + onMounted(() => { + connect() + }) + + watch( + () => configStore.config.enable_maxkb, + (newValue) => { + if (newValue) { + loadMaxKBScript() + } else { + console.log("removeMaxKBScript") + removeMaxKBScript() + } + }, + { immediate: true } + ) + + return { + loadMaxKBScript, + removeMaxKBScript, + isLoaded: readonly(isLoaded) + } +} diff --git a/src/shared/composables/websocket.ts b/src/shared/composables/websocket.ts index 880a766..d1129c2 100644 --- a/src/shared/composables/websocket.ts +++ b/src/shared/composables/websocket.ts @@ -410,3 +410,63 @@ export function createWebSocketComposable( removeHandler: (h: MessageHandler) => ws.removeHandler(h), } } + +/** + * 配置更新消息类型 + */ +export interface ConfigUpdate extends WebSocketMessage { + type: "config_update" + key: string + value: any +} + +/** + * 配置 WebSocket 连接管理类 + */ +class ConfigWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "config", + }) + } + + /** + * 发送配置更新 + */ + updateConfig(key: string, value: any) { + this.send({ + type: "config_update", + key, + value, + }) + } +} + +/** + * 用于组件中使用配置 WebSocket 的 Composable + */ +export function useConfigWebSocket(handler?: MessageHandler) { + const ws = new ConfigWebSocket() + + onMounted(() => { + if (handler) { + ws.addHandler(handler) + } + }) + + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + updateConfig: (key: string, value: any) => ws.updateConfig(key, value), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} diff --git a/src/shared/store/config.ts b/src/shared/store/config.ts index 478f680..f24c741 100644 --- a/src/shared/store/config.ts +++ b/src/shared/store/config.ts @@ -10,6 +10,7 @@ export const useConfigStore = defineStore("config", () => { submission_list_show_all: true, allow_register: false, class_list: [], + enable_maxkb: true, }) async function getConfig() { const res = await getWebsiteConfig() diff --git a/src/utils/types.ts b/src/utils/types.ts index 48765ae..da92edc 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -340,6 +340,7 @@ export interface WebsiteConfig { allow_register: boolean submission_list_show_all: boolean class_list: string[] & never[] + enable_maxkb: boolean } export interface Server {