feat: M2.1 客户端骨架、BLE 修复、仓库整理与现状板
- BLE: 修复 BlueZ LocalName 与 Includes 冲突,串行注册与退避 - HTTP: 语音对话补 Live2D talking 标志与 session;Web 补 X-Session-Id - Flutter: 角色/对话/模型页 + API,Gradle/依赖升级,麦克风权限 - 文档: 新增 docs/STATUS.md,校准 CLAUDE/PROGRESS/README - 清理: 移除根目录截图与临时模型,运维脚本迁入 scripts/device
This commit is contained in:
@@ -5,8 +5,11 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../models/ai_model.dart';
|
||||
import '../models/api_response.dart';
|
||||
import '../models/ble_status.dart';
|
||||
import '../models/character_pack.dart';
|
||||
import '../models/chat_models.dart';
|
||||
import '../models/player_status.dart';
|
||||
import '../models/video_item.dart';
|
||||
import '../models/wifi_network.dart';
|
||||
@@ -300,6 +303,118 @@ class HttpApiService {
|
||||
return ApiResponse.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<AvailableConfigs> getAvailableCharacterConfigs() async {
|
||||
final map = await getAvailableConfigs();
|
||||
return AvailableConfigs.fromJson(map);
|
||||
}
|
||||
|
||||
// ── M2.1 AI 对话 / 模型管理 ──
|
||||
|
||||
Future<ChatTurnResponse> chatText({
|
||||
required String text,
|
||||
String? sessionId,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
_uri('/api/chat/text'),
|
||||
headers: _jsonHeaders,
|
||||
body: jsonEncode(<String, dynamic>{
|
||||
'text': text,
|
||||
if (sessionId != null && sessionId.isNotEmpty) 'session_id': sessionId,
|
||||
}),
|
||||
);
|
||||
// AI 错误也以 JSON ChatResponse 返回(可能是 5xx)
|
||||
final map = _decodeMap(response.body);
|
||||
final result = ChatTurnResponse.fromJson(map);
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return result;
|
||||
}
|
||||
if (result.error != null && result.error!.isNotEmpty) {
|
||||
return result;
|
||||
}
|
||||
throw ApiException(
|
||||
map['message']?.toString() ?? '对话失败 (${response.statusCode})',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ChatTurnResponse> chatAudio({
|
||||
required List<int> bytes,
|
||||
String format = 'wav',
|
||||
String? sessionId,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
_uri('/api/chat/audio'),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Accept': 'application/json',
|
||||
'X-Audio-Format': format,
|
||||
if (sessionId != null && sessionId.isNotEmpty) 'X-Session-Id': sessionId,
|
||||
},
|
||||
body: bytes,
|
||||
);
|
||||
final map = _decodeMap(response.body);
|
||||
final result = ChatTurnResponse.fromJson(map);
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return result;
|
||||
}
|
||||
if (result.error != null && result.error!.isNotEmpty) {
|
||||
return result;
|
||||
}
|
||||
throw ApiException(
|
||||
map['message']?.toString() ?? '语音对话失败 (${response.statusCode})',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
String chatAudioFileUrl(String absolutePath) {
|
||||
return _uri(
|
||||
'/api/chat/audio/file',
|
||||
<String, String>{'path': absolutePath},
|
||||
).toString();
|
||||
}
|
||||
|
||||
Future<ModelsSnapshot> getModels() async {
|
||||
final response = await _client.get(_uri('/api/models'));
|
||||
_ensureSuccess(response);
|
||||
return ModelsSnapshot.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<ApiResponse> downloadModel(String modelId) async {
|
||||
final response = await _client.post(
|
||||
_uri('/api/models/download'),
|
||||
headers: _jsonHeaders,
|
||||
body: jsonEncode(<String, dynamic>{'model_id': modelId}),
|
||||
);
|
||||
_ensureSuccess(response);
|
||||
return ApiResponse.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<ApiResponse> switchModel({
|
||||
required String modelId,
|
||||
required String kind,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
_uri('/api/models/switch'),
|
||||
headers: _jsonHeaders,
|
||||
body: jsonEncode(<String, dynamic>{
|
||||
'model_id': modelId,
|
||||
'kind': kind,
|
||||
}),
|
||||
);
|
||||
_ensureSuccess(response);
|
||||
return ApiResponse.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<ApiResponse> deleteModel(String modelId) async {
|
||||
final response = await _client.post(
|
||||
_uri('/api/models/delete'),
|
||||
headers: _jsonHeaders,
|
||||
body: jsonEncode(<String, dynamic>{'model_id': modelId}),
|
||||
);
|
||||
_ensureSuccess(response);
|
||||
return ApiResponse.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> listFiles(String dirKey, [String? path]) async {
|
||||
final response = await _client.get(
|
||||
_uri('/api/files/$dirKey', _pathQuery(path)),
|
||||
|
||||
Reference in New Issue
Block a user