דמויות
יצירה, חיפוש ואימון של דמויות, יצירת מועמדים לפורטרט, אישור אחד מהם והוספת הבעות, תנוחות וקליפי תנועה עם client.characters.
client.characters מאפשר לעשות בסקריפט כל מה שעושים באולפן הדמות: יצירה ועריכה של דמויות, יצירת מועמדים לפורטרט, אישור אחד מהם כפנים של הדמות והוספת הבעות, תנוחות, תאורה, זוויות וקליפי תנועה. דמות שומרת את הפורטרט שלה, את קבוצות הווריאנטים שלה, את תמונות הייחוס שלה ותיאור שמתאר אותה, כך שכל תמונה וכל סרטון שנוצרים אחר כך יכולים להציג את אותו אדם. המתודות קוראות לנקודות הקצה שבדף ה-API דמויות. המדריך אולפן הדמות מציג את אותה עבודה בעורך.
מתודות
| מתודה | מה היא עושה |
|---|---|
list(params?) | רשימת הדמויות שלכם, עמוד אחד בכל פעם |
get(id) | קריאת דמות אחת, עם המשימות שלה שעדיין רצות |
create(input), update(id, input) ו-upsert(input) | יצירה או שינוי של דמות |
delete(id) | העברת דמות לארכיון |
restore(id) | החזרת דמות מהארכיון |
duplicate(id, input?) | העתקת דמות |
usage(id) | ספירת התהליכים שמשתמשים בדמות |
generate(input) | יצירת מועמדים לפורטרט |
generateAsset(input) | יצירת וריאנט של הבעה, תנוחה, תאורה או זווית |
generateMotion(input) | הנפשת הפורטרט לקליפ תנועה |
approvePortrait(id, candidateJobId) | קביעת מועמד כפורטרט של הדמות |
recaption(id) | כתיבת התיאור של הדמות מחדש |
train(id, input?) | אימון מודל בנאמנות גבוהה של הדמות, ב-Nodaro Cloud |
trainingStatus(id) | קריאת מצב האימון של הדמות |
deleteLora(id) | הסרת המודל המאומן של הדמות |
יצירת דמות מההתחלה ועד הסוף
הסדר הרגיל: יוצרים את הדמות, יוצרים מועמדים לפורטרט, מאשרים אחד מהם, ואז מוסיפים וריאנטים.
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",
})כשהמשימות מסתיימות, טענו את הדמות מחדש עם get(). עכשיו יש לה פורטרט, הבעת חיוך וקליפ תנועה. חברו אותה ליצירות עם רכיב נכס דמות, או הזכירו אותה בפרומפט עם @. ראו את המדריך דמויות עקביות.
client.characters
list(params?)
מחזירה את הדמויות שלכם, מהחדש לישן. כברירת מחדל היא מחזירה רק דמויות פעילות.
list(params?: { projectId?: string; archived?: boolean; q?: string; limit?: number; cursor?: string }): Promise<{
characters: Character[]
nextCursor: string | null
}>מאפיין
סוג
קריאה אחת מחזירה לכל היותר limit דמויות, ולכן היא לא תמיד כוללת את הרשימה המלאה. המשיכו לבקש עמודים עד ש-nextCursor הוא 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)העבירו את nextCursor בחזרה כמו שהוא, ואל תשמרו אותו ואל תפענחו אותו. קריאה עם cursor פגום נכשלת עם validation_error, ולא מתחילה שוב מהעמוד הראשון.
חיפוש לפי מילים. הפרמטר q משאיר רק את הדמויות שהשם או התיאור שלהן כוללים את כל המילים שבו, בכל סדר, בלי הבדל בין אותיות גדולות לקטנות. המילים מופרדות לפי רווחים, ורק 5 המילים הראשונות נספרות, ולכן "red car" מוצא את "Car, red". כל תו במילה נבדק כמו שהוא: %, _ ו-* הם לא תווים כלליים. ערך ריק ב-q פירושו שאין חיפוש, והאורך של q מוגבל ל-200 תווים. אפשר לשלב אותו עם שאר המסננים ועם הדפדוף בין העמודים, ולכן שלחו את אותו q עם כל cursor.
const { characters } = await client.characters.list({ q: "maya pilot", limit: 20 })get(id)
מחזירה דמות אחת, עם שלוש רשימות נוספות שהאולפן משתמש בהן אחרי טעינה מחדש: pendingJobs (וריאנטים שעדיין נוצרים), portraitCandidates (המועמדים של הרצת הפורטרט הנוכחית, עם ההתקדמות שלהם) ו-previousCandidates (מועמדים קודמים). הדמות כוללת גם את המצב שלה על המדף של Nodaro Keepers, בשדות catalog, catalogSort ו-catalogCategory, שהערכים שלהם הם false, 0 ו-null ברשומות שלכם ובכל עותק.
get(id: string, options?: { candidates?: "alternates" | "all" }): Promise<CharacterDetail>מאפיין
סוג
const character = await client.characters.get(characterId)
console.log(character.sourceImageUrl, character.expressions, character.motions)דמות שבארכיון עדיין חוזרת לפי מזהה, ולכן רכיבים בתהליכים שמצביעים עליה ממשיכים להיטען. אובייקט Character כולל את הפורטרט ב-sourceImageUrl, שש קבוצות וריאנטים (expressions, poses, motions, angles, bodyAngles ו-lightingVariations, כל אחת רשימה של { name, url }), boards, voice, personality, canonicalDescription ו-identityLock.
upsert(input), create(input) ו-update(id, input)
המתודה upsert() יוצרת דמות כשאין input.id, ומעדכנת אותה כש-id מוגדר. המתודות create() ו-update() הן קיצורי דרך שמגדירים את id בשבילכם. עדכון כותב רק את השדות שאתם שולחים; השדות האחרים, כולל name, לא משתנים.
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 }>מאפיין
סוג
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" })שם שכבר בשימוש נדחה עם 409 name_taken.
delete(id)
מעבירה דמות לארכיון. הדמות נעלמת מ-list(), אבל עדיין נטענת עם get(id). כדי להחזיר אותה, קראו ל-restore().
delete(id: string): Promise<{ success: true; archived: true }>מאפיין
סוג
await client.characters.delete(characterId)restore(id)
מחזירה דמות מהארכיון. כשבינתיים יש לדמות פעילה אחרת אותו שם, השרת מוסיף לשם את (restored) (שוחזר) ומחזיר את השם שבו הוא השתמש.
restore(id: string): Promise<{ id: string; name: string }>מאפיין
סוג
const { name } = await client.characters.restore(characterId)duplicate(id, input?)
מעתיקה דמות לדמות חדשה שהשם שלה מסתיים ב-(copy) (עותק). העותק משתמש באותן כתובות של תמונות וקליפים כמו המקור, עד שתיצרו חדשים.
duplicate(id: string, input?: { nodeId?: string; projectId?: string }): Promise<{ id: string; name: string }>מאפיין
סוג
const { id: copyId, name } = await client.characters.duplicate(characterId)usage(id)
מחזירה כמה תהליכים משתמשים בדמות, ואילו תהליכים. העורך מציג את המידע הזה לפני העברה לארכיון.
usage(id: string): Promise<{ workflowCount: number; workflows: Array<{ id: string; name: string }> }>מאפיין
סוג
const { workflowCount } = await client.characters.usage(characterId)generate(input)
יוצרת מועמדים לפורטרט (POST /v1/generate-character). כש-count גדול מ-1, הקרדיטים של כל המשימות משוריינים לפני שמשימה כלשהי מתחילה, ולכן כשל באמצע מבטל את האצווה כולה.
generate(input: GenerateCharacterInput): Promise<{ jobId: string; jobIds: string[] }>מאפיין
סוג
const { jobIds } = await client.characters.generate({
name: "Kira",
seedPrompt: "Kira portrait, warm natural lighting",
subject: { type: "woman", age: "age-30s", hairBase: "base-short-straight" },
count: 4,
attachToCharacterId: characterId,
provider: "gpt-image",
quality: "high", // priced as gpt-image:high
})עם attachToCharacterId, כל מועמד שמסתיים נכתב לפורטרט, ולכן המועמד שמסתיים אחרון גובר. כשיש מועמד אחד, התוצאה הופכת לפורטרט כשהמשימה מסתיימת. כשיש כמה מועמדים, בחרו אחד מהם עם approvePortrait(). כדי שאף מועמד לא ייכתב לפורטרט עד שתאשרו אחד מהם, העבירו attachPortrait: false. הפרמטרים quality ו-resolution מתומחרים בדיוק כמו ברכיב יצירת תמונה, ולכן להרצה ב-4K או באיכות גבוהה משוריינים יותר קרדיטים. גם ערך מהרשימה שהמודל לא תומך בו לא נדחה. Nodaro משתמש במקומו בערך שהמודל תומך בו, או משמיט את ההגדרה כשאין למודל הגדרה כזו. הקרדיטים נקבעים לפי הערך שרץ בפועל, והשדה input_data של המשימה מציג אותו.
השדה subject מתאר מי מופיע בפורטרט, כמזהים מקטלוגי הבוררים: הגיל, השיער והסטיילינג של אדם, אביזר יד, חומר או חיה. Nodaro כותב את הניסוח ומוסיף אותו לפרומפט אחרי seedPrompt, ולכן שלחו את המזהים במקום לכתוב את המילים בעצמכם. עדיין צריך לשלוח אחד מהשדות seedPrompt, referencePhotos או description. ראו בדף ה-API תיאור מי שמופיע בפורטרט.
generateAsset(input)
יוצרת וריאנט אחד מהפורטרט של הדמות: הבעה, תנוחה, מערך תאורה או זווית. עם attachToCharacterId, attachToColumn ו-attachName, התוצאה מתווספת לקבוצה הזו של הדמות כשהמשימה מסתיימת.
generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }>מאפיין
סוג
await client.characters.generateAsset({
name: "Kira",
assetType: "expressions",
variant: "smile",
attachToCharacterId: characterId,
attachToColumn: "expressions",
attachName: "smile",
})generateMotion(input)
מנפישה את הפורטרט של הדמות לקליפ תנועה, עם רכיב יצירת וידאו במצב תמונה לווידאו. עם attachToCharacterId, הקליפ מתווסף ל-motions של הדמות. בלי sourceImageUrl, המתודה משתמשת בפורטרט של הדמות.
generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }>מאפיין
סוג
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)
קובעת מועמד שהושלם מ-generate() כפורטרט של הדמות. אחר כך מודל ראייה כותב את התיאור של הדמות, והמתודה מחזירה את שניהם.
approvePortrait(id: string, candidateJobId: string): Promise<{ portraitUrl: string; canonicalDescription: string | null }>מאפיין
סוג
const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, jobIds[0])השדה canonicalDescription הוא null כשלא היה אפשר לכתוב את התיאור. הפורטרט בכל זאת נקבע; קראו ל-recaption() כדי לנסות שוב.
recaption(id)
כותבת מחדש את התיאור של הדמות מהפורטרט הנוכחי שלה.
recaption(id: string): Promise<{ canonicalDescription: string }>מאפיין
סוג
const { canonicalDescription } = await client.characters.recaption(characterId)המתודה נכשלת עם 400 no_portrait כשלדמות אין פורטרט, ועם 502 כשמודל הראייה נכשל.
אימון דמויות
ב-Nodaro Cloud בלבד. במהדורות אחרות הנתיבים האלה לא קיימים, וכל מתודה זורקת NotFoundError. אימון בונה מודל בנאמנות גבוהה של הדמות מהתמונות שלה עצמה. אחר כך, כשהפרומפט של רכיב יצירת תמונה מזכיר עם @ רק את הדמות הזו, הרכיב רץ על המודל המאומן. המדריך אימון דמויות מציג את אותה עבודה בעורך.
כשאימון מתחיל, משוריינים לו 1,650 קרדיטים. האימון נמשך כ-15 דקות, ואם הוא נכשל או מבוטל, הקרדיטים מוחזרים. עם אסימון של אפליקציית OAuth, המתודות train() ו-deleteLora() דורשות את ההרשאה assets:write, ובלעדיה הן זורקות ForbiddenError. המתודה trainingStatus() לא דורשת הרשאה. סשן בדפדפן או אסימון API אישי לא צריכים הרשאה.
train(id, input?)
מתחילה אימון ומחזירה מיד { jobId, trainingId, triggerWord }.
train(id: string, input?: { selectedImageUrls?: string[] }): Promise<TrainCharacterResult>מאפיין
סוג
כברירת מחדל, המתודה מאמנת את המודל על כל התמונות שמתאימות לאימון: הפורטרט, תמונות הייחוס, ההבעות, התנוחות, זוויות הראש והגוף ווריאציות התאורה. תמונות כפולות מושמטות, והאימון משתמש ב-20 תמונות לכל היותר. צריכות להישאר לפחות 4 תמונות שונות. אפשר לקרוא למתודה עד 3 פעמים בדקה. על אימון חוזר של דמות שכבר אומנה משלמים שוב את המחיר המלא.
מלבד מחלקות השגיאה הייעודיות, כמו ForbiddenError, InsufficientCreditsError, RateLimitedError ו-JobBlockedError, דחייה היא NodaroError עם אחד מהקודים האלה:
code | סטטוס | משמעות |
|---|---|---|
insufficient_training_images | 400 | פחות מ-4 תמונות שונות. השדה message מציין כמה תמונות יש. |
validation_error | 400 | מזהה או selectedImageUrls פגומים. |
already_training_or_not_found | 409 | אימון של הדמות הזו כבר רץ, או שהדמות לא שלכם, או שהיא בארכיון. |
claim_failed | 500 | השרת לא הצליח לנעול את האימון לבקשה הזאת, ושום דבר לא התחיל. נסו שוב. |
training_dispatch_failed | 502 | לא היה אפשר להתחיל את האימון, והקרדיטים המשוריינים מוחזרים. אחר כך הסטטוס הוא untrained. |
public_url_not_configured, price_not_configured | 503 | הפריסה לא מוגדרת לאימון. |
dedup_race_winner_unresolvable | 503 | בקשה זהה כבר הייתה בטיפול, ולא היה אפשר לקרוא את התוצאה שלה. בקריאה הזו לא שוריינו קרדיטים. קראו ל-trainingStatus() לפני שאתם מנסים שוב. |
import { NodaroError } from "@nodaro/sdk"
try {
const { jobId, triggerWord } = await client.characters.train(characterId)
} catch (err) {
if (err instanceof NodaroError && err.code === "insufficient_training_images") {
// err.message: "Need at least 4 reference photos to train (have 2). …"
} else throw err
}trainingStatus(id)
מחזירה את מצב האימון של הדמות.
trainingStatus(id: string): Promise<CharacterTrainingStatus>| שדה | תיאור |
|---|---|
status | untrained, queued, training, succeeded, failed או cancelled. הערך untrained פירושו שמעולם לא אומן מודל לדמות, שהמודל הוסר או שאימון לא הצליח להתחיל. |
trainingId | האימון האחרון. |
error | למה אימון נכשל. |
trainedAt, version | האימון המוצלח האחרון. השדות שומרים על הערכים שלהם גם במהלך אימון חדש, אחרי אימון שנכשל או בוטל ואחרי אימון שלא הצליח להתחיל. |
triggerWord | המילה ש-Nodaro מוסיף לפרומפטים בשבילכם. |
imageCount | בכמה תמונות האימון האחרון השתמש. |
היצירה משתמשת במודל המאומן רק כל עוד status הוא succeeded. המתודה זורקת NotFoundError על דמות שלא שייכת לכם או שנמצאת בארכיון.
אין פונקציית עזר שממתינה לסיום. כל עוד הסטטוס הוא queued או training, בדקו אותו בערך כל 10 שניות. גם המשימה של האימון, ה-jobId מ-train(), מסתיימת ב-completed, failed או cancelled, ולכן אפשר במקום זאת לבדוק את המצב שלה עם client.jobs.getStatus(jobId).
let training = await client.characters.trainingStatus(characterId)
while (training.status === "queued" || training.status === "training") {
await new Promise((resolve) => setTimeout(resolve, 10_000))
training = await client.characters.trainingStatus(characterId)
}
if (training.status !== "succeeded") console.warn(training.status, training.error)deleteLora(id)
מסירה את המודל המאומן של הדמות. המתודה מבטלת אימון שבעיצומו, והקרדיטים ששוריינו לו מוחזרים. היא גם מוחקת את המודל ומנקה את הרשומה. אחר כך המתודה trainingStatus() מחזירה untrained, והיצירות חוזרות להשתמש בתמונות ייחוס. אי אפשר לבטל את הפעולה: מודל חדש דורש אימון חדש, בתשלום.
deleteLora(id: string): Promise<void>המתודה זורקת ForbiddenError כשאסימון של אפליקציית OAuth לא כולל את assets:write, ו-NotFoundError על דמות שלא שייכת לכם או שנמצאת בארכיון.
שאלות נפוצות
איך יוצרים דמות עם ה-SDK של Nodaro?
כדי ליצור דמות, קראו ל-client.characters.create עם nodeId ושם, אחר כך ל-client.characters.generate כדי ליצור מועמדים לפורטרט, ול-client.characters.approvePortrait כדי לבחור אחד מהם. אחרי זה אפשר להשתמש בדמות בכל יצירה של תמונה או וידאו.
האם יצירת פורטרט לדמות עולה קרדיטים?
כן. יצירת פורטרט לדמות עולה כמו הרצה של רכיב יצירת תמונה במודל שנבחר. הפרמטרים quality ו-resolution משנים את המחיר: למשל, הרצה ב-4K או באיכות גבוהה עולה יותר.
מה קורה כשמוחקים דמות?
המתודה delete מעבירה את הדמות לארכיון. הדמות נעלמת מ-list(), אבל עדיין נטענת לפי מזהה, ולכן תהליכים שמשתמשים בה ממשיכים לעבוד. המתודה restore מחזירה אותה.
איך מאמנים דמות מה-SDK?
כדי לאמן דמות מה-SDK, קראו ב-Nodaro Cloud ל-client.characters.train(id), ואז בדקו את הסטטוס עם client.characters.trainingStatus(id) מדי כמה שניות, עד שהוא כבר לא queued או training. אימון עולה 1,650 קרדיטים ונמשך כ-15 דקות, ואם הוא נכשל, הקרדיטים מוחזרים.
מה עושה identityLock?
השדה identityLock קובע עד כמה הפנים נשמרות כש-Nodaro יוצר את ההבעות, התנוחות ושאר הווריאנטים של הדמות: off, soft או strict. ברירת המחדל היא off.
דפים קשורים
אולפן הדמות
דמויות עקביות
נכס דמות
דמויות
אימון דמויות
עודכן לאחרונה