@stack-spot/ai-chat-widget 2.4.1 → 2.5.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/app-metadata.json +5 -5
  3. package/dist/chat-interceptors/send-message.d.ts +16 -1
  4. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  5. package/dist/chat-interceptors/send-message.js +143 -137
  6. package/dist/chat-interceptors/send-message.js.map +1 -1
  7. package/dist/components/form/DescribedRadioGroup.d.ts +3 -1
  8. package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
  9. package/dist/components/form/DescribedRadioGroup.js +33 -18
  10. package/dist/components/form/DescribedRadioGroup.js.map +1 -1
  11. package/dist/features.d.ts +4 -0
  12. package/dist/features.d.ts.map +1 -1
  13. package/dist/features.js +1 -0
  14. package/dist/features.js.map +1 -1
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/state/ChatEntry.d.ts +13 -1
  20. package/dist/state/ChatEntry.d.ts.map +1 -1
  21. package/dist/state/ChatEntry.js.map +1 -1
  22. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  23. package/dist/views/Agents/AgentsTab.js +8 -5
  24. package/dist/views/Agents/AgentsTab.js.map +1 -1
  25. package/dist/views/Agents/useAgentFavorites.d.ts +1 -1
  26. package/dist/views/Agents/useAgentFavorites.js +3 -3
  27. package/dist/views/Agents/useAgentFavorites.js.map +1 -1
  28. package/dist/views/Chat/StepsList.js +3 -3
  29. package/dist/views/Chat/StepsList.js.map +1 -1
  30. package/dist/views/MessageInput/ButtonBar.js +1 -1
  31. package/dist/views/MessageInput/ButtonBar.js.map +1 -1
  32. package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -1
  33. package/dist/views/MessageInput/ModelSwitcher/index.js +1 -1
  34. package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/app-metadata.json +5 -5
  37. package/src/chat-interceptors/send-message.ts +159 -150
  38. package/src/components/form/DescribedRadioGroup.tsx +66 -39
  39. package/src/features.ts +8 -3
  40. package/src/index.ts +2 -0
  41. package/src/state/ChatEntry.ts +19 -7
  42. package/src/views/Agents/AgentsTab.tsx +40 -33
  43. package/src/views/Agents/useAgentFavorites.ts +3 -3
  44. package/src/views/Chat/StepsList.tsx +3 -3
  45. package/src/views/MessageInput/ButtonBar.tsx +1 -1
  46. package/src/views/MessageInput/ModelSwitcher/index.tsx +2 -1
  47. package/src/views/MessageInput/index.tsx +2 -2
@@ -1,4 +1,4 @@
1
- import { AgentInfo, aiClient, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
1
+ import { AgentInfo, aiClient, ChatResponseWithPMResources, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
2
2
  import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
3
3
  import { findLast } from 'lodash'
4
4
  import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
@@ -18,8 +18,8 @@ import { planningToolDictionaryHelper } from '../utils/planning-tool'
18
18
  * @param includeDate whether or not to include the date in the chat entry.
19
19
  * @returns the TextChatEntry to build a ChatEntry.
20
20
  */
21
- function createEntryValueFromChatResponse(
22
- response: Partial<ChatResponseWithSteps>,
21
+ export function createEntryValueFromChatResponse(
22
+ response: Partial<ChatResponseWithSteps> & ChatResponseWithPMResources,
23
23
  knowledgeSources: KnowledgeSource[] | undefined,
24
24
  agent: LabeledWithImage | undefined,
25
25
  includeDate = false,
@@ -34,6 +34,9 @@ function createEntryValueFromChatResponse(
34
34
  updated: includeDate ? new Date().toISOString() : undefined,
35
35
  steps: response.steps,
36
36
  tools: response.tools,
37
+ opportunities: response.opportunities,
38
+ hypothesis: response.hypothesis,
39
+ prfaq: response.prfaq,
37
40
  }
38
41
  return entry as TextChatEntry
39
42
  }
@@ -43,6 +46,155 @@ function buildPrompt(content: string, data?: any) {
43
46
  return typeof data === 'string' ? data : JSON.stringify(data)
44
47
  }
45
48
 
49
+ //Verify if the last planning in the messages has status awaiting_approval
50
+ const getLastPlanningAwaiting = (messages: ChatEntry[]) => findLast(messages, item => {
51
+ const steps = item.getValue().steps
52
+ if (steps) {
53
+ const hasPlanning = steps.find((step) => step.type === 'planning')
54
+ return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
55
+ }
56
+ return false
57
+ })
58
+
59
+ const updateToolStatus = (agentInfo: AgentInfo, messages: ChatEntry[]) => {
60
+ const executionId = agentInfo.id
61
+
62
+ if (executionId) {
63
+ //Update message with type step which contains the planning steps
64
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
65
+ const originalItem = messages.find((message) => `${message.id}` === messageId)
66
+ const originalItemValue = originalItem?.getValue()
67
+ let update = false
68
+ const status = agentInfo.action === 'start' ? 'running' : 'success'
69
+ const step = originalItemValue?.steps
70
+ ?.find(step => step.type === 'step' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
71
+ if (step && step.status !== status) {
72
+ step.status = status
73
+ update = true
74
+ }
75
+ if (update) {
76
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
77
+ }
78
+
79
+ //Updates message with type tool which contains the actually tool steps
80
+ //We only want to show tools banner when they are awaiting_approval, by removing the step
81
+ // we avoid the entire bot message to be visible
82
+ const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
83
+ const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
84
+ const toolOriginalItemValue = toolOriginalItem?.getValue()
85
+ const toolStep = toolOriginalItemValue?.steps?.find(step =>
86
+ step.type === 'tool' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
87
+ update = false
88
+ if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
89
+ toolOriginalItemValue.steps = undefined
90
+ update = true
91
+ }
92
+ if (update) {
93
+ toolOriginalItem?.setValue({ ...toolOriginalItemValue as TextChatEntry })
94
+ }
95
+ }
96
+ }
97
+
98
+ const updateStepMessage = (step: ChatStep, messages: ChatEntry[]) => {
99
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
100
+
101
+ if (lastPlanningAwaiting) {
102
+ const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
103
+ const originalItemValue = originalItem?.getValue()
104
+ originalItemValue?.steps?.filter((messageStep) => {
105
+ if (messageStep.id === step.id) {
106
+ messageStep = { ...step }
107
+ }
108
+ })
109
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
110
+ }
111
+ }
112
+
113
+
114
+ const updatePlanningMessage = (messages: ChatEntry[]) => {
115
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
116
+
117
+ if (lastPlanningAwaiting) {
118
+ const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
119
+ const originalItemValue = originalItem?.getValue()
120
+ originalItemValue?.steps?.map((step) => {
121
+ if (step.type === 'planning' && step.status === 'awaiting_approval') {
122
+ step.status = 'success'
123
+ }
124
+ })
125
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
126
+ }
127
+ }
128
+
129
+ export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResponseWithSteps> & { opportunities?: any },
130
+ chat: ChatState, botEntry: ChatEntry, knowledgeSources: KnowledgeSource[] | undefined) {
131
+ if (value.agent_info?.type === 'planning') {
132
+ if (value.agent_info.action === 'start') {
133
+ chat.set('isPlaning', true)
134
+ } else {
135
+ chat.set('isPlaning', false)
136
+ }
137
+ }
138
+
139
+ if (value.agent_info?.type === 'chat' && value.agent_info?.action === 'end') {
140
+ //When an error happens, the step can still be running, so we enforce the error
141
+ const stepRunning = findLast(value.steps, (item) => item.status === 'running')
142
+ if (stepRunning?.status) {
143
+ stepRunning.status = 'error'
144
+ }
145
+ }
146
+
147
+ if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
148
+ knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
149
+ }
150
+
151
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
152
+ if (lastPlanningAwaiting && value.steps) {
153
+ value.steps?.map(step => {
154
+ if (step.type === 'planning') {
155
+ updatePlanningMessage(messages)
156
+ } else if (step.type === 'step') {
157
+ updateStepMessage(step, messages)
158
+ }
159
+ })
160
+ }
161
+
162
+ if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
163
+ updateToolStatus(value.agent_info, messages)
164
+ }
165
+
166
+ if (value.steps) {
167
+ const tool = findLast(value.steps, (item) => item.type === 'tool')
168
+ if (tool && tool.status === 'running') {
169
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
170
+ const originalItem = messages.find((message) => `${message.id}` === messageId)
171
+ const originalItemValue = originalItem?.getValue()
172
+ let update = false
173
+ const step = originalItemValue?.steps?.find(step => step.type === 'step')
174
+ if (step && step.attempts?.[0]?.tools?.[0]?.executionId === tool.id) {
175
+ step.attempts?.forEach((attempt, i) => {
176
+ const newAttempt = tool.attempts?.[i]
177
+ if (!newAttempt) return
178
+ attempt.tools = attempt.tools?.map((origTool, j) => {
179
+ const newTool = newAttempt.tools?.[j]
180
+ if (!newTool || origTool?.executionId !== newTool?.executionId) return origTool
181
+ update = true
182
+ return { ...origTool, ...newTool }
183
+ })
184
+ })
185
+ if (step.attempts.length < tool.attempts.length) {
186
+ step.attempts.push(tool.attempts[tool.attempts.length - 1])
187
+ }
188
+ }
189
+ if (update) {
190
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
191
+ }
192
+ }
193
+ }
194
+
195
+ botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
196
+ }
197
+
46
198
  /**
47
199
  * The chat interceptor that sends the message to the AI agent, interprets the response and adds it to the chat.
48
200
  *
@@ -59,24 +211,13 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
59
211
  const context = buildConversationContext(chat, entry)
60
212
  chat.set('isLoading', true)
61
213
  const untitled = chat.untitled
62
- const isFirstMessage = chat.getMessages().length === 1
214
+ const messages = chat.getMessages()
215
+ const isFirstMessage = messages.length === 1
63
216
  if (untitled) {
64
217
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
65
218
  chat.untitled = false
66
219
  }
67
220
 
68
- const messages = chat.getMessages()
69
-
70
- //Verify if the last planning in the messages has status awaiting_approval
71
- const getLastPlanningAwaiting = () => findLast(messages, item => {
72
- const steps = item.getValue().steps
73
- if (steps) {
74
- const hasPlanning = steps.find((step) => step.type === 'planning')
75
- return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
76
- }
77
- return false
78
- })
79
-
80
221
  const stream = aiClient.sendChatMessage({ context, user_prompt: buildPrompt(content, data) })
81
222
  signal.addEventListener('abort', () => stream.cancel())
82
223
  const botEntry = ChatEntry.createStreamedBotEntry()
@@ -86,147 +227,15 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
86
227
  }
87
228
  let knowledgeSources: KnowledgeSource[] | undefined
88
229
 
89
- const updatePlanningMessage = () => {
90
- const lastPlanningAwaiting = getLastPlanningAwaiting()
91
-
92
- if (lastPlanningAwaiting) {
93
- const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
94
- const originalItemValue = originalItem?.getValue()
95
- originalItemValue?.steps?.map((step) => {
96
- if (step.type === 'planning' && step.status === 'awaiting_approval') {
97
- step.status = 'success'
98
- }
99
- })
100
- originalItem?.setValue({ ...originalItemValue as TextChatEntry })
101
- }
102
- }
103
-
104
- const updateStepMessage = (step: ChatStep) => {
105
- const lastPlanningAwaiting = getLastPlanningAwaiting()
106
-
107
- if (lastPlanningAwaiting) {
108
- const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
109
- const originalItemValue = originalItem?.getValue()
110
- originalItemValue?.steps?.filter((messageStep) => {
111
- if (messageStep.id === step.id) {
112
- messageStep = { ...step }
113
- }
114
- })
115
- originalItem?.setValue({ ...originalItemValue as TextChatEntry })
116
- }
117
- }
118
-
119
- const updateToolStatus = (agentInfo: AgentInfo) => {
120
- const executionId = agentInfo.id
121
- if (executionId) {
122
- //Update message with type step which contains the planning steps
123
- const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
124
- const originalItem = messages.find((message) => `${message.id}` === messageId)
125
- const originalItemValue = originalItem?.getValue()
126
- let update = false
127
- const status = agentInfo.action === 'start' ? 'running' : 'success'
128
- const step = originalItemValue?.steps
129
- ?.find(step => step.type === 'step' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
130
- if (step && step.status !== status) {
131
- step.status = status
132
- update = true
133
- }
134
- if (update) {
135
- originalItem?.setValue({ ...originalItemValue as TextChatEntry })
136
- }
137
-
138
- //Updates message with type tool which contains the actually tool steps
139
- //We only want to show tools banner when they are awaiting_approval, by removing the step
140
- // we avoid the entire bot message to be visible
141
- const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
142
- const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
143
- const toolOriginalItemValue = toolOriginalItem?.getValue()
144
- const toolStep = toolOriginalItemValue?.steps?.find(step =>
145
- step.type === 'tool' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
146
- update = false
147
- if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
148
- toolOriginalItemValue.steps = undefined
149
- update = true
150
- }
151
- if (update) {
152
- toolOriginalItem?.setValue({ ...toolOriginalItemValue as TextChatEntry })
153
- }
154
- }
155
- }
156
-
157
230
  stream.onChange(value => {
158
- if (value.agent_info?.type === 'planning') {
159
- if (value.agent_info.action === 'start') {
160
- chat.set('isPlaning', true)
161
- } else {
162
- chat.set('isPlaning', false)
163
- }
164
- }
165
-
166
- if (value.agent_info?.type === 'chat' && value.agent_info?.action === 'end') {
167
- //When an error happens, the step can still be running, so we enforce the error
168
- const stepRunning = findLast(value.steps, (item) => item.status === 'running')
169
- if (stepRunning?.status) {
170
- stepRunning.status = 'error'
171
- }
172
- }
173
-
174
- if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
175
- knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
176
- }
177
-
178
- const lastPlanningAwaiting = getLastPlanningAwaiting()
179
- if (lastPlanningAwaiting && value.steps) {
180
- value.steps?.map(step => {
181
- if (step.type === 'planning') {
182
- updatePlanningMessage()
183
- } else if (step.type === 'step') {
184
- updateStepMessage(step)
185
- }
186
- })
187
- }
188
-
189
- if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
190
- updateToolStatus(value.agent_info)
191
- }
192
-
193
- if (value.steps) {
194
- const tool = findLast(value.steps, (item) => item.type === 'tool')
195
- if (tool && tool.status === 'running') {
196
- const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
197
- const originalItem = messages.find((message) => `${message.id}` === messageId)
198
- const originalItemValue = originalItem?.getValue()
199
- let update = false
200
- const step = originalItemValue?.steps?.find(step => step.type === 'step')
201
- if (step && step.attempts?.[0]?.tools?.[0]?.executionId === tool.id) {
202
- step.attempts?.forEach((attempt, i) => {
203
- const newAttempt = tool.attempts?.[i]
204
- if (!newAttempt) return
205
- attempt.tools = attempt.tools?.map((origTool, j) => {
206
- const newTool = newAttempt.tools?.[j]
207
- if (!newTool || origTool?.executionId !== newTool?.executionId) return origTool
208
- update = true
209
- return { ...origTool, ...newTool }
210
- })
211
- })
212
- if (step.attempts.length < tool.attempts.length) {
213
- step.attempts.push(tool.attempts[tool.attempts.length - 1])
214
- }
215
- }
216
- if (update) {
217
- originalItem?.setValue({ ...originalItemValue as TextChatEntry })
218
- }
219
- }
220
- }
221
-
222
- botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
231
+ helperSendMessage(messages, value, chat, botEntry, knowledgeSources)
223
232
  })
224
233
 
225
234
  let finalValue: Partial<ChatResponse3> | undefined
226
235
  try {
227
236
  finalValue = await stream.getValue()
228
237
 
229
- const lastPlanningAwaiting = getLastPlanningAwaiting()
238
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
230
239
  if (lastPlanningAwaiting) {
231
240
  const value = lastPlanningAwaiting.getValue()
232
241
  value.content = finalValue.answer || value.content
@@ -1,6 +1,9 @@
1
1
  import { Icon } from '@stack-spot/citric-icons'
2
2
  import { Accordion, FieldGroup, ImageBox, Input, RadioGroup, Row, Text, useRadioGroupControls } from '@stack-spot/citric-react'
3
+ import { InfiniteScroll } from '@stack-spot/portal-components/InfiniteScroll'
4
+ import { isEqual } from 'lodash'
3
5
  import { useEffect, useState } from 'react'
6
+ import styled from 'styled-components'
4
7
  import { ButtonFavorite, Favorite } from '../ButtonFavorite'
5
8
 
6
9
  interface Props<T> {
@@ -15,15 +18,24 @@ interface Props<T> {
15
18
  emptyResults: React.ReactNode,
16
19
  emptyDataset: React.ReactNode,
17
20
  onChange?: (value: T | undefined) => void,
21
+ fetchNextPage?: () => void,
22
+ hasNextPage?: boolean,
18
23
  }
19
24
 
25
+ const StyledDiv = styled.div`
26
+ &#agents-content {
27
+ overflow: auto;
28
+ }
29
+ `
30
+
20
31
  /**
21
32
  * Renders a radio group where each option has a label and a description.
22
33
  * The description in placed under the label and checkbox as an accordion.
23
34
  *
24
35
  * Also renders a search input.
25
36
  */
26
- export function DescribedRadioGroup<T>({ initialValue, options: opt, data, emptyDataset, emptyResults, onChange }: Props<T>) {
37
+ export function DescribedRadioGroup<T>({ initialValue, options: opt, data, emptyDataset, emptyResults, onChange,
38
+ fetchNextPage, hasNextPage }: Props<T>) {
27
39
  const [options, setOptions] = useState(opt)
28
40
  const controls = useRadioGroupControls({
29
41
  initialValue,
@@ -37,48 +49,63 @@ export function DescribedRadioGroup<T>({ initialValue, options: opt, data, empty
37
49
  if (controls.value === undefined && initialValue !== undefined) controls.setValue(initialValue)
38
50
  }, [initialValue])
39
51
 
52
+ useEffect(() => {
53
+ if (!isEqual(opt, options)) {
54
+ setOptions(opt)
55
+ }
56
+ }, [opt])
57
+
40
58
  return options.length ? <>
41
59
  <FieldGroup fullWidth>
42
60
  <Icon icon="Search" />
43
61
  <Input type="search" value={controls.filter} onChange={controls.setFilter} />
44
62
  </FieldGroup>
45
- {controls.options.length ? <RadioGroup
46
- options={controls.options}
47
- value={controls.value}
48
- onChange={controls.setValue}
49
- renderKey={controls.renderKey}
50
- className="option-list"
51
- renderItem={(radio, o) => {
52
- const { description, idOrSlug, name, image, listFavorites, onAddFavorite, onRemoveFavorite } = data(o)
53
- return (
54
- <Row className={controls.isUnfilteredButChecked(o) ? 'filtered-out' : ''}>
55
- <Accordion
56
- header={btn => <>
57
- {radio}
58
- {image && <ImageBox size="xs" style={{ fontSize: '16px' }}>{image}</ImageBox>}
59
- <Text>{name}</Text>{btn}</>
60
- }
61
- appearance="card"
62
- >
63
- {typeof description === 'string' ? <Text appearance="microtext1" color="light.700">{description}</Text> : description}
64
- </Accordion>
65
- {onAddFavorite && <ButtonFavorite favorite={{
66
- idOrSlug: idOrSlug,
67
- listFavorites,
68
- onAddFavorite: async (...args) => {
69
- const res = await onAddFavorite(...args)
70
- setOptions([...options]) // forces options re-rendering
71
- return res
72
- },
73
- onRemoveFavorite: async (...args) => {
74
- const res = await onRemoveFavorite?.(...args)
75
- setOptions([...options]) // forces options re-rendering
76
- return res ?? false
77
- },
78
- }} />}
79
- </Row>
80
- )
81
- }}
82
- /> : emptyResults}
63
+ <StyledDiv id="agents-content">
64
+ {controls.options.length ?
65
+ <InfiniteScroll scrollableTarget="agents-content"
66
+ dataLength={controls.options.length}
67
+ next={fetchNextPage as any ?? undefined}
68
+ hasMore={hasNextPage ?? false}>
69
+ <RadioGroup
70
+ options={controls.options}
71
+ value={controls.value}
72
+ onChange={controls.setValue}
73
+ renderKey={controls.renderKey}
74
+ className="option-list"
75
+ renderItem={(radio, o) => {
76
+ const { description, idOrSlug, name, image, listFavorites, onAddFavorite, onRemoveFavorite } = data(o)
77
+ return (
78
+ <Row className={controls.isUnfilteredButChecked(o) ? 'filtered-out' : ''}>
79
+ <Accordion
80
+ header={btn => <>
81
+ {radio}
82
+ {image && <ImageBox size="xs" style={{ fontSize: '16px' }}>{image}</ImageBox>}
83
+ <Text>{name}</Text>{btn}</>
84
+ }
85
+ appearance="card"
86
+ >
87
+ {typeof description === 'string' ? <Text appearance="microtext1" color="light.700">{description}</Text> : description}
88
+ </Accordion>
89
+ {onAddFavorite && <ButtonFavorite favorite={{
90
+ idOrSlug: idOrSlug,
91
+ listFavorites,
92
+ onAddFavorite: async (...args) => {
93
+ const res = await onAddFavorite(...args)
94
+ setOptions([...options]) // forces options re-rendering
95
+ return res
96
+ },
97
+ onRemoveFavorite: async (...args) => {
98
+ const res = await onRemoveFavorite?.(...args)
99
+ setOptions([...options]) // forces options re-rendering
100
+ return res ?? false
101
+ },
102
+ }} />}
103
+ </Row>
104
+ )
105
+ }}
106
+ />
107
+ </InfiniteScroll>
108
+ : emptyResults}
109
+ </StyledDiv>
83
110
  </> : emptyDataset
84
111
  }
package/src/features.ts CHANGED
@@ -39,6 +39,10 @@ export interface ChatFeatures {
39
39
  * Enables streaming in chat responses.
40
40
  */
41
41
  streaming: boolean,
42
+ /**
43
+ * Enables LLM select.
44
+ */
45
+ showLLMSelect: boolean,
42
46
  }
43
47
 
44
48
  export type Scope = 'favorite' | 'builtin' | 'personal' | 'shared' | 'workspace' | 'account'
@@ -54,9 +58,9 @@ export interface GlobalFeatures {
54
58
  * If this is set, only the scopes in this list will be enabled.
55
59
  */
56
60
  scopes?: Scope[],
57
- /**
58
- * When set, only items(knowledge sources, agents, stacks ai, quick commands) from that workspace will be shown to the user.
59
- */
61
+ /**
62
+ * When set, only items(knowledge sources, agents, stacks ai, quick commands) from that workspace will be shown to the user.
63
+ */
60
64
  workspaceId?: string,
61
65
  /**
62
66
  * when set to false it does not show tab by scope, it shows a single list.
@@ -83,6 +87,7 @@ export function getFeaturesWithDefaults(features?: Partial<AIWidgetFeatures>): A
83
87
  messageInput: true,
84
88
  upload: true,
85
89
  streaming: true,
90
+ showLLMSelect: true,
86
91
  showSourcesInResponse: true,
87
92
  groupResourcesByScope: true,
88
93
  ...features,
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /* Attention: in order for the package "page" to work without linking the lib, we must export types separately, using the "type" keyword */
2
2
 
3
+
4
+ export { createEntryValueFromChatResponse, helperSendMessage } from './chat-interceptors/send-message'
3
5
  export { QuickStartButton } from './components/QuickStartButton'
4
6
  export { AIWidgetProvider } from './context/AIWidgetProvider'
5
7
  export * from './context/hooks'
@@ -1,10 +1,10 @@
1
- import { ChatStep } from '@stack-spot/portal-network'
1
+ import { ChatStep, HypothesisPMAgent, OpportunitiesPMAgent, PrfaqPMAgent } from '@stack-spot/portal-network'
2
2
  import { ColorPaletteName, ColorSchemeName } from '@stack-spot/portal-theme'
3
3
  import { pull } from 'lodash'
4
4
  import { LabeledAgent } from './types'
5
5
 
6
6
  export interface ActionDataClick {
7
- name?: string,
7
+ name?: string,
8
8
  value?: string[],
9
9
  }
10
10
 
@@ -44,9 +44,9 @@ export interface ChatAction extends SerializableAction {
44
44
  * @default button
45
45
  */
46
46
  buttonType?: 'submit' | 'button',
47
- /**
48
- * @default lg
49
- */
47
+ /**
48
+ * @default lg
49
+ */
50
50
  size?: 'lg' | 'md' | 'sm',
51
51
  }
52
52
 
@@ -146,10 +146,22 @@ export interface TextChatEntry {
146
146
  * If any tool was used to generate the response, its id is returned in this list.
147
147
  */
148
148
  tools?: string[],
149
+ /**
150
+ * If opportunities exists in pm agent, its send in this list.
151
+ */
152
+ opportunities?: OpportunitiesPMAgent[],
153
+ /**
154
+ * If hypothesis exists in pm agent, its send in this list.
155
+ */
156
+ hypothesis?: HypothesisPMAgent[],
157
+ /**
158
+ * If prfaq exists in pm agent, its send in this object.
159
+ */
160
+ prfaq?: PrfaqPMAgent,
149
161
  /*
150
162
  * Options for radio, checkbox or button type.
151
163
  */
152
- options?: { color?: ColorSchemeName, label: string, value?: string, hasInput?: boolean}[],
164
+ options?: { color?: ColorSchemeName, label: string, value?: string, hasInput?: boolean }[],
153
165
  /**
154
166
  * Name to be used in input type fields.
155
167
  */
@@ -211,7 +223,7 @@ export class ChatEntry {
211
223
  type: isMd ? 'md' : 'text',
212
224
  content,
213
225
  name: fieldName,
214
- hiddenContent,
226
+ hiddenContent,
215
227
  data,
216
228
  updated: new Date().toISOString(),
217
229
  })