Initial commit: DLS - Dental Lab System
- Flutter + PocketBase dental lab management system - Clinic & lab dashboards, job tracking, patient management - Product catalog, finance tracking, multi-language support - AI assistant integration, realtime notifications - Windows installer (Inno Setup) included - Developed by kovakyazilim.com
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import '../../features/shared/job_files_repository.dart';
|
||||
import '../../features/shared/tenant_team_repository.dart';
|
||||
import '../../models/job_file.dart';
|
||||
import '../../models/tenant.dart';
|
||||
import '../api/pocketbase_client.dart';
|
||||
|
||||
// ── Message segments ──────────────────────────────────────────────────────────
|
||||
|
||||
sealed class MessageSegment {}
|
||||
|
||||
class TextSegment extends MessageSegment {
|
||||
TextSegment(this.text);
|
||||
final String text;
|
||||
}
|
||||
|
||||
class ActionSegment extends MessageSegment {
|
||||
ActionSegment(this.action);
|
||||
final AiAction action;
|
||||
}
|
||||
|
||||
// ── Action model ──────────────────────────────────────────────────────────────
|
||||
|
||||
class AiAction {
|
||||
const AiAction({
|
||||
required this.type,
|
||||
required this.params,
|
||||
required this.label,
|
||||
});
|
||||
final String type;
|
||||
final Map<String, String> params;
|
||||
final String label;
|
||||
|
||||
bool get isDangerous => type == 'cancel_job';
|
||||
bool get isFileAction => type == 'job_files';
|
||||
}
|
||||
|
||||
// ── Action outcome ────────────────────────────────────────────────────────────
|
||||
|
||||
sealed class ActionOutcome {}
|
||||
|
||||
class ActionSuccess extends ActionOutcome {
|
||||
ActionSuccess(this.message);
|
||||
final String message;
|
||||
}
|
||||
|
||||
class ActionError extends ActionOutcome {
|
||||
ActionError(this.error);
|
||||
final String error;
|
||||
}
|
||||
|
||||
class ActionFiles extends ActionOutcome {
|
||||
ActionFiles(this.files);
|
||||
final List<JobFile> files;
|
||||
}
|
||||
|
||||
// ── Parser ────────────────────────────────────────────────────────────────────
|
||||
|
||||
List<MessageSegment> parseSegments(String text) {
|
||||
// Strip code fences wrapping <dls-action> tags that the AI sometimes emits.
|
||||
// Handles: ```xml\n<dls-action .../>\n``` and ```\n<dls-action .../>\n```
|
||||
text = text.replaceAllMapped(
|
||||
RegExp(r'```(?:xml)?\s*\n(\s*<dls-action\s[^>]*/>)\s*\n\s*```'),
|
||||
(m) => m.group(1)!,
|
||||
);
|
||||
// Also handle inline variant: ```xml <dls-action .../> ```
|
||||
text = text.replaceAllMapped(
|
||||
RegExp(r'```(?:xml)?\s*(<dls-action\s[^>]*/>)\s*```'),
|
||||
(m) => m.group(1)!,
|
||||
);
|
||||
|
||||
final pattern = RegExp(r'<dls-action\s([^/]*?)/>', dotAll: true);
|
||||
final segments = <MessageSegment>[];
|
||||
int last = 0;
|
||||
|
||||
for (final m in pattern.allMatches(text)) {
|
||||
final before = text.substring(last, m.start).trim();
|
||||
if (before.isNotEmpty) segments.add(TextSegment(before));
|
||||
|
||||
final attrs = _parseAttrs(m.group(1) ?? '');
|
||||
segments.add(ActionSegment(AiAction(
|
||||
type: attrs['type'] ?? '',
|
||||
params: attrs,
|
||||
label: attrs['label'] ?? attrs['type'] ?? 'İşlem',
|
||||
)));
|
||||
last = m.end;
|
||||
}
|
||||
|
||||
final rest = text.substring(last).trim();
|
||||
if (rest.isNotEmpty) segments.add(TextSegment(rest));
|
||||
return segments;
|
||||
}
|
||||
|
||||
Map<String, String> _parseAttrs(String s) {
|
||||
final result = <String, String>{};
|
||||
for (final m in RegExp(r'(\w+)="([^"]*)"').allMatches(s)) {
|
||||
result[m.group(1)!] = m.group(2)!;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Executor ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class AiActionExecutor {
|
||||
static final _pb = PocketBaseClient.instance.pb;
|
||||
|
||||
static Future<ActionOutcome> execute(
|
||||
AiAction action,
|
||||
TenantMembership membership,
|
||||
) async {
|
||||
try {
|
||||
return switch (action.type) {
|
||||
'cancel_job' => await _cancelJob(action.params),
|
||||
'mark_delivered' => await _markDelivered(action.params),
|
||||
'job_files' => await _jobFiles(action.params),
|
||||
'add_member' => await _addMember(action.params, membership),
|
||||
_ => ActionError('Bilinmeyen işlem türü: ${action.type}'),
|
||||
};
|
||||
} catch (e) {
|
||||
final msg = e.toString();
|
||||
if (msg.length > 120) return ActionError('Sunucu hatası');
|
||||
return ActionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<ActionOutcome> _cancelJob(Map<String, String> p) async {
|
||||
final id = p['job_id'];
|
||||
if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.');
|
||||
await _pb.collection('jobs').update(id, body: {'status': 'cancelled'});
|
||||
return ActionSuccess('İş başarıyla iptal edildi.');
|
||||
}
|
||||
|
||||
static Future<ActionOutcome> _markDelivered(Map<String, String> p) async {
|
||||
final id = p['job_id'];
|
||||
if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.');
|
||||
await _pb.collection('jobs').update(id, body: {'status': 'delivered'});
|
||||
return ActionSuccess('İş teslim edildi olarak işaretlendi.');
|
||||
}
|
||||
|
||||
static Future<ActionOutcome> _jobFiles(Map<String, String> p) async {
|
||||
final id = p['job_id'];
|
||||
if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.');
|
||||
final files = await JobFilesRepository.instance.listForJob(id);
|
||||
if (files.isEmpty) return ActionSuccess('Bu iş için henüz dosya yüklenmemiş.');
|
||||
return ActionFiles(files);
|
||||
}
|
||||
|
||||
static Future<ActionOutcome> _addMember(
|
||||
Map<String, String> p,
|
||||
TenantMembership membership,
|
||||
) async {
|
||||
final email = p['email'];
|
||||
final firstName = p['first_name'];
|
||||
final lastName = p['last_name'] ?? '';
|
||||
final role = p['role'];
|
||||
final password = p['password'];
|
||||
|
||||
if (email == null || firstName == null || role == null || password == null) {
|
||||
return ActionError('Eksik bilgi: e-posta, ad, rol ve şifre gerekli.');
|
||||
}
|
||||
|
||||
await TenantTeamRepository.instance.addMember(
|
||||
tenantId: membership.tenant.id,
|
||||
email: email,
|
||||
password: password,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
role: TenantMembership.parseRole(role),
|
||||
);
|
||||
return ActionSuccess('$firstName $lastName ekibe eklendi.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user