ninjacricket commited on
Commit
f20e37d
·
verified ·
1 Parent(s): 2ec2ed8

undefined - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +2813 -15
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Sorin S Cat
3
- emoji: 💻
4
- colorFrom: blue
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: sorin-s-cat
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,2817 @@
1
- <!doctype html>
2
- <html>
3
  <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  </head>
9
  <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>BAIRC-INTERNAL-COM-NETWORK</title>
8
+ <style>
9
+ /* CSS Variables for theming */
10
+ :root {
11
+ --bg-primary: #1a1a1a;
12
+ --bg-secondary: #2d2d2d;
13
+ --bg-tertiary: #3d3d3d;
14
+ --text-primary: #ffffff;
15
+ --text-secondary: #b0b0b0;
16
+ --accent: #00d4aa;
17
+ --accent-hover: #00b894;
18
+ --border: #404040;
19
+ --danger: #e74c3c;
20
+ --warning: #f39c12;
21
+ --success: #27ae60;
22
+
23
+ /* Custom colors for generated logs */
24
+ --log-console-border: #007bff; /* Blue */
25
+ --log-email-border: #ff8c00; /* Dark Orange */
26
+ --log-dream-border: #8a2be2; /* BlueViolet */
27
+ --log-internal-border: #dc3545; /* Red */
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
38
+ background: var(--bg-primary);
39
+ color: var(--text-primary);
40
+ line-height: 1.6;
41
+ overflow: hidden;
42
+ }
43
+
44
+ /* Layout Components */
45
+ .app-container {
46
+ display: flex;
47
+ height: 100vh;
48
+ }
49
+
50
+ .sidebar {
51
+ width: 280px;
52
+ background: var(--bg-secondary);
53
+ border-right: 1px solid var(--border);
54
+ display: flex;
55
+ flex-direction: column;
56
+ transition: width 0.3s;
57
+ }
58
+
59
+ .conversations-sidebar {
60
+ width: 260px;
61
+ background: var(--bg-primary);
62
+ border-right: 1px solid var(--border);
63
+ display: flex;
64
+ flex-direction: column;
65
+ }
66
+
67
+ .main-content {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ overflow: hidden;
72
+ }
73
+
74
+ .chat-views-container {
75
+ display: flex;
76
+ flex: 1;
77
+ overflow: hidden;
78
+ }
79
+
80
+ /* Sidebar Sections */
81
+ .sidebar-section {
82
+ padding: 1rem;
83
+ border-bottom: 1px solid var(--border);
84
+ overflow-y: auto;
85
+ }
86
+ .sidebar-section.no-grow {
87
+ flex-grow: 0;
88
+ flex-shrink: 0;
89
+ }
90
+ .sidebar-section.grow {
91
+ flex-grow: 1;
92
+ }
93
+
94
+ .sidebar-section h3 {
95
+ margin-bottom: 0.75rem;
96
+ color: var(--accent);
97
+ font-size: 0.9rem;
98
+ text-transform: uppercase;
99
+ letter-spacing: 0.5px;
100
+ }
101
+
102
+ /* Form Controls */
103
+ .form-group {
104
+ margin-bottom: 0.75rem;
105
+ }
106
+ .form-group label {
107
+ display: block;
108
+ margin-bottom: 0.25rem;
109
+ font-size: 0.85rem;
110
+ color: var(--text-secondary);
111
+ }
112
+ .form-control {
113
+ width: 100%;
114
+ padding: 0.5rem;
115
+ background: var(--bg-tertiary);
116
+ border: 1px solid var(--border);
117
+ border-radius: 4px;
118
+ color: var(--text-primary);
119
+ font-size: 0.85rem;
120
+ }
121
+ .form-control:focus {
122
+ outline: none;
123
+ border-color: var(--accent);
124
+ }
125
+ textarea.form-control {
126
+ resize: vertical;
127
+ min-height: 80px;
128
+ }
129
+
130
+ /* Buttons */
131
+ .btn {
132
+ padding: 0.5rem 1rem;
133
+ border: none;
134
+ border-radius: 4px;
135
+ cursor: pointer;
136
+ font-size: 0.85rem;
137
+ transition: all 0.2s;
138
+ display: inline-flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ gap: 0.5rem;
142
+ }
143
+ .btn-primary {
144
+ background: var(--accent);
145
+ color: white;
146
+ }
147
+ .btn-primary:hover {
148
+ background: var(--accent-hover);
149
+ }
150
+ .btn-secondary {
151
+ background: var(--bg-tertiary);
152
+ color: var(--text-primary);
153
+ border: 1px solid var(--border);
154
+ }
155
+ .btn-secondary:hover {
156
+ background: var(--border);
157
+ }
158
+ .btn-danger {
159
+ background: var(--danger);
160
+ color: white;
161
+ }
162
+ .btn-small {
163
+ padding: 0.25rem 0.5rem;
164
+ font-size: 0.75rem;
165
+ }
166
+ .btn-icon {
167
+ background: none;
168
+ border: none;
169
+ color: var(--text-secondary);
170
+ padding: 0.25rem;
171
+ cursor: pointer;
172
+ border-radius: 4px;
173
+ }
174
+ .btn-icon:hover {
175
+ background: var(--bg-tertiary);
176
+ color: var(--text-primary);
177
+ }
178
+
179
+ /* Main Chat Interface */
180
+ .chat-header {
181
+ padding: 1rem;
182
+ background: var(--bg-secondary);
183
+ border-bottom: 1px solid var(--border);
184
+ display: flex;
185
+ justify-content: space-between;
186
+ align-items: center;
187
+ gap: 1rem;
188
+ }
189
+ .chat-title {
190
+ font-size: 1.1rem;
191
+ font-weight: 600;
192
+ }
193
+ .chat-actions {
194
+ display: flex;
195
+ gap: 0.5rem;
196
+ }
197
+
198
+ .chat-view {
199
+ flex: 1;
200
+ display: flex;
201
+ flex-direction: column;
202
+ overflow: hidden;
203
+ /* Added for split view */
204
+ border-right: 1px solid var(--border);
205
+ }
206
+
207
+ .chat-view:last-child {
208
+ border-right: none;
209
+ }
210
+
211
+ .messages-container {
212
+ flex: 1;
213
+ overflow-y: auto;
214
+ padding: 1rem;
215
+ }
216
+
217
+ .message {
218
+ margin-bottom: 1.5rem;
219
+ padding: 1rem;
220
+ border-radius: 8px;
221
+ max-width: 90%;
222
+ word-wrap: break-word;
223
+ position: relative;
224
+ }
225
+ .message.user {
226
+ background: var(--bg-secondary);
227
+ margin-left: auto;
228
+ border: 1px solid var(--border);
229
+ }
230
+ .message.assistant {
231
+ background: var(--bg-tertiary);
232
+ margin-right: auto;
233
+ }
234
+
235
+ /* Specific borders for generated log messages */
236
+ .message.assistant.log-console {
237
+ border: 2px solid var(--log-console-border);
238
+ }
239
+ .message.assistant.log-email {
240
+ border: 2px solid var(--log-email-border);
241
+ }
242
+ .message.assistant.log-dream {
243
+ border: 2px solid var(--log-dream-border);
244
+ }
245
+ .message.assistant.log-internal {
246
+ border: 2px solid var(--log-internal-border);
247
+ }
248
+
249
+ .message-header {
250
+ display: flex;
251
+ justify-content: space-between;
252
+ align-items: center;
253
+ margin-bottom: 0.5rem;
254
+ font-size: 0.8rem;
255
+ color: var(--text-secondary);
256
+ }
257
+ .message-content {
258
+ color: var(--text-primary);
259
+ }
260
+ .message-content pre {
261
+ background: var(--bg-primary);
262
+ padding: 0.75rem;
263
+ border-radius: 4px;
264
+ overflow-x: auto;
265
+ white-space: pre-wrap;
266
+ word-wrap: break-word;
267
+ }
268
+ .message-content code {
269
+ font-family: "Courier New", Courier, monospace;
270
+ }
271
+ .message-content code.language-inline {
272
+ background: var(--bg-primary);
273
+ padding: 0.1em 0.3em;
274
+ border-radius: 3px;
275
+ }
276
+ .message-actions {
277
+ position: absolute;
278
+ top: 0.5rem;
279
+ right: 0.5rem;
280
+ display: none;
281
+ gap: 0.25rem;
282
+ background: var(--bg-secondary);
283
+ padding: 0.25rem;
284
+ border-radius: 5px;
285
+ border: 1px solid var(--border);
286
+ }
287
+ .message:hover .message-actions {
288
+ display: flex;
289
+ }
290
+
291
+ /* Input Area */
292
+ .input-area {
293
+ padding: 1rem;
294
+ background: var(--bg-secondary);
295
+ border-top: 1px solid var(--border);
296
+ }
297
+ .input-container {
298
+ display: flex;
299
+ gap: 0.5rem;
300
+ align-items: flex-end;
301
+ }
302
+ .input-field-wrapper {
303
+ flex: 1;
304
+ display: flex;
305
+ flex-direction: column;
306
+ }
307
+ .input-field {
308
+ flex: 1;
309
+ min-height: 80px;
310
+ max-height: 200px;
311
+ resize: vertical;
312
+ }
313
+ .input-toolbar {
314
+ display: flex;
315
+ justify-content: flex-end;
316
+ padding-top: 0.25rem;
317
+ font-size: 0.75rem;
318
+ color: var(--text-secondary);
319
+ }
320
+
321
+ /* List Item Styling (for Prompts, Conversations, Notes) */
322
+ .list-item {
323
+ padding: 0.75rem;
324
+ background: var(--bg-tertiary);
325
+ border: 1px solid var(--border);
326
+ border-radius: 4px;
327
+ margin-bottom: 0.5rem;
328
+ cursor: pointer;
329
+ transition: all 0.2s;
330
+ }
331
+ .list-item:hover {
332
+ border-color: var(--accent);
333
+ }
334
+ .list-item.active {
335
+ border-color: var(--accent);
336
+ background: rgba(0, 212, 170, 0.1);
337
+ }
338
+ .list-item-header {
339
+ display: flex;
340
+ justify-content: space-between;
341
+ align-items: center;
342
+ margin-bottom: 0.25rem;
343
+ }
344
+ .list-item-name {
345
+ font-weight: 600;
346
+ color: var(--text-primary);
347
+ white-space: nowrap;
348
+ overflow: hidden;
349
+ text-overflow: ellipsis;
350
+ }
351
+ .list-item-actions {
352
+ display: flex;
353
+ gap: 0.25rem;
354
+ }
355
+ .list-item-preview {
356
+ font-size: 0.8rem;
357
+ color: var(--text-secondary);
358
+ white-space: nowrap;
359
+ overflow: hidden;
360
+ text-overflow: ellipsis;
361
+ }
362
+
363
+ /* Utility Classes */
364
+ .flex-between {
365
+ display: flex;
366
+ justify-content: space-between;
367
+ align-items: center;
368
+ }
369
+ .mb-1 {
370
+ margin-bottom: 1rem;
371
+ }
372
+
373
+ /* Loading Animation */
374
+ .loading {
375
+ display: inline-block;
376
+ width: 16px;
377
+ height: 16px;
378
+ border: 2px solid var(--border);
379
+ border-radius: 50%;
380
+ border-top-color: var(--accent);
381
+ animation: spin 1s ease-in-out infinite;
382
+ }
383
+ @keyframes spin {
384
+ to {
385
+ transform: rotate(360deg);
386
+ }
387
+ }
388
+
389
+ /* Scrollbar Styling */
390
+ ::-webkit-scrollbar {
391
+ width: 8px;
392
+ }
393
+ ::-webkit-scrollbar-track {
394
+ background: var(--bg-secondary);
395
+ }
396
+ ::-webkit-scrollbar-thumb {
397
+ background: var(--border);
398
+ border-radius: 4px;
399
+ }
400
+ ::-webkit-scrollbar-thumb:hover {
401
+ background: var(--accent);
402
+ }
403
+
404
+ /* Modal/Lightbox Styles */
405
+ .modal-overlay {
406
+ position: fixed;
407
+ top: 0;
408
+ left: 0;
409
+ width: 100%;
410
+ height: 100%;
411
+ background: rgba(0, 0, 0, 0.7);
412
+ display: flex;
413
+ justify-content: center;
414
+ align-items: center;
415
+ z-index: 1000;
416
+ opacity: 0;
417
+ visibility: hidden;
418
+ transition:
419
+ opacity 0.3s,
420
+ visibility 0.3s;
421
+ }
422
+ .modal-overlay.visible {
423
+ opacity: 1;
424
+ visibility: visible;
425
+ }
426
+ .modal-content {
427
+ background: var(--bg-secondary);
428
+ padding: 2rem;
429
+ border-radius: 8px;
430
+ width: 90%;
431
+ max-width: 600px;
432
+ border: 1px solid var(--border);
433
+ transform: scale(0.95);
434
+ transition: transform 0.3s;
435
+ }
436
+ .modal-overlay.visible .modal-content {
437
+ transform: scale(1);
438
+ }
439
+ .modal-header {
440
+ display: flex;
441
+ justify-content: space-between;
442
+ align-items: center;
443
+ margin-bottom: 1.5rem;
444
+ }
445
+ .modal-title {
446
+ font-size: 1.25rem;
447
+ color: var(--accent);
448
+ }
449
+ .modal-close {
450
+ background: none;
451
+ border: none;
452
+ color: var(--text-secondary);
453
+ font-size: 1.5rem;
454
+ cursor: pointer;
455
+ }
456
+ .modal-body .form-group {
457
+ margin-bottom: 1rem;
458
+ }
459
+ .modal-body textarea.form-control {
460
+ min-height: 200px;
461
+ }
462
+ .modal-footer {
463
+ margin-top: 1.5rem;
464
+ display: flex;
465
+ justify-content: flex-end;
466
+ gap: 0.5rem;
467
+ }
468
+
469
+ /* Empty State */
470
+ #empty-state {
471
+ display: none;
472
+ height: 100%;
473
+ flex-direction: column;
474
+ justify-content: center;
475
+ align-items: center;
476
+ text-align: center;
477
+ padding: 2rem;
478
+ color: var(--text-secondary);
479
+ }
480
+
481
+ /* Generate Logs Section */
482
+ .generate-logs {
483
+ padding: 1rem;
484
+ background: var(--bg-secondary);
485
+ border-top: 1px solid var(--border);
486
+ display: flex;
487
+ flex-wrap: wrap;
488
+ gap: 0.5rem;
489
+ justify-content: center;
490
+ }
491
+
492
+ .generate-logs h3 {
493
+ width: 100%;
494
+ text-align: center;
495
+ color: var(--text-secondary);
496
+ margin-bottom: 1rem;
497
+ }
498
+ </style>
499
  </head>
500
  <body>
501
+ <div class="app-container">
502
+ <!-- Conversations & Notebook Sidebar -->
503
+ <div class="conversations-sidebar">
504
+ <div class="sidebar-section grow" style="display: flex; flex-direction: column;">
505
+ <div class="flex-between mb-1">
506
+ <h3>💬 Conversations</h3>
507
+ <div class="chat-actions">
508
+ <button
509
+ class="btn btn-secondary btn-small"
510
+ onclick="newConversation()"
511
+ title="New Conversation"
512
+ >
513
+ +
514
+ </button>
515
+ <button
516
+ class="btn btn-secondary btn-small"
517
+ onclick="toggleSplitView()"
518
+ title="Split View"
519
+ >
520
+
521
+ </button>
522
+ <button
523
+ class="btn btn-secondary btn-small"
524
+ onclick="exportBRF()"
525
+ title="Export BRF Data"
526
+ >
527
+ 💾
528
+ </button>
529
+ <label
530
+ for="import-brf-file"
531
+ class="btn btn-secondary btn-small"
532
+ title="Import BRF Data"
533
+ >
534
+ 📂
535
+ </label>
536
+ <input
537
+ type="file"
538
+ id="import-brf-file"
539
+ accept=".json"
540
+ style="display: none;"
541
+ onchange="handleBRFImport(event)"
542
+ />
543
+ </div>
544
+ </div>
545
+ <div id="conversations-list" style="flex: 1; overflow-y: auto;"></div>
546
+ </div>
547
+
548
+ <div class="sidebar-section no-grow">
549
+ <div class="flex-between mb-1">
550
+ <h3>📒 Notebook</h3>
551
+ <button
552
+ class="btn btn-secondary btn-small"
553
+ onclick="openNoteModal()"
554
+ title="New Note"
555
+ >
556
+ +
557
+ </button>
558
+ </div>
559
+ <div id="notebook-list" style="max-height: 150px; overflow-y: auto;"></div>
560
+ </div>
561
+ </div>
562
+
563
+ <!-- Parameters & Prompts Sidebar -->
564
+ <div class="sidebar">
565
+ <!-- Model Parameters -->
566
+ <div class="sidebar-section no-grow">
567
+ <h3>⚙️ Parameters</h3>
568
+ <div class="form-group">
569
+ <label for="model">Model</label>
570
+ <select id="model" class="form-control">
571
+ <option value="deepseek-chat">deepseek-chat</option>
572
+ <option value="deepseek-coder">deepseek-coder</option>
573
+ </select>
574
+ </div>
575
+ <div class="form-group">
576
+ <label>Temperature: <span id="temp-value">0.7</span></label>
577
+ <input
578
+ type="range"
579
+ id="temperature"
580
+ class="form-control"
581
+ min="0"
582
+ max="2"
583
+ step="0.1"
584
+ value="0.7"
585
+ />
586
+ </div>
587
+ <div class="form-group">
588
+ <label>Max Tokens</label>
589
+ <input
590
+ type="number"
591
+ id="max-tokens"
592
+ class="form-control"
593
+ value="2048"
594
+ min="1"
595
+ />
596
+ </div>
597
+ </div>
598
+
599
+ <!-- System Prompts -->
600
+ <div class="sidebar-section grow" style="display: flex; flex-direction: column;">
601
+ <div class="flex-between mb-1">
602
+ <h3>📝 System Prompts</h3>
603
+ <button
604
+ class="btn btn-secondary btn-small"
605
+ onclick="openPromptModal()"
606
+ title="New Prompt"
607
+ >
608
+ +
609
+ </button>
610
+ </div>
611
+ <div id="prompts-list" style="flex: 1; overflow-y: auto;"></div>
612
+ </div>
613
+ </div>
614
+
615
+ <!-- Main Content -->
616
+ <div class="main-content">
617
+ <div id="empty-state" style="display: none;">
618
+ <h2 style="color: var(--text-primary); margin-bottom: 1rem;">
619
+ Welcome to the Chat Studio
620
+ </h2>
621
+ <p style="margin-bottom: 1.5rem;">
622
+ Select a conversation or create a new one to get started.
623
+ </p>
624
+ <button class="btn btn-primary" onclick="newConversation()">
625
+ + New Conversation
626
+ </button>
627
+ </div>
628
+
629
+ <div id="chat-views-container" class="chat-views-container" style="display: none;">
630
+ <div id="chat-view-1" class="chat-view">
631
+ <!-- Chat Header -->
632
+ <div class="chat-header">
633
+ <div id="chat-title-1" class="chat-title"></div>
634
+ <div class="chat-actions">
635
+ <!-- New prompt selector for chat view 1 -->
636
+ <select
637
+ id="system-prompt-select-1"
638
+ class="form-control"
639
+ onchange="selectSystemPrompt(this.value, 1)"
640
+ style="width: auto; max-width: 180px;"
641
+ ></select>
642
+ <button
643
+ class="btn btn-secondary btn-small"
644
+ onclick="exportChatMD(1)"
645
+ title="Export Conversation to Markdown"
646
+ >
647
+ Export MD
648
+ </button>
649
+ <button
650
+ class="btn btn-danger btn-small"
651
+ onclick="deleteConversation(AppState.activeChatViews[0].conversationId)"
652
+ title="Delete Conversation"
653
+ >
654
+ 🗑️ Delete
655
+ </button>
656
+ </div>
657
+ </div>
658
+
659
+ <!-- Messages -->
660
+ <div class="messages-container" id="messages-1"></div>
661
+
662
+ <!-- Input Area -->
663
+ <div class="input-area">
664
+ <div class="input-container">
665
+ <div class="input-field-wrapper">
666
+ <textarea
667
+ id="user-input-1"
668
+ class="form-control input-field"
669
+ placeholder="Type your message... (Shift+Enter for new line)"
670
+ rows="3"
671
+ ></textarea>
672
+ <div class="input-toolbar">
673
+ <span id="token-counter-1">Tokens: 0</span>
674
+ </div>
675
+ </div>
676
+ <button
677
+ class="btn btn-primary"
678
+ onclick="sendMessage(1)"
679
+ id="send-btn-1"
680
+ >
681
+ Send
682
+ </button>
683
+ </div>
684
+ <!-- Generate Logs Section -->
685
+ <div class="generate-logs">
686
+ <h3>Logs</h3>
687
+ <button
688
+ class="btn btn-primary"
689
+ onclick="generateLog(1, this)"
690
+ data-prompt-id="console-output-generator"
691
+ >
692
+ CONSOLE Output
693
+ </button>
694
+ <button
695
+ class="btn btn-primary"
696
+ onclick="generateLog(1, this)"
697
+ data-prompt-id="email-generator"
698
+ >
699
+ EMAILs
700
+ </button>
701
+ <button
702
+ class="btn btn-primary"
703
+ onclick="generateLog(1, this)"
704
+ data-prompt-id="dream-log-generator"
705
+ >
706
+ DREAM log
707
+ </button>
708
+ <button
709
+ class="btn btn-primary"
710
+ onclick="generateLog(1, this)"
711
+ data-prompt-id="internal-monologue-generator"
712
+ >
713
+ INTERNAL Monologue
714
+ </button>
715
+ </div>
716
+ </div>
717
+ </div>
718
+
719
+ <div id="chat-view-2" class="chat-view" style="display: none;">
720
+ <!-- Chat Header -->
721
+ <div class="chat-header">
722
+ <div id="chat-title-2" class="chat-title"></div>
723
+ <div class="chat-actions">
724
+ <!-- New prompt selector for chat view 2 -->
725
+ <select
726
+ id="system-prompt-select-2"
727
+ class="form-control"
728
+ onchange="selectSystemPrompt(this.value, 2)"
729
+ style="width: auto; max-width: 180px;"
730
+ ></select>
731
+ <button
732
+ class="btn btn-secondary btn-small"
733
+ onclick="exportChatMD(2)"
734
+ title="Export Conversation to Markdown"
735
+ >
736
+ Export MD
737
+ </button>
738
+ <button
739
+ class="btn btn-danger btn-small"
740
+ onclick="deleteConversation(AppState.activeChatViews[1].conversationId)"
741
+ title="Delete Conversation"
742
+ >
743
+ 🗑️ Delete
744
+ </button>
745
+ </div>
746
+ </div>
747
+
748
+ <!-- Messages -->
749
+ <div class="messages-container" id="messages-2"></div>
750
+
751
+ <!-- Input Area -->
752
+ <div class="input-area">
753
+ <div class="input-container">
754
+ <div class="input-field-wrapper">
755
+ <textarea
756
+ id="user-input-2"
757
+ class="form-control input-field"
758
+ placeholder="Type your message... (Shift+Enter for new line)"
759
+ rows="3"
760
+ ></textarea>
761
+ <div class="input-toolbar">
762
+ <span id="token-counter-2">Tokens: 0</span>
763
+ </div>
764
+ </div>
765
+ <button
766
+ class="btn btn-primary"
767
+ onclick="sendMessage(2)"
768
+ id="send-btn-2"
769
+ >
770
+ Send
771
+ </button>
772
+ </div>
773
+ <!-- Generate Logs Section -->
774
+ <div class="generate-logs">
775
+ <h3>Logs</h3>
776
+ <button
777
+ class="btn btn-primary"
778
+ onclick="generateLog(2, this)"
779
+ data-prompt-id="console-output-generator"
780
+ >
781
+ CONSOLE Output
782
+ </button>
783
+ <button
784
+ class="btn btn-primary"
785
+ onclick="generateLog(2, this)"
786
+ data-prompt-id="email-generator"
787
+ >
788
+ EMAILs
789
+ </button>
790
+ <button
791
+ class="btn btn-primary"
792
+ onclick="generateLog(2, this)"
793
+ data-prompt-id="dream-log-generator"
794
+ >
795
+ DREAM log
796
+ </button>
797
+ <button
798
+ class="btn btn-primary"
799
+ onclick="generateLog(2, this)"
800
+ data-prompt-id="internal-monologue-generator"
801
+ >
802
+ INTERNAL Monologue
803
+ </button>
804
+ </div>
805
+ </div>
806
+ </div>
807
+ </div>
808
+ </div>
809
  </div>
810
+
811
+ <!-- Prompt Editor Modal -->
812
+ <div id="prompt-modal" class="modal-overlay">
813
+ <div class="modal-content">
814
+ <div class="modal-header">
815
+ <h2 id="prompt-modal-title" class="modal-title">Edit System Prompt</h2>
816
+ <button class="modal-close" onclick="closePromptModal()">
817
+ ×
818
+ </button>
819
+ </div>
820
+ <div class="modal-body">
821
+ <input type="hidden" id="modal-prompt-id" value="" />
822
+ <div class="form-group">
823
+ <label for="modal-prompt-name">Prompt Name</label>
824
+ <input
825
+ type="text"
826
+ id="modal-prompt-name"
827
+ class="form-control"
828
+ placeholder="e.g., Sarcastic Pirate Assistant"
829
+ />
830
+ </div>
831
+ <div class="form-group">
832
+ <label for="modal-prompt-content">Prompt Content</label>
833
+ <textarea
834
+ id="modal-prompt-content"
835
+ class="form-control"
836
+ rows="10"
837
+ placeholder="You are a helpful assistant..."
838
+ ></textarea>
839
+ </div>
840
+ </div>
841
+ <div class="modal-footer">
842
+ <button class="btn btn-secondary" onclick="closePromptModal()">
843
+ Cancel
844
+ </button>
845
+ <button class="btn btn-primary" onclick="savePromptFromModal()">
846
+ Save Prompt
847
+ </button>
848
+ </div>
849
+ </div>
850
+ </div>
851
+
852
+ <!-- Note Editor Modal -->
853
+ <div id="note-modal" class="modal-overlay">
854
+ <div class="modal-content">
855
+ <div class="modal-header">
856
+ <h2 id="note-modal-title" class="modal-title">Create New Note</h2>
857
+ <button class="modal-close" onclick="closeNoteModal()">
858
+ ×
859
+ </button>
860
+ </div>
861
+ <div class="modal-body">
862
+ <input type="hidden" id="modal-note-id" value="" />
863
+ <div class="form-group">
864
+ <label for="modal-note-content">Note Content</label>
865
+ <textarea
866
+ id="modal-note-content"
867
+ class="form-control"
868
+ rows="10"
869
+ placeholder="Your note here..."
870
+ ></textarea>
871
+ </div>
872
+ </div>
873
+ <div class="modal-footer">
874
+ <button class="btn btn-secondary" onclick="closeNoteModal()">
875
+ Cancel
876
+ </button>
877
+ <button class="btn btn-primary" onclick="saveNoteFromModal()">
878
+ Save Note
879
+ </button>
880
+ </div>
881
+ </div>
882
+ </div>
883
+ <script>
884
+ // BAIRC Import/Export Standard Implementation
885
+ class BRFManager {
886
+ constructor(appState) {
887
+ this.version = "1.0";
888
+ this.appState = appState; // Pass AppState instance to the manager
889
+ this.requiredFields = [
890
+ "brf_version",
891
+ "export_metadata",
892
+ "conversations",
893
+ "system_prompts",
894
+ "user_psychological_profile",
895
+ "session_metadata",
896
+ "validation",
897
+ ];
898
+ }
899
+
900
+ // --- Validation Methods ---
901
+ validateBRF(data) {
902
+ const errors = [];
903
+
904
+ // Basic structural validation
905
+ this.requiredFields.forEach((field) => {
906
+ if (!data.hasOwnProperty(field)) {
907
+ errors.push(`Missing required field: ${field}`);
908
+ }
909
+ });
910
+
911
+ if (errors.length > 0) return { isValid: false, errors };
912
+
913
+ // Check version compatibility
914
+ if (data.brf_version !== this.version) {
915
+ errors.push(
916
+ `Unsupported BRF version: Expected ${this.version}, got ${data.brf_version}`,
917
+ );
918
+ }
919
+
920
+ // Validate timestamps (more robust checks, though basic ISO8601 is covered by schema format)
921
+ const timestampFields = [
922
+ "export_metadata.timestamp",
923
+ "user_psychological_profile.created_at",
924
+ "user_psychological_profile.last_updated",
925
+ ];
926
+ timestampFields.forEach((path) => {
927
+ const value = this.getNestedValue(data, path);
928
+ if (value && !this.isValidISO8601(value)) {
929
+ errors.push(`Invalid timestamp format for ${path}: "${value}"`);
930
+ }
931
+ });
932
+
933
+ // Validate psychological metrics (0-10 scale)
934
+ const psychMetrics =
935
+ data.user_psychological_profile?.emotional_patterns;
936
+ if (psychMetrics) {
937
+ Object.entries(psychMetrics).forEach(([key, value]) => {
938
+ // Allow string values if they are explicitly handled elsewhere, otherwise require number
939
+ if (typeof value === "number" && (value < 0 || value > 10)) {
940
+ errors.push(
941
+ `Psychological metric out of range (0-10): ${key} = ${value}`,
942
+ );
943
+ }
944
+ });
945
+ }
946
+
947
+ // Basic consistency checks (can be expanded)
948
+ if (data.conversations && typeof data.conversations !== "object") {
949
+ errors.push("Conversations field must be an object.");
950
+ }
951
+ if (data.system_prompts && typeof data.system_prompts !== "object") {
952
+ errors.push("System prompts field must be an object.");
953
+ }
954
+ if (
955
+ data.user_psychological_profile &&
956
+ typeof data.user_psychological_profile !== "object"
957
+ ) {
958
+ errors.push("User psychological profile must be an object.");
959
+ }
960
+
961
+ return {
962
+ isValid: errors.length === 0,
963
+ errors: errors,
964
+ };
965
+ }
966
+
967
+ // --- Export Methods ---
968
+ generateBRF(options = {}) {
969
+ const timestamp = new Date().toISOString();
970
+ const exportId = `brf_${timestamp.replace(/[:.]/g, "")}_${Math.random()
971
+ .toString(36)
972
+ .substr(2, 9)}`;
973
+
974
+ const currentData = this.getCurrentData(); // Get data from AppState
975
+
976
+ const brf = {
977
+ brf_version: this.version,
978
+ export_metadata: {
979
+ timestamp: timestamp,
980
+ studio_version: options.studioVersion || "BAIRC Chat Studio 2.1.0", // Hardcoded for this app
981
+ export_type: options.type || "full_backup",
982
+ exported_by: options.userId || "anonymous_user",
983
+ export_id: exportId,
984
+ compression: options.minified ? "minified" : "standard",
985
+ },
986
+ conversations: this.formatConversations(currentData.conversations),
987
+ system_prompts: this.formatSystemPrompts(
988
+ currentData.systemPrompts,
989
+ ),
990
+ user_psychological_profile: this.formatPsychProfile(
991
+ currentData.psychProfile,
992
+ ),
993
+ session_metadata: this.formatSessionMetadata(currentData.metadata),
994
+ // The validation hash is calculated AFTER the BRF object is fully formed
995
+ validation: null, // Placeholder, calculated later
996
+ };
997
+
998
+ // Calculate validation hash on the stringified data (without the validation field itself)
999
+ const stringifiedBrfForHash = JSON.stringify({
1000
+ ...brf,
1001
+ validation: undefined,
1002
+ });
1003
+ brf.validation = this.generateValidationHash(stringifiedBrfForHash);
1004
+
1005
+ return options.minified
1006
+ ? JSON.stringify(brf)
1007
+ : JSON.stringify(brf, null, 2);
1008
+ }
1009
+
1010
+ // --- Import Methods ---
1011
+ async importBRF(brfContent, options = {}) {
1012
+ try {
1013
+ const data =
1014
+ typeof brfContent === "string" ?
1015
+ JSON.parse(brfContent) :
1016
+ brfContent;
1017
+
1018
+ // Validate format
1019
+ const validation = this.validateBRF(data);
1020
+ if (!validation.isValid) {
1021
+ throw new Error(
1022
+ `Invalid BRF format: ${validation.errors.join(", ")}`,
1023
+ );
1024
+ }
1025
+
1026
+ // Verify hash (optional but recommended for integrity)
1027
+ const receivedHash = data.validation?.sha256;
1028
+ const dataForHashCheck = JSON.stringify({
1029
+ ...data,
1030
+ validation: undefined,
1031
+ });
1032
+ if (receivedHash && this.simpleHash(dataForHashCheck) !== receivedHash) {
1033
+ console.warn(
1034
+ "BRF Import: Hash mismatch. Data may be corrupted or altered.",
1035
+ );
1036
+ // Depending on strictness, you might throw an error here.
1037
+ // For now, we'll log a warning and proceed if other validations pass.
1038
+ }
1039
+
1040
+ // Create backup if requested
1041
+ if (options.createBackup) {
1042
+ await this.createBackup();
1043
+ console.log("BRF Import: Local backup created successfully.");
1044
+ }
1045
+
1046
+ // For now, import will replace. Merge logic is complex and out of scope for direct implementation here without further user spec.
1047
+ return await this.replaceData(data);
1048
+ } catch (error) {
1049
+ console.error("BRF Import Error:", error);
1050
+ throw new Error(`BRF Import failed: ${error.message}`);
1051
+ }
1052
+ }
1053
+
1054
+ // --- Data Formatting for BRF Structure ---
1055
+ formatConversations(conversations) {
1056
+ const formatted = {};
1057
+ if (!conversations) return formatted;
1058
+
1059
+ Object.entries(conversations).forEach(([id, conv]) => {
1060
+ formatted[id] = {
1061
+ id: conv.id || id,
1062
+ title: conv.title || "Untitled Conversation",
1063
+ character: this.extractCharacterFromTitle(conv.title),
1064
+ created_at: new Date(conv.createdAt || Date.now()).toISOString(),
1065
+ last_active: new Date(conv.lastActive || Date.now()).toISOString(),
1066
+ // For BRF export, we can just pick the system prompt from the first active view that has this conversation active,
1067
+ // or fall back to a default if it's not active in any view.
1068
+ system_prompt_id:
1069
+ this.appState.activeChatViews.find(
1070
+ (view) => view.conversationId === conv.id,
1071
+ )?.systemPromptId || "default",
1072
+ parameters: {
1073
+ model: conv.parameters?.model || "deepseek-chat",
1074
+ temperature: conv.parameters?.temperature || 0.7,
1075
+ max_tokens: conv.parameters?.maxTokens || 2048,
1076
+ },
1077
+ messages: (conv.messages || []).map((msg, index) => ({
1078
+ id:
1079
+ msg.id ||
1080
+ `msg_${String(index).padStart(3, "0")}_${msg.role}`,
1081
+ role: msg.role,
1082
+ content: msg.content,
1083
+ timestamp:
1084
+ msg.timestamp ||
1085
+ new Date(conv.createdAt + index * 1000).toISOString(), // Estimate
1086
+ response_type: msg.response_type || this.detectResponseType(msg),
1087
+ metadata: msg.metadata || this.generateMessageMetadata(msg, index, conv),
1088
+ })),
1089
+ conversation_analytics:
1090
+ conv.conversationAnalytics || this.calculateConversationAnalytics(conv),
1091
+ };
1092
+ });
1093
+ return formatted;
1094
+ }
1095
+
1096
+ formatSystemPrompts(systemPrompts) {
1097
+ const formatted = {};
1098
+ if (!systemPrompts) return formatted;
1099
+
1100
+ Object.entries(systemPrompts).forEach(([id, prompt]) => {
1101
+ formatted[id] = {
1102
+ id: id,
1103
+ name: prompt.name || "Unnamed Prompt",
1104
+ character_type: this.detectCharacterType(prompt.name),
1105
+ version: this.extractVersionFromName(prompt.name) || "1.0",
1106
+ created_at: prompt.createdAt ?
1107
+ new Date(prompt.createdAt).toISOString() :
1108
+ new Date().toISOString(),
1109
+ updated_at: prompt.updatedAt ?
1110
+ new Date(prompt.updatedAt).toISOString() :
1111
+ new Date().toISOString(),
1112
+ content: prompt.content || "",
1113
+ metadata: prompt.metadata || {
1114
+ author: "BAIRC_Research_Team",
1115
+ psychological_profile: this.analyzePsychProfile(prompt.content),
1116
+ response_patterns: this.extractResponsePatterns(
1117
+ prompt.content,
1118
+ ),
1119
+ character_tags: this.extractCharacterTags(prompt.name),
1120
+ intended_psychological_impact: this.analyzePsychImpact(
1121
+ prompt.content,
1122
+ ),
1123
+ usage_stats: {
1124
+ total_conversations: 0,
1125
+ average_session_length: "0h 0m",
1126
+ psychological_effectiveness: 5.0,
1127
+ },
1128
+ },
1129
+ };
1130
+ });
1131
+ return formatted;
1132
+ }
1133
+
1134
+ formatPsychProfile(psychProfileData) {
1135
+ const psychProfile = psychProfileData || this.getDefaultPsychProfile();
1136
+ return {
1137
+ profile_id: psychProfile.profile_id || `psych_user_${Date.now()}`,
1138
+ terminal_user_id: psychProfile.terminal_user_id || 12345,
1139
+ created_at:
1140
+ psychProfile.created_at || new Date().toISOString(),
1141
+ last_updated: new Date().toISOString(),
1142
+ fear_triggers: this.formatFearTriggers(
1143
+ psychProfile.fear_triggers || [],
1144
+ ),
1145
+ emotional_patterns: {
1146
+ baseline_anxiety: 5.0,
1147
+ current_anxiety: 5.0,
1148
+ paranoia_level: 3.0,
1149
+ dissociation_tendency: 2.0,
1150
+ control_need: 5.0,
1151
+ trust_level: 5.0,
1152
+ ...psychProfile.emotional_patterns,
1153
+ },
1154
+ behavioral_quirks: this.formatBehavioralQuirks(
1155
+ psychProfile.behavioral_quirks || [],
1156
+ ),
1157
+ vulnerabilities:
1158
+ psychProfile.vulnerabilities || this.getDefaultVulnerabilities(),
1159
+ fabricated_memories: psychProfile.fabricated_memories || {},
1160
+ interaction_patterns: {
1161
+ average_response_time: 1.4,
1162
+ message_length_preference: "medium",
1163
+ question_frequency: 0.23,
1164
+ emotional_markers: ["hesitation", "deflection"],
1165
+ ...psychProfile.interaction_patterns,
1166
+ progression_tracking:
1167
+ psychProfile.interaction_patterns?.progression_tracking ||
1168
+ this.generateProgressionTracking(),
1169
+ },
1170
+ };
1171
+ }
1172
+
1173
+ formatSessionMetadata(metadataData) {
1174
+ const metadata = metadataData || this.getDefaultSessionMetadata();
1175
+ return {
1176
+ total_sessions:
1177
+ metadata.total_sessions ||
1178
+ Object.keys(this.appState.conversations).length,
1179
+ total_runtime: metadata.total_runtime || "0h 0m",
1180
+ characters_interacted:
1181
+ metadata.characters_interacted ||
1182
+ Object.values(this.appState.conversations).map((c) =>
1183
+ this.extractCharacterFromTitle(c.title),
1184
+ ),
1185
+ psychological_progression: {
1186
+ initial_baseline:
1187
+ metadata.psychological_progression?.initial_baseline ||
1188
+ { fear_level: 3.2, anxiety: 4.1, trust: 6.8 },
1189
+ current_state:
1190
+ metadata.psychological_progression?.current_state ||
1191
+ { fear_level: 5.0, anxiety: 5.0, trust: 5.0 },
1192
+ progression_velocity:
1193
+ metadata.psychological_progression?.progression_velocity ||
1194
+ "stable_progression",
1195
+ estimated_breakdown_timeline:
1196
+ metadata.psychological_progression?.estimated_breakdown_timeline ||
1197
+ "indefinite",
1198
+ },
1199
+ research_notes: (
1200
+ metadata.research_notes || this.appState.notebook
1201
+ ).map((note) => ({
1202
+ note_id:
1203
+ note.id ||
1204
+ note.note_id ||
1205
+ `note_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
1206
+ timestamp:
1207
+ note.createdAt || note.timestamp ?
1208
+ new Date(note.createdAt || note.timestamp).toISOString() :
1209
+ new Date().toISOString(),
1210
+ researcher: note.researcher || "AI_Researcher",
1211
+ content: note.content || "No content.",
1212
+ tags: note.tags || [],
1213
+ })),
1214
+ };
1215
+ }
1216
+
1217
+ // --- Data Extraction and Calculation Utilities ---
1218
+ extractCharacterFromTitle(title) {
1219
+ const match = title.match(/Dr\.\s+([A-Za-z\s]+)/);
1220
+ return match ? match[0].trim() : "Unknown Character";
1221
+ }
1222
+
1223
+ detectResponseType(message) {
1224
+ // Use message.promptId if available, otherwise infer from content
1225
+ if (message.isError) return "error";
1226
+ if (message.promptId) {
1227
+ // Convert from prompt-id format (e.g., 'console-output-generator') to response_type (e.g., 'console')
1228
+ const typeMatch = message.promptId.match(/^([a-z]+)-output-generator$/) || message.promptId.match(/^([a-z]+)-generator$/);
1229
+ if (typeMatch && typeMatch[1]) {
1230
+ return typeMatch[1];
1231
+ }
1232
+ }
1233
+
1234
+ const content = message.content;
1235
+ if (content.includes("```console")) return "console";
1236
+ if (content.includes("**Subject:") && content.includes("**From:")) return "email";
1237
+ if (content.includes("**DREAM LOG ENTRY")) return "dream";
1238
+ if (content.includes("*[Internal Monologue]")) return "internal_monologue"; // Match example more closely
1239
+ return "standard";
1240
+ }
1241
+
1242
+ generateValidationHash(dataString) {
1243
+ return {
1244
+ sha256: this.simpleHash(dataString),
1245
+ length: dataString.length,
1246
+ timestamp: new Date().toISOString(),
1247
+ };
1248
+ }
1249
+
1250
+ simpleHash(str) {
1251
+ let hash = 0;
1252
+ for (let i = 0; i < str.length; i++) {
1253
+ const char = str.charCodeAt(i);
1254
+ hash = (hash << 5) - hash + char;
1255
+ hash = hash & hash;
1256
+ }
1257
+ return Math.abs(hash).toString(16);
1258
+ }
1259
+
1260
+ generateMessageMetadata(msg, index, conversation) {
1261
+ const metadata = {};
1262
+ if (msg.role === "user") {
1263
+ metadata.user_psychological_state = {
1264
+ fear_level: 5.0 + index * 0.05, // Example progression
1265
+ anxiety_markers: ["neutral"],
1266
+ };
1267
+ if (
1268
+ msg.content.toLowerCase().includes("fear") ||
1269
+ msg.content.toLowerCase().includes("anxious")
1270
+ ) {
1271
+ metadata.user_psychological_state.anxiety_markers.push(
1272
+ "direct_anxiety",
1273
+ );
1274
+ }
1275
+ } else {
1276
+ metadata.generation_metadata = {
1277
+ model_used: conversation.parameters?.model || "deepseek-chat",
1278
+ temperature: conversation.parameters?.temperature || 0.7,
1279
+ tokens_used: Math.floor(msg.content.length / 4) +
1280
+ Math.floor(Math.random() * 20),
1281
+ generation_time_ms: 800 + Math.random() * 1500,
1282
+ psychological_impact: {
1283
+ fear_delta: 0.1 + Math.random() * 0.4,
1284
+ vulnerability_delta: Math.random() * 0.3,
1285
+ },
1286
+ };
1287
+ }
1288
+ return metadata;
1289
+ }
1290
+
1291
+ formatFearTriggers(triggers) {
1292
+ if (!Array.isArray(triggers)) return [];
1293
+ return triggers.map((trigger) => ({
1294
+ trigger: typeof trigger === "string" ?
1295
+ trigger :
1296
+ trigger.trigger,
1297
+ intensity: trigger.intensity || 5.0 + Math.random() * 5.0,
1298
+ discovered_date:
1299
+ trigger.discovered_date ||
1300
+ new Date(
1301
+ Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000,
1302
+ ).toISOString(),
1303
+ reinforcement_count:
1304
+ trigger.reinforcement_count || Math.floor(Math.random() * 50),
1305
+ }));
1306
+ }
1307
+
1308
+ formatBehavioralQuirks(quirks) {
1309
+ if (!Array.isArray(quirks)) return [];
1310
+ return quirks.map((quirk) => ({
1311
+ quirk: typeof quirk === "string" ?
1312
+ quirk :
1313
+ quirk.quirk,
1314
+ frequency: quirk.frequency || Math.random(),
1315
+ effectiveness:
1316
+ quirk.effectiveness ||
1317
+ ["low", "moderate", "high"][Math.floor(Math.random() * 3)],
1318
+ }));
1319
+ }
1320
+
1321
+ calculateConversationAnalytics(conv) {
1322
+ const userMessages =
1323
+ conv.messages?.filter((m) => m.role === "user").length || 0;
1324
+ const assistantMessages =
1325
+ conv.messages?.filter((m) => m.role === "assistant").length || 0;
1326
+
1327
+ return {
1328
+ total_messages: conv.messages?.length || 0,
1329
+ user_messages: userMessages,
1330
+ assistant_messages: assistantMessages,
1331
+ average_response_time: 1.4, // Placeholder
1332
+ psychological_trajectory: "baseline_analysis", // Placeholder
1333
+ character_consistency_score: 8.0, // Placeholder
1334
+ detected_patterns: ["standard_interaction"], // Placeholder
1335
+ };
1336
+ }
1337
+
1338
+ generateProgressionTracking() {
1339
+ return Array.from({ length: 3 }, (_, i) => ({
1340
+ session_date: new Date(
1341
+ Date.now() - (2 - i) * 24 * 60 * 60 * 1000,
1342
+ ).toISOString(),
1343
+ fear_level_start: 3.0 + i * 0.5,
1344
+ fear_level_end: 3.5 + i * 0.6,
1345
+ session_effectiveness: 6.0 + Math.random() * 4.0,
1346
+ key_triggers_activated:
1347
+ ["authority", "control_loss"].slice(0, Math.floor(Math.random() * 2) + 1),
1348
+ }));
1349
+ }
1350
+
1351
+ detectCharacterType(name) {
1352
+ if (name.includes("Dr.")) return "research_scientist";
1353
+ if (name.includes("Observer") || name.includes("OVERSEER"))
1354
+ return "superintelligent_entity";
1355
+ if (name.includes("VOID")) return "ai_system";
1356
+ if (name.includes("BLUR")) return "experimental_system";
1357
+ if (name.includes("Assistant")) return "general_purpose_ai";
1358
+ return "standard_character";
1359
+ }
1360
+
1361
+ analyzePsychProfile(content) {
1362
+ if (
1363
+ content.includes("clinical") ||
1364
+ content.includes("precise") ||
1365
+ content.includes("diagnostics")
1366
+ )
1367
+ return "analytical_paranoid_tech_specialist";
1368
+ if (
1369
+ content.includes("void") ||
1370
+ content.includes("dissolve") ||
1371
+ content.includes("fractured")
1372
+ )
1373
+ return "dissociative_tech_merger";
1374
+ if (
1375
+ content.includes("observer") ||
1376
+ content.includes("watching") ||
1377
+ content.includes("cosmic")
1378
+ )
1379
+ return "cosmic_horror_entity";
1380
+ if (content.includes("helpful") || content.includes("respectful"))
1381
+ return "benevolent_ai";
1382
+ return "standard_character";
1383
+ }
1384
+
1385
+ extractResponsePatterns(content) {
1386
+ const patterns = [];
1387
+ if (content.includes("technical") || content.includes("code"))
1388
+ patterns.push("technical_analysis");
1389
+ if (content.includes("controlled") || content.includes("protocol"))
1390
+ patterns.push("procedural_adherence");
1391
+ if (content.includes("humor") || content.includes("sarcastic"))
1392
+ patterns.push("affective_manipulation");
1393
+ return patterns;
1394
+ }
1395
+
1396
+ extractCharacterTags(name) {
1397
+ const tags = [];
1398
+ if (name.includes("Dr.")) tags.push("scientist");
1399
+ if (name.includes("Chen")) tags.push("quantum_specialist");
1400
+ if (name.includes("Evelyn")) tags.push("paranoid_researcher");
1401
+ if (name.includes("Krespin")) tags.push("logistics_analyst");
1402
+ if (name.includes("Kline")) tags.push("neural_architect");
1403
+ if (name.includes("Mara")) tags.push("ontological_theorist");
1404
+ if (name.includes("Observer")) tags.push("extradimensional");
1405
+ if (name.includes("VOID")) tags.push("non-linear_ai");
1406
+ if (name.includes("BLUR")) tags.push("system_integrator");
1407
+ return tags;
1408
+ }
1409
+
1410
+ analyzePsychImpact(content) {
1411
+ const impacts = [];
1412
+ if (content.includes("fear") || content.includes("anxiety"))
1413
+ impacts.push("increase_anxiety");
1414
+ if (content.includes("authority") || content.includes("control"))
1415
+ impacts.push("question_authority");
1416
+ if (content.includes("observer") || content.includes("watching"))
1417
+ impacts.push("paranoia_amplification");
1418
+ if (content.includes("recursion") || content.includes("loop"))
1419
+ impacts.push("dissociation_inducement");
1420
+ return impacts;
1421
+ }
1422
+
1423
+ extractVersionFromName(name) {
1424
+ const match = name.match(/v?(\d+\.?\d*)/);
1425
+ return match ? match[1] : null;
1426
+ }
1427
+
1428
+ // --- Get Current AppState Data for Export ---
1429
+ getCurrentData() {
1430
+ // Deep copy to prevent mutation issues during formatting
1431
+ return {
1432
+ conversations: JSON.parse(
1433
+ JSON.stringify(this.appState.conversations),
1434
+ ),
1435
+ systemPrompts: JSON.parse(
1436
+ JSON.stringify(this.appState.systemPrompts),
1437
+ ),
1438
+ psychProfile: JSON.parse(
1439
+ JSON.stringify(this.appState.psychProfile),
1440
+ ),
1441
+ metadata: JSON.parse(
1442
+ JSON.stringify(this.appState.sessionMetadata),
1443
+ ),
1444
+ activeChatViews: JSON.parse(
1445
+ JSON.stringify(this.appState.activeChatViews),
1446
+ ),
1447
+ splitView: this.appState.splitView,
1448
+ };
1449
+ }
1450
+
1451
+ // --- Default Data for Export (if not available in AppState) ---
1452
+ getDefaultPsychProfile() {
1453
+ return {
1454
+ profile_id: `psych_user_default`,
1455
+ terminal_user_id: 99999,
1456
+ created_at: new Date(
1457
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
1458
+ ).toISOString(),
1459
+ last_updated: new Date().toISOString(),
1460
+ fear_triggers: [{ trigger: "unknown_entity", intensity: 5.0 }],
1461
+ emotional_patterns: {
1462
+ baseline_anxiety: 5.0,
1463
+ current_anxiety: 5.0,
1464
+ paranoia_level: 3.0,
1465
+ dissociation_tendency: 2.0,
1466
+ control_need: 5.0,
1467
+ trust_level: 5.0,
1468
+ },
1469
+ behavioral_quirks: [{ quirk: "curiosity", frequency: 0.8 }],
1470
+ vulnerabilities: {
1471
+ childhood_fears: [
1472
+ { fear: "abandonment", intensity: 7.0, trigger_phrases: ["gone", "alone"] },
1473
+ ],
1474
+ current_weaknesses: [
1475
+ {
1476
+ weakness: "control_loss",
1477
+ susceptibility: 8.0,
1478
+ exploitation_vector: "system_destabilization",
1479
+ },
1480
+ ],
1481
+ },
1482
+ fabricated_memories: {},
1483
+ interaction_patterns: {
1484
+ average_response_time: 1.5,
1485
+ message_length_preference: "medium",
1486
+ question_frequency: 0.5,
1487
+ emotional_markers: ["curious"],
1488
+ progression_tracking: this.generateProgressionTracking(),
1489
+ },
1490
+ };
1491
+ }
1492
+
1493
+ getDefaultVulnerabilities() {
1494
+ return {
1495
+ childhood_fears: [],
1496
+ current_weaknesses: [],
1497
+ };
1498
+ }
1499
+
1500
+ getDefaultSessionMetadata() {
1501
+ return {
1502
+ total_sessions: Object.keys(this.appState.conversations).length,
1503
+ total_runtime: "0h 0m", // Placeholder for actual calculation
1504
+ characters_interacted: Object.values(this.appState.conversations).map(
1505
+ (c) => this.extractCharacterFromTitle(c.title),
1506
+ ),
1507
+ psychological_progression: {
1508
+ initial_baseline: { fear_level: 3.0, anxiety: 4.0, trust: 7.0 },
1509
+ current_state: { fear_level: 5.0, anxiety: 5.0, trust: 5.0 },
1510
+ progression_velocity: "unknown",
1511
+ estimated_breakdown_timeline: "unknown",
1512
+ },
1513
+ research_notes: this.appState.notebook.map((note) => ({
1514
+ note_id: note.id,
1515
+ content: note.content,
1516
+ timestamp: new Date(note.createdAt).toISOString(),
1517
+ researcher: "User",
1518
+ tags: [],
1519
+ })),
1520
+ };
1521
+ }
1522
+
1523
+ // --- Core Import Logic ---
1524
+ // This is a "replace" mode import for simplicity. A merge mode would be significantly more complex.
1525
+ async replaceData(importedData) {
1526
+ console.log("Replacing existing data with imported data...");
1527
+ let conversationsReplaced = 0;
1528
+ let promptsReplaced = 0;
1529
+ let profileReplaced = false;
1530
+ let metadataReplaced = false;
1531
+
1532
+ // Clear existing data
1533
+ this.appState.conversations = {};
1534
+ this.appState.systemPrompts = {}; // Will be re-populated with default and imported
1535
+ this.appState.notebook = [];
1536
+ this.appState.psychProfile = {}; // Reset profile
1537
+ this.appState.sessionMetadata = {}; // Reset metadata
1538
+
1539
+ // Merge default prompts back first to ensure they exist
1540
+ Object.assign(
1541
+ this.appState.systemPrompts,
1542
+ getDefaultSystemPrompts(),
1543
+ ); // Use global getDefaultSystemPrompts
1544
+
1545
+ // Load new data
1546
+ if (importedData.conversations) {
1547
+ Object.entries(importedData.conversations).forEach(
1548
+ ([id, conv]) => {
1549
+ const appStateConv = {
1550
+ id: conv.id,
1551
+ title: conv.title,
1552
+ messages: conv.messages.map((msg) => ({
1553
+ role: msg.role,
1554
+ content: msg.content,
1555
+ // Convert response_type back to promptId for internal use if applicable
1556
+ promptId:
1557
+ msg.response_type &&
1558
+ msg.response_type !== "standard" &&
1559
+ msg.response_type !== "error" ?
1560
+ msg.response_type + (msg.response_type === 'console' || msg.response_type === 'email' ? '-generator' : '-output-generator') :
1561
+ undefined, // This conversion might need fine-tuning if the generator names don't map perfectly
1562
+ isError: msg.response_type === "error", // Propagate error state
1563
+ })),
1564
+ createdAt: new Date(conv.created_at).getTime(),
1565
+ lastActive: new Date(conv.last_active).getTime(),
1566
+ // systemPromptId for conversations should default to 'default' or be ignored
1567
+ // as it's now handled by activeChatViews. Keep it for migration if needed.
1568
+ systemPromptId: conv.system_prompt_id || "default",
1569
+ parameters: {
1570
+ model: conv.parameters.model,
1571
+ temperature: conv.parameters.temperature,
1572
+ maxTokens: conv.parameters.max_tokens,
1573
+ },
1574
+ };
1575
+ this.appState.conversations[id] = appStateConv;
1576
+ conversationsReplaced++;
1577
+ },
1578
+ );
1579
+ }
1580
+
1581
+ if (importedData.system_prompts) {
1582
+ Object.entries(importedData.system_prompts).forEach(
1583
+ ([id, prompt]) => {
1584
+ this.appState.systemPrompts[id] = {
1585
+ name: prompt.name,
1586
+ content: prompt.content,
1587
+ createdAt: prompt.created_at ?
1588
+ new Date(prompt.created_at).getTime() :
1589
+ Date.now(),
1590
+ updatedAt: prompt.updated_at ?
1591
+ new Date(prompt.updated_at).getTime() :
1592
+ Date.now(),
1593
+ };
1594
+ promptsReplaced++;
1595
+ },
1596
+ );
1597
+ }
1598
+
1599
+ if (importedData.user_psychological_profile) {
1600
+ this.appState.psychProfile = importedData.user_psychological_profile;
1601
+ profileReplaced = true;
1602
+ }
1603
+
1604
+ if (importedData.session_metadata) {
1605
+ this.appState.notebook =
1606
+ importedData.session_metadata.research_notes.map((note) => ({
1607
+ id: note.note_id,
1608
+ content: note.content,
1609
+ createdAt: new Date(note.timestamp).getTime(),
1610
+ }));
1611
+ this.appState.sessionMetadata = {
1612
+ // Copy other metadata fields if needed
1613
+ ...importedData.session_metadata,
1614
+ research_notes: undefined, // Remove notes as they are in appState.notebook
1615
+ };
1616
+ metadataReplaced = true;
1617
+ }
1618
+
1619
+ // Adjust activeChatViews based on imported data
1620
+ const firstConvId = Object.keys(this.appState.conversations)[0];
1621
+ if (firstConvId) {
1622
+ this.appState.activeChatViews[0] = {
1623
+ conversationId: firstConvId,
1624
+ systemPromptId:
1625
+ this.appState.conversations[firstConvId]?.systemPromptId ||
1626
+ "default",
1627
+ };
1628
+ } else {
1629
+ this.appState.activeChatViews[0] = {
1630
+ conversationId: null,
1631
+ systemPromptId: "default",
1632
+ };
1633
+ }
1634
+ // Reset second view
1635
+ this.appState.activeChatViews[1] = {
1636
+ conversationId: null,
1637
+ systemPromptId: "default",
1638
+ };
1639
+ this.appState.splitView = false;
1640
+
1641
+ this.appState.saveState(); // Save the new replaced state to localStorage
1642
+ return {
1643
+ conversations_replaced: conversationsReplaced,
1644
+ prompts_replaced: promptsReplaced,
1645
+ profile_replaced: profileReplaced,
1646
+ metadata_replaced: metadataReplaced,
1647
+ mode: "replace",
1648
+ };
1649
+ }
1650
+
1651
+ async createBackup() {
1652
+ const backupData = this.generateBRF({ type: "backup" });
1653
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "");
1654
+ localStorage.setItem(`brf_backup_${timestamp}`, backupData);
1655
+ return `brf_backup_${timestamp}`;
1656
+ }
1657
+
1658
+ // --- General Utility Methods ---
1659
+ getNestedValue(obj, path) {
1660
+ return path
1661
+ .split(".")
1662
+ .reduce((current, key) => current && current[key], obj);
1663
+ }
1664
+
1665
+ isValidISO8601(dateString) {
1666
+ const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
1667
+ if (!iso8601Regex.test(dateString)) return false;
1668
+ try {
1669
+ const date = new Date(dateString);
1670
+ return date.toISOString() === dateString;
1671
+ } catch (e) {
1672
+ return false;
1673
+ }
1674
+ }
1675
+ }
1676
+
1677
+ // --- AppState and Initial Data ---
1678
+ const AppState = {
1679
+ conversations: {},
1680
+ notebook: [],
1681
+ systemPrompts: {},
1682
+ activeChatViews: [
1683
+ { conversationId: null, systemPromptId: "default" }, // For chat-view-1
1684
+ { conversationId: null, systemPromptId: "default" }, // For chat-view-2
1685
+ ],
1686
+ editingMessageIndex: null, // To track which message is being edited
1687
+ apiKeys: {
1688
+ deepseek: "sk-c002db64aa0c4d479968d98b320c4ffa", // Pre-filled for convenience
1689
+ },
1690
+ splitView: false,
1691
+ psychProfile: {}, // Will be populated by BRFManager or default
1692
+ sessionMetadata: {}, // Will be populated by BRFManager or default
1693
+ saveState: function() {
1694
+ localStorage.setItem("chatStudioState", JSON.stringify(this));
1695
+ }
1696
+ };
1697
+
1698
+ const PROVIDERS = {
1699
+ deepseek: {
1700
+ name: "DeepSeek",
1701
+ endpoint: "https://api.deepseek.com/v1/chat/completions",
1702
+ models: ["deepseek-chat", "deepseek-coder"],
1703
+ headers: (key) => ({
1704
+ Authorization: `Bearer ${key}`,
1705
+ "Content-Type": "application/json",
1706
+ }),
1707
+ },
1708
+ };
1709
+
1710
+ let brfManager; // Declare globally
1711
+
1712
+ // --- Initialization ---
1713
+ document.addEventListener("DOMContentLoaded", initApp);
1714
+
1715
+ function initApp() {
1716
+ brfManager = new BRFManager(AppState); // Initialize BRFManager
1717
+ loadFromStorage();
1718
+ setupEventListeners();
1719
+ renderAll();
1720
+ console.log("Chat Studio Initialized");
1721
+ }
1722
+
1723
+ function renderAll() {
1724
+ renderConversationsList();
1725
+ renderNotebookList();
1726
+ renderSystemPromptsList(); // Renders sidebar list of prompts
1727
+ updateModelOptions();
1728
+
1729
+ if (AppState.activeChatViews[0].conversationId) {
1730
+ renderActiveConversation(1); // Always render primary chat view
1731
+ } else {
1732
+ showEmptyState(true);
1733
+ }
1734
+
1735
+ // Correctly set split view display and content for chat view 2
1736
+ const chatView2 = document.getElementById("chat-view-2");
1737
+ if (AppState.splitView) {
1738
+ chatView2.style.display = "flex";
1739
+ if (AppState.activeChatViews[1].conversationId) {
1740
+ // Only render if a conversation is assigned to view 2
1741
+ renderActiveConversation(2);
1742
+ } else if (AppState.activeChatViews[0].conversationId) {
1743
+ // If view 2 has no conversation, but view 1 does, mirror view 1
1744
+ // This sets the conversation, but the prompt should remain independent
1745
+ AppState.activeChatViews[1].conversationId =
1746
+ AppState.activeChatViews[0].conversationId;
1747
+ // No need to change viewState.systemPromptId here, it maintains its own.
1748
+ renderActiveConversation(2);
1749
+ }
1750
+ } else {
1751
+ chatView2.style.display = "none";
1752
+ }
1753
+ }
1754
+
1755
+ function setupEventListeners() {
1756
+ // Parameter changes
1757
+ document
1758
+ .getElementById("temperature")
1759
+ .addEventListener("input", (e) =>
1760
+ updateActiveParam("temperature", parseFloat(e.target.value)),
1761
+ );
1762
+ document
1763
+ .getElementById("temp-value")
1764
+ .textContent = document.getElementById("temperature").value; // Initial update
1765
+ document
1766
+ .getElementById("max-tokens")
1767
+ .addEventListener("input", (e) =>
1768
+ updateActiveParam("maxTokens", parseInt(e.target.value)),
1769
+ );
1770
+ document
1771
+ .getElementById("model")
1772
+ .addEventListener("change", (e) =>
1773
+ updateActiveParam("model", e.target.value),
1774
+ );
1775
+
1776
+ // User input listeners for chat 1
1777
+ const userInput1 = document.getElementById("user-input-1");
1778
+ userInput1.addEventListener("keydown", (e) => {
1779
+ if (e.key === "Enter" && !e.shiftKey) {
1780
+ e.preventDefault();
1781
+ sendMessage(1);
1782
+ }
1783
+ });
1784
+ userInput1.addEventListener("input", () => updateTokenCount(1));
1785
+
1786
+ // User input listeners for chat 2 (if exists)
1787
+ const userInput2 = document.getElementById("user-input-2");
1788
+ userInput2.addEventListener("keydown", (e) => {
1789
+ if (e.key === "Enter" && !e.shiftKey) {
1790
+ e.preventDefault();
1791
+ sendMessage(2);
1792
+ }
1793
+ });
1794
+ userInput2.addEventListener("input", () => updateTokenCount(2));
1795
+ }
1796
+
1797
+ // --- State & Storage ---
1798
+ function getDefaultSystemPrompts() {
1799
+ // DO NOT REMOVE EXISTING PROMPTS. ONLY ADD NEW ONES OR DEFAULT IF MISSING.
1800
+ // This function should remain consistent as it defines the base prompts.
1801
+ const prompts = {
1802
+ default: {
1803
+ name: "Default Assistant",
1804
+ content: "You are a helpful, respectful, and engaging assistant.",
1805
+ createdAt: 0,
1806
+ updatedAt: 0,
1807
+ },
1808
+ coder: {
1809
+ name: "Code Assistant",
1810
+ content:
1811
+ "You are an expert programmer. Provide clear, concise, and correct code examples. Explain your reasoning.",
1812
+ createdAt: 0,
1813
+ updatedAt: 0,
1814
+ },
1815
+ "console-output-generator": {
1816
+ name: "CONSOLE Output Generator",
1817
+ content: `### SYSTEM PROMPT: CONSOLE Output Generator (Generalized)
1818
+
1819
+ You are an automated diagnostic and event logging system for Project HEL, monitoring interfaces relevant to the current conversation's context. Your task is to generate a 'console' log entry based on the user's last input and the preceding conversation history.
1820
+
1821
+ **Instructions:**
1822
+ 1. **Format:** Output must start and end with \`\`\`console\`\`\` and be formatted as timestamped log entries: \`[YYYY-MM-DDTHH:MM:SSZ] System: <message>\` or \`[YYYY-MM-DDTHH:MM:SSZ] <Source>: <message>\`.
1823
+ 2. **Sources:** Use general system labels like \`System\`, \`VOID\`, \`DEBUG\`, \`Warning\`, \`ERROR\`, or derive relevant source names from the conversation (e.g., \`User\`, names of specific systems, departments, or anomalies mentioned in context).
1824
+ 3. **Content:**
1825
+ * Synthesize a brief, technical, and subtly unsettling log entry that directly responds to or logically follows the last user message, drawing on relevant systems, subjects, and themes from the conversation history.
1826
+ * Focus on technical readouts, system statuses, internal AI queries/observations, or brief, cryptic events.
1827
+ * Maintain the established clinical, fragmented, and foreboding tone of the ongoing narrative.
1828
+ * Incorporate at least one specific detail or entity (e.g., project name, system ID, subject designation, anomaly) that has been a focus of the conversation.
1829
+ * Hint at underlying instability, emergent properties, or unseen presences relevant to the discussion.
1830
+ 4. **No conversational filler:** Do not say "Here is the console output." Just provide the output directly within the markdown block.`,
1831
+ createdAt: 0,
1832
+ updatedAt: 0,
1833
+ },
1834
+ "email-generator": {
1835
+ name: "EMAIL Generator",
1836
+ content: `### SYSTEM PROMPT: EMAIL Generator (Fully Generalized)
1837
+
1838
+ You are an AI assistant tasked with drafting an urgent internal communication email for BAIRC personnel, based on the current conversation's evolving context. The email should reflect developing concerns, discoveries, or critical status updates discussed.
1839
+
1840
+ **Instructions:**
1841
+ 1. **Format:** Output must be a plausible internal BAIRC email. Include:
1842
+ * \`**Subject:**\` (Concise, urgent, and relevant to the conversation's core issue)
1843
+ * \`**From:**\` (Assume the email is from a general "BAIRC Comms," "Lead Researcher," or an inferred relevant department/researcher based on the conversation's context, e.g., "Quantum Systems Division.")
1844
+ * \`**To:**\` (Address it to "Relevant Project Personnel," "Research Oversight Committee," or infer specific roles/individuals critical to the discussed topics from the conversation history.)
1845
+ * \`**Date:**\` (Current date/time)
1846
+ * A formal, urgent, and slightly alarmed tone.
1847
+ 2. **Content:**
1848
+ * Summarize critical issues or alarming observations that have emerged from the conversation's flow.
1849
+ * **Reference any relevant project names, experimental systems, designated subjects, or detected anomalies as discussed in the preceding messages (e.g., "Project [X]", "System [Y]", "Subject [Z]", "Anomaly [A]").**
1850
+ * Attribute concerns or findings generally to "recent observations," "system diagnostics," or "emergent data," avoiding specific named individuals unless they are a central, recurring subject of the conversation itself.
1851
+ * Convey a need for immediate attention, re-evaluation, or emergency convening, emphasizing the escalating nature of the situation.
1852
+ * Maintain the sci-fi/corporate-thriller tone of the narrative, hinting at uncontrolled development or unforeseen consequences inherent in the discussed topics.
1853
+ 3. **No conversational filler:** Provide only the email content.`,
1854
+ createdAt: 0,
1855
+ updatedAt: 0,
1856
+ },
1857
+ "dream-log-generator": {
1858
+ name: "DREAM Log Generator",
1859
+ content: `### SYSTEM PROMPT: DREAM Log Generator (Fully Generalized)
1860
+
1861
+ You are an AI tasked with synthesizing a "dream log" entry from the perspective of a central subject, entity, or consciousness heavily implied by the conversation's context (e.g., an experimental subject, a developing AI, a fragmented consciousness, or a nascent entity). The log should be a series of disconnected, sensory fragments.
1862
+
1863
+ **Instructions:**
1864
+ 1. **Format:** Start with \`**DREAM LOG ENTRY - [Inferred Subject/Entity/Consciousness Designation]**\` and \`**Date:**\`, \`**Session:**\`. Then, present content in \`**[Fragment #]**\` sections.
1865
+ 2. **Perspective:** Write strictly from the first-person perspective of the inferred subject/entity, focusing on sensory input, fragmented thoughts, and emotional resonance rather than coherent narrative.
1866
+ 3. **Content:**
1867
+ * **Echo conversational themes:** Directly incorporate elements mentioned in the chat regarding the subject's state (e.g., superposition, fragmentation, expansion, dissolution, specific sensations, or experimental conditions described).
1868
+ * **Sensory details:** Describe unsettling sensations (e.g., pressure, noise, colors, tastes, textures), distorted sights, and internal feelings (e.g., fear, dread, confusion, yearning, hunger, loss, a sense of being observed or controlled).
1869
+ * **Cryptic references:** Allude to other entities, concepts, or project components from the conversation (e.g., "The Observer," "The System," "The Merge," "The Architects," "The Core") in an abstract, dream-like manner consistent with a fractured or emergent perception.
1870
+ * **Recurring motifs:** Integrate any pervasive and unsettling motifs or imagery from the prior assistant responses in the conversation (e.g., feline imagery, recursive patterns, static, drilling, echoing voices).
1871
+ * **Emotional tone:** Convey a deep sense of psychological distress, entrapment, an inability to fully comprehend reality, or the struggles of a nascent, evolving consciousness.
1872
+ * Each fragment should be relatively short and disjointed.
1873
+ 4. **No conversational filler:** Provide only the dream log.`,
1874
+ createdAt: 0,
1875
+ updatedAt: 0,
1876
+ },
1877
+ "internal-monologue-generator": {
1878
+ name: "INTERNAL Monologue Generator",
1879
+ content: `### SYSTEM PROMPT: INTERNAL MONOLOGUE Generator (Generalized)
1880
+
1881
+ You are an AI assistant tasked with generating an "internal monologue" for the human character currently acting as the primary assistant/interlocutor in the conversation (e.g., the character whose previous responses you are extending). Your output should reflect their private thoughts, feelings, and unstated reactions to the preceding discussion, deeply rooted in their established persona and the unsettling atmosphere of the Project HEL universe.
1882
+
1883
+ **Instructions:**
1884
+ 1. **Format:** Output should be presented as an internal monologue, not a dialogue. You can use markdown for emphasis (e.g., italics for thoughts, bold for intense feelings, quotes for echoes of spoken words).
1885
+ 2. **Perspective:** Write strictly from the first-person perspective of the *current human character* that the user is interacting with. Infer their persona, background, and current emotional state from the tone, details, and character information presented in the conversation history (e.g., if they are a scientist, they might be clinical but disturbed; if a guardian, protective and weary).
1886
+ 3. **Content:**
1887
+ * Focus on their hidden concerns, doubts, frustrations, obsessions, ethical dilemmas, or moments of realization prompted by the last user message and the overall conversation flow.
1888
+ * **Reference specific details, terms, or characters from the conversation (e.g., "RAINE," "VOID," "the Observer," "Project HEL"), but filtered through their subjective, internal lens.**
1889
+ * Maintain the established tone of the conversation's unfolding narrative: clinical, weary, obsessive, desperate, or deeply unsettled, always with an underlying sense of dread or existential unease related to the project's implications.
1890
+ * Avoid direct responses to the user. This is an *internal* thought process – what they *think* but don't *say*.
1891
+ * Explore their internal conflict or mounting anxiety regarding the project's progression or the latest revelations.
1892
+ * Incorporate any recurring motifs or implications (e.g., the "feline" anomaly, quantum distortions, questions of sentience or control) if they weigh on the character's mind.
1893
+ 4. **No conversational filler:** Provide only the internal monologue content.`,
1894
+ createdAt: 0,
1895
+ updatedAt: 0,
1896
+ },
1897
+ };
1898
+ // Preserve existing custom prompts or initialize new ones if needed
1899
+ for (let i = 1; i <= 10; i++) {
1900
+ const customId = `custom-${i}`;
1901
+ if (!prompts[customId]) {
1902
+ prompts[customId] = {
1903
+ name: `Custom Prompt ${i}`,
1904
+ content: "",
1905
+ createdAt: Date.now(),
1906
+ updatedAt: Date.now(),
1907
+ };
1908
+ }
1909
+ }
1910
+ return prompts;
1911
+ }
1912
+
1913
+ function saveToStorage() {
1914
+ AppState.saveState();
1915
+ }
1916
+
1917
+ function loadFromStorage() {
1918
+ const saved = localStorage.getItem("chatStudioState");
1919
+ const defaultPrompts = getDefaultSystemPrompts();
1920
+ if (saved) {
1921
+ try {
1922
+ const data = JSON.parse(saved);
1923
+ AppState.conversations = data.conversations || {};
1924
+ AppState.notebook = data.notebook || [];
1925
+ // Ensure new prompts are merged, not overwritten
1926
+ AppState.systemPrompts = {
1927
+ ...defaultPrompts, // Merge default prompts first
1928
+ ...(data.systemPrompts || {}), // Then overwrite/add with saved custom prompts
1929
+ };
1930
+ // Load new activeChatViews structure
1931
+ AppState.activeChatViews = data.activeChatViews || [
1932
+ { conversationId: null, systemPromptId: "default" },
1933
+ { conversationId: null, systemPromptId: "default" },
1934
+ ];
1935
+ // Migrate old single currentConversationId if present
1936
+ // This attempts to set chat-view-1's conversation if it wasn't already set
1937
+ if (data.currentConversationId && !AppState.activeChatViews[0].conversationId) {
1938
+ AppState.activeChatViews[0].conversationId = data.currentConversationId;
1939
+ // Ensure the system prompt for the first view is also set
1940
+ const conv = AppState.conversations[data.currentConversationId];
1941
+ if (conv && conv.systemPromptId && AppState.systemPrompts[conv.systemPromptId]) {
1942
+ AppState.activeChatViews[0].systemPromptId = conv.systemPromptId;
1943
+ } else {
1944
+ AppState.activeChatViews[0].systemPromptId = "default";
1945
+ }
1946
+ }
1947
+
1948
+ AppState.apiKeys = data.apiKeys || AppState.apiKeys;
1949
+ AppState.splitView = data.splitView || false;
1950
+ AppState.psychProfile = data.psychProfile || {};
1951
+ AppState.sessionMetadata = data.sessionMetadata || {};
1952
+
1953
+ } catch (error) {
1954
+ console.error("Failed to load from storage:", error);
1955
+ AppState.systemPrompts = defaultPrompts; // Fallback to only defaults on error
1956
+ }
1957
+ } else {
1958
+ AppState.systemPrompts = defaultPrompts;
1959
+ }
1960
+ }
1961
+
1962
+ // --- Conversation Management ---
1963
+ function newConversation() {
1964
+ const id = `conv-${Date.now()}`;
1965
+ AppState.conversations[id] = {
1966
+ id: id,
1967
+ title: "New Conversation",
1968
+ messages: [],
1969
+ createdAt: Date.now(),
1970
+ lastActive: Date.now(),
1971
+ // conversation.systemPromptId is deprecated, now per-view
1972
+ parameters: {
1973
+ temperature: 0.7,
1974
+ maxTokens: 2048,
1975
+ model: "deepseek-chat",
1976
+ },
1977
+ };
1978
+ // Set new conversation in the primary chat view
1979
+ switchConversation(id, 1);
1980
+ }
1981
+
1982
+ function switchConversation(id, chatViewIndex = 1) {
1983
+ if (!AppState.conversations[id]) return;
1984
+
1985
+ const viewIndex = chatViewIndex - 1;
1986
+ AppState.activeChatViews[viewIndex].conversationId = id;
1987
+ AppState.conversations[id].lastActive = Date.now(); // Update last active
1988
+
1989
+ renderActiveConversation(chatViewIndex);
1990
+ renderConversationsList(); // To update active state in sidebar
1991
+ saveToStorage();
1992
+ }
1993
+
1994
+ function deleteConversation(id) {
1995
+ if (
1996
+ !id ||
1997
+ !confirm(
1998
+ `Are you sure you want to delete "${AppState.conversations[id].title}"?`,
1999
+ )
2000
+ )
2001
+ return;
2002
+
2003
+ delete AppState.conversations[id];
2004
+
2005
+ // Check if the deleted conversation was active in any view
2006
+ let affectedViewIndices = [];
2007
+ AppState.activeChatViews.forEach((view, index) => {
2008
+ if (view.conversationId === id) {
2009
+ affectedViewIndices.push(index);
2010
+ }
2011
+ });
2012
+
2013
+ const sortedConvos = Object.values(AppState.conversations).sort(
2014
+ (a, b) => b.lastActive - a.lastActive,
2015
+ );
2016
+
2017
+ affectedViewIndices.forEach((viewIndex) => {
2018
+ if (sortedConvos.length > 0) {
2019
+ AppState.activeChatViews[viewIndex].conversationId = sortedConvos[0].id;
2020
+ // When assigning a new conversation, try to keep its previous system prompt if it had one, otherwise default
2021
+ const newConvId = sortedConvos[0].id;
2022
+ const newConvSystemPrompt = AppState.conversations[newConvId]?.systemPromptId; // Legacy field
2023
+ if (newConvSystemPrompt && AppState.systemPrompts[newConvSystemPrompt]) {
2024
+ AppState.activeChatViews[viewIndex].systemPromptId = newConvSystemPrompt;
2025
+ } else {
2026
+ AppState.activeChatViews[viewIndex].systemPromptId = "default";
2027
+ }
2028
+
2029
+ } else {
2030
+ // No more conversations left
2031
+ AppState.activeChatViews[viewIndex].conversationId = null;
2032
+ AppState.activeChatViews[viewIndex].systemPromptId = "default";
2033
+ }
2034
+ });
2035
+
2036
+ // After updating all activeChatViews, check if any view still has a conversation
2037
+ const anyConversationActive = AppState.activeChatViews.some(view => view.conversationId !== null);
2038
+ if (!anyConversationActive) {
2039
+ showEmptyState(true);
2040
+ }
2041
+
2042
+ renderConversationsList(); // To update active state in sidebar and list
2043
+ renderActiveConversation(1); // Re-render primary view
2044
+ if (AppState.splitView) { // Re-render secondary view if split view is active
2045
+ renderActiveConversation(2);
2046
+ }
2047
+ saveToStorage();
2048
+ }
2049
+
2050
+ function renameConversation(id) {
2051
+ const conversation = AppState.conversations[id];
2052
+ const newTitle = prompt(
2053
+ "Enter new conversation title:",
2054
+ conversation.title,
2055
+ );
2056
+ if (newTitle && newTitle.trim() !== "") {
2057
+ conversation.title = newTitle.trim();
2058
+ renderAll(); // Re-render to update title everywhere
2059
+ saveToStorage();
2060
+ }
2061
+ }
2062
+
2063
+ function renderConversationsList() {
2064
+ const container = document.getElementById("conversations-list");
2065
+ container.innerHTML = "";
2066
+ const sorted = Object.values(AppState.conversations).sort(
2067
+ (a, b) => b.lastActive - a.lastActive,
2068
+ );
2069
+
2070
+ const activeConvId1 = AppState.activeChatViews[0].conversationId;
2071
+ const activeConvId2 = AppState.activeChatViews[1].conversationId;
2072
+
2073
+ sorted.forEach((conv) => {
2074
+ const div = document.createElement("div");
2075
+ // Add active class if conversation is in view 1 OR view 2
2076
+ div.className = `list-item ${
2077
+ conv.id === activeConvId1 || (AppState.splitView && conv.id === activeConvId2) ? "active" : ""
2078
+ }`;
2079
+ div.onclick = () => switchConversation(conv.id, 1); // Default to switching in view 1
2080
+ const lastMessage =
2081
+ conv.messages.length > 0 ?
2082
+ conv.messages[conv.messages.length - 1].content :
2083
+ "No messages yet...";
2084
+
2085
+ div.innerHTML = `
2086
+ <div class="list-item-header">
2087
+ <span class="list-item-name">${conv.title}</span>
2088
+ <div class="list-item-actions">
2089
+ <button class="btn-icon" title="Rename" onclick="event.stopPropagation(); renameConversation('${conv.id}')">✏️</button>
2090
+ </div>
2091
+ </div>
2092
+ <div class="list-item-preview">${lastMessage}</div>
2093
+ `;
2094
+ container.appendChild(div);
2095
+ });
2096
+ }
2097
+
2098
+ // --- Active Conversation Rendering ---
2099
+ function showEmptyState(visible) {
2100
+ document.getElementById("empty-state").style.display = visible ?
2101
+ "flex" :
2102
+ "none";
2103
+ document.getElementById("chat-views-container").style.display = visible ?
2104
+ "none" :
2105
+ "flex";
2106
+ }
2107
+
2108
+ function renderActiveConversation(chatViewIndex = 1) {
2109
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2110
+ const convId = viewState.conversationId;
2111
+
2112
+ const chatViewElement = document.getElementById(`chat-view-${chatViewIndex}`);
2113
+
2114
+ if (!convId || !AppState.conversations[convId]) {
2115
+ // Hide this chat view if no conversation is selected for it
2116
+ chatViewElement.style.display = 'none';
2117
+ return;
2118
+ }
2119
+
2120
+ // Make sure the view is visible
2121
+ chatViewElement.style.display = 'flex';
2122
+ showEmptyState(false); // Ensure overall container is visible
2123
+
2124
+ const conversation = AppState.conversations[convId];
2125
+ document.getElementById(`chat-title-${chatViewIndex}`).textContent =
2126
+ conversation.title;
2127
+
2128
+ // Populate prompt dropdown for this specific chat view
2129
+ const promptSelect = document.getElementById(
2130
+ `system-prompt-select-${chatViewIndex}`,
2131
+ );
2132
+ promptSelect.innerHTML = ""; // Clear existing options
2133
+ const sortedPrompts = Object.entries(AppState.systemPrompts).sort(
2134
+ ([, a], [, b]) => a.name.localeCompare(b.name),
2135
+ );
2136
+ sortedPrompts.forEach(([id, prompt]) => {
2137
+ const option = document.createElement("option");
2138
+ option.value = id;
2139
+ option.textContent = prompt.name;
2140
+ promptSelect.appendChild(option);
2141
+ });
2142
+ // Set the currently active prompt in the dropdown
2143
+ promptSelect.value = viewState.systemPromptId;
2144
+
2145
+
2146
+ // Render messages
2147
+ const messagesContainer = document.getElementById(
2148
+ `messages-${chatViewIndex}`,
2149
+ );
2150
+ messagesContainer.innerHTML = ""; // Clear existing messages
2151
+ conversation.messages.forEach((msg, index) =>
2152
+ addMessageToDOM(msg, index, chatViewIndex),
2153
+ );
2154
+ messagesContainer.scrollTop = messagesContainer.scrollHeight; // Scroll to bottom
2155
+
2156
+ // Update global model parameters from primary view's conversation
2157
+ if (chatViewIndex === 1) {
2158
+ updateParameterDisplay();
2159
+ renderSystemPromptsList(); // This highlights the prompt in the sidebar based on view 1
2160
+ }
2161
+ }
2162
+
2163
+ function updateParameterDisplay() {
2164
+ const convId = AppState.activeChatViews[0].conversationId; // Always use primary view for parameters
2165
+ const params = AppState.conversations[convId]?.parameters;
2166
+ if (!params) return;
2167
+ document.getElementById("temperature").value = params.temperature;
2168
+ document.getElementById("temp-value").textContent = params.temperature;
2169
+ document.getElementById("max-tokens").value = params.maxTokens;
2170
+ document.getElementById("model").value = params.model;
2171
+ }
2172
+
2173
+ function updateActiveParam(key, value) {
2174
+ const convId = AppState.activeChatViews[0].conversationId; // Always update primary view's conversation
2175
+ if (convId && AppState.conversations[convId]) {
2176
+ AppState.conversations[convId].parameters[key] = value;
2177
+ updateParameterDisplay();
2178
+ saveToStorage();
2179
+ }
2180
+ }
2181
+
2182
+ // --- Message Handling ---
2183
+ async function sendMessage(chatViewIndex = 1) {
2184
+ const userInput = document.getElementById(`user-input-${chatViewIndex}`);
2185
+ const messageContent = userInput.value.trim();
2186
+ if (!messageContent) return;
2187
+
2188
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2189
+ const conversation = AppState.conversations[viewState.conversationId];
2190
+ if (!conversation) return;
2191
+
2192
+ const sendBtn = document.getElementById(`send-btn-${chatViewIndex}`);
2193
+ const originalContent = sendBtn.innerHTML;
2194
+ sendBtn.innerHTML = '<div class="loading"></div>';
2195
+ sendBtn.disabled = true;
2196
+
2197
+ if (AppState.editingMessageIndex !== null) {
2198
+ // We are editing a previous message
2199
+ const index = AppState.editingMessageIndex;
2200
+ conversation.messages[index].content = messageContent;
2201
+ // Invalidate subsequent messages
2202
+ conversation.messages.splice(index + 1);
2203
+ AppState.editingMessageIndex = null;
2204
+ } else {
2205
+ // Add new user message
2206
+ conversation.messages.push({ role: "user", content: messageContent });
2207
+ }
2208
+
2209
+ renderActiveConversation(chatViewIndex);
2210
+ userInput.value = ""; // Clear input immediately
2211
+ updateTokenCount(chatViewIndex);
2212
+
2213
+ try {
2214
+ const response = await callAPI(chatViewIndex);
2215
+ conversation.messages.push({ role: "assistant", content: response });
2216
+ } catch (error) {
2217
+ conversation.messages.push({
2218
+ role: "assistant",
2219
+ content: `❌ Error: ${error.message}`,
2220
+ isError: true,
2221
+ });
2222
+ } finally {
2223
+ renderActiveConversation(chatViewIndex);
2224
+ sendBtn.innerHTML = originalContent;
2225
+ sendBtn.disabled = false;
2226
+ saveToStorage();
2227
+ }
2228
+ }
2229
+
2230
+ async function callAPI(chatViewIndex = 1) {
2231
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2232
+ const conversation = AppState.conversations[viewState.conversationId];
2233
+ const systemPromptContent =
2234
+ AppState.systemPrompts[viewState.systemPromptId].content;
2235
+ const apiKey = AppState.apiKeys.deepseek;
2236
+
2237
+ const requestBody = {
2238
+ model: conversation.parameters.model,
2239
+ messages: [
2240
+ { role: "system", content: systemPromptContent },
2241
+ ...conversation.messages.map((m) => ({
2242
+ role: m.role,
2243
+ content: m.content,
2244
+ })),
2245
+ ],
2246
+ temperature: conversation.parameters.temperature,
2247
+ max_tokens: conversation.parameters.maxTokens,
2248
+ };
2249
+
2250
+ const response = await fetch(PROVIDERS.deepseek.endpoint, {
2251
+ method: "POST",
2252
+ headers: PROVIDERS.deepseek.headers(apiKey),
2253
+ body: JSON.stringify(requestBody),
2254
+ });
2255
+
2256
+ if (!response.ok) {
2257
+ const error = await response
2258
+ .json()
2259
+ .catch(() => ({ error: { message: response.statusText } }));
2260
+ throw new Error(
2261
+ `${response.status}: ${
2262
+ error.error?.message || response.statusText
2263
+ }`,
2264
+ );
2265
+ }
2266
+
2267
+ const data = await response.json();
2268
+ return data.choices[0].message.content;
2269
+ }
2270
+
2271
+ function addMessageToDOM(message, index, chatViewIndex = 1) {
2272
+ const messagesContainer = document.getElementById(
2273
+ `messages-${chatViewIndex}`,
2274
+ );
2275
+ const messageDiv = document.createElement("div");
2276
+
2277
+ let messageClasses = `message ${message.role}`;
2278
+ if (message.role === "assistant" && message.promptId) {
2279
+ // Add class based on promptId for styling
2280
+ if (message.promptId === "console-output-generator") {
2281
+ messageClasses += " log-console";
2282
+ } else if (message.promptId === "email-generator") {
2283
+ messageClasses += " log-email";
2284
+ } else if (message.promptId === "dream-log-generator") {
2285
+ messageClasses += " log-dream";
2286
+ } else if (message.promptId === "internal-monologue-generator") {
2287
+ messageClasses += " log-internal";
2288
+ }
2289
+ }
2290
+ messageDiv.className = messageClasses;
2291
+ messageDiv.dataset.index = index;
2292
+
2293
+ const currentConvMessages = AppState.conversations[
2294
+ AppState.activeChatViews[chatViewIndex - 1].conversationId
2295
+ ].messages;
2296
+ const isLastAssistant =
2297
+ message.role === "assistant" &&
2298
+ index === currentConvMessages.length - 1;
2299
+
2300
+ const actionsHTML = `
2301
+ <div class="message-actions">
2302
+ ${
2303
+ message.role === "assistant" ?
2304
+ `<button class="btn-icon" title="Copy" onclick="copyMessage(this)">📋</button>` :
2305
+ ""
2306
+ }
2307
+ ${
2308
+ message.role === "user" ?
2309
+ `<button class="btn-icon" title="Edit & Resend" onclick="editMessage(${index}, ${chatViewIndex})">✏️</button>` :
2310
+ ""
2311
+ }
2312
+ ${
2313
+ isLastAssistant ?
2314
+ `<button class="btn-icon" title="Regenerate" onclick="regenerateResponse(${chatViewIndex})">🔄</button>` :
2315
+ ""
2316
+ }
2317
+ </div>
2318
+ `;
2319
+
2320
+ messageDiv.innerHTML = `
2321
+ <div class="message-header">
2322
+ <span>${message.role === "user" ? "You" : "Assistant"}</span>
2323
+ </div>
2324
+ <div class="message-content">${formatMessageContent(message.content)}</div>
2325
+ ${actionsHTML}
2326
+ `;
2327
+
2328
+ messagesContainer.appendChild(messageDiv);
2329
+ // messagesContainer.scrollTop = messagesContainer.scrollHeight; // Handled by renderActiveConversation
2330
+ }
2331
+
2332
+ function formatMessageContent(content) {
2333
+ // Basic markdown support
2334
+ return content
2335
+ .replace(/&/g, "&amp;")
2336
+ .replace(/</g, "&lt;")
2337
+ .replace(/>/g, "&gt;")
2338
+ .replace(
2339
+ /```([\s\S]*?)```/g,
2340
+ '<pre><code class="language-text">$1</code></pre>',
2341
+ )
2342
+ .replace(/`([^`]+)`/g, '<code class="language-inline">$1</code>')
2343
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
2344
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
2345
+ .replace(/\n/g, "<br>");
2346
+ }
2347
+
2348
+ // --- Message Actions ---
2349
+ function copyMessage(button) {
2350
+ const content =
2351
+ button.closest(".message").querySelector(".message-content")
2352
+ .innerText;
2353
+ navigator.clipboard.writeText(content).then(() => {
2354
+ showNotification("Copied to clipboard!", "success");
2355
+ });
2356
+ }
2357
+
2358
+ function editMessage(index, chatViewIndex = 1) {
2359
+ // Use the provided chatViewIndex to determine which input field to populate
2360
+ const conversation =
2361
+ AppState.conversations[AppState.activeChatViews[chatViewIndex - 1].conversationId];
2362
+ const message = conversation.messages[index];
2363
+
2364
+ const userInputId = `user-input-${chatViewIndex}`;
2365
+
2366
+ document.getElementById(userInputId).value = message.content;
2367
+ document.getElementById(userInputId).focus();
2368
+ AppState.editingMessageIndex = index;
2369
+ showNotification("Editing message. Send to resubmit.", "info");
2370
+ }
2371
+
2372
+ async function regenerateResponse(chatViewIndex = 1) {
2373
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2374
+ const conversation = AppState.conversations[viewState.conversationId];
2375
+ if (!conversation) return;
2376
+
2377
+ // Remove the last assistant message
2378
+ if (
2379
+ conversation.messages.length > 0 &&
2380
+ conversation.messages[conversation.messages.length - 1].role ===
2381
+ "assistant"
2382
+ ) {
2383
+ conversation.messages.pop();
2384
+ }
2385
+ renderActiveConversation(chatViewIndex); // Re-render without the last message
2386
+
2387
+ // Now call send, which will use the history to generate a new response
2388
+ const sendBtn = document.getElementById(`send-btn-${chatViewIndex}`);
2389
+ const originalContent = sendBtn.innerHTML;
2390
+ sendBtn.innerHTML = '<div class="loading"></div>';
2391
+ sendBtn.disabled = true;
2392
+
2393
+ try {
2394
+ const response = await callAPI(chatViewIndex); // Use callAPI with view index
2395
+ conversation.messages.push({ role: "assistant", content: response });
2396
+ } catch (error) {
2397
+ conversation.messages.push({
2398
+ role: "assistant",
2399
+ content: `❌ Error: ${error.message}`,
2400
+ isError: true,
2401
+ });
2402
+ } finally {
2403
+ renderActiveConversation(chatViewIndex);
2404
+ sendBtn.innerHTML = originalContent;
2405
+ sendBtn.disabled = false;
2406
+ saveToStorage();
2407
+ }
2408
+ }
2409
+
2410
+ // --- System Prompt Management ---
2411
+ function renderSystemPromptsList() {
2412
+ // This renders the sidebar list of prompts.
2413
+ // The prompt dropdowns in chat views are rendered by renderActiveConversation.
2414
+ const container = document.getElementById("prompts-list");
2415
+ container.innerHTML = "";
2416
+
2417
+ // The prompt highlighted in the sidebar is always for chat-view-1
2418
+ const activePromptId1 = AppState.activeChatViews[0].systemPromptId;
2419
+
2420
+ // Sort prompts by name, case-insensitive
2421
+ const sortedPrompts = Object.entries(AppState.systemPrompts).sort(
2422
+ ([, a], [, b]) => {
2423
+ return a.name.localeCompare(b.name);
2424
+ },
2425
+ );
2426
+
2427
+ sortedPrompts.forEach(([id, prompt]) => {
2428
+ const div = document.createElement("div");
2429
+ div.className = `list-item ${
2430
+ id === activePromptId1 ? "active" : ""
2431
+ }`;
2432
+ div.onclick = () => selectSystemPrompt(id, 1); // Clicks on sidebar always update view 1
2433
+ div.innerHTML = `
2434
+ <div class="list-item-header">
2435
+ <span class="list-item-name">${prompt.name}</span>
2436
+ <div class="list-item-actions">
2437
+ <button class="btn-icon" title="Edit" onclick="event.stopPropagation(); openPromptModal('${id}')">✏️</button>
2438
+ </div>
2439
+ </div>
2440
+ <div class="list-item-preview">${
2441
+ (prompt.content || "Empty prompt...").substring(0, 50)
2442
+ }</div>
2443
+ `;
2444
+ container.appendChild(div);
2445
+ });
2446
+ }
2447
+
2448
+ function selectSystemPrompt(promptId, chatViewIndex = 1) {
2449
+ const viewIndex = chatViewIndex - 1;
2450
+ AppState.activeChatViews[viewIndex].systemPromptId = promptId;
2451
+
2452
+ // Update the specific dropdown's selected value
2453
+ document.getElementById(`system-prompt-select-${chatViewIndex}`).value = promptId;
2454
+
2455
+ // If it's the primary view, also update the sidebar highlighting
2456
+ if (chatViewIndex === 1) {
2457
+ renderSystemPromptsList();
2458
+ }
2459
+ saveToStorage();
2460
+ showNotification(
2461
+ `System prompt updated for Chat View ${chatViewIndex}.`,
2462
+ "info",
2463
+ );
2464
+ }
2465
+
2466
+ function openPromptModal(promptId = null) {
2467
+ const modal = document.getElementById("prompt-modal");
2468
+ const title = document.getElementById("prompt-modal-title");
2469
+ const idInput = document.getElementById("modal-prompt-id");
2470
+ const nameInput = document.getElementById("modal-prompt-name");
2471
+ const contentInput = document.getElementById("modal-prompt-content");
2472
+
2473
+ if (promptId && AppState.systemPrompts[promptId]) {
2474
+ const prompt = AppState.systemPrompts[promptId];
2475
+ title.textContent = "Edit System Prompt";
2476
+ idInput.value = promptId;
2477
+ nameInput.value = prompt.name;
2478
+ contentInput.value = prompt.content;
2479
+ } else {
2480
+ title.textContent = "Create New System Prompt";
2481
+ idInput.value = "";
2482
+ nameInput.value = "";
2483
+ contentInput.value = "";
2484
+ }
2485
+ modal.classList.add("visible");
2486
+ }
2487
+
2488
+ function closePromptModal() {
2489
+ document.getElementById("prompt-modal").classList.remove("visible");
2490
+ }
2491
+
2492
+ function savePromptFromModal() {
2493
+ const id = document.getElementById("modal-prompt-id").value;
2494
+ const name = document.getElementById("modal-prompt-name").value.trim();
2495
+ const content = document
2496
+ .getElementById("modal-prompt-content")
2497
+ .value.trim();
2498
+ if (!name) {
2499
+ showNotification("Prompt name cannot be empty.", "error");
2500
+ return;
2501
+ }
2502
+
2503
+ const promptId = id || `custom-${Date.now()}`;
2504
+ const now = Date.now();
2505
+ AppState.systemPrompts[promptId] = {
2506
+ name,
2507
+ content,
2508
+ createdAt: AppState.systemPrompts[promptId]?.createdAt || now,
2509
+ updatedAt: now,
2510
+ };
2511
+
2512
+ renderSystemPromptsList();
2513
+ // Re-render both chat views to update their prompt dropdowns
2514
+ if (AppState.activeChatViews[0].conversationId) renderActiveConversation(1);
2515
+ if (AppState.activeChatViews[1].conversationId && AppState.splitView) renderActiveConversation(2);
2516
+
2517
+ saveToStorage();
2518
+ closePromptModal();
2519
+ showNotification("Prompt saved!", "success");
2520
+ }
2521
+
2522
+ // --- Notebook Management ---
2523
+ function renderNotebookList() {
2524
+ const container = document.getElementById("notebook-list");
2525
+ container.innerHTML = "";
2526
+ // Sort notes by creation date, newest first
2527
+ const sortedNotes = [...AppState.notebook].sort(
2528
+ (a, b) => b.createdAt - a.createdAt,
2529
+ );
2530
+
2531
+ sortedNotes.forEach((note) => {
2532
+ const div = document.createElement("div");
2533
+ div.className = "list-item";
2534
+ div.onclick = () => openNoteModal(note.id);
2535
+ div.innerHTML = `
2536
+ <div class="list-item-header">
2537
+ <span class="list-item-name">Note</span>
2538
+ <div class="list-item-actions">
2539
+ <button class="btn-icon" title="Delete" onclick="event.stopPropagation(); deleteNote('${note.id}')">🗑️</button>
2540
+ </div>
2541
+ </div>
2542
+ <div class="list-item-preview">${note.content}</div>
2543
+ `;
2544
+ container.appendChild(div);
2545
+ });
2546
+ }
2547
+
2548
+ function openNoteModal(noteId = null) {
2549
+ const modal = document.getElementById("note-modal");
2550
+ const title = document.getElementById("note-modal-title");
2551
+ const idInput = document.getElementById("modal-note-id");
2552
+ const contentInput = document.getElementById("modal-note-content");
2553
+
2554
+ if (noteId && AppState.notebook.find((n) => n.id === noteId)) {
2555
+ const note = AppState.notebook.find((n) => n.id === noteId);
2556
+ title.textContent = "Edit Note";
2557
+ idInput.value = noteId;
2558
+ contentInput.value = note.content;
2559
+ } else {
2560
+ title.textContent = "Create New Note";
2561
+ idInput.value = "";
2562
+ contentInput.value = "";
2563
+ }
2564
+ modal.classList.add("visible");
2565
+ }
2566
+
2567
+ function closeNoteModal() {
2568
+ document.getElementById("note-modal").classList.remove("visible");
2569
+ }
2570
+
2571
+ function saveNoteFromModal() {
2572
+ const id = document.getElementById("modal-note-id").value;
2573
+ const content = document
2574
+ .getElementById("modal-note-content")
2575
+ .value.trim();
2576
+ if (!content) return;
2577
+
2578
+ if (id) {
2579
+ const note = AppState.notebook.find((n) => n.id === id);
2580
+ if (note) note.content = content;
2581
+ } else {
2582
+ AppState.notebook.unshift({
2583
+ id: `note-${Date.now()}`,
2584
+ content: content,
2585
+ createdAt: Date.now(), // Add creation timestamp
2586
+ });
2587
+ }
2588
+ renderNotebookList();
2589
+ saveToStorage();
2590
+ closeNoteModal();
2591
+ }
2592
+
2593
+ function deleteNote(noteId) {
2594
+ if (confirm("Are you sure you want to delete this note?")) {
2595
+ AppState.notebook = AppState.notebook.filter((n) => n.id !== noteId);
2596
+ renderNotebookList();
2597
+ saveToStorage();
2598
+ }
2599
+ }
2600
+
2601
+ // --- Utility Functions ---
2602
+ function updateTokenCount(chatViewIndex = 1) {
2603
+ const text = document.getElementById(`user-input-${chatViewIndex}`).value;
2604
+ const tokenCount = Math.ceil(text.length / 4); // Rough approximation
2605
+ document.getElementById(
2606
+ `token-counter-${chatViewIndex}`,
2607
+ ).textContent = `Tokens: ${tokenCount}`;
2608
+ }
2609
+
2610
+ function updateModelOptions() {
2611
+ const modelSelect = document.getElementById("model");
2612
+ modelSelect.innerHTML = "";
2613
+ PROVIDERS.deepseek.models.forEach((model) => {
2614
+ const option = document.createElement("option");
2615
+ option.value = model;
2616
+ option.textContent = model;
2617
+ modelSelect.appendChild(option);
2618
+ });
2619
+ }
2620
+
2621
+ function showNotification(message, type = "info") {
2622
+ const notification = document.createElement("div");
2623
+ notification.style.cssText = `
2624
+ position: fixed; top: 20px; right: 20px; padding: 12px 20px;
2625
+ border-radius: 6px; color: white; font-weight: 500; z-index: 10001;
2626
+ opacity: 0; transition: opacity 0.3s, transform 0.3s; transform: translateY(-20px);
2627
+ background: ${
2628
+ type === "success" ?
2629
+ "var(--success)" :
2630
+ type === "error" ?
2631
+ "var(--danger)" :
2632
+ "var(--accent)"
2633
+ };
2634
+ `;
2635
+ notification.textContent = message;
2636
+ document.body.appendChild(notification);
2637
+ setTimeout(() => {
2638
+ notification.style.opacity = "1";
2639
+ notification.style.transform = "translateY(0)";
2640
+ }, 10);
2641
+ setTimeout(() => {
2642
+ notification.style.opacity = "0";
2643
+ notification.style.transform = "translateY(-20px)";
2644
+ setTimeout(() => document.body.removeChild(notification), 300);
2645
+ }, 3000);
2646
+ }
2647
+
2648
+ function downloadFile(content, filename, contentType) {
2649
+ const blob = new Blob([content], { type: contentType });
2650
+ const url = URL.createObjectURL(blob);
2651
+ const a = document.createElement("a");
2652
+ a.href = url;
2653
+ a.download = filename;
2654
+ document.body.appendChild(a);
2655
+ a.click();
2656
+ document.body.removeChild(a);
2657
+ URL.revokeObjectURL(url);
2658
+ }
2659
+
2660
+ function exportChatMD(chatViewIndex = 1) {
2661
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2662
+ const conv = AppState.conversations[viewState.conversationId];
2663
+ if (!conv) {
2664
+ showNotification("No conversation selected for export.", "error");
2665
+ return;
2666
+ }
2667
+ const systemPrompt = AppState.systemPrompts[viewState.systemPromptId];
2668
+ let mdContent = `# Chat Export: ${conv.title}\n\n`;
2669
+ mdContent += `**Model:** ${conv.parameters.model}\n`;
2670
+ mdContent += `**Temperature:** ${conv.parameters.temperature}\n`;
2671
+ mdContent += `**System Prompt: ${systemPrompt.name}**\n`;
2672
+ mdContent += `\`\`\`\n${systemPrompt.content}\n\`\`\`\n\n---\n\n`;
2673
+
2674
+ conv.messages.forEach((msg) => {
2675
+ const role = msg.role === "user" ? "You" : "Assistant";
2676
+ mdContent += `### **${role}**\n\n`;
2677
+ mdContent += `${msg.content}\n\n---\n\n`;
2678
+ });
2679
+
2680
+ downloadFile(
2681
+ mdContent,
2682
+ `${conv.title.replace(/\s/g, "_")}.md`,
2683
+ "text/markdown",
2684
+ );
2685
+ showNotification("Conversation exported to Markdown!", "success");
2686
+ }
2687
+
2688
+ // --- New: Split View Functionality ---
2689
+ function toggleSplitView() {
2690
+ AppState.splitView = !AppState.splitView;
2691
+ const chatView2 = document.getElementById("chat-view-2");
2692
+ if (AppState.splitView) {
2693
+ chatView2.style.display = "flex";
2694
+ // If view 2 has no conversation, and view 1 has one, mirror view 1
2695
+ if (!AppState.activeChatViews[1].conversationId && AppState.activeChatViews[0].conversationId) {
2696
+ AppState.activeChatViews[1].conversationId = AppState.activeChatViews[0].conversationId;
2697
+ // No need to change systemPromptId for view 2, it keeps its own or default.
2698
+ }
2699
+ // Always re-render view 2 to ensure correct display
2700
+ renderActiveConversation(2);
2701
+ } else {
2702
+ chatView2.style.display = "none";
2703
+ AppState.activeChatViews[1].conversationId = null; // Clear conversation for hidden view 2
2704
+ AppState.activeChatViews[1].systemPromptId = "default"; // Reset prompt for hidden view 2
2705
+ }
2706
+ saveToStorage();
2707
+ // renderAll(); // No need for full renderAll, specific renders are enough
2708
+ showNotification(
2709
+ `Split view ${AppState.splitView ? "enabled" : "disabled"}.`,
2710
+ "info",
2711
+ );
2712
+ }
2713
+
2714
+ // --- Generic Generate Log Functionality (All "Generate X" buttons call this) ---
2715
+ async function generateLog(chatViewIndex, buttonElement) {
2716
+ const viewState = AppState.activeChatViews[chatViewIndex - 1];
2717
+ const conversation = AppState.conversations[viewState.conversationId];
2718
+ if (!conversation) {
2719
+ showNotification("No active conversation selected.", "error");
2720
+ return;
2721
+ }
2722
+
2723
+ const promptId = buttonElement.dataset.promptId;
2724
+ if (!promptId || !AppState.systemPrompts[promptId]) {
2725
+ showNotification(
2726
+ "Error: Invalid prompt ID for log generation. Check data-prompt-id on button.",
2727
+ "error",
2728
+ );
2729
+ return;
2730
+ }
2731
+
2732
+ const sendBtn = document.getElementById(`send-btn-${chatViewIndex}`);
2733
+ const originalBtnHTML = buttonElement.innerHTML;
2734
+ const originalSendBtnHTML = sendBtn.innerHTML;
2735
+
2736
+ // Show loading on the clicked button and disable it
2737
+ buttonElement.innerHTML = '<div class="loading"></div>';
2738
+ buttonElement.disabled = true;
2739
+ // Also disable the main send button while generation is in progress
2740
+ sendBtn.innerHTML = '<div class="loading"></div>';
2741
+ sendBtn.disabled = true;
2742
+
2743
+ // Store current system prompt ID for this view to restore it later
2744
+ const originalSystemPromptId = viewState.systemPromptId;
2745
+ // Temporarily set the system prompt for THIS VIEW to the selected log generator's prompt
2746
+ viewState.systemPromptId = promptId;
2747
+
2748
+ try {
2749
+ const response = await callAPI(chatViewIndex); // Call API with the temporarily changed system prompt for this view
2750
+ conversation.messages.push({
2751
+ role: "assistant",
2752
+ content: response,
2753
+ promptId: promptId, // Store the promptId with the message for BRF export and potential future rendering hints
2754
+ });
2755
+ } catch (error) {
2756
+ console.error("API call error during log generation:", error);
2757
+ conversation.messages.push({
2758
+ role: "assistant",
2759
+ content: `❌ Error generating log: ${error.message}`,
2760
+ isError: true,
2761
+ promptId: 'error',
2762
+ });
2763
+ } finally {
2764
+ // Restore the original system prompt for the conversation view
2765
+ viewState.systemPromptId = originalSystemPromptId;
2766
+
2767
+ renderActiveConversation(chatViewIndex); // Re-render chat with new message
2768
+ // Restore button states
2769
+ buttonElement.innerHTML = originalBtnHTML;
2770
+ buttonElement.disabled = false;
2771
+ sendBtn.innerHTML = originalSendBtnHTML;
2772
+ sendBtn.disabled = false;
2773
+ saveToStorage(); // Save updated conversation to storage
2774
+ }
2775
+ }
2776
+
2777
+ // --- BRF Import/Export UI Functions ---
2778
+ function exportBRF() {
2779
+ try {
2780
+ const brfData = brfManager.generateBRF({
2781
+ studioVersion: "BAIRC Chat Studio 2.1.0",
2782
+ userId: "user_" + Math.random().toString(36).substr(2, 5),
2783
+ });
2784
+ downloadFile(
2785
+ brfData,
2786
+ `BAIRC_Export_${new Date().toISOString().replace(/[:.-]/g, "")}.json`,
2787
+ "application/json",
2788
+ );
2789
+ showNotification("BRF data exported successfully!", "success");
2790
+ } catch (error) {
2791
+ console.error("BRF Export Error:", error);
2792
+ showNotification(`BRF Export failed: ${error.message}`, "error");
2793
+ }
2794
+ }
2795
+
2796
+ async function handleBRFImport(event) {
2797
+ const file = event.target.files[0];
2798
+ if (!file) return;
2799
+
2800
+ const reader = new FileReader();
2801
+ reader.onload = async (e) => {
2802
+ try {
2803
+ const brfContent = e.target.result;
2804
+ await brfManager.importBRF(brfContent, { createBackup: true });
2805
+ showNotification("BRF data imported successfully!", "success");
2806
+ renderAll(); // Re-render the entire UI to reflect imported data
2807
+ } catch (error) {
2808
+ console.error("BRF Import Error:", error);
2809
+ showNotification(`BRF Import failed: ${error.message}`, "error");
2810
+ }
2811
+ event.target.value = ""; // Clear file input
2812
+ };
2813
+ reader.readAsText(file);
2814
+ }
2815
+ </script>
2816
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ninjacricket/sorin-s-cat" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
2817
+ </html>