81 lines
3.0 KiB
Dart
81 lines
3.0 KiB
Dart
import 'json_util.dart';
|
||
|
||
/// 一套 AI 人设(system prompt)
|
||
///
|
||
/// 与 [AiConfigItem](模型配置)是**平行的两套配置**:一个管「用哪个模型」,
|
||
/// 一个管「用什么人设」。两者正交 —— App 里切模型不会换人设,切人设也不会换模型。
|
||
///
|
||
/// ⚠️ [systemPrompt] 里**不含日期等环境信息**。那些是运行时事实,
|
||
/// 由服务端在每次对话时追加(今天是几号、「这个月」指哪个月)。
|
||
/// 所以「新建时预填」拿到的是可编辑的那部分,用户不用管日期。
|
||
class AiPersonaItem {
|
||
const AiPersonaItem({
|
||
required this.id,
|
||
required this.name,
|
||
required this.systemPrompt,
|
||
required this.promptLength,
|
||
required this.isDefault,
|
||
required this.editable,
|
||
required this.defaultPrompt,
|
||
});
|
||
|
||
/// null = 虚拟的「默认」项(内容是服务端代码内置的)
|
||
final int? id;
|
||
|
||
final String name;
|
||
|
||
/// 人设 + 运行规则。默认项上是内置默认文本,用户项上是自己配的
|
||
final String systemPrompt;
|
||
|
||
/// 服务端数好的字数,前端直接显示
|
||
final int promptLength;
|
||
|
||
/// 是否是「默认」那一项
|
||
final bool isDefault;
|
||
|
||
/// 是否可编辑/删除。默认项为 false
|
||
final bool editable;
|
||
|
||
/// 内置默认文本。**只有默认项上才有值**,供「恢复默认」按钮使用。
|
||
///
|
||
/// 放服务端下发而不是 App 里硬编码,是为了两边永远一致 ——
|
||
/// 以后改了默认人设,App 不用跟着改版本。
|
||
final String? defaultPrompt;
|
||
|
||
/// 用于「显示已选中的是哪一项」的稳定标识。
|
||
///
|
||
/// 默认项没有 id,用 -1 代替 —— 不能直接用 null 当 map key 去比较。
|
||
int get selectionKey => id ?? -1;
|
||
|
||
factory AiPersonaItem.fromJson(Map<String, dynamic> json) {
|
||
return AiPersonaItem(
|
||
id: asNullableInt(json['id']),
|
||
name: asString(json['name'], fallback: '未命名'),
|
||
systemPrompt: asString(json['systemPrompt']),
|
||
promptLength: asInt(json['promptLength']),
|
||
isDefault: json['isDefault'] == true,
|
||
editable: json['editable'] != false,
|
||
// 用户项上这个字段是 null —— 不能直接用 asNonEmpty(它要 fallback 参数,
|
||
// 而我们恰恰需要保留「null = 没有」这个语义,供「恢复默认」按钮判断)
|
||
defaultPrompt: _nullableNonEmpty(json['defaultPrompt']),
|
||
);
|
||
}
|
||
|
||
/// 列表里显示的两行预览:把换行压平,太长的截断
|
||
String get preview {
|
||
final flat = systemPrompt.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||
if (flat.length <= 50) return flat;
|
||
return '${flat.substring(0, 50)}…';
|
||
}
|
||
}
|
||
|
||
/// 空串/null 都返回 null,否则原样返回。
|
||
///
|
||
/// 不用 `asNonEmpty` 是因为它需要一个 fallback 字符串,而这里
|
||
/// 「有没有值」本身是有意义的(defaultPrompt 只有默认项才有)。
|
||
String? _nullableNonEmpty(Object? value) {
|
||
if (value == null) return null;
|
||
final s = value.toString();
|
||
return s.isEmpty ? null : s;
|
||
}
|