@xingjisoft/neutrino 0.1.7 → 0.2.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/README.md +151 -25
- package/assets/planner.txt +35 -0
- package/assets/prompt.txt +57 -21
- package/assets/step.txt +14 -0
- package/assets/subagent.txt +20 -0
- package/dist/app.js +44 -2
- package/dist/bin.js +38 -13
- package/dist/config.js +58 -1
- package/dist/header.js +5 -0
- package/dist/llm/client.js +101 -142
- package/dist/llm/plan.js +452 -0
- package/dist/llm/retry.js +209 -0
- package/dist/llm/subagent.js +137 -0
- package/dist/llm/tools.js +43 -1
- package/dist/screens/chat.js +384 -51
- package/dist/screens/settings.js +30 -1
- package/dist/state.js +1 -0
- package/dist/web/session.js +235 -24
- package/package.json +2 -2
- package/web/README.md +46 -10
- package/web/app.js +52 -16
- package/web/index.html +55 -17
- package/web/style.css +2070 -1093
package/web/app.js
CHANGED
|
@@ -61,6 +61,13 @@ async function apiFetch(path, init = {}) {
|
|
|
61
61
|
return response;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// The orbit mark is shared by the sidebar, the favicon and the login card, so
|
|
65
|
+
// the console never falls back to a typed glyph for its own identity.
|
|
66
|
+
const LOGO_MARK =
|
|
67
|
+
'<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">' +
|
|
68
|
+
'<ellipse cx="12" cy="12" rx="9.4" ry="3.8" transform="rotate(-30 12 12)" stroke="currentColor" stroke-width="1.7"/>' +
|
|
69
|
+
'<circle cx="12" cy="12" r="3.1" stroke="currentColor" stroke-width="1.7"/></svg>';
|
|
70
|
+
|
|
64
71
|
let gateShown = false;
|
|
65
72
|
|
|
66
73
|
function showGate(message = "") {
|
|
@@ -76,8 +83,9 @@ function showGate(message = "") {
|
|
|
76
83
|
gate.setAttribute("aria-labelledby", "login-title");
|
|
77
84
|
gate.innerHTML =
|
|
78
85
|
'<form class="gate-card">' +
|
|
79
|
-
'<
|
|
80
|
-
'<
|
|
86
|
+
'<div class="gate-brand"><span class="logo">' + LOGO_MARK + "</span><strong>Neutrino</strong></div>" +
|
|
87
|
+
'<h2 id="login-title">Sign in</h2>' +
|
|
88
|
+
'<p>Enter your Web password to open this console. The default password is 114514.</p>' +
|
|
81
89
|
'<label for="login-password">Password</label>' +
|
|
82
90
|
'<input id="login-password" name="password" type="password" autocomplete="current-password" maxlength="256" required autofocus>' +
|
|
83
91
|
'<p class="form-feedback error" role="alert"></p>' +
|
|
@@ -240,6 +248,16 @@ function truncate(value, max) {
|
|
|
240
248
|
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
241
249
|
}
|
|
242
250
|
|
|
251
|
+
// Sidebar paths are identifiers, not navigation: keep the project end of a long
|
|
252
|
+
// path so two checkouts under different parents stay tellable apart.
|
|
253
|
+
function shortenPath(value, depth = 2) {
|
|
254
|
+
const text = String(value ?? "");
|
|
255
|
+
const separator = text.includes("\\") ? "\\" : "/";
|
|
256
|
+
const parts = text.split(/[\\/]+/).filter(Boolean);
|
|
257
|
+
if (parts.length <= depth + 1) return text;
|
|
258
|
+
return `…${separator}${parts.slice(-depth).join(separator)}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
243
261
|
function safeJson(value) {
|
|
244
262
|
try {
|
|
245
263
|
return JSON.stringify(value ?? {});
|
|
@@ -364,8 +382,10 @@ function renderGroup(group) {
|
|
|
364
382
|
const header = document.createElement("button");
|
|
365
383
|
header.className = "group-header";
|
|
366
384
|
header.innerHTML =
|
|
367
|
-
`<span class="chevron
|
|
368
|
-
|
|
385
|
+
`<span class="chevron${collapsed ? " collapsed" : ""}">` +
|
|
386
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>' +
|
|
387
|
+
"</span>" +
|
|
388
|
+
`<span class="path" title="${escapeHtml(group.cwd)}">${escapeHtml(shortenPath(group.cwd))}</span>` +
|
|
369
389
|
`<span class="count">${group.sessions.length}</span>`;
|
|
370
390
|
header.addEventListener("click", () => {
|
|
371
391
|
if (state.collapsed.has(group.key)) state.collapsed.delete(group.key);
|
|
@@ -413,12 +433,14 @@ function renderSession(session) {
|
|
|
413
433
|
if (session.live) item.classList.add("live");
|
|
414
434
|
|
|
415
435
|
const title = session.title || "New conversation";
|
|
436
|
+
const count = Number(session.message_count ?? 0);
|
|
437
|
+
item.title = title;
|
|
416
438
|
item.innerHTML =
|
|
417
439
|
`<div class="session-top"><span class="session-title">${escapeHtml(title)}</span>` +
|
|
418
440
|
`${session.live ? '<span class="dot" title="Live"></span>' : ""}</div>` +
|
|
419
441
|
`<div class="session-meta"><span class="model">${escapeHtml(session.model || "?")}</span>` +
|
|
420
442
|
`<span class="time">${escapeHtml(formatTime(session.updated_at))}</span>` +
|
|
421
|
-
`<span class="count">${
|
|
443
|
+
`<span class="count">${count} message${count === 1 ? "" : "s"}</span></div>`;
|
|
422
444
|
|
|
423
445
|
item.addEventListener("click", () => {
|
|
424
446
|
if (session.liveKey) selectLive(session.liveKey);
|
|
@@ -459,6 +481,7 @@ async function selectLive(key) {
|
|
|
459
481
|
state.openContext = null;
|
|
460
482
|
renderSidebar();
|
|
461
483
|
el.controls.innerHTML = "";
|
|
484
|
+
renderApproval(null);
|
|
462
485
|
el.title.textContent = "Connecting…";
|
|
463
486
|
el.sub.textContent = "";
|
|
464
487
|
setComposerMode("live");
|
|
@@ -516,6 +539,7 @@ async function selectFile(id) {
|
|
|
516
539
|
state.openContext = null;
|
|
517
540
|
renderSidebar();
|
|
518
541
|
el.controls.innerHTML = "";
|
|
542
|
+
renderApproval(null);
|
|
519
543
|
setComposerMode("resume");
|
|
520
544
|
el.title.textContent = "Loading…";
|
|
521
545
|
el.sub.textContent = "";
|
|
@@ -552,12 +576,12 @@ function renderFileControls(data) {
|
|
|
552
576
|
|
|
553
577
|
const badge = document.createElement("span");
|
|
554
578
|
badge.className = "badge readonly";
|
|
555
|
-
badge.textContent = "
|
|
579
|
+
badge.textContent = "Read-only";
|
|
556
580
|
el.controls.appendChild(badge);
|
|
557
581
|
|
|
558
582
|
const button = document.createElement("button");
|
|
559
583
|
button.className = "control primary";
|
|
560
|
-
button.textContent = "
|
|
584
|
+
button.textContent = "Continue here";
|
|
561
585
|
button.title = "Resume this conversation in the browser and keep chatting";
|
|
562
586
|
button.addEventListener("click", async () => {
|
|
563
587
|
button.disabled = true;
|
|
@@ -567,7 +591,7 @@ function renderFileControls(data) {
|
|
|
567
591
|
el.input.focus();
|
|
568
592
|
} catch (error) {
|
|
569
593
|
button.disabled = false;
|
|
570
|
-
button.textContent = "
|
|
594
|
+
button.textContent = "Continue here";
|
|
571
595
|
el.sub.textContent = `Could not continue this conversation: ${error.message}`;
|
|
572
596
|
}
|
|
573
597
|
});
|
|
@@ -595,8 +619,8 @@ function setComposerMode(mode) {
|
|
|
595
619
|
el.composer.classList.remove("hidden");
|
|
596
620
|
el.input.placeholder =
|
|
597
621
|
mode === "resume"
|
|
598
|
-
? "Continue this conversation…
|
|
599
|
-
: "Message
|
|
622
|
+
? "Continue this conversation…"
|
|
623
|
+
: "Message Neutrino…";
|
|
600
624
|
}
|
|
601
625
|
|
|
602
626
|
function hideComposer() {
|
|
@@ -709,7 +733,7 @@ function renderApproval(snapshot) {
|
|
|
709
733
|
const head = document.createElement("div");
|
|
710
734
|
head.className = "approval-head";
|
|
711
735
|
const title = document.createElement("strong");
|
|
712
|
-
title.textContent = "
|
|
736
|
+
title.textContent = "Approval required";
|
|
713
737
|
const risk = document.createElement("span");
|
|
714
738
|
risk.className = "approval-risk";
|
|
715
739
|
risk.textContent = extreme ? "EXTREME RISK" : "HIGH RISK";
|
|
@@ -800,12 +824,21 @@ function renderControls(snapshot) {
|
|
|
800
824
|
const badge = document.createElement("span");
|
|
801
825
|
const mode = snapshot.paused ? "paused" : snapshot.busy ? "busy" : "idle";
|
|
802
826
|
badge.className = `badge ${mode}`;
|
|
827
|
+
// A retried request leaves the session busy but silent: name the retry so the
|
|
828
|
+
// badge does not just say "working" for minutes. The same goes for a
|
|
829
|
+
// sub-agent, whose own messages never reach this transcript.
|
|
830
|
+
const retrying = snapshot.retrying;
|
|
831
|
+
const subagent = snapshot.subagent;
|
|
803
832
|
badge.textContent = snapshot.paused
|
|
804
833
|
? "paused"
|
|
805
834
|
: snapshot.busy
|
|
806
835
|
? snapshot.compressing
|
|
807
836
|
? "compressing"
|
|
808
|
-
:
|
|
837
|
+
: retrying
|
|
838
|
+
? `retrying ${retrying.attempt}/${retrying.attempts}`
|
|
839
|
+
: subagent
|
|
840
|
+
? `sub-agent: ${subagent}`
|
|
841
|
+
: "working"
|
|
809
842
|
: "idle";
|
|
810
843
|
el.controls.appendChild(badge);
|
|
811
844
|
|
|
@@ -819,7 +852,7 @@ function renderControls(snapshot) {
|
|
|
819
852
|
if (snapshot.busy || snapshot.paused) {
|
|
820
853
|
const button = document.createElement("button");
|
|
821
854
|
button.className = "control";
|
|
822
|
-
button.textContent = snapshot.paused ? "
|
|
855
|
+
button.textContent = snapshot.paused ? "Resume" : "Pause";
|
|
823
856
|
button.addEventListener("click", () => {
|
|
824
857
|
postAction(snapshot.key, snapshot.paused ? "resume" : "pause");
|
|
825
858
|
});
|
|
@@ -852,7 +885,7 @@ function renderMessageList(messages, streaming, options) {
|
|
|
852
885
|
if (messages.length === 0 && !streaming) {
|
|
853
886
|
const empty = document.createElement("div");
|
|
854
887
|
empty.className = "empty";
|
|
855
|
-
empty.textContent = options?.readOnly ? "This conversation is empty." : "
|
|
888
|
+
empty.textContent = options?.readOnly ? "This conversation is empty." : "Send a message to get started.";
|
|
856
889
|
el.messages.appendChild(empty);
|
|
857
890
|
return;
|
|
858
891
|
}
|
|
@@ -931,7 +964,9 @@ function renderMessage(message, index) {
|
|
|
931
964
|
|
|
932
965
|
const summary = document.createElement("summary");
|
|
933
966
|
summary.innerHTML =
|
|
934
|
-
|
|
967
|
+
'<span class="tool-name">' +
|
|
968
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>' +
|
|
969
|
+
`${escapeHtml(message.name || "tool")}</span>` +
|
|
935
970
|
`<span class="tool-args">${escapeHtml(truncate(safeJson(message.arguments), 120))}</span>`;
|
|
936
971
|
details.appendChild(summary);
|
|
937
972
|
|
|
@@ -979,7 +1014,8 @@ function renderTurnFold(start, messages) {
|
|
|
979
1014
|
const summary = document.createElement("summary");
|
|
980
1015
|
const chevron = document.createElement("span");
|
|
981
1016
|
chevron.className = "turn-chevron";
|
|
982
|
-
chevron.
|
|
1017
|
+
chevron.innerHTML =
|
|
1018
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>';
|
|
983
1019
|
const you = document.createElement("span");
|
|
984
1020
|
you.className = "turn-you";
|
|
985
1021
|
you.textContent = `You: ${truncate(turnPreview(messages, "user"), 110)}`;
|
package/web/index.html
CHANGED
|
@@ -4,28 +4,44 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
6
|
<meta name="color-scheme" content="dark" />
|
|
7
|
+
<meta name="theme-color" content="#151517" />
|
|
7
8
|
<title>Neutrino</title>
|
|
9
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f9fafb' stroke-width='1.7'%3E%3Cellipse cx='12' cy='12' rx='9.4' ry='3.8' transform='rotate(-30 12 12)'/%3E%3Ccircle cx='12' cy='12' r='3.1'/%3E%3C/svg%3E" />
|
|
8
10
|
<link rel="stylesheet" href="style.css" />
|
|
9
11
|
</head>
|
|
10
12
|
<body>
|
|
11
13
|
<div id="app">
|
|
12
14
|
<aside id="sidebar">
|
|
13
15
|
<header class="brand">
|
|
14
|
-
<div class="logo" aria-hidden="true"
|
|
16
|
+
<div class="logo" aria-hidden="true">
|
|
17
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
18
|
+
<ellipse cx="12" cy="12" rx="9.4" ry="3.8" transform="rotate(-30 12 12)" stroke="currentColor" stroke-width="1.7" />
|
|
19
|
+
<circle cx="12" cy="12" r="3.1" stroke="currentColor" stroke-width="1.7" />
|
|
20
|
+
</svg>
|
|
21
|
+
</div>
|
|
15
22
|
<div class="brand-text">
|
|
16
23
|
<strong>Neutrino</strong>
|
|
17
24
|
<span id="status" class="status">connecting…</span>
|
|
18
25
|
</div>
|
|
19
|
-
<
|
|
20
|
-
|
|
26
|
+
<div class="brand-actions">
|
|
27
|
+
<button id="refresh" class="icon-btn" type="button" title="Refresh conversations" aria-label="Refresh conversations">
|
|
28
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-2.64-6.36" /><path d="M21 3v6h-6" /></svg>
|
|
29
|
+
</button>
|
|
30
|
+
<button id="settings-open" class="icon-btn" type="button" title="Settings" aria-label="Settings">
|
|
31
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
21
34
|
</header>
|
|
22
35
|
<div id="sessions" class="sessions"></div>
|
|
36
|
+
<footer class="sidebar-foot">
|
|
37
|
+
<span>Local agent console</span>
|
|
38
|
+
</footer>
|
|
23
39
|
</aside>
|
|
24
40
|
|
|
25
41
|
<main id="main">
|
|
26
42
|
<header id="main-header" class="main-header">
|
|
27
43
|
<button id="back-to-menu" class="icon-btn back-to-menu" type="button" title="Back to conversations" aria-label="Back to conversations" aria-controls="sidebar">
|
|
28
|
-
<svg
|
|
44
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 5-7 7 7 7M5 12h14" /></svg>
|
|
29
45
|
</button>
|
|
30
46
|
<div class="main-title">
|
|
31
47
|
<h1 id="session-title">Select a conversation</h1>
|
|
@@ -37,19 +53,36 @@
|
|
|
37
53
|
<form id="model-picker" class="model-picker hidden" aria-label="Conversation model">
|
|
38
54
|
<label>Provider<select id="model-provider"></select></label>
|
|
39
55
|
<label>Model<select id="model-name"></select></label>
|
|
40
|
-
<label>Thinking
|
|
41
|
-
<button id="model-apply" class="control" type="submit">Apply
|
|
56
|
+
<label>Thinking<select id="model-intensity"></select></label>
|
|
57
|
+
<button id="model-apply" class="control" type="submit">Apply</button>
|
|
42
58
|
<p id="model-feedback" class="form-feedback" role="status" aria-live="polite"></p>
|
|
43
59
|
</form>
|
|
44
60
|
|
|
45
61
|
<section id="messages" class="messages">
|
|
46
|
-
<div class="empty">
|
|
62
|
+
<div class="empty">
|
|
63
|
+
<span class="empty-mark" aria-hidden="true">
|
|
64
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
65
|
+
<ellipse cx="12" cy="12" rx="9.4" ry="3.8" transform="rotate(-30 12 12)" stroke="currentColor" stroke-width="1.7" />
|
|
66
|
+
<circle cx="12" cy="12" r="3.1" stroke="currentColor" stroke-width="1.7" />
|
|
67
|
+
</svg>
|
|
68
|
+
</span>
|
|
69
|
+
<strong>No conversation open</strong>
|
|
70
|
+
<span>Choose a conversation from the sidebar to view it.</span>
|
|
71
|
+
</div>
|
|
47
72
|
</section>
|
|
48
73
|
|
|
49
74
|
<div id="approval" class="approval hidden" role="alert" aria-live="assertive"></div>
|
|
75
|
+
|
|
50
76
|
<footer id="composer" class="composer hidden">
|
|
51
|
-
<
|
|
52
|
-
|
|
77
|
+
<div class="composer-inner">
|
|
78
|
+
<div class="composer-card">
|
|
79
|
+
<textarea id="input" rows="1" placeholder="Message Neutrino…" aria-label="Message"></textarea>
|
|
80
|
+
<button id="send" class="primary" type="button" title="Send message" aria-label="Send message">
|
|
81
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7" /></svg>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="composer-hint"><kbd>Enter</kbd> to send · <kbd>Shift</kbd> + <kbd>Enter</kbd> for a new line</div>
|
|
85
|
+
</div>
|
|
53
86
|
</footer>
|
|
54
87
|
</main>
|
|
55
88
|
</div>
|
|
@@ -57,8 +90,13 @@
|
|
|
57
90
|
<dialog id="settings-dialog" class="settings-dialog" aria-labelledby="settings-title">
|
|
58
91
|
<form id="settings-form">
|
|
59
92
|
<header class="settings-header">
|
|
60
|
-
<
|
|
61
|
-
|
|
93
|
+
<div>
|
|
94
|
+
<h2 id="settings-title">Settings</h2>
|
|
95
|
+
<p class="settings-sub">Models, approvals, and console access.</p>
|
|
96
|
+
</div>
|
|
97
|
+
<button id="settings-close" class="icon-btn" type="button" aria-label="Close settings">
|
|
98
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
|
|
99
|
+
</button>
|
|
62
100
|
</header>
|
|
63
101
|
<div class="settings-fields">
|
|
64
102
|
<h3>Defaults for new conversations</h3>
|
|
@@ -66,21 +104,21 @@
|
|
|
66
104
|
<label>Model<select id="default-model"></select></label>
|
|
67
105
|
<label>Thinking intensity<select id="default-intensity"></select></label>
|
|
68
106
|
<h3>Conversation and approvals</h3>
|
|
69
|
-
<label>Model context window (tokens)<input id="settings-context" type="number" min="1" max="9007199254740991" step="1" required
|
|
107
|
+
<label>Model context window (tokens)<input id="settings-context" type="number" min="1" max="9007199254740991" step="1" required /></label>
|
|
70
108
|
<p id="settings-compress-note" class="settings-note">Auto-compress at 80% of this model's context window. Default: 1,000,000 tokens.</p>
|
|
71
109
|
<label>Approval reviewer model<select id="settings-reviewer"></select></label>
|
|
72
|
-
<label class="checkbox-label"><input id="settings-extreme" type="checkbox"
|
|
73
|
-
<label class="checkbox-label"><input id="settings-sound" type="checkbox"
|
|
110
|
+
<label class="checkbox-label"><input id="settings-extreme" type="checkbox" /> Extreme-risk auto-approval (del class, 3 separate review rounds)</label>
|
|
111
|
+
<label class="checkbox-label"><input id="settings-sound" type="checkbox" /> Sound alerts (turn finished, approval waiting)</label>
|
|
74
112
|
<p class="settings-note">Safety determines automatic approval; task relevance only blocks unanimously confirmed, extreme mismatches. Delete-class automatic approval also requires this setting.</p>
|
|
75
113
|
<h3>Web access</h3>
|
|
76
|
-
<label>New Web password<input id="settings-password" type="password" autocomplete="new-password" maxlength="256" placeholder="Leave blank to keep current password"
|
|
77
|
-
<label>Confirm password<input id="settings-password-confirm" type="password" autocomplete="new-password" maxlength="256" placeholder="Repeat new password"
|
|
114
|
+
<label>New Web password<input id="settings-password" type="password" autocomplete="new-password" maxlength="256" placeholder="Leave blank to keep current password" /></label>
|
|
115
|
+
<label>Confirm password<input id="settings-password-confirm" type="password" autocomplete="new-password" maxlength="256" placeholder="Repeat new password" /></label>
|
|
78
116
|
<p class="settings-note">The password is permanent and survives restarts. Default: 114514. Saving a new password signs out other browsers.</p>
|
|
79
117
|
<p id="settings-feedback" class="form-feedback" role="status" aria-live="polite"></p>
|
|
80
118
|
</div>
|
|
81
119
|
<footer class="settings-actions">
|
|
82
120
|
<button id="settings-cancel" class="control" type="button">Cancel</button>
|
|
83
|
-
<button id="settings-save" class="primary" type="submit">Save
|
|
121
|
+
<button id="settings-save" class="primary" type="submit">Save changes</button>
|
|
84
122
|
</footer>
|
|
85
123
|
</form>
|
|
86
124
|
</dialog>
|