راهنمای جامع APIهای هوش مصنوعی داخلی مرورگر

Chrome (Gemini Nano) & Microsoft Edge (Phi-4-mini)

📦 تمام نمونه کدهای آموزش

برای دسترسی آسان به تمام نمونه کدهای این آموزش، روی لینک‌های زیر کلیک کنید:

۰۰. دانلود مدل
مشاهده دمو →
۰۱. بررسی Availability (کنسول)
مشاهده دمو →
۰۱. بررسی Availability (UI)
مشاهده دمو →
۰۲. Prompt (کنسول)
مشاهده دمو →
۰۲. Prompt (UI)
مشاهده دمو →
۰۳. Streaming (کنسول)
مشاهده دمو →
۰۳. Streaming (UI)
مشاهده دمو →
۰۳. Streaming (Markdown)
مشاهده دمو →
۰۳. Streaming با Abort
مشاهده دمو →
۰۳. Streaming با Abort v2
مشاهده دمو →
۰۴. Clone Session
مشاهده دمو →
۰۵. Few-Shot (کنسول)
مشاهده دمو →
۰۵. Few-Shot (UI)
مشاهده دمو →
۰۶. Structured Output (کنسول)
مشاهده دمو →
۰۶. Structured Output (UI)
مشاهده دمو →
۰۶. تحلیل احساسات فیلم
مشاهده دمو →
۰۶. Structured Output
مشاهده دمو →
۰۷. Multimodal VLM (کنسول)
مشاهده دمو →
۰۷. Multimodal VLM (کنسول 2)
مشاهده دمو →
۰۷. Multimodal VLM (UI)
مشاهده دمو →

۱. مقدمه

ظهور هوش مصنوعی روی دستگاه (on-device AI) یک تغییر استراتژیک در توسعه وب مدرن را رقم زده است. با انتقال پردازش‌های هوش مصنوعی از سرورهای ابری به دستگاه کاربر، توسعه‌دهندگان اکنون می‌توانند برنامه‌هایی بسازند که سریع‌تر، خصوصی‌تر و مقرون‌به‌صرفه‌تر هستند. APIهای هوش مصنوعی داخلی، مجموعه‌ای از رابط‌های برنامه‌نویسی استاندارد هستند که به وب‌سایت‌ها و برنامه‌های وب اجازه می‌دهند تا از قابلیت‌های مدل‌های زبان بزرگ (LLM) که مستقیماً در مرورگر تعبیه شده‌اند، بهره‌مند شوند. این مدل‌ها، مانند Gemini Nano در کروم و Phi-4-mini در اج، بدون نیاز به ارسال داده‌ها به سرورهای خارجی، وظایف پیچیده‌ای را انجام می‌دهند.

۱.۱. مزایای کلیدی

این رویکرد مزایای کلیدی زیر را به همراه دارد:

این راهنما چهار API اصلی در این مجموعه را پوشش می‌دهد: Prompt API، Summarizer API، Writer API، Rewriter API، Translator API و Language Detector API. در ادامه، به بررسی مفاهیم اصلی و مراحل راه‌اندازی مورد نیاز برای پیاده‌سازی این ابزارهای قدرتمند در هر دو مرورگر خواهیم پرداخت.

فهرست مطالب

۲. مدل‌های زبانی در مرورگرها

۲.۱. Gemini Nano Chrome

Gemini Nano مدل زبانی قدرتمند Google است که در Chrome تعبیه شده است. این مدل برای اجرا بر روی دستگاه‌های کاربر بهینه شده و قابلیت‌های پیشرفته‌ای در پردازش متن و تصویر دارد.

۲.۲. Phi-4-mini Edge

Phi-4-mini مدل زبانی کوچک (SLM) Microsoft است که در Edge تعبیه شده است. این مدل با 3.8 میلیارد پارامتر، در وظایف مبتنی بر متن عملکرد فوق‌العاده‌ای دارد و برای تجربیات هوش مصنوعی بلادرنگ و کارآمد طراحی شده است.

اطلاعات بیشتر:

۳. الزامات سخت‌افزاری

۳.۱. Chrome

نکته: برای بررسی اندازه دقیق مدل، به آدرس chrome://on-device-internals بروید.

۳.۲. Microsoft Edge

نکته: برای بررسی کلاس عملکرد دستگاه در Edge، به آدرس edge://on-device-internals بروید. اگر کلاس عملکرد High یا بالاتر باشد، APIها باید پشتیبانی شوند.

۴. نصب و فعال‌سازی

۴.۱. فعال‌سازی در Chrome

مرحله ۱: نصب Chrome Canary یا Dev

ابتدا نسخه آخر Chrome Canary یا Chrome Dev را نصب کنید.

مرحله ۲: فعال‌سازی فلگ‌ها

به آدرس chrome://flags/ بروید و فلگ‌های زیر را فعال کنید:

فعال‌سازی اصلی API (چندزبانه):

chrome://flags/#prompt-api-for-gemini-nano

مقدار آن را به "Enabled Multilingual" تغییر دهید.

(اختیاری) فعال‌سازی ورودی چندوجهی (تصویر و صوت):

chrome://flags/#prompt-api-for-gemini-nano-multimodal-input

مقدار آن را به "Enabled" تغییر دهید.

مرحله ۳: راه‌اندازی مجدد

Chrome را مجدداً راه‌اندازی کنید تا تغییرات اعمال شوند.

۴.۲. فعال‌سازی در Microsoft Edge

مرحله ۱: نصب Edge Canary یا Dev

نسخه 138.0.3309.2 یا جدیدتر Microsoft Edge Canary یا Dev را نصب کنید.

مرحله ۲: فعال‌سازی فلگ‌ها

به آدرس edge://flags/ بروید و فلگ‌های مورد نیاز را فعال کنید:

Prompt API:

edge://flags/#edge-llm-prompt-api-for-phi-mini

Summarizer API:

edge://flags/#edge-llm-summarization-api-for-phi-mini

Writer API:

edge://flags/#edge-llm-writer-api-for-phi-mini

Rewriter API:

edge://flags/#edge-llm-rewriter-api-for-phi-mini

مرحله ۳: راه‌اندازی مجدد

Edge را مجدداً راه‌اندازی کنید.

۵. Prompt API Chrome Edge

Prompt API قدرتمندترین و انعطاف‌پذیرترین API در این مجموعه است که به شما امکان می‌دهد با مدل زبانی به صورت مستقیم تعامل کنید.

۵.۱. بررسی دسترسی به API و دانلود مدل

if (!LanguageModel) {
  // Prompt API در دسترس نیست
  console.log("API not available");
} else {
  // Prompt API در دسترس است
  console.log("API available");
}

📝 نمونه کدهای مرتبط:

۵.۲. بررسی دسترسی به مدل

const availability = await LanguageModel.availability();

if (availability === "unavailable") {
  // مدل در دسترس نیست
  console.log("Model unavailable");
}

if (availability === "downloadable" || availability === "downloading") {
  // مدل قابل استفاده است اما ابتدا باید دانلود شود
  console.log("Model needs download");
}

if (availability === "available") {
  // مدل در دسترس است و می‌تواند استفاده شود
  console.log("Model ready");
}

۵.۳. دریافت پارامترهای مدل

برای مشاهده پارامترهای پیش‌فرض و حداکثر مدل:

const params = await LanguageModel.params();
console.log(params);
// {
//   defaultTopK: 3,
//   maxTopK: 128,
//   defaultTemperature: 1,
//   maxTemperature: 2
// }

۵.۴. ایجاد Session با مانیتور دانلود

const session = await LanguageModel.create({
  monitor(m) {
    m.addEventListener('downloadprogress', (e) => {
      const percentage = (e.loaded / e.total) * 100;
      console.log(`دانلود: ${percentage.toFixed(1)}%`);
    });
  }
});

۵.۵. پرامپت کردن مدل

پاسخ غیر استریمینگ:

const session = await LanguageModel.create();
const result = await session.prompt('Write me a poem about AI!');
console.log(result);

📝 نمونه کدهای مرتبط:

پاسخ استریمینگ:

const session = await LanguageModel.create();
const stream = session.promptStreaming('Write me a long story!');

for await (const chunk of stream) {
  console.log(chunk);
}

📝 نمونه کدهای Streaming:

۵.۶. استفاده از Initial Prompts

برای ارائه زمینه از تعاملات قبلی:

const session = await LanguageModel.create({
  initialPrompts: [
    { 
      role: 'system', 
      content: 'You are a helpful and friendly assistant.' 
    },
    { 
      role: 'user', 
      content: 'What is the capital of Italy?' 
    },
    { 
      role: 'assistant', 
      content: 'The capital of Italy is Rome.' 
    },
    { 
      role: 'user', 
      content: 'What language is spoken there?' 
    },
    {
      role: 'assistant',
      content: 'The official language of Italy is Italian.'
    }
  ]
});

۵.۷. N-shot Prompting (Few-Shot Learning)

برای آموزش الگو به مدل از طریق مثال‌ها:

const session = await LanguageModel.create({
  initialPrompts: [
    { role: "system", content: "Classify reviews as OK or Not OK." },
    { role: "user", content: "Great product! Very happy." },
    { role: "assistant", content: "OK" },
    { role: "user", content: "Terrible quality. Waste of money." },
    { role: "assistant", content: "Not OK" }
  ]
});

📝 نمونه کدهای Few-Shot:

۵.۸. محدود کردن پاسخ با Prefix

برای هدایت مدل به استفاده از فرمت خاص:

const characterSheet = await session.prompt([
  {
    role: 'user',
    content: 'Create a TOML character sheet for a gnome barbarian'
  },
  {
    role: 'assistant',
    content: '```toml\n',
    prefix: true  // این باعث می‌شود مدل با این فرمت ادامه دهد
  }
]);

۵.۹. اضافه کردن پیام‌ها با append()

برای ارسال پیام‌های از پیش تعیین شده قبل از پرامپت اصلی:

const session = await LanguageModel.create({
  initialPrompts: [{
    role: 'system',
    content: 'You are a skilled analyst who correlates patterns across multiple images.'
  }],
  expectedInputs: [{ type: 'image' }]
});

// بعداً، وقتی کاربر فایلی آپلود کرد
fileUpload.onchange = async () => {
  await session.append([{
    role: 'user',
    content: [
      {
        type: 'text',
        value: `Here's one image. Notes: ${fileNotesInput.value}`
      },
      { type: 'image', value: fileUpload.files[0] }
    ]
  }]);
};

// و سپس تحلیل
analyzeButton.onclick = async () => {
  const result = await session.prompt(userQuestionInput.value);
  console.log(result);
};

۵.۱۰. بررسی محدودیت‌های Session

// بررسی مصرف توکن
console.log(`استفاده شده: ${session.inputUsage} از ${session.inputQuota}`);

۵.۱۱. استفاده از JSON Schema (Structured Output)

const session = await LanguageModel.create();

const schema = {
  "type": "object",
  "required": ["sentiment", "confidence"],
  "properties": {
    "sentiment": {
      "type": "string",
      "enum": ["positive", "negative", "neutral"]
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    }
  }
};

const response = await session.prompt(
  "The food was delicious, service was excellent!",
  {
    initialPrompts: [{
      role: "system",
      content: "Analyze sentiment and return JSON with sentiment and confidence."
    }],
    responseConstraint: schema
  }
);

const { sentiment, confidence } = JSON.parse(response);
console.log(`احساس: ${sentiment}, اطمینان: ${confidence}`);

📝 نمونه کدهای Structured Output:

۵.۱۲. توقف تولید متن

const controller = new AbortController();
const session = await LanguageModel.create();

stopButton.onclick = () => controller.abort();

const result = await session.prompt('Write me a poem!', {
  signal: controller.signal
});

۵.۱۳. Clone کردن Session

const firstSession = await LanguageModel.create({
  initialPrompts: [{
    role: "system",
    content: "You are a helpful assistant."
  }]
});

// Clone با حفظ تنظیمات اولیه
const secondSession = await firstSession.clone();

📝 نمونه کد Clone Session:

۵.۱۴. نابود کردن Session

// روش اول
session.destroy();

// روش دوم با AbortController
const controller = new AbortController();
const session = await LanguageModel.create({ 
  signal: controller.signal 
});
controller.abort();

۵.۱۵. قابلیت‌های چندوجهی (Multimodal) Chrome

Prompt API در Chrome از ورودی تصویر و صوت پشتیبانی می‌کند:

استفاده از تصویر:

const session = await LanguageModel.create({
  expectedInputs: [{ type: 'image' }]
});

const referenceImage = await (await fetch('/reference-image.jpeg')).blob();
const userDrawnImage = document.querySelector('canvas');

const response = await session.prompt([
  {
    role: 'user',
    content: [
      {
        type: 'text',
        value: 'Compare these two images:'
      },
      { type: 'image', value: referenceImage },
      { type: 'image', value: userDrawnImage }
    ]
  }
]);

console.log(response);

استفاده از صوت:

const session = await LanguageModel.create({
  expectedInputs: [{ type: 'audio' }]
});

const audioBlob = await captureMicrophoneInput({ seconds: 10 });

const response = await session.prompt([
  {
    role: 'user',
    content: [
      { type: 'text', value: 'Transcribe this audio:' },
      { type: 'audio', value: audioBlob }
    ]
  }
]);

console.log(response);

📝 نمونه کدهای Multimodal (VLM):

۶. Writing Assistance APIs Edge

Writing Assistance APIs شامل سه API است که برای وظایف خاص نوشتاری بهینه شده‌اند و فقط در Microsoft Edge در دسترس هستند.

۶.۱. Summarizer API

برای خلاصه‌سازی متن در سبک‌ها و طول‌های مختلف.

بررسی دسترسی:

if (!Summarizer) {
  console.log("Summarizer API not available");
}

const availability = await Summarizer.availability();
console.log(availability); // "unavailable", "downloadable", "downloading", "available"

ایجاد Session و خلاصه‌سازی:

const summarizerSession = await Summarizer.create({
  sharedContext: "An article from the Daily Economic News magazine",
  type: "tl;dr",
  length: "short",
  format: "markdown"
});

const summary = await summarizerSession.summarize(articleText, {
  context: "This article was written on 2024-08-07"
});

console.log(summary);

گزینه‌های Summarizer:

گزینه مقادیر ممکن توضیحات
type "tl;dr", "key-points", "teaser", "headline" نوع خلاصه
length "short", "medium", "long" طول خلاصه
format "plain-text", "markdown" فرمت خروجی
sharedContext String زمینه مشترک

خلاصه‌سازی استریمینگ:

const session = await Summarizer.create({ type: "key-points" });
const stream = session.summarizeStreaming(longText);

for await (const chunk of stream) {
  console.log(chunk);
}

۶.۲. Writer API

برای نوشتن متن جدید بر اساس پرامپت.

استفاده پایه:

if (!Writer) {
  console.log("Writer API not available");
}

const writer = await Writer.create({ 
  tone: "formal",
  length: "medium"
});

const result = await writer.write(
  "A draft for an inquiry to my bank about enabling wire transfers"
);

console.log(result);

گزینه‌های Writer:

گزینه مقادیر ممکن توضیحات
tone "formal", "neutral", "casual" لحن متن
length "short", "medium", "long" طول متن
format "plain-text", "markdown" فرمت خروجی

۶.۳. Rewriter API

برای بازنویسی و اصلاح متن موجود.

استفاده پایه:

if (!Rewriter) {
  console.log("Rewriter API not available");
}

const rewriter = await Rewriter.create({ 
  sharedContext: "A user review for shoes",
  tone: "more-formal"
});

const result = await rewriter.rewrite(originalText, { 
  context: "Remove harsh language while preserving the core feedback"
});

console.log(result);

گزینه‌های Rewriter:

گزینه مقادیر ممکن توضیحات
tone "as-is", "more-formal", "more-casual" تغییر لحن
length "as-is", "shorter", "longer" تغییر طول
format "as-is", "plain-text", "markdown" تغییر فرمت

۷. Translator API Chrome Desktop

Translator API امکان ترجمه متن بین زبان‌های مختلف را فراهم می‌کند. این API فقط در نسخه دسکتاپ Chrome کار می‌کند.

توجه: Translator API در حال حاضر فقط در Chrome روی دسکتاپ (Windows، macOS، Linux) کار می‌کند و در دستگاه‌های موبایل پشتیبانی نمی‌شود.

۷.۱. بررسی دسترسی و قابلیت ترجمه

if (!translation || !translation.createTranslator) {
  console.log("Translator API not available");
}

// بررسی پشتیبانی از یک جفت زبان خاص
const canTranslate = await translation.canTranslate({
  sourceLanguage: 'en',
  targetLanguage: 'fa'
});

console.log(canTranslate);
// "readily" - آماده استفاده فوری
// "after-download" - نیاز به دانلود دارد
// "no" - پشتیبانی نمی‌شود

۷.۲. ایجاد Translator

const translator = await translation.createTranslator({
  sourceLanguage: 'en',
  targetLanguage: 'fa'
});

const translatedText = await translator.translate("Hello, how are you?");
console.log(translatedText); // "سلام، حال شما چطور است؟"

۷.۳. ترجمه استریمینگ

const translator = await translation.createTranslator({
  sourceLanguage: 'en',
  targetLanguage: 'fa'
});

const stream = translator.translateStreaming("A very long text to be translated...");

for await (const chunk of stream) {
  console.log(chunk);
}

۸. Language Detector API Chrome Desktop

Language Detector API برای شناسایی زبان یک قطعه متن استفاده می‌شود. این API معمولاً به عنوان مکمل Translator API کار می‌کند.

۸.۱. استفاده از Language Detector

if (!translation || !translation.createDetector) {
  console.log("Language Detector API not available");
}

// ایجاد detector
const detector = await translation.createDetector();

// شناسایی زبان
const results = await detector.detect("Hallo und herzlich willkommen!");

// نتایج بر اساس اطمینان مرتب شده‌اند
if (results.length > 0) {
  const topResult = results[0];
  console.log(`زبان: ${topResult.detectedLanguage}`);
  console.log(`اطمینان: ${topResult.confidence.toFixed(4)}`);
}
// خروجی: زبان: de (آلمانی)
هشدار: دقت این API برای متون بسیار کوتاه و کلمات منفرد پایین است. اگر با متون کوتاه کار می‌کنید، میزان اطمینان را بررسی کنید و در صورتی که خیلی پایین بود، نتیجه را نامشخص در نظر بگیرید.

۸.۲. ترکیب Detector و Translator

// شناسایی زبان متن
const detector = await translation.createDetector();
const results = await detector.detect(userInput);

if (results.length > 0 && results[0].confidence > 0.5) {
  const sourceLanguage = results[0].detectedLanguage;
  
  // بررسی امکان ترجمه
  const canTranslate = await translation.canTranslate({
    sourceLanguage: sourceLanguage,
    targetLanguage: 'fa'
  });
  
  if (canTranslate !== 'no') {
    // ایجاد مترجم
    const translator = await translation.createTranslator({
      sourceLanguage: sourceLanguage,
      targetLanguage: 'fa'
    });
    
    // ترجمه متن
    const translatedText = await translator.translate(userInput);
    console.log(translatedText);
  }
}

۹. مباحث پیشرفته و بهترین شیوه‌ها

۹.۱. مدیریت خطاها

خطاهای رایج که ممکن است با آن‌ها مواجه شوید:

۹.۲. بهترین شیوه‌ها

۹.۳. Permission Policy و iframes

به صورت پیش‌فرض، این APIها فقط برای پنجره‌های سطح بالا و iframe‌های هم‌منشأ آن‌ها در دسترس است. برای دسترسی در iframe‌های cross-origin:

<!-- سایت اصلی در https://main.example.com -->
<iframe 
  src="https://cross-origin.example.com/" 
  allow="language-model"
></iframe>

۹.۴. نمونه کامل: برنامه چت هوشمند

class AIChat {
  constructor() {
    this.session = null;
    this.messageHistory = [];
  }

  async initialize() {
    // بررسی دسترسی
    if (!LanguageModel) {
      throw new Error("API not available");
    }

    // بررسی وضعیت مدل
    const availability = await LanguageModel.availability();
    if (availability === "unavailable") {
      throw new Error("Model not available");
    }

    // ایجاد session با مانیتور
    this.session = await LanguageModel.create({
      initialPrompts: [{
        role: 'system',
        content: 'You are a helpful AI assistant. Keep answers concise.'
      }],
      topK: 5,
      temperature: 0.7,
      monitor: (m) => {
        m.addEventListener('downloadprogress', (e) => {
          const progress = (e.loaded / e.total) * 100;
          this.onDownloadProgress(progress);
        });
      }
    });

    console.log("AI Chat initialized");
  }

  async sendMessage(userMessage) {
    if (!this.session) {
      throw new Error("Session not initialized");
    }

    // ذخیره پیام کاربر
    this.messageHistory.push({
      role: 'user',
      content: userMessage
    });

    try {
      // ارسال پرامپت و دریافت پاسخ
      const response = await this.session.prompt(userMessage);

      // ذخیره پاسخ
      this.messageHistory.push({
        role: 'assistant',
        content: response
      });

      // بررسی محدودیت
      const usage = this.session.inputUsage;
      const quota = this.session.inputQuota;
      console.log(`Token usage: ${usage}/${quota}`);

      if (usage > quota * 0.8) {
        console.warn("Approaching token limit");
      }

      return response;
    } catch (error) {
      console.error("Error sending message:", error);
      throw error;
    }
  }

  async sendMessageStreaming(userMessage, onChunk) {
    if (!this.session) {
      throw new Error("Session not initialized");
    }

    const stream = this.session.promptStreaming(userMessage);
    let fullResponse = '';

    for await (const chunk of stream) {
      fullResponse += chunk;
      onChunk(chunk);
    }

    // ذخیره در تاریخچه
    this.messageHistory.push({
      role: 'user',
      content: userMessage
    });
    this.messageHistory.push({
      role: 'assistant',
      content: fullResponse
    });

    return fullResponse;
  }

  async resetConversation() {
    if (this.session) {
      // Clone برای حفظ تنظیمات اولیه
      const oldSession = this.session;
      this.session = await oldSession.clone();
      oldSession.destroy();
      this.messageHistory = [];
      console.log("Conversation reset");
    }
  }

  destroy() {
    if (this.session) {
      this.session.destroy();
      this.session = null;
      this.messageHistory = [];
      console.log("AI Chat destroyed");
    }
  }

  onDownloadProgress(percentage) {
    console.log(`Model download: ${percentage.toFixed(1)}%`);
  }
}

// استفاده
const chat = new AIChat();

// مقداردهی اولیه
await chat.initialize();

// ارسال پیام
const response = await chat.sendMessage("What is artificial intelligence?");
console.log(response);

// ارسال پیام استریمینگ
await chat.sendMessageStreaming(
  "Tell me a story",
  (chunk) => {
    process.stdout.write(chunk); // نمایش به محض تولید
  }
);

// ریست مکالمه
await chat.resetConversation();

// در پایان
chat.destroy();

۱۰. نتیجه‌گیری

APIهای هوش مصنوعی داخلی مرورگر، چه در Chrome با Gemini Nano و چه در Microsoft Edge با Phi-4-mini، امکانات بی‌نظیری را برای توسعه‌دهندگان وب فراهم می‌کنند. این APIها با ارائه قابلیت‌هایی از جمله:

مسیر جدیدی را برای ساخت برنامه‌های وب هوشمند، کارآمد و کاربرپسند باز کرده‌اند.

یادآوری مهم: این APIها هنوز در مرحله پیش‌نمایش توسعه‌دهندگان هستند و ممکن است در آینده تغییراتی داشته باشند. برای به‌روزرسانی‌ها و اطلاعات جدید، منابع رسمی را دنبال کنید.

۱۰.۱. مقایسه Chrome و Edge

ویژگی Chrome (Gemini Nano) Edge (Phi-4-mini)
Prompt API
Summarizer API ✅ (در حال توسعه)
Writer API ✅ (در حال توسعه)
Rewriter API ✅ (در حال توسعه)
Translator API ✅ (فقط دسکتاپ) ❌ (در حال توسعه)
Language Detector ✅ (فقط دسکتاپ) ❌ (در حال توسعه)
ورودی چندوجهی (تصویر/صوت) ❌ (در حال توسعه)
پشتیبانی موبایل ❌ (در حال توسعه)

۱۰.۲. منابع و لینک‌های مفید

Chrome / Gemini Nano:

Microsoft Edge / Phi-4-mini:

W3C و استانداردها:

مشارکت در توسعه: شما می‌توانید با ارسال بازخورد، گزارش باگ و پیشنهادات خود به بهبود این APIها و شکل‌گیری استانداردهای وب کمک کنید. از لینک‌های Feedback در بالا استفاده کنید.
تشکر: این راهنما بر اساس مستندات رسمی Chrome و Microsoft Edge تهیه شده است. تمام نمونه کدها از منابع رسمی اخذ شده‌اند.