nosible 0.1.5
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/README.md +438 -0
- package/dist/index.cjs +1841 -0
- package/dist/index.cjs.map +29 -0
- package/dist/index.js +1815 -0
- package/dist/index.js.map +29 -0
- package/package.json +63 -0
- package/src/api/api.test.ts +366 -0
- package/src/api/index.ts +179 -0
- package/src/api/schemas.ts +152 -0
- package/src/client.test.ts +685 -0
- package/src/client.ts +762 -0
- package/src/index.ts +4 -0
- package/src/scrape/types.ts +119 -0
- package/src/scrape/webPageData.test.ts +302 -0
- package/src/scrape/webPageData.ts +103 -0
- package/src/search/analyze.test.ts +396 -0
- package/src/search/analyze.ts +151 -0
- package/src/search/bulkSearch.ts +62 -0
- package/src/search/result.test.ts +423 -0
- package/src/search/result.ts +391 -0
- package/src/search/result.types.ts +32 -0
- package/src/search/resultFactory.ts +21 -0
- package/src/search/resultSet.io.test.ts +320 -0
- package/src/search/resultSet.test.ts +368 -0
- package/src/search/resultSet.ts +387 -0
- package/src/search/resultSet.types.ts +3 -0
- package/src/search/search.test.ts +299 -0
- package/src/search/search.ts +187 -0
- package/src/search/searchSet.io.test.ts +321 -0
- package/src/search/searchSet.ts +122 -0
- package/src/search/sqlFilter.test.ts +129 -0
- package/src/search/sqlFilter.ts +147 -0
- package/src/test-utils/mocks.ts +159 -0
- package/src/topicTrend/topicTrend.ts +53 -0
- package/src/utils/browser.test.ts +209 -0
- package/src/utils/browser.ts +21 -0
- package/src/utils/fernet.ts +47 -0
- package/src/utils/file.test.ts +81 -0
- package/src/utils/file.ts +195 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/llm.test.ts +279 -0
- package/src/utils/llm.ts +244 -0
- package/src/utils/userPlan.test.ts +332 -0
- package/src/utils/userPlan.ts +211 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {describe, it, expect, beforeEach, afterEach, jest} from "bun:test";
|
|
2
|
+
import {callNosibleApi} from ".";
|
|
3
|
+
|
|
4
|
+
describe("callNosibleApi mock responses", () => {
|
|
5
|
+
const mockApiKey = "test-api-key";
|
|
6
|
+
const mockEndpoint = "test-endpoint";
|
|
7
|
+
const mockBody = {test: "data"};
|
|
8
|
+
|
|
9
|
+
// Store the original fetch function
|
|
10
|
+
const originalFetch = globalThis.fetch;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Reset fetch mock before each test
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Restore the original fetch function after each test
|
|
18
|
+
globalThis.fetch = originalFetch;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should handle 200 OK response correctly", async () => {
|
|
22
|
+
// Mock a successful response
|
|
23
|
+
const mockResponse = Promise.resolve({
|
|
24
|
+
ok: true,
|
|
25
|
+
status: 200,
|
|
26
|
+
json: () =>
|
|
27
|
+
Promise.resolve({
|
|
28
|
+
data: "test response data",
|
|
29
|
+
message: "success",
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Replace global fetch with our mock
|
|
34
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
35
|
+
|
|
36
|
+
const result = await callNosibleApi({
|
|
37
|
+
endpoint: mockEndpoint,
|
|
38
|
+
body: mockBody,
|
|
39
|
+
apiKey: mockApiKey,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
data: "test response data",
|
|
44
|
+
message: "success",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Verify fetch was called with correct parameters
|
|
48
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
49
|
+
`https://www.nosible.ai/search/v2/${mockEndpoint}`,
|
|
50
|
+
{
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
Accept: "application/json",
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"Api-Key": mockApiKey,
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(mockBody),
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should handle 401 Unauthorized response correctly", async () => {
|
|
63
|
+
// Mock a 401 response
|
|
64
|
+
const mockResponse = Promise.resolve({
|
|
65
|
+
ok: false,
|
|
66
|
+
status: 401,
|
|
67
|
+
json: () => Promise.resolve({}),
|
|
68
|
+
text: () => Promise.resolve("Unauthorized"),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Replace global fetch with our mock
|
|
72
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
73
|
+
|
|
74
|
+
// Expect the function to throw an error
|
|
75
|
+
await expect(
|
|
76
|
+
callNosibleApi({
|
|
77
|
+
endpoint: mockEndpoint,
|
|
78
|
+
body: mockBody,
|
|
79
|
+
apiKey: mockApiKey,
|
|
80
|
+
})
|
|
81
|
+
).rejects.toThrow("Your API key is not valid.");
|
|
82
|
+
|
|
83
|
+
// Verify fetch was called with correct parameters
|
|
84
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
85
|
+
`https://www.nosible.ai/search/v2/${mockEndpoint}`,
|
|
86
|
+
{
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
Accept: "application/json",
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"Api-Key": mockApiKey,
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify(mockBody),
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle 422 with string_too_short error correctly", async () => {
|
|
99
|
+
// Mock a 422 response with string_too_short error
|
|
100
|
+
const mockResponse = Promise.resolve({
|
|
101
|
+
ok: false,
|
|
102
|
+
status: 422,
|
|
103
|
+
headers: {
|
|
104
|
+
get: (name: string) => {
|
|
105
|
+
if (name.toLowerCase() === "content-type") return "application/json";
|
|
106
|
+
return "";
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
json: () =>
|
|
110
|
+
Promise.resolve([
|
|
111
|
+
{
|
|
112
|
+
type: "string_too_short",
|
|
113
|
+
message: "API key is too short",
|
|
114
|
+
},
|
|
115
|
+
]),
|
|
116
|
+
text: () => Promise.resolve("Bad Request"),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Replace global fetch with our mock
|
|
120
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
121
|
+
|
|
122
|
+
// Expect the function to throw an error
|
|
123
|
+
await expect(
|
|
124
|
+
callNosibleApi({
|
|
125
|
+
endpoint: mockEndpoint,
|
|
126
|
+
body: mockBody,
|
|
127
|
+
apiKey: mockApiKey,
|
|
128
|
+
})
|
|
129
|
+
).rejects.toThrow("Your API key is not valid: Too Short.");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should handle 422 with general bad request correctly", async () => {
|
|
133
|
+
// Mock a 422 response with general error
|
|
134
|
+
const mockResponse = Promise.resolve({
|
|
135
|
+
ok: false,
|
|
136
|
+
status: 422,
|
|
137
|
+
headers: {
|
|
138
|
+
get: (name: string) => {
|
|
139
|
+
if (name.toLowerCase() === "content-type") return "application/json";
|
|
140
|
+
return "";
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
json: () =>
|
|
144
|
+
Promise.resolve([
|
|
145
|
+
{
|
|
146
|
+
message: "Bad request",
|
|
147
|
+
},
|
|
148
|
+
]),
|
|
149
|
+
text: () => Promise.resolve("Bad Request"),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Replace global fetch with our mock
|
|
153
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
154
|
+
|
|
155
|
+
// Expect the function to throw an error
|
|
156
|
+
await expect(
|
|
157
|
+
callNosibleApi({
|
|
158
|
+
endpoint: mockEndpoint,
|
|
159
|
+
body: mockBody,
|
|
160
|
+
apiKey: mockApiKey,
|
|
161
|
+
})
|
|
162
|
+
).rejects.toThrow("You made a bad request.");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should handle 429 rate limit error correctly", async () => {
|
|
166
|
+
// Mock a 429 response
|
|
167
|
+
const mockResponse = Promise.resolve({
|
|
168
|
+
ok: false,
|
|
169
|
+
status: 429,
|
|
170
|
+
headers: {
|
|
171
|
+
get: () => "",
|
|
172
|
+
},
|
|
173
|
+
json: () => Promise.resolve({}),
|
|
174
|
+
text: () => Promise.resolve("Rate Limit Exceeded"),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Replace global fetch with our mock
|
|
178
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
179
|
+
|
|
180
|
+
// Expect the function to throw an error
|
|
181
|
+
await expect(
|
|
182
|
+
callNosibleApi({
|
|
183
|
+
endpoint: mockEndpoint,
|
|
184
|
+
body: mockBody,
|
|
185
|
+
apiKey: mockApiKey,
|
|
186
|
+
})
|
|
187
|
+
).rejects.toThrow("You have hit your rate limit.");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should handle 409 concurrent searches error correctly", async () => {
|
|
191
|
+
// Mock a 409 response
|
|
192
|
+
const mockResponse = Promise.resolve({
|
|
193
|
+
ok: false,
|
|
194
|
+
status: 409,
|
|
195
|
+
headers: {
|
|
196
|
+
get: () => "",
|
|
197
|
+
},
|
|
198
|
+
json: () => Promise.resolve({}),
|
|
199
|
+
text: () => Promise.resolve("Too Many Requests"),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Replace global fetch with our mock
|
|
203
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
204
|
+
|
|
205
|
+
// Expect the function to throw an error
|
|
206
|
+
await expect(
|
|
207
|
+
callNosibleApi({
|
|
208
|
+
endpoint: mockEndpoint,
|
|
209
|
+
body: mockBody,
|
|
210
|
+
apiKey: mockApiKey,
|
|
211
|
+
})
|
|
212
|
+
).rejects.toThrow("Too many concurrent searches.");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should handle 500 internal server error correctly", async () => {
|
|
216
|
+
// Mock a 500 response
|
|
217
|
+
const mockResponse = Promise.resolve({
|
|
218
|
+
ok: false,
|
|
219
|
+
status: 500,
|
|
220
|
+
headers: {
|
|
221
|
+
get: () => "",
|
|
222
|
+
},
|
|
223
|
+
json: () => Promise.resolve({}),
|
|
224
|
+
text: () => Promise.resolve("Internal Server Error"),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Replace global fetch with our mock
|
|
228
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
229
|
+
|
|
230
|
+
// Expect the function to throw an error
|
|
231
|
+
await expect(
|
|
232
|
+
callNosibleApi({
|
|
233
|
+
endpoint: mockEndpoint,
|
|
234
|
+
body: mockBody,
|
|
235
|
+
apiKey: mockApiKey,
|
|
236
|
+
})
|
|
237
|
+
).rejects.toThrow("An unexpected error occurred.");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should handle 502 bad gateway error correctly", async () => {
|
|
241
|
+
// Mock a 502 response
|
|
242
|
+
const mockResponse = Promise.resolve({
|
|
243
|
+
ok: false,
|
|
244
|
+
status: 502,
|
|
245
|
+
headers: {
|
|
246
|
+
get: () => "",
|
|
247
|
+
},
|
|
248
|
+
json: () => Promise.resolve({}),
|
|
249
|
+
text: () => Promise.resolve("Bad Gateway"),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Replace global fetch with our mock
|
|
253
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
254
|
+
|
|
255
|
+
// Expect the function to throw an error
|
|
256
|
+
await expect(
|
|
257
|
+
callNosibleApi({
|
|
258
|
+
endpoint: mockEndpoint,
|
|
259
|
+
body: mockBody,
|
|
260
|
+
apiKey: mockApiKey,
|
|
261
|
+
})
|
|
262
|
+
).rejects.toThrow("NOSIBLE is currently restarting.");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should handle 504 gateway timeout error correctly", async () => {
|
|
266
|
+
// Mock a 504 response
|
|
267
|
+
const mockResponse = Promise.resolve({
|
|
268
|
+
ok: false,
|
|
269
|
+
status: 504,
|
|
270
|
+
headers: {
|
|
271
|
+
get: () => "",
|
|
272
|
+
},
|
|
273
|
+
json: () => Promise.resolve({}),
|
|
274
|
+
text: () => Promise.resolve("Gateway Timeout"),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Replace global fetch with our mock
|
|
278
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
279
|
+
|
|
280
|
+
// Expect the function to throw an error
|
|
281
|
+
await expect(
|
|
282
|
+
callNosibleApi({
|
|
283
|
+
endpoint: mockEndpoint,
|
|
284
|
+
body: mockBody,
|
|
285
|
+
apiKey: mockApiKey,
|
|
286
|
+
})
|
|
287
|
+
).rejects.toThrow("NOSIBLE is currently overloaded.");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should handle fallback for other status codes", async () => {
|
|
291
|
+
// Mock a 418 response (I'm a teapot)
|
|
292
|
+
const mockResponse = Promise.resolve({
|
|
293
|
+
ok: false,
|
|
294
|
+
status: 418,
|
|
295
|
+
headers: {
|
|
296
|
+
get: () => "",
|
|
297
|
+
},
|
|
298
|
+
json: () => Promise.resolve({}),
|
|
299
|
+
text: () => Promise.resolve("I'm a teapot"),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Replace global fetch with our mock
|
|
303
|
+
globalThis.fetch = jest.fn(() => mockResponse) as any;
|
|
304
|
+
|
|
305
|
+
// Expect the function to throw an error
|
|
306
|
+
await expect(
|
|
307
|
+
callNosibleApi({
|
|
308
|
+
endpoint: mockEndpoint,
|
|
309
|
+
body: mockBody,
|
|
310
|
+
apiKey: mockApiKey,
|
|
311
|
+
})
|
|
312
|
+
).rejects.toThrow("HTTP error! status: 418");
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("callNosibleApi", () => {
|
|
317
|
+
const apiKey = process.env.NOSIBLE_API_KEY;
|
|
318
|
+
|
|
319
|
+
// Only run the API test if we have a valid API key
|
|
320
|
+
const shouldRunApiTest = apiKey && apiKey.length > 0;
|
|
321
|
+
|
|
322
|
+
it("should respond with JSON when calling the real API", async () => {
|
|
323
|
+
if (!shouldRunApiTest) {
|
|
324
|
+
// Skip the test if no API key is available
|
|
325
|
+
expect(true).toBe(true);
|
|
326
|
+
console.log("Skipping API test - no valid API key found");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// When we have a valid API key, test the actual API call
|
|
331
|
+
const response = await callNosibleApi({
|
|
332
|
+
endpoint: "fast-search",
|
|
333
|
+
body: {
|
|
334
|
+
question: "machine learning",
|
|
335
|
+
sql_filter: "SELECT loc FROM engine",
|
|
336
|
+
algorithm: "hybrid-2",
|
|
337
|
+
n_results: 10,
|
|
338
|
+
language: "en",
|
|
339
|
+
continent: "North America",
|
|
340
|
+
country: "United States",
|
|
341
|
+
sector: "Information Technology",
|
|
342
|
+
},
|
|
343
|
+
apiKey: apiKey,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Verify that we get a response with the expected structure
|
|
347
|
+
expect(response).toBeDefined();
|
|
348
|
+
// The response should be an object
|
|
349
|
+
expect(typeof response).toBe("object");
|
|
350
|
+
// Log the response structure for debugging
|
|
351
|
+
console.log("Response keys:", Object.keys(response));
|
|
352
|
+
// It should have the expected properties
|
|
353
|
+
expect(response).toHaveProperty("message");
|
|
354
|
+
expect(response).toHaveProperty("query");
|
|
355
|
+
expect(response).toHaveProperty("response");
|
|
356
|
+
// Response should be an array
|
|
357
|
+
expect(Array.isArray(response.response)).toBe(true);
|
|
358
|
+
// Each item in response should have required properties
|
|
359
|
+
if (response.response.length > 0) {
|
|
360
|
+
const firstItem = response.response[0];
|
|
361
|
+
expect(firstItem).toHaveProperty("url");
|
|
362
|
+
expect(firstItem).toHaveProperty("title");
|
|
363
|
+
expect(firstItem).toHaveProperty("content");
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
algorithmEnum,
|
|
3
|
+
brandSafetyEnum,
|
|
4
|
+
continentEnum,
|
|
5
|
+
regionEnum,
|
|
6
|
+
} from "./schemas";
|
|
7
|
+
import {z} from "zod";
|
|
8
|
+
|
|
9
|
+
/* ------------------ SEARCH ------------------ */
|
|
10
|
+
|
|
11
|
+
// Number of results
|
|
12
|
+
export const fastNResults = z.number().min(10).max(100).optional().default(100);
|
|
13
|
+
export const searchNResults = z
|
|
14
|
+
.number()
|
|
15
|
+
.min(100)
|
|
16
|
+
.max(1000)
|
|
17
|
+
.optional()
|
|
18
|
+
.default(100);
|
|
19
|
+
export const bulkNResults = z
|
|
20
|
+
.number()
|
|
21
|
+
.min(1000)
|
|
22
|
+
.max(10000)
|
|
23
|
+
.optional()
|
|
24
|
+
.default(1000);
|
|
25
|
+
|
|
26
|
+
// AI search params for search only
|
|
27
|
+
export const aiSearchParamsSchema = z.object({
|
|
28
|
+
prompt: z.string().min(25).max(2500),
|
|
29
|
+
agent: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
export type SearchParamsType = z.infer<typeof aiSearchParamsSchema>;
|
|
32
|
+
|
|
33
|
+
// Standard search params for fast and bulk search only
|
|
34
|
+
export const stdSearchParams = z.object({
|
|
35
|
+
question: z.string(),
|
|
36
|
+
expansions: z.array(z.string()).optional(),
|
|
37
|
+
sql_filter: z.string().optional(),
|
|
38
|
+
algorithm: algorithmEnum.optional(),
|
|
39
|
+
n_results: z.number(),
|
|
40
|
+
n_probes: z.number().min(1).max(10).optional(),
|
|
41
|
+
n_contextify: z.number().min(64).max(1024).optional(),
|
|
42
|
+
min_similarity: z.number().min(0).max(1).optional(),
|
|
43
|
+
must_include: z.array(z.string()).max(100).optional(),
|
|
44
|
+
must_exclude: z.array(z.string()).max(100).optional(),
|
|
45
|
+
brand_safety: brandSafetyEnum.optional(),
|
|
46
|
+
language: z.string().min(2).max(2).optional(),
|
|
47
|
+
continent: continentEnum.optional(),
|
|
48
|
+
region: regionEnum.optional(),
|
|
49
|
+
country: z.string().optional(),
|
|
50
|
+
sector: z.string().optional(),
|
|
51
|
+
industry_group: z.string().optional(),
|
|
52
|
+
industry: z.string().optional(),
|
|
53
|
+
sub_industry: z.string().optional(),
|
|
54
|
+
iab_tier_1: z.string().optional(),
|
|
55
|
+
iab_tier_2: z.string().optional(),
|
|
56
|
+
iab_tier_3: z.string().optional(),
|
|
57
|
+
iab_tier_4: z.string().optional(),
|
|
58
|
+
companies: z.array(z.string()).max(30).optional(),
|
|
59
|
+
});
|
|
60
|
+
export type StdSearchParamsType = z.infer<typeof stdSearchParams>;
|
|
61
|
+
|
|
62
|
+
// Fast search params
|
|
63
|
+
const fastSearchParamsSchema = z.object({
|
|
64
|
+
...stdSearchParams.shape,
|
|
65
|
+
n_results: fastNResults, // Overrides stdSearchParams
|
|
66
|
+
});
|
|
67
|
+
export type FastSearchParamsType = z.infer<typeof fastSearchParamsSchema>;
|
|
68
|
+
|
|
69
|
+
// Bulk search params
|
|
70
|
+
const bulkSearchParamsSchema = z.object({
|
|
71
|
+
...stdSearchParams.shape,
|
|
72
|
+
n_results: bulkNResults, // Overrides stdSearchParams
|
|
73
|
+
});
|
|
74
|
+
type BulkSearchParamsType = z.infer<typeof bulkSearchParamsSchema>;
|
|
75
|
+
|
|
76
|
+
// Validation Functions
|
|
77
|
+
export const validateAiSearchParams = (params: StdSearchParamsType) => {
|
|
78
|
+
const validatedParams = aiSearchParamsSchema.parse(params);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const validateFastSearchParams = (params: FastSearchParamsType) => {
|
|
82
|
+
const validatedParams = fastSearchParamsSchema.parse(params);
|
|
83
|
+
return validatedParams;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const validateBulkSearchParams = (params: BulkSearchParamsType) => {
|
|
87
|
+
const validatedParams = bulkSearchParamsSchema.parse(params);
|
|
88
|
+
return validatedParams;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/* --------------- SCRAPE URL --------------- */
|
|
92
|
+
const scapeUrlBody = z.object({
|
|
93
|
+
url: z.string(),
|
|
94
|
+
});
|
|
95
|
+
type ScrapeUrlBodyType = z.infer<typeof scapeUrlBody>;
|
|
96
|
+
|
|
97
|
+
/* ------------------ TOPIC TREND ------------------ */
|
|
98
|
+
const topicTrendBody = z.object({
|
|
99
|
+
query: z.string(),
|
|
100
|
+
sql_filter: z.string().optional(),
|
|
101
|
+
});
|
|
102
|
+
type TopicTrendBodyType = z.infer<typeof topicTrendBody>;
|
|
103
|
+
|
|
104
|
+
/* ------------------ API CALL ------------------ */
|
|
105
|
+
|
|
106
|
+
// Call API
|
|
107
|
+
|
|
108
|
+
export const callNosibleApi = async ({
|
|
109
|
+
baseUrl = "https://www.nosible.ai/search/v2",
|
|
110
|
+
endpoint,
|
|
111
|
+
body,
|
|
112
|
+
apiKey,
|
|
113
|
+
}: {
|
|
114
|
+
baseUrl?: string;
|
|
115
|
+
endpoint: string;
|
|
116
|
+
body: any;
|
|
117
|
+
apiKey: string;
|
|
118
|
+
}): Promise<any> => {
|
|
119
|
+
/** Make authenticated POST request to the API */
|
|
120
|
+
const response = await fetch(`${baseUrl}/${endpoint}`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
Accept: "application/json",
|
|
124
|
+
"Content-Type": "application/json",
|
|
125
|
+
"Api-Key": `${apiKey}`,
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify(body),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/** Handle error responses with detailed logging */
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
console.error("Status: " + response.status);
|
|
133
|
+
|
|
134
|
+
if (response.status === 401) {
|
|
135
|
+
throw new Error("Your API key is not valid.");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (response.status === 422) {
|
|
139
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
140
|
+
if (contentType.startsWith("application/json")) {
|
|
141
|
+
const body = await response.json();
|
|
142
|
+
const errorBody = Array.isArray(body) ? body[0] : body;
|
|
143
|
+
console.error(errorBody);
|
|
144
|
+
if (errorBody?.type === "string_too_short") {
|
|
145
|
+
throw new Error("Your API key is not valid: Too Short.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new Error("You made a bad request.");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (response.status === 429) {
|
|
152
|
+
throw new Error("You have hit your rate limit.");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (response.status === 409) {
|
|
156
|
+
throw new Error("Too many concurrent searches.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (response.status === 500) {
|
|
160
|
+
throw new Error("An unexpected error occurred.");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (response.status === 502) {
|
|
164
|
+
throw new Error("NOSIBLE is currently restarting.");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (response.status === 504) {
|
|
168
|
+
throw new Error("NOSIBLE is currently overloaded.");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Fallback for other status codes
|
|
172
|
+
console.error(await response.text());
|
|
173
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
174
|
+
} else {
|
|
175
|
+
/** Log successful response and return parsed data */
|
|
176
|
+
const data = await response.json();
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
export const queryTypeEnum = z.enum(["fast-search", "search", "bulk-search"]);
|
|
4
|
+
type QueryType = z.infer<typeof queryTypeEnum>;
|
|
5
|
+
|
|
6
|
+
export const algorithmEnum = z.enum([
|
|
7
|
+
"string",
|
|
8
|
+
"lexical",
|
|
9
|
+
"baseline",
|
|
10
|
+
"hamming",
|
|
11
|
+
"hybrid-1",
|
|
12
|
+
"hybrid-2",
|
|
13
|
+
"hybrid-3",
|
|
14
|
+
"company",
|
|
15
|
+
]);
|
|
16
|
+
export type AlgorithmEnum = z.infer<typeof algorithmEnum>;
|
|
17
|
+
|
|
18
|
+
export const brandSafetyEnum = z.enum(["safe", "sensitive", "unsafe"]);
|
|
19
|
+
export type BrandSafetyEnum = z.infer<typeof brandSafetyEnum>;
|
|
20
|
+
|
|
21
|
+
export const continentEnum = z.enum([
|
|
22
|
+
"Africa",
|
|
23
|
+
"Asia",
|
|
24
|
+
"Europe",
|
|
25
|
+
"North America",
|
|
26
|
+
"Oceania",
|
|
27
|
+
"South America",
|
|
28
|
+
"Worldwide",
|
|
29
|
+
]);
|
|
30
|
+
export type ContinentEnum = z.infer<typeof continentEnum>;
|
|
31
|
+
|
|
32
|
+
export const regionEnum = z.enum([
|
|
33
|
+
"Caribbean",
|
|
34
|
+
"Central Africa",
|
|
35
|
+
"Central America",
|
|
36
|
+
"Central Asia",
|
|
37
|
+
"Central Europe",
|
|
38
|
+
"East Africa",
|
|
39
|
+
"East Asia",
|
|
40
|
+
"Eastern Europe",
|
|
41
|
+
"Middle East",
|
|
42
|
+
"North Africa",
|
|
43
|
+
"North America",
|
|
44
|
+
"Northern Europe",
|
|
45
|
+
"Oceania",
|
|
46
|
+
"South America",
|
|
47
|
+
"South Asia",
|
|
48
|
+
"Southeast Asia",
|
|
49
|
+
"Southern Africa",
|
|
50
|
+
"Southern Europe",
|
|
51
|
+
"Sub-Saharan Africa",
|
|
52
|
+
"The Amazon Basin",
|
|
53
|
+
"The Andes",
|
|
54
|
+
"The Arctic Region",
|
|
55
|
+
"The Balkans",
|
|
56
|
+
"The Caucasus",
|
|
57
|
+
"The Horn of Africa",
|
|
58
|
+
"The Levant",
|
|
59
|
+
"The Middle East",
|
|
60
|
+
"The Sahel",
|
|
61
|
+
"West Africa",
|
|
62
|
+
"Western Europe",
|
|
63
|
+
"Worldwide",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
export type RegionEnum = z.infer<typeof regionEnum>;
|
|
67
|
+
|
|
68
|
+
export interface SearchResType {
|
|
69
|
+
message: string;
|
|
70
|
+
query: SearchQuery;
|
|
71
|
+
response: SearchResponse[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const searchQuerySchema = z.object({
|
|
75
|
+
instruction: z.string(),
|
|
76
|
+
question: z.string(),
|
|
77
|
+
expansions: z.array(z.string()),
|
|
78
|
+
brand_safety: z.null(),
|
|
79
|
+
language: z.string().nullable(),
|
|
80
|
+
continent: z.string().nullable(),
|
|
81
|
+
region: z.string().nullable(),
|
|
82
|
+
country: z.string().nullable(),
|
|
83
|
+
sector: z.string().nullable(),
|
|
84
|
+
industry_group: z.string().nullable(),
|
|
85
|
+
industry: z.string().nullable(),
|
|
86
|
+
sub_industry: z.string().nullable(),
|
|
87
|
+
iab_tier_1: z.string().nullable(),
|
|
88
|
+
iab_tier_2: z.string().nullable(),
|
|
89
|
+
iab_tier_3: z.string().nullable(),
|
|
90
|
+
iab_tier_4: z.string().nullable(),
|
|
91
|
+
algorithm: z.string(),
|
|
92
|
+
n_results: z.number(),
|
|
93
|
+
n_contextify: z.number(),
|
|
94
|
+
n_probes: z.number(),
|
|
95
|
+
min_similarity: z.number(),
|
|
96
|
+
pass_similarity: z.number(),
|
|
97
|
+
must_exclude: z.array(z.string()),
|
|
98
|
+
must_include: z.array(z.string()),
|
|
99
|
+
shard_selector: z.string(),
|
|
100
|
+
shard_reranker: z.string(),
|
|
101
|
+
search_lang: z.string(),
|
|
102
|
+
search_words: z.record(z.string(), z.number()),
|
|
103
|
+
search_intents: z.record(z.string(), z.any()), // Empty object type
|
|
104
|
+
companies: z.array(z.any()),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export type SearchQuery = z.infer<typeof searchQuerySchema>;
|
|
108
|
+
|
|
109
|
+
// SearchIntents is an empty object type
|
|
110
|
+
export type SearchIntents = Record<string, any>;
|
|
111
|
+
|
|
112
|
+
const searchResultSemanticsSchema = z.object({
|
|
113
|
+
origin_shard: z.number(),
|
|
114
|
+
chunks_total: z.number(),
|
|
115
|
+
chunks_matched: z.number(),
|
|
116
|
+
chunks_kept: z.number(),
|
|
117
|
+
similarity: z.number(),
|
|
118
|
+
bitstring: z.string(),
|
|
119
|
+
});
|
|
120
|
+
export type SearchResultSemantics = z.infer<typeof searchResultSemanticsSchema>;
|
|
121
|
+
|
|
122
|
+
export const searchResponseSchema = z.object({
|
|
123
|
+
url_hash: z.string(),
|
|
124
|
+
url: z.string(),
|
|
125
|
+
netloc: z.string(),
|
|
126
|
+
published: z.string(),
|
|
127
|
+
visited: z.string(),
|
|
128
|
+
language: z.string(),
|
|
129
|
+
author: z.string(),
|
|
130
|
+
title: z.string(),
|
|
131
|
+
description: z.string(),
|
|
132
|
+
content: z.string(),
|
|
133
|
+
best_chunk: z.string(),
|
|
134
|
+
semantics: searchResultSemanticsSchema,
|
|
135
|
+
});
|
|
136
|
+
export type SearchResponse = z.infer<typeof searchResponseSchema>;
|
|
137
|
+
|
|
138
|
+
// Bulk Search Response
|
|
139
|
+
const bulkResSchema = z.object({
|
|
140
|
+
message: z.string(),
|
|
141
|
+
decrypt_using: z.string(),
|
|
142
|
+
download_from: z.string(),
|
|
143
|
+
});
|
|
144
|
+
export type BulkResType = z.infer<typeof bulkResSchema>;
|
|
145
|
+
|
|
146
|
+
// Re-export scrape types from scrape.ts
|
|
147
|
+
export {
|
|
148
|
+
scrapeResponseSchema,
|
|
149
|
+
scrapeFullResSchema,
|
|
150
|
+
type ScrapeResponse,
|
|
151
|
+
type ScrapeFullRes,
|
|
152
|
+
} from "../scrape/types";
|