From bfa0059d3ad090bf65c8b6e915e02828a9814d7f Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 23 Sep 2026 11:17:43 +0800 Subject: [PATCH] feat: cache contacts and add forced sync --- control-plane/account_store.go | 184 ++++++++++++-- control-plane/contact_cache_test.go | 70 ++++++ control-plane/data_routes.go | 134 +++++++++-- control-plane/data_routes_test.go | 24 +- control-plane/server.go | 2 +- .../web/dist/assets/index-BS_ZFWFu.js | 40 ++++ .../web/dist/assets/index-PxzqqEA7.js | 40 ---- control-plane/web/dist/index.html | 2 +- control-plane/web/src/main.jsx | 34 ++- .../WxAgent.Core/RemoteControlClient.cs | 7 +- node-agent/WxAgent.Core/RemoteDataSync.cs | 43 +++- .../RemoteAgentHostedService.cs | 224 +++++++++++++++--- .../WxAgent.Core.Tests/RemoteDataSyncTests.cs | 25 ++ 13 files changed, 708 insertions(+), 121 deletions(-) create mode 100644 control-plane/contact_cache_test.go create mode 100644 control-plane/web/dist/assets/index-BS_ZFWFu.js delete mode 100644 control-plane/web/dist/assets/index-PxzqqEA7.js diff --git a/control-plane/account_store.go b/control-plane/account_store.go index b1567b1..283bdcd 100644 --- a/control-plane/account_store.go +++ b/control-plane/account_store.go @@ -30,7 +30,7 @@ var ( ) const ( - accountSchemaVersion = "1" + accountSchemaVersion = "2" catalogSchemaVersion = "1" ) @@ -94,17 +94,27 @@ type MessageRecord struct { PayloadHash string } +type ContactRecord struct { + ChatID string + ChatType string + DisplayName string + Remark string + ObservedAt time.Time +} + type IngestBatch struct { - BatchID string - SourceGeneration string - StreamKey string - Sequence int64 - CursorStart string - CursorEnd string - PayloadHash string - CoverageState string - Conversations []ConversationRecord - Messages []MessageRecord + BatchID string + SourceGeneration string + StreamKey string + Sequence int64 + CursorStart string + CursorEnd string + PayloadHash string + CoverageState string + Conversations []ConversationRecord + Messages []MessageRecord + Contacts []ContactRecord + ContactsSnapshotID string } type BatchApplyResult struct { @@ -662,6 +672,25 @@ func (s *AccountStore) ApplyBatch(ctx context.Context, batch IngestBatch) (Batch return rollback(fmt.Errorf("write message: %w", err)) } } + for _, contact := range batch.Contacts { + if _, err := tx.ExecContext(ctx, ` + INSERT INTO contacts (chat_id, chat_type, display_name, remark, observed_at, snapshot_id) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(chat_id) DO UPDATE SET + chat_type = excluded.chat_type, + display_name = excluded.display_name, + remark = excluded.remark, + observed_at = excluded.observed_at, + snapshot_id = excluded.snapshot_id`, contact.ChatID, contact.ChatType, + contact.DisplayName, contact.Remark, formatTime(ptrTime(contact.ObservedAt)), batch.ContactsSnapshotID); err != nil { + return rollback(fmt.Errorf("write contact: %w", err)) + } + } + if batch.StreamKey == "contacts" && batch.CoverageState == "complete" { + if _, err := tx.ExecContext(ctx, `DELETE FROM contacts WHERE snapshot_id <> ?`, batch.ContactsSnapshotID); err != nil { + return rollback(fmt.Errorf("replace contacts snapshot: %w", err)) + } + } if _, err := tx.ExecContext(ctx, ` INSERT INTO sync_state (stream_key, source_generation, confirmed_sequence, confirmed_cursor, coverage_state, last_success_at) VALUES (?, ?, ?, ?, ?, ?) @@ -798,6 +827,78 @@ func (s *AccountStore) QueryConversations(ctx context.Context, limit int, cursor return records, nil } +func (s *AccountStore) QueryContacts(ctx context.Context, contains string, groupsOnly bool, limit, offset int) ([]ContactRecord, bool, error) { + if limit < 1 || limit > 200 || offset < 0 || offset > 1_000_000 { + return nil, false, errors.New("invalid contacts pagination") + } + scopes, _, err := s.manager.activeScopes(ctx, s.accountID) + if err != nil { + return nil, false, err + } + allChats := slices.ContainsFunc(scopes, func(scope ReportingScope) bool { + return scope.ChatID == "*" && scopeAllows(scopes, "*", "contacts") + }) + allowedChats := make([]string, 0, len(scopes)) + seenChats := make(map[string]struct{}, len(scopes)) + for _, scope := range scopes { + if scope.ChatID == "*" || !scopeAllows(scopes, scope.ChatID, "contacts") { + continue + } + if _, exists := seenChats[scope.ChatID]; exists { + continue + } + seenChats[scope.ChatID] = struct{}{} + allowedChats = append(allowedChats, scope.ChatID) + } + if !allChats && len(allowedChats) == 0 { + return nil, false, ErrAccountNotAuthorized + } + + query := `SELECT chat_id, chat_type, display_name, remark, observed_at FROM contacts` + conditions := make([]string, 0, 3) + args := make([]any, 0, len(allowedChats)+4) + if !allChats { + conditions = append(conditions, `chat_id IN (`+strings.TrimSuffix(strings.Repeat("?,", len(allowedChats)), ",")+")") + for _, chatID := range allowedChats { + args = append(args, chatID) + } + } + if groupsOnly { + conditions = append(conditions, `chat_type = 'Group'`) + } else { + conditions = append(conditions, `chat_type = 'Private'`) + } + if contains != "" { + conditions = append(conditions, `instr(lower(chat_id || ' ' || display_name || ' ' || remark), lower(?)) > 0`) + args = append(args, contains) + } + query += ` WHERE ` + strings.Join(conditions, ` AND `) + ` ORDER BY lower(CASE WHEN remark <> '' THEN remark WHEN display_name <> '' THEN display_name ELSE chat_id END), chat_id LIMIT ? OFFSET ?` + args = append(args, limit+1, offset) + rows, err := s.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, false, fmt.Errorf("query contacts: %w", err) + } + defer rows.Close() + var records []ContactRecord + for rows.Next() { + var item ContactRecord + var observed string + if err := rows.Scan(&item.ChatID, &item.ChatType, &item.DisplayName, &item.Remark, &observed); err != nil { + return nil, false, err + } + item.ObservedAt = parseTime(observed) + records = append(records, item) + } + if err := rows.Err(); err != nil { + return nil, false, err + } + hasMore := len(records) > limit + if hasMore { + records = records[:limit] + } + return records, hasMore, nil +} + type AccountSyncStatus struct { StreamKey string SourceGeneration string @@ -932,6 +1033,31 @@ func validateAccountSchema(ctx context.Context, db *sql.DB) error { if err := db.QueryRowContext(ctx, `SELECT value FROM schema_meta WHERE key = 'schema_version'`).Scan(&version); err != nil { return fmt.Errorf("read account schema version: %w", err) } + if version == "1" { + tx, err := db.Begin() + if err != nil { + return err + } + if _, err := tx.Exec(`CREATE TABLE IF NOT EXISTS contacts ( + chat_id TEXT PRIMARY KEY, + chat_type TEXT NOT NULL, + display_name TEXT NOT NULL, + remark TEXT NOT NULL, + observed_at TEXT NOT NULL, + snapshot_id TEXT NOT NULL + )`); err != nil { + _ = tx.Rollback() + return fmt.Errorf("migrate account contacts schema: %w", err) + } + if _, err := tx.Exec(`UPDATE schema_meta SET value = ? WHERE key = 'schema_version'`, accountSchemaVersion); err != nil { + _ = tx.Rollback() + return fmt.Errorf("update account schema version: %w", err) + } + if err := tx.Commit(); err != nil { + return err + } + version = accountSchemaVersion + } if version != accountSchemaVersion { return fmt.Errorf("unsupported account schema version %q", version) } @@ -984,7 +1110,15 @@ func createAccountShard(path string) error { CREATE INDEX IF NOT EXISTS idx_messages_chat_time ON messages(chat_id, source_time DESC, message_id DESC); CREATE INDEX IF NOT EXISTS idx_messages_observed_at ON messages(observed_at); CREATE INDEX IF NOT EXISTS idx_conversations_activity ON conversations(last_activity_at DESC, observed_at DESC, chat_id); - CREATE TABLE IF NOT EXISTS sync_state ( + CREATE TABLE IF NOT EXISTS contacts ( + chat_id TEXT PRIMARY KEY, + chat_type TEXT NOT NULL, + display_name TEXT NOT NULL, + remark TEXT NOT NULL, + observed_at TEXT NOT NULL, + snapshot_id TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS sync_state ( stream_key TEXT PRIMARY KEY, source_generation TEXT NOT NULL, confirmed_sequence INTEGER NOT NULL DEFAULT 0, @@ -1007,7 +1141,7 @@ func createAccountShard(path string) error { received_at TEXT NOT NULL, confirmed_at TEXT ); - INSERT INTO schema_meta(key, value) VALUES ('schema_version', '1') + INSERT INTO schema_meta(key, value) VALUES ('schema_version', '2') ON CONFLICT(key) DO UPDATE SET value = excluded.value; `) if err != nil { @@ -1075,6 +1209,15 @@ func validateBatch(batch IngestBatch) error { if strings.TrimSpace(batch.BatchID) == "" || strings.TrimSpace(batch.SourceGeneration) == "" || strings.TrimSpace(batch.StreamKey) == "" || batch.Sequence < 1 || strings.TrimSpace(batch.PayloadHash) == "" { return errors.New("batch id, source generation, stream key, positive sequence, and payload hash are required") } + if batch.StreamKey != "messages" && batch.StreamKey != "contacts" { + return errors.New("unsupported data stream") + } + if batch.StreamKey == "contacts" && (strings.TrimSpace(batch.ContactsSnapshotID) == "" || len(batch.Conversations) > 0 || len(batch.Messages) > 0) { + return errors.New("contacts batches require a snapshot id and cannot contain messages or conversations") + } + if batch.StreamKey == "messages" && (len(batch.Contacts) > 0 || batch.ContactsSnapshotID != "") { + return errors.New("message batches cannot contain contacts") + } for _, conversation := range batch.Conversations { if strings.TrimSpace(conversation.ChatID) == "" || strings.TrimSpace(conversation.Source) == "" || conversation.ObservedAt.IsZero() { return errors.New("conversation identity and observation time are required") @@ -1085,11 +1228,16 @@ func validateBatch(batch IngestBatch) error { return errors.New("message identity, payload hash, source time, and observation time are required") } } + for _, contact := range batch.Contacts { + if strings.TrimSpace(contact.ChatID) == "" || len(contact.ChatID) > 512 || len(contact.DisplayName) > 2048 || len(contact.Remark) > 2048 || (contact.ChatType != string(ChatPrivate) && contact.ChatType != string(ChatGroup)) || contact.ObservedAt.IsZero() { + return errors.New("contact identity, type, and observation time are required") + } + } return nil } func authorizeBatch(batch IngestBatch, scopes []ReportingScope) error { - if len(scopes) == 0 && (len(batch.Conversations) > 0 || len(batch.Messages) > 0) { + if len(scopes) == 0 && (len(batch.Conversations) > 0 || len(batch.Messages) > 0 || len(batch.Contacts) > 0) { return ErrAccountNotAuthorized } for _, conversation := range batch.Conversations { @@ -1102,6 +1250,11 @@ func authorizeBatch(batch IngestBatch, scopes []ReportingScope) error { return ErrAccountNotAuthorized } } + for _, contact := range batch.Contacts { + if !scopeAllows(scopes, contact.ChatID, "contacts") { + return ErrAccountNotAuthorized + } + } return nil } @@ -1176,6 +1329,9 @@ func estimateBatchBytes(batch IngestBatch) int64 { for _, item := range batch.Messages { total += int64(len(item.MessageID) + len(item.ChatID) + len(item.SourceMessageID) + len(item.Direction) + len(item.MessageType) + len(item.Text) + len(item.SourceVersion) + len(item.PayloadHash) + 192) } + for _, item := range batch.Contacts { + total += int64(len(item.ChatID) + len(item.ChatType) + len(item.DisplayName) + len(item.Remark) + 128) + } return total } diff --git a/control-plane/contact_cache_test.go b/control-plane/contact_cache_test.go new file mode 100644 index 0000000..b77f055 --- /dev/null +++ b/control-plane/contact_cache_test.go @@ -0,0 +1,70 @@ +package controlplane + +import ( + "context" + "testing" + "time" +) + +func TestContactSnapshotsReplaceOnlyAfterCompleteSync(t *testing.T) { + ctx := context.Background() + manager, err := OpenAccountStoreManager(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer manager.Close() + store, err := manager.RegisterAccount(ctx, AccountRegistration{ + AccountID: "account-contacts", StableIdentity: "wechat-contacts", SourceNodeID: "node-contacts", + SourceGeneration: "generation-contacts", Verified: true, AuthorizationVersion: 1, + ReportingScopes: []ReportingScope{{ChatID: "*", DataType: "*", ConfigVersion: 1}}, + }) + if err != nil { + t.Fatal(err) + } + defer func() { _ = store.Close() }() + + // Simulate an existing v1 shard so opening it exercises the contacts-table migration. + if _, err := store.db.ExecContext(ctx, `DROP TABLE contacts`); err != nil { + t.Fatal(err) + } + if _, err := store.db.ExecContext(ctx, `UPDATE schema_meta SET value = '1' WHERE key = 'schema_version'`); err != nil { + t.Fatal(err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + store, err = manager.OpenAccount(ctx, "account-contacts") + if err != nil { + t.Fatal(err) + } + + now := time.Now().UTC() + private := ContactRecord{ChatID: "wxid-contact", ChatType: string(ChatPrivate), DisplayName: "Contact", Remark: "Local", ObservedAt: now} + group := ContactRecord{ChatID: "group@chatroom", ChatType: string(ChatGroup), DisplayName: "Group", ObservedAt: now} + apply := func(sequence int64, snapshotID, coverage string, contacts ...ContactRecord) error { + _, err := store.ApplyBatch(ctx, IngestBatch{ + BatchID: "contacts-" + snapshotID, SourceGeneration: "generation-contacts", StreamKey: "contacts", + Sequence: sequence, CursorStart: "0", CursorEnd: "0", PayloadHash: "hash-" + snapshotID, + CoverageState: coverage, ContactsSnapshotID: snapshotID, Contacts: contacts, + }) + return err + } + if err := apply(1, "snapshot-1", "complete", private, group); err != nil { + t.Fatal(err) + } + if items, _, err := store.QueryContacts(ctx, "", true, 10, 0); err != nil || len(items) != 1 || items[0].ChatID != group.ChatID { + t.Fatalf("unexpected cached groups: %v %+v", err, items) + } + if err := apply(2, "snapshot-2", "partial", private); err != nil { + t.Fatal(err) + } + if items, _, err := store.QueryContacts(ctx, "", true, 10, 0); err != nil || len(items) != 1 { + t.Fatalf("partial snapshot removed prior contacts: %v %+v", err, items) + } + if err := apply(3, "snapshot-3", "complete", private); err != nil { + t.Fatal(err) + } + if items, _, err := store.QueryContacts(ctx, "", true, 10, 0); err != nil || len(items) != 0 { + t.Fatalf("complete snapshot did not remove stale contacts: %v %+v", err, items) + } +} diff --git a/control-plane/data_routes.go b/control-plane/data_routes.go index b8771de..4b0e0ed 100644 --- a/control-plane/data_routes.go +++ b/control-plane/data_routes.go @@ -6,24 +6,27 @@ import ( "errors" "net/http" "strconv" + "strings" "time" ) const maxDataBatchBytes = 512 * 1024 type dataBatchRequest struct { - NodeID string `json:"node_id"` - AccountID string `json:"account_id"` - BatchID string `json:"batch_id"` - SourceGeneration string `json:"source_generation"` - StreamKey string `json:"stream_key"` - Sequence int64 `json:"sequence"` - CursorStart string `json:"cursor_start"` - CursorEnd string `json:"cursor_end"` - PayloadHash string `json:"payload_hash"` - CoverageState string `json:"coverage_state"` - Conversations []dataConversationRequest `json:"conversations"` - Messages []dataMessageRequest `json:"messages"` + NodeID string `json:"node_id"` + AccountID string `json:"account_id"` + BatchID string `json:"batch_id"` + SourceGeneration string `json:"source_generation"` + StreamKey string `json:"stream_key"` + Sequence int64 `json:"sequence"` + CursorStart string `json:"cursor_start"` + CursorEnd string `json:"cursor_end"` + PayloadHash string `json:"payload_hash"` + CoverageState string `json:"coverage_state"` + Conversations []dataConversationRequest `json:"conversations"` + Messages []dataMessageRequest `json:"messages"` + Contacts []dataContactRequest `json:"contacts"` + ContactsSnapshotID string `json:"contacts_snapshot_id"` } type dataConversationRequest struct { @@ -50,6 +53,14 @@ type dataMessageRequest struct { PayloadHash string `json:"payload_hash"` } +type dataContactRequest struct { + ChatID string `json:"chat_id"` + ChatType ChatType `json:"chat_type"` + DisplayName string `json:"display_name"` + Remark string `json:"remark"` + ObservedAt time.Time `json:"observed_at"` +} + type dataBatchAck struct { Accepted bool `json:"accepted"` Duplicate bool `json:"duplicate"` @@ -80,6 +91,14 @@ type dataConversationView struct { DirectoryState string `json:"directory_state"` } +type dataContactView struct { + ChatID string `json:"id"` + ChatType string `json:"type"` + DisplayName string `json:"display_name"` + Remark string `json:"remark"` + ObservedAt time.Time `json:"observed_at"` +} + type dataMessageView struct { MessageID string `json:"message_id"` ChatID string `json:"chat_id"` @@ -166,7 +185,14 @@ func (s *Server) nodeDataRoute(w http.ResponseWriter, r *http.Request, nodeID st return requestError{status: http.StatusNotFound, code: "AccountDataNotFound", message: "No platform data is available for this account."} } defer store.Close() - status, err := store.GetSyncStatus(r.Context(), "messages") + streamKey := r.URL.Query().Get("stream_key") + if streamKey == "" { + streamKey = "messages" + } + if streamKey != "messages" && streamKey != "contacts" { + return requestError{status: http.StatusBadRequest, code: "InvalidStream", message: "The sync stream is invalid."} + } + status, err := store.GetSyncStatus(r.Context(), streamKey) if err != nil { return requestError{status: http.StatusInternalServerError, code: "DataStatusFailed", message: "The platform sync status query failed."} } @@ -183,7 +209,7 @@ func (s *Server) ingestDataBatch(w http.ResponseWriter, r *http.Request, correla if err := decodeJSON(r, &request, maxDataBatchBytes); err != nil { return err } - if request.NodeID != nodeID || request.AccountID == "" || request.BatchID == "" || request.SourceGeneration == "" || request.StreamKey != "messages" || request.Sequence < 1 || request.PayloadHash == "" || len(request.Messages) > 5000 || len(request.Conversations) > 1000 { + if request.NodeID != nodeID || request.AccountID == "" || request.BatchID == "" || request.SourceGeneration == "" || (request.StreamKey != "messages" && request.StreamKey != "contacts") || request.Sequence < 1 || request.PayloadHash == "" || len(request.Messages) > 5000 || len(request.Conversations) > 1000 || len(request.Contacts) > 1000 || request.StreamKey == "contacts" && (request.ContactsSnapshotID == "" || len(request.Messages) > 0 || len(request.Conversations) > 0) || request.StreamKey == "messages" && (len(request.Contacts) > 0 || request.ContactsSnapshotID != "") { return requestError{status: http.StatusBadRequest, code: "InvalidDataBatch", message: "The data batch is invalid."} } var account AccountSummary @@ -214,6 +240,7 @@ func (s *Server) ingestDataBatch(w http.ResponseWriter, r *http.Request, correla BatchID: request.BatchID, SourceGeneration: request.SourceGeneration, StreamKey: request.StreamKey, Sequence: request.Sequence, CursorStart: request.CursorStart, CursorEnd: request.CursorEnd, PayloadHash: request.PayloadHash, CoverageState: request.CoverageState, + ContactsSnapshotID: request.ContactsSnapshotID, } for _, conversation := range request.Conversations { if !validChatType(conversation.ChatType) || conversation.ObservedAt.IsZero() { @@ -236,6 +263,15 @@ func (s *Server) ingestDataBatch(w http.ResponseWriter, r *http.Request, correla PayloadHash: message.PayloadHash, }) } + for _, contact := range request.Contacts { + if !validChatType(contact.ChatType) || strings.TrimSpace(contact.ChatID) == "" || len(contact.ChatID) > 512 || len(contact.DisplayName) > 2048 || len(contact.Remark) > 2048 || contact.ObservedAt.IsZero() { + return requestError{status: http.StatusBadRequest, code: "InvalidDataBatch", message: "A contact record is invalid."} + } + batch.Contacts = append(batch.Contacts, ContactRecord{ + ChatID: contact.ChatID, ChatType: string(contact.ChatType), DisplayName: contact.DisplayName, + Remark: contact.Remark, ObservedAt: contact.ObservedAt, + }) + } result, err := store.ApplyBatch(r.Context(), batch) if err != nil { switch { @@ -282,6 +318,11 @@ func (s *Server) dataRoute(w http.ResponseWriter, r *http.Request, correlationID return requestError{status: http.StatusMethodNotAllowed, code: "MethodNotAllowed", message: "Method is not allowed."} } return s.listStoredConversations(w, r, accountID) + case "contacts": + if r.Method != http.MethodGet { + return requestError{status: http.StatusMethodNotAllowed, code: "MethodNotAllowed", message: "Method is not allowed."} + } + return s.listStoredContacts(w, r, accountID) case "messages": if r.Method != http.MethodGet { return requestError{status: http.StatusMethodNotAllowed, code: "MethodNotAllowed", message: "Method is not allowed."} @@ -354,6 +395,52 @@ func (s *Server) listStoredConversations(w http.ResponseWriter, r *http.Request, return nil } +func (s *Server) listStoredContacts(w http.ResponseWriter, r *http.Request, accountID string) error { + limit := queryLimit(r.URL.Query().Get("limit")) + offset := 0 + if raw := r.URL.Query().Get("offset"); raw != "" { + parsed, err := strconv.Atoi(raw) + if err != nil || parsed < 0 || parsed > 1_000_000 { + return requestError{status: http.StatusBadRequest, code: "InvalidOffset", message: "The contact offset is invalid."} + } + offset = parsed + } + groupsOnly := false + if raw := r.URL.Query().Get("groups_only"); raw != "" { + parsed, err := strconv.ParseBool(raw) + if err != nil { + return requestError{status: http.StatusBadRequest, code: "InvalidGroupFilter", message: "groups_only must be a boolean."} + } + groupsOnly = parsed + } + contains := r.URL.Query().Get("contains") + if len(contains) > 200 { + return requestError{status: http.StatusBadRequest, code: "InvalidQuery", message: "The contact query is too long."} + } + store, err := s.accountStores.OpenAccount(r.Context(), accountID) + if err != nil { + return requestError{status: http.StatusNotFound, code: "AccountDataNotFound", message: "No platform data is available for this account."} + } + defer store.Close() + items, hasMore, err := store.QueryContacts(r.Context(), contains, groupsOnly, limit, offset) + if err != nil { + if errors.Is(err, ErrAccountNotAuthorized) { + return requestError{status: http.StatusForbidden, code: "DataNotAuthorized", message: "The account has no active contact reporting scope."} + } + return requestError{status: http.StatusInternalServerError, code: "DataQueryFailed", message: "The platform contact query failed."} + } + status, err := store.GetSyncStatus(r.Context(), "contacts") + if err != nil { + return requestError{status: http.StatusInternalServerError, code: "DataStatusFailed", message: "The platform sync status query failed."} + } + views := make([]dataContactView, 0, len(items)) + for _, item := range items { + views = append(views, dataContactView{item.ChatID, item.ChatType, item.DisplayName, item.Remark, item.ObservedAt}) + } + writeJSON(w, http.StatusOK, map[string]any{"items": views, "limit": limit, "offset": offset, "has_more": hasMore, "sync": syncView(status)}) + return nil +} + func (s *Server) listStoredMessages(w http.ResponseWriter, r *http.Request, accountID string) error { chatID := r.URL.Query().Get("chat_id") if !validIdentifier(chatID, 512) { @@ -435,6 +522,21 @@ func (s *Server) revokeDataAuthorization(w http.ResponseWriter, r *http.Request, } func (s *Server) requestDataRefresh(w http.ResponseWriter, r *http.Request, accountID, username, correlationID string) error { + streamKey := "messages" + if r.ContentLength > 0 || strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { + var request struct { + StreamKey string `json:"stream_key"` + } + if err := decodeJSON(r, &request, 8*1024); err != nil { + return err + } + if request.StreamKey != "" { + streamKey = request.StreamKey + } + } + if streamKey != "messages" && streamKey != "contacts" { + return requestError{status: http.StatusBadRequest, code: "InvalidStream", message: "The sync stream is invalid."} + } var nodeID string if err := s.store.Read(func(state PersistedState) error { for id, node := range state.Nodes { @@ -455,6 +557,6 @@ func (s *Server) requestDataRefresh(w http.ResponseWriter, r *http.Request, acco if nodeID == "" { return requestError{status: http.StatusConflict, code: "NoAuthorizedNode", message: "No verified node is available for this account."} } - payload := json.RawMessage(`{"stream_key":"messages","reason":"web-refresh"}`) - return s.createTaskSubmission(w, TaskSubmission{NodeID: nodeID, AccountID: accountID, Kind: "sync-data", IdempotencyKey: "sync:" + accountID + ":" + strconv.FormatInt(time.Now().UTC().Unix()/5, 10), Payload: payload}, username, correlationID) + payload, _ := json.Marshal(map[string]string{"stream_key": streamKey, "reason": "web-refresh"}) + return s.createTaskSubmission(w, TaskSubmission{NodeID: nodeID, AccountID: accountID, Kind: "sync-data", IdempotencyKey: "sync:" + accountID + ":" + streamKey + ":" + strconv.FormatInt(time.Now().UTC().Unix()/5, 10), Payload: payload}, username, correlationID) } diff --git a/control-plane/data_routes_test.go b/control-plane/data_routes_test.go index 7ae4eb9..43e1100 100644 --- a/control-plane/data_routes_test.go +++ b/control-plane/data_routes_test.go @@ -92,7 +92,29 @@ func TestDataBatchRoundTripAndWebQueriesUseAccountShard(t *testing.T) { t.Fatalf("unexpected message body: %+v", messageBody) } - refresh := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/data/accounts/account-a/refresh", webAuth, nil) + contactBatch := dataBatchRequest{ + NodeID: "node-a", AccountID: "account-a", BatchID: "contacts-1", SourceGeneration: "account-a", StreamKey: "contacts", Sequence: 1, + CursorStart: "{}", CursorEnd: "{}", PayloadHash: "contacts-hash", CoverageState: "complete", ContactsSnapshotID: "snapshot-1", + Contacts: []dataContactRequest{{ChatID: "chat-a", ChatType: ChatPrivate, DisplayName: "测试联系人", Remark: "本地备注", ObservedAt: now}}, + } + contactPost := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/data/batches", "Bearer secret-a", contactBatch) + if contactPost.Code != http.StatusOK { + t.Fatalf("contact batch status = %d: %s", contactPost.Code, contactPost.Body.String()) + } + contacts := doJSON(t, client, http.MethodGet, httpServer.URL+"/v1/data/accounts/account-a/contacts?groups_only=false&limit=20", webAuth, nil) + if contacts.Code != http.StatusOK { + t.Fatalf("contact query status = %d: %s", contacts.Code, contacts.Body.String()) + } + var contactBody struct { + Items []dataContactView `json:"items"` + Sync dataSyncView `json:"sync"` + } + decodeBody(t, contacts, &contactBody) + if len(contactBody.Items) != 1 || contactBody.Items[0].ChatID != "chat-a" || contactBody.Sync.State != "complete" { + t.Fatalf("unexpected contact body: %+v", contactBody) + } + + refresh := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/data/accounts/account-a/refresh", webAuth, map[string]string{"stream_key": "contacts"}) if refresh.Code != http.StatusAccepted { t.Fatalf("refresh status = %d: %s", refresh.Code, refresh.Body.String()) } diff --git a/control-plane/server.go b/control-plane/server.go index ecb0402..09458e1 100644 --- a/control-plane/server.go +++ b/control-plane/server.go @@ -981,7 +981,7 @@ func validTaskPayload(kind string, payload jsonRaw) bool { StreamKey string `json:"stream_key"` Reason string `json:"reason"` } - return decodeRaw(payload, &value) && value.StreamKey == "messages" && (value.Reason == "" || validIdentifier(value.Reason, 80)) + return decodeRaw(payload, &value) && (value.StreamKey == "messages" || value.StreamKey == "contacts") && (value.Reason == "" || validIdentifier(value.Reason, 80)) } if !isReadTaskKind(kind) || len(payload) == 0 || len(payload) > 64*1024 || !json.Valid(payload) { return false diff --git a/control-plane/web/dist/assets/index-BS_ZFWFu.js b/control-plane/web/dist/assets/index-BS_ZFWFu.js new file mode 100644 index 0000000..284e668 --- /dev/null +++ b/control-plane/web/dist/assets/index-BS_ZFWFu.js @@ -0,0 +1,40 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();var uu={exports:{}},cl={},au={exports:{}},I={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var nr=Symbol.for("react.element"),Nc=Symbol.for("react.portal"),_c=Symbol.for("react.fragment"),Ec=Symbol.for("react.strict_mode"),Pc=Symbol.for("react.profiler"),zc=Symbol.for("react.provider"),Tc=Symbol.for("react.context"),Lc=Symbol.for("react.forward_ref"),Rc=Symbol.for("react.suspense"),Mc=Symbol.for("react.memo"),Ic=Symbol.for("react.lazy"),Ys=Symbol.iterator;function Oc(e){return e===null||typeof e!="object"?null:(e=Ys&&e[Ys]||e["@@iterator"],typeof e=="function"?e:null)}var cu={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},du=Object.assign,fu={};function hn(e,t,n){this.props=e,this.context=t,this.refs=fu,this.updater=n||cu}hn.prototype.isReactComponent={};hn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};hn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function pu(){}pu.prototype=hn.prototype;function qi(e,t,n){this.props=e,this.context=t,this.refs=fu,this.updater=n||cu}var bi=qi.prototype=new pu;bi.constructor=qi;du(bi,hn.prototype);bi.isPureReactComponent=!0;var Gs=Array.isArray,hu=Object.prototype.hasOwnProperty,es={current:null},mu={key:!0,ref:!0,__self:!0,__source:!0};function vu(e,t,n){var r,l={},i=null,s=null;if(t!=null)for(r in t.ref!==void 0&&(s=t.ref),t.key!==void 0&&(i=""+t.key),t)hu.call(t,r)&&!mu.hasOwnProperty(r)&&(l[r]=t[r]);var u=arguments.length-2;if(u===1)l.children=n;else if(1>>1,b=E[G];if(0>>1;Gl(Tl,R))ktl(ur,Tl)?(E[G]=ur,E[kt]=R,G=kt):(E[G]=Tl,E[St]=R,G=St);else if(ktl(ur,R))E[G]=ur,E[kt]=R,G=kt;else break e}}return L}function l(E,L){var R=E.sortIndex-L.sortIndex;return R!==0?R:E.id-L.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var s=Date,u=s.now();e.unstable_now=function(){return s.now()-u}}var a=[],d=[],v=1,h=null,m=3,w=!1,k=!1,S=!1,O=typeof setTimeout=="function"?setTimeout:null,f=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function p(E){for(var L=n(d);L!==null;){if(L.callback===null)r(d);else if(L.startTime<=E)r(d),L.sortIndex=L.expirationTime,t(a,L);else break;L=n(d)}}function g(E){if(S=!1,p(E),!k)if(n(a)!==null)k=!0,Pl(j);else{var L=n(d);L!==null&&zl(g,L.startTime-E)}}function j(E,L){k=!1,S&&(S=!1,f(x),x=-1),w=!0;var R=m;try{for(p(L),h=n(a);h!==null&&(!(h.expirationTime>L)||E&&!$());){var G=h.callback;if(typeof G=="function"){h.callback=null,m=h.priorityLevel;var b=G(h.expirationTime<=L);L=e.unstable_now(),typeof b=="function"?h.callback=b:h===n(a)&&r(a),p(L)}else r(a);h=n(a)}if(h!==null)var or=!0;else{var St=n(d);St!==null&&zl(g,St.startTime-L),or=!1}return or}finally{h=null,m=R,w=!1}}var C=!1,_=null,x=-1,z=5,P=-1;function $(){return!(e.unstable_now()-PE||125G?(E.sortIndex=R,t(d,E),n(a)===null&&E===n(d)&&(S?(f(x),x=-1):S=!0,zl(g,R-G))):(E.sortIndex=b,t(a,E),k||w||(k=!0,Pl(j))),E},e.unstable_shouldYield=$,e.unstable_wrapCallback=function(E){var L=m;return function(){var R=m;m=L;try{return E.apply(this,arguments)}finally{m=R}}}})(Su);wu.exports=Su;var Kc=wu.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Yc=T,ke=Kc;function y(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ri=Object.prototype.hasOwnProperty,Gc=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Zs={},Js={};function Xc(e){return ri.call(Js,e)?!0:ri.call(Zs,e)?!1:Gc.test(e)?Js[e]=!0:(Zs[e]=!0,!1)}function Zc(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Jc(e,t,n,r){if(t===null||typeof t>"u"||Zc(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function pe(e,t,n,r,l,i,s){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=s}var ie={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){ie[e]=new pe(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];ie[t]=new pe(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){ie[e]=new pe(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){ie[e]=new pe(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){ie[e]=new pe(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){ie[e]=new pe(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){ie[e]=new pe(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){ie[e]=new pe(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){ie[e]=new pe(e,5,!1,e.toLowerCase(),null,!1,!1)});var ns=/[\-:]([a-z])/g;function rs(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ns,rs);ie[t]=new pe(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ns,rs);ie[t]=new pe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ns,rs);ie[t]=new pe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){ie[e]=new pe(e,1,!1,e.toLowerCase(),null,!1,!1)});ie.xlinkHref=new pe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){ie[e]=new pe(e,1,!1,e.toLowerCase(),null,!0,!0)});function ls(e,t,n,r){var l=ie.hasOwnProperty(t)?ie[t]:null;(l!==null?l.type!==0:r||!(2u||l[s]!==i[u]){var a=` +`+l[s].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=s&&0<=u);break}}}finally{Ml=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Nn(e):""}function qc(e){switch(e.tag){case 5:return Nn(e.type);case 16:return Nn("Lazy");case 13:return Nn("Suspense");case 19:return Nn("SuspenseList");case 0:case 2:case 15:return e=Il(e.type,!1),e;case 11:return e=Il(e.type.render,!1),e;case 1:return e=Il(e.type,!0),e;default:return""}}function oi(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Vt:return"Fragment";case At:return"Portal";case li:return"Profiler";case is:return"StrictMode";case ii:return"Suspense";case si:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Cu:return(e.displayName||"Context")+".Consumer";case ju:return(e._context.displayName||"Context")+".Provider";case ss:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case os:return t=e.displayName||null,t!==null?t:oi(e.type)||"Memo";case tt:t=e._payload,e=e._init;try{return oi(e(t))}catch{}}return null}function bc(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return oi(t);case 8:return t===is?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function mt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function _u(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ed(e){var t=_u(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(s){r=""+s,i.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function dr(e){e._valueTracker||(e._valueTracker=ed(e))}function Eu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=_u(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Ur(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function ui(e,t){var n=t.checked;return K({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function bs(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=mt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Pu(e,t){t=t.checked,t!=null&&ls(e,"checked",t,!1)}function ai(e,t){Pu(e,t);var n=mt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ci(e,t.type,n):t.hasOwnProperty("defaultValue")&&ci(e,t.type,mt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function eo(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ci(e,t,n){(t!=="number"||Ur(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var _n=Array.isArray;function bt(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=fr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function An(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var zn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},td=["Webkit","ms","Moz","O"];Object.keys(zn).forEach(function(e){td.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),zn[t]=zn[e]})});function Ru(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||zn.hasOwnProperty(e)&&zn[e]?(""+t).trim():t+"px"}function Mu(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Ru(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var nd=K({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function pi(e,t){if(t){if(nd[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(y(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(y(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(y(61))}if(t.style!=null&&typeof t.style!="object")throw Error(y(62))}}function hi(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var mi=null;function us(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var vi=null,en=null,tn=null;function ro(e){if(e=ir(e)){if(typeof vi!="function")throw Error(y(280));var t=e.stateNode;t&&(t=ml(t),vi(e.stateNode,e.type,t))}}function Iu(e){en?tn?tn.push(e):tn=[e]:en=e}function Ou(){if(en){var e=en,t=tn;if(tn=en=null,ro(e),t)for(e=0;e>>=0,e===0?32:31-(pd(e)/hd|0)|0}var pr=64,hr=4194304;function En(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Br(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,s=n&268435455;if(s!==0){var u=s&~l;u!==0?r=En(u):(i&=s,i!==0&&(r=En(i)))}else s=n&~l,s!==0?r=En(s):i!==0&&(r=En(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function rr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Fe(t),e[t]=n}function yd(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Ln),po=" ",ho=!1;function ta(e,t){switch(e){case"keyup":return Kd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function na(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Wt=!1;function Gd(e,t){switch(e){case"compositionend":return na(t);case"keypress":return t.which!==32?null:(ho=!0,po);case"textInput":return e=t.data,e===po&&ho?null:e;default:return null}}function Xd(e,t){if(Wt)return e==="compositionend"||!vs&&ta(e,t)?(e=bu(),Tr=ps=it=null,Wt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=yo(n)}}function sa(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?sa(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function oa(){for(var e=window,t=Ur();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ur(e.document)}return t}function gs(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function lf(e){var t=oa(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&sa(n.ownerDocument.documentElement,n)){if(r!==null&&gs(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=xo(n,i);var s=xo(n,r);l&&s&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Bt=null,ki=null,Mn=null,ji=!1;function wo(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ji||Bt==null||Bt!==Ur(r)||(r=Bt,"selectionStart"in r&&gs(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Mn&&Kn(Mn,r)||(Mn=r,r=Kr(ki,"onSelect"),0Kt||(e.current=zi[Kt],zi[Kt]=null,Kt--)}function U(e,t){Kt++,zi[Kt]=e.current,e.current=t}var vt={},ae=xt(vt),ve=xt(!1),Tt=vt;function on(e,t){var n=e.type.contextTypes;if(!n)return vt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function ge(e){return e=e.childContextTypes,e!=null}function Gr(){V(ve),V(ae)}function Eo(e,t,n){if(ae.current!==vt)throw Error(y(168));U(ae,t),U(ve,n)}function va(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(y(108,bc(e)||"Unknown",l));return K({},n,r)}function Xr(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||vt,Tt=ae.current,U(ae,e),U(ve,ve.current),!0}function Po(e,t,n){var r=e.stateNode;if(!r)throw Error(y(169));n?(e=va(e,t,Tt),r.__reactInternalMemoizedMergedChildContext=e,V(ve),V(ae),U(ae,e)):V(ve),U(ve,n)}var Qe=null,vl=!1,Gl=!1;function ga(e){Qe===null?Qe=[e]:Qe.push(e)}function gf(e){vl=!0,ga(e)}function wt(){if(!Gl&&Qe!==null){Gl=!0;var e=0,t=D;try{var n=Qe;for(D=1;e>=s,l-=s,Ke=1<<32-Fe(t)+l|n<x?(z=_,_=null):z=_.sibling;var P=m(f,_,p[x],g);if(P===null){_===null&&(_=z);break}e&&_&&P.alternate===null&&t(f,_),c=i(P,c,x),C===null?j=P:C.sibling=P,C=P,_=z}if(x===p.length)return n(f,_),W&&jt(f,x),j;if(_===null){for(;xx?(z=_,_=null):z=_.sibling;var $=m(f,_,P.value,g);if($===null){_===null&&(_=z);break}e&&_&&$.alternate===null&&t(f,_),c=i($,c,x),C===null?j=$:C.sibling=$,C=$,_=z}if(P.done)return n(f,_),W&&jt(f,x),j;if(_===null){for(;!P.done;x++,P=p.next())P=h(f,P.value,g),P!==null&&(c=i(P,c,x),C===null?j=P:C.sibling=P,C=P);return W&&jt(f,x),j}for(_=r(f,_);!P.done;x++,P=p.next())P=w(_,f,x,P.value,g),P!==null&&(e&&P.alternate!==null&&_.delete(P.key===null?x:P.key),c=i(P,c,x),C===null?j=P:C.sibling=P,C=P);return e&&_.forEach(function(M){return t(f,M)}),W&&jt(f,x),j}function O(f,c,p,g){if(typeof p=="object"&&p!==null&&p.type===Vt&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case cr:e:{for(var j=p.key,C=c;C!==null;){if(C.key===j){if(j=p.type,j===Vt){if(C.tag===7){n(f,C.sibling),c=l(C,p.props.children),c.return=f,f=c;break e}}else if(C.elementType===j||typeof j=="object"&&j!==null&&j.$$typeof===tt&&Lo(j)===C.type){n(f,C.sibling),c=l(C,p.props),c.ref=kn(f,C,p),c.return=f,f=c;break e}n(f,C);break}else t(f,C);C=C.sibling}p.type===Vt?(c=zt(p.props.children,f.mode,g,p.key),c.return=f,f=c):(g=$r(p.type,p.key,p.props,null,f.mode,g),g.ref=kn(f,c,p),g.return=f,f=g)}return s(f);case At:e:{for(C=p.key;c!==null;){if(c.key===C)if(c.tag===4&&c.stateNode.containerInfo===p.containerInfo&&c.stateNode.implementation===p.implementation){n(f,c.sibling),c=l(c,p.children||[]),c.return=f,f=c;break e}else{n(f,c);break}else t(f,c);c=c.sibling}c=ni(p,f.mode,g),c.return=f,f=c}return s(f);case tt:return C=p._init,O(f,c,C(p._payload),g)}if(_n(p))return k(f,c,p,g);if(gn(p))return S(f,c,p,g);Sr(f,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,c!==null&&c.tag===6?(n(f,c.sibling),c=l(c,p),c.return=f,f=c):(n(f,c),c=ti(p,f.mode,g),c.return=f,f=c),s(f)):n(f,c)}return O}var an=Sa(!0),ka=Sa(!1),qr=xt(null),br=null,Xt=null,Ss=null;function ks(){Ss=Xt=br=null}function js(e){var t=qr.current;V(qr),e._currentValue=t}function Ri(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function rn(e,t){br=e,Ss=Xt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(me=!0),e.firstContext=null)}function ze(e){var t=e._currentValue;if(Ss!==e)if(e={context:e,memoizedValue:t,next:null},Xt===null){if(br===null)throw Error(y(308));Xt=e,br.dependencies={lanes:0,firstContext:e}}else Xt=Xt.next=e;return t}var _t=null;function Cs(e){_t===null?_t=[e]:_t.push(e)}function ja(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Cs(t)):(n.next=l.next,l.next=n),t.interleaved=n,Je(e,r)}function Je(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var nt=!1;function Ns(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ca(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Ge(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function dt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,F&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Je(e,n)}return l=r.interleaved,l===null?(t.next=t,Cs(r)):(t.next=l.next,l.next=t),r.interleaved=t,Je(e,n)}function Rr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,cs(e,n)}}function Ro(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=s:i=i.next=s,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function el(e,t,n,r){var l=e.updateQueue;nt=!1;var i=l.firstBaseUpdate,s=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var a=u,d=a.next;a.next=null,s===null?i=d:s.next=d,s=a;var v=e.alternate;v!==null&&(v=v.updateQueue,u=v.lastBaseUpdate,u!==s&&(u===null?v.firstBaseUpdate=d:u.next=d,v.lastBaseUpdate=a))}if(i!==null){var h=l.baseState;s=0,v=d=a=null,u=i;do{var m=u.lane,w=u.eventTime;if((r&m)===m){v!==null&&(v=v.next={eventTime:w,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var k=e,S=u;switch(m=t,w=n,S.tag){case 1:if(k=S.payload,typeof k=="function"){h=k.call(w,h,m);break e}h=k;break e;case 3:k.flags=k.flags&-65537|128;case 0:if(k=S.payload,m=typeof k=="function"?k.call(w,h,m):k,m==null)break e;h=K({},h,m);break e;case 2:nt=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,m=l.effects,m===null?l.effects=[u]:m.push(u))}else w={eventTime:w,lane:m,tag:u.tag,payload:u.payload,callback:u.callback,next:null},v===null?(d=v=w,a=h):v=v.next=w,s|=m;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;m=u,u=m.next,m.next=null,l.lastBaseUpdate=m,l.shared.pending=null}}while(!0);if(v===null&&(a=h),l.baseState=a,l.firstBaseUpdate=d,l.lastBaseUpdate=v,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Mt|=s,e.lanes=s,e.memoizedState=h}}function Mo(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Zl.transition;Zl.transition={};try{e(!1),t()}finally{D=n,Zl.transition=r}}function Va(){return Te().memoizedState}function Sf(e,t,n){var r=pt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Wa(e))Ba(t,n);else if(n=ja(e,t,n,r),n!==null){var l=de();De(n,e,r,l),Ha(n,t,r)}}function kf(e,t,n){var r=pt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Wa(e))Ba(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var s=t.lastRenderedState,u=i(s,n);if(l.hasEagerState=!0,l.eagerState=u,$e(u,s)){var a=t.interleaved;a===null?(l.next=l,Cs(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=ja(e,t,l,r),n!==null&&(l=de(),De(n,e,r,l),Ha(n,t,r))}}function Wa(e){var t=e.alternate;return e===Q||t!==null&&t===Q}function Ba(e,t){In=nl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ha(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,cs(e,n)}}var rl={readContext:ze,useCallback:se,useContext:se,useEffect:se,useImperativeHandle:se,useInsertionEffect:se,useLayoutEffect:se,useMemo:se,useReducer:se,useRef:se,useState:se,useDebugValue:se,useDeferredValue:se,useTransition:se,useMutableSource:se,useSyncExternalStore:se,useId:se,unstable_isNewReconciler:!1},jf={readContext:ze,useCallback:function(e,t){return Ae().memoizedState=[e,t===void 0?null:t],e},useContext:ze,useEffect:Oo,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Ir(4194308,4,Fa.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ir(4194308,4,e,t)},useInsertionEffect:function(e,t){return Ir(4,2,e,t)},useMemo:function(e,t){var n=Ae();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Ae();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Sf.bind(null,Q,e),[r.memoizedState,e]},useRef:function(e){var t=Ae();return e={current:e},t.memoizedState=e},useState:Io,useDebugValue:Ms,useDeferredValue:function(e){return Ae().memoizedState=e},useTransition:function(){var e=Io(!1),t=e[0];return e=wf.bind(null,e[1]),Ae().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=Q,l=Ae();if(W){if(n===void 0)throw Error(y(407));n=n()}else{if(n=t(),te===null)throw Error(y(349));Rt&30||Pa(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Oo(Ta.bind(null,r,i,e),[e]),r.flags|=2048,er(9,za.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Ae(),t=te.identifierPrefix;if(W){var n=Ye,r=Ke;n=(r&~(1<<32-Fe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=qn++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ve]=t,e[Xn]=r,ec(e,t,!1,!1),t.stateNode=e;e:{switch(s=hi(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;lfn&&(t.flags|=128,r=!0,jn(i,!1),t.lanes=4194304)}else{if(!r)if(e=tl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),jn(i,!0),i.tail===null&&i.tailMode==="hidden"&&!s.alternate&&!W)return oe(t),null}else 2*X()-i.renderingStartTime>fn&&n!==1073741824&&(t.flags|=128,r=!0,jn(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(n=i.last,n!==null?n.sibling=s:t.child=s,i.last=s)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=X(),t.sibling=null,n=B.current,U(B,r?n&1|2:n&1),t):(oe(t),null);case 22:case 23:return Us(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?xe&1073741824&&(oe(t),t.subtreeFlags&6&&(t.flags|=8192)):oe(t),null;case 24:return null;case 25:return null}throw Error(y(156,t.tag))}function Lf(e,t){switch(xs(t),t.tag){case 1:return ge(t.type)&&Gr(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return cn(),V(ve),V(ae),Ps(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Es(t),null;case 13:if(V(B),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(y(340));un()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return V(B),null;case 4:return cn(),null;case 10:return js(t.type._context),null;case 22:case 23:return Us(),null;case 24:return null;default:return null}}var jr=!1,ue=!1,Rf=typeof WeakSet=="function"?WeakSet:Set,N=null;function Zt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Y(e,t,r)}else n.current=null}function Vi(e,t,n){try{n()}catch(r){Y(e,t,r)}}var Ko=!1;function Mf(e,t){if(Ci=Hr,e=oa(),gs(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var s=0,u=-1,a=-1,d=0,v=0,h=e,m=null;t:for(;;){for(var w;h!==n||l!==0&&h.nodeType!==3||(u=s+l),h!==i||r!==0&&h.nodeType!==3||(a=s+r),h.nodeType===3&&(s+=h.nodeValue.length),(w=h.firstChild)!==null;)m=h,h=w;for(;;){if(h===e)break t;if(m===n&&++d===l&&(u=s),m===i&&++v===r&&(a=s),(w=h.nextSibling)!==null)break;h=m,m=h.parentNode}h=w}n=u===-1||a===-1?null:{start:u,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ni={focusedElem:e,selectionRange:n},Hr=!1,N=t;N!==null;)if(t=N,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,N=e;else for(;N!==null;){t=N;try{var k=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(k!==null){var S=k.memoizedProps,O=k.memoizedState,f=t.stateNode,c=f.getSnapshotBeforeUpdate(t.elementType===t.type?S:Re(t.type,S),O);f.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(y(163))}}catch(g){Y(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,N=e;break}N=t.return}return k=Ko,Ko=!1,k}function On(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&Vi(t,n,i)}l=l.next}while(l!==r)}}function xl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Wi(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function rc(e){var t=e.alternate;t!==null&&(e.alternate=null,rc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ve],delete t[Xn],delete t[Pi],delete t[mf],delete t[vf])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function lc(e){return e.tag===5||e.tag===3||e.tag===4}function Yo(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||lc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Bi(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Yr));else if(r!==4&&(e=e.child,e!==null))for(Bi(e,t,n),e=e.sibling;e!==null;)Bi(e,t,n),e=e.sibling}function Hi(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Hi(e,t,n),e=e.sibling;e!==null;)Hi(e,t,n),e=e.sibling}var re=null,Me=!1;function et(e,t,n){for(n=n.child;n!==null;)ic(e,t,n),n=n.sibling}function ic(e,t,n){if(We&&typeof We.onCommitFiberUnmount=="function")try{We.onCommitFiberUnmount(dl,n)}catch{}switch(n.tag){case 5:ue||Zt(n,t);case 6:var r=re,l=Me;re=null,et(e,t,n),re=r,Me=l,re!==null&&(Me?(e=re,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):re.removeChild(n.stateNode));break;case 18:re!==null&&(Me?(e=re,n=n.stateNode,e.nodeType===8?Yl(e.parentNode,n):e.nodeType===1&&Yl(e,n),Hn(e)):Yl(re,n.stateNode));break;case 4:r=re,l=Me,re=n.stateNode.containerInfo,Me=!0,et(e,t,n),re=r,Me=l;break;case 0:case 11:case 14:case 15:if(!ue&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,s=i.destroy;i=i.tag,s!==void 0&&(i&2||i&4)&&Vi(n,t,s),l=l.next}while(l!==r)}et(e,t,n);break;case 1:if(!ue&&(Zt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){Y(n,t,u)}et(e,t,n);break;case 21:et(e,t,n);break;case 22:n.mode&1?(ue=(r=ue)||n.memoizedState!==null,et(e,t,n),ue=r):et(e,t,n);break;default:et(e,t,n)}}function Go(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Rf),t.forEach(function(r){var l=Wf.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~i}if(r=l,r=X()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Of(r/1960))-r,10e?16:e,st===null)var r=!1;else{if(e=st,st=null,sl=0,F&6)throw Error(y(331));var l=F;for(F|=4,N=e.current;N!==null;){var i=N,s=i.child;if(N.flags&16){var u=i.deletions;if(u!==null){for(var a=0;aX()-Ds?Pt(e,0):Fs|=n),ye(e,t)}function pc(e,t){t===0&&(e.mode&1?(t=hr,hr<<=1,!(hr&130023424)&&(hr=4194304)):t=1);var n=de();e=Je(e,t),e!==null&&(rr(e,t,n),ye(e,n))}function Vf(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),pc(e,n)}function Wf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(y(314))}r!==null&&r.delete(t),pc(e,n)}var hc;hc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ve.current)me=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return me=!1,zf(e,t,n);me=!!(e.flags&131072)}else me=!1,W&&t.flags&1048576&&ya(t,Jr,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Or(e,t),e=t.pendingProps;var l=on(t,ae.current);rn(t,n),l=Ts(null,t,r,e,l,n);var i=Ls();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,ge(r)?(i=!0,Xr(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ns(t),l.updater=yl,t.stateNode=l,l._reactInternals=t,Ii(t,r,e,n),t=Di(null,t,r,!0,i,n)):(t.tag=0,W&&i&&ys(t),ce(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Or(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Hf(r),e=Re(r,e),l){case 0:t=Fi(null,t,r,e,n);break e;case 1:t=Bo(null,t,r,e,n);break e;case 11:t=Vo(null,t,r,e,n);break e;case 14:t=Wo(null,t,r,Re(r.type,e),n);break e}throw Error(y(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Fi(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Bo(e,t,r,l,n);case 3:e:{if(Ja(t),e===null)throw Error(y(387));r=t.pendingProps,i=t.memoizedState,l=i.element,Ca(e,t),el(t,r,null,n);var s=t.memoizedState;if(r=s.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=dn(Error(y(423)),t),t=Ho(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(y(424)),t),t=Ho(e,t,r,n,l);break e}else for(we=ct(t.stateNode.containerInfo.firstChild),Se=t,W=!0,Ie=null,n=ka(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(un(),r===l){t=qe(e,t,n);break e}ce(e,t,r,n)}t=t.child}return t;case 5:return Na(t),e===null&&Li(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,s=l.children,_i(r,l)?s=null:i!==null&&_i(r,i)&&(t.flags|=32),Za(e,t),ce(e,t,s,n),t.child;case 6:return e===null&&Li(t),null;case 13:return qa(e,t,n);case 4:return _s(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=an(t,null,r,n):ce(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Vo(e,t,r,l,n);case 7:return ce(e,t,t.pendingProps,n),t.child;case 8:return ce(e,t,t.pendingProps.children,n),t.child;case 12:return ce(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,s=l.value,U(qr,r._currentValue),r._currentValue=s,i!==null)if($e(i.value,s)){if(i.children===l.children&&!ve.current){t=qe(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var u=i.dependencies;if(u!==null){s=i.child;for(var a=u.firstContext;a!==null;){if(a.context===r){if(i.tag===1){a=Ge(-1,n&-n),a.tag=2;var d=i.updateQueue;if(d!==null){d=d.shared;var v=d.pending;v===null?a.next=a:(a.next=v.next,v.next=a),d.pending=a}}i.lanes|=n,a=i.alternate,a!==null&&(a.lanes|=n),Ri(i.return,n,t),u.lanes|=n;break}a=a.next}}else if(i.tag===10)s=i.type===t.type?null:i.child;else if(i.tag===18){if(s=i.return,s===null)throw Error(y(341));s.lanes|=n,u=s.alternate,u!==null&&(u.lanes|=n),Ri(s,n,t),s=i.sibling}else s=i.child;if(s!==null)s.return=i;else for(s=i;s!==null;){if(s===t){s=null;break}if(i=s.sibling,i!==null){i.return=s.return,s=i;break}s=s.return}i=s}ce(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,rn(t,n),l=ze(l),r=r(l),t.flags|=1,ce(e,t,r,n),t.child;case 14:return r=t.type,l=Re(r,t.pendingProps),l=Re(r.type,l),Wo(e,t,r,l,n);case 15:return Ga(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Or(e,t),t.tag=1,ge(r)?(e=!0,Xr(t)):e=!1,rn(t,n),Qa(t,r,l),Ii(t,r,l,n),Di(null,t,r,!0,e,n);case 19:return ba(e,t,n);case 22:return Xa(e,t,n)}throw Error(y(156,t.tag))};function mc(e,t){return Wu(e,t)}function Bf(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ee(e,t,n,r){return new Bf(e,t,n,r)}function Vs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Hf(e){if(typeof e=="function")return Vs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ss)return 11;if(e===os)return 14}return 2}function ht(e,t){var n=e.alternate;return n===null?(n=Ee(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function $r(e,t,n,r,l,i){var s=2;if(r=e,typeof e=="function")Vs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Vt:return zt(n.children,l,i,t);case is:s=8,l|=8;break;case li:return e=Ee(12,n,t,l|2),e.elementType=li,e.lanes=i,e;case ii:return e=Ee(13,n,t,l),e.elementType=ii,e.lanes=i,e;case si:return e=Ee(19,n,t,l),e.elementType=si,e.lanes=i,e;case Nu:return Sl(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Cu:s=9;break e;case ss:s=11;break e;case os:s=14;break e;case tt:s=16,r=null;break e}throw Error(y(130,e==null?e:typeof e,""))}return t=Ee(s,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function zt(e,t,n,r){return e=Ee(7,e,r,t),e.lanes=n,e}function Sl(e,t,n,r){return e=Ee(22,e,r,t),e.elementType=Nu,e.lanes=n,e.stateNode={isHidden:!1},e}function ti(e,t,n){return e=Ee(6,e,null,t),e.lanes=n,e}function ni(e,t,n){return t=Ee(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Qf(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fl(0),this.expirationTimes=Fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ws(e,t,n,r,l,i,s,u,a){return e=new Qf(e,t,n,u,a),t===1?(t=1,i===!0&&(t|=8)):t=0,i=Ee(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ns(i),e}function Kf(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(xc)}catch(e){console.error(e)}}xc(),xu.exports=je;var Jf=xu.exports,wc,nu=Jf;wc=nu.createRoot,nu.hydrateRoot;function Sc(e,t){if(Array.isArray(e))return e;for(const n of t)if(Array.isArray(e==null?void 0:e[n]))return e[n];return[]}function qf(e){return Sc(e,["items","sessions","chats"]).map((t,n)=>({id:String(t.chat_id??t.chatId??t.session_id??t.sessionId??t.automation_id??t.automationId??t.id??n),title:String(t.title??t.name??t.display_name??t.displayName??t.nickname??t.chat_id??t.id??"未命名会话"),preview:String(t.last_message??t.lastMessage??t.preview??"暂无最近消息"),unread:Number(t.unread_count??t.unreadCount??0),type:t.chat_type??t.chatType??"Private"}))}function bf(e){return Sc(e,["items","messages"]).map((t,n)=>{const r=String(t.direction??"").toLowerCase();return{id:String(t.message_id??t.messageId??t.id??n),sender:String(t.sender??t.sender_name??t.senderName??(r==="outgoing"?"我":"对方")),text:typeof t=="string"?t:String(t.content??t.text??t.message??""),at:t.occurred_at??t.occurredAt??t.created_at??t.createdAt??t.source_time??t.sourceTime??t.observed_at??t.observedAt}})}function ru(e,t=0){var r;const n=(e==null?void 0:e.coverage)||((r=e==null?void 0:e.metadata)==null?void 0:r.coverage)||(e==null?void 0:e.sync);return!n||typeof n!="object"?{state:t===0?"unknown":"complete",source:"legacy-result",observedCount:t,authorizedScopeCount:null,matchedScopeCount:null,errorCode:t===0?"CoverageUnavailable":null}:{state:String(n.state||"unknown").toLowerCase(),source:n.source||"unknown",observedCount:Number(n.observedCount??n.observed_count??t),authorizedScopeCount:n.authorizedScopeCount??n.authorized_scope_count??null,matchedScopeCount:n.matchedScopeCount??n.matched_scope_count??null,observedAt:n.observedAt??n.observed_at??n.lastSuccessAt??n.last_success_at??null,lastSuccessAt:n.lastSuccessAt??n.last_success_at??null,backlogCount:n.backlogCount??n.backlog_count??null,errorCode:n.errorCode??n.error_code??null,errorMessage:n.errorMessage??n.error_message??null}}function lu(e,t,n=r=>r.id){const r=[...t],l=new Set(r.map(n));for(const i of e){const s=n(i);l.has(s)||(r.push(i),l.add(s))}return r}function iu(e){return(e==null?void 0:e.state)==="complete"}const kc=[{id:"messages",label:"消息",description:"查找会话、阅读并回复",icon:"message"},{id:"broadcast",label:"群发",description:"创建受控群发任务",icon:"send"},{id:"contacts",label:"通讯录",description:"查找联系人和群聊",icon:"contacts"},{id:"tasks",label:"任务",description:"查看执行结果",icon:"tasks"}],jc={Online:"在线",Degraded:"需要注意",Offline:"已离线",Registered:"已注册",SessionLocked:"桌面已锁定",WechatNotRunning:"微信未运行",WechatNotLoggedIn:"微信未登录",Pending:"待处理",WaitingForClient:"等待 Client",Accepted:"已接收",Running:"执行中",Succeeded:"已完成",Failed:"失败",Cancelled:"已取消",Expired:"已过期",ResultUnconfirmed:"待核对"},ep=new Set(["Succeeded","Failed","Cancelled","Expired","ResultUnconfirmed"]);class qt extends Error{constructor(t,n,r="RequestFailed"){super(t),this.status=n,this.code=r}}async function Oe(e,{token:t,method:n="GET",body:r}={}){var u,a;const l={};t&&(l.Authorization=`Bearer ${t}`),r!==void 0&&(l["Content-Type"]="application/json");const i=await fetch(e,{method:n,headers:l,body:r===void 0?void 0:JSON.stringify(r)}),s=await i.json().catch(()=>({}));if(!i.ok)throw new qt(((u=s.error)==null?void 0:u.message)||`请求失败(${i.status})`,i.status,(a=s.error)==null?void 0:a.code);return s}const tp=e=>new Promise(t=>window.setTimeout(t,e)),np={ReportingDisabled:"Client 未启用 Reporting 白名单。请在托盘远程连接页启用并配置白名单。",ReportingConfigInvalid:"Client 的 Reporting 配置无效,请检查托盘远程连接页。",AccountNotAuthorized:"当前账号未加入 Client 的 Reporting 白名单。",ChatNotAuthorized:"当前会话不在 Client 的 Reporting 白名单中。",ChatIdentityUnconfirmed:"当前会话身份尚未确认,暂不能读取。",DataTypeNotAuthorized:"当前读取类型未被 Reporting 白名单授权。"};function rp(e){var n,r;const t=((n=e.result)==null?void 0:n.error_code)||e.status;return np[t]||((r=e.result)==null?void 0:r.message)||`读取任务${jc[e.status]||"未完成"}。`}async function lp(e,t){var i,s;const n=Date.now()+3e4;let r=250,l={status:"Pending"};for(;Date.now()t?`${e.slice(0,t)}…`:e:"—"}function op(e){return["Online","Succeeded","Accepted"].includes(e)?"positive":["Running","Pending","WaitingForClient","Degraded"].includes(e)?"warning":["Failed","Cancelled","Expired","ResultUnconfirmed","Offline","SessionLocked","WechatNotRunning","WechatNotLoggedIn"].includes(e)?"negative":"neutral"}function Cc(e){return jc[e]||e||"未知"}function al(e){return!!(e&&["Online","Degraded","Registered"].includes(e.status))}function Ks(e){var t,n;return e?e.active_account_id?e.active_account_id:((n=(t=e.accounts)==null?void 0:t.find(r=>r.active&&r.verified))==null?void 0:n.account_id)||"":""}function up(e){return extractItems(e,["items","contacts","sessions"]).map((t,n)=>({id:String(t.contact_id??t.contactId??t.chat_id??t.chatId??t.id??n),title:String(t.name??t.display_name??t.displayName??t.nickname??t.contact_id??t.id??"未命名联系人"),type:t.chat_type??t.chatType??(t.is_group?"Group":"Private"),detail:String(t.remark??t.alias??t.account_id??t.accountId??"")}))}function H({name:e,size:t=18}){const n={message:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v7a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 3v-3h0A2.5 2.5 0 0 1 4 12.5z"}),o.jsx("path",{d:"M8 8h8M8 11h5"})]}),send:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"m3 4 18 8-18 8 3.5-8z"}),o.jsx("path",{d:"M6.5 12H21"})]}),contacts:o.jsxs(o.Fragment,{children:[o.jsx("circle",{cx:"9",cy:"8",r:"3"}),o.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0M17 7v6M14 10h6"})]}),tasks:o.jsxs(o.Fragment,{children:[o.jsx("rect",{x:"4",y:"3",width:"16",height:"18",rx:"2"}),o.jsx("path",{d:"M8 8h8M8 12h8M8 16h5"})]}),settings:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"}),o.jsx("circle",{cx:"12",cy:"12",r:"4"})]}),refresh:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M20 11a8 8 0 0 0-14.5-4.5L4 8"}),o.jsx("path",{d:"M4 4v4h4M4 13a8 8 0 0 0 14.5 4.5L20 16"}),o.jsx("path",{d:"M20 20v-4h-4"})]}),chevron:o.jsx("path",{d:"m9 6 6 6-6 6"}),search:o.jsxs(o.Fragment,{children:[o.jsx("circle",{cx:"10.5",cy:"10.5",r:"6.5"}),o.jsx("path",{d:"m16 16 4.5 4.5"})]}),shield:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 3 19 6v5c0 4.2-2.8 7.8-7 9-4.2-1.2-7-4.8-7-9V6z"}),o.jsx("path",{d:"m9 12 2 2 4-4"})]}),alert:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 4 21 20H3z"}),o.jsx("path",{d:"M12 10v4M12 17v.2"})]}),close:o.jsx(o.Fragment,{children:o.jsx("path",{d:"m6 6 12 12M18 6 6 18"})}),logout:o.jsx(o.Fragment,{children:o.jsx("path",{d:"M10 4H5v16h5M14 8l4 4-4 4M8 12h10"})}),arrow:o.jsx(o.Fragment,{children:o.jsx("path",{d:"M4 12h15M13 6l6 6-6 6"})}),check:o.jsx("path",{d:"m5 12 4 4L19 6"})};return o.jsx("svg",{"aria-hidden":"true",width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round",children:n[e]||n.settings})}function pn({value:e}){return o.jsxs("span",{className:`status-badge ${op(e)}`,children:[o.jsx("i",{}),Cc(e)]})}function ap({onLogin:e}){const[t,n]=T.useState(""),[r,l]=T.useState(""),[i,s]=T.useState(""),[u,a]=T.useState(!1),d=async v=>{v.preventDefault(),s(""),a(!0);try{const h=await Oe("/v1/auth/login",{method:"POST",body:{username:t,password:r}});e(h.access_token,t)}catch(h){s(h.message)}finally{a(!1)}};return o.jsx("main",{className:"login-page",children:o.jsxs("section",{className:"login-panel","aria-labelledby":"login-title",children:[o.jsx("div",{className:"brand-mark",children:"W"}),o.jsx("p",{className:"eyebrow",children:"WXAGENT WORKSPACE"}),o.jsx("h1",{id:"login-title",children:"进入工作台"}),o.jsx("p",{className:"login-intro",children:"连接已授权的 Client,处理消息、通讯录和任务结果。"}),o.jsxs("form",{onSubmit:d,className:"login-form",children:[o.jsxs("label",{children:["用户名",o.jsx("input",{autoFocus:!0,value:t,onChange:v=>n(v.target.value),autoComplete:"username"})]}),o.jsxs("label",{children:["密码",o.jsx("input",{type:"password",value:r,onChange:v=>l(v.target.value),autoComplete:"current-password"})]}),i&&o.jsx("div",{className:"form-error",role:"alert",children:i}),o.jsx("button",{className:"primary-button full",disabled:u||!t||!r,children:u?"登录中…":"登录工作台"})]}),o.jsxs("p",{className:"login-footnote",children:[o.jsx("span",{className:"secure-dot"})," 会话只保存在当前浏览器标签页"]})]})})}function cp({view:e,setView:t,onLogout:n,username:r}){return o.jsxs("aside",{className:"sidebar",children:[o.jsxs("div",{className:"sidebar-brand",children:[o.jsx("div",{className:"brand-mark small",children:"W"}),o.jsxs("div",{children:[o.jsx("strong",{children:"WxAgent"}),o.jsx("span",{children:"用户工作台"})]})]}),o.jsx("p",{className:"nav-caption",children:"工作区"}),o.jsx("nav",{"aria-label":"主导航",children:kc.map(l=>o.jsxs("button",{className:e===l.id?"nav-item active":"nav-item",onClick:()=>t(l.id),children:[o.jsx(H,{name:l.icon,size:18}),o.jsx("span",{children:l.label})]},l.id))}),o.jsx("div",{className:"sidebar-spacer"}),o.jsxs("button",{className:e==="settings"?"nav-item active":"nav-item",onClick:()=>t("settings"),children:[o.jsx(H,{name:"settings",size:18}),o.jsx("span",{children:"设置与诊断"})]}),o.jsxs("div",{className:"connection-hint",children:[o.jsx("span",{className:"secure-dot"}),o.jsxs("div",{children:[o.jsx("strong",{children:"安全连接"}),o.jsx("small",{children:"控制面 API 已认证"})]})]}),o.jsxs("div",{className:"user-menu",children:[o.jsx("div",{className:"avatar",children:(r||"A").slice(0,1).toUpperCase()}),o.jsxs("div",{className:"user-name",children:[o.jsx("strong",{children:r||"管理员"}),o.jsx("small",{children:"已登录"})]}),o.jsx("button",{title:"退出登录","aria-label":"退出登录",onClick:n,children:o.jsx(H,{name:"logout",size:17})})]})]})}function dp({title:e,subtitle:t,nodes:n,selectedClientId:r,onClientChange:l,onRefresh:i,loading:s}){const u=n.find(a=>a.node_id===r);return o.jsxs("header",{className:"topbar",children:[o.jsxs("div",{className:"topbar-copy",children:[o.jsx("h1",{children:e}),o.jsx("p",{children:t})]}),o.jsxs("div",{className:"topbar-actions",children:[o.jsxs("label",{className:"client-switcher",children:[o.jsx("span",{children:"当前 Client"}),o.jsxs("select",{value:r,onChange:a=>l(a.target.value),disabled:!n.length,children:[!n.length&&o.jsx("option",{value:"",children:"暂无 Client"}),n.map(a=>o.jsxs("option",{value:a.node_id,children:[a.node_id," · ",Cc(a.status)]},a.node_id))]})]}),u&&o.jsx(pn,{value:u.status}),o.jsxs("button",{className:"refresh-button",onClick:i,disabled:s,"aria-label":"刷新数据",children:[o.jsx("span",{className:s?"spin":"",children:o.jsx(H,{name:"refresh",size:16})}),s?"同步中":"刷新"]})]})]})}function fp({nodes:e,selectedNode:t,onSettings:n}){if(!e.length)return o.jsxs("div",{className:"connection-banner negative",children:[o.jsx(H,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"还没有可用 Client"}),o.jsx("span",{children:"请启动已配置的 Desktop Agent;浏览器不会模拟连接状态。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"查看连接说明"})]});if(!al(t)){const r=e.some(al);return o.jsxs("div",{className:"connection-banner warning",children:[o.jsx(H,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"当前 Client 不可用"}),o.jsx("span",{children:r?"其它 Client 不受影响,请从顶部切换。":"所有 Client 当前都不可用,请先恢复连接。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"设置与诊断"})]})}return Ks(t)?null:o.jsxs("div",{className:"connection-banner warning",children:[o.jsx(H,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"当前 Client 尚未确认微信账号"}),o.jsx("span",{children:"只读数据需要已验证的活动账号,先完成账号绑定后再操作。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"查看账号"})]})}function Xi({title:e="没有选中的 Client",text:t="请先连接并选择一个可用 Client。",onSettings:n}){return o.jsxs("div",{className:"context-empty",children:[o.jsx("div",{className:"empty-icon",children:o.jsx(H,{name:"shield",size:28})}),o.jsx("h3",{children:e}),o.jsx("p",{children:t}),n&&o.jsx("button",{className:"secondary-button",onClick:n,children:"打开设置与诊断"})]})}function Zi({text:e="正在读取…"}){return o.jsxs("div",{className:"loading-state",children:[o.jsx("span",{className:"loader"}),e]})}function Ot({text:e,action:t}){return o.jsxs("div",{className:"empty-state",children:[o.jsx("div",{className:"empty-icon",children:o.jsx(H,{name:"search",size:26})}),o.jsx("p",{children:e}),t]})}function Ji({error:e,onRetry:t}){return o.jsxs("div",{className:"inline-error",role:"alert",children:[o.jsx(H,{name:"alert",size:17}),o.jsx("span",{children:e}),t&&o.jsx("button",{className:"text-button",onClick:t,children:"重试"})]})}function _r({text:e,onRetry:t}){return o.jsxs("div",{className:"read-notice",role:"status",children:[o.jsx(H,{name:"alert",size:16}),o.jsx("span",{children:e}),t&&o.jsx("button",{className:"text-button",onClick:t,children:"重试"})]})}function ou(e,t){if(!t)return"";const n=t.lastSuccessAt?`最后同步 ${gt(t.lastSuccessAt)}`:"尚未成功同步",r=t.backlogCount==null?"积压未知":`积压 ${t.backlogCount}`,l=`${n} · ${r}`;return t.state==="complete"?t.source==="platform-cache"?`${e}来自平台副本,${l}。`:"":t.state==="partial"?`${e}同步不完整,已保留已有数据;${l}。`:`${e}同步状态未知,未用本次结果清除已有数据;${l}${t.errorMessage?` · ${t.errorMessage}`:""}。`}function pp({token:e,client:t,onSettings:n}){const r=(t==null?void 0:t.node_id)||"",l=Ks(t),[i,s]=T.useState([]),[u,a]=T.useState({loading:!1,error:"",notice:""}),[d,v]=T.useState(""),[h,m]=T.useState(null),[w,k]=T.useState([]),[S,O]=T.useState({loading:!1,error:"",notice:""}),f=T.useRef(0),c=T.useRef(0),p=T.useRef(0),g=T.useRef(0),j=T.useCallback(async()=>{if(!l||!r)return;const x=++g.current;try{await Oe($n(l,"refresh"),{token:e,method:"POST"})}catch(z){x===g.current&&a(P=>({...P,notice:P.notice||`后台同步未受理:${z.message}`}))}},[l,r,e]),C=T.useCallback(async()=>{if(!r||!l)return;const x=++c.current,z=f.current;a(P=>({...P,loading:!0,error:""}));try{const P=await su({storedPath:$n(l,"conversations","?limit=100"),legacyPath:"/v1/reads/sessions",legacyBody:{node_id:r,account_id:l,limit:100},token:e});if(x!==c.current||z!==f.current)return;const $=qf(P);P.sync&&P.sync.state!=="complete"&&j();const M=ru(P,$.length);s(ne=>iu(M)?$:lu(ne,$)),a({loading:!1,error:"",notice:ou("会话",M)})}catch(P){x===c.current&&z===f.current&&a({loading:!1,error:P.message,notice:""})}},[l,r,j,e]);if(T.useEffect(()=>{f.current+=1,c.current+=1,p.current+=1,m(null),s([]),k([]),a({loading:!1,error:"",notice:""}),O({loading:!1,error:"",notice:""}),!(!r||!l)&&C()},[r,l,C]),T.useEffect(()=>{if(!h||h.clientId!==r||h.accountId!==l||!r||!l)return;const x=++p.current,z=f.current;O(P=>({...P,loading:!0,error:""})),su({storedPath:$n(l,"messages",`?chat_id=${encodeURIComponent(h.id)}&limit=100`),legacyPath:"/v1/reads/messages",legacyBody:{node_id:r,account_id:l,chat_id:h.id,limit:100,include_content:!0},token:e}).then(P=>{if(x!==p.current||z!==f.current)return;const $=bf(P),M=ru(P,$.length);k(ne=>iu(M)?$:lu(ne,$)),O({loading:!1,error:"",notice:ou("消息",M)})}).catch(P=>{x===p.current&&z===f.current&&O({loading:!1,error:P.message,notice:""})})},[l,r,h,e]),!t)return o.jsx(Xi,{onSettings:n});const _=i.filter(x=>!d||`${x.title} ${x.id}`.toLowerCase().includes(d.toLowerCase()));return o.jsxs("div",{className:"message-workspace",children:[o.jsxs("section",{className:"session-panel",children:[o.jsxs("div",{className:"section-heading compact",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"会话"}),o.jsx("p",{children:i.length?`${i.length} 个会话`:"当前 Client 的会话"})]}),o.jsx("button",{className:"icon-button",onClick:C,disabled:u.loading,"aria-label":"刷新会话",children:o.jsx(H,{name:"refresh",size:16})})]}),o.jsxs("label",{className:"search-box",children:[o.jsx(H,{name:"search",size:16}),o.jsx("input",{value:d,onChange:x=>v(x.target.value),placeholder:"搜索会话"})]}),u.loading&&i.length===0?o.jsx(Zi,{text:"正在读取会话…"}):u.error&&i.length===0?o.jsx(Ji,{error:u.error,onRetry:C}):o.jsxs(o.Fragment,{children:[u.error&&o.jsx(_r,{text:`读取会话失败,继续显示上次数据:${u.error}`,onRetry:C}),!u.error&&u.notice&&o.jsx(_r,{text:u.notice,onRetry:C}),u.loading&&i.length>0&&o.jsx("div",{className:"refresh-hint",children:"正在刷新,会话列表保持可用…"}),_.length===0?o.jsx(Ot,{text:"暂无会话,或当前账号还没有可见数据。"}):o.jsx("div",{className:"session-list",children:_.map(x=>o.jsxs("button",{className:(h==null?void 0:h.id)===x.id?"session-row selected":"session-row",onClick:()=>m({...x,clientId:r,accountId:l}),children:[o.jsx("span",{className:"session-avatar",children:x.title.slice(0,1).toUpperCase()}),o.jsxs("span",{className:"session-copy",children:[o.jsx("strong",{children:x.title}),o.jsx("small",{children:x.preview})]}),x.unread>0&&o.jsx("b",{children:x.unread})]},x.id))})]})]}),o.jsx("section",{className:"conversation-panel",children:h?o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"conversation-head",children:[o.jsxs("div",{children:[o.jsxs("p",{className:"eyebrow",children:["当前 Client · ",r]}),o.jsx("h2",{children:h.title}),o.jsxs("span",{children:[h.type==="Group"?"群聊":"私聊"," · ",_l(h.id,28)]})]}),o.jsx(pn,{value:t.status})]}),S.loading&&w.length===0?o.jsx(Zi,{text:"正在读取消息…"}):S.error&&w.length===0?o.jsx(Ji,{error:S.error,onRetry:()=>m({...h})}):o.jsxs(o.Fragment,{children:[S.error&&o.jsx(_r,{text:`读取消息失败,继续显示上次数据:${S.error}`,onRetry:()=>m({...h})}),!S.error&&S.notice&&o.jsx(_r,{text:S.notice,onRetry:()=>m({...h})}),S.loading&&w.length>0&&o.jsx("div",{className:"refresh-hint",children:"正在刷新,消息历史保持可用…"}),w.length===0?o.jsx(Ot,{text:"这个会话暂时没有可显示的消息。"}):o.jsx("div",{className:"message-list",children:w.map(x=>o.jsxs("article",{className:"message-row",children:[o.jsx("div",{className:"message-avatar",children:x.sender.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsxs("div",{className:"message-meta",children:[o.jsx("strong",{children:x.sender}),o.jsx("span",{children:gt(x.at)})]}),o.jsx("p",{children:x.text||"(无正文)"})]})]},x.id))})]}),o.jsxs("div",{className:"composer",children:[o.jsx("textarea",{disabled:!0,rows:2,placeholder:"发送消息暂未开放,需完成 Windows 真机验收"}),o.jsx("button",{className:"primary-button",disabled:!0,title:"写操作尚未通过真机验收",children:"发送"})]})]}):o.jsx(Xi,{title:"选择一个会话",text:"从左侧选择会话后,读取该 Client 的消息。"})})]})}function hp({client:e,onSettings:t}){return o.jsxs("section",{className:"workspace-panel gated-panel",children:[o.jsx("div",{className:"gated-mark",children:o.jsx(H,{name:"shield",size:24})}),o.jsxs("div",{children:[o.jsx("p",{className:"eyebrow",children:"CONTROLLED ACTION"}),o.jsx("h2",{children:"受控群发验证"}),o.jsx("p",{children:"单 Client 的 Client 端已提供 broadcast-text:冻结获准对象名单,逐项串行发送,返回每个对象的结果,并支持停止和幂等重放。"}),o.jsxs("div",{className:"step-list",children:[o.jsxs("span",{children:[o.jsx("b",{children:"1"}),"冻结对象"]}),o.jsxs("span",{children:[o.jsx("b",{children:"2"}),"预览确认"]}),o.jsxs("span",{children:[o.jsx("b",{children:"3"}),"逐项发送"]}),o.jsxs("span",{children:[o.jsx("b",{children:"4"}),"查看结果"]})]}),o.jsx("button",{className:"primary-button",disabled:!0,children:e?"Web 提交仍需单独验收":"请先连接 Client"}),o.jsxs("button",{className:"text-button",onClick:t,children:["查看 Client 验证状态 ",o.jsx(H,{name:"arrow",size:14})]})]})]})}function mp({token:e,client:t,onSettings:n}){const r=(t==null?void 0:t.node_id)||"",l=Ks(t),[i,s]=T.useState(!1),[u,a]=T.useState(""),[d,v]=T.useState([]),[h,m]=T.useState(null),[w,k]=T.useState(!1),[S,O]=T.useState(!1),[f,c]=T.useState(""),[p,g]=T.useState({loading:!1,error:""}),j=T.useRef(0),C=T.useCallback(async(z=!1)=>{if(!r||!l)return;const P=++j.current;g({loading:!0,error:""});try{const $=new URLSearchParams({limit:"200",offset:String(z?d.length:0),groups_only:String(i)});u.trim()&&$.set("contains",u.trim());const M=await Oe($n(l,"contacts",`?${$.toString()}`),{token:e});if(P!==j.current)return;const ne=up(M);v($t=>z?[...$t,...ne]:ne),k(!!M.has_more),m(M.sync||null),g({loading:!1,error:""})}catch($){P===j.current&&g({loading:!1,error:$.message})}},[l,r,d.length,i,u,e]),_=async()=>{if(!(!r||!l||S)){O(!0),c("");try{await Oe($n(l,"refresh"),{token:e,method:"POST",body:{stream_key:"contacts"}}),c("通讯录同步任务已提交;完成后点击“刷新缓存”查看最新数据。")}catch(z){g(P=>({...P,error:z.message}))}finally{O(!1)}}};if(T.useEffect(()=>{v([]),k(!1),m(null),c(""),r&&l&&C()},[r,l,i]),!t)return o.jsx(Xi,{onSettings:n});const x=d.filter(z=>!u||`${z.title} ${z.id} ${z.detail}`.toLowerCase().includes(u.toLowerCase()));return o.jsxs("section",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"通讯录"}),o.jsxs("p",{children:["平台缓存 · ",(h==null?void 0:h.state)||"尚未同步"," · 最近同步 ",gt(h==null?void 0:h.last_success_at)]})]}),o.jsxs("div",{className:"heading-actions",children:[o.jsxs("label",{className:"check-filter",children:[o.jsx("input",{type:"checkbox",checked:i,onChange:z=>s(z.target.checked)}),"只看群聊"]}),o.jsx("button",{className:"secondary-button",onClick:_,disabled:S||p.loading,children:S?"提交中…":"同步通讯录"}),o.jsx("button",{className:"secondary-button",disabled:!0,title:"添加好友尚未通过真机验收",children:"添加好友"})]})]}),o.jsxs("div",{className:"toolbar",children:[o.jsxs("label",{className:"search-box wide",children:[o.jsx(H,{name:"search",size:16}),o.jsx("input",{value:u,onChange:z=>a(z.target.value),onKeyDown:z=>z.key==="Enter"&&C(),placeholder:"搜索联系人或群聊"})]}),o.jsx("button",{className:"secondary-button",onClick:C,disabled:p.loading,children:p.loading?"读取中…":"刷新缓存"}),o.jsx("button",{className:"secondary-button",onClick:()=>C(!0),disabled:p.loading||!w,children:"加载更多"}),f&&o.jsx("span",{className:"read-only-note",role:"status",children:f})]}),p.loading?o.jsx(Zi,{text:"正在读取通讯录…"}):p.error?o.jsx(Ji,{error:p.error,onRetry:C}):x.length===0?o.jsx(Ot,{text:(h==null?void 0:h.state)==="unknown"?"平台尚未缓存通讯录,请点击“同步通讯录”。":"暂无可见联系人或群聊。"}):o.jsx("div",{className:"contact-list",children:x.map(z=>o.jsxs("div",{className:"contact-row",children:[o.jsx("span",{className:"contact-avatar",children:z.title.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsx("strong",{children:z.title}),o.jsxs("small",{children:[z.type==="Group"?"群聊":"联系人"," · ",_l(z.id,28),z.detail?` · ${z.detail}`:""]})]}),o.jsx("button",{className:"text-button",disabled:!0,title:"写操作尚未通过真机验收",children:"添加好友"})]},z.id))})]})}function vp({tasks:e,selectedClientId:t}){const[n,r]=T.useState("all"),l=e.filter(i=>i.node_id===t&&(n==="all"||i.status===n));return o.jsxs("section",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"任务结果"}),o.jsx("p",{children:"当前 Client 的任务状态和安全边界。"})]}),o.jsxs("div",{className:"heading-actions",children:[o.jsxs("span",{className:"read-only-note",children:[o.jsx(H,{name:"shield",size:14}),"只读视图"]}),o.jsxs("select",{value:n,onChange:i=>r(i.target.value),children:[o.jsx("option",{value:"all",children:"全部状态"}),o.jsx("option",{value:"Pending",children:"待处理"}),o.jsx("option",{value:"WaitingForClient",children:"等待 Client"}),o.jsx("option",{value:"Running",children:"执行中"}),o.jsx("option",{value:"Succeeded",children:"已完成"}),o.jsx("option",{value:"Failed",children:"失败"}),o.jsx("option",{value:"ResultUnconfirmed",children:"待核对"})]})]})]}),l.length===0?o.jsx(Ot,{text:"当前 Client 暂无任务记录。"}):o.jsx("div",{className:"task-list",children:l.map(i=>{var s,u;return o.jsxs("article",{className:"task-row",children:[o.jsxs("div",{className:"task-main",children:[o.jsxs("div",{className:"task-title",children:[o.jsx("strong",{children:i.kind}),o.jsx(pn,{value:i.status})]}),o.jsxs("p",{children:[i.account_id," · ",_l(i.task_id,22)]}),o.jsx("small",{children:i.status==="WaitingForClient"?"Client 离线后等待显式恢复;不会自动重放写操作。":((s=i.result)==null?void 0:s.message)||((u=i.result)==null?void 0:u.error_code)||`更新于 ${gt(i.updated_at)}`})]}),o.jsxs("div",{className:"task-meta",children:[o.jsxs("span",{children:["第 ",i.lease_generation||0," 代租约"]}),o.jsx("time",{children:gt(i.updated_at)})]})]},i.task_id)})})]})}function gp({nodes:e}){return o.jsx("div",{className:"diagnostic-list",children:e.length===0?o.jsx(Ot,{text:"暂无注册 Client。"}):e.map(t=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{className:"diagnostic-title",children:[o.jsx("span",{className:"node-avatar",children:t.node_id.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsx("strong",{children:t.node_id}),o.jsxs("small",{children:[t.active_account_id||"尚未确认活动账号"," · 最近心跳 ",gt(t.last_heartbeat_at)]})]})]}),o.jsx(pn,{value:t.status})]},t.node_id))})}function yp({events:e,selectedClientId:t}){const n=e.filter(r=>!t||r.node_id===t);return o.jsx("div",{className:"diagnostic-list",children:n.length===0?o.jsx(Ot,{text:"当前 Client 暂无事件。"}):n.slice(0,30).map(r=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{children:[o.jsx("strong",{children:_l(r.chat_id,24)}),o.jsxs("small",{children:[r.node_id," · ",r.chat_type==="Group"?"群聊":"私聊"," · ",gt(r.received_at)]})]}),o.jsx("span",{className:"event-preview",children:r.content||"无正文"})]},r.event_id))})}function xp({audit:e}){return o.jsx("div",{className:"diagnostic-list",children:e.length===0?o.jsx(Ot,{text:"暂无审计记录。"}):e.slice(0,30).map(t=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{children:[o.jsx("strong",{children:t.action}),o.jsxs("small",{children:[t.principal," · ",t.resource]})]}),o.jsx("span",{children:gt(t.at)})]},t.id))})}function wp({nodes:e,events:t,audit:n,selectedClientId:r,onSelectClient:l}){const[i,s]=T.useState("connection");return o.jsxs("section",{className:"settings-layout",children:[o.jsxs("aside",{className:"settings-nav",children:[o.jsxs("button",{className:i==="connection"?"settings-tab active":"settings-tab",onClick:()=>s("connection"),children:[o.jsx("strong",{children:"连接与账号"}),o.jsx("span",{children:"选择 Client、确认状态"})]}),o.jsxs("button",{className:i==="diagnostics"?"settings-tab active":"settings-tab",onClick:()=>s("diagnostics"),children:[o.jsx("strong",{children:"高级诊断"}),o.jsx("span",{children:"节点、事件、AI、审计"})]})]}),o.jsx("div",{className:"settings-content",children:i==="connection"?o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"连接与账号"}),o.jsx("p",{children:"每个 Client 独立连接,单个 Client 离线不会阻断其它 Client。"})]}),o.jsx(pn,{value:e.some(al)?"Online":"Offline"})]}),o.jsx(gp,{nodes:e})]}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"安全边界"}),o.jsx("p",{children:"当前版本只开放真实后端已支持且可验证的只读能力。"})]})}),o.jsxs("ul",{className:"boundary-list",children:[o.jsxs("li",{children:[o.jsx(H,{name:"check",size:15}),"消息、通讯录、任务结果按选定 Client 隔离读取"]}),o.jsxs("li",{children:[o.jsx(H,{name:"check",size:15}),"Client 掉线只影响当前上下文,任务不会自动改投"]}),o.jsxs("li",{children:[o.jsx(H,{name:"alert",size:15}),"发送、群发、添加好友等待 Windows 真机验收"]})]})]})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"Client 诊断"}),o.jsx("p",{children:"技术状态只在这里展示,不干扰普通操作。"})]})}),o.jsx("div",{className:"client-pills",children:e.map(u=>o.jsxs("button",{className:u.node_id===r?"client-pill active":"client-pill",onClick:()=>l(u.node_id),children:[o.jsx("span",{children:u.node_id}),o.jsx(pn,{value:u.status})]},u.node_id))})]}),o.jsx("div",{className:"workspace-panel",children:o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"AI 能力"}),o.jsx("p",{children:"AI 入口保留在高级诊断中;当前不自动调用,也不影响消息、通讯录和任务操作。"})]}),o.jsxs("span",{className:"read-only-note",children:[o.jsx(H,{name:"shield",size:14}),"按能力矩阵开放"]})]})}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"白名单事件"}),o.jsx("p",{children:"当前选中 Client 的已授权事件。"})]})}),o.jsx(yp,{events:t,selectedClientId:r})]}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"操作审计"}),o.jsx("p",{children:"用于定位连接、任务和权限问题。"})]})}),o.jsx(xp,{audit:n})]})]})})]})}function Sp(){const[e,t]=T.useState(()=>sessionStorage.getItem("wxagent-token")||""),[n,r]=T.useState(()=>sessionStorage.getItem("wxagent-user")||""),[l,i]=T.useState("messages"),[s,u]=T.useState([]),[a,d]=T.useState(()=>sessionStorage.getItem("wxagent-client")||""),[v,h]=T.useState([]),[m,w]=T.useState([]),[k,S]=T.useState([]),[O,f]=T.useState(!1),[c,p]=T.useState(""),g=T.useCallback(()=>{sessionStorage.removeItem("wxagent-token"),sessionStorage.removeItem("wxagent-user"),sessionStorage.removeItem("wxagent-client"),t(""),r("")},[]),j=T.useCallback(async()=>{if(e){f(!0),p("");try{const[M,ne,$t,El]=await Promise.all([Oe("/v1/nodes",{token:e}),Oe("/v1/tasks?limit=200",{token:e}),Oe("/v1/events?limit=200",{token:e}),Oe("/v1/audit?limit=200",{token:e})]);u(M.nodes||[]),h(ne.tasks||[]),w($t.events||[]),S(El.audit||[])}catch(M){M.status===401?g():p(M.message)}finally{f(!1)}}},[g,e]);T.useEffect(()=>{if(j(),!e)return;const M=window.setInterval(j,15e3);return()=>window.clearInterval(M)},[j,e]),T.useEffect(()=>{if(!s.length){d("");return}if(!s.some(M=>M.node_id===a)){const M=s.find(al)||s[0];d(M.node_id),sessionStorage.setItem("wxagent-client",M.node_id)}},[s,a]);const C=M=>{d(M),sessionStorage.setItem("wxagent-client",M)},_=(M,ne)=>{sessionStorage.setItem("wxagent-token",M),sessionStorage.setItem("wxagent-user",ne),t(M),r(ne)},x=T.useMemo(()=>s.find(M=>M.node_id===a),[s,a]),z=kc.find(M=>M.id===l),P=(z==null?void 0:z.label)||"设置与诊断",$=(z==null?void 0:z.description)||"连接、账号和高级诊断信息";return e?o.jsxs("div",{className:"app-shell",children:[o.jsx(cp,{view:l,setView:i,onLogout:g,username:n}),o.jsxs("main",{className:"main-area",children:[o.jsx(dp,{title:P,subtitle:$,nodes:s,selectedClientId:a,onClientChange:C,onRefresh:j,loading:O}),c&&o.jsxs("div",{className:"global-error",role:"alert",children:[o.jsx(H,{name:"alert",size:16}),o.jsx("span",{children:c}),o.jsx("button",{onClick:()=>p(""),"aria-label":"关闭错误",children:o.jsx(H,{name:"close",size:16})})]}),o.jsx(fp,{nodes:s,selectedNode:x,onSettings:()=>i("settings")}),o.jsxs("div",{className:"page-content",children:[l==="messages"&&o.jsx(pp,{token:e,client:x,onSettings:()=>i("settings")}),l==="broadcast"&&o.jsx(hp,{client:x,onSettings:()=>i("settings")}),l==="contacts"&&o.jsx(mp,{token:e,client:x,onSettings:()=>i("settings")}),l==="tasks"&&o.jsx(vp,{tasks:v,selectedClientId:a}),l==="settings"&&o.jsx(wp,{nodes:s,events:m,audit:k,selectedClientId:a,onSelectClient:C})]}),o.jsxs("footer",{className:"footer",children:[o.jsx("span",{children:"WxAgent 工作台 · Client 独立隔离"}),o.jsx("span",{children:"写操作需通过真实能力与真机验收"})]})]})]}):o.jsx(ap,{onLogin:_})}wc(document.getElementById("root")).render(o.jsx(Sp,{})); diff --git a/control-plane/web/dist/assets/index-PxzqqEA7.js b/control-plane/web/dist/assets/index-PxzqqEA7.js deleted file mode 100644 index 0d21ab6..0000000 --- a/control-plane/web/dist/assets/index-PxzqqEA7.js +++ /dev/null @@ -1,40 +0,0 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();var uu={exports:{}},al={},au={exports:{}},M={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var er=Symbol.for("react.element"),_c=Symbol.for("react.portal"),Ec=Symbol.for("react.fragment"),zc=Symbol.for("react.strict_mode"),Pc=Symbol.for("react.profiler"),Tc=Symbol.for("react.provider"),Lc=Symbol.for("react.context"),Rc=Symbol.for("react.forward_ref"),Mc=Symbol.for("react.suspense"),Ic=Symbol.for("react.memo"),Oc=Symbol.for("react.lazy"),Ys=Symbol.iterator;function Fc(e){return e===null||typeof e!="object"?null:(e=Ys&&e[Ys]||e["@@iterator"],typeof e=="function"?e:null)}var cu={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},du=Object.assign,fu={};function pn(e,t,n){this.props=e,this.context=t,this.refs=fu,this.updater=n||cu}pn.prototype.isReactComponent={};pn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};pn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function pu(){}pu.prototype=pn.prototype;function qi(e,t,n){this.props=e,this.context=t,this.refs=fu,this.updater=n||cu}var bi=qi.prototype=new pu;bi.constructor=qi;du(bi,pn.prototype);bi.isPureReactComponent=!0;var Gs=Array.isArray,hu=Object.prototype.hasOwnProperty,es={current:null},mu={key:!0,ref:!0,__self:!0,__source:!0};function vu(e,t,n){var r,l={},i=null,s=null;if(t!=null)for(r in t.ref!==void 0&&(s=t.ref),t.key!==void 0&&(i=""+t.key),t)hu.call(t,r)&&!mu.hasOwnProperty(r)&&(l[r]=t[r]);var u=arguments.length-2;if(u===1)l.children=n;else if(1>>1,b=_[G];if(0>>1;Gl(Pl,R))Stl(or,Pl)?(_[G]=or,_[St]=R,G=St):(_[G]=Pl,_[wt]=R,G=wt);else if(Stl(or,R))_[G]=or,_[St]=R,G=St;else break e}}return L}function l(_,L){var R=_.sortIndex-L.sortIndex;return R!==0?R:_.id-L.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var s=Date,u=s.now();e.unstable_now=function(){return s.now()-u}}var a=[],f=[],v=1,h=null,m=3,x=!1,k=!1,S=!1,P=typeof setTimeout=="function"?setTimeout:null,d=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function p(_){for(var L=n(f);L!==null;){if(L.callback===null)r(f);else if(L.startTime<=_)r(f),L.sortIndex=L.expirationTime,t(a,L);else break;L=n(f)}}function g(_){if(S=!1,p(_),!k)if(n(a)!==null)k=!0,El(j);else{var L=n(f);L!==null&&zl(g,L.startTime-_)}}function j(_,L){k=!1,S&&(S=!1,d(w),w=-1),x=!0;var R=m;try{for(p(L),h=n(a);h!==null&&(!(h.expirationTime>L)||_&&!Y());){var G=h.callback;if(typeof G=="function"){h.callback=null,m=h.priorityLevel;var b=G(h.expirationTime<=L);L=e.unstable_now(),typeof b=="function"?h.callback=b:h===n(a)&&r(a),p(L)}else r(a);h=n(a)}if(h!==null)var sr=!0;else{var wt=n(f);wt!==null&&zl(g,wt.startTime-L),sr=!1}return sr}finally{h=null,m=R,x=!1}}var C=!1,E=null,w=-1,I=5,z=-1;function Y(){return!(e.unstable_now()-z_||125<_?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):I=0<_?Math.floor(1e3/_):5},e.unstable_getCurrentPriorityLevel=function(){return m},e.unstable_getFirstCallbackNode=function(){return n(a)},e.unstable_next=function(_){switch(m){case 1:case 2:case 3:var L=3;break;default:L=m}var R=m;m=L;try{return _()}finally{m=R}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(_,L){switch(_){case 1:case 2:case 3:case 4:case 5:break;default:_=3}var R=m;m=_;try{return L()}finally{m=R}},e.unstable_scheduleCallback=function(_,L,R){var G=e.unstable_now();switch(typeof R=="object"&&R!==null?(R=R.delay,R=typeof R=="number"&&0G?(_.sortIndex=R,t(f,_),n(a)===null&&_===n(f)&&(S?(d(w),w=-1):S=!0,zl(g,R-G))):(_.sortIndex=b,t(a,_),k||x||(k=!0,El(j))),_},e.unstable_shouldYield=Y,e.unstable_wrapCallback=function(_){var L=m;return function(){var R=m;m=L;try{return _.apply(this,arguments)}finally{m=R}}}})(Su);wu.exports=Su;var Yc=wu.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Gc=T,ke=Yc;function y(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ri=Object.prototype.hasOwnProperty,Xc=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Zs={},Js={};function Zc(e){return ri.call(Js,e)?!0:ri.call(Zs,e)?!1:Xc.test(e)?Js[e]=!0:(Zs[e]=!0,!1)}function Jc(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function qc(e,t,n,r){if(t===null||typeof t>"u"||Jc(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function fe(e,t,n,r,l,i,s){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=s}var le={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){le[e]=new fe(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];le[t]=new fe(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){le[e]=new fe(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){le[e]=new fe(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){le[e]=new fe(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){le[e]=new fe(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){le[e]=new fe(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){le[e]=new fe(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){le[e]=new fe(e,5,!1,e.toLowerCase(),null,!1,!1)});var ns=/[\-:]([a-z])/g;function rs(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ns,rs);le[t]=new fe(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ns,rs);le[t]=new fe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ns,rs);le[t]=new fe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){le[e]=new fe(e,1,!1,e.toLowerCase(),null,!1,!1)});le.xlinkHref=new fe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){le[e]=new fe(e,1,!1,e.toLowerCase(),null,!0,!0)});function ls(e,t,n,r){var l=le.hasOwnProperty(t)?le[t]:null;(l!==null?l.type!==0:r||!(2u||l[s]!==i[u]){var a=` -`+l[s].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=s&&0<=u);break}}}finally{Rl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Cn(e):""}function bc(e){switch(e.tag){case 5:return Cn(e.type);case 16:return Cn("Lazy");case 13:return Cn("Suspense");case 19:return Cn("SuspenseList");case 0:case 2:case 15:return e=Ml(e.type,!1),e;case 11:return e=Ml(e.type.render,!1),e;case 1:return e=Ml(e.type,!0),e;default:return""}}function oi(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ut:return"Fragment";case At:return"Portal";case li:return"Profiler";case is:return"StrictMode";case ii:return"Suspense";case si:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Cu:return(e.displayName||"Context")+".Consumer";case ju:return(e._context.displayName||"Context")+".Provider";case ss:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case os:return t=e.displayName||null,t!==null?t:oi(e.type)||"Memo";case tt:t=e._payload,e=e._init;try{return oi(e(t))}catch{}}return null}function ed(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return oi(t);case 8:return t===is?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function mt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function _u(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function td(e){var t=_u(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(s){r=""+s,i.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function cr(e){e._valueTracker||(e._valueTracker=td(e))}function Eu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=_u(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function $r(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function ui(e,t){var n=t.checked;return Q({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function bs(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=mt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function zu(e,t){t=t.checked,t!=null&&ls(e,"checked",t,!1)}function ai(e,t){zu(e,t);var n=mt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ci(e,t.type,n):t.hasOwnProperty("defaultValue")&&ci(e,t.type,mt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function eo(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ci(e,t,n){(t!=="number"||$r(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Nn=Array.isArray;function qt(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=dr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function $n(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var zn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},nd=["Webkit","ms","Moz","O"];Object.keys(zn).forEach(function(e){nd.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),zn[t]=zn[e]})});function Ru(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||zn.hasOwnProperty(e)&&zn[e]?(""+t).trim():t+"px"}function Mu(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Ru(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var rd=Q({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function pi(e,t){if(t){if(rd[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(y(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(y(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(y(61))}if(t.style!=null&&typeof t.style!="object")throw Error(y(62))}}function hi(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var mi=null;function us(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var vi=null,bt=null,en=null;function ro(e){if(e=rr(e)){if(typeof vi!="function")throw Error(y(280));var t=e.stateNode;t&&(t=hl(t),vi(e.stateNode,e.type,t))}}function Iu(e){bt?en?en.push(e):en=[e]:bt=e}function Ou(){if(bt){var e=bt,t=en;if(en=bt=null,ro(e),t)for(e=0;e>>=0,e===0?32:31-(hd(e)/md|0)|0}var fr=64,pr=4194304;function _n(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Wr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,s=n&268435455;if(s!==0){var u=s&~l;u!==0?r=_n(u):(i&=s,i!==0&&(r=_n(i)))}else s=n&~l,s!==0?r=_n(s):i!==0&&(r=_n(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function tr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Oe(t),e[t]=n}function xd(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Tn),po=" ",ho=!1;function ta(e,t){switch(e){case"keyup":return Yd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function na(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Vt=!1;function Xd(e,t){switch(e){case"compositionend":return na(t);case"keypress":return t.which!==32?null:(ho=!0,po);case"textInput":return e=t.data,e===po&&ho?null:e;default:return null}}function Zd(e,t){if(Vt)return e==="compositionend"||!vs&&ta(e,t)?(e=bu(),Pr=ps=it=null,Vt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=yo(n)}}function sa(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?sa(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function oa(){for(var e=window,t=$r();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=$r(e.document)}return t}function gs(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function sf(e){var t=oa(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&sa(n.ownerDocument.documentElement,n)){if(r!==null&&gs(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=xo(n,i);var s=xo(n,r);l&&s&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Wt=null,ki=null,Rn=null,ji=!1;function wo(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ji||Wt==null||Wt!==$r(r)||(r=Wt,"selectionStart"in r&&gs(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Rn&&Hn(Rn,r)||(Rn=r,r=Qr(ki,"onSelect"),0Qt||(e.current=Pi[Qt],Pi[Qt]=null,Qt--)}function $(e,t){Qt++,Pi[Qt]=e.current,e.current=t}var vt={},ue=yt(vt),ve=yt(!1),Pt=vt;function sn(e,t){var n=e.type.contextTypes;if(!n)return vt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function ge(e){return e=e.childContextTypes,e!=null}function Yr(){U(ve),U(ue)}function Eo(e,t,n){if(ue.current!==vt)throw Error(y(168));$(ue,t),$(ve,n)}function va(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(y(108,ed(e)||"Unknown",l));return Q({},n,r)}function Gr(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||vt,Pt=ue.current,$(ue,e),$(ve,ve.current),!0}function zo(e,t,n){var r=e.stateNode;if(!r)throw Error(y(169));n?(e=va(e,t,Pt),r.__reactInternalMemoizedMergedChildContext=e,U(ve),U(ue),$(ue,e)):U(ve),$(ve,n)}var He=null,ml=!1,Yl=!1;function ga(e){He===null?He=[e]:He.push(e)}function yf(e){ml=!0,ga(e)}function xt(){if(!Yl&&He!==null){Yl=!0;var e=0,t=D;try{var n=He;for(D=1;e>=s,l-=s,Qe=1<<32-Oe(t)+l|n<w?(I=E,E=null):I=E.sibling;var z=m(d,E,p[w],g);if(z===null){E===null&&(E=I);break}e&&E&&z.alternate===null&&t(d,E),c=i(z,c,w),C===null?j=z:C.sibling=z,C=z,E=I}if(w===p.length)return n(d,E),V&&kt(d,w),j;if(E===null){for(;ww?(I=E,E=null):I=E.sibling;var Y=m(d,E,z.value,g);if(Y===null){E===null&&(E=I);break}e&&E&&Y.alternate===null&&t(d,E),c=i(Y,c,w),C===null?j=Y:C.sibling=Y,C=Y,E=I}if(z.done)return n(d,E),V&&kt(d,w),j;if(E===null){for(;!z.done;w++,z=p.next())z=h(d,z.value,g),z!==null&&(c=i(z,c,w),C===null?j=z:C.sibling=z,C=z);return V&&kt(d,w),j}for(E=r(d,E);!z.done;w++,z=p.next())z=x(E,d,w,z.value,g),z!==null&&(e&&z.alternate!==null&&E.delete(z.key===null?w:z.key),c=i(z,c,w),C===null?j=z:C.sibling=z,C=z);return e&&E.forEach(function(O){return t(d,O)}),V&&kt(d,w),j}function P(d,c,p,g){if(typeof p=="object"&&p!==null&&p.type===Ut&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case ar:e:{for(var j=p.key,C=c;C!==null;){if(C.key===j){if(j=p.type,j===Ut){if(C.tag===7){n(d,C.sibling),c=l(C,p.props.children),c.return=d,d=c;break e}}else if(C.elementType===j||typeof j=="object"&&j!==null&&j.$$typeof===tt&&Lo(j)===C.type){n(d,C.sibling),c=l(C,p.props),c.ref=Sn(d,C,p),c.return=d,d=c;break e}n(d,C);break}else t(d,C);C=C.sibling}p.type===Ut?(c=zt(p.props.children,d.mode,g,p.key),c.return=d,d=c):(g=Dr(p.type,p.key,p.props,null,d.mode,g),g.ref=Sn(d,c,p),g.return=d,d=g)}return s(d);case At:e:{for(C=p.key;c!==null;){if(c.key===C)if(c.tag===4&&c.stateNode.containerInfo===p.containerInfo&&c.stateNode.implementation===p.implementation){n(d,c.sibling),c=l(c,p.children||[]),c.return=d,d=c;break e}else{n(d,c);break}else t(d,c);c=c.sibling}c=ti(p,d.mode,g),c.return=d,d=c}return s(d);case tt:return C=p._init,P(d,c,C(p._payload),g)}if(Nn(p))return k(d,c,p,g);if(vn(p))return S(d,c,p,g);wr(d,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,c!==null&&c.tag===6?(n(d,c.sibling),c=l(c,p),c.return=d,d=c):(n(d,c),c=ei(p,d.mode,g),c.return=d,d=c),s(d)):n(d,c)}return P}var un=Sa(!0),ka=Sa(!1),Jr=yt(null),qr=null,Gt=null,Ss=null;function ks(){Ss=Gt=qr=null}function js(e){var t=Jr.current;U(Jr),e._currentValue=t}function Ri(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function nn(e,t){qr=e,Ss=Gt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(me=!0),e.firstContext=null)}function Pe(e){var t=e._currentValue;if(Ss!==e)if(e={context:e,memoizedValue:t,next:null},Gt===null){if(qr===null)throw Error(y(308));Gt=e,qr.dependencies={lanes:0,firstContext:e}}else Gt=Gt.next=e;return t}var Nt=null;function Cs(e){Nt===null?Nt=[e]:Nt.push(e)}function ja(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Cs(t)):(n.next=l.next,l.next=n),t.interleaved=n,Je(e,r)}function Je(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var nt=!1;function Ns(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ca(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Ge(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function dt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,F&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Je(e,n)}return l=r.interleaved,l===null?(t.next=t,Cs(r)):(t.next=l.next,l.next=t),r.interleaved=t,Je(e,n)}function Lr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,cs(e,n)}}function Ro(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=s:i=i.next=s,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function br(e,t,n,r){var l=e.updateQueue;nt=!1;var i=l.firstBaseUpdate,s=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var a=u,f=a.next;a.next=null,s===null?i=f:s.next=f,s=a;var v=e.alternate;v!==null&&(v=v.updateQueue,u=v.lastBaseUpdate,u!==s&&(u===null?v.firstBaseUpdate=f:u.next=f,v.lastBaseUpdate=a))}if(i!==null){var h=l.baseState;s=0,v=f=a=null,u=i;do{var m=u.lane,x=u.eventTime;if((r&m)===m){v!==null&&(v=v.next={eventTime:x,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var k=e,S=u;switch(m=t,x=n,S.tag){case 1:if(k=S.payload,typeof k=="function"){h=k.call(x,h,m);break e}h=k;break e;case 3:k.flags=k.flags&-65537|128;case 0:if(k=S.payload,m=typeof k=="function"?k.call(x,h,m):k,m==null)break e;h=Q({},h,m);break e;case 2:nt=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,m=l.effects,m===null?l.effects=[u]:m.push(u))}else x={eventTime:x,lane:m,tag:u.tag,payload:u.payload,callback:u.callback,next:null},v===null?(f=v=x,a=h):v=v.next=x,s|=m;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;m=u,u=m.next,m.next=null,l.lastBaseUpdate=m,l.shared.pending=null}}while(!0);if(v===null&&(a=h),l.baseState=a,l.firstBaseUpdate=f,l.lastBaseUpdate=v,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Rt|=s,e.lanes=s,e.memoizedState=h}}function Mo(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Xl.transition;Xl.transition={};try{e(!1),t()}finally{D=n,Xl.transition=r}}function Va(){return Te().memoizedState}function kf(e,t,n){var r=pt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Wa(e))Ba(t,n);else if(n=ja(e,t,n,r),n!==null){var l=ce();Fe(n,e,r,l),Ha(n,t,r)}}function jf(e,t,n){var r=pt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Wa(e))Ba(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var s=t.lastRenderedState,u=i(s,n);if(l.hasEagerState=!0,l.eagerState=u,De(u,s)){var a=t.interleaved;a===null?(l.next=l,Cs(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=ja(e,t,l,r),n!==null&&(l=ce(),Fe(n,e,r,l),Ha(n,t,r))}}function Wa(e){var t=e.alternate;return e===H||t!==null&&t===H}function Ba(e,t){Mn=tl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ha(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,cs(e,n)}}var nl={readContext:Pe,useCallback:ie,useContext:ie,useEffect:ie,useImperativeHandle:ie,useInsertionEffect:ie,useLayoutEffect:ie,useMemo:ie,useReducer:ie,useRef:ie,useState:ie,useDebugValue:ie,useDeferredValue:ie,useTransition:ie,useMutableSource:ie,useSyncExternalStore:ie,useId:ie,unstable_isNewReconciler:!1},Cf={readContext:Pe,useCallback:function(e,t){return Ae().memoizedState=[e,t===void 0?null:t],e},useContext:Pe,useEffect:Oo,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Mr(4194308,4,Fa.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Mr(4194308,4,e,t)},useInsertionEffect:function(e,t){return Mr(4,2,e,t)},useMemo:function(e,t){var n=Ae();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Ae();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=kf.bind(null,H,e),[r.memoizedState,e]},useRef:function(e){var t=Ae();return e={current:e},t.memoizedState=e},useState:Io,useDebugValue:Ms,useDeferredValue:function(e){return Ae().memoizedState=e},useTransition:function(){var e=Io(!1),t=e[0];return e=Sf.bind(null,e[1]),Ae().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=H,l=Ae();if(V){if(n===void 0)throw Error(y(407));n=n()}else{if(n=t(),te===null)throw Error(y(349));Lt&30||za(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Oo(Ta.bind(null,r,i,e),[e]),r.flags|=2048,qn(9,Pa.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Ae(),t=te.identifierPrefix;if(V){var n=Ke,r=Qe;n=(r&~(1<<32-Oe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Zn++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[Ue]=t,e[Yn]=r,ec(e,t,!1,!1),t.stateNode=e;e:{switch(s=hi(n,r),n){case"dialog":A("cancel",e),A("close",e),l=r;break;case"iframe":case"object":case"embed":A("load",e),l=r;break;case"video":case"audio":for(l=0;ldn&&(t.flags|=128,r=!0,kn(i,!1),t.lanes=4194304)}else{if(!r)if(e=el(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),kn(i,!0),i.tail===null&&i.tailMode==="hidden"&&!s.alternate&&!V)return se(t),null}else 2*X()-i.renderingStartTime>dn&&n!==1073741824&&(t.flags|=128,r=!0,kn(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(n=i.last,n!==null?n.sibling=s:t.child=s,i.last=s)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=X(),t.sibling=null,n=W.current,$(W,r?n&1|2:n&1),t):(se(t),null);case 22:case 23:return As(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?xe&1073741824&&(se(t),t.subtreeFlags&6&&(t.flags|=8192)):se(t),null;case 24:return null;case 25:return null}throw Error(y(156,t.tag))}function Rf(e,t){switch(xs(t),t.tag){case 1:return ge(t.type)&&Yr(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return an(),U(ve),U(ue),zs(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Es(t),null;case 13:if(U(W),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(y(340));on()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return U(W),null;case 4:return an(),null;case 10:return js(t.type._context),null;case 22:case 23:return As(),null;case 24:return null;default:return null}}var kr=!1,oe=!1,Mf=typeof WeakSet=="function"?WeakSet:Set,N=null;function Xt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){K(e,t,r)}else n.current=null}function Vi(e,t,n){try{n()}catch(r){K(e,t,r)}}var Ko=!1;function If(e,t){if(Ci=Br,e=oa(),gs(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var s=0,u=-1,a=-1,f=0,v=0,h=e,m=null;t:for(;;){for(var x;h!==n||l!==0&&h.nodeType!==3||(u=s+l),h!==i||r!==0&&h.nodeType!==3||(a=s+r),h.nodeType===3&&(s+=h.nodeValue.length),(x=h.firstChild)!==null;)m=h,h=x;for(;;){if(h===e)break t;if(m===n&&++f===l&&(u=s),m===i&&++v===r&&(a=s),(x=h.nextSibling)!==null)break;h=m,m=h.parentNode}h=x}n=u===-1||a===-1?null:{start:u,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ni={focusedElem:e,selectionRange:n},Br=!1,N=t;N!==null;)if(t=N,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,N=e;else for(;N!==null;){t=N;try{var k=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(k!==null){var S=k.memoizedProps,P=k.memoizedState,d=t.stateNode,c=d.getSnapshotBeforeUpdate(t.elementType===t.type?S:Re(t.type,S),P);d.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(y(163))}}catch(g){K(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,N=e;break}N=t.return}return k=Ko,Ko=!1,k}function In(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&Vi(t,n,i)}l=l.next}while(l!==r)}}function yl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Wi(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function rc(e){var t=e.alternate;t!==null&&(e.alternate=null,rc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ue],delete t[Yn],delete t[zi],delete t[vf],delete t[gf])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function lc(e){return e.tag===5||e.tag===3||e.tag===4}function Yo(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||lc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Bi(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Kr));else if(r!==4&&(e=e.child,e!==null))for(Bi(e,t,n),e=e.sibling;e!==null;)Bi(e,t,n),e=e.sibling}function Hi(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Hi(e,t,n),e=e.sibling;e!==null;)Hi(e,t,n),e=e.sibling}var ne=null,Me=!1;function et(e,t,n){for(n=n.child;n!==null;)ic(e,t,n),n=n.sibling}function ic(e,t,n){if(Ve&&typeof Ve.onCommitFiberUnmount=="function")try{Ve.onCommitFiberUnmount(cl,n)}catch{}switch(n.tag){case 5:oe||Xt(n,t);case 6:var r=ne,l=Me;ne=null,et(e,t,n),ne=r,Me=l,ne!==null&&(Me?(e=ne,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):ne.removeChild(n.stateNode));break;case 18:ne!==null&&(Me?(e=ne,n=n.stateNode,e.nodeType===8?Kl(e.parentNode,n):e.nodeType===1&&Kl(e,n),Wn(e)):Kl(ne,n.stateNode));break;case 4:r=ne,l=Me,ne=n.stateNode.containerInfo,Me=!0,et(e,t,n),ne=r,Me=l;break;case 0:case 11:case 14:case 15:if(!oe&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,s=i.destroy;i=i.tag,s!==void 0&&(i&2||i&4)&&Vi(n,t,s),l=l.next}while(l!==r)}et(e,t,n);break;case 1:if(!oe&&(Xt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){K(n,t,u)}et(e,t,n);break;case 21:et(e,t,n);break;case 22:n.mode&1?(oe=(r=oe)||n.memoizedState!==null,et(e,t,n),oe=r):et(e,t,n);break;default:et(e,t,n)}}function Go(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Mf),t.forEach(function(r){var l=Bf.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Le(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~i}if(r=l,r=X()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Ff(r/1960))-r,10e?16:e,st===null)var r=!1;else{if(e=st,st=null,il=0,F&6)throw Error(y(331));var l=F;for(F|=4,N=e.current;N!==null;){var i=N,s=i.child;if(N.flags&16){var u=i.deletions;if(u!==null){for(var a=0;aX()-Ds?Et(e,0):Fs|=n),ye(e,t)}function pc(e,t){t===0&&(e.mode&1?(t=pr,pr<<=1,!(pr&130023424)&&(pr=4194304)):t=1);var n=ce();e=Je(e,t),e!==null&&(tr(e,t,n),ye(e,n))}function Wf(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),pc(e,n)}function Bf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(y(314))}r!==null&&r.delete(t),pc(e,n)}var hc;hc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ve.current)me=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return me=!1,Tf(e,t,n);me=!!(e.flags&131072)}else me=!1,V&&t.flags&1048576&&ya(t,Zr,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Ir(e,t),e=t.pendingProps;var l=sn(t,ue.current);nn(t,n),l=Ts(null,t,r,e,l,n);var i=Ls();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,ge(r)?(i=!0,Gr(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ns(t),l.updater=gl,t.stateNode=l,l._reactInternals=t,Ii(t,r,e,n),t=Di(null,t,r,!0,i,n)):(t.tag=0,V&&i&&ys(t),ae(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Ir(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Qf(r),e=Re(r,e),l){case 0:t=Fi(null,t,r,e,n);break e;case 1:t=Bo(null,t,r,e,n);break e;case 11:t=Vo(null,t,r,e,n);break e;case 14:t=Wo(null,t,r,Re(r.type,e),n);break e}throw Error(y(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Fi(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Bo(e,t,r,l,n);case 3:e:{if(Ja(t),e===null)throw Error(y(387));r=t.pendingProps,i=t.memoizedState,l=i.element,Ca(e,t),br(t,r,null,n);var s=t.memoizedState;if(r=s.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=cn(Error(y(423)),t),t=Ho(e,t,r,n,l);break e}else if(r!==l){l=cn(Error(y(424)),t),t=Ho(e,t,r,n,l);break e}else for(we=ct(t.stateNode.containerInfo.firstChild),Se=t,V=!0,Ie=null,n=ka(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(on(),r===l){t=qe(e,t,n);break e}ae(e,t,r,n)}t=t.child}return t;case 5:return Na(t),e===null&&Li(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,s=l.children,_i(r,l)?s=null:i!==null&&_i(r,i)&&(t.flags|=32),Za(e,t),ae(e,t,s,n),t.child;case 6:return e===null&&Li(t),null;case 13:return qa(e,t,n);case 4:return _s(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=un(t,null,r,n):ae(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Vo(e,t,r,l,n);case 7:return ae(e,t,t.pendingProps,n),t.child;case 8:return ae(e,t,t.pendingProps.children,n),t.child;case 12:return ae(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,s=l.value,$(Jr,r._currentValue),r._currentValue=s,i!==null)if(De(i.value,s)){if(i.children===l.children&&!ve.current){t=qe(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var u=i.dependencies;if(u!==null){s=i.child;for(var a=u.firstContext;a!==null;){if(a.context===r){if(i.tag===1){a=Ge(-1,n&-n),a.tag=2;var f=i.updateQueue;if(f!==null){f=f.shared;var v=f.pending;v===null?a.next=a:(a.next=v.next,v.next=a),f.pending=a}}i.lanes|=n,a=i.alternate,a!==null&&(a.lanes|=n),Ri(i.return,n,t),u.lanes|=n;break}a=a.next}}else if(i.tag===10)s=i.type===t.type?null:i.child;else if(i.tag===18){if(s=i.return,s===null)throw Error(y(341));s.lanes|=n,u=s.alternate,u!==null&&(u.lanes|=n),Ri(s,n,t),s=i.sibling}else s=i.child;if(s!==null)s.return=i;else for(s=i;s!==null;){if(s===t){s=null;break}if(i=s.sibling,i!==null){i.return=s.return,s=i;break}s=s.return}i=s}ae(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,nn(t,n),l=Pe(l),r=r(l),t.flags|=1,ae(e,t,r,n),t.child;case 14:return r=t.type,l=Re(r,t.pendingProps),l=Re(r.type,l),Wo(e,t,r,l,n);case 15:return Ga(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Re(r,l),Ir(e,t),t.tag=1,ge(r)?(e=!0,Gr(t)):e=!1,nn(t,n),Qa(t,r,l),Ii(t,r,l,n),Di(null,t,r,!0,e,n);case 19:return ba(e,t,n);case 22:return Xa(e,t,n)}throw Error(y(156,t.tag))};function mc(e,t){return Wu(e,t)}function Hf(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ee(e,t,n,r){return new Hf(e,t,n,r)}function Vs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Qf(e){if(typeof e=="function")return Vs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ss)return 11;if(e===os)return 14}return 2}function ht(e,t){var n=e.alternate;return n===null?(n=Ee(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Dr(e,t,n,r,l,i){var s=2;if(r=e,typeof e=="function")Vs(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case Ut:return zt(n.children,l,i,t);case is:s=8,l|=8;break;case li:return e=Ee(12,n,t,l|2),e.elementType=li,e.lanes=i,e;case ii:return e=Ee(13,n,t,l),e.elementType=ii,e.lanes=i,e;case si:return e=Ee(19,n,t,l),e.elementType=si,e.lanes=i,e;case Nu:return wl(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ju:s=10;break e;case Cu:s=9;break e;case ss:s=11;break e;case os:s=14;break e;case tt:s=16,r=null;break e}throw Error(y(130,e==null?e:typeof e,""))}return t=Ee(s,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function zt(e,t,n,r){return e=Ee(7,e,r,t),e.lanes=n,e}function wl(e,t,n,r){return e=Ee(22,e,r,t),e.elementType=Nu,e.lanes=n,e.stateNode={isHidden:!1},e}function ei(e,t,n){return e=Ee(6,e,null,t),e.lanes=n,e}function ti(e,t,n){return t=Ee(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Kf(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Ol(0),this.expirationTimes=Ol(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Ol(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ws(e,t,n,r,l,i,s,u,a){return e=new Kf(e,t,n,u,a),t===1?(t=1,i===!0&&(t|=8)):t=0,i=Ee(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ns(i),e}function Yf(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(xc)}catch(e){console.error(e)}}xc(),xu.exports=je;var qf=xu.exports,wc,nu=qf;wc=nu.createRoot,nu.hydrateRoot;function Sc(e,t){if(Array.isArray(e))return e;for(const n of t)if(Array.isArray(e==null?void 0:e[n]))return e[n];return[]}function bf(e){return Sc(e,["items","sessions","chats"]).map((t,n)=>({id:String(t.chat_id??t.chatId??t.session_id??t.sessionId??t.automation_id??t.automationId??t.id??n),title:String(t.title??t.name??t.display_name??t.displayName??t.nickname??t.chat_id??t.id??"未命名会话"),preview:String(t.last_message??t.lastMessage??t.preview??"暂无最近消息"),unread:Number(t.unread_count??t.unreadCount??0),type:t.chat_type??t.chatType??"Private"}))}function ep(e){return Sc(e,["items","messages"]).map((t,n)=>{const r=String(t.direction??"").toLowerCase();return{id:String(t.message_id??t.messageId??t.id??n),sender:String(t.sender??t.sender_name??t.senderName??(r==="outgoing"?"我":"对方")),text:typeof t=="string"?t:String(t.content??t.text??t.message??""),at:t.occurred_at??t.occurredAt??t.created_at??t.createdAt??t.source_time??t.sourceTime??t.observed_at??t.observedAt}})}function ru(e,t=0){var r;const n=(e==null?void 0:e.coverage)||((r=e==null?void 0:e.metadata)==null?void 0:r.coverage)||(e==null?void 0:e.sync);return!n||typeof n!="object"?{state:t===0?"unknown":"complete",source:"legacy-result",observedCount:t,authorizedScopeCount:null,matchedScopeCount:null,errorCode:t===0?"CoverageUnavailable":null}:{state:String(n.state||"unknown").toLowerCase(),source:n.source||"unknown",observedCount:Number(n.observedCount??n.observed_count??t),authorizedScopeCount:n.authorizedScopeCount??n.authorized_scope_count??null,matchedScopeCount:n.matchedScopeCount??n.matched_scope_count??null,observedAt:n.observedAt??n.observed_at??n.lastSuccessAt??n.last_success_at??null,lastSuccessAt:n.lastSuccessAt??n.last_success_at??null,backlogCount:n.backlogCount??n.backlog_count??null,errorCode:n.errorCode??n.error_code??null,errorMessage:n.errorMessage??n.error_message??null}}function lu(e,t,n=r=>r.id){const r=[...t],l=new Set(r.map(n));for(const i of e){const s=n(i);l.has(s)||(r.push(i),l.add(s))}return r}function iu(e){return(e==null?void 0:e.state)==="complete"}const kc=[{id:"messages",label:"消息",description:"查找会话、阅读并回复",icon:"message"},{id:"broadcast",label:"群发",description:"创建受控群发任务",icon:"send"},{id:"contacts",label:"通讯录",description:"查找联系人和群聊",icon:"contacts"},{id:"tasks",label:"任务",description:"查看执行结果",icon:"tasks"}],jc={Online:"在线",Degraded:"需要注意",Offline:"已离线",Registered:"已注册",SessionLocked:"桌面已锁定",WechatNotRunning:"微信未运行",WechatNotLoggedIn:"微信未登录",Pending:"待处理",WaitingForClient:"等待 Client",Accepted:"已接收",Running:"执行中",Succeeded:"已完成",Failed:"失败",Cancelled:"已取消",Expired:"已过期",ResultUnconfirmed:"待核对"},tp=new Set(["Succeeded","Failed","Cancelled","Expired","ResultUnconfirmed"]);class Jt extends Error{constructor(t,n,r="RequestFailed"){super(t),this.status=n,this.code=r}}async function Ye(e,{token:t,method:n="GET",body:r}={}){var u,a;const l={};t&&(l.Authorization=`Bearer ${t}`),r!==void 0&&(l["Content-Type"]="application/json");const i=await fetch(e,{method:n,headers:l,body:r===void 0?void 0:JSON.stringify(r)}),s=await i.json().catch(()=>({}));if(!i.ok)throw new Jt(((u=s.error)==null?void 0:u.message)||`请求失败(${i.status})`,i.status,(a=s.error)==null?void 0:a.code);return s}const np=e=>new Promise(t=>window.setTimeout(t,e)),rp={ReportingDisabled:"Client 未启用 Reporting 白名单。请在托盘远程连接页启用并配置白名单。",ReportingConfigInvalid:"Client 的 Reporting 配置无效,请检查托盘远程连接页。",AccountNotAuthorized:"当前账号未加入 Client 的 Reporting 白名单。",ChatNotAuthorized:"当前会话不在 Client 的 Reporting 白名单中。",ChatIdentityUnconfirmed:"当前会话身份尚未确认,暂不能读取。",DataTypeNotAuthorized:"当前读取类型未被 Reporting 白名单授权。"};function lp(e){var n,r;const t=((n=e.result)==null?void 0:n.error_code)||e.status;return rp[t]||((r=e.result)==null?void 0:r.message)||`读取任务${jc[e.status]||"未完成"}。`}async function ip(e,t){var i,s;const n=Date.now()+3e4;let r=250,l={status:"Pending"};for(;Date.now()t?`${e.slice(0,t)}…`:e:"—"}function op(e){return["Online","Succeeded","Accepted"].includes(e)?"positive":["Running","Pending","WaitingForClient","Degraded"].includes(e)?"warning":["Failed","Cancelled","Expired","ResultUnconfirmed","Offline","SessionLocked","WechatNotRunning","WechatNotLoggedIn"].includes(e)?"negative":"neutral"}function Nc(e){return jc[e]||e||"未知"}function ul(e){return!!(e&&["Online","Degraded","Registered"].includes(e.status))}function Ks(e){var t,n;return e?e.active_account_id?e.active_account_id:((n=(t=e.accounts)==null?void 0:t.find(r=>r.active&&r.verified))==null?void 0:n.account_id)||"":""}function up(e){return extractItems(e,["items","contacts","sessions"]).map((t,n)=>({id:String(t.contact_id??t.contactId??t.chat_id??t.chatId??t.id??n),title:String(t.name??t.display_name??t.displayName??t.nickname??t.contact_id??t.id??"未命名联系人"),type:t.chat_type??t.chatType??(t.is_group?"Group":"Private"),detail:String(t.remark??t.alias??t.account_id??t.accountId??"")}))}function B({name:e,size:t=18}){const n={message:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v7a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 3v-3h0A2.5 2.5 0 0 1 4 12.5z"}),o.jsx("path",{d:"M8 8h8M8 11h5"})]}),send:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"m3 4 18 8-18 8 3.5-8z"}),o.jsx("path",{d:"M6.5 12H21"})]}),contacts:o.jsxs(o.Fragment,{children:[o.jsx("circle",{cx:"9",cy:"8",r:"3"}),o.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0M17 7v6M14 10h6"})]}),tasks:o.jsxs(o.Fragment,{children:[o.jsx("rect",{x:"4",y:"3",width:"16",height:"18",rx:"2"}),o.jsx("path",{d:"M8 8h8M8 12h8M8 16h5"})]}),settings:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"}),o.jsx("circle",{cx:"12",cy:"12",r:"4"})]}),refresh:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M20 11a8 8 0 0 0-14.5-4.5L4 8"}),o.jsx("path",{d:"M4 4v4h4M4 13a8 8 0 0 0 14.5 4.5L20 16"}),o.jsx("path",{d:"M20 20v-4h-4"})]}),chevron:o.jsx("path",{d:"m9 6 6 6-6 6"}),search:o.jsxs(o.Fragment,{children:[o.jsx("circle",{cx:"10.5",cy:"10.5",r:"6.5"}),o.jsx("path",{d:"m16 16 4.5 4.5"})]}),shield:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 3 19 6v5c0 4.2-2.8 7.8-7 9-4.2-1.2-7-4.8-7-9V6z"}),o.jsx("path",{d:"m9 12 2 2 4-4"})]}),alert:o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"M12 4 21 20H3z"}),o.jsx("path",{d:"M12 10v4M12 17v.2"})]}),close:o.jsx(o.Fragment,{children:o.jsx("path",{d:"m6 6 12 12M18 6 6 18"})}),logout:o.jsx(o.Fragment,{children:o.jsx("path",{d:"M10 4H5v16h5M14 8l4 4-4 4M8 12h10"})}),arrow:o.jsx(o.Fragment,{children:o.jsx("path",{d:"M4 12h15M13 6l6 6-6 6"})}),check:o.jsx("path",{d:"m5 12 4 4L19 6"})};return o.jsx("svg",{"aria-hidden":"true",width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round",children:n[e]||n.settings})}function fn({value:e}){return o.jsxs("span",{className:`status-badge ${op(e)}`,children:[o.jsx("i",{}),Nc(e)]})}function ap({onLogin:e}){const[t,n]=T.useState(""),[r,l]=T.useState(""),[i,s]=T.useState(""),[u,a]=T.useState(!1),f=async v=>{v.preventDefault(),s(""),a(!0);try{const h=await Ye("/v1/auth/login",{method:"POST",body:{username:t,password:r}});e(h.access_token,t)}catch(h){s(h.message)}finally{a(!1)}};return o.jsx("main",{className:"login-page",children:o.jsxs("section",{className:"login-panel","aria-labelledby":"login-title",children:[o.jsx("div",{className:"brand-mark",children:"W"}),o.jsx("p",{className:"eyebrow",children:"WXAGENT WORKSPACE"}),o.jsx("h1",{id:"login-title",children:"进入工作台"}),o.jsx("p",{className:"login-intro",children:"连接已授权的 Client,处理消息、通讯录和任务结果。"}),o.jsxs("form",{onSubmit:f,className:"login-form",children:[o.jsxs("label",{children:["用户名",o.jsx("input",{autoFocus:!0,value:t,onChange:v=>n(v.target.value),autoComplete:"username"})]}),o.jsxs("label",{children:["密码",o.jsx("input",{type:"password",value:r,onChange:v=>l(v.target.value),autoComplete:"current-password"})]}),i&&o.jsx("div",{className:"form-error",role:"alert",children:i}),o.jsx("button",{className:"primary-button full",disabled:u||!t||!r,children:u?"登录中…":"登录工作台"})]}),o.jsxs("p",{className:"login-footnote",children:[o.jsx("span",{className:"secure-dot"})," 会话只保存在当前浏览器标签页"]})]})})}function cp({view:e,setView:t,onLogout:n,username:r}){return o.jsxs("aside",{className:"sidebar",children:[o.jsxs("div",{className:"sidebar-brand",children:[o.jsx("div",{className:"brand-mark small",children:"W"}),o.jsxs("div",{children:[o.jsx("strong",{children:"WxAgent"}),o.jsx("span",{children:"用户工作台"})]})]}),o.jsx("p",{className:"nav-caption",children:"工作区"}),o.jsx("nav",{"aria-label":"主导航",children:kc.map(l=>o.jsxs("button",{className:e===l.id?"nav-item active":"nav-item",onClick:()=>t(l.id),children:[o.jsx(B,{name:l.icon,size:18}),o.jsx("span",{children:l.label})]},l.id))}),o.jsx("div",{className:"sidebar-spacer"}),o.jsxs("button",{className:e==="settings"?"nav-item active":"nav-item",onClick:()=>t("settings"),children:[o.jsx(B,{name:"settings",size:18}),o.jsx("span",{children:"设置与诊断"})]}),o.jsxs("div",{className:"connection-hint",children:[o.jsx("span",{className:"secure-dot"}),o.jsxs("div",{children:[o.jsx("strong",{children:"安全连接"}),o.jsx("small",{children:"控制面 API 已认证"})]})]}),o.jsxs("div",{className:"user-menu",children:[o.jsx("div",{className:"avatar",children:(r||"A").slice(0,1).toUpperCase()}),o.jsxs("div",{className:"user-name",children:[o.jsx("strong",{children:r||"管理员"}),o.jsx("small",{children:"已登录"})]}),o.jsx("button",{title:"退出登录","aria-label":"退出登录",onClick:n,children:o.jsx(B,{name:"logout",size:17})})]})]})}function dp({title:e,subtitle:t,nodes:n,selectedClientId:r,onClientChange:l,onRefresh:i,loading:s}){const u=n.find(a=>a.node_id===r);return o.jsxs("header",{className:"topbar",children:[o.jsxs("div",{className:"topbar-copy",children:[o.jsx("h1",{children:e}),o.jsx("p",{children:t})]}),o.jsxs("div",{className:"topbar-actions",children:[o.jsxs("label",{className:"client-switcher",children:[o.jsx("span",{children:"当前 Client"}),o.jsxs("select",{value:r,onChange:a=>l(a.target.value),disabled:!n.length,children:[!n.length&&o.jsx("option",{value:"",children:"暂无 Client"}),n.map(a=>o.jsxs("option",{value:a.node_id,children:[a.node_id," · ",Nc(a.status)]},a.node_id))]})]}),u&&o.jsx(fn,{value:u.status}),o.jsxs("button",{className:"refresh-button",onClick:i,disabled:s,"aria-label":"刷新数据",children:[o.jsx("span",{className:s?"spin":"",children:o.jsx(B,{name:"refresh",size:16})}),s?"同步中":"刷新"]})]})]})}function fp({nodes:e,selectedNode:t,onSettings:n}){if(!e.length)return o.jsxs("div",{className:"connection-banner negative",children:[o.jsx(B,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"还没有可用 Client"}),o.jsx("span",{children:"请启动已配置的 Desktop Agent;浏览器不会模拟连接状态。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"查看连接说明"})]});if(!ul(t)){const r=e.some(ul);return o.jsxs("div",{className:"connection-banner warning",children:[o.jsx(B,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"当前 Client 不可用"}),o.jsx("span",{children:r?"其它 Client 不受影响,请从顶部切换。":"所有 Client 当前都不可用,请先恢复连接。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"设置与诊断"})]})}return Ks(t)?null:o.jsxs("div",{className:"connection-banner warning",children:[o.jsx(B,{name:"alert",size:18}),o.jsxs("div",{children:[o.jsx("strong",{children:"当前 Client 尚未确认微信账号"}),o.jsx("span",{children:"只读数据需要已验证的活动账号,先完成账号绑定后再操作。"})]}),o.jsx("button",{className:"text-button",onClick:n,children:"查看账号"})]})}function Xi({title:e="没有选中的 Client",text:t="请先连接并选择一个可用 Client。",onSettings:n}){return o.jsxs("div",{className:"context-empty",children:[o.jsx("div",{className:"empty-icon",children:o.jsx(B,{name:"shield",size:28})}),o.jsx("h3",{children:e}),o.jsx("p",{children:t}),n&&o.jsx("button",{className:"secondary-button",onClick:n,children:"打开设置与诊断"})]})}function Zi({text:e="正在读取…"}){return o.jsxs("div",{className:"loading-state",children:[o.jsx("span",{className:"loader"}),e]})}function Ot({text:e,action:t}){return o.jsxs("div",{className:"empty-state",children:[o.jsx("div",{className:"empty-icon",children:o.jsx(B,{name:"search",size:26})}),o.jsx("p",{children:e}),t]})}function Ji({error:e,onRetry:t}){return o.jsxs("div",{className:"inline-error",role:"alert",children:[o.jsx(B,{name:"alert",size:17}),o.jsx("span",{children:e}),t&&o.jsx("button",{className:"text-button",onClick:t,children:"重试"})]})}function Nr({text:e,onRetry:t}){return o.jsxs("div",{className:"read-notice",role:"status",children:[o.jsx(B,{name:"alert",size:16}),o.jsx("span",{children:e}),t&&o.jsx("button",{className:"text-button",onClick:t,children:"重试"})]})}function ou(e,t){if(!t)return"";const n=t.lastSuccessAt?`最后同步 ${It(t.lastSuccessAt)}`:"尚未成功同步",r=t.backlogCount==null?"积压未知":`积压 ${t.backlogCount}`,l=`${n} · ${r}`;return t.state==="complete"?t.source==="platform-cache"?`${e}来自平台副本,${l}。`:"":t.state==="partial"?`${e}同步不完整,已保留已有数据;${l}。`:`${e}同步状态未知,未用本次结果清除已有数据;${l}${t.errorMessage?` · ${t.errorMessage}`:""}。`}function pp({token:e,client:t,onSettings:n}){const r=(t==null?void 0:t.node_id)||"",l=Ks(t),[i,s]=T.useState([]),[u,a]=T.useState({loading:!1,error:"",notice:""}),[f,v]=T.useState(""),[h,m]=T.useState(null),[x,k]=T.useState([]),[S,P]=T.useState({loading:!1,error:"",notice:""}),d=T.useRef(0),c=T.useRef(0),p=T.useRef(0),g=T.useRef(0),j=T.useCallback(async()=>{if(!l||!r)return;const w=++g.current;try{await Ye(ni(l,"refresh"),{token:e,method:"POST"})}catch(I){w===g.current&&a(z=>({...z,notice:z.notice||`后台同步未受理:${I.message}`}))}},[l,r,e]),C=T.useCallback(async()=>{if(!r||!l)return;const w=++c.current,I=d.current;a(z=>({...z,loading:!0,error:""}));try{const z=await su({storedPath:ni(l,"conversations","?limit=100"),legacyPath:"/v1/reads/sessions",legacyBody:{node_id:r,account_id:l,limit:100},token:e});if(w!==c.current||I!==d.current)return;const Y=bf(z);z.sync&&z.sync.state!=="complete"&&j();const O=ru(z,Y.length);s(pe=>iu(O)?Y:lu(pe,Y)),a({loading:!1,error:"",notice:ou("会话",O)})}catch(z){w===c.current&&I===d.current&&a({loading:!1,error:z.message,notice:""})}},[l,r,j,e]);if(T.useEffect(()=>{d.current+=1,c.current+=1,p.current+=1,m(null),s([]),k([]),a({loading:!1,error:"",notice:""}),P({loading:!1,error:"",notice:""}),!(!r||!l)&&C()},[r,l,C]),T.useEffect(()=>{if(!h||h.clientId!==r||h.accountId!==l||!r||!l)return;const w=++p.current,I=d.current;P(z=>({...z,loading:!0,error:""})),su({storedPath:ni(l,"messages",`?chat_id=${encodeURIComponent(h.id)}&limit=100`),legacyPath:"/v1/reads/messages",legacyBody:{node_id:r,account_id:l,chat_id:h.id,limit:100,include_content:!0},token:e}).then(z=>{if(w!==p.current||I!==d.current)return;const Y=ep(z),O=ru(z,Y.length);k(pe=>iu(O)?Y:lu(pe,Y)),P({loading:!1,error:"",notice:ou("消息",O)})}).catch(z=>{w===p.current&&I===d.current&&P({loading:!1,error:z.message,notice:""})})},[l,r,h,e]),!t)return o.jsx(Xi,{onSettings:n});const E=i.filter(w=>!f||`${w.title} ${w.id}`.toLowerCase().includes(f.toLowerCase()));return o.jsxs("div",{className:"message-workspace",children:[o.jsxs("section",{className:"session-panel",children:[o.jsxs("div",{className:"section-heading compact",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"会话"}),o.jsx("p",{children:i.length?`${i.length} 个会话`:"当前 Client 的会话"})]}),o.jsx("button",{className:"icon-button",onClick:C,disabled:u.loading,"aria-label":"刷新会话",children:o.jsx(B,{name:"refresh",size:16})})]}),o.jsxs("label",{className:"search-box",children:[o.jsx(B,{name:"search",size:16}),o.jsx("input",{value:f,onChange:w=>v(w.target.value),placeholder:"搜索会话"})]}),u.loading&&i.length===0?o.jsx(Zi,{text:"正在读取会话…"}):u.error&&i.length===0?o.jsx(Ji,{error:u.error,onRetry:C}):o.jsxs(o.Fragment,{children:[u.error&&o.jsx(Nr,{text:`读取会话失败,继续显示上次数据:${u.error}`,onRetry:C}),!u.error&&u.notice&&o.jsx(Nr,{text:u.notice,onRetry:C}),u.loading&&i.length>0&&o.jsx("div",{className:"refresh-hint",children:"正在刷新,会话列表保持可用…"}),E.length===0?o.jsx(Ot,{text:"暂无会话,或当前账号还没有可见数据。"}):o.jsx("div",{className:"session-list",children:E.map(w=>o.jsxs("button",{className:(h==null?void 0:h.id)===w.id?"session-row selected":"session-row",onClick:()=>m({...w,clientId:r,accountId:l}),children:[o.jsx("span",{className:"session-avatar",children:w.title.slice(0,1).toUpperCase()}),o.jsxs("span",{className:"session-copy",children:[o.jsx("strong",{children:w.title}),o.jsx("small",{children:w.preview})]}),w.unread>0&&o.jsx("b",{children:w.unread})]},w.id))})]})]}),o.jsx("section",{className:"conversation-panel",children:h?o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"conversation-head",children:[o.jsxs("div",{children:[o.jsxs("p",{className:"eyebrow",children:["当前 Client · ",r]}),o.jsx("h2",{children:h.title}),o.jsxs("span",{children:[h.type==="Group"?"群聊":"私聊"," · ",Nl(h.id,28)]})]}),o.jsx(fn,{value:t.status})]}),S.loading&&x.length===0?o.jsx(Zi,{text:"正在读取消息…"}):S.error&&x.length===0?o.jsx(Ji,{error:S.error,onRetry:()=>m({...h})}):o.jsxs(o.Fragment,{children:[S.error&&o.jsx(Nr,{text:`读取消息失败,继续显示上次数据:${S.error}`,onRetry:()=>m({...h})}),!S.error&&S.notice&&o.jsx(Nr,{text:S.notice,onRetry:()=>m({...h})}),S.loading&&x.length>0&&o.jsx("div",{className:"refresh-hint",children:"正在刷新,消息历史保持可用…"}),x.length===0?o.jsx(Ot,{text:"这个会话暂时没有可显示的消息。"}):o.jsx("div",{className:"message-list",children:x.map(w=>o.jsxs("article",{className:"message-row",children:[o.jsx("div",{className:"message-avatar",children:w.sender.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsxs("div",{className:"message-meta",children:[o.jsx("strong",{children:w.sender}),o.jsx("span",{children:It(w.at)})]}),o.jsx("p",{children:w.text||"(无正文)"})]})]},w.id))})]}),o.jsxs("div",{className:"composer",children:[o.jsx("textarea",{disabled:!0,rows:2,placeholder:"发送消息暂未开放,需完成 Windows 真机验收"}),o.jsx("button",{className:"primary-button",disabled:!0,title:"写操作尚未通过真机验收",children:"发送"})]})]}):o.jsx(Xi,{title:"选择一个会话",text:"从左侧选择会话后,读取该 Client 的消息。"})})]})}function hp({client:e,onSettings:t}){return o.jsxs("section",{className:"workspace-panel gated-panel",children:[o.jsx("div",{className:"gated-mark",children:o.jsx(B,{name:"shield",size:24})}),o.jsxs("div",{children:[o.jsx("p",{className:"eyebrow",children:"CONTROLLED ACTION"}),o.jsx("h2",{children:"受控群发验证"}),o.jsx("p",{children:"单 Client 的 Client 端已提供 broadcast-text:冻结获准对象名单,逐项串行发送,返回每个对象的结果,并支持停止和幂等重放。"}),o.jsxs("div",{className:"step-list",children:[o.jsxs("span",{children:[o.jsx("b",{children:"1"}),"冻结对象"]}),o.jsxs("span",{children:[o.jsx("b",{children:"2"}),"预览确认"]}),o.jsxs("span",{children:[o.jsx("b",{children:"3"}),"逐项发送"]}),o.jsxs("span",{children:[o.jsx("b",{children:"4"}),"查看结果"]})]}),o.jsx("button",{className:"primary-button",disabled:!0,children:e?"Web 提交仍需单独验收":"请先连接 Client"}),o.jsxs("button",{className:"text-button",onClick:t,children:["查看 Client 验证状态 ",o.jsx(B,{name:"arrow",size:14})]})]})]})}function mp({token:e,client:t,onSettings:n}){const r=(t==null?void 0:t.node_id)||"",l=Ks(t),[i,s]=T.useState(!1),[u,a]=T.useState(""),[f,v]=T.useState([]),[h,m]=T.useState({loading:!1,error:""}),x=T.useRef(0),k=T.useCallback(async()=>{if(!r||!l)return;const P=++x.current;m({loading:!0,error:""});try{const d=await Cc("/v1/reads/contacts",{node_id:r,account_id:l,limit:200,groups_only:i,contains:u},e);if(P!==x.current)return;v(up(d)),m({loading:!1,error:""})}catch(d){P===x.current&&m({loading:!1,error:d.message})}},[l,r,i,u,e]);if(T.useEffect(()=>{v([]),r&&l&&k()},[r,l,i]),!t)return o.jsx(Xi,{onSettings:n});const S=f.filter(P=>!u||`${P.title} ${P.id} ${P.detail}`.toLowerCase().includes(u.toLowerCase()));return o.jsxs("section",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"通讯录"}),o.jsx("p",{children:"只读查看当前 Client 已授权的联系人和群聊。"})]}),o.jsxs("div",{className:"heading-actions",children:[o.jsxs("label",{className:"check-filter",children:[o.jsx("input",{type:"checkbox",checked:i,onChange:P=>s(P.target.checked)}),"只看群聊"]}),o.jsx("button",{className:"secondary-button",disabled:!0,title:"添加好友尚未通过真机验收",children:"添加好友"})]})]}),o.jsxs("div",{className:"toolbar",children:[o.jsxs("label",{className:"search-box wide",children:[o.jsx(B,{name:"search",size:16}),o.jsx("input",{value:u,onChange:P=>a(P.target.value),onKeyDown:P=>P.key==="Enter"&&k(),placeholder:"搜索联系人或群聊"})]}),o.jsx("button",{className:"secondary-button",onClick:k,disabled:h.loading,children:h.loading?"读取中…":"刷新"})]}),h.loading?o.jsx(Zi,{text:"正在读取通讯录…"}):h.error?o.jsx(Ji,{error:h.error,onRetry:k}):S.length===0?o.jsx(Ot,{text:"暂无可见联系人或群聊。"}):o.jsx("div",{className:"contact-list",children:S.map(P=>o.jsxs("div",{className:"contact-row",children:[o.jsx("span",{className:"contact-avatar",children:P.title.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsx("strong",{children:P.title}),o.jsxs("small",{children:[P.type==="Group"?"群聊":"联系人"," · ",Nl(P.id,28),P.detail?` · ${P.detail}`:""]})]}),o.jsx("button",{className:"text-button",disabled:!0,title:"写操作尚未通过真机验收",children:"添加好友"})]},P.id))})]})}function vp({tasks:e,selectedClientId:t}){const[n,r]=T.useState("all"),l=e.filter(i=>i.node_id===t&&(n==="all"||i.status===n));return o.jsxs("section",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"任务结果"}),o.jsx("p",{children:"当前 Client 的任务状态和安全边界。"})]}),o.jsxs("div",{className:"heading-actions",children:[o.jsxs("span",{className:"read-only-note",children:[o.jsx(B,{name:"shield",size:14}),"只读视图"]}),o.jsxs("select",{value:n,onChange:i=>r(i.target.value),children:[o.jsx("option",{value:"all",children:"全部状态"}),o.jsx("option",{value:"Pending",children:"待处理"}),o.jsx("option",{value:"WaitingForClient",children:"等待 Client"}),o.jsx("option",{value:"Running",children:"执行中"}),o.jsx("option",{value:"Succeeded",children:"已完成"}),o.jsx("option",{value:"Failed",children:"失败"}),o.jsx("option",{value:"ResultUnconfirmed",children:"待核对"})]})]})]}),l.length===0?o.jsx(Ot,{text:"当前 Client 暂无任务记录。"}):o.jsx("div",{className:"task-list",children:l.map(i=>{var s,u;return o.jsxs("article",{className:"task-row",children:[o.jsxs("div",{className:"task-main",children:[o.jsxs("div",{className:"task-title",children:[o.jsx("strong",{children:i.kind}),o.jsx(fn,{value:i.status})]}),o.jsxs("p",{children:[i.account_id," · ",Nl(i.task_id,22)]}),o.jsx("small",{children:i.status==="WaitingForClient"?"Client 离线后等待显式恢复;不会自动重放写操作。":((s=i.result)==null?void 0:s.message)||((u=i.result)==null?void 0:u.error_code)||`更新于 ${It(i.updated_at)}`})]}),o.jsxs("div",{className:"task-meta",children:[o.jsxs("span",{children:["第 ",i.lease_generation||0," 代租约"]}),o.jsx("time",{children:It(i.updated_at)})]})]},i.task_id)})})]})}function gp({nodes:e}){return o.jsx("div",{className:"diagnostic-list",children:e.length===0?o.jsx(Ot,{text:"暂无注册 Client。"}):e.map(t=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{className:"diagnostic-title",children:[o.jsx("span",{className:"node-avatar",children:t.node_id.slice(0,1).toUpperCase()}),o.jsxs("div",{children:[o.jsx("strong",{children:t.node_id}),o.jsxs("small",{children:[t.active_account_id||"尚未确认活动账号"," · 最近心跳 ",It(t.last_heartbeat_at)]})]})]}),o.jsx(fn,{value:t.status})]},t.node_id))})}function yp({events:e,selectedClientId:t}){const n=e.filter(r=>!t||r.node_id===t);return o.jsx("div",{className:"diagnostic-list",children:n.length===0?o.jsx(Ot,{text:"当前 Client 暂无事件。"}):n.slice(0,30).map(r=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{children:[o.jsx("strong",{children:Nl(r.chat_id,24)}),o.jsxs("small",{children:[r.node_id," · ",r.chat_type==="Group"?"群聊":"私聊"," · ",It(r.received_at)]})]}),o.jsx("span",{className:"event-preview",children:r.content||"无正文"})]},r.event_id))})}function xp({audit:e}){return o.jsx("div",{className:"diagnostic-list",children:e.length===0?o.jsx(Ot,{text:"暂无审计记录。"}):e.slice(0,30).map(t=>o.jsxs("article",{className:"diagnostic-row",children:[o.jsxs("div",{children:[o.jsx("strong",{children:t.action}),o.jsxs("small",{children:[t.principal," · ",t.resource]})]}),o.jsx("span",{children:It(t.at)})]},t.id))})}function wp({nodes:e,events:t,audit:n,selectedClientId:r,onSelectClient:l}){const[i,s]=T.useState("connection");return o.jsxs("section",{className:"settings-layout",children:[o.jsxs("aside",{className:"settings-nav",children:[o.jsxs("button",{className:i==="connection"?"settings-tab active":"settings-tab",onClick:()=>s("connection"),children:[o.jsx("strong",{children:"连接与账号"}),o.jsx("span",{children:"选择 Client、确认状态"})]}),o.jsxs("button",{className:i==="diagnostics"?"settings-tab active":"settings-tab",onClick:()=>s("diagnostics"),children:[o.jsx("strong",{children:"高级诊断"}),o.jsx("span",{children:"节点、事件、AI、审计"})]})]}),o.jsx("div",{className:"settings-content",children:i==="connection"?o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"workspace-panel",children:[o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"连接与账号"}),o.jsx("p",{children:"每个 Client 独立连接,单个 Client 离线不会阻断其它 Client。"})]}),o.jsx(fn,{value:e.some(ul)?"Online":"Offline"})]}),o.jsx(gp,{nodes:e})]}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"安全边界"}),o.jsx("p",{children:"当前版本只开放真实后端已支持且可验证的只读能力。"})]})}),o.jsxs("ul",{className:"boundary-list",children:[o.jsxs("li",{children:[o.jsx(B,{name:"check",size:15}),"消息、通讯录、任务结果按选定 Client 隔离读取"]}),o.jsxs("li",{children:[o.jsx(B,{name:"check",size:15}),"Client 掉线只影响当前上下文,任务不会自动改投"]}),o.jsxs("li",{children:[o.jsx(B,{name:"alert",size:15}),"发送、群发、添加好友等待 Windows 真机验收"]})]})]})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"Client 诊断"}),o.jsx("p",{children:"技术状态只在这里展示,不干扰普通操作。"})]})}),o.jsx("div",{className:"client-pills",children:e.map(u=>o.jsxs("button",{className:u.node_id===r?"client-pill active":"client-pill",onClick:()=>l(u.node_id),children:[o.jsx("span",{children:u.node_id}),o.jsx(fn,{value:u.status})]},u.node_id))})]}),o.jsx("div",{className:"workspace-panel",children:o.jsxs("div",{className:"section-heading",children:[o.jsxs("div",{children:[o.jsx("h2",{children:"AI 能力"}),o.jsx("p",{children:"AI 入口保留在高级诊断中;当前不自动调用,也不影响消息、通讯录和任务操作。"})]}),o.jsxs("span",{className:"read-only-note",children:[o.jsx(B,{name:"shield",size:14}),"按能力矩阵开放"]})]})}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"白名单事件"}),o.jsx("p",{children:"当前选中 Client 的已授权事件。"})]})}),o.jsx(yp,{events:t,selectedClientId:r})]}),o.jsxs("div",{className:"workspace-panel",children:[o.jsx("div",{className:"section-heading",children:o.jsxs("div",{children:[o.jsx("h2",{children:"操作审计"}),o.jsx("p",{children:"用于定位连接、任务和权限问题。"})]})}),o.jsx(xp,{audit:n})]})]})})]})}function Sp(){const[e,t]=T.useState(()=>sessionStorage.getItem("wxagent-token")||""),[n,r]=T.useState(()=>sessionStorage.getItem("wxagent-user")||""),[l,i]=T.useState("messages"),[s,u]=T.useState([]),[a,f]=T.useState(()=>sessionStorage.getItem("wxagent-client")||""),[v,h]=T.useState([]),[m,x]=T.useState([]),[k,S]=T.useState([]),[P,d]=T.useState(!1),[c,p]=T.useState(""),g=T.useCallback(()=>{sessionStorage.removeItem("wxagent-token"),sessionStorage.removeItem("wxagent-user"),sessionStorage.removeItem("wxagent-client"),t(""),r("")},[]),j=T.useCallback(async()=>{if(e){d(!0),p("");try{const[O,pe,ir,_l]=await Promise.all([Ye("/v1/nodes",{token:e}),Ye("/v1/tasks?limit=200",{token:e}),Ye("/v1/events?limit=200",{token:e}),Ye("/v1/audit?limit=200",{token:e})]);u(O.nodes||[]),h(pe.tasks||[]),x(ir.events||[]),S(_l.audit||[])}catch(O){O.status===401?g():p(O.message)}finally{d(!1)}}},[g,e]);T.useEffect(()=>{if(j(),!e)return;const O=window.setInterval(j,15e3);return()=>window.clearInterval(O)},[j,e]),T.useEffect(()=>{if(!s.length){f("");return}if(!s.some(O=>O.node_id===a)){const O=s.find(ul)||s[0];f(O.node_id),sessionStorage.setItem("wxagent-client",O.node_id)}},[s,a]);const C=O=>{f(O),sessionStorage.setItem("wxagent-client",O)},E=(O,pe)=>{sessionStorage.setItem("wxagent-token",O),sessionStorage.setItem("wxagent-user",pe),t(O),r(pe)},w=T.useMemo(()=>s.find(O=>O.node_id===a),[s,a]),I=kc.find(O=>O.id===l),z=(I==null?void 0:I.label)||"设置与诊断",Y=(I==null?void 0:I.description)||"连接、账号和高级诊断信息";return e?o.jsxs("div",{className:"app-shell",children:[o.jsx(cp,{view:l,setView:i,onLogout:g,username:n}),o.jsxs("main",{className:"main-area",children:[o.jsx(dp,{title:z,subtitle:Y,nodes:s,selectedClientId:a,onClientChange:C,onRefresh:j,loading:P}),c&&o.jsxs("div",{className:"global-error",role:"alert",children:[o.jsx(B,{name:"alert",size:16}),o.jsx("span",{children:c}),o.jsx("button",{onClick:()=>p(""),"aria-label":"关闭错误",children:o.jsx(B,{name:"close",size:16})})]}),o.jsx(fp,{nodes:s,selectedNode:w,onSettings:()=>i("settings")}),o.jsxs("div",{className:"page-content",children:[l==="messages"&&o.jsx(pp,{token:e,client:w,onSettings:()=>i("settings")}),l==="broadcast"&&o.jsx(hp,{client:w,onSettings:()=>i("settings")}),l==="contacts"&&o.jsx(mp,{token:e,client:w,onSettings:()=>i("settings")}),l==="tasks"&&o.jsx(vp,{tasks:v,selectedClientId:a}),l==="settings"&&o.jsx(wp,{nodes:s,events:m,audit:k,selectedClientId:a,onSelectClient:C})]}),o.jsxs("footer",{className:"footer",children:[o.jsx("span",{children:"WxAgent 工作台 · Client 独立隔离"}),o.jsx("span",{children:"写操作需通过真实能力与真机验收"})]})]})]}):o.jsx(ap,{onLogin:E})}wc(document.getElementById("root")).render(o.jsx(Sp,{})); diff --git a/control-plane/web/dist/index.html b/control-plane/web/dist/index.html index 9d9df32..a081dd7 100644 --- a/control-plane/web/dist/index.html +++ b/control-plane/web/dist/index.html @@ -6,7 +6,7 @@ WxAgent 工作台 - + diff --git a/control-plane/web/src/main.jsx b/control-plane/web/src/main.jsx index 502da44..99d065d 100644 --- a/control-plane/web/src/main.jsx +++ b/control-plane/web/src/main.jsx @@ -476,25 +476,47 @@ function ContactsView({ token, client, onSettings }) { const [groupsOnly, setGroupsOnly] = useState(false); const [query, setQuery] = useState(""); const [contacts, setContacts] = useState([]); + const [syncInfo, setSyncInfo] = useState(null); + const [hasMore, setHasMore] = useState(false); + const [syncing, setSyncing] = useState(false); + const [syncNotice, setSyncNotice] = useState(""); const [state, setState] = useState({ loading: false, error: "" }); const requestRef = useRef(0); - const load = useCallback(async () => { + const load = useCallback(async (append = false) => { if (!clientId || !accountId) return; const requestId = ++requestRef.current; setState({ loading: true, error: "" }); try { - const content = await readWithTask("/v1/reads/contacts", { node_id: clientId, account_id: accountId, limit: 200, groups_only: groupsOnly, contains: query }, token); + const params = new URLSearchParams({ limit: "200", offset: String(append ? contacts.length : 0), groups_only: String(groupsOnly) }); + if (query.trim()) params.set("contains", query.trim()); + const content = await api(dataPath(accountId, "contacts", `?${params.toString()}`), { token }); if (requestId !== requestRef.current) return; - setContacts(normalizeContacts(content)); + const nextContacts = normalizeContacts(content); + setContacts((current) => append ? [...current, ...nextContacts] : nextContacts); + setHasMore(Boolean(content.has_more)); + setSyncInfo(content.sync || null); setState({ loading: false, error: "" }); } catch (reason) { if (requestId === requestRef.current) setState({ loading: false, error: reason.message }); } - }, [accountId, clientId, groupsOnly, query, token]); - useEffect(() => { setContacts([]); if (clientId && accountId) load(); }, [clientId, accountId, groupsOnly]); + }, [accountId, clientId, contacts.length, groupsOnly, query, token]); + const syncContacts = async () => { + if (!clientId || !accountId || syncing) return; + setSyncing(true); + setSyncNotice(""); + try { + await api(dataPath(accountId, "refresh"), { token, method: "POST", body: { stream_key: "contacts" } }); + setSyncNotice("通讯录同步任务已提交;完成后点击“刷新缓存”查看最新数据。"); + } catch (reason) { + setState((current) => ({ ...current, error: reason.message })); + } finally { + setSyncing(false); + } + }; + useEffect(() => { setContacts([]); setHasMore(false); setSyncInfo(null); setSyncNotice(""); if (clientId && accountId) load(); }, [clientId, accountId, groupsOnly]); if (!client) return ; const visible = contacts.filter((contact) => !query || `${contact.title} ${contact.id} ${contact.detail}`.toLowerCase().includes(query.toLowerCase())); - return

通讯录

只读查看当前 Client 已授权的联系人和群聊。

{state.loading ? : state.error ? : visible.length === 0 ? :
{visible.map((contact) =>
{contact.title.slice(0, 1).toUpperCase()}
{contact.title}{contact.type === "Group" ? "群聊" : "联系人"} · {shortId(contact.id, 28)}{contact.detail ? ` · ${contact.detail}` : ""}
)}
}
; + return

通讯录

平台缓存 · {syncInfo?.state || "尚未同步"} · 最近同步 {formatTime(syncInfo?.last_success_at)}

{syncNotice && {syncNotice}}
{state.loading ? : state.error ? : visible.length === 0 ? :
{visible.map((contact) =>
{contact.title.slice(0, 1).toUpperCase()}
{contact.title}{contact.type === "Group" ? "群聊" : "联系人"} · {shortId(contact.id, 28)}{contact.detail ? ` · ${contact.detail}` : ""}
)}
}
; } function TasksView({ tasks, selectedClientId }) { diff --git a/node-agent/WxAgent.Core/RemoteControlClient.cs b/node-agent/WxAgent.Core/RemoteControlClient.cs index 67c6eeb..18b11a5 100644 --- a/node-agent/WxAgent.Core/RemoteControlClient.cs +++ b/node-agent/WxAgent.Core/RemoteControlClient.cs @@ -168,12 +168,15 @@ public sealed class RemoteControlClient : IDisposable public Task GetDataSyncStatusAsync( string accountId, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, + string streamKey = "messages") { RemoteAgentOptions.ValidateIdentifier(accountId, "accountId", 200); + if (streamKey is not ("messages" or "contacts")) + throw new ArgumentOutOfRangeException(nameof(streamKey)); return SendAuthenticatedAsync( HttpMethod.Get, - $"/v1/nodes/{Escape(_options.NodeId!)}/data/accounts/{Escape(accountId)}/sync-status", + $"/v1/nodes/{Escape(_options.NodeId!)}/data/accounts/{Escape(accountId)}/sync-status?stream_key={Escape(streamKey)}", null, cancellationToken); } diff --git a/node-agent/WxAgent.Core/RemoteDataSync.cs b/node-agent/WxAgent.Core/RemoteDataSync.cs index c17d306..6dbdf58 100644 --- a/node-agent/WxAgent.Core/RemoteDataSync.cs +++ b/node-agent/WxAgent.Core/RemoteDataSync.cs @@ -24,7 +24,21 @@ public sealed record RemoteSyncBatch( [property: JsonPropertyName("payload_hash")] string PayloadHash, [property: JsonPropertyName("coverage_state")] string CoverageState, [property: JsonPropertyName("conversations")] IReadOnlyList Conversations, - [property: JsonPropertyName("messages")] IReadOnlyList Messages); + [property: JsonPropertyName("messages")] IReadOnlyList Messages) +{ + [JsonPropertyName("contacts")] + public IReadOnlyList Contacts { get; init; } = []; + + [JsonPropertyName("contacts_snapshot_id")] + public string? ContactsSnapshotId { get; init; } +} + +public sealed record RemoteSyncContact( + [property: JsonPropertyName("chat_id")] string ChatId, + [property: JsonPropertyName("chat_type")] ReportingChatType ChatType, + [property: JsonPropertyName("display_name")] string DisplayName, + [property: JsonPropertyName("remark")] string Remark, + [property: JsonPropertyName("observed_at")] DateTimeOffset ObservedAt); public sealed record RemoteSyncConversation( [property: JsonPropertyName("chat_id")] string ChatId, @@ -276,14 +290,19 @@ public static class RemoteDataBatchAuthorization ReportingAuthorization.Check(config, batch.AccountId, conversation.ChatId, conversation.ChatType, ReportingDataType.TaskResult).Allowed).ToArray(); var messages = batch.Messages.Where(message => ReportingAuthorization.Check(config, batch.AccountId, message.ChatId, message.ChatType, ReportingDataType.Message).Allowed).ToArray(); - var dropped = conversations.Length != batch.Conversations.Count || messages.Length != batch.Messages.Count; + var contacts = batch.Contacts.Where(contact => + ReportingAuthorization.Check(config, batch.AccountId, contact.ChatId, contact.ChatType, ReportingDataType.TaskResult).Allowed).ToArray(); + var dropped = conversations.Length != batch.Conversations.Count || messages.Length != batch.Messages.Count || contacts.Length != batch.Contacts.Count; reason = dropped ? "SomeRecordsDroppedByReporting" : "Authorized"; - if (conversations.Length == 0 && messages.Length == 0 && (batch.Conversations.Count > 0 || batch.Messages.Count > 0)) + if (conversations.Length == 0 && messages.Length == 0 && contacts.Length == 0 && (batch.Conversations.Count > 0 || batch.Messages.Count > 0 || batch.Contacts.Count > 0)) { reason = "AllRecordsDroppedByReporting"; return null; } - return dropped ? batch with { CoverageState = "partial", Conversations = conversations, Messages = messages } : batch; + if (!dropped) + return batch; + var filteredBatch = batch with { CoverageState = "partial", Conversations = conversations, Messages = messages, Contacts = contacts }; + return filteredBatch with { PayloadHash = ComputePayloadHash(filteredBatch) }; } } @@ -440,7 +459,21 @@ public sealed class RemoteDataBatchQueue { foreach (var value in new[] { batch.NodeId, batch.AccountId, batch.BatchId, batch.SourceGeneration, batch.StreamKey, batch.PayloadHash }) RemoteAgentOptions.ValidateIdentifier(value, "sync batch field", 512); - if (batch.Sequence < 1 || batch.Conversations.Count + batch.Messages.Count > RemoteDataProtocol.MaxBatchMessages) + if (batch.Sequence < 1 || batch.Conversations.Count + batch.Messages.Count > RemoteDataProtocol.MaxBatchMessages + || batch.Contacts.Count > RemoteDataProtocol.MaxBatchItems + || batch.StreamKey is not ("messages" or "contacts") + || batch.StreamKey == "contacts" && (string.IsNullOrWhiteSpace(batch.ContactsSnapshotId) || batch.Conversations.Count > 0 || batch.Messages.Count > 0) + || batch.StreamKey == "messages" && (batch.Contacts.Count > 0 || batch.ContactsSnapshotId is not null)) throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "The sync batch sequence or item count is invalid."); + if (batch.ContactsSnapshotId is not null) + RemoteAgentOptions.ValidateIdentifier(batch.ContactsSnapshotId, "contacts snapshot", 512); + foreach (var contact in batch.Contacts) + { + if (string.IsNullOrWhiteSpace(contact.ChatId) || contact.ChatId.Length > 512 + || contact.DisplayName.Length > 2048 || contact.Remark.Length > 2048 + || contact.ChatType is not (ReportingChatType.Group or ReportingChatType.Private) + || contact.ObservedAt == default) + throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "The contact record is invalid."); + } } } diff --git a/node-agent/WxAgent.Service/RemoteAgentHostedService.cs b/node-agent/WxAgent.Service/RemoteAgentHostedService.cs index 4f8f60d..43d0290 100644 --- a/node-agent/WxAgent.Service/RemoteAgentHostedService.cs +++ b/node-agent/WxAgent.Service/RemoteAgentHostedService.cs @@ -136,7 +136,7 @@ public sealed class RemoteAgentHostedService( var tasks = await client.PollTasksAsync(accountId, stoppingToken, waitSeconds: 5); foreach (var task in tasks) { - await ProcessTaskAsync(client, remote, reporting, ledger, task, stoppingToken); + await ProcessTaskAsync(client, remote, reporting, ledger, task, dataQueue, syncState, stoppingToken); } } @@ -284,40 +284,47 @@ public sealed class RemoteAgentHostedService( { foreach (var account in reporting.Accounts.Where(item => item.Enabled)) { - var local = state.GetLatest(account.AccountId, "messages"); - RemoteDataSyncStatus remote; - try + var revoked = false; + foreach (var streamKey in new[] { "messages", "contacts" }) { - remote = await client.GetDataSyncStatusAsync(account.AccountId, cancellationToken).ConfigureAwait(false); - } - catch (RemoteClientException exception) when (exception.StatusCode == 404 && exception.Code == "NotFound") - { - // Older control planes do not expose the reconciliation endpoint; retain the legacy ACK path. - continue; - } - catch (RemoteClientException exception) when (exception.StatusCode == 403 && exception.Code == "AccountNotAuthorized") - { - var droppedOnRevoke = queue.DropAllForAccount(account.AccountId); - blockedAccountIds.Add(account.AccountId); - logger.LogWarning( - "Data sync authorization is revoked; accountId={AccountId}; pending content was discarded and collection is paused; droppedPendingBatches={DroppedPendingBatches}.", - account.AccountId, droppedOnRevoke); - continue; - } - catch (HttpRequestException) - { - // A disconnected platform must not prevent local DB collection. - return; - } + var local = state.GetLatest(account.AccountId, streamKey); + RemoteDataSyncStatus remote; + try + { + remote = await client.GetDataSyncStatusAsync(account.AccountId, cancellationToken, streamKey).ConfigureAwait(false); + } + catch (RemoteClientException exception) when (exception.StatusCode == 404 && exception.Code == "NotFound") + { + // Older control planes do not expose the reconciliation endpoint; retain the legacy ACK path. + continue; + } + catch (RemoteClientException exception) when (exception.StatusCode == 403 && exception.Code == "AccountNotAuthorized") + { + var droppedOnRevoke = queue.DropAllForAccount(account.AccountId); + blockedAccountIds.Add(account.AccountId); + logger.LogWarning( + "Data sync authorization is revoked; accountId={AccountId}; pending content was discarded and collection is paused; droppedPendingBatches={DroppedPendingBatches}.", + account.AccountId, droppedOnRevoke); + revoked = true; + break; + } + catch (HttpRequestException) + { + // A disconnected platform must not prevent local DB collection. + return; + } - if (blockedAccountIds.Remove(account.AccountId)) - logger.LogInformation("Data sync authorization was restored; accountId={AccountId}; collection is resumed.", account.AccountId); - if (!state.Reconcile(remote, account.AccountId, "messages")) + if (blockedAccountIds.Remove(account.AccountId)) + logger.LogInformation("Data sync authorization was restored; accountId={AccountId}; collection is resumed.", account.AccountId); + if (!state.Reconcile(remote, account.AccountId, streamKey)) + continue; + var dropped = queue.DropForAccount(account.AccountId, streamKey); + logger.LogWarning( + "Data sync checkpoint reconciled with the platform; accountId={AccountId}; stream={StreamKey}; localSequence={LocalSequence}; platformSequence={PlatformSequence}; droppedPendingBatches={DroppedPendingBatches}.", + account.AccountId, streamKey, local.ConfirmedSequence, remote.ConfirmedSequence, dropped); + } + if (revoked) continue; - var dropped = queue.DropForAccount(account.AccountId, "messages"); - logger.LogWarning( - "Data sync checkpoint reconciled with the platform; accountId={AccountId}; localSequence={LocalSequence}; platformSequence={PlatformSequence}; droppedPendingBatches={DroppedPendingBatches}.", - account.AccountId, local.ConfirmedSequence, remote.ConfirmedSequence, dropped); } } @@ -347,6 +354,8 @@ public sealed class RemoteAgentHostedService( ReportingConfig reporting, RemoteTaskLedger ledger, RemoteTaskEnvelope task, + RemoteDataBatchQueue? dataQueue, + RemoteDataSyncStateStore? syncState, CancellationToken cancellationToken) { if (task.Status is RemoteTaskStatus.Accepted or RemoteTaskStatus.Running) @@ -415,10 +424,56 @@ public sealed class RemoteAgentHostedService( var renewed = await client.RenewTaskAsync(started, cancellationToken); if (task.Kind == "sync-data") { - var result = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, + var streamKey = task.Payload.ValueKind == JsonValueKind.Object + && task.Payload.TryGetProperty("stream_key", out var streamElement) + && streamElement.ValueKind == JsonValueKind.String + ? streamElement.GetString() ?? "messages" + : "messages"; + if (streamKey == "contacts") + { + try + { + if (dataQueue is null || syncState is null) + throw new ServiceException("SyncUnavailable", 503, "The contacts sync queue is not available."); + var queued = await QueueContactsSnapshotAsync(remote.NodeId!, reporting, task.AccountId, dataQueue, syncState, cancellationToken); + var message = queued ? "通讯录同步已加入节点本地队列。" : "已有通讯录同步待上传,本次请求未重复排队。"; + var result = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, + RemoteTaskStatus.Succeeded, null, message, false, null, Guid.NewGuid().ToString("N")); + ledger.Complete(result); + await ReportResultAsync(client, ledger, result, reporting, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (ServiceException exception) + { + var result = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, + RemoteTaskStatus.Failed, exception.Code, "通讯录同步失败。", false, null, Guid.NewGuid().ToString("N")); + ledger.Complete(result); + await ReportResultAsync(client, ledger, result, reporting, cancellationToken); + } + catch (WxAgentException exception) + { + var result = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, + RemoteTaskStatus.Failed, exception.Code.ToString(), "通讯录同步失败。", false, null, Guid.NewGuid().ToString("N")); + ledger.Complete(result); + await ReportResultAsync(client, ledger, result, reporting, cancellationToken); + } + return; + } + if (streamKey != "messages") + { + var result = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, + RemoteTaskStatus.Failed, "InvalidTaskPayload", "The sync stream is unsupported.", false, null, Guid.NewGuid().ToString("N")); + ledger.Complete(result); + await ReportResultAsync(client, ledger, result, reporting, cancellationToken); + return; + } + var messagesResult = new RemoteTaskResult(task.TaskId, task.AccountId, renewed.LeaseGeneration, RemoteTaskStatus.Succeeded, null, "后台数据同步已受理;采集器将在下一轮同步周期执行。", false, null, Guid.NewGuid().ToString("N")); - ledger.Complete(result); - await ReportResultAsync(client, ledger, result, reporting, cancellationToken); + ledger.Complete(messagesResult); + await ReportResultAsync(client, ledger, messagesResult, reporting, cancellationToken); return; } @@ -600,6 +655,105 @@ public sealed class RemoteAgentHostedService( } } + private async Task QueueContactsSnapshotAsync( + string nodeId, + ReportingConfig reporting, + string accountId, + RemoteDataBatchQueue dataQueue, + RemoteDataSyncStateStore syncState, + CancellationToken cancellationToken) + { + if (dataQueue.Pending().Any(batch => batch.AccountId == accountId && batch.StreamKey == "contacts")) + return false; + + var scopes = AuthorizedChatScopes(reporting, accountId); + RequireScopes(reporting, accountId, scopes); + var authorizedContacts = await ReadContactsForScopesAsync(accountId, scopes, cancellationToken); + var authorizedById = authorizedContacts.ToDictionary(contact => contact.Id, StringComparer.Ordinal); + var groupIds = new HashSet(StringComparer.Ordinal); + for (var offset = 0; ;) + { + var page = await backend.ContactsAsync(accountId, null, true, 200, offset, cancellationToken); + foreach (var group in page.Items) + { + if (authorizedById.ContainsKey(group.Id)) + groupIds.Add(group.Id); + } + if (!page.HasMore) + break; + var nextOffset = page.NextOffset ?? offset + page.Items.Count; + if (nextOffset <= offset) + throw new ServiceException("InvalidPagination", 502, "The contacts page did not advance."); + offset = nextOffset; + } + + var observedAt = DateTimeOffset.UtcNow; + var contacts = authorizedContacts + .Select(contact => new RemoteSyncContact( + contact.Id, + groupIds.Contains(contact.Id) ? ReportingChatType.Group : ReportingChatType.Private, + contact.DisplayName ?? string.Empty, + contact.Remark ?? string.Empty, + observedAt)) + .Where(contact => scopes.Any(scope => scope.ChatType == contact.ChatType + && (scope.ChatId == "*" || scope.ChatId == contact.ChatId))) + .GroupBy(contact => contact.ChatId, StringComparer.Ordinal) + .Select(group => group.First()) + .OrderBy(contact => contact.ChatType) + .ThenBy(contact => contact.ChatId, StringComparer.Ordinal) + .ToArray(); + + const string streamKey = "contacts"; + var checkpoint = syncState.GetLatest(accountId, streamKey); + var sourceGeneration = syncState.GetLatest(accountId, "messages").SourceGeneration; + if (string.IsNullOrWhiteSpace(sourceGeneration)) + sourceGeneration = checkpoint.SourceGeneration; + if (string.IsNullOrWhiteSpace(sourceGeneration)) + throw new ServiceException("SyncUnavailable", 503, "The account data source has not been initialized."); + var sequence = checkpoint.SourceGeneration == sourceGeneration ? checkpoint.ConfirmedSequence + 1 : 1; + var snapshotId = Guid.NewGuid().ToString("N"); + var pendingContacts = new List(); + + RemoteSyncBatch CreateBatch(IReadOnlyList items, bool complete) + { + var batch = new RemoteSyncBatch( + nodeId, accountId, Guid.NewGuid().ToString("N"), sourceGeneration, streamKey, sequence, + "{}", "{}", string.Empty, complete ? "complete" : "partial", [], []) + { + Contacts = items, + ContactsSnapshotId = snapshotId + }; + return batch with { PayloadHash = RemoteDataBatchAuthorization.ComputePayloadHash(batch) }; + } + + void Enqueue(bool complete) + { + var batch = CreateBatch(pendingContacts.ToArray(), complete); + if (JsonSerializer.SerializeToUtf8Bytes(batch, RemoteJson.Options).Length > RemoteDataProtocol.MaxBatchBytes - 2048) + throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "A contacts sync batch exceeds the size limit."); + var result = dataQueue.Enqueue(reporting, batch); + if (!result.Accepted) + throw new ServiceException(result.Reason ?? "SyncQueueFull", 503, "The contacts sync batch was not queued."); + sequence++; + pendingContacts.Clear(); + } + + foreach (var contact in contacts) + { + pendingContacts.Add(contact); + if (pendingContacts.Count <= RemoteDataProtocol.MaxBatchItems + && JsonSerializer.SerializeToUtf8Bytes(CreateBatch(pendingContacts, false), RemoteJson.Options).Length <= RemoteDataProtocol.MaxBatchBytes - 2048) + continue; + pendingContacts.RemoveAt(pendingContacts.Count - 1); + if (pendingContacts.Count == 0) + throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "A contact record exceeds the sync batch size limit."); + Enqueue(false); + pendingContacts.Add(contact); + } + Enqueue(true); + return true; + } + private async Task> ReadContactsForScopesAsync( string accountId, IReadOnlyList scopes, CancellationToken cancellationToken) { diff --git a/tests/node-agent/WxAgent.Core.Tests/RemoteDataSyncTests.cs b/tests/node-agent/WxAgent.Core.Tests/RemoteDataSyncTests.cs index 8bdb477..1c0c044 100644 --- a/tests/node-agent/WxAgent.Core.Tests/RemoteDataSyncTests.cs +++ b/tests/node-agent/WxAgent.Core.Tests/RemoteDataSyncTests.cs @@ -160,4 +160,29 @@ public sealed class RemoteDataSyncTests private static RemoteSyncMessage Message(string messageId, string chatId) => new( messageId, chatId, ReportingChatType.Private, "source-" + messageId, "incoming", "text", "hello", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "wx-1", "payload-" + messageId); + + [Fact] + public void ContactBatches_FilterUnauthorizedContactsAndRehash() + { + var config = ConfigFor("chat-a"); + var batch = new RemoteSyncBatch( + "node-a", "account-a", "contacts-1", "generation-a", "contacts", 1, "{}", "{}", string.Empty, "complete", [], []) + { + ContactsSnapshotId = "snapshot-1", + Contacts = + [ + new RemoteSyncContact("chat-a", ReportingChatType.Private, "Allowed", "", DateTimeOffset.UtcNow), + new RemoteSyncContact("chat-b", ReportingChatType.Private, "Filtered", "", DateTimeOffset.UtcNow) + ] + }; + + var filtered = RemoteDataBatchAuthorization.Filter(config, batch, out var reason); + + Assert.NotNull(filtered); + Assert.Equal("SomeRecordsDroppedByReporting", reason); + Assert.Equal("partial", filtered!.CoverageState); + Assert.Single(filtered.Contacts); + Assert.Equal("chat-a", filtered.Contacts[0].ChatId); + Assert.Equal(RemoteDataBatchAuthorization.ComputePayloadHash(filtered), filtered.PayloadHash); + } }