Skip to content

AI SDK examples by template ​

Every DojoCode project template can use the DojoCode SDK to call real AI models. This page has a complete, copy-paste example for each template: which file to create or replace, and the code to put in it. The examples use different modalities, so together they show all nine.

Each example follows the same rules:

  • It reads the connection with the exact names for its template — nothing to configure and no API key to paste.
  • It starts a generation only when you click (the preview reloads on every save, so a generation on page load would charge you again and again).
  • It handles both answers — 200 (finished) and 202 (still running, polled every 2 seconds) — and shows the error message when something fails.

Or just ask the assistant

Open the AI Chat panel in your project and describe the feature — the assistant writes this code for your template. The examples below are what it produces, ready to read, copy and adapt.

TemplateWhere the code goesExampleModality
React/App.jsxImage generatortext-to-image
React TS/App.tsxPhoto graderimage-to-text
Vue/App.vueMusic composertext-to-music
Vue TS/App.vuePhoto restylerimage-to-image
Svelte/App.svelteAsk anythingtext-to-text
SvelteKit/src/routes/+page.svelteSticker makertext-to-image
SvelteKit TS/src/routes/+page.svelteVideo generatortext-to-video
SolidJS/App.jsxTwo-image generatortext-to-image
SolidJS TS/App.tsxQuiz generator (JSON)text-to-text
Vanilla JS/index.jsVoice notes to textspeech-to-text
Vanilla TS/index.tsNarrator with a cloned voicetext-to-speech
Remix/app/routes/_index.jsxPoster generatortext-to-image
Remix TS/app/routes/_index.tsxPhoto animatorimage-to-video
React Native/App.jsxWallpaper generatortext-to-image
React Native TS/App.tsxCoding tutortext-to-text
Next.js/src/app/page.jsxVideo generatortext-to-video
Next.js TS/src/app/page.tsxMusic composertext-to-music
Astro/src/pages/index.astroStory writertext-to-text
Astro TS/src/pages/index.astroImage generatortext-to-image
Angular/app.component.tsImage generatortext-to-image
Fastify/src/app.tsGET /fact routetext-to-text
Hono/src/app.tsGET /poster routetext-to-image
NestJS/src/app.service.tsPOST /summarize routetext-to-text
Node.js/main.jsFun facttext-to-text
Node.js TS/main.tsFun facttext-to-text
Python — browser preview/main.py + /index.htmlPostcard generatortext-to-image
Python — Run/main.pyImage with pollingtext-to-image
Java/Main.javaFun facttext-to-text
Go/main.goFun facttext-to-text
C#/Main.csFun facttext-to-text
PHP/main.phpFun facttext-to-text
Ruby/main.rbFun facttext-to-text
Rust/main.rsFun facttext-to-text

C and C++

C and C++ projects can't call the SDK yet: their Run environment has no built-in HTTPS client. Use one of the templates above for AI features.

Browser templates (Vite) ​

React, Vue, Svelte, SvelteKit, SolidJS, Vanilla, Remix and React Native all run on Vite and read the connection from import.meta.env.VITE_DOJOCODE_API_URL and import.meta.env.VITE_DOJOCODE_AI_TOKEN.

The helper for Vite templates ​

Save the helper once next to your components — /dojocode-ai.js in the JavaScript templates, /dojocode-ai.ts in the TypeScript ones (in SvelteKit put it in /src/lib/, in Remix in /app/). The examples below import it.

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 ​

Replace /App.jsx — an image generator (text-to-image, 3 AI tokens per image):

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 ​

Replace /App.tsx — a photo grader (image-to-text, 2 AI tokens):

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 ​

Replace /App.vue — a music composer (text-to-music, 4 AI tokens per 10 seconds):

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 ​

Replace /App.vue — a photo restyler (image-to-image, 5 AI tokens per image):

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 ​

Replace /App.svelte — ask anything (text-to-text, 1 AI token):

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 ​

Save the helper as /src/lib/dojocode-ai.js, then replace /src/routes/+page.svelte — a sticker maker (text-to-image, 3 AI tokens per image):

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 ​

Save the helper as /src/lib/dojocode-ai.ts, then replace /src/routes/+page.svelte — a video generator (text-to-video, 12 AI tokens per ~4 seconds):

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 ​

Replace /App.jsx — two images at once (text-to-image, 3 AI tokens per image):

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 ​

Replace /App.tsx — a quiz generator that asks for JSON (text-to-text, 1 AI token):

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 ​

Replace /index.js — voice notes to text (speech-to-text, 2 AI tokens per recording). The page is built from JavaScript, so no HTML file is needed:

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 ​

Replace /index.ts — a narrator that speaks in a cloned voice (text-to-speech, 3 AI tokens per 1,000 characters):

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 ​

Save the helper as /app/dojocode-ai.js, then replace /app/routes/_index.jsx — a poster generator (text-to-image, 3 AI tokens per image):

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 ​

Save the helper as /app/dojocode-ai.ts, then replace /app/routes/_index.tsx — a photo animator (image-to-video, 12 AI tokens per ~4 seconds):

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 ​

Replace /App.jsx — a phone wallpaper generator (text-to-image, 3 AI tokens):

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 and images

Image from 'react-native' shadows the browser's Image, so the helper creates image elements with document.createElement('img'). Never use new Image() in React Native code.

React Native TS ​

Replace /App.tsx — a coding tutor (text-to-text, 1 AI token):

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 }
});

Next.js templates ​

Only NEXT_PUBLIC_ variables reach browser code, and import.meta.env doesn't exist in Next.js. Call the SDK from a Client Component ('use client').

Next.js ​

Create /src/lib/dojocode-ai.js and replace /src/app/page.jsx — a video generator (text-to-video, 12 AI tokens per ~4 seconds):

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 ​

Create /src/lib/dojocode-ai.ts and replace /src/app/page.tsx — a music composer (text-to-music, 4 AI tokens per 10 seconds):

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>
  );
}

Astro templates ​

Astro exposes PUBLIC_ variables to browser <script> code (the VITE_ names are undefined there). The page's <script> runs in the browser and can import the helper.

Astro ​

Create /src/lib/dojocode-ai.js and replace /src/pages/index.astro — a story writer (text-to-text, 1 AI token):

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 ​

Create /src/lib/dojocode-ai.ts (the Next.js TS helper above, with import.meta.env.PUBLIC_DOJOCODE_API_URL and import.meta.env.PUBLIC_DOJOCODE_AI_TOKEN in its first two lines) and replace /src/pages/index.astro — an image generator (text-to-image, 3 AI tokens):

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>

Angular template ​

The platform adds dojocode-env.ts next to app.component.ts — it is hidden from the file tree, so just import it. process.env, import.meta.env and window globals are not available in Angular. FormsModule is already imported in app.module.ts, so [(ngModel)] works out of the box.

Angular ​

Create /dojocode-ai.ts and replace /app.component.ts and /app.component.html — an image generator (text-to-image, 3 AI tokens):

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>

Node.js servers in the preview ​

NestJS, Fastify and Hono run in the preview with the connection in process.env.DOJOCODE_API_URL and process.env.DOJOCODE_AI_TOKEN. They call the SDK with the built-in fetch — no HTTP library needed. Save this helper as /src/dojocode-ai.ts and test the routes from the preview or the API Tester:

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 ​

Replace /src/app.ts (main.ts keeps calling buildApp()) — GET /fact?topic=octopus (text-to-text, 1 AI token per call):

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 ​

Replace /src/app.ts (main.ts keeps serving the exported app) — GET /poster?prompt=retro+robot (text-to-image, 3 AI tokens per call):

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 ​

Replace /src/app.service.ts and /src/app.controller.ts — POST /summarize with { "text": "…" } (text-to-text, 1 AI token per call):

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);
  }
}

Node.js templates ​

The Node.js templates read the same process.env variables whether the project runs in the preview or with Run (where a run stops after 20 seconds — see Python — Run for slow modalities).

Node.js ​

Replace /main.js (text-to-text, 1 AI token):

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 ​

Replace /main.ts (text-to-text, 1 AI token):

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 — browser preview ​

Python projects with the browser preview run in the browser, and the platform installs a ready-made dojocode_ai module — no HTTP code needed. Create /index.html (the page the preview shows) and replace /main.py (it wires up the page) — a postcard generator (text-to-image, 3 AI tokens):

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>

Use await generate_async(…) / await get_async(id) in event handlers. The synchronous generate() / get() exist for plain scripts, but they freeze the page until the answer arrives.

Python — Run ​

Without the browser preview, Run executes /main.py in a container with DOJOCODE_API_URL and DOJOCODE_AI_TOKEN in the environment. A run stops after 20 seconds, so this example submits without waiting, polls for up to ~10 seconds and prints the id if the result isn't ready yet:

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.

Run templates ​

These templates run your program in a container when you press Run, with DOJOCODE_API_URL, DOJOCODE_AI_TOKEN and DOJOCODE_PROJECT_ID in the environment. The examples use text-to-text, which finishes well inside the 20-second limit; for slow modalities follow the Python — Run pattern (submit with ?wait=false, print the id, read it back on the next run).

Java ​

Replace /Main.java (text-to-text, 1 AI token):

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 ​

Replace /main.go (text-to-text, 1 AI token):

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# ​

Replace /Main.cs (text-to-text, 1 AI token):

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 ​

Replace /main.php (text-to-text, 1 AI token):

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 ​

Replace /main.rb (text-to-text, 1 AI token):

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 ​

Replace /main.rs (text-to-text, 1 AI token). The standard library has no HTTP client, so this example calls the curl command, which the Rust Run environment includes; for a larger app add a crate such as ureq or reqwest (plus serde_json) from the Dependencies panel:

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));
}

What's next? ​