@shop-prompter/react 0.1.1 → 1.1.0
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/CHANGELOG.md +12 -0
- package/dist/index.js +1433 -373
- package/dist/index.mjs +1434 -374
- package/package.json +24 -23
- package/src/api/icons.ts +9 -0
- package/src/api/product-api.ts +121 -0
- package/src/api/session-handler.ts +153 -0
- package/src/api/shop-prompter-api.ts +58 -30
- package/src/api/shop-prompter.types.ts +3 -1
- package/src/product-card-list.component.jsx +271 -0
- package/src/shop-prompter.component.jsx +922 -276
- package/src/shop-prompter.service.ts +60 -18
- package/src/thumbs-down-icon.component.jsx +25 -0
- package/src/thumbs-up-icon.component.jsx +25 -0
package/package.json
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
"name": "@shop-prompter/react",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"react": "^18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"react": "^19.2.7",
|
|
15
|
+
"tsup": "^8.5.1",
|
|
16
|
+
"typescript": "^6.0.3",
|
|
17
|
+
"@shop-prompter/core": "0.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"marked": "^18.0.5",
|
|
21
|
+
"uuid": "^14.0.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup src/index.ts --format cjs,esm --dts"
|
|
25
|
+
}
|
|
25
26
|
}
|
package/src/api/icons.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SVG Path Constants for Thumbs-Up and Thumbs-Down Icons
|
|
2
|
+
|
|
3
|
+
export const THUMBS_UP_WRIST = 'M7 10v12';
|
|
4
|
+
export const THUMBS_UP_HAND = 'M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z';
|
|
5
|
+
|
|
6
|
+
export const THUMBS_DOWN_WRIST = 'M17 14V2';
|
|
7
|
+
export const THUMBS_DOWN_HAND = 'M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z';
|
|
8
|
+
|
|
9
|
+
export const THINKING_SPINNER = 'M21 12a9 9 0 1 1-6.219-8.56';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ShopPrompterApiConfiguration } from './shop-prompter.types'
|
|
2
|
+
|
|
3
|
+
export interface ProductPricing {
|
|
4
|
+
taxPercentage: number
|
|
5
|
+
gross: number
|
|
6
|
+
currency: string | undefined
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ProductAvailability {
|
|
10
|
+
count: number
|
|
11
|
+
alwaysAvailable: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ProductCategory {
|
|
15
|
+
key: string
|
|
16
|
+
name: Record<string, string>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ProductLocalization {
|
|
20
|
+
name: string
|
|
21
|
+
description: string,
|
|
22
|
+
extra: Record<string, any>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProductApiResponse {
|
|
26
|
+
pricing: ProductPricing
|
|
27
|
+
availability: ProductAvailability
|
|
28
|
+
images: string[]
|
|
29
|
+
categories: ProductCategory[]
|
|
30
|
+
sku: string
|
|
31
|
+
localization: Record<string, ProductLocalization>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ProductApi {
|
|
35
|
+
setApiConfig(config: ShopPrompterApiConfiguration): void
|
|
36
|
+
getProducts(skus: string[]): Promise<ProductApiResponse[]>
|
|
37
|
+
isCached(skus: string[]): boolean
|
|
38
|
+
getCached(skus: string[]): ProductApiResponse[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class DefaultProductApi implements ProductApi {
|
|
42
|
+
private config: ShopPrompterApiConfiguration | null = null
|
|
43
|
+
private cache = new Map<string, ProductApiResponse | null>()
|
|
44
|
+
|
|
45
|
+
setApiConfig(config: ShopPrompterApiConfiguration): void {
|
|
46
|
+
this.config = config
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
isCached(skus: string[]): boolean {
|
|
50
|
+
if (!skus || skus.length === 0) return true
|
|
51
|
+
return skus.every(sku => this.cache.has(sku))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getCached(skus: string[]): ProductApiResponse[] {
|
|
55
|
+
if (!skus || skus.length === 0) return []
|
|
56
|
+
return skus.map(sku => this.cache.get(sku)).filter(Boolean) as ProductApiResponse[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getProducts(skus: string[]): Promise<ProductApiResponse[]> {
|
|
60
|
+
if (!this.config) {
|
|
61
|
+
throw new Error('API configuration not set. Call setApiConfig first.')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const cachedProducts: ProductApiResponse[] = []
|
|
65
|
+
const missingSkus: string[] = []
|
|
66
|
+
|
|
67
|
+
for (const sku of skus) {
|
|
68
|
+
if (this.cache.has(sku)) {
|
|
69
|
+
const cachedVal = this.cache.get(sku)
|
|
70
|
+
if (cachedVal) {
|
|
71
|
+
cachedProducts.push(cachedVal)
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
missingSkus.push(sku)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (missingSkus.length === 0) {
|
|
79
|
+
// Maintain the order of requested SKUs from cached products
|
|
80
|
+
return skus.map(sku => this.cache.get(sku)).filter(Boolean) as ProductApiResponse[]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const baseUrl = this.config.productsBaseUrl || this.config.baseUrl
|
|
84
|
+
const url = `${baseUrl}/products?skus=${encodeURIComponent(missingSkus.join(','))}`
|
|
85
|
+
|
|
86
|
+
const headers: Record<string, string> = {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const response = await fetch(url, {
|
|
91
|
+
method: 'GET',
|
|
92
|
+
headers,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`Product API error: ${response.status}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const newProducts: ProductApiResponse[] = await response.json()
|
|
100
|
+
|
|
101
|
+
const returnedSkus = new Set<string>()
|
|
102
|
+
for (const p of newProducts) {
|
|
103
|
+
if (p.sku) {
|
|
104
|
+
this.cache.set(p.sku, p)
|
|
105
|
+
returnedSkus.add(p.sku)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Cache missing SKUs as null to prevent repeating API requests
|
|
110
|
+
for (const sku of missingSkus) {
|
|
111
|
+
if (!returnedSkus.has(sku)) {
|
|
112
|
+
this.cache.set(sku, null)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return all requested products maintaining the initial skus array order
|
|
117
|
+
return skus.map(sku => this.cache.get(sku)).filter(Boolean) as ProductApiResponse[]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const productApi = new DefaultProductApi()
|
|
@@ -2,14 +2,54 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
2
2
|
|
|
3
3
|
const SESSION_KEY = 'shopprompter_sessionId';
|
|
4
4
|
const USER_KEY = 'shopprompter_userId';
|
|
5
|
+
const SESSIONS_KEY = 'shopprompter_sessions';
|
|
6
|
+
const MAX_SESSIONS = 3;
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Persisted message shape stored in localStorage
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface PersistedMessage {
|
|
13
|
+
id: string;
|
|
14
|
+
role: 'user' | 'assistant';
|
|
15
|
+
content: string;
|
|
16
|
+
productSkus?: string[];
|
|
17
|
+
/** User feedback on an assistant message. Only relevant when role === 'assistant'. */
|
|
18
|
+
feedback?: 'upvote' | 'downvote' | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SessionData {
|
|
22
|
+
createdAt: number;
|
|
23
|
+
messages: PersistedMessage[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SessionStore {
|
|
27
|
+
[sessionId: string]: SessionData;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Interface
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
5
33
|
|
|
6
34
|
export interface SessionHandler {
|
|
7
35
|
initialize(userIdentifier?: string): void;
|
|
8
36
|
regenerate(): void;
|
|
9
37
|
getSessionId(): string;
|
|
10
38
|
getUserId(): string;
|
|
39
|
+
saveUserMessage(sessionId: string, message: PersistedMessage): void;
|
|
40
|
+
saveAssistantMessage(sessionId: string, message: PersistedMessage): void;
|
|
41
|
+
loadMessages(sessionId: string): PersistedMessage[];
|
|
42
|
+
updateMessageFeedback(
|
|
43
|
+
sessionId: string,
|
|
44
|
+
messageId: string,
|
|
45
|
+
feedback: 'upvote' | 'downvote' | null,
|
|
46
|
+
): void;
|
|
11
47
|
}
|
|
12
48
|
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Implementation
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
13
53
|
export class DefaultSessionHandler implements SessionHandler {
|
|
14
54
|
private sessionId: string = '';
|
|
15
55
|
private userId: string = '';
|
|
@@ -27,6 +67,8 @@ export class DefaultSessionHandler implements SessionHandler {
|
|
|
27
67
|
regenerate(): void {
|
|
28
68
|
this.sessionId = uuidv4();
|
|
29
69
|
this.persistSessionId(this.sessionId);
|
|
70
|
+
// The new session's store entry (and eviction) is deferred until the first
|
|
71
|
+
// message is saved, keeping regenerate() lightweight.
|
|
30
72
|
}
|
|
31
73
|
|
|
32
74
|
getSessionId(): string {
|
|
@@ -43,6 +85,117 @@ export class DefaultSessionHandler implements SessionHandler {
|
|
|
43
85
|
return this.userId;
|
|
44
86
|
}
|
|
45
87
|
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Message persistence
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/** Persist a user message immediately when it is sent. */
|
|
93
|
+
saveUserMessage(sessionId: string, message: PersistedMessage): void {
|
|
94
|
+
try {
|
|
95
|
+
const store = this.loadSessionStore();
|
|
96
|
+
if (!store[sessionId]) {
|
|
97
|
+
// New session entry — evict the oldest existing session first if needed.
|
|
98
|
+
this.evictIfNeeded(store);
|
|
99
|
+
store[sessionId] = { createdAt: Date.now(), messages: [] };
|
|
100
|
+
}
|
|
101
|
+
store[sessionId].messages.push(message);
|
|
102
|
+
this.saveSessionStore(store);
|
|
103
|
+
} catch {
|
|
104
|
+
// localStorage not available or quota exceeded — silently ignore.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Persist the fully-streamed assistant message.
|
|
110
|
+
* If the message ID already exists in the store (shouldn't normally happen)
|
|
111
|
+
* it is replaced; otherwise it is appended.
|
|
112
|
+
*/
|
|
113
|
+
saveAssistantMessage(sessionId: string, message: PersistedMessage): void {
|
|
114
|
+
try {
|
|
115
|
+
const store = this.loadSessionStore();
|
|
116
|
+
if (!store[sessionId]) {
|
|
117
|
+
store[sessionId] = { createdAt: Date.now(), messages: [] };
|
|
118
|
+
}
|
|
119
|
+
const msgs = store[sessionId].messages;
|
|
120
|
+
const existingIdx = msgs.findIndex((m) => m.id === message.id);
|
|
121
|
+
if (existingIdx !== -1) {
|
|
122
|
+
msgs[existingIdx] = message;
|
|
123
|
+
} else {
|
|
124
|
+
msgs.push(message);
|
|
125
|
+
}
|
|
126
|
+
this.saveSessionStore(store);
|
|
127
|
+
} catch {
|
|
128
|
+
// localStorage not available or quota exceeded — silently ignore.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Return the persisted messages for the given session (newest first order preserved). */
|
|
133
|
+
loadMessages(sessionId: string): PersistedMessage[] {
|
|
134
|
+
try {
|
|
135
|
+
const store = this.loadSessionStore();
|
|
136
|
+
return store[sessionId]?.messages ?? [];
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Update the feedback field of a specific persisted message. */
|
|
143
|
+
updateMessageFeedback(
|
|
144
|
+
sessionId: string,
|
|
145
|
+
messageId: string,
|
|
146
|
+
feedback: 'upvote' | 'downvote' | null,
|
|
147
|
+
): void {
|
|
148
|
+
try {
|
|
149
|
+
const store = this.loadSessionStore();
|
|
150
|
+
const session = store[sessionId];
|
|
151
|
+
if (!session) return;
|
|
152
|
+
|
|
153
|
+
const msg = session.messages.find((m) => m.id === messageId);
|
|
154
|
+
if (!msg) return;
|
|
155
|
+
|
|
156
|
+
msg.feedback = feedback;
|
|
157
|
+
this.saveSessionStore(store);
|
|
158
|
+
} catch {
|
|
159
|
+
// localStorage not available — silently ignore.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
// Private helpers
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Remove the single oldest session from the store when it already holds
|
|
169
|
+
* MAX_SESSIONS entries. Mutates the store object in place.
|
|
170
|
+
*/
|
|
171
|
+
private evictIfNeeded(store: SessionStore): void {
|
|
172
|
+
const sessionIds = Object.keys(store);
|
|
173
|
+
if (sessionIds.length < MAX_SESSIONS) return;
|
|
174
|
+
|
|
175
|
+
let oldestId = sessionIds[0];
|
|
176
|
+
let oldestTime = store[oldestId].createdAt;
|
|
177
|
+
for (const id of sessionIds) {
|
|
178
|
+
if (store[id].createdAt < oldestTime) {
|
|
179
|
+
oldestId = id;
|
|
180
|
+
oldestTime = store[id].createdAt;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
delete store[oldestId];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private loadSessionStore(): SessionStore {
|
|
187
|
+
try {
|
|
188
|
+
const raw = localStorage.getItem(SESSIONS_KEY);
|
|
189
|
+
return raw ? (JSON.parse(raw) as SessionStore) : {};
|
|
190
|
+
} catch {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private saveSessionStore(store: SessionStore): void {
|
|
196
|
+
localStorage.setItem(SESSIONS_KEY, JSON.stringify(store));
|
|
197
|
+
}
|
|
198
|
+
|
|
46
199
|
private getPersistedSessionId(): string | null {
|
|
47
200
|
try {
|
|
48
201
|
return localStorage.getItem(SESSION_KEY);
|
|
@@ -27,6 +27,11 @@ export interface ShopPrompterApi {
|
|
|
27
27
|
onEvent: (event: ShopPrompterStreamEvent) => void,
|
|
28
28
|
): Promise<void>
|
|
29
29
|
getAgentExperience(): Promise<AgentExperience | null>
|
|
30
|
+
sendFeedback(
|
|
31
|
+
threadId: string,
|
|
32
|
+
message: string,
|
|
33
|
+
feedback: 'upvote' | 'downvote' | 'clear',
|
|
34
|
+
): Promise<void>
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
@@ -36,6 +41,21 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
36
41
|
this.config = config
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
private getHeaders(): Record<string, string> {
|
|
45
|
+
if (!this.config) {
|
|
46
|
+
return { 'Content-Type': 'application/json' }
|
|
47
|
+
}
|
|
48
|
+
const headers: Record<string, string> = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
}
|
|
51
|
+
if (this.config.shopPrompterToken) {
|
|
52
|
+
headers['Authorization'] = `Bearer ${this.config.shopPrompterToken}`
|
|
53
|
+
} else if (this.config.apiKey) {
|
|
54
|
+
headers['X-Api-Key'] = this.config.apiKey
|
|
55
|
+
}
|
|
56
|
+
return headers
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
async execute(
|
|
40
60
|
prompt: string,
|
|
41
61
|
userId: string,
|
|
@@ -47,10 +67,7 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
47
67
|
|
|
48
68
|
const response = await fetch(`${this.config.baseUrl}/interaction`, {
|
|
49
69
|
method: 'POST',
|
|
50
|
-
headers:
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
'X-Api-Key': this.config.apiKey,
|
|
53
|
-
},
|
|
70
|
+
headers: this.getHeaders(),
|
|
54
71
|
body: JSON.stringify({
|
|
55
72
|
prompt,
|
|
56
73
|
userId,
|
|
@@ -77,21 +94,13 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
const url = `${this.config.baseUrl}/interaction/stream`
|
|
80
|
-
console.log('ShopPrompter Request:', {
|
|
81
|
-
url,
|
|
82
|
-
prompt,
|
|
83
|
-
userId,
|
|
84
|
-
sessionId,
|
|
85
|
-
cartContext,
|
|
86
|
-
})
|
|
87
97
|
|
|
88
98
|
let response: Response
|
|
89
99
|
try {
|
|
90
100
|
response = await fetch(url, {
|
|
91
101
|
method: 'POST',
|
|
92
102
|
headers: {
|
|
93
|
-
|
|
94
|
-
'X-Api-Key': this.config.apiKey,
|
|
103
|
+
...this.getHeaders(),
|
|
95
104
|
Accept: 'text/event-stream',
|
|
96
105
|
'Cache-Control': 'no-cache',
|
|
97
106
|
},
|
|
@@ -111,8 +120,6 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
111
120
|
return
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
console.log('ShopPrompter Response:', response.status)
|
|
115
|
-
|
|
116
123
|
if (!response.ok) {
|
|
117
124
|
const errorText = await response.text().catch(() => '')
|
|
118
125
|
console.error('ShopPrompter API Error:', response.status, errorText)
|
|
@@ -137,24 +144,20 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
137
144
|
const { done, value } = await reader.read()
|
|
138
145
|
|
|
139
146
|
if (done) {
|
|
140
|
-
console.log('ShopPrompter Stream done')
|
|
141
147
|
onEvent({ type: 'close' })
|
|
142
148
|
break
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
const chunk = decoder.decode(value, { stream: true })
|
|
146
|
-
console.log('ShopPrompter Raw chunk:', chunk)
|
|
147
152
|
buffer += chunk
|
|
148
153
|
const lines = buffer.split('\n')
|
|
149
154
|
buffer = lines.pop() || ''
|
|
150
155
|
|
|
151
156
|
let currentEventType = ''
|
|
152
157
|
for (const line of lines) {
|
|
153
|
-
console.log('ShopPrompter Line:', line)
|
|
154
158
|
if (line.startsWith('event:')) {
|
|
155
159
|
// Capture the event type for the next data line
|
|
156
160
|
currentEventType = line.slice(6).trim()
|
|
157
|
-
console.log('ShopPrompter Event type:', currentEventType)
|
|
158
161
|
|
|
159
162
|
// If it's an error event, we need to handle it
|
|
160
163
|
if (currentEventType === 'error') {
|
|
@@ -166,7 +169,6 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
166
169
|
}
|
|
167
170
|
} else if (line.startsWith('data:')) {
|
|
168
171
|
const data = line.slice(5).trim()
|
|
169
|
-
console.log('ShopPrompter Data:', data)
|
|
170
172
|
if (data) {
|
|
171
173
|
// If we got an error event type, try to parse error details
|
|
172
174
|
if (currentEventType === 'error') {
|
|
@@ -187,7 +189,6 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
187
189
|
}
|
|
188
190
|
} else {
|
|
189
191
|
const event = this.parseEvent(data)
|
|
190
|
-
console.log('ShopPrompter Parsed event:', event)
|
|
191
192
|
if (event) {
|
|
192
193
|
onEvent(event)
|
|
193
194
|
}
|
|
@@ -217,10 +218,7 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
217
218
|
try {
|
|
218
219
|
const response = await fetch(`${this.config.baseUrl}/agent-experience`, {
|
|
219
220
|
method: 'GET',
|
|
220
|
-
headers:
|
|
221
|
-
'Content-Type': 'application/json',
|
|
222
|
-
'X-Api-Key': this.config.apiKey,
|
|
223
|
-
},
|
|
221
|
+
headers: this.getHeaders(),
|
|
224
222
|
})
|
|
225
223
|
|
|
226
224
|
if (!response.ok) {
|
|
@@ -230,11 +228,6 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
230
228
|
|
|
231
229
|
const data: AgentExperience = await response.json()
|
|
232
230
|
|
|
233
|
-
console.log(
|
|
234
|
-
'## ~ DefaultShopPrompterApi ~ getAgentExperience ~ data:',
|
|
235
|
-
data,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
231
|
return data
|
|
239
232
|
} catch (error) {
|
|
240
233
|
console.error('Error fetching agent experience:', error)
|
|
@@ -242,6 +235,41 @@ export class DefaultShopPrompterApi implements ShopPrompterApi {
|
|
|
242
235
|
}
|
|
243
236
|
}
|
|
244
237
|
|
|
238
|
+
async sendFeedback(
|
|
239
|
+
threadId: string,
|
|
240
|
+
message: string,
|
|
241
|
+
feedback: 'upvote' | 'downvote' | 'clear',
|
|
242
|
+
): Promise<void> {
|
|
243
|
+
if (!this.config) {
|
|
244
|
+
throw new Error('API configuration not set. Call setApiConfig first.')
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const response = await fetch(`${this.config.baseUrl}/feedback`, {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
headers: this.getHeaders(),
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
threadId,
|
|
253
|
+
message,
|
|
254
|
+
feedback,
|
|
255
|
+
}),
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
'We are sorry, but we could not process your feedback. Please try again later.',
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error instanceof Error && error.message.startsWith('We are sorry')) {
|
|
265
|
+
throw error
|
|
266
|
+
}
|
|
267
|
+
throw new Error(
|
|
268
|
+
'We are sorry, but we could not process your feedback. Please try again later.',
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
245
273
|
private parseEvent(data: string): ShopPrompterStreamEvent | null {
|
|
246
274
|
try {
|
|
247
275
|
const json = JSON.parse(data)
|