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étodo | O 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
Páginas relacionadas
Estúdio de personagens
Personagens consistentes
Personagem
Personagens
Locais
Última atualização
Cenas 3D
Em TypeScript, gere uma cena 3D editável com um prompt, edite-a, renderize-a em MP4 e execute a Renderização 3D Pro com client.scene3d e os nós de cena 3D.
Locais
Crie locais, gere planos de estabelecimento, aprove um deles e adicione variações de hora do dia, clima, estação, ângulo e movimento em TypeScript.