feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+286 -58
View File
@@ -1,11 +1,16 @@
import '../models/syncretic_session_model.dart';
import '../services/oracle_master_prompt_generator.dart';
import 'symbol_detail_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/history_entry_model.dart';
import '../models/word_model.dart';
import '../services/ai_oracle_service.dart';
import '../services/settings_repository.dart';
import '../services/words_repository.dart';
import '../widgets/d10_dice_roller_widget.dart';
import '../widgets/gradient_star_badge.dart';
import '../widgets/interpreter_persona_picker.dart';
class SessionResultScreen extends StatefulWidget {
final OracleSession session;
@@ -35,6 +40,33 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
_session = widget.session;
_repo.saveSession(_session);
// Auto-record this session into active Syncretic Day Session
final readingItems = _session.words.map((w) {
final fw = _repo.getWordByName(w.word) ?? _repo.getWordBySessionNumber(w.sessionNumber);
return ReadingSignItem(
title: '#${w.formattedSessionNumber} ${w.word.toUpperCase()}',
subtitle: w.category,
symbol: '🎲',
lore: w.symbolicMeaning,
linkedWord: fw,
);
}).toList();
_repo.addReadingToSyncreticSession(
SyncreticReading(
id: 'reading_${_session.id}',
systemId: 'cleromancy',
systemName: 'Оракул (Клеромантия)',
systemIcon: '🎲',
timestamp: _session.timestamp,
layoutTitle: '${_session.words.length} ${_session.words.length == 1 ? "знак" : "знаков"}',
inputMethod: 'dice',
items: readingItems,
question: _session.question,
),
question: _session.question,
);
// If initial interpretation is not yet done, trigger it
if (_session.aiInterpretation == null) {
_fetchInitialAi();
@@ -48,26 +80,32 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
super.dispose();
}
Future<void> _fetchInitialAi() async {
Future<void> _fetchInitialAi({bool replaceOpening = true}) async {
setState(() => _isGeneratingInitialAi = true);
try {
final voice = SettingsRepository().persona;
final result = await _aiService.interpretSession(
words: _session.words,
question: _session.question ?? '',
);
if (mounted) {
final initialMessage = SessionChatMessage(
id: 'msg_0',
final labeled = '[${voice.title}] $result';
final message = SessionChatMessage(
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
sender: 'oracle',
text: result,
text: labeled,
timestamp: DateTime.now(),
);
final nextMessages = replaceOpening && _session.messages.isEmpty
? [message]
: [..._session.messages, message];
final updated = _session.copyWith(
aiInterpretation: result,
messages: _session.messages.isEmpty ? [initialMessage] : _session.messages,
aiInterpretation: labeled,
messages: nextMessages,
);
setState(() {
@@ -190,9 +228,140 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
);
}
void _showMasterPromptDialog() {
HapticFeedback.mediumImpact();
final prompt = OracleMasterPromptGenerator.generateForSession(_session);
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: const Color(0xFF0F1222),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) {
return DraggableScrollableSheet(
initialChildSize: 0.85,
minChildSize: 0.5,
maxChildSize: 0.95,
expand: false,
builder: (context, scrollController) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 44,
height: 4,
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 14),
Row(
children: [
const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F), size: 24),
const SizedBox(width: 10),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Мастер-Промпт Сессии',
style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold),
),
Text(
'Для отправки в Grok, Claude 3.5 Sonnet, Gemini 1.5 Pro, GPT-4o',
style: TextStyle(color: Color(0xFF90A4AE), fontSize: 11),
),
],
),
),
IconButton(
icon: const Icon(Icons.close_rounded, color: Color(0xFF90A4AE)),
onPressed: () => Navigator.pop(ctx),
),
],
),
const SizedBox(height: 12),
// Quick Actions Bar
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF1C223D),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${prompt.length} символов • ${prompt.split(RegExp(r"\s+")).length} слов',
style: const TextStyle(color: Color(0xFF90CAF9), fontSize: 11),
),
),
const Spacer(),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFFD54F),
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
),
onPressed: () {
Clipboard.setData(ClipboardData(text: prompt));
HapticFeedback.heavyImpact();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Мастер-промпт сессии скопирован! Вставьте в Grok или Claude.'),
backgroundColor: Color(0xFF388E3C),
behavior: SnackBarBehavior.floating,
),
);
},
icon: const Icon(Icons.copy_rounded, size: 16),
label: const Text('Скопировать всё', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12.5)),
),
],
),
const SizedBox(height: 12),
// Scrollable Prompt Text Area
Expanded(
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF090A14),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF232742), width: 1.0),
),
child: SingleChildScrollView(
controller: scrollController,
child: SelectableText(
prompt,
style: const TextStyle(
color: Color(0xFFECEFF1),
fontFamily: 'monospace',
fontSize: 11.5,
height: 1.45,
),
),
),
),
),
],
),
);
},
);
},
);
}
void _copySessionToClipboard() {
final buf = StringBuffer();
buf.writeln('✨ Оракул 999 — Сессия Клеромантии ✨');
buf.writeln('✨ Оракул — Сессия Клеромантии ✨');
buf.writeln('Дата: ${_session.formattedDate}');
if (_session.question != null && _session.question!.isNotEmpty) {
buf.writeln('Вопрос: «${_session.question}»');
@@ -247,6 +416,11 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
],
),
actions: [
IconButton(
tooltip: 'Мастер-промпт для нейросетей',
icon: const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F)),
onPressed: _showMasterPromptDialog,
),
IconButton(
tooltip: 'Докинуть уточняющий знак',
icon: const Icon(Icons.add_circle_outline_rounded, color: Color(0xFFFFD54F)),
@@ -369,6 +543,48 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
],
),
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: InterpreterPersonaPicker(
compact: true,
onSelected: (_) {
if (_isGeneratingInitialAi || _isChatLoading) return;
_fetchInitialAi(replaceOpening: false);
},
),
),
const SizedBox(width: 8),
InkWell(
onTap: _showMasterPromptDialog,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFF2A1F45),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFBA68C8).withValues(alpha: 0.6)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.auto_awesome_rounded, size: 14, color: Color(0xFFFFD54F)),
SizedBox(width: 5),
Text(
'Мастер-Промпт',
style: TextStyle(
color: Color(0xFFFFF9C4),
fontSize: 11.5,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
],
),
),
@@ -498,7 +714,6 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
children: [
// Header: Sender icon & name
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isUser ? Icons.person_outline_rounded : Icons.psychology_rounded,
@@ -507,32 +722,66 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
),
const SizedBox(width: 5),
Text(
isUser ? 'Вы' : 'Оракул 999',
isUser ? 'Вы' : 'Оракул',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w900,
color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7),
),
),
const Spacer(),
IconButton(
tooltip: 'Копировать текст',
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
icon: Icon(
Icons.copy_rounded,
size: 15,
color: isUser ? const Color(0xFFFFD54F) : const Color(0xFFE1BEE7),
),
onPressed: () {
Clipboard.setData(ClipboardData(text: msg.text));
HapticFeedback.lightImpact();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Текст скопирован'),
duration: Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
),
);
},
),
],
),
// Attached new word pill if present
if (msg.attachedWord != null) ...[
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF3B2B5E),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)),
),
child: Text(
'🎲 Добавлен знак: #${msg.attachedWord!.formattedSessionNumber} «${msg.attachedWord!.word.toUpperCase()}»',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Color(0xFFFFF9C4),
InkWell(
onTap: () => _showWordDetails(msg.attachedWord!),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF3B2B5E),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'🎲 Добавлен знак: #${msg.attachedWord!.formattedSessionNumber} «${msg.attachedWord!.word.toUpperCase()}»',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Color(0xFFFFF9C4),
),
),
const SizedBox(width: 4),
const Icon(Icons.arrow_forward_ios_rounded, size: 10, color: Color(0xFFFFD54F)),
],
),
),
),
@@ -540,8 +789,7 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
const SizedBox(height: 6),
// Message text
Text(
SelectableText(
msg.text,
style: TextStyle(
fontSize: 13.5,
@@ -589,40 +837,20 @@ class _SessionResultScreenState extends State<SessionResultScreen> {
}
void _showWordDetails(SessionWord word) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Row(
children: [
const GradientStar(size: 20),
const SizedBox(width: 8),
Text(
'#${word.formattedSessionNumber} ${word.word.toUpperCase()}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${word.partOfSpeech} • ${word.category}',
style: TextStyle(color: Theme.of(ctx).colorScheme.outline),
),
const SizedBox(height: 12),
Text(
word.symbolicMeaning,
style: const TextStyle(fontSize: 14, height: 1.45),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Закрыть'),
),
],
final fullWord = _repo.getWordByName(word.word) ??
_repo.getWordBySessionNumber(word.sessionNumber) ??
WordItem(
id: word.sessionNumber,
word: word.word,
partOfSpeech: word.partOfSpeech.isNotEmpty ? word.partOfSpeech : 'знак',
frequency: 0,
category: word.category,
description: word.symbolicMeaning,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SymbolDetailScreen(word: fullWord),
),
);
}