@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.
@@ -1,13 +1,7 @@
1
1
  import { shopPrompterApi } from './api/shop-prompter-api'
2
+ import { productApi } from './api/product-api'
2
3
  import { sessionHandler } from './api/session-handler'
3
- import { ShopPrompterStreamEvent } from './api/shop-prompter.types'
4
-
5
- // Configuration - you can move these to environment variables
6
- const SHOP_PROMPTER_CONFIG = {
7
- baseUrl: 'https://demo.dev.shop-prompter.com/touch-points-api',
8
- apiKey:
9
- '864c87266ef52c079c09b3248d86b4b5ef430bf9e5a4188476124c93ee2f6dda380f337986837b30b60f73e6aceefe7a0e0f778b3d21a7fa7698cf8546afc709',
10
- }
4
+ import { ShopPrompterApiConfiguration, ShopPrompterCartOperation } from './api/shop-prompter.types'
11
5
 
12
6
  export interface ChatMessage {
13
7
  id: string
@@ -15,6 +9,7 @@ export interface ChatMessage {
15
9
  content: string
16
10
  productSkus?: string[]
17
11
  isStreaming?: boolean
12
+ feedback?: 'upvote' | 'downvote' | null
18
13
  }
19
14
 
20
15
  export class ShopPrompterService {
@@ -22,8 +17,9 @@ export class ShopPrompterService {
22
17
  private error: string | null = null
23
18
  private streamingText = ''
24
19
 
25
- constructor() {
26
- shopPrompterApi.setApiConfig(SHOP_PROMPTER_CONFIG)
20
+ constructor(apiConfig: ShopPrompterApiConfiguration) {
21
+ shopPrompterApi.setApiConfig(apiConfig)
22
+ productApi.setApiConfig(apiConfig)
27
23
  sessionHandler.initialize()
28
24
  }
29
25
 
@@ -34,11 +30,7 @@ export class ShopPrompterService {
34
30
  onProductSkus?: (skus: string[]) => void
35
31
  onComplete?: () => void
36
32
  onCartOperation?: (
37
- operations: {
38
- sku: string
39
- action: 'add' | 'remove'
40
- quantity: number
41
- }[],
33
+ operations: ShopPrompterCartOperation[],
42
34
  ) => void
43
35
  onError?: (error: string) => void
44
36
  onLoadingChange?: (loading: boolean) => void
@@ -53,7 +45,19 @@ export class ShopPrompterService {
53
45
  const userId = sessionHandler.getUserId()
54
46
  const sessionId = sessionHandler.getSessionId()
55
47
 
48
+ // Persist the user message immediately.
49
+ const userMsgId = Date.now().toString() + '-user'
50
+ sessionHandler.saveUserMessage(sessionId, {
51
+ id: userMsgId,
52
+ role: 'user',
53
+ content: prompt,
54
+ productSkus: [],
55
+ })
56
+
57
+ // Tracking variables for the assistant reply.
58
+ const assistantMsgId = Date.now().toString() + '-assistant'
56
59
  let accumulatedText = ''
60
+ let accumulatedSkus: string[] = []
57
61
 
58
62
  try {
59
63
  await shopPrompterApi.executeStream(
@@ -69,6 +73,7 @@ export class ShopPrompterService {
69
73
  break
70
74
 
71
75
  case 'productSkus':
76
+ accumulatedSkus = event.skus
72
77
  callbacks.onProductSkus?.(event.skus)
73
78
  break
74
79
 
@@ -85,6 +90,13 @@ export class ShopPrompterService {
85
90
  this.isLoading = false
86
91
  callbacks.onLoadingChange?.(false)
87
92
  callbacks.onComplete?.()
93
+ // Persist the fully-streamed assistant message.
94
+ sessionHandler.saveAssistantMessage(sessionId, {
95
+ id: assistantMsgId,
96
+ role: 'assistant',
97
+ content: accumulatedText,
98
+ productSkus: accumulatedSkus,
99
+ })
88
100
  break
89
101
 
90
102
  case 'error':
@@ -92,6 +104,7 @@ export class ShopPrompterService {
92
104
  this.isLoading = false
93
105
  callbacks.onLoadingChange?.(false)
94
106
  callbacks.onError?.(event.reason)
107
+ // Do NOT persist a failed assistant message.
95
108
  break
96
109
  }
97
110
  },
@@ -109,9 +122,6 @@ export class ShopPrompterService {
109
122
  }
110
123
 
111
124
  public async getAgentExperience() {
112
- console.log(
113
- '## ~ ShopPrompterService ~ getAgentExperience ~ getAgentExperience:',
114
- )
115
125
 
116
126
  try {
117
127
  return await shopPrompterApi.getAgentExperience()
@@ -121,6 +131,38 @@ export class ShopPrompterService {
121
131
  }
122
132
  }
123
133
 
134
+ /**
135
+ * Load persisted chat messages for the current session from localStorage.
136
+ * Returns an empty array if nothing is stored yet.
137
+ */
138
+ public loadMessages(): ChatMessage[] {
139
+ const sessionId = sessionHandler.getSessionId()
140
+ const persisted = sessionHandler.loadMessages(sessionId)
141
+ return persisted.map((msg) => ({
142
+ id: msg.id,
143
+ role: msg.role,
144
+ content: msg.content,
145
+ productSkus: msg.productSkus ?? [],
146
+ feedback: msg.feedback ?? null,
147
+ }))
148
+ }
149
+
150
+ public async sendFeedback(
151
+ messageId: string,
152
+ messageContent: string,
153
+ feedback: 'upvote' | 'downvote' | 'clear',
154
+ ): Promise<void> {
155
+ const userId = sessionHandler.getUserId()
156
+ const sessionId = sessionHandler.getSessionId()
157
+ const threadId = `${userId}:${sessionId}`
158
+
159
+ await shopPrompterApi.sendFeedback(threadId, messageContent, feedback)
160
+
161
+ // Persist the feedback locally on success.
162
+ const storedFeedback = feedback === 'clear' ? null : feedback
163
+ sessionHandler.updateMessageFeedback(sessionId, messageId, storedFeedback)
164
+ }
165
+
124
166
  public resetSession(): void {
125
167
  sessionHandler.regenerate()
126
168
  }
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+
3
+ function ThumbsDownIcon(props) {
4
+ return (
5
+ <svg
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ width="18"
8
+ height="18"
9
+ viewBox="0 0 24 24"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ fill={props.fill || "none"}
14
+ stroke={props.stroke || "currentColor"}
15
+ style={{
16
+ overflow: "visible",
17
+ }}
18
+ >
19
+ <path d="M17 14V2" />
20
+ <path d="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" />
21
+ </svg>
22
+ );
23
+ }
24
+
25
+ export default ThumbsDownIcon;
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+
3
+ function ThumbsUpIcon(props) {
4
+ return (
5
+ <svg
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ width="18"
8
+ height="18"
9
+ viewBox="0 0 24 24"
10
+ stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ fill={props.fill || "none"}
14
+ stroke={props.stroke || "currentColor"}
15
+ style={{
16
+ overflow: "visible",
17
+ }}
18
+ >
19
+ <path d="M7 10v12" />
20
+ <path d="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" />
21
+ </svg>
22
+ );
23
+ }
24
+
25
+ export default ThumbsUpIcon;