@awiki/dsh-plugin 0.2.4 → 0.2.5
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.
- package/README.md +80 -11
- package/README.zh.md +67 -12
- package/THIRD_PARTY_NOTICES.md +8 -1
- package/cordis.patch.yml +13 -0
- package/lib/client.js +8223 -1939
- package/lib/client.js.map +1 -1
- package/lib/index.js +1486 -59
- package/lib/model-proxy-contract.js +89 -0
- package/lib/model-proxy.js +383 -0
- package/lib/provider.js +2 -1
- package/lib/sdk-adapter-DKgkyApB.mjs +669 -0
- package/lib/typert.host.js +470 -482
- package/lib/typert.remote-client.d.ts +15 -1
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +469 -103
- package/lib/types/attachment-cache.d.ts +28 -0
- package/lib/types/attachment-cache.d.ts.map +1 -0
- package/lib/types/attachment-cache.js +194 -0
- package/lib/types/attachment-cache.js.map +1 -0
- package/lib/types/client/AwikiMail.d.ts +14 -0
- package/lib/types/client/AwikiMail.d.ts.map +1 -0
- package/lib/types/client/AwikiMail.js +260 -0
- package/lib/types/client/AwikiMail.js.map +1 -0
- package/lib/types/client/AwikiOnboarding.d.ts +22 -0
- package/lib/types/client/AwikiOnboarding.d.ts.map +1 -0
- package/lib/types/client/AwikiOnboarding.js +87 -0
- package/lib/types/client/AwikiOnboarding.js.map +1 -0
- package/lib/types/client/AwikiOverlay.d.ts +15 -0
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +251 -20
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +20 -4
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +279 -23
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/RechargeComingSoonDialog.d.ts +11 -0
- package/lib/types/client/RechargeComingSoonDialog.d.ts.map +1 -0
- package/lib/types/client/RechargeComingSoonDialog.js +8 -0
- package/lib/types/client/RechargeComingSoonDialog.js.map +1 -0
- package/lib/types/client/controller.d.ts +82 -4
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +517 -74
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/image-cache.d.ts +20 -0
- package/lib/types/client/image-cache.d.ts.map +1 -0
- package/lib/types/client/image-cache.js +177 -0
- package/lib/types/client/image-cache.js.map +1 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +84 -41
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/model-account-settings-availability.d.ts +4 -0
- package/lib/types/client/model-account-settings-availability.d.ts.map +1 -0
- package/lib/types/client/model-account-settings-availability.js +4 -0
- package/lib/types/client/model-account-settings-availability.js.map +1 -0
- package/lib/types/client/model-availability-controller.d.ts +27 -0
- package/lib/types/client/model-availability-controller.d.ts.map +1 -0
- package/lib/types/client/model-availability-controller.js +111 -0
- package/lib/types/client/model-availability-controller.js.map +1 -0
- package/lib/types/client/model-proxy-controller.d.ts +41 -0
- package/lib/types/client/model-proxy-controller.d.ts.map +1 -0
- package/lib/types/client/model-proxy-controller.js +241 -0
- package/lib/types/client/model-proxy-controller.js.map +1 -0
- package/lib/types/client/recharge-availability.d.ts +6 -0
- package/lib/types/client/recharge-availability.d.ts.map +1 -0
- package/lib/types/client/recharge-availability.js +6 -0
- package/lib/types/client/recharge-availability.js.map +1 -0
- package/lib/types/client/settings-locales.d.ts +1 -1
- package/lib/types/client/settings-locales.d.ts.map +1 -1
- package/lib/types/client/settings-locales.js +180 -2
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +15 -2
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +56 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +462 -31
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +23 -0
- package/lib/types/listener-state.d.ts.map +1 -0
- package/lib/types/listener-state.js +124 -0
- package/lib/types/listener-state.js.map +1 -0
- package/lib/types/listener.d.ts +96 -0
- package/lib/types/listener.d.ts.map +1 -0
- package/lib/types/listener.js +575 -0
- package/lib/types/listener.js.map +1 -0
- package/lib/types/mail.d.ts +8 -0
- package/lib/types/mail.d.ts.map +1 -0
- package/lib/types/mail.js +75 -0
- package/lib/types/mail.js.map +1 -0
- package/lib/types/model-proxy-contract.d.ts +61 -0
- package/lib/types/model-proxy-contract.d.ts.map +1 -0
- package/lib/types/model-proxy-contract.js +129 -0
- package/lib/types/model-proxy-contract.js.map +1 -0
- package/lib/types/model-proxy.d.ts +14 -0
- package/lib/types/model-proxy.d.ts.map +1 -0
- package/lib/types/model-proxy.js +371 -0
- package/lib/types/model-proxy.js.map +1 -0
- package/lib/types/provider-api.d.ts +78 -2
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +1 -0
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +26 -2
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +380 -24
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/tools.d.ts +10 -0
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/tools.js +98 -0
- package/lib/types/tools.js.map +1 -1
- package/lib/types/types.d.ts +103 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +30 -5
- package/lib/sdk-adapter-DK6RqV-c.mjs +0 -355
|
@@ -4,8 +4,10 @@ import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
|
|
|
4
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';
|
|
5
5
|
import { AWIKI_ME_APP_ICON_DATA_URL } from "./assets.js";
|
|
6
6
|
import { createAttachmentObjectUrl, fileToBase64, saveDownloadedAttachment } from "./file.js";
|
|
7
|
+
import { AwikiMail } from "./AwikiMail.js";
|
|
7
8
|
import css from './AwikiOverlay.module.css';
|
|
8
9
|
export const AWIKI_LAUNCHER_POSITION_KEY = 'dsh-awiki-launcher-position-v1';
|
|
10
|
+
export const AWIKI_DRAWER_FRAME_KEY = 'dsh-awiki-drawer-frame-v1';
|
|
9
11
|
const LAUNCHER_SIZE = 48;
|
|
10
12
|
const LAUNCHER_EDGE_GAP = 8;
|
|
11
13
|
const LAUNCHER_RIGHT_OFFSET = 28;
|
|
@@ -15,10 +17,15 @@ const DRAWER_LONG_PRESS_MS = 300;
|
|
|
15
17
|
const DRAWER_ANCHOR_GAP = 8;
|
|
16
18
|
const DRAWER_EDGE_GAP = 8;
|
|
17
19
|
const DRAWER_NOMINAL_WIDTH = 720;
|
|
20
|
+
const MAIL_DRAWER_NOMINAL_WIDTH = 1040;
|
|
18
21
|
const DRAWER_NOMINAL_HEIGHT = 720;
|
|
19
22
|
const DRAWER_HORIZONTAL_RESERVE = 80;
|
|
23
|
+
const DRAWER_COMPACT_HORIZONTAL_RESERVE = 56;
|
|
24
|
+
const DRAWER_MIN_WIDTH = 360;
|
|
25
|
+
const DRAWER_MIN_HEIGHT = 360;
|
|
20
26
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
21
27
|
const HISTORY_BOTTOM_THRESHOLD = 24;
|
|
28
|
+
const DRAWER_RESIZE_DIRECTIONS = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'];
|
|
22
29
|
/** Keep the floating launcher fully reachable inside the current viewport. */
|
|
23
30
|
export function clampAwikiLauncherPosition(position, width, height) {
|
|
24
31
|
return {
|
|
@@ -69,6 +76,40 @@ export function resolveAwikiDrawerPlacement(launcher, panelWidth, panelHeight, v
|
|
|
69
76
|
top: Math.min(Math.max(selected.top, DRAWER_EDGE_GAP), Math.max(DRAWER_EDGE_GAP, viewportHeight - panelHeight - DRAWER_EDGE_GAP)),
|
|
70
77
|
};
|
|
71
78
|
}
|
|
79
|
+
/** Keep a user-sized AWiki drawer reachable and within the current viewport. */
|
|
80
|
+
export function clampAwikiDrawerFrame(frame, viewportWidth, viewportHeight) {
|
|
81
|
+
const maxWidth = Math.max(1, viewportWidth - DRAWER_EDGE_GAP * 2);
|
|
82
|
+
const maxHeight = Math.max(1, viewportHeight - DRAWER_EDGE_GAP * 2);
|
|
83
|
+
const minWidth = Math.min(DRAWER_MIN_WIDTH, maxWidth);
|
|
84
|
+
const minHeight = Math.min(DRAWER_MIN_HEIGHT, maxHeight);
|
|
85
|
+
const width = Math.min(Math.max(frame.width, minWidth), maxWidth);
|
|
86
|
+
const height = Math.min(Math.max(frame.height, minHeight), maxHeight);
|
|
87
|
+
return {
|
|
88
|
+
left: Math.min(Math.max(frame.left, DRAWER_EDGE_GAP), Math.max(DRAWER_EDGE_GAP, viewportWidth - width - DRAWER_EDGE_GAP)),
|
|
89
|
+
top: Math.min(Math.max(frame.top, DRAWER_EDGE_GAP), Math.max(DRAWER_EDGE_GAP, viewportHeight - height - DRAWER_EDGE_GAP)),
|
|
90
|
+
width,
|
|
91
|
+
height,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Resize one or two drawer boundaries while keeping the opposite boundaries fixed. */
|
|
95
|
+
export function resizeAwikiDrawerFrame(frame, direction, deltaX, deltaY, viewportWidth, viewportHeight) {
|
|
96
|
+
const origin = clampAwikiDrawerFrame(frame, viewportWidth, viewportHeight);
|
|
97
|
+
const minWidth = Math.min(DRAWER_MIN_WIDTH, Math.max(1, viewportWidth - DRAWER_EDGE_GAP * 2));
|
|
98
|
+
const minHeight = Math.min(DRAWER_MIN_HEIGHT, Math.max(1, viewportHeight - DRAWER_EDGE_GAP * 2));
|
|
99
|
+
let left = origin.left;
|
|
100
|
+
let top = origin.top;
|
|
101
|
+
let right = origin.left + origin.width;
|
|
102
|
+
let bottom = origin.top + origin.height;
|
|
103
|
+
if (direction.includes('w'))
|
|
104
|
+
left = Math.min(Math.max(left + deltaX, DRAWER_EDGE_GAP), right - minWidth);
|
|
105
|
+
if (direction.includes('e'))
|
|
106
|
+
right = Math.max(Math.min(right + deltaX, viewportWidth - DRAWER_EDGE_GAP), left + minWidth);
|
|
107
|
+
if (direction.includes('n'))
|
|
108
|
+
top = Math.min(Math.max(top + deltaY, DRAWER_EDGE_GAP), bottom - minHeight);
|
|
109
|
+
if (direction.includes('s'))
|
|
110
|
+
bottom = Math.max(Math.min(bottom + deltaY, viewportHeight - DRAWER_EDGE_GAP), top + minHeight);
|
|
111
|
+
return clampAwikiDrawerFrame({ left, top, width: right - left, height: bottom - top }, viewportWidth, viewportHeight);
|
|
112
|
+
}
|
|
72
113
|
function defaultLauncherPosition() {
|
|
73
114
|
return clampAwikiLauncherPosition({
|
|
74
115
|
left: window.innerWidth - LAUNCHER_SIZE - LAUNCHER_RIGHT_OFFSET,
|
|
@@ -99,6 +140,30 @@ function saveLauncherPosition(position) {
|
|
|
99
140
|
// The launcher remains usable when session storage is unavailable.
|
|
100
141
|
}
|
|
101
142
|
}
|
|
143
|
+
function readDrawerFrame() {
|
|
144
|
+
try {
|
|
145
|
+
const stored = window.sessionStorage.getItem(AWIKI_DRAWER_FRAME_KEY);
|
|
146
|
+
if (stored !== null) {
|
|
147
|
+
const value = JSON.parse(stored);
|
|
148
|
+
const { left, top, width, height } = value;
|
|
149
|
+
if ([left, top, width, height].every(item => typeof item === 'number' && Number.isFinite(item))) {
|
|
150
|
+
return clampAwikiDrawerFrame({ left: left, top: top, width: width, height: height }, window.innerWidth, window.innerHeight);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// The default drawer remains usable when session storage is unavailable.
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function saveDrawerFrame(frame) {
|
|
160
|
+
try {
|
|
161
|
+
window.sessionStorage.setItem(AWIKI_DRAWER_FRAME_KEY, JSON.stringify(frame));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Resizing remains usable when session storage is unavailable.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
102
167
|
function callPointerCapture(target, method, pointerId) {
|
|
103
168
|
const capture = Reflect.get(target, method);
|
|
104
169
|
if (typeof capture === 'function')
|
|
@@ -116,7 +181,7 @@ function conversationTime(value, now = Date.now()) {
|
|
|
116
181
|
: new Intl.DateTimeFormat('zh-CN', { month: 'numeric', day: 'numeric' }).format(value);
|
|
117
182
|
}
|
|
118
183
|
/** Render the identity registration form and its OTP challenge transition. */
|
|
119
|
-
function
|
|
184
|
+
export function AwikiRegistrationForm(props) {
|
|
120
185
|
const [phone, setPhone] = useState('');
|
|
121
186
|
const [handle, setHandle] = useState('');
|
|
122
187
|
const [otp, setOtp] = useState('');
|
|
@@ -158,7 +223,7 @@ function Registration(props) {
|
|
|
158
223
|
return;
|
|
159
224
|
setNotice(null);
|
|
160
225
|
};
|
|
161
|
-
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" })] }), _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 })] }));
|
|
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 })] }));
|
|
162
227
|
}
|
|
163
228
|
/** Let a signed-out installation resume its preserved local identity. */
|
|
164
229
|
function SignedOut(props) {
|
|
@@ -222,6 +287,10 @@ function IdentityCard(props) {
|
|
|
222
287
|
}
|
|
223
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 })] }));
|
|
224
289
|
}
|
|
290
|
+
/** Switch the shared identity between messaging and on-demand mail. */
|
|
291
|
+
function ModeTabs(props) {
|
|
292
|
+
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 })] })] }));
|
|
293
|
+
}
|
|
225
294
|
/** Incoming sender label: WNS display name, then Handle, then DID. */
|
|
226
295
|
function senderLabel(message, peerLabel) {
|
|
227
296
|
if (message.outgoing)
|
|
@@ -354,7 +423,10 @@ function Chat(props) {
|
|
|
354
423
|
const summary = selected === undefined ? undefined : view.summaries[selected.id];
|
|
355
424
|
const summaryPanelId = useId();
|
|
356
425
|
selectedConversationId.current = view.selectedConversationId;
|
|
357
|
-
const visibleSendingDraft = sendingDraft?.conversationId === view.selectedConversationId
|
|
426
|
+
const visibleSendingDraft = sendingDraft?.conversationId === view.selectedConversationId
|
|
427
|
+
&& !view.messages.some(message => message.id === sendingDraft.messageId)
|
|
428
|
+
? sendingDraft
|
|
429
|
+
: null;
|
|
358
430
|
const markSelectedConversationReadAtBottom = () => {
|
|
359
431
|
const node = history.current;
|
|
360
432
|
const newestRendered = view.messages.at(-1);
|
|
@@ -362,7 +434,7 @@ function Chat(props) {
|
|
|
362
434
|
|| selected === undefined
|
|
363
435
|
|| newestRendered === undefined
|
|
364
436
|
|| (selected.unreadCount ?? 0) <= 0
|
|
365
|
-
|| view.
|
|
437
|
+
|| view.localPending
|
|
366
438
|
|| (selected.lastMessageAt !== undefined && newestRendered.sentAt < selected.lastMessageAt)
|
|
367
439
|
|| node.scrollHeight - node.scrollTop - node.clientHeight > HISTORY_BOTTOM_THRESHOLD)
|
|
368
440
|
return;
|
|
@@ -400,7 +472,7 @@ function Chat(props) {
|
|
|
400
472
|
};
|
|
401
473
|
useLayoutEffect(() => {
|
|
402
474
|
markSelectedConversationReadAtBottom();
|
|
403
|
-
}, [selected?.id, selected?.lastMessageAt, selected?.unreadCount, view.
|
|
475
|
+
}, [selected?.id, selected?.lastMessageAt, selected?.unreadCount, view.localPending, view.messages]);
|
|
404
476
|
useLayoutEffect(() => {
|
|
405
477
|
const conversationId = view.selectedConversationId;
|
|
406
478
|
if (conversationId !== previousConversationId.current) {
|
|
@@ -416,7 +488,7 @@ function Chat(props) {
|
|
|
416
488
|
}
|
|
417
489
|
if (conversationId === null || history.current === null)
|
|
418
490
|
return;
|
|
419
|
-
if (view.
|
|
491
|
+
if (view.localPending)
|
|
420
492
|
return;
|
|
421
493
|
if (conversationAwaitingBottom.current === conversationId) {
|
|
422
494
|
if (view.messages.length === 0)
|
|
@@ -447,7 +519,7 @@ function Chat(props) {
|
|
|
447
519
|
setHistoryAwayFromBottom(true);
|
|
448
520
|
setUnseenMessageCount(current => current + appendedMessageCount);
|
|
449
521
|
}
|
|
450
|
-
}, [view.
|
|
522
|
+
}, [view.localPending, view.messages, view.selectedConversationId]);
|
|
451
523
|
useLayoutEffect(() => {
|
|
452
524
|
if (visibleSendingDraft === null || history.current === null)
|
|
453
525
|
return;
|
|
@@ -504,9 +576,10 @@ function Chat(props) {
|
|
|
504
576
|
/* v8 ignore next -- the only invocation control is disabled while both text and attachment are empty. */
|
|
505
577
|
if (draft === '')
|
|
506
578
|
return;
|
|
507
|
-
|
|
579
|
+
const messageId = `msg-${crypto.randomUUID()}`;
|
|
580
|
+
setSendingDraft({ conversationId, messageId, startedAt: Date.now(), content: { kind: 'text', text: draft } });
|
|
508
581
|
setText('');
|
|
509
|
-
const result = await props.sendText(draft);
|
|
582
|
+
const result = await props.sendText(draft, messageId);
|
|
510
583
|
setSendingDraft(null);
|
|
511
584
|
if (!result.ok && selectedConversationId.current === conversationId)
|
|
512
585
|
setText(draft);
|
|
@@ -519,8 +592,10 @@ function Chat(props) {
|
|
|
519
592
|
setFileError(null);
|
|
520
593
|
const selectedFile = file;
|
|
521
594
|
const bytesBase64 = await fileToBase64(selectedFile);
|
|
595
|
+
const messageId = `msg-${crypto.randomUUID()}`;
|
|
522
596
|
setSendingDraft({
|
|
523
597
|
conversationId,
|
|
598
|
+
messageId,
|
|
524
599
|
startedAt: Date.now(),
|
|
525
600
|
content: {
|
|
526
601
|
kind: 'attachment',
|
|
@@ -536,6 +611,7 @@ function Chat(props) {
|
|
|
536
611
|
mimeType: selectedFile.type || 'application/octet-stream',
|
|
537
612
|
bytesBase64,
|
|
538
613
|
...(draft === '' ? {} : { caption: draft }),
|
|
614
|
+
clientMessageId: messageId,
|
|
539
615
|
});
|
|
540
616
|
setSendingDraft(null);
|
|
541
617
|
if (!result.ok && selectedConversationId.current === conversationId) {
|
|
@@ -543,7 +619,7 @@ function Chat(props) {
|
|
|
543
619
|
setText(draft);
|
|
544
620
|
}
|
|
545
621
|
};
|
|
546
|
-
return (_jsxs("div", { className: css.chat, children: [_jsxs("aside", { className: css.roster, "data-hidden": selected !== undefined || undefined, children: [_jsx(IdentityCard, { identity: view.identity, pending: view.pending !== null, updateDisplayName: props.updateDisplayName }), _jsx("div", { className: css.rosterTitle, children: "\u4F1A\u8BDD" }), _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: () => {
|
|
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: () => {
|
|
547
623
|
if (summary !== undefined && !summary.collapsed && summary.status !== 'error') {
|
|
548
624
|
props.setSummaryCollapsed(selected.id, true);
|
|
549
625
|
}
|
|
@@ -553,7 +629,7 @@ function Chat(props) {
|
|
|
553
629
|
else {
|
|
554
630
|
void props.summarizeConversation();
|
|
555
631
|
}
|
|
556
|
-
}, 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.
|
|
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
|
|
557
633
|
? _jsx("span", { className: css.filePreviewIcon, children: _jsx(IconPaperclipOutline16, {}) })
|
|
558
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) => {
|
|
559
635
|
if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing)
|
|
@@ -573,17 +649,27 @@ export function AwikiOverlay(props) {
|
|
|
573
649
|
const view = props.useAwiki(state => state);
|
|
574
650
|
const titleId = useId();
|
|
575
651
|
const composeTitleId = useId();
|
|
652
|
+
const groupComposeTitleId = useId();
|
|
576
653
|
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
|
577
654
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
578
655
|
const [composeDirect, setComposeDirect] = useState(false);
|
|
579
656
|
const [peerHandle, setPeerHandle] = useState('');
|
|
580
657
|
const [composeError, setComposeError] = useState(null);
|
|
658
|
+
const [composeGroup, setComposeGroup] = useState(false);
|
|
659
|
+
const [groupName, setGroupName] = useState('');
|
|
660
|
+
const [groupMembers, setGroupMembers] = useState('');
|
|
661
|
+
const [groupComposeError, setGroupComposeError] = useState(null);
|
|
581
662
|
const [logoutOpen, setLogoutOpen] = useState(false);
|
|
582
663
|
const [logoutPending, setLogoutPending] = useState(false);
|
|
583
664
|
const [logoutError, setLogoutError] = useState(null);
|
|
665
|
+
const [mode, setMode] = useState('chat');
|
|
666
|
+
const [mailUnreadCount, setMailUnreadCount] = useState(0);
|
|
667
|
+
const [mailRefreshRevision, setMailRefreshRevision] = useState(0);
|
|
584
668
|
const [launcherPosition, setLauncherPosition] = useState(readLauncherPosition);
|
|
669
|
+
const [drawerFrame, setDrawerFrame] = useState(readDrawerFrame);
|
|
585
670
|
const [launcherDragging, setLauncherDragging] = useState(false);
|
|
586
671
|
const [drawerDragging, setDrawerDragging] = useState(false);
|
|
672
|
+
const [drawerResizing, setDrawerResizing] = useState(null);
|
|
587
673
|
const [drawerDragDirection, setDrawerDragDirection] = useState(null);
|
|
588
674
|
const launcherRef = useRef(null);
|
|
589
675
|
const rememberedConversationId = useRef(null);
|
|
@@ -591,12 +677,21 @@ export function AwikiOverlay(props) {
|
|
|
591
677
|
const suppressLauncherClick = useRef(false);
|
|
592
678
|
const launcherDrag = useRef(null);
|
|
593
679
|
const drawerDrag = useRef(null);
|
|
680
|
+
const drawerResize = useRef(null);
|
|
594
681
|
const registered = view.status === 'ready' && view.identity !== null;
|
|
595
682
|
const unreadCount = view.conversations.reduce((total, conversation) => total + (conversation.unreadCount ?? 0), 0);
|
|
596
683
|
const unreadLabel = unreadCount > 99 ? '99+' : String(unreadCount);
|
|
597
|
-
const
|
|
598
|
-
const
|
|
684
|
+
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)));
|
|
685
|
+
const nominalDrawerHeight = Math.min(DRAWER_NOMINAL_HEIGHT, Math.max(1, window.innerHeight - DRAWER_EDGE_GAP * 2));
|
|
686
|
+
const drawerWidth = drawerFrame?.width ?? nominalDrawerWidth;
|
|
687
|
+
const drawerHeight = drawerFrame?.height ?? nominalDrawerHeight;
|
|
599
688
|
const drawerPlacement = resolveAwikiDrawerPlacement(launcherPosition, drawerWidth, drawerHeight, window.innerWidth, window.innerHeight, drawerDragDirection ?? undefined);
|
|
689
|
+
const renderedDrawerFrame = drawerFrame ?? {
|
|
690
|
+
left: drawerPlacement.left,
|
|
691
|
+
top: drawerPlacement.top,
|
|
692
|
+
width: drawerWidth,
|
|
693
|
+
height: drawerHeight,
|
|
694
|
+
};
|
|
600
695
|
useEffect(() => {
|
|
601
696
|
void props.open();
|
|
602
697
|
return props.close;
|
|
@@ -629,11 +724,18 @@ export function AwikiOverlay(props) {
|
|
|
629
724
|
setComposeDirect(false);
|
|
630
725
|
setPeerHandle('');
|
|
631
726
|
setComposeError(null);
|
|
727
|
+
setComposeGroup(false);
|
|
728
|
+
setGroupName('');
|
|
729
|
+
setGroupMembers('');
|
|
730
|
+
setGroupComposeError(null);
|
|
731
|
+
setMode('chat');
|
|
632
732
|
const drag = drawerDrag.current;
|
|
633
733
|
if (drag !== null)
|
|
634
734
|
clearTimeout(drag.timer);
|
|
635
735
|
drawerDrag.current = null;
|
|
736
|
+
drawerResize.current = null;
|
|
636
737
|
setDrawerDragging(false);
|
|
738
|
+
setDrawerResizing(null);
|
|
637
739
|
setDrawerDragDirection(null);
|
|
638
740
|
if (wasOpen && view.selectedConversationId !== null) {
|
|
639
741
|
rememberedConversationId.current = view.selectedConversationId;
|
|
@@ -659,6 +761,10 @@ export function AwikiOverlay(props) {
|
|
|
659
761
|
setComposeDirect(false);
|
|
660
762
|
return;
|
|
661
763
|
}
|
|
764
|
+
if (composeGroup) {
|
|
765
|
+
setComposeGroup(false);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
662
768
|
if (menuOpen) {
|
|
663
769
|
setMenuOpen(false);
|
|
664
770
|
return;
|
|
@@ -672,7 +778,7 @@ export function AwikiOverlay(props) {
|
|
|
672
778
|
};
|
|
673
779
|
document.addEventListener('keydown', onKeyDown);
|
|
674
780
|
return () => { document.removeEventListener('keydown', onKeyDown); };
|
|
675
|
-
}, [open, accountMenuOpen, composeDirect, logoutOpen, logoutPending, menuOpen, props.actions]);
|
|
781
|
+
}, [open, accountMenuOpen, composeDirect, composeGroup, logoutOpen, logoutPending, menuOpen, props.actions]);
|
|
676
782
|
useEffect(() => {
|
|
677
783
|
const onResize = () => {
|
|
678
784
|
setLauncherPosition((current) => {
|
|
@@ -680,6 +786,13 @@ export function AwikiOverlay(props) {
|
|
|
680
786
|
saveLauncherPosition(next);
|
|
681
787
|
return next;
|
|
682
788
|
});
|
|
789
|
+
setDrawerFrame((current) => {
|
|
790
|
+
if (current === null)
|
|
791
|
+
return null;
|
|
792
|
+
const next = clampAwikiDrawerFrame(current, window.innerWidth, window.innerHeight);
|
|
793
|
+
saveDrawerFrame(next);
|
|
794
|
+
return next;
|
|
795
|
+
});
|
|
683
796
|
};
|
|
684
797
|
window.addEventListener('resize', onResize);
|
|
685
798
|
return () => { window.removeEventListener('resize', onResize); };
|
|
@@ -694,6 +807,8 @@ export function AwikiOverlay(props) {
|
|
|
694
807
|
origin: launcherPosition,
|
|
695
808
|
moved: false,
|
|
696
809
|
current: launcherPosition,
|
|
810
|
+
originFrame: drawerFrame,
|
|
811
|
+
currentFrame: drawerFrame,
|
|
697
812
|
};
|
|
698
813
|
callPointerCapture(event.currentTarget, 'setPointerCapture', event.pointerId);
|
|
699
814
|
};
|
|
@@ -712,6 +827,14 @@ export function AwikiOverlay(props) {
|
|
|
712
827
|
top: drag.origin.top + deltaY,
|
|
713
828
|
}, window.innerWidth, window.innerHeight);
|
|
714
829
|
setLauncherPosition(drag.current);
|
|
830
|
+
if (drag.originFrame !== null) {
|
|
831
|
+
drag.currentFrame = clampAwikiDrawerFrame({
|
|
832
|
+
...drag.originFrame,
|
|
833
|
+
left: drag.originFrame.left + deltaX,
|
|
834
|
+
top: drag.originFrame.top + deltaY,
|
|
835
|
+
}, window.innerWidth, window.innerHeight);
|
|
836
|
+
setDrawerFrame(drag.currentFrame);
|
|
837
|
+
}
|
|
715
838
|
};
|
|
716
839
|
const finishLauncherDrag = (event) => {
|
|
717
840
|
const drag = launcherDrag.current;
|
|
@@ -721,6 +844,8 @@ export function AwikiOverlay(props) {
|
|
|
721
844
|
suppressLauncherClick.current = true;
|
|
722
845
|
saveLauncherPosition(drag.current);
|
|
723
846
|
}
|
|
847
|
+
if (drag.currentFrame !== null)
|
|
848
|
+
saveDrawerFrame(drag.currentFrame);
|
|
724
849
|
launcherDrag.current = null;
|
|
725
850
|
setLauncherDragging(false);
|
|
726
851
|
callPointerCapture(event.currentTarget, 'releasePointerCapture', event.pointerId);
|
|
@@ -738,6 +863,8 @@ export function AwikiOverlay(props) {
|
|
|
738
863
|
armed: false,
|
|
739
864
|
moved: false,
|
|
740
865
|
current: launcherPosition,
|
|
866
|
+
originFrame: drawerFrame,
|
|
867
|
+
currentFrame: drawerFrame,
|
|
741
868
|
};
|
|
742
869
|
drag.timer = setTimeout(() => {
|
|
743
870
|
if (drawerDrag.current !== drag)
|
|
@@ -770,6 +897,14 @@ export function AwikiOverlay(props) {
|
|
|
770
897
|
top: drag.origin.top + deltaY,
|
|
771
898
|
}, window.innerWidth, window.innerHeight);
|
|
772
899
|
setLauncherPosition(drag.current);
|
|
900
|
+
if (drag.originFrame !== null) {
|
|
901
|
+
drag.currentFrame = clampAwikiDrawerFrame({
|
|
902
|
+
...drag.originFrame,
|
|
903
|
+
left: drag.originFrame.left + deltaX,
|
|
904
|
+
top: drag.originFrame.top + deltaY,
|
|
905
|
+
}, window.innerWidth, window.innerHeight);
|
|
906
|
+
setDrawerFrame(drag.currentFrame);
|
|
907
|
+
}
|
|
773
908
|
};
|
|
774
909
|
const finishDrawerDrag = (event) => {
|
|
775
910
|
const drag = drawerDrag.current;
|
|
@@ -778,11 +913,76 @@ export function AwikiOverlay(props) {
|
|
|
778
913
|
clearTimeout(drag.timer);
|
|
779
914
|
if (drag.moved)
|
|
780
915
|
saveLauncherPosition(drag.current);
|
|
916
|
+
if (drag.currentFrame !== null)
|
|
917
|
+
saveDrawerFrame(drag.currentFrame);
|
|
781
918
|
drawerDrag.current = null;
|
|
782
919
|
setDrawerDragging(false);
|
|
783
920
|
setDrawerDragDirection(null);
|
|
784
921
|
callPointerCapture(event.currentTarget, 'releasePointerCapture', event.pointerId);
|
|
785
922
|
};
|
|
923
|
+
const onDrawerResizePointerDown = (event, direction) => {
|
|
924
|
+
if (event.button !== 0)
|
|
925
|
+
return;
|
|
926
|
+
event.preventDefault();
|
|
927
|
+
event.stopPropagation();
|
|
928
|
+
const origin = clampAwikiDrawerFrame(renderedDrawerFrame, window.innerWidth, window.innerHeight);
|
|
929
|
+
drawerResize.current = {
|
|
930
|
+
pointerId: event.pointerId,
|
|
931
|
+
direction,
|
|
932
|
+
startX: event.clientX,
|
|
933
|
+
startY: event.clientY,
|
|
934
|
+
origin,
|
|
935
|
+
current: origin,
|
|
936
|
+
};
|
|
937
|
+
setDrawerFrame(origin);
|
|
938
|
+
setDrawerResizing(direction);
|
|
939
|
+
callPointerCapture(event.currentTarget, 'setPointerCapture', event.pointerId);
|
|
940
|
+
};
|
|
941
|
+
const onDrawerResizePointerMove = (event) => {
|
|
942
|
+
const resize = drawerResize.current;
|
|
943
|
+
if (resize === null || resize.pointerId !== event.pointerId)
|
|
944
|
+
return;
|
|
945
|
+
event.preventDefault();
|
|
946
|
+
resize.current = resizeAwikiDrawerFrame(resize.origin, resize.direction, event.clientX - resize.startX, event.clientY - resize.startY, window.innerWidth, window.innerHeight);
|
|
947
|
+
setDrawerFrame(resize.current);
|
|
948
|
+
};
|
|
949
|
+
const finishDrawerResize = (event) => {
|
|
950
|
+
const resize = drawerResize.current;
|
|
951
|
+
if (resize === null || resize.pointerId !== event.pointerId)
|
|
952
|
+
return;
|
|
953
|
+
saveDrawerFrame(resize.current);
|
|
954
|
+
drawerResize.current = null;
|
|
955
|
+
setDrawerResizing(null);
|
|
956
|
+
callPointerCapture(event.currentTarget, 'releasePointerCapture', event.pointerId);
|
|
957
|
+
};
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
if (drawerResizing === null)
|
|
960
|
+
return;
|
|
961
|
+
const onPointerMove = (event) => {
|
|
962
|
+
const resize = drawerResize.current;
|
|
963
|
+
if (resize === null || resize.pointerId !== event.pointerId)
|
|
964
|
+
return;
|
|
965
|
+
event.preventDefault();
|
|
966
|
+
resize.current = resizeAwikiDrawerFrame(resize.origin, resize.direction, event.clientX - resize.startX, event.clientY - resize.startY, window.innerWidth, window.innerHeight);
|
|
967
|
+
setDrawerFrame(resize.current);
|
|
968
|
+
};
|
|
969
|
+
const onPointerEnd = (event) => {
|
|
970
|
+
const resize = drawerResize.current;
|
|
971
|
+
if (resize === null || resize.pointerId !== event.pointerId)
|
|
972
|
+
return;
|
|
973
|
+
saveDrawerFrame(resize.current);
|
|
974
|
+
drawerResize.current = null;
|
|
975
|
+
setDrawerResizing(null);
|
|
976
|
+
};
|
|
977
|
+
window.addEventListener('pointermove', onPointerMove, { passive: false });
|
|
978
|
+
window.addEventListener('pointerup', onPointerEnd);
|
|
979
|
+
window.addEventListener('pointercancel', onPointerEnd);
|
|
980
|
+
return () => {
|
|
981
|
+
window.removeEventListener('pointermove', onPointerMove);
|
|
982
|
+
window.removeEventListener('pointerup', onPointerEnd);
|
|
983
|
+
window.removeEventListener('pointercancel', onPointerEnd);
|
|
984
|
+
};
|
|
985
|
+
}, [drawerResizing]);
|
|
786
986
|
const toggleLauncher = () => {
|
|
787
987
|
if (suppressLauncherClick.current) {
|
|
788
988
|
suppressLauncherClick.current = false;
|
|
@@ -800,6 +1000,21 @@ export function AwikiOverlay(props) {
|
|
|
800
1000
|
setComposeDirect(false);
|
|
801
1001
|
setPeerHandle('');
|
|
802
1002
|
};
|
|
1003
|
+
const createGroup = async () => {
|
|
1004
|
+
setGroupComposeError(null);
|
|
1005
|
+
const members = groupMembers
|
|
1006
|
+
.split(/[\n,,]+/u)
|
|
1007
|
+
.map(member => member.trim())
|
|
1008
|
+
.filter(member => member !== '');
|
|
1009
|
+
const result = await props.createGroup(groupName, members);
|
|
1010
|
+
if (!result.ok) {
|
|
1011
|
+
setGroupComposeError(result.error);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
setComposeGroup(false);
|
|
1015
|
+
setGroupName('');
|
|
1016
|
+
setGroupMembers('');
|
|
1017
|
+
};
|
|
803
1018
|
const logout = async () => {
|
|
804
1019
|
setLogoutPending(true);
|
|
805
1020
|
setLogoutError(null);
|
|
@@ -814,14 +1029,30 @@ export function AwikiOverlay(props) {
|
|
|
814
1029
|
};
|
|
815
1030
|
return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: launcherRef, type: "button", className: css.trigger, style: { left: launcherPosition.left, top: launcherPosition.top }, "data-dragging": launcherDragging || undefined, "aria-label": open
|
|
816
1031
|
? '收起 AWiki'
|
|
817
|
-
: unreadCount > 0 ? `打开 AWiki,${unreadCount} 条未读消息` : '打开 AWiki', "aria-expanded": open, "aria-haspopup": "dialog", title: "AWiki", onClick: toggleLauncher, onPointerDown: onLauncherPointerDown, onPointerMove: onLauncherPointerMove, onPointerUp: finishLauncherDrag, onPointerCancel: finishLauncherDrag, children: [_jsx("img", { className: css.launcherIcon, src: AWIKI_ME_APP_ICON_DATA_URL, alt: "", "aria-hidden": "true", draggable: "false" }), unreadCount > 0 && _jsx("span", { className: css.unreadBadge, "aria-hidden": "true", children: unreadLabel })] }), open && (_jsxs("div", { className: css.drawer, style: {
|
|
1032
|
+
: unreadCount > 0 ? `打开 AWiki,${unreadCount} 条未读消息` : '打开 AWiki', "aria-expanded": open, "aria-haspopup": "dialog", title: "AWiki", onClick: toggleLauncher, onPointerDown: onLauncherPointerDown, onPointerMove: onLauncherPointerMove, onPointerUp: finishLauncherDrag, onPointerCancel: finishLauncherDrag, children: [_jsx("img", { className: css.launcherIcon, src: AWIKI_ME_APP_ICON_DATA_URL, alt: "", "aria-hidden": "true", draggable: "false" }), unreadCount > 0 && _jsx("span", { className: css.unreadBadge, "aria-hidden": "true", children: unreadLabel })] }), open && (_jsxs("div", { className: css.drawer, style: {
|
|
1033
|
+
left: renderedDrawerFrame.left,
|
|
1034
|
+
top: renderedDrawerFrame.top,
|
|
1035
|
+
width: renderedDrawerFrame.width,
|
|
1036
|
+
height: renderedDrawerFrame.height,
|
|
1037
|
+
}, "data-placement": drawerPlacement.direction, "data-mode": mode, "data-resizing": drawerResizing ?? undefined, role: "dialog", "aria-modal": "false", "aria-labelledby": titleId, children: [_jsxs("header", { className: css.drawerHeader, "data-dragging": drawerDragging || undefined, title: "\u957F\u6309\u62D6\u52A8 AWiki", onPointerDown: onDrawerPointerDown, onPointerMove: onDrawerPointerMove, onPointerUp: finishDrawerDrag, onPointerCancel: finishDrawerDrag, children: [_jsxs("div", { children: [registered ? (_jsx(Menu, { open: accountMenuOpen, onClose: () => { setAccountMenuOpen(false); }, align: "start", portal: true, compact: true, items: [{ id: 'logout', label: '退出登录', danger: true }], onSelect: () => {
|
|
818
1038
|
setAccountMenuOpen(false);
|
|
819
1039
|
setLogoutError(null);
|
|
820
1040
|
setLogoutOpen(true);
|
|
821
|
-
}, 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" })] }),
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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: [
|
|
1047
|
+
{ id: 'direct', label: '发起私聊' },
|
|
1048
|
+
{ id: 'group', label: '发起群聊' },
|
|
1049
|
+
], onSelect: (id) => {
|
|
1050
|
+
setMenuOpen(false);
|
|
1051
|
+
if (id === 'direct')
|
|
1052
|
+
setComposeDirect(true);
|
|
1053
|
+
if (id === 'group')
|
|
1054
|
+
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)))] }))] }));
|
|
826
1057
|
}
|
|
827
1058
|
//# sourceMappingURL=AwikiOverlay.js.map
|