@shaxpir/duiduidui-models 1.19.0 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,6 +57,12 @@ export interface ChatScratchpad {
57
57
  extended_thinking?: boolean;
58
58
  [key: string]: any;
59
59
  }
60
+ export interface ChatStub {
61
+ id: ContentId;
62
+ title?: string;
63
+ last_message_preview?: string;
64
+ updated_at: CompactDateTime;
65
+ }
60
66
  export interface ChatPayload {
61
67
  title?: string;
62
68
  messages: ChatMessage[];
@@ -80,6 +86,7 @@ export declare class Chat extends Content {
80
86
  setScratchpad(scratchpad: ChatScratchpad): void;
81
87
  updateScratchpadField(key: string, value: any): void;
82
88
  clearScratchpad(): void;
89
+ toStub(): ChatStub;
83
90
  appendMessage(message: ChatMessage): void;
84
91
  setBreakpoint(messageIndex: number, ttl: 'ephemeral' | 'standard'): void;
85
92
  removeBreakpoint(messageIndex: number): void;
@@ -82,6 +82,25 @@ class Chat extends Content_1.Content {
82
82
  batch.setPathValue(['payload', 'scratchpad'], {});
83
83
  batch.commit();
84
84
  }
85
+ // ---- Stub generation ----
86
+ toStub() {
87
+ this.checkDisposed('Chat.toStub');
88
+ const msgs = this.messages.values;
89
+ let preview;
90
+ if (msgs.length > 0) {
91
+ const lastMsg = msgs[msgs.length - 1];
92
+ const textBlock = lastMsg.content.find(b => b.type === 'text');
93
+ if (textBlock && textBlock.type === 'text') {
94
+ preview = textBlock.text.slice(0, 100);
95
+ }
96
+ }
97
+ return {
98
+ id: this.id,
99
+ title: this.title,
100
+ last_message_preview: preview,
101
+ updated_at: this.updatedAt.utc_time,
102
+ };
103
+ }
85
104
  // ---- Message operations ----
86
105
  appendMessage(message) {
87
106
  this.checkDisposed('Chat.appendMessage');
@@ -2,6 +2,7 @@ import { Doc } from '@shaxpir/sharedb/lib/client';
2
2
  import { CompactDate, CompactDateTime, MultiTime } from "@shaxpir/shaxpir-common";
3
3
  import { ShareSync } from '../repo';
4
4
  import { ArrayView } from './ArrayView';
5
+ import { ChatStub } from './Chat';
5
6
  import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
6
7
  import { Conditions } from './Condition';
7
8
  import { Session } from './Session';
@@ -14,7 +15,7 @@ export interface WorkspacePayload {
14
15
  devices: ContentId[];
15
16
  sessions: MultiTime[];
16
17
  collections: string[];
17
- chats: ContentId[];
18
+ chats: ChatStub[];
18
19
  journey: {
19
20
  [key: string]: CompactDateTime;
20
21
  };
@@ -44,7 +45,17 @@ export declare class Workspace extends Content {
44
45
  static create(userId: ContentId, payload: WorkspacePayload): Workspace;
45
46
  get sessions(): ArrayView<MultiTime>;
46
47
  get collections(): ArrayView<string>;
47
- get chats(): ArrayView<ContentId>;
48
+ get chats(): ArrayView<ChatStub>;
49
+ /**
50
+ * Update a chat stub in the chats array and move it to the top.
51
+ * If the stub doesn't exist yet, it is prepended.
52
+ *
53
+ * When the stub already exists but isn't at the top, we remove it
54
+ * entirely and insert a fresh copy at index 0 (cleaner for OT than
55
+ * editing fields and then moving). When it's already at the top,
56
+ * we update its fields in place to avoid unnecessary churn.
57
+ */
58
+ upsertChatStub(stub: ChatStub): void;
48
59
  get devices(): ArrayView<ContentId>;
49
60
  get uploadedAvatars(): ArrayView<ContentId>;
50
61
  acquireSessionForUtcTime(utcTime: CompactDateTime): Promise<Session | null>;
@@ -85,6 +85,34 @@ class Workspace extends Content_1.Content {
85
85
  this.checkDisposed("Workspace.chats");
86
86
  return this._chatsView;
87
87
  }
88
+ /**
89
+ * Update a chat stub in the chats array and move it to the top.
90
+ * If the stub doesn't exist yet, it is prepended.
91
+ *
92
+ * When the stub already exists but isn't at the top, we remove it
93
+ * entirely and insert a fresh copy at index 0 (cleaner for OT than
94
+ * editing fields and then moving). When it's already at the top,
95
+ * we update its fields in place to avoid unnecessary churn.
96
+ */
97
+ upsertChatStub(stub) {
98
+ this.checkDisposed("Workspace.upsertChatStub");
99
+ const index = this._chatsView.firstIndexWhere(s => s.id === stub.id);
100
+ if (index === 0) {
101
+ // Already at the top — update fields in place
102
+ this._chatsView.setObjectValueAtIndex(0, 'title', stub.title);
103
+ this._chatsView.setObjectValueAtIndex(0, 'last_message_preview', stub.last_message_preview);
104
+ this._chatsView.setObjectValueAtIndex(0, 'updated_at', stub.updated_at);
105
+ }
106
+ else if (index > 0) {
107
+ // Exists but not at top — remove and reinsert at head
108
+ this._chatsView.removeAt(index);
109
+ this._chatsView.unshift(stub);
110
+ }
111
+ else {
112
+ // New chat — prepend
113
+ this._chatsView.unshift(stub);
114
+ }
115
+ }
88
116
  // NOTE: As the user adds devices, we will always add new devices to the end of the array.
89
117
  // So the most-recently-added device will always be at the end.
90
118
  get devices() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaxpir/duiduidui-models",
3
- "version": "1.19.0",
3
+ "version": "1.20.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/shaxpir/duiduidui-models"