Docs do Nodaro
DocumentaçãoReferência de nósModelosAgentes de IA (MCP)DesenvolvedoresSelf-hostingPesquisa
SDK para TypeScript

Personagens

Crie personagens, gere candidatos a retrato, aprove um deles e adicione expressões, poses e clipes de movimento em TypeScript com client.characters.

client.characters automatiza por script tudo o que o Estúdio de personagens faz: cria e edita personagens, gera candidatos a retrato, aprova um deles como o rosto do personagem e adiciona expressões, poses, iluminação, ângulos e clipes de movimento. Um personagem guarda o retrato, as coleções de mídias, as fotos de referência e uma legenda que o descreve, para que todas as imagens e vídeos seguintes mostrem a mesma pessoa. Os métodos chamam a API REST de personagens. Veja Estúdio de personagens para a visão do editor.

Métodos

MétodoO que faz
list(params?)Lista os seus personagens, uma página por vez
get(id)Lê um personagem, com os jobs dele em andamento
create(input), update(id, input) e upsert(input)Cria ou altera um personagem
delete(id)Arquiva um personagem
restore(id)Traz de volta um personagem arquivado
duplicate(id, input?)Copia um personagem
usage(id)Conta os workflows que usam um personagem
generate(input)Gera candidatos a retrato
generateAsset(input)Gera uma variação de expressão, pose, iluminação ou ângulo
generateMotion(input)Anima o retrato em um clipe de movimento
approvePortrait(id, candidateJobId)Torna um candidato o retrato do personagem
recaption(id)Escreve de novo a descrição do personagem

Criar um personagem do início ao fim

A ordem habitual: crie o personagem, gere candidatos a retrato, aprove um deles e depois adicione variações a partir dele.

import { createClient, StaticTokenAuth } from "@nodaro/sdk"

const client = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})

async function waitForJob(jobId: string) {
  for (;;) {
    const { data } = await client.jobs.getStatus(jobId)
    if (data.status === "completed" || data.status === "failed" || data.status === "cancelled") return data
    await new Promise((resolve) => setTimeout(resolve, 3_000))
  }
}

// 1. Create the character
const { id: characterId } = await client.characters.create({
  nodeId: "scripted",
  name: "Kira",
  description: "Young protagonist with auburn hair",
  style: "realistic",
  seedPrompt: "Kira portrait, warm natural lighting",
})

// 2. Generate 4 portrait candidates
const { jobIds } = await client.characters.generate({
  name: "Kira",
  seedPrompt: "Kira portrait, warm natural lighting",
  count: 4,
  attachToCharacterId: characterId,
})
const results = await Promise.all(jobIds.map(waitForJob))
const firstDone = jobIds[results.findIndex((r) => r.status === "completed")]

// 3. Approve one candidate as the portrait
const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, firstDone)

// 4. Add a smile
await client.characters.generateAsset({
  name: "Kira",
  assetType: "expressions",
  variant: "smile",
  attachToCharacterId: characterId,
  attachToColumn: "expressions",
  attachName: "smile",
})

// 5. Animate the portrait
await client.characters.generateMotion({
  name: "Kira",
  motionPrompt: "Slow head turn to the left, soft smile",
  provider: "kling",
  attachToCharacterId: characterId,
  attachName: "head turn",
})

Leia o personagem de novo com get() quando os jobs terminarem. Ele terá então um retrato, uma expressão de sorriso e um clipe de movimento. Conecte-o às gerações com o nó Personagem (Character Asset) ou mencione-o com @ em um prompt. Veja Personagens consistentes.

client.characters

list(params?)

Lista os seus personagens, dos mais recentes para os mais antigos. Por padrão, retorna apenas os personagens ativos.

list(params?: { projectId?: string; archived?: boolean; limit?: number; cursor?: string }): Promise<{
  characters: Character[]
  nextCursor: string | null
}>

Prop

Type

Uma chamada retorna no máximo limit personagens, então uma única chamada não traz a lista completa para todo mundo. Pagine até nextCursor ser null:

import type { Character } from "@nodaro/sdk"

const all: Character[] = []
let cursor: string | undefined
do {
  const page = await client.characters.list({ projectId, cursor })
  all.push(...page.characters)
  cursor = page.nextCursor ?? undefined
} while (cursor)

Passe nextCursor de volta como está, sem armazená-lo nem interpretá-lo. Um cursor malformado falha com validation_error em vez de recomeçar da primeira página.

get(id)

Lê um personagem, com três listas extras que o estúdio usa depois de recarregar: pendingJobs (variações ainda em geração), portraitCandidates (os candidatos da execução de retrato atual, com o progresso deles) e previousCandidates (candidatos anteriores).

get(id: string): Promise<CharacterDetail>

Prop

Type

const character = await client.characters.get(characterId)
console.log(character.sourceImageUrl, character.expressions, character.motions)

Um personagem arquivado continua sendo retornado pelo ID, então os nós de workflow que apontam para ele continuam carregando. Um Character tem o retrato em sourceImageUrl, seis coleções de mídias (expressions, poses, motions, angles, bodyAngles e lightingVariations, cada uma uma lista de { name, url }), boards, voice, personality, canonicalDescription e identityLock.

upsert(input), create(input) e update(id, input)

upsert() cria um personagem quando input.id está ausente e o atualiza quando id está definido. create() e update() são atalhos que definem id por você. Uma atualização grava apenas os campos que você envia; os outros, incluindo name, continuam como estão.

upsert(input: UpsertCharacterInput): Promise<{ id: string; name?: string }>
create(input: Omit<UpsertCharacterInput, "id"> & { name: string }): Promise<{ id: string; name?: string }>
update(id: string, input: Omit<UpsertCharacterInput, "id">): Promise<{ id: string; name?: string }>

Prop

Type

const { id } = await client.characters.create({
  nodeId: "scripted",
  name: "Kira",
  description: "Young protagonist with auburn hair",
  style: "realistic",
  identityLock: "strict",
})

await client.characters.update(id, { baseOutfit: "Green raincoat and boots" })

Um nome que já está em uso falha com 409 name_taken.

delete(id)

Arquiva um personagem. Ele some de list(), mas continua carregando com get(id). Use restore() para trazê-lo de volta.

delete(id: string): Promise<{ success: true; archived: true }>

Prop

Type

await client.characters.delete(characterId)

restore(id)

Traz de volta um personagem arquivado. Quando o nome dele já é usado por outro personagem ativo, o servidor acrescenta (restored) ao nome e retorna o nome que usou.

restore(id: string): Promise<{ id: string; name: string }>

Prop

Type

const { name } = await client.characters.restore(characterId)

duplicate(id, input?)

Copia um personagem para um novo, cujo nome termina em (copy). A cópia compartilha as URLs de mídias do original até você gerar novas.

duplicate(id: string, input?: { nodeId?: string; projectId?: string }): Promise<{ id: string; name: string }>

Prop

Type

const { id: copyId, name } = await client.characters.duplicate(characterId)

usage(id)

Retorna quantos workflows usam um personagem, e quais. O editor mostra essa informação antes de arquivar.

usage(id: string): Promise<{ workflowCount: number; workflows: Array<{ id: string; name: string }> }>

Prop

Type

const { workflowCount } = await client.characters.usage(characterId)

generate(input)

Gera candidatos a retrato (POST /v1/generate-character). Com count acima de 1, todos os jobs são reservados antes que qualquer um comece, então uma falha no meio do caminho desfaz o lote inteiro.

generate(input: GenerateCharacterInput): Promise<{ jobId: string; jobIds: string[] }>

Prop

Type

const { jobIds } = await client.characters.generate({
  name: "Kira",
  seedPrompt: "Kira portrait, warm natural lighting",
  count: 4,
  attachToCharacterId: characterId,
  provider: "gpt-image",
  quality: "high", // priced as gpt-image:high
})

Com attachToCharacterId e um único candidato, o resultado vira o retrato quando o job termina. Com vários candidatos, escolha um com approvePortrait(). quality e resolution são cobrados exatamente como no nó Gerar imagem (Generate Image), então uma execução em 4K ou em alta qualidade reserva mais créditos. Um valor que o modelo não aceita é ignorado, não recusado.

generateAsset(input)

Gera uma variação a partir do retrato do personagem: uma expressão, uma pose, uma configuração de iluminação ou um ângulo. Com attachToCharacterId, attachToColumn e attachName, o resultado é adicionado a essa coleção do personagem quando o job termina.

generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }>

Prop

Type

await client.characters.generateAsset({
  name: "Kira",
  assetType: "expressions",
  variant: "smile",
  attachToCharacterId: characterId,
  attachToColumn: "expressions",
  attachName: "smile",
})

generateMotion(input)

Anima o retrato do personagem em um clipe de movimento, com o Gerar vídeo (Generate Video) no modo imagem para vídeo. Com attachToCharacterId, o clipe é adicionado a motions do personagem. Sem sourceImageUrl, é usado o retrato do personagem.

generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }>

Prop

Type

await client.characters.generateMotion({
  name: "Kira",
  motionPrompt: "Slow head turn to the left, soft smile",
  provider: "kling",
  attachToCharacterId: characterId,
  attachName: "head turn",
})

approvePortrait(id, candidateJobId)

Torna um candidato concluído de generate() o retrato do personagem. Em seguida, um modelo de visão escreve a descrição do personagem, e o método retorna os dois.

approvePortrait(id: string, candidateJobId: string): Promise<{ portraitUrl: string; canonicalDescription: string | null }>

Prop

Type

const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, jobIds[0])

canonicalDescription é null quando não foi possível escrever a descrição. O retrato é definido mesmo assim; chame recaption() para tentar de novo.

recaption(id)

Escreve de novo a descrição do personagem a partir do retrato atual.

recaption(id: string): Promise<{ canonicalDescription: string }>

Prop

Type

const { canonicalDescription } = await client.characters.recaption(characterId)

Falha com 400 no_portrait quando o personagem não tem retrato, e com um 502 quando o modelo de visão falha.

Perguntas frequentes

Última atualização

Nesta página