vite-plugin-opencode-assistant 1.0.27 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/es/client/App.vue.js +64 -36
  2. package/es/client/composables/index.d.ts +10 -0
  3. package/es/client/composables/index.js +22 -0
  4. package/es/client/composables/useOpencodeSSE.d.ts +64 -0
  5. package/es/client/composables/useOpencodeSSE.js +29 -0
  6. package/es/client/composables/useOpencodeSessionSSE.d.ts +66 -0
  7. package/es/client/composables/useOpencodeSessionSSE.js +168 -0
  8. package/es/client/composables/useSSE.d.ts +85 -18
  9. package/es/client/composables/useSSE.js +101 -44
  10. package/es/client/composables/useServerSSE.d.ts +53 -0
  11. package/es/client/composables/useServerSSE.js +36 -0
  12. package/es/client/composables/useServiceStatus.d.ts +0 -2
  13. package/es/client/composables/useServiceStatus.js +1 -7
  14. package/es/client/composables/useSessions.d.ts +24 -5
  15. package/es/client/composables/useSessions.js +16 -2
  16. package/es/core/proxy-server.js +38 -148
  17. package/es/index.js +3 -1
  18. package/lib/client/App.vue.js +63 -35
  19. package/lib/client/composables/index.d.ts +10 -0
  20. package/lib/client/composables/index.js +54 -0
  21. package/lib/client/composables/useOpencodeSSE.d.ts +64 -0
  22. package/lib/client/composables/useOpencodeSSE.js +52 -0
  23. package/lib/client/composables/useOpencodeSessionSSE.d.ts +66 -0
  24. package/lib/client/composables/useOpencodeSessionSSE.js +187 -0
  25. package/lib/client/composables/useSSE.d.ts +85 -18
  26. package/lib/client/composables/useSSE.js +100 -43
  27. package/lib/client/composables/useServerSSE.d.ts +53 -0
  28. package/lib/client/composables/useServerSSE.js +59 -0
  29. package/lib/client/composables/useServiceStatus.d.ts +0 -2
  30. package/lib/client/composables/useServiceStatus.js +1 -7
  31. package/lib/client/composables/useSessions.d.ts +24 -5
  32. package/lib/client/composables/useSessions.js +16 -2
  33. package/lib/client.js +2823 -2566
  34. package/lib/core/proxy-server.js +38 -148
  35. package/lib/index.js +3 -1
  36. package/lib/style.css +1 -1
  37. package/package.json +4 -4
@@ -1,59 +1,116 @@
1
- import { ref, onUnmounted } from "vue";
2
- const MAX_SSE_RETRIES = 10;
3
- const SSE_RETRY_DELAY = 1e3;
4
- function useSSE(onStatusSync, onTaskUpdate, onClearElements, onConnected) {
5
- const sseConnection = ref(null);
6
- const sseRetryCount = ref(0);
7
- const setupSSE = () => {
8
- if (sseConnection.value) return;
1
+ import { ref, computed, onUnmounted } from "vue";
2
+ const DEFAULT_MAX_RETRIES = 10;
3
+ const DEFAULT_RETRY_DELAY = 1e3;
4
+ function useSSE(options) {
5
+ const {
6
+ endpoint,
7
+ autoConnect = true,
8
+ enabled,
9
+ maxRetries = DEFAULT_MAX_RETRIES,
10
+ retryDelay = DEFAULT_RETRY_DELAY,
11
+ onConnected,
12
+ onDisconnected,
13
+ onError,
14
+ onMessage
15
+ } = options;
16
+ const connection = ref(null);
17
+ const status = ref("idle");
18
+ const retryCount = ref(0);
19
+ function handleMessage(event) {
9
20
  try {
10
- sseConnection.value = new EventSource("/__opencode_events__");
11
- sseConnection.value.onmessage = (event) => {
12
- try {
13
- const data = JSON.parse(event.data);
14
- if (data.type === "CONNECTED") {
15
- onConnected();
16
- sseRetryCount.value = 0;
17
- } else if (data.type === "STATUS_SYNC") {
18
- onStatusSync(data);
19
- } else if (data.type === "TASK_UPDATE") {
20
- onTaskUpdate(data);
21
- } else if (data.type === "CLEAR_ELEMENTS") {
22
- onClearElements();
23
- }
24
- } catch (e) {
25
- }
21
+ const data = JSON.parse(event.data);
22
+ onMessage == null ? void 0 : onMessage(data);
23
+ } catch (e) {
24
+ onMessage == null ? void 0 : onMessage(event.data);
25
+ }
26
+ }
27
+ function connect() {
28
+ if (connection.value || status.value === "connecting") {
29
+ return;
30
+ }
31
+ if ((enabled == null ? void 0 : enabled.value) === false) {
32
+ status.value = "idle";
33
+ return;
34
+ }
35
+ status.value = "connecting";
36
+ retryCount.value = 0;
37
+ try {
38
+ connection.value = new EventSource(endpoint);
39
+ connection.value.onopen = () => {
40
+ status.value = "connected";
41
+ retryCount.value = 0;
42
+ onConnected == null ? void 0 : onConnected();
26
43
  };
27
- sseConnection.value.onerror = () => {
44
+ connection.value.onmessage = handleMessage;
45
+ connection.value.onerror = () => {
28
46
  var _a;
29
- (_a = sseConnection.value) == null ? void 0 : _a.close();
30
- sseConnection.value = null;
31
- if (sseRetryCount.value < MAX_SSE_RETRIES) {
32
- sseRetryCount.value++;
33
- setTimeout(setupSSE, SSE_RETRY_DELAY * sseRetryCount.value);
47
+ const wasConnected = status.value === "connected";
48
+ status.value = "error";
49
+ (_a = connection.value) == null ? void 0 : _a.close();
50
+ connection.value = null;
51
+ const error = new Error(`SSE connection error: ${endpoint}`);
52
+ onError == null ? void 0 : onError(error);
53
+ if (retryCount.value < maxRetries) {
54
+ retryCount.value++;
55
+ const delay = retryDelay * retryCount.value;
56
+ setTimeout(() => {
57
+ if ((enabled == null ? void 0 : enabled.value) !== false && !connection.value) {
58
+ connect();
59
+ }
60
+ }, delay);
61
+ } else if (wasConnected) {
62
+ onDisconnected == null ? void 0 : onDisconnected();
34
63
  }
35
64
  };
36
65
  } catch (e) {
37
- sseConnection.value = null;
38
- if (sseRetryCount.value < MAX_SSE_RETRIES) {
39
- sseRetryCount.value++;
40
- setTimeout(setupSSE, SSE_RETRY_DELAY * sseRetryCount.value);
66
+ status.value = "error";
67
+ const error = e instanceof Error ? e : new Error(String(e));
68
+ onError == null ? void 0 : onError(error);
69
+ if (retryCount.value < maxRetries) {
70
+ retryCount.value++;
71
+ const delay = retryDelay * retryCount.value;
72
+ setTimeout(() => {
73
+ if (!connection.value) {
74
+ connect();
75
+ }
76
+ }, delay);
41
77
  }
42
78
  }
43
- };
44
- const closeSSE = () => {
45
- if (sseConnection.value) {
46
- sseConnection.value.close();
47
- sseConnection.value = null;
79
+ }
80
+ function disconnect() {
81
+ if (connection.value) {
82
+ connection.value.close();
83
+ connection.value = null;
84
+ status.value = "disconnected";
85
+ retryCount.value = 0;
86
+ onDisconnected == null ? void 0 : onDisconnected();
48
87
  }
49
- };
88
+ }
89
+ function reconnect() {
90
+ disconnect();
91
+ connect();
92
+ }
93
+ function resetRetryCount() {
94
+ retryCount.value = 0;
95
+ }
96
+ if (autoConnect && (enabled == null ? void 0 : enabled.value) !== false) {
97
+ connect();
98
+ }
50
99
  onUnmounted(() => {
51
- closeSSE();
100
+ disconnect();
52
101
  });
53
102
  return {
54
- setupSSE,
55
- closeSSE,
56
- sseRetryCount
103
+ // 状态
104
+ connection,
105
+ status,
106
+ retryCount,
107
+ isConnected: computed(() => status.value === "connected"),
108
+ isConnecting: computed(() => status.value === "connecting"),
109
+ // 方法
110
+ connect,
111
+ disconnect,
112
+ reconnect,
113
+ resetRetryCount
57
114
  };
58
115
  }
59
116
  export {
@@ -0,0 +1,53 @@
1
+ import { ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
2
+ /**
3
+ * Server SSE 状态同步数据
4
+ */
5
+ export interface ServerSSEStatusSyncData {
6
+ type: "STATUS_SYNC";
7
+ isStarted?: boolean;
8
+ task: ServiceStartupTask;
9
+ errorType?: string;
10
+ errorMessage?: string;
11
+ }
12
+ /**
13
+ * Server SSE 任务更新数据
14
+ */
15
+ export interface ServerSSETaskUpdateData {
16
+ type: "TASK_UPDATE";
17
+ task: ServiceStartupTask;
18
+ errorType?: string;
19
+ errorMessage?: string;
20
+ }
21
+ /**
22
+ * Server SSE 消息类型
23
+ */
24
+ export type ServerSSEMessage = {
25
+ type: "CONNECTED";
26
+ } | ServerSSEStatusSyncData | ServerSSETaskUpdateData | {
27
+ type: "CLEAR_ELEMENTS";
28
+ };
29
+ /**
30
+ * Server SSE 配置选项
31
+ */
32
+ export interface ServerSSEOptions {
33
+ /** 状态同步回调 */
34
+ onStatusSync?: (data: ServerSSEStatusSyncData) => void;
35
+ /** 任务更新回调 */
36
+ onTaskUpdate?: (data: ServerSSETaskUpdateData) => void;
37
+ /** 清除元素回调 */
38
+ onClearElements?: () => void;
39
+ /** 连接成功回调 */
40
+ onConnected?: () => void;
41
+ /** 是否启用 */
42
+ enabled?: boolean;
43
+ }
44
+ /**
45
+ * 监听 Vite Server SSE 事件
46
+ * 端点: /__opencode_events__
47
+ */
48
+ export declare function useServerSSE(options?: ServerSSEOptions): {
49
+ status: import("vue").Ref<import("./useSSE").SSEConnectionStatus, import("./useSSE").SSEConnectionStatus>;
50
+ isConnected: import("vue").ComputedRef<boolean>;
51
+ connect: () => void;
52
+ disconnect: () => void;
53
+ };
@@ -0,0 +1,36 @@
1
+ import { useSSE } from "./useSSE";
2
+ function useServerSSE(options = {}) {
3
+ const { onStatusSync, onTaskUpdate, onClearElements, onConnected } = options;
4
+ const { status, isConnected, connect, disconnect } = useSSE({
5
+ endpoint: "/__opencode_events__",
6
+ autoConnect: false,
7
+ onMessage: (data) => {
8
+ const message = data;
9
+ switch (message.type) {
10
+ case "CONNECTED":
11
+ onConnected == null ? void 0 : onConnected();
12
+ break;
13
+ case "STATUS_SYNC":
14
+ onStatusSync == null ? void 0 : onStatusSync(message);
15
+ break;
16
+ case "TASK_UPDATE":
17
+ onTaskUpdate == null ? void 0 : onTaskUpdate(message);
18
+ break;
19
+ case "CLEAR_ELEMENTS":
20
+ onClearElements == null ? void 0 : onClearElements();
21
+ break;
22
+ }
23
+ }
24
+ });
25
+ return {
26
+ // 状态
27
+ status,
28
+ isConnected,
29
+ // 方法
30
+ connect,
31
+ disconnect
32
+ };
33
+ }
34
+ export {
35
+ useServerSSE
36
+ };
@@ -5,9 +5,7 @@ export declare function useServiceStatus(): {
5
5
  chromeMcpFailed: import("vue").Ref<boolean, boolean>;
6
6
  chromeMcpErrorType: import("vue").Ref<string | undefined, string | undefined>;
7
7
  chromeMcpErrorMessage: import("vue").Ref<string | undefined, string | undefined>;
8
- thinking: import("vue").Ref<boolean, boolean>;
9
8
  loadingText: import("vue").ComputedRef<string>;
10
9
  updateStatusFromTask: (task: ServiceStartupTask | "", errorType?: string, errorMessage?: string) => void;
11
10
  setStarting: () => void;
12
- setThinking: (value: boolean) => void;
13
11
  };
@@ -6,7 +6,6 @@ function useServiceStatus() {
6
6
  const chromeMcpFailed = ref(false);
7
7
  const chromeMcpErrorType = ref(void 0);
8
8
  const chromeMcpErrorMessage = ref(void 0);
9
- const thinking = ref(false);
10
9
  const loadingText = computed(() => {
11
10
  if (!currentTask.value) return "\u52A0\u8F7D\u4E2D...";
12
11
  return SERVICE_STARTUP_TASKS[currentTask.value] || "\u52A0\u8F7D\u4E2D...";
@@ -32,20 +31,15 @@ function useServiceStatus() {
32
31
  const setStarting = () => {
33
32
  serviceStatus.value = "starting";
34
33
  };
35
- const setThinking = (value) => {
36
- thinking.value = value;
37
- };
38
34
  return {
39
35
  currentTask,
40
36
  serviceStatus,
41
37
  chromeMcpFailed,
42
38
  chromeMcpErrorType,
43
39
  chromeMcpErrorMessage,
44
- thinking,
45
40
  loadingText,
46
41
  updateStatusFromTask,
47
- setStarting,
48
- setThinking
42
+ setStarting
49
43
  };
50
44
  }
51
45
  export {
@@ -1,6 +1,18 @@
1
+ import { type Ref } from "vue";
1
2
  import type { OpenCodeWidgetSession } from "@vite-plugin-opencode-assistant/shared";
2
- export declare function useSessions(showNotification: (msg: string) => void): {
3
- sessions: import("vue").Ref<{
3
+ export interface UseSessionsOptions {
4
+ showNotification: (msg: string) => void;
5
+ /** Session 更新回调 (从 SSE 事件接收) */
6
+ onSessionUpdate?: Ref<((session: {
7
+ id: string;
8
+ title?: string;
9
+ time?: {
10
+ updated?: number;
11
+ };
12
+ }) => void) | undefined>;
13
+ }
14
+ export declare function useSessions(options: UseSessionsOptions): {
15
+ sessions: Ref<{
4
16
  id: string;
5
17
  title?: string | undefined;
6
18
  updatedAt?: (string | number | Date) | undefined;
@@ -15,12 +27,19 @@ export declare function useSessions(showNotification: (msg: string) => void): {
15
27
  directory?: string | undefined;
16
28
  url?: string | undefined;
17
29
  }[]>;
18
- loadingSessionList: import("vue").Ref<boolean | undefined, boolean | undefined>;
19
- currentSessionId: import("vue").Ref<string | null, string | null>;
30
+ loadingSessionList: Ref<boolean | undefined, boolean | undefined>;
31
+ currentSessionId: Ref<string | null, string | null>;
20
32
  iframeSrc: import("vue").ComputedRef<string>;
21
- iframeLoading: import("vue").Ref<boolean, boolean>;
33
+ iframeLoading: Ref<boolean, boolean>;
22
34
  loadSessions: () => Promise<void>;
23
35
  createSession: () => Promise<void>;
24
36
  deleteSession: (session: OpenCodeWidgetSession) => Promise<void>;
25
37
  selectSession: (session: OpenCodeWidgetSession) => void;
38
+ updateSessionInfo: (sessionUpdate: {
39
+ id: string;
40
+ title?: string;
41
+ time?: {
42
+ updated?: number;
43
+ };
44
+ }) => void;
26
45
  };
@@ -39,7 +39,8 @@ var __async = (__this, __arguments, generator) => {
39
39
  };
40
40
  import { ref, computed } from "vue";
41
41
  import { SESSIONS_API_PATH } from "@vite-plugin-opencode-assistant/shared";
42
- function useSessions(showNotification) {
42
+ function useSessions(options) {
43
+ const { showNotification } = options;
43
44
  const sessions = ref([]);
44
45
  const loadingSessionList = ref(void 0);
45
46
  const currentSessionId = ref(null);
@@ -70,6 +71,18 @@ function useSessions(showNotification) {
70
71
  loadingSessionList.value = false;
71
72
  }
72
73
  });
74
+ const updateSessionInfo = (sessionUpdate) => {
75
+ var _a;
76
+ const index = sessions.value.findIndex((s) => s.id === sessionUpdate.id);
77
+ if (index === -1) return;
78
+ const session = sessions.value[index];
79
+ if (sessionUpdate.title && sessionUpdate.title !== session.title) {
80
+ sessions.value[index] = __spreadProps(__spreadValues({}, session), {
81
+ title: sessionUpdate.title,
82
+ updatedAt: ((_a = sessionUpdate.time) == null ? void 0 : _a.updated) || Date.now()
83
+ });
84
+ }
85
+ };
73
86
  const createSession = () => __async(null, null, function* () {
74
87
  try {
75
88
  const response = yield fetch(SESSIONS_API_PATH, { method: "POST" });
@@ -119,7 +132,8 @@ function useSessions(showNotification) {
119
132
  loadSessions,
120
133
  createSession,
121
134
  deleteSession,
122
- selectSession
135
+ selectSession,
136
+ updateSessionInfo
123
137
  };
124
138
  }
125
139
  export {
@@ -102,15 +102,15 @@ function generateBridgeScript(options) {
102
102
  if (event.data && event.data.type === "OPENCODE_SET_THEME") {
103
103
  setTheme(event.data.theme);
104
104
  }
105
-
105
+
106
106
  if (event.data && event.data.type === "OPENCODE_INSERT_FILE_PART") {
107
107
  insertFilePart(event.data.element);
108
108
  }
109
-
109
+
110
110
  if (event.data && event.data.type === "minimize-state-change") {
111
111
  handleMinimizeStateChange(event.data.minimized);
112
112
  }
113
-
113
+
114
114
  if (event.data && event.data.type === "prompt-dock-visibility-change") {
115
115
  handlePromptDockVisibilityChange(event.data.visible);
116
116
  }
@@ -124,11 +124,11 @@ function generateBridgeScript(options) {
124
124
  savedMinimizedState = minimized;
125
125
  const dockSurface = document.querySelector('[data-dock-surface="tray"]');
126
126
  const sessionTurnList = document.querySelector('[data-slot="session-turn-list"]');
127
-
127
+
128
128
  if (dockSurface) {
129
129
  dockSurface.style.display = minimized ? 'none' : '';
130
130
  }
131
-
131
+
132
132
  if (sessionTurnList) {
133
133
  sessionTurnList.style.paddingBottom = minimized ? '10px' : '';
134
134
  }
@@ -155,11 +155,11 @@ function generateBridgeScript(options) {
155
155
 
156
156
  // === \u4FDD\u5B58\u8F93\u5165\u6846\u5149\u6807\u4F4D\u7F6E ===
157
157
  let savedRange = null;
158
-
158
+
159
159
  function setupPromptInputListener() {
160
160
  const promptInput = document.querySelector('[data-component="prompt-input"]');
161
161
  if (!promptInput) return;
162
-
162
+
163
163
  promptInput.addEventListener('blur', function() {
164
164
  const selection = window.getSelection();
165
165
  if (selection && selection.rangeCount > 0) {
@@ -169,7 +169,7 @@ function generateBridgeScript(options) {
169
169
  }
170
170
  }
171
171
  });
172
-
172
+
173
173
  promptInput.addEventListener('focus', function() {
174
174
  savedRange = null;
175
175
  });
@@ -195,29 +195,29 @@ function generateBridgeScript(options) {
195
195
 
196
196
  const jsonStr = JSON.stringify({
197
197
  nodeContext: {
198
- "filePath": {
199
- "value": filePath ?? '\u672A\u77E5',
200
- "desc": "\u6E90\u7801\u6587\u4EF6\u8DEF\u5F84"
198
+ "filePath": {
199
+ "value": filePath ?? '\u672A\u77E5',
200
+ "desc": "\u6E90\u7801\u6587\u4EF6\u8DEF\u5F84"
201
201
  },
202
- "line": {
203
- "value": line ?? '\u672A\u77E5',
204
- "desc": "\u4EE3\u7801\u6240\u5728\u884C\u53F7"
202
+ "line": {
203
+ "value": line ?? '\u672A\u77E5',
204
+ "desc": "\u4EE3\u7801\u6240\u5728\u884C\u53F7"
205
205
  },
206
- "column": {
207
- "value": column ?? '\u672A\u77E5',
208
- "desc": "\u4EE3\u7801\u6240\u5728\u5217\u53F7"
206
+ "column": {
207
+ "value": column ?? '\u672A\u77E5',
208
+ "desc": "\u4EE3\u7801\u6240\u5728\u5217\u53F7"
209
209
  },
210
- "description": {
211
- "value": description ?? '\u672A\u77E5',
212
- "desc": "DOM \u5143\u7D20\u9009\u62E9\u5668"
210
+ "description": {
211
+ "value": description ?? '\u672A\u77E5',
212
+ "desc": "DOM \u5143\u7D20\u9009\u62E9\u5668"
213
213
  },
214
- "innerText": {
215
- "value": innerText ? innerText.substring(0, 500) : '',
216
- "desc": "DOM \u5143\u7D20\u5185\u90E8\u6587\u672C"
214
+ "innerText": {
215
+ "value": innerText ? innerText.substring(0, 500) : '',
216
+ "desc": "DOM \u5143\u7D20\u5185\u90E8\u6587\u672C"
217
217
  },
218
- "selectAt": {
219
- "value": previewPageUrl || '\u672A\u77E5',
220
- "desc": "\u7528\u6237\u9009\u4E2D\u8282\u70B9\u65F6\u7684\u9875\u9762 URL"
218
+ "selectAt": {
219
+ "value": previewPageUrl || '\u672A\u77E5',
220
+ "desc": "\u7528\u6237\u9009\u4E2D\u8282\u70B9\u65F6\u7684\u9875\u9762 URL"
221
221
  }
222
222
  }
223
223
  });
@@ -226,30 +226,30 @@ function generateBridgeScript(options) {
226
226
  span.setAttribute('data-type', 'file');
227
227
  span.setAttribute('data-path', jsonStr);
228
228
  span.setAttribute('contenteditable', 'false');
229
-
229
+
230
230
  span.textContent = displayText;
231
231
 
232
232
  if (savedRange) {
233
233
  const range = savedRange;
234
234
  range.collapse(false);
235
235
  range.insertNode(span);
236
-
236
+
237
237
  const space = document.createTextNode('\\u00A0');
238
238
  span.parentNode.insertBefore(space, span.nextSibling);
239
-
239
+
240
240
  const newRange = document.createRange();
241
241
  newRange.setStartAfter(space);
242
242
  newRange.collapse(true);
243
-
243
+
244
244
  promptInput.focus();
245
-
245
+
246
246
  const selection = window.getSelection();
247
247
  if (selection) {
248
248
  selection.removeAllRanges();
249
249
  selection.addRange(newRange);
250
250
  }
251
251
  savedRange = null;
252
-
252
+
253
253
  promptInput.dispatchEvent(new Event('input', { bubbles: true }));
254
254
  return;
255
255
  }
@@ -257,20 +257,20 @@ function generateBridgeScript(options) {
257
257
  const selection = window.getSelection();
258
258
  if (selection && selection.rangeCount > 0) {
259
259
  const range = selection.getRangeAt(0);
260
-
260
+
261
261
  if (promptInput.contains(range.commonAncestorContainer)) {
262
262
  range.collapse(false);
263
263
  range.insertNode(span);
264
-
264
+
265
265
  const space = document.createTextNode('\\u00A0');
266
266
  span.parentNode.insertBefore(space, span.nextSibling);
267
-
267
+
268
268
  const newRange = document.createRange();
269
269
  newRange.setStartAfter(space);
270
270
  newRange.collapse(true);
271
271
  selection.removeAllRanges();
272
272
  selection.addRange(newRange);
273
-
273
+
274
274
  promptInput.dispatchEvent(new Event('input', { bubbles: true }));
275
275
  return;
276
276
  }
@@ -279,7 +279,7 @@ function generateBridgeScript(options) {
279
279
  promptInput.appendChild(span);
280
280
  const space = document.createTextNode('\\u00A0');
281
281
  promptInput.appendChild(space);
282
-
282
+
283
283
  const newRange = document.createRange();
284
284
  newRange.setStartAfter(space);
285
285
  newRange.collapse(true);
@@ -288,119 +288,16 @@ function generateBridgeScript(options) {
288
288
  newSelection.removeAllRanges();
289
289
  newSelection.addRange(newRange);
290
290
  }
291
-
291
+
292
292
  promptInput.dispatchEvent(new Event('input', { bubbles: true }));
293
293
  promptInput.focus();
294
294
  }
295
295
 
296
- // === \u601D\u8003\u72B6\u6001\u76D1\u542C (\u5B8C\u5168\u590D\u523B OpenCode Web \u5B9E\u73B0) ===
297
- // OpenCode Web \u6838\u5FC3\u903B\u8F91:
298
- // working = !!pending() || sessionStatus().type !== "idle"
299
- // pending = \u6700\u540E\u4E00\u6761\u672A\u5B8C\u6210\u7684 assistant \u6D88\u606F (time.completed \u4E0D\u662F\u6570\u5B57)
300
- // sessionStatus = sync.data.session_status[sessionID]
301
-
302
- let eventSource = null;
303
- const sessionStatus = {};
304
- const pendingMessages = {};
305
-
306
- function getCurrentSessionID() {
307
- const match = window.location.pathname.match(/\\/session\\/([^\\/]+)/);
308
- return match ? match[1] : null;
309
- }
310
-
311
- function isPending(message) {
312
- return message.role === 'assistant' && typeof message.time?.completed !== 'number';
313
- }
314
-
315
- function updateThinkingState(sessionID) {
316
- const status = sessionStatus[sessionID];
317
- const pending = pendingMessages[sessionID];
318
-
319
- const isThinking = !!pending || (status && status.type !== 'idle');
320
-
321
- if (window.parent !== window) {
322
- window.parent.postMessage({
323
- type: 'OPENCODE_THINKING_STATE',
324
- thinking: isThinking,
325
- sessionID: sessionID,
326
- statusType: status?.type || 'idle',
327
- hasPending: !!pending
328
- }, '*');
329
- }
330
- }
331
-
332
- function handleEvent(payload) {
333
- const type = payload.type;
334
- const props = payload.properties;
335
-
336
- switch (type) {
337
- case 'session.status': {
338
- const sessionID = props.sessionID;
339
- sessionStatus[sessionID] = props.status;
340
- updateThinkingState(sessionID);
341
- break;
342
- }
343
-
344
- case 'message.updated': {
345
- const info = props.info;
346
- if (!info || !info.sessionID) break;
347
- const sessionID = info.sessionID;
348
-
349
- if (info.role === 'assistant') {
350
- if (isPending(info)) {
351
- pendingMessages[sessionID] = info;
352
- } else {
353
- delete pendingMessages[sessionID];
354
- }
355
- updateThinkingState(sessionID);
356
- }
357
- break;
358
- }
359
-
360
- case 'message.part.delta': {
361
- const sessionID = props.sessionID;
362
- if (sessionID && !pendingMessages[sessionID]) {
363
- pendingMessages[sessionID] = { role: 'assistant', time: {} };
364
- updateThinkingState(sessionID);
365
- }
366
- break;
367
- }
368
- }
369
- }
370
-
371
- function setupThinkingListener() {
372
- if (eventSource) {
373
- eventSource.close();
374
- }
375
-
376
- eventSource = new EventSource('/global/event');
377
-
378
- eventSource.onmessage = function(event) {
379
- try {
380
- const data = JSON.parse(event.data);
381
- const payload = data.payload;
382
- if (!payload) return;
383
- handleEvent(payload);
384
- } catch (e) {
385
- // ignore parse errors
386
- }
387
- };
388
-
389
- eventSource.onerror = function(err) {
390
- console.warn('[OpenCode Bridge] SSE connection error, retrying in 3s...');
391
- eventSource.close();
392
- setTimeout(setupThinkingListener, 3000);
393
- };
394
-
395
- console.log('[OpenCode Bridge] SSE listener setup complete');
396
- }
397
-
398
296
  // === \u5C31\u7EEA\u901A\u77E5 ===
399
297
  function init() {
400
298
  if (window.parent !== window) {
401
299
  window.parent.postMessage({ type: "OPENCODE_READY" }, "*");
402
300
  }
403
- setupThinkingListener();
404
301
  setupPromptInputListener();
405
302
  applySavedStates();
406
303
 
@@ -426,13 +323,6 @@ function generateBridgeScript(options) {
426
323
  } else {
427
324
  init();
428
325
  }
429
-
430
- window.addEventListener('beforeunload', function() {
431
- if (eventSource) {
432
- eventSource.close();
433
- eventSource = null;
434
- }
435
- });
436
326
  })();
437
327
  `;
438
328
  }