@memberjunction/ng-conversations 6.1.0-edge.2 → 6.1.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +180 -4
- package/README.md +32 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +159 -9
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +497 -106
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts +14 -2
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.js +37 -19
- package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +7 -3
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +78 -159
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +287 -7
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +850 -56
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +98 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +218 -69
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +206 -0
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -0
- package/dist/lib/services/conversation-detail-window.store.js +503 -0
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -0
- package/dist/lib/services/search.service.d.ts +10 -4
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +16 -11
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/agent-artifact-summary.d.ts +54 -0
- package/dist/lib/utils/agent-artifact-summary.d.ts.map +1 -0
- package/dist/lib/utils/agent-artifact-summary.js +49 -0
- package/dist/lib/utils/agent-artifact-summary.js.map +1 -0
- package/dist/lib/utils/conversation-detail-window.d.ts +49 -0
- package/dist/lib/utils/conversation-detail-window.d.ts.map +1 -0
- package/dist/lib/utils/conversation-detail-window.js +86 -0
- package/dist/lib/utils/conversation-detail-window.js.map +1 -0
- package/dist/lib/utils/date-jump.d.ts +65 -0
- package/dist/lib/utils/date-jump.d.ts.map +1 -0
- package/dist/lib/utils/date-jump.js +106 -0
- package/dist/lib/utils/date-jump.js.map +1 -0
- package/package.json +32 -32
|
@@ -2,11 +2,22 @@ import { Injectable } from '@angular/core';
|
|
|
2
2
|
import { BehaviorSubject, Subject } from 'rxjs';
|
|
3
3
|
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
4
4
|
import { RunView, Metadata } from '@memberjunction/core';
|
|
5
|
+
import { EscapeSQLString } from '@memberjunction/global';
|
|
5
6
|
import * as i0 from "@angular/core";
|
|
6
7
|
/**
|
|
7
8
|
* Service for searching across conversations, messages, and artifacts
|
|
8
9
|
* Provides debounced search with result ranking and filtering
|
|
9
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* NOTE ON `LIKE` ESCAPING
|
|
13
|
+
*
|
|
14
|
+
* The predicates below interpolate the user's query into `LIKE '%…%'` via `EscapeSQLString`, which
|
|
15
|
+
* neutralises quotes but NOT LIKE metacharacters — `%`, `_` and `[` in a search term are still
|
|
16
|
+
* treated as wildcards rather than literal text. That is this service's long-standing search
|
|
17
|
+
* behaviour, so it is left as-is deliberately; changing it would change what users' searches match.
|
|
18
|
+
* If literal matching is wanted, escape the metacharacters and append `ESCAPE '\\'` (see
|
|
19
|
+
* `escapeLikeValue()` in `@memberjunction/core`, `generic/runQuerySQLFilterImplementations.ts`).
|
|
20
|
+
*/
|
|
10
21
|
export class SearchService {
|
|
11
22
|
_searchQuery$ = new Subject();
|
|
12
23
|
_searchFilter$ = new BehaviorSubject('all');
|
|
@@ -114,7 +125,7 @@ export class SearchService {
|
|
|
114
125
|
const rv = RunView.FromMetadataProvider(this.Provider);
|
|
115
126
|
const lowerQuery = query.toLowerCase();
|
|
116
127
|
let filter = `EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0)`;
|
|
117
|
-
filter += ` AND (LOWER(Name) LIKE '%${
|
|
128
|
+
filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;
|
|
118
129
|
if (dateRange.start) {
|
|
119
130
|
filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;
|
|
120
131
|
}
|
|
@@ -142,7 +153,7 @@ export class SearchService {
|
|
|
142
153
|
const lowerQuery = query.toLowerCase();
|
|
143
154
|
// First get conversations in this environment
|
|
144
155
|
let filter = `ConversationID IN (SELECT ID FROM vwConversations WHERE EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0))`;
|
|
145
|
-
filter += ` AND LOWER(Message) LIKE '%${
|
|
156
|
+
filter += ` AND LOWER(Message) LIKE '%${EscapeSQLString(lowerQuery)}%'`;
|
|
146
157
|
filter += ` AND (HiddenToUser IS NULL OR HiddenToUser=0)`;
|
|
147
158
|
if (dateRange.start) {
|
|
148
159
|
filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;
|
|
@@ -172,7 +183,7 @@ export class SearchService {
|
|
|
172
183
|
const lowerQuery = query.toLowerCase();
|
|
173
184
|
// Search artifacts directly by name and description
|
|
174
185
|
let filter = `EnvironmentID='${environmentId}'`;
|
|
175
|
-
filter += ` AND (LOWER(Name) LIKE '%${
|
|
186
|
+
filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;
|
|
176
187
|
if (dateRange.start) {
|
|
177
188
|
filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;
|
|
178
189
|
}
|
|
@@ -228,7 +239,7 @@ export class SearchService {
|
|
|
228
239
|
)`;
|
|
229
240
|
let filter = `EnvironmentID='${environmentId}'`;
|
|
230
241
|
filter += ` AND (OwnerID IS NULL OR ${ownerFilter} OR ${permissionSubquery})`;
|
|
231
|
-
filter += ` AND (LOWER(Name) LIKE '%${
|
|
242
|
+
filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;
|
|
232
243
|
if (dateRange.start) {
|
|
233
244
|
filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;
|
|
234
245
|
}
|
|
@@ -271,7 +282,7 @@ export class SearchService {
|
|
|
271
282
|
SELECT ID FROM [${c.SchemaName}].[${c.BaseView}] WHERE UserID='${currentUser.ID}'
|
|
272
283
|
)
|
|
273
284
|
))`;
|
|
274
|
-
filter += ` AND (LOWER(Name) LIKE '%${
|
|
285
|
+
filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;
|
|
275
286
|
if (dateRange.start) {
|
|
276
287
|
filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;
|
|
277
288
|
}
|
|
@@ -436,12 +447,6 @@ export class SearchService {
|
|
|
436
447
|
return text;
|
|
437
448
|
return text.substring(0, maxLength) + '...';
|
|
438
449
|
}
|
|
439
|
-
/**
|
|
440
|
-
* Escape SQL special characters
|
|
441
|
-
*/
|
|
442
|
-
escapeSQL(value) {
|
|
443
|
-
return value.replace(/'/g, "''");
|
|
444
|
-
}
|
|
445
450
|
/**
|
|
446
451
|
* Set search filter
|
|
447
452
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../../src/lib/services/search.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, Subject, of } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, switchMap, map } from 'rxjs/operators';\nimport {\n MJConversationEntity,\n MJConversationDetailEntity,\n MJConversationArtifactEntity,\n MJCollectionEntity,\n MJCollectionArtifactEntity,\n MJTaskEntity,\n MJArtifactEntity\n} from '@memberjunction/core-entities';\nimport { RunView, UserInfo, Metadata, IMetadataProvider } from '@memberjunction/core';\n\n/**\n * Types of searchable content\n */\nexport type SearchResultType = 'conversation' | 'message' | 'artifact' | 'collection' | 'task';\n\n/**\n * Filter options for search\n */\nexport type SearchFilter = 'all' | 'conversations' | 'messages' | 'artifacts' | 'collections' | 'tasks';\n\n/**\n * Unified search result\n */\nexport interface SearchResult {\n id: string;\n type: SearchResultType;\n title: string;\n preview: string;\n matchedText?: string;\n conversationId?: string;\n conversationName?: string;\n artifactType?: string;\n collectionId?: string;\n collectionName?: string;\n createdAt: Date;\n relevanceScore: number;\n}\n\n/**\n * Search results grouped by type\n */\nexport interface GroupedSearchResults {\n conversations: SearchResult[];\n messages: SearchResult[];\n artifacts: SearchResult[];\n collections: SearchResult[];\n tasks: SearchResult[];\n total: number;\n}\n\n/**\n * Date range filter\n */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\n/**\n * Service for searching across conversations, messages, and artifacts\n * Provides debounced search with result ranking and filtering\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class SearchService {\n private _searchQuery$ = new Subject<string>();\n private _searchFilter$ = new BehaviorSubject<SearchFilter>('all');\n private _dateRange$ = new BehaviorSubject<DateRange>({ start: null, end: null });\n private _isSearching$ = new BehaviorSubject<boolean>(false);\n private _searchResults$ = new BehaviorSubject<GroupedSearchResults>({\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n });\n\n // Recent searches stored in memory (could be persisted to localStorage)\n private recentSearches: string[] = [];\n private readonly MAX_RECENT_SEARCHES = 10;\n\n // Public observables\n public readonly searchQuery$ = this._searchQuery$.asObservable();\n public readonly searchFilter$ = this._searchFilter$.asObservable();\n public readonly dateRange$ = this._dateRange$.asObservable();\n public readonly isSearching$ = this._isSearching$.asObservable();\n public readonly searchResults$ = this._searchResults$.asObservable();\n\n private _provider: IMetadataProvider | null = null;\n\n constructor() {\n this.initializeSearch();\n this.loadRecentSearches();\n }\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n /**\n * Initialize debounced search\n */\n private initializeSearch(): void {\n this._searchQuery$.pipe(\n debounceTime(300),\n distinctUntilChanged()\n ).subscribe(query => {\n if (query && query.trim().length > 0) {\n this.addToRecentSearches(query);\n }\n });\n }\n\n /**\n * Search across all content types\n */\n public async search(\n query: string,\n environmentId: string,\n currentUser: UserInfo\n ): Promise<GroupedSearchResults> {\n if (!query || query.trim().length === 0) {\n const emptyResults: GroupedSearchResults = {\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n };\n this._searchResults$.next(emptyResults);\n return emptyResults;\n }\n\n this._isSearching$.next(true);\n this._searchQuery$.next(query);\n\n try {\n const filter = this._searchFilter$.value;\n const dateRange = this._dateRange$.value;\n\n // Search based on active filter\n const [conversations, messages, artifacts, collections, tasks] = await Promise.all([\n filter === 'all' || filter === 'conversations'\n ? this.searchConversations(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'messages'\n ? this.searchMessages(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'artifacts'\n ? this.searchArtifacts(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'collections'\n ? this.searchCollections(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'tasks'\n ? this.searchTasks(query, environmentId, currentUser, dateRange)\n : Promise.resolve([])\n ]);\n\n const results: GroupedSearchResults = {\n conversations,\n messages,\n artifacts,\n collections,\n tasks,\n total: conversations.length + messages.length + artifacts.length + collections.length + tasks.length\n };\n\n this._searchResults$.next(results);\n return results;\n } finally {\n this._isSearching$.next(false);\n }\n }\n\n /**\n * Search conversations by name and description\n */\n private async searchConversations(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n let filter = `EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0)`;\n filter += ` AND (LOWER(Name) LIKE '%${this.escapeSQL(lowerQuery)}%' OR LOWER(Description) LIKE '%${this.escapeSQL(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJConversationEntity>(\n {\n EntityName: 'MJ: Conversations',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search conversations:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(conv => this.mapConversationToSearchResult(conv, query));\n }\n\n /**\n * Search message content\n */\n private async searchMessages(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // First get conversations in this environment\n let filter = `ConversationID IN (SELECT ID FROM vwConversations WHERE EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0))`;\n filter += ` AND LOWER(Message) LIKE '%${this.escapeSQL(lowerQuery)}%'`;\n filter += ` AND (HiddenToUser IS NULL OR HiddenToUser=0)`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJConversationDetailEntity>(\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: filter,\n OrderBy: '__mj_CreatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search messages:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(msg => this.mapMessageToSearchResult(msg, query));\n }\n\n /**\n * Search artifacts by name and description\n * Includes artifacts from both conversations and collections\n */\n private async searchArtifacts(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // Search artifacts directly by name and description\n let filter = `EnvironmentID='${environmentId}'`;\n filter += ` AND (LOWER(Name) LIKE '%${this.escapeSQL(lowerQuery)}%' OR LOWER(Description) LIKE '%${this.escapeSQL(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJArtifactEntity>(\n {\n EntityName: 'MJ: Artifacts',\n ExtraFilter: `${filter} AND (Visibility IS NULL OR Visibility='Always')`,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search artifacts:', result.ErrorMessage);\n return [];\n }\n\n const searchResults: SearchResult[] = [];\n\n // For each artifact, check if it's in a collection for context\n for (const artifact of result.Results) {\n // Check for collection associations\n const collResult = await rv.RunView<MJCollectionArtifactEntity>(\n {\n EntityName: 'MJ: Collection Artifacts',\n ExtraFilter: `ArtifactVersionID IN (\n SELECT ID FROM [__mj].[vwArtifactVersions] WHERE ArtifactID='${artifact.ID}'\n )`,\n MaxRows: 1,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (collResult.Success && collResult.Results && collResult.Results.length > 0) {\n const collArtifact = collResult.Results[0];\n searchResults.push(\n this.mapArtifactToSearchResult(\n artifact,\n query,\n collArtifact.CollectionID,\n collArtifact.Collection\n )\n );\n } else {\n // No collection association\n searchResults.push(this.mapArtifactToSearchResult(artifact, query));\n }\n }\n\n return searchResults;\n }\n\n /**\n * Search collections by name and description\n * Includes both owned collections and shared collections user has access to\n */\n private async searchCollections(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n const ownerFilter = `OwnerID='${currentUser.ID}'`;\n const permissionSubquery = `ID IN (\n SELECT CollectionID\n FROM [__mj].[vwCollectionPermissions]\n WHERE UserID='${currentUser.ID}'\n )`;\n\n let filter = `EnvironmentID='${environmentId}'`;\n filter += ` AND (OwnerID IS NULL OR ${ownerFilter} OR ${permissionSubquery})`;\n filter += ` AND (LOWER(Name) LIKE '%${this.escapeSQL(lowerQuery)}%' OR LOWER(Description) LIKE '%${this.escapeSQL(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJCollectionEntity>(\n {\n EntityName: 'MJ: Collections',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search collections:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(coll => this.mapCollectionToSearchResult(coll, query));\n }\n\n /**\n * Search tasks by name, description, and notes\n * Only includes tasks in conversations user has access to\n */\n private async searchTasks(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // Build filter using same logic as TasksFullViewComponent\n const md = this.Provider;\n const cd = md.EntityByName('MJ: Conversation Details');\n const c = md.EntityByName('MJ: Conversations');\n\n if (!cd || !c) {\n console.warn('⚠️ Missing metadata for Conversations or Conversation Details');\n return [];\n }\n\n let filter = `ParentID IS NULL AND (UserID = '${currentUser.ID}' OR ConversationDetailID IN (\n SELECT ID FROM [${cd.SchemaName}].[${cd.BaseView}]\n WHERE\n UserID ='${currentUser.ID}' OR\n ConversationID IN (\n SELECT ID FROM [${c.SchemaName}].[${c.BaseView}] WHERE UserID='${currentUser.ID}'\n )\n ))`;\n\n filter += ` AND (LOWER(Name) LIKE '%${this.escapeSQL(lowerQuery)}%' OR LOWER(Description) LIKE '%${this.escapeSQL(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJTaskEntity>(\n {\n EntityName: 'MJ: Tasks',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search tasks:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(task => this.mapTaskToSearchResult(task, query));\n }\n\n /**\n * Map conversation entity to search result\n */\n private mapConversationToSearchResult(conversation: MJConversationEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = conversation.Name || 'Untitled Conversation';\n const description = conversation.Description || '';\n\n // Calculate relevance score\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n // Find matched text\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: conversation.ID,\n type: 'conversation',\n title: name,\n preview: description || 'No description',\n matchedText,\n conversationId: conversation.ID,\n conversationName: name,\n createdAt: conversation.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map message entity to search result\n */\n private mapMessageToSearchResult(message: MJConversationDetailEntity, query: string): SearchResult {\n const messageText = message.Message || '';\n const matchedText = this.extractMatchContext(messageText, query);\n\n return {\n id: message.ID,\n type: 'message',\n title: `Message in ${message.Conversation || 'Unknown Conversation'}`,\n preview: this.truncateText(messageText, 150),\n matchedText,\n conversationId: message.ConversationID,\n conversationName: message.Conversation || undefined,\n createdAt: message.__mj_CreatedAt,\n relevanceScore: 5\n };\n }\n\n /**\n * Map artifact entity to search result\n */\n private mapArtifactToSearchResult(\n artifact: MJArtifactEntity,\n query: string,\n collectionId?: string,\n collectionName?: string\n ): SearchResult {\n const name = artifact.Name || 'Untitled Artifact';\n const description = artifact.Description || '';\n const lowerQuery = query.toLowerCase();\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: artifact.ID,\n type: 'artifact',\n title: name,\n preview: description || 'No description',\n matchedText,\n collectionId,\n collectionName,\n artifactType: artifact.Type || undefined,\n createdAt: artifact.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map collection entity to search result\n */\n private mapCollectionToSearchResult(collection: MJCollectionEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = collection.Name || 'Untitled Collection';\n const description = collection.Description || '';\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: collection.ID,\n type: 'collection',\n title: name,\n preview: description || 'No description',\n matchedText,\n collectionId: collection.ID,\n collectionName: name,\n createdAt: collection.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map task entity to search result\n */\n private mapTaskToSearchResult(task: MJTaskEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = task.Name || 'Untitled Task';\n const description = task.Description || '';\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: task.ID,\n type: 'task',\n title: name,\n preview: description || 'No description',\n matchedText,\n createdAt: task.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Extract context around matched text\n */\n private extractMatchContext(text: string, query: string, contextLength: number = 100): string {\n const lowerText = text.toLowerCase();\n const lowerQuery = query.toLowerCase();\n const index = lowerText.indexOf(lowerQuery);\n\n if (index === -1) return this.truncateText(text, contextLength);\n\n const start = Math.max(0, index - contextLength / 2);\n const end = Math.min(text.length, index + query.length + contextLength / 2);\n\n let context = text.substring(start, end);\n if (start > 0) context = '...' + context;\n if (end < text.length) context = context + '...';\n\n return context;\n }\n\n /**\n * Truncate text to specified length\n */\n private truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.substring(0, maxLength) + '...';\n }\n\n /**\n * Escape SQL special characters\n */\n private escapeSQL(value: string): string {\n return value.replace(/'/g, \"''\");\n }\n\n /**\n * Set search filter\n */\n public setSearchFilter(filter: SearchFilter): void {\n this._searchFilter$.next(filter);\n }\n\n /**\n * Set date range filter\n */\n public setDateRange(range: DateRange): void {\n this._dateRange$.next(range);\n }\n\n /**\n * Clear all filters\n */\n public clearFilters(): void {\n this._searchFilter$.next('all');\n this._dateRange$.next({ start: null, end: null });\n }\n\n /**\n * Clear search results\n */\n public clearResults(): void {\n this._searchResults$.next({\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n });\n }\n\n /**\n * Get recent searches\n */\n public getRecentSearches(): string[] {\n return [...this.recentSearches];\n }\n\n /**\n * Add to recent searches\n */\n private addToRecentSearches(query: string): void {\n const trimmed = query.trim();\n if (!trimmed) return;\n\n // Remove if already exists\n this.recentSearches = this.recentSearches.filter(q => q !== trimmed);\n\n // Add to front\n this.recentSearches.unshift(trimmed);\n\n // Keep only MAX_RECENT_SEARCHES\n if (this.recentSearches.length > this.MAX_RECENT_SEARCHES) {\n this.recentSearches = this.recentSearches.slice(0, this.MAX_RECENT_SEARCHES);\n }\n\n this.saveRecentSearches();\n }\n\n /**\n * Clear recent searches\n */\n public clearRecentSearches(): void {\n this.recentSearches = [];\n this.saveRecentSearches();\n }\n\n /**\n * Load recent searches from localStorage\n */\n private loadRecentSearches(): void {\n try {\n const stored = localStorage.getItem('mj-recent-searches');\n if (stored) {\n this.recentSearches = JSON.parse(stored);\n }\n } catch (error) {\n console.error('Failed to load recent searches:', error);\n this.recentSearches = [];\n }\n }\n\n /**\n * Save recent searches to localStorage\n */\n private saveRecentSearches(): void {\n try {\n localStorage.setItem('mj-recent-searches', JSON.stringify(this.recentSearches));\n } catch (error) {\n console.error('Failed to save recent searches:', error);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../../src/lib/services/search.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, Subject, of } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, switchMap, map } from 'rxjs/operators';\nimport {\n MJConversationEntity,\n MJConversationDetailEntity,\n MJConversationArtifactEntity,\n MJCollectionEntity,\n MJCollectionArtifactEntity,\n MJTaskEntity,\n MJArtifactEntity\n} from '@memberjunction/core-entities';\nimport { RunView, UserInfo, Metadata, IMetadataProvider } from '@memberjunction/core';\nimport { EscapeSQLString } from '@memberjunction/global';\n\n/**\n * Types of searchable content\n */\nexport type SearchResultType = 'conversation' | 'message' | 'artifact' | 'collection' | 'task';\n\n/**\n * Filter options for search\n */\nexport type SearchFilter = 'all' | 'conversations' | 'messages' | 'artifacts' | 'collections' | 'tasks';\n\n/**\n * Unified search result\n */\nexport interface SearchResult {\n id: string;\n type: SearchResultType;\n title: string;\n preview: string;\n matchedText?: string;\n conversationId?: string;\n conversationName?: string;\n artifactType?: string;\n collectionId?: string;\n collectionName?: string;\n createdAt: Date;\n relevanceScore: number;\n}\n\n/**\n * Search results grouped by type\n */\nexport interface GroupedSearchResults {\n conversations: SearchResult[];\n messages: SearchResult[];\n artifacts: SearchResult[];\n collections: SearchResult[];\n tasks: SearchResult[];\n total: number;\n}\n\n/**\n * Date range filter\n */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\n/**\n * Service for searching across conversations, messages, and artifacts\n * Provides debounced search with result ranking and filtering\n */\n/**\n * NOTE ON `LIKE` ESCAPING\n *\n * The predicates below interpolate the user's query into `LIKE '%…%'` via `EscapeSQLString`, which\n * neutralises quotes but NOT LIKE metacharacters — `%`, `_` and `[` in a search term are still\n * treated as wildcards rather than literal text. That is this service's long-standing search\n * behaviour, so it is left as-is deliberately; changing it would change what users' searches match.\n * If literal matching is wanted, escape the metacharacters and append `ESCAPE '\\\\'` (see\n * `escapeLikeValue()` in `@memberjunction/core`, `generic/runQuerySQLFilterImplementations.ts`).\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class SearchService {\n private _searchQuery$ = new Subject<string>();\n private _searchFilter$ = new BehaviorSubject<SearchFilter>('all');\n private _dateRange$ = new BehaviorSubject<DateRange>({ start: null, end: null });\n private _isSearching$ = new BehaviorSubject<boolean>(false);\n private _searchResults$ = new BehaviorSubject<GroupedSearchResults>({\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n });\n\n // Recent searches stored in memory (could be persisted to localStorage)\n private recentSearches: string[] = [];\n private readonly MAX_RECENT_SEARCHES = 10;\n\n // Public observables\n public readonly searchQuery$ = this._searchQuery$.asObservable();\n public readonly searchFilter$ = this._searchFilter$.asObservable();\n public readonly dateRange$ = this._dateRange$.asObservable();\n public readonly isSearching$ = this._isSearching$.asObservable();\n public readonly searchResults$ = this._searchResults$.asObservable();\n\n private _provider: IMetadataProvider | null = null;\n\n constructor() {\n this.initializeSearch();\n this.loadRecentSearches();\n }\n\n /**\n * Set the metadata provider this service should use. When unset, falls back to Metadata.Provider.\n */\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n\n /**\n * Initialize debounced search\n */\n private initializeSearch(): void {\n this._searchQuery$.pipe(\n debounceTime(300),\n distinctUntilChanged()\n ).subscribe(query => {\n if (query && query.trim().length > 0) {\n this.addToRecentSearches(query);\n }\n });\n }\n\n /**\n * Search across all content types\n */\n public async search(\n query: string,\n environmentId: string,\n currentUser: UserInfo\n ): Promise<GroupedSearchResults> {\n if (!query || query.trim().length === 0) {\n const emptyResults: GroupedSearchResults = {\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n };\n this._searchResults$.next(emptyResults);\n return emptyResults;\n }\n\n this._isSearching$.next(true);\n this._searchQuery$.next(query);\n\n try {\n const filter = this._searchFilter$.value;\n const dateRange = this._dateRange$.value;\n\n // Search based on active filter\n const [conversations, messages, artifacts, collections, tasks] = await Promise.all([\n filter === 'all' || filter === 'conversations'\n ? this.searchConversations(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'messages'\n ? this.searchMessages(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'artifacts'\n ? this.searchArtifacts(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'collections'\n ? this.searchCollections(query, environmentId, currentUser, dateRange)\n : Promise.resolve([]),\n filter === 'all' || filter === 'tasks'\n ? this.searchTasks(query, environmentId, currentUser, dateRange)\n : Promise.resolve([])\n ]);\n\n const results: GroupedSearchResults = {\n conversations,\n messages,\n artifacts,\n collections,\n tasks,\n total: conversations.length + messages.length + artifacts.length + collections.length + tasks.length\n };\n\n this._searchResults$.next(results);\n return results;\n } finally {\n this._isSearching$.next(false);\n }\n }\n\n /**\n * Search conversations by name and description\n */\n private async searchConversations(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n let filter = `EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0)`;\n filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJConversationEntity>(\n {\n EntityName: 'MJ: Conversations',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search conversations:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(conv => this.mapConversationToSearchResult(conv, query));\n }\n\n /**\n * Search message content\n */\n private async searchMessages(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // First get conversations in this environment\n let filter = `ConversationID IN (SELECT ID FROM vwConversations WHERE EnvironmentID='${environmentId}' AND (IsArchived IS NULL OR IsArchived=0))`;\n filter += ` AND LOWER(Message) LIKE '%${EscapeSQLString(lowerQuery)}%'`;\n filter += ` AND (HiddenToUser IS NULL OR HiddenToUser=0)`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJConversationDetailEntity>(\n {\n EntityName: 'MJ: Conversation Details',\n ExtraFilter: filter,\n OrderBy: '__mj_CreatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search messages:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(msg => this.mapMessageToSearchResult(msg, query));\n }\n\n /**\n * Search artifacts by name and description\n * Includes artifacts from both conversations and collections\n */\n private async searchArtifacts(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // Search artifacts directly by name and description\n let filter = `EnvironmentID='${environmentId}'`;\n filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJArtifactEntity>(\n {\n EntityName: 'MJ: Artifacts',\n ExtraFilter: `${filter} AND (Visibility IS NULL OR Visibility='Always')`,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search artifacts:', result.ErrorMessage);\n return [];\n }\n\n const searchResults: SearchResult[] = [];\n\n // For each artifact, check if it's in a collection for context\n for (const artifact of result.Results) {\n // Check for collection associations\n const collResult = await rv.RunView<MJCollectionArtifactEntity>(\n {\n EntityName: 'MJ: Collection Artifacts',\n ExtraFilter: `ArtifactVersionID IN (\n SELECT ID FROM [__mj].[vwArtifactVersions] WHERE ArtifactID='${artifact.ID}'\n )`,\n MaxRows: 1,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (collResult.Success && collResult.Results && collResult.Results.length > 0) {\n const collArtifact = collResult.Results[0];\n searchResults.push(\n this.mapArtifactToSearchResult(\n artifact,\n query,\n collArtifact.CollectionID,\n collArtifact.Collection\n )\n );\n } else {\n // No collection association\n searchResults.push(this.mapArtifactToSearchResult(artifact, query));\n }\n }\n\n return searchResults;\n }\n\n /**\n * Search collections by name and description\n * Includes both owned collections and shared collections user has access to\n */\n private async searchCollections(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n const ownerFilter = `OwnerID='${currentUser.ID}'`;\n const permissionSubquery = `ID IN (\n SELECT CollectionID\n FROM [__mj].[vwCollectionPermissions]\n WHERE UserID='${currentUser.ID}'\n )`;\n\n let filter = `EnvironmentID='${environmentId}'`;\n filter += ` AND (OwnerID IS NULL OR ${ownerFilter} OR ${permissionSubquery})`;\n filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJCollectionEntity>(\n {\n EntityName: 'MJ: Collections',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search collections:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(coll => this.mapCollectionToSearchResult(coll, query));\n }\n\n /**\n * Search tasks by name, description, and notes\n * Only includes tasks in conversations user has access to\n */\n private async searchTasks(\n query: string,\n environmentId: string,\n currentUser: UserInfo,\n dateRange: DateRange\n ): Promise<SearchResult[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const lowerQuery = query.toLowerCase();\n\n // Build filter using same logic as TasksFullViewComponent\n const md = this.Provider;\n const cd = md.EntityByName('MJ: Conversation Details');\n const c = md.EntityByName('MJ: Conversations');\n\n if (!cd || !c) {\n console.warn('⚠️ Missing metadata for Conversations or Conversation Details');\n return [];\n }\n\n let filter = `ParentID IS NULL AND (UserID = '${currentUser.ID}' OR ConversationDetailID IN (\n SELECT ID FROM [${cd.SchemaName}].[${cd.BaseView}]\n WHERE\n UserID ='${currentUser.ID}' OR\n ConversationID IN (\n SELECT ID FROM [${c.SchemaName}].[${c.BaseView}] WHERE UserID='${currentUser.ID}'\n )\n ))`;\n\n filter += ` AND (LOWER(Name) LIKE '%${EscapeSQLString(lowerQuery)}%' OR LOWER(Description) LIKE '%${EscapeSQLString(lowerQuery)}%')`;\n\n if (dateRange.start) {\n filter += ` AND __mj_CreatedAt >= '${dateRange.start.toISOString()}'`;\n }\n if (dateRange.end) {\n filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;\n }\n\n const result = await rv.RunView<MJTaskEntity>(\n {\n EntityName: 'MJ: Tasks',\n ExtraFilter: filter,\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: 100,\n ResultType: 'entity_object'\n },\n currentUser\n );\n\n if (!result.Success || !result.Results) {\n console.error('Failed to search tasks:', result.ErrorMessage);\n return [];\n }\n\n return result.Results.map(task => this.mapTaskToSearchResult(task, query));\n }\n\n /**\n * Map conversation entity to search result\n */\n private mapConversationToSearchResult(conversation: MJConversationEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = conversation.Name || 'Untitled Conversation';\n const description = conversation.Description || '';\n\n // Calculate relevance score\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n // Find matched text\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: conversation.ID,\n type: 'conversation',\n title: name,\n preview: description || 'No description',\n matchedText,\n conversationId: conversation.ID,\n conversationName: name,\n createdAt: conversation.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map message entity to search result\n */\n private mapMessageToSearchResult(message: MJConversationDetailEntity, query: string): SearchResult {\n const messageText = message.Message || '';\n const matchedText = this.extractMatchContext(messageText, query);\n\n return {\n id: message.ID,\n type: 'message',\n title: `Message in ${message.Conversation || 'Unknown Conversation'}`,\n preview: this.truncateText(messageText, 150),\n matchedText,\n conversationId: message.ConversationID,\n conversationName: message.Conversation || undefined,\n createdAt: message.__mj_CreatedAt,\n relevanceScore: 5\n };\n }\n\n /**\n * Map artifact entity to search result\n */\n private mapArtifactToSearchResult(\n artifact: MJArtifactEntity,\n query: string,\n collectionId?: string,\n collectionName?: string\n ): SearchResult {\n const name = artifact.Name || 'Untitled Artifact';\n const description = artifact.Description || '';\n const lowerQuery = query.toLowerCase();\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: artifact.ID,\n type: 'artifact',\n title: name,\n preview: description || 'No description',\n matchedText,\n collectionId,\n collectionName,\n artifactType: artifact.Type || undefined,\n createdAt: artifact.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map collection entity to search result\n */\n private mapCollectionToSearchResult(collection: MJCollectionEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = collection.Name || 'Untitled Collection';\n const description = collection.Description || '';\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: collection.ID,\n type: 'collection',\n title: name,\n preview: description || 'No description',\n matchedText,\n collectionId: collection.ID,\n collectionName: name,\n createdAt: collection.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Map task entity to search result\n */\n private mapTaskToSearchResult(task: MJTaskEntity, query: string): SearchResult {\n const lowerQuery = query.toLowerCase();\n const name = task.Name || 'Untitled Task';\n const description = task.Description || '';\n\n let score = 0;\n if (name.toLowerCase().includes(lowerQuery)) score += 10;\n if (description.toLowerCase().includes(lowerQuery)) score += 5;\n\n const matchedText = this.extractMatchContext(\n name.toLowerCase().includes(lowerQuery) ? name : description,\n query\n );\n\n return {\n id: task.ID,\n type: 'task',\n title: name,\n preview: description || 'No description',\n matchedText,\n createdAt: task.__mj_CreatedAt,\n relevanceScore: score\n };\n }\n\n /**\n * Extract context around matched text\n */\n private extractMatchContext(text: string, query: string, contextLength: number = 100): string {\n const lowerText = text.toLowerCase();\n const lowerQuery = query.toLowerCase();\n const index = lowerText.indexOf(lowerQuery);\n\n if (index === -1) return this.truncateText(text, contextLength);\n\n const start = Math.max(0, index - contextLength / 2);\n const end = Math.min(text.length, index + query.length + contextLength / 2);\n\n let context = text.substring(start, end);\n if (start > 0) context = '...' + context;\n if (end < text.length) context = context + '...';\n\n return context;\n }\n\n /**\n * Truncate text to specified length\n */\n private truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.substring(0, maxLength) + '...';\n }\n\n /**\n * Set search filter\n */\n public setSearchFilter(filter: SearchFilter): void {\n this._searchFilter$.next(filter);\n }\n\n /**\n * Set date range filter\n */\n public setDateRange(range: DateRange): void {\n this._dateRange$.next(range);\n }\n\n /**\n * Clear all filters\n */\n public clearFilters(): void {\n this._searchFilter$.next('all');\n this._dateRange$.next({ start: null, end: null });\n }\n\n /**\n * Clear search results\n */\n public clearResults(): void {\n this._searchResults$.next({\n conversations: [],\n messages: [],\n artifacts: [],\n collections: [],\n tasks: [],\n total: 0\n });\n }\n\n /**\n * Get recent searches\n */\n public getRecentSearches(): string[] {\n return [...this.recentSearches];\n }\n\n /**\n * Add to recent searches\n */\n private addToRecentSearches(query: string): void {\n const trimmed = query.trim();\n if (!trimmed) return;\n\n // Remove if already exists\n this.recentSearches = this.recentSearches.filter(q => q !== trimmed);\n\n // Add to front\n this.recentSearches.unshift(trimmed);\n\n // Keep only MAX_RECENT_SEARCHES\n if (this.recentSearches.length > this.MAX_RECENT_SEARCHES) {\n this.recentSearches = this.recentSearches.slice(0, this.MAX_RECENT_SEARCHES);\n }\n\n this.saveRecentSearches();\n }\n\n /**\n * Clear recent searches\n */\n public clearRecentSearches(): void {\n this.recentSearches = [];\n this.saveRecentSearches();\n }\n\n /**\n * Load recent searches from localStorage\n */\n private loadRecentSearches(): void {\n try {\n const stored = localStorage.getItem('mj-recent-searches');\n if (stored) {\n this.recentSearches = JSON.parse(stored);\n }\n } catch (error) {\n console.error('Failed to load recent searches:', error);\n this.recentSearches = [];\n }\n }\n\n /**\n * Save recent searches to localStorage\n */\n private saveRecentSearches(): void {\n try {\n localStorage.setItem('mj-recent-searches', JSON.stringify(this.recentSearches));\n } catch (error) {\n console.error('Failed to save recent searches:', error);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouping for the "Prior Artifacts Created by This Agent" block fed to the Check Sage Intent
|
|
3
|
+
* prompt.
|
|
4
|
+
*
|
|
5
|
+
* PURE (no Angular, no entities, no provider) so the ordering contract below is unit-testable
|
|
6
|
+
* without the service's DI — same reason `conversation-detail-window.ts` and `date-jump.ts`
|
|
7
|
+
* live out here rather than on their consumers.
|
|
8
|
+
*/
|
|
9
|
+
/** The version fields the grouping needs. Structural, so the RunView row type satisfies it. */
|
|
10
|
+
export interface AgentArtifactVersionRow {
|
|
11
|
+
ID: string;
|
|
12
|
+
ArtifactID: string;
|
|
13
|
+
VersionNumber: number;
|
|
14
|
+
Name: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** The artifact fields the prompt prints. */
|
|
17
|
+
export interface AgentArtifactRow {
|
|
18
|
+
ID: string;
|
|
19
|
+
Name: string;
|
|
20
|
+
Type: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One artifact an agent produced in this conversation, with its versions newest-first.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately narrower than the array-scan shape it replaces, which also carried `runId`,
|
|
26
|
+
* `artifactDescription`, `versionDescription` and `createdAt` — none of which the intent
|
|
27
|
+
* prompt ever read. Dropping them is what lets the caller resolve this without joining back
|
|
28
|
+
* to agent runs, turning a four-table reconstruction into two reads.
|
|
29
|
+
*/
|
|
30
|
+
export interface AgentArtifactSummary {
|
|
31
|
+
artifactId: string;
|
|
32
|
+
artifactName: string;
|
|
33
|
+
artifactType: string;
|
|
34
|
+
/** Newest first, so `versions[0]` is the latest. */
|
|
35
|
+
versions: Array<{
|
|
36
|
+
versionId: string;
|
|
37
|
+
versionNumber: number;
|
|
38
|
+
versionName: string | null;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Folds versions into per-artifact summaries.
|
|
43
|
+
*
|
|
44
|
+
* NO SORT, and that is deliberate rather than an omission. The caller reads versions
|
|
45
|
+
* `__mj_CreatedAt DESC`, so inserting in arrival order leaves each artifact's versions
|
|
46
|
+
* newest-first AND leaves the Map ordered by whichever artifact was touched most recently —
|
|
47
|
+
* which is exactly what the explicit sort in the array-scan version used to compute. The
|
|
48
|
+
* assumption is load-bearing and invisible, so it is pinned by tests.
|
|
49
|
+
*
|
|
50
|
+
* @param versions - Artifact versions, newest first
|
|
51
|
+
* @param artifactsById - The artifact rows behind those versions
|
|
52
|
+
*/
|
|
53
|
+
export declare function GroupVersionsByArtifact(versions: readonly AgentArtifactVersionRow[], artifactsById: ReadonlyMap<string, AgentArtifactRow>): AgentArtifactSummary[];
|
|
54
|
+
//# sourceMappingURL=agent-artifact-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-artifact-summary.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/agent-artifact-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+FAA+F;AAC/F,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC7F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACnC,QAAQ,EAAE,SAAS,uBAAuB,EAAE,EAC5C,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACrD,oBAAoB,EAAE,CA4BxB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouping for the "Prior Artifacts Created by This Agent" block fed to the Check Sage Intent
|
|
3
|
+
* prompt.
|
|
4
|
+
*
|
|
5
|
+
* PURE (no Angular, no entities, no provider) so the ordering contract below is unit-testable
|
|
6
|
+
* without the service's DI — same reason `conversation-detail-window.ts` and `date-jump.ts`
|
|
7
|
+
* live out here rather than on their consumers.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Folds versions into per-artifact summaries.
|
|
11
|
+
*
|
|
12
|
+
* NO SORT, and that is deliberate rather than an omission. The caller reads versions
|
|
13
|
+
* `__mj_CreatedAt DESC`, so inserting in arrival order leaves each artifact's versions
|
|
14
|
+
* newest-first AND leaves the Map ordered by whichever artifact was touched most recently —
|
|
15
|
+
* which is exactly what the explicit sort in the array-scan version used to compute. The
|
|
16
|
+
* assumption is load-bearing and invisible, so it is pinned by tests.
|
|
17
|
+
*
|
|
18
|
+
* @param versions - Artifact versions, newest first
|
|
19
|
+
* @param artifactsById - The artifact rows behind those versions
|
|
20
|
+
*/
|
|
21
|
+
export function GroupVersionsByArtifact(versions, artifactsById) {
|
|
22
|
+
const byArtifact = new Map();
|
|
23
|
+
for (const version of versions) {
|
|
24
|
+
const artifact = artifactsById.get(version.ArtifactID);
|
|
25
|
+
if (!artifact) {
|
|
26
|
+
// Mirrors the stored query's INNER JOIN semantics. A version can outlive the
|
|
27
|
+
// artifact row the caller can read; emitting a summary with an undefined name
|
|
28
|
+
// would put the string "undefined" straight into the classifier prompt.
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
let summary = byArtifact.get(version.ArtifactID);
|
|
32
|
+
if (!summary) {
|
|
33
|
+
summary = {
|
|
34
|
+
artifactId: version.ArtifactID,
|
|
35
|
+
artifactName: artifact.Name,
|
|
36
|
+
artifactType: artifact.Type,
|
|
37
|
+
versions: []
|
|
38
|
+
};
|
|
39
|
+
byArtifact.set(version.ArtifactID, summary);
|
|
40
|
+
}
|
|
41
|
+
summary.versions.push({
|
|
42
|
+
versionId: version.ID,
|
|
43
|
+
versionNumber: version.VersionNumber,
|
|
44
|
+
versionName: version.Name
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return [...byArtifact.values()];
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=agent-artifact-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-artifact-summary.js","sourceRoot":"","sources":["../../../src/lib/utils/agent-artifact-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiCH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CACnC,QAA4C,EAC5C,aAAoD;IAEpD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAgC,CAAC;IAE3D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,6EAA6E;YAC7E,8EAA8E;YAC9E,wEAAwE;YACxE,SAAS;QACb,CAAC;QACD,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,GAAG;gBACN,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,YAAY,EAAE,QAAQ,CAAC,IAAI;gBAC3B,YAAY,EAAE,QAAQ,CAAC,IAAI;gBAC3B,QAAQ,EAAE,EAAE;aACf,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YAClB,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,WAAW,EAAE,OAAO,CAAC,IAAI;SAC5B,CAAC,CAAC;IACP,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;AACpC,CAAC","sourcesContent":["/**\n * Grouping for the \"Prior Artifacts Created by This Agent\" block fed to the Check Sage Intent\n * prompt.\n *\n * PURE (no Angular, no entities, no provider) so the ordering contract below is unit-testable\n * without the service's DI — same reason `conversation-detail-window.ts` and `date-jump.ts`\n * live out here rather than on their consumers.\n */\n\n/** The version fields the grouping needs. Structural, so the RunView row type satisfies it. */\nexport interface AgentArtifactVersionRow {\n ID: string;\n ArtifactID: string;\n VersionNumber: number;\n Name: string | null;\n}\n\n/** The artifact fields the prompt prints. */\nexport interface AgentArtifactRow {\n ID: string;\n Name: string;\n Type: string;\n}\n\n/**\n * One artifact an agent produced in this conversation, with its versions newest-first.\n *\n * Deliberately narrower than the array-scan shape it replaces, which also carried `runId`,\n * `artifactDescription`, `versionDescription` and `createdAt` — none of which the intent\n * prompt ever read. Dropping them is what lets the caller resolve this without joining back\n * to agent runs, turning a four-table reconstruction into two reads.\n */\nexport interface AgentArtifactSummary {\n artifactId: string;\n artifactName: string;\n artifactType: string;\n /** Newest first, so `versions[0]` is the latest. */\n versions: Array<{ versionId: string; versionNumber: number; versionName: string | null }>;\n}\n\n/**\n * Folds versions into per-artifact summaries.\n *\n * NO SORT, and that is deliberate rather than an omission. The caller reads versions\n * `__mj_CreatedAt DESC`, so inserting in arrival order leaves each artifact's versions\n * newest-first AND leaves the Map ordered by whichever artifact was touched most recently —\n * which is exactly what the explicit sort in the array-scan version used to compute. The\n * assumption is load-bearing and invisible, so it is pinned by tests.\n *\n * @param versions - Artifact versions, newest first\n * @param artifactsById - The artifact rows behind those versions\n */\nexport function GroupVersionsByArtifact(\n versions: readonly AgentArtifactVersionRow[],\n artifactsById: ReadonlyMap<string, AgentArtifactRow>\n): AgentArtifactSummary[] {\n const byArtifact = new Map<string, AgentArtifactSummary>();\n\n for (const version of versions) {\n const artifact = artifactsById.get(version.ArtifactID);\n if (!artifact) {\n // Mirrors the stored query's INNER JOIN semantics. A version can outlive the\n // artifact row the caller can read; emitting a summary with an undefined name\n // would put the string \"undefined\" straight into the classifier prompt.\n continue;\n }\n let summary = byArtifact.get(version.ArtifactID);\n if (!summary) {\n summary = {\n artifactId: version.ArtifactID,\n artifactName: artifact.Name,\n artifactType: artifact.Type,\n versions: []\n };\n byArtifact.set(version.ArtifactID, summary);\n }\n summary.versions.push({\n versionId: version.ID,\n versionNumber: version.VersionNumber,\n versionName: version.Name\n });\n }\n return [...byArtifact.values()];\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type RealtimeTimelineSourceDetail } from './realtime-session-timeline';
|
|
2
|
+
export declare function SelectLatestTimelinePage<T extends RealtimeTimelineSourceDetail>(chronologicalDetails: readonly T[], pageSize: number): {
|
|
3
|
+
Page: T[];
|
|
4
|
+
OldestIncluded: T | null;
|
|
5
|
+
};
|
|
6
|
+
/** Default number of *timeline items* shown on first paint and per older page. */
|
|
7
|
+
export declare const DEFAULT_TRANSCRIPT_PAGE_SIZE = 10;
|
|
8
|
+
/** Raw-row over-read so session collapse still fills a page of timeline items. */
|
|
9
|
+
export declare const DEFAULT_RAW_OVERREAD: number;
|
|
10
|
+
/**
|
|
11
|
+
* How many times a short page may re-fetch with a wider over-read before settling.
|
|
12
|
+
*
|
|
13
|
+
* The over-read exists because raw rows are not display items — a realtime session folds
|
|
14
|
+
* many rows into ONE timeline card, so `3 × pageSize` rows can yield two or three items
|
|
15
|
+
* instead of ten. Growing the read is what keeps a page a page.
|
|
16
|
+
*
|
|
17
|
+
* Bounded, because the pathological case is a conversation that is almost entirely one long
|
|
18
|
+
* session: growing without limit there would walk the whole table, which is the exact cost
|
|
19
|
+
* windowing exists to avoid. Three attempts reaches 12 × pageSize, past which a short page is
|
|
20
|
+
* the honest answer and the sentinel simply fires again.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_OVERREAD_ATTEMPTS = 3;
|
|
23
|
+
/** Multiplier applied to the over-read on each retry. */
|
|
24
|
+
export declare const OVERREAD_GROWTH_FACTOR = 2;
|
|
25
|
+
/**
|
|
26
|
+
* Extra pages a tail refresh may read to bridge back to the rows it already holds.
|
|
27
|
+
*
|
|
28
|
+
* A refresh reads the NEWEST page. If the conversation grew by more than a page since the
|
|
29
|
+
* last read — a long agent burst, a tab left open — that page starts above the loaded tail
|
|
30
|
+
* and the rows in between belong to neither. Merging the two anyway produces a set that
|
|
31
|
+
* sorts into order and looks contiguous while silently missing its middle, which is the
|
|
32
|
+
* worst possible outcome: nothing about the transcript says anything is absent.
|
|
33
|
+
*
|
|
34
|
+
* So the refresh pages down until it reaches known rows. Bounded, because "the tail ran away
|
|
35
|
+
* from us" has no upper limit and chasing it without one is the unbounded read this whole
|
|
36
|
+
* feature exists to remove. Past the budget the refresh drops the disconnected older rows
|
|
37
|
+
* instead and lets the sentinel offer them back — a visible gap the reader can close, rather
|
|
38
|
+
* than an invisible one they cannot.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MAX_REFRESH_BACKFILL_PAGES = 3;
|
|
41
|
+
export interface ConversationDetailWindowCursor {
|
|
42
|
+
/** Exclusive upper bound for the next older fetch. Null only when the window is empty. */
|
|
43
|
+
OldestSequence: number | null;
|
|
44
|
+
/** Inclusive high-water mark of the loaded tail. Used to detect live appends. */
|
|
45
|
+
NewestSequence: number | null;
|
|
46
|
+
/** True when at least one older raw row exists that is not in LoadedDetails. */
|
|
47
|
+
HasMoreAbove: boolean;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=conversation-detail-window.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-detail-window.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/conversation-detail-window.ts"],"names":[],"mappings":"AACA,OAAO,EAEH,KAAK,4BAA4B,EACpC,MAAM,6BAA6B,CAAC;AAErC,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,4BAA4B,EAC3E,oBAAoB,EAAE,SAAS,CAAC,EAAE,EAClC,QAAQ,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,cAAc,EAAE,CAAC,GAAG,IAAI,CAAA;CAAE,CA0BzC;AAyBD,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAAmC,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,MAAM,WAAW,8BAA8B;IAC3C,0FAA0F;IAC1F,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iFAAiF;IACjF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gFAAgF;IAChF,YAAY,EAAE,OAAO,CAAC;CACzB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NormalizeUUID } from '@memberjunction/global';
|
|
2
|
+
import { BuildConversationTimeline } from './realtime-session-timeline';
|
|
3
|
+
export function SelectLatestTimelinePage(chronologicalDetails, pageSize) {
|
|
4
|
+
const timeline = BuildConversationTimeline(chronologicalDetails);
|
|
5
|
+
const pageItems = timeline.slice(Math.max(0, timeline.length - pageSize));
|
|
6
|
+
// Message items match by identity (the grouping pass passes the row through
|
|
7
|
+
// untouched); session items by normalized id, so every folded row comes along.
|
|
8
|
+
const includedMessages = new Set();
|
|
9
|
+
const includedSessionKeys = new Set();
|
|
10
|
+
for (const item of pageItems) {
|
|
11
|
+
if (item.Kind === 'message') {
|
|
12
|
+
includedMessages.add(item.Detail);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
includedSessionKeys.add(NormalizeUUID(item.Group.SessionID));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Filter the ORIGINAL row set rather than walking the page items, so the result keeps
|
|
19
|
+
// chronological order and picks up session rows interleaved after the card's position.
|
|
20
|
+
const page = chronologicalDetails.filter(detail => {
|
|
21
|
+
const sessionId = trimmedSessionId(detail.AgentSessionID);
|
|
22
|
+
return sessionId === null
|
|
23
|
+
? includedMessages.has(detail)
|
|
24
|
+
: includedSessionKeys.has(NormalizeUUID(sessionId));
|
|
25
|
+
});
|
|
26
|
+
return { Page: page, OldestIncluded: page.length > 0 ? page[0] : null };
|
|
27
|
+
}
|
|
28
|
+
// NOTE: the plan also called for a `NeedsSessionExpansion` helper here, shared between the
|
|
29
|
+
// store and the engine. It is not exported, because there is no such shared caller: deciding
|
|
30
|
+
// whether a fetched page landed mid-session is something only the fetcher can act on, and the
|
|
31
|
+
// fetcher is `ConversationEngine.expandOldestSession` in `@memberjunction/core-entities`.
|
|
32
|
+
// That package cannot import from this one — the dependency runs Angular → core-entities and
|
|
33
|
+
// must never run the other way — so a helper here would have been exported, tested, and
|
|
34
|
+
// called by nothing. The engine makes the same `trim()` check inline against the row it just
|
|
35
|
+
// read. `trimmedSessionId` below keeps that definition honest on THIS side of the boundary,
|
|
36
|
+
// where `SelectLatestTimelinePage` does need it.
|
|
37
|
+
/**
|
|
38
|
+
* Trims the stamped session id; empty/whitespace stamps count as unstamped — mirrors the
|
|
39
|
+
* grouping pass, so a row this module treats as a normal message is one the timeline
|
|
40
|
+
* treats as a normal message too.
|
|
41
|
+
*/
|
|
42
|
+
function trimmedSessionId(raw) {
|
|
43
|
+
const trimmed = raw?.trim() ?? '';
|
|
44
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
45
|
+
}
|
|
46
|
+
/** Default number of *timeline items* shown on first paint and per older page. */
|
|
47
|
+
export const DEFAULT_TRANSCRIPT_PAGE_SIZE = 10;
|
|
48
|
+
/** Raw-row over-read so session collapse still fills a page of timeline items. */
|
|
49
|
+
export const DEFAULT_RAW_OVERREAD = DEFAULT_TRANSCRIPT_PAGE_SIZE * 3;
|
|
50
|
+
/**
|
|
51
|
+
* How many times a short page may re-fetch with a wider over-read before settling.
|
|
52
|
+
*
|
|
53
|
+
* The over-read exists because raw rows are not display items — a realtime session folds
|
|
54
|
+
* many rows into ONE timeline card, so `3 × pageSize` rows can yield two or three items
|
|
55
|
+
* instead of ten. Growing the read is what keeps a page a page.
|
|
56
|
+
*
|
|
57
|
+
* Bounded, because the pathological case is a conversation that is almost entirely one long
|
|
58
|
+
* session: growing without limit there would walk the whole table, which is the exact cost
|
|
59
|
+
* windowing exists to avoid. Three attempts reaches 12 × pageSize, past which a short page is
|
|
60
|
+
* the honest answer and the sentinel simply fires again.
|
|
61
|
+
*/
|
|
62
|
+
export const MAX_OVERREAD_ATTEMPTS = 3;
|
|
63
|
+
/** Multiplier applied to the over-read on each retry. */
|
|
64
|
+
export const OVERREAD_GROWTH_FACTOR = 2;
|
|
65
|
+
/**
|
|
66
|
+
* Extra pages a tail refresh may read to bridge back to the rows it already holds.
|
|
67
|
+
*
|
|
68
|
+
* A refresh reads the NEWEST page. If the conversation grew by more than a page since the
|
|
69
|
+
* last read — a long agent burst, a tab left open — that page starts above the loaded tail
|
|
70
|
+
* and the rows in between belong to neither. Merging the two anyway produces a set that
|
|
71
|
+
* sorts into order and looks contiguous while silently missing its middle, which is the
|
|
72
|
+
* worst possible outcome: nothing about the transcript says anything is absent.
|
|
73
|
+
*
|
|
74
|
+
* So the refresh pages down until it reaches known rows. Bounded, because "the tail ran away
|
|
75
|
+
* from us" has no upper limit and chasing it without one is the unbounded read this whole
|
|
76
|
+
* feature exists to remove. Past the budget the refresh drops the disconnected older rows
|
|
77
|
+
* instead and lets the sentinel offer them back — a visible gap the reader can close, rather
|
|
78
|
+
* than an invisible one they cannot.
|
|
79
|
+
*/
|
|
80
|
+
export const MAX_REFRESH_BACKFILL_PAGES = 3;
|
|
81
|
+
// NOTE: the loaded-window and fetch-result shapes the plan sketched here live on the
|
|
82
|
+
// consumer instead — `ConversationDetailWindowSnapshot` in
|
|
83
|
+
// `../services/conversation-detail-window.store`, and `DetailWindowLoadResult` in
|
|
84
|
+
// `@memberjunction/core-entities`. Declaring unused duplicates here would give a future
|
|
85
|
+
// contributor two plausible contracts to pick between.
|
|
86
|
+
//# sourceMappingURL=conversation-detail-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-detail-window.js","sourceRoot":"","sources":["../../../src/lib/utils/conversation-detail-window.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACH,yBAAyB,EAE5B,MAAM,6BAA6B,CAAC;AAErC,MAAM,UAAU,wBAAwB,CACpC,oBAAkC,EAClC,QAAgB;IAEhB,MAAM,QAAQ,GAAG,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAE1E,4EAA4E;IAC5E,+EAA+E;IAC/E,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAK,CAAC;IACtC,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1B,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACJ,mBAAmB,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACjE,CAAC;IACL,CAAC;IAED,sFAAsF;IACtF,uFAAuF;IACvF,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;QAC9C,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC1D,OAAO,SAAS,KAAK,IAAI;YACrB,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC;YAC9B,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5E,CAAC;AAID,2FAA2F;AAC3F,6FAA6F;AAC7F,8FAA8F;AAC9F,0FAA0F;AAC1F,6FAA6F;AAC7F,wFAAwF;AACxF,6FAA6F;AAC7F,4FAA4F;AAC5F,iDAAiD;AAEjD;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,GAA8B;IACpD,MAAM,OAAO,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAGD,kFAAkF;AAClF,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAE/C,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,4BAA4B,GAAG,CAAC,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,yDAAyD;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAW5C,qFAAqF;AACrF,2DAA2D;AAC3D,kFAAkF;AAClF,wFAAwF;AACxF,uDAAuD","sourcesContent":["import { NormalizeUUID } from '@memberjunction/global';\nimport {\n BuildConversationTimeline,\n type RealtimeTimelineSourceDetail\n} from './realtime-session-timeline';\n\nexport function SelectLatestTimelinePage<T extends RealtimeTimelineSourceDetail>(\n chronologicalDetails: readonly T[],\n pageSize: number\n): { Page: T[]; OldestIncluded: T | null } {\n const timeline = BuildConversationTimeline(chronologicalDetails);\n const pageItems = timeline.slice(Math.max(0, timeline.length - pageSize));\n\n // Message items match by identity (the grouping pass passes the row through\n // untouched); session items by normalized id, so every folded row comes along.\n const includedMessages = new Set<T>();\n const includedSessionKeys = new Set<string>();\n for (const item of pageItems) {\n if (item.Kind === 'message') {\n includedMessages.add(item.Detail);\n } else {\n includedSessionKeys.add(NormalizeUUID(item.Group.SessionID));\n }\n }\n\n // Filter the ORIGINAL row set rather than walking the page items, so the result keeps\n // chronological order and picks up session rows interleaved after the card's position.\n const page = chronologicalDetails.filter(detail => {\n const sessionId = trimmedSessionId(detail.AgentSessionID);\n return sessionId === null\n ? includedMessages.has(detail)\n : includedSessionKeys.has(NormalizeUUID(sessionId));\n });\n\n return { Page: page, OldestIncluded: page.length > 0 ? page[0] : null };\n}\n\n\n\n// NOTE: the plan also called for a `NeedsSessionExpansion` helper here, shared between the\n// store and the engine. It is not exported, because there is no such shared caller: deciding\n// whether a fetched page landed mid-session is something only the fetcher can act on, and the\n// fetcher is `ConversationEngine.expandOldestSession` in `@memberjunction/core-entities`.\n// That package cannot import from this one — the dependency runs Angular → core-entities and\n// must never run the other way — so a helper here would have been exported, tested, and\n// called by nothing. The engine makes the same `trim()` check inline against the row it just\n// read. `trimmedSessionId` below keeps that definition honest on THIS side of the boundary,\n// where `SelectLatestTimelinePage` does need it.\n\n/**\n * Trims the stamped session id; empty/whitespace stamps count as unstamped — mirrors the\n * grouping pass, so a row this module treats as a normal message is one the timeline\n * treats as a normal message too.\n */\nfunction trimmedSessionId(raw: string | null | undefined): string | null {\n const trimmed = raw?.trim() ?? '';\n return trimmed.length > 0 ? trimmed : null;\n}\n\n\n/** Default number of *timeline items* shown on first paint and per older page. */\nexport const DEFAULT_TRANSCRIPT_PAGE_SIZE = 10;\n\n/** Raw-row over-read so session collapse still fills a page of timeline items. */\nexport const DEFAULT_RAW_OVERREAD = DEFAULT_TRANSCRIPT_PAGE_SIZE * 3;\n\n/**\n * How many times a short page may re-fetch with a wider over-read before settling.\n *\n * The over-read exists because raw rows are not display items — a realtime session folds\n * many rows into ONE timeline card, so `3 × pageSize` rows can yield two or three items\n * instead of ten. Growing the read is what keeps a page a page.\n *\n * Bounded, because the pathological case is a conversation that is almost entirely one long\n * session: growing without limit there would walk the whole table, which is the exact cost\n * windowing exists to avoid. Three attempts reaches 12 × pageSize, past which a short page is\n * the honest answer and the sentinel simply fires again.\n */\nexport const MAX_OVERREAD_ATTEMPTS = 3;\n\n/** Multiplier applied to the over-read on each retry. */\nexport const OVERREAD_GROWTH_FACTOR = 2;\n\n/**\n * Extra pages a tail refresh may read to bridge back to the rows it already holds.\n *\n * A refresh reads the NEWEST page. If the conversation grew by more than a page since the\n * last read — a long agent burst, a tab left open — that page starts above the loaded tail\n * and the rows in between belong to neither. Merging the two anyway produces a set that\n * sorts into order and looks contiguous while silently missing its middle, which is the\n * worst possible outcome: nothing about the transcript says anything is absent.\n *\n * So the refresh pages down until it reaches known rows. Bounded, because \"the tail ran away\n * from us\" has no upper limit and chasing it without one is the unbounded read this whole\n * feature exists to remove. Past the budget the refresh drops the disconnected older rows\n * instead and lets the sentinel offer them back — a visible gap the reader can close, rather\n * than an invisible one they cannot.\n */\nexport const MAX_REFRESH_BACKFILL_PAGES = 3;\n\nexport interface ConversationDetailWindowCursor {\n /** Exclusive upper bound for the next older fetch. Null only when the window is empty. */\n OldestSequence: number | null;\n /** Inclusive high-water mark of the loaded tail. Used to detect live appends. */\n NewestSequence: number | null;\n /** True when at least one older raw row exists that is not in LoadedDetails. */\n HasMoreAbove: boolean;\n}\n\n// NOTE: the loaded-window and fetch-result shapes the plan sketched here live on the\n// consumer instead — `ConversationDetailWindowSnapshot` in\n// `../services/conversation-detail-window.store`, and `DetailWindowLoadResult` in\n// `@memberjunction/core-entities`. Declaring unused duplicates here would give a future\n// contributor two plausible contracts to pick between.\n\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RealtimeTimelineSourceDetail } from './realtime-session-timeline';
|
|
2
|
+
/**
|
|
3
|
+
* Periods the transcript's date navigator offers. Kept as a union rather than a string so a
|
|
4
|
+
* new dropdown entry cannot silently fall through to "no cutoff".
|
|
5
|
+
*/
|
|
6
|
+
export type DateJumpPeriod = 'today' | 'yesterday' | 'last-week' | 'last-month';
|
|
7
|
+
/**
|
|
8
|
+
* How far back a jump had to page before it stopped.
|
|
9
|
+
*
|
|
10
|
+
* The plan's requirement is that a jump never silently no-ops, so every terminal state here is
|
|
11
|
+
* something the caller can report: `reached` scrolled to a real message, `oldest` ran out of
|
|
12
|
+
* history, `capped` hit {@link DATE_JUMP_MAX_PAGES}, `empty` had nothing loaded at all.
|
|
13
|
+
*/
|
|
14
|
+
export type DateJumpOutcome = 'reached' | 'oldest' | 'capped' | 'empty';
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling on older-page loads for one jump.
|
|
17
|
+
*
|
|
18
|
+
* A jump into a very old conversation would otherwise page the entire history back — the
|
|
19
|
+
* exact unbounded read windowing exists to prevent. On a miss the caller lands the user at
|
|
20
|
+
* the oldest loaded message instead, which is where paging left them anyway.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DATE_JUMP_MAX_PAGES = 50;
|
|
23
|
+
/**
|
|
24
|
+
* Inclusive lower bound for a period — the oldest instant a message may carry and still count
|
|
25
|
+
* as "in" that period. `today` is midnight today; the rest reach further back.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ResolveDateJumpCutoff(period: DateJumpPeriod, now: Date): Date;
|
|
28
|
+
export interface DateJumpTarget<T extends RealtimeTimelineSourceDetail> {
|
|
29
|
+
/** Oldest loaded detail at or after the cutoff — the message to scroll to. */
|
|
30
|
+
Detail: T | null;
|
|
31
|
+
/**
|
|
32
|
+
* True when the loaded set cannot yet answer the question: everything loaded is at or
|
|
33
|
+
* after the cutoff, so an older page might contain a still-earlier message in-period.
|
|
34
|
+
*/
|
|
35
|
+
NeedsOlder: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolves which loaded message a date jump should land on.
|
|
39
|
+
*
|
|
40
|
+
* The target is the OLDEST message at or after the cutoff — jumping to "last week" means the
|
|
41
|
+
* start of last week's conversation, not its end.
|
|
42
|
+
*
|
|
43
|
+
* `NeedsOlder` is the windowing-aware part. If every loaded message is in-period, the true
|
|
44
|
+
* start of the period may still be below the loaded window, so the caller pages and asks
|
|
45
|
+
* again. If any loaded message predates the cutoff, the boundary is already inside the loaded
|
|
46
|
+
* set and no amount of paging would improve the answer.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ResolveDateJumpTarget<T extends RealtimeTimelineSourceDetail>(chronologicalDetails: readonly T[], period: DateJumpPeriod, now: Date): DateJumpTarget<T>;
|
|
49
|
+
/** Human-readable result, for the caller to surface instead of failing silently. */
|
|
50
|
+
export declare function DescribeDateJumpOutcome(outcome: DateJumpOutcome, period: DateJumpPeriod): string;
|
|
51
|
+
/**
|
|
52
|
+
* Final outcome for a date jump.
|
|
53
|
+
*
|
|
54
|
+
* The paging loop and the scroll answer DIFFERENT questions: the loop knows only whether it
|
|
55
|
+
* ran out of history (or pages) to load, while the scroll knows whether an in-period message
|
|
56
|
+
* was actually found. Letting the loop's verdict win was a bug — a conversation whose oldest
|
|
57
|
+
* message is itself in-period never sees a pre-cutoff row, so the loop reports "ran out"
|
|
58
|
+
* even though the jump landed on a perfectly good target and the user was told no such
|
|
59
|
+
* messages exist.
|
|
60
|
+
*
|
|
61
|
+
* So: a successful scroll always wins. The cap only surfaces when the scroll did NOT reach,
|
|
62
|
+
* because that is the only case where "we stopped early" explains the miss.
|
|
63
|
+
*/
|
|
64
|
+
export declare function CombineDateJumpOutcome(scrollOutcome: DateJumpOutcome, hitPageCap: boolean): DateJumpOutcome;
|
|
65
|
+
//# sourceMappingURL=date-jump.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-jump.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/date-jump.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExE;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAStC;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,CAW7E;AAYD,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,4BAA4B;IAClE,8EAA8E;IAC9E,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,4BAA4B,EACxE,oBAAoB,EAAE,SAAS,CAAC,EAAE,EAClC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,IAAI,GACV,cAAc,CAAC,CAAC,CAAC,CAqBnB;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAchG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAClC,aAAa,EAAE,eAAe,EAC9B,UAAU,EAAE,OAAO,GACpB,eAAe,CAKjB"}
|