Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,15 @@ Scene interaction 是 browser client 與 Kit runtime 之間的 DataChannel JSON

## 5.4 Collaboration Flow

> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:本節
> collaboration broadcast(highlight / selection / annotation)在 coordinator 與
> viewer 兩端的 implementation 已從 fast MVP product runtime 移除(`reviewNamespace.ts`
> 內的 `highlightRequest` / `selectionUpdate` / `annotationCreate` Socket.IO event
> handlers、viewer `IssuePanel` / `EventLogPanel` 已刪)。本 sequence 保留作為
> archive context;viewer Change 2 (`fast-ifc-link-demo-loop`) 將 viewer 主畫面
> 收斂為「全螢幕 stream + 邊框 HUD」,不含多人協作 UI。若未來重新引入,以新
> OpenSpec change form ADD requirement 與 viewer slot。

```mermaid
sequenceDiagram
participant WV1 as web-viewer-sample User A
Expand All @@ -703,6 +712,13 @@ bim-streaming-server 不作為多人協作事件中心。

## 5.5 Review Result Visualization Flow

> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:本節
> review issue → DataChannel `highlightPrimsRequest` 的「issue 流」入口
> (viewer `IssuePanel` + coordinator `getReviewIssues` / `review-bootstrap`
> endpoint)已從 fast MVP product runtime 移除。DataChannel `highlightPrimsRequest`
> 本身保留作 mapping highlight 工具(Window.tsx `_onMappingItemClick`),Change 2
> 重做 viewer 時再評估。若 issue 流要重新引入,以新 OpenSpec change form ADD。

```mermaid
sequenceDiagram
participant BC as _bim-control
Expand Down Expand Up @@ -803,6 +819,13 @@ mapping runtime usage → web-viewer-sample / bim-streaming-server

## 7.4 Review 資料

> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:issue
> / annotation / review result 的 fast MVP product runtime 已移除(`ReviewIssue`
> interface、`getReviewIssues` / `createAnnotation` / `getReviewBootstrap` /
> `IssuePanel` / `EventLogPanel` 已刪)。本表保留作 archive context,記錄歷史權威
> 劃分。若 review 流要重新引入,以新 OpenSpec change form ADD requirement 與
> coordinator / viewer 端配套。

```txt
issue / annotation / review result
```
Expand Down
10 changes: 9 additions & 1 deletion bim-review-coordinator/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@

## Role

IFC-ready Intake / Callback Outbox / Session Control Plane — 唯一外部 IFC-ready intake;協調 browser client 與 Kit streaming server 的連線資訊;廣播 presence / selection / annotation 等多人事件;將 streaming conversion 結果放入 metadata-only callback outbox。
IFC-ready Intake / Callback Outbox / Session Control Plane — 唯一外部 IFC-ready intake;協調 browser client 與 Kit streaming server 的連線資訊;廣播 presence(`joinSession` / `leaveSession` / `presenceUpdated`)等基本 session 事件;將 streaming conversion 結果放入 metadata-only callback outbox。

> **退役狀態(2026-05-21,change `remove-conflict-review-from-fast-mvp`)**:
> `highlightRequest` / `selectionUpdate` / `annotationCreate` 等 collaboration
> Socket.IO event handlers 已從本 service 移除(`src/socket/reviewNamespace.ts`);
> `getReviewIssues` / `createAnnotation` / `/api/model-versions/:id/review-bootstrap`
> 也已刪。`/api/review-sessions/:id/events` 與 `/lifecycle-events` 仍保留;
> lifecycle endpoint 排除 collaboration event 的語意 wording 保留作 archive
> compatibility(舊 event log 仍可能含這些 type)。

埠口:`localhost:8004`(含 Socket.IO)

Expand Down
26 changes: 1 addition & 25 deletions bim-review-coordinator/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,22 +494,6 @@ export function createCoordinatorApp(overrides: Partial<CoordinatorConfig> = {})
response.json(closed);
});

app.get("/api/model-versions/:modelVersionId/review-bootstrap", async (request, response, next) => {
try {
const [artifacts, issues] = await Promise.all([
safeArtifacts(bimControlClient, request.params.modelVersionId),
safeIssues(bimControlClient, request.params.modelVersionId),
]);
response.json({
model_version_id: request.params.modelVersionId,
artifacts,
issues,
});
} catch (error) {
next(error);
}
});

// B-scheme(local-coordinator-ifc-ready-intake-boundary T3):
// 唯一對外 IFC-ready intake。caller = 客戶落地端 IFC Worker(落地端內網,
// machine-to-machine)。streaming 為 internal-only 轉檔引擎(T4)。
Expand Down Expand Up @@ -1030,7 +1014,7 @@ export function createCoordinatorApp(overrides: Partial<CoordinatorConfig> = {})
response.status(500).json({ detail: error instanceof Error ? error.message : String(error) });
});

registerReviewNamespace(io, store, eventLog, bimControlClient);
registerReviewNamespace(io, store, eventLog);

return { app, server, io, config, store, eventLog };
}
Expand Down Expand Up @@ -1105,14 +1089,6 @@ async function safeArtifacts(client: BimControlClient, modelVersionId: string):
}
}

async function safeIssues(client: BimControlClient, modelVersionId: string) {
try {
return await client.getReviewIssues(modelVersionId);
} catch {
return [];
}
}

async function proxyConversionService(
response: express.Response,
conversionApiBase: string,
Expand Down
86 changes: 1 addition & 85 deletions bim-review-coordinator/src/public/dev-console.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,13 @@
<body>
<header class="demo-header">
<div class="demo-header__brand">BIM 審查雲端 Demo|審查協調 (Review Coordinator)</div>
<div class="demo-header__step-label">步驟 ③ / 5</div>
<div class="demo-header__step-label">步驟 ③ / 3</div>
</header>

<nav class="demo-stepbar" aria-label="Demo 流程">
<a class="demo-stepbar__item" href="#uploadDemoCard" onclick="openUploadPicker(event)"><span class="demo-stepbar__num">①</span>上傳建模 (Upload)</a>
<a class="demo-stepbar__item" href="#conversionDemoCard" onclick="startCoordinatorConversion(event, true)"><span class="demo-stepbar__num">②</span>自動轉換 (Convert)</a>
<a class="demo-stepbar__item demo-stepbar__item--active" href="#" aria-current="step"><span class="demo-stepbar__num">③</span>建立會議 (Meeting)</a>
<a class="demo-stepbar__item" href="http://127.0.0.1:5173"><span class="demo-stepbar__num">④</span>標記問題 (Mark)</a>
<a class="demo-stepbar__item" href="/dev-console"><span class="demo-stepbar__num">⑤</span>紀錄回寫 (Record)</a>
</nav>

<main class="demo-main">
Expand Down Expand Up @@ -353,47 +351,6 @@ <h3 class="demo-lab-card__title">加入即時頻道</h3>
<button type="button" onclick="guidedJoinChannel()">連線並加入會議</button>
</article>

<article class="demo-lab-card">
<div class="demo-lab-card__top">
<div>
<span class="demo-lab-card__step">④</span>
<h3 class="demo-lab-card__title">標示問題位置</h3>
<div class="demo-lab-card__route">coordinator → viewer → streaming server</div>
</div>
<span class="demo-status demo-status--warn">需 viewer 開啟</span>
</div>
<p class="demo-lab-card__copy">送出 highlightRequest,示範審查人員點到問題元件後,如何同步到瀏覽器審查端。</p>
<div class="demo-lab-card__effect">觀察效果:event feed 出現「送出問題高亮」;5173 viewer 會收到 Socket.IO 事件並可再送 DataChannel 高亮。</div>
<button type="button" onclick="guidedHighlightIssue()">同步問題高亮</button>
</article>

<article class="demo-lab-card">
<div class="demo-lab-card__top">
<div>
<span class="demo-lab-card__step">⑤</span>
<h3 class="demo-lab-card__title">建立審查標註</h3>
<div class="demo-lab-card__route">coordinator → company-cloud callback outbox</div>
</div>
<span class="demo-status demo-status--ok">可操作</span>
</div>
<p class="demo-lab-card__copy">把審查意見變成 annotation event,示範哪些資料會被視為 review metadata。</p>
<div class="demo-lab-card__effect">觀察效果:event feed 出現「建立審查標註」與 ack;後續由 outbox 追蹤雲端回寫狀態。</div>
<button type="button" onclick="guidedAnnotation()">建立示範標註</button>
</article>

<article class="demo-lab-card">
<div class="demo-lab-card__top">
<div>
<span class="demo-lab-card__step">②</span>
<h3 class="demo-lab-card__title">轉檔資料流</h3>
<div class="demo-lab-card__route">external IFC Worker → coordinator → streaming</div>
</div>
<span class="demo-status demo-status--warn">部分未完成</span>
</div>
<p class="demo-lab-card__copy">展示 IFC 來源檔如何產生 USDC 與 element_mapping.json,並讓產物 URL 回到 session。</p>
<div class="demo-lab-card__gap">步驟 ①/② 由外部 IFC Worker 送出 ifc-ready;這裡只展示 session 端如何消費 ready artifact binding。</div>
<button type="button" onclick="guidedConversion()">展示轉檔流程</button>
</article>
</div>

<div class="demo-lab-flow">
Expand Down Expand Up @@ -465,9 +422,6 @@ <h3>Socket.IO /review</h3>
<button onclick="disconnectSocket()">disconnect</button>
<button onclick="emitJoin()">joinSession</button>
<button onclick="emitLeave()">leaveSession</button>
<button onclick="emitHighlight()">highlightRequest</button>
<button onclick="emitSelection()">selectionUpdate</button>
<button onclick="emitAnnotation()">annotationCreate</button>
<button onclick="emitHeartbeat()">heartbeat</button>

<h3>Raw HTTP response</h3>
Expand Down Expand Up @@ -988,40 +942,6 @@ <h3>Socket events</h3>
}
}

async function guidedHighlightIssue() {
setGuidedOutcome('warn', '正在同步問題高亮', 'coordinator 會廣播 highlightRequest;viewer 可把事件轉成 DataChannel 3D 高亮。');
try {
await ensureGuidedSocket();
emitHighlight();
appendDemoFeed('送出問題高亮', { actor_id: userId.value, target: '/World', issue_id: 'ISSUE-DEMO-001' });
setGuidedOutcome('ok', '問題高亮事件已送出', '請觀察 Live review events;若 5173 viewer 已開啟,也會在 Socket.IO events 看見 highlightRequest。');
} catch (error) {
setGuidedOutcome('bad', '問題高亮失敗', error instanceof Error ? error.message : String(error));
}
}

async function guidedAnnotation() {
setGuidedOutcome('warn', '正在建立審查標註', 'annotation 會經 coordinator 進入 fake review metadata 流程,作為 Step ⑤ 回寫示範。');
try {
await ensureGuidedSocket();
emitAnnotation();
appendDemoFeed('建立審查標註', { actor_id: userId.value, target: '/World' });
setGuidedOutcome('ok', '標註事件已送出', 'event feed 會顯示 annotationCreate;若後端 ack 成功,代表 fake metadata 回寫流程可用。');
} catch (error) {
setGuidedOutcome('bad', '建立標註失敗', error instanceof Error ? error.message : String(error));
}
}

async function guidedConversion() {
setGuidedOutcome('warn', '正在展示轉檔資料流', '會用 dev mock conversion 顯示 IFC → USDC + mapping 的階段與產物 URL。');
try {
await startCoordinatorConversion(null, true);
setGuidedOutcome('ok', '轉檔展示已完成', '請觀察 Step ② 卡片的進度、job id、USDC URL 與 mapping URL;未完成的 storage upload 已明確標示。');
} catch (error) {
setGuidedOutcome('bad', '轉檔展示失敗', error instanceof Error ? error.message : String(error));
}
}

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Expand Down Expand Up @@ -1099,10 +1019,6 @@ <h3>Socket events</h3>
if (m === 'disconnect') return '即時頻道中斷';
if (m.startsWith('joinSession')) return '使用者加入審查會議';
if (m.startsWith('leaveSession')) return '使用者離開審查會議';
if (m.startsWith('highlightRequest')) return '送出問題高亮';
if (m.startsWith('selectionUpdate')) return '同步元件選取';
if (m.startsWith('annotationCreate')) return '建立審查標註';
if (m.startsWith('annotationCreated')) return '標註已寫入主資料庫';
if (m.startsWith('presenceUpdated')) return '與會者列表更新';
if (m.startsWith('heartbeat')) return '心跳';
if (m === 'clientWarning') return '前端提示';
Expand Down
37 changes: 0 additions & 37 deletions bim-review-coordinator/src/public/dev-console.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,43 +135,6 @@ function emitLeave() {
emit("leaveSession", baseSocketPayload());
}

function emitHighlight() {
emit("highlightRequest", {
session_id: sessionId.value || "review_session_demo_001",
actor_id: userId.value,
items: [
{
prim_path: "/World",
ifc_guid: "2VJ3sK9L000fake001",
color: [1, 0, 0, 1],
label: "示範:從 coordinator 協作控制台送出的高亮",
source: "coordinator_dev_console",
issue_id: "ISSUE-DEMO-001"
}
]
});
}

function emitSelection() {
emit("selectionUpdate", {
session_id: sessionId.value || "review_session_demo_001",
actor_id: userId.value,
selected_paths: ["/World"]
});
}

function emitAnnotation() {
emit("annotationCreate", {
session_id: sessionId.value || "review_session_demo_001",
actor_id: userId.value,
text: "從 coordinator UI 建立的示範標註",
target: {
usd_prim_path: "/World",
ifc_guid: "2VJ3sK9L000fake001"
}
});
}

function emitHeartbeat() {
emit("heartbeat", { session_id: sessionId.value || "review_session_demo_001", actor_id: userId.value });
}
Expand Down
26 changes: 1 addition & 25 deletions bim-review-coordinator/src/services/bimControlClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Artifact, ReviewIssue } from "../types.js";
import type { Artifact } from "../types.js";

const DEFAULT_REQUEST_TIMEOUT_MS = 3000;

Expand All @@ -24,27 +24,6 @@ export class BimControlClient {
return asItems<Artifact>(payload, "artifacts");
}

async getReviewIssues(modelVersionId: string): Promise<ReviewIssue[]> {
const payload = await this.getJson(`/api/model-versions/${modelVersionId}/review-issues`);
return asItems<ReviewIssue>(payload);
}

async createAnnotation(sessionId: string, payload: unknown): Promise<unknown> {
this.ensureConfigured();
const response = await this.fetchWithTimeout(
`${this.baseUrl}/api/review-sessions/${sessionId}/annotations`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
},
);
if (!response.ok) {
throw new Error(`external control-plane annotation save failed: ${response.status}`);
}
return response.json();
}

private async getJson(path: string): Promise<unknown> {
this.ensureConfigured();
const response = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
Expand All @@ -57,9 +36,6 @@ export class BimControlClient {
}

private async fetchWithTimeout(url: string, init: RequestInit = {}): Promise<Response> {
// The external control-plane may be unavailable in local B-scheme runs. Without an explicit
// timeout, a hung connection (firewalled host, SYN drop, etc.) would
// tie up an entire request and make tests / live endpoints flaky.
return fetch(url, { ...init, signal: AbortSignal.timeout(this.requestTimeoutMs) });
}

Expand Down
Loading