- BLE: 修复 BlueZ LocalName 与 Includes 冲突,串行注册与退避 - HTTP: 语音对话补 Live2D talking 标志与 session;Web 补 X-Session-Id - Flutter: 角色/对话/模型页 + API,Gradle/依赖升级,麦克风权限 - 文档: 新增 docs/STATUS.md,校准 CLAUDE/PROGRESS/README - 清理: 移除根目录截图与临时模型,运维脚本迁入 scripts/device
117 lines
2.9 KiB
Dart
117 lines
2.9 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import '../models/ai_model.dart';
|
|
import '../models/api_response.dart';
|
|
import '../services/http_api_service.dart';
|
|
import 'debug_provider.dart';
|
|
|
|
class ModelProvider extends ChangeNotifier {
|
|
ModelProvider({
|
|
required HttpApiService httpApiService,
|
|
required DebugProvider debugProvider,
|
|
}) : _http = httpApiService,
|
|
_debug = debugProvider;
|
|
|
|
final HttpApiService _http;
|
|
final DebugProvider _debug;
|
|
|
|
ModelsSnapshot? _snapshot;
|
|
bool _loading = false;
|
|
bool _acting = false;
|
|
String? _error;
|
|
String? _status;
|
|
Timer? _pollTimer;
|
|
|
|
ModelsSnapshot? get snapshot => _snapshot;
|
|
bool get isLoading => _loading;
|
|
bool get isActing => _acting;
|
|
String? get error => _error;
|
|
String? get status => _status;
|
|
|
|
Future<void> refresh() async {
|
|
_loading = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
try {
|
|
_snapshot = await _http.getModels();
|
|
_debug.addHttpLog(
|
|
'Loaded models',
|
|
details: 'count=${_snapshot?.models.length ?? 0}',
|
|
);
|
|
} catch (error) {
|
|
_error = error.toString();
|
|
_debug.addHttpLog('Load models failed', details: _error);
|
|
} finally {
|
|
_loading = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> download(String modelId) async {
|
|
await _runAction(() => _http.downloadModel(modelId), '下载已启动: $modelId');
|
|
_startPolling();
|
|
}
|
|
|
|
Future<void> switchTo({required String modelId, required String kind}) async {
|
|
await _runAction(
|
|
() => _http.switchModel(modelId: modelId, kind: kind),
|
|
'已切换: $modelId',
|
|
);
|
|
await refresh();
|
|
}
|
|
|
|
Future<void> delete(String modelId) async {
|
|
await _runAction(() => _http.deleteModel(modelId), '已删除: $modelId');
|
|
await refresh();
|
|
}
|
|
|
|
Future<void> _runAction(
|
|
Future<ApiResponse> Function() action,
|
|
String okStatus,
|
|
) async {
|
|
if (_acting) return;
|
|
_acting = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
try {
|
|
final result = await action();
|
|
_status = result.message.isNotEmpty ? result.message : okStatus;
|
|
if (!result.isOk) {
|
|
_error = _status;
|
|
}
|
|
_debug.addHttpLog('Model action ok', details: _status);
|
|
} catch (error) {
|
|
_error = error.toString();
|
|
_status = _error;
|
|
_debug.addHttpLog('Model action failed', details: _error);
|
|
} finally {
|
|
_acting = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void _startPolling() {
|
|
_pollTimer?.cancel();
|
|
var ticks = 0;
|
|
_pollTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
|
ticks += 1;
|
|
await refresh();
|
|
final downloading = _snapshot?.models.any(
|
|
(m) => m.downloadProgress > 0 && m.downloadProgress < 100 && !m.downloaded,
|
|
) ??
|
|
false;
|
|
if (!downloading || ticks > 90) {
|
|
timer.cancel();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pollTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|