ps-claw 1.0.9 β 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/ps-claw.cmd +2 -0
- package/ps-claw.sh +2 -0
- package/web-ui/public/index.html +935 -1072
- package/web-ui/server.mjs +46 -143
package/web-ui/public/index.html
CHANGED
|
@@ -1,414 +1,362 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="pt-BR">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.gw-btn.primary:hover { background: var(--accent-hover); }
|
|
111
|
-
.gw-btn.danger:hover { border-color: var(--danger); color: var(--danger); }
|
|
112
|
-
.gw-add-btn {
|
|
113
|
-
width: 100%; padding: 10px; border: 1px dashed var(--border); border-radius: var(--radius-sm);
|
|
114
|
-
background: transparent; color: var(--text-muted); cursor: pointer; font-size: 13px;
|
|
115
|
-
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
116
|
-
transition: all var(--transition);
|
|
117
|
-
}
|
|
118
|
-
.gw-add-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
119
|
-
.gw-list { overflow-y: auto; flex: 1; }
|
|
120
|
-
|
|
121
|
-
/* MODELS TAB */
|
|
122
|
-
.model-section { padding: 12px; overflow-y: auto; flex: 1; }
|
|
123
|
-
.provider-group { margin-bottom: 16px; }
|
|
124
|
-
.provider-header {
|
|
125
|
-
display: flex; align-items: center; gap: 8px; padding: 8px 10px;
|
|
126
|
-
background: var(--input-bg); border-radius: var(--radius-sm); margin-bottom: 8px;
|
|
127
|
-
cursor: pointer; transition: all var(--transition);
|
|
128
|
-
}
|
|
129
|
-
.provider-header:hover { background: rgba(255,255,255,0.05); }
|
|
130
|
-
.provider-icon { font-size: 18px; }
|
|
131
|
-
.provider-name { font-size: 13px; font-weight: 600; flex: 1; }
|
|
132
|
-
.provider-count { font-size: 11px; color: var(--text-muted); background: var(--bg-deep); padding: 2px 8px; border-radius: 10px; }
|
|
133
|
-
.provider-toggle { font-size: 12px; color: var(--text-muted); transition: transform var(--transition); }
|
|
134
|
-
.provider-toggle.open { transform: rotate(180deg); }
|
|
135
|
-
.model-list { display: none; padding-left: 4px; }
|
|
136
|
-
.model-list.open { display: block; }
|
|
137
|
-
.model-item {
|
|
138
|
-
display: flex; align-items: center; gap: 8px; padding: 7px 10px;
|
|
139
|
-
border-radius: 6px; cursor: pointer; font-size: 13px; transition: background var(--transition);
|
|
140
|
-
}
|
|
141
|
-
.model-item:hover { background: var(--input-bg); }
|
|
142
|
-
.model-item.selected { background: rgba(16,163,127,0.1); color: var(--accent); }
|
|
143
|
-
.model-item.selected .model-radio { border-color: var(--accent); background: var(--accent); }
|
|
144
|
-
.model-radio {
|
|
145
|
-
width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--border);
|
|
146
|
-
flex-shrink: 0; position: relative; transition: all var(--transition);
|
|
147
|
-
}
|
|
148
|
-
.model-item.selected .model-radio::after {
|
|
149
|
-
content: ''; position: absolute; top: 3px; left: 3px; width: 6px; height: 6px;
|
|
150
|
-
border-radius: 50%; background: white;
|
|
151
|
-
}
|
|
152
|
-
.model-name { flex: 1; }
|
|
153
|
-
.model-tag { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: var(--bg-deep); color: var(--text-muted); }
|
|
154
|
-
|
|
155
|
-
/* SETTINGS TAB */
|
|
156
|
-
.settings-section { padding: 12px; overflow-y: auto; flex: 1; }
|
|
157
|
-
.settings-group { margin-bottom: 20px; }
|
|
158
|
-
.settings-group-title {
|
|
159
|
-
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;
|
|
160
|
-
color: var(--text-muted); margin-bottom: 10px;
|
|
161
|
-
}
|
|
162
|
-
.settings-field { margin-bottom: 12px; }
|
|
163
|
-
.settings-field label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 5px; font-weight: 500; }
|
|
164
|
-
.settings-field input, .settings-field select, .settings-field textarea {
|
|
165
|
-
width: 100%; padding: 8px 10px; background: var(--input-bg);
|
|
166
|
-
border: 1px solid var(--border); border-radius: 6px; color: var(--text);
|
|
167
|
-
font-size: 13px; outline: none; transition: border-color var(--transition);
|
|
168
|
-
}
|
|
169
|
-
.settings-field input:focus, .settings-field select:focus, .settings-field textarea:focus { border-color: var(--accent); }
|
|
170
|
-
.settings-field textarea { resize: vertical; min-height: 60px; font-family: monospace; font-size: 12px; }
|
|
171
|
-
.settings-field .hint { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
|
172
|
-
.settings-toggle {
|
|
173
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
174
|
-
padding: 8px 0; border-bottom: 1px solid var(--border);
|
|
175
|
-
}
|
|
176
|
-
.settings-toggle-label { font-size: 13px; }
|
|
177
|
-
.toggle-switch {
|
|
178
|
-
width: 40px; height: 22px; background: var(--border); border-radius: 11px;
|
|
179
|
-
cursor: pointer; position: relative; transition: background var(--transition);
|
|
180
|
-
}
|
|
181
|
-
.toggle-switch.on { background: var(--accent); }
|
|
182
|
-
.toggle-switch::after {
|
|
183
|
-
content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
|
|
184
|
-
background: white; border-radius: 50%; transition: transform var(--transition);
|
|
185
|
-
}
|
|
186
|
-
.toggle-switch.on::after { transform: translateX(18px); }
|
|
187
|
-
|
|
188
|
-
/* SIDEBAR FOOTER */
|
|
189
|
-
#sidebar-footer { padding: 10px; border-top: 1px solid var(--border); }
|
|
190
|
-
#status-badge {
|
|
191
|
-
display: flex; align-items: center; gap: 8px; font-size: 12px;
|
|
192
|
-
color: var(--text-muted); padding: 8px; border-radius: var(--radius-sm); background: var(--input-bg);
|
|
193
|
-
}
|
|
194
|
-
#status-dot { width: 8px; height: 8px; border-radius: 50%; background: #888; flex-shrink: 0; }
|
|
195
|
-
#status-dot.online { background: var(--accent); box-shadow: 0 0 6px var(--accent-glow); }
|
|
196
|
-
#status-dot.offline { background: var(--danger); }
|
|
197
|
-
|
|
198
|
-
/* MAIN */
|
|
199
|
-
#main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
200
|
-
#topbar {
|
|
201
|
-
padding: 12px 20px; border-bottom: 1px solid var(--border);
|
|
202
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
203
|
-
background: var(--bg);
|
|
204
|
-
}
|
|
205
|
-
#topbar-left { display: flex; align-items: center; gap: 10px; }
|
|
206
|
-
#topbar-title { font-size: 15px; font-weight: 600; }
|
|
207
|
-
#model-indicator {
|
|
208
|
-
display: flex; align-items: center; gap: 6px; font-size: 12px;
|
|
209
|
-
padding: 4px 10px; background: var(--input-bg); border-radius: 20px;
|
|
210
|
-
color: var(--text-muted); border: 1px solid var(--border);
|
|
211
|
-
}
|
|
212
|
-
#model-indicator .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
|
|
213
|
-
|
|
214
|
-
/* MESSAGES */
|
|
215
|
-
#messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 4px; scroll-behavior: smooth; }
|
|
216
|
-
#messages::-webkit-scrollbar { width: 6px; }
|
|
217
|
-
#messages::-webkit-scrollbar-track { background: transparent; }
|
|
218
|
-
#messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
219
|
-
|
|
220
|
-
.msg-row { display: flex; gap: 12px; padding: 12px 0; max-width: 800px; margin: 0 auto; width: 100%; }
|
|
221
|
-
.msg-row.user { flex-direction: row-reverse; }
|
|
222
|
-
.avatar {
|
|
223
|
-
width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
|
|
224
|
-
display: flex; align-items: center; justify-content: center; font-size: 14px;
|
|
225
|
-
font-weight: 700;
|
|
226
|
-
}
|
|
227
|
-
.avatar.ai { background: linear-gradient(135deg, var(--accent), #0ea5e9); color: white; }
|
|
228
|
-
.avatar.user { background: linear-gradient(135deg, #6c6ef5, #8b5cf6); color: white; }
|
|
229
|
-
.bubble {
|
|
230
|
-
max-width: calc(100% - 50px); padding: 12px 16px; border-radius: 16px;
|
|
231
|
-
font-size: 14px; line-height: 1.65; word-break: break-word;
|
|
232
|
-
}
|
|
233
|
-
.bubble.ai { background: var(--ai-bubble); border-radius: 0 16px 16px 16px; }
|
|
234
|
-
.bubble.user { background: var(--user-bubble); border-radius: 16px 16px 0 16px; }
|
|
235
|
-
.bubble pre {
|
|
236
|
-
background: #0d0d1a; border: 1px solid var(--border); border-radius: 8px;
|
|
237
|
-
padding: 12px; overflow-x: auto; margin: 8px 0; font-size: 13px;
|
|
238
|
-
}
|
|
239
|
-
.bubble code { font-family: 'Cascadia Code', 'Fira Code', monospace; }
|
|
240
|
-
.bubble p { margin-bottom: 8px; }
|
|
241
|
-
.bubble p:last-child { margin-bottom: 0; }
|
|
242
|
-
.bubble ul, .bubble ol { padding-left: 20px; margin-bottom: 8px; }
|
|
243
|
-
.bubble strong { color: #fff; }
|
|
244
|
-
.typing-dots span { animation: blink 1.2s infinite; font-size: 20px; color: var(--accent); }
|
|
245
|
-
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
246
|
-
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
247
|
-
@keyframes blink { 0%,80%,100%{opacity:0} 40%{opacity:1} }
|
|
248
|
-
|
|
249
|
-
#welcome {
|
|
250
|
-
flex: 1; display: flex; flex-direction: column; align-items: center;
|
|
251
|
-
justify-content: center; gap: 16px; color: var(--text-muted); padding: 40px;
|
|
252
|
-
}
|
|
253
|
-
#welcome h2 { font-size: 28px; color: var(--text); background: linear-gradient(135deg, var(--accent), #0ea5e9); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
254
|
-
#welcome p { font-size: 15px; text-align: center; max-width: 440px; }
|
|
255
|
-
.welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 8px; }
|
|
256
|
-
.chip {
|
|
257
|
-
padding: 8px 14px; background: var(--input-bg); border: 1px solid var(--border);
|
|
258
|
-
border-radius: 20px; font-size: 13px; cursor: pointer; transition: all var(--transition);
|
|
259
|
-
color: var(--text);
|
|
260
|
-
}
|
|
261
|
-
.chip:hover { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 10px var(--accent-glow); }
|
|
262
|
-
|
|
263
|
-
/* INPUT */
|
|
264
|
-
#input-area { padding: 16px 20px 20px; background: var(--bg); }
|
|
265
|
-
#input-box {
|
|
266
|
-
max-width: 800px; margin: 0 auto;
|
|
267
|
-
background: var(--input-bg); border: 1px solid var(--border);
|
|
268
|
-
border-radius: var(--radius); display: flex; align-items: flex-end; gap: 8px;
|
|
269
|
-
padding: 12px 14px; transition: border-color var(--transition);
|
|
270
|
-
}
|
|
271
|
-
#input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 10px var(--accent-glow); }
|
|
272
|
-
#msg-input {
|
|
273
|
-
flex: 1; background: transparent; border: none; outline: none;
|
|
274
|
-
color: var(--text); font-size: 14px; resize: none; max-height: 200px;
|
|
275
|
-
line-height: 1.5; font-family: inherit;
|
|
276
|
-
}
|
|
277
|
-
#msg-input::placeholder { color: var(--text-muted); }
|
|
278
|
-
#send-btn {
|
|
279
|
-
width: 34px; height: 34px; border-radius: 8px; border: none;
|
|
280
|
-
background: var(--accent); color: white; cursor: pointer;
|
|
281
|
-
display: flex; align-items: center; justify-content: center;
|
|
282
|
-
transition: background var(--transition); flex-shrink: 0;
|
|
283
|
-
}
|
|
284
|
-
#send-btn:hover:not(:disabled) { background: var(--accent-hover); }
|
|
285
|
-
#send-btn:disabled { background: var(--border); cursor: not-allowed; }
|
|
286
|
-
#input-hint { text-align: center; font-size: 11px; color: var(--text-muted); margin-top: 8px; }
|
|
287
|
-
|
|
288
|
-
/* MODAL */
|
|
289
|
-
#modal-overlay {
|
|
290
|
-
display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7);
|
|
291
|
-
z-index: 100; align-items: center; justify-content: center;
|
|
292
|
-
backdrop-filter: blur(4px);
|
|
293
|
-
}
|
|
294
|
-
#modal-overlay.open { display: flex; }
|
|
295
|
-
#modal-box {
|
|
296
|
-
background: var(--sidebar-bg); border: 1px solid var(--border); border-radius: 16px;
|
|
297
|
-
padding: 24px; width: 460px; max-width: 95vw; max-height: 85vh; overflow-y: auto;
|
|
298
|
-
}
|
|
299
|
-
#modal-box h2 { margin-bottom: 20px; font-size: 18px; display: flex; align-items: center; gap: 8px; }
|
|
300
|
-
.field { margin-bottom: 14px; }
|
|
301
|
-
.field label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 5px; font-weight: 500; }
|
|
302
|
-
.field input, .field select {
|
|
303
|
-
width: 100%; padding: 9px 12px; background: var(--input-bg);
|
|
304
|
-
border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text);
|
|
305
|
-
font-size: 13px; outline: none; transition: border-color var(--transition);
|
|
306
|
-
}
|
|
307
|
-
.field input:focus, .field select:focus { border-color: var(--accent); }
|
|
308
|
-
.field .hint { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
|
309
|
-
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
|
310
|
-
.btn { padding: 8px 18px; border-radius: var(--radius-sm); font-size: 13px; cursor: pointer; border: none; transition: all var(--transition); }
|
|
311
|
-
.btn-ghost { background: transparent; border: 1px solid var(--border); color: var(--text); }
|
|
312
|
-
.btn-primary { background: var(--accent); color: white; }
|
|
313
|
-
.btn-danger { background: var(--danger); color: white; }
|
|
314
|
-
.btn:hover { opacity: 0.85; }
|
|
4
|
+
<meta charset="UTF-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
|
6
|
+
<title>PS Claw</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|
8
|
+
<style>
|
|
9
|
+
:root{
|
|
10
|
+
--bg:#0e0e11;
|
|
11
|
+
--surface:#16161a;
|
|
12
|
+
--surface2:#1e1e24;
|
|
13
|
+
--surface3:#26262e;
|
|
14
|
+
--border:#2e2e38;
|
|
15
|
+
--border2:#3a3a48;
|
|
16
|
+
--txt:#f0f0f5;
|
|
17
|
+
--txt2:#9090a8;
|
|
18
|
+
--txt3:#5a5a70;
|
|
19
|
+
--acc:#7c6af7;
|
|
20
|
+
--acc2:#9b8fff;
|
|
21
|
+
--acc3:#5c4fe0;
|
|
22
|
+
--green:#22c98a;
|
|
23
|
+
--red:#f05a5a;
|
|
24
|
+
--orange:#f0a048;
|
|
25
|
+
--yellow:#f0d060;
|
|
26
|
+
--rad:14px;
|
|
27
|
+
--rad-sm:8px;
|
|
28
|
+
}
|
|
29
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
30
|
+
html{height:100%}
|
|
31
|
+
body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--txt);display:flex;height:100vh;overflow:hidden;font-size:14px}
|
|
32
|
+
|
|
33
|
+
/* ββ SIDEBAR ββ */
|
|
34
|
+
#sidebar{
|
|
35
|
+
width:268px;min-width:268px;background:var(--surface);
|
|
36
|
+
display:flex;flex-direction:column;
|
|
37
|
+
border-right:1px solid var(--border);
|
|
38
|
+
transition:transform .3s cubic-bezier(.4,0,.2,1);z-index:20
|
|
39
|
+
}
|
|
40
|
+
#sb-logo{
|
|
41
|
+
padding:20px 18px 16px;
|
|
42
|
+
border-bottom:1px solid var(--border);
|
|
43
|
+
display:flex;align-items:center;gap:10px
|
|
44
|
+
}
|
|
45
|
+
#sb-logo .logo-icon{
|
|
46
|
+
width:34px;height:34px;border-radius:10px;
|
|
47
|
+
background:linear-gradient(135deg,var(--acc3),var(--acc2));
|
|
48
|
+
display:flex;align-items:center;justify-content:center;
|
|
49
|
+
font-size:18px;flex-shrink:0
|
|
50
|
+
}
|
|
51
|
+
#sb-logo h1{font-size:17px;font-weight:800;letter-spacing:-.3px}
|
|
52
|
+
#sb-logo span{font-size:10px;color:var(--acc2);font-weight:600;letter-spacing:.5px;display:block;margin-top:1px}
|
|
53
|
+
#new-btn{
|
|
54
|
+
margin:12px 12px 6px;
|
|
55
|
+
padding:10px 14px;
|
|
56
|
+
background:linear-gradient(135deg,var(--acc3),var(--acc));
|
|
57
|
+
border:none;border-radius:var(--rad-sm);
|
|
58
|
+
color:#fff;cursor:pointer;font-size:13px;font-weight:700;
|
|
59
|
+
font-family:'Syne',sans-serif;
|
|
60
|
+
display:flex;align-items:center;gap:8px;
|
|
61
|
+
transition:opacity .15s;letter-spacing:.2px
|
|
62
|
+
}
|
|
63
|
+
#new-btn:hover{opacity:.85}
|
|
64
|
+
#chat-list{flex:1;overflow-y:auto;padding:4px 8px;scrollbar-width:thin;scrollbar-color:var(--border) transparent}
|
|
65
|
+
.ci{
|
|
66
|
+
padding:9px 10px;border-radius:var(--rad-sm);cursor:pointer;
|
|
67
|
+
font-size:13px;color:var(--txt2);
|
|
68
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
69
|
+
transition:all .15s;display:flex;align-items:center;justify-content:space-between;
|
|
70
|
+
gap:8px;border:1px solid transparent
|
|
71
|
+
}
|
|
72
|
+
.ci:hover{background:var(--surface2);color:var(--txt);border-color:var(--border)}
|
|
73
|
+
.ci.active{background:var(--surface2);color:var(--txt);border-color:var(--border2)}
|
|
74
|
+
.ci-del{opacity:0;border:none;background:transparent;color:var(--txt3);cursor:pointer;font-size:12px;padding:2px 5px;border-radius:4px;flex-shrink:0}
|
|
75
|
+
.ci:hover .ci-del{opacity:1}
|
|
76
|
+
.ci-del:hover{color:var(--red)}
|
|
77
|
+
#sb-footer{padding:12px;border-top:1px solid var(--border)}
|
|
78
|
+
#st-badge{
|
|
79
|
+
display:flex;align-items:center;gap:8px;font-size:12px;
|
|
80
|
+
color:var(--txt2);padding:9px 12px;border-radius:var(--rad-sm);
|
|
81
|
+
background:var(--surface2);border:1px solid var(--border)
|
|
82
|
+
}
|
|
83
|
+
#st-dot{width:7px;height:7px;border-radius:50%;background:var(--border2);flex-shrink:0;transition:all .4s}
|
|
84
|
+
#st-dot.on{background:var(--green);box-shadow:0 0 8px var(--green)}
|
|
85
|
+
#st-dot.off{background:var(--red)}
|
|
86
|
+
#st-dot.warn{background:var(--orange)}
|
|
87
|
+
|
|
88
|
+
/* ββ MAIN ββ */
|
|
89
|
+
#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
|
|
90
|
+
|
|
91
|
+
/* ββ TOPBAR ββ */
|
|
92
|
+
#topbar{
|
|
93
|
+
padding:0 20px;border-bottom:1px solid var(--border);
|
|
94
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
95
|
+
height:54px;background:var(--surface);flex-shrink:0
|
|
96
|
+
}
|
|
97
|
+
#tb-left{display:flex;align-items:center;gap:12px}
|
|
98
|
+
#menu-btn{display:none;background:transparent;border:none;color:var(--txt2);font-size:20px;cursor:pointer;padding:4px}
|
|
99
|
+
#tb-title{font-size:15px;font-weight:700;letter-spacing:-.2px}
|
|
100
|
+
#tb-right{display:flex;align-items:center;gap:8px}
|
|
101
|
+
#model-pill{
|
|
102
|
+
display:flex;align-items:center;gap:6px;
|
|
103
|
+
background:var(--surface2);border:1px solid var(--border);
|
|
104
|
+
padding:6px 12px;border-radius:20px;cursor:pointer;
|
|
105
|
+
font-size:12px;font-weight:600;color:var(--txt2);
|
|
106
|
+
transition:all .15s
|
|
107
|
+
}
|
|
108
|
+
#model-pill:hover{border-color:var(--acc);color:var(--acc)}
|
|
109
|
+
#model-pill .dot{width:6px;height:6px;border-radius:50%;background:var(--green)}
|
|
315
110
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
111
|
+
/* ββ TABS ββ */
|
|
112
|
+
#tabs{
|
|
113
|
+
display:flex;gap:0;border-bottom:1px solid var(--border);
|
|
114
|
+
background:var(--surface);flex-shrink:0;padding:0 20px
|
|
115
|
+
}
|
|
116
|
+
.tab{
|
|
117
|
+
padding:11px 16px;font-size:13px;font-weight:600;
|
|
118
|
+
color:var(--txt3);cursor:pointer;
|
|
119
|
+
border-bottom:2px solid transparent;
|
|
120
|
+
transition:all .15s;white-space:nowrap;
|
|
121
|
+
display:flex;align-items:center;gap:7px;
|
|
122
|
+
letter-spacing:.1px
|
|
123
|
+
}
|
|
124
|
+
.tab:hover{color:var(--txt2)}
|
|
125
|
+
.tab.active{color:var(--acc2);border-bottom-color:var(--acc)}
|
|
126
|
+
|
|
127
|
+
/* ββ PAGES ββ */
|
|
128
|
+
.page{flex:1;overflow-y:auto;display:none;scrollbar-width:thin;scrollbar-color:var(--border) transparent}
|
|
129
|
+
.page.active{display:flex;flex-direction:column}
|
|
130
|
+
.page-inner{padding:24px;max-width:780px;width:100%}
|
|
131
|
+
|
|
132
|
+
/* ββ CHAT ββ */
|
|
133
|
+
#chat-page{position:relative}
|
|
134
|
+
#messages{
|
|
135
|
+
flex:1;overflow-y:auto;
|
|
136
|
+
padding:24px 20px;
|
|
137
|
+
display:flex;flex-direction:column;gap:2px;
|
|
138
|
+
scroll-behavior:smooth;
|
|
139
|
+
scrollbar-width:thin;scrollbar-color:var(--border) transparent
|
|
140
|
+
}
|
|
141
|
+
#welcome{
|
|
142
|
+
flex:1;display:flex;flex-direction:column;
|
|
143
|
+
align-items:center;justify-content:center;
|
|
144
|
+
gap:20px;padding:40px 20px;text-align:center
|
|
145
|
+
}
|
|
146
|
+
#welcome .w-icon{
|
|
147
|
+
width:72px;height:72px;border-radius:20px;
|
|
148
|
+
background:linear-gradient(135deg,var(--acc3),var(--acc2));
|
|
149
|
+
display:flex;align-items:center;justify-content:center;font-size:36px;
|
|
150
|
+
box-shadow:0 0 40px rgba(124,106,247,.3)
|
|
151
|
+
}
|
|
152
|
+
#welcome h2{font-size:28px;font-weight:800;letter-spacing:-.5px}
|
|
153
|
+
#welcome p{font-size:14px;color:var(--txt2);max-width:400px;line-height:1.7}
|
|
154
|
+
.chips{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:4px}
|
|
155
|
+
.chip{
|
|
156
|
+
padding:8px 16px;background:var(--surface2);
|
|
157
|
+
border:1px solid var(--border);border-radius:20px;
|
|
158
|
+
font-size:13px;cursor:pointer;transition:all .15s;color:var(--txt2);
|
|
159
|
+
font-family:'Syne',sans-serif;font-weight:600
|
|
160
|
+
}
|
|
161
|
+
.chip:hover{border-color:var(--acc);color:var(--acc);background:rgba(124,106,247,.08)}
|
|
162
|
+
|
|
163
|
+
.msg-row{display:flex;gap:12px;padding:10px 0;max-width:820px;margin:0 auto;width:100%;animation:fadeUp .2s ease}
|
|
164
|
+
@keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
|
165
|
+
.msg-row.user{flex-direction:row-reverse}
|
|
166
|
+
.av{
|
|
167
|
+
width:32px;height:32px;border-radius:10px;flex-shrink:0;
|
|
168
|
+
display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800
|
|
169
|
+
}
|
|
170
|
+
.av.ai{background:linear-gradient(135deg,var(--acc3),var(--acc2));color:#fff}
|
|
171
|
+
.av.user{background:var(--surface3);color:var(--txt);border:1px solid var(--border2)}
|
|
172
|
+
.bbl{
|
|
173
|
+
max-width:calc(100% - 48px);padding:12px 16px;
|
|
174
|
+
font-size:14px;line-height:1.75;word-break:break-word
|
|
175
|
+
}
|
|
176
|
+
.bbl.ai{color:var(--txt)}
|
|
177
|
+
.bbl.user{
|
|
178
|
+
background:var(--surface2);border:1px solid var(--border);
|
|
179
|
+
border-radius:14px 14px 4px 14px;color:var(--txt)
|
|
180
|
+
}
|
|
181
|
+
.bbl pre{
|
|
182
|
+
background:var(--surface3);border:1px solid var(--border2);
|
|
183
|
+
border-radius:var(--rad-sm);padding:14px;overflow-x:auto;
|
|
184
|
+
margin:10px 0;font-size:13px;font-family:'JetBrains Mono',monospace
|
|
185
|
+
}
|
|
186
|
+
.bbl code{font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--acc2)}
|
|
187
|
+
.bbl p{margin-bottom:8px}.bbl p:last-child{margin:0}
|
|
188
|
+
.bbl ul,.bbl ol{padding-left:20px;margin-bottom:8px}
|
|
189
|
+
.bbl strong{color:var(--txt);font-weight:700}
|
|
190
|
+
.bbl h1,.bbl h2,.bbl h3{margin:10px 0 6px;font-weight:700}
|
|
191
|
+
.bbl table{border-collapse:collapse;width:100%;margin:10px 0}
|
|
192
|
+
.bbl th,.bbl td{border:1px solid var(--border2);padding:8px 12px;text-align:left}
|
|
193
|
+
.bbl th{background:var(--surface3);font-weight:700}
|
|
194
|
+
|
|
195
|
+
/* typing */
|
|
196
|
+
.typing{display:flex;gap:4px;align-items:center;padding:14px 16px}
|
|
197
|
+
.typing span{width:6px;height:6px;border-radius:50%;background:var(--acc);animation:pulse 1.2s infinite}
|
|
198
|
+
.typing span:nth-child(2){animation-delay:.2s}
|
|
199
|
+
.typing span:nth-child(3){animation-delay:.4s}
|
|
200
|
+
@keyframes pulse{0%,60%,100%{opacity:.2;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
|
|
201
|
+
|
|
202
|
+
/* ββ INPUT ββ */
|
|
203
|
+
#input-wrap{padding:16px 20px 20px;background:var(--surface);border-top:1px solid var(--border);flex-shrink:0}
|
|
204
|
+
#input-box{
|
|
205
|
+
max-width:820px;margin:0 auto;
|
|
206
|
+
background:var(--surface2);border:1px solid var(--border);
|
|
207
|
+
border-radius:var(--rad);display:flex;align-items:flex-end;gap:8px;
|
|
208
|
+
padding:12px 14px;transition:border-color .2s
|
|
209
|
+
}
|
|
210
|
+
#input-box:focus-within{border-color:var(--acc)}
|
|
211
|
+
#msg-in{
|
|
212
|
+
flex:1;background:transparent;border:none;outline:none;
|
|
213
|
+
color:var(--txt);font-size:14px;resize:none;max-height:180px;
|
|
214
|
+
line-height:1.6;font-family:'Syne',sans-serif
|
|
215
|
+
}
|
|
216
|
+
#msg-in::placeholder{color:var(--txt3)}
|
|
217
|
+
#send-btn{
|
|
218
|
+
width:36px;height:36px;border-radius:10px;border:none;
|
|
219
|
+
background:linear-gradient(135deg,var(--acc3),var(--acc));
|
|
220
|
+
color:#fff;cursor:pointer;
|
|
221
|
+
display:flex;align-items:center;justify-content:center;
|
|
222
|
+
transition:opacity .15s;flex-shrink:0
|
|
223
|
+
}
|
|
224
|
+
#send-btn:hover:not(:disabled){opacity:.85}
|
|
225
|
+
#send-btn:disabled{background:var(--surface3);cursor:not-allowed}
|
|
226
|
+
#input-hint{
|
|
227
|
+
text-align:center;font-size:11px;color:var(--txt3);
|
|
228
|
+
margin-top:8px;max-width:820px;margin-left:auto;margin-right:auto
|
|
229
|
+
}
|
|
328
230
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
231
|
+
/* ββ SETTINGS PAGES ββ */
|
|
232
|
+
.sp-header{margin-bottom:24px}
|
|
233
|
+
.sp-title{font-size:22px;font-weight:800;letter-spacing:-.4px;margin-bottom:4px}
|
|
234
|
+
.sp-sub{font-size:13px;color:var(--txt2);line-height:1.6}
|
|
235
|
+
.card{
|
|
236
|
+
background:var(--surface2);border:1px solid var(--border);
|
|
237
|
+
border-radius:var(--rad);padding:20px;margin-bottom:14px
|
|
238
|
+
}
|
|
239
|
+
.card h3{font-size:13px;font-weight:700;margin-bottom:16px;color:var(--txt2);text-transform:uppercase;letter-spacing:.8px;display:flex;align-items:center;gap:8px}
|
|
240
|
+
.field{margin-bottom:14px}.field:last-child{margin:0}
|
|
241
|
+
.field label{display:block;font-size:12px;color:var(--txt3);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}
|
|
242
|
+
.field input,.field select,.field textarea{
|
|
243
|
+
width:100%;padding:10px 13px;
|
|
244
|
+
background:var(--surface);border:1px solid var(--border);
|
|
245
|
+
border-radius:var(--rad-sm);color:var(--txt);
|
|
246
|
+
font-size:14px;outline:none;font-family:'Syne',sans-serif;
|
|
247
|
+
transition:border-color .15s
|
|
248
|
+
}
|
|
249
|
+
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--acc)}
|
|
250
|
+
.field input[type=password]{font-family:'JetBrains Mono',monospace;letter-spacing:.1em}
|
|
251
|
+
.field select option{background:var(--surface)}
|
|
252
|
+
.field textarea{resize:vertical;min-height:80px;line-height:1.6}
|
|
253
|
+
.row{display:flex;gap:12px}.row .field{flex:1}
|
|
254
|
+
.btn{padding:10px 20px;border-radius:var(--rad-sm);font-size:13px;cursor:pointer;border:none;font-weight:700;transition:all .15s;font-family:'Syne',sans-serif;letter-spacing:.2px}
|
|
255
|
+
.btn-primary{background:linear-gradient(135deg,var(--acc3),var(--acc));color:#fff}
|
|
256
|
+
.btn-primary:hover{opacity:.85}
|
|
257
|
+
.btn-ghost{background:transparent;border:1px solid var(--border);color:var(--txt2)}
|
|
258
|
+
.btn-ghost:hover{border-color:var(--border2);color:var(--txt)}
|
|
259
|
+
.btn-danger{background:transparent;border:1px solid var(--red);color:var(--red)}
|
|
260
|
+
.btn-danger:hover{background:var(--red);color:#fff}
|
|
261
|
+
.btn-sm{padding:6px 14px;font-size:12px}
|
|
262
|
+
.btn-xs{padding:4px 10px;font-size:11px}
|
|
263
|
+
|
|
264
|
+
/* ββ MODEL GRID ββ */
|
|
265
|
+
.model-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-top:4px}
|
|
266
|
+
.mc{
|
|
267
|
+
background:var(--surface);border:1.5px solid var(--border);
|
|
268
|
+
border-radius:var(--rad);padding:14px;cursor:pointer;
|
|
269
|
+
transition:all .2s;position:relative;overflow:hidden
|
|
270
|
+
}
|
|
271
|
+
.mc::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--acc3),var(--acc));opacity:0;transition:opacity .2s}
|
|
272
|
+
.mc:hover{border-color:var(--border2);transform:translateY(-2px)}
|
|
273
|
+
.mc.sel{border-color:var(--acc);box-shadow:0 0 0 1px var(--acc)}
|
|
274
|
+
.mc.sel::before{opacity:.06}
|
|
275
|
+
.mc-check{position:absolute;top:10px;right:10px;width:18px;height:18px;border-radius:50%;background:var(--acc);color:#fff;display:none;align-items:center;justify-content:center;font-size:10px;font-weight:700}
|
|
276
|
+
.mc.sel .mc-check{display:flex}
|
|
277
|
+
.mc-prov{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;opacity:.7}
|
|
278
|
+
.mc-name{font-size:14px;font-weight:700;margin-bottom:4px;position:relative}
|
|
279
|
+
.mc-desc{font-size:11px;color:var(--txt2);position:relative;line-height:1.5}
|
|
280
|
+
.prov-anthropic .mc-prov{color:var(--orange)}
|
|
281
|
+
.prov-openai .mc-prov{color:var(--green)}
|
|
282
|
+
.prov-google .mc-prov{color:#4da6ff}
|
|
283
|
+
.prov-mistral .mc-prov{color:var(--acc2)}
|
|
284
|
+
.prov-local .mc-prov{color:var(--txt3)}
|
|
285
|
+
.prov-filter-bar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px}
|
|
286
|
+
.pf{padding:6px 14px;border-radius:20px;border:1px solid var(--border);background:transparent;color:var(--txt3);cursor:pointer;font-size:12px;font-weight:700;transition:all .15s;font-family:'Syne',sans-serif}
|
|
287
|
+
.pf:hover{color:var(--txt2);border-color:var(--border2)}
|
|
288
|
+
.pf.on{background:var(--acc);border-color:var(--acc);color:#fff}
|
|
289
|
+
|
|
290
|
+
/* ββ GATEWAY CARDS ββ */
|
|
291
|
+
.gw-card{
|
|
292
|
+
background:var(--surface);border:1.5px solid var(--border);
|
|
293
|
+
border-radius:var(--rad);padding:16px;margin-bottom:10px;
|
|
294
|
+
display:flex;align-items:center;gap:14px;transition:border-color .15s
|
|
295
|
+
}
|
|
296
|
+
.gw-card.active-gw{border-color:var(--acc)}
|
|
297
|
+
.gw-left{flex:1;min-width:0}
|
|
298
|
+
.gw-name{font-size:14px;font-weight:700;margin-bottom:4px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
299
|
+
.gw-url{font-size:11px;color:var(--txt3);font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
300
|
+
.badge{display:inline-flex;align-items:center;font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px}
|
|
301
|
+
.badge-green{background:rgba(34,201,138,.15);color:var(--green)}
|
|
302
|
+
.badge-red{background:rgba(240,90,90,.15);color:var(--red)}
|
|
303
|
+
.badge-acc{background:rgba(124,106,247,.15);color:var(--acc2)}
|
|
304
|
+
.badge-gray{background:var(--surface3);color:var(--txt3)}
|
|
305
|
+
.gw-acts{display:flex;gap:6px;flex-shrink:0}
|
|
306
|
+
|
|
307
|
+
/* ββ TOAST ββ */
|
|
308
|
+
#toast{
|
|
309
|
+
position:fixed;bottom:24px;right:24px;
|
|
310
|
+
background:var(--surface2);border:1px solid var(--border2);
|
|
311
|
+
color:var(--txt);padding:12px 20px;border-radius:var(--rad-sm);
|
|
312
|
+
font-size:13px;z-index:999;opacity:0;
|
|
313
|
+
transform:translateY(10px);transition:all .25s;pointer-events:none;
|
|
314
|
+
box-shadow:0 8px 32px rgba(0,0,0,.4);font-weight:600
|
|
315
|
+
}
|
|
316
|
+
#toast.show{opacity:1;transform:none}
|
|
317
|
+
|
|
318
|
+
/* ββ MODAL ββ */
|
|
319
|
+
#modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:200;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
|
|
320
|
+
#modal-bg.open{display:flex}
|
|
321
|
+
#modal{background:var(--surface);border:1px solid var(--border2);border-radius:var(--rad);padding:28px;width:480px;max-width:95vw;box-shadow:0 24px 80px rgba(0,0,0,.5)}
|
|
322
|
+
#modal h2{font-size:18px;font-weight:800;margin-bottom:20px;letter-spacing:-.3px}
|
|
323
|
+
.modal-acts{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}
|
|
324
|
+
|
|
325
|
+
/* ββ KEY STATUS ββ */
|
|
326
|
+
.key-row{display:flex;align-items:center;gap:10px}
|
|
327
|
+
.key-row .field{flex:1;margin:0}
|
|
328
|
+
.key-status{width:8px;height:8px;border-radius:50%;background:var(--border2);flex-shrink:0}
|
|
329
|
+
.key-status.ok{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
330
|
+
|
|
331
|
+
/* ββ RESPONSIVE ββ */
|
|
332
|
+
@media(max-width:700px){
|
|
333
|
+
#sidebar{position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);box-shadow:4px 0 24px rgba(0,0,0,.5)}
|
|
334
|
+
#sidebar.open{transform:none}
|
|
335
|
+
#menu-btn{display:flex}
|
|
336
|
+
.model-grid{grid-template-columns:1fr 1fr}
|
|
337
|
+
}
|
|
338
|
+
</style>
|
|
339
339
|
</head>
|
|
340
340
|
<body>
|
|
341
341
|
|
|
342
|
-
<!-- TOAST CONTAINER -->
|
|
343
|
-
<div id="toast-container"></div>
|
|
344
|
-
|
|
345
342
|
<!-- SIDEBAR -->
|
|
346
343
|
<div id="sidebar">
|
|
347
|
-
<div id="
|
|
348
|
-
<
|
|
349
|
-
<
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<!-- TAB NAV -->
|
|
353
|
-
<div class="tab-nav">
|
|
354
|
-
<button class="tab-btn active" onclick="switchTab('chat')" id="tab-chat">
|
|
355
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
356
|
-
<span class="tab-label">Chat</span>
|
|
357
|
-
</button>
|
|
358
|
-
<button class="tab-btn" onclick="switchTab('gateways')" id="tab-gateways">
|
|
359
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 6v6m11-7h-6m-6 0H1m16.66-5.66l-4.24 4.24m-2.84 0L6.34 6.34m11.32 11.32l-4.24-4.24m-2.84 0L6.34 17.66"/></svg>
|
|
360
|
-
<span class="tab-label">Gateways</span>
|
|
361
|
-
</button>
|
|
362
|
-
<button class="tab-btn" onclick="switchTab('models')" id="tab-models">
|
|
363
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
364
|
-
<span class="tab-label">Modelos</span>
|
|
365
|
-
</button>
|
|
366
|
-
<button class="tab-btn" onclick="switchTab('settings')" id="tab-settings">
|
|
367
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
368
|
-
<span class="tab-label">Config</span>
|
|
369
|
-
</button>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
<!-- CHAT TAB PANEL -->
|
|
373
|
-
<div class="tab-panel active" id="panel-chat">
|
|
374
|
-
<div style="padding: 8px 8px 4px;">
|
|
375
|
-
<button id="new-chat-btn" onclick="newChat()">
|
|
376
|
-
<span>βοΈ</span> Novo Chat
|
|
377
|
-
</button>
|
|
344
|
+
<div id="sb-logo">
|
|
345
|
+
<div class="logo-icon">π¦</div>
|
|
346
|
+
<div>
|
|
347
|
+
<h1>PS Claw</h1>
|
|
348
|
+
<span>AI AGENT v1.1</span>
|
|
378
349
|
</div>
|
|
379
|
-
<div id="chat-list"></div>
|
|
380
350
|
</div>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
<div class="gw-section">
|
|
391
|
-
<button class="gw-add-btn" onclick="openModal('addGateway')">
|
|
392
|
-
<span>+</span> Adicionar Gateway
|
|
393
|
-
</button>
|
|
394
|
-
</div>
|
|
395
|
-
</div>
|
|
396
|
-
|
|
397
|
-
<!-- MODELS TAB PANEL -->
|
|
398
|
-
<div class="tab-panel" id="panel-models">
|
|
399
|
-
<div class="model-section" id="model-list"></div>
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
<!-- SETTINGS TAB PANEL -->
|
|
403
|
-
<div class="tab-panel" id="panel-settings">
|
|
404
|
-
<div class="settings-section" id="settings-panel"></div>
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
<!-- SIDEBAR FOOTER -->
|
|
408
|
-
<div id="sidebar-footer">
|
|
409
|
-
<div id="status-badge">
|
|
410
|
-
<div id="status-dot"></div>
|
|
411
|
-
<span id="status-text">Verificando gateway...</span>
|
|
351
|
+
<button id="new-btn" onclick="newChat()">
|
|
352
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
353
|
+
Novo Chat
|
|
354
|
+
</button>
|
|
355
|
+
<div id="chat-list"></div>
|
|
356
|
+
<div id="sb-footer">
|
|
357
|
+
<div id="st-badge">
|
|
358
|
+
<div id="st-dot" class="warn"></div>
|
|
359
|
+
<span id="st-txt">Configure uma API key</span>
|
|
412
360
|
</div>
|
|
413
361
|
</div>
|
|
414
362
|
</div>
|
|
@@ -416,764 +364,679 @@
|
|
|
416
364
|
<!-- MAIN -->
|
|
417
365
|
<div id="main">
|
|
418
366
|
<div id="topbar">
|
|
419
|
-
<div id="
|
|
420
|
-
<button id="menu-btn" onclick="
|
|
421
|
-
<span id="
|
|
422
|
-
</div>
|
|
423
|
-
<div id="model-indicator">
|
|
424
|
-
<div class="dot"></div>
|
|
425
|
-
<span id="current-model-name">Modelo padrao</span>
|
|
367
|
+
<div id="tb-left">
|
|
368
|
+
<button id="menu-btn" onclick="toggleSb()">β°</button>
|
|
369
|
+
<span id="tb-title">Chat</span>
|
|
426
370
|
</div>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
<div style="font-size:56px;">π¦</div>
|
|
432
|
-
<h2>PS Claw</h2>
|
|
433
|
-
<p>Agente de IA autonomo leve e poderoso. Configure sua chave de API e comece a conversar.</p>
|
|
434
|
-
<div class="welcome-chips">
|
|
435
|
-
<div class="chip" onclick="sendQuick('Ola! O que voce consegue fazer?')">O que voce consegue fazer?</div>
|
|
436
|
-
<div class="chip" onclick="sendQuick('Me ajude a escrever um script Python')">Escrever codigo</div>
|
|
437
|
-
<div class="chip" onclick="sendQuick('Pesquise na web sobre IA em 2025')">Pesquisar na web</div>
|
|
438
|
-
<div class="chip" onclick="sendQuick('Explique como funciona o PS Claw')">Sobre o PS Claw</div>
|
|
371
|
+
<div id="tb-right">
|
|
372
|
+
<div id="model-pill" onclick="goTab('models')">
|
|
373
|
+
<span class="dot"></span>
|
|
374
|
+
<span id="model-name-badge">Selecionar modelo</span>
|
|
439
375
|
</div>
|
|
440
376
|
</div>
|
|
441
377
|
</div>
|
|
442
378
|
|
|
443
|
-
<div id="
|
|
444
|
-
<div
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
450
|
-
</svg>
|
|
451
|
-
</button>
|
|
452
|
-
</div>
|
|
453
|
-
<div id="input-hint">Enter para enviar Β· Shift+Enter para nova linha</div>
|
|
379
|
+
<div id="tabs">
|
|
380
|
+
<div class="tab active" data-t="chat" onclick="goTab('chat')">π¬ Chat</div>
|
|
381
|
+
<div class="tab" data-t="keys" onclick="goTab('keys')">π API Keys</div>
|
|
382
|
+
<div class="tab" data-t="models" onclick="goTab('models')">π€ Modelos</div>
|
|
383
|
+
<div class="tab" data-t="gateways" onclick="goTab('gateways')">π Gateways</div>
|
|
384
|
+
<div class="tab" data-t="settings" onclick="goTab('settings')">βοΈ Config</div>
|
|
454
385
|
</div>
|
|
455
|
-
</div>
|
|
456
386
|
|
|
457
|
-
<!--
|
|
458
|
-
<div
|
|
459
|
-
|
|
460
|
-
|
|
387
|
+
<!-- CHAT -->
|
|
388
|
+
<div class="page active" id="chat-page">
|
|
389
|
+
<div id="messages">
|
|
390
|
+
<div id="welcome">
|
|
391
|
+
<div class="w-icon">π¦</div>
|
|
392
|
+
<h2>Bem-vindo ao PS Claw</h2>
|
|
393
|
+
<p>Configure uma chave de API na aba <strong>π API Keys</strong> e selecione um modelo em <strong>π€ Modelos</strong> para comeΓ§ar.</p>
|
|
394
|
+
<div class="chips">
|
|
395
|
+
<div class="chip" onclick="sendQ('O que vocΓͺ consegue fazer?')">O que vocΓͺ faz?</div>
|
|
396
|
+
<div class="chip" onclick="sendQ('Me ajude a escrever um script Python para automaΓ§Γ£o')">Escrever cΓ³digo</div>
|
|
397
|
+
<div class="chip" onclick="sendQ('Quais sΓ£o as principais tendΓͺncias de IA em 2025?')">TendΓͺncias de IA</div>
|
|
398
|
+
<div class="chip" onclick="sendQ('Explique o que Γ© o PS Claw e como funciona')">Sobre o PS Claw</div>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div id="input-wrap">
|
|
403
|
+
<div id="input-box">
|
|
404
|
+
<textarea id="msg-in" placeholder="Mensagem..." rows="1"
|
|
405
|
+
onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea>
|
|
406
|
+
<button id="send-btn" onclick="sendMsg()">
|
|
407
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
408
|
+
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
409
|
+
</svg>
|
|
410
|
+
</button>
|
|
411
|
+
</div>
|
|
412
|
+
<div id="input-hint">Enter para enviar Β· Shift+Enter para nova linha</div>
|
|
413
|
+
</div>
|
|
461
414
|
</div>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<script>
|
|
465
|
-
// === STATE ===
|
|
466
|
-
let sessions = JSON.parse(localStorage.getItem('ps-claw-sessions') || '[]');
|
|
467
|
-
let currentSession = null;
|
|
468
|
-
let isTyping = false;
|
|
469
|
-
let activeTab = 'chat';
|
|
470
415
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
cfg.temperature = cfg.temperature ?? 0.7;
|
|
479
|
-
cfg.maxTokens = cfg.maxTokens ?? 4096;
|
|
480
|
-
cfg.systemPrompt = cfg.systemPrompt || '';
|
|
481
|
-
cfg.streamResponses = cfg.streamResponses ?? true;
|
|
482
|
-
cfg.darkMode = cfg.darkMode ?? true;
|
|
483
|
-
|
|
484
|
-
const PROVIDERS = [
|
|
485
|
-
{
|
|
486
|
-
id: 'openai', name: 'OpenAI', icon: 'π’',
|
|
487
|
-
models: [
|
|
488
|
-
{ id: 'gpt-4o', name: 'GPT-4o', tag: 'Premium' },
|
|
489
|
-
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', tag: 'Rapido' },
|
|
490
|
-
{ id: 'gpt-4-turbo', name: 'GPT-4 Turbo', tag: 'Avancado' },
|
|
491
|
-
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', tag: 'Economico' },
|
|
492
|
-
{ id: 'o1-preview', name: 'o1 Preview', tag: 'Raciocinio' },
|
|
493
|
-
{ id: 'o1-mini', name: 'o1 Mini', tag: 'Raciocinio' },
|
|
494
|
-
{ id: 'gpt-4-vision-preview', name: 'GPT-4 Vision', tag: 'Visao' },
|
|
495
|
-
]
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
id: 'anthropic', name: 'Anthropic', icon: 'π ',
|
|
499
|
-
models: [
|
|
500
|
-
{ id: 'claude-opus-4-5', name: 'Claude Opus 4.5', tag: 'Premium' },
|
|
501
|
-
{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5', tag: 'Balanceado' },
|
|
502
|
-
{ id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', tag: 'Rapido' },
|
|
503
|
-
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', tag: 'Economico' },
|
|
504
|
-
]
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
id: 'google', name: 'Google', icon: 'π΅',
|
|
508
|
-
models: [
|
|
509
|
-
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', tag: 'Premium' },
|
|
510
|
-
{ id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', tag: 'Rapido' },
|
|
511
|
-
{ id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', tag: 'Avancado' },
|
|
512
|
-
{ id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', tag: 'Economico' },
|
|
513
|
-
]
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
id: 'deepseek', name: 'DeepSeek', icon: 'π£',
|
|
517
|
-
models: [
|
|
518
|
-
{ id: 'deepseek-chat', name: 'DeepSeek Chat', tag: 'Geral' },
|
|
519
|
-
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', tag: 'Raciocinio' },
|
|
520
|
-
]
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
id: 'openrouter', name: 'OpenRouter', icon: 'π',
|
|
524
|
-
models: [
|
|
525
|
-
{ id: 'openrouter-auto', name: 'Auto (melhor modelo)', tag: 'Auto' },
|
|
526
|
-
{ id: 'meta-llama/llama-3.1-405b-instruct', name: 'Llama 3.1 405B', tag: 'Open Source' },
|
|
527
|
-
{ id: 'mistralai/mixtral-8x7b-instruct', name: 'Mixtral 8x7B', tag: 'Open Source' },
|
|
528
|
-
]
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
id: 'local', name: 'Local / LM Studio', icon: 'π»',
|
|
532
|
-
models: [
|
|
533
|
-
{ id: 'local-default', name: 'Modelo Local Padrao', tag: 'Local' },
|
|
534
|
-
]
|
|
535
|
-
}
|
|
536
|
-
];
|
|
537
|
-
|
|
538
|
-
// === INIT ===
|
|
539
|
-
renderChatList();
|
|
540
|
-
renderGatewayList();
|
|
541
|
-
renderModelList();
|
|
542
|
-
renderSettingsPanel();
|
|
543
|
-
checkAllGateways();
|
|
544
|
-
setInterval(checkAllGateways, 15000);
|
|
545
|
-
updateModelIndicator();
|
|
546
|
-
|
|
547
|
-
// === TAB NAVIGATION ===
|
|
548
|
-
function switchTab(tab) {
|
|
549
|
-
activeTab = tab;
|
|
550
|
-
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
551
|
-
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
|
552
|
-
document.getElementById('tab-' + tab).classList.add('active');
|
|
553
|
-
document.getElementById('panel-' + tab).classList.add('active');
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// === GATEWAY ===
|
|
557
|
-
function getActiveGateway() {
|
|
558
|
-
return cfg.gateways.find(g => g.id === cfg.selectedGateway) || cfg.gateways[0];
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
async function checkAllGateways() {
|
|
562
|
-
for (const gw of cfg.gateways) {
|
|
563
|
-
try {
|
|
564
|
-
const r = await fetch(gw.url + '/health', {
|
|
565
|
-
headers: gw.token ? { Authorization: 'Bearer ' + gw.token } : {},
|
|
566
|
-
signal: AbortSignal.timeout(3000)
|
|
567
|
-
});
|
|
568
|
-
gw.connected = r.ok;
|
|
569
|
-
} catch {
|
|
570
|
-
gw.connected = false;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
updateGatewayStatus();
|
|
574
|
-
renderGatewayList();
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function updateGatewayStatus() {
|
|
578
|
-
const dot = document.getElementById('status-dot');
|
|
579
|
-
const text = document.getElementById('status-text');
|
|
580
|
-
const activeGw = getActiveGateway();
|
|
581
|
-
if (activeGw?.connected) {
|
|
582
|
-
dot.className = 'online'; text.textContent = activeGw.name + ' - Online';
|
|
583
|
-
} else {
|
|
584
|
-
dot.className = 'offline'; text.textContent = activeGw?.name + ' - Offline';
|
|
585
|
-
}
|
|
586
|
-
}
|
|
416
|
+
<!-- API KEYS -->
|
|
417
|
+
<div class="page" id="keys-page">
|
|
418
|
+
<div class="page-inner">
|
|
419
|
+
<div class="sp-header">
|
|
420
|
+
<div class="sp-title">π API Keys</div>
|
|
421
|
+
<div class="sp-sub">Configure suas chaves de API. SΓ£o salvas localmente no navegador e nunca enviadas a terceiros.</div>
|
|
422
|
+
</div>
|
|
587
423
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
424
|
+
<div class="card">
|
|
425
|
+
<h3>π Anthropic β Claude</h3>
|
|
426
|
+
<div class="field">
|
|
427
|
+
<label>API Key</label>
|
|
428
|
+
<div class="key-row">
|
|
429
|
+
<div class="field">
|
|
430
|
+
<input type="password" id="k-anthropic" placeholder="sk-ant-api03-..." oninput="saveKeys()"/>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="key-status" id="ks-anthropic"></div>
|
|
597
433
|
</div>
|
|
598
|
-
<span style="font-size:11px;color:var(--text-muted);">${gw.type}</span>
|
|
599
434
|
</div>
|
|
600
|
-
<div
|
|
601
|
-
|
|
602
|
-
<button class="gw-btn primary" onclick="selectGateway('${gw.id}')">Conectar</button>
|
|
603
|
-
<button class="gw-btn" onclick="openModal('editGateway','${gw.id}')">Editar</button>
|
|
604
|
-
${gw.id !== 'default' ? `<button class="gw-btn danger" onclick="removeGateway('${gw.id}')">Remover</button>` : ''}
|
|
435
|
+
<div style="font-size:12px;color:var(--txt3);margin-top:8px">
|
|
436
|
+
Obtenha em: <a href="https://console.anthropic.com/api-keys" target="_blank" style="color:var(--acc2)">console.anthropic.com</a> Β· GrΓ‘tis $5 de crΓ©dito
|
|
605
437
|
</div>
|
|
606
438
|
</div>
|
|
607
|
-
</div>
|
|
608
|
-
`).join('');
|
|
609
|
-
}
|
|
610
439
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (id === 'default') return;
|
|
621
|
-
cfg.gateways = cfg.gateways.filter(g => g.id !== id);
|
|
622
|
-
if (cfg.selectedGateway === id) cfg.selectedGateway = 'default';
|
|
623
|
-
saveCfg();
|
|
624
|
-
renderGatewayList();
|
|
625
|
-
checkAllGateways();
|
|
626
|
-
showToast('Gateway removido', 'info');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// === MODELS ===
|
|
630
|
-
function renderModelList() {
|
|
631
|
-
const container = document.getElementById('model-list');
|
|
632
|
-
container.innerHTML = PROVIDERS.map(p => `
|
|
633
|
-
<div class="provider-group">
|
|
634
|
-
<div class="provider-header" onclick="toggleProvider('${p.id}')">
|
|
635
|
-
<span class="provider-icon">${p.icon}</span>
|
|
636
|
-
<span class="provider-name">${p.name}</span>
|
|
637
|
-
<span class="provider-count">${p.models.length}</span>
|
|
638
|
-
<span class="provider-toggle" id="toggle-${p.id}">βΌ</span>
|
|
639
|
-
</div>
|
|
640
|
-
<div class="model-list" id="models-${p.id}">
|
|
641
|
-
${p.models.map(m => `
|
|
642
|
-
<div class="model-item ${cfg.selectedModel === m.id ? 'selected' : ''}" onclick="selectModel('${m.id}', '${m.name}')">
|
|
643
|
-
<div class="model-radio"></div>
|
|
644
|
-
<span class="model-name">${m.name}</span>
|
|
645
|
-
<span class="model-tag">${m.tag}</span>
|
|
440
|
+
<div class="card">
|
|
441
|
+
<h3>π’ OpenAI β GPT-4</h3>
|
|
442
|
+
<div class="field">
|
|
443
|
+
<label>API Key</label>
|
|
444
|
+
<div class="key-row">
|
|
445
|
+
<div class="field">
|
|
446
|
+
<input type="password" id="k-openai" placeholder="sk-proj-..." oninput="saveKeys()"/>
|
|
447
|
+
</div>
|
|
448
|
+
<div class="key-status" id="ks-openai"></div>
|
|
646
449
|
</div>
|
|
647
|
-
|
|
450
|
+
</div>
|
|
451
|
+
<div style="font-size:12px;color:var(--txt3);margin-top:8px">
|
|
452
|
+
Obtenha em: <a href="https://platform.openai.com/api-keys" target="_blank" style="color:var(--acc2)">platform.openai.com</a> Β· GrΓ‘tis $5 de crΓ©dito
|
|
453
|
+
</div>
|
|
648
454
|
</div>
|
|
649
|
-
</div>
|
|
650
|
-
`).join('');
|
|
651
|
-
}
|
|
652
455
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
456
|
+
<div class="card">
|
|
457
|
+
<h3>π΅ Google β Gemini</h3>
|
|
458
|
+
<div class="field">
|
|
459
|
+
<label>API Key</label>
|
|
460
|
+
<div class="key-row">
|
|
461
|
+
<div class="field">
|
|
462
|
+
<input type="password" id="k-google" placeholder="AIzaSy..." oninput="saveKeys()"/>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="key-status" id="ks-google"></div>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div style="font-size:12px;color:var(--txt3);margin-top:8px">
|
|
468
|
+
Obtenha em: <a href="https://aistudio.google.com/apikey" target="_blank" style="color:var(--acc2)">aistudio.google.com</a> Β· <strong style="color:var(--green)">GrΓ‘tis para sempre</strong>
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
667
471
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
472
|
+
<div class="card">
|
|
473
|
+
<h3>π£ Mistral AI</h3>
|
|
474
|
+
<div class="field">
|
|
475
|
+
<label>API Key</label>
|
|
476
|
+
<div class="key-row">
|
|
477
|
+
<div class="field">
|
|
478
|
+
<input type="password" id="k-mistral" placeholder="..." oninput="saveKeys()"/>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="key-status" id="ks-mistral"></div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
<div style="font-size:12px;color:var(--txt3);margin-top:8px">
|
|
484
|
+
Obtenha em: <a href="https://console.mistral.ai/api-keys" target="_blank" style="color:var(--acc2)">console.mistral.ai</a>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
681
487
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
panel.innerHTML = `
|
|
686
|
-
<div class="settings-group">
|
|
687
|
-
<div class="settings-group-title">Perfil</div>
|
|
688
|
-
<div class="settings-field">
|
|
689
|
-
<label>Nome de exibicao</label>
|
|
690
|
-
<input type="text" id="s-name" value="${escAttr(cfg.displayName)}" placeholder="Voce" onchange="updateSetting('displayName', this.value)" />
|
|
488
|
+
<div style="display:flex;gap:10px;margin-top:8px">
|
|
489
|
+
<button class="btn btn-primary" onclick="testAllKeys()">π Testar todas as chaves</button>
|
|
490
|
+
<button class="btn btn-ghost" onclick="goTab('models')">β Selecionar modelo</button>
|
|
691
491
|
</div>
|
|
692
492
|
</div>
|
|
493
|
+
</div>
|
|
693
494
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
<
|
|
699
|
-
<div class="
|
|
700
|
-
</div>
|
|
701
|
-
<div class="settings-field">
|
|
702
|
-
<label>Max Tokens</label>
|
|
703
|
-
<input type="number" id="s-maxtokens" value="${cfg.maxTokens}" min="100" max="128000" step="100" onchange="updateSetting('maxTokens', parseInt(this.value))" />
|
|
704
|
-
<div class="hint">Limite maximo de tokens na resposta.</div>
|
|
495
|
+
<!-- MODELS -->
|
|
496
|
+
<div class="page" id="models-page">
|
|
497
|
+
<div class="page-inner">
|
|
498
|
+
<div class="sp-header">
|
|
499
|
+
<div class="sp-title">π€ Modelos</div>
|
|
500
|
+
<div class="sp-sub">Selecione o modelo a ser usado no chat. Certifique-se de ter a API Key correspondente configurada.</div>
|
|
705
501
|
</div>
|
|
706
|
-
<div class="
|
|
707
|
-
<
|
|
708
|
-
<
|
|
709
|
-
<
|
|
502
|
+
<div class="prov-filter-bar">
|
|
503
|
+
<button class="pf on" data-p="all" onclick="filterM('all',this)">Todos</button>
|
|
504
|
+
<button class="pf" data-p="anthropic" onclick="filterM('anthropic',this)">π Anthropic</button>
|
|
505
|
+
<button class="pf" data-p="openai" onclick="filterM('openai',this)">π’ OpenAI</button>
|
|
506
|
+
<button class="pf" data-p="google" onclick="filterM('google',this)">π΅ Google</button>
|
|
507
|
+
<button class="pf" data-p="mistral" onclick="filterM('mistral',this)">π£ Mistral</button>
|
|
508
|
+
<button class="pf" data-p="local" onclick="filterM('local',this)">β« Local</button>
|
|
710
509
|
</div>
|
|
510
|
+
<div id="model-grid" class="model-grid"></div>
|
|
711
511
|
</div>
|
|
512
|
+
</div>
|
|
712
513
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
<div class="
|
|
514
|
+
<!-- GATEWAYS -->
|
|
515
|
+
<div class="page" id="gateways-page">
|
|
516
|
+
<div class="page-inner">
|
|
517
|
+
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;flex-wrap:wrap;gap:12px">
|
|
518
|
+
<div class="sp-header" style="margin:0">
|
|
519
|
+
<div class="sp-title">π Gateways</div>
|
|
520
|
+
<div class="sp-sub">Opcional. Use se tiver um servidor PS Claw/OpenClaw rodando.</div>
|
|
521
|
+
</div>
|
|
522
|
+
<button class="btn btn-primary btn-sm" onclick="openAddGw()">+ Adicionar</button>
|
|
718
523
|
</div>
|
|
719
|
-
<div class="
|
|
720
|
-
<
|
|
721
|
-
<
|
|
524
|
+
<div class="card" style="margin-bottom:16px">
|
|
525
|
+
<h3>π‘ O que Γ© um Gateway?</h3>
|
|
526
|
+
<p style="font-size:13px;color:var(--txt2);line-height:1.75">
|
|
527
|
+
Gateways sΓ£o <strong style="color:var(--txt)">opcionais</strong>. Sem eles, o PS Claw chama as APIs diretamente usando suas API Keys.<br><br>
|
|
528
|
+
Se vocΓͺ tem um servidor PS Claw/OpenClaw rodando (ex: <code style="background:var(--surface3);padding:1px 6px;border-radius:4px;font-family:'JetBrains Mono',monospace">localhost:18789</code>),
|
|
529
|
+
adicione aqui para rotear as mensagens atravΓ©s dele.
|
|
530
|
+
</p>
|
|
722
531
|
</div>
|
|
532
|
+
<div id="gw-list"></div>
|
|
723
533
|
</div>
|
|
534
|
+
</div>
|
|
724
535
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
<
|
|
536
|
+
<!-- SETTINGS -->
|
|
537
|
+
<div class="page" id="settings-page">
|
|
538
|
+
<div class="page-inner">
|
|
539
|
+
<div class="sp-header">
|
|
540
|
+
<div class="sp-title">βοΈ ConfiguraΓ§Γ΅es</div>
|
|
730
541
|
</div>
|
|
731
|
-
<div class="
|
|
732
|
-
<
|
|
733
|
-
<
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
542
|
+
<div class="card">
|
|
543
|
+
<h3>π€ Perfil</h3>
|
|
544
|
+
<div class="row">
|
|
545
|
+
<div class="field">
|
|
546
|
+
<label>Seu nome</label>
|
|
547
|
+
<input type="text" id="cfg-name" placeholder="VocΓͺ" oninput="saveCfg()"/>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="field">
|
|
550
|
+
<label>Nome do agente</label>
|
|
551
|
+
<input type="text" id="cfg-agent" placeholder="PS Claw" oninput="saveCfg()"/>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
738
554
|
</div>
|
|
739
|
-
<div class="
|
|
740
|
-
<
|
|
741
|
-
<
|
|
555
|
+
<div class="card">
|
|
556
|
+
<h3>π¬ Comportamento</h3>
|
|
557
|
+
<div class="field">
|
|
558
|
+
<label>System prompt</label>
|
|
559
|
+
<textarea id="cfg-sys" placeholder="VocΓͺ Γ© um assistente de IA ΓΊtil e direto." oninput="saveCfg()"></textarea>
|
|
560
|
+
</div>
|
|
561
|
+
<div class="row">
|
|
562
|
+
<div class="field">
|
|
563
|
+
<label>Temperatura (0β2)</label>
|
|
564
|
+
<input type="number" id="cfg-temp" min="0" max="2" step="0.1" placeholder="0.7" oninput="saveCfg()"/>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="field">
|
|
567
|
+
<label>Max tokens</label>
|
|
568
|
+
<input type="number" id="cfg-tok" min="256" max="128000" step="256" placeholder="4096" oninput="saveCfg()"/>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
742
571
|
</div>
|
|
743
|
-
<div class="
|
|
744
|
-
<
|
|
745
|
-
<
|
|
572
|
+
<div class="card">
|
|
573
|
+
<h3>ποΈ Dados</h3>
|
|
574
|
+
<p style="font-size:13px;color:var(--txt2);margin-bottom:14px">Todos os dados sΓ£o salvos localmente no seu navegador.</p>
|
|
575
|
+
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
|
576
|
+
<button class="btn btn-danger btn-sm" onclick="clearChats()">Apagar histΓ³rico</button>
|
|
577
|
+
<button class="btn btn-danger btn-sm" onclick="clearAll()">Resetar tudo</button>
|
|
578
|
+
</div>
|
|
746
579
|
</div>
|
|
747
580
|
</div>
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
581
|
+
</div>
|
|
582
|
+
</div><!-- /main -->
|
|
583
|
+
|
|
584
|
+
<!-- MODAL ADD GATEWAY -->
|
|
585
|
+
<div id="modal-bg" onclick="closeMod(event)">
|
|
586
|
+
<div id="modal">
|
|
587
|
+
<h2>π Adicionar Gateway</h2>
|
|
588
|
+
<div class="field"><label>Nome</label><input type="text" id="gw-n" placeholder="PS Claw Local"/></div>
|
|
589
|
+
<div class="field"><label>URL</label><input type="text" id="gw-u" placeholder="http://localhost:18789"/></div>
|
|
590
|
+
<div class="field"><label>Token (opcional)</label><input type="password" id="gw-t" placeholder="Bearer token"/></div>
|
|
591
|
+
<div class="modal-acts">
|
|
592
|
+
<button class="btn btn-ghost" onclick="closeMod()">Cancelar</button>
|
|
593
|
+
<button class="btn btn-primary" onclick="addGw()">Adicionar</button>
|
|
755
594
|
</div>
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
function updateSetting(key, value) {
|
|
760
|
-
cfg[key] = value;
|
|
761
|
-
saveCfg();
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function toggleSetting(key, el) {
|
|
765
|
-
cfg[key] = !cfg[key];
|
|
766
|
-
el.classList.toggle('on');
|
|
767
|
-
saveCfg();
|
|
768
|
-
}
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
769
597
|
|
|
770
|
-
|
|
771
|
-
if (!cfg.apiKeys) cfg.apiKeys = {};
|
|
772
|
-
cfg.apiKeys[provider] = value;
|
|
773
|
-
saveCfg();
|
|
774
|
-
showToast('Chave ' + provider.toUpperCase() + ' salva localmente', 'success');
|
|
775
|
-
}
|
|
598
|
+
<div id="toast"></div>
|
|
776
599
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
600
|
+
<script>
|
|
601
|
+
// βββ MODELOS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
602
|
+
const MODELS = [
|
|
603
|
+
// Anthropic
|
|
604
|
+
{id:'claude-opus-4-5', name:'Claude Opus 4.5', p:'anthropic', desc:'Mais poderoso Β· raciocΓnio complexo',
|
|
605
|
+
api:'https://api.anthropic.com/v1/messages'},
|
|
606
|
+
{id:'claude-sonnet-4-5', name:'Claude Sonnet 4.5', p:'anthropic', desc:'RΓ‘pido e inteligente Β· uso geral',
|
|
607
|
+
api:'https://api.anthropic.com/v1/messages'},
|
|
608
|
+
{id:'claude-haiku-4-5', name:'Claude Haiku 4.5', p:'anthropic', desc:'Ultra rΓ‘pido Β· econΓ΄mico',
|
|
609
|
+
api:'https://api.anthropic.com/v1/messages'},
|
|
610
|
+
// OpenAI
|
|
611
|
+
{id:'gpt-4o', name:'GPT-4o', p:'openai', desc:'Flagship multimodal da OpenAI',
|
|
612
|
+
api:'https://api.openai.com/v1/chat/completions'},
|
|
613
|
+
{id:'gpt-4o-mini', name:'GPT-4o mini', p:'openai', desc:'RΓ‘pido e barato Β· uso geral',
|
|
614
|
+
api:'https://api.openai.com/v1/chat/completions'},
|
|
615
|
+
{id:'gpt-4-turbo', name:'GPT-4 Turbo', p:'openai', desc:'Alta capacidade Β· longo contexto',
|
|
616
|
+
api:'https://api.openai.com/v1/chat/completions'},
|
|
617
|
+
{id:'o3-mini', name:'o3-mini', p:'openai', desc:'RaciocΓnio avanΓ§ado Β· lΓ³gica',
|
|
618
|
+
api:'https://api.openai.com/v1/chat/completions'},
|
|
619
|
+
// Google
|
|
620
|
+
{id:'gemini-2.5-pro', name:'Gemini 2.5 Pro', p:'google', desc:'Mais poderoso do Google',
|
|
621
|
+
api:'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent'},
|
|
622
|
+
{id:'gemini-2.5-flash', name:'Gemini 2.5 Flash', p:'google', desc:'RΓ‘pido Β· contexto longo Β· gratuito',
|
|
623
|
+
api:'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'},
|
|
624
|
+
{id:'gemini-1.5-flash', name:'Gemini 1.5 Flash', p:'google', desc:'EstΓ‘vel e confiΓ‘vel Β· gratuito',
|
|
625
|
+
api:'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent'},
|
|
626
|
+
// Mistral
|
|
627
|
+
{id:'mistral-large-latest', name:'Mistral Large', p:'mistral', desc:'Poderoso Β· multilingual',
|
|
628
|
+
api:'https://api.mistral.ai/v1/chat/completions'},
|
|
629
|
+
{id:'mistral-small-latest', name:'Mistral Small', p:'mistral', desc:'RΓ‘pido Β· barato Β· europeu',
|
|
630
|
+
api:'https://api.mistral.ai/v1/chat/completions'},
|
|
631
|
+
// Local
|
|
632
|
+
{id:'llama3.2', name:'Llama 3.2 (Ollama)', p:'local', desc:'Roda localmente Β· configure URL do gateway',
|
|
633
|
+
api:''},
|
|
634
|
+
];
|
|
786
635
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
saveCfg();
|
|
796
|
-
renderSettingsPanel();
|
|
797
|
-
renderGatewayList();
|
|
798
|
-
renderModelList();
|
|
799
|
-
updateModelIndicator();
|
|
800
|
-
showToast('Configuracoes importadas com sucesso', 'success');
|
|
801
|
-
} catch {
|
|
802
|
-
showToast('Erro ao importar: arquivo invalido', 'error');
|
|
803
|
-
}
|
|
804
|
-
};
|
|
805
|
-
reader.readAsText(file);
|
|
806
|
-
}
|
|
636
|
+
// βββ ESTADO βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
637
|
+
let S = JSON.parse(localStorage.getItem('psc2') || '{}');
|
|
638
|
+
S.chats = S.chats || [];
|
|
639
|
+
S.model = S.model || null;
|
|
640
|
+
S.gws = S.gws || [];
|
|
641
|
+
S.activeGw = S.activeGw || null;
|
|
642
|
+
S.keys = S.keys || {anthropic:'',openai:'',google:'',mistral:''};
|
|
643
|
+
S.cfg = S.cfg || {name:'VocΓͺ',agent:'PS Claw',sys:'',temp:'0.7',tok:'4096'};
|
|
807
644
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
645
|
+
let curChat = null;
|
|
646
|
+
let isTyping = false;
|
|
647
|
+
let mFilter = 'all';
|
|
648
|
+
|
|
649
|
+
function save(){ localStorage.setItem('psc2', JSON.stringify(S)) }
|
|
650
|
+
|
|
651
|
+
// βββ TABS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
652
|
+
function goTab(t){
|
|
653
|
+
document.querySelectorAll('.tab').forEach(e=>e.classList.toggle('active',e.dataset.t===t));
|
|
654
|
+
document.querySelectorAll('.page').forEach(e=>e.classList.remove('active'));
|
|
655
|
+
document.getElementById(t+'-page').classList.add('active');
|
|
656
|
+
document.getElementById('tb-title').textContent={chat:'Chat',keys:'API Keys',models:'Modelos',gateways:'Gateways',settings:'ConfiguraΓ§Γ΅es'}[t]||t;
|
|
657
|
+
if(t==='models') renderModels();
|
|
658
|
+
if(t==='gateways') renderGws();
|
|
659
|
+
if(t==='keys') loadKeys();
|
|
660
|
+
if(t==='settings') loadCfg();
|
|
815
661
|
}
|
|
816
662
|
|
|
817
|
-
//
|
|
818
|
-
function newChat()
|
|
819
|
-
const id
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
saveSessions();
|
|
823
|
-
selectChat(id);
|
|
824
|
-
switchTab('chat');
|
|
663
|
+
// βββ CHAT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
664
|
+
function newChat(){
|
|
665
|
+
const id='c'+Date.now();
|
|
666
|
+
S.chats.unshift({id,title:'Nova conversa',msgs:[],ts:Date.now()});
|
|
667
|
+
save(); selectChat(id); goTab('chat');
|
|
825
668
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (!currentSession) return;
|
|
830
|
-
renderChatList();
|
|
831
|
-
renderMessages();
|
|
669
|
+
function selectChat(id){
|
|
670
|
+
curChat=S.chats.find(c=>c.id===id);
|
|
671
|
+
renderChatList(); renderMsgs();
|
|
832
672
|
}
|
|
833
|
-
|
|
834
|
-
function deleteChat(id, e) {
|
|
673
|
+
function deleteChat(id,e){
|
|
835
674
|
e.stopPropagation();
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
renderChatList();
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function saveSessions() {
|
|
843
|
-
localStorage.setItem('ps-claw-sessions', JSON.stringify(sessions));
|
|
675
|
+
S.chats=S.chats.filter(c=>c.id!==id);
|
|
676
|
+
if(curChat?.id===id){curChat=null;renderMsgs();}
|
|
677
|
+
save(); renderChatList();
|
|
844
678
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escHtml(s.title)}</span>
|
|
852
|
-
<button class="chat-item-del" onclick="deleteChat('${s.id}', event)" title="Excluir">x</button>
|
|
853
|
-
</div>
|
|
854
|
-
`).join('');
|
|
679
|
+
function renderChatList(){
|
|
680
|
+
document.getElementById('chat-list').innerHTML=S.chats.map(c=>`
|
|
681
|
+
<div class="ci ${c.id===curChat?.id?'active':''}" onclick="selectChat('${c.id}')">
|
|
682
|
+
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">${h(c.title)}</span>
|
|
683
|
+
<button class="ci-del" onclick="deleteChat('${c.id}',event)">β</button>
|
|
684
|
+
</div>`).join('');
|
|
855
685
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
<
|
|
862
|
-
<
|
|
863
|
-
<
|
|
864
|
-
|
|
865
|
-
<div class="chip" onclick="
|
|
866
|
-
<div class="chip" onclick="
|
|
867
|
-
<div class="chip" onclick="
|
|
868
|
-
<div class="chip" onclick="sendQuick('Explique como funciona o PS Claw')">Sobre o PS Claw</div>
|
|
686
|
+
function renderMsgs(){
|
|
687
|
+
const box=document.getElementById('messages');
|
|
688
|
+
if(!curChat||!curChat.msgs.length){
|
|
689
|
+
box.innerHTML=`<div id="welcome">
|
|
690
|
+
<div class="w-icon">π¦</div>
|
|
691
|
+
<h2>${h(S.cfg.agent||'PS Claw')}</h2>
|
|
692
|
+
<p>Configure uma chave de API na aba <strong>π API Keys</strong> e selecione um modelo em <strong>π€ Modelos</strong> para comeΓ§ar.</p>
|
|
693
|
+
<div class="chips">
|
|
694
|
+
<div class="chip" onclick="sendQ('O que vocΓͺ consegue fazer?')">O que vocΓͺ faz?</div>
|
|
695
|
+
<div class="chip" onclick="sendQ('Me ajude com um script Python')">Escrever cΓ³digo</div>
|
|
696
|
+
<div class="chip" onclick="sendQ('TendΓͺncias de IA em 2025')">TendΓͺncias de IA</div>
|
|
697
|
+
<div class="chip" onclick="sendQ('Explique o PS Claw')">Sobre o PS Claw</div>
|
|
869
698
|
</div>
|
|
870
699
|
</div>`;
|
|
871
700
|
return;
|
|
872
701
|
}
|
|
873
|
-
box.innerHTML
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
return `
|
|
880
|
-
<div class="msg-row ${isUser ? 'user' : 'ai'}">
|
|
881
|
-
<div class="avatar ${isUser ? 'user' : 'ai'}">${isUser ? (cfg.displayName[0]||'V').toUpperCase() : 'π¦'}</div>
|
|
882
|
-
<div class="bubble ${isUser ? 'user' : 'ai'}">${formatText(m.content)}</div>
|
|
883
|
-
</div>`;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function addTypingIndicator() {
|
|
887
|
-
const box = document.getElementById('messages');
|
|
888
|
-
const el = document.createElement('div');
|
|
889
|
-
el.id = 'typing-row';
|
|
890
|
-
el.className = 'msg-row ai';
|
|
891
|
-
el.innerHTML = `<div class="avatar ai">π¦</div><div class="bubble ai"><div class="typing-dots"><span>β’</span><span>β’</span><span>β’</span></div></div>`;
|
|
892
|
-
box.appendChild(el);
|
|
893
|
-
box.scrollTop = box.scrollHeight;
|
|
702
|
+
box.innerHTML=curChat.msgs.map(m=>`
|
|
703
|
+
<div class="msg-row ${m.role==='user'?'user':''}">
|
|
704
|
+
<div class="av ${m.role==='user'?'user':'ai'}">${m.role==='user'?(S.cfg.name||'V')[0].toUpperCase():'π¦'}</div>
|
|
705
|
+
<div class="bbl ${m.role==='user'?'user':'ai'}">${fmt(m.content)}</div>
|
|
706
|
+
</div>`).join('');
|
|
707
|
+
box.scrollTop=box.scrollHeight;
|
|
894
708
|
}
|
|
895
|
-
function
|
|
896
|
-
document.getElementById('
|
|
709
|
+
function addTyping(){
|
|
710
|
+
const box=document.getElementById('messages');
|
|
711
|
+
document.getElementById('welcome')?.remove();
|
|
712
|
+
const el=document.createElement('div');
|
|
713
|
+
el.id='typdiv';el.className='msg-row';
|
|
714
|
+
el.innerHTML=`<div class="av ai">π¦</div><div class="bbl ai"><div class="typing"><span></span><span></span><span></span></div></div>`;
|
|
715
|
+
box.appendChild(el);box.scrollTop=box.scrollHeight;
|
|
897
716
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
if
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
717
|
+
function rmTyping(){document.getElementById('typdiv')?.remove()}
|
|
718
|
+
|
|
719
|
+
// βββ ENVIO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
720
|
+
async function sendMsg(){
|
|
721
|
+
const inp=document.getElementById('msg-in');
|
|
722
|
+
const text=inp.value.trim();
|
|
723
|
+
if(!text||isTyping)return;
|
|
724
|
+
if(!curChat)newChat();
|
|
725
|
+
|
|
726
|
+
// Verificar se tem modelo e chave
|
|
727
|
+
const model=MODELS.find(m=>m.id===S.model);
|
|
728
|
+
if(!model){toast('β οΈ Selecione um modelo na aba π€ Modelos');goTab('models');return;}
|
|
729
|
+
const key=S.keys[model.p];
|
|
730
|
+
if(!key&&model.p!=='local'){toast('β οΈ Configure a API Key na aba π API Keys');goTab('keys');return;}
|
|
731
|
+
|
|
732
|
+
inp.value='';inp.style.height='auto';
|
|
733
|
+
document.getElementById('send-btn').disabled=true;
|
|
734
|
+
isTyping=true;
|
|
911
735
|
document.getElementById('welcome')?.remove();
|
|
912
736
|
|
|
913
|
-
|
|
914
|
-
if
|
|
915
|
-
|
|
916
|
-
renderChatList();
|
|
917
|
-
}
|
|
918
|
-
saveSessions();
|
|
919
|
-
renderMessages();
|
|
920
|
-
addTypingIndicator();
|
|
921
|
-
|
|
922
|
-
try {
|
|
923
|
-
const gw = getActiveGateway();
|
|
924
|
-
const body = {
|
|
925
|
-
text,
|
|
926
|
-
...(cfg.selectedModel && { model: cfg.selectedModel }),
|
|
927
|
-
temperature: cfg.temperature,
|
|
928
|
-
max_tokens: cfg.maxTokens,
|
|
929
|
-
...(cfg.systemPrompt && { system: cfg.systemPrompt }),
|
|
930
|
-
history: currentSession.messages.slice(0, -1).map(m => ({ role: m.role, content: m.content }))
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
const resp = await fetch(gw.url + '/chat', {
|
|
934
|
-
method: 'POST',
|
|
935
|
-
headers: {
|
|
936
|
-
'Content-Type': 'application/json',
|
|
937
|
-
...(gw.token && { Authorization: 'Bearer ' + gw.token })
|
|
938
|
-
},
|
|
939
|
-
body: JSON.stringify(body),
|
|
940
|
-
signal: AbortSignal.timeout(120000)
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
removeTypingIndicator();
|
|
944
|
-
|
|
945
|
-
let replyText;
|
|
946
|
-
if (resp.ok) {
|
|
947
|
-
const data = await resp.json();
|
|
948
|
-
replyText = data.reply || data.text || data.content || data.message || JSON.stringify(data, null, 2);
|
|
949
|
-
} else {
|
|
950
|
-
replyText = await tryFallbackChat(text);
|
|
951
|
-
}
|
|
737
|
+
curChat.msgs.push({role:'user',content:text});
|
|
738
|
+
if(curChat.msgs.length===1)curChat.title=text.slice(0,44)+(text.length>44?'β¦':'');
|
|
739
|
+
save();renderChatList();renderMsgs();addTyping();
|
|
952
740
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
741
|
+
let reply;
|
|
742
|
+
try{
|
|
743
|
+
reply = await callApi(model, text, key);
|
|
744
|
+
}catch(e){
|
|
745
|
+
reply=`β **Erro:** ${e.message}`;
|
|
746
|
+
}
|
|
747
|
+
rmTyping();
|
|
748
|
+
curChat.msgs.push({role:'assistant',content:reply});
|
|
749
|
+
save();renderMsgs();
|
|
750
|
+
isTyping=false;
|
|
751
|
+
document.getElementById('send-btn').disabled=false;
|
|
752
|
+
inp.focus();
|
|
753
|
+
}
|
|
956
754
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
755
|
+
// βββ CHAMADAS DE API ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
756
|
+
async function callApi(model, text, key){
|
|
757
|
+
const history = curChat.msgs.slice(0,-1).map(m=>({role:m.role,content:m.content}));
|
|
758
|
+
const messages = [...history, {role:'user',content:text}];
|
|
759
|
+
const sys = S.cfg.sys || 'VocΓͺ Γ© um assistente de IA ΓΊtil e direto.';
|
|
760
|
+
const temp = parseFloat(S.cfg.temp)||0.7;
|
|
761
|
+
const maxTok = parseInt(S.cfg.tok)||4096;
|
|
762
|
+
|
|
763
|
+
// Verificar se tem gateway ativo (prioridade sobre chamada direta)
|
|
764
|
+
const gw = S.gws.find(g=>g.id===S.activeGw);
|
|
765
|
+
if(gw){
|
|
766
|
+
return await callGateway(gw, model, messages, sys, temp, maxTok);
|
|
963
767
|
}
|
|
964
768
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
769
|
+
// Chamada direta por provedor
|
|
770
|
+
if(model.p==='anthropic') return await callAnthropic(model, messages, sys, key, maxTok, temp);
|
|
771
|
+
if(model.p==='openai') return await callOpenAI(model, messages, sys, key, maxTok, temp);
|
|
772
|
+
if(model.p==='google') return await callGoogle(model, messages, sys, key, maxTok, temp);
|
|
773
|
+
if(model.p==='mistral') return await callOpenAI(model, messages, sys, key, maxTok, temp, 'https://api.mistral.ai/v1/chat/completions');
|
|
774
|
+
throw new Error('Provedor desconhecido. Configure um gateway ou selecione outro modelo.');
|
|
968
775
|
}
|
|
969
776
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
}),
|
|
985
|
-
signal: AbortSignal.timeout(10000)
|
|
986
|
-
});
|
|
987
|
-
if (r.ok) {
|
|
988
|
-
const d = await r.json();
|
|
989
|
-
return d.reply || d.text || d.content || d.message || JSON.stringify(d, null, 2);
|
|
990
|
-
}
|
|
991
|
-
} catch {}
|
|
992
|
-
}
|
|
993
|
-
const gwName = gw ? gw.name : 'Desconhecido';
|
|
994
|
-
const gwUrl = gw ? gw.url : 'N/A';
|
|
995
|
-
return `**Gateway offline ou configuracao necessaria**\n\nO PS Claw nao esta respondendo. Verifique:\n\n1. Execute o PS Claw: \`ps-claw gateway run\`\n2. Va na aba **Gateways** e configure a URL\n3. Adicione seu token se necessario\n4. Selecione um modelo na aba **Modelos**\n\nGateway: ${gwName} (${gwUrl})`;
|
|
777
|
+
// Proxy URL para evitar CORS (usa o servidor local)
|
|
778
|
+
function px(url){ return `/proxy?url=${encodeURIComponent(url)}`; }
|
|
779
|
+
|
|
780
|
+
async function callAnthropic(model, messages, sys, key, maxTok, temp){
|
|
781
|
+
// Separar mensagens de sistema
|
|
782
|
+
const msgs = messages.filter(m=>m.role!=='system').map(m=>({role:m.role,content:m.content}));
|
|
783
|
+
const r = await fetch(px('https://api.anthropic.com/v1/messages'),{
|
|
784
|
+
method:'POST',
|
|
785
|
+
headers:{'Content-Type':'application/json','x-api-key':key,'anthropic-version':'2023-06-01'},
|
|
786
|
+
body:JSON.stringify({model:model.id,max_tokens:maxTok,temperature:temp,system:sys,messages:msgs})
|
|
787
|
+
});
|
|
788
|
+
const d=await r.json();
|
|
789
|
+
if(!r.ok)throw new Error(d.error?.message||JSON.stringify(d));
|
|
790
|
+
return d.content?.[0]?.text||JSON.stringify(d);
|
|
996
791
|
}
|
|
997
792
|
|
|
998
|
-
function
|
|
999
|
-
|
|
1000
|
-
|
|
793
|
+
async function callOpenAI(model, messages, sys, key, maxTok, temp, overrideUrl){
|
|
794
|
+
const url = overrideUrl || 'https://api.openai.com/v1/chat/completions';
|
|
795
|
+
const msgs = [{role:'system',content:sys},...messages];
|
|
796
|
+
const r = await fetch(px(url),{
|
|
797
|
+
method:'POST',
|
|
798
|
+
headers:{'Content-Type':'application/json','Authorization':'Bearer '+key},
|
|
799
|
+
body:JSON.stringify({model:model.id,max_tokens:maxTok,temperature:temp,messages:msgs})
|
|
800
|
+
});
|
|
801
|
+
const d=await r.json();
|
|
802
|
+
if(!r.ok)throw new Error(d.error?.message||JSON.stringify(d));
|
|
803
|
+
return d.choices?.[0]?.message?.content||JSON.stringify(d);
|
|
1001
804
|
}
|
|
1002
805
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
const
|
|
806
|
+
async function callGoogle(model, messages, sys, key, maxTok, temp){
|
|
807
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model.id}:generateContent?key=${key}`;
|
|
808
|
+
const contents = messages.map(m=>({
|
|
809
|
+
role: m.role==='assistant'?'model':'user',
|
|
810
|
+
parts:[{text:m.content}]
|
|
811
|
+
}));
|
|
812
|
+
const r = await fetch(px(url),{
|
|
813
|
+
method:'POST',
|
|
814
|
+
headers:{'Content-Type':'application/json'},
|
|
815
|
+
body:JSON.stringify({
|
|
816
|
+
contents,
|
|
817
|
+
systemInstruction:{parts:[{text:sys}]},
|
|
818
|
+
generationConfig:{maxOutputTokens:maxTok,temperature:temp}
|
|
819
|
+
})
|
|
820
|
+
});
|
|
821
|
+
const d=await r.json();
|
|
822
|
+
if(!r.ok)throw new Error(d.error?.message||JSON.stringify(d));
|
|
823
|
+
return d.candidates?.[0]?.content?.parts?.[0]?.text||JSON.stringify(d);
|
|
824
|
+
}
|
|
1006
825
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
<div class="field">
|
|
1019
|
-
<label>Tipo</label>
|
|
1020
|
-
<select id="m-gw-type">
|
|
1021
|
-
<option value="ps-claw">PS Claw</option>
|
|
1022
|
-
<option value="openai">OpenAI Compatible</option>
|
|
1023
|
-
<option value="anthropic">Anthropic Compatible</option>
|
|
1024
|
-
<option value="ollama">Ollama</option>
|
|
1025
|
-
<option value="lmstudio">LM Studio</option>
|
|
1026
|
-
<option value="custom">Custom</option>
|
|
1027
|
-
</select>
|
|
1028
|
-
</div>
|
|
1029
|
-
<div class="field">
|
|
1030
|
-
<label>Token / API Key (opcional)</label>
|
|
1031
|
-
<input type="password" id="m-gw-token" placeholder="Seu token de autenticacao" />
|
|
1032
|
-
</div>
|
|
1033
|
-
<div class="modal-actions">
|
|
1034
|
-
<button class="btn btn-ghost" onclick="closeModal()">Cancelar</button>
|
|
1035
|
-
<button class="btn btn-primary" onclick="addGateway()">Adicionar</button>
|
|
1036
|
-
</div>
|
|
1037
|
-
`;
|
|
1038
|
-
} else if (type === 'editGateway') {
|
|
1039
|
-
const gw = cfg.gateways.find(g => g.id === data);
|
|
1040
|
-
if (!gw) return;
|
|
1041
|
-
content.innerHTML = `
|
|
1042
|
-
<h2>βοΈ Editar Gateway</h2>
|
|
1043
|
-
<div class="field">
|
|
1044
|
-
<label>Nome</label>
|
|
1045
|
-
<input type="text" id="m-gw-name" value="${escAttr(gw.name)}" />
|
|
1046
|
-
</div>
|
|
1047
|
-
<div class="field">
|
|
1048
|
-
<label>URL</label>
|
|
1049
|
-
<input type="text" id="m-gw-url" value="${escAttr(gw.url)}" />
|
|
1050
|
-
</div>
|
|
1051
|
-
<div class="field">
|
|
1052
|
-
<label>Tipo</label>
|
|
1053
|
-
<select id="m-gw-type">
|
|
1054
|
-
<option value="ps-claw" ${gw.type==='ps-claw'?'selected':''}>PS Claw</option>
|
|
1055
|
-
<option value="openai" ${gw.type==='openai'?'selected':''}>OpenAI Compatible</option>
|
|
1056
|
-
<option value="anthropic" ${gw.type==='anthropic'?'selected':''}>Anthropic Compatible</option>
|
|
1057
|
-
<option value="ollama" ${gw.type==='ollama'?'selected':''}>Ollama</option>
|
|
1058
|
-
<option value="lmstudio" ${gw.type==='lmstudio'?'selected':''}>LM Studio</option>
|
|
1059
|
-
<option value="custom" ${gw.type==='custom'?'selected':''}>Custom</option>
|
|
1060
|
-
</select>
|
|
1061
|
-
</div>
|
|
1062
|
-
<div class="field">
|
|
1063
|
-
<label>Token / API Key</label>
|
|
1064
|
-
<input type="password" id="m-gw-token" value="${escAttr(gw.token)}" />
|
|
1065
|
-
</div>
|
|
1066
|
-
<div class="modal-actions">
|
|
1067
|
-
<button class="btn btn-ghost" onclick="closeModal()">Cancelar</button>
|
|
1068
|
-
<button class="btn btn-primary" onclick="editGateway('${gw.id}')">Salvar</button>
|
|
1069
|
-
</div>
|
|
1070
|
-
`;
|
|
1071
|
-
} else if (type === 'about') {
|
|
1072
|
-
content.innerHTML = `
|
|
1073
|
-
<h2>π¦ Sobre o PS Claw</h2>
|
|
1074
|
-
<p style="margin-bottom:12px;color:var(--text-muted);font-size:14px;line-height:1.7;">
|
|
1075
|
-
PS Claw e um fork leve do OpenClaw β um agente de IA autonomo multi-canal com interface web no estilo ChatGPT.
|
|
1076
|
-
</p>
|
|
1077
|
-
<p style="margin-bottom:12px;color:var(--text-muted);font-size:14px;line-height:1.7;">
|
|
1078
|
-
Use no terminal com <code style="background:var(--input-bg);padding:2px 6px;border-radius:4px;">ps-claw gateway run</code> ou acesse esta interface web para gerenciar gateways, modelos e provedores de API.
|
|
1079
|
-
</p>
|
|
1080
|
-
<div style="background:var(--input-bg);border-radius:8px;padding:12px;font-size:12px;font-family:monospace;color:var(--text-muted);">
|
|
1081
|
-
<div>Comandos uteis:</div>
|
|
1082
|
-
<div style="margin-top:6px;">ps-claw gateway run # Inicia o gateway</div>
|
|
1083
|
-
<div>ps-claw secrets set # Configurar chaves</div>
|
|
1084
|
-
<div>ps-claw doctor # Diagnostico</div>
|
|
1085
|
-
<div>ps-claw configure # Configurar</div>
|
|
1086
|
-
<div>ps-claw models list # Listar modelos</div>
|
|
1087
|
-
</div>
|
|
1088
|
-
<div class="modal-actions">
|
|
1089
|
-
<button class="btn btn-primary" onclick="closeModal()">Fechar</button>
|
|
1090
|
-
</div>
|
|
1091
|
-
`;
|
|
826
|
+
async function callGateway(gw, model, messages, sys, temp, maxTok){
|
|
827
|
+
const endpoints=['/v1/chat/completions','/api/chat','/chat'];
|
|
828
|
+
const headers={'Content-Type':'application/json',...(gw.token&&{Authorization:'Bearer '+gw.token})};
|
|
829
|
+
const body={model:model.id,messages:[{role:'system',content:sys},...messages],temperature:temp,max_tokens:maxTok};
|
|
830
|
+
for(const ep of endpoints){
|
|
831
|
+
try{
|
|
832
|
+
const r=await fetch(px(gw.url+ep),{method:'POST',headers,body:JSON.stringify(body),signal:AbortSignal.timeout(60000)});
|
|
833
|
+
if(!r.ok)continue;
|
|
834
|
+
const d=await r.json();
|
|
835
|
+
return d.choices?.[0]?.message?.content||d.reply||d.text||d.content||JSON.stringify(d);
|
|
836
|
+
}catch{}
|
|
1092
837
|
}
|
|
1093
|
-
|
|
1094
|
-
document.getElementById('modal-overlay').classList.add('open');
|
|
838
|
+
throw new Error('Gateway nΓ£o respondeu. Verifique a URL.');
|
|
1095
839
|
}
|
|
1096
840
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
841
|
+
// βββ API KEY TEST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
842
|
+
async function testAllKeys(){
|
|
843
|
+
toast('π Testando chaves...',3000);
|
|
844
|
+
const tests=[
|
|
845
|
+
{id:'anthropic',fn:async()=>{
|
|
846
|
+
if(!S.keys.anthropic)return false;
|
|
847
|
+
const r=await fetch(px('https://api.anthropic.com/v1/models'),{headers:{'x-api-key':S.keys.anthropic,'anthropic-version':'2023-06-01'}});
|
|
848
|
+
return r.ok;
|
|
849
|
+
}},
|
|
850
|
+
{id:'openai',fn:async()=>{
|
|
851
|
+
if(!S.keys.openai)return false;
|
|
852
|
+
const r=await fetch(px('https://api.openai.com/v1/models'),{headers:{Authorization:'Bearer '+S.keys.openai}});
|
|
853
|
+
return r.ok;
|
|
854
|
+
}},
|
|
855
|
+
{id:'google',fn:async()=>{
|
|
856
|
+
if(!S.keys.google)return false;
|
|
857
|
+
const r=await fetch(px(`https://generativelanguage.googleapis.com/v1beta/models?key=${S.keys.google}`));
|
|
858
|
+
return r.ok;
|
|
859
|
+
}},
|
|
860
|
+
{id:'mistral',fn:async()=>{
|
|
861
|
+
if(!S.keys.mistral)return false;
|
|
862
|
+
const r=await fetch(px('https://api.mistral.ai/v1/models'),{headers:{Authorization:'Bearer '+S.keys.mistral}});
|
|
863
|
+
return r.ok;
|
|
864
|
+
}},
|
|
865
|
+
];
|
|
866
|
+
for(const t of tests){
|
|
867
|
+
try{
|
|
868
|
+
const ok=await t.fn();
|
|
869
|
+
const el=document.getElementById('ks-'+t.id);
|
|
870
|
+
if(el)el.className='key-status '+(ok?'ok':'');
|
|
871
|
+
}catch{}
|
|
872
|
+
}
|
|
873
|
+
updateStatus();
|
|
874
|
+
toast('β
Teste concluΓdo!');
|
|
1100
875
|
}
|
|
1101
876
|
|
|
1102
|
-
function
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
1106
|
-
const
|
|
1107
|
-
if
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
saveCfg();
|
|
1111
|
-
renderGatewayList();
|
|
1112
|
-
closeModal();
|
|
1113
|
-
checkAllGateways();
|
|
1114
|
-
showToast('Gateway adicionado: ' + name, 'success');
|
|
877
|
+
function updateStatus(){
|
|
878
|
+
const dot=document.getElementById('st-dot');
|
|
879
|
+
const txt=document.getElementById('st-txt');
|
|
880
|
+
const model=MODELS.find(m=>m.id===S.model);
|
|
881
|
+
const key=model?S.keys[model.p]:'';
|
|
882
|
+
if(!S.model){dot.className='warn';txt.textContent='Selecione um modelo';return;}
|
|
883
|
+
if(!key&&model?.p!=='local'){dot.className='off';txt.textContent='Sem API Key para '+model.p;return;}
|
|
884
|
+
dot.className='on';txt.textContent=(model?.name||S.model)+' Β· Pronto';
|
|
1115
885
|
}
|
|
1116
886
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
887
|
+
// βββ MODELOS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
888
|
+
function renderModels(){
|
|
889
|
+
const list=mFilter==='all'?MODELS:MODELS.filter(m=>m.p===mFilter);
|
|
890
|
+
document.getElementById('model-grid').innerHTML=list.map(m=>`
|
|
891
|
+
<div class="mc prov-${m.p} ${S.model===m.id?'sel':''}" onclick="selModel('${m.id}')">
|
|
892
|
+
<div class="mc-check">β</div>
|
|
893
|
+
<div class="mc-prov">${{anthropic:'π Anthropic',openai:'π’ OpenAI',google:'π΅ Google',mistral:'π£ Mistral',local:'β« Local'}[m.p]}</div>
|
|
894
|
+
<div class="mc-name">${h(m.name)}</div>
|
|
895
|
+
<div class="mc-desc">${h(m.desc)}</div>
|
|
896
|
+
</div>`).join('');
|
|
897
|
+
}
|
|
898
|
+
function filterM(p,btn){
|
|
899
|
+
mFilter=p;
|
|
900
|
+
document.querySelectorAll('.pf').forEach(b=>b.classList.remove('on'));
|
|
901
|
+
btn.classList.add('on');
|
|
902
|
+
renderModels();
|
|
903
|
+
}
|
|
904
|
+
function selModel(id){
|
|
905
|
+
S.model=id;save();
|
|
906
|
+
renderModels();
|
|
907
|
+
const m=MODELS.find(x=>x.id===id);
|
|
908
|
+
document.getElementById('model-name-badge').textContent=m?.name||id;
|
|
909
|
+
updateStatus();
|
|
910
|
+
toast('β
Modelo: '+m?.name);
|
|
1129
911
|
}
|
|
1130
912
|
|
|
1131
|
-
//
|
|
1132
|
-
function
|
|
1133
|
-
|
|
913
|
+
// βββ KEYS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
914
|
+
function loadKeys(){
|
|
915
|
+
document.getElementById('k-anthropic').value=S.keys.anthropic||'';
|
|
916
|
+
document.getElementById('k-openai').value=S.keys.openai||'';
|
|
917
|
+
document.getElementById('k-google').value=S.keys.google||'';
|
|
918
|
+
document.getElementById('k-mistral').value=S.keys.mistral||'';
|
|
1134
919
|
}
|
|
1135
|
-
function
|
|
1136
|
-
|
|
1137
|
-
|
|
920
|
+
function saveKeys(){
|
|
921
|
+
S.keys.anthropic=document.getElementById('k-anthropic').value.trim();
|
|
922
|
+
S.keys.openai=document.getElementById('k-openai').value.trim();
|
|
923
|
+
S.keys.google=document.getElementById('k-google').value.trim();
|
|
924
|
+
S.keys.mistral=document.getElementById('k-mistral').value.trim();
|
|
925
|
+
save();updateStatus();
|
|
1138
926
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
927
|
+
|
|
928
|
+
// βββ GATEWAYS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
929
|
+
function renderGws(){
|
|
930
|
+
const el=document.getElementById('gw-list');
|
|
931
|
+
if(!S.gws.length){
|
|
932
|
+
el.innerHTML=`<div style="text-align:center;padding:32px;color:var(--txt3)">
|
|
933
|
+
<div style="font-size:32px;margin-bottom:10px">π</div>
|
|
934
|
+
Nenhum gateway. O PS Claw chamarΓ‘ as APIs diretamente com suas API Keys.
|
|
935
|
+
</div>`;return;
|
|
936
|
+
}
|
|
937
|
+
el.innerHTML=S.gws.map(g=>`
|
|
938
|
+
<div class="gw-card ${S.activeGw===g.id?'active-gw':''}">
|
|
939
|
+
<div class="gw-left">
|
|
940
|
+
<div class="gw-name">
|
|
941
|
+
${h(g.name)}
|
|
942
|
+
${S.activeGw===g.id?'<span class="badge badge-acc">Ativo</span>':''}
|
|
943
|
+
<span class="badge badge-gray" id="gst-${g.id}">...</span>
|
|
944
|
+
</div>
|
|
945
|
+
<div class="gw-url">${h(g.url)}</div>
|
|
946
|
+
</div>
|
|
947
|
+
<div class="gw-acts">
|
|
948
|
+
<button class="btn btn-ghost btn-xs" onclick="testGw('${g.id}')">Testar</button>
|
|
949
|
+
${S.activeGw!==g.id?`<button class="btn btn-primary btn-xs" onclick="setGw('${g.id}')">Usar</button>`:''}
|
|
950
|
+
<button class="btn btn-danger btn-xs" onclick="delGw('${g.id}')">β</button>
|
|
951
|
+
</div>
|
|
952
|
+
</div>`).join('');
|
|
953
|
+
S.gws.forEach(g=>testGw(g.id,true));
|
|
1141
954
|
}
|
|
1142
|
-
function
|
|
1143
|
-
|
|
955
|
+
function openAddGw(){
|
|
956
|
+
document.getElementById('gw-n').value='';
|
|
957
|
+
document.getElementById('gw-u').value='http://localhost:18789';
|
|
958
|
+
document.getElementById('gw-t').value='';
|
|
959
|
+
document.getElementById('modal-bg').classList.add('open');
|
|
1144
960
|
}
|
|
1145
|
-
function
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
t = t.replace(/^## (.+)$/gm, '<h2 style="margin:10px 0 6px;font-size:17px;">$1</h2>');
|
|
1155
|
-
t = t.replace(/^# (.+)$/gm, '<h1 style="margin:12px 0 8px;font-size:20px;">$1</h1>');
|
|
1156
|
-
t = t.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
1157
|
-
t = t.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
|
1158
|
-
t = t.split('\n\n').map(p => p.startsWith('<') ? p : `<p>${p.replace(/\n/g, '<br>')}</p>`).join('\n');
|
|
1159
|
-
return t;
|
|
961
|
+
function closeMod(e){if(!e||e.target===document.getElementById('modal-bg'))document.getElementById('modal-bg').classList.remove('open')}
|
|
962
|
+
function addGw(){
|
|
963
|
+
const id='gw'+Date.now();
|
|
964
|
+
const name=document.getElementById('gw-n').value.trim()||'Gateway';
|
|
965
|
+
const url=document.getElementById('gw-u').value.trim()||'http://localhost:18789';
|
|
966
|
+
const token=document.getElementById('gw-t').value.trim();
|
|
967
|
+
S.gws.push({id,name,url,token});
|
|
968
|
+
if(!S.activeGw)S.activeGw=id;
|
|
969
|
+
save();closeMod();renderGws();toast('β
Gateway adicionado!');
|
|
1160
970
|
}
|
|
1161
|
-
function
|
|
1162
|
-
|
|
971
|
+
function delGw(id){S.gws=S.gws.filter(g=>g.id!==id);if(S.activeGw===id)S.activeGw=S.gws[0]?.id||null;save();renderGws();}
|
|
972
|
+
function setGw(id){S.activeGw=id;save();renderGws();toast('β
Gateway ativo!');}
|
|
973
|
+
async function testGw(id,silent){
|
|
974
|
+
const gw=S.gws.find(g=>g.id===id);const el=document.getElementById('gst-'+id);
|
|
975
|
+
if(!el)return;el.textContent='...';el.className='badge badge-gray';
|
|
976
|
+
try{
|
|
977
|
+
const r=await fetch(px(gw.url+'/health'),{headers:gw.token?{Authorization:'Bearer '+gw.token}:{},signal:AbortSignal.timeout(4000)});
|
|
978
|
+
el.textContent=r.ok?'Online':'Erro';
|
|
979
|
+
el.className='badge '+(r.ok?'badge-green':'badge-red');
|
|
980
|
+
if(!silent&&r.ok)toast('β
Gateway online!');
|
|
981
|
+
}catch{el.textContent='Offline';el.className='badge badge-red';if(!silent)toast('β InacessΓvel');}
|
|
1163
982
|
}
|
|
1164
983
|
|
|
1165
|
-
|
|
1166
|
-
|
|
984
|
+
// βββ CFG ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
985
|
+
function loadCfg(){
|
|
986
|
+
document.getElementById('cfg-name').value=S.cfg.name||'VocΓͺ';
|
|
987
|
+
document.getElementById('cfg-agent').value=S.cfg.agent||'PS Claw';
|
|
988
|
+
document.getElementById('cfg-sys').value=S.cfg.sys||'';
|
|
989
|
+
document.getElementById('cfg-temp').value=S.cfg.temp||'0.7';
|
|
990
|
+
document.getElementById('cfg-tok').value=S.cfg.tok||'4096';
|
|
1167
991
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
992
|
+
function saveCfg(){
|
|
993
|
+
S.cfg.name=document.getElementById('cfg-name').value;
|
|
994
|
+
S.cfg.agent=document.getElementById('cfg-agent').value;
|
|
995
|
+
S.cfg.sys=document.getElementById('cfg-sys').value;
|
|
996
|
+
S.cfg.temp=document.getElementById('cfg-temp').value;
|
|
997
|
+
S.cfg.tok=document.getElementById('cfg-tok').value;
|
|
998
|
+
save();
|
|
999
|
+
}
|
|
1000
|
+
function clearChats(){if(!confirm('Apagar histΓ³rico?'))return;S.chats=[];curChat=null;save();renderChatList();renderMsgs();toast('ποΈ HistΓ³rico apagado');}
|
|
1001
|
+
function clearAll(){if(!confirm('Resetar tudo?'))return;localStorage.removeItem('psc2');location.reload();}
|
|
1002
|
+
|
|
1003
|
+
// βββ UTILS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1004
|
+
function h(s=''){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
1005
|
+
function fmt(text=''){
|
|
1006
|
+
let t=h(text);
|
|
1007
|
+
t=t.replace(/```(\w*)\n?([\s\S]*?)```/g,(_,l,c)=>`<pre><code>${c.trim()}</code></pre>`);
|
|
1008
|
+
t=t.replace(/`([^`]+)`/g,'<code>$1</code>');
|
|
1009
|
+
t=t.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
|
|
1010
|
+
t=t.replace(/\*(.+?)\*/g,'<em>$1</em>');
|
|
1011
|
+
t=t.replace(/^### (.+)$/gm,'<h3>$1</h3>');
|
|
1012
|
+
t=t.replace(/^## (.+)$/gm,'<h2>$1</h2>');
|
|
1013
|
+
t=t.replace(/^# (.+)$/gm,'<h1>$1</h1>');
|
|
1014
|
+
t=t.replace(/^- (.+)$/gm,'<li>$1</li>');
|
|
1015
|
+
t=t.replace(/(\|.+\|\n?)+/g,tbl=>{
|
|
1016
|
+
const rows=tbl.trim().split('\n').filter(r=>!r.match(/^\|[-\s|]+\|$/));
|
|
1017
|
+
return '<table>'+rows.map((r,i)=>{
|
|
1018
|
+
const cells=r.split('|').filter((_,j)=>j>0&&j<r.split('|').length-1);
|
|
1019
|
+
return i===0?`<tr>${cells.map(c=>`<th>${c.trim()}</th>`).join('')}</tr>`:`<tr>${cells.map(c=>`<td>${c.trim()}</td>`).join('')}</tr>`;
|
|
1020
|
+
}).join('')+'</table>';
|
|
1021
|
+
});
|
|
1022
|
+
t=t.split('\n\n').map(p=>p.startsWith('<')?p:`<p>${p.replace(/\n/g,'<br>')}</p>`).join('');
|
|
1023
|
+
return t;
|
|
1024
|
+
}
|
|
1025
|
+
function toast(msg,dur=2600){
|
|
1026
|
+
const el=document.getElementById('toast');
|
|
1027
|
+
el.textContent=msg;el.classList.add('show');
|
|
1028
|
+
setTimeout(()=>el.classList.remove('show'),dur);
|
|
1176
1029
|
}
|
|
1030
|
+
function handleKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}}
|
|
1031
|
+
function autoResize(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,180)+'px';}
|
|
1032
|
+
function sendQ(t){document.getElementById('msg-in').value=t;goTab('chat');sendMsg();}
|
|
1033
|
+
function toggleSb(){document.getElementById('sidebar').classList.toggle('open');}
|
|
1034
|
+
|
|
1035
|
+
// βββ BOOT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1036
|
+
renderChatList();renderMsgs();updateStatus();
|
|
1037
|
+
const bm=MODELS.find(m=>m.id===S.model);
|
|
1038
|
+
if(bm)document.getElementById('model-name-badge').textContent=bm.name;
|
|
1039
|
+
loadKeys();
|
|
1177
1040
|
</script>
|
|
1178
1041
|
</body>
|
|
1179
1042
|
</html>
|