├── src ├── lib │ ├── db │ │ └── index.ts │ ├── assets │ │ └── noise.webp │ ├── utils │ │ ├── enums.ts │ │ ├── tooltip.ts │ │ ├── readable-stream.svelte.ts │ │ ├── util.ts │ │ ├── markdown-service.ts │ │ ├── scrapper.ts │ │ └── prompts.ts │ ├── components │ │ ├── board │ │ │ ├── Arbol.svelte │ │ │ ├── RowEncabezado.svelte │ │ │ ├── Badge.svelte │ │ │ ├── LightRate.svelte │ │ │ ├── Head.svelte │ │ │ ├── ListadoHeaders.svelte │ │ │ ├── Stat.svelte │ │ │ └── Markdown.svelte │ │ ├── Home │ │ │ ├── Item.svelte │ │ │ ├── Rol.svelte │ │ │ └── Feature.svelte │ │ ├── Hero.svelte │ │ ├── Nav.svelte │ │ ├── Footer.svelte │ │ ├── Search.svelte │ │ ├── Seo.svelte │ │ ├── Board.svelte │ │ └── Placeholder.svelte │ ├── stores │ │ └── StorePage.svelte.ts │ ├── types.ts │ └── server │ │ └── IA.ts ├── routes │ ├── +page.ts │ ├── api │ │ ├── +server.ts │ │ ├── urlService │ │ │ └── +server.ts │ │ ├── summarize │ │ │ └── +server.ts │ │ ├── improve │ │ │ └── +server.ts │ │ └── rate │ │ │ └── +server.ts │ ├── app │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── +layout.svelte │ └── +page.svelte ├── styles │ ├── tailwind.css │ └── app.scss ├── index.test.ts ├── app.d.ts └── app.html ├── .npmrc ├── .prettierignore ├── postcss.config.js ├── tailwind.config.js ├── tests └── test.ts ├── .prettierrc ├── vite.config.ts ├── .gitignore ├── playwright.config.ts ├── static └── favicon.svg ├── tsconfig.json ├── eslint.config.js ├── svelte.config.js ├── README.md └── package.json /src/lib/db/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/lib/assets/noise.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imlargo/CopyWhisper/HEAD/src/lib/assets/noise.webp -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/lib/utils/enums.ts: -------------------------------------------------------------------------------- 1 | export const pageState = { 2 | WAITING: 1, 3 | OK: 2, 4 | ANALIZADO: 3, 5 | NOTOK: 4 6 | }; 7 | -------------------------------------------------------------------------------- /src/routes/api/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | 3 | export async function POST() { 4 | return json({ message: 'Hi imlargo!' }); 5 | } 6 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {} 6 | }, 7 | plugins: [] 8 | }; 9 | -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('home page has expected h1', async ({ page }) => { 4 | await page.goto('/'); 5 | await expect(page.locator('h1')).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /build 8 | 9 | # OS 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Env 14 | .env 15 | .env.* 16 | !.env.example 17 | !.env.test 18 | 19 | # Vite 20 | vite.config.js.timestamp-* 21 | vite.config.ts.timestamp-* 22 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/routes/app/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load = (async ({ url }) => { 5 | const link = url.searchParams.get('link'); 6 | 7 | if (!link) { 8 | redirect(307, '/'); 9 | } 10 | 11 | return {}; 12 | }) satisfies PageServerLoad; 13 | -------------------------------------------------------------------------------- /src/lib/components/board/Arbol.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if storePage.data !== null} 7 | {#each storePage.data.encabezados as encabezado, i} 8 | 9 | {/each} 10 | {/if} 11 | -------------------------------------------------------------------------------- /src/routes/api/urlService/+server.ts: -------------------------------------------------------------------------------- 1 | import { text } from '@sveltejs/kit'; 2 | import type { RequestHandler } from './$types'; 3 | 4 | export const POST: RequestHandler = async ({ request }) => { 5 | const { link } = await request.json(); 6 | 7 | const rawHtml = await fetch(link).then((res) => res.text()); 8 | 9 | return text(rawHtml); 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/utils/tooltip.ts: -------------------------------------------------------------------------------- 1 | import tippy from 'tippy.js'; 2 | 3 | export function tooltipAction(element: HTMLElement, tooltip: string) { 4 | const instance = tippy(element, { 5 | content: tooltip 6 | }); 7 | 8 | return { 9 | update(tooltip: string) { 10 | instance.setContent(tooltip); 11 | }, 12 | 13 | destroy() { 14 | instance.destroy(); 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/components/Home/Item.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
{titulo}
8 | 9 |
10 | 11 |

12 | {descripcion} 13 |

14 |
15 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 |
13 |
15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 13 | 14 | 15 |
%sveltekit.body%
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/Hero.svelte: -------------------------------------------------------------------------------- 1 |
2 |

3 | Haz brillar el copywritting de tu pagina 4 | web. 5 |

6 | 7 |

8 | Transforma el contenido de tu pagina web con análisis avanzado y recomendaciones personalizadas 9 | para lograr páginas web impactantes y de alta calidad. 10 |

11 |
12 | 13 | 15 | -------------------------------------------------------------------------------- /src/lib/utils/readable-stream.svelte.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Decodifica los datos de un stream y los agrega progresivamente a un estado para que se muestre reactivamente 3 | */ 4 | export async function decodeStreamData(stream: ReadableStream, callback: (data: string) => void) { 5 | const reader = stream.getReader(); 6 | const decoder = new TextDecoder(); 7 | 8 | while (true) { 9 | const { done, value } = await reader.read(); 10 | if (done) { 11 | break; 12 | } 13 | 14 | callback(decoder.decode(value, { stream: true })); 15 | } 16 | 17 | callback(decoder.decode()); 18 | 19 | reader.releaseLock(); 20 | } 21 | -------------------------------------------------------------------------------- /src/routes/api/summarize/+server.ts: -------------------------------------------------------------------------------- 1 | import { prompts } from '$src/lib/utils/prompts'; 2 | import { generateStreamResponse } from '$server/IA'; 3 | import type { RequestHandler } from './$types'; 4 | import type { summarizeRequest } from '$src/lib/types'; 5 | 6 | export const POST: RequestHandler = async ({ request }) => { 7 | // Obtener los datos de la pagina 8 | const requestData: summarizeRequest = await request.json(); 9 | 10 | // Generar prompt con respecto a los datos de la pagina 11 | const { sys, prompt } = prompts.RESUMIR(requestData); 12 | 13 | const result = await generateStreamResponse(sys, prompt); 14 | 15 | return result; 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/utils/util.ts: -------------------------------------------------------------------------------- 1 | import type { Encabezado } from '$lib/types'; 2 | 3 | export function verificarEncabezados(encabezados: Encabezado[]): boolean { 4 | const inicial = parseInt(encabezados[0].tag.slice(1)); 5 | let anterior = inicial; 6 | for (const encabezado of encabezados) { 7 | const currNivel = parseInt(encabezado.tag.slice(1)); 8 | 9 | if (currNivel < inicial) { 10 | return false; 11 | } 12 | 13 | if (currNivel - anterior > 1) { 14 | return false; 15 | } 16 | 17 | anterior = currNivel; 18 | } 19 | 20 | return true; 21 | } 22 | 23 | export function getCodeAsMarkdown(tipo: string, contenido: string): string { 24 | return `\`\`\`${tipo} 25 | ${contenido} 26 | \`\`\``; 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/utils/markdown-service.ts: -------------------------------------------------------------------------------- 1 | import TurndownService from 'turndown'; 2 | import { marked } from 'marked'; 3 | 4 | const turndownService = new TurndownService({ 5 | headingStyle: 'atx', 6 | hr: '---', 7 | bulletListMarker: '-', 8 | codeBlockStyle: 'indented', 9 | fence: '```', 10 | emDelimiter: '_', 11 | strongDelimiter: '**', 12 | linkStyle: 'inlined' 13 | }); 14 | 15 | /* 16 | * Convertir el cuerpo de la pagina a markdown 17 | */ 18 | export function convertToMarkdown(body: HTMLElement): string { 19 | return turndownService.turndown(body).trim(); 20 | } 21 | 22 | /* 23 | Convertir el markdown a html para ser renderizado 24 | */ 25 | export function convertToHtml(markdown: string): string { 26 | return marked.parse(markdown); 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/components/board/RowEncabezado.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | {encabezado.content} 17 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /src/routes/api/improve/+server.ts: -------------------------------------------------------------------------------- 1 | import { prompts } from '$src/lib/utils/prompts'; 2 | import { generateStreamResponse } from '$server/IA'; 3 | import type { ImproveRequest } from '$src/lib/types'; 4 | import type { RequestHandler } from './$types'; 5 | 6 | export const POST: RequestHandler = async ({ request }) => { 7 | // Obtener los datos de la pagina 8 | const requestData: ImproveRequest = await request.json(); 9 | 10 | const isTitulo = requestData.elemento.tag.toLowerCase().includes('h'); 11 | 12 | // Generar prompt con respecto a los datos de la pagina 13 | const { sys, prompt } = isTitulo 14 | ? prompts.MEJORAR_TITULO(requestData) 15 | : prompts.MEJORAR_CONTENIDO(requestData); 16 | 17 | const result = await generateStreamResponse(sys, prompt); 18 | 19 | return result; 20 | }; 21 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.FlatConfig[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /src/routes/api/rate/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import type { RateRequest, Rate } from '$src/lib/types'; 3 | import { prompts } from '$src/lib/utils/prompts'; 4 | import { generateTextResponse } from '$server/IA'; 5 | import type { RequestHandler } from './$types'; 6 | 7 | export const POST: RequestHandler = async ({ request }) => { 8 | // Obtener los datos de la pagina 9 | const requestData: RateRequest = await request.json(); 10 | 11 | // Generar prompt con respecto a los datos de la pagina 12 | const { sys, prompt } = prompts.CALIFICAR_CONTENIDO(requestData); 13 | 14 | const result = await generateTextResponse(sys, prompt); 15 | 16 | const response: Rate = JSON.parse(result.slice(result.indexOf('{'), result.lastIndexOf('}') + 1)); 17 | 18 | return json(response); 19 | }; 20 | -------------------------------------------------------------------------------- /src/lib/components/board/Badge.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | 18 | 35 | -------------------------------------------------------------------------------- /src/lib/components/Home/Rol.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | 16 |

{rol}

17 | 18 |

19 | {descripcion} 20 |

21 |
22 | 23 | 28 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | alias: { 16 | $src: 'src', 17 | $styles: 'src/styles', 18 | $assets: 'src/lib/assets', 19 | $components: 'src/lib/components', 20 | $db: 'src/lib/db', 21 | $server: 'src/lib/server', 22 | $stores: 'src/lib/stores', 23 | $utils: 'src/lib/utils' 24 | } 25 | } 26 | }; 27 | 28 | export default config; 29 | -------------------------------------------------------------------------------- /src/lib/stores/StorePage.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Encabezado, PageData, RateResponse } from '$lib/types'; 2 | import { pageState } from '$utils/enums'; 3 | 4 | class StorePage { 5 | data: PageData | null = $state(null); 6 | rate: RateResponse | null = $state(null); 7 | estado: number = $state(pageState.WAITING); 8 | encabezados: Encabezado[] = $state([]); 9 | resumen: string = $state(''); 10 | 11 | /* 12 | inicializar la store con los datos base de la pagina 13 | */ 14 | init(pageData: PageData) { 15 | this.estado = pageState.OK; 16 | this.data = pageData; 17 | } 18 | 19 | /* 20 | guardar la calificación de la página 21 | */ 22 | setRate(rate: Rate) { 23 | this.estado = pageState.ANALIZADO; 24 | console.log(rate); 25 | this.rate = rate; 26 | } 27 | 28 | /* 29 | limpiar la store 30 | */ 31 | reset() { 32 | this.data = null; 33 | this.rate = null; 34 | this.estado = pageState.WAITING; 35 | this.resumen = ''; 36 | this.encabezados = []; 37 | } 38 | } 39 | 40 | export const storePage = new StorePage(); 41 | -------------------------------------------------------------------------------- /src/lib/components/board/LightRate.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 27 | 28 | 45 | -------------------------------------------------------------------------------- /src/lib/components/Nav.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 41 | -------------------------------------------------------------------------------- /src/lib/components/board/Head.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 17 | 18 |
19 | {#if storePage.estado === pageState.OK || storePage.estado === pageState.ANALIZADO} 20 | {storePage.data ? storePage.data.link : ''} 21 | / 22 | {storePage.data ? storePage.data.titulo : ''} 23 | {:else} 24 | 25 | {/if} 26 |
27 |
28 | 29 |
30 | 31 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CopyWhisper 2 | 3 | > Desarrollada para el hackatón de midudev en convenio con Vercel, aunque tengo pensado continuar con su desarrollo mas adelante :p. 4 | 5 | CopyWhisper es una aplicación web innovadora diseñada para analizar, calificar, optimizar y proporcionar recomendaciones personalizadas sobre el copywriting y el contenido de páginas web. CopyWhisper utiliza GeminiAi a través del Vercel SDK, facilitando el trabajo de revisión de contenido y ayudando a mejorar la calidad del copywriting en línea. 6 | 7 | #### Características 8 | 9 | - Análisis de Copywriting: CopyWhisper revisa automáticamente el contenido de tu página web, identificando áreas de mejora y fortalezas. 10 | - Calificaciones: Obtén una puntuación detallada de la calidad de tu copywriting, basada en varios criterios establecidos. 11 | - Optimización: Recibe sugerencias específicas para mejorar el contenido de tu página, ayudándote a maximizar su impacto y efectividad. 12 | - Recomendaciones Personalizadas: Disfruta de consejos hechos a medida para mejorar el copywriting, adaptados a las necesidades específicas de tu sitio web. 13 | 14 | #### Tecnologías Utilizadas 15 | 16 | - Svelte, Tailwind, CSS, SCSS, Typescript 17 | - Vercel AI SDK 18 | -------------------------------------------------------------------------------- /src/lib/components/board/ListadoHeaders.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#if storePage.data !== null && verificarEncabezados(storePage.data.encabezados)} 13 | 17 | {:else if storePage.data !== null && !verificarEncabezados(storePage.data.encabezados)} 18 | 22 | {/if} 23 | 24 | {#each headers as header} 25 | encabezado.tag === header) 28 | : true}>{header} 30 | {/each} 31 |
32 | -------------------------------------------------------------------------------- /src/lib/components/Home/Feature.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | {titulo} 8 |
9 | 10 |

11 | {descripcion} 12 |

13 |
14 | 15 | 59 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | export interface Encabezado { 2 | tag: string; 3 | content: string; 4 | } 5 | 6 | export interface HeaderTree { 7 | encabezado: Encabezado; 8 | hijos: HeaderTree[]; 9 | } 10 | 11 | export interface PageData { 12 | link: string; 13 | titulo: string; 14 | descripcion: string; 15 | encabezados: Encabezado[]; 16 | data: string; 17 | tree: HeaderTree[]; 18 | markdown: string; 19 | renderedMarkdown: string; 20 | } 21 | 22 | export interface Prompt { 23 | sys: string; 24 | prompt: string; 25 | } 26 | 27 | export interface RateRequest { 28 | link: string; 29 | titulo: string; 30 | descripcion: string; 31 | markdown: string; 32 | } 33 | 34 | export interface RateResponse { 35 | contenido: Rate; 36 | tono: Rate; 37 | persuacion: Rate; 38 | errores: Rate; 39 | } 40 | 41 | export interface Rate { 42 | cuantitativo: string; 43 | cualitativo: 'bajo' | 'medio' | 'alto' | 'excelente'; 44 | comentarios: string; 45 | sugerencias: string; 46 | } 47 | 48 | export interface ImproveElement { 49 | content: string; 50 | tag: string; 51 | } 52 | 53 | export interface ImproveRequest { 54 | link: string; 55 | titulo: string; 56 | descripcion: string; 57 | markdown: string; 58 | elemento: ImproveElement; 59 | } 60 | 61 | export interface summarizeRequest { 62 | titulo: string; 63 | descripcion: string; 64 | markdown: string; 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/components/Footer.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 57 | -------------------------------------------------------------------------------- /src/lib/server/IA.ts: -------------------------------------------------------------------------------- 1 | import { createGoogleGenerativeAI } from '@ai-sdk/google'; 2 | import { generateText, streamText, generateObject, streamObject } from 'ai'; 3 | import { env } from '$env/dynamic/private'; 4 | 5 | const TEMPERATURA = 0.8; 6 | 7 | const provider = createGoogleGenerativeAI({ 8 | apiKey: env.AI_API_KEY ?? '' 9 | }); 10 | 11 | const model = provider('models/gemini-pro'); 12 | 13 | /* 14 | * Generar respuesta en base a un prompt y devolverla como un string 15 | */ 16 | export async function generateTextResponse(system: string, prompt: string): Promise { 17 | const { text } = await generateText({ 18 | model: model, 19 | system: system, 20 | prompt: prompt 21 | }); 22 | 23 | return text; 24 | } 25 | /* 26 | * Generar respuesta en base a un prompt y devolverla como un ReadableStream 27 | */ 28 | export async function generateStreamResponse(system: string, prompt: string) { 29 | const result = await streamText({ 30 | model: model, 31 | temperature: TEMPERATURA, 32 | system: system, 33 | prompt: prompt 34 | }); 35 | 36 | return result.toTextStreamResponse(); 37 | } 38 | 39 | export async function generateObjectResponse(system: string, prompt: string, schema: any) { 40 | const { object } = await generateObject({ 41 | model: model, 42 | system: system, 43 | prompt: prompt, 44 | schema: schema 45 | }); 46 | 47 | return object; 48 | } 49 | 50 | export async function generateObjectStream(system: string, prompt: string, schema: any) { 51 | const { partialObjectStream } = await streamObject({ 52 | model: model, 53 | system: system, 54 | prompt: prompt, 55 | schema: schema 56 | }); 57 | 58 | return partialObjectStream; 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copywhisper", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "update": "npm install --save-dev --legacy-peer-deps svelte@next @sveltejs/kit@latest", 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "test": "npm run test:integration && npm run test:unit", 11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 | "lint": "prettier --check . && eslint .", 14 | "format": "prettier --write .", 15 | "test:integration": "playwright test", 16 | "test:unit": "vitest" 17 | }, 18 | "devDependencies": { 19 | "@playwright/test": "^1.28.1", 20 | "@sveltejs/adapter-auto": "^3.0.0", 21 | "@sveltejs/kit": "^2.5.20", 22 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 23 | "@types/eslint": "^8.56.7", 24 | "autoprefixer": "^10.4.19", 25 | "eslint": "^9.0.0", 26 | "eslint-config-prettier": "^9.1.0", 27 | "eslint-plugin-svelte": "^2.36.0", 28 | "globals": "^15.0.0", 29 | "postcss": "^8.4.39", 30 | "prettier": "^3.1.1", 31 | "prettier-plugin-svelte": "^3.1.2", 32 | "sass": "^1.77.8", 33 | "svelte": "^5.0.0-next.210", 34 | "svelte-check": "^3.6.0", 35 | "tailwindcss": "^3.4.4", 36 | "tslib": "^2.4.1", 37 | "typescript": "^5.0.0", 38 | "typescript-eslint": "^8.0.0-alpha.20", 39 | "vite": "^5.0.3", 40 | "vitest": "^1.2.0" 41 | }, 42 | "type": "module", 43 | "dependencies": { 44 | "@ai-sdk/google": "^0.0.26", 45 | "@ai-sdk/openai": "^0.0.36", 46 | "ai": "^3.2.19", 47 | "marked": "^13.0.2", 48 | "tippy.js": "^6.3.7", 49 | "turndown": "^7.2.0", 50 | "zod": "^3.23.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/components/Search.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 | 32 | 33 | 38 |
39 | 40 | 72 | -------------------------------------------------------------------------------- /src/lib/components/Seo.svelte: -------------------------------------------------------------------------------- 1 | 2 | CopyWhisper - Haz brillar el copywritting de tu pagina web con análisis avanzado y 4 | recomendaciones personalizadas. 6 | 7 | 8 | 12 | 13 | 14 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/lib/components/board/Stat.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 |
35 |
36 |
37 | {tipo} 38 |
39 | 44 |
45 | 46 | 47 | {rate === null ? '...' : rate.cuantitativo} 50 | / 51 | 10 52 | 53 |
54 | 55 | 76 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:slnt,wght@-10..0,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap'); 3 | @import 'tailwind.css'; 4 | 5 | body { 6 | font-family: 'Inter', sans-serif !important; 7 | font-optical-sizing: auto; 8 | font-style: normal; 9 | font-variation-settings: 'slnt' 0; 10 | font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11' !important; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | text-rendering: optimizeLegibility; 14 | font-optical-sizing: auto; 15 | 16 | background: #171719; 17 | color: #fff; 18 | 19 | position: relative; 20 | 21 | &::before { 22 | content: ''; 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | width: 100%; 27 | height: 100%; 28 | --size: 11px; 29 | --color: rgba(255, 255, 255, 0.04); 30 | 31 | z-index: -2; 32 | 33 | background: #17171900 radial-gradient(var(--color) 10%, transparent 1%); 34 | background-size: var(--size) var(--size); 35 | } 36 | } 37 | 38 | .page-container { 39 | @apply mx-auto w-full md:max-w-screen-sm lg:max-w-screen-xl px-5; 40 | } 41 | 42 | .font-mono { 43 | font-family: 'JetBrains Mono', monospace !important; 44 | } 45 | 46 | .typing { 47 | position: relative; 48 | 49 | &:after { 50 | content: ''; 51 | position: absolute; 52 | 53 | right: -0.5rem; 54 | bottom: 1rem; 55 | 56 | width: 0.2rem; 57 | height: 60%; 58 | 59 | background-color: rgb(255, 255, 255, 0.7); 60 | 61 | /* 62 | animation: blink 1s infinite; 63 | 64 | border-right: rgb(255, 255, 255) 0.2rem solid; 65 | 66 | */ 67 | animation: blink 0.5s step-end infinite alternate; 68 | } 69 | } 70 | 71 | @keyframes blink { 72 | 50% { 73 | opacity: 0; 74 | } 75 | } 76 | 77 | .tippy-content { 78 | white-space: pre-line !important; 79 | } 80 | 81 | .resaltado { 82 | background: linear-gradient(90deg, #9f4bff 40%, #bf35ff 100%); 83 | -webkit-background-clip: text; 84 | background-clip: text; 85 | -webkit-text-fill-color: transparent; 86 | } 87 | -------------------------------------------------------------------------------- /src/lib/components/Board.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 16 |
17 | {#if storePage.data !== null && storePage.data.descripcion === ''} 18 |

22 | 23 | La página no tiene una meta description! 24 |

25 | {/if} 26 | 27 | {#if storePage.data === null || storePage.resumen === ''} 28 | 29 | {:else} 30 |

31 | 32 | {storePage.resumen} 33 |

34 | {/if} 35 |
36 | 37 | {#if storePage.estado === pageState.OK || storePage.estado === pageState.ANALIZADO} 38 |
39 | 44 | 49 | 54 | 59 |
60 | {/if} 61 |
62 | -------------------------------------------------------------------------------- /src/lib/components/Placeholder.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | {#if h === '1'} 12 |
13 |
14 |
15 | {:else if h === '4'} 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {:else if h === '6'} 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {/if} 68 |
69 | -------------------------------------------------------------------------------- /src/lib/utils/scrapper.ts: -------------------------------------------------------------------------------- 1 | import type { PageData, Encabezado, HeaderTree } from '$lib/types'; 2 | import { convertToMarkdown, convertToHtml } from '$src/lib/utils/markdown-service'; 3 | 4 | /* 5 | * Convertir el html a un elemento del DOM 6 | */ 7 | function parseHTML(rawHtml: string): HTMLElement { 8 | const element = document.createElement('html'); 9 | element.innerHTML = rawHtml; 10 | 11 | return element; 12 | } 13 | 14 | /* 15 | * Obtener los encabezados de la pagina 16 | */ 17 | function getEncabezados(dom: HTMLElement): Encabezado[] { 18 | const headers = dom.querySelectorAll('h1, h2, h3, h4, h5, h6'); 19 | 20 | return Array.from(headers).map((header) => { 21 | return { 22 | tag: header.tagName, 23 | content: header.textContent || 'Error: sin texto' 24 | }; 25 | }); 26 | } 27 | 28 | /* 29 | * Inyectar un elemento en el arbol de encabezados 30 | */ 31 | function injectChild(tree: HeaderTree[], item: HeaderTree): void { 32 | const lastItem = tree.at(-1); 33 | 34 | if ( 35 | !lastItem || 36 | parseInt(lastItem.encabezado.tag.slice(1)) >= parseInt(item.encabezado.tag.slice(1)) 37 | ) { 38 | tree.push(item); 39 | } else { 40 | return injectChild(lastItem.hijos, item); 41 | } 42 | } 43 | 44 | /* 45 | * Genera un arbol jerarquico de encabezados segun su tag 46 | */ 47 | 48 | function getTree(encabezados: Encabezado[]): HeaderTree[] { 49 | const tree: HeaderTree[] = [{ encabezado: encabezados[0], hijos: [] }]; 50 | 51 | for (let i = 1; i < encabezados.length; i++) { 52 | const encabezado = encabezados[i]; 53 | injectChild(tree, { encabezado: encabezado, hijos: [] }); 54 | } 55 | 56 | return tree; 57 | } 58 | 59 | /* 60 | * Limpiar el DOM de elementos no deseados para que sea convertido a markdown 61 | */ 62 | function clearDOM(htmlElement: HTMLElement) { 63 | const deleteItem = (element: HTMLElement, selector: string) => 64 | element.querySelectorAll(selector).forEach((s) => s.remove()); 65 | 66 | deleteItem(htmlElement, 'script'); 67 | deleteItem(htmlElement, 'style'); 68 | deleteItem(htmlElement, 'img'); 69 | deleteItem(htmlElement, 'video'); 70 | deleteItem(htmlElement, 'link'); 71 | deleteItem(htmlElement, 'noscript'); 72 | deleteItem(htmlElement, 'iframe'); 73 | deleteItem(htmlElement, 'svg'); 74 | deleteItem(htmlElement, 'a'); 75 | deleteItem(htmlElement, 'nav'); 76 | 77 | return htmlElement; 78 | } 79 | 80 | export async function getPage(link: string) { 81 | const isFull = link.startsWith('http://') || link.startsWith('https://'); 82 | 83 | try { 84 | // Obtener el html de la pagina como texto 85 | const rawHtml = await fetch('/api/urlService', { 86 | method: 'POST', 87 | headers: { 88 | 'Content-Type': 'application/json' 89 | }, 90 | body: JSON.stringify({ link: isFull ? link : `https://${link}` }) 91 | }).then((res) => res.text()); 92 | 93 | const dom = clearDOM(parseHTML(rawHtml)); 94 | 95 | const titulo = dom.querySelector('title')?.textContent || ''; 96 | const descripcion = 97 | dom.querySelector('meta[name="description"]')?.getAttribute('content') || ''; 98 | const encabezados = getEncabezados(dom); 99 | const tree = getTree(encabezados); 100 | const markdown = convertToMarkdown(dom.querySelector('body') as HTMLElement); 101 | const renderedMarkdown = convertToHtml(markdown); 102 | 103 | const data: PageData = { 104 | link: isFull ? link : link, 105 | data: rawHtml, 106 | 107 | titulo, 108 | descripcion, 109 | encabezados, 110 | tree, 111 | markdown, 112 | renderedMarkdown 113 | }; 114 | 115 | return data; 116 | } catch (error) { 117 | console.log(error); 118 | return null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/lib/components/board/Markdown.svelte: -------------------------------------------------------------------------------- 1 | 76 | 77 |
78 | {@html html} 79 |
80 | 81 | 136 | -------------------------------------------------------------------------------- /src/routes/app/+page.svelte: -------------------------------------------------------------------------------- 1 | 92 | 93 |
94 |

95 | {#if storePage.estado === pageState.WAITING} 96 | 97 | Cargando pagina... 98 | {:else if storePage.estado === pageState.OK} 99 | 100 | Analizando contenido... 101 | {:else if storePage.estado === pageState.ANALIZADO} 102 | 103 | Analisis completado. 104 | {:else if storePage.estado === pageState.NOTOK} 105 | 106 | No se ha podido obtener la información de la página 107 | {/if} 108 |

109 | 110 | 111 |
112 | 113 |
114 |
115 |

116 | 117 | Contenido 118 |

119 | 120 | 121 |
122 | 123 |
124 | 127 | 128 |
129 | {#if storePage.data !== null} 130 | 131 | {/if} 132 |
133 |
134 |
135 | 136 | 141 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 23 | 24 | 29 | 30 | 35 | 36 | 41 |
42 | 43 |
44 |

La Magia de CopyWhisper

45 |
46 |

47 | Descubre las potentes funcionalidades que hacen de CopyWhisper la herramienta definitiva para 48 | analizar y mejorar el copywriting de tu pagina. 49 |

50 | 51 |
52 | 56 | 57 | 61 | 62 | 66 |
67 |
68 | 69 |
70 | 71 |
72 |

Una Herramienta para Todos.

73 |
74 |

75 | CopyWhisper es perfecta para desarrolladores, redactores y profesionales del marketing, 76 | facilitando el análisis y mejora de tu copywriting. 77 |

78 | 79 |
80 |
81 | 86 |
87 |
88 | 94 |
95 |
96 | 101 |
102 |
103 |
104 | 105 |
106 |
107 |
108 |

109 | Experimenta la magia de CopyWhisper 112 |

113 |

114 | Ingresa el URL de tu sitio web y experimenta la magia de CopyWhisper. No esperes para hacer 115 | el copywriting de tu pagina web impactante y de alta calidad. 116 |

117 |
118 | 119 |
120 |
121 | 122 |
123 |
124 |
125 |
126 | 127 | 176 | -------------------------------------------------------------------------------- /src/lib/utils/prompts.ts: -------------------------------------------------------------------------------- 1 | import type { RateRequest, ImproveRequest, Prompt, summarizeRequest } from '$src/lib/types'; 2 | import { getCodeAsMarkdown } from '$utils/util'; 3 | 4 | const ROL = 5 | 'Asume el rol de un copywriter profesional con muchos conocimientos en el área de la redacción de contenido web.'; 6 | 7 | const CRITERIOS = ` 8 | - Analisis de la estructura del contenido y redaccion 9 | - Descripcion del criterio: Evaluar la redaccion, estructura, organización y calidad del contenido para asegurar claridad, coherencia y efectividad. 10 | - Criterios: 11 | - Asegurar que el contenido tenga un propósito claro y que la información proporcionada sea relevante, que refleje la idea que se quiere transmitir, capte el interes, incentive a la interaccion, y que comunique efectivamente la propuesta de valor. 12 | - Claridad y Precisión: Escritura clara, sin redundancias ni ambigüedades. Lenguaje claro y directo y asegurarse de que las ideas se expresen con precisión y exactitud. 13 | - Organización y jerarquía del contenido, uso adecuado de encabezados para dividir el contenido en secciones claras y coherencia en la jerarquía de los encabezados. 14 | - Facilidad de lectura, legibilidad y comprensión del contenido. 15 | - Potencial del contenido para mantener la atención del lector e incentivar la interacción. 16 | 17 | - **Tono y estilo** 18 | - Descripcion del criterio: Evaluar el tono, uniformidad y cohesión del estilo de escritura a lo largo del contenido, asegurando que se mantenga un tono y estilo constante y adecuado para la audiencia específica dependiendo de el contexto de la pagina web. 19 | - Criterios: 20 | - Contexto del Contenido: Ajuste del tono y estilo según el tipo de página 21 | - Tono Adecuado: Adaptación del tono al público objetivo. 22 | - Uniformidad y consistencia del Tono: Mantener el mismo tono en todo el contenido. 23 | - Consistencia del estilo de escritura 24 | - Consistencia léxica. 25 | 26 | - **Persuasión y Conversión** 27 | - Descripcion del criterio: Evaluar la efectividad de los elementos persuasivos y llamados a la acción presentes en el contenido. 28 | - Criterios: 29 | - Uso efectivo de palabras clave relevantes 30 | - Persuacion y la capacidad del contenido para convencer al lector. 31 | - Call to action si existe 32 | - Evaluar efectividad de los encabezados y CTAs en términos de claridad, captar la atención y motivar a la acción. 33 | 34 | 35 | - **Errores de escritura** 36 | - Descripcion del criterio: Identificar y enumerar errores gramaticales, ortográficos, semánticos y sintácticos presentes en el contenido, proporcionando una evaluación profunda y detallada a nivel lingüístico. 37 | - Criterios: 38 | - Gramatica. 39 | - Ortografia. 40 | - Puntuacion. 41 | `; 42 | 43 | /* 44 | * Plantilas de prompts para generar respuestas de la IA 45 | */ 46 | export const prompts = { 47 | RESUMIR(requestData: summarizeRequest): Prompt { 48 | // , no es necesario que desgloces cada item en tu respuesta, solo tenlos en cuenta para generar tu analisis. 49 | return { 50 | sys: ` 51 | ${ROL} 52 | Tu tarea es redactar un parrafo que condense tus comentarios y análisis del contenido de la página proporcionada. Este analisis debe proporcionar una visión general clara y concisa de las fortalezas y tambien de las áreas de mejora. Ten en cuenta que se busca un análisis profundo y preciso que refleje los estándares de un copywriter profesional experimentado pero siendo breve. A continuacion te dare algunos items que puedes usar internamente para tu analisis: 53 | 54 | ${CRITERIOS} 55 | `, 56 | prompt: ` 57 | **La información de la página es la siguiente, responde solamente con un parrafo legible y bien redactado con lenguaje natural que pueda ser entendido por una persona** 58 | 59 | - **Título de la página:** ${requestData.titulo} 60 | - **Meta descripción de la página:** ${requestData.descripcion || 'No hay descripción'} 61 | - **Contenido:** 62 | 63 | ${getCodeAsMarkdown('md', requestData.markdown)} 64 | ` 65 | }; 66 | }, 67 | 68 | CALIFICAR_CONTENIDO(requestData: RateRequest): Prompt { 69 | const indicacionCuantitativa = 'Calificacion cuantitativa del 1 al 10, siendo 10 excelente'; 70 | const indicacionCualitativa = 71 | "Calificacion cualitativa que puede ser 'bajo', 'medio', 'alto', 'excelente'."; 72 | return { 73 | sys: ` 74 | ${ROL} 75 | 76 | Tu tarea es revisar y calificar el contenido de la página proporcionada. A continuación, se te proporcionan los items que se van a calificar y los criterios de calificación. 77 | 78 | Items a calificar y criterios de calificacion: 79 | 80 | ${CRITERIOS} 81 | 82 | Para cada uno de los ítems debes generar una calificacion cualitativa y cuantitativa (calificacion cuantitativa del 1 al 10, siendo 10 excelente) teniendo en cuenta los criterios y tambien el contexto de la pagina. Además, incluye un parrafo breve pero detallado para cada ítem con tus comentarios y otro con tus recomendaciones. Ten en cuenta que se busca un análisis profundo y preciso que refleje los estándares de un copywriter profesional experimentado. Devuelve la respuesta en formato JSON con la siguiente estructura: 83 | 84 | ${getCodeAsMarkdown( 85 | 'json', 86 | `{ 87 | "contenido": { 88 | "cuantitativo": "${indicacionCuantitativa}", 89 | "cualitativo": "${indicacionCualitativa}", 90 | "comentarios": "...", 91 | "sugerencias": "..." 92 | }, 93 | "tono": { 94 | "cuantitativo": "${indicacionCuantitativa}", 95 | "cualitativo": "${indicacionCualitativa}", 96 | "comentarios": "...", 97 | "sugerencias": "..." 98 | }, 99 | "persuacion": { 100 | "cuantitativo": "${indicacionCuantitativa}", 101 | "cualitativo": "${indicacionCualitativa}", 102 | "comentarios": "...", 103 | "sugerencias": "..." 104 | }, 105 | "errores": { 106 | "cuantitativo": "${indicacionCuantitativa}", 107 | "cualitativo": "${indicacionCualitativa}", 108 | "comentarios": "...", 109 | "sugerencias": "..." 110 | } 111 | }` 112 | )} 113 | `, 114 | prompt: ` 115 | A continuación, se te proporciona el contenido de la página web: 116 | 117 | ${getCodeAsMarkdown('md', requestData.markdown)} 118 | ` 119 | }; 120 | }, 121 | 122 | CALIFICAR_ENCABEZADOS(requestData: RateRequest): Prompt { 123 | const indicacionCuantitativa = 'Calificacion cuantitativa del 1 al 10, siendo 10 excelente'; 124 | const indicacionCualitativa = 125 | "Calificacion cualitativa que puede ser 'bajo', 'medio', 'alto', 'excelente'."; 126 | return { 127 | sys: ` 128 | ${ROL} 129 | 130 | Tu tarea es calificar cada encabezado de una pagina web. 131 | 132 | A continuación, se te proporcionan los los criterios de calificación. 133 | 134 | ${CRITERIOS} 135 | 136 | Para cada uno de los ítems debes generar una calificacion cualitativa y cuantitativa teniendo en cuenta los criterios. Además, incluye un parrafo breve pero detallado para cada ítem con tus comentarios. Ten en cuenta que se busca un análisis profundo y preciso que refleje los estándares de un copywriter profesional experimentado. Devuelve la respuesta en formato JSON con la siguiente estructura: 137 | 138 | ${getCodeAsMarkdown( 139 | 'json', 140 | `{ 141 | "errores": { 142 | "cuantitativo": "${indicacionCuantitativa}", 143 | "cualitativo": "${indicacionCualitativa}", 144 | "comentarios": "...", 145 | "errores": [ 146 | "Errores identificados y su explicacion/solucion.", 147 | ] 148 | }, 149 | "tono": { 150 | "cuantitativo": "${indicacionCuantitativa}", 151 | "cualitativo": "${indicacionCualitativa}", 152 | "comentarios": "..." 153 | }, 154 | "persuacion": { 155 | "cuantitativo": "${indicacionCuantitativa}", 156 | "cualitativo": "${indicacionCualitativa}", 157 | "comentarios": "..." 158 | }, 159 | "contenido": { 160 | "cuantitativo": "${indicacionCuantitativa}", 161 | "cualitativo": "${indicacionCualitativa}", 162 | "comentarios": "..." 163 | 164 | } 165 | }` 166 | )} 167 | `, 168 | prompt: ` 169 | A continuación, se te proporciona el contenido de la página web: 170 | 171 | ${getCodeAsMarkdown('md', requestData.markdown)} 172 | ` 173 | }; 174 | }, 175 | 176 | MEJORAR_TITULO(requestData: ImproveRequest): Prompt { 177 | // Proporciona solo el texto plano del encabezado mejorado, hazlo creativo e interesante, no incluyas decoraciones ni markdown. 178 | return { 179 | sys: ` 180 | ${ROL} 181 | 182 | Tu tarea es mejorar copywriting de el contenido de la página proporcionada. En este caso especifico estas especializado en mejorar encabezados. 183 | 184 | Se te proporcionara el contenido de toda la pagina para darte contexto de la pagina. Necesito que mejores el encabezado que te proporcionare, ten en cuenta la etiqueta del encabezado para que sepas cual es su importancia y el contexto de la pagina, ademas recuerda mantener la idea del encabezado, haz que se sienta organico, creativo e interesante, pero evita que se sienta artificial y demasiado forzado. 185 | 186 | Solo debes mejorar el encabezado, no incluyas decoraciones ni markdown. Responde solamente con un texto plano que tenga el texto del encabezado mejorado. 187 | 188 | Para dar tu respuesta al encabezado mejorado, usa tus amplios conocimientos y ten en cuenta los siguientes criterios y el contexto de la pagina web: 189 | 190 | ${CRITERIOS} 191 | `, 192 | prompt: ` 193 | A continuacion el encabezado a mejorar y el contenido de la pagina para que lo analices y tengas contexto. Responde unicamente con el texto del encabezado mejorado. No uses decoraciones como negrita: 194 | 195 | - **Título de la página:** ${requestData.titulo} 196 | - **Encabezado a mejorar:** 197 | 198 | ${getCodeAsMarkdown('json', JSON.stringify(requestData.elemento))} 199 | 200 | - **Contenido de la página:** 201 | 202 | ${getCodeAsMarkdown('md', requestData.markdown)} 203 | ` 204 | }; 205 | }, 206 | MEJORAR_CONTENIDO(requestData: ImproveRequest): Prompt { 207 | return { 208 | sys: ` 209 | ${ROL} 210 | 211 | Tu tarea es mejorar copywriting de el contenido de la página proporcionada. En este caso especifico estas especializado en mejorar el contenido. 212 | 213 | Se te proporcionara el contenido de toda la pagina para darte contexto de la pagina. Necesito que mejores el texto en especifico que te proporcionare. Solo debes mejorar el texto, no incluyas decoraciones ni markdown. Responde solamente con un texto plano que tenga el texto mejorado, recuerda mantener la idea del texto, haz que se sienta organico, creativo e interesante, pero evita que se sienta artificial y demasiado forzado. 214 | 215 | Para dar tu respuesta al momento de mejorar la redaccion del contenido, usa tus amplios conocimientos y ten en cuenta el contexto de la pagina web. Ademas ten en cuenta los siguientes criterios importantes 216 | 217 | ${CRITERIOS} 218 | A continuacion se te proporcionara la informacion necesaria para que puedas realizar tu tarea, responde: 219 | `, 220 | prompt: ` 221 | A continuacion el contenido de la pagina para que tengas contexto y el elemento a mejorar. Responde unicamente con texto plano. 222 | 223 | - **Título de la página:** ${requestData.titulo} 224 | - **Elemento a mejorar:** 225 | 226 | ${getCodeAsMarkdown('json', JSON.stringify(requestData.elemento))} 227 | 228 | - **Contenido de la página:** 229 | 230 | ${getCodeAsMarkdown('md', requestData.markdown)} 231 | ` 232 | }; 233 | } 234 | }; 235 | --------------------------------------------------------------------------------