@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.
- package/CHANGELOG.md +16 -0
- package/dist/app-metadata.json +5 -5
- package/dist/chat-interceptors/send-message.d.ts +16 -1
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +143 -137
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/form/DescribedRadioGroup.d.ts +3 -1
- package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedRadioGroup.js +33 -18
- package/dist/components/form/DescribedRadioGroup.js.map +1 -1
- package/dist/features.d.ts +4 -0
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +1 -0
- package/dist/features.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +13 -1
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.js +8 -5
- package/dist/views/Agents/AgentsTab.js.map +1 -1
- package/dist/views/Agents/useAgentFavorites.d.ts +1 -1
- package/dist/views/Agents/useAgentFavorites.js +3 -3
- package/dist/views/Agents/useAgentFavorites.js.map +1 -1
- package/dist/views/Chat/StepsList.js +3 -3
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/MessageInput/ButtonBar.js +1 -1
- package/dist/views/MessageInput/ButtonBar.js.map +1 -1
- package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -1
- package/dist/views/MessageInput/ModelSwitcher/index.js +1 -1
- package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -1
- package/package.json +4 -4
- package/src/app-metadata.json +5 -5
- package/src/chat-interceptors/send-message.ts +159 -150
- package/src/components/form/DescribedRadioGroup.tsx +66 -39
- package/src/features.ts +8 -3
- package/src/index.ts +2 -0
- package/src/state/ChatEntry.ts +19 -7
- package/src/views/Agents/AgentsTab.tsx +40 -33
- package/src/views/Agents/useAgentFavorites.ts +3 -3
- package/src/views/Chat/StepsList.tsx +3 -3
- package/src/views/MessageInput/ButtonBar.tsx +1 -1
- package/src/views/MessageInput/ModelSwitcher/index.tsx +2 -1
- 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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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'
|
package/src/state/ChatEntry.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
})
|