@awiki/dsh-plugin 0.2.5 → 0.3.0-rc.2

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 (124) hide show
  1. package/README.md +69 -28
  2. package/README.zh.md +60 -21
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/cordis.patch.yml +0 -8
  5. package/lib/client.js +6755 -2022
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +1321 -72
  8. package/lib/model-proxy-contract.js +8 -1
  9. package/lib/provider.js +3 -1
  10. package/lib/{sdk-adapter-DKgkyApB.mjs → sdk-adapter-BIbsJmRj.mjs} +245 -4
  11. package/lib/typert.host.js +944 -57
  12. package/lib/typert.remote-client.d.ts +37 -1
  13. package/lib/typert.remote-client.d.ts.map +1 -1
  14. package/lib/typert.remote-client.js +944 -57
  15. package/lib/types/client/AwikiGroupAccessNotice.d.ts +12 -0
  16. package/lib/types/client/AwikiGroupAccessNotice.d.ts.map +1 -0
  17. package/lib/types/client/AwikiGroupAccessNotice.js +31 -0
  18. package/lib/types/client/AwikiGroupAccessNotice.js.map +1 -0
  19. package/lib/types/client/AwikiGroupDetails.d.ts +23 -0
  20. package/lib/types/client/AwikiGroupDetails.d.ts.map +1 -0
  21. package/lib/types/client/AwikiGroupDetails.js +144 -0
  22. package/lib/types/client/AwikiGroupDetails.js.map +1 -0
  23. package/lib/types/client/AwikiIdentityAccess.d.ts +22 -0
  24. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -0
  25. package/lib/types/client/AwikiIdentityAccess.js +156 -0
  26. package/lib/types/client/AwikiIdentityAccess.js.map +1 -0
  27. package/lib/types/client/AwikiIdentityPage.d.ts +11 -0
  28. package/lib/types/client/AwikiIdentityPage.d.ts.map +1 -0
  29. package/lib/types/client/AwikiIdentityPage.js +8 -0
  30. package/lib/types/client/AwikiIdentityPage.js.map +1 -0
  31. package/lib/types/client/AwikiMail.d.ts +2 -1
  32. package/lib/types/client/AwikiMail.d.ts.map +1 -1
  33. package/lib/types/client/AwikiMail.js +157 -33
  34. package/lib/types/client/AwikiMail.js.map +1 -1
  35. package/lib/types/client/AwikiOnboarding.d.ts.map +1 -1
  36. package/lib/types/client/AwikiOnboarding.js +29 -9
  37. package/lib/types/client/AwikiOnboarding.js.map +1 -1
  38. package/lib/types/client/AwikiOverlay.d.ts +0 -5
  39. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  40. package/lib/types/client/AwikiOverlay.js +202 -129
  41. package/lib/types/client/AwikiOverlay.js.map +1 -1
  42. package/lib/types/client/AwikiProfileCard.d.ts +9 -0
  43. package/lib/types/client/AwikiProfileCard.d.ts.map +1 -0
  44. package/lib/types/client/AwikiProfileCard.js +90 -0
  45. package/lib/types/client/AwikiProfileCard.js.map +1 -0
  46. package/lib/types/client/AwikiRecoveryForm.d.ts +27 -0
  47. package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -0
  48. package/lib/types/client/AwikiRecoveryForm.js +152 -0
  49. package/lib/types/client/AwikiRecoveryForm.js.map +1 -0
  50. package/lib/types/client/AwikiSettingsSection.d.ts +0 -2
  51. package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
  52. package/lib/types/client/AwikiSettingsSection.js +18 -16
  53. package/lib/types/client/AwikiSettingsSection.js.map +1 -1
  54. package/lib/types/client/MentionText.d.ts +7 -0
  55. package/lib/types/client/MentionText.d.ts.map +1 -0
  56. package/lib/types/client/MentionText.js +10 -0
  57. package/lib/types/client/MentionText.js.map +1 -0
  58. package/lib/types/client/controller.d.ts +131 -3
  59. package/lib/types/client/controller.d.ts.map +1 -1
  60. package/lib/types/client/controller.js +908 -29
  61. package/lib/types/client/controller.js.map +1 -1
  62. package/lib/types/client/index.d.ts.map +1 -1
  63. package/lib/types/client/index.js +24 -4
  64. package/lib/types/client/index.js.map +1 -1
  65. package/lib/types/client/mail-list-cache.d.ts +15 -0
  66. package/lib/types/client/mail-list-cache.d.ts.map +1 -0
  67. package/lib/types/client/mail-list-cache.js +212 -0
  68. package/lib/types/client/mail-list-cache.js.map +1 -0
  69. package/lib/types/client/mentions.d.ts +51 -0
  70. package/lib/types/client/mentions.d.ts.map +1 -0
  71. package/lib/types/client/mentions.js +165 -0
  72. package/lib/types/client/mentions.js.map +1 -0
  73. package/lib/types/client/model-proxy-controller.d.ts +4 -0
  74. package/lib/types/client/model-proxy-controller.d.ts.map +1 -1
  75. package/lib/types/client/model-proxy-controller.js +68 -3
  76. package/lib/types/client/model-proxy-controller.js.map +1 -1
  77. package/lib/types/client/recharge-availability.d.ts +1 -1
  78. package/lib/types/client/recharge-availability.d.ts.map +1 -1
  79. package/lib/types/client/recharge-availability.js +1 -1
  80. package/lib/types/client/recharge-availability.js.map +1 -1
  81. package/lib/types/client/settings-locales.d.ts +1 -1
  82. package/lib/types/client/settings-locales.d.ts.map +1 -1
  83. package/lib/types/client/settings-locales.js +14 -8
  84. package/lib/types/client/settings-locales.js.map +1 -1
  85. package/lib/types/client/slots.d.ts +27 -2
  86. package/lib/types/client/slots.d.ts.map +1 -1
  87. package/lib/types/conversation-preferences.d.ts +20 -0
  88. package/lib/types/conversation-preferences.d.ts.map +1 -0
  89. package/lib/types/conversation-preferences.js +294 -0
  90. package/lib/types/conversation-preferences.js.map +1 -0
  91. package/lib/types/index.d.ts +45 -6
  92. package/lib/types/index.d.ts.map +1 -1
  93. package/lib/types/index.js +562 -23
  94. package/lib/types/index.js.map +1 -1
  95. package/lib/types/model-proxy-contract.d.ts +6 -0
  96. package/lib/types/model-proxy-contract.d.ts.map +1 -1
  97. package/lib/types/model-proxy-contract.js +6 -0
  98. package/lib/types/model-proxy-contract.js.map +1 -1
  99. package/lib/types/provider-api.d.ts +29 -1
  100. package/lib/types/provider-api.d.ts.map +1 -1
  101. package/lib/types/provider.d.ts.map +1 -1
  102. package/lib/types/provider.js +2 -0
  103. package/lib/types/provider.js.map +1 -1
  104. package/lib/types/sdk-adapter.d.ts +18 -1
  105. package/lib/types/sdk-adapter.d.ts.map +1 -1
  106. package/lib/types/sdk-adapter.js +277 -4
  107. package/lib/types/sdk-adapter.js.map +1 -1
  108. package/lib/types/sent-mail-store.d.ts +22 -0
  109. package/lib/types/sent-mail-store.d.ts.map +1 -0
  110. package/lib/types/sent-mail-store.js +246 -0
  111. package/lib/types/sent-mail-store.js.map +1 -0
  112. package/lib/types/types.d.ts +183 -2
  113. package/lib/types/types.d.ts.map +1 -1
  114. package/lib/types/types.js.map +1 -1
  115. package/package.json +15 -26
  116. package/lib/model-proxy.js +0 -383
  117. package/lib/types/client/model-account-settings-availability.d.ts +0 -4
  118. package/lib/types/client/model-account-settings-availability.d.ts.map +0 -1
  119. package/lib/types/client/model-account-settings-availability.js +0 -4
  120. package/lib/types/client/model-account-settings-availability.js.map +0 -1
  121. package/lib/types/model-proxy.d.ts +0 -14
  122. package/lib/types/model-proxy.d.ts.map +0 -1
  123. package/lib/types/model-proxy.js +0 -371
  124. package/lib/types/model-proxy.js.map +0 -1
@@ -1,16 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /** AWiki trigger, identity registration, and direct/group messaging drawer. */
3
3
  import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
4
- import { IconChevronLeftOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconCloseOutline16, IconCopyOutline16, IconDataOutline16, IconDownloadOutline16, IconEditOutline16, IconGlobeOutline14, IconGoalOutline16, IconLoadingOutline16, IconPaperclipOutline16, IconPlusOutline16, IconRefreshOutline16, IconRefreshOutline14, IconSendOutline16, IconSparkle16, IconUserOutline16, Button, Menu, Modal, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { IconChevronLeftOutline14, IconCheckOutline16, IconChecklistOutline14, IconArchiveOutline20, IconChevronDownOutline14, IconCloseOutline16, IconCopyOutline16, IconDataOutline16, IconDownloadOutline16, IconEllipsisOutline16, IconGlobeOutline14, IconGoalOutline16, IconLoadingOutline16, IconPaperclipOutline16, IconPlusOutline16, IconRefreshOutline16, IconRefreshOutline14, IconSendOutline16, IconSettingsOutline16, IconSparkle16, Button, Menu, Modal, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives';
5
5
  import { AWIKI_ME_APP_ICON_DATA_URL } from "./assets.js";
6
6
  import { createAttachmentObjectUrl, fileToBase64, saveDownloadedAttachment } from "./file.js";
7
7
  import { AwikiMail } from "./AwikiMail.js";
8
+ import { AwikiGroupAccessNotice } from "./AwikiGroupAccessNotice.js";
9
+ import { AwikiGroupDetails } from "./AwikiGroupDetails.js";
10
+ import { AwikiIdentityAccess } from "./AwikiIdentityAccess.js";
11
+ import { AwikiProfileCard } from "./AwikiProfileCard.js";
12
+ import { MentionText } from "./MentionText.js";
13
+ import { activeMentionQuery, codePointIndexToUtf16Index, insertMention, mentionCandidates, protocolMentions, transformMentionDrafts, utf16IndexToCodePointIndex, } from "./mentions.js";
8
14
  import css from './AwikiOverlay.module.css';
9
15
  export const AWIKI_LAUNCHER_POSITION_KEY = 'dsh-awiki-launcher-position-v1';
10
16
  export const AWIKI_DRAWER_FRAME_KEY = 'dsh-awiki-drawer-frame-v1';
11
17
  const LAUNCHER_SIZE = 48;
12
18
  const LAUNCHER_EDGE_GAP = 8;
13
- const LAUNCHER_RIGHT_OFFSET = 28;
19
+ const LAUNCHER_LEFT_OFFSET = 176;
14
20
  const LAUNCHER_BOTTOM_CLEARANCE = 152;
15
21
  const LAUNCHER_DRAG_THRESHOLD = 4;
16
22
  const DRAWER_LONG_PRESS_MS = 300;
@@ -112,7 +118,7 @@ export function resizeAwikiDrawerFrame(frame, direction, deltaX, deltaY, viewpor
112
118
  }
113
119
  function defaultLauncherPosition() {
114
120
  return clampAwikiLauncherPosition({
115
- left: window.innerWidth - LAUNCHER_SIZE - LAUNCHER_RIGHT_OFFSET,
121
+ left: LAUNCHER_LEFT_OFFSET,
116
122
  top: window.innerHeight - LAUNCHER_SIZE - LAUNCHER_BOTTOM_CLEARANCE,
117
123
  }, window.innerWidth, window.innerHeight);
118
124
  }
@@ -180,113 +186,12 @@ function conversationTime(value, now = Date.now()) {
180
186
  ? new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }).format(value)
181
187
  : new Intl.DateTimeFormat('zh-CN', { month: 'numeric', day: 'numeric' }).format(value);
182
188
  }
183
- /** Render the identity registration form and its OTP challenge transition. */
184
- export function AwikiRegistrationForm(props) {
185
- const [phone, setPhone] = useState('');
186
- const [handle, setHandle] = useState('');
187
- const [otp, setOtp] = useState('');
188
- const [otpSent, setOtpSent] = useState(false);
189
- const [notice, setNotice] = useState(null);
190
- const [retryDeadline, setRetryDeadline] = useState(null);
191
- const [retrySeconds, setRetrySeconds] = useState(0);
192
- useEffect(() => {
193
- if (retryDeadline === null)
194
- return;
195
- const update = () => {
196
- const remaining = Math.max(0, Math.ceil((retryDeadline - Date.now()) / 1000));
197
- setRetrySeconds(remaining);
198
- if (remaining === 0)
199
- setRetryDeadline(null);
200
- };
201
- update();
202
- const timer = setInterval(update, 250);
203
- return () => { clearInterval(timer); };
204
- }, [retryDeadline]);
205
- const requestOtp = async () => {
206
- const result = await props.sendRegistrationOtp({ handle: handle.trim(), phone: phone.trim() });
207
- if (!result.ok)
208
- return;
209
- const cooldownSeconds = Math.max(0, Math.ceil(result.value.retryAfterSeconds));
210
- setOtpSent(true);
211
- setRetryDeadline(Date.now() + cooldownSeconds * 1000);
212
- setRetrySeconds(cooldownSeconds);
213
- setNotice(`验证码已发送;${cooldownSeconds} 秒后可重新获取。`);
214
- };
215
- const register = async () => {
216
- /* v8 ignore next -- the registration action is rendered only after an OTP challenge starts. */
217
- if (!otpSent)
218
- return;
219
- const result = await props.registerIdentity({
220
- phone: phone.trim(), handle: handle.trim(), otp: otp.trim(),
221
- });
222
- if (!result.ok)
223
- return;
224
- setNotice(null);
225
- };
226
- return (_jsxs("div", { className: css.registration, children: [_jsx("div", { className: css.registrationIcon, children: _jsx(IconUserOutline16, { size: 24 }) }), _jsx("h3", { children: "\u6CE8\u518C AWiki \u8EAB\u4EFD" }), _jsx("p", { children: "\u8BE5\u8EAB\u4EFD\u7531\u5F53\u524D Harness \u90E8\u7F72\u4E2D\u7684\u5168\u90E8 Agent \u5171\u540C\u4F7F\u7528\u3002" }), _jsxs("label", { children: ["Handle", _jsx("input", { value: handle, onChange: (event) => { setHandle(event.target.value); }, autoComplete: "username", placeholder: "\u4F8B\u5982 alice", autoFocus: props.autoFocusHandle })] }), _jsxs("label", { children: ["\u624B\u673A\u53F7", _jsx("input", { value: phone, onChange: (event) => { setPhone(event.target.value); }, autoComplete: "tel" })] }), !otpSent ? (_jsx("button", { type: "button", className: css.primary, disabled: props.pending || phone.trim() === '' || handle.trim() === '', onClick: () => { void requestOtp(); }, children: "\u83B7\u53D6\u9A8C\u8BC1\u7801" })) : (_jsxs(_Fragment, { children: [_jsxs("label", { children: ["\u9A8C\u8BC1\u7801", _jsx("input", { value: otp, onChange: (event) => { setOtp(event.target.value); }, inputMode: "numeric", autoComplete: "one-time-code" })] }), _jsx("button", { type: "button", className: css.primary, disabled: props.pending || handle.trim() === '' || otp.trim() === '', onClick: () => { void register(); }, children: "\u6CE8\u518C\u8EAB\u4EFD" }), _jsx("button", { type: "button", className: css.linkButton, disabled: props.pending || retrySeconds > 0, onClick: () => { void requestOtp(); }, children: retrySeconds > 0 ? `${retrySeconds} 秒后重新获取` : '重新获取验证码' })] })), notice !== null && _jsx("p", { className: css.notice, role: "status", children: notice })] }));
227
- }
228
- /** Let a signed-out installation resume its preserved local identity. */
229
- function SignedOut(props) {
230
- const [error, setError] = useState(null);
231
- const login = async () => {
232
- setError(null);
233
- const result = await props.login();
234
- if (!result.ok)
235
- setError(result.error);
236
- };
237
- return (_jsxs("div", { className: css.centerState, children: [_jsx("div", { className: css.registrationIcon, children: _jsx(IconUserOutline16, { size: 24 }) }), _jsx("h3", { children: "\u5DF2\u9000\u51FA AWiki" }), _jsx("p", { children: "\u672C\u673A\u8EAB\u4EFD\u548C\u6D88\u606F\u6570\u636E\u4ECD\u5B89\u5168\u4FDD\u7559\u3002\u91CD\u65B0\u8FDB\u5165\u540E\u4F1A\u7EE7\u7EED\u4F7F\u7528\u539F\u6765\u7684 DID \u548C Handle\u3002" }), _jsx("button", { type: "button", className: css.primary, disabled: props.pending, onClick: () => { void login(); }, children: "\u91CD\u65B0\u8FDB\u5165" }), error !== null && _jsx("small", { className: css.inlineError, role: "alert", children: error })] }));
238
- }
239
189
  /** Prefer the peer WNS display name for a direct chat; groups keep their title. */
240
190
  function conversationLabel(conversation) {
241
191
  return conversation.kind === 'direct'
242
192
  ? (conversation.displayName ?? conversation.title)
243
193
  : conversation.title;
244
194
  }
245
- /** Show only the deployment identity's WNS display name, never its routing Handle. */
246
- function identityLabel(identity) {
247
- return identity.displayName ?? '未设置昵称';
248
- }
249
- /** Editable deployment identity summary shown above the conversation roster. */
250
- function IdentityCard(props) {
251
- const [editing, setEditing] = useState(false);
252
- const [draft, setDraft] = useState(props.identity.displayName ?? '');
253
- const [error, setError] = useState(null);
254
- useEffect(() => {
255
- if (!editing)
256
- setDraft(props.identity.displayName ?? '');
257
- }, [editing, props.identity.displayName]);
258
- const cancel = () => {
259
- setDraft(props.identity.displayName ?? '');
260
- setError(null);
261
- setEditing(false);
262
- };
263
- const save = async () => {
264
- const displayName = draft.trim();
265
- const length = Array.from(displayName).length;
266
- if (length === 0) {
267
- setError('请输入昵称');
268
- return;
269
- }
270
- if (length > 50) {
271
- setError('昵称不能超过 50 个字符');
272
- return;
273
- }
274
- setError(null);
275
- const result = await props.updateDisplayName(displayName);
276
- if (!result.ok) {
277
- setError(result.error);
278
- return;
279
- }
280
- setDraft(result.value.displayName ?? displayName);
281
- setEditing(false);
282
- };
283
- return (_jsxs("div", { className: css.identityCard, children: [_jsx("div", { className: css.identityNameRow, children: editing ? (_jsxs("form", { className: css.identityEditor, onSubmit: (event) => { event.preventDefault(); void save(); }, children: [_jsx("input", { "aria-label": "\u6635\u79F0", autoFocus: true, disabled: props.pending, value: draft, onChange: (event) => { setDraft(event.target.value); }, onKeyDown: (event) => {
284
- if (event.key === 'Escape') {
285
- event.stopPropagation();
286
- cancel();
287
- }
288
- } }), _jsx("button", { type: "submit", "aria-label": "\u4FDD\u5B58\u6635\u79F0", disabled: props.pending, children: _jsx(IconCheckOutline16, { size: 14 }) }), _jsx("button", { type: "button", "aria-label": "\u53D6\u6D88\u4FEE\u6539\u6635\u79F0", disabled: props.pending, onClick: cancel, children: _jsx(IconCloseOutline16, { size: 14 }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: props.identity.did, side: "bottom", children: _jsx("button", { type: "button", className: css.identityName, disabled: props.pending, onClick: () => { setError(null); setEditing(true); }, children: identityLabel(props.identity) }) }), _jsx(Tooltip, { label: "\u4FEE\u6539\u6635\u79F0", side: "right", children: _jsx("button", { type: "button", className: css.identityEdit, "aria-label": "\u4FEE\u6539\u6635\u79F0", disabled: props.pending, onClick: () => { setError(null); setEditing(true); }, children: _jsx(IconEditOutline16, { size: 14 }) }) })] })) }), _jsx("small", { className: css.identityHandle, children: props.identity.handle }), _jsxs("span", { className: css.identityStatus, children: [_jsx("i", {}), "\u5728\u7EBF"] }), error !== null && _jsx("small", { className: css.identityError, role: "alert", children: error })] }));
289
- }
290
195
  /** Switch the shared identity between messaging and on-demand mail. */
291
196
  function ModeTabs(props) {
292
197
  return (_jsxs("div", { className: css.modeTabs, role: "tablist", "aria-label": "AWiki \u529F\u80FD", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": props.mode === 'chat', onClick: () => { props.onChange('chat'); }, children: "\u4F1A\u8BDD" }), _jsxs("button", { type: "button", role: "tab", "aria-selected": props.mode === 'mail', onClick: () => { props.onChange('mail'); }, children: ["\u90AE\u4EF6", props.mailUnreadCount > 0 && _jsx("small", { children: props.mailUnreadCount > 99 ? '99+' : props.mailUnreadCount })] })] }));
@@ -299,11 +204,20 @@ function senderLabel(message, peerLabel) {
299
204
  }
300
205
  /** Render one direct or group conversation row. */
301
206
  function ConversationRow(props) {
207
+ const [menuOpen, setMenuOpen] = useState(false);
208
+ const labelId = useId();
302
209
  const label = conversationLabel(props.conversation);
303
210
  const unreadCount = props.conversation.unreadCount ?? 0;
304
211
  const unreadLabel = unreadCount > 99 ? '99+' : String(unreadCount);
305
212
  const preview = props.conversation.lastMessagePreview ?? '暂无消息';
306
- return (_jsxs("button", { type: "button", className: css.conversationRow, "data-active": props.active || undefined, "aria-label": unreadCount > 0 ? `${label},${unreadCount} 条未读消息` : undefined, onClick: props.onSelect, children: [_jsxs("span", { className: css.avatar, children: [props.conversation.kind === 'direct' ? '私' : '群', unreadCount > 0 && _jsx("span", { className: css.conversationUnreadBadge, "aria-hidden": "true", children: unreadLabel })] }), _jsxs("span", { className: css.conversationText, children: [_jsxs("span", { className: css.conversationHeader, children: [_jsx("strong", { children: label }), props.conversation.lastMessageAt !== undefined && (_jsx("time", { className: css.conversationTime, children: conversationTime(props.conversation.lastMessageAt) }))] }), _jsx("small", { children: preview })] })] }));
213
+ return (_jsxs("div", { className: css.conversationRow, "data-active": props.active || undefined, children: [_jsxs("button", { type: "button", className: css.conversationSelect, "aria-label": unreadCount > 0 ? `${label},${unreadCount} 条未读消息` : undefined, onClick: props.onSelect, children: [_jsxs("span", { className: css.avatar, children: [props.conversation.kind === 'direct' ? '私' : '群', unreadCount > 0 && _jsx("span", { className: css.conversationUnreadBadge, "aria-hidden": "true", children: unreadLabel })] }), _jsxs("span", { className: css.conversationText, children: [_jsxs("span", { className: css.conversationHeader, children: [_jsx("strong", { id: labelId, children: label }), props.conversation.lastMessageAt !== undefined && (_jsx("time", { className: css.conversationTime, children: conversationTime(props.conversation.lastMessageAt) }))] }), _jsx("small", { children: preview })] })] }), _jsx(Menu, { open: menuOpen, onClose: () => { setMenuOpen(false); }, align: "end", portal: true, compact: true, items: [{
214
+ id: 'remove',
215
+ label: '从会话列表移除',
216
+ icon: _jsx(IconArchiveOutline20, { size: 14 }),
217
+ }], onSelect: () => {
218
+ setMenuOpen(false);
219
+ props.onRemove();
220
+ }, anchor: (_jsx("button", { type: "button", className: css.conversationMenu, "aria-label": "\u66F4\u591A\u4F1A\u8BDD\u64CD\u4F5C", "aria-describedby": labelId, title: `管理会话:${label}`, "aria-expanded": menuOpen, "aria-haspopup": "menu", disabled: props.pending, onClick: (event) => { event.stopPropagation(); setMenuOpen(value => !value); }, children: _jsx(IconEllipsisOutline16, { size: 15 }) })) })] }));
307
221
  }
308
222
  /** Render one AWiki message, including an attachment download action. */
309
223
  function MessageRow(props) {
@@ -353,11 +267,11 @@ function MessageRow(props) {
353
267
  }
354
268
  saveDownloadedAttachment(result.value);
355
269
  };
356
- return (_jsxs("div", { className: css.message, "data-message-id": props.message.id, "data-outgoing": props.message.outgoing || undefined, children: [_jsxs("div", { className: css.messageMeta, children: [_jsx("span", { children: senderLabel(props.message, props.peerLabel) }), _jsx("time", { children: time(props.message.sentAt) })] }), props.message.content.kind === 'text' ? (_jsx("p", { children: props.message.content.text })) : preview !== null ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.imageAttachment, "aria-label": `下载图片 ${props.message.content.attachment.fileName}`, onClick: () => { void download(); }, children: [_jsx("img", { src: preview.url, alt: props.message.content.attachment.fileName, onLoad: () => { props.onImageLoad?.(props.message.id); } }), _jsxs("span", { children: [_jsx("strong", { children: props.message.content.attachment.fileName }), _jsxs("small", { children: [props.message.content.attachment.size, " \u5B57\u8282"] }), _jsx(IconDownloadOutline16, { size: 16 })] })] }), props.message.content.caption !== undefined && _jsx("p", { className: css.caption, children: props.message.content.caption })] })) : (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.attachment, disabled: previewLoading, onClick: () => { void download(); }, children: [_jsxs("span", { children: [_jsx("strong", { children: props.message.content.attachment.fileName }), _jsx("small", { children: previewLoading ? '正在加载图片预览…' : `${props.message.content.attachment.size} 字节` })] }), _jsx(IconDownloadOutline16, { size: 16 })] }), props.message.content.caption !== undefined && _jsx("p", { className: css.caption, children: props.message.content.caption })] })), error !== null && _jsx("small", { className: css.inlineError, children: error })] }));
270
+ return (_jsxs("div", { className: css.message, "data-message-id": props.message.id, "data-outgoing": props.message.outgoing || undefined, children: [_jsxs("div", { className: css.messageMeta, children: [_jsx("span", { children: senderLabel(props.message, props.peerLabel) }), _jsx("time", { children: time(props.message.sentAt) })] }), props.message.content.kind === 'text' ? (_jsx("p", { children: _jsx(MentionText, { text: props.message.content.text, ...props.message.content.mentions === undefined ? {} : { mentions: props.message.content.mentions } }) })) : preview !== null ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.imageAttachment, "aria-label": `下载图片 ${props.message.content.attachment.fileName}`, onClick: () => { void download(); }, children: [_jsx("img", { src: preview.url, alt: props.message.content.attachment.fileName, onLoad: () => { props.onImageLoad?.(props.message.id); } }), _jsxs("span", { children: [_jsx("strong", { children: props.message.content.attachment.fileName }), _jsxs("small", { children: [props.message.content.attachment.size, " \u5B57\u8282"] }), _jsx(IconDownloadOutline16, { size: 16 })] })] }), props.message.content.caption !== undefined && _jsx("p", { className: css.caption, children: props.message.content.caption })] })) : (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.attachment, disabled: previewLoading, onClick: () => { void download(); }, children: [_jsxs("span", { children: [_jsx("strong", { children: props.message.content.attachment.fileName }), _jsx("small", { children: previewLoading ? '正在加载图片预览…' : `${props.message.content.attachment.size} 字节` })] }), _jsx(IconDownloadOutline16, { size: 16 })] }), props.message.content.caption !== undefined && _jsx("p", { className: css.caption, children: props.message.content.caption })] })), error !== null && _jsx("small", { className: css.inlineError, children: error })] }));
357
271
  }
358
272
  /** Render one optimistic outgoing bubble while the Host confirms delivery. */
359
273
  function PendingMessageRow(props) {
360
- return (_jsxs("div", { className: css.pendingMessage, role: "status", "aria-live": "polite", "aria-label": "\u6D88\u606F\u53D1\u9001\u4E2D", children: [_jsx(IconLoadingOutline16, { className: css.pendingMessageSpinner, size: 14 }), _jsxs("div", { className: css.pendingMessageContent, children: [_jsxs("div", { className: css.messageMeta, children: [_jsx("span", { children: "\u6211" }), _jsx("time", { children: time(props.draft.startedAt) })] }), props.draft.content.kind === 'text' ? (_jsx("p", { children: props.draft.content.text })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.pendingAttachment, children: [_jsx(IconPaperclipOutline16, { size: 16 }), _jsxs("span", { children: [_jsx("strong", { children: props.draft.content.fileName }), _jsxs("small", { children: [props.draft.content.size, " \u5B57\u8282"] })] })] }), props.draft.content.caption !== undefined && _jsx("p", { className: css.pendingCaption, children: props.draft.content.caption })] }))] })] }));
274
+ return (_jsxs("div", { className: css.pendingMessage, role: "status", "aria-live": "polite", "aria-label": "\u6D88\u606F\u53D1\u9001\u4E2D", children: [_jsx(IconLoadingOutline16, { className: css.pendingMessageSpinner, size: 14 }), _jsxs("div", { className: css.pendingMessageContent, children: [_jsxs("div", { className: css.messageMeta, children: [_jsx("span", { children: "\u6211" }), _jsx("time", { children: time(props.draft.startedAt) })] }), props.draft.content.kind === 'text' ? (_jsx("p", { children: _jsx(MentionText, { text: props.draft.content.text, mentions: protocolMentions(props.draft.content.text, props.draft.content.mentions) }) })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.pendingAttachment, children: [_jsx(IconPaperclipOutline16, { size: 16 }), _jsxs("span", { children: [_jsx("strong", { children: props.draft.content.fileName }), _jsxs("small", { children: [props.draft.content.size, " \u5B57\u8282"] })] })] }), props.draft.content.caption !== undefined && _jsx("p", { className: css.pendingCaption, children: props.draft.content.caption })] }))] })] }));
361
275
  }
362
276
  function summaryRangeLabel(summary) {
363
277
  const scope = summary.range.kind === 'unread' ? '未读以来' : '最近消息';
@@ -405,11 +319,20 @@ function SummaryPanel(props) {
405
319
  function Chat(props) {
406
320
  const { view } = props;
407
321
  const [text, setText] = useState('');
322
+ const [mentionDrafts, setMentionDrafts] = useState([]);
323
+ const [mentionQuery, setMentionQuery] = useState(null);
324
+ const [mentionCandidateIndex, setMentionCandidateIndex] = useState(0);
408
325
  const [file, setFile] = useState(null);
409
326
  const [sendingDraft, setSendingDraft] = useState(null);
327
+ const [groupDetailsOpen, setGroupDetailsOpen] = useState(false);
328
+ const [threadMenuOpen, setThreadMenuOpen] = useState(false);
329
+ const [hiddenConversationsOpen, setHiddenConversationsOpen] = useState(false);
330
+ const [removeCandidate, setRemoveCandidate] = useState(null);
331
+ const [conversationPreferenceError, setConversationPreferenceError] = useState(null);
410
332
  const [previewUrl, setPreviewUrl] = useState(null);
411
333
  const [fileError, setFileError] = useState(null);
412
334
  const input = useRef(null);
335
+ const composer = useRef(null);
413
336
  const history = useRef(null);
414
337
  const previousConversationId = useRef(null);
415
338
  const previousMessageTail = useRef(null);
@@ -420,13 +343,66 @@ function Chat(props) {
420
343
  const [historyAwayFromBottom, setHistoryAwayFromBottom] = useState(false);
421
344
  const [unseenMessageCount, setUnseenMessageCount] = useState(0);
422
345
  const selected = view.conversations.find(value => value.id === view.selectedConversationId);
346
+ const selectedGroupAccess = selected?.kind === 'group'
347
+ ? view.groupAccess?.groupDid === selected.groupDid
348
+ ? view.groupAccess
349
+ : { groupDid: selected.groupDid, status: 'loading' }
350
+ : null;
351
+ const groupWritable = selectedGroupAccess === null || selectedGroupAccess.status === 'available';
352
+ const candidates = mentionQuery === null || selected?.kind !== 'group'
353
+ ? []
354
+ : mentionCandidates(view.groupMembers, view.identity.did, mentionQuery.query);
423
355
  const summary = selected === undefined ? undefined : view.summaries[selected.id];
424
356
  const summaryPanelId = useId();
357
+ const mentionListId = useId();
425
358
  selectedConversationId.current = view.selectedConversationId;
426
359
  const visibleSendingDraft = sendingDraft?.conversationId === view.selectedConversationId
427
360
  && !view.messages.some(message => message.id === sendingDraft.messageId)
428
361
  ? sendingDraft
429
362
  : null;
363
+ useEffect(() => {
364
+ setMentionDrafts([]);
365
+ setMentionQuery(null);
366
+ setMentionCandidateIndex(0);
367
+ setGroupDetailsOpen(false);
368
+ setThreadMenuOpen(false);
369
+ }, [view.selectedConversationId]);
370
+ useEffect(() => {
371
+ if (view.hiddenConversations.length === 0)
372
+ setHiddenConversationsOpen(false);
373
+ }, [view.hiddenConversations.length]);
374
+ useEffect(() => {
375
+ if (mentionCandidateIndex >= candidates.length)
376
+ setMentionCandidateIndex(0);
377
+ }, [candidates.length, mentionCandidateIndex]);
378
+ const syncMentionQuery = (value, selectionStart) => {
379
+ if (selected?.kind !== 'group' || selectionStart === null) {
380
+ setMentionQuery(null);
381
+ return;
382
+ }
383
+ setMentionQuery(activeMentionQuery(value, utf16IndexToCodePointIndex(value, selectionStart)));
384
+ setMentionCandidateIndex(0);
385
+ };
386
+ const chooseMention = (index) => {
387
+ const query = mentionQuery;
388
+ const candidate = candidates[index];
389
+ if (query === null || candidate === undefined)
390
+ return;
391
+ const insertion = insertMention(text, query, candidate, `mention-${crypto.randomUUID()}`);
392
+ const retained = transformMentionDrafts(text, insertion.text, mentionDrafts);
393
+ setText(insertion.text);
394
+ setMentionDrafts([...retained, insertion.mention]);
395
+ setMentionQuery(null);
396
+ setMentionCandidateIndex(0);
397
+ requestAnimationFrame(() => {
398
+ const textarea = composer.current;
399
+ if (textarea === null)
400
+ return;
401
+ const caret = codePointIndexToUtf16Index(insertion.text, insertion.caret);
402
+ textarea.focus();
403
+ textarea.setSelectionRange(caret, caret);
404
+ });
405
+ };
430
406
  const markSelectedConversationReadAtBottom = () => {
431
407
  const node = history.current;
432
408
  const newestRendered = view.messages.at(-1);
@@ -570,19 +546,24 @@ function Chat(props) {
570
546
  const sendMessage = async () => {
571
547
  if (sendingDraft !== null || view.selectedConversationId === null)
572
548
  return;
573
- const draft = text.trim();
549
+ const draft = text;
574
550
  const conversationId = view.selectedConversationId;
575
551
  if (file === null) {
576
552
  /* v8 ignore next -- the only invocation control is disabled while both text and attachment are empty. */
577
- if (draft === '')
553
+ if (draft.trim() === '')
578
554
  return;
555
+ const mentions = selected?.kind === 'group' ? protocolMentions(draft, mentionDrafts) : [];
579
556
  const messageId = `msg-${crypto.randomUUID()}`;
580
- setSendingDraft({ conversationId, messageId, startedAt: Date.now(), content: { kind: 'text', text: draft } });
557
+ setSendingDraft({ conversationId, messageId, startedAt: Date.now(), content: { kind: 'text', text: draft, mentions: mentionDrafts } });
581
558
  setText('');
582
- const result = await props.sendText(draft, messageId);
559
+ setMentionDrafts([]);
560
+ setMentionQuery(null);
561
+ const result = await props.sendText(draft, messageId, mentions);
583
562
  setSendingDraft(null);
584
- if (!result.ok && selectedConversationId.current === conversationId)
563
+ if (!result.ok && selectedConversationId.current === conversationId) {
585
564
  setText(draft);
565
+ setMentionDrafts(mentionDrafts);
566
+ }
586
567
  return;
587
568
  }
588
569
  if (file.size > view.attachmentMaxBytes) {
@@ -601,25 +582,62 @@ function Chat(props) {
601
582
  kind: 'attachment',
602
583
  fileName: selectedFile.name,
603
584
  size: selectedFile.size,
604
- ...(draft === '' ? {} : { caption: draft }),
585
+ ...(draft.trim() === '' ? {} : { caption: draft.trim() }),
605
586
  },
606
587
  });
607
588
  clearFile();
608
589
  setText('');
590
+ setMentionDrafts([]);
591
+ setMentionQuery(null);
609
592
  const result = await props.sendAttachment({
610
593
  fileName: selectedFile.name,
611
594
  mimeType: selectedFile.type || 'application/octet-stream',
612
595
  bytesBase64,
613
- ...(draft === '' ? {} : { caption: draft }),
596
+ ...(draft.trim() === '' ? {} : { caption: draft.trim() }),
614
597
  clientMessageId: messageId,
615
598
  });
616
599
  setSendingDraft(null);
617
600
  if (!result.ok && selectedConversationId.current === conversationId) {
618
601
  setFile(selectedFile);
619
602
  setText(draft);
603
+ setMentionDrafts(mentionDrafts);
604
+ }
605
+ };
606
+ const removeConversation = async () => {
607
+ if (removeCandidate === null)
608
+ return;
609
+ setConversationPreferenceError(null);
610
+ const result = await props.hideConversation(removeCandidate.id);
611
+ if (!result.ok) {
612
+ setConversationPreferenceError(result.error);
613
+ return;
620
614
  }
615
+ setRemoveCandidate(null);
616
+ setGroupDetailsOpen(false);
621
617
  };
622
- return (_jsxs("div", { className: css.chat, children: [_jsxs("aside", { className: css.roster, "data-hidden": selected !== undefined || undefined, "aria-label": "\u4F1A\u8BDD", children: [_jsx(IdentityCard, { identity: view.identity, pending: view.pending !== null, updateDisplayName: props.updateDisplayName }), props.modeTabs, _jsxs("div", { className: css.rosterHeader, children: [_jsx("div", { className: css.rosterTitle, children: "\u4F1A\u8BDD" }), props.composeMenu] }), _jsxs("div", { className: css.conversationList, children: [view.conversations.map(conversation => (_jsx(ConversationRow, { conversation: conversation, active: conversation.id === view.selectedConversationId, onSelect: () => { void props.selectConversation(conversation.id); } }, conversation.id))), view.conversations.length === 0 && _jsx("p", { className: css.empty, children: "\u8FD8\u6CA1\u6709\u53EF\u7528\u7684\u79C1\u804A\u6216\u7FA4\u804A\u3002" })] }), view.conversationsHasMore && _jsx("button", { type: "button", className: css.more, onClick: () => { void props.loadMoreConversations(); }, children: "\u52A0\u8F7D\u66F4\u591A\u4F1A\u8BDD" })] }), _jsx("section", { className: css.thread, "data-visible": selected !== undefined || undefined, children: selected === undefined ? (_jsxs("div", { className: css.threadEmpty, children: [_jsx(IconGlobeOutline14, { size: 28 }), _jsx("p", { children: "\u9009\u62E9\u4E00\u4E2A\u79C1\u804A\u6216\u7FA4\u804A\u67E5\u770B\u6D88\u606F\u3002" })] })) : (_jsxs(_Fragment, { children: [_jsxs("header", { className: css.threadHeader, children: [_jsx("button", { type: "button", className: css.back, "aria-label": "\u8FD4\u56DE\u4F1A\u8BDD\u5217\u8868", onClick: () => { void props.selectConversation(null); }, children: _jsx(IconChevronLeftOutline14, {}) }), _jsxs("div", { className: css.threadTitle, children: [_jsx("strong", { children: conversationLabel(selected) }), _jsx("small", { children: selected.kind === 'direct' ? '私聊' : '群聊' })] }), _jsxs("button", { type: "button", className: css.summaryTrigger, "aria-controls": summaryPanelId, "aria-expanded": summary === undefined ? undefined : !summary.collapsed, "aria-label": summary?.status === 'loading' ? '正在生成 AI 总结' : summary?.collapsed === true ? '展开 AI 总结' : '生成 AI 总结', disabled: summary?.status === 'loading', onClick: () => {
618
+ const restoreConversation = async (conversationId) => {
619
+ setConversationPreferenceError(null);
620
+ const result = await props.restoreConversation(conversationId);
621
+ if (!result.ok)
622
+ setConversationPreferenceError(result.error);
623
+ };
624
+ return (_jsxs("div", { className: css.chat, children: [_jsxs("aside", { className: css.roster, "data-hidden": selected !== undefined || undefined, "aria-label": "\u4F1A\u8BDD", children: [_jsx(AwikiProfileCard, { identity: view.identity, profile: view.profile, pending: view.pending !== null, updateProfile: props.updateProfile }), props.modeTabs, _jsxs("div", { className: css.rosterHeader, children: [_jsx("div", { className: css.rosterTitle, children: "\u4F1A\u8BDD" }), _jsxs("div", { className: css.rosterActions, children: [view.hiddenConversations.length > 0 && (_jsx(Tooltip, { label: "\u67E5\u770B\u5DF2\u9690\u85CF\u4F1A\u8BDD", side: "right", children: _jsxs("button", { type: "button", className: css.rosterAction, "aria-label": `查看已隐藏会话,${view.hiddenConversations.length} 个`, onClick: () => { setConversationPreferenceError(null); setHiddenConversationsOpen(true); }, children: [_jsx(IconArchiveOutline20, { size: 15 }), _jsx("small", { children: view.hiddenConversations.length > 99 ? '99+' : view.hiddenConversations.length })] }) })), props.composeMenu] })] }), view.groupRecovery !== null && (_jsxs("div", { className: css.groupRecoveryNotice, "data-status": view.groupRecovery.status, role: view.groupRecovery.status === 'blocked' ? 'alert' : 'status', children: [_jsxs("span", { children: [_jsx("strong", { children: view.groupRecovery.status === 'pending'
625
+ ? '正在恢复旧群聊身份'
626
+ : view.groupRecovery.status === 'blocked'
627
+ ? '部分旧群聊需要处理'
628
+ : '暂时无法检查旧群聊身份' }), _jsx("small", { children: view.groupRecovery.status === 'pending'
629
+ ? `${view.groupRecovery.pending} 个群聊尚未完成,其他会话不受影响。`
630
+ : view.groupRecovery.status === 'blocked'
631
+ ? `${view.groupRecovery.blocked} 个群聊未能自动恢复,其他会话不受影响。`
632
+ : '私聊和新群聊不受影响,可稍后重试。' })] }), _jsx(Tooltip, { label: "\u91CD\u8BD5\u7FA4\u804A\u8EAB\u4EFD\u6062\u590D", side: "right", children: _jsx("button", { type: "button", "aria-label": "\u91CD\u8BD5\u7FA4\u804A\u8EAB\u4EFD\u6062\u590D", disabled: view.pending !== null, onClick: () => { void props.retryGroupRebindRecovery(); }, children: _jsx(IconRefreshOutline14, { size: 14 }) }) }), _jsx(Tooltip, { label: "\u7A0D\u540E\u5904\u7406", side: "right", children: _jsx("button", { type: "button", "aria-label": "\u5173\u95ED\u65E7\u7FA4\u804A\u5904\u7406\u63D0\u793A", disabled: view.pending !== null, onClick: () => { void props.dismissGroupRecoveryNotice(); }, children: _jsx(IconCloseOutline16, { size: 14 }) }) })] })), _jsxs("div", { className: css.conversationList, children: [view.conversations.map(conversation => (_jsx(ConversationRow, { conversation: conversation, active: conversation.id === view.selectedConversationId, pending: view.pending !== null, onSelect: () => { void props.selectConversation(conversation.id); }, onRemove: () => { setConversationPreferenceError(null); setRemoveCandidate(conversation); } }, conversation.id))), view.conversations.length === 0 && _jsx("p", { className: css.empty, children: "\u8FD8\u6CA1\u6709\u53EF\u7528\u7684\u79C1\u804A\u6216\u7FA4\u804A\u3002" })] }), view.conversationsHasMore && _jsx("button", { type: "button", className: css.more, onClick: () => { void props.loadMoreConversations(); }, children: "\u52A0\u8F7D\u66F4\u591A\u4F1A\u8BDD" })] }), _jsx("section", { className: css.thread, "data-visible": selected !== undefined || undefined, children: selected === undefined ? (_jsxs("div", { className: css.threadEmpty, children: [_jsx(IconGlobeOutline14, { size: 28 }), _jsx("p", { children: "\u9009\u62E9\u4E00\u4E2A\u79C1\u804A\u6216\u7FA4\u804A\u67E5\u770B\u6D88\u606F\u3002" })] })) : (_jsxs(_Fragment, { children: [_jsxs("header", { className: css.threadHeader, children: [_jsx("button", { type: "button", className: css.back, "aria-label": "\u8FD4\u56DE\u4F1A\u8BDD\u5217\u8868", onClick: () => { void props.selectConversation(null); }, children: _jsx(IconChevronLeftOutline14, {}) }), _jsxs("div", { className: css.threadTitle, children: [_jsx("strong", { children: conversationLabel(selected) }), _jsx("small", { children: selected.kind === 'direct' ? '私聊' : '群聊' })] }), selected.kind === 'group' && (_jsx(Tooltip, { label: "\u7FA4\u804A\u8BE6\u60C5", side: "bottom", children: _jsx("button", { type: "button", className: css.threadAction, "aria-label": "\u6253\u5F00\u7FA4\u804A\u8BE6\u60C5", "aria-expanded": groupDetailsOpen, onClick: () => { setGroupDetailsOpen(value => !value); }, children: _jsx(IconSettingsOutline16, { size: 15 }) }) })), _jsx(Menu, { open: threadMenuOpen, onClose: () => { setThreadMenuOpen(false); }, align: "end", portal: true, compact: true, items: [{
633
+ id: 'remove',
634
+ label: '从会话列表移除',
635
+ icon: _jsx(IconArchiveOutline20, { size: 14 }),
636
+ }], onSelect: () => {
637
+ setThreadMenuOpen(false);
638
+ setConversationPreferenceError(null);
639
+ setRemoveCandidate(selected);
640
+ }, anchor: (_jsx("button", { type: "button", className: css.threadAction, "aria-label": "\u66F4\u591A\u4F1A\u8BDD\u64CD\u4F5C", "aria-expanded": threadMenuOpen, "aria-haspopup": "menu", disabled: view.pending !== null, onClick: () => { setThreadMenuOpen(value => !value); }, children: _jsx(IconEllipsisOutline16, { size: 15 }) })) }), _jsxs("button", { type: "button", className: css.summaryTrigger, "aria-controls": summaryPanelId, "aria-expanded": summary === undefined ? undefined : !summary.collapsed, "aria-label": summary?.status === 'loading' ? '正在生成 AI 总结' : summary?.collapsed === true ? '展开 AI 总结' : '生成 AI 总结', disabled: summary?.status === 'loading', onClick: () => {
623
641
  if (summary !== undefined && !summary.collapsed && summary.status !== 'error') {
624
642
  props.setSummaryCollapsed(selected.id, true);
625
643
  }
@@ -629,15 +647,50 @@ function Chat(props) {
629
647
  else {
630
648
  void props.summarizeConversation();
631
649
  }
632
- }, children: [summary?.status === 'loading' ? _jsx(IconLoadingOutline16, { size: 14 }) : _jsx(IconSparkle16, { size: 14 }), _jsx("span", { children: summary?.status === 'loading' ? '总结中' : 'AI 总结' }), summary !== undefined && _jsx(IconChevronDownOutline14, { size: 12 })] })] }), summary !== undefined && (_jsx(SummaryPanel, { id: summaryPanelId, summary: summary, regenerate: () => { void props.summarizeConversation(); }, collapse: collapsed => { props.setSummaryCollapsed(selected.id, collapsed); }, viewSource: viewSummarySource })), _jsxs("div", { className: css.historyShell, children: [_jsxs("div", { ref: history, className: css.history, role: "log", "aria-label": "\u6D88\u606F\u8BB0\u5F55", onScroll: syncHistoryPosition, children: [view.historyHasMore && _jsx("button", { type: "button", className: css.more, onClick: () => { void props.loadOlderHistory(); }, children: "\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F" }), view.localPending && (_jsxs("div", { className: css.historyLoading, role: "status", "aria-live": "polite", "aria-label": "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u6D88\u606F", children: [_jsx(IconLoadingOutline16, { size: 18 }), _jsx("span", { children: "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u6D88\u606F\u2026" })] })), !view.localPending && view.refreshing && view.messages.length === 0 && (_jsxs("div", { className: css.historyLoading, role: "status", "aria-live": "polite", "aria-label": "\u6B63\u5728\u540C\u6B65\u6D88\u606F", children: [_jsx(IconLoadingOutline16, { size: 18 }), _jsx("span", { children: "\u6B63\u5728\u540C\u6B65\u6D88\u606F\u2026" })] })), view.messages.map(message => (_jsx(MessageRow, { message: message, peerLabel: selected.kind === 'direct' ? conversationLabel(selected) : undefined, download: props.downloadAttachment, onImageLoad: scrollAfterInitialImage }, message.id))), visibleSendingDraft !== null && _jsx(PendingMessageRow, { draft: visibleSendingDraft }), !view.localPending && !view.refreshing && view.messages.length === 0 && visibleSendingDraft === null && _jsx("p", { className: css.empty, children: "\u6682\u65E0\u6D88\u606F\u3002" })] }), historyAwayFromBottom && (_jsxs("button", { type: "button", className: css.latestMessages, "aria-label": unseenMessageCount === 0 ? '下滑到最新消息' : `有 ${unseenMessageCount} 条新消息,下滑到最新消息`, onClick: () => { scrollHistoryToLatest(true); }, children: [_jsx(IconChevronDownOutline14, { size: 14 }), unseenMessageCount > 0 && _jsxs("span", { children: ["\u65B0\u6D88\u606F\uFF08", unseenMessageCount, "\uFF09"] })] }))] }), _jsxs("div", { className: css.composer, children: [fileError !== null && _jsx("small", { className: css.inlineError, role: "alert", children: fileError }), _jsxs("div", { className: css.composeInput, children: [file !== null && (_jsxs("div", { className: css.filePreview, "data-image": previewUrl !== null || undefined, children: [previewUrl === null
650
+ }, children: [summary?.status === 'loading' ? _jsx(IconLoadingOutline16, { size: 14 }) : _jsx(IconSparkle16, { size: 14 }), _jsx("span", { children: summary?.status === 'loading' ? '总结中' : 'AI 总结' }), summary !== undefined && _jsx(IconChevronDownOutline14, { size: 12 })] })] }), selectedGroupAccess !== null && selectedGroupAccess.status !== 'available' && (_jsx(AwikiGroupAccessNotice, { access: selectedGroupAccess, pending: view.pending !== null, onRetry: () => { void props.refreshSelectedGroup(); }, onRejoin: () => { void props.joinGroup(selectedGroupAccess.groupDid); }, onBack: () => { void props.selectConversation(null); }, onRemove: () => { setConversationPreferenceError(null); setRemoveCandidate(selected); } })), selected.kind === 'group' && groupDetailsOpen && (_jsx(AwikiGroupDetails, { group: view.selectedGroup, fallback: { groupDid: selected.groupDid, title: conversationLabel(selected) }, access: selectedGroupAccess ?? { groupDid: selected.groupDid, status: 'loading' }, members: view.groupMembers, hasMore: view.groupMembersHasMore, identity: view.identity, pending: view.pending !== null, refreshSelectedGroup: props.refreshSelectedGroup, loadMoreGroupMembers: props.loadMoreGroupMembers, addSelectedGroupMember: props.addSelectedGroupMember, removeSelectedGroupMember: props.removeSelectedGroupMember, leaveSelectedGroup: props.leaveSelectedGroup, joinGroup: props.joinGroup, onClose: () => { setGroupDetailsOpen(false); }, onRemove: () => { setConversationPreferenceError(null); setRemoveCandidate(selected); } })), summary !== undefined && (_jsx(SummaryPanel, { id: summaryPanelId, summary: summary, regenerate: () => { void props.summarizeConversation(); }, collapse: collapsed => { props.setSummaryCollapsed(selected.id, collapsed); }, viewSource: viewSummarySource })), _jsxs("div", { className: css.historyShell, children: [_jsxs("div", { ref: history, className: css.history, role: "log", "aria-label": "\u6D88\u606F\u8BB0\u5F55", onScroll: syncHistoryPosition, children: [view.historyHasMore && _jsx("button", { type: "button", className: css.more, onClick: () => { void props.loadOlderHistory(); }, children: "\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F" }), view.localPending && (_jsxs("div", { className: css.historyLoading, role: "status", "aria-live": "polite", "aria-label": "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u6D88\u606F", children: [_jsx(IconLoadingOutline16, { size: 18 }), _jsx("span", { children: "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u6D88\u606F\u2026" })] })), !view.localPending && view.refreshing && view.messages.length === 0 && (_jsxs("div", { className: css.historyLoading, role: "status", "aria-live": "polite", "aria-label": "\u6B63\u5728\u540C\u6B65\u6D88\u606F", children: [_jsx(IconLoadingOutline16, { size: 18 }), _jsx("span", { children: "\u6B63\u5728\u540C\u6B65\u6D88\u606F\u2026" })] })), view.messages.map(message => (_jsx(MessageRow, { message: message, peerLabel: selected.kind === 'direct' ? conversationLabel(selected) : undefined, download: props.downloadAttachment, onImageLoad: scrollAfterInitialImage }, message.id))), visibleSendingDraft !== null && _jsx(PendingMessageRow, { draft: visibleSendingDraft }), !view.localPending && !view.refreshing && view.messages.length === 0 && visibleSendingDraft === null && _jsx("p", { className: css.empty, children: "\u6682\u65E0\u6D88\u606F\u3002" })] }), historyAwayFromBottom && (_jsxs("button", { type: "button", className: css.latestMessages, "aria-label": unseenMessageCount === 0 ? '下滑到最新消息' : `有 ${unseenMessageCount} 条新消息,下滑到最新消息`, onClick: () => { scrollHistoryToLatest(true); }, children: [_jsx(IconChevronDownOutline14, { size: 14 }), unseenMessageCount > 0 && _jsxs("span", { children: ["\u65B0\u6D88\u606F\uFF08", unseenMessageCount, "\uFF09"] })] }))] }), _jsxs("div", { className: css.composer, children: [fileError !== null && _jsx("small", { className: css.inlineError, role: "alert", children: fileError }), _jsxs("div", { className: css.composeInput, children: [file !== null && (_jsxs("div", { className: css.filePreview, "data-image": previewUrl !== null || undefined, children: [previewUrl === null
633
651
  ? _jsx("span", { className: css.filePreviewIcon, children: _jsx(IconPaperclipOutline16, {}) })
634
- : _jsx("img", { src: previewUrl, alt: file.name }), previewUrl === null && _jsx("span", { className: css.filePreviewName, children: file.name }), _jsx("button", { type: "button", className: css.removeFile, "aria-label": `移除附件 ${file.name}`, onClick: clearFile, children: _jsx(IconCloseOutline16, { size: 12 }) })] })), _jsx("textarea", { value: text, onChange: (event) => { setText(event.target.value); }, onKeyDown: (event) => {
652
+ : _jsx("img", { src: previewUrl, alt: file.name }), previewUrl === null && _jsx("span", { className: css.filePreviewName, children: file.name }), _jsx("button", { type: "button", className: css.removeFile, "aria-label": `移除附件 ${file.name}`, disabled: !groupWritable, onClick: clearFile, children: _jsx(IconCloseOutline16, { size: 12 }) })] })), mentionQuery !== null && candidates.length > 0 && (_jsx("div", { id: mentionListId, className: css.mentionCandidates, role: "listbox", "aria-label": "\u53EF\u63D0\u53CA\u7684\u7FA4\u6210\u5458", children: candidates.map((candidate, index) => (_jsxs("button", { type: "button", role: "option", "aria-selected": index === mentionCandidateIndex, onMouseDown: (event) => { event.preventDefault(); chooseMention(index); }, children: [_jsx("span", { children: candidate.label.slice(0, 1).toLocaleUpperCase() }), _jsx("strong", { children: candidate.label }), _jsx("small", { children: candidate.member.handle ?? candidate.member.did })] }, candidate.member.did))) })), _jsx("textarea", { ref: composer, value: text, disabled: !groupWritable, "aria-controls": mentionQuery !== null && candidates.length > 0 ? mentionListId : undefined, "aria-expanded": mentionQuery !== null && candidates.length > 0, "aria-autocomplete": "list", onChange: (event) => {
653
+ const next = event.target.value;
654
+ setMentionDrafts(current => transformMentionDrafts(text, next, current));
655
+ setText(next);
656
+ syncMentionQuery(next, event.target.selectionStart);
657
+ }, onSelect: (event) => { syncMentionQuery(text, event.currentTarget.selectionStart); }, onKeyDown: (event) => {
658
+ if (mentionQuery !== null && candidates.length > 0) {
659
+ if (event.key === 'ArrowDown') {
660
+ event.preventDefault();
661
+ setMentionCandidateIndex(index => (index + 1) % candidates.length);
662
+ return;
663
+ }
664
+ if (event.key === 'ArrowUp') {
665
+ event.preventDefault();
666
+ setMentionCandidateIndex(index => (index - 1 + candidates.length) % candidates.length);
667
+ return;
668
+ }
669
+ if ((event.key === 'Enter' || event.key === 'Tab') && !event.nativeEvent.isComposing) {
670
+ event.preventDefault();
671
+ chooseMention(mentionCandidateIndex);
672
+ return;
673
+ }
674
+ if (event.key === 'Escape') {
675
+ event.preventDefault();
676
+ setMentionQuery(null);
677
+ return;
678
+ }
679
+ }
635
680
  if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing)
636
681
  return;
637
682
  event.preventDefault();
638
683
  if (view.pending === null && sendingDraft === null && (file !== null || text.trim() !== ''))
639
684
  void sendMessage();
640
- }, placeholder: "\u8F93\u5165\u6D88\u606F", rows: 2 }), _jsxs("div", { className: css.composeActions, children: [_jsx(Tooltip, { label: "\u6DFB\u52A0\u9644\u4EF6", side: "top", children: _jsx("button", { type: "button", className: css.filePicker, "aria-label": "\u6DFB\u52A0\u9644\u4EF6", disabled: view.pending !== null || sendingDraft !== null, onClick: () => { input.current?.click(); }, children: _jsx(IconPaperclipOutline16, {}) }) }), _jsx("input", { ref: input, type: "file", className: css.fileInput, "aria-label": "\u9009\u62E9\u4E00\u4E2A\u9644\u4EF6", onChange: (event) => { setFile(event.target.files?.[0] ?? null); setFileError(null); } }), _jsx("button", { type: "button", className: css.send, "aria-label": "\u53D1\u9001\u6D88\u606F", disabled: view.pending !== null || sendingDraft !== null || (file === null && text.trim() === ''), onClick: () => { void sendMessage(); }, children: _jsx(IconSendOutline16, {}) })] })] })] })] })) })] }));
685
+ }, placeholder: groupWritable ? '输入消息' : '当前群聊暂不可发送消息', rows: 2 }), _jsxs("div", { className: css.composeActions, children: [_jsx(Tooltip, { label: "\u6DFB\u52A0\u9644\u4EF6", side: "top", children: _jsx("button", { type: "button", className: css.filePicker, "aria-label": "\u6DFB\u52A0\u9644\u4EF6", disabled: !groupWritable || view.pending !== null || sendingDraft !== null, onClick: () => { input.current?.click(); }, children: _jsx(IconPaperclipOutline16, {}) }) }), _jsx("input", { ref: input, type: "file", className: css.fileInput, "aria-label": "\u9009\u62E9\u4E00\u4E2A\u9644\u4EF6", disabled: !groupWritable, onChange: (event) => { setFile(event.target.files?.[0] ?? null); setFileError(null); } }), _jsx("button", { type: "button", className: css.send, "aria-label": "\u53D1\u9001\u6D88\u606F", disabled: !groupWritable || view.pending !== null || sendingDraft !== null || (file === null && text.trim() === ''), onClick: () => { void sendMessage(); }, children: _jsx(IconSendOutline16, {}) })] })] })] })] })) }), _jsx(Modal, { open: removeCandidate !== null, onClose: () => { if (view.pending === null) {
686
+ setRemoveCandidate(null);
687
+ setConversationPreferenceError(null);
688
+ } }, title: "\u4ECE\u4F1A\u8BDD\u5217\u8868\u79FB\u9664", closeLabel: "\u53D6\u6D88", className: css.compactModal ?? '', description: removeCandidate === null
689
+ ? ''
690
+ : `“${conversationLabel(removeCandidate)}”只会从本机最近会话中移除,不会${removeCandidate.kind === 'group' ? '退出群聊或' : ''}清除已有消息。收到新消息后可能重新出现。`, footer: _jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: view.pending !== null, onClick: () => { setRemoveCandidate(null); setConversationPreferenceError(null); }, children: "\u53D6\u6D88" }), _jsx(Button, { type: "button", variant: "outline", disabled: view.pending !== null, onClick: () => { void removeConversation(); }, children: "\u786E\u8BA4\u79FB\u9664" })] }), children: conversationPreferenceError !== null && _jsx("div", { className: css.conversationPreferenceError, role: "alert", children: conversationPreferenceError }) }), _jsxs(Modal, { open: hiddenConversationsOpen, onClose: () => { if (view.pending === null) {
691
+ setHiddenConversationsOpen(false);
692
+ setConversationPreferenceError(null);
693
+ } }, title: "\u5DF2\u9690\u85CF\u4F1A\u8BDD", closeLabel: "\u5173\u95ED", className: css.compactModal ?? '', description: "\u8FD9\u4E9B\u4F1A\u8BDD\u53EA\u5728\u672C\u673A\u5217\u8868\u4E2D\u9690\u85CF\uFF0C\u6D88\u606F\u548C\u7FA4\u6210\u5458\u5173\u7CFB\u6CA1\u6709\u6539\u53D8\u3002", footer: _jsx(Button, { type: "button", variant: "outline", disabled: view.pending !== null, onClick: () => { setHiddenConversationsOpen(false); setConversationPreferenceError(null); }, children: "\u5B8C\u6210" }), children: [_jsx("div", { className: css.hiddenConversationList, children: view.hiddenConversations.map(conversation => (_jsxs("div", { className: css.hiddenConversationRow, children: [_jsx("span", { className: css.avatar, children: conversation.kind === 'direct' ? '私' : '群' }), _jsxs("span", { children: [_jsx("strong", { children: conversationLabel(conversation) }), _jsx("small", { children: conversation.lastMessagePreview ?? '暂无消息' })] }), _jsx("button", { type: "button", disabled: view.pending !== null, onClick: () => { void restoreConversation(conversation.id); }, children: "\u6062\u590D" })] }, conversation.id))) }), conversationPreferenceError !== null && _jsx("div", { className: css.conversationPreferenceError, role: "alert", children: conversationPreferenceError })] })] }));
641
694
  }
642
695
  /**
643
696
  * Render the frame-wide AWiki trigger and right-side drawer.
@@ -650,6 +703,7 @@ export function AwikiOverlay(props) {
650
703
  const titleId = useId();
651
704
  const composeTitleId = useId();
652
705
  const groupComposeTitleId = useId();
706
+ const groupJoinTitleId = useId();
653
707
  const [accountMenuOpen, setAccountMenuOpen] = useState(false);
654
708
  const [menuOpen, setMenuOpen] = useState(false);
655
709
  const [composeDirect, setComposeDirect] = useState(false);
@@ -659,12 +713,14 @@ export function AwikiOverlay(props) {
659
713
  const [groupName, setGroupName] = useState('');
660
714
  const [groupMembers, setGroupMembers] = useState('');
661
715
  const [groupComposeError, setGroupComposeError] = useState(null);
716
+ const [joinGroupOpen, setJoinGroupOpen] = useState(false);
717
+ const [joinGroupDid, setJoinGroupDid] = useState('');
718
+ const [joinGroupError, setJoinGroupError] = useState(null);
662
719
  const [logoutOpen, setLogoutOpen] = useState(false);
663
720
  const [logoutPending, setLogoutPending] = useState(false);
664
721
  const [logoutError, setLogoutError] = useState(null);
665
722
  const [mode, setMode] = useState('chat');
666
723
  const [mailUnreadCount, setMailUnreadCount] = useState(0);
667
- const [mailRefreshRevision, setMailRefreshRevision] = useState(0);
668
724
  const [launcherPosition, setLauncherPosition] = useState(readLauncherPosition);
669
725
  const [drawerFrame, setDrawerFrame] = useState(readDrawerFrame);
670
726
  const [launcherDragging, setLauncherDragging] = useState(false);
@@ -678,7 +734,7 @@ export function AwikiOverlay(props) {
678
734
  const launcherDrag = useRef(null);
679
735
  const drawerDrag = useRef(null);
680
736
  const drawerResize = useRef(null);
681
- const registered = view.status === 'ready' && view.identity !== null;
737
+ const registered = view.status === 'ready' && view.sessionStatus === 'active' && view.identity !== null;
682
738
  const unreadCount = view.conversations.reduce((total, conversation) => total + (conversation.unreadCount ?? 0), 0);
683
739
  const unreadLabel = unreadCount > 99 ? '99+' : String(unreadCount);
684
740
  const nominalDrawerWidth = Math.min(mode === 'mail' ? MAIL_DRAWER_NOMINAL_WIDTH : DRAWER_NOMINAL_WIDTH, Math.max(1, window.innerWidth - (window.innerWidth <= 620 ? DRAWER_COMPACT_HORIZONTAL_RESERVE : DRAWER_HORIZONTAL_RESERVE)));
@@ -728,6 +784,9 @@ export function AwikiOverlay(props) {
728
784
  setGroupName('');
729
785
  setGroupMembers('');
730
786
  setGroupComposeError(null);
787
+ setJoinGroupOpen(false);
788
+ setJoinGroupDid('');
789
+ setJoinGroupError(null);
731
790
  setMode('chat');
732
791
  const drag = drawerDrag.current;
733
792
  if (drag !== null)
@@ -765,6 +824,10 @@ export function AwikiOverlay(props) {
765
824
  setComposeGroup(false);
766
825
  return;
767
826
  }
827
+ if (joinGroupOpen) {
828
+ setJoinGroupOpen(false);
829
+ return;
830
+ }
768
831
  if (menuOpen) {
769
832
  setMenuOpen(false);
770
833
  return;
@@ -778,7 +841,7 @@ export function AwikiOverlay(props) {
778
841
  };
779
842
  document.addEventListener('keydown', onKeyDown);
780
843
  return () => { document.removeEventListener('keydown', onKeyDown); };
781
- }, [open, accountMenuOpen, composeDirect, composeGroup, logoutOpen, logoutPending, menuOpen, props.actions]);
844
+ }, [open, accountMenuOpen, composeDirect, composeGroup, joinGroupOpen, logoutOpen, logoutPending, menuOpen, props.actions]);
782
845
  useEffect(() => {
783
846
  const onResize = () => {
784
847
  setLauncherPosition((current) => {
@@ -1015,6 +1078,16 @@ export function AwikiOverlay(props) {
1015
1078
  setGroupName('');
1016
1079
  setGroupMembers('');
1017
1080
  };
1081
+ const joinGroup = async () => {
1082
+ setJoinGroupError(null);
1083
+ const result = await props.joinGroup(joinGroupDid);
1084
+ if (!result.ok) {
1085
+ setJoinGroupError(result.error);
1086
+ return;
1087
+ }
1088
+ setJoinGroupOpen(false);
1089
+ setJoinGroupDid('');
1090
+ };
1018
1091
  const logout = async () => {
1019
1092
  setLogoutPending(true);
1020
1093
  setLogoutError(null);
@@ -1038,21 +1111,21 @@ export function AwikiOverlay(props) {
1038
1111
  setAccountMenuOpen(false);
1039
1112
  setLogoutError(null);
1040
1113
  setLogoutOpen(true);
1041
- }, anchor: (_jsx("button", { type: "button", "aria-label": "AWiki \u8D26\u6237\u83DC\u5355", "aria-expanded": accountMenuOpen, "aria-haspopup": "menu", onClick: () => { setAccountMenuOpen(value => !value); }, children: _jsx(IconGlobeOutline14, { size: 18 }) })) })) : _jsx(IconGlobeOutline14, { size: 18 }), _jsx("h2", { id: titleId, children: "AWiki" })] }), _jsx("button", { type: "button", "aria-label": mode === 'mail' ? '刷新邮箱' : '刷新 AWiki', disabled: view.pending !== null, onClick: () => {
1042
- if (mode === 'mail')
1043
- setMailRefreshRevision(value => value + 1);
1044
- else
1045
- void props.open();
1046
- }, children: _jsx(IconRefreshOutline16, {}) }), _jsx("button", { type: "button", "aria-label": "\u5173\u95ED AWiki", onClick: props.actions.close, children: _jsx(IconCloseOutline16, {}) })] }), view.status === 'loading' && _jsx("div", { className: css.centerState, role: "status", children: "\u6B63\u5728\u8FDE\u63A5 AWiki\u2026" }), view.status === 'error' && _jsxs("div", { className: css.centerState, children: [_jsx("p", { children: view.error }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void props.open(); }, children: "\u91CD\u8BD5" })] }), view.status === 'ready' && view.sessionStatus === 'unregistered' && _jsx(AwikiRegistrationForm, { ...props, pending: view.pending !== null }), view.status === 'ready' && view.sessionStatus === 'signed-out' && _jsx(SignedOut, { login: props.login, pending: view.pending !== null }), view.status === 'ready' && view.sessionStatus === 'active' && view.identity !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.modePanel, "data-active": mode === 'chat' || undefined, hidden: mode !== 'chat', children: _jsx(Chat, { ...props, selectConversation: selectConversation, view: { ...view, identity: view.identity }, modeTabs: _jsx(ModeTabs, { mode: mode, mailUnreadCount: mailUnreadCount, onChange: setMode }), composeMenu: (_jsx(Menu, { open: menuOpen, onClose: () => { setMenuOpen(false); }, align: "end", portal: true, compact: true, items: [
1114
+ }, anchor: (_jsx("button", { type: "button", "aria-label": "AWiki \u8D26\u6237\u83DC\u5355", "aria-expanded": accountMenuOpen, "aria-haspopup": "menu", onClick: () => { setAccountMenuOpen(value => !value); }, children: _jsx(IconGlobeOutline14, { size: 18 }) })) })) : _jsx(IconGlobeOutline14, { size: 18 }), _jsx("h2", { id: titleId, children: "AWiki" })] }), mode === 'chat' && (_jsx("button", { type: "button", "aria-label": "\u5237\u65B0 AWiki", disabled: view.pending !== null, onClick: () => { void props.open(); }, children: _jsx(IconRefreshOutline16, {}) })), _jsx("button", { type: "button", "aria-label": "\u5173\u95ED AWiki", onClick: props.actions.close, children: _jsx(IconCloseOutline16, {}) })] }), view.status === 'loading' && _jsx("div", { className: css.centerState, role: "status", children: "\u6B63\u5728\u8FDE\u63A5 AWiki\u2026" }), view.status === 'error' && _jsxs("div", { className: css.centerState, children: [_jsx("p", { children: view.error }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void props.open(); }, children: "\u91CD\u8BD5" })] }), view.status === 'ready' && (view.sessionStatus === 'unregistered'
1115
+ || view.sessionStatus === 'signed-out'
1116
+ || view.sessionStatus === 'recovery-required') && (_jsx("div", { className: css.identityAccess, children: _jsx(AwikiIdentityAccess, { ...props, sessionStatus: view.sessionStatus, identity: view.identity, recoveryOperationId: view.recoveryOperationId, recoveryProgress: view.recoveryProgress, pending: view.pending !== null }) })), view.status === 'ready' && view.sessionStatus === 'active' && view.identity !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.modePanel, "data-active": mode === 'chat' || undefined, hidden: mode !== 'chat', children: _jsx(Chat, { ...props, selectConversation: selectConversation, view: { ...view, identity: view.identity }, modeTabs: _jsx(ModeTabs, { mode: mode, mailUnreadCount: mailUnreadCount, onChange: setMode }), composeMenu: (_jsx(Menu, { open: menuOpen, onClose: () => { setMenuOpen(false); }, align: "end", portal: true, compact: true, items: [
1047
1117
  { id: 'direct', label: '发起私聊' },
1048
1118
  { id: 'group', label: '发起群聊' },
1119
+ { id: 'join-group', label: '加入群聊' },
1049
1120
  ], onSelect: (id) => {
1050
1121
  setMenuOpen(false);
1051
1122
  if (id === 'direct')
1052
1123
  setComposeDirect(true);
1053
1124
  if (id === 'group')
1054
1125
  setComposeGroup(true);
1055
- }, anchor: (_jsx("button", { type: "button", className: css.rosterAction, "aria-label": "\u53D1\u8D77\u4F1A\u8BDD", "aria-expanded": menuOpen, "aria-haspopup": "menu", onClick: () => { setMenuOpen(value => !value); }, children: _jsx(IconPlusOutline16, {}) })) })) }) }), _jsx("div", { className: css.modePanel, "data-active": mode === 'mail' || undefined, hidden: mode !== 'mail', children: _jsx(AwikiMail, { active: mode === 'mail', identityCard: mode === 'mail' ? _jsx(IdentityCard, { identity: view.identity, pending: view.pending !== null, updateDisplayName: props.updateDisplayName }) : null, modeTabs: _jsx(ModeTabs, { mode: mode, mailUnreadCount: mailUnreadCount, onChange: setMode }), refreshRevision: mailRefreshRevision, onUnreadCountChange: setMailUnreadCount, getMailAccount: props.getMailAccount, listMailInbox: props.listMailInbox, readMail: props.readMail, markMailRead: props.markMailRead, sendMail: props.sendMail }) })] })), composeDirect && (_jsx("div", { className: css.composeBackdrop, children: _jsxs("form", { className: css.composeCard, role: "dialog", "aria-modal": "true", "aria-labelledby": composeTitleId, onSubmit: (event) => { event.preventDefault(); void startDirect(); }, children: [_jsx("h3", { id: composeTitleId, children: "\u53D1\u8D77\u79C1\u804A" }), _jsx("p", { children: "\u8F93\u5165\u5BF9\u65B9 Handle\u3002\u6253\u5F00\u4F1A\u8BDD\u524D\u4F1A\u5148\u786E\u8BA4\u8BE5\u7528\u6237\u5B58\u5728\u3002" }), _jsxs("label", { children: ["Handle", _jsx("input", { value: peerHandle, onChange: (event) => { setPeerHandle(event.target.value); setComposeError(null); }, autoComplete: "off", placeholder: "\u4F8B\u5982 alice", autoFocus: true })] }), view.pending === '查找用户' && _jsx("p", { role: "status", children: "\u6B63\u5728\u67E5\u627E\u7528\u6237\u2026" }), composeError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: composeError }), _jsxs("div", { className: css.composeActions, children: [_jsx("button", { type: "button", className: css.secondary, onClick: () => { setComposeDirect(false); setComposeError(null); }, children: "\u53D6\u6D88" }), _jsx("button", { type: "submit", className: css.primary, disabled: view.pending !== null || peerHandle.trim() === '', children: "\u6253\u5F00\u4F1A\u8BDD" })] })] }) })), composeGroup && (_jsx("div", { className: css.composeBackdrop, children: _jsxs("form", { className: css.composeCard, role: "dialog", "aria-modal": "true", "aria-labelledby": groupComposeTitleId, onSubmit: (event) => { event.preventDefault(); void createGroup(); }, children: [_jsx("h3", { id: groupComposeTitleId, children: "\u53D1\u8D77\u7FA4\u804A" }), _jsx("p", { children: "\u586B\u5199\u7FA4\u540D\u548C\u9996\u6279\u6210\u5458\u3002\u6210\u5458\u652F\u6301 Handle \u6216 DID\uFF0C\u6BCF\u884C\u4E00\u4E2A\uFF0C\u4E5F\u53EF\u4EE5\u7528\u9017\u53F7\u5206\u9694\u3002" }), _jsxs("label", { children: ["\u7FA4\u804A\u540D\u79F0", _jsx("input", { value: groupName, onChange: (event) => { setGroupName(event.target.value); setGroupComposeError(null); }, autoComplete: "off", placeholder: "\u4F8B\u5982 \u53D1\u5E03\u534F\u4F5C\u7FA4", autoFocus: true })] }), _jsxs("label", { children: ["\u7FA4\u6210\u5458", _jsx("textarea", { value: groupMembers, onChange: (event) => { setGroupMembers(event.target.value); setGroupComposeError(null); }, rows: 4, placeholder: '例如 alice.awiki.info\nbob.awiki.info' })] }), view.pending === '创建群聊' && _jsx("p", { role: "status", children: "\u6B63\u5728\u521B\u5EFA\u7FA4\u804A\u5E76\u9080\u8BF7\u6210\u5458\u2026" }), groupComposeError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: groupComposeError }), _jsxs("div", { className: css.composeActions, children: [_jsx("button", { type: "button", className: css.secondary, onClick: () => { setComposeGroup(false); setGroupComposeError(null); }, children: "\u53D6\u6D88" }), _jsx("button", { type: "submit", className: css.primary, disabled: view.pending !== null || groupName.trim() === '' || groupMembers.trim() === '', children: "\u521B\u5EFA\u7FA4\u804A" })] })] }) })), _jsx(Modal, { open: logoutOpen, onClose: () => { if (!logoutPending)
1056
- setLogoutOpen(false); }, title: "\u9000\u51FA\u767B\u5F55", closeLabel: "\u53D6\u6D88", description: "\u9000\u51FA\u540E\uFF0C\u672C\u673A\u5C06\u6682\u505C\u4F7F\u7528 AWiki\uFF1B\u8EAB\u4EFD\u548C\u672C\u5730\u6570\u636E\u90FD\u4F1A\u4FDD\u7559\u3002", footer: (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: logoutPending, onClick: () => { setLogoutOpen(false); }, children: "\u53D6\u6D88" }), _jsx(Button, { type: "button", variant: "outline", className: css.logoutConfirm, disabled: logoutPending, onClick: () => { void logout(); }, children: logoutPending ? '正在退出…' : '确认退出' })] })), children: _jsxs("div", { className: css.logoutWarning, children: [_jsx("p", { children: "\u9000\u51FA\u671F\u95F4\uFF0CWeb UI \u548C Agent \u90FD\u4E0D\u80FD\u8BFB\u53D6\u4F1A\u8BDD\u6216\u4F7F\u7528\u8BE5\u8EAB\u4EFD\u53D1\u9001\u6D88\u606F\u3002" }), _jsx("p", { children: "\u7A0D\u540E\u70B9\u51FB\u201C\u91CD\u65B0\u8FDB\u5165\u201D\u5373\u53EF\u7531\u672C\u673A Rust SDK \u6062\u590D\u540C\u4E00\u4E2A DID\u3001Handle \u548C\u6D88\u606F\u6570\u636E\u5E93\u3002" }), logoutError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: logoutError })] }) }), view.error !== null && view.status !== 'error' && _jsx("div", { className: css.error, role: "alert", children: view.error }), view.pending !== null && view.pending !== '发送消息' && view.pending !== '发送附件' && view.pending !== '加载消息' && _jsxs("div", { className: css.pending, role: "status", children: [view.pending, "\u2026"] }), DRAWER_RESIZE_DIRECTIONS.map(direction => (_jsx("div", { className: css.resizeHandle, "data-resize-handle": direction, "aria-hidden": "true", onPointerDown: (event) => { onDrawerResizePointerDown(event, direction); }, onPointerMove: onDrawerResizePointerMove, onPointerUp: finishDrawerResize, onPointerCancel: finishDrawerResize }, direction)))] }))] }));
1126
+ if (id === 'join-group')
1127
+ setJoinGroupOpen(true);
1128
+ }, anchor: (_jsx("button", { type: "button", className: css.rosterAction, "aria-label": "\u53D1\u8D77\u4F1A\u8BDD", "aria-expanded": menuOpen, "aria-haspopup": "menu", onClick: () => { setMenuOpen(value => !value); }, children: _jsx(IconPlusOutline16, {}) })) })) }) }), _jsx("div", { className: css.modePanel, "data-active": mode === 'mail' || undefined, hidden: mode !== 'mail', children: _jsx(AwikiMail, { active: mode === 'mail', cacheOwner: view.identity.did, identityCard: mode === 'mail' ? _jsx(AwikiProfileCard, { identity: view.identity, profile: view.profile, pending: view.pending !== null, updateProfile: props.updateProfile }) : null, modeTabs: _jsx(ModeTabs, { mode: mode, mailUnreadCount: mailUnreadCount, onChange: setMode }), onUnreadCountChange: setMailUnreadCount, getMailAccount: props.getMailAccount, listMailInbox: props.listMailInbox, readMail: props.readMail, markMailRead: props.markMailRead, sendMail: props.sendMail }, view.identity.did) })] })), composeDirect && (_jsx("div", { className: css.composeBackdrop, children: _jsxs("form", { className: css.composeCard, role: "dialog", "aria-modal": "true", "aria-labelledby": composeTitleId, onSubmit: (event) => { event.preventDefault(); void startDirect(); }, children: [_jsx("h3", { id: composeTitleId, children: "\u53D1\u8D77\u79C1\u804A" }), _jsx("p", { children: "\u8F93\u5165\u5BF9\u65B9 Handle\u3002\u6253\u5F00\u4F1A\u8BDD\u524D\u4F1A\u5148\u786E\u8BA4\u8BE5\u7528\u6237\u5B58\u5728\u3002" }), _jsxs("label", { children: ["Handle", _jsx("input", { value: peerHandle, onChange: (event) => { setPeerHandle(event.target.value); setComposeError(null); }, autoComplete: "off", placeholder: "\u4F8B\u5982 alice", autoFocus: true })] }), view.pending === '查找用户' && _jsx("p", { role: "status", children: "\u6B63\u5728\u67E5\u627E\u7528\u6237\u2026" }), composeError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: composeError }), _jsxs("div", { className: css.composeActions, children: [_jsx("button", { type: "button", className: css.secondary, onClick: () => { setComposeDirect(false); setComposeError(null); }, children: "\u53D6\u6D88" }), _jsx("button", { type: "submit", className: css.primary, disabled: view.pending !== null || peerHandle.trim() === '', children: "\u6253\u5F00\u4F1A\u8BDD" })] })] }) })), composeGroup && (_jsx("div", { className: css.composeBackdrop, children: _jsxs("form", { className: css.composeCard, role: "dialog", "aria-modal": "true", "aria-labelledby": groupComposeTitleId, onSubmit: (event) => { event.preventDefault(); void createGroup(); }, children: [_jsx("h3", { id: groupComposeTitleId, children: "\u53D1\u8D77\u7FA4\u804A" }), _jsx("p", { children: "\u586B\u5199\u7FA4\u540D\u5373\u53EF\u521B\u5EFA\u3002\u4E5F\u53EF\u4EE5\u73B0\u5728\u9080\u8BF7\u9996\u6279\u6210\u5458\uFF0C\u652F\u6301 Handle \u6216 DID\u3002" }), _jsxs("label", { children: ["\u7FA4\u804A\u540D\u79F0", _jsx("input", { value: groupName, onChange: (event) => { setGroupName(event.target.value); setGroupComposeError(null); }, autoComplete: "off", placeholder: "\u4F8B\u5982 \u53D1\u5E03\u534F\u4F5C\u7FA4", autoFocus: true })] }), _jsxs("label", { children: ["\u9996\u6279\u7FA4\u6210\u5458\uFF08\u53EF\u9009\uFF09", _jsx("textarea", { "aria-label": "\u7FA4\u6210\u5458", value: groupMembers, onChange: (event) => { setGroupMembers(event.target.value); setGroupComposeError(null); }, rows: 4, placeholder: '例如 alice.awiki.ai\nbob.awiki.ai' })] }), view.pending === '创建群聊' && _jsx("p", { role: "status", children: "\u6B63\u5728\u521B\u5EFA\u7FA4\u804A\u5E76\u9080\u8BF7\u6210\u5458\u2026" }), groupComposeError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: groupComposeError }), _jsxs("div", { className: css.composeActions, children: [_jsx("button", { type: "button", className: css.secondary, onClick: () => { setComposeGroup(false); setGroupComposeError(null); }, children: "\u53D6\u6D88" }), _jsx("button", { type: "submit", className: css.primary, disabled: view.pending !== null || groupName.trim() === '', children: "\u521B\u5EFA\u7FA4\u804A" })] })] }) })), joinGroupOpen && (_jsx("div", { className: css.composeBackdrop, children: _jsxs("form", { className: css.composeCard, role: "dialog", "aria-modal": "true", "aria-labelledby": groupJoinTitleId, onSubmit: (event) => { event.preventDefault(); void joinGroup(); }, children: [_jsx("h3", { id: groupJoinTitleId, children: "\u52A0\u5165\u7FA4\u804A" }), _jsx("p", { children: "\u8F93\u5165\u7FA4\u804A\u7684\u5B8C\u6574 DID\u3002\u52A0\u5165\u6210\u529F\u540E\u4F1A\u6253\u5F00\u8BE5\u7FA4\u804A\u5E76\u8BFB\u53D6\u6700\u65B0\u6210\u5458\u5217\u8868\u3002" }), _jsxs("label", { children: ["\u7FA4 DID", _jsx("input", { value: joinGroupDid, onChange: (event) => { setJoinGroupDid(event.target.value); setJoinGroupError(null); }, autoComplete: "off", placeholder: "did:wba:...", autoFocus: true })] }), joinGroupError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: joinGroupError }), _jsxs("div", { className: css.composeActions, children: [_jsx("button", { type: "button", className: css.secondary, onClick: () => { setJoinGroupOpen(false); setJoinGroupError(null); }, children: "\u53D6\u6D88" }), _jsx("button", { type: "submit", className: css.primary, disabled: view.pending !== null || joinGroupDid.trim() === '', children: "\u52A0\u5165\u7FA4\u804A" })] })] }) })), _jsx(Modal, { open: logoutOpen, onClose: () => { if (!logoutPending)
1129
+ setLogoutOpen(false); }, title: "\u9000\u51FA\u767B\u5F55", closeLabel: "\u53D6\u6D88", className: css.compactModal ?? '', contentClassName: css.compactModalContent ?? '', description: "\u9000\u51FA\u540E\uFF0C\u672C\u673A\u5C06\u6682\u505C\u4F7F\u7528 AWiki\uFF1B\u8EAB\u4EFD\u548C\u672C\u5730\u6570\u636E\u90FD\u4F1A\u4FDD\u7559\u3002", footer: (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: logoutPending, onClick: () => { setLogoutOpen(false); }, children: "\u53D6\u6D88" }), _jsx(Button, { type: "button", variant: "outline", className: css.logoutConfirm, disabled: logoutPending, onClick: () => { void logout(); }, children: logoutPending ? '正在退出…' : '确认退出' })] })), children: _jsxs("div", { className: css.logoutWarning, children: [_jsx("p", { children: "\u9000\u51FA\u671F\u95F4\uFF0CWeb UI \u548C Agent \u90FD\u4E0D\u80FD\u8BFB\u53D6\u4F1A\u8BDD\u6216\u4F7F\u7528\u8BE5\u8EAB\u4EFD\u53D1\u9001\u6D88\u606F\u3002" }), _jsx("p", { children: "\u7A0D\u540E\u70B9\u51FB\u201C\u91CD\u65B0\u8FDB\u5165\u672C\u673A\u8EAB\u4EFD\u201D\u5373\u53EF\u7531\u672C\u673A Rust SDK \u6062\u590D\u540C\u4E00\u4E2A DID\u3001Handle \u548C\u6D88\u606F\u6570\u636E\u5E93\u3002" }), logoutError !== null && _jsx("p", { className: css.inlineError, role: "alert", children: logoutError })] }) }), view.error !== null && view.status !== 'error' && _jsx("div", { className: css.error, role: "alert", children: view.error }), view.pending !== null && view.pending !== '发送消息' && view.pending !== '发送附件' && view.pending !== '加载消息' && _jsxs("div", { className: css.pending, role: "status", children: [view.pending, "\u2026"] }), DRAWER_RESIZE_DIRECTIONS.map(direction => (_jsx("div", { className: css.resizeHandle, "data-resize-handle": direction, "aria-hidden": "true", onPointerDown: (event) => { onDrawerResizePointerDown(event, direction); }, onPointerMove: onDrawerResizePointerMove, onPointerUp: finishDrawerResize, onPointerCancel: finishDrawerResize }, direction)))] }))] }));
1057
1130
  }
1058
1131
  //# sourceMappingURL=AwikiOverlay.js.map