# אובייקטים ויצורים

> יצירת אובייקטים ויצורים, תמונות ראשיות ווריאנטים, הנפשה שלהם ויצור שמדבר, מקוד TypeScript עם ה-SDK של Nodaro.

Source: https://nodaro.ai/he/docs/developers/sdk/objects-and-creatures

**`client.objects`** מאפשר לעשות בסקריפט כל מה שעושים באולפן האובייקטים/אביזרים לאביזרים, למוצרים ולכלי רכב, והמשאב **`client.creatures`** עושה את אותו דבר לחיות וליצורים. שניהם יוצרים ועורכים פריטים, יוצרים מועמדים לתמונה הראשית, מאשרים אחד מהם, מוסיפים וריאנטים וקליפי תנועה וכותבים תיאור ששומר על עקביות הפריט בפרומפטים מאוחרים יותר. המתודות קוראות לנקודות הקצה שבדפי ה-API [אובייקטים](https://nodaro.ai/docs/developers/api/objects) ו[יצורים](https://nodaro.ai/docs/developers/api/creatures). את אותה עבודה בעורך מתארים המדריכים [אובייקטים ואביזרים](https://nodaro.ai/docs/guides/objects) ו[חיות ויצורים](https://nodaro.ai/docs/guides/creatures).

## מתודות
לשני המשאבים יש אותן מתודות.

| מתודה | מה היא עושה |
| --- | --- |
| [`objects.list(params?)`](#objectslistparams), [`creatures.list(params?)`](#creatureslistparams) | רשימת הפריטים שלכם |
| [`objects.listArchived(params?)`](#objectslistarchivedparams) | רשימת הפריטים שלכם שבארכיון |
| [`objects.get(id)`](#objectsgetid) | קריאת פריט אחד, עם המשימות שלו שעדיין רצות |
| [`objects.create(input)`](#objectscreateinput), [`creatures.create(input)`](#creaturescreateinput) | יצירת פריט |
| [`objects.update(id, input)`](#objectsupdateid-input), [`creatures.update(id, input)`](#creaturesupdateid-input) | שינוי פריט |
| [`objects.delete(id)` ו-`restore(id)`](#objectsdeleteid-and-restoreid) | העברת פריט לארכיון, או שחזור שלו |
| [`objects.permanentDelete(id)`](#objectspermanentdeleteid) | מחיקה לצמיתות של פריט שבארכיון ושל הקבצים שלו |
| [`objects.generate(input)`](#objectsgenerateinput), [`creatures.generate(input)`](#creaturesgenerateinput) | יצירת מועמדים לתמונה הראשית |
| [`objects.generateAsset(input)`](#objectsgenerateassetinput), [`creatures.generateAsset(input)`](#creaturesgenerateassetinput) | יצירת וריאנט |
| [`objects.generateMotion(input)`](#objectsgeneratemotioninput), [`creatures.generateMotion(input)`](#creaturesgeneratemotioninput) | הנפשת התמונה הראשית לקליפ |
| [`objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?)`](#objectsapprovemainimageid-candidatejobid-expectedupdatedat) | קביעת מועמד כתמונה הראשית |
| [`objects.recaption(id)`](#objectsrecaptionid) | כתיבה מחדש של התיאור |

## client.objects
לאובייקט יש תמונה ראשית ב-`sourceImageUrl`, ארבע קבוצות וריאנטים (`angles`, `materials`, `variations` ו-`motionClips`, כל אחת רשימה של `{ name, url }`) וגם `boards`, `referencePhotos`, `canonicalDescription` ו-`styleLock`. השדה `category` הוא אחד מהערכים `furniture`, `vehicle`, `weapon`, `food`, `clothing`, `electronics`, `nature`, `tool`, `animal` או `other`.

לטיפוס `Object` יש אותו שם כמו לאובייקט הגלובלי של JavaScript. כשצריך את שניהם, ייבאו אותו בשם אחר: `import type { Object as NodaroObject } from "@nodaro/sdk"`.

### objects.list(params?)‎
מחזירה את רשימת האובייקטים שלכם. כברירת מחדל, היא מחזירה רק אובייקטים פעילים. החלוקה לעמודים היא אופציונלית: בלי `limit` מקבלים את הרשימה המלאה, ועם `limit`, עד 500, מקבלים עמוד אחד ו-`nextCursor`.

```ts
list(params?: { archived?: boolean; projectId?: string; q?: string; limit?: number; cursor?: string }): Promise<{
objects: Object[]
nextCursor?: string | null
}>
```

<TypeTable
type={{
archived: { type: 'boolean', default: 'false', description: "עם true, המתודה מחזירה במקום זאת את האובייקטים שבארכיון." },
projectId: { type: 'string', description: "רק אובייקטים של הפרויקט הזה." },
q: { type: 'string', description: "מילות חיפוש: רק אובייקטים שהשם או התיאור שלהם כוללים את כל המילים, בכל סדר, בלי הבדל בין אותיות גדולות לקטנות. אותו חיפוש כמו ב-characters.list()‎." },
limit: { type: 'number', description: "גודל העמוד, עד 500. השמיטו אותו כדי לקבל את הרשימה המלאה." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
}}
/>

```ts
const { objects } = await client.objects.list()
const { objects: cars } = await client.objects.list({ q: "red car" })
const page = await client.objects.list({ limit: 100 })
```

### objects.listArchived(params?)‎
מחזירה את רשימת האובייקטים שלכם שבארכיון, כקיצור דרך ל-`list({ archived: true })`. המתודה `creatures.listArchived()` עובדת באותו אופן.

```ts
listArchived(params?: { projectId?: string; limit?: number; cursor?: string }): Promise<{ objects: Object[]; nextCursor?: string | null }>
```

<TypeTable
type={{
projectId: { type: 'string', description: "רק אובייקטים של הפרויקט הזה." },
limit: { type: 'number', description: "גודל העמוד, עד 500." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
}}
/>

```ts
const { objects: archived } = await client.objects.listArchived()
```

### objects.get(id)‎
מחזירה אובייקט אחד, עם `pendingJobs`: הווריאנטים שעדיין נוצרים. אובייקט שבארכיון **לא** מוחזר: המתודה זורקת `NotFoundError`. המתודה `creatures.get()` עובדת באותו אופן.

```ts
get(id: string): Promise<ObjectDetail>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האובייקט." },
}}
/>

```ts
const object = await client.objects.get(objectId)
console.log(object.sourceImageUrl, object.materials)
```

### objects.create(input)‎
יוצרת אובייקט. השדות `name` ו-`nodeId` נדרשים. סקריפט בלי רכיב על הקנבס יכול להעביר `"mcp-managed"` בתור `nodeId`.

```ts
create(input: CreateObjectInput): Promise<{ id: string }>
```

<TypeTable
type={{
nodeId: { type: 'string', required: true, description: "הרכיב על הקנבס שהאובייקט שייך אליו, או mcp-managed." },
name: { type: 'string', required: true, description: "השם." },
description: { type: 'string', description: "תיאור בטקסט חופשי." },
category: { type: 'ObjectCategory', description: "furniture, vehicle, weapon, food, clothing, electronics, nature, tool, animal או other." },
style: { type: 'string', description: "הסגנון החזותי, למשל realistic." },
projectId: { type: 'string', description: "הפרויקט שבו לשמור את האובייקט." },
workflowId: { type: 'string', description: "התהליך שהאובייקט מגיע ממנו." },
sourceImageUrl: { type: 'string', description: "הכתובת של התמונה הראשית." },
imageProvider: { type: 'string | null', description: "מודל התמונה של התמונה הראשית." },
referencePhotos: { type: 'ObjectReferencePhoto[]', description: "תמונות ייחוס, כל אחת ‎{ url, kind }‎. השדה kind הוא front, side, detail, context, moodBoard או other." },
canonicalDescription: { type: 'string', description: "התיאור שמשמש בפרומפטים." },
styleLock: { type: 'boolean', description: "שמירה על הווריאנטים בסגנון המאושר." },
}}
/>

```ts
const { id: objectId } = await client.objects.create({
nodeId: "mcp-managed",
name: "Antique Lantern",
description: "Weathered brass lantern with hand-engraved filigree",
category: "tool",
style: "realistic",
})
```

### objects.update(id, input)‎
משנה אובייקט. רק השדות שאתם שולחים נכתבים. קבוצות הווריאנטים הן לא חלק מהקריאה הזו, כי משימות יצירה מוסיפות להן בזמן שאתם עובדים.

```ts
update(id: string, input: UpdateObjectInput): Promise<{ id: string; updatedAt: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האובייקט." },
name: { type: 'string', description: "השם." },
description: { type: 'string', description: "התיאור." },
category: { type: 'ObjectCategory', description: "הקטגוריה." },
style: { type: 'string', description: "הסגנון החזותי." },
sourceImageUrl: { type: 'string', description: "הכתובת של התמונה הראשית." },
imageProvider: { type: 'string | null', description: "מודל התמונה של התמונה הראשית." },
referencePhotos: { type: 'ObjectReferencePhoto[]', description: "תמונות ייחוס." },
canonicalDescription: { type: 'string', description: "התיאור שמשמש בפרומפטים." },
styleLock: { type: 'boolean', description: "שמירה על הווריאנטים בסגנון המאושר." },
boards: { type: 'Array<{ name, url, type?, sourceImages? }>', description: "לוחות ייחוס." },
selectedAssetByVariant: { type: 'Record<string, string>', description: "הגרסה שנבחרה של כל וריאנט." },
expectedUpdatedAt: { type: 'string', description: "ערך ה-updatedAt שקראתם. כשהאובייקט השתנה מאז, העדכון נכשל עם 409 concurrent_modification." },
}}
/>

```ts
await client.objects.update(objectId, {
canonicalDescription: "A weathered brass lantern with engraved filigree and a glass chimney",
expectedUpdatedAt: object.updatedAt,
})
```

השגיאה `409 concurrent_modification` מגיעה כ-`NodaroError` רגיל. טענו את האובייקט שוב, מזגו את השינויים ונסו שוב.

### objects.delete(id)‎ ו-restore(id)‎
המתודה `delete()` מעבירה אובייקט לארכיון. חזרה על הקריאה, כשהאובייקט כבר בארכיון, לא משנה כלום. המתודה `restore()` משחזרת אותו; כשהשם שלו זהה עכשיו לשם של אובייקט פעיל, בלי הבדל בין אותיות גדולות לקטנות, השרת מוסיף `(restored)` (שוחזר) ומחזיר את השם שבו הוא השתמש.

```ts
delete(id: string): Promise<{ success: true; archived: true }>
restore(id: string): Promise<{ id: string; name: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האובייקט." },
}}
/>

```ts
await client.objects.delete(objectId)
const { name } = await client.objects.restore(objectId)
```

### objects.permanentDelete(id)‎
מוחקת לצמיתות אובייקט שבארכיון ואת כל הקבצים השמורים שהוא מצביע עליהם. המתודה עובדת רק על אובייקטים שבארכיון: על אובייקט פעיל היא נכשלת עם `400 not_archived`. העבירו אותו קודם לארכיון עם `delete()`. המתודה `creatures.permanentDelete()` עובדת באותו אופן.

```ts
permanentDelete(id: string): Promise<{ success: true; permanent: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "המזהה של אובייקט שבארכיון." },
}}
/>

```ts
await client.objects.delete(objectId)
await client.objects.permanentDelete(objectId)
```

כלי ה-MCP של Nodaro לא מציעים את הפעולה הזו, ולכן עוזר AI לא יכול למחוק לצמיתות את האובייקטים שלכם.

### objects.generate(input)‎
יוצרת מועמדים לתמונה הראשית (`POST /v1/generate-object`). כש-`count` גדול מ-1, הקרדיטים לכל המשימות משוריינים לפני שאחת מהן מתחילה, ולכן כשל באמצע מבטל את כל האצווה.

```ts
generate(input: GenerateObjectInput): Promise<{ jobIds: string[]; jobId?: string }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "שם האובייקט." },
description: { type: 'string', description: "איך האובייקט נראה." },
userPrompt: { type: 'string', description: "הוראות נוספות." },
category: { type: 'ObjectCategory', description: "הקטגוריה." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "הסגנון החזותי." },
sourceImageUrl: { type: 'string', description: "צילום שהתמונה תתבסס עליו." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
count: { type: 'number', description: "כמה מועמדים ליצור." },
seedPromptHint: { type: 'string', description: "בחירה מבורר שתתווסף לפרומפט, למשל antique brass (פליז עתיק) מהבורר חומר." },
attachToObjectId: { type: 'string', description: "האובייקט שתוצאה יחידה תיכתב אליו." },
attachName: { type: 'string', description: "שם לתוצאה המצורפת." },
expectedUpdatedAt: { type: 'string', description: "ערך ה-updatedAt שקראתם, כדי לא לדרוס שינוי חדש יותר." },
}}
/>

```ts
const { jobIds } = await client.objects.generate({ name: "Antique Lantern", count: 4 })
for (const jobId of jobIds) {
// poll each candidate with client.jobs.getStatus(jobId)
}
```

השדה `jobIds` תמיד מופיע, עם מזהה אחד לכל מועמד. השדה `jobId` הוא שם חלופי ישן יותר למועמד יחיד; השתמשו ב-`jobIds`. עם `attachToObjectId` ומועמד אחד, התוצאה הופכת לתמונה הראשית כשהמשימה מסתיימת. אחרת, בחרו מועמד עם `approveMainImage()`.

### objects.generateAsset(input)‎
יוצרת וריאנט אחד (`POST /v1/generate-object-asset`). עם `attachToObjectId`, `attachToColumn` ו-`attachName`, התוצאה מתווספת לקבוצה הזו כשהמשימה מסתיימת.

```ts
generateAsset(input: GenerateObjectAssetInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
assetType: { type: '"angles" | "materials" | "variations" | "motion" | "custom"', required: true, description: "סוג הווריאנט." },
variant: { type: 'string', required: true, description: "הווריאנט, למשל gold או three-quarter." },
name: { type: 'string', required: true, description: "שם האובייקט." },
description: { type: 'string', description: "הפרומפט של הווריאנט. כשמשמיטים אותו והתוצאה מצורפת לאובייקט, מודל שפה כותב פרומפט מהתיאור של האובייקט ומשם הווריאנט." },
userPrompt: { type: 'string', description: "הוראות נוספות." },
sourceImageUrl: { type: 'string', description: "התמונה שממנה מתחילים, בדרך כלל התמונה הראשית." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
seedPromptHint: { type: 'string', description: "בחירה מבורר שתתווסף לפרומפט." },
attachToObjectId: { type: 'string', description: "האובייקט שהתוצאה תתווסף אליו." },
attachToColumn: { type: 'string', description: "הקבוצה: angles, materials, variations, motion_clips, sheets או detail_closeups. נדרש ב-custom." },
attachName: { type: 'string', description: "השם של הפריט החדש." },
}}
/>

```ts
const { jobId } = await client.objects.generateAsset({
name: "Antique Lantern",
assetType: "materials",
variant: "gold",
attachToObjectId: objectId,
attachToColumn: "materials",
attachName: "gold",
})
```

ב-`angles`, ב-`materials`, ב-`variations` וב-`motion`, הקבוצה נקבעת לפי סוג הווריאנט. וריאנט `custom` צריך `attachToColumn`.

### objects.generateMotion(input)‎
מנפישה את התמונה של האובייקט לקליפ (`POST /v1/generate-object-motion`), בעזרת רכיב [יצירת וידאו](https://nodaro.ai/docs/nodes/video/generate-video) במצב תמונה לווידאו. הקליפ תמיד נשמר ב-`motionClips`. ברירות המחדל מתאימות לשוטים של מוצר: המודל הוא `kling-turbo`, ויחס הגובה־רוחב הוא `1:1`.

```ts
generateMotion(input: GenerateObjectMotionInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
motionPrompt: { type: 'string', required: true, description: "איך האובייקט זז, או איך המצלמה נעה סביבו." },
sourceImageUrl: { type: 'string', required: true, description: "התמונה להנפשה. אין לה ערך חלופי, ולכן העבירו את התמונה הראשית." },
name: { type: 'string', required: true, description: "שם האובייקט." },
provider: { type: 'string', default: '"kling-turbo"', description: "מזהה מודל הווידאו." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16" | "4:3"', default: '"1:1"', description: "יחס הגובה־רוחב של הקליפ. אובייקטים מוסיפים את 4:3, לשוטים של קטלוג." },
duration: { type: 'number', description: "אורך הקליפ בשניות." },
refineFromVideoUrl: { type: 'string', description: "קליפ קיים לעיבוד מחדש עם הפרומפט החדש, במצב וידאו לווידאו, במקום להתחיל מהתמונה." },
category: { type: 'string', description: "הקטגוריה." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "הסגנון החזותי." },
canonicalDescription: { type: 'string', description: "התיאור של האובייקט." },
seedPromptHint: { type: 'string', description: "בחירה מבורר שתתווסף לפרומפט." },
attachToObjectId: { type: 'string', description: "האובייקט שהקליפ יתווסף אליו." },
attachName: { type: 'string', description: "השם של הקליפ החדש." },
}}
/>

```ts
const { jobId } = await client.objects.generateMotion({
name: "Antique Lantern",
motionPrompt: "Slow 360-degree rotation, soft golden rim light",
sourceImageUrl: object.sourceImageUrl!,
attachToObjectId: objectId,
attachName: "rotate-360",
})
```

### objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?)‎
קובעת מועמד שהושלם מ-`generate()` כתמונה הראשית של האובייקט. אחר כך מודל ראייה כותב את התיאור של האובייקט, והמתודה מחזירה את שניהם.

```ts
approveMainImage(id: string, candidateJobId: string, expectedUpdatedAt?: string): Promise<{
sourceImageUrl: string
canonicalDescription: string | null
}>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האובייקט." },
candidateJobId: { type: 'string', required: true, description: "מזהה המשימה של המועמד שהושלם." },
expectedUpdatedAt: { type: 'string', description: "ערך ה-updatedAt שקראתם. כשהאובייקט השתנה מאז, הקריאה נכשלת עם 409 concurrent_modification." },
}}
/>

```ts
const { sourceImageUrl, canonicalDescription } = await client.objects.approveMainImage(objectId, jobIds[0])
```

השדה `canonicalDescription` הוא `null` כשאי אפשר היה לכתוב את התיאור. התמונה הראשית בכל זאת נקבעת; קראו למתודה `recaption()` כדי לנסות שוב.

### objects.recaption(id)‎
כותבת מחדש את התיאור של האובייקט מהתמונה הראשית הנוכחית שלו. אפשר לחזור על הקריאה בלי חשש, והיא לא מקבלת אסימון מקביליות.

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

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האובייקט." },
}}
/>

```ts
const { canonicalDescription } = await client.objects.recaption(objectId)
```

המתודה נכשלת עם `400 main_image_required` כשלאובייקט אין תמונה ראשית, ועם 502 כשמודל הראייה נכשל.

## client.creatures
יצור הוא חיה או יצור פנטזיה. הוא עובד כמו אובייקט, עם ארבעה הבדלים:

- **`species`** הוא סוג בטקסט חופשי, למשל `dragon` (דרקון) או `wolf` (זאב), והוא הנושא של הפרומפט לתמונה הראשית. גם `category` הוא טקסט חופשי.
- **`poses`** מחליף את `materials`, ולכן הקבוצות הן `angles`, `poses`, `variations` ו-`motionClips`. סוגי הווריאנטים הם `angles`, `poses`, `variations` ו-`custom`.
- **`boards`** כולל עד 24 לוחות יצור עם שם: גיליונות ייחוס צפופים שנוצרים עם הערכה לוח יצור של רכיב [יצירת תמונה](https://nodaro.ai/docs/nodes/image/generate-image). את הרשימה הזו אתם מנהלים: המתודות `create()` ו-`update()` מחליפות אותה כולה.
- **`voice`** הופך את היצור ליצור שמדבר. המבנה שלו זהה לקול של דמות: `{ voiceId, voiceName, traits, voiceType?, previewUrl?, ttsProvider? }`. העבירו `voice: null` כדי להסיר אותו.

המתודות `creatures.listArchived()`, `get()`, `delete()`, `restore()`, `permanentDelete()`, `approveMainImage()` ו-`recaption()` מקבלות את אותם ארגומנטים ומתנהגות כמו המתודות של האובייקטים שלמעלה.

### creatures.list(params?)‎
```ts
list(params?: { archived?: boolean; projectId?: string; q?: string; limit?: number; cursor?: string }): Promise<{
creatures: Creature[]
nextCursor?: string | null
}>
```

<TypeTable
type={{
archived: { type: 'boolean', default: 'false', description: "עם true, המתודה מחזירה במקום זאת את היצורים שבארכיון." },
projectId: { type: 'string', description: "רק יצורים של הפרויקט הזה." },
q: { type: 'string', description: "מילות חיפוש: רק יצורים שהשם או התיאור שלהם כוללים את כל המילים, בכל סדר, בלי הבדל בין אותיות גדולות לקטנות." },
limit: { type: 'number', description: "גודל העמוד, עד 500. השמיטו אותו כדי לקבל את הרשימה המלאה." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
}}
/>

```ts
const { creatures } = await client.creatures.list({ q: "dragon" })
```

המתודה `creatures.list()` מקבלת את אותם פרמטרים כמו `objects.list()`, כולל החלוקה האופציונלית לעמודים עם `limit` ו-`cursor`. כללי החיפוש של `q` מפורטים בדף [דמויות](https://nodaro.ai/docs/developers/sdk/characters#listparams).

### creatures.create(input)‎
```ts
create(input: CreateCreatureInput): Promise<{ id: string }>
```

<TypeTable
type={{
nodeId: { type: 'string', required: true, description: "הרכיב על הקנבס שהיצור שייך אליו, או mcp-managed." },
name: { type: 'string', required: true, description: "השם." },
species: { type: 'string', description: "סוג היצור, למשל dragon (דרקון) או wolf (זאב)." },
description: { type: 'string', description: "תיאור בטקסט חופשי." },
category: { type: 'string', description: "קטגוריה בטקסט חופשי." },
style: { type: 'string', description: "הסגנון החזותי." },
projectId: { type: 'string', description: "הפרויקט שבו לשמור את היצור." },
sourceImageUrl: { type: 'string', description: "הכתובת של התמונה הראשית." },
referencePhotos: { type: 'CreatureReferencePhoto[]', description: "תמונות ייחוס, כל אחת ‎{ url, kind }‎. השדה kind הוא front, side, detail, context, moodBoard או other." },
canonicalDescription: { type: 'string', description: "התיאור שמשמש בפרומפטים." },
styleLock: { type: 'boolean', description: "שמירה על הווריאנטים בסגנון המאושר." },
voice: { type: 'CreatureVoice | null', description: "הקול של היצור." },
}}
/>

```ts
const { id: creatureId } = await client.creatures.create({
nodeId: "mcp-managed",
name: "Biscuit",
species: "ginger cat",
style: "realistic",
})
```

### creatures.update(id, input)‎
מקבלת את השדות של `create()` חוץ מ-`nodeId`, ובנוסף `boards`, `selectedAssetByVariant` ו-`expectedUpdatedAt`. רק השדות שאתם שולחים נכתבים.

```ts
update(id: string, input: UpdateCreatureInput): Promise<{ id: string; updatedAt: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה היצור." },
boards: { type: 'Array<{ name, url, type?, sourceImages? }>', description: "לוחות היצור, עד 24. הרשימה מוחלפת כולה." },
voice: { type: 'CreatureVoice | null', description: "הקול. הערך null מסיר אותו." },
expectedUpdatedAt: { type: 'string', description: "ערך ה-updatedAt שקראתם, כדי לא לדרוס שינוי חדש יותר." },
}}
/>

```ts
await client.creatures.update(creatureId, {
voice: { voiceId: chosenVoiceId, voiceName: "Aria", traits: "smug, unhurried" },
})
```

### creatures.generate(input)‎
יוצרת מועמדים לתמונה הראשית. המתודה מקבלת את השדות של `objects.generate()`, ובנוסף `species`, ומחזירה `{ jobIds }`.

```ts
generate(input: GenerateCreatureInput): Promise<{ jobIds: string[]; jobId?: string }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "שם היצור." },
species: { type: 'string', description: "סוג היצור." },
description: { type: 'string', description: "איך היצור נראה." },
count: { type: 'number', description: "כמה מועמדים ליצור." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
attachToCreatureId: { type: 'string', description: "היצור שתוצאה יחידה תיכתב אליו." },
subject: { type: 'Subject', description: "מי מופיע בתמונה, כמזהי בוררים, כמו ב-client.characters.generate()‎." },
}}
/>

```ts
const { jobIds } = await client.creatures.generate({ name: "Biscuit", species: "ginger cat", count: 4 })
```

### creatures.generateAsset(input)‎
יוצרת וריאנט אחד. המתודה עובדת כמו `objects.generateAsset()`, עם `attachToCreatureId`.

```ts
generateAsset(input: GenerateCreatureAssetInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
assetType: { type: '"angles" | "poses" | "variations" | "custom"', required: true, description: "סוג הווריאנט." },
variant: { type: 'string', required: true, description: "הווריאנט, למשל walking או sitting." },
name: { type: 'string', required: true, description: "שם היצור." },
attachToCreatureId: { type: 'string', description: "היצור שהתוצאה תתווסף אליו." },
attachToColumn: { type: 'string', description: "angles, poses, variations, motion_clips, sheets או detail_closeups. נדרש ב-custom." },
attachName: { type: 'string', description: "השם של הפריט החדש." },
}}
/>

```ts
await client.creatures.generateAsset({
name: "Biscuit",
assetType: "poses",
variant: "sitting",
attachToCreatureId: creatureId,
attachToColumn: "poses",
attachName: "sitting",
})
```

### creatures.generateMotion(input)‎
מנפישה את התמונה של היצור לקליפ. המתודה עובדת כמו `objects.generateMotion()`, עם אותן ברירות מחדל, `kling-turbo` ו-`1:1`, ומוסיפה את הקליפ ל-`motionClips`.

```ts
generateMotion(input: GenerateCreatureMotionInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
motionPrompt: { type: 'string', required: true, description: "מה היצור עושה." },
sourceImageUrl: { type: 'string', required: true, description: "התמונה להנפשה." },
name: { type: 'string', required: true, description: "שם היצור." },
provider: { type: 'string', default: '"kling-turbo"', description: "מזהה מודל הווידאו." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16" | "4:3"', default: '"1:1"', description: "יחס הגובה־רוחב של הקליפ." },
attachToCreatureId: { type: 'string', description: "היצור שהקליפ יתווסף אליו." },
attachName: { type: 'string', description: "השם של הקליפ החדש." },
}}
/>

```ts
await client.creatures.generateMotion({
name: "Biscuit",
motionPrompt: "The cat stretches, then yawns",
sourceImageUrl: creature.sourceImageUrl!,
attachToCreatureId: creatureId,
})
```

## יצור שמדבר
לדיבור לא צריך מתודה מיוחדת ליצורים. רנדרו את השורה בקול של היצור, ואז סנכרנו אותה לתמונה של היצור בעזרת סנכרון שפתיים:

```ts
const creature = await client.creatures.get(creatureId)

// 1. Speak the line in the creature's voice
const speech = await client.nodes.runAndWait("text-to-speech", {
text: "I knocked the vase off the shelf. I regret nothing.",
voice: creature.voice!.voiceId,
provider: creature.voice!.ttsProvider,
voiceType: creature.voice!.voiceType,
})

// 2. Lip-sync the audio onto the creature's main image
const clip = await client.nodes.runAndWait("lip-sync", {
imageUrl: creature.sourceImageUrl!,
audioUrl: speech.audioUrl,
provider: "kling-avatar",
})
console.log(clip.videoUrl)
```

רכיב [סנכרון שפתיים](https://nodaro.ai/docs/nodes/video/lip-sync) גם מדבב סרטון קיים: העבירו `videoUrl` ומודל שמקבל וידאו. המודל `volcengine-lipsync` הוא האפשרות הזולה ביותר לדיבוב, והיחידה שמטפלת בכמה דוברים:

```ts
const dub = await client.nodes.runAndWait("lip-sync", {
videoUrl: "https://example.com/scene.mp4",
audioUrl: "https://example.com/new-vocal.mp3",
provider: "volcengine-lipsync",
mode: "basic",          // for complex scenes
openScenedet: true,     // several speakers: scene and speaker detection
audioDurationSec: 42,   // sets the per-second price; without it, you pay for 5 minutes
})
```

כדי להכניס יצור לשוט כייחוס, בנו את הייחוס עם `toConnectedReference({ kind: "creature", id, name, url, description })` מ-`@nodaro/shared`. רכיב יצירת תמונה מוסיף אז שורה ששומרת על האנטומיה, על הסימונים ועל הצבעים של היצור. ראו את הסעיף [ייחוסים](https://nodaro.ai/docs/developers/sdk/nodes#references) בדף הרצת רכיבים.

## שאלות נפוצות

### מה ההבדל בין אובייקט ליצור ב-Nodaro?

אובייקט הוא אביזר, מוצר או רכב, עם וריאנטים של זווית, חומר ווריאציה. יצור הוא חיה או יצור פנטזיה, עם וריאנטים של זווית, תנוחה ווריאציה, מין, לוחות ייחוס וקול אופציונלי.

### איך יוצרים מועמדים לתמונה הראשית של אובייקט?

קראו למתודה client.objects.generate עם שם ועם count. המתודה תמיד מחזירה jobIds, מזהה אחד לכל מועמד. כשהמשימות מסתיימות, אשרו מועמד אחד עם המתודה client.objects.approveMainImage.

### האם אפשר למחוק אובייקט לצמיתות עם ה-SDK?

כן, בשני שלבים. העבירו את האובייקט לארכיון עם המתודה delete()‎, ואז קראו למתודה permanentDelete()‎, שמסירה את האובייקט ואת כל הקבצים שהוא מצביע עליהם. בקשה למחוק לצמיתות אובייקט פעיל נדחית עם 400 not_archived.

### איך גורמים לחיה או ליצור לדבר?

רנדרו דיבור עם רכיב טקסט לדיבור ועם הקול של היצור, ואז הריצו את רכיב סנכרון שפתיים עם התמונה הראשית של היצור ועם האודיו הזה.
