content: part of 'p.dart';
  
  class _RWKV {
    /// Send message to RWKV isolate
    SendPort? _sendPort;
  
    /// Receive message from RWKV isolate
    late final _receivePort = ReceivePort();
  
    @Deprecated("Use _streamController instead")
    late final _oldMessagesController = StreamController<LLMEvent>();
  
    @Deprecated("Use _broadcastStream instead")
    static Stream<LLMEvent>? _oldBroadcastStream;
  
    @Deprecated("Use broadcastStream instead")
    Stream<LLMEvent> get oldBroadcastStream {
      _oldBroadcastStream ??= _oldMessagesController.stream.asBroadcastStream();
      return _oldBroadcastStream!;
    }
  
    late final _messagesController = StreamController<from_rwkv.FromRWKV>();
  
    static Stream<from_rwkv.FromRWKV>? _broadcastStream;
  
    Stream<from_rwkv.FromRWKV> get broadcastStream {
      _broadcastStream ??= _messagesController.stream.asBroadcastStream();
      return _broadcastStream!;
    }
  
    late Completer<void> _initRuntimeCompleter = Completer<void>();
  
    late final prefillSpeed = qs<double>(.0);
    late final decodeSpeed = qs<double>(.0);
    late final prefillProgress = qs<double>(.0);
    late final argumentsPanelShown = qs(false);
    late final logPanelShown = qs(false);
  
    late final decodeParamType = qp<DecodeParamType>((ref) {
      final temp = ref.watch(arguments(Argument.temperature));
      final topP = ref.watch(arguments(Argument.topP));
      final presencePenalty = ref.watch(arguments(Argument.presencePenalty));
      final frequencyPenalty = ref.watch(arguments(Argument.frequencyPenalty));
      final penaltyDecay = ref.watch(arguments(Argument.penaltyDecay));
      return DecodeParamType.fromValue(
        temperature: temp,
        topP: topP,
        presencePenalty: presencePenalty,
        frequencyPenalty: frequencyPenalty,
        penaltyDecay: penaltyDecay,
      );
    });
  
    late final arguments = qsff<Argument, double>((ref, argument) {
      return argument.defaults;
    });
  
    late final reasoning = qp((ref) => ref.watch(_thinkingMode).hasThinkTag);
    late final thinkingMode = qp((ref) => ref.watch(_thinkingMode));
    late final _thinkingMode = qs<thinking_mode.ThinkingMode>(const thinking_mode.Lighting());
  
    thinking_mode.ThinkingMode reasoningOnOrder = const thinking_mode.Free();
    thinking_mode.ThinkingMode reasoningOffOrder = const thinking_mode.Lighting();
  
    /// 模型是否已加载
    late final loaded = qp((ref) {
      final currentModel = ref.watch(this.currentModel);
      return currentModel != null;
    });
  
    /// 当前加载的权重
    late final currentModel = qs<FileInfo?>(null);
  
    /// 当前模型是否是2025年9月22日之前发布的
    ///
    /// 新的权重要使用新的 thinking mode 组
    late final currentModelIsBefore20250922 = qp((ref) {
      final currentModel = ref.watch(this.currentModel);
      if (currentModel == null) return false;
      final date = currentModel.date;
      return date != null && date.isBefore(DateTime(2025, 9, 22));
    });
  
    late final currentWorldType = qs<WorldType?>(null);
  
    late final currentGroupInfo = qs<GroupInfo?>(null);
  
    late final loading = qp((ref) {
      return ref.watch(_loading);
    });
  
    late final _loading = qs(false);
  
    late final argumentUpdatingDebouncer = Debouncer(milliseconds: 300);
  
    Timer? _getTokensTimer;
  
    late final socName = qs("");
    late final socBrand = qs<SocBrand>(SocBrand.unknown);
  
    late final _qnnLibsCopied = qs(false);
  
    Timer? _ttsPerformanceTimer;
  
    // TODO: Use it @WangCe
    late final receiving = qs(false);
  
    late final inTTSOrTranslateMode = qp((ref) {
      final model = ref.watch(P.rwkv.currentModel);
      if (model == null) return false;
      final isTTS = model.isTTS;
      final isTranslate = model.tags.contains("translate");
      return isTTS || isTranslate;
    });
  
    late final supportedBatchSizes = qs<List<int>>([]);
  
    late final runtimeLog = qs<String>("");
  }
  
  extension $RWKVLoad on _RWKV {
    Future<void> loadWorldVision({
      required String modelPath,
      required String encoderPath,
      required Backend backend,
      required bool enableReasoning,
      required String? adapterPath,
    }) async {
      qq;
      _loading.q = true;
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      _thinkingMode.q = enableReasoning ? const thinking_mode.Free() : const thinking_mode.None();
  
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/b_rwkv_vocab_v20230424.txt");
  
      await _ensureQNNCopied();
  
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        try {
          send(to_rwkv.ReleaseWhisperEncoder());
          send(to_rwkv.ReleaseModel());
          final startMS = HF.milliseconds;
          await reInitRuntime(backend: backend, modelPath: modelPath, tokenizerPath: tokenizerPath);
          final endMS = HF.milliseconds;
          qqr("initRuntime done in ${endMS - startMS}ms");
        } catch (e) {
          qqe("initRuntime failed: $e");
          if (!kDebugMode) Sentry.captureException(e, stackTrace: StackTrace.current);
          Alert.error("Failed to load model: $e");
          return;
        }
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      if (adapterPath != null) {
        send(to_rwkv.LoadVisionEncoderAndAdapter(encoderPath, adapterPath));
      } else {
        send(to_rwkv.LoadVisionEncoder(encoderPath));
      }
  
      await setModelConfig(
        enableReasoning: enableReasoning,
        preferChinese: false,
        setPrompt: false,
        thinkingMode: _thinkingMode.q,
      );
      await resetSamplerParams(enableReasoning: enableReasoning);
      await resetMaxLength(enableReasoning: enableReasoning);
      send(to_rwkv.SetEosToken("\x17"));
      send(to_rwkv.SetBosToken("\x16"));
      send(to_rwkv.SetTokenBanned([0]));
      _loading.q = false;
    }
  
    Future<void> loadWorldEngAudioQA({
      required String modelPath,
      required String encoderPath,
      required Backend backend,
    }) async {
      qq;
      _loading.q = true;
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
  
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/b_rwkv_vocab_v20230424.txt");
  
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        send(to_rwkv.ReleaseVisionEncoder());
        send(to_rwkv.ReleaseModel());
        final startMS = HF.milliseconds;
        await reInitRuntime(backend: backend, modelPath: modelPath, tokenizerPath: tokenizerPath);
        final endMS = HF.milliseconds;
        qqr("initRuntime done in ${endMS - startMS}ms");
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      send(to_rwkv.LoadWhisperEncoder(encoderPath));
      await setModelConfig(
        enableReasoning: false,
        preferChinese: false,
        setPrompt: false,
      );
      await resetSamplerParams(enableReasoning: false);
      await resetMaxLength(enableReasoning: false);
      send(to_rwkv.SetEosToken("\x17"));
      send(to_rwkv.SetBosToken("\x16"));
      send(to_rwkv.SetTokenBanned([0]));
      send(to_rwkv.SetUserRole(""));
      _loading.q = false;
    }
  
    Future<void> loadSparkTTS({
      required String modelPath,
      required String wav2vec2Path,
      required String detokenizePath,
      required String bicodecTokenzerPath,
      required Backend backend,
    }) async {
      qq;
      _loading.q = true;
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
  
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/vocab_talk.txt");
      await _ensureQNNCopied();
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        try {
          send(to_rwkv.ReleaseTTSModels());
          final startMS = HF.milliseconds;
          await reInitRuntime(backend: backend, modelPath: modelPath, tokenizerPath: tokenizerPath);
          final endMS = HF.milliseconds;
          qqr("initRuntime done in ${endMS - startMS}ms");
        } catch (e) {
          qqe("initRuntime failed: $e");
          if (!kDebugMode) Sentry.captureException(e, stackTrace: StackTrace.current);
          Alert.error("Failed to load model: $e");
          return;
        }
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      if (_ttsPerformanceTimer != null) {
        _ttsPerformanceTimer!.cancel();
        _ttsPerformanceTimer = null;
      }
  
      _ttsPerformanceTimer = Timer.periodic(225.ms, (timer) async {
        send(to_rwkv.GetPrefillAndDecodeSpeed());
      });
  
      send(
        to_rwkv.LoadSparkTTSModels(
          wav2vec2Path: wav2vec2Path,
          bicodecTokenizerPath: bicodecTokenzerPath,
          bicodecDetokenizerPath: detokenizePath,
        ),
      );
  
      final ttsTextNormalizerDatePath = await fromAssetsToTemp("assets/config/chat/date-zh.fst");
      final ttsTextNormalizerNumberPath = await fromAssetsToTemp("assets/config/chat/number-zh.fst");
      final ttsTextNormalizerPhonePath = await fromAssetsToTemp("assets/config/chat/phone-zh.fst");
      // note: order matters here
      send(to_rwkv.LoadTTSTextNormalizer(ttsTextNormalizerDatePath));
      send(to_rwkv.LoadTTSTextNormalizer(ttsTextNormalizerPhonePath));
      send(to_rwkv.LoadTTSTextNormalizer(ttsTextNormalizerNumberPath));
  
      _loading.q = false;
    }
  
    Future switchChatModel(FileInfo fileInfo) async {
      final current = P.rwkv.currentModel.q;
      if (current == fileInfo) {
        return;
      }
      final localFile = P.fileManager.locals(fileInfo).q;
      final modelPath = localFile.targetPath;
      final backend = fileInfo.backend;
      try {
        P.rwkv.clearStates();
        await P.rwkv.loadChat(
          modelPath: modelPath,
          backend: backend!,
          enableReasoning: fileInfo.isReasoning,
        );
        P.rwkv.currentModel.q = fileInfo;
      } catch (e) {
        qqe;
        Alert.error(e.toString());
        return;
      }
  
      final batchAllowed = fileInfo.tags.contains("batch");
      if (!batchAllowed) P.chat.batchEnabled.q = false;
    }
  
    Future<void> loadChat({
      required String modelPath,
      required Backend backend,
      required bool enableReasoning,
    }) async {
      qq;
      _loading.q = true;
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/b_rwkv_vocab_v20230424.txt");
  
      await _ensureQNNCopied();
  
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        try {
          final startMS = HF.milliseconds;
          await reInitRuntime(backend: backend, modelPath: modelPath, tokenizerPath: tokenizerPath);
          final endMS = HF.milliseconds;
          qqr("initRuntime done in ${endMS - startMS}ms");
        } catch (e) {
          qqe("initRuntime failed: $e");
          if (!kDebugMode) Sentry.captureException(e, stackTrace: StackTrace.current);
          Alert.error("Failed to load model: $e");
          return;
        }
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      P.app.demoType.q = DemoType.chat;
      await setModelConfig(enableReasoning: enableReasoning);
      await resetSamplerParams(enableReasoning: enableReasoning);
      await resetMaxLength(enableReasoning: enableReasoning);
      send(to_rwkv.GetSamplerParams());
      _loading.q = false;
      send(to_rwkv.GetSupportedBatchSizes());
    }
  
    Future<void> loadOthello() async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
  
      late final String modelPath;
      late final Backend backend;
  
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/b_othello_vocab.txt");
  
      if (Platform.isIOS || Platform.isMacOS) {
        modelPath = await fromAssetsToTemp("assets/model/chat/rwkv7_othello_26m_L10_D448_extended.st");
        backend = Backend.webRwkv;
      } else {
        modelPath = await fromAssetsToTemp("assets/model/chat/rwkv7_othello_26m_L10_D448_extended-ncnn.bin");
        await fromAssetsToTemp("assets/model/chat/rwkv7_othello_26m_L10_D448_extended-ncnn.param");
        backend = Backend.ncnn;
      }
  
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        send(
          to_rwkv.ReInitRuntime(
            modelPath: modelPath,
            backend: backend,
            tokenizerPath: tokenizerPath,
          ),
        );
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      P.app.demoType.q = DemoType.othello;
  
      send(to_rwkv.SetMaxLength(64000));
      send(
        to_rwkv.SetSamplerParams(
          temperature: 1.0,
          topK: 1,
          topP: 1.0,
          presencePenalty: .0,
          frequencyPenalty: .0,
          penaltyDecay: .0,
        ),
      );
      send(to_rwkv.SetGenerationStopToken(0));
      send(to_rwkv.ClearStates());
    }
  
    Future<void> loadSudoku({
      required String modelPath,
      required Backend backend,
    }) async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
  
      final tokenizerPath = await fromAssetsToTemp("assets/config/chat/b_sudoku_vocab.txt");
      final data = await rootBundle.load("assets/config/chat/sudoku_rwkv_20241120_ncnn.param");
      final paramFile = File(P.app.documentsDir.q!.path + "/sudoku_rwkv_20241120_ncnn.param");
      await paramFile.writeAsBytes(data.buffer.asUint8List());
  
      await _ensureQNNCopied();
      final rootIsolateToken = RootIsolateToken.instance;
  
      if (_sendPort != null) {
        send(
          to_rwkv.ReInitRuntime(
            modelPath: modelPath,
            backend: backend,
            tokenizerPath: tokenizerPath,
          ),
        );
      } else {
        final options = StartOptions(
          modelPath: modelPath,
          tokenizerPath: tokenizerPath,
          backend: backend,
          sendPort: _receivePort.sendPort,
          rootIsolateToken: rootIsolateToken!,
        );
        await RWKVMobile().runIsolate(options);
      }
  
      while (_sendPort == null) {
        qqq("waiting for sendPort...");
        await Future.delayed(const Duration(milliseconds: 50));
      }
  
      P.app.demoType.q = DemoType.sudoku;
  
      send(to_rwkv.SetMaxLength(6000_000));
      send(
        to_rwkv.SetSamplerParams(
          temperature: 1.0,
          topK: 1,
          topP: 1.0,
          presencePenalty: .0,
          frequencyPenalty: .0,
          penaltyDecay: .0,
        ),
      );
      send(to_rwkv.SetGenerationStopToken(_Sudoku.tokenStop));
      send(to_rwkv.ClearStates());
      _loading.q = false;
    }
  }
  
  /// Public methods
  extension $RWKV on _RWKV {
    Future<void> setAudioPrompt({required String path}) async {
      send(to_rwkv.SetAudioPrompt(path));
    }
  
    Future<void> sendMessages(
      List<String> messages, {
      double getIsGeneratingRate = .5,
      double getResponseBufferContentRate = .5,
      int batchSize = 1,
    }) async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
  
      final sendPort = _sendPort;
  
      if (sendPort == null) {
        qqw("sendPort is null");
        return;
      }
  
      final isBatch = batchSize > 1;
  
      final startInferenceCalling = isBatch
          ? to_rwkv.ChatBatchAsync(messages, reasoning: _thinkingMode.q.hasThinkTag, batchSize: batchSize) //
          : to_rwkv.ChatAsync(messages, reasoning: _thinkingMode.q.hasThinkTag);
      send(startInferenceCalling);
  
      if (_getTokensTimer != null) _getTokensTimer!.cancel();
  
      _getTokensTimer = Timer.periodic(const Duration(milliseconds: 20), (timer) async {
        final getResponseCalling = isBatch
            ? to_rwkv.GetBatchResponseBufferContent(messages) //
            : to_rwkv.GetResponseBufferContent(messages);
        send(getResponseCalling);
        if (HF.randomBool(truePercentage: getIsGeneratingRate)) send(to_rwkv.GetIsGenerating());
        if (HF.randomBool(truePercentage: getResponseBufferContentRate)) send(to_rwkv.GetPrefillAndDecodeSpeed());
      });
    }
  
    Future<void> completion(String prompt) async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      final sendPort = _sendPort;
      if (sendPort == null) {
        qqw("sendPort is null");
        return;
      }
      send(to_rwkv.GenerateAsync(prompt));
  
      if (_getTokensTimer != null) {
        _getTokensTimer!.cancel();
      }
  
      _getTokensTimer = Timer.periodic(const Duration(milliseconds: 20), (timer) async {
        send(to_rwkv.GetResponseBufferIds());
        send(to_rwkv.GetPrefillAndDecodeSpeed());
        send(to_rwkv.GetResponseBufferContent());
        await Future.delayed(const Duration(milliseconds: 1000));
        send(to_rwkv.GetIsGenerating());
      });
    }
  
    /// 直接在 ffi+cpp 线程中进行推理工作, 也就是说, 会让 ffi 线程不接受任何新的 event
    Future<void> generate(String prompt) async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      final sendPort = _sendPort;
      if (sendPort == null) {
        qqw("sendPort is null");
        return;
      }
      send(to_rwkv.SudokuOthelloGenerate(prompt));
  
      if (_getTokensTimer != null) {
        _getTokensTimer!.cancel();
      }
  
      _getTokensTimer = Timer.periodic(const Duration(milliseconds: 20), (timer) async {
        send(to_rwkv.GetResponseBufferIds());
        send(to_rwkv.GetPrefillAndDecodeSpeed());
        send(to_rwkv.GetResponseBufferContent());
        await Future.delayed(const Duration(milliseconds: 1000));
        send(to_rwkv.GetIsGenerating());
      });
    }
  
    Future<void> setImagePath({required String path}) async {
      send(to_rwkv.SetVisionPrompt(path));
    }
  
    Future<void> clearStates() async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      final sendPort = _sendPort;
      if (sendPort == null) {
        qqw("sendPort is null");
        return;
      }
      send(to_rwkv.ClearStates());
    }
  
    void send(to_rwkv.ToRWKV toRwkv) {
      final sendPort = _sendPort;
      if (sendPort == null) {
        qqw("sendPort is null");
        return;
      }
      sendPort.send(toRwkv);
      return;
    }
  
    Future<void> stop() async => send(to_rwkv.Stop());
  
    Future<void> reInitRuntime({
      required String modelPath,
      required Backend backend,
      required String tokenizerPath,
    }) async {
      prefillSpeed.q = 0;
      decodeSpeed.q = 0;
      _initRuntimeCompleter = Completer<void>();
      send(
        to_rwkv.ReInitRuntime(
          modelPath: modelPath,
          backend: backend,
          tokenizerPath: tokenizerPath,
        ),
      );
      return _initRuntimeCompleter.future;
    }
  
    void setGenerateMode(bool isGenerateMode) {
      if (isGenerateMode) {
        send(to_rwkv.SetPrompt(""));
      } else {
        setModelConfig(thinkingMode: _thinkingMode.q);
      }
    }
  
    Future<void> setModelConfig({
      thinking_mode.ThinkingMode? thinkingMode,
      @Deprecated("Use thinkingMode instead, 不能排除之后突然来个不支持 <think> 的模型, 所以先不删除") bool? enableReasoning,
      @Deprecated("Use thinkingMode instead, 不能排除之后突然来个不支持 <think> 的模型, 所以先不删除") bool? preferChinese,
      @Deprecated("Use thinkingMode instead, 不能排除之后突然来个不支持 <think> 的模型, 所以先不删除") bool? preferPseudo,
      bool setPrompt = true,
      String? prompt,
    }) async {
      qqr(thinkingMode);
      _thinkingMode.q = thinkingMode ?? const thinking_mode.Lighting();
  
      final systemPrompt = P.preference.promptTemplate.systemPrompt.trim();
  
      if (setPrompt) {
        if (prompt != null) {
          send(to_rwkv.SetPrompt(prompt));
        } else {
          String p = prompt ?? "<EOD>";
          if (systemPrompt.isNotEmpty) {
            p = "$systemPrompt\n\n";
          }
          send(to_rwkv.SetPrompt(p));
        }
        qqw("setPrompt: $prompt");
      }
  
      final custom = P.preference.promptTemplate;
      final thinkingToken = custom.apply(_thinkingMode.q);
      qqq("setThinkingToken: $thinkingToken");
      send(to_rwkv.SetThinkingToken(thinkingToken));
    }
  
    Future<void> resetSamplerParams({required bool enableReasoning}) async {
      await syncSamplerParams(
        temperature: enableReasoning ? Argument.temperature.reasonDefaults : Argument.temperature.defaults,
        topK: enableReasoning ? Argument.topK.reasonDefaults : Argument.topK.defaults,
        topP: enableReasoning ? Argument.topP.reasonDefaults : Argument.topP.defaults,
        presencePenalty: enableReasoning ? Argument.presencePenalty.reasonDefaults : Argument.presencePenalty.defaults,
        frequencyPenalty: enableReasoning ? Argument.frequencyPenalty.reasonDefaults : Argument.frequencyPenalty.defaults,
        penaltyDecay: enableReasoning ? Argument.penaltyDecay.reasonDefaults : Argument.penaltyDecay.defaults,
      );
    }
  
    Future syncSamplerParamsFromDefault(DecodeParamType param) async {
      await syncSamplerParams(
        temperature: param.temperature,
        topP: param.topP,
        penaltyDecay: param.penaltyDecay,
        presencePenalty: param.presencePenalty,
        frequencyPenalty: param.frequencyPenalty,
      );
    }
  
    Future<void> syncSamplerParams({
      double? temperature,
      double? topK,
      double? topP,
      double? presencePenalty,
      double? frequencyPenalty,
      double? penaltyDecay,
    }) async {
      if (temperature != null) arguments(Argument.temperature).q = temperature;
      if (topK != null) arguments(Argument.topK).q = topK;
      if (topP != null) arguments(Argument.topP).q = topP;
      if (presencePenalty != null) arguments(Argument.presencePenalty).q = presencePenalty;
      if (frequencyPenalty != null) arguments(Argument.frequencyPenalty).q = frequencyPenalty;
      if (penaltyDecay != null) arguments(Argument.penaltyDecay).q = penaltyDecay;
  
      send(
        to_rwkv.SetSamplerParams(
          temperature: _intIfFixedDecimalsIsZero(Argument.temperature),
          topK: _intIfFixedDecimalsIsZero(Argument.topK),
          topP: _intIfFixedDecimalsIsZero(Argument.topP),
          presencePenalty: _intIfFixedDecimalsIsZero(Argument.presencePenalty),
          frequencyPenalty: _intIfFixedDecimalsIsZero(Argument.frequencyPenalty),
          penaltyDecay: _intIfFixedDecimalsIsZero(Argument.penaltyDecay),
        ),
      );
  
      if (kDebugMode) send(to_rwkv.GetSamplerParams());
    }
  
    Future<void> resetMaxLength({required bool enableReasoning}) async {
      await syncMaxLength(
        maxLength: enableReasoning ? Argument.maxLength.reasonDefaults : Argument.maxLength.defaults,
      );
    }
  
    Future<void> syncMaxLength({num? maxLength}) async {
      if (maxLength != null) arguments(Argument.maxLength).q = maxLength.toDouble();
      send(to_rwkv.SetMaxLength(_intIfFixedDecimalsIsZero(Argument.maxLength).toInt()));
    }
  
    Future<void> onThinkModeTapped() async {
      final receiving = P.chat.receivingTokens.q;
      if (receiving) {
        Alert.info(S.current.please_wait_for_the_model_to_finish_generating);
        return;
      }
  
      if (!checkModelSelection()) return;
  
      P.app.hapticLight();
  
      final s = S.current;
  
      final currentModelIsBefore20250922 = P.rwkv.currentModelIsBefore20250922.q;
      if (currentModelIsBefore20250922) {
        final current = thinkingMode.q;
        switch (current) {
          case thinking_mode.Lighting():
            setModelConfig(thinkingMode: const thinking_mode.Free());
            Alert.success(s.thinking_mode_high(s.thinking_mode_alert_footer));
          case thinking_mode.Free():
            setModelConfig(thinkingMode: const thinking_mode.None());
            Alert.success(s.thinking_mode_off(s.thinking_mode_alert_footer));
          case thinking_mode.PreferChinese():
            setModelConfig(thinkingMode: const thinking_mode.None());
            Alert.success(s.thinking_mode_off(s.thinking_mode_alert_footer));
          case thinking_mode.None():
            setModelConfig(thinkingMode: const thinking_mode.Lighting());
            Alert.success(s.thinking_mode_auto(s.thinking_mode_alert_footer));
          default:
            break;
        }
        return;
      }
  
      final current = thinkingMode.q;
  
      final actionPairs = [
        (label: s.thinking_mode_off(""), key: const thinking_mode.None()),
        (label: s.think_button_mode_fast(""), key: const thinking_mode.Fast()),
        (label: s.thinking_mode_high(""), key: const thinking_mode.Free()),
        (label: s.think_button_mode_en(""), key: const thinking_mode.En()),
        (label: s.think_button_mode_en_short(""), key: const thinking_mode.EnShort()),
        (label: s.think_button_mode_en_long(""), key: const thinking_mode.EnLong()),
      ];
  
      final actions = actionPairs.map((e) {
        final isCurrent = e.key == current;
        final label = isCurrent ? "☑ ${e.label}" : e.label;
        final key = e.key;
        return SheetAction(label: label, key: key);
      }).toList();
  
      final res = await showModalActionSheet<thinking_mode.ThinkingMode>(
        context: getContext()!,
        title: s.think_mode_selector_title,
        message: s.think_mode_selector_message,
        actions: actions,
      );
  
      if (res == null) return;
  
      setModelConfig(thinkingMode: res);
      switch (res) {
        case thinking_mode.None():
          Alert.success(s.thinking_mode_off(s.thinking_mode_alert_footer));
        case thinking_mode.Fast():
          Alert.success(s.think_button_mode_fast(s.thinking_mode_alert_footer));
        case thinking_mode.Free():
          Alert.success(s.thinking_mode_high(s.thinking_mode_alert_footer));
        case thinking_mode.En():
          Alert.success(s.think_button_mode_en(s.thinking_mode_alert_footer));
        case thinking_mode.EnShort():
          Alert.success(s.think_button_mode_en_short(s.thinking_mode_alert_footer));
        case thinking_mode.EnLong():
          Alert.success(s.think_button_mode_en_long(s.thinking_mode_alert_footer));
        default:
          break;
      }
    }
  
    void onBatchInferenceTapped() async {
      final receiving = P.chat.receivingTokens.q;
      if (receiving) {
        Alert.info(S.current.please_wait_for_the_model_to_finish_generating);
        return;
      }
  
      if (!checkModelSelection()) return;
  
      final currentModel = P.rwkv.currentModel.q;
  
      final batchAllowed = currentModel!.tags.contains("batch");
  
      if (!batchAllowed) {
        Alert.info(S.current.this_model_does_not_support_batch_inference);
        await Future.delayed(const Duration(milliseconds: 500));
        ModelSelector.show();
        return;
      }
  
      await BatchSettingsPanel.show();
    }
  
    void onSecondaryOptionsTapped() async {
      final receiving = P.chat.receivingTokens.q;
      if (receiving) {
        Alert.info(S.current.please_wait_for_the_model_to_finish_generating);
        return;
      }
  
      if (!checkModelSelection()) return;
  
      final current = thinkingMode.q;
      P.app.hapticLight();
      switch (current) {
        case thinking_mode.Lighting():
        case thinking_mode.None():
          break;
        case thinking_mode.Free():
          setModelConfig(thinkingMode: const thinking_mode.PreferChinese());
          Alert.success(S.current.prefer_chinese);
        case thinking_mode.PreferChinese():
          setModelConfig(thinkingMode: const thinking_mode.Free());
          Alert.success(S.current.thinking_mode_high(S.current.thinking_mode_alert_footer));
        default:
          break;
      }
    }
  
    Future<void> refreshRuntimeLog() async {
      send(to_rwkv.DumpLog());
    }
  }
  
  /// Private methods
  extension _$RWKV on _RWKV {
    Future<void> _init() async {
      P.app.pageKey.lv(_onPageKeyChanged);
      _receivePort.listen(_onMessage);
      final r = await compute((_) {
        final socName = RWKVMobile.getSocName();
        final platformName = RWKVMobile.getPlatformName();
        final socBrand = SocBrand.fromString(platformName);
        return (socName, socBrand);
      }, []);
      socName.q = r.$1;
      socBrand.q = r.$2;
      currentModel.lb(_onCurrentModelChanged);
    }
  
    void _onCurrentModelChanged(FileInfo? oldModel, FileInfo? newModel) async {
      if (oldModel == null || newModel == null) return;
      final oldModelSize = oldModel.modelSize;
      final newModelSize = newModel.modelSize;
      if (oldModelSize == null || newModelSize == null) return;
      if (oldModelSize >= newModelSize) return;
      if (P.app.pageKey.q != PageKey.chat) return;
      if (P.msg.list.q.isEmpty) return;
      await Future.delayed(2000.ms);
      Alert.info(S.current.model_size_increased_please_open_a_new_conversation);
    }
  
    num _intIfFixedDecimalsIsZero(Argument argument) {
      if (argument.fixedDecimals == 0) {
        return arguments(argument).q.toInt();
      } else {
        return double.parse(arguments(argument).q.toStringAsFixed(argument.fixedDecimals));
      }
    }
  
    Future<void> _onPageKeyChanged() async {
      final pageKey = P.app.pageKey.q;
      switch (pageKey) {
        case PageKey.othello:
          await loadOthello();
          break;
        case PageKey.chat:
          qq;
          send(to_rwkv.GetSupportedBatchSizes());
          break;
        default:
          break;
      }
    }
  
    // ignore: unused_element
    Future<void> _loadFifthteenPuzzle() async {
      throw "Not support, please contact the developer";
    }
  
    // ignore: unused_element
    Future<void> _loadSudoku() async {
      throw "Not support, please contact the developer";
    }
  
    void _onMessage(message) {
      if (message is SendPort) {
        _sendPort = message;
        return;
      }
  
      if (message is from_rwkv.FromRWKV) {
        _handleFromRWKV(message);
        return;
      }
  
      if (message["responseBufferIds"] != null) {
        final responseBufferIdsList = message["responseBufferIds"];
        _oldMessagesController.add(
          LLMEvent(
            responseBufferIds: (responseBufferIdsList as List).map((e) => e as int).toList(),
            type: _RWKVMessageType.responseBufferIds,
          ),
        );
        return;
      }
  
      if (message["isGenerating"] != null) {
        final isGenerating = message["isGenerating"];
        _oldMessagesController.add(
          LLMEvent(
            content: isGenerating.toString(),
            type: _RWKVMessageType.isGenerating,
          ),
        );
        if (!isGenerating) {
          _getTokensTimer?.cancel();
          _getTokensTimer = null;
        }
        return;
      }
  
      if (message["sudokuOthelloResponse"] != null) {
        final responseText = message["sudokuOthelloResponse"].toString();
        _oldMessagesController.add(
          LLMEvent(
            content: responseText,
            type: _RWKVMessageType.sudokuOthelloResponse,
          ),
        );
        return;
      }
  
      if (message["streamResponse"] != null) {
        final responseText = message["streamResponse"].toString();
        _oldMessagesController.add(
          LLMEvent(
            content: responseText,
            token: message["streamResponseToken"],
            type: _RWKVMessageType.streamResponse,
          ),
        );
        if (message["prefillSpeed"] != null && message["prefillSpeed"] != -1.0) {
          prefillSpeed.q = message["prefillSpeed"];
        }
        if (message["decodeSpeed"] != null && message["decodeSpeed"] != -1.0) {
          decodeSpeed.q = message["decodeSpeed"];
        }
        return;
      }
  
      qqe("unknown message: $message");
      if (!kDebugMode) Sentry.captureException(Exception("unknown message: $message"), stackTrace: StackTrace.current);
    }
  
    void _handleFromRWKV(from_rwkv.FromRWKV message) {
      _messagesController.add(message);
      switch (message) {
        case from_rwkv.ReInitSteps res:
          final done = res.done;
          final success = res.success;
          final error = res.error;
  
          if (done) {
            if (success == true) {
              if (_initRuntimeCompleter.isCompleted) return;
              _initRuntimeCompleter.complete();
            } else if (success == false) {
              qqe("initRuntime failed: $error");
              final exception = Exception("initRuntime failed: $error");
              if (!kDebugMode) Sentry.captureException(exception, stackTrace: StackTrace.current);
              if (_initRuntimeCompleter.isCompleted) return;
              _initRuntimeCompleter.completeError(exception);
            } else {}
          }
  
        case from_rwkv.Error response:
          if (kDebugMode) {
            String errorLog = "error: ${response.message}";
            if (message.to != null) errorLog += " in ${message.to.runtimeType}";
            if (message.to?.requestId != null) errorLog += " requestId: ${message.to?.requestId}";
            qqe(errorLog);
          }
          qqe;
          Alert.error(response.message);
  
        case from_rwkv.Speed response:
          prefillSpeed.q = response.prefillSpeed;
          decodeSpeed.q = response.decodeSpeed;
          prefillProgress.q = response.prefillProgress;
  
        case from_rwkv.StreamResponse response:
          final decodeSpeed = response.decodeSpeed;
          final prefillSpeed = response.prefillSpeed;
          if (decodeSpeed != -1.0) this.decodeSpeed.q = decodeSpeed;
          if (prefillSpeed != -1.0) this.prefillSpeed.q = prefillSpeed;
  
        case from_rwkv.SupportedBatchSizes response:
          supportedBatchSizes.q = response.supportedBatchSizes;
  
        case from_rwkv.RuntimeLog response:
          runtimeLog.q = response.runtimeLog;
  
        default:
          break;
      }
    }
  
    Future<void> _ensureQNNCopied() async {
      if (Platform.isAndroid && !_qnnLibsCopied.q) {
        // TODO: @Molly better solution here
        // TODO: @wangce Ask Molly why there are "better" solution here
        final qnnLibList = {
          "libQnnHtp.so",
          "libQnnHtpNetRunExtensions.so",
          "libQnnHtpV68Stub.so",
          "libQnnHtpV69Stub.so",
          "libQnnHtpV73Stub.so",
          "libQnnHtpV75Stub.so",
          "libQnnHtpV79Stub.so",
          "libQnnHtpV68Skel.so",
          "libQnnHtpV69Skel.so",
          "libQnnHtpV73Skel.so",
          "libQnnHtpV75Skel.so",
          "libQnnHtpV79Skel.so",
          "libQnnHtpPrepare.so",
          "libQnnSystem.so",
          "libQnnRwkvWkvOpPackageV68.so",
          "libQnnRwkvWkvOpPackageV69.so",
          "libQnnRwkvWkvOpPackageV73.so",
          "libQnnRwkvWkvOpPackageV75.so",
          "libQnnRwkvWkvOpPackageV79.so",
        };
        for (final lib in qnnLibList) {
          await fromAssetsToTemp("assets/lib/qnn/$lib", targetPath: "assets/lib/$lib");
        }
        _qnnLibsCopied.q = true;
      }
    }
  }
  
  @Deprecated("Use FromRWKV instead")
  enum _RWKVMessageType {
    /// 模型吐完 token 了会被调用, 调用内容该次 generate 吐出的总文本
    @Deprecated("Use FromRWKV instead")
    sudokuOthelloResponse,
  
    /// 模型每吐一个token，调用一次, 调用内容为该次 generate 已经吐出的文本
    @Deprecated("Use FromRWKV instead")
    streamResponse,
  
    /// 模型是否正在生成
    @Deprecated("Use FromRWKV instead")
    isGenerating,
    @Deprecated("Use FromRWKV instead")
    responseBufferIds,
  }
  
  @Deprecated("Use FromRWKV instead")
  @immutable
  final class LLMEvent {
    final _RWKVMessageType type;
    final String content;
    final List<int>? responseBufferIds;
    final int? token;
  
    const LLMEvent({
      required this.type,
      this.content = "",
      this.responseBufferIds,
      this.token,
    });
  
    @override
    String toString() {
      return "LLMEvent.type: $type";
    }
  }
  