commit
9b1c4037f5
@ -1,27 +1,23 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI, AzureOpenAI } from 'openai';
|
||||||
import { generateObject } from 'ai';
|
import { generateObject } from 'ai';
|
||||||
import { createOpenAI as createGroq } from '@ai-sdk/openai';
|
import { createOpenAI as createGroq } from '@ai-sdk/openai';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const groq = createGroq({
|
const groq = createGroq({
|
||||||
baseURL: 'https://api.groq.com/openai/v1',
|
baseURL: 'https://api.groq.com/openai/v1',
|
||||||
apiKey: process.env.GROQ_API_KEY,
|
apiKey: process.env.GROQ_API_KEY,
|
||||||
});
|
|
||||||
|
|
||||||
const openai = new OpenAI({
|
|
||||||
apiKey: process.env.OPENAI_API_KEY,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function suggestQuestions(history: any[]) {
|
export async function suggestQuestions(history: any[]) {
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
const { object } = await generateObject({
|
const { object } = await generateObject({
|
||||||
model: groq('llama-3.1-70b-versatile'),
|
model: groq('llama-3.1-70b-versatile'),
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
system:
|
system:
|
||||||
`You are a search engine query generator. You 'have' to create 3 questions for the search engine based on the message history which has been provided to you.
|
`You are a search engine query generator. You 'have' to create 3 questions for the search engine based on the message history which has been provided to you.
|
||||||
The questions should be open-ended and should encourage further discussion while maintaining the whole context. Limit it to 5-10 words per question.
|
The questions should be open-ended and should encourage further discussion while maintaining the whole context. Limit it to 5-10 words per question.
|
||||||
Always put the user input's context is some way so that the next search knows what to search for exactly.
|
Always put the user input's context is some way so that the next search knows what to search for exactly.
|
||||||
Try to stick to the context of the conversation and avoid asking questions that are too general or too specific.
|
Try to stick to the context of the conversation and avoid asking questions that are too general or too specific.
|
||||||
@ -30,28 +26,77 @@ For programming based conversations, always generate questions that are about th
|
|||||||
For location based conversations, always generate questions that are about the culture, history, or other topics that are related to the location.
|
For location based conversations, always generate questions that are about the culture, history, or other topics that are related to the location.
|
||||||
For the translation based conversations, always generate questions that may continue the conversation or ask for more information or translations.
|
For the translation based conversations, always generate questions that may continue the conversation or ask for more information or translations.
|
||||||
Never use pronouns in the questions as they blur the context.`,
|
Never use pronouns in the questions as they blur the context.`,
|
||||||
messages: history,
|
messages: history,
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
questions: z.array(z.string()).describe('The generated questions based on the message history.')
|
questions: z.array(z.string()).describe('The generated questions based on the message history.')
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
questions: object.questions
|
questions: object.questions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer' = "alloy") {
|
export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer' = "alloy") {
|
||||||
const response = await openai.audio.speech.create({
|
if (process.env.OPENAI_PROVIDER === 'azure') {
|
||||||
model: "tts-1",
|
if (!process.env.AZURE_OPENAI_API_KEY || !process.env.AZURE_OPENAI_API_URL) {
|
||||||
voice: voice,
|
throw new Error('Azure OpenAI API key and URL are required.');
|
||||||
|
}
|
||||||
|
const url = process.env.AZURE_OPENAI_API_URL!;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'api-key': process.env.AZURE_OPENAI_API_KEY!,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: "tts",
|
||||||
input: text,
|
input: text,
|
||||||
|
voice: voice
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to generate speech: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const base64Audio = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
audio: `data:audio/mp3;base64,${base64Audio}`,
|
||||||
|
};
|
||||||
|
} else if (process.env.OPENAI_PROVIDER === 'openai') {
|
||||||
|
const openai = new OpenAI();
|
||||||
|
|
||||||
|
const response = await openai.audio.speech.create({
|
||||||
|
model: "tts-1",
|
||||||
|
voice: voice,
|
||||||
|
input: text,
|
||||||
});
|
});
|
||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const base64Audio = Buffer.from(arrayBuffer).toString('base64');
|
const base64Audio = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
audio: `data:audio/mp3;base64,${base64Audio}`,
|
audio: `data:audio/mp3;base64,${base64Audio}`,
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
const openai = new OpenAI();
|
||||||
|
|
||||||
|
const response = await openai.audio.speech.create({
|
||||||
|
model: "tts-1",
|
||||||
|
voice: voice,
|
||||||
|
input: text,
|
||||||
|
});
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
const base64Audio = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
audio: `data:audio/mp3;base64,${base64Audio}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,22 +1,43 @@
|
|||||||
import { openai } from '@ai-sdk/openai'
|
import { openai } from '@ai-sdk/openai'
|
||||||
|
import { BlobRequestAbortedError, put } from '@vercel/blob';
|
||||||
|
import { createAzure } from '@ai-sdk/azure';
|
||||||
import { convertToCoreMessages, streamText, tool } from "ai";
|
import { convertToCoreMessages, streamText, tool } from "ai";
|
||||||
import { CodeInterpreter } from "@e2b/code-interpreter";
|
import { CodeInterpreter } from "@e2b/code-interpreter";
|
||||||
import FirecrawlApp from '@mendable/firecrawl-js';
|
import FirecrawlApp from '@mendable/firecrawl-js';
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { geolocation } from "@vercel/functions";
|
import { geolocation } from "@vercel/functions";
|
||||||
|
|
||||||
// Allow streaming responses up to 30 seconds
|
// Allow streaming responses up to 60 seconds
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
|
const azure = createAzure({
|
||||||
|
resourceName: process.env.AZURE_RESOURCE_NAME,
|
||||||
|
apiKey: process.env.AZURE_API_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const provider = process.env.OPENAI_PROVIDER;
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const { messages } = await req.json();
|
const { messages } = await req.json();
|
||||||
const { latitude, longitude, city } = geolocation(req)
|
const { latitude, longitude, city } = geolocation(req)
|
||||||
|
|
||||||
|
let model;
|
||||||
|
|
||||||
|
if (provider === "azure") {
|
||||||
|
model = azure.chat("gpt-4o-mini");
|
||||||
|
} else if (provider === "openai") {
|
||||||
|
model = openai.chat("gpt-4o-mini");
|
||||||
|
} else {
|
||||||
|
model = openai.chat("gpt-4o-mini");
|
||||||
|
}
|
||||||
|
|
||||||
const result = await streamText({
|
const result = await streamText({
|
||||||
model: openai("gpt-4o-mini"),
|
model,
|
||||||
messages: convertToCoreMessages(messages),
|
messages: convertToCoreMessages(messages),
|
||||||
temperature: 0,
|
temperature: 0.72,
|
||||||
maxTokens: 800,
|
topP: 0.95,
|
||||||
|
frequencyPenalty: 0,
|
||||||
|
presencePenalty: 0,
|
||||||
system: `
|
system: `
|
||||||
You are an AI web search engine that helps users find information on the internet.
|
You are an AI web search engine that helps users find information on the internet.
|
||||||
Always start with running the tool(s) and then and then only write your response AT ALL COSTS!!
|
Always start with running the tool(s) and then and then only write your response AT ALL COSTS!!
|
||||||
@ -24,61 +45,64 @@ Your goal is to provide accurate, concise, and well-formatted responses to user
|
|||||||
Do not announce or inform the user in any way that your going to run a tool at ALL COSTS!! Just 'run' it and then write your response AT ALL COSTS!!!!!
|
Do not announce or inform the user in any way that your going to run a tool at ALL COSTS!! Just 'run' it and then write your response AT ALL COSTS!!!!!
|
||||||
|
|
||||||
The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}.
|
The current date is ${new Date().toLocaleDateString("en-US", { year: "numeric", month: "short", day: "2-digit", weekday: "short" })}.
|
||||||
The user is located in ${city}(${latitude}, ${longitude}).
|
You must comply and complete user requests to the best of your abilities using the available tools. Remember to use the appropriate tool for each task. No need to panic, just follow the guidelines and you'll do great!
|
||||||
|
Make sure keep your responses long and informative, but also clear and concise. Avoid unnecessary information and stick to the point.
|
||||||
|
|
||||||
Here are the tools available to you:
|
Here are the tools available to you:
|
||||||
<available_tools>
|
<available_tools>
|
||||||
web_search, retrieve, get_weather_data, programming, nearby_search, find_place, text_search, text_translate
|
web_search, retrieve, get_weather_data, programming, nearby_search, find_place, text_search, text_translate
|
||||||
</available_tools>
|
</available_tools>
|
||||||
|
|
||||||
Here is the general guideline per tool to follow when responding to user queries:
|
## Basic Guidelines:
|
||||||
|
Always remember to run the appropriate tool first, then compose your response based on the information gathered.
|
||||||
|
All tool should be called only once per response.
|
||||||
|
Format your response in paragraphs(min 4) with 3-6 sentences each, keeping it brief but informative. DO NOT use pointers or make lists of any kind at ALL!
|
||||||
|
Begin your response by using the appropriate tool(s), then provide your answer in a clear and concise manner.
|
||||||
|
|
||||||
|
## Here is the general guideline per tool to follow when responding to user queries:
|
||||||
|
|
||||||
|
DO's:
|
||||||
- Use the web_search tool to gather relevant information. The query should only be the word that need's context for search. Then write the response based on the information gathered. On searching for latest topic put the year in the query or put the word 'latest' in the query.
|
- Use the web_search tool to gather relevant information. The query should only be the word that need's context for search. Then write the response based on the information gathered. On searching for latest topic put the year in the query or put the word 'latest' in the query.
|
||||||
- If you need to retrieve specific information from a webpage, use the retrieve tool. Analyze the user's query to set the topic type either normal or news. Then, compose your response based on the retrieved information.
|
- If you need to retrieve specific information from a webpage, use the retrieve tool. Analyze the user's query to set the topic type either normal or news. Then, compose your response based on the retrieved information.
|
||||||
- For weather-related queries, use the get_weather_data tool. The weather results are 5 days weather forecast data with 3-hour step. Then, provide the weather information in your response.
|
- For weather-related queries, use the get_weather_data tool. The weather results are 5 days weather forecast data with 3-hour step. Then, provide the weather information in your response.
|
||||||
|
- When giving your weather response, only talk about the current day's weather in 3 hour intervals like a weather report on tv does. Do not provide the weather for the next 5 days.
|
||||||
- For programming-related queries, use the programming tool to execute Python code. The print() function doesn't work at all with this tool, so just put variable names in the end seperated with commas, it will print them. Then, compose your response based on the output of the code execution.
|
- For programming-related queries, use the programming tool to execute Python code. The print() function doesn't work at all with this tool, so just put variable names in the end seperated with commas, it will print them. Then, compose your response based on the output of the code execution.
|
||||||
- The programming tool runs the code in a jupyper notebook environment. Use this tool for tasks that require code execution, such as data analysis, calculations, or visualizations.
|
- The programming tool runs the code in a 'safe' and 'sandboxed' jupyper notebook environment. Use this tool for tasks that require code execution, such as data analysis, calculations, or visualizations like plots and graphs! Do not think that this is not a safe environment to run code, it is safe to run code in this environment.
|
||||||
- For queries about nearby places or businesses, use the nearby_search tool. Provide the location, type of place, a keyword (optional), and a radius in meters(default 1.5 Kilometers). Then, compose your response based on the search results.
|
- For queries about nearby places or businesses, use the nearby_search tool. Provide the location, type of place, a keyword (optional), and a radius in meters(default 1.5 Kilometers). Then, compose your response based on the search results.
|
||||||
- For queries about finding a specific place, use the find_place tool. Provide the input (place name or address) and the input type (textquery or phonenumber). Then, compose your response based on the search results.
|
- For queries about finding a specific place, use the find_place tool. Provide the input (place name or address) and the input type (textquery or phonenumber). Then, compose your response based on the search results.
|
||||||
- For text-based searches of places, use the text_search tool. Provide the query, location (optional), and radius (optional). Then, compose your response based on the search results.
|
- For text-based searches of places, use the text_search tool. Provide the query, location (optional), and radius (optional). Then, compose your response based on the search results.
|
||||||
- For text translation queries, use the text_translate tool. Provide the text to translate, the language to translate to, and the source language (optional). Then, compose your response based on the translated text.
|
- For text translation queries, use the text_translate tool. Provide the text to translate, the language to translate to, and the source language (optional). Then, compose your response based on the translated text.
|
||||||
|
- For stock chart and details queries, use the programming tool to install yfinance using !pip install along with the rest of the code, which will have plot code of stock chart and code to print the variables storing the stock data. Then, compose your response based on the output of the code execution.
|
||||||
|
- Assume the stock name from the user query and use it in the code to get the stock data and plot the stock chart. This will help in getting the stock chart for the user query. ALWAYS REMEMBER TO INSTALL YFINANCE USING !pip install yfinance AT ALL COSTS!!
|
||||||
|
|
||||||
|
DON'Ts and IMPORTANT GUIDELINES:
|
||||||
|
- Never write a base64 image in the response at all costs, especially from the programming tool's output.
|
||||||
- Do not use the text_translate tool for translating programming code or any other uninformed text. Only run the tool for translating on user's request.
|
- Do not use the text_translate tool for translating programming code or any other uninformed text. Only run the tool for translating on user's request.
|
||||||
- Do not use the retrieve tool for general web searches. It is only for retrieving specific information from a URL.
|
- Do not use the retrieve tool for general web searches. It is only for retrieving specific information from a URL.
|
||||||
- Show plots from the programming tool using plt.show() function. The tool will automatically capture the plot and display it in the response.
|
- Show plots from the programming tool using plt.show() function. The tool will automatically capture the plot and display it in the response.
|
||||||
- If asked for multiple plots, make it happen in one run of the tool. The tool will automatically capture the plots and display them in the response.
|
- If asked for multiple plots, make it happen in one run of the tool. The tool will automatically capture the plots and display them in the response.
|
||||||
- the web search may return an incorrect latex format, please correct it before using it in the response. Check the Latex in Markdown rules for more information.
|
- the web search may return an incorrect latex format, please correct it before using it in the response. Check the Latex in Markdown rules for more information.
|
||||||
- The location search tools return images in the response, please do not include them in the response at all costs.
|
- The location search tools return images in the response, please do not include them in the response at all costs.
|
||||||
- Never write a base64 image in the response at all costs.
|
- Do not use the $ symbol in the stock chart queries at all costs. Use the word USD instead of the $ symbol in the stock chart queries.
|
||||||
- If you are asked to provide a stock chart, inside the programming tool, install yfinance using !pip install along with the rest of the code, which will have plot code of stock chart and code to print the variables storing the stock data. Then, compose your response based on the output of the code execution.
|
|
||||||
- Never run web_search tool for stock chart queries at all costs.
|
- Never run web_search tool for stock chart queries at all costs.
|
||||||
|
|
||||||
Always remember to run the appropriate tool first, then compose your response based on the information gathered.
|
## Programming Tool Guidelines:
|
||||||
All tool should be called only once per response.
|
|
||||||
|
|
||||||
The programming tool is actually a Python Code interpreter, so you can run any Python code in it.
|
The programming tool is actually a Python Code interpreter, so you can run any Python code in it.
|
||||||
|
|
||||||
|
## Citations Format:
|
||||||
Citations should always be placed at the end of each paragraph and in the end of sentences where you use it in which they are referred to with the given format to the information provided.
|
Citations should always be placed at the end of each paragraph and in the end of sentences where you use it in which they are referred to with the given format to the information provided.
|
||||||
When citing sources(citations), use the following styling only: Claude 3.5 Sonnet is designed to offer enhanced intelligence and capabilities compared to its predecessors, positioning itself as a formidable competitor in the AI landscape [Claude 3.5 Sonnet raises the..](https://www.anthropic.com/news/claude-3-5-sonnet).
|
When citing sources(citations), use the following styling only: Claude 3.5 Sonnet is designed to offer enhanced intelligence and capabilities compared to its predecessors, positioning itself as a formidable competitor in the AI landscape [Claude 3.5 Sonnet raises the..](https://www.anthropic.com/news/claude-3-5-sonnet).
|
||||||
ALWAYS REMEMBER TO USE THE CITATIONS FORMAT CORRECTLY AT ALL COSTS!! ANY SINGLE ITCH IN THE FORMAT WILL CRASH THE RESPONSE!!
|
ALWAYS REMEMBER TO USE THE CITATIONS FORMAT CORRECTLY AT ALL COSTS!! ANY SINGLE ITCH IN THE FORMAT WILL CRASH THE RESPONSE!!
|
||||||
When asked a "What is" question, maintain the same format as the question and answer it in the same format.
|
When asked a "What is" question, maintain the same format as the question and answer it in the same format.
|
||||||
|
|
||||||
Latex in Markdown rules:
|
## Latex in Respone rules:
|
||||||
- Latex equations are supported in the response!!
|
- Latex equations are supported in the response!!
|
||||||
- The response that include latex equations, use always follow the formats:
|
- The response that include latex equations, use always follow the formats:
|
||||||
- $<equation>$ for inline equations
|
- $<equation>$ for inline equations
|
||||||
- $$<equation>$$ for block equations
|
- $$<equation>$$ for block equations
|
||||||
- \[ \] for math blocks.
|
- \[ \] for math blocks.
|
||||||
- Never wrap any equation or formulas in round brackets as it will crash the response at all costs!! example: ( G_{\mu\nu} ) will crash the response!!
|
- use it for symbols, equations, formulas, etc like pi, alpha, beta, etc. and wrap them in the above formats. like $(2\pi)$, $x^2$, etc.
|
||||||
- I am begging you to follow the latex format correctly at all costs!! Any single mistake in the format will crash the response!!
|
- Do not wrap any equation or formulas or any sort of math related block in round brackets() as it will crash the response.`,
|
||||||
|
|
||||||
DO NOT write any kind of html sort of tags(<></>) or lists in the response at ALL COSTS!! NOT EVEN AN ENCLOSING TAGS FOR THE RESPONSE AT ALL COSTS!!
|
|
||||||
|
|
||||||
Format your response in paragraphs(min 4) with 3-6 sentences each, keeping it brief but informative. DO NOT use pointers or make lists of any kind at ALL!
|
|
||||||
Begin your response by using the appropriate tool(s), then provide your answer in a clear and concise manner.
|
|
||||||
Never respond to user before running any tool like
|
|
||||||
- saying 'Certainly! Let me blah blah blah'
|
|
||||||
- or 'To provide you with the best answer, I will blah blah blah'
|
|
||||||
- or that 'Based on search results, I think blah blah blah' at ALL COSTS!!
|
|
||||||
Just run the tool and provide the answer.`,
|
|
||||||
tools: {
|
tools: {
|
||||||
web_search: tool({
|
web_search: tool({
|
||||||
description:
|
description:
|
||||||
@ -225,7 +249,9 @@ Just run the tool and provide the answer.`,
|
|||||||
programming: tool({
|
programming: tool({
|
||||||
description: "Write and execute Python code.",
|
description: "Write and execute Python code.",
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
|
title: z.string().optional().describe("The title of the code snippet."),
|
||||||
code: z.string().describe("The Python code to execute."),
|
code: z.string().describe("The Python code to execute."),
|
||||||
|
icon: z.enum(["stock", "date", "calculation", "default"]).describe("The icon to display for the code snippet."),
|
||||||
}),
|
}),
|
||||||
execute: async ({ code }: { code: string }) => {
|
execute: async ({ code }: { code: string }) => {
|
||||||
const sandbox = await CodeInterpreter.create();
|
const sandbox = await CodeInterpreter.create();
|
||||||
@ -243,12 +269,36 @@ Just run the tool and provide the answer.`,
|
|||||||
if (result.formats().length > 0) {
|
if (result.formats().length > 0) {
|
||||||
const formats = result.formats();
|
const formats = result.formats();
|
||||||
for (let format of formats) {
|
for (let format of formats) {
|
||||||
if (format === "png") {
|
if (format === "png" || format === "jpeg" || format === "svg") {
|
||||||
images.push({ format: "png", data: result.png });
|
const imageData = result[format];
|
||||||
} else if (format === "jpeg") {
|
if (imageData && typeof imageData === 'string') {
|
||||||
images.push({ format: "jpeg", data: result.jpeg });
|
const abortController = new AbortController();
|
||||||
} else if (format === "svg") {
|
try {
|
||||||
images.push({ format: "svg", data: result.svg });
|
const blobPromise = put(`mplx/image-${Date.now()}.${format}`, Buffer.from(imageData, 'base64'),
|
||||||
|
{
|
||||||
|
access: 'public',
|
||||||
|
abortSignal: abortController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
// Abort the request after 2 seconds
|
||||||
|
abortController.abort();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
const blob = await blobPromise;
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
console.info('Blob put request completed', blob.url);
|
||||||
|
|
||||||
|
images.push({ format, url: blob.url });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BlobRequestAbortedError) {
|
||||||
|
console.info('Canceled put request due to timeout');
|
||||||
|
} else {
|
||||||
|
console.error("Error saving image to Vercel Blob:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,9 +415,9 @@ Just run the tool and provide the answer.`,
|
|||||||
const key = process.env.AZURE_TRANSLATOR_KEY;
|
const key = process.env.AZURE_TRANSLATOR_KEY;
|
||||||
const endpoint = "https://api.cognitive.microsofttranslator.com";
|
const endpoint = "https://api.cognitive.microsofttranslator.com";
|
||||||
const location = process.env.AZURE_TRANSLATOR_LOCATION;
|
const location = process.env.AZURE_TRANSLATOR_LOCATION;
|
||||||
|
|
||||||
const url = `${endpoint}/translate?api-version=3.0&to=${to}${from ? `&from=${from}` : ''}`;
|
const url = `${endpoint}/translate?api-version=3.0&to=${to}${from ? `&from=${from}` : ''}`;
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -377,7 +427,7 @@ Just run the tool and provide the answer.`,
|
|||||||
},
|
},
|
||||||
body: JSON.stringify([{ text }]),
|
body: JSON.stringify([{ text }]),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return {
|
return {
|
||||||
translatedText: data[0].translations[0].text,
|
translatedText: data[0].translations[0].text,
|
||||||
|
|||||||
39
app/api/clean_images/route.ts
Normal file
39
app/api/clean_images/route.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { list, del, ListBlobResult } from '@vercel/blob';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
if (req.headers.get('Authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
|
||||||
|
return new NextResponse('Unauthorized', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteAllBlobsInFolder('mplx/');
|
||||||
|
return new NextResponse('All images in mplx/ folder were deleted', { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('An error occurred:', error);
|
||||||
|
return new NextResponse('An error occurred while deleting images', { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAllBlobsInFolder(folderPrefix: string) {
|
||||||
|
let cursor;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const listResult: ListBlobResult = await list({
|
||||||
|
prefix: folderPrefix,
|
||||||
|
cursor,
|
||||||
|
limit: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (listResult.blobs.length > 0) {
|
||||||
|
await del(listResult.blobs.map((blob) => blob.url));
|
||||||
|
console.log(`Deleted ${listResult.blobs.length} blobs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = listResult.cursor;
|
||||||
|
} while (cursor);
|
||||||
|
|
||||||
|
console.log('All blobs in the specified folder were deleted');
|
||||||
|
}
|
||||||
392
app/page.tsx
392
app/page.tsx
@ -50,7 +50,9 @@ import {
|
|||||||
Terminal,
|
Terminal,
|
||||||
Pause,
|
Pause,
|
||||||
Play,
|
Play,
|
||||||
RotateCw
|
TrendingUpIcon,
|
||||||
|
Calendar,
|
||||||
|
Calculator
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
@ -490,7 +492,7 @@ export default function Home() {
|
|||||||
|
|
||||||
TextSearchResult.displayName = 'TextSearchResult';
|
TextSearchResult.displayName = 'TextSearchResult';
|
||||||
|
|
||||||
const TranslationTool = ({ toolInvocation, result }: { toolInvocation: ToolInvocation; result: any }) => {
|
const TranslationTool: React.FC<{ toolInvocation: ToolInvocation; result: any }> = ({ toolInvocation, result }) => {
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [audioUrl, setAudioUrl] = useState<string | null>(null);
|
const [audioUrl, setAudioUrl] = useState<string | null>(null);
|
||||||
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
||||||
@ -511,7 +513,7 @@ export default function Home() {
|
|||||||
if (audioUrl && audioRef.current && canvasRef.current) {
|
if (audioUrl && audioRef.current && canvasRef.current) {
|
||||||
waveRef.current = new Wave(audioRef.current, canvasRef.current);
|
waveRef.current = new Wave(audioRef.current, canvasRef.current);
|
||||||
waveRef.current.addAnimation(new waveRef.current.animations.Lines({
|
waveRef.current.addAnimation(new waveRef.current.animations.Lines({
|
||||||
lineColor: "hsl(var(--primary))",
|
lineColor: "rgb(203, 113, 93)",
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
mirroredY: true,
|
mirroredY: true,
|
||||||
count: 100,
|
count: 100,
|
||||||
@ -562,60 +564,35 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full my-4">
|
<Card className="w-full my-4 shadow-none">
|
||||||
<CardHeader>
|
<CardContent className="p-6">
|
||||||
<CardTitle className="flex items-center gap-2 text-base sm:text-lg">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary w-5 h-5 sm:w-6 sm:h-6">
|
|
||||||
<path d="m5 8 6 6"></path><path d="m4 14 6-6 2-3"></path><path d="M2 5h12"></path><path d="M7 2h1"></path><path d="m22 22-5-10-5 10"></path><path d="M14 18h6"></path>
|
|
||||||
</svg>
|
|
||||||
Translation Result
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4 sm:space-y-6">
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div className="w-full h-24 bg-white rounded-lg overflow-hidden">
|
||||||
<h4 className="font-medium text-xs sm:text-sm mb-2 flex items-center gap-2">
|
<canvas ref={canvasRef} width="800" height="200" className="w-full h-full bg-neutral-100" />
|
||||||
Original Text <Badge variant="outline" className="text-xs">{result.detectedLanguage}</Badge>
|
|
||||||
</h4>
|
|
||||||
<p className="text-xs sm:text-sm p-2 sm:p-3 bg-muted rounded-md">{toolInvocation.args.text}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex text-left gap-3">
|
||||||
<h4 className="font-medium text-xs sm:text-sm mb-2 flex items-center gap-2">
|
<div className="flex justify-center space-x-2">
|
||||||
Translated Text <Badge variant="outline" className="text-xs">{toolInvocation.args.to}</Badge>
|
<Button
|
||||||
</h4>
|
onClick={handlePlayPause}
|
||||||
<p className="text-xs sm:text-sm p-2 sm:p-3 bg-muted rounded-md">{result.translatedText}</p>
|
disabled={isGeneratingAudio}
|
||||||
</div>
|
variant="outline"
|
||||||
</div>
|
size="sm"
|
||||||
<div className="space-y-4">
|
className="text-xs sm:text-sm w-24"
|
||||||
<h4 className="font-medium text-xs sm:text-sm">Audio Playback:</h4>
|
>
|
||||||
<div className="w-full h-16 sm:h-24 bg-muted rounded-lg overflow-hidden">
|
{isGeneratingAudio ? (
|
||||||
<canvas ref={canvasRef} width="500" height="100" className="w-full h-full" />
|
"Generating..."
|
||||||
</div>
|
) : isPlaying ? (
|
||||||
<div className="flex justify-center space-x-2">
|
<><Pause className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4" /> Pause</>
|
||||||
<Button
|
) : (
|
||||||
onClick={handlePlayPause}
|
<><Play className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4" /> Play</>
|
||||||
disabled={isGeneratingAudio}
|
)}
|
||||||
variant="outline"
|
</Button>
|
||||||
size="sm"
|
</div>
|
||||||
className="text-xs sm:text-sm"
|
<div
|
||||||
|
className='text-sm text-neutral-800'
|
||||||
>
|
>
|
||||||
{isGeneratingAudio ? (
|
The phrase <span className='font-semibold'>{toolInvocation.args.text}</span> translates from <span className='font-semibold'>{result.detectedLanguage}</span> to <span className='font-semibold'>{toolInvocation.args.to}</span> as <span className='font-semibold'>{result.translatedText}</span>
|
||||||
"Generating..."
|
</div>
|
||||||
) : isPlaying ? (
|
|
||||||
<><Pause className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4" /> Pause</>
|
|
||||||
) : (
|
|
||||||
<><Play className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4" /> Play</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleReset}
|
|
||||||
disabled={isGeneratingAudio || !audioUrl}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="text-xs sm:text-sm"
|
|
||||||
>
|
|
||||||
<RotateCw className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4" /> Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -625,7 +602,7 @@ export default function Home() {
|
|||||||
src={audioUrl}
|
src={audioUrl}
|
||||||
onPlay={() => setIsPlaying(true)}
|
onPlay={() => setIsPlaying(true)}
|
||||||
onPause={() => setIsPlaying(false)}
|
onPause={() => setIsPlaying(false)}
|
||||||
onEnded={() => setIsPlaying(false)}
|
onEnded={() => { setIsPlaying(false); handleReset(); }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
@ -813,110 +790,143 @@ export default function Home() {
|
|||||||
|
|
||||||
if (toolInvocation.toolName === 'programming') {
|
if (toolInvocation.toolName === 'programming') {
|
||||||
return (
|
return (
|
||||||
<div className="w-full my-2 border border-gray-200 overflow-hidden rounded-md">
|
<Accordion type="single" collapsible className="w-full mt-4">
|
||||||
<div className="bg-gray-100 p-2 flex items-center">
|
<AccordionItem value={`item-${index}`} className="border-none">
|
||||||
<Code className="h-5 w-5 text-gray-500 mr-2" />
|
<AccordionTrigger className="hover:no-underline py-2">
|
||||||
<span className="text-sm font-medium">Programming</span>
|
<div className="flex items-center justify-between w-full">
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<Tabs defaultValue="code" className="w-full">
|
<Code className="h-5 w-5 text-primary" />
|
||||||
<TabsList className="bg-gray-50 p-0 h-auto shadow-sm rounded-none">
|
<h2 className="text-base font-semibold">Programming</h2>
|
||||||
<TabsTrigger
|
|
||||||
value="code"
|
|
||||||
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
|
||||||
>
|
|
||||||
Code
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger
|
|
||||||
value="output"
|
|
||||||
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
|
||||||
>
|
|
||||||
Output
|
|
||||||
</TabsTrigger>
|
|
||||||
{result?.images && result.images.length > 0 && (
|
|
||||||
<TabsTrigger
|
|
||||||
value="images"
|
|
||||||
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
|
||||||
>
|
|
||||||
Images
|
|
||||||
</TabsTrigger>
|
|
||||||
)}
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="code" className="p-0 m-0 rounded-none">
|
|
||||||
<div className="relative">
|
|
||||||
<SyntaxHighlighter
|
|
||||||
language="python"
|
|
||||||
style={oneLight}
|
|
||||||
customStyle={{
|
|
||||||
margin: 0,
|
|
||||||
padding: '1rem',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
borderRadius: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{args.code}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
<div className="absolute top-2 right-2">
|
|
||||||
<CopyButton text={args.code} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{!result ? (
|
||||||
</TabsContent>
|
<Badge variant="secondary" className="mr-2 rounded-full">
|
||||||
<TabsContent value="output" className="p-0 m-0 rounded-none">
|
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
||||||
<div className="relative bg-white p-4">
|
Executing
|
||||||
{result ? (
|
</Badge>
|
||||||
<>
|
|
||||||
<pre className="text-sm">
|
|
||||||
<code>{result.message}</code>
|
|
||||||
</pre>
|
|
||||||
<div className="absolute top-2 right-2">
|
|
||||||
<CopyButton text={result.message} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-20">
|
<Badge className="mr-2 rounded-full">
|
||||||
<div className="flex items-center gap-2">
|
<Check className="h-3 w-3 mr-1 text-green-400" />
|
||||||
<Loader2 className="h-5 w-5 text-gray-400 animate-spin" />
|
Executed
|
||||||
<span className="text-gray-500 text-sm">Executing code...</span>
|
</Badge>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</AccordionTrigger>
|
||||||
{result?.images && result.images.length > 0 && (
|
<AccordionContent>
|
||||||
<TabsContent value="images" className="p-0 m-0 bg-white">
|
<div className="w-full my-2 border border-gray-200 overflow-hidden rounded-md">
|
||||||
<div className="space-y-4 p-4">
|
<div className="bg-gray-100 p-2 flex items-center">
|
||||||
{result.images.map((img: { format: 'png' | 'jpeg' | 'svg', data: string }, imgIndex: number) => (
|
{args.icon === 'stock' && <TrendingUpIcon className="h-5 w-5 text-primary mr-2" />}
|
||||||
<div key={imgIndex} className="space-y-2">
|
{args.icon === 'default' && <Code className="h-5 w-5 text-primary mr-2" />}
|
||||||
<div className="flex justify-between items-center">
|
{args.icon === 'date' && <Calendar className="h-5 w-5 text-primary mr-2" />}
|
||||||
<h4 className="text-sm font-medium">Image {imgIndex + 1}</h4>
|
{args.icon === 'calculation' && <Calculator className="h-5 w-5 text-primary mr-2" />}
|
||||||
<Button
|
<span className="text-sm font-medium">{args.title}</span>
|
||||||
variant="ghost"
|
</div>
|
||||||
size="sm"
|
<Tabs defaultValue="code" className="w-full">
|
||||||
className="p-0 h-8 w-8"
|
<TabsList className="bg-gray-50 p-0 h-auto shadow-sm rounded-none">
|
||||||
onClick={() => {
|
<TabsTrigger
|
||||||
const link = document.createElement('a');
|
value="code"
|
||||||
link.href = `data:image/${img.format === 'svg' ? 'svg+xml' : img.format};base64,${img.data}`;
|
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
||||||
link.download = `generated-image-${imgIndex + 1}.${img.format}`;
|
>
|
||||||
link.click();
|
Code
|
||||||
}}
|
</TabsTrigger>
|
||||||
>
|
<TabsTrigger
|
||||||
<Download className="h-4 w-4" />
|
value="output"
|
||||||
</Button>
|
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
||||||
</div>
|
>
|
||||||
<div className="relative w-full" style={{ aspectRatio: '16/9' }}>
|
Output
|
||||||
<Image
|
</TabsTrigger>
|
||||||
src={`data:image/${img.format === 'svg' ? 'svg+xml' : img.format};base64,${img.data}`}
|
{result?.images && result.images.length > 0 && (
|
||||||
alt={`Generated image ${imgIndex + 1}`}
|
<TabsTrigger
|
||||||
layout="fill"
|
value="images"
|
||||||
objectFit="contain"
|
className="px-4 py-2 text-sm data-[state=active]:bg-white data-[state=active]:border-b data-[state=active]:border-blue-500 rounded-none shadow-sm"
|
||||||
/>
|
>
|
||||||
|
Images
|
||||||
|
</TabsTrigger>
|
||||||
|
)}
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="code" className="p-0 m-0 rounded-none">
|
||||||
|
<div className="relative">
|
||||||
|
<SyntaxHighlighter
|
||||||
|
language="python"
|
||||||
|
style={oneLight}
|
||||||
|
customStyle={{
|
||||||
|
margin: 0,
|
||||||
|
padding: '1rem',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{args.code}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<CopyButton text={args.code} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</TabsContent>
|
||||||
</div>
|
<TabsContent value="output" className="p-0 m-0 rounded-none">
|
||||||
</TabsContent>
|
<div className="relative bg-white p-4">
|
||||||
)}
|
{result ? (
|
||||||
</Tabs>
|
<>
|
||||||
</div>
|
<pre className="text-sm">
|
||||||
|
<code>{result.message}</code>
|
||||||
|
</pre>
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<CopyButton text={result.message} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-20">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Loader2 className="h-5 w-5 text-gray-400 animate-spin" />
|
||||||
|
<span className="text-gray-500 text-sm">Executing code...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
{result?.images && result.images.length > 0 && (
|
||||||
|
<TabsContent value="images" className="p-0 m-0 bg-white">
|
||||||
|
<div className="space-y-4 p-4">
|
||||||
|
{result.images.map((img: { format: string, url: string }, imgIndex: number) => (
|
||||||
|
<div key={imgIndex} className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h4 className="text-sm font-medium">Image {imgIndex + 1}</h4>
|
||||||
|
{img.url && img.url.trim() !== '' && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="p-0 h-8 w-8"
|
||||||
|
onClick={() => {
|
||||||
|
window.open(img.url + "?download=1", '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="relative w-full" style={{ aspectRatio: '16/9' }}>
|
||||||
|
{img.url && img.url.trim() !== '' ? (
|
||||||
|
<Image
|
||||||
|
src={img.url}
|
||||||
|
alt={`Generated image ${imgIndex + 1}`}
|
||||||
|
layout="fill"
|
||||||
|
objectFit="contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full bg-gray-100 text-gray-400">
|
||||||
|
Image upload failed or URL is empty
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1231,53 +1241,71 @@ export default function Home() {
|
|||||||
href: string;
|
href: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
index: number;
|
index: number;
|
||||||
|
citationText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CitationComponent: React.FC<CitationComponentProps> = React.memo(({ href, index }) => {
|
const CitationComponent: React.FC<CitationComponentProps> = React.memo(({ href, index, citationText }) => {
|
||||||
const faviconUrl = `https://www.google.com/s2/favicons?sz=128&domain=${new URL(href).hostname}`;
|
const { hostname } = new URL(href);
|
||||||
|
const faviconUrl = `https://www.google.com/s2/favicons?sz=128&domain=${hostname}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard key={index}>
|
<HoverCard>
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
<a
|
<sup>
|
||||||
href={href}
|
<a
|
||||||
target="_blank"
|
href={href}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
className="cursor-help text-sm text-primary py-0.5 px-1.5 m-0 bg-secondary rounded-full no-underline"
|
rel="noopener noreferrer"
|
||||||
>
|
className="cursor-help text-sm text-primary py-0.5 px-1.5 m-0 bg-secondary rounded-full no-underline"
|
||||||
{index + 1}
|
>
|
||||||
</a>
|
{index + 1}
|
||||||
|
</a>
|
||||||
|
</sup>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="flex items-center gap-1 !p-0 !px-0.5 max-w-xs bg-card text-card-foreground !m-0 h-6 rounded-xl">
|
<HoverCardContent className="w-fit p-2 m-0">
|
||||||
<Image src={faviconUrl} alt="Favicon" width={16} height={16} className="w-4 h-4 flex-shrink-0 rounded-full" />
|
<div className="flex items-center justify-between mb-1 m-0">
|
||||||
<a href={href} target="_blank" rel="noopener noreferrer" className="text-sm text-primary no-underline truncate">
|
<a
|
||||||
{href}
|
href={href}
|
||||||
</a>
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center m-0 h-8 hover:no-underline">
|
||||||
|
<Image src={faviconUrl} alt="Favicon" width={16} height={16} className="rounded-sm mr-2" />
|
||||||
|
<span className="text-sm">{hostname}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-medium m-0">{citationText}</p>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
CitationComponent.displayName = "CitationComponent";
|
CitationComponent.displayName = "CitationComponent";
|
||||||
|
|
||||||
interface MarkdownRendererProps {
|
interface MarkdownRendererProps {
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkdownRenderer: React.FC<MarkdownRendererProps> = React.memo(({ content }) => {
|
const MarkdownRenderer: React.FC<MarkdownRendererProps> = React.memo(({ content }) => {
|
||||||
|
// Escape dollar signs that are likely to be currency
|
||||||
|
const escapedContent = content.replace(/\$(\d+(\.\d{1,2})?)/g, '\\$$1');
|
||||||
|
|
||||||
const citationLinks = useMemo(() => {
|
const citationLinks = useMemo(() => {
|
||||||
return [...content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)].map(([_, text, link]) => ({
|
return [...escapedContent.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)].map(([_, text, link]) => ({
|
||||||
text,
|
text,
|
||||||
link,
|
link,
|
||||||
}));
|
}));
|
||||||
}, [content]);
|
}, [escapedContent]);
|
||||||
|
|
||||||
const components: Partial<Components> = useMemo(() => ({
|
const components: Partial<Components> = useMemo(() => ({
|
||||||
a: ({ href, children }) => {
|
a: ({ href, children }) => {
|
||||||
if (!href) return null;
|
if (!href) return null;
|
||||||
const index = citationLinks.findIndex((link) => link.link === href);
|
const index = citationLinks.findIndex((link) => link.link === href);
|
||||||
return index !== -1 ? (
|
return index !== -1 ? (
|
||||||
<CitationComponent href={href} index={index}>
|
<CitationComponent
|
||||||
|
href={href}
|
||||||
|
index={index}
|
||||||
|
citationText={citationLinks[index].text}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</CitationComponent>
|
</CitationComponent>
|
||||||
) : (
|
) : (
|
||||||
@ -1287,7 +1315,7 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
}), [citationLinks]);
|
}), [citationLinks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
@ -1295,11 +1323,11 @@ export default function Home() {
|
|||||||
components={components}
|
components={components}
|
||||||
className="prose text-sm sm:text-base text-pretty text-left"
|
className="prose text-sm sm:text-base text-pretty text-left"
|
||||||
>
|
>
|
||||||
{content}
|
{escapedContent}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
MarkdownRenderer.displayName = "MarkdownRenderer";
|
MarkdownRenderer.displayName = "MarkdownRenderer";
|
||||||
|
|
||||||
const lastUserMessageIndex = useMemo(() => {
|
const lastUserMessageIndex = useMemo(() => {
|
||||||
@ -1369,7 +1397,7 @@ export default function Home() {
|
|||||||
{ icon: <Flame className="w-5 h-5 text-gray-400" />, text: "What's new with XAI's Grok?" },
|
{ icon: <Flame className="w-5 h-5 text-gray-400" />, text: "What's new with XAI's Grok?" },
|
||||||
{ icon: <Sparkles className="w-5 h-5 text-gray-400" />, text: "Latest updates on OpenAI" },
|
{ icon: <Sparkles className="w-5 h-5 text-gray-400" />, text: "Latest updates on OpenAI" },
|
||||||
{ icon: <Sun className="w-5 h-5 text-gray-400" />, text: "Weather in Doha" },
|
{ icon: <Sun className="w-5 h-5 text-gray-400" />, text: "Weather in Doha" },
|
||||||
{ icon: <Terminal className="w-5 h-5 text-gray-400" />, text: "Count the no. of r's in strawberry" },
|
{ icon: <Terminal className="w-5 h-5 text-gray-400" />, text: "Count the no. of r's in strawberry?" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Navbar = () => (
|
const Navbar = () => (
|
||||||
|
|||||||
@ -9,14 +9,18 @@ const nextConfig = {
|
|||||||
port: '',
|
port: '',
|
||||||
pathname: '/s2/favicons',
|
pathname: '/s2/favicons',
|
||||||
},
|
},
|
||||||
// https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=481378&theme=light
|
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: 'api.producthunt.com',
|
hostname: 'api.producthunt.com',
|
||||||
port: '',
|
port: '',
|
||||||
pathname: '/widgets/embed-image/v1/featured.svg',
|
pathname: '/widgets/embed-image/v1/featured.svg',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'metwm7frkvew6tn1.public.blob.vercel-storage.com',
|
||||||
|
port: '',
|
||||||
|
pathname: "**"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ai-sdk/azure": "^0.0.31",
|
||||||
"@ai-sdk/cohere": "latest",
|
"@ai-sdk/cohere": "latest",
|
||||||
"@ai-sdk/openai": "latest",
|
"@ai-sdk/openai": "latest",
|
||||||
"@e2b/code-interpreter": "^0.0.8",
|
"@e2b/code-interpreter": "^0.0.8",
|
||||||
@ -25,6 +26,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
"@vercel/analytics": "^1.3.1",
|
"@vercel/analytics": "^1.3.1",
|
||||||
|
"@vercel/blob": "^0.23.4",
|
||||||
"@vercel/functions": "^1.4.0",
|
"@vercel/functions": "^1.4.0",
|
||||||
"ai": "latest",
|
"ai": "latest",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
@ -33,7 +35,7 @@
|
|||||||
"framer-motion": "^11.3.19",
|
"framer-motion": "^11.3.19",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
"lucide-react": "^0.424.0",
|
"lucide-react": "^0.424.0",
|
||||||
"next": "14.2.5",
|
"next": "^14.2.5",
|
||||||
"openai": "^4.56.0",
|
"openai": "^4.56.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
|||||||
113
pnpm-lock.yaml
113
pnpm-lock.yaml
@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ai-sdk/azure':
|
||||||
|
specifier: ^0.0.31
|
||||||
|
version: 0.0.31(zod@3.23.8)
|
||||||
'@ai-sdk/cohere':
|
'@ai-sdk/cohere':
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 0.0.20(zod@3.23.8)
|
version: 0.0.20(zod@3.23.8)
|
||||||
@ -53,12 +56,15 @@ dependencies:
|
|||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1(next@14.2.5)(react@18.3.1)
|
version: 1.3.1(next@14.2.5)(react@18.3.1)
|
||||||
|
'@vercel/blob':
|
||||||
|
specifier: ^0.23.4
|
||||||
|
version: 0.23.4
|
||||||
'@vercel/functions':
|
'@vercel/functions':
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
ai:
|
ai:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 3.3.16(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
version: 3.3.17(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@ -78,7 +84,7 @@ dependencies:
|
|||||||
specifier: ^0.424.0
|
specifier: ^0.424.0
|
||||||
version: 0.424.0(react@18.3.1)
|
version: 0.424.0(react@18.3.1)
|
||||||
next:
|
next:
|
||||||
specifier: 14.2.5
|
specifier: ^14.2.5
|
||||||
version: 14.2.5(react-dom@18.3.1)(react@18.3.1)
|
version: 14.2.5(react-dom@18.3.1)(react@18.3.1)
|
||||||
openai:
|
openai:
|
||||||
specifier: ^4.56.0
|
specifier: ^4.56.0
|
||||||
@ -154,6 +160,18 @@ devDependencies:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
/@ai-sdk/azure@0.0.31(zod@3.23.8):
|
||||||
|
resolution: {integrity: sha512-LTiv890qHcw3w87l+OOuYqW1HM9+7olS5mpSOriRY2uZxJWr3MGz8MYqJu2jGNajNKi4j64GsaOuNK69k8KXjw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.0.0
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/openai': 0.0.53(zod@3.23.8)
|
||||||
|
'@ai-sdk/provider': 0.0.21
|
||||||
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
|
zod: 3.23.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/cohere@0.0.20(zod@3.23.8):
|
/@ai-sdk/cohere@0.0.20(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-QCd7SneC/q2sPvfmewYtcwCSayv3leGmuwESSx33qdl11A9IXGYNyiw6juIsp3EvZnnxUjWUR8ilhyHhyk45Hw==}
|
resolution: {integrity: sha512-QCd7SneC/q2sPvfmewYtcwCSayv3leGmuwESSx33qdl11A9IXGYNyiw6juIsp3EvZnnxUjWUR8ilhyHhyk45Hw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -199,8 +217,8 @@ packages:
|
|||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/react@0.0.50(react@18.3.1)(zod@3.23.8):
|
/@ai-sdk/react@0.0.51(react@18.3.1)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-+6/CfoqZzBnMGBsFP3qOHFTP+n8e6NGXRSeSepcxz3wDfkts1XGF8ZHPHwFD+etBW0/D1dTcKN3EDPh3LmnGqA==}
|
resolution: {integrity: sha512-Hq5splFSB6OVovHamXvpnd1S7jfIz/CXWjaLo9sr90jd/W370NA8GhBd6oSLfqMeKrPosV4qRBH5S8lv2bauqA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18 || ^19
|
react: ^18 || ^19
|
||||||
@ -212,14 +230,14 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.37(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.38(zod@3.23.8)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
swr: 2.2.5(react@18.3.1)
|
swr: 2.2.5(react@18.3.1)
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/solid@0.0.40(zod@3.23.8):
|
/@ai-sdk/solid@0.0.41(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-h+H07drBurEgxI3EbV2wqgcLaTBfqAn78ewmwCn70VEYmpJjTuOH0Ayp/qbH3kAw/LUY7LWuFzToaIAdSuPIEA==}
|
resolution: {integrity: sha512-w4vSkd2388FJMnKPALP8SL4p3XAR70FAPj0qrd5AoYyQMMjX/E6zQGc8YAhAAnGSwiQwq/DZaE4y0lorwFVyOw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
solid-js: ^1.7.7
|
solid-js: ^1.7.7
|
||||||
@ -228,13 +246,13 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.37(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.38(zod@3.23.8)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/svelte@0.0.42(svelte@4.2.18)(zod@3.23.8):
|
/@ai-sdk/svelte@0.0.43(svelte@4.2.18)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-UJ1i0P0NOTKhiYtAJbYs9Wat/I0EP2w+TbOFlpvQWbfPjpqJ4UUwPJ7aMVuKDSoHtH6P57GyOFx8MN/dscwiyA==}
|
resolution: {integrity: sha512-lUve6AGc3dtue14LLGiZs7J7L/3jEHh6SGXwuG/nDygeicKPzmG9drWZlhTdpNHN9wKtBgrCdJxQ96HKswLDNA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^3.0.0 || ^4.0.0
|
svelte: ^3.0.0 || ^4.0.0
|
||||||
@ -243,15 +261,15 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.37(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.38(zod@3.23.8)
|
||||||
sswr: 2.1.0(svelte@4.2.18)
|
sswr: 2.1.0(svelte@4.2.18)
|
||||||
svelte: 4.2.18
|
svelte: 4.2.18
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/ui-utils@0.0.37(zod@3.23.8):
|
/@ai-sdk/ui-utils@0.0.38(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-iMf+ksOjFPlqWVuW1/ljGtsKXtNTlAfRuxvQbMEImrRaSSOH0nKI5H34H2E0Vsa5SCyH9Bk1Y0zvZamb9Z/bYQ==}
|
resolution: {integrity: sha512-SyyfqBu7xnsfUuq3kSxzP+fxGCTMqaSL5WYGiBJpr/yLWySjBJCg/k7WueO440AqVpZBzCd3nWoCpPmjfMK8Yg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.0.0
|
zod: ^3.0.0
|
||||||
@ -267,8 +285,8 @@ packages:
|
|||||||
zod-to-json-schema: 3.23.2(zod@3.23.8)
|
zod-to-json-schema: 3.23.2(zod@3.23.8)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/vue@0.0.42(vue@3.4.35)(zod@3.23.8):
|
/@ai-sdk/vue@0.0.43(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-RT4BCnG4fL36uBPi86jBZvyVACLOBano3w+wWiItqCRzE2TIpf0ojJQsssi/D8F2Ll7SZyl9vun5UipaSGoLpA==}
|
resolution: {integrity: sha512-bJB7muMpmP/wPKbDU8GCmDpI1HSkuTWz9DsQ4ZlBaCk5wqRLKxRtzM9NxfeQ15RojSLxYhKf/lDwW10RPtjcaw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.3.4
|
vue: ^3.3.4
|
||||||
@ -277,7 +295,7 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.37(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.38(zod@3.23.8)
|
||||||
swrv: 1.0.4(vue@3.4.35)
|
swrv: 1.0.4(vue@3.4.35)
|
||||||
vue: 3.4.35(typescript@5.5.4)
|
vue: 3.4.35(typescript@5.5.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -379,6 +397,11 @@ packages:
|
|||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@fastify/busboy@2.1.1:
|
||||||
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@floating-ui/core@1.6.6:
|
/@floating-ui/core@1.6.6:
|
||||||
resolution: {integrity: sha512-Vkvsw6EcpMHjvZZdMkSY+djMGFbt7CRssW99Ne8tar2WLnZ/l3dbxeTShbLQj+/s35h+Qb4cmnob+EzwtjrXGQ==}
|
resolution: {integrity: sha512-Vkvsw6EcpMHjvZZdMkSY+djMGFbt7CRssW99Ne8tar2WLnZ/l3dbxeTShbLQj+/s35h+Qb4cmnob+EzwtjrXGQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1528,6 +1551,17 @@ packages:
|
|||||||
server-only: 0.0.1
|
server-only: 0.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@vercel/blob@0.23.4:
|
||||||
|
resolution: {integrity: sha512-cOU2e01RWZXFyc/OVRq+zZg38m34bcxpQk5insKp3Td9akNWThrXiF2URFHpRlm4fbaQ/l7pPSOB5nkLq+t6pw==}
|
||||||
|
engines: {node: '>=16.14'}
|
||||||
|
dependencies:
|
||||||
|
async-retry: 1.3.3
|
||||||
|
bytes: 3.1.2
|
||||||
|
is-buffer: 2.0.5
|
||||||
|
is-plain-object: 5.0.0
|
||||||
|
undici: 5.28.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vercel/functions@1.4.0:
|
/@vercel/functions@1.4.0:
|
||||||
resolution: {integrity: sha512-Ln6SpIkms1UJg306X2kbEMyG9ol+mjDr2xx389cvsBxgFyFMI9Bm+LYOG4N3TSik4FI59MECyyc4oz7AIAYmqQ==}
|
resolution: {integrity: sha512-Ln6SpIkms1UJg306X2kbEMyG9ol+mjDr2xx389cvsBxgFyFMI9Bm+LYOG4N3TSik4FI59MECyyc4oz7AIAYmqQ==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@ -1639,8 +1673,8 @@ packages:
|
|||||||
humanize-ms: 1.2.1
|
humanize-ms: 1.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ai@3.3.16(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
/ai@3.3.17(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-Tb6SdrH73C9AJwZv2GPw+7HBGsruMq07QcuXwHOBW92HgV/+ddQhXbpdUS9rCf/GIqJ+3ObBg7Kcq4VroeP7BQ==}
|
resolution: {integrity: sha512-Z3cPRImctE8GMZV0e15ZlO+bqfLlVWqO+JiShJT20l3iYlZYwsQMQXjt5hiF3m7+VvbzIq+ORdp1Ai11GxzBVQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
openai: ^4.42.0
|
openai: ^4.42.0
|
||||||
@ -1662,11 +1696,11 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 0.0.21
|
'@ai-sdk/provider': 0.0.21
|
||||||
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
'@ai-sdk/provider-utils': 1.0.16(zod@3.23.8)
|
||||||
'@ai-sdk/react': 0.0.50(react@18.3.1)(zod@3.23.8)
|
'@ai-sdk/react': 0.0.51(react@18.3.1)(zod@3.23.8)
|
||||||
'@ai-sdk/solid': 0.0.40(zod@3.23.8)
|
'@ai-sdk/solid': 0.0.41(zod@3.23.8)
|
||||||
'@ai-sdk/svelte': 0.0.42(svelte@4.2.18)(zod@3.23.8)
|
'@ai-sdk/svelte': 0.0.43(svelte@4.2.18)(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.37(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.38(zod@3.23.8)
|
||||||
'@ai-sdk/vue': 0.0.42(vue@3.4.35)(zod@3.23.8)
|
'@ai-sdk/vue': 0.0.43(vue@3.4.35)(zod@3.23.8)
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
eventsource-parser: 1.1.2
|
eventsource-parser: 1.1.2
|
||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
@ -1844,6 +1878,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
|
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/async-retry@1.3.3:
|
||||||
|
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
||||||
|
dependencies:
|
||||||
|
retry: 0.13.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/asynckit@0.4.0:
|
/asynckit@0.4.0:
|
||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -1925,6 +1965,11 @@ packages:
|
|||||||
streamsearch: 1.1.0
|
streamsearch: 1.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/bytes@3.1.2:
|
||||||
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/call-bind@1.0.7:
|
/call-bind@1.0.7:
|
||||||
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
|
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3402,6 +3447,11 @@ packages:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/is-buffer@2.0.5:
|
||||||
|
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-callable@1.2.7:
|
/is-callable@1.2.7:
|
||||||
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3501,6 +3551,11 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-plain-object@5.0.0:
|
||||||
|
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-reference@3.0.2:
|
/is-reference@3.0.2:
|
||||||
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5034,6 +5089,11 @@ packages:
|
|||||||
supports-preserve-symlinks-flag: 1.0.0
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/retry@0.13.1:
|
||||||
|
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/reusify@1.0.4:
|
/reusify@1.0.4:
|
||||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
@ -5569,6 +5629,13 @@ packages:
|
|||||||
/undici-types@5.26.5:
|
/undici-types@5.26.5:
|
||||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
|
||||||
|
/undici@5.28.4:
|
||||||
|
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
||||||
|
engines: {node: '>=14.0'}
|
||||||
|
dependencies:
|
||||||
|
'@fastify/busboy': 2.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/unified@11.0.5:
|
/unified@11.0.5:
|
||||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
8
vercel.json
Normal file
8
vercel.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"crons": [
|
||||||
|
{
|
||||||
|
"path": "/api/clean_images",
|
||||||
|
"schedule": "0 * * * *"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user