教师端的协作编辑器只有高亮和括号匹配,没装 autocompletion —— 老师替学生 写代码时没有下拉补全,只能盲敲。而且语言写死 cpp(),学生写 Python 时老师 看到的是 C 的高亮。 - 求助协议带上语言:help_request 记到 HelpRequest,accept 时写进 Room, room_open 发给两端;认不出的语言一律当 C(老客户端不带这个字段,而它 以前就是写死 C 的) - 新增 help_language / room_language:学生在排队或协作期间切语言,只更新 语言,不动队列位置和房间;已开房就把新语言推给老师,弹框的高亮和补全 实时跟着换 - CollabModal 装上 autocompletion,和学生端用同一套 enhanceCompletion + completeAnyWord;语言→高亮扩展的映射抽到 shared/extensions/language.ts 两端共用,免得再分叉 - 学生端求助按钮合并状态提示:原来按钮旁边还挂一个 n-tag,一行工具栏在 1280 的机房屏上放不下,改成状态全进 label(已求助 · 待接入 / 已求助 · 前面 N 人 / xxx 老师帮你中) 扩展数组变了 vue-codemirror 会整体 reconfigure,但 CM6 对已存在的 compartment 取 `compartments.get() || ext.inner`,collabDoc 那个装 yCollab 的 compartment 内容会被沿用,切语言、切主题都不会把协作弄断 —— 实跑验证过 双向同步、补全下拉、协作中切语言三条路径。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015APGLsyaCUa7XFMYAaCho4
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
getRoom,
|
||||
hasTeacherOnline,
|
||||
listRequests,
|
||||
normalizeLanguage,
|
||||
openRoom,
|
||||
queueAheadOf,
|
||||
removeRequest,
|
||||
@@ -179,7 +180,12 @@ export function handleCollabClose(ws: CollabSocket) {
|
||||
}
|
||||
|
||||
export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
||||
let message: { type?: unknown; problemId?: unknown; studentId?: unknown }
|
||||
let message: {
|
||||
type?: unknown
|
||||
problemId?: unknown
|
||||
studentId?: unknown
|
||||
language?: unknown
|
||||
}
|
||||
try {
|
||||
message = JSON.parse(raw) as typeof message
|
||||
} catch {
|
||||
@@ -201,7 +207,10 @@ export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
||||
|
||||
switch (message.type) {
|
||||
case "help_request":
|
||||
await handleHelpRequest(ws, message.problemId)
|
||||
await handleHelpRequest(ws, message.problemId, message.language)
|
||||
return
|
||||
case "help_language":
|
||||
handleHelpLanguage(ws, message.language)
|
||||
return
|
||||
case "help_cancel":
|
||||
handleHelpCancel(ws)
|
||||
@@ -220,7 +229,11 @@ export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
||||
async function handleHelpRequest(
|
||||
ws: CollabSocket,
|
||||
problemId: unknown,
|
||||
language: unknown,
|
||||
) {
|
||||
if (typeof problemId !== "string" || !problemId) {
|
||||
ws.send(JSON.stringify({ type: "error", message: "Invalid problemId" }))
|
||||
return
|
||||
@@ -266,6 +279,7 @@ async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
||||
className: student?.className ?? null,
|
||||
problemId,
|
||||
problemTitle: problem.title,
|
||||
language: normalizeLanguage(language),
|
||||
createdAt: Date.now(),
|
||||
status: "pending",
|
||||
socket: ws,
|
||||
@@ -274,6 +288,29 @@ async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
||||
broadcastRequests()
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生在求助期间换了语言。
|
||||
*
|
||||
* 只更新语言,不动队列位置、不动房间 —— 换语言不该让他重新排队。已经在协作中的
|
||||
* 话再推一条 room_language,老师端的高亮和补全立刻跟着换;不推的话老师会拿着
|
||||
* 建房那一刻的语言,对着一套错的补全替学生写代码。
|
||||
*/
|
||||
function handleHelpLanguage(ws: CollabSocket, language: unknown) {
|
||||
const request = getRequest(ws.data.userId)
|
||||
// 比对 socket 归属:同账号的另一个标签页停在别的题上切语言,不该改这条求助
|
||||
if (!request || request.socket !== ws) return
|
||||
const next = normalizeLanguage(language)
|
||||
if (request.language === next) return
|
||||
request.language = next
|
||||
|
||||
const room = getRoom(ws.data.userId)
|
||||
if (!room) return
|
||||
room.language = next
|
||||
room.teacherSocket.send(
|
||||
JSON.stringify({ type: "room_language", language: next }),
|
||||
)
|
||||
}
|
||||
|
||||
function handleHelpCancel(ws: CollabSocket) {
|
||||
const request = getRequest(ws.data.userId)
|
||||
// 不比对 socket 归属:取消的是这个学生自己的求助,不管从他哪个标签页发起都
|
||||
@@ -341,6 +378,7 @@ async function handleAccept(ws: CollabSocket, studentId: unknown) {
|
||||
studentSocket: request.socket,
|
||||
teacherSocket: ws,
|
||||
problemId: request.problemId,
|
||||
language: request.language,
|
||||
})
|
||||
|
||||
const openFrame = (peerName: string, peerRole: "student" | "teacher") =>
|
||||
@@ -348,6 +386,7 @@ async function handleAccept(ws: CollabSocket, studentId: unknown) {
|
||||
type: "room_open",
|
||||
peer: { name: peerName, role: peerRole },
|
||||
problemId: request.problemId,
|
||||
language: request.language,
|
||||
})
|
||||
request.socket.send(openFrame(request.teacherName, "teacher"))
|
||||
ws.send(openFrame(request.studentName, "student"))
|
||||
|
||||
@@ -8,6 +8,30 @@
|
||||
|
||||
export type CollabSocket = Bun.ServerWebSocket<import("../websocket").SubmissionSocketData>
|
||||
|
||||
/**
|
||||
* 协作支持的语言。和前端 utils/types.ts 里的 LANGUAGE 对齐,去掉 Flowchart ——
|
||||
* 流程图题没有代码编辑器,求助入口本身就是隐藏的。
|
||||
*/
|
||||
export const COLLAB_LANGUAGES = [
|
||||
"C",
|
||||
"C++",
|
||||
"Python2",
|
||||
"Python3",
|
||||
"Java",
|
||||
"JavaScript",
|
||||
"Golang",
|
||||
"SQL",
|
||||
] as const
|
||||
|
||||
export type CollabLanguage = (typeof COLLAB_LANGUAGES)[number]
|
||||
|
||||
/** 认不出来的一律当 C:老客户端不带这个字段,而它以前就是写死 C 的 */
|
||||
export function normalizeLanguage(value: unknown): CollabLanguage {
|
||||
return (COLLAB_LANGUAGES as readonly string[]).includes(value as string)
|
||||
? (value as CollabLanguage)
|
||||
: "C"
|
||||
}
|
||||
|
||||
export interface HelpRequest {
|
||||
studentId: number
|
||||
studentName: string
|
||||
@@ -15,6 +39,11 @@ export interface HelpRequest {
|
||||
/** 题目的展示号(problem._id 列,前端一路用的都是它),不是自增主键 */
|
||||
problemId: string
|
||||
problemTitle: string
|
||||
/**
|
||||
* 学生编辑器当前的语言。决定教师端弹框用哪套高亮和补全 —— 求助时带上,
|
||||
* 协作期间学生切语言会用 help_language 更新这里
|
||||
*/
|
||||
language: CollabLanguage
|
||||
createdAt: number
|
||||
status: "pending" | "active"
|
||||
teacherId?: number
|
||||
@@ -83,6 +112,8 @@ export interface Room {
|
||||
studentSocket: CollabSocket
|
||||
teacherSocket: CollabSocket
|
||||
problemId: string
|
||||
/** 建房那一刻学生的语言,之后跟着 help_language 走 */
|
||||
language: CollabLanguage
|
||||
}
|
||||
|
||||
const rooms = new Map<number, Room>()
|
||||
|
||||
Reference in New Issue
Block a user