# מיקומים

> יוצרים מיקומים ושוטים פותחים מקוד TypeScript, מאשרים אחד מהם ומוסיפים וריאנטים של שעות היום, מזג אוויר, עונה, זווית ותנועה.

Source: https://nodaro.ai/he/docs/developers/sdk/locations

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

## מתודות
| מתודה | מה היא עושה |
| --- | --- |
| [`list(params?)`](#listparams) | רשימת המיקומים שלכם |
| [`listArchived(params?)`](#listarchivedparams) | רשימת המיקומים שלכם שבארכיון |
| [`get(id)`](#getid) | קריאת מיקום אחד, עם המשימות שלו שעדיין רצות |
| [`create(input)`](#createinput) | יצירת מיקום |
| [`update(id, input)`](#updateid-input) | שינוי מיקום |
| [`delete(id)` ו-`restore(id)`](#deleteid-and-restoreid) | העברת מיקום לארכיון או שחזור שלו |
| [`generate(input)`](#generateinput) | יצירת מועמדים לשוט הפותח |
| [`generateAsset(input)`](#generateassetinput) | יצירת וריאנט של שעת יום, מזג אוויר, עונה, זווית או תאורה |
| [`generateSurroundContinuation(input)`](#generatesurroundcontinuationinput) | יצירת התצוגה הבאה בטבעת של 360 מעלות |
| [`generateMotion(input)`](#generatemotioninput) | הנפשת התמונה הראשית לקליפ אווירה |
| [`removeAsset(id, data)`](#removeassetid-data) | הסרת תוצאה אחת מקבוצת וריאנטים |
| [`approveMainImage(id, candidateJobId)`](#approvemainimageid-candidatejobid) | קביעת מועמד כתמונה הראשית |
| [`recaption(id)`](#recaptionid) | כתיבת התיאור של המיקום מחדש |

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

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

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

```ts
const { locations } = await client.locations.list()
const { locations: cafes } = await client.locations.list({ q: "café" })

const page = await client.locations.list({ limit: 100 })
const next = await client.locations.list({ limit: 100, cursor: page.nextCursor ?? undefined })
```

המשיכו לבקש עמודים עד ש-`nextCursor` הוא `null`. כללי החיפוש של `q` מפורטים בדף [דמויות](https://nodaro.ai/docs/developers/sdk/characters#listparams): החיפוש כולל רק את 5 המילים הראשונות, ו-`%`, `_` ו-`*` הם לא תווים כלליים. שלחו את אותו `q` עם כל `cursor`.

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

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

<TypeTable
type={{
limit: { type: 'number', description: "גודל העמוד, עד 500." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
}}
/>

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

### get(id)‎
מחזירה מיקום אחד, עם `pendingJobs`, הווריאנטים שעדיין נוצרים, ועם `previousCandidates`, עד 5 מועמדים קודמים לתמונה הראשית, מהחדש לישן. כדי לקבוע אחד מהם כתמונה הראשית, קראו ל-`approveMainImage()`.

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

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

```ts
const location = await client.locations.get(locationId)
console.log(location.sourceImageUrl, location.weather, location.atmosphereMotions)
```

מיקום שבארכיון עדיין חוזר לפי המזהה, ולכן רכיבי תהליך שמצביעים עליו ממשיכים להיטען. אובייקט `Location` כולל את התמונה הראשית ב-`sourceImageUrl`, שש קבוצות של וריאנטים (`timeOfDay`, `weather`, `seasons`, `angles`, `lighting` ו-`atmosphereMotions`, כל אחת רשימה של `{ name, url }`), `boards`, `referencePhotos`, `canonicalDescription`, `styleLock` ו-`updatedAt`.

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

```ts
create(input: CreateLocationInput): 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: 'string', description: "הקטגוריה, כגון urban או nature." },
style: { type: 'string', description: "הסגנון החזותי, כגון realistic או anime." },
projectId: { type: 'string', description: "הפרויקט שבו לשמור את המיקום." },
workflowId: { type: 'string', description: "התהליך שהמיקום מגיע ממנו." },
sourceImageUrl: { type: 'string', description: "כתובת התמונה הראשית." },
imageProvider: { type: 'string | null', description: "מודל התמונה שבו נוצרה התמונה הראשית." },
referencePhotos: { type: 'LocationReferencePhoto[]', description: "תמונות מודבורד, עד 20, כל אחת בצורה ‎{ url, kind }‎. הערך של kind הוא wide, interior, exterior, detail, moodBoard או other." },
canonicalDescription: { type: 'string', description: "התיאור שמשמש בפרומפטים." },
styleLock: { type: 'boolean', description: "שמירה על הווריאנטים בסגנון המאושר של המיקום." },
}}
/>

```ts
const { id: locationId } = await client.locations.create({
nodeId: "mcp-managed",
name: "Rainy Tokyo Alley",
description: "Neon-soaked alley with vending machines",
category: "urban",
style: "realistic",
})
```

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

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

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

```ts
await client.locations.update(locationId, {
canonicalDescription: "A narrow, rain-soaked alley lit by neon signs",
styleLock: false,
piiConsentAt: new Date().toISOString(),
expectedUpdatedAt: location.updatedAt,
})
```

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

### delete(id)‎ ו-restore(id)‎
המתודה `delete()` מעבירה מיקום לארכיון, והמתודה `restore()` מחזירה אותו מהארכיון. מחיקה לצמיתות זמינה רק באפליקציה של Nodaro. כששם של מיקום משוחזר זהה לשם של מיקום פעיל, בלי הבדל בין אותיות גדולות לקטנות, השרת מוסיף `(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.locations.delete(locationId)
const { name } = await client.locations.restore(locationId)
```

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

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

<TypeTable
type={{
name: { type: 'string', required: true, description: "שם המיקום." },
description: { type: 'string', description: "איך המקום נראה." },
userPrompt: { type: 'string', description: "הוראות נוספות." },
category: { type: '"indoor" | "outdoor" | "urban" | "nature" | "fantasy" | "sci-fi" | "historical" | "futuristic" | "other"', description: "הקטגוריה." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "הסגנון החזותי." },
sourceImageUrl: { type: 'string', description: "תמונה שהשוט יתבסס עליה." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
count: { type: 'number', description: "כמה מועמדים ליצור." },
quality: { type: 'string', description: "אחת מהרמות medium, high או basic, במודלים שיש להם הגדרת איכות. הערך משנה את המחיר." },
resolution: { type: 'string', description: "אחת מהרזולוציות 1K, 2K, 4K, 0.5 MP, 1 MP, 2 MP או 4 MP, במודלים שתומכים בכך. הערך משנה את המחיר." },
attachToLocationId: { type: 'string', description: "המיקום שאליו נכתבת תוצאה יחידה." },
}}
/>

```ts
// One candidate, written to the location when it completes
const { jobIds: [jobId] } = await client.locations.generate({
name: "Rainy Tokyo Alley",
description: "Neon-soaked alley with vending machines",
attachToLocationId: locationId,
})

// Four candidates to choose from
const { jobIds } = await client.locations.generate({ name: "Rainy Tokyo Alley", count: 4 })
```

עם `attachToLocationId` וכש-`count` הוא 1, התוצאה הופכת לתמונה הראשית כשהמשימה מסתיימת. אחרת, בחרו מועמד עם `approveMainImage()`. התמחור של `quality` ושל `resolution` הוא כמו ברכיב [יצירת תמונה](https://nodaro.ai/docs/nodes/image/generate-image). גם ערך מהרשימה שהמודל לא תומך בו לא נדחה. Nodaro משתמש במקומו בערך שהמודל תומך בו, או משמיט את ההגדרה כשאין למודל הגדרה כזו. הקרדיטים נקבעים לפי הערך שרץ בפועל, והשדה `input_data` של המשימה מציג אותו.

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

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

<TypeTable
type={{
assetType: { type: '"timeOfDay" | "weather" | "seasons" | "angles" | "lighting" | "custom"', required: true, description: "סוג הווריאנט." },
variant: { type: 'string', required: true, description: "הווריאנט, כגון golden hour, storm, winter, aerial או neon." },
name: { type: 'string', required: true, description: "שם המיקום." },
description: { type: 'string', description: "התיאור של המיקום." },
userPrompt: { type: 'string', description: "הוראות נוספות." },
sourceImageUrl: { type: 'string', description: "התמונה שממנה מתחילים. בדרך כלל התמונה הראשית." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16"', description: "יחס הגובה־רוחב של התמונה." },
quality: { type: 'string', description: "כמו ב-generate()‎. הערך משנה את המחיר." },
resolution: { type: 'string', description: "כמו ב-generate()‎. הערך משנה את המחיר." },
attachToLocationId: { type: 'string', description: "המיקום שאליו התוצאה תתווסף." },
attachToColumn: { type: 'string', description: "הקבוצה: time_of_day, weather, seasons, angles, lighting, atmosphere_motions, sheets או detail_closeups." },
attachName: { type: 'string', description: "השם של הפריט החדש." },
}}
/>

```ts
const { jobId } = await client.locations.generateAsset({
name: "Rainy Tokyo Alley",
assetType: "weather",
variant: "storm",
attachToLocationId: locationId,
attachToColumn: "weather",
attachName: "storm",
})
```

### generateSurroundContinuation(input)‎
יוצרת את התצוגה הבאה בטבעת של 360 מעלות (`POST /v1/generate-surround-continuation`), בהמשך לתצוגה הקודמת. השרת משאיר חצי מהתצוגה הקודמת בדיוק כמו שהוא, משלים את החצי השני ומתאים את הצבעים שלו, כך שתצוגות סמוכות מתחברות זו לזו בלי תפר. המתודה הזו עובדת ב-Nodaro Cloud; במהדורות אחרות הנתיב מחזיר `403 edition_required` לפני כל עיבוד, וה-SDK זורק את התשובה הזו כ-`ForbiddenError`.

```ts
generateSurroundContinuation(input: GenerateSurroundContinuationInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
referenceImageUrl: { type: 'string', required: true, description: "התצוגה הקודמת בטבעת." },
direction: { type: '"right" | "up" | "down"', required: true, description: "לאיזה כיוון הטבעת ממשיכה." },
degrees: { type: 'number', description: "בכמה מעלות התצוגה מסתובבת, כגון 45." },
carriedFraction: { type: 'number', default: '0.5', description: "כמה מהתצוגה הקודמת מועבר בלי שינוי." },
userPrompt: { type: 'string', description: "הוראות נוספות." },
provider: { type: 'string', description: "מזהה מודל התמונה." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16"', description: "יחס הגובה־רוחב של התמונה." },
attachToLocationId: { type: 'string', description: "המיקום שאליו התצוגה תתווסף." },
attachToColumn: { type: 'string', description: "הקבוצה. האולפן משתמש ב-angles." },
attachName: { type: 'string', description: "השם של הפריט החדש, כגון Surround 45°." },
}}
/>

```ts
const { jobId } = await client.locations.generateSurroundContinuation({
referenceImageUrl: previousRingView,
direction: "right",
degrees: 45,
provider: "nano-banana-pro",
aspectRatio: "16:9",
attachToLocationId: locationId,
attachToColumn: "angles",
attachName: "Surround 45°",
})
```

### generateMotion(input)‎
מנפישה את התמונה של המיקום לקליפ אווירה (`POST /v1/generate-location-motion`), בעזרת רכיב [יצירת וידאו](https://nodaro.ai/docs/nodes/video/generate-video) במצב תמונה לווידאו. למיקום יש קבוצת תנועה אחת, ולכן הקליפ תמיד נכנס ל-`atmosphereMotions` ואין `attachToColumn`.

```ts
generateMotion(input: GenerateLocationMotionInput): 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', description: "מזהה מודל הווידאו, כגון kling." },
category: { type: 'string', description: "הקטגוריה." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "הסגנון החזותי." },
canonicalDescription: { type: 'string', description: "התיאור של המיקום." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16"', description: "יחס הגובה־רוחב של הקליפ." },
attachToLocationId: { type: 'string', description: "המיקום שאליו הקליפ יתווסף." },
attachName: { type: 'string', description: "השם של הקליפ החדש." },
}}
/>

```ts
const { jobId } = await client.locations.generateMotion({
name: "Rainy Tokyo Alley",
motionPrompt: "Slow dolly-in, neon signs flicker, light rain falling",
sourceImageUrl: location.sourceImageUrl!,
provider: "kling",
attachToLocationId: locationId,
attachName: "neon dolly-in",
})
```

### removeAsset(id, data)‎
מסירה תוצאה אחת מקבוצת וריאנטים (`POST /v1/locations/:id/remove-asset`). כל הפריטים עם ה-`url` הזה נמחקים בצעד אחד. אפשר להשתמש בה, למשל, לפני שיוצרים מחדש תצוגה במבט של 360 מעלות.

```ts
removeAsset(id: string, data: { column: LocationAttachColumn; url: string }): Promise<{ removed: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה המיקום." },
column: { type: 'LocationAttachColumn', required: true, description: "הקבוצה, כגון angles או weather." },
url: { type: 'string', required: true, description: "הכתובת של התוצאה שמסירים." },
}}
/>

```ts
await client.locations.removeAsset(locationId, { column: "angles", url: oldViewUrl })
```

המתודה זורקת `NotFoundError` כשהכתובת לא נמצאת בקבוצה הזו או כשהמיקום לא שייך לכם.

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

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

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה המיקום." },
candidateJobId: { type: 'string', required: true, description: "מזהה המשימה של המועמד שהושלם." },
}}
/>

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

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

### recaption(id)‎
כותבת מחדש את התיאור של המיקום מהתמונה הראשית הנוכחית שלו.

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

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

```ts
const { canonicalDescription } = await client.locations.recaption(locationId)
```

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

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

### איך יוצרים מיקום עם ה-SDK של Nodaro?

קראו למתודה client.locations.create עם nodeId ועם שם, צרו שוטים פותחים עם המתודה client.locations.generate ואשרו אחד מהם עם המתודה client.locations.approveMainImage.

### אילו וריאנטים אפשר ליצור למיקום?

המתודה generateAsset יוצרת וריאנטים של שעות היום, מזג האוויר, העונות, הזוויות והתאורה, וגם וריאנטים מותאמים אישית, והמתודה generateMotion יוצרת קליפי אווירה. ב-Nodaro Cloud, המתודה generateSurroundContinuation מוסיפה תצוגות למבט של 360 מעלות.

### איך נמנעים מדריסה של מיקום שמישהו אחר שינה?

העבירו למתודה client.locations.update את השדה expectedUpdatedAt, כלומר את ערך ה-updatedAt שקיבלתם. אם המיקום השתנה, הקריאה נכשלת עם 409 concurrent_modification. טענו את המיקום שוב, מזגו את השינויים ונסו שוב.

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

לא. המתודה delete מעבירה את המיקום לארכיון, והמתודה restore מחזירה אותו מהארכיון. מחיקה לצמיתות זמינה רק באפליקציה של Nodaro.
