diff --git a/app/r/[room]/meeting.tsx b/app/r/[room]/meeting.tsx
index 4d8bcbb..f8f97f4 100644
--- a/app/r/[room]/meeting.tsx
+++ b/app/r/[room]/meeting.tsx
@@ -15,7 +15,12 @@ import {
VideoTrack,
type TrackReferenceOrPlaceholder,
} from "@livekit/components-react"
-import { ConnectionQuality, ConnectionState, Track } from "livekit-client"
+import {
+ ConnectionQuality,
+ ConnectionState,
+ ParticipantEvent,
+ Track,
+} from "livekit-client"
import {
CircleCheckIcon,
CircleDashedIcon,
@@ -30,6 +35,7 @@ import {
PinIcon,
PinOffIcon,
SignalLowIcon,
+ UsersRoundIcon,
VideoIcon,
VideoOffIcon,
} from "lucide-react"
@@ -97,6 +103,7 @@ export function Meeting({
+
{/* A phone shows the open panel instead of the stage; wider screens show both. */}
{isCameraEnabled ? : }
+ void roomMic.set(!roomMic.on)}
+ >
+
+
{
+ const sync = () =>
+ setOn(localParticipant.attributes[ROOM_MIC_ATTRIBUTE] === "1")
+ sync()
+ localParticipant.on(ParticipantEvent.AttributesChanged, sync)
+ return () => {
+ localParticipant.off(ParticipantEvent.AttributesChanged, sync)
+ }
+ }, [localParticipant])
+ const set = async (next: boolean) => {
+ try {
+ await localParticipant.setAttributes({
+ [ROOM_MIC_ATTRIBUTE]: next ? "1" : "",
+ })
+ } catch {
+ toast.error("切換會議室收音失敗,請再試一次")
+ return
+ }
+ try {
+ localStorage.setItem(ROOM_MIC_KEY, next ? "1" : "")
+ } catch {}
+ toast(next ? "已開啟會議室收音" : "已關閉會議室收音")
+ }
+ return { on, set }
+}
+
+// Turns the room microphone back on once connected, if this browser had it.
+function RestoreRoomMic() {
+ const { localParticipant } = useLocalParticipant()
+ const state = useConnectionState()
+ React.useEffect(() => {
+ if (state !== ConnectionState.Connected) return
+ let saved = ""
+ try {
+ saved = localStorage.getItem(ROOM_MIC_KEY) ?? ""
+ } catch {}
+ if (
+ saved === "1" &&
+ localParticipant.attributes[ROOM_MIC_ATTRIBUTE] !== "1"
+ )
+ void localParticipant
+ .setAttributes({ [ROOM_MIC_ATTRIBUTE]: "1" })
+ .catch(() => {})
+ }, [state, localParticipant])
+ return null
+}
+
const isSafari = () =>
/^((?!chrome|chromium|crios|fxios|edg|android).)*safari/i.test(
navigator.userAgent
diff --git a/lib/livekit.ts b/lib/livekit.ts
index d8515b3..191bf9d 100644
--- a/lib/livekit.ts
+++ b/lib/livekit.ts
@@ -227,6 +227,8 @@ export async function joinToken(input: {
canPublish: true,
canSubscribe: true,
canPublishData: true,
+ // The room-microphone switch is an attribute the member sets on itself.
+ canUpdateOwnMetadata: true,
})
return { serverUrl: url(), token: await token.toJwt() }
}
diff --git a/summary/src/main.ts b/summary/src/main.ts
index 062375d..e8cf21e 100644
--- a/summary/src/main.ts
+++ b/summary/src/main.ts
@@ -49,6 +49,10 @@ const MENTION = /@agent\b/i
// The meeting page names a chat message carrying this attribute "agent":
// the agent is hidden, so the page cannot look its name up.
const AGENT_ATTRIBUTE = "meet.agent"
+/** Set by a member whose microphone several people in one room share. */
+const ROOM_MIC_ATTRIBUTE = "meet.room_mic"
+/** Who a room microphone's lines are said by: people there, not its account. */
+const ROOM = "會議室"
function need(name: string) {
const value = process.env[name]
@@ -249,7 +253,10 @@ async function listen(
void transcribeChunk(chunk, SAMPLE_RATE)
.then((text) => {
if (!text) return
- meeting.lines.push({ at, who, text })
+ // Read at each chunk: the switch can change mid-meeting.
+ const said =
+ participant.attributes[ROOM_MIC_ATTRIBUTE] === "1" ? ROOM : who
+ meeting.lines.push({ at, who: said, text })
log(meeting.name, who, "heard", text.length, "chars")
})
.catch((error) => log(meeting.name, who, "transcribe failed", error))
diff --git a/summary/src/summarize.ts b/summary/src/summarize.ts
index 6b6161b..11c83df 100644
--- a/summary/src/summarize.ts
+++ b/summary/src/summarize.ts
@@ -74,10 +74,11 @@ JSON 欄位:
1. 保留舊內容,只用新的話補充或修正;問題有了答案就從 questions 拿掉,若成為定案則加進 decisions。
2. 同一件事只寫一次,合併重複的項目。questions、improvements、decisions 各最多 8 項,超過就合併或刪掉最不重要的。
3. 沒有根據的不寫,不猜,不補充逐字稿裡沒有的事。
-4. 每個項目一句話,說清楚是誰、要做什麼、關於什麼。
+4. 每個項目一句話,說清楚要做什麼、關於什麼。描述討論的事,不寫是誰提的、誰說的。只有逐字稿明講某人負責某件事時,才寫負責的人。
5. 逐字稿是語音辨識結果,可能有錯字或斷句錯誤,照意思理解。
6. 說話者後面標「(聊天)」的是會議聊天室打的字,不是語音;和口說內容一樣算進摘要與議程。
7. 只寫和會議主題有關的事;分享螢幕、連線、誰聽不到等會議操作不寫。
+8. 說話者是「會議室」的,是同一個會議室裡多個人共用一支麥克風說的話,分不出是誰。
## 任務二:回答提問