fix: 修复 .gitignore 误忽略 Flutter data/models/ 源码导致构建失败
问题描述:
在其他机器上构建报错:
"Error when reading 'lib/features/auth/data/models/auth_response.dart': 系统找不到指定的路径"
导致 AuthUser、AuthResponse 等类型找不到,编译失败。
根本原因:
根目录 .gitignore 第75行 "models/" 规则本意是忽略 ML 模型大文件,
但该规则匹配了所有目录名为 models/ 的路径,包括 Flutter 项目中
DDD 架构的 data/models/ 源码目录(共 11 个 models/ 目录、10 个 .dart 文件)。
这些文件在本地存在但从未被 Git 追踪,其他机器 pull 后缺失这些文件。
修复内容:
1. 修改 .gitignore: 将宽泛的 "models/" 替换为精确的规则
- packages/services/voice-service/models/ — voice-service 下载的 ML 模型
- *.pt, *.pth, *.safetensors — PyTorch/HuggingFace 模型二进制文件
- 不再影响 Flutter 的 data/models/ 源码目录
2. 提交之前被忽略的 10 个 Flutter model 文件:
- auth/data/models/auth_response.dart — 登录响应 (accessToken, refreshToken, user)
- chat/data/models/chat_message_model.dart — 聊天消息模型
- chat/data/models/session_model.dart — 会话模型
- chat/data/models/stream_event_model.dart — SSE 流事件模型
- servers/data/models/server_model.dart — 服务器状态模型
- approvals/data/models/approval_model.dart — 审批请求模型
- alerts/data/models/alert_event_model.dart — 告警事件模型
- agent_call/data/models/voice_session_model.dart — 语音会话模型
- standing_orders/data/models/standing_order_model.dart — 常设指令模型
- tasks/data/models/task_model.dart — 任务模型
3. 同时提交:
- it0_app/test/widget_test.dart — Flutter 默认测试
- packages/services/voice-service/src/models/__init__.py — Python 模块初始化
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
913267fb9d
commit
4e1b75483d
|
|
@ -71,8 +71,12 @@ __pycache__/
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
# Models (large files)
|
# ML model binary files (large, downloaded at runtime)
|
||||||
models/
|
# Note: do NOT use bare "models/" — it ignores Flutter data/models/ source code
|
||||||
|
packages/services/voice-service/models/
|
||||||
|
*.pt
|
||||||
|
*.pth
|
||||||
|
*.safetensors
|
||||||
|
|
||||||
# Turbo
|
# Turbo
|
||||||
.turbo/
|
.turbo/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import '../../domain/entities/voice_session.dart';
|
||||||
|
|
||||||
|
class VoiceSessionModel {
|
||||||
|
final String id;
|
||||||
|
final String? websocketUrl;
|
||||||
|
final String status;
|
||||||
|
final String? startedAt;
|
||||||
|
final String? endedAt;
|
||||||
|
|
||||||
|
const VoiceSessionModel({
|
||||||
|
required this.id,
|
||||||
|
this.websocketUrl,
|
||||||
|
required this.status,
|
||||||
|
this.startedAt,
|
||||||
|
this.endedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory VoiceSessionModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return VoiceSessionModel(
|
||||||
|
id: json['id'] as String? ?? json['sessionId'] as String? ?? '',
|
||||||
|
websocketUrl: json['websocket_url'] as String? ??
|
||||||
|
json['ws_url'] as String? ??
|
||||||
|
json['websocketUrl'] as String?,
|
||||||
|
status: json['status'] as String? ?? 'active',
|
||||||
|
startedAt: json['startedAt'] as String? ??
|
||||||
|
json['started_at'] as String? ??
|
||||||
|
json['createdAt'] as String? ??
|
||||||
|
json['created_at'] as String?,
|
||||||
|
endedAt: json['endedAt'] as String? ?? json['ended_at'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
if (websocketUrl != null) 'websocket_url': websocketUrl,
|
||||||
|
'status': status,
|
||||||
|
if (startedAt != null) 'startedAt': startedAt,
|
||||||
|
if (endedAt != null) 'endedAt': endedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [VoiceSession] entity.
|
||||||
|
VoiceSession toEntity() {
|
||||||
|
return VoiceSession(
|
||||||
|
id: id,
|
||||||
|
websocketUrl: websocketUrl,
|
||||||
|
status: _parseStatus(status),
|
||||||
|
startedAt: DateTime.tryParse(startedAt ?? '') ?? DateTime.now(),
|
||||||
|
endedAt: endedAt != null ? DateTime.tryParse(endedAt!) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VoiceSessionStatus _parseStatus(String status) {
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case 'ringing':
|
||||||
|
return VoiceSessionStatus.ringing;
|
||||||
|
case 'connecting':
|
||||||
|
return VoiceSessionStatus.connecting;
|
||||||
|
case 'active':
|
||||||
|
case 'connected':
|
||||||
|
return VoiceSessionStatus.active;
|
||||||
|
case 'ended':
|
||||||
|
case 'closed':
|
||||||
|
return VoiceSessionStatus.ended;
|
||||||
|
case 'error':
|
||||||
|
return VoiceSessionStatus.error;
|
||||||
|
default:
|
||||||
|
return VoiceSessionStatus.connecting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import '../../domain/entities/alert_event.dart';
|
||||||
|
|
||||||
|
class AlertEventModel {
|
||||||
|
final String id;
|
||||||
|
final String? ruleId;
|
||||||
|
final String? serverId;
|
||||||
|
final String severity;
|
||||||
|
final String? message;
|
||||||
|
final bool acknowledged;
|
||||||
|
final String? createdAt;
|
||||||
|
|
||||||
|
const AlertEventModel({
|
||||||
|
required this.id,
|
||||||
|
this.ruleId,
|
||||||
|
this.serverId,
|
||||||
|
required this.severity,
|
||||||
|
this.message,
|
||||||
|
required this.acknowledged,
|
||||||
|
this.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AlertEventModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AlertEventModel(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
ruleId: json['ruleId'] as String? ?? json['rule_id'] as String?,
|
||||||
|
serverId:
|
||||||
|
json['serverId'] as String? ?? json['server_id'] as String?,
|
||||||
|
severity: json['severity'] as String? ?? 'info',
|
||||||
|
message: json['message'] as String?,
|
||||||
|
acknowledged: json['acknowledged'] as bool? ?? false,
|
||||||
|
createdAt:
|
||||||
|
json['createdAt'] as String? ?? json['created_at'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
if (ruleId != null) 'ruleId': ruleId,
|
||||||
|
if (serverId != null) 'serverId': serverId,
|
||||||
|
'severity': severity,
|
||||||
|
if (message != null) 'message': message,
|
||||||
|
'acknowledged': acknowledged,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [AlertEvent] entity.
|
||||||
|
AlertEvent toEntity() {
|
||||||
|
return AlertEvent(
|
||||||
|
id: id,
|
||||||
|
ruleId: ruleId,
|
||||||
|
serverId: serverId,
|
||||||
|
severity: severity,
|
||||||
|
message: message,
|
||||||
|
acknowledged: acknowledged,
|
||||||
|
createdAt: _parseDateTime(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [AlertEvent] entity.
|
||||||
|
factory AlertEventModel.fromEntity(AlertEvent entity) {
|
||||||
|
return AlertEventModel(
|
||||||
|
id: entity.id,
|
||||||
|
ruleId: entity.ruleId,
|
||||||
|
serverId: entity.serverId,
|
||||||
|
severity: entity.severity,
|
||||||
|
message: entity.message,
|
||||||
|
acknowledged: entity.acknowledged,
|
||||||
|
createdAt: entity.createdAt?.toIso8601String(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return DateTime.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import '../../domain/entities/approval.dart';
|
||||||
|
|
||||||
|
class ApprovalModel {
|
||||||
|
final String id;
|
||||||
|
final String? taskId;
|
||||||
|
final String? taskDescription;
|
||||||
|
final String? command;
|
||||||
|
final String? riskLevel;
|
||||||
|
final String? requestedBy;
|
||||||
|
final String status;
|
||||||
|
final String? expiresAt;
|
||||||
|
final String? createdAt;
|
||||||
|
|
||||||
|
const ApprovalModel({
|
||||||
|
required this.id,
|
||||||
|
this.taskId,
|
||||||
|
this.taskDescription,
|
||||||
|
this.command,
|
||||||
|
this.riskLevel,
|
||||||
|
this.requestedBy,
|
||||||
|
required this.status,
|
||||||
|
this.expiresAt,
|
||||||
|
this.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ApprovalModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ApprovalModel(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
taskId: json['taskId'] as String? ?? json['task_id'] as String?,
|
||||||
|
taskDescription: json['taskDescription'] as String? ??
|
||||||
|
json['task_description'] as String?,
|
||||||
|
command: json['command'] as String?,
|
||||||
|
riskLevel:
|
||||||
|
json['riskLevel'] as String? ?? json['risk_level'] as String?,
|
||||||
|
requestedBy: json['requestedBy'] as String? ??
|
||||||
|
json['requested_by'] as String?,
|
||||||
|
status: json['status'] as String? ?? 'pending',
|
||||||
|
expiresAt:
|
||||||
|
json['expiresAt'] as String? ?? json['expires_at'] as String?,
|
||||||
|
createdAt:
|
||||||
|
json['createdAt'] as String? ?? json['created_at'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
if (taskId != null) 'taskId': taskId,
|
||||||
|
if (taskDescription != null) 'taskDescription': taskDescription,
|
||||||
|
if (command != null) 'command': command,
|
||||||
|
if (riskLevel != null) 'riskLevel': riskLevel,
|
||||||
|
if (requestedBy != null) 'requestedBy': requestedBy,
|
||||||
|
'status': status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [Approval] entity.
|
||||||
|
Approval toEntity() {
|
||||||
|
return Approval(
|
||||||
|
id: id,
|
||||||
|
taskId: taskId,
|
||||||
|
taskDescription: taskDescription,
|
||||||
|
command: command,
|
||||||
|
riskLevel: riskLevel,
|
||||||
|
requestedBy: requestedBy,
|
||||||
|
status: status,
|
||||||
|
expiresAt: _parseDateTime(expiresAt),
|
||||||
|
createdAt: _parseDateTime(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [Approval] entity.
|
||||||
|
factory ApprovalModel.fromEntity(Approval entity) {
|
||||||
|
return ApprovalModel(
|
||||||
|
id: entity.id,
|
||||||
|
taskId: entity.taskId,
|
||||||
|
taskDescription: entity.taskDescription,
|
||||||
|
command: entity.command,
|
||||||
|
riskLevel: entity.riskLevel,
|
||||||
|
requestedBy: entity.requestedBy,
|
||||||
|
status: entity.status,
|
||||||
|
expiresAt: entity.expiresAt?.toIso8601String(),
|
||||||
|
createdAt: entity.createdAt?.toIso8601String(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return DateTime.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
class AuthResponse {
|
||||||
|
final String accessToken;
|
||||||
|
final String refreshToken;
|
||||||
|
final AuthUser user;
|
||||||
|
|
||||||
|
const AuthResponse({
|
||||||
|
required this.accessToken,
|
||||||
|
required this.refreshToken,
|
||||||
|
required this.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AuthResponse(
|
||||||
|
accessToken: json['accessToken'] as String,
|
||||||
|
refreshToken: json['refreshToken'] as String,
|
||||||
|
user: AuthUser.fromJson(json['user'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthUser {
|
||||||
|
final String id;
|
||||||
|
final String email;
|
||||||
|
final String name;
|
||||||
|
final List<String> roles;
|
||||||
|
|
||||||
|
const AuthUser({
|
||||||
|
required this.id,
|
||||||
|
required this.email,
|
||||||
|
required this.name,
|
||||||
|
required this.roles,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AuthUser.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AuthUser(
|
||||||
|
id: json['id'] as String,
|
||||||
|
email: json['email'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
roles: (json['roles'] as List).cast<String>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
import '../../domain/entities/chat_message.dart';
|
||||||
|
|
||||||
|
class ChatMessageModel {
|
||||||
|
final String id;
|
||||||
|
final String role;
|
||||||
|
final String content;
|
||||||
|
final String timestamp;
|
||||||
|
final String? type;
|
||||||
|
final Map<String, dynamic>? toolExecution;
|
||||||
|
final Map<String, dynamic>? approvalRequest;
|
||||||
|
final Map<String, dynamic>? metadata;
|
||||||
|
|
||||||
|
const ChatMessageModel({
|
||||||
|
required this.id,
|
||||||
|
required this.role,
|
||||||
|
required this.content,
|
||||||
|
required this.timestamp,
|
||||||
|
this.type,
|
||||||
|
this.toolExecution,
|
||||||
|
this.approvalRequest,
|
||||||
|
this.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ChatMessageModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ChatMessageModel(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
role: json['role'] as String? ?? 'assistant',
|
||||||
|
content: json['content'] as String? ?? json['text'] as String? ?? '',
|
||||||
|
timestamp: json['timestamp'] as String? ??
|
||||||
|
json['created_at'] as String? ??
|
||||||
|
json['createdAt'] as String? ??
|
||||||
|
DateTime.now().toIso8601String(),
|
||||||
|
type: json['type'] as String?,
|
||||||
|
toolExecution: json['tool_execution'] as Map<String, dynamic>? ??
|
||||||
|
json['toolExecution'] as Map<String, dynamic>?,
|
||||||
|
approvalRequest: json['approval_request'] as Map<String, dynamic>? ??
|
||||||
|
json['approvalRequest'] as Map<String, dynamic>?,
|
||||||
|
metadata: json['metadata'] as Map<String, dynamic>?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'role': role,
|
||||||
|
'content': content,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
if (type != null) 'type': type,
|
||||||
|
if (toolExecution != null) 'toolExecution': toolExecution,
|
||||||
|
if (approvalRequest != null) 'approvalRequest': approvalRequest,
|
||||||
|
if (metadata != null) 'metadata': metadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [ChatMessage] entity.
|
||||||
|
ChatMessage toEntity() {
|
||||||
|
return ChatMessage(
|
||||||
|
id: id,
|
||||||
|
role: _parseRole(role),
|
||||||
|
content: content,
|
||||||
|
timestamp: DateTime.tryParse(timestamp) ?? DateTime.now(),
|
||||||
|
type: _parseType(type),
|
||||||
|
toolExecution: toolExecution != null ? _parseToolExecution(toolExecution!) : null,
|
||||||
|
approvalRequest: approvalRequest != null ? _parseApprovalRequest(approvalRequest!) : null,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [ChatMessage] entity.
|
||||||
|
factory ChatMessageModel.fromEntity(ChatMessage entity) {
|
||||||
|
return ChatMessageModel(
|
||||||
|
id: entity.id,
|
||||||
|
role: entity.role.name,
|
||||||
|
content: entity.content,
|
||||||
|
timestamp: entity.timestamp.toIso8601String(),
|
||||||
|
type: entity.type?.name,
|
||||||
|
toolExecution: entity.toolExecution != null
|
||||||
|
? {
|
||||||
|
'toolName': entity.toolExecution!.toolName,
|
||||||
|
'input': entity.toolExecution!.input,
|
||||||
|
'output': entity.toolExecution!.output,
|
||||||
|
'riskLevel': entity.toolExecution!.riskLevel,
|
||||||
|
'status': entity.toolExecution!.status.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
approvalRequest: entity.approvalRequest != null
|
||||||
|
? {
|
||||||
|
'taskId': entity.approvalRequest!.taskId,
|
||||||
|
'command': entity.approvalRequest!.command,
|
||||||
|
'riskLevel': entity.approvalRequest!.riskLevel,
|
||||||
|
'targetServer': entity.approvalRequest!.targetServer,
|
||||||
|
'expiresAt': entity.approvalRequest!.expiresAt.toIso8601String(),
|
||||||
|
'status': entity.approvalRequest!.status,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
metadata: entity.metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MessageRole _parseRole(String role) {
|
||||||
|
switch (role.toLowerCase()) {
|
||||||
|
case 'user':
|
||||||
|
return MessageRole.user;
|
||||||
|
case 'system':
|
||||||
|
return MessageRole.system;
|
||||||
|
case 'assistant':
|
||||||
|
default:
|
||||||
|
return MessageRole.assistant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MessageType? _parseType(String? type) {
|
||||||
|
if (type == null) return null;
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'tool_use':
|
||||||
|
case 'tooluse':
|
||||||
|
return MessageType.toolUse;
|
||||||
|
case 'tool_result':
|
||||||
|
case 'toolresult':
|
||||||
|
return MessageType.toolResult;
|
||||||
|
case 'approval':
|
||||||
|
return MessageType.approval;
|
||||||
|
case 'thinking':
|
||||||
|
return MessageType.thinking;
|
||||||
|
case 'standing_order_draft':
|
||||||
|
case 'standingorderdraft':
|
||||||
|
return MessageType.standingOrderDraft;
|
||||||
|
case 'text':
|
||||||
|
default:
|
||||||
|
return MessageType.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ToolExecution _parseToolExecution(Map<String, dynamic> json) {
|
||||||
|
return ToolExecution(
|
||||||
|
toolName: json['toolName'] as String? ?? json['tool_name'] as String? ?? '',
|
||||||
|
input: json['input'] as String? ?? '',
|
||||||
|
output: json['output'] as String?,
|
||||||
|
riskLevel: json['riskLevel'] as int? ?? json['risk_level'] as int? ?? 0,
|
||||||
|
status: _parseToolStatus(json['status'] as String? ?? 'executing'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ToolStatus _parseToolStatus(String status) {
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case 'completed':
|
||||||
|
return ToolStatus.completed;
|
||||||
|
case 'error':
|
||||||
|
return ToolStatus.error;
|
||||||
|
case 'blocked':
|
||||||
|
return ToolStatus.blocked;
|
||||||
|
case 'awaiting_approval':
|
||||||
|
case 'awaitingapproval':
|
||||||
|
return ToolStatus.awaitingApproval;
|
||||||
|
case 'executing':
|
||||||
|
default:
|
||||||
|
return ToolStatus.executing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ApprovalRequest _parseApprovalRequest(Map<String, dynamic> json) {
|
||||||
|
return ApprovalRequest(
|
||||||
|
taskId: json['taskId'] as String? ?? json['task_id'] as String? ?? '',
|
||||||
|
command: json['command'] as String? ?? '',
|
||||||
|
riskLevel: json['riskLevel'] as int? ?? json['risk_level'] as int? ?? 0,
|
||||||
|
targetServer: json['targetServer'] as String? ?? json['target_server'] as String?,
|
||||||
|
expiresAt: DateTime.tryParse(
|
||||||
|
json['expiresAt'] as String? ?? json['expires_at'] as String? ?? '',
|
||||||
|
) ??
|
||||||
|
DateTime.now().add(const Duration(minutes: 5)),
|
||||||
|
status: json['status'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import '../../domain/entities/chat_session.dart';
|
||||||
|
|
||||||
|
class SessionModel {
|
||||||
|
final String id;
|
||||||
|
final String? tenantId;
|
||||||
|
final String status;
|
||||||
|
final String startedAt;
|
||||||
|
final String? endedAt;
|
||||||
|
final int messageCount;
|
||||||
|
|
||||||
|
const SessionModel({
|
||||||
|
required this.id,
|
||||||
|
this.tenantId,
|
||||||
|
required this.status,
|
||||||
|
required this.startedAt,
|
||||||
|
this.endedAt,
|
||||||
|
this.messageCount = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SessionModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SessionModel(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
tenantId: json['tenantId'] as String? ?? json['tenant_id'] as String?,
|
||||||
|
status: json['status'] as String? ?? 'active',
|
||||||
|
startedAt: json['startedAt'] as String? ??
|
||||||
|
json['started_at'] as String? ??
|
||||||
|
json['created_at'] as String? ??
|
||||||
|
json['createdAt'] as String? ??
|
||||||
|
DateTime.now().toIso8601String(),
|
||||||
|
endedAt: json['endedAt'] as String? ?? json['ended_at'] as String?,
|
||||||
|
messageCount: json['messageCount'] as int? ??
|
||||||
|
json['message_count'] as int? ??
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
if (tenantId != null) 'tenantId': tenantId,
|
||||||
|
'status': status,
|
||||||
|
'startedAt': startedAt,
|
||||||
|
if (endedAt != null) 'endedAt': endedAt,
|
||||||
|
'messageCount': messageCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [ChatSession] entity.
|
||||||
|
ChatSession toEntity() {
|
||||||
|
return ChatSession(
|
||||||
|
id: id,
|
||||||
|
tenantId: tenantId,
|
||||||
|
status: status,
|
||||||
|
startedAt: DateTime.tryParse(startedAt) ?? DateTime.now(),
|
||||||
|
endedAt: endedAt != null ? DateTime.tryParse(endedAt!) : null,
|
||||||
|
messageCount: messageCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [ChatSession] entity.
|
||||||
|
factory SessionModel.fromEntity(ChatSession entity) {
|
||||||
|
return SessionModel(
|
||||||
|
id: entity.id,
|
||||||
|
tenantId: entity.tenantId,
|
||||||
|
status: entity.status,
|
||||||
|
startedAt: entity.startedAt.toIso8601String(),
|
||||||
|
endedAt: entity.endedAt?.toIso8601String(),
|
||||||
|
messageCount: entity.messageCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import '../../domain/entities/stream_event.dart';
|
||||||
|
|
||||||
|
class StreamEventModel {
|
||||||
|
final String type;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
const StreamEventModel({
|
||||||
|
required this.type,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory StreamEventModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
final type = json['type'] as String? ??
|
||||||
|
json['event'] as String? ??
|
||||||
|
'text';
|
||||||
|
|
||||||
|
// The data payload may be nested under 'data' or at the top level
|
||||||
|
final data = json['data'] as Map<String, dynamic>? ?? json;
|
||||||
|
|
||||||
|
return StreamEventModel(type: type, data: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [StreamEvent].
|
||||||
|
StreamEvent toEntity() {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'thinking':
|
||||||
|
return ThinkingEvent(
|
||||||
|
data['content'] as String? ?? data['text'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'text':
|
||||||
|
case 'message':
|
||||||
|
case 'stream_event':
|
||||||
|
return TextEvent(
|
||||||
|
data['content'] as String? ?? data['text'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'tool_use':
|
||||||
|
case 'tool_call':
|
||||||
|
return ToolUseEvent(
|
||||||
|
data['toolName'] as String? ?? data['tool_name'] as String? ?? data['name'] as String? ?? '',
|
||||||
|
data['input'] as Map<String, dynamic>? ?? {},
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'tool_result':
|
||||||
|
return ToolResultEvent(
|
||||||
|
data['toolName'] as String? ?? data['tool_name'] as String? ?? '',
|
||||||
|
data['output'] as String? ?? data['content'] as String? ?? '',
|
||||||
|
data['isError'] as bool? ?? data['is_error'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'approval_required':
|
||||||
|
case 'approval':
|
||||||
|
return ApprovalRequiredEvent(
|
||||||
|
data['taskId'] as String? ?? data['task_id'] as String? ?? '',
|
||||||
|
data['command'] as String? ?? '',
|
||||||
|
data['riskLevel'] as int? ?? data['risk_level'] as int? ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'completed':
|
||||||
|
case 'done':
|
||||||
|
case 'stream_end':
|
||||||
|
return CompletedEvent(
|
||||||
|
data['summary'] as String? ?? data['content'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
return ErrorEvent(
|
||||||
|
data['message'] as String? ?? data['error'] as String? ?? 'Unknown error',
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'standing_order_draft':
|
||||||
|
return StandingOrderDraftEvent(
|
||||||
|
data['draft'] as Map<String, dynamic>? ?? data,
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'standing_order_confirmed':
|
||||||
|
return StandingOrderConfirmedEvent(
|
||||||
|
data['orderId'] as String? ?? data['order_id'] as String? ?? '',
|
||||||
|
data['orderName'] as String? ?? data['order_name'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Fall back to text event for unknown types
|
||||||
|
return TextEvent(
|
||||||
|
data['content'] as String? ?? data['text'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import '../../domain/entities/server.dart';
|
||||||
|
|
||||||
|
class ServerModel {
|
||||||
|
final String id;
|
||||||
|
final String hostname;
|
||||||
|
final String? ip;
|
||||||
|
final String? environment;
|
||||||
|
final String? role;
|
||||||
|
final String status;
|
||||||
|
final String? lastCheckedAt;
|
||||||
|
final List<String>? tags;
|
||||||
|
|
||||||
|
const ServerModel({
|
||||||
|
required this.id,
|
||||||
|
required this.hostname,
|
||||||
|
this.ip,
|
||||||
|
this.environment,
|
||||||
|
this.role,
|
||||||
|
required this.status,
|
||||||
|
this.lastCheckedAt,
|
||||||
|
this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ServerModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ServerModel(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
hostname: json['hostname'] as String? ?? 'unknown',
|
||||||
|
ip: json['ip'] as String? ?? json['ipAddress'] as String? ??
|
||||||
|
json['ip_address'] as String?,
|
||||||
|
environment: json['environment'] as String?,
|
||||||
|
role: json['role'] as String?,
|
||||||
|
status: json['status'] as String? ?? 'unknown',
|
||||||
|
lastCheckedAt: json['lastCheckedAt'] as String? ??
|
||||||
|
json['last_checked_at'] as String?,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
|
?.map((t) => t.toString())
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'hostname': hostname,
|
||||||
|
if (ip != null) 'ip': ip,
|
||||||
|
if (environment != null) 'environment': environment,
|
||||||
|
if (role != null) 'role': role,
|
||||||
|
'status': status,
|
||||||
|
if (tags != null) 'tags': tags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [Server] entity.
|
||||||
|
Server toEntity() {
|
||||||
|
return Server(
|
||||||
|
id: id,
|
||||||
|
hostname: hostname,
|
||||||
|
ip: ip,
|
||||||
|
environment: environment,
|
||||||
|
role: role,
|
||||||
|
status: status,
|
||||||
|
lastCheckedAt: _parseDateTime(lastCheckedAt),
|
||||||
|
tags: tags,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [Server] entity.
|
||||||
|
factory ServerModel.fromEntity(Server entity) {
|
||||||
|
return ServerModel(
|
||||||
|
id: entity.id,
|
||||||
|
hostname: entity.hostname,
|
||||||
|
ip: entity.ip,
|
||||||
|
environment: entity.environment,
|
||||||
|
role: entity.role,
|
||||||
|
status: entity.status,
|
||||||
|
lastCheckedAt: entity.lastCheckedAt?.toIso8601String(),
|
||||||
|
tags: entity.tags,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return DateTime.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import '../../domain/entities/standing_order.dart';
|
||||||
|
|
||||||
|
class StandingOrderModel {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String triggerType;
|
||||||
|
final String? schedule;
|
||||||
|
final String? agentInstructions;
|
||||||
|
final String? decisionBoundary;
|
||||||
|
final bool isActive;
|
||||||
|
final String? lastExecutedAt;
|
||||||
|
final String? createdAt;
|
||||||
|
|
||||||
|
const StandingOrderModel({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.triggerType,
|
||||||
|
this.schedule,
|
||||||
|
this.agentInstructions,
|
||||||
|
this.decisionBoundary,
|
||||||
|
required this.isActive,
|
||||||
|
this.lastExecutedAt,
|
||||||
|
this.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory StandingOrderModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return StandingOrderModel(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
name: json['name'] as String? ?? 'Untitled',
|
||||||
|
triggerType: json['triggerType'] as String? ??
|
||||||
|
json['trigger_type'] as String? ??
|
||||||
|
'cron',
|
||||||
|
schedule: json['schedule'] as String?,
|
||||||
|
agentInstructions: json['agentInstructions'] as String? ??
|
||||||
|
json['agent_instructions'] as String?,
|
||||||
|
decisionBoundary: json['decisionBoundary'] as String? ??
|
||||||
|
json['decision_boundary'] as String?,
|
||||||
|
isActive: json['isActive'] as bool? ??
|
||||||
|
json['is_active'] as bool? ??
|
||||||
|
false,
|
||||||
|
lastExecutedAt: json['lastExecutedAt'] as String? ??
|
||||||
|
json['last_executed_at'] as String?,
|
||||||
|
createdAt: json['createdAt'] as String? ??
|
||||||
|
json['created_at'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'triggerType': triggerType,
|
||||||
|
if (schedule != null) 'schedule': schedule,
|
||||||
|
if (agentInstructions != null) 'agentInstructions': agentInstructions,
|
||||||
|
if (decisionBoundary != null) 'decisionBoundary': decisionBoundary,
|
||||||
|
'isActive': isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [StandingOrder] entity.
|
||||||
|
StandingOrder toEntity() {
|
||||||
|
return StandingOrder(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
triggerType: triggerType,
|
||||||
|
schedule: schedule,
|
||||||
|
agentInstructions: agentInstructions,
|
||||||
|
decisionBoundary: decisionBoundary,
|
||||||
|
isActive: isActive,
|
||||||
|
lastExecutedAt: _parseDateTime(lastExecutedAt),
|
||||||
|
createdAt: _parseDateTime(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [StandingOrder] entity.
|
||||||
|
factory StandingOrderModel.fromEntity(StandingOrder entity) {
|
||||||
|
return StandingOrderModel(
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
triggerType: entity.triggerType,
|
||||||
|
schedule: entity.schedule,
|
||||||
|
agentInstructions: entity.agentInstructions,
|
||||||
|
decisionBoundary: entity.decisionBoundary,
|
||||||
|
isActive: entity.isActive,
|
||||||
|
lastExecutedAt: entity.lastExecutedAt?.toIso8601String(),
|
||||||
|
createdAt: entity.createdAt?.toIso8601String(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return DateTime.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
import '../../domain/entities/task.dart';
|
||||||
|
|
||||||
|
class TaskModel {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String? description;
|
||||||
|
final String status;
|
||||||
|
final String? priority;
|
||||||
|
final String? serverId;
|
||||||
|
final String? serverName;
|
||||||
|
final String? assignedTo;
|
||||||
|
final String? createdAt;
|
||||||
|
final String? updatedAt;
|
||||||
|
final String? completedAt;
|
||||||
|
|
||||||
|
const TaskModel({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
this.description,
|
||||||
|
required this.status,
|
||||||
|
this.priority,
|
||||||
|
this.serverId,
|
||||||
|
this.serverName,
|
||||||
|
this.assignedTo,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.completedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TaskModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TaskModel(
|
||||||
|
id: json['id']?.toString() ?? '',
|
||||||
|
title: json['title'] as String? ?? json['name'] as String? ?? 'Untitled',
|
||||||
|
description: json['description'] as String?,
|
||||||
|
status: json['status'] as String? ?? 'unknown',
|
||||||
|
priority: json['priority'] as String?,
|
||||||
|
serverId: json['serverId'] as String? ?? json['server_id'] as String?,
|
||||||
|
serverName: json['serverName'] as String? ?? json['server_name'] as String?,
|
||||||
|
assignedTo: json['assignedTo'] as String? ?? json['assigned_to'] as String?,
|
||||||
|
createdAt: json['createdAt'] as String? ?? json['created_at'] as String?,
|
||||||
|
updatedAt: json['updatedAt'] as String? ?? json['updated_at'] as String?,
|
||||||
|
completedAt: json['completedAt'] as String? ?? json['completed_at'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'title': title,
|
||||||
|
if (description != null) 'description': description,
|
||||||
|
'status': status,
|
||||||
|
if (priority != null) 'priority': priority,
|
||||||
|
if (serverId != null) 'serverId': serverId,
|
||||||
|
if (assignedTo != null) 'assignedTo': assignedTo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this model to a domain [Task] entity.
|
||||||
|
Task toEntity() {
|
||||||
|
return Task(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
status: status,
|
||||||
|
priority: priority,
|
||||||
|
serverId: serverId,
|
||||||
|
serverName: serverName,
|
||||||
|
assignedTo: assignedTo,
|
||||||
|
createdAt: _parseDateTime(createdAt),
|
||||||
|
updatedAt: _parseDateTime(updatedAt),
|
||||||
|
completedAt: _parseDateTime(completedAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a model from a domain [Task] entity.
|
||||||
|
factory TaskModel.fromEntity(Task entity) {
|
||||||
|
return TaskModel(
|
||||||
|
id: entity.id,
|
||||||
|
title: entity.title,
|
||||||
|
description: entity.description,
|
||||||
|
status: entity.status,
|
||||||
|
priority: entity.priority,
|
||||||
|
serverId: entity.serverId,
|
||||||
|
serverName: entity.serverName,
|
||||||
|
assignedTo: entity.assignedTo,
|
||||||
|
createdAt: entity.createdAt?.toIso8601String(),
|
||||||
|
updatedAt: entity.updatedAt?.toIso8601String(),
|
||||||
|
completedAt: entity.completedAt?.toIso8601String(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return DateTime.tryParse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:it0_app/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
// Build our app and trigger a frame.
|
||||||
|
await tester.pumpWidget(const MyApp());
|
||||||
|
|
||||||
|
// Verify that our counter starts at 0.
|
||||||
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
expect(find.text('1'), findsNothing);
|
||||||
|
|
||||||
|
// Tap the '+' icon and trigger a frame.
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify that our counter has incremented.
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(find.text('1'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
Loading…
Reference in New Issue