flutter_deepseek 0.1.1 copy "flutter_deepseek: ^0.1.1" to clipboard
flutter_deepseek: ^0.1.1 copied to clipboard

DeepSeek API client for Flutter with SSE streaming, reasoner model, function calling, and typed errors. BYOK.

example/lib/main.dart

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

void main() {
  runApp(const DeepSeekDemoApp());
}

class DeepSeekDemoApp extends StatelessWidget {
  const DeepSeekDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'flutter_deepseek demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const ChatScreen(),
    );
  }
}

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

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final _apiKeyController = TextEditingController();
  final _inputController = TextEditingController();
  final _scrollController = ScrollController();

  DeepSeekClient? _client;
  String _selectedModel = DeepSeekModel.chat;
  bool _streaming = true;
  bool _loading = false;

  final List<_ChatBubble> _messages = [];

  @override
  void dispose() {
    _client?.dispose();
    _apiKeyController.dispose();
    _inputController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  DeepSeekClient _getClient() {
    final key = _apiKeyController.text.trim();
    _client ??= DeepSeekClient(apiKey: key);
    return _client!;
  }

  void _resetClient() {
    _client?.dispose();
    _client = null;
  }

  void _showError(Object error) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(error.toString()),
        backgroundColor: Colors.red.shade700,
      ),
    );
  }

  Future<void> _send() async {
    final key = _apiKeyController.text.trim();
    final text = _inputController.text.trim();
    if (key.isEmpty) {
      _showError('Enter your DeepSeek API key');
      return;
    }
    if (text.isEmpty || _loading) return;

    _resetClient();
    setState(() {
      _messages.add(_ChatBubble.user(text));
      _inputController.clear();
      _loading = true;
    });
    _scrollToBottom();

    try {
      final client = _getClient();

      if (_selectedModel == DeepSeekModel.reasoner) {
        final result = await client.reason(prompt: text);
        if (!mounted) return;
        setState(() {
          if (result.reasoningContent.isNotEmpty) {
            _messages.add(
              _ChatBubble.assistant(
                'Reasoning:\n${result.reasoningContent}\n\nAnswer:\n${result.content}',
                isReasoner: true,
              ),
            );
          } else {
            _messages.add(_ChatBubble.assistant(result.content));
          }
          _loading = false;
        });
      } else if (_streaming) {
        setState(() {
          _messages.add(_ChatBubble.assistant('', streaming: true));
        });
        final buffer = StringBuffer();
        await for (final token in client.chatStream(
          messages: [ChatMessage.user(text)],
          model: _selectedModel,
        )) {
          buffer.write(token);
          if (!mounted) return;
          setState(() {
            _messages.last = _ChatBubble.assistant(
              buffer.toString(),
              streaming: true,
            );
          });
          _scrollToBottom();
        }
        if (!mounted) return;
        setState(() {
          _messages.last = _ChatBubble.assistant(buffer.toString());
          _loading = false;
        });
      } else {
        final response = await client.chat(
          messages: [ChatMessage.user(text)],
          model: _selectedModel,
        );
        if (!mounted) return;
        setState(() {
          _messages.add(_ChatBubble.assistant(response.text));
          _loading = false;
        });
      }
    } on DeepSeekAuthException catch (e) {
      _showError('Auth error: ${e.message}');
      setState(() => _loading = false);
    } on DeepSeekRateLimitException catch (e) {
      _showError('Rate limit: ${e.message}');
      setState(() => _loading = false);
    } on DeepSeekException catch (e) {
      _showError(e.message);
      setState(() => _loading = false);
    } catch (e) {
      _showError(e);
      setState(() => _loading = false);
    }

    _scrollToBottom();
  }

  void _scrollToBottom() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!_scrollController.hasClients) return;
      _scrollController.animateTo(
        _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeOut,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('flutter_deepseek'),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(112),
          child: Padding(
            padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
            child: Column(
              children: [
                TextField(
                  controller: _apiKeyController,
                  decoration: const InputDecoration(
                    labelText: 'API key (memory only)',
                    border: OutlineInputBorder(),
                    isDense: true,
                  ),
                  obscureText: true,
                  onChanged: (_) => _resetClient(),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: DropdownButtonFormField<String>(
                        initialValue: _selectedModel,
                        decoration: const InputDecoration(
                          labelText: 'Model',
                          border: OutlineInputBorder(),
                          isDense: true,
                        ),
                        items: const [
                          DropdownMenuItem(
                            value: DeepSeekModel.chat,
                            child: Text('deepseek-chat'),
                          ),
                          DropdownMenuItem(
                            value: DeepSeekModel.reasoner,
                            child: Text('deepseek-reasoner'),
                          ),
                        ],
                        onChanged: _loading
                            ? null
                            : (value) {
                                if (value == null) return;
                                setState(() => _selectedModel = value);
                              },
                      ),
                    ),
                    const SizedBox(width: 12),
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        const Text('Stream'),
                        Switch(
                          value: _streaming,
                          onChanged: _loading || _selectedModel == DeepSeekModel.reasoner
                              ? null
                              : (v) => setState(() => _streaming = v),
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              controller: _scrollController,
              padding: const EdgeInsets.all(16),
              itemCount: _messages.length,
              itemBuilder: (context, index) {
                final bubble = _messages[index];
                return Align(
                  alignment: bubble.isUser
                      ? Alignment.centerRight
                      : Alignment.centerLeft,
                  child: Container(
                    margin: const EdgeInsets.only(bottom: 8),
                    padding: const EdgeInsets.symmetric(
                      horizontal: 14,
                      vertical: 10,
                    ),
                    constraints: BoxConstraints(
                      maxWidth: MediaQuery.sizeOf(context).width * 0.85,
                    ),
                    decoration: BoxDecoration(
                      color: bubble.isUser
                          ? Theme.of(context).colorScheme.primaryContainer
                          : bubble.isReasoner
                              ? Colors.amber.shade50
                              : Theme.of(context).colorScheme.surfaceContainerHighest,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          bubble.text.isEmpty && bubble.streaming
                              ? '…'
                              : bubble.text,
                        ),
                        if (bubble.streaming && _loading)
                          Padding(
                            padding: const EdgeInsets.only(top: 8),
                            child: SizedBox(
                              width: 18,
                              height: 18,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                color: Theme.of(context).colorScheme.primary,
                              ),
                            ),
                          ),
                      ],
                    ),
                  ),
                );
              },
            ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: _inputController,
                      decoration: const InputDecoration(
                        hintText: 'Message…',
                        border: OutlineInputBorder(),
                      ),
                      onSubmitted: (_) => _send(),
                      enabled: !_loading,
                    ),
                  ),
                  const SizedBox(width: 8),
                  IconButton.filled(
                    onPressed: _loading ? null : _send,
                    icon: _loading
                        ? const SizedBox(
                            width: 20,
                            height: 20,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Icon(Icons.send),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _ChatBubble {
  _ChatBubble._({
    required this.text,
    required this.isUser,
    this.streaming = false,
    this.isReasoner = false,
  });

  factory _ChatBubble.user(String text) =>
      _ChatBubble._(text: text, isUser: true);

  factory _ChatBubble.assistant(
    String text, {
    bool streaming = false,
    bool isReasoner = false,
  }) =>
      _ChatBubble._(
        text: text,
        isUser: false,
        streaming: streaming,
        isReasoner: isReasoner,
      );

  final String text;
  final bool isUser;
  final bool streaming;
  final bool isReasoner;
}
1
likes
160
points
0
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publishermhammad.app

Weekly Downloads

DeepSeek API client for Flutter with SSE streaming, reasoner model, function calling, and typed errors. BYOK.

Repository (GitHub)
View/report issues

Topics

#deepseek #ai #llm #streaming #flutter

License

MIT (license)

Dependencies

flutter, http

More

Packages that depend on flutter_deepseek