Skip to content

Commit c845fc9

Browse files
authored
[masterbots.ai] feat: chatbot search tool v0.1a (#295)
* wip: chatbot search tool * wip(impr): ww api + chat ui tweaks * fix: init sidebar load state * fix: nesting layout * fix: thread-popup ui header * wip(impr): chatbot tooling * impr: loading state + debug chatbot tools * wip(fix): nodejs context * fix(temp): rm edge runtime api config
1 parent 4100858 commit c845fc9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1378
-1241
lines changed

apps/masterbots.ai/actions.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

apps/masterbots.ai/app/(browse)/layout.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ interface BrowseLayoutProps {
1111
export default async function BrowseLayout({ children }: BrowseLayoutProps) {
1212
return (
1313
<BrowseProvider>
14-
<main className="flex flex-col w-full h-[calc(100vh-theme(spacing.16))]">
14+
<section className="flex flex-col w-full h-[calc(100vh-theme(spacing.16))]">
1515
<NextTopLoader color="#1ED761" initialPosition={0.2} />
1616
<ResponsiveSidebar />
17-
<ChatLayoutSection>{children}</ChatLayoutSection>
18-
<div className="group w-full overflow-auto animate-in duration-300 ease-in-out relative lg:w-[calc(100%-250px)] xl:w-[calc(100%-300px)] px-4 md:px-10 lg:ml-[250px] xl:ml-[300px] ">
17+
<ChatLayoutSection>
18+
{children}
19+
</ChatLayoutSection>
1920
<FooterCT />
20-
</div>
21-
</main>
21+
</section>
2222
</BrowseProvider>
2323
)
2424
}
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
'use server'
2+
3+
import { parseWordwareResponse } from '@/components/shared/wordware-chat'
4+
import { wordwareFlows } from '@/lib/constants/wordware-flows'
5+
import type { aiTools } from '@/lib/helpers/ai-schemas'
6+
import { validateAndSanitizeJson } from '@/lib/helpers/ai-streams'
7+
import { WordWareDescribeDAtaResponse } from '@/types/wordware-flows.types'
8+
import axios from 'axios'
9+
import type { z } from 'zod'
10+
import { subtractChatbotMetadataLabels } from '.'
11+
12+
const { WORDWARE_API_KEY } = process.env
13+
14+
export async function getChatbotMetadataTool({
15+
chatbot,
16+
userContent
17+
}: z.infer<typeof aiTools.chatbotMetadataExamples.parameters>) {
18+
console.info('Executing Chatbot Metadata Tool... Chatbot: ', {
19+
chatbot,
20+
userContent
21+
})
22+
23+
try {
24+
const chatbotMetadata = await subtractChatbotMetadataLabels(
25+
{
26+
domain: chatbot.categoryId,
27+
chatbot: chatbot.chatbotId
28+
},
29+
userContent,
30+
// ? We will be using OpenAi for a while, at least for these tools
31+
'OpenAI'
32+
)
33+
34+
console.log('chatbotMetadata ==> ', chatbotMetadata)
35+
return JSON.stringify({
36+
chatbotMetadata
37+
})
38+
} catch (error) {
39+
console.error('Error fetching chatbot metadata: ', error)
40+
return JSON.stringify({
41+
error: 'Internal Server Error while fetching chatbot metadata'
42+
})
43+
}
44+
}
45+
46+
export async function getWebSearchTool({
47+
query
48+
}: z.infer<typeof aiTools.webSearch.parameters>) {
49+
console.info('Executing Web Search Tool... Query: ', query)
50+
const webSearchFlow = wordwareFlows.find(flow => flow.path === 'webSearch')
51+
52+
if (!webSearchFlow) {
53+
return JSON.stringify({
54+
error: 'Web Search tool is not available'
55+
})
56+
}
57+
58+
try {
59+
const appDataResponse = await axios.get(
60+
`https://api.wordware.ai/v1alpha/apps/masterbots/${webSearchFlow.id}`,
61+
{
62+
headers: {
63+
Authorization: `Bearer ${WORDWARE_API_KEY}`,
64+
'Content-Type': 'application/json'
65+
}
66+
}
67+
)
68+
69+
if (appDataResponse.status >= 400) {
70+
console.error('Error fetching app data: ', appDataResponse)
71+
if (appDataResponse.status >= 500) {
72+
return JSON.stringify({
73+
error:
74+
'Internal Server Error while fetching app data. Please try again later.'
75+
})
76+
}
77+
return JSON.stringify({
78+
error: 'Failed to authenticate for the app. Please try again.'
79+
})
80+
}
81+
82+
const appData: WordWareDescribeDAtaResponse = await appDataResponse.data
83+
84+
console.log('appData ==> ', appData)
85+
86+
const runAppResponse = await fetch(
87+
`https://api.wordware.ai/v1alpha/apps/masterbots/${webSearchFlow.id}/${appData.version}/runs/stream`,
88+
{
89+
method: 'POST',
90+
headers: {
91+
Authorization: `Bearer ${WORDWARE_API_KEY}`,
92+
'Content-Type': 'application/json'
93+
},
94+
body: JSON.stringify({
95+
inputs: {
96+
query
97+
}
98+
})
99+
}
100+
)
101+
102+
if (
103+
runAppResponse.status >= 400 ||
104+
!runAppResponse.ok ||
105+
!runAppResponse.body
106+
) {
107+
console.error('Error running app: ', runAppResponse)
108+
if (appDataResponse.status >= 500) {
109+
return JSON.stringify({
110+
error:
111+
'Internal Server Error while fetching app data. Please try again later.'
112+
})
113+
}
114+
return JSON.stringify({
115+
error: 'Failed to authenticate for the app. Please try again.'
116+
})
117+
}
118+
119+
const reader = runAppResponse.body.getReader()
120+
const decoder = new TextDecoder()
121+
const jsonRegex =
122+
/data:\s*\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\}))*\}))*\}/g
123+
124+
let buffer = ''
125+
let results = ''
126+
127+
try {
128+
while (true) {
129+
const { done, value } = await reader.read()
130+
if (done) break
131+
132+
buffer += decoder.decode(value, { stream: true })
133+
134+
// console.log('Buffer: [Before Changes] => ', buffer)
135+
136+
let match
137+
let lastIndex = 0
138+
while ((match = jsonRegex.exec(buffer)) !== null) {
139+
const jsonStr = match[0].replace(/^data:\s*/, '')
140+
const validatedJson = validateAndSanitizeJson(jsonStr)
141+
if (validatedJson) {
142+
const jsonData = JSON.parse(validatedJson)
143+
console.log('jsonData [TRY] ==> ', jsonData)
144+
if (jsonData.path === webSearchFlow.path) {
145+
console.log('jsonData [CAUGHT] ==> ', jsonData)
146+
147+
results += jsonData.content
148+
}
149+
}
150+
lastIndex = jsonRegex.lastIndex
151+
}
152+
153+
//* Keeping the unmatched part in the buffer
154+
buffer = buffer.slice(lastIndex)
155+
156+
//? Buffer is getting too large warning
157+
if (buffer.length > 10000) {
158+
console.warn('Buffer overflow, clearing unmatched data')
159+
buffer = ''
160+
}
161+
162+
await new Promise(resolve => setTimeout(resolve, 10))
163+
}
164+
165+
//* Process any remaining data in the buffer
166+
if (buffer.length > 0) {
167+
const validatedJson = validateAndSanitizeJson(buffer)
168+
if (validatedJson) {
169+
const jsonData = JSON.parse(validatedJson)
170+
171+
if (jsonData.path === webSearchFlow.path) {
172+
console.log('jsonData ==> ', jsonData)
173+
174+
results += jsonData.content
175+
}
176+
}
177+
}
178+
} catch (error) {
179+
console.error('Error reading stream: ', error)
180+
} finally {
181+
reader.releaseLock()
182+
}
183+
184+
const [searchResults, sources] = results.split(
185+
/\*\*Source:\*\*|\*\*Sources:\*\*|\*\*Source\*\*:|\*\*Sources\*\*:/
186+
)
187+
console.log('searchResults ==> ', searchResults)
188+
console.log('sources ==> ', sources)
189+
190+
return JSON.stringify({
191+
searchResults,
192+
// ? See: https://rapidapi.com/facundoPri/api/url-to-metadata/playground/apiendpoint_830f2c62-e40f-4b62-9fe0-a5dbc2bc07e6
193+
sources,
194+
error: null
195+
})
196+
} catch (error) {
197+
console.error('Error fetching app data: ', error)
198+
return 'Internal Server Error while fetching app data'
199+
}
200+
}
201+
202+
export async function getPromptDetails(promptId: string) {
203+
let data = null
204+
let error = null
205+
let inputs = {}
206+
207+
try {
208+
if (!promptId) {
209+
throw new Error('Prompt ID is required')
210+
}
211+
212+
const response = await fetch(`/api/wordware/describe?promptId=${promptId}`)
213+
data = await response.json()
214+
if (!response.ok) {
215+
throw new Error(data.error || 'Failed to fetch prompt details')
216+
}
217+
inputs = data.inputs.reduce(
218+
(acc: any, input: { label: any }) => ({
219+
...acc,
220+
[input.label]: ''
221+
}),
222+
{}
223+
)
224+
} catch (error) {
225+
console.error('Error fetching prompt details:', error)
226+
error = (error as Error).message
227+
} finally {
228+
return { data, error, inputs }
229+
}
230+
}
231+
232+
export async function runWordWarePrompt({
233+
promptId,
234+
inputs,
235+
appVersion
236+
}: {
237+
promptId: string
238+
appVersion: string
239+
inputs: Record<string, any>
240+
}) {
241+
let fullResponse = ''
242+
let error = null
243+
244+
try {
245+
const response = await fetch('/api/wordware/run', {
246+
method: 'POST',
247+
headers: {
248+
'Content-Type': 'application/json'
249+
},
250+
body: JSON.stringify({ promptId, inputs })
251+
})
252+
253+
if (!response.ok) {
254+
throw new Error(`HTTP error! status: ${response.status}`)
255+
}
256+
257+
const reader = response.body?.getReader()
258+
if (!reader) {
259+
throw new Error('No reader available')
260+
}
261+
262+
while (true) {
263+
const { done, value } = await reader.read()
264+
if (done) break
265+
const chunk = new TextDecoder().decode(value)
266+
fullResponse += chunk
267+
}
268+
269+
const parsed = parseWordwareResponse(fullResponse)
270+
return { fullResponse, parsed, error }
271+
} catch (err) {
272+
console.error('Error running prompt:', err)
273+
error = (err as Error).message
274+
return { fullResponse, parsed: null, error }
275+
}
276+
}

0 commit comments

Comments
 (0)