Skip to content

Exemple de SDK AI pe bază de șablon

Fiecare șablon de proiect DojoCode poate folosi SDK-ul DojoCode pentru a apela modele AI reale. Această pagină conține un exemplu complet, gata de copiat și lipit, pentru fiecare șablon: ce fișier să creezi sau să înlocuiești și codul pe care să îl adaugi. Exemplele folosesc modalități diferite, astfel încât, împreună, le prezintă pe toate cele nouă.

Fiecare exemplu respectă aceleași reguli:

  • Citește conexiunea cu numele exacte pentru șablonul său — nimic de configurat și nicio cheie API de lipit.
  • Pornește o generare doar când dai clic (previzualizarea se reîncarcă la fiecare salvare, așa că o generare la încărcarea paginii te-ar taxa la nesfârșit).
  • Gestionează ambele răspunsuri — 200 (finalizat) și 202 (încă rulează, interogat la fiecare 2 secunde) — și afișează mesajul de eroare atunci când ceva eșuează.

Sau întreabă direct asistentul

Deschide panoul AI Chat din proiectul tău și descrie funcționalitatea — asistentul va scrie acest cod pentru șablonul tău. Exemplele de mai jos sunt exact ceea ce produce acesta, gata de citit, copiat și adaptat.

ȘablonUnde se pune codulExempluModalitate
React/App.jsxGenerator de imaginitext-to-image
React TS/App.tsxEvaluator de fotografiiimage-to-text
Vue/App.vueCompozitor de muzicătext-to-music
Vue TS/App.vueRestilizator de fotografiiimage-to-image
Svelte/App.svelteÎntreabă oricetext-to-text
SvelteKit/src/routes/+page.svelteCreator de stickeretext-to-image
SvelteKit TS/src/routes/+page.svelteGenerator de clipuri videotext-to-video
SolidJS/App.jsxGenerator de două imaginitext-to-image
SolidJS TS/App.tsxGenerator de chestionare (JSON)text-to-text
Vanilla JS/index.jsNotițe vocale în textspeech-to-text
Vanilla TS/index.tsNarator cu voce clonatătext-to-speech
Remix/app/routes/_index.jsxGenerator de posteretext-to-image
Remix TS/app/routes/_index.tsxAnimator de fotografiiimage-to-video
React Native/App.jsxGenerator de imagini de fundaltext-to-image
React Native TS/App.tsxTutor de programaretext-to-text
Next.js/src/app/page.jsxGenerator de clipuri videotext-to-video
Next.js TS/src/app/page.tsxCompozitor de muzicătext-to-music
Astro/src/pages/index.astroScriitor de poveștitext-to-text
Astro TS/src/pages/index.astroGenerator de imaginitext-to-image
Angular/app.component.tsGenerator de imaginitext-to-image
Fastify/src/app.tsRuta GET /facttext-to-text
Hono/src/app.tsRuta GET /postertext-to-image
NestJS/src/app.service.tsRuta POST /summarizetext-to-text
Node.js/main.jsCuriozitatetext-to-text
Node.js TS/main.tsCuriozitatetext-to-text
Python — browser preview/main.py + /index.htmlGenerator de cărți poștaletext-to-image
Python — Run/main.pyImagine cu interogare (polling)text-to-image
Java/Main.javaCuriozitatetext-to-text
Go/main.goCuriozitatetext-to-text
C#/Main.csCuriozitatetext-to-text
PHP/main.phpCuriozitatetext-to-text
Ruby/main.rbCuriozitatetext-to-text
Rust/main.rsCuriozitatetext-to-text

C și C++

Proiectele în C și C++ nu pot apela încă SDK-ul: mediul lor de rulare (Run) nu are un client HTTPS încorporat. Folosește unul dintre șabloanele de mai sus pentru funcționalitățile AI.

Șabloane pentru browser (Vite)

React, Vue, Svelte, SvelteKit, SolidJS, Vanilla, Remix și React Native rulează toate pe Vite și citesc conexiunea din import.meta.env.VITE_DOJOCODE_API_URL și import.meta.env.VITE_DOJOCODE_AI_TOKEN.

Helper-ul pentru șabloanele Vite

Salvează helper-ul o singură dată lângă componentele tale — /dojocode-ai.js în șabloanele JavaScript, /dojocode-ai.ts în cele TypeScript (în SvelteKit pune-l în /src/lib/, iar în Remix în /app/). Exemplele de mai jos îl importă.

js
const API_URL = import.meta.env.VITE_DOJOCODE_API_URL;
const API_TOKEN = import.meta.env.VITE_DOJOCODE_AI_TOKEN;
const FINISHED = ['succeeded', 'failed', 'cancelled'];

async function request(path, options = {}) {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data;
}

/** Submit a generation and resolve with its output (handles 200 and 202). */
export async function generate(modality, input) {
  let record = await request('/ai-generation/generations', {
    method: 'POST',
    body: JSON.stringify({ modality, input })
  });
  while (!FINISHED.includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output;
}

/** Downscale a picked image to at most 1024 px and return a JPEG data URI. */
export function imageToDataUri(file, maxSide = 1024) {
  return new Promise((resolve, reject) => {
    const img = document.createElement('img');
    img.onload = () => {
      const scale = Math.min(1, maxSide / Math.max(img.width, img.height));
      const canvas = document.createElement('canvas');
      canvas.width = Math.round(img.width * scale);
      canvas.height = Math.round(img.height * scale);
      canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
      URL.revokeObjectURL(img.src);
      resolve(canvas.toDataURL('image/jpeg', 0.8));
    };
    img.onerror = reject;
    img.src = URL.createObjectURL(file);
  });
}

/** Turn a Blob or File (audio, image) into a data URI. */
export function blobToDataUri(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}
ts
const API_URL = import.meta.env.VITE_DOJOCODE_API_URL as string;
const API_TOKEN = import.meta.env.VITE_DOJOCODE_AI_TOKEN as string;

export type Modality =
  | 'text-to-text'
  | 'image-to-text'
  | 'text-to-image'
  | 'image-to-image'
  | 'text-to-video'
  | 'image-to-video'
  | 'text-to-music'
  | 'text-to-speech'
  | 'speech-to-text';

interface GenerationRecord {
  id: string;
  status: 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
  output: unknown;
  error: { message: string } | null;
}

async function request(path: string, options: RequestInit = {}): Promise<GenerationRecord> {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data as GenerationRecord;
}

/** Submit a generation and resolve with its output (handles 200 and 202). */
export async function generate<T = unknown>(modality: Modality, input: Record<string, unknown>): Promise<T> {
  let record = await request('/ai-generation/generations', {
    method: 'POST',
    body: JSON.stringify({ modality, input })
  });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output as T;
}

/** Downscale a picked image to at most 1024 px and return a JPEG data URI. */
export function imageToDataUri(file: File, maxSide = 1024): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = document.createElement('img');
    img.onload = () => {
      const scale = Math.min(1, maxSide / Math.max(img.width, img.height));
      const canvas = document.createElement('canvas');
      canvas.width = Math.round(img.width * scale);
      canvas.height = Math.round(img.height * scale);
      canvas.getContext('2d')?.drawImage(img, 0, 0, canvas.width, canvas.height);
      URL.revokeObjectURL(img.src);
      resolve(canvas.toDataURL('image/jpeg', 0.8));
    };
    img.onerror = reject;
    img.src = URL.createObjectURL(file);
  });
}

/** Turn a Blob or File (audio, image) into a data URI. */
export function blobToDataUri(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result as string);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

React

Înlocuiește /App.jsx — un generator de imagini (text-to-image, 3 tokeni AI per imagine):

jsx
import { useState } from 'react';
import { generate } from './dojocode-ai';

export default function App() {
  const [prompt, setPrompt] = useState('');
  const [images, setImages] = useState([]);
  const [status, setStatus] = useState('');
  const [busy, setBusy] = useState(false);

  async function handleGenerate() {
    setBusy(true);
    setStatus('Generating…');
    try {
      setImages(await generate('text-to-image', { prompt, aspect_ratio: '16:9' }));
      setStatus('');
    } catch (error) {
      setStatus(error.message);
    } finally {
      setBusy(false);
    }
  }

  return (
    <main style={{ maxWidth: 640, margin: '2rem auto', fontFamily: 'sans-serif' }}>
      <h1>Image generator</h1>
      <input value={prompt} onChange={(event) => setPrompt(event.target.value)} placeholder="A lighthouse in a storm, oil painting" style={{ width: '100%' }} />
      <button onClick={handleGenerate} disabled={busy || !prompt.trim()}>
        Generate (3 AI tokens)
      </button>
      <p>{status}</p>
      {images.map((url) => (
        <img key={url} src={url} alt={prompt} style={{ width: '100%', borderRadius: 8 }} />
      ))}
    </main>
  );
}

React TS

Înlocuiește /App.tsx — un evaluator de fotografii (image-to-text, 2 tokeni AI):

tsx
import { useState, type ChangeEvent } from 'react';
import { generate, imageToDataUri } from './dojocode-ai';

export default function App() {
  const [image, setImage] = useState<string | null>(null);
  const [answer, setAnswer] = useState('');
  const [busy, setBusy] = useState(false);

  async function handleFile(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (!file) return;
    setImage(await imageToDataUri(file));
    setAnswer('');
  }

  async function handleGrade() {
    if (!image) return;
    setBusy(true);
    setAnswer('Looking at your photo…');
    try {
      setAnswer(
        await generate<string>('image-to-text', {
          prompt: 'Grade this photo from 1 to 10, then give three short tips to improve it.',
          image
        })
      );
    } catch (error) {
      setAnswer((error as Error).message);
    } finally {
      setBusy(false);
    }
  }

  return (
    <main style={{ maxWidth: 640, margin: '2rem auto', fontFamily: 'sans-serif' }}>
      <h1>Photo grader</h1>
      <input type="file" accept="image/*" onChange={handleFile} />
      {image && <img src={image} alt="Your photo" style={{ width: '100%', marginTop: 12 }} />}
      <button onClick={handleGrade} disabled={!image || busy}>
        Grade my photo (2 AI tokens)
      </button>
      <p style={{ whiteSpace: 'pre-wrap' }}>{answer}</p>
    </main>
  );
}

Vue

Înlocuiește /App.vue — un compozitor de muzică (text-to-music, 4 tokeni AI per 10 secunde):

vue
<script setup>
import { ref } from 'vue';
import { generate } from './dojocode-ai';

const prompt = ref('Upbeat lo-fi hip hop with warm piano chords and a relaxed drum groove');
const duration = ref(8);
const audioUrl = ref('');
const status = ref('');
const busy = ref(false);

async function compose() {
  busy.value = true;
  status.value = 'Composing — this takes about a minute…';
  try {
    audioUrl.value = await generate('text-to-music', { prompt: prompt.value, duration: duration.value, output_format: 'mp3' });
    status.value = '';
  } catch (error) {
    status.value = error.message;
  } finally {
    busy.value = false;
  }
}
</script>

<template>
  <main>
    <h1>Music composer</h1>
    <textarea v-model="prompt" rows="3"></textarea>
    <select v-model.number="duration">
      <option :value="8">8 seconds (4 AI tokens)</option>
      <option :value="15">15 seconds (8 AI tokens)</option>
      <option :value="30">30 seconds (12 AI tokens)</option>
    </select>
    <button :disabled="busy || !prompt.trim()" @click="compose">Compose</button>
    <p>{{ status }}</p>
    <audio v-if="audioUrl" :src="audioUrl" controls></audio>
  </main>
</template>

Vue TS

Înlocuiește /App.vue — un stilizator de fotografii (image-to-image, 5 tokeni AI per imagine):

vue
<script setup lang="ts">
import { ref } from 'vue';
import { generate, imageToDataUri } from './dojocode-ai';

const source = ref<string | null>(null);
const prompt = ref('Turn this photo into a watercolor painting');
const results = ref<string[]>([]);
const status = ref('');

async function pick(event: Event) {
  const file = (event.target as HTMLInputElement).files?.[0];
  if (file) source.value = await imageToDataUri(file);
}

async function restyle() {
  if (!source.value) return;
  status.value = 'Painting…';
  try {
    results.value = await generate<string[]>('image-to-image', {
      prompt: prompt.value,
      image: source.value,
      prompt_strength: 0.6
    });
    status.value = '';
  } catch (error) {
    status.value = (error as Error).message;
  }
}
</script>

<template>
  <main>
    <h1>Photo restyler</h1>
    <input type="file" accept="image/*" @change="pick" />
    <input v-model="prompt" />
    <button :disabled="!source" @click="restyle">Restyle (5 AI tokens)</button>
    <p>{{ status }}</p>
    <img v-for="url in results" :key="url" :src="url" width="480" />
  </main>
</template>

Svelte

Înlocuiește /App.svelte — întreabă orice (text-to-text, 1 token AI):

svelte
<script>
  import { generate } from './dojocode-ai';

  let prompt = '';
  let answer = '';
  let busy = false;

  async function ask() {
    busy = true;
    answer = 'Thinking…';
    try {
      answer = await generate('text-to-text', { prompt, max_tokens: 400 });
    } catch (error) {
      answer = error.message;
    } finally {
      busy = false;
    }
  }
</script>

<main>
  <h1>Ask anything</h1>
  <textarea bind:value={prompt} rows="4" placeholder="Explain recursion like I'm ten"></textarea>
  <button on:click={ask} disabled={busy || !prompt.trim()}>Ask (1 AI token)</button>
  <p style="white-space: pre-wrap">{answer}</p>
</main>

SvelteKit

Salvează fișierul ajutător ca /src/lib/dojocode-ai.js, apoi înlocuiește /src/routes/+page.svelte — un generator de stickere (text-to-image, 3 tokeni AI per imagine):

svelte
<script>
  import { generate } from '$lib/dojocode-ai';

  let subject = '';
  let stickers = [];
  let status = '';

  async function makeStickers() {
    status = 'Drawing 2 stickers…';
    try {
      stickers = await generate('text-to-image', {
        prompt: `A cute die-cut sticker of ${subject}, thick white border, flat colors`,
        num_outputs: 2,
        aspect_ratio: '1:1'
      });
      status = '';
    } catch (error) {
      status = error.message;
    }
  }
</script>

<h1>Sticker maker</h1>
<input bind:value={subject} placeholder="a sleepy cat astronaut" />
<button on:click={makeStickers} disabled={!subject.trim()}>Make 2 stickers (6 AI tokens)</button>
<p>{status}</p>
{#each stickers as url}
  <img src={url} alt={subject} width="240" />
{/each}

SvelteKit TS

Salvează fișierul ajutător ca /src/lib/dojocode-ai.ts, apoi înlocuiește /src/routes/+page.svelte — un generator video (text-to-video, 12 tokeni AI pentru ~4 secunde):

svelte
<script lang="ts">
  import { generate } from '$lib/dojocode-ai';

  let prompt = '';
  let videoUrl = '';
  let status = '';

  async function render() {
    status = 'Rendering — this takes a minute or two…';
    try {
      const [url] = await generate<string[]>('text-to-video', { prompt, aspect_ratio: '16:9' });
      videoUrl = url;
      status = '';
    } catch (error) {
      status = (error as Error).message;
    }
  }
</script>

<h1>Video generator</h1>
<textarea bind:value={prompt} rows="4" placeholder="Describe the scene, the motion, the camera and the light"></textarea>
<button on:click={render} disabled={!prompt.trim()}>Generate video (12 AI tokens)</button>
<p>{status}</p>
{#if videoUrl}
  <video src={videoUrl} controls width="640"></video>
{/if}

SolidJS

Înlocuiește /App.jsx — două imagini simultan (text-to-image, 3 tokeni AI per imagine):

jsx
import { createSignal, For } from 'solid-js';
import { generate } from './dojocode-ai';

export default function App() {
  const [prompt, setPrompt] = createSignal('');
  const [images, setImages] = createSignal([]);
  const [status, setStatus] = createSignal('');

  async function handleGenerate() {
    setStatus('Generating 2 images…');
    try {
      setImages(await generate('text-to-image', { prompt: prompt(), num_outputs: 2 }));
      setStatus('');
    } catch (error) {
      setStatus(error.message);
    }
  }

  return (
    <main>
      <h1>Two-image generator</h1>
      <input value={prompt()} onInput={(event) => setPrompt(event.currentTarget.value)} placeholder="A cozy cabin in the snow" />
      <button onClick={handleGenerate} disabled={!prompt().trim()}>
        Generate 2 images (6 AI tokens)
      </button>
      <p>{status()}</p>
      <For each={images()}>{(url) => <img src={url} width={320} />}</For>
    </main>
  );
}

SolidJS TS

Înlocuiește /App.tsx — un generator de quiz-uri care solicită JSON (text-to-text, 1 token AI):

tsx
import { createSignal, For, Show } from 'solid-js';
import { generate } from './dojocode-ai';

interface Question {
  question: string;
  options: string[];
  answer: number;
}

/** The model may wrap JSON in a code fence or add text around it. */
function parseQuiz(text: string): Question[] {
  const start = text.indexOf('[');
  const end = text.lastIndexOf(']');
  try {
    const data = JSON.parse(text.slice(start, end + 1));
    return Array.isArray(data) ? data : [];
  } catch {
    return [];
  }
}

export default function App() {
  const [topic, setTopic] = createSignal('');
  const [quiz, setQuiz] = createSignal<Question[]>([]);
  const [status, setStatus] = createSignal('');

  async function makeQuiz() {
    setStatus('Writing the quiz…');
    try {
      const text = await generate<string>('text-to-text', {
        prompt: `Write 3 multiple-choice questions about ${topic()}. Reply ONLY with a JSON array of objects with "question", "options" (4 strings) and "answer" (the index of the correct option).`,
        max_tokens: 800,
        temperature: 0.4
      });
      const questions = parseQuiz(text);
      setQuiz(questions);
      setStatus(questions.length ? '' : 'The answer was not valid JSON — try again.');
    } catch (error) {
      setStatus((error as Error).message);
    }
  }

  return (
    <main>
      <h1>Quiz generator</h1>
      <input value={topic()} onInput={(event) => setTopic(event.currentTarget.value)} placeholder="the solar system" />
      <button onClick={makeQuiz} disabled={!topic().trim()}>Make a quiz (1 AI token)</button>
      <p>{status()}</p>
      <For each={quiz()}>
        {(item) => (
          <section>
            <h3>{item.question}</h3>
            <ol type="A">
              <For each={item.options}>{(option, index) => <li>{option}{index() === item.answer ? ' ✓' : ''}</li>}</For>
            </ol>
          </section>
        )}
      </For>
      <Show when={!quiz().length && !status()}>
        <p>Pick a topic to start.</p>
      </Show>
    </main>
  );
}

Vanilla JS

Înlocuiește /index.js — note vocale în text (speech-to-text, 2 tokeni AI per înregistrare). Pagina este construită din JavaScript, deci nu este necesar un fișier HTML:

js
import { generate, blobToDataUri } from './dojocode-ai.js';

document.body.innerHTML = `
  <main style="max-width: 640px; margin: 2rem auto; font-family: sans-serif">
    <h1>Voice notes to text</h1>
    <input type="file" id="recording" accept="audio/*">
    <button id="transcribe">Transcribe (2 AI tokens)</button>
    <pre id="result" style="white-space: pre-wrap"></pre>
  </main>
`;

const button = document.querySelector('#transcribe');
const result = document.querySelector('#result');

button.addEventListener('click', async () => {
  const file = document.querySelector('#recording').files[0];
  if (!file) {
    result.textContent = 'Pick an audio file first.';
    return;
  }
  button.disabled = true;
  result.textContent = 'Transcribing…';
  try {
    const { transcription, detected_language } = await generate('speech-to-text', {
      audio: await blobToDataUri(file),
      language: 'auto'
    });
    result.textContent = `[${detected_language}] ${transcription}`;
  } catch (error) {
    result.textContent = error.message;
  } finally {
    button.disabled = false;
  }
});

Vanilla TS

Înlocuiește /index.ts — un narator care vorbește cu o voce clonată (text-to-speech, 3 tokenuri AI per 1.000 de caractere):

ts
import { generate, blobToDataUri } from './dojocode-ai';

document.body.innerHTML = `
  <main style="max-width: 640px; margin: 2rem auto; font-family: sans-serif">
    <h1>Narrator</h1>
    <p><label>Voice sample (10–30 s): <input type="file" id="sample" accept="audio/*"></label></p>
    <p><label>What the sample says: <input id="sampleText" style="width: 100%"></label></p>
    <textarea id="text" rows="4" style="width: 100%" placeholder="Text to read aloud"></textarea>
    <button id="speak">Speak (3 AI tokens per 1,000 characters)</button>
    <p id="status"></p>
    <audio id="player" controls hidden></audio>
  </main>
`;

const status = document.querySelector<HTMLParagraphElement>('#status')!;
const player = document.querySelector<HTMLAudioElement>('#player')!;

document.querySelector('#speak')!.addEventListener('click', async () => {
  const sample = document.querySelector<HTMLInputElement>('#sample')!.files?.[0];
  const sampleText = document.querySelector<HTMLInputElement>('#sampleText')!.value;
  const text = document.querySelector<HTMLTextAreaElement>('#text')!.value;
  if (!sample || !sampleText.trim() || !text.trim()) {
    status.textContent = 'Add a voice sample, its exact words and the text to read.';
    return;
  }
  status.textContent = 'Generating the voice — this can take a minute…';
  try {
    player.src = await generate<string>('text-to-speech', {
      text,
      speaker_reference: await blobToDataUri(sample),
      text_reference: sampleText
    });
    player.hidden = false;
    status.textContent = '';
  } catch (error) {
    status.textContent = (error as Error).message;
  }
});

Remix

Salvează helper-ul ca /app/dojocode-ai.js, apoi înlocuiește /app/routes/_index.jsx — un generator de postere (text-to-image, 3 tokenuri AI per imagine):

jsx
import { useState } from 'react';
import { generate } from '../dojocode-ai';

export default function Index() {
  const [title, setTitle] = useState('');
  const [poster, setPoster] = useState('');
  const [status, setStatus] = useState('');

  async function handleGenerate() {
    setStatus('Designing your poster…');
    try {
      const [url] = await generate('text-to-image', {
        prompt: `A bold retro travel poster for "${title}", screen-print style, strong typography`,
        aspect_ratio: '2:3'
      });
      setPoster(url);
      setStatus('');
    } catch (error) {
      setStatus(error.message);
    }
  }

  return (
    <main>
      <h1>Poster generator</h1>
      <input value={title} onChange={(event) => setTitle(event.target.value)} placeholder="Visit Mars" />
      <button onClick={handleGenerate} disabled={!title.trim()}>
        Generate (3 AI tokens)
      </button>
      <p>{status}</p>
      {poster && <img src={poster} alt={title} width={360} />}
    </main>
  );
}

Remix TS

Salvează helper-ul ca /app/dojocode-ai.ts, apoi înlocuiește /app/routes/_index.tsx — un animator de fotografii (image-to-video, 12 tokenuri AI per ~4 secunde):

tsx
import { useState, type ChangeEvent } from 'react';
import { generate, imageToDataUri } from '../dojocode-ai';

export default function Index() {
  const [image, setImage] = useState<string | null>(null);
  const [prompt, setPrompt] = useState('The scene slowly comes alive, gentle camera push-in, soft wind');
  const [videoUrl, setVideoUrl] = useState('');
  const [status, setStatus] = useState('');

  async function handleFile(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (file) setImage(await imageToDataUri(file));
  }

  async function animate() {
    if (!image) return;
    setStatus('Animating — this takes a minute or two…');
    try {
      const [url] = await generate<string[]>('image-to-video', { prompt, image });
      setVideoUrl(url);
      setStatus('');
    } catch (error) {
      setStatus((error as Error).message);
    }
  }

  return (
    <main>
      <h1>Photo animator</h1>
      <input type="file" accept="image/*" onChange={handleFile} />
      <textarea value={prompt} onChange={(event) => setPrompt(event.target.value)} rows={3} />
      <button onClick={animate} disabled={!image}>
        Animate (12 AI tokens)
      </button>
      <p>{status}</p>
      {videoUrl && <video src={videoUrl} controls width={480} />}
    </main>
  );
}

React Native

Înlocuiește /App.jsx — un generator de imagini de fundal pentru telefon (text-to-image, 3 tokenuri AI):

jsx
import { useState } from 'react';
import { View, Text, TextInput, Pressable, Image, StyleSheet } from 'react-native';
import { generate } from './dojocode-ai';

export default function App() {
  const [prompt, setPrompt] = useState('');
  const [imageUrl, setImageUrl] = useState(null);
  const [status, setStatus] = useState('');

  async function handleGenerate() {
    setStatus('Generating…');
    try {
      const [url] = await generate('text-to-image', { prompt, aspect_ratio: '9:16' });
      setImageUrl(url);
      setStatus('');
    } catch (error) {
      setStatus(error.message);
    }
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Wallpaper generator</Text>
      <TextInput value={prompt} onChangeText={setPrompt} placeholder="Neon city at night" style={styles.input} />
      <Pressable onPress={handleGenerate} style={styles.button}>
        <Text style={styles.buttonText}>Generate (3 AI tokens)</Text>
      </Pressable>
      <Text>{status}</Text>
      {imageUrl && <Image source={{ uri: imageUrl }} style={styles.wallpaper} />}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', padding: 24, gap: 12 },
  title: { fontSize: 24, fontWeight: '600' },
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10, width: 280 },
  button: { backgroundColor: '#6c5ce7', borderRadius: 8, paddingVertical: 12, paddingHorizontal: 20 },
  buttonText: { color: 'white', fontWeight: '600' },
  wallpaper: { width: 216, height: 384, borderRadius: 12 }
});

React Native și imaginile

Image din 'react-native' eclipsează elementul Image al browserului, așa că helper-ul creează elemente de tip imagine folosind document.createElement('img'). Nu folosi niciodată new Image() în codul React Native.

React Native TS

Înlocuiește /App.tsx — un tutor de programare (text-to-text, 1 token AI):

tsx
import { useState } from 'react';
import { ScrollView, Text, TextInput, Pressable, StyleSheet } from 'react-native';
import { generate } from './dojocode-ai';

export default function App() {
  const [question, setQuestion] = useState('');
  const [answer, setAnswer] = useState('');
  const [busy, setBusy] = useState(false);

  async function ask() {
    setBusy(true);
    setAnswer('Thinking…');
    try {
      setAnswer(
        await generate<string>('text-to-text', {
          prompt: question,
          system_prompt: 'You are a patient coding tutor. Answer in at most five sentences with one small example.',
          max_tokens: 400
        })
      );
    } catch (error) {
      setAnswer((error as Error).message);
    } finally {
      setBusy(false);
    }
  }

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <Text style={styles.title}>Coding tutor</Text>
      <TextInput value={question} onChangeText={setQuestion} placeholder="What is a closure?" multiline style={styles.input} />
      <Pressable onPress={ask} disabled={busy || !question.trim()} style={styles.button}>
        <Text style={styles.buttonText}>Ask (1 AI token)</Text>
      </Pressable>
      <Text style={styles.answer}>{answer}</Text>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: { padding: 24, gap: 12 },
  title: { fontSize: 24, fontWeight: '600' },
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10, minHeight: 80 },
  button: { backgroundColor: '#6c5ce7', borderRadius: 8, padding: 12, alignItems: 'center' },
  buttonText: { color: 'white', fontWeight: '600' },
  answer: { fontSize: 16, lineHeight: 22 }
});

Șabloane Next.js

Doar variabilele NEXT_PUBLIC_ ajung în codul browserului, iar import.meta.env nu există în Next.js. Apelează SDK-ul dintr-un Client Component ('use client').

Next.js

Creează /src/lib/dojocode-ai.js și înlocuiește /src/app/page.jsx — un generator video (text-to-video, 12 token-uri AI per ~4 secunde):

js
const API_URL = process.env.NEXT_PUBLIC_DOJOCODE_API_URL;
const API_TOKEN = process.env.NEXT_PUBLIC_DOJOCODE_AI_TOKEN;

async function request(path, options = {}) {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data;
}

export async function generate(modality, input) {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output;
}
jsx
'use client';

import { useState } from 'react';
import { generate } from '../lib/dojocode-ai';

export default function Page() {
  const [prompt, setPrompt] = useState('');
  const [videoUrl, setVideoUrl] = useState('');
  const [status, setStatus] = useState('');

  async function handleGenerate() {
    setStatus('Rendering the video — this takes a minute or two…');
    try {
      const [url] = await generate('text-to-video', { prompt, aspect_ratio: '16:9', length: 97 });
      setVideoUrl(url);
      setStatus('');
    } catch (error) {
      setStatus(error.message);
    }
  }

  return (
    <main>
      <h1>Video generator</h1>
      <textarea value={prompt} onChange={(event) => setPrompt(event.target.value)} rows={4} placeholder="A slow drone shot over a misty forest at sunrise" />
      <button onClick={handleGenerate} disabled={!prompt.trim()}>
        Generate video (12 AI tokens)
      </button>
      <p>{status}</p>
      {videoUrl && <video src={videoUrl} controls width={640} />}
    </main>
  );
}

Next.js TS

Creează /src/lib/dojocode-ai.ts și înlocuiește /src/app/page.tsx — un compozitor de muzică (text-to-music, 4 token-uri AI per 10 secunde):

ts
const API_URL = process.env.NEXT_PUBLIC_DOJOCODE_API_URL;
const API_TOKEN = process.env.NEXT_PUBLIC_DOJOCODE_AI_TOKEN;

interface GenerationRecord {
  id: string;
  status: string;
  output: unknown;
  error: { message: string } | null;
}

async function request(path: string, options: RequestInit = {}): Promise<GenerationRecord> {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data as GenerationRecord;
}

export async function generate<T = unknown>(modality: string, input: Record<string, unknown>): Promise<T> {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output as T;
}
tsx
'use client';

import { useState } from 'react';
import { generate } from '../lib/dojocode-ai';

export default function Page() {
  const [prompt, setPrompt] = useState('Epic orchestral trailer music with big drums and strings');
  const [duration, setDuration] = useState(15);
  const [audioUrl, setAudioUrl] = useState('');
  const [status, setStatus] = useState('');

  async function compose() {
    setStatus('Composing…');
    try {
      setAudioUrl(await generate<string>('text-to-music', { prompt, duration, output_format: 'mp3' }));
      setStatus('');
    } catch (error) {
      setStatus((error as Error).message);
    }
  }

  return (
    <main>
      <h1>Music composer</h1>
      <textarea value={prompt} onChange={(event) => setPrompt(event.target.value)} rows={3} />
      <select value={duration} onChange={(event) => setDuration(Number(event.target.value))}>
        <option value={8}>8 seconds (4 AI tokens)</option>
        <option value={15}>15 seconds (8 AI tokens)</option>
        <option value={30}>30 seconds (12 AI tokens)</option>
      </select>
      <button onClick={compose}>Compose</button>
      <p>{status}</p>
      {audioUrl && <audio src={audioUrl} controls />}
    </main>
  );
}

Șabloane Astro

Astro expune variabilele PUBLIC_ către codul <script> din browser (numele VITE_ sunt undefined acolo). Scriptul <script> al paginii rulează în browser și poate importa funcția ajutătoare.

Astro

Creează /src/lib/dojocode-ai.js și înlocuiește /src/pages/index.astro — un scriitor de povești (text-to-text, 1 token AI):

js
const API_URL = import.meta.env.PUBLIC_DOJOCODE_API_URL;
const API_TOKEN = import.meta.env.PUBLIC_DOJOCODE_AI_TOKEN;

async function request(path, options = {}) {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data;
}

export async function generate(modality, input) {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output;
}
astro
---
const title = 'Story writer';
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{title}</title>
  </head>
  <body>
    <h1>{title}</h1>
    <input id="topic" placeholder="a dragon who is afraid of the dark" />
    <button id="write">Write a story (1 AI token)</button>
    <p id="story" style="white-space: pre-wrap"></p>

    <script>
      import { generate } from '../lib/dojocode-ai.js';

      const topic = document.querySelector('#topic');
      const story = document.querySelector('#story');

      document.querySelector('#write').addEventListener('click', async () => {
        story.textContent = 'Writing…';
        try {
          story.textContent = await generate('text-to-text', {
            prompt: `Write a short bedtime story for kids about ${topic.value}.`,
            max_tokens: 600
          });
        } catch (error) {
          story.textContent = error.message;
        }
      });
    </script>
  </body>
</html>

Astro TS

Creează /src/lib/dojocode-ai.ts (helper-ul TS Next.js de mai sus, cu import.meta.env.PUBLIC_DOJOCODE_API_URL și import.meta.env.PUBLIC_DOJOCODE_AI_TOKEN în primele sale două linii) și înlocuiește /src/pages/index.astro — un generator de imagini (text-to-image, 3 tokeni AI):

ts
const API_URL = import.meta.env.PUBLIC_DOJOCODE_API_URL as string;
const API_TOKEN = import.meta.env.PUBLIC_DOJOCODE_AI_TOKEN as string;

interface GenerationRecord {
  id: string;
  status: string;
  output: unknown;
  error: { message: string } | null;
}

async function request(path: string, options: RequestInit = {}): Promise<GenerationRecord> {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data as GenerationRecord;
}

export async function generate<T = unknown>(modality: string, input: Record<string, unknown>): Promise<T> {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output as T;
}
astro
---
const title = 'Image generator';
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{title}</title>
  </head>
  <body>
    <h1>{title}</h1>
    <input id="prompt" placeholder="A koi pond in autumn, ukiyo-e print" />
    <button id="generate">Generate (3 AI tokens)</button>
    <p id="status"></p>
    <img id="result" width="480" hidden />

    <script>
      import { generate } from '../lib/dojocode-ai';

      const prompt = document.querySelector('#prompt') as HTMLInputElement;
      const status = document.querySelector('#status') as HTMLParagraphElement;
      const result = document.querySelector('#result') as HTMLImageElement;

      document.querySelector('#generate')?.addEventListener('click', async () => {
        status.textContent = 'Generating…';
        try {
          const [url] = await generate<string[]>('text-to-image', { prompt: prompt.value });
          result.src = url;
          result.hidden = false;
          status.textContent = '';
        } catch (error) {
          status.textContent = (error as Error).message;
        }
      });
    </script>
  </body>
</html>

Șablon Angular

Platforma adaugă dojocode-env.ts lângă app.component.ts — este ascuns în arborele de fișiere, așa că doar importă-l. Variabilele globale process.env, import.meta.env și window nu sunt disponibile în Angular. FormsModule este deja importat în app.module.ts, așa că [(ngModel)] funcționează direct.

Angular

Creează /dojocode-ai.ts și înlocuiește /app.component.ts și /app.component.html — un generator de imagini (text-to-image, 3 tokeni AI):

ts
import { DOJOCODE_ENV } from './dojocode-env';

const API_URL = DOJOCODE_ENV['DOJOCODE_API_URL'];
const API_TOKEN = DOJOCODE_ENV['DOJOCODE_AI_TOKEN'];

interface GenerationRecord {
  id: string;
  status: string;
  output: unknown;
  error: { message: string } | null;
}

async function request(path: string, options: RequestInit = {}): Promise<GenerationRecord> {
  const response = await fetch(`${API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data as GenerationRecord;
}

export async function generate<T = unknown>(modality: string, input: Record<string, unknown>): Promise<T> {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!['succeeded', 'failed', 'cancelled'].includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output as T;
}
ts
import { Component } from '@angular/core';
import { generate } from './dojocode-ai';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: []
})
export class AppComponent {
  prompt = '';
  images: string[] = [];
  status = '';
  busy = false;

  async generateImages(): Promise<void> {
    this.busy = true;
    this.status = 'Generating…';
    try {
      this.images = await generate<string[]>('text-to-image', { prompt: this.prompt });
      this.status = '';
    } catch (error) {
      this.status = (error as Error).message;
    } finally {
      this.busy = false;
    }
  }
}
html
<main>
  <h1>Image generator</h1>
  <input [(ngModel)]="prompt" placeholder="A paper boat on a blue pond" />
  <button (click)="generateImages()" [disabled]="busy || !prompt.trim()">Generate (3 AI tokens)</button>
  <p>{{ status }}</p>
  <img *ngFor="let url of images" [src]="url" width="480" />
</main>

Servere Node.js în previzualizare

NestJS, Fastify și Hono rulează în previzualizare cu conexiunea în process.env.DOJOCODE_API_URL și process.env.DOJOCODE_AI_TOKEN. Acestea apelează SDK-ul folosind funcția nativă fetch — nu este necesară nicio bibliotecă HTTP. Salvează acest helper ca /src/dojocode-ai.ts și testează rutele din previzualizare sau din Testerul API:

ts
// src/dojocode-ai.ts
const FINISHED = ['succeeded', 'failed', 'cancelled'];

async function request(path: string, options: RequestInit = {}): Promise<any> {
  const response = await fetch(`${process.env.DOJOCODE_API_URL}${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.DOJOCODE_AI_TOKEN}` }
  });
  const data = await response.json().catch(() => null);
  if (!response.ok) throw new Error(data?.errors?.[0]?.message ?? `Request failed (${response.status})`);
  return data;
}

/** Submit a generation and resolve with its output (handles 200 and 202). */
export async function generate(modality: string, input: Record<string, unknown>): Promise<unknown> {
  let record = await request('/ai-generation/generations', { method: 'POST', body: JSON.stringify({ modality, input }) });
  while (!FINISHED.includes(record.status)) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    record = await request(`/ai-generation/generations/${record.id}`);
  }
  if (record.status !== 'succeeded') throw new Error(record.error?.message ?? 'The generation failed.');
  return record.output;
}

Fastify

Înlocuiește /src/app.ts (main.ts continuă să apeleze buildApp()) — GET /fact?topic=octopus (text-to-text, 1 token AI per apel):

ts
import Fastify, { type FastifyInstance } from 'fastify';
import { generate } from './dojocode-ai';

export function buildApp(): FastifyInstance {
  const app = Fastify({ logger: false });

  app.get('/', async () => 'Hello, World! Try GET /fact?topic=octopus');

  app.get<{ Querystring: { topic?: string } }>('/fact', async (request, reply) => {
    try {
      const fact = await generate('text-to-text', {
        prompt: `Write one surprising fact about ${request.query.topic ?? 'space'}.`,
        max_tokens: 120
      });
      return { fact };
    } catch (error) {
      return reply.status(502).send({ error: (error as Error).message });
    }
  });

  return app;
}

Hono

Înlocuiește /src/app.ts (main.ts continuă să deservească app exportat) — GET /poster?prompt=retro+robot (text-to-image, 3 tokenuri AI per apel):

ts
import { Hono } from 'hono';
import { generate } from './dojocode-ai';

export const app = new Hono();

app.get('/', (c) => c.text('Hello, World! Try GET /poster?prompt=retro+robot'));

app.get('/poster', async (c) => {
  try {
    const images = await generate('text-to-image', { prompt: c.req.query('prompt') ?? 'a retro robot poster' });
    return c.json({ images });
  } catch (error) {
    return c.json({ error: (error as Error).message }, 502);
  }
});

NestJS

Înlocuiește /src/app.service.ts și /src/app.controller.tsPOST /summarize cu { "text": "…" } (text-to-text, 1 token AI per apel):

ts
import { BadGatewayException, Injectable } from '@nestjs/common';
import { generate } from './dojocode-ai';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello, World! Try POST /summarize';
  }

  async summarize(text: string): Promise<{ summary: unknown }> {
    try {
      const summary = await generate('text-to-text', {
        prompt: `Summarize in three bullet points:\n\n${text}`,
        max_tokens: 250
      });
      return { summary };
    } catch (error) {
      throw new BadGatewayException((error as Error).message);
    }
  }
}
ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Post('summarize')
  summarize(@Body('text') text: string) {
    return this.appService.summarize(text);
  }
}

Șabloane Node.js

Șabloanele Node.js citesc aceleași variabile process.env indiferent dacă proiectul rulează în previzualizare sau cu Run (unde o rulare se oprește după 20 de secunde — vezi Python — Run pentru modalități lente).

Node.js

Înlocuiește /main.js (text-to-text, 1 token AI):

js
async function main() {
  const response = await fetch(`${process.env.DOJOCODE_API_URL}/ai-generation/generations`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.DOJOCODE_AI_TOKEN}` },
    body: JSON.stringify({
      modality: 'text-to-text',
      input: { prompt: 'Write a one-sentence fun fact about Node.js.', max_tokens: 80 }
    }),
    signal: AbortSignal.timeout(15000)
  });
  const record = await response.json();
  if (!response.ok) {
    console.log('Generation failed: ' + record.errors[0].message);
    return;
  }
  console.log(record.status, String(record.output).trim());
}

main().catch((error) => console.log('Generation failed: ' + error.message));

Node.js TS

Înlocuiește /main.ts (text-to-text, 1 token AI):

ts
interface GenerationRecord {
  status?: string;
  output?: unknown;
  errors?: Array<{ message: string }>;
}

async function main(): Promise<void> {
  const response = await fetch(`${process.env.DOJOCODE_API_URL}/ai-generation/generations`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.DOJOCODE_AI_TOKEN}` },
    body: JSON.stringify({
      modality: 'text-to-text',
      input: { prompt: 'Write a one-sentence fun fact about TypeScript.', max_tokens: 80 }
    }),
    signal: AbortSignal.timeout(15000)
  });
  const record = (await response.json()) as GenerationRecord;
  if (!response.ok) {
    console.log('Generation failed: ' + record.errors?.[0]?.message);
    return;
  }
  console.log(record.status, String(record.output).trim());
}

main().catch((error: Error) => console.log('Generation failed: ' + error.message));

Python

Python — previzualizare în browser

Proiectele Python cu previzualizare în browser rulează în browser, iar platforma instalează un modul predefinit dojocode_ai — nu este necesar cod HTTP. Creează /index.html (pagina pe care o afișează previzualizarea) și înlocuiește /main.py (care conectează pagina) — un generator de cărți poștale (text-to-image, 3 tokenuri AI):

python
from js import document
from pyodide.ffi import create_proxy
from dojocode_ai import generate_async, DojoCodeAiError


async def on_generate(event):
    result = document.getElementById("result")
    result.textContent = "Painting your postcard..."
    try:
        # Waits until the generation finishes; the page stays responsive meanwhile.
        record = await generate_async(
            "text-to-image",
            {"prompt": f"A vintage postcard of {document.getElementById('place').value}", "aspect_ratio": "3:2"},
            timeout=150,
        )
        result.innerHTML = f'<img src="{record["output"][0]}" style="max-width: 100%; border-radius: 8px">'
    except DojoCodeAiError as error:
        result.textContent = f"Generation failed: {error}"


document.getElementById("generate").addEventListener("click", create_proxy(on_generate))
html
<main style="max-width: 640px; margin: 2rem auto; font-family: sans-serif">
  <h1>Postcard generator</h1>
  <input id="place" placeholder="Lisbon at sunset" />
  <button id="generate">Generate (3 AI tokens)</button>
  <div id="result"></div>
</main>

Folosește await generate_async(…) / await get_async(id) în gestionarii de evenimente. Funcțiile sincrone generate() / get() există pentru scripturi simple, dar acestea blochează pagina până când sosește răspunsul.

Python — Run

Fără previzualizarea în browser, Run execută /main.py într-un container cu DOJOCODE_API_URL și DOJOCODE_AI_TOKEN în mediu. O rulare se oprește după 20 de secunde, așa că acest exemplu trimite solicitarea fără a aștepta, verifică periodic timp de până la ~10 secunde și afișează ID-ul dacă rezultatul nu este gata încă:

python
import json, os, time, urllib.request, urllib.error

API_URL = os.environ["DOJOCODE_API_URL"]
HEADERS = {"Content-Type": "application/json", "Authorization": f"Bearer {os.environ['DOJOCODE_AI_TOKEN']}"}


def api(method, path, body=None):
    request = urllib.request.Request(f"{API_URL}{path}", method=method, headers=HEADERS,
                                     data=json.dumps(body).encode() if body is not None else None)
    try:
        with urllib.request.urlopen(request, timeout=15) as response:
            return json.load(response)
    except urllib.error.HTTPError as error:
        raise RuntimeError(json.load(error)["errors"][0]["message"]) from None


record = api("POST", "/ai-generation/generations?wait=false",
             {"modality": "text-to-image", "input": {"prompt": "a watercolor fox in the snow"}})
for _ in range(5):
    if record["status"] in ("succeeded", "failed", "cancelled"):
        break
    time.sleep(2)
    record = api("GET", f"/ai-generation/generations/{record['id']}")

if record["status"] == "succeeded":
    print("Image:", record["output"][0])
else:
    print(f"Still {record['status']} — run again to read generation {record['id']}")
    # Next run: api("GET", "/ai-generation/generations?limit=1") returns your latest generation.

Șabloane de rulare

Aceste șabloane îți rulează programul într-un container când apeși pe Run, cu DOJOCODE_API_URL, DOJOCODE_AI_TOKEN și DOJOCODE_PROJECT_ID în mediu. Exemplele folosesc text-to-text, care se termină cu mult înainte de limita de 20 de secunde; pentru modalități mai lente, urmează modelul Python — Run (trimite cu ?wait=false, afișează ID-ul, citește-l din nou la următoarea rulare).

Java

Înlocuiește /Main.java (text-to-text, 1 token AI):

java
package challenge;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Main {
    public static void main(String[] args) throws Exception {
        String apiUrl = System.getenv("DOJOCODE_API_URL");
        String token = System.getenv("DOJOCODE_AI_TOKEN");

        // Use HTTP/1.1: Java's default HTTP/2 upgrade over plain http is not supported.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        String body = "{\"modality\":\"text-to-text\",\"input\":{\"prompt\":\"Write a one-sentence fun fact about Java.\",\"max_tokens\":80}}";

        HttpRequest request = HttpRequest.newBuilder(URI.create(apiUrl + "/ai-generation/generations"))
            .timeout(Duration.ofSeconds(15))
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer " + token)
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("HTTP " + response.statusCode());
        System.out.println(response.body()); // the generation record as JSON
    }
}

Go

Înlocuiește /main.go (text-to-text, 1 token AI):

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

func main() {
	payload, _ := json.Marshal(map[string]interface{}{
		"modality": "text-to-text",
		"input":    map[string]interface{}{"prompt": "Write a one-sentence fun fact about Go.", "max_tokens": 80},
	})
	request, _ := http.NewRequest("POST", os.Getenv("DOJOCODE_API_URL")+"/ai-generation/generations", bytes.NewReader(payload))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Authorization", "Bearer "+os.Getenv("DOJOCODE_AI_TOKEN"))

	client := &http.Client{Timeout: 15 * time.Second}
	response, err := client.Do(request)
	if err != nil {
		fmt.Println("Generation failed:", err)
		return
	}
	defer response.Body.Close()

	var record map[string]interface{}
	json.NewDecoder(response.Body).Decode(&record)
	if failures, ok := record["errors"].([]interface{}); ok && len(failures) > 0 {
		fmt.Println("Generation failed:", failures[0].(map[string]interface{})["message"])
		return
	}
	fmt.Println(record["status"], record["output"])
}

C#

Înlocuiește /Main.cs (text-to-text, 1 token AI):

csharp
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace Challenge
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var apiUrl = Environment.GetEnvironmentVariable("DOJOCODE_API_URL");
            var token = Environment.GetEnvironmentVariable("DOJOCODE_AI_TOKEN");

            var payload = JsonSerializer.Serialize(new
            {
                modality = "text-to-text",
                input = new { prompt = "Write a one-sentence fun fact about C#.", max_tokens = 80 }
            });

            using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var content = new StringContent(payload, Encoding.UTF8, "application/json");
            var response = client.PostAsync(apiUrl + "/ai-generation/generations", content).Result;

            using var document = JsonDocument.Parse(response.Content.ReadAsStringAsync().Result);
            var record = document.RootElement;
            if (record.TryGetProperty("errors", out var errors))
            {
                Console.WriteLine("Generation failed: " + errors[0].GetProperty("message").GetString());
                return;
            }
            Console.WriteLine(record.GetProperty("status").GetString() + ": " + record.GetProperty("output").GetString()?.Trim());
        }
    }
}

PHP

Înlocuiește /main.php (text-to-text, 1 token AI):

php
<?php

$apiUrl = getenv('DOJOCODE_API_URL');
$token = getenv('DOJOCODE_AI_TOKEN');

$context = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Content-Type: application/json\r\nAuthorization: Bearer $token\r\n",
    'content' => json_encode([
        'modality' => 'text-to-text',
        'input' => ['prompt' => 'Write a one-sentence fun fact about PHP.', 'max_tokens' => 80],
    ]),
    'timeout' => 15,
    'ignore_errors' => true,
]]);

$record = json_decode(file_get_contents("$apiUrl/ai-generation/generations", false, $context) ?: 'null', true);
if (!is_array($record) || isset($record['errors'])) {
    echo 'Generation failed: ' . ($record['errors'][0]['message'] ?? 'no response') . "\n";
    exit;
}
echo "{$record['status']}: " . trim((string) $record['output']) . "\n";

Ruby

Înlocuiește /main.rb (text-to-text, 1 token AI):

ruby
require 'json'
require 'net/http'
require 'uri'

uri = URI("#{ENV['DOJOCODE_API_URL']}/ai-generation/generations")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = 15

request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json',
                                   'Authorization' => "Bearer #{ENV['DOJOCODE_AI_TOKEN']}")
request.body = { modality: 'text-to-text', input: { prompt: 'Write a one-sentence fun fact about Ruby.', max_tokens: 80 } }.to_json

record = JSON.parse(http.request(request).body)
if record['errors']
  puts "Generation failed: #{record['errors'][0]['message']}"
else
  puts "#{record['status']}: #{record['output'].to_s.strip}"
end

Rust

Înlocuiește /main.rs (text-to-text, 1 token AI). Biblioteca standard nu are un client HTTP, așa că acest exemplu apelează comanda curl, pe care mediul de rulare Rust Run o include; pentru o aplicație mai mare adaugă un crate precum ureq sau reqwest (plus serde_json) din panoul Dependencies:

rust
use std::env;
use std::process::Command;

fn main() {
    let api_url = env::var("DOJOCODE_API_URL").unwrap_or_default();
    let token = env::var("DOJOCODE_AI_TOKEN").unwrap_or_default();

    let payload = r#"{"modality":"text-to-text","input":{"prompt":"Write a one-sentence fun fact about Rust.","max_tokens":80}}"#;
    let output = Command::new("curl")
        .args([
            "-s", "--max-time", "15", "-X", "POST",
            "-H", "Content-Type: application/json",
            "-H", &format!("Authorization: Bearer {}", token),
            "-d", payload,
            &format!("{}/ai-generation/generations", api_url),
        ])
        .output()
        .expect("curl is not available");

    // The generation record as JSON — parse it with serde_json in a real app.
    println!("{}", String::from_utf8_lossy(&output.stdout));
}

Ce urmează?