mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-11-20 20:18:15 +00:00
feat(app): add widgets
This commit is contained in:
6
src/lib/agents/search/widgets/index.ts
Normal file
6
src/lib/agents/search/widgets/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import WidgetRegistry from './registry';
|
||||||
|
import weatherWidget from './weatherWidget';
|
||||||
|
|
||||||
|
WidgetRegistry.register(weatherWidget);
|
||||||
|
|
||||||
|
export { WidgetRegistry };
|
||||||
65
src/lib/agents/search/widgets/registry.ts
Normal file
65
src/lib/agents/search/widgets/registry.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
AdditionalConfig,
|
||||||
|
SearchAgentConfig,
|
||||||
|
Widget,
|
||||||
|
WidgetConfig,
|
||||||
|
WidgetOutput,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
class WidgetRegistry {
|
||||||
|
private static widgets = new Map<string, Widget>();
|
||||||
|
|
||||||
|
static register(widget: Widget<any>) {
|
||||||
|
this.widgets.set(widget.name, widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(name: string): Widget | undefined {
|
||||||
|
return this.widgets.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAll(): Widget[] {
|
||||||
|
return Array.from(this.widgets.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDescriptions(): string {
|
||||||
|
return Array.from(this.widgets.values())
|
||||||
|
.map((widget) => `${widget.name}: ${widget.description}`)
|
||||||
|
.join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async execute(
|
||||||
|
name: string,
|
||||||
|
params: any,
|
||||||
|
config: AdditionalConfig,
|
||||||
|
): Promise<WidgetOutput> {
|
||||||
|
const widget = this.get(name);
|
||||||
|
|
||||||
|
if (!widget) {
|
||||||
|
throw new Error(`Widget with name ${name} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return widget.execute(params, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async executeAll(
|
||||||
|
widgets: WidgetConfig[],
|
||||||
|
additionalConfig: AdditionalConfig,
|
||||||
|
): Promise<WidgetOutput[]> {
|
||||||
|
const results: WidgetOutput[] = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
widgets.map(async (widgetConfig) => {
|
||||||
|
const output = await this.execute(
|
||||||
|
widgetConfig.type,
|
||||||
|
widgetConfig.params,
|
||||||
|
additionalConfig,
|
||||||
|
);
|
||||||
|
results.push(output);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WidgetRegistry;
|
||||||
123
src/lib/agents/search/widgets/weatherWidget.ts
Normal file
123
src/lib/agents/search/widgets/weatherWidget.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
import { Widget } from '../types';
|
||||||
|
|
||||||
|
const WeatherWidgetSchema = z.object({
|
||||||
|
type: z.literal('weather'),
|
||||||
|
location: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'Human-readable location name (e.g., "New York, NY, USA", "London, UK"). Use this OR lat/lon coordinates, never both. Leave empty string if providing coordinates.',
|
||||||
|
),
|
||||||
|
lat: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'Latitude coordinate in decimal degrees (e.g., 40.7128). Only use when location name is empty.',
|
||||||
|
),
|
||||||
|
lon: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'Longitude coordinate in decimal degrees (e.g., -74.0060). Only use when location name is empty.',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const weatherWidget = {
|
||||||
|
name: 'weather',
|
||||||
|
description:
|
||||||
|
'Provides current weather information for a specified location. It can return details such as temperature, humidity, wind speed, and weather conditions. It needs either a location name or latitude/longitude coordinates to function.',
|
||||||
|
schema: WeatherWidgetSchema,
|
||||||
|
execute: async (params, _) => {
|
||||||
|
if (
|
||||||
|
params.location === '' &&
|
||||||
|
(params.lat === undefined || params.lon === undefined)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'Either location name or both latitude and longitude must be provided.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.location !== '') {
|
||||||
|
const openStreetMapUrl = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(params.location)}&format=json&limit=1`;
|
||||||
|
|
||||||
|
const locationRes = await fetch(openStreetMapUrl, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Perplexica',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await locationRes.json();
|
||||||
|
|
||||||
|
const location = data[0];
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find coordinates for location: ${params.location}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const weatherRes = await fetch(
|
||||||
|
`https://api.open-meteo.com/v1/forecast?latitude=${location.lat}&longitude=${location.lon}¤t_weather=true`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Perplexica',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const weatherData = await weatherRes.json();
|
||||||
|
|
||||||
|
/* this is like a very simple implementation just to see the bacckend works, when we're working on the frontend, we'll return more data i guess? */
|
||||||
|
return {
|
||||||
|
type: 'weather',
|
||||||
|
data: {
|
||||||
|
location: params.location,
|
||||||
|
latitude: location.lat,
|
||||||
|
longitude: location.lon,
|
||||||
|
weather: weatherData.current_weather,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (params.lat !== undefined && params.lon !== undefined) {
|
||||||
|
const [weatherRes, locationRes] = await Promise.all([
|
||||||
|
fetch(
|
||||||
|
`https://api.open-meteo.com/v1/forecast?latitude=${params.lat}&longitude=${params.lon}¤t_weather=true`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Perplexica',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
fetch(
|
||||||
|
`https://nominatim.openstreetmap.org/reverse?lat=${params.lat}&lon=${params.lon}&format=json`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Perplexica',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const weatherData = await weatherRes.json();
|
||||||
|
const locationData = await locationRes.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'weather',
|
||||||
|
data: {
|
||||||
|
location: locationData.display_name,
|
||||||
|
latitude: params.lat,
|
||||||
|
longitude: params.lon,
|
||||||
|
weather: weatherData.current_weather,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'weather',
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} satisfies Widget<typeof WeatherWidgetSchema>;
|
||||||
|
|
||||||
|
export default weatherWidget;
|
||||||
Reference in New Issue
Block a user