Files
jizhi/frontend/lib/shared/api/config_api.dart
T

227 lines
7.1 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miaoji_zhang/shared/api/api_client.dart';
import 'package:miaoji_zhang/shared/api/backend_identity.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
class BrandConfig {
final String appName, slogan, logoUrl;
final bool voiceEnabled;
final bool imageEnabled;
final bool screenshotEnabled;
final bool stickersEnabled;
final bool aiAutoBookEnabled;
BrandConfig.fromJson(Map<String, dynamic> j)
: appName = _appName(j['appName']),
slogan = j['slogan'] as String? ?? '',
logoUrl = j['logoUrl'] as String? ?? '',
voiceEnabled = _feature(j, 'voice'),
imageEnabled = _feature(j, 'image'),
screenshotEnabled = _feature(j, 'screenshot'),
stickersEnabled = _feature(j, 'stickers'),
aiAutoBookEnabled = _feature(j, 'aiAutoBook');
static String _appName(Object? value) {
final name = value?.toString().trim();
return name == null || name.isEmpty || name == '喵记账' || name == '喵记'
? '记之'
: name;
}
static bool _feature(Map<String, dynamic> json, String key) {
final features = json['features'];
return features is Map ? features[key] as bool? ?? true : true;
}
}
class AvatarItem {
final String key, defaultName, speechTic;
final String? imageUrl;
AvatarItem.fromJson(Map<String, dynamic> j)
: key = j['key'] as String,
defaultName = j['defaultName'] as String,
speechTic = j['speechTic'] as String? ?? '',
imageUrl = j['imageUrl'] as String?;
Map<String, dynamic> toJson() => {
'key': key,
'defaultName': defaultName,
'speechTic': speechTic,
'imageUrl': imageUrl,
};
}
class PersonaItem {
final String key, name, description, sampleLine;
PersonaItem.fromJson(Map<String, dynamic> j)
: key = j['key'] as String,
name = j['name'] as String,
description = j['description'] as String? ?? '',
sampleLine = j['sampleLine'] as String? ?? '';
Map<String, dynamic> toJson() => {
'key': key,
'name': name,
'description': description,
'sampleLine': sampleLine,
};
}
class CompanionDisplay {
final String name;
final String avatarKey;
const CompanionDisplay({required this.name, required this.avatarKey});
}
class PublicConfigApi {
static final _dio = ApiClient.instance.dio;
static BrandConfig? _cachedBrand;
static Future<void> init() async {
if (!SessionStore.instance.isGuest) {
try {
final b = await _dio.get('/api/public/brand');
_cachedBrand = BrandConfig.fromJson(b.data as Map<String, dynamic>);
} catch (_) {}
}
await refreshCompanion();
}
static String get appName => _cachedBrand?.appName ?? '记之';
static String get slogan => _cachedBrand?.slogan ?? '';
static bool get voiceEnabled => _cachedBrand?.voiceEnabled ?? true;
static bool get imageEnabled => _cachedBrand?.imageEnabled ?? true;
static bool get screenshotEnabled => _cachedBrand?.screenshotEnabled ?? true;
static bool get stickersEnabled => _cachedBrand?.stickersEnabled ?? true;
static bool get aiAutoBookEnabled => _cachedBrand?.aiAutoBookEnabled ?? true;
static final companionNotifier = ValueNotifier<CompanionDisplay>(
const CompanionDisplay(name: 'AI', avatarKey: 'cat'),
);
static String get companionName => companionNotifier.value.name;
static String get companionAvatarKey => companionNotifier.value.avatarKey;
static Future<void> refreshCompanion() async {
final session = SessionStore.instance;
if (!session.isAccount || session.shouldUseLocalOnly) {
companionNotifier.value = const CompanionDisplay(
name: 'AI',
avatarKey: 'cat',
);
return;
}
try {
final responses = await Future.wait([
_dio.get('/api/users/me'),
_dio.get('/api/public/avatars'),
]);
final profile = responses[0].data as Map<String, dynamic>;
final avatars = responses[1].data as List;
final companion = profile['aiCompanion'] as Map<String, dynamic>?;
final selectedAvatarKey = companion?['avatarKey'] as String?;
final customName = _normalizeName(companion?['customName'] as String?);
var avatarKey = selectedAvatarKey;
String? defaultName;
for (final item in avatars) {
final avatar = item as Map<String, dynamic>;
final key = avatar['key'] as String?;
if ((avatarKey == null || avatarKey.isEmpty) &&
key != null &&
key.isNotEmpty) {
avatarKey = key;
}
if (key == selectedAvatarKey) {
defaultName = _normalizeName(avatar['defaultName'] as String?);
break;
}
}
companionNotifier.value = CompanionDisplay(
name: customName ?? defaultName ?? '小账喵',
avatarKey: avatarKey ?? 'cat',
);
} catch (_) {
try {
final a = await _dio.get('/api/public/avatars');
final list = a.data as List;
if (list.isEmpty) return;
final first = list.first as Map<String, dynamic>;
companionNotifier.value = CompanionDisplay(
name: _normalizeName(first['defaultName'] as String?) ?? '小账喵',
avatarKey: first['key'] as String? ?? 'cat',
);
} catch (_) {}
}
}
static Future<List<AvatarItem>> avatars() async {
final res = await _dio.get('/api/public/avatars');
final values = (res.data as List)
.map((e) => AvatarItem.fromJson(e as Map<String, dynamic>))
.toList();
await _cacheCatalog(
_avatarCacheKey,
values.map((e) => e.toJson()).toList(),
);
return values;
}
static Future<List<PersonaItem>> personas() async {
final res = await _dio.get('/api/public/personas');
final values = (res.data as List)
.map((e) => PersonaItem.fromJson(e as Map<String, dynamic>))
.toList();
await _cacheCatalog(
_personaCacheKey,
values.map((e) => e.toJson()).toList(),
);
return values;
}
static Future<List<AvatarItem>> cachedAvatars() async =>
(await _cachedCatalog(_avatarCacheKey)).map(AvatarItem.fromJson).toList();
static Future<List<PersonaItem>> cachedPersonas() async =>
(await _cachedCatalog(
_personaCacheKey,
)).map(PersonaItem.fromJson).toList();
static String get _avatarCacheKey =>
'public_avatars_${BackendIdentity.scope}';
static String get _personaCacheKey =>
'public_personas_${BackendIdentity.scope}';
static Future<void> _cacheCatalog(
String key,
List<Map<String, dynamic>> values,
) async {
await (await SharedPreferences.getInstance()).setString(
key,
jsonEncode(values),
);
}
static Future<List<Map<String, dynamic>>> _cachedCatalog(String key) async {
final value = (await SharedPreferences.getInstance()).getString(key);
if (value == null) return const [];
try {
return (jsonDecode(value) as List).cast<Map<String, dynamic>>().toList();
} catch (_) {
return const [];
}
}
static String? _normalizeName(String? value) {
final text = value?.trim();
if (text == null || text.isEmpty) return null;
return text;
}
}