هوش مصنوعی در Flutter؛ آموزش ساخت اپلیکیشن AI با Dart و API درواره

در این آموزش عملی، از هوش مصنوعی برای کدنویسی Flutter استفاده می‌کنیم و یک اپلیکیشن چت واقعی با Dart، معماری MVVM، Backend پایتون و API درواره برای Android، iOS و Web می‌سازیم.

Share
هوش مصنوعی در Flutter؛ آموزش ساخت اپلیکیشن AI با Dart و API درواره

Flutter یکی از محبوب‌ترین فریم‌ورک‌ها برای ساخت اپلیکیشن‌های چندسکویی است. با یک کدبیس مشترک می‌توانید برای Android، iOS، وب، ویندوز، macOS و لینوکس اپلیکیشن بسازید. ترکیب Flutter با مدل‌های هوش مصنوعی این امکان را فراهم می‌کند که محصولاتی مانند دستیار هوشمند، چت‌بات، خلاصه‌ساز، مترجم، تولیدکننده محتوا و تحلیل‌گر متن را با رابط کاربری یکپارچه روی چند پلتفرم ارائه کنید.

عبارت «هوش مصنوعی در Flutter» معمولاً به دو کاربرد متفاوت اشاره دارد:

  1. استفاده از ابزارهای هوش مصنوعی برای نوشتن، توضیح، دیباگ و تست کدهای Flutter و Dart
  2. اتصال اپلیکیشن Flutter به مدل‌های هوش مصنوعی از طریق API

در کاربرد اول، هوش مصنوعی نقش دستیار برنامه‌نویس را دارد. در کاربرد دوم، هوش مصنوعی یکی از قابلیت‌های اصلی محصول شما است و کاربران اپلیکیشن مستقیماً از آن استفاده می‌کنند.

در این راهنمای عملی هر دو مسیر را بررسی می‌کنیم و سپس یک اپلیکیشن چت کامل می‌سازیم که:

  • با Flutter و Dart توسعه داده می‌شود.
  • روی Android، iOS و Web قابل اجرا است.
  • معماری قابل توسعه دارد.
  • پیام کاربر را به Backend اختصاصی می‌فرستد.
  • Backend به API هوش مصنوعی درواره متصل می‌شود.
  • پاسخ مدل را در رابط چت نمایش می‌دهد.
  • وضعیت Loading، خطا و ارسال دوباره را مدیریت می‌کند.
  • کلید API را داخل اپلیکیشن قرار نمی‌دهد.

Flutter چیست؟

Flutter فریم‌ورک توسعه رابط کاربری است که با زبان Dart کار می‌کند. در Flutter، رابط کاربری از Widgetها ساخته می‌شود و تغییر State باعث بازسازی بخش‌های وابسته رابط می‌شود.

مهم‌ترین مزایای Flutter عبارت‌اند از:

  • یک کدبیس برای چند پلتفرم
  • توسعه سریع رابط کاربری
  • Hot Reload
  • Widgetهای قابل ترکیب
  • پشتیبانی از Material و Cupertino
  • اکوسیستم گسترده Packageها
  • عملکرد مناسب برای اپلیکیشن‌های موبایل
  • امکان توسعه Web و Desktop

Flutter برای ساخت رابط اپلیکیشن بسیار مناسب است؛ اما اجرای مدل‌های زبانی بزرگ معمولاً روی خود دستگاه انجام نمی‌شود. اپلیکیشن از طریق شبکه به Backend متصل می‌شود و Backend درخواست را برای مدل ارسال می‌کند.

هوش مصنوعی چه کاربردی در توسعه Flutter دارد؟

هوش مصنوعی می‌تواند در مراحل مختلف توسعه Flutter به برنامه‌نویس کمک کند.

مرحلهکاربرد هوش مصنوعی
تحلیل نیازمندیتبدیل ایده به صفحه، Feature و User Flow
طراحی پروژهپیشنهاد معماری و ساختار پوشه‌ها
ساخت رابطتولید Widget و Layout
مدیریت Stateپیشنهاد ViewModel، Provider، Riverpod یا BLoC
شبکهتولید Service و مدل JSON
دیباگتحلیل Exception و Stack Trace
تستتولید Unit Test و Widget Test
ریفکتورتقسیم Widgetهای بزرگ و کاهش تکرار
مستندسازیتولید README و Doc Comment
انتشارتهیه چک‌لیست Build و تنظیمات محیط

نمونه پرامپت برای تولید صفحه Flutter:

یک صفحه چت با Flutter و Material 3 بساز.

نیازمندی‌ها:
- پیام‌های کاربر در سمت راست باشند.
- پیام‌های دستیار در سمت چپ باشند.
- متن فارسی و RTL پشتیبانی شود.
- پیام‌ها با ListView.builder نمایش داده شوند.
- پایین صفحه TextField و دکمه ارسال باشد.
- هنگام دریافت پاسخ Loading نمایش داده شود.
- رابط از Dark Mode پشتیبانی کند.
- منطق شبکه داخل Widget قرار نگیرد.

ابزارهای هوش مصنوعی مناسب برنامه‌نویسی Flutter

برای توسعه Flutter می‌توانید از گروه‌های مختلف ابزارهای هوش مصنوعی استفاده کنید.

دستیارهای گفت‌وگویی

مانند ChatGPT، Claude و Gemini برای این کارها مناسب‌اند:

  • یادگیری Dart
  • توضیح Widgetها
  • تحلیل خطا
  • طراحی معماری
  • مقایسه روش‌های مدیریت State
  • تولید نمونه‌کد
  • نوشتن تست

ابزارهای داخل IDE

ابزارهایی مانند GitHub Copilot، Cursor، Cline، Roo Code و سایر Coding Agentها می‌توانند با Context بیشتری از پروژه کار کنند:

  • تکمیل کد در فایل
  • ویرایش چند فایل
  • تحلیل ساختار پروژه
  • تولید تست
  • اصلاح Importها
  • اجرای Command
  • بررسی خطاهای Analyzer
  • ساخت Feature چندمرحله‌ای

API هوش مصنوعی

API زمانی استفاده می‌شود که می‌خواهید قابلیت هوش مصنوعی را به خود اپلیکیشن اضافه کنید.

برای مثال:

  • دستیار پاسخ‌گویی
  • تولید متن
  • خلاصه‌سازی
  • استخراج اطلاعات
  • تحلیل بازخورد
  • ترجمه
  • اصلاح نگارش
  • ساخت برنامه روزانه
  • پاسخ‌گویی براساس اسناد

آیا می‌توان مستقیماً Flutter را به API هوش مصنوعی متصل کرد؟

از نظر فنی می‌توانید یک درخواست HTTP مستقیم از Flutter ارسال کنید، اما قرار‌دادن کلید API در اپلیکیشن منتشرشده روش مناسبی نیست.

اگر کلید در این محل‌ها قرار بگیرد، همچنان امکان استخراج آن وجود دارد:

  • فایل Dart
  • فایل .env داخل اپلیکیشن
  • dart-define
  • Native Code
  • فایل Asset
  • Build Configuration
  • APK یا IPA

معماری پیشنهادی:

اپلیکیشن Flutter
        ↓
Backend اختصاصی شما
        ↓
API هوش مصنوعی درواره
        ↓
مدل انتخابی

Backend مسئول نگهداری کلید، احراز هویت، محدودیت مصرف، مدیریت هزینه و اعتبارسنجی درخواست‌ها است.

معماری پیشنهادی اپلیکیشن Flutter

مستندات رسمی Flutter برای پروژه‌های قابل توسعه، جداسازی مسئولیت‌ها و معماری لایه‌ای را توصیه می‌کند. در الگوی پیشنهادی، View و ViewModel در لایه UI و Repository و Service در لایه داده قرار می‌گیرند. راهنمای رسمی معماری Flutter

در پروژه این مقاله از ساختار زیر استفاده می‌کنیم:

lib/
├── main.dart
├── models/
│   └── chat_message.dart
├── services/
│   └── chat_api_service.dart
├── repositories/
│   └── chat_repository.dart
├── view_models/
│   └── chat_view_model.dart
└── views/
    ├── chat_page.dart
    └── widgets/
        ├── message_bubble.dart
        └── message_input.dart

مسئولیت اجزا:

جزءمسئولیت
Modelنمایش ساختار داده
Serviceارتباط مستقیم با Backend
Repositoryمنبع داده و مدیریت نتیجه Service
ViewModelمدیریت State و منطق رابط
Viewنمایش State و دریافت Event کاربر
Widgetاجزای قابل استفاده مجدد رابط

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

پیش‌نیازهای آموزش

برای اجرای پروژه به این موارد نیاز دارید:

  • Flutter SDK
  • Dart SDK همراه Flutter
  • Android Studio یا VS Code
  • Android Emulator، دستگاه واقعی یا مرورگر
  • Python برای Backend
  • حساب درواره
  • کلید API درواره
  • Model ID یکی از مدل‌های متنی

برای شروع به درواره مراجعه کنید. شناسه مدل‌ها و قیمت به‌روز آن‌ها در صفحه مدل‌های درواره قرار دارد.

در کدهای مقاله از این مقادیر استفاده می‌کنیم:

YOUR_DARVAREH_API_KEY
YOUR_MODEL_ID

نصب و بررسی Flutter

پس از نصب Flutter، دستور زیر را اجرا کنید:

flutter doctor

این دستور وضعیت Flutter SDK، Android Toolchain، مرورگر، IDE و دستگاه‌های متصل را بررسی می‌کند.

سپس دستگاه‌ها را نمایش دهید:

flutter devices

نسخه Flutter:

flutter --version

اگر flutter doctor خطایی نمایش می‌دهد، قبل از ادامه موارد اصلی Android Toolchain یا SDK را برطرف کنید.

ساخت پروژه Flutter

دستور زیر را اجرا کنید:

flutter create darvareh_ai_app
cd darvareh_ai_app

پروژه را در VS Code باز کنید:

code .

یا پوشه را از Android Studio باز کنید.

اجرای اولیه:

flutter run

پیش از اضافه‌کردن قابلیت هوش مصنوعی، مطمئن شوید پروژه نمونه بدون خطا اجرا می‌شود.

افزودن Packageهای موردنیاز

برای ارتباط HTTP و مدیریت ساده State، Packageهای زیر را اضافه کنید:

flutter pub add http
flutter pub add provider

در این مقاله از Package رسمی و رایج http برای درخواست شبکه استفاده می‌کنیم. راهنمای Flutter نیز برای ارسال داده به سرور، اضافه‌کردن http، ارسال درخواست و تبدیل پاسخ JSON به Object را توضیح می‌دهد. آموزش رسمی ارسال داده در Flutter

بخش اول: ساخت Backend با FastAPI

ابتدا Backend را ایجاد می‌کنیم تا کلید درواره خارج از اپلیکیشن باقی بماند.

پوشه Backend:

mkdir flutter-ai-backend
cd flutter-ai-backend

ساخت محیط مجازی:

python -m venv .venv

فعال‌سازی در ویندوز:

.venv\Scripts\Activate.ps1

فعال‌سازی در لینوکس و macOS:

source .venv/bin/activate

نصب وابستگی‌ها:

pip install fastapi uvicorn openai python-dotenv

فایل .env:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL=YOUR_MODEL_ID
DARVAREH_BASE_URL=https://api.darvareh.ir/v1

فایل .gitignore:

.env
.venv/
__pycache__/
*.pyc

فایل main.py:

import os

from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from openai import (
    APIConnectionError,
    AuthenticationError,
    OpenAI,
    RateLimitError,
)
from pydantic import BaseModel, Field

load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model_id = os.getenv("DARVAREH_MODEL")
base_url = os.getenv(
    "DARVAREH_BASE_URL",
    "https://api.darvareh.ir/v1",
)

if not api_key:
    raise RuntimeError(
        "DARVAREH_API_KEY is not configured."
    )

if not model_id:
    raise RuntimeError(
        "DARVAREH_MODEL is not configured."
    )

client = OpenAI(
    api_key=api_key,
    base_url=base_url,
    timeout=30.0,
    max_retries=2,
)

app = FastAPI(
    title="Flutter AI Backend",
    version="1.0.0",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://localhost:5000",
    ],
    allow_credentials=False,
    allow_methods=["GET", "POST"],
    allow_headers=["Content-Type"],
)


class ChatRequest(BaseModel):
    message: str = Field(
        min_length=1,
        max_length=4000,
    )


class ChatResponse(BaseModel):
    answer: str


@app.get("/health")
def health():
    return {
        "status": "ok",
    }


@app.post(
    "/chat",
    response_model=ChatResponse,
)
def chat(request: ChatRequest):
    try:
        response = client.chat.completions.create(
            model=model_id,
            messages=[
                {
                    "role": "system",
                    "content": (
                        "تو یک دستیار فارسی دقیق و کاربردی هستی. "
                        "پاسخ را متناسب با سؤال کاربر بنویس. "
                        "اگر اطلاعات کافی نیست، آن را اعلام کن."
                    ),
                },
                {
                    "role": "user",
                    "content": request.message,
                },
            ],
            temperature=0.3,
            max_tokens=800,
        )

        answer = response.choices[0].message.content

        if not answer:
            raise HTTPException(
                status_code=502,
                detail=(
                    "مدل پاسخ قابل استفاده‌ای تولید نکرد."
                ),
            )

        return ChatResponse(answer=answer)

    except AuthenticationError:
        raise HTTPException(
            status_code=500,
            detail=(
                "تنظیمات سرویس هوش مصنوعی نامعتبر است."
            ),
        )

    except RateLimitError:
        raise HTTPException(
            status_code=429,
            detail=(
                "تعداد درخواست‌ها بیش از حد مجاز است."
            ),
        )

    except APIConnectionError:
        raise HTTPException(
            status_code=503,
            detail=(
                "ارتباط با سرویس هوش مصنوعی برقرار نشد."
            ),
        )

اجرای Backend:

uvicorn main:app --reload --host 0.0.0.0 --port 8000

بررسی سلامت:

http://127.0.0.1:8000/health

مستندات تعاملی:

http://127.0.0.1:8000/docs

نمونه Request:

{
  "message": "Flutter را در یک پاراگراف معرفی کن."
}

نمونه Response:

{
  "answer": "Flutter فریم‌ورکی برای ساخت اپلیکیشن‌های چندسکویی با زبان Dart است."
}

تنظیم CORS برای Flutter Web

CORS بیشتر برای نسخه وب اهمیت دارد. در محیط Production نباید هر Origin را بدون محدودیت مجاز کنید.

در مثال قبلی فقط چند Origin محلی قرار داده شده‌اند:

allow_origins=[
    "http://localhost:3000",
    "http://localhost:5000",
]

پورت واقعی اجرای Flutter Web ممکن است متفاوت باشد. هنگام اجرا، آدرس نمایش‌داده‌شده در ترمینال را بررسی و همان Origin را به تنظیمات Development اضافه کنید.

برای اجرای روی یک پورت مشخص:

flutter run -d chrome --web-port 5000

در این حالت Origin محلی برابر است با:

http://localhost:5000

بخش دوم: Permission اینترنت در Android

فایل زیر را باز کنید:

android/app/src/main/AndroidManifest.xml

Permission اینترنت را اضافه کنید:

<uses-permission android:name="android.permission.INTERNET" />

نمونه:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:label="darvareh_ai_app"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <!-- Flutter activity -->

    </application>

</manifest>

ارتباط HTTP محلی در Android

برای اجرای Development با HTTP محلی ممکن است لازم باشد در تگ application این گزینه را موقتاً اضافه کنید:

android:usesCleartextTraffic="true"

نمونه:

<application
    android:usesCleartextTraffic="true"
    android:label="darvareh_ai_app"
    ...>

این گزینه برای Production مناسب نیست. در نسخه نهایی، Backend باید HTTPS داشته باشد و Cleartext Traffic غیرفعال شود.

آدرس Backend در هر پلتفرم

آدرس localhost در تمام پلتفرم‌ها رفتار یکسانی ندارد.

محیط اجراآدرس معمول Backend محلی
Android Emulatorhttp://10.0.2.2:8000
iOS Simulatorhttp://127.0.0.1:8000
Flutter Webhttp://127.0.0.1:8000
Windows Desktophttp://127.0.0.1:8000
macOS Desktophttp://127.0.0.1:8000
دستگاه واقعیIP شبکه کامپیوتر یا دامنه HTTPS

در Android Emulator، این آدرس اشتباه است:

http://127.0.0.1:8000

زیرا به خود Emulator اشاره می‌کند. از این آدرس استفاده کنید:

http://10.0.2.2:8000

بخش سوم: ساخت مدل پیام

فایل زیر را ایجاد کنید:

lib/models/chat_message.dart

محتوا:

enum MessageRole {
  user,
  assistant,
}

class ChatMessage {
  const ChatMessage({
    required this.id,
    required this.text,
    required this.role,
    required this.createdAt,
  });

  final String id;
  final String text;
  final MessageRole role;
  final DateTime createdAt;

  bool get isUser => role == MessageRole.user;
}

بخش چهارم: ساخت Chat API Service

فایل:

lib/services/chat_api_service.dart

کد:

import 'dart:convert';

import 'package:http/http.dart' as http;

class ChatApiException implements Exception {
  const ChatApiException(this.message);

  final String message;

  @override
  String toString() => message;
}


class ChatApiService {
  ChatApiService({
    required this.baseUrl,
    http.Client? client,
  }) : _client = client ?? http.Client();

  final String baseUrl;
  final http.Client _client;

  Future<String> sendMessage(String message) async {
    final uri = Uri.parse('$baseUrl/chat');

    try {
      final response = await _client
          .post(
            uri,
            headers: const {
              'Content-Type':
                  'application/json; charset=UTF-8',
            },
            body: jsonEncode({
              'message': message,
            }),
          )
          .timeout(
            const Duration(seconds: 40),
          );

      final decodedBody = _decodeBody(response.body);

      if (response.statusCode == 200) {
        final answer = decodedBody['answer'];

        if (answer is! String || answer.trim().isEmpty) {
          throw const ChatApiException(
            'پاسخ سرور معتبر نیست.',
          );
        }

        return answer.trim();
      }

      final detail = decodedBody['detail'];

      if (detail is String && detail.isNotEmpty) {
        throw ChatApiException(detail);
      }

      throw ChatApiException(
        'خطای سرور با کد ${response.statusCode}',
      );
    } on ChatApiException {
      rethrow;
    } on FormatException {
      throw const ChatApiException(
        'پاسخ سرور قابل پردازش نیست.',
      );
    } on Exception {
      throw const ChatApiException(
        'ارتباط با سرور برقرار نشد.',
      );
    }
  }

  Map<String, dynamic> _decodeBody(String body) {
    if (body.trim().isEmpty) {
      return {};
    }

    final decoded = jsonDecode(body);

    if (decoded is! Map<String, dynamic>) {
      throw const FormatException(
        'Invalid response format',
      );
    }

    return decoded;
  }

  void dispose() {
    _client.close();
  }
}

چرا Timeout تعریف کردیم؟

درخواست مدل ممکن است چند ثانیه طول بکشد. اگر هیچ Timeoutی نداشته باشیم، برنامه می‌تواند مدت زیادی منتظر بماند.

.timeout(
  const Duration(seconds: 40),
)

مقدار مناسب به مدل، طول پاسخ، نوع درخواست و شرایط شبکه بستگی دارد. Timeout بسیار کوتاه باعث شکست درخواست‌های سالم و Timeout بسیار بلند باعث تجربه کاربری نامناسب می‌شود.

بخش پنجم: ساخت Repository

فایل:

lib/repositories/chat_repository.dart

کد:

import '../services/chat_api_service.dart';

abstract interface class ChatRepository {
  Future<String> sendMessage(String message);
}


class RemoteChatRepository
    implements ChatRepository {
  RemoteChatRepository({
    required ChatApiService service,
  }) : _service = service;

  final ChatApiService _service;

  @override
  Future<String> sendMessage(
    String message,
  ) {
    return _service.sendMessage(message);
  }
}

Repository به ViewModel اجازه می‌دهد بدون وابستگی مستقیم به HTTP با داده کار کند. در تست نیز می‌توانیم نسخه Fake آن را جایگزین کنیم.

بخش ششم: ساخت ViewModel

فایل:

lib/view_models/chat_view_model.dart

کد:

import 'package:flutter/foundation.dart';

import '../models/chat_message.dart';
import '../repositories/chat_repository.dart';

class ChatViewModel extends ChangeNotifier {
  ChatViewModel({
    required ChatRepository repository,
  }) : _repository = repository;

  final ChatRepository _repository;

  final List<ChatMessage> _messages = [];

  List<ChatMessage> get messages =>
      List.unmodifiable(_messages);

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _errorMessage;
  String? get errorMessage => _errorMessage;

  Future<void> sendMessage(
    String rawMessage,
  ) async {
    final message = rawMessage.trim();

    if (message.isEmpty || _isLoading) {
      return;
    }

    _errorMessage = null;

    _messages.add(
      ChatMessage(
        id: _createId(),
        text: message,
        role: MessageRole.user,
        createdAt: DateTime.now(),
      ),
    );

    _isLoading = true;
    notifyListeners();

    try {
      final answer =
          await _repository.sendMessage(message);

      _messages.add(
        ChatMessage(
          id: _createId(),
          text: answer,
          role: MessageRole.assistant,
          createdAt: DateTime.now(),
        ),
      );
    } on Exception catch (error) {
      _errorMessage = error.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  void clearError() {
    if (_errorMessage == null) {
      return;
    }

    _errorMessage = null;
    notifyListeners();
  }

  void clearConversation() {
    if (_isLoading) {
      return;
    }

    _messages.clear();
    _errorMessage = null;
    notifyListeners();
  }

  String _createId() {
    return DateTime
        .now()
        .microsecondsSinceEpoch
        .toString();
  }
}

بخش هفتم: ساخت Message Bubble

فایل:

lib/views/widgets/message_bubble.dart

کد:

import 'package:flutter/material.dart';

import '../../models/chat_message.dart';

class MessageBubble extends StatelessWidget {
  const MessageBubble({
    required this.message,
    super.key,
  });

  final ChatMessage message;

  @override
  Widget build(BuildContext context) {
    final colorScheme =
        Theme.of(context).colorScheme;

    final alignment = message.isUser
        ? Alignment.centerRight
        : Alignment.centerLeft;

    final backgroundColor = message.isUser
        ? colorScheme.primaryContainer
        : colorScheme.surfaceContainerHighest;

    return Align(
      alignment: alignment,
      child: Container(
        constraints: const BoxConstraints(
          maxWidth: 560,
        ),
        margin: const EdgeInsets.symmetric(
          vertical: 5,
        ),
        padding: const EdgeInsets.symmetric(
          horizontal: 14,
          vertical: 11,
        ),
        decoration: BoxDecoration(
          color: backgroundColor,
          borderRadius: BorderRadius.circular(18),
        ),
        child: SelectableText(
          message.text,
          textDirection: TextDirection.rtl,
          style: Theme.of(context)
              .textTheme
              .bodyLarge,
        ),
      ),
    );
  }
}

از SelectableText استفاده کرده‌ایم تا کاربر بتواند پاسخ را انتخاب و کپی کند.

بخش هشتم: ساخت Message Input

فایل:

lib/views/widgets/message_input.dart

کد:

import 'package:flutter/material.dart';

class MessageInput extends StatefulWidget {
  const MessageInput({
    required this.isLoading,
    required this.onSend,
    super.key,
  });

  final bool isLoading;
  final Future<void> Function(String message)
      onSend;

  @override
  State<MessageInput> createState() =>
      _MessageInputState();
}


class _MessageInputState
    extends State<MessageInput> {
  final _controller = TextEditingController();

  bool get _canSend {
    return _controller.text.trim().isNotEmpty &&
        !widget.isLoading;
  }

  Future<void> _submit() async {
    final message = _controller.text.trim();

    if (message.isEmpty || widget.isLoading) {
      return;
    }

    _controller.clear();
    setState(() {});

    await widget.onSend(message);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      top: false,
      child: Material(
        color: Theme.of(context)
            .colorScheme
            .surface,
        elevation: 8,
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            crossAxisAlignment:
                CrossAxisAlignment.end,
            children: [
              Expanded(
                child: TextField(
                  controller: _controller,
                  enabled: !widget.isLoading,
                  textDirection:
                      TextDirection.rtl,
                  minLines: 1,
                  maxLines: 5,
                  onChanged: (_) {
                    setState(() {});
                  },
                  onSubmitted: (_) {
                    if (_canSend) {
                      _submit();
                    }
                  },
                  decoration:
                      const InputDecoration(
                    hintText:
                        'پیام خود را بنویسید...',
                    border: OutlineInputBorder(),
                  ),
                ),
              ),
              const SizedBox(width: 8),
              IconButton.filled(
                onPressed:
                    _canSend ? _submit : null,
                tooltip: 'ارسال',
                icon: widget.isLoading
                    ? const SizedBox(
                        width: 20,
                        height: 20,
                        child:
                            CircularProgressIndicator(
                          strokeWidth: 2,
                        ),
                      )
                    : const Icon(Icons.send),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

بخش نهم: ساخت صفحه Chat

فایل:

lib/views/chat_page.dart

کد:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../view_models/chat_view_model.dart';
import 'widgets/message_bubble.dart';
import 'widgets/message_input.dart';

class ChatPage extends StatefulWidget {
  const ChatPage({super.key});

  @override
  State<ChatPage> createState() =>
      _ChatPageState();
}


class _ChatPageState extends State<ChatPage> {
  final _scrollController =
      ScrollController();

  void _scrollToBottom() {
    WidgetsBinding.instance
        .addPostFrameCallback((_) {
      if (!_scrollController.hasClients) {
        return;
      }

      _scrollController.animateTo(
        _scrollController
            .position
            .maxScrollExtent,
        duration:
            const Duration(milliseconds: 250),
        curve: Curves.easeOut,
      );
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final viewModel =
        context.watch<ChatViewModel>();

    _scrollToBottom();

    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'دستیار هوشمند',
        ),
        actions: [
          IconButton(
            onPressed: viewModel.isLoading
                ? null
                : viewModel.clearConversation,
            tooltip: 'پاک‌کردن گفتگو',
            icon: const Icon(
              Icons.delete_outline,
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: viewModel.messages.isEmpty
                ? const _EmptyConversation()
                : ListView.builder(
                    controller:
                        _scrollController,
                    padding:
                        const EdgeInsets.all(
                      12,
                    ),
                    itemCount:
                        viewModel.messages.length,
                    itemBuilder: (
                      context,
                      index,
                    ) {
                      return MessageBubble(
                        message:
                            viewModel
                                .messages[index],
                      );
                    },
                  ),
          ),
          if (viewModel.errorMessage != null)
            _ErrorBanner(
              message:
                  viewModel.errorMessage!,
              onClose:
                  viewModel.clearError,
            ),
          MessageInput(
            isLoading:
                viewModel.isLoading,
            onSend:
                viewModel.sendMessage,
          ),
        ],
      ),
    );
  }
}


class _EmptyConversation
    extends StatelessWidget {
  const _EmptyConversation();

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              Icons.auto_awesome,
              size: 48,
              color: Theme.of(context)
                  .colorScheme
                  .primary,
            ),
            const SizedBox(height: 16),
            Text(
              'گفتگو را شروع کنید',
              style: Theme.of(context)
                  .textTheme
                  .headlineSmall,
            ),
            const SizedBox(height: 8),
            const Text(
              'سؤال خود را به زبان فارسی بنویسید.',
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}


class _ErrorBanner
    extends StatelessWidget {
  const _ErrorBanner({
    required this.message,
    required this.onClose,
  });

  final String message;
  final VoidCallback onClose;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Theme.of(context)
          .colorScheme
          .errorContainer,
      child: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: 12,
          vertical: 8,
        ),
        child: Row(
          children: [
            Expanded(
              child: Text(
                message,
                textDirection:
                    TextDirection.rtl,
              ),
            ),
            IconButton(
              onPressed: onClose,
              icon: const Icon(Icons.close),
            ),
          ],
        ),
      ),
    );
  }
}

بخش دهم: تنظیم فایل main.dart

فایل lib/main.dart:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'repositories/chat_repository.dart';
import 'services/chat_api_service.dart';
import 'view_models/chat_view_model.dart';
import 'views/chat_page.dart';

void main() {
  final baseUrl =
      _resolveDevelopmentBaseUrl();

  final service = ChatApiService(
    baseUrl: baseUrl,
  );

  final repository = RemoteChatRepository(
    service: service,
  );

  runApp(
    DarvarehAiApp(
      repository: repository,
    ),
  );
}


String _resolveDevelopmentBaseUrl() {
  if (kIsWeb) {
    return 'http://127.0.0.1:8000';
  }

  switch (defaultTargetPlatform) {
    case TargetPlatform.android:
      return 'http://10.0.2.2:8000';

    case TargetPlatform.iOS:
    case TargetPlatform.macOS:
    case TargetPlatform.windows:
    case TargetPlatform.linux:
      return 'http://127.0.0.1:8000';

    default:
      return 'http://127.0.0.1:8000';
  }
}


class DarvarehAiApp
    extends StatelessWidget {
  const DarvarehAiApp({
    required this.repository,
    super.key,
  });

  final ChatRepository repository;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ChatViewModel(
        repository: repository,
      ),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Darvareh AI App',
        themeMode: ThemeMode.system,
        theme: ThemeData(
          colorScheme:
              ColorScheme.fromSeed(
            seedColor:
                const Color(0xFF6750A4),
            brightness: Brightness.light,
          ),
          useMaterial3: true,
        ),
        darkTheme: ThemeData(
          colorScheme:
              ColorScheme.fromSeed(
            seedColor:
                const Color(0xFF8B7CF6),
            brightness: Brightness.dark,
          ),
          useMaterial3: true,
        ),
        home: const Directionality(
          textDirection: TextDirection.rtl,
          child: ChatPage(),
        ),
      ),
    );
  }
}

اجرای اپلیکیشن

ابتدا Backend را اجرا کنید:

uvicorn main:app --reload --host 0.0.0.0 --port 8000

سپس در پروژه Flutter:

flutter pub get
flutter run

اجرای نسخه وب:

flutter run -d chrome --web-port 5000

اجرای روی Emulator مشخص:

flutter devices
flutter run -d DEVICE_ID

پس از اجرا یک پیام فارسی بفرستید. اپلیکیشن باید پیام را نمایش دهد، وارد حالت Loading شود و پاسخ Backend را در سمت مقابل نشان دهد.

خطاهای رایج اتصال Flutter به Backend

خطای SocketException

دلایل احتمالی:

  • Backend اجرا نشده است.
  • Base URL اشتباه است.
  • پورت اشتباه است.
  • Android Emulator به localhost متصل شده است.
  • Firewall مانع اتصال شده است.
  • دستگاه واقعی و کامپیوتر در یک شبکه نیستند.

خطای Cleartext Traffic

در Android Development، تنظیم زیر را بررسی کنید:

android:usesCleartextTraffic="true"

برای Production از HTTPS استفاده کنید.

خطای CORS در Flutter Web

نشانه خطا معمولاً در Console مرورگر دیده می‌شود. Origin واقعی Flutter Web را به تنظیمات CORS Backend اضافه کنید.

از این تنظیم عمومی در Production استفاده نکنید:

allow_origins=["*"]

Originهای واقعی وب‌سایت خود را به‌صورت دقیق تعریف کنید.

خطای 422

Backend انتظار این ساختار را دارد:

{
  "message": "متن پیام"
}

نام فیلد، نوع داده و خالی‌نبودن پیام را بررسی کنید.

خطای 401 یا 500 Backend

کلید درواره باید در فایل .env سمت Backend تنظیم شود:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY

کلید را داخل Flutter قرار ندهید.

خطای Model Not Found

مقدار زیر را با شناسه واقعی جایگزین کنید:

DARVAREH_MODEL=YOUR_MODEL_ID

Model ID را از صفحه مدل‌های درواره دریافت کنید.

نمایش نشدن پاسخ فارسی

بررسی کنید:

  • پاسخ با UTF-8 پردازش شود.
  • از Font سازگار با فارسی استفاده شود.
  • TextDirection.rtl در بخش مناسب فعال باشد.
  • JSON به‌درستی Decode شود.

نگهداری تاریخچه مکالمه

پروژه فعلی پیام‌ها را فقط در حافظه اپلیکیشن نگه می‌دارد. پس از بسته‌شدن برنامه، تاریخچه از بین می‌رود.

برای ذخیره‌سازی محلی می‌توانید از ابزارهایی مانند این‌ها استفاده کنید:

  • SQLite
  • Drift
  • Isar
  • Hive
  • SharedPreferences برای تنظیمات ساده

اما برای همگام‌سازی چند دستگاه بهتر است تاریخچه روی Backend ذخیره شود.

مدل پیشنهادی Backend:

Conversation
- id
- user_id
- title
- created_at
- updated_at

Message
- id
- conversation_id
- role
- content
- created_at

در این حالت اپلیکیشن همراه پیام، conversation_id را نیز می‌فرستد.

ارسال تاریخچه به مدل

برای مکالمه چندمرحله‌ای، Backend باید پیام‌های مرتبط را برای مدل ارسال کند:

messages = [
    {
        "role": "system",
        "content": "تو یک دستیار فارسی هستی.",
    },
    {
        "role": "user",
        "content": "Flutter چیست؟",
    },
    {
        "role": "assistant",
        "content": "Flutter یک فریم‌ورک چندسکویی است.",
    },
    {
        "role": "user",
        "content": "چه زبانی استفاده می‌کند؟",
    },
]

تاریخچه نباید بدون محدودیت رشد کند. برای مدیریت Context:

  • فقط پیام‌های اخیر را ارسال کنید.
  • پیام‌های قدیمی را خلاصه کنید.
  • پیام‌های نامرتبط را حذف کنید.
  • برای هر گفتگو سقف مصرف تعریف کنید.
  • تعداد توکن را ثبت کنید.

اضافه‌کردن Streaming

در نسخه فعلی پاسخ پس از کامل‌شدن نمایش داده می‌شود. برای تجربه مشابه چت زنده می‌توانید Streaming را اضافه کنید.

روش‌های متداول:

  • Server-Sent Events
  • WebSocket
  • HTTP Stream

در Flutter می‌توانید Chunkهای دریافتی را به یک پیام در حال ساخت اضافه و notifyListeners را فراخوانی کنید.

ساختار State پیشنهادی:

class ChatMessage {
  const ChatMessage({
    required this.id,
    required this.text,
    required this.role,
    this.isStreaming = false,
  });

  final String id;
  final String text;
  final MessageRole role;
  final bool isStreaming;
}

در زمان Streaming، متن همان پیام دستیار به‌روزرسانی می‌شود. پس از دریافت پایان Stream، isStreaming به false تغییر می‌کند.

پیاده‌سازی Streaming باید این موارد را مدیریت کند:

  • قطع اتصال
  • دریافت Chunk ناقص
  • لغو درخواست
  • ارسال دوباره
  • ذخیره پیام نهایی
  • جلوگیری از اجرای هم‌زمان چند Stream
  • نمایش پاسخ ناقص در صورت قطع شبکه

ساخت خروجی JSON در اپلیکیشن

اگر قابلیت شما نیازمند داده ساختاریافته است، پاسخ آزاد متنی کافی نیست.

فرض کنید اپلیکیشن برنامه روزانه می‌سازد:

{
  "title": "برنامه امروز",
  "tasks": [
    {
      "title": "مطالعه Flutter",
      "duration_minutes": 45
    }
  ]
}

در این حالت باید:

  1. Schema خروجی را در Backend تعریف کنید.
  2. از مدل سازگار با Structured Output استفاده کنید.
  3. پاسخ را در Backend اعتبارسنجی کنید.
  4. فقط JSON معتبر را برای Flutter بفرستید.
  5. در Dart مدل داده مشخص بسازید.
  6. خطای Parse را مدیریت کنید.

به پاسخ مدل مستقیماً اعتماد نکنید. اعتبارسنجی باید پیش از ذخیره یا اجرای عملیات انجام شود.

تست Service در Flutter

با MockClient می‌توان درخواست شبکه را بدون Backend واقعی تست کرد.

نمونه مفهومی:

import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test(
    'returns answer when server responds 200',
    () async {
      final client = MockClient(
        (request) async {
          return http.Response(
            '{"answer":"پاسخ آزمایشی"}',
            200,
            headers: {
              'content-type':
                  'application/json',
            },
          );
        },
      );

      final service = ChatApiService(
        baseUrl: 'https://example.com',
        client: client,
      );

      final result =
          await service.sendMessage('سلام');

      expect(result, 'پاسخ آزمایشی');
    },
  );
}

سناریوهای مهم تست:

  • پاسخ موفق
  • پاسخ خالی
  • JSON نامعتبر
  • وضعیت 422
  • وضعیت 429
  • وضعیت 500
  • Timeout
  • قطع شبکه
  • پیام خطای Backend

تست ViewModel

نسخه Fake Repository:

class FakeChatRepository
    implements ChatRepository {
  FakeChatRepository({
    this.answer = 'پاسخ آزمایشی',
    this.error,
  });

  final String answer;
  final Exception? error;

  @override
  Future<String> sendMessage(
    String message,
  ) async {
    if (error != null) {
      throw error!;
    }

    return answer;
  }
}

نمونه تست:

import 'package:flutter_test/flutter_test.dart';

void main() {
  test(
    'adds user and assistant messages',
    () async {
      final repository =
          FakeChatRepository(
        answer: 'سلام! چطور کمک کنم؟',
      );

      final viewModel = ChatViewModel(
        repository: repository,
      );

      await viewModel.sendMessage('سلام');

      expect(viewModel.messages.length, 2);
      expect(
        viewModel.messages.first.isUser,
        isTrue,
      );
      expect(
        viewModel.messages.last.isUser,
        isFalse,
      );
      expect(viewModel.isLoading, isFalse);
      expect(viewModel.errorMessage, isNull);
    },
  );
}

استفاده از هوش مصنوعی برای دیباگ Flutter

برای دریافت پاسخ دقیق، اطلاعات کامل ارائه کنید.

پرامپت پیشنهادی:

در یک پروژه Flutter با خطای زیر مواجه شده‌ام.

محیط:
- Flutter version:
- Dart version:
- Platform:
- Device:
- State management:
- Packageهای مرتبط:

دستور اجرا:
flutter run

خطای کامل:
[STACK_TRACE]

کد Service:
[CODE]

کد ViewModel:
[CODE]

رفتار مورد انتظار:
پس از ارسال پیام، پاسخ Backend نمایش داده شود.

رفتار فعلی:
Loading متوقف نمی‌شود.

لطفاً:
1. علت‌های محتمل را به ترتیب احتمال بنویس.
2. مشخص کن کدام خطا Root Cause است.
3. برای هر فرضیه روش آزمایش ارائه کن.
4. حداقل Patch لازم را پیشنهاد بده.
5. تستی بنویس که مشکل را بازتولید کند.

پرامپت‌های آماده برای توسعه Flutter

ساخت Widget

یک Widget قابل استفاده مجدد برای نمایش پیام چت در Flutter بساز.

شرایط:
- از Material 3 استفاده کند.
- فارسی و RTL را پشتیبانی کند.
- متن قابل انتخاب باشد.
- پیام User و Assistant ظاهر متفاوت داشته باشند.
- Widget بدون وابستگی به ViewModel باشد.
- Widget Test نیز اضافه کن.

ساخت ViewModel

برای صفحه چت یک ViewModel با ChangeNotifier طراحی کن.

State:
- messages
- isLoading
- errorMessage

رفتار:
- پیام خالی ارسال نشود.
- ارسال هم‌زمان دو پیام مجاز نباشد.
- پیام کاربر بلافاصله نمایش داده شود.
- پاسخ Service به فهرست اضافه شود.
- خطا در State قرار بگیرد.

ViewModel نباید مستقیماً از http استفاده کند.

تحلیل معماری

ساختار این پروژه Flutter را بررسی کن.

مشخص کن:
- UI Logic کجا قرار دارد؟
- Data Source کدام است؟
- آیا Widgetها Business Logic دارند؟
- Source of Truth چیست؟
- وابستگی‌ها چگونه Inject می‌شوند؟
- کدام بخش‌ها تست‌پذیر نیستند؟

فعلاً کد را بازنویسی نکن.
مشکلات را براساس اثر و هزینه اصلاح اولویت‌بندی کن.

تولید تست

برای این ChatViewModel یک Test Matrix بنویس.

ستون‌ها:
- سناریو
- ورودی
- Fake یا Mock لازم
- State میانی
- State نهایی
- خطای مورد انتظار

پس از Test Matrix، تست‌های flutter_test را تولید کن.

کنترل هزینه API در اپلیکیشن Flutter

در یک اپلیکیشن عمومی، محدودیت‌های UI کافی نیستند. کاربر می‌تواند Client را تغییر دهد یا مستقیماً به Endpoint Backend درخواست بفرستد. تمام کنترل‌های اصلی باید روی سرور اعمال شوند.

راهکارهای مدیریت هزینه:

  • احراز هویت کاربران
  • محدودیت درخواست برای هر کاربر
  • سقف مصرف روزانه
  • محدودیت طول ورودی
  • محدودیت طول خروجی
  • انتخاب مدل متناسب با وظیفه
  • ثبت توکن و هزینه هر درخواست
  • Cache کردن پاسخ‌های تکراری
  • حذف Context غیرضروری
  • خلاصه‌سازی تاریخچه
  • جلوگیری از درخواست تکراری
  • بودجه کلی ماهانه
  • غیرفعال‌کردن قابلیت پس از عبور از سقف

قیمت مدل‌ها به نوع مدل و میزان مصرف بستگی دارد. برای اطلاعات به‌روز به صفحه مدل‌های درواره مراجعه کنید.

انتخاب مدل مناسب برای اپلیکیشن Flutter

کاربردمعیار انتخاب مدل
چت فارسیکیفیت زبان فارسی، سرعت و هزینه
خلاصه‌سازیContext Window و هزینه ورودی
تولید متن کوتاهسرعت و قیمت
برنامه‌نویسیتوانایی Coding
استخراج دادهثبات خروجی ساختاریافته
تحلیل تصویرپشتیبانی از Vision
دستیار چندمرحله‌ایReasoning و Tool Calling
اپ پرترافیکLatency، هزینه و پایداری

قبل از انتخاب نهایی:

  1. حداقل ۲۰ تا ۵۰ ورودی واقعی آماده کنید.
  2. چند مدل را روی داده یکسان آزمایش کنید.
  3. کیفیت، زمان و هزینه را ثبت کنید.
  4. خروجی‌های اشتباه را دسته‌بندی کنید.
  5. مدل را براساس نیاز واقعی انتخاب کنید.

کاربردهای عملی هوش مصنوعی در Flutter

اپلیکیشن دستیار فارسی

قابلیت‌ها:

  • گفت‌وگوی چندمرحله‌ای
  • ذخیره تاریخچه
  • انتخاب موضوع
  • پاسخ Streaming
  • کپی و اشتراک پاسخ

خلاصه‌ساز متن

ورودی:

  • مقاله
  • یادداشت
  • گزارش
  • متن جلسه

خروجی:

  • خلاصه کوتاه
  • نکات اصلی
  • اقدام‌های بعدی

دستیار تولید محتوا

کاربر می‌تواند موضوع، لحن و طول را انتخاب کند و خروجی‌هایی مانند عنوان، کپشن، توضیحات محصول یا ایمیل دریافت کند.

اپلیکیشن یادداشت هوشمند

امکانات:

  • اصلاح نگارش
  • دسته‌بندی یادداشت
  • استخراج کارها
  • ساخت عنوان
  • خلاصه‌سازی
  • تبدیل متن به چک‌لیست

تحلیل بازخورد مشتری

خروجی ساختاریافته:

{
  "sentiment": "negative",
  "topic": "delivery",
  "priority": "high",
  "summary": "سفارش با تأخیر تحویل شده است."
}

دستیار فروش

می‌تواند اطلاعات محصول را به توضیح فروش، مزایا، سؤال متداول و پاسخ پیشنهادی تبدیل کند.

چک‌لیست Production

Flutter

  • API Key داخل برنامه نباشد.
  • Base URL از تنظیمات محیط دریافت شود.
  • Backend از HTTPS استفاده کند.
  • Loading و Error کامل باشند.
  • Timeout مدیریت شود.
  • ارسال تکراری کنترل شود.
  • RTL آزمایش شود.
  • Dark Mode بررسی شود.
  • Widgetها روی صفحه‌های کوچک تست شوند.
  • متن طولانی رابط را خراب نکند.
  • وضعیت قطع اینترنت مشخص باشد.
  • Logها اطلاعات حساس نداشته باشند.

Backend

  • کلید فقط در متغیر محیطی باشد.
  • کاربران احراز هویت شوند.
  • Rate Limit اعمال شود.
  • ورودی اعتبارسنجی شود.
  • CORS فقط Originهای مجاز را بپذیرد.
  • Timeout و Retry محدود باشند.
  • هزینه هر کاربر ثبت شود.
  • پاسخ مدل اعتبارسنجی شود.
  • خطای داخلی مستقیماً افشا نشود.
  • مدل از تنظیمات قابل تغییر باشد.

هوش مصنوعی

  • Prompt نسخه‌بندی شود.
  • مدل روی داده واقعی ارزیابی شود.
  • طول Context محدود شود.
  • خروجی ساختاریافته Validate شود.
  • پاسخ‌های اشتباه ثبت و تحلیل شوند.
  • مدل اقتصادی برای وظایف ساده استفاده شود.
  • Fallback فقط در صورت نیاز طراحی شود.

اشتباهات رایج

قرار‌دادن کلید در فایل .env اپلیکیشن

Packageهای .env در Flutter مقدار را از فایل داخل برنامه می‌خوانند. این روش کلید را به Secret غیرقابل استخراج تبدیل نمی‌کند.

فراخوانی مستقیم API مدل از موبایل

این کار کنترل مصرف، تعویض کلید و جلوگیری از سوءاستفاده را دشوار می‌کند. از Backend استفاده کنید.

نگهداری تمام منطق در Widget

ارتباط شبکه و Business Logic را از View جدا کنید. Widget باید بیشتر مسئول نمایش State و ارسال Event باشد.

استفاده از یک Boolean برای تمام وضعیت‌ها

در پروژه بزرگ‌تر، State را دقیق‌تر مدل کنید:

enum ChatStatus {
  idle,
  sending,
  streaming,
  success,
  failure,
}

ارسال تاریخچه نامحدود

تاریخچه طولانی باعث افزایش زمان و هزینه می‌شود. پیام‌های قدیمی را خلاصه یا حذف کنید.

Retry در اپ و Backend

اجرای Retry در چند لایه می‌تواند یک درخواست را چند بار تکرار کند. یک سیاست مشخص برای Retry داشته باشید.

نمایش مستقیم خطای داخلی

متن خام Exception یا پاسخ سرویس بالادستی را مستقیماً به کاربر نمایش ندهید. پیام قابل فهم بسازید و جزئیات فنی را در Log کنترل‌شده ثبت کنید.

پرسش‌های متداول

هوش مصنوعی در Flutter چیست؟

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

آیا می‌توان با Flutter اپلیکیشن هوش مصنوعی ساخت؟

بله. Flutter رابط چندسکویی را می‌سازد و Backend از طریق API به مدل هوش مصنوعی متصل می‌شود.

آیا Flutter برای ساخت چت‌بات مناسب است؟

بله. Flutter برای ساخت رابط چت روی Android، iOS، وب و Desktop مناسب است. برای پاسخ‌گویی مدل به Backend و API نیاز دارید.

آیا باید کلید API درواره را در Flutter قرار دهیم؟

خیر. کلید باید فقط روی Backend نگهداری شود. اپلیکیشن Flutter به Backend اختصاصی شما متصل می‌شود.

Base URL درواره چیست؟

آدرس پایه API درواره:

https://api.darvareh.ir/v1

این آدرس همراه کلید در Backend تنظیم می‌شود.

Model ID را از کجا پیدا کنیم؟

فهرست مدل‌ها، شناسه آن‌ها و اطلاعات قیمت در صفحه مدل‌های درواره قرار دارد.

چرا Android Emulator به localhost متصل نمی‌شود؟

در Android Emulator، localhost به خود Emulator اشاره می‌کند. برای اتصال به Backend کامپیوتر معمولاً از این آدرس استفاده کنید:

http://10.0.2.2:8000

برای Flutter Web چرا خطای CORS می‌گیرم؟

مرورگر فقط به Originهای مجاز اجازه دسترسی می‌دهد. Origin اجرای Flutter Web باید در تنظیمات CORS Backend ثبت شده باشد.

آیا می‌توان پاسخ مدل را Streaming کرد؟

بله. می‌توانید از SSE، WebSocket یا HTTP Stream استفاده کنید و متن پیام دستیار را به‌تدریج در ViewModel به‌روزرسانی کنید.

بهترین State Management برای اپلیکیشن هوش مصنوعی چیست؟

انتخاب به اندازه پروژه و تجربه تیم بستگی دارد. ChangeNotifier برای آموزش و پروژه ساده مناسب است. Provider، Riverpod و BLoC نیز برای پروژه‌های بزرگ‌تر قابل استفاده‌اند. مهم‌تر از نام ابزار، داشتن State مشخص و جداسازی منطق از UI است.

آیا می‌توان یک کدبیس را برای Android و iOS استفاده کرد؟

بله، یکی از مزایای Flutter اشتراک بخش بزرگی از کد میان پلتفرم‌هاست. بااین‌حال تنظیمات شبکه، Permissionها و برخی قابلیت‌های Native باید برای هر پلتفرم جداگانه بررسی شوند.

چگونه هزینه اپلیکیشن را کنترل کنیم؟

محدودیت مصرف را در Backend اعمال کنید، طول ورودی و خروجی را محدود کنید، مدل مناسب انتخاب کنید، تاریخچه را مدیریت و مصرف هر کاربر را ثبت کنید.

آیا خروجی مدل را می‌توان مستقیماً در دیتابیس ذخیره کرد؟

ابتدا خروجی را Parse و اعتبارسنجی کنید. برای داده‌های ساختاریافته از Schema مشخص استفاده کنید و قواعد کسب‌وکار را نیز سمت Backend بررسی کنید.

جمع‌بندی

Flutter امکان ساخت رابط یکپارچه برای Android، iOS، وب و Desktop را فراهم می‌کند و API هوش مصنوعی قابلیت‌های هوشمند را به این رابط اضافه می‌کند.

در این مقاله یاد گرفتیم چگونه:

  • از هوش مصنوعی برای توسعه Flutter استفاده کنیم.
  • ساختار پروژه را به لایه‌های View، ViewModel، Repository و Service تقسیم کنیم.
  • Backend مبتنی بر FastAPI بسازیم.
  • Backend را به API درواره متصل کنیم.
  • با Package http از Flutter درخواست ارسال کنیم.
  • رابط چت فارسی و RTL بسازیم.
  • Loading، Error و Timeout را مدیریت کنیم.
  • تفاوت آدرس Backend در Android، iOS و Web را در نظر بگیریم.
  • از قرار‌دادن API Key در اپلیکیشن جلوگیری کنیم.
  • مسیر توسعه تاریخچه، Streaming و خروجی ساختاریافته را مشخص کنیم.

برای ساخت اولین اپلیکیشن هوش مصنوعی خود، در درواره ثبت‌نام کنید، کلید API بسازید و یک مدل متناسب با کیفیت، سرعت و بودجه پروژه از صفحه مدل‌ها و قیمت‌ها انتخاب کنید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

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

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.