diff --git a/ui/.env.example b/.env.example similarity index 74% rename from ui/.env.example rename to .env.example index 3f16280e..035290e3 100644 --- a/ui/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +# Dependant on what stack you want to run + OPENAI_API_KEY=sk- HELICONE_API_KEY=sk- PINECONE_API_KEY= diff --git a/.gitignore b/.gitignore index bb76686a..5d37bd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +.vscode +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc .DS_Store -node_modules -stackRegistry.json -dist \ No newline at end of file +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index f06e0928..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Change Log - -All notable changes to the "stackwise" extension will be documented in this file. - -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. - -## [Unreleased] - -- Initial release diff --git a/README.md b/README.md index 2a52354a..8c09e8fa 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,13 @@ ### [Visit the Stackwise collection](https://stackwise.ai/stacks) -![Stackwise stacks collections](stacks_homepage.png) - -### Roadmap - -Our future developments include: - -- [ ] Interactive stack modification: Chat with your stack to refine and improve it. - -- [ ] Host a stack as an sdk - -- [ ] Expand integrations: Continually adding more stacks based on community feedback and contributors. Let us know what you want to see next! +![Stackwise stacks collections](public/stacks_homepage.png) ### Join The Community [![Discord Follow](https://dcbadge.vercel.app/api/server/KfUxa8h3s6?style=flat)](https://discord.gg/KfUxa8h3s6) -We welcome contributions, feedback, and suggestions to further enhance Stackwise. If you made it here you're at the very least intruiged and we'd love to have you :) +We welcome contributions, feedback, and suggestions to further enhance Stackwise. If you made it here you're at the very least intrigued and we'd love to have you :) --- diff --git a/ui/app/api/basic-openai/route.ts b/app/api/stacks/basic-openai/route.ts similarity index 100% rename from ui/app/api/basic-openai/route.ts rename to app/api/stacks/basic-openai/route.ts diff --git a/ui/app/api/boilerplate-basic/route.ts b/app/api/stacks/boilerplate-basic/route.ts similarity index 100% rename from ui/app/api/boilerplate-basic/route.ts rename to app/api/stacks/boilerplate-basic/route.ts diff --git a/ui/app/api/chat-gemini-streaming-langchain/route.ts b/app/api/stacks/chat-gemini-streaming-langchain/route.ts similarity index 100% rename from ui/app/api/chat-gemini-streaming-langchain/route.ts rename to app/api/stacks/chat-gemini-streaming-langchain/route.ts diff --git a/ui/app/api/chat-with-gemini-langchain/route.ts b/app/api/stacks/chat-with-gemini-langchain/route.ts similarity index 100% rename from ui/app/api/chat-with-gemini-langchain/route.ts rename to app/api/stacks/chat-with-gemini-langchain/route.ts diff --git a/ui/app/api/chat-with-gemini-streaming/route.ts b/app/api/stacks/chat-with-gemini-streaming/route.ts similarity index 100% rename from ui/app/api/chat-with-gemini-streaming/route.ts rename to app/api/stacks/chat-with-gemini-streaming/route.ts diff --git a/ui/app/api/chat-with-gemini/route.ts b/app/api/stacks/chat-with-gemini/route.ts similarity index 100% rename from ui/app/api/chat-with-gemini/route.ts rename to app/api/stacks/chat-with-gemini/route.ts diff --git a/ui/app/api/chat-with-openai-streaming-helicone/route.ts b/app/api/stacks/chat-with-openai-streaming-helicone/route.ts similarity index 100% rename from ui/app/api/chat-with-openai-streaming-helicone/route.ts rename to app/api/stacks/chat-with-openai-streaming-helicone/route.ts diff --git a/ui/app/api/chat-with-openai-streaming-langchain/route.ts b/app/api/stacks/chat-with-openai-streaming-langchain/route.ts similarity index 100% rename from ui/app/api/chat-with-openai-streaming-langchain/route.ts rename to app/api/stacks/chat-with-openai-streaming-langchain/route.ts diff --git a/ui/app/api/chat-with-openai-streaming/route.ts b/app/api/stacks/chat-with-openai-streaming/route.ts similarity index 100% rename from ui/app/api/chat-with-openai-streaming/route.ts rename to app/api/stacks/chat-with-openai-streaming/route.ts diff --git a/app/api/stacks/cover-image-and-subtitle/route.ts b/app/api/stacks/cover-image-and-subtitle/route.ts new file mode 100644 index 00000000..f2dcfe10 --- /dev/null +++ b/app/api/stacks/cover-image-and-subtitle/route.ts @@ -0,0 +1,220 @@ +import { spawn } from 'child_process'; +import fs from 'fs'; +import { Readable } from 'stream'; +import AWS from 'aws-sdk'; +import OpenAI from 'openai'; +import Replicate from 'replicate'; +import { v4 as uuidv4 } from 'uuid'; + +AWS.config.update({ + region: process.env.AWS_REGION, + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, +}); + +const s3 = new AWS.S3(); +const transcoder = new AWS.ElasticTranscoder(); + +export const maxDuration = 300; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN as string, +}); + +// Function to check the status of a transcoding job +const checkTranscodeJobStatus = async (jobId) => { + const params = { Id: jobId }; + return transcoder.readJob(params).promise(); +}; + +// Function to wait for the job to complete +const waitForJobCompletion = async ( + jobId, + interval = 1000, + timeout = 30000, +) => { + let timePassed = 0; + + while (timePassed < timeout) { + const { Job } = await checkTranscodeJobStatus(jobId); + if (Job) { + console.log('Job status:', Job.Status); + if (Job.Status === 'Complete') { + return true; + } else if (Job.Status === 'Error') { + // Log or return the specific error message from the job + throw new Error(`Transcoding job failed from an Error`); + } + + // Wait for the specified interval before checking again + await new Promise((resolve) => setTimeout(resolve, interval)); + timePassed += interval; + } else { + throw new Error('Transcoding job failed: Job status not available'); + } + } + + throw new Error('Transcoding job timed out'); +}; + +const startTranscodeJob = async (inputKey, outputKey, pipelineId, presetId) => { + const params = { + PipelineId: pipelineId, + Input: { Key: inputKey }, + Outputs: [{ Key: outputKey, PresetId: presetId }], + }; + return transcoder.createJob(params).promise(); +}; + +// Function to get the base64 string of the audio file +const getAudioBase64 = async (bucketName, audioKey) => { + const params = { + Bucket: bucketName, + Key: audioKey, + }; + try { + const data = await s3.getObject(params).promise(); + if (data.Body) { + return data.Body.toString('base64'); + } else { + throw new Error('No data body in response'); + } + } catch (error) { + console.error('Error getting audio base64:', error); + throw error; // Re-throw the error for handling it in the calling function + } +}; + +const createAudioFile = async (fileName: string): Promise => { + const pipelineId = '1705538698802-kk2tc9'; // Replace with your pipeline ID + const presetId = '1705539076861-v8ozl7'; // MP3 preset ID + const inputBucket = 'cover-image-and-subtitle-stack'; // Your input bucket name + const outputBucket = 'cover-image-and-subtitle-stack'; // Your output bucket name + const inputKey = fileName; + // Replace only the last occurrence of .mp4 with .mp3 + let outputKey = fileName.replace(/\.mp4$/, '.mp3'); + + try { + // Check if the output file already exists + try { + await s3.headObject({ Bucket: outputBucket, Key: outputKey }).promise(); + // File exists, create a new unique name + outputKey = `${fileName.split('.')[0]}-${Date.now()}.mp3`; + } catch (error) { + if (error.statusCode !== 404) { + throw error; // An error other than 'Not Found' + } + // If error is 404 (Not Found), it means file does not exist and we can proceed + } + // Start the transcoding job + const transcodeResponse = await startTranscodeJob( + inputKey, + outputKey, + pipelineId, + presetId, + ); + + if (transcodeResponse.Job) { + // Wait for the job to complete + await waitForJobCompletion(transcodeResponse.Job.Id); + + // Get the base64 encoded audio string + const audioBase64 = await getAudioBase64(outputBucket, outputKey); + + return `data:audio/mp3;base64,${audioBase64}`; + } else { + throw new Error('No job ID in response'); + } + } catch (error) { + console.error('Error in createAudioFile:', error); + return ''; + } +}; + +export async function POST(req: Request) { + try { + const body = await req.json(); + + //extract the audio and create an audiofile from the video buffer + const audioUri = await createAudioFile(body.fileName); + + // send the audio to replicate and getback the subtitles and long descrp + const output = await replicate.run( + 'm1guelpf/whisper-subtitles:7f686e243a96c7f6f0f481bcef24d688a1369ed3983cea348d1f43b879615766', + { + input: { + format: 'vtt', + audio_path: audioUri, + model_name: 'base', + }, + }, + ); + + if (!output || !('text' in output) || !('subtitles' in output)) { + return Response.json({ + message: 'Some Error occured in fetching subtitles !!', + }); + } + + console.log('Subtitle has beeen generate Successfully !!'); + + // send the long prompt to openAI and get back a short summary + + const prompt = output.text as string; + const subtitle = output.subtitles as string; + + const completion = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: `You are a subtitle summarizer. You will be given a large paragraph of subtitles from a video. Your goal is to summarize the video based on what was said within. Keep the summary to only a few sentences and not more than that. Give the summary directly, don't use words like "Okay,Sure" or "The paragraph" or "author" or any other words about the author or speaker. Here are the original subtitles ${prompt}.`, + }, + ], + }); + + const summarizedText = completion.choices[0]?.message?.content; + + if (!summarizedText) { + return Response.json({ message: 'Summarizer is missing !!!' }); + } + + console.log('Generated Short Description !'); + + // send the short summary to replicate and generate back an image + const imgArr = await replicate.run( + 'stability-ai/stable-diffusion:ac732df83cea7fff18b8472768c88ad041fa750ff7682a21affe81863cbe77e4', + { + input: { + prompt: `Generate a thumbnail for the following content. It should be scenic with no text on it, make sure it ABSOLUTELY does not have any text embedded on it. Understand the following prompt and generate a high quality image without any text: ${summarizedText}`, + width: 1024, + height: 576, + scheduler: 'K_EULER', + }, + }, + ); + + if (!imgArr || !imgArr[0]) { + return Response.json({ + message: 'Some Error occured in Image Generation !!', + }); + } + + console.log('The Image generated Successfully !!!'); + const imgUrl = imgArr[0] as string; + + return Response.json({ + message: 'The Audio has been extracted and stored in the server !', + subtitle, + imgUrl, + summarizedText, + }); + } catch (error) { + console.error(error); + return Response.error(); + } +} diff --git a/ui/public/stacks/create-stack-boilerplate/create-stack.ts b/app/api/stacks/create-stack-boilerplate/create-stack.ts similarity index 70% rename from ui/public/stacks/create-stack-boilerplate/create-stack.ts rename to app/api/stacks/create-stack-boilerplate/create-stack.ts index 2b5188b9..6e2fe188 100644 --- a/ui/public/stacks/create-stack-boilerplate/create-stack.ts +++ b/app/api/stacks/create-stack-boilerplate/create-stack.ts @@ -1,5 +1,5 @@ +import { getSupabaseClient } from '@/app/components/stacks/utils/stack-db'; -import { getSupabaseClient } from '@/app/stacks/stack-db'; import getFileFromGithub from './get-file-from-github'; import pushMultipleFilesToBranch from './push-multiple-files-to-branch'; @@ -8,32 +8,31 @@ export default async function createStack(data, token) { .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/\s+/g, '-') .toLowerCase(); - + const supabase = await getSupabaseClient(); const stackInfo = { name: data.name, id: stackId, description: data.description, tags: ['draft'], - } - const { data: insertedData, error } = await supabase + }; + const { data: insertedData, error } = await supabase .from('stack') - .insert([ - stackInfo - ]) + .insert([stackInfo]) .single(); - if (error) { - if (error.message.includes('duplicate key ')) { - throw new Error('This app already exists.'); - } - throw error; + if (error) { + if (error.message.includes('duplicate key ')) { + throw new Error('This app already exists.'); } - + throw error; + } + // creating api key let path = `ui/app/components/stacks/${stackInfo.id}.tsx`; let message = `Frontend For ${stackInfo.id} created`; let response = await getFileFromGithub( - 'ui/public/stacks/boilerplate-basic.tsx', token + 'ui/public/stacks/boilerplate-basic.tsx', + token, ); await new Promise((resolve) => setTimeout(resolve, 500)); @@ -45,10 +44,11 @@ export default async function createStack(data, token) { }, ]; - path = `ui/app/api/${stackInfo.id}/route.ts`; + path = `ui/app/api/stacks/${stackInfo.id}/route.ts`; message = `Backend For ${stackInfo.id} created`; response = await getFileFromGithub( - 'ui/public/stacks/boilerplate-basic/route.ts', token + 'ui/public/stacks/boilerplate-basic/route.ts', + token, ); await new Promise((resolve) => setTimeout(resolve, 500)); @@ -62,7 +62,8 @@ export default async function createStack(data, token) { message = `Preview For ${stackInfo.id} created`; response = await getFileFromGithub( - 'ui/public/stack-pictures/boilerplate-basic.png', token + 'ui/public/stack-pictures/boilerplate-basic.png', + token, ); await new Promise((resolve) => setTimeout(resolve, 500)); @@ -73,6 +74,10 @@ export default async function createStack(data, token) { }); // const sourceBranch = process.env.VERCEL_GIT_COMMIT_REF ?? ''; // or 'master', depending on your repository - const prLink = await pushMultipleFilesToBranch(filesArray, stackInfo.id, token); + const prLink = await pushMultipleFilesToBranch( + filesArray, + stackInfo.id, + token, + ); return prLink; } diff --git a/ui/app/api/create-stack-boilerplate/get-file-from-github.ts b/app/api/stacks/create-stack-boilerplate/get-file-from-github.ts similarity index 100% rename from ui/app/api/create-stack-boilerplate/get-file-from-github.ts rename to app/api/stacks/create-stack-boilerplate/get-file-from-github.ts diff --git a/ui/app/api/create-stack-boilerplate/push-multiple-files-to-branch.ts b/app/api/stacks/create-stack-boilerplate/push-multiple-files-to-branch.ts similarity index 100% rename from ui/app/api/create-stack-boilerplate/push-multiple-files-to-branch.ts rename to app/api/stacks/create-stack-boilerplate/push-multiple-files-to-branch.ts diff --git a/ui/app/api/create-stack-boilerplate/route.ts b/app/api/stacks/create-stack-boilerplate/route.ts similarity index 100% rename from ui/app/api/create-stack-boilerplate/route.ts rename to app/api/stacks/create-stack-boilerplate/route.ts diff --git a/ui/app/api/elevenlabs-tts/route.ts b/app/api/stacks/elevenlabs-tts/route.ts similarity index 100% rename from ui/app/api/elevenlabs-tts/route.ts rename to app/api/stacks/elevenlabs-tts/route.ts diff --git a/ui/app/api/get-image-description-openai/route.ts b/app/api/stacks/get-image-description-openai/route.ts similarity index 100% rename from ui/app/api/get-image-description-openai/route.ts rename to app/api/stacks/get-image-description-openai/route.ts diff --git a/ui/app/api/image-sharpener/route.ts b/app/api/stacks/image-sharpener/route.ts similarity index 100% rename from ui/app/api/image-sharpener/route.ts rename to app/api/stacks/image-sharpener/route.ts diff --git a/ui/app/api/image-to-music/route.ts b/app/api/stacks/image-to-music/route.ts similarity index 100% rename from ui/app/api/image-to-music/route.ts rename to app/api/stacks/image-to-music/route.ts diff --git a/ui/app/api/rag-pdf-with-langchain/route.ts b/app/api/stacks/rag-pdf-with-langchain/route.ts similarity index 100% rename from ui/app/api/rag-pdf-with-langchain/route.ts rename to app/api/stacks/rag-pdf-with-langchain/route.ts diff --git a/ui/app/api/stable-video-diffusion/route.ts b/app/api/stacks/stable-video-diffusion/route.ts similarity index 100% rename from ui/app/api/stable-video-diffusion/route.ts rename to app/api/stacks/stable-video-diffusion/route.ts diff --git a/app/api/stacks/text-to-qr/route.ts b/app/api/stacks/text-to-qr/route.ts new file mode 100644 index 00000000..56d024c7 --- /dev/null +++ b/app/api/stacks/text-to-qr/route.ts @@ -0,0 +1,38 @@ +import Replicate from 'replicate'; + +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN!, +}); + +export const maxDuration = 300; + +export async function POST(req: Request) { + const { qrPrompt, url } = await req.json(); + + try { + const controlNetVersion = + 'zylim0702/qr_code_controlnet:628e604e13cf63d8ec58bd4d238474e8986b054bc5e1326e50995fdbc851c557'; + const qrCode = await replicate.run(controlNetVersion, { + input: { + url: url, + prompt: qrPrompt, + qr_conditioning_scale: 1.3, + }, + }); + + return new Response( + JSON.stringify({ + img: qrCode, + }), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + ); + } catch (error) { + console.log(error); + return new Response(JSON.stringify({ error }), { + status: 500, + }); + } +} diff --git a/ui/app/api/use-openai-assistant/route.ts b/app/api/stacks/use-openai-assistant/route.ts similarity index 100% rename from ui/app/api/use-openai-assistant/route.ts rename to app/api/stacks/use-openai-assistant/route.ts diff --git a/app/api/utils/getAWSPresignedUrl/route.ts b/app/api/utils/getAWSPresignedUrl/route.ts new file mode 100644 index 00000000..ee03359c --- /dev/null +++ b/app/api/utils/getAWSPresignedUrl/route.ts @@ -0,0 +1,37 @@ +import AWS from 'aws-sdk'; + +AWS.config.update({ + region: process.env.AWS_REGION, + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, +}); + +export async function POST(req: Request) { + try { + const body = await req.json(); + const s3 = new AWS.S3(); + const { fileName, fileType } = body; + const params = { + Bucket: 'cover-image-and-subtitle-stack', + Key: fileName, + Expires: 60, // URL expiration time in seconds + ContentType: fileType, + }; + + try { + const presignedUrl = await s3.getSignedUrlPromise('putObject', params); + return new Response(JSON.stringify({ url: presignedUrl }), { + status: 200, + }); + } catch (error) { + return new Response( + JSON.stringify({ error: 'Error creating presigned URL' }), + { status: 500 }, + ); + } + } catch (error) { + return new Response(JSON.stringify({ error: 'Bad Request' }), { + status: 400, + }); + } +} diff --git a/ui/app/components/ContactButton.tsx b/app/components/shared/ContactButton.tsx similarity index 100% rename from ui/app/components/ContactButton.tsx rename to app/components/shared/ContactButton.tsx diff --git a/ui/app/components/clipboard.tsx b/app/components/shared/clipboard.tsx similarity index 100% rename from ui/app/components/clipboard.tsx rename to app/components/shared/clipboard.tsx diff --git a/ui/public/stacks/boilerplate-basic.tsx b/app/components/stacks/boilerplate-basic.tsx similarity index 94% rename from ui/public/stacks/boilerplate-basic.tsx rename to app/components/stacks/boilerplate-basic.tsx index 2fcfd8c3..cdcdd7da 100644 --- a/ui/public/stacks/boilerplate-basic.tsx +++ b/app/components/stacks/boilerplate-basic.tsx @@ -14,7 +14,7 @@ export const ChatWithOpenAIStreaming = () => { setOutput(''); setLoading(true); - const response = await fetch('/api/boilerplate-basic', { + const response = await fetch('/api/stacks/boilerplate-basic', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -53,7 +53,7 @@ export const ChatWithOpenAIStreaming = () => { -
+
{loading ? ( Generating... ) : output ? ( diff --git a/ui/public/stacks/chat-gemini-streaming-langchain.tsx b/app/components/stacks/chat-gemini-streaming-langchain.tsx similarity index 88% rename from ui/public/stacks/chat-gemini-streaming-langchain.tsx rename to app/components/stacks/chat-gemini-streaming-langchain.tsx index a47b0254..59b7e946 100644 --- a/ui/public/stacks/chat-gemini-streaming-langchain.tsx +++ b/app/components/stacks/chat-gemini-streaming-langchain.tsx @@ -15,13 +15,16 @@ export const ChatWithOpenAIStreaming = () => { setLoading(true); try { - const response = await fetch('/api/chat-gemini-streaming-langchain', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', + const response = await fetch( + '/api/stacks/chat-gemini-streaming-langchain', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ messages: inputValue }), }, - body: JSON.stringify({ messages: inputValue }), - }); + ); const data = await response.body; if (!data) { @@ -75,7 +78,7 @@ export const ChatWithOpenAIStreaming = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/ui/app/components/stacks/chat-with-gemini-langchain.tsx b/app/components/stacks/chat-with-gemini-langchain.tsx similarity index 94% rename from ui/app/components/stacks/chat-with-gemini-langchain.tsx rename to app/components/stacks/chat-with-gemini-langchain.tsx index 10d5f8c1..e29cfaf7 100644 --- a/ui/app/components/stacks/chat-with-gemini-langchain.tsx +++ b/app/components/stacks/chat-with-gemini-langchain.tsx @@ -15,7 +15,7 @@ export const ChatWithOpenAIStreaming = () => { setLoading(true); try { - const response = await fetch('/api/chat-with-gemini-langchain', { + const response = await fetch('/api/stacks/chat-with-gemini-langchain', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -62,7 +62,7 @@ export const ChatWithOpenAIStreaming = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/ui/app/components/stacks/chat-with-gemini-streaming.tsx b/app/components/stacks/chat-with-gemini-streaming.tsx similarity index 95% rename from ui/app/components/stacks/chat-with-gemini-streaming.tsx rename to app/components/stacks/chat-with-gemini-streaming.tsx index 8374f1f1..186481b5 100644 --- a/ui/app/components/stacks/chat-with-gemini-streaming.tsx +++ b/app/components/stacks/chat-with-gemini-streaming.tsx @@ -15,7 +15,7 @@ export const ChatWithOpenAIStreaming = () => { setLoading(true); try { - const response = await fetch('/api/chat-with-gemini-streaming', { + const response = await fetch('/api/stacks/chat-with-gemini-streaming', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -75,7 +75,7 @@ export const ChatWithOpenAIStreaming = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/ui/public/stacks/chat-with-gemini.tsx b/app/components/stacks/chat-with-gemini.tsx similarity index 95% rename from ui/public/stacks/chat-with-gemini.tsx rename to app/components/stacks/chat-with-gemini.tsx index ae92a83e..f143bdc2 100644 --- a/ui/public/stacks/chat-with-gemini.tsx +++ b/app/components/stacks/chat-with-gemini.tsx @@ -14,7 +14,7 @@ export const ChatWithOpenAIStreaming = () => { setLoading(true); try { - const response = await fetch('/api/chat-with-gemini', { + const response = await fetch('/api/stacks/chat-with-gemini', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -74,7 +74,7 @@ export const ChatWithOpenAIStreaming = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/ui/app/components/stacks/chat-with-openai-streaming-helicone.tsx b/app/components/stacks/chat-with-openai-streaming-helicone.tsx similarity index 88% rename from ui/app/components/stacks/chat-with-openai-streaming-helicone.tsx rename to app/components/stacks/chat-with-openai-streaming-helicone.tsx index 22e1261b..20563f6e 100644 --- a/ui/app/components/stacks/chat-with-openai-streaming-helicone.tsx +++ b/app/components/stacks/chat-with-openai-streaming-helicone.tsx @@ -14,13 +14,16 @@ export const ChatWithOpenAIStreamingHelicone = () => { setLoading(true); try { - const response = await fetch('/api/chat-openai-streaming-helicone', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', + const response = await fetch( + '/api/stacks/chat-openai-streaming-helicone', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ messages: inputValue }), }, - body: JSON.stringify({ messages: inputValue }), - }); + ); const data = await response.body; if (!data) { @@ -74,7 +77,7 @@ export const ChatWithOpenAIStreamingHelicone = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/ui/app/components/stacks/chat-with-openai-streaming-langchain.tsx b/app/components/stacks/chat-with-openai-streaming-langchain.tsx similarity index 100% rename from ui/app/components/stacks/chat-with-openai-streaming-langchain.tsx rename to app/components/stacks/chat-with-openai-streaming-langchain.tsx diff --git a/ui/public/stacks/chat-with-openai-streaming.tsx b/app/components/stacks/chat-with-openai-streaming.tsx similarity index 95% rename from ui/public/stacks/chat-with-openai-streaming.tsx rename to app/components/stacks/chat-with-openai-streaming.tsx index fd13f6ab..8802a713 100644 --- a/ui/public/stacks/chat-with-openai-streaming.tsx +++ b/app/components/stacks/chat-with-openai-streaming.tsx @@ -14,7 +14,7 @@ export const ChatWithOpenAIStreaming = () => { setLoading(true); try { - const response = await fetch('/api/chat-with-openai-streaming', { + const response = await fetch('/api/stacks/chat-with-openai-streaming', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -74,7 +74,7 @@ export const ChatWithOpenAIStreaming = () => {
-
+
{loading ? ( Generating... ) : generatedFileContents ? ( diff --git a/app/components/stacks/cover-image-and-subtitle.tsx b/app/components/stacks/cover-image-and-subtitle.tsx new file mode 100644 index 00000000..e8f7bde9 --- /dev/null +++ b/app/components/stacks/cover-image-and-subtitle.tsx @@ -0,0 +1,209 @@ +'use client'; + +import { useState } from 'react'; + +export const GenerateImageAndSubtitle = () => { + const [video, setVideo] = useState(null); + const [loading, setLoading] = useState(false); + const [summary, setSummary] = useState(''); + const [subtitle, setSubtitle] = useState(''); + const [imgUrl, setImgUrl] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [videoUploading, setVideoUploading] = useState(false); + + const handleVideoUpload = async (e: React.ChangeEvent) => { + if (subtitle) { + setSubtitle(''); + } + if (imgUrl) { + setImgUrl(''); + } + if (e.target.files && e.target.files[0]) { + setVideoUploading(true); + const file = e.target.files[0]; + try { + // Fetch the presigned URL + const response = await fetch('/api/utils/getAWSPresignedUrl', { + method: 'POST', + body: JSON.stringify({ + fileName: file.name, + fileType: file.type, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + const { url } = await response.json(); + + // Upload the file using the presigned URL + const uploadResponse = await fetch(url, { + method: 'PUT', + body: file, + headers: { + 'Content-Type': file.type, + }, + }); + + if (uploadResponse.ok) { + setVideo(file); + setVideoUploading(false); + } else { + console.error('Upload failed.'); + } + } catch (error) { + console.error('Error during upload:', error); + } + } + }; + + const getSubtitle = async () => { + try { + if (loading || !video) { + return; + } + + if (subtitle || imgUrl) { + setSubtitle(''); + setImgUrl(''); + return; + } + + setLoading(true); + if (!video) return; + + const response = await fetch('/api/stacks/cover-image-and-subtitle', { + method: 'POST', + body: JSON.stringify({ fileName: video.name }), + }); + + const data = await response.json(); + setSubtitle(data.subtitle); + setImgUrl(data.imgUrl); + setSummary(data.summarizedText); + setLoading(false); + } catch (error) { + console.error(error); + } + }; + + const downloadSubtitle = () => { + if (!subtitle) { + console.error('Subtitle not Present !!'); + return; + } + + const blob = new Blob([subtitle], { type: 'text/vtt' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'subtitle.vtt'; + + document.body.appendChild(link); + link.click(); + + // Clean up the link + document.body.removeChild(link); + }; + + return ( +
+
+ +
+ {videoUploading && <>Video Uploading...} +
+ {video && ( +
+ +
+ ); +}; + +export default GenerateImageAndSubtitle; diff --git a/ui/public/stacks/create-ai-canvas.tsx b/app/components/stacks/create-ai-canvas.tsx similarity index 98% rename from ui/public/stacks/create-ai-canvas.tsx rename to app/components/stacks/create-ai-canvas.tsx index f13c4fb0..d599b092 100644 --- a/ui/public/stacks/create-ai-canvas.tsx +++ b/app/components/stacks/create-ai-canvas.tsx @@ -51,7 +51,7 @@ const CreateAICanvas: React.FC = () => { sync_mode: true, image_url: dataUriImage, strength: 0.65, - enable_safety_checks: true, + enable_safety_checks: false, }); }, [dataUriImage, imagePrompt]); diff --git a/ui/public/stacks/create-stack-boilerplate.tsx b/app/components/stacks/create-stack-boilerplate.tsx similarity index 68% rename from ui/public/stacks/create-stack-boilerplate.tsx rename to app/components/stacks/create-stack-boilerplate.tsx index 52f3cb09..43a67afe 100644 --- a/ui/public/stacks/create-stack-boilerplate.tsx +++ b/app/components/stacks/create-stack-boilerplate.tsx @@ -1,38 +1,41 @@ -"use client" -import SignIn from '@/app/stacks/signIn'; -import { supabaseClient } from '@/app/stacks/stack-db'; -import Link from 'next/link'; +'use client'; + import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import SignIn from '@/app/components/stacks/utils/signIn'; +import { supabaseClient } from '@/app/components/stacks/utils/stack-db'; export const BasicForm = () => { const [formData, setFormData] = useState({ - name: 'My New App', + name: 'My New App', }); const [formErrors, setFormErrors] = useState({ id: '' }); const [Message, setMessage] = useState(''); const [isUserSignedIn, setIsUserSignedIn] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(""); - const [pullRequestUrl, setPullRequestUrl] = useState(""); + const [username, setUsername] = useState(''); + const [token, setToken] = useState(''); + const [pullRequestUrl, setPullRequestUrl] = useState(''); const [isLoading, setIsLoading] = useState(false); useEffect(() => { // Check if user is signed in async function checkUser() { try { - const session = await supabaseClient.auth.getSession() - const token = session?.data?.session?.provider_token + const session = await supabaseClient.auth.getSession(); + const token = session?.data?.session?.provider_token; if (token) { setIsUserSignedIn(true); - setUsername(session?.data?.session?.user.user_metadata.preferred_username) - setToken(token) + setUsername( + session?.data?.session?.user.user_metadata.preferred_username, + ); + setToken(token); } } catch { - console.log("Error getting user") + console.log('Error getting user'); } } - checkUser() + checkUser(); }, []); // const { getToken } = useAuth(); @@ -58,7 +61,7 @@ export const BasicForm = () => { event.preventDefault(); try { - const response = await fetch('/api/create-stack-boilerplate', { + const response = await fetch('/api/stacks/create-stack-boilerplate', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -77,13 +80,12 @@ export const BasicForm = () => { } } catch (error) { console.log(error); - setMessage("error on form submission"); - + setMessage('error on form submission'); } }; if (!isUserSignedIn) { - return (); + return ; } return ( @@ -107,15 +109,14 @@ export const BasicForm = () => { {isLoading &&
Loading...
} {Message &&
{Message}
} - - {pullRequestUrl && - - -
-

You can now view your pull Request

-
- - } + + {pullRequestUrl && ( + +
+

You can now view your pull Request

+
+ + )}
); diff --git a/ui/app/components/Inputs.tsx b/app/components/stacks/creation/Inputs.tsx similarity index 100% rename from ui/app/components/Inputs.tsx rename to app/components/stacks/creation/Inputs.tsx diff --git a/ui/app/components/content.tsx b/app/components/stacks/creation/content.tsx similarity index 95% rename from ui/app/components/content.tsx rename to app/components/stacks/creation/content.tsx index 955a2f19..add436d4 100644 --- a/ui/app/components/content.tsx +++ b/app/components/stacks/creation/content.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { useFormState } from 'react-dom'; import tw from 'tailwind-styled-components'; -import { callStack, parseFormData } from '../actions'; +import { callStack, parseFormData } from '../utils/actions'; import InputWithButton from './input-with-button'; import Inputs from './Inputs'; import Outputs from './outputs'; diff --git a/ui/app/components/input-with-button.tsx b/app/components/stacks/creation/input-with-button.tsx similarity index 97% rename from ui/app/components/input-with-button.tsx rename to app/components/stacks/creation/input-with-button.tsx index aeb84b37..a40d10d5 100644 --- a/ui/app/components/input-with-button.tsx +++ b/app/components/stacks/creation/input-with-button.tsx @@ -7,7 +7,7 @@ import { IoSend } from 'react-icons/io5'; import tw from 'tailwind-styled-components'; // import SearchStacks from './search-stacks'; -import { StackDescription } from '../stacks/stack-db'; +import { StackDescription } from '../utils/stack-db'; export const SubmitButton = () => { const { pending } = useFormStatus(); diff --git a/ui/app/components/main-content.tsx b/app/components/stacks/creation/main-content.tsx similarity index 100% rename from ui/app/components/main-content.tsx rename to app/components/stacks/creation/main-content.tsx diff --git a/ui/app/components/outputs.tsx b/app/components/stacks/creation/outputs.tsx similarity index 100% rename from ui/app/components/outputs.tsx rename to app/components/stacks/creation/outputs.tsx diff --git a/ui/public/stacks/elevenlabs-tts.tsx b/app/components/stacks/elevenlabs-tts.tsx similarity index 95% rename from ui/public/stacks/elevenlabs-tts.tsx rename to app/components/stacks/elevenlabs-tts.tsx index 448c0125..25f5c9b8 100644 --- a/ui/public/stacks/elevenlabs-tts.tsx +++ b/app/components/stacks/elevenlabs-tts.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useState } from 'react'; -import ClipboardComponent from '@/app/components/clipboard'; import { IoSend } from 'react-icons/io5'; // Chat component @@ -50,7 +49,7 @@ const Chat = () => { }; async function playText(text: string) { - const response = await fetch('/api/elevenlabs-tts', { + const response = await fetch('/api/stacks/elevenlabs-tts', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/ui/app/components/stacks/get-image-description-openai.tsx b/app/components/stacks/get-image-description-openai.tsx similarity index 100% rename from ui/app/components/stacks/get-image-description-openai.tsx rename to app/components/stacks/get-image-description-openai.tsx diff --git a/ui/app/components/stacks/image-sharpener.tsx b/app/components/stacks/image-sharpener.tsx similarity index 98% rename from ui/app/components/stacks/image-sharpener.tsx rename to app/components/stacks/image-sharpener.tsx index cb7d87ab..beaa0495 100644 --- a/ui/app/components/stacks/image-sharpener.tsx +++ b/app/components/stacks/image-sharpener.tsx @@ -49,7 +49,7 @@ export default function ImageToMusic() { if (img) { formData.append('img', img); } - const response = await fetch('/api/image-sharpener', { + const response = await fetch('/api/stacks/image-sharpener', { method: 'POST', body: formData, }); diff --git a/ui/public/stacks/image-to-music.tsx b/app/components/stacks/image-to-music.tsx similarity index 98% rename from ui/public/stacks/image-to-music.tsx rename to app/components/stacks/image-to-music.tsx index 0a4cf6ba..394e83d5 100644 --- a/ui/public/stacks/image-to-music.tsx +++ b/app/components/stacks/image-to-music.tsx @@ -52,7 +52,7 @@ export default function ImageToMusic() { formData.append('img', img); } formData.append('length', musicLength.toString()); - const response = await fetch('/api/image-to-music', { + const response = await fetch('/api/stacks/image-to-music', { method: 'POST', body: formData, }); diff --git a/ui/app/components/stacks/instant-video-to-image.tsx b/app/components/stacks/instant-video-to-image.tsx similarity index 100% rename from ui/app/components/stacks/instant-video-to-image.tsx rename to app/components/stacks/instant-video-to-image.tsx diff --git a/ui/app/components/stacks/rag-pdf-with-langchain.tsx b/app/components/stacks/rag-pdf-with-langchain.tsx similarity index 99% rename from ui/app/components/stacks/rag-pdf-with-langchain.tsx rename to app/components/stacks/rag-pdf-with-langchain.tsx index 027a91e6..c67ed2d3 100644 --- a/ui/app/components/stacks/rag-pdf-with-langchain.tsx +++ b/app/components/stacks/rag-pdf-with-langchain.tsx @@ -160,7 +160,7 @@ const RAGPDFWithLangchain = () => { setChatHistory((prev) => [...prev, `Q: ${question}`, placeholderAnswering]); try { - const response = await fetch('/api/rag-pdf-with-langchain', { + const response = await fetch('/api/stacks/rag-pdf-with-langchain', { method: 'POST', body: formData, signal: signal, diff --git a/ui/app/components/stacks/stable-video-diffusion.tsx b/app/components/stacks/stable-video-diffusion.tsx similarity index 98% rename from ui/app/components/stacks/stable-video-diffusion.tsx rename to app/components/stacks/stable-video-diffusion.tsx index 07678525..e5c5f197 100644 --- a/ui/app/components/stacks/stable-video-diffusion.tsx +++ b/app/components/stacks/stable-video-diffusion.tsx @@ -159,7 +159,7 @@ const StableVideoDiffusion = () => { formData.append('degreeOfMotion', ensureMotion.toString()); // Call the API with FormData - const apiResponse = await fetch('/api/stable-video-diffusion', { + const apiResponse = await fetch('/api/stacks/stable-video-diffusion', { method: 'POST', body: formData, // FormData is used directly here }); diff --git a/app/components/stacks/text-to-qr.tsx b/app/components/stacks/text-to-qr.tsx new file mode 100644 index 00000000..32e40534 --- /dev/null +++ b/app/components/stacks/text-to-qr.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { useState } from 'react'; + +export default function ImageToMusic() { + const [qrPrompt, setQrPrompt] = useState('a city view with clouds'); + const [url, setUrl] = useState(''); + const [loading, setLoading] = useState(false); + const [img, setImg] = useState(''); + + const createMusic = async () => { + if (loading) return; + if (img) { + setImg(''); + return; + } + + setLoading(true); + + const response = await fetch('/api/stacks/text-to-qr', { + method: 'POST', + body: JSON.stringify({ qrPrompt, url }), + }); + const data = await response.json(); + setImg(data.img); + setLoading(false); + }; + + return ( +
+
+ {img ? ( + + ) : ( + <> + setUrl(e.target.value)} + value={url} + placeholder="Enter message or website link..." + className="mb-2 w-full rounded-lg border border-gray-300 p-2 pl-4 sm:w-3/4 md:w-1/2" + /> +