chesspal 0.3.0 → 0.3.2
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 +1 -1
- package/dist/cli/cli.js +6 -6
- package/dist/commands/{analyse-game.d.ts → analysis/analyse.d.ts} +2 -2
- package/dist/commands/{analyse-game.js → analysis/analyse.js} +7 -11
- package/dist/commands/{annotate-game.d.ts → analysis/annotate.d.ts} +1 -1
- package/dist/commands/{annotate-game.js → analysis/annotate.js} +6 -9
- package/dist/commands/{delete-review.d.ts → analysis/delete-review.d.ts} +1 -1
- package/dist/commands/{delete-review.js → analysis/delete-review.js} +5 -5
- package/dist/commands/analysis/index.d.ts +4 -0
- package/dist/commands/analysis/index.js +5 -0
- package/dist/commands/{study-games.d.ts → analysis/study-games.d.ts} +1 -1
- package/dist/commands/{study-games.js → analysis/study-games.js} +4 -5
- package/dist/commands/{agents.d.ts → answers/agents.d.ts} +3 -3
- package/dist/commands/{agents.js → answers/agents.js} +5 -5
- package/dist/commands/{clock.d.ts → answers/clock.d.ts} +2 -2
- package/dist/commands/{clock.js → answers/clock.js} +2 -2
- package/dist/commands/{drawings.d.ts → answers/drawings.d.ts} +1 -1
- package/dist/commands/{drawings.js → answers/drawings.js} +1 -1
- package/dist/commands/answers/index.d.ts +12 -0
- package/dist/commands/answers/index.js +14 -0
- package/dist/commands/{lead-input.d.ts → answers/lead-input.d.ts} +1 -1
- package/dist/commands/{lead-input.js → answers/lead-input.js} +1 -1
- package/dist/commands/{answer-lines.d.ts → answers/lines.d.ts} +2 -2
- package/dist/commands/{answer-lines.js → answers/lines.js} +4 -4
- package/dist/commands/{opening-book.d.ts → answers/opening-book.d.ts} +1 -1
- package/dist/commands/{opening-book.js → answers/opening-book.js} +3 -3
- package/dist/commands/{play-move.d.ts → answers/play-move.d.ts} +2 -2
- package/dist/commands/{play-move.js → answers/play-move.js} +3 -3
- package/dist/commands/{say.d.ts → answers/say.d.ts} +3 -3
- package/dist/commands/{say.js → answers/say.js} +4 -4
- package/dist/commands/{speech.js → answers/speech.js} +1 -1
- package/dist/commands/{study.d.ts → answers/study.d.ts} +3 -3
- package/dist/commands/{study.js → answers/study.js} +3 -3
- package/dist/commands/{game-summary.d.ts → answers/summary.d.ts} +2 -2
- package/dist/commands/{game-summary.js → answers/summary.js} +4 -4
- package/dist/commands/{view.d.ts → answers/view.d.ts} +1 -1
- package/dist/commands/{ai-move.d.ts → game/ai-move.d.ts} +2 -2
- package/dist/commands/{ai-move.js → game/ai-move.js} +6 -11
- package/dist/commands/{coach.d.ts → game/coach.d.ts} +1 -1
- package/dist/commands/{coach.js → game/coach.js} +7 -13
- package/dist/commands/{draw-offer.d.ts → game/draw-offer.d.ts} +1 -1
- package/dist/commands/{draw-offer.js → game/draw-offer.js} +4 -4
- package/dist/commands/{end-game.d.ts → game/end.d.ts} +2 -2
- package/dist/commands/{end-game.js → game/end.js} +5 -9
- package/dist/commands/{export-game.d.ts → game/export.d.ts} +1 -1
- package/dist/commands/{export-game.js → game/export.js} +4 -4
- package/dist/commands/game/index.d.ts +11 -0
- package/dist/commands/game/index.js +13 -0
- package/dist/commands/{levels.d.ts → game/levels.d.ts} +3 -3
- package/dist/commands/{levels.js → game/levels.js} +3 -3
- package/dist/commands/{look.d.ts → game/look.d.ts} +3 -4
- package/dist/commands/{look.js → game/look.js} +5 -10
- package/dist/commands/{new-game.d.ts → game/new.d.ts} +3 -4
- package/dist/commands/{new-game.js → game/new.js} +17 -13
- package/dist/commands/{page-link.d.ts → game/page-link.d.ts} +1 -1
- package/dist/commands/{player-move.d.ts → game/player-move.d.ts} +2 -2
- package/dist/commands/{player-move.js → game/player-move.js} +4 -8
- package/dist/commands/{ponder.d.ts → game/ponder.d.ts} +3 -3
- package/dist/commands/{ponder.js → game/ponder.js} +6 -9
- package/dist/commands/{position.d.ts → game/position.d.ts} +2 -2
- package/dist/commands/{position.js → game/position.js} +5 -7
- package/dist/commands/{status.d.ts → game/status.d.ts} +2 -4
- package/dist/commands/{status.js → game/status.js} +12 -17
- package/dist/commands/index.d.ts +6 -17
- package/dist/commands/index.js +11 -38
- package/dist/commands/{delete-game.d.ts → profiles/delete-game.d.ts} +1 -1
- package/dist/commands/{delete-game.js → profiles/delete-game.js} +3 -3
- package/dist/commands/profiles/index.d.ts +4 -0
- package/dist/commands/profiles/index.js +5 -0
- package/dist/commands/{past-games.d.ts → profiles/past-games.d.ts} +1 -1
- package/dist/commands/{past-games.js → profiles/past-games.js} +5 -5
- package/dist/commands/{profiles.d.ts → profiles/profiles.d.ts} +7 -7
- package/dist/commands/{profiles.js → profiles/profiles.js} +4 -4
- package/dist/commands/{ratings.d.ts → profiles/ratings.d.ts} +1 -1
- package/dist/commands/{ratings.js → profiles/ratings.js} +2 -2
- package/dist/commands/{context.d.ts → registry/context.d.ts} +2 -0
- package/dist/commands/{context.js → registry/context.js} +2 -2
- package/dist/commands/registry/index.d.ts +2 -0
- package/dist/commands/registry/index.js +3 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +3 -0
- package/dist/commands/{init.d.ts → setup/init.d.ts} +1 -1
- package/dist/commands/{init.js → setup/init.js} +2 -3
- package/dist/commands/{instructions.d.ts → setup/instructions.d.ts} +2 -2
- package/dist/commands/{instructions.js → setup/instructions.js} +3 -3
- package/dist/commands/{ai-say.d.ts → talk/ai-say.d.ts} +2 -3
- package/dist/commands/{ai-say.js → talk/ai-say.js} +5 -9
- package/dist/commands/talk/index.d.ts +2 -0
- package/dist/commands/talk/index.js +3 -0
- package/dist/commands/{player-say.d.ts → talk/player-say.d.ts} +1 -1
- package/dist/commands/{player-say.js → talk/player-say.js} +4 -4
- package/dist/page/app/app.js +44 -20
- package/dist/page/app/served.js +7 -0
- package/dist/page/avatars/torretta.js +10 -3
- package/dist/page/dialogs/dialogs.css +10 -0
- package/dist/page/dialogs/profiles.js +3 -7
- package/dist/page/dialogs/settings.js +18 -4
- package/dist/page/game/column.js +1 -1
- package/dist/page/game/game.css +1 -1
- package/dist/page/guide/blocks.js +13 -2
- package/dist/page/guide/credits.js +6 -0
- package/dist/page/guide/drawings/analysis.js +13 -0
- package/dist/page/guide/drawings/drawings.css +329 -0
- package/dist/page/guide/drawings/drawings.js +15 -0
- package/dist/page/guide/drawings/game.js +15 -0
- package/dist/page/guide/drawings/helps.js +13 -0
- package/dist/page/guide/drawings/how-it-works.js +13 -0
- package/dist/page/guide/drawings/parts.js +103 -0
- package/dist/page/guide/drawings/players.js +19 -0
- package/dist/page/guide/drawings/talk.js +9 -0
- package/dist/page/guide/en.md +39 -25
- package/dist/page/guide/guide.css +123 -91
- package/dist/page/guide/guide.js +47 -25
- package/dist/page/guide/it.md +39 -25
- package/dist/page/guide/markdown.js +5 -0
- package/dist/page/helps/helps.css +127 -83
- package/dist/page/helps/helps.js +45 -26
- package/dist/page/helps/view.js +13 -3
- package/dist/page/{guide/foot.js → layout/footer.js} +7 -8
- package/dist/page/layout/header.js +5 -3
- package/dist/page/layout/layout.css +114 -36
- package/dist/page/layout/window.js +6 -26
- package/dist/page/main.js +1 -1
- package/dist/page/pieces/pieces.js +5 -2
- package/dist/page/style.css +2 -1
- package/dist/page/table/table.css +192 -0
- package/dist/page/table/update.js +82 -0
- package/dist/page/table/window.js +130 -0
- package/dist/page/talk/chat.css +19 -8
- package/dist/page/talk/talk.css +17 -67
- package/dist/page/talk/talk.js +7 -7
- package/dist/page/talk/voice.js +47 -9
- package/dist/page/text.js +118 -22
- package/dist/page/theme.css +2 -0
- package/dist/server/{bell.js → bell/bell.js} +3 -3
- package/dist/server/bell/index.d.ts +1 -0
- package/dist/server/bell/index.js +2 -0
- package/dist/server/{caller.d.ts → mcp/caller.d.ts} +1 -1
- package/dist/server/{caller.js → mcp/caller.js} +1 -1
- package/dist/server/mcp/index.d.ts +1 -0
- package/dist/server/mcp/index.js +2 -0
- package/dist/server/{mcp.d.ts → mcp/mcp.d.ts} +1 -1
- package/dist/server/{mcp.js → mcp/mcp.js} +9 -9
- package/dist/server/page/chat.d.ts +5 -0
- package/dist/server/page/chat.js +9 -1
- package/dist/server/page/game.js +16 -3
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/index.d.ts +3 -0
- package/dist/server/page/index.js +3 -0
- package/dist/server/page/page.d.ts +16 -3
- package/dist/server/page/page.js +18 -4
- package/dist/server/page/table.d.ts +40 -15
- package/dist/server/page/table.js +203 -19
- package/dist/server/page/update.d.ts +18 -0
- package/dist/server/page/update.js +40 -0
- package/dist/server/table/browser.d.ts +1 -1
- package/dist/server/table/browser.js +1 -1
- package/dist/server/table/hosts/chats.d.ts +1 -1
- package/dist/server/table/hosts/chats.js +7 -4
- package/dist/server/table/hosts/index.d.ts +1 -1
- package/dist/server/table/hosts/index.js +1 -1
- package/dist/server/table/hosts/play.d.ts +15 -4
- package/dist/server/table/hosts/play.js +75 -11
- package/dist/server/table/hosts/seat.d.ts +17 -5
- package/dist/server/table/hosts/seat.js +21 -15
- package/dist/server/table/index.d.ts +3 -2
- package/dist/server/table/index.js +3 -2
- package/dist/server/table/launch.d.ts +5 -2
- package/dist/server/table/launch.js +6 -3
- package/dist/server/table/link.d.ts +1 -1
- package/dist/server/table/link.js +4 -3
- package/dist/server/table/table.d.ts +4 -5
- package/dist/server/table/table.js +15 -12
- package/dist/server/table/update.d.ts +17 -0
- package/dist/server/table/update.js +77 -0
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +2 -2
- package/dist/version.d.ts +4 -0
- package/dist/version.js +12 -1
- package/package.json +1 -1
- package/dist/page/table/opener.css +0 -65
- package/dist/page/table/opener.js +0 -59
- /package/dist/commands/{speech.d.ts → answers/speech.d.ts} +0 -0
- /package/dist/commands/{view.js → answers/view.js} +0 -0
- /package/dist/commands/{page-link.js → game/page-link.js} +0 -0
- /package/dist/commands/{registry.d.ts → registry/registry.d.ts} +0 -0
- /package/dist/commands/{registry.js → registry/registry.js} +0 -0
- /package/dist/server/{bell.d.ts → bell/bell.d.ts} +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "preact/hooks";
|
|
3
|
+
import { Dialog } from "../dialogs/dialog.js";
|
|
4
|
+
import { checkIcon } from "../dialogs/profiles.js";
|
|
5
|
+
/** The chat's window: closed once a chat opens or starts again, the page then saying where it stands. */
|
|
6
|
+
export function ChatDialog({ open, table, settings, onChange, name, onClose, text }) {
|
|
7
|
+
const [sending, setSending] = useState(false);
|
|
8
|
+
const [failed, setFailed] = useState(false);
|
|
9
|
+
useEffect(() => setFailed(false), [open]);
|
|
10
|
+
const words = text.table;
|
|
11
|
+
const installed = table.hosts.filter((host) => host.installed && host.name in choiceKeys);
|
|
12
|
+
// A chat opened by hand is the player's: Chesspal cannot start it again. Its rows stay readable, as the ones waiting.
|
|
13
|
+
const byHand = table.chat && !table.held;
|
|
14
|
+
const busy = sending || table.opening !== undefined || table.waiting === true;
|
|
15
|
+
const choose = async (host) => {
|
|
16
|
+
setSending(true);
|
|
17
|
+
setFailed(false);
|
|
18
|
+
const done = table.chat ? await restartChat(host) : await openChat(host);
|
|
19
|
+
setSending(false);
|
|
20
|
+
if (done) {
|
|
21
|
+
onClose();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
setFailed(true);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
// While a chat opens or waits to start again, the rows wait too, and the note says why.
|
|
28
|
+
const note = installed.length === 0
|
|
29
|
+
? words.install(name)
|
|
30
|
+
: busy
|
|
31
|
+
? chatState(table, name, text) ?? words.choose
|
|
32
|
+
: byHand
|
|
33
|
+
? words.byHand(name)
|
|
34
|
+
: table.chat
|
|
35
|
+
? words.restartHint
|
|
36
|
+
: words.choose;
|
|
37
|
+
const offersChoices = installed.some((host) => host.models.length > 0 || host.efforts.length > 0);
|
|
38
|
+
// The chat at the table started with other choices than the ones kept now: its row starts it again with these, else it only closes the window.
|
|
39
|
+
const started = table.held ? table.started : undefined;
|
|
40
|
+
const changed = (host) => started !== undefined && (chosen(host, "model", settings) !== (started.model ?? "") || chosen(host, "effort", settings) !== (started.effort ?? ""));
|
|
41
|
+
return (_jsxs(Dialog, { open: open, title: words.title, onClose: onClose, text: text, children: [_jsx("p", { class: "note", children: note }), _jsxs("div", { class: "chat-hosts", role: "group", "aria-label": words.title, children: [offersChoices
|
|
42
|
+
? (_jsxs("div", { class: "chat-head", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: words.model }), _jsx("span", { children: words.effort })] }))
|
|
43
|
+
: null, installed.map((host) => {
|
|
44
|
+
const atTable = table.chat && table.host === host.name;
|
|
45
|
+
const off = busy || byHand;
|
|
46
|
+
const again = !atTable || changed(host);
|
|
47
|
+
return (_jsxs("div", { class: "chat-host", children: [_jsxs("button", { class: "choice", type: "button", title: atTable && again ? words.restartChanged : undefined, "aria-current": atTable ? "true" : undefined, "aria-disabled": off, onClick: off ? undefined : again ? () => void choose(host.name) : onClose, children: [_jsx("span", { children: hostTitles[host.name] ?? host.name }), atTable ? (again ? restartIcon : checkIcon) : null] }), _jsx(Choice, { host: host, kind: "model", settings: settings, onChange: onChange, text: text }), _jsx(Choice, { host: host, kind: "effort", settings: settings, onChange: onChange, text: text })] }, host.name));
|
|
48
|
+
})] }), _jsx("p", { class: "chat-state", role: "status", children: failed ? (table.chat ? words.notRestarted : words.failed) : null }), table.chat || installed.length === 0 ? null : (_jsxs("p", { class: "chat-command", children: [words.typeIt, " ", installed.map((host, index) => (_jsxs("span", { children: [index === 0 ? "" : " · ", _jsx("code", { children: host.command })] }, host.name)))] }))] }));
|
|
49
|
+
}
|
|
50
|
+
/** The AI's name on its card, which opens the chat's window: with no chat at the table, a hollow ring before it says so. */
|
|
51
|
+
export function AiNameButton({ name, table, onOpen, text }) {
|
|
52
|
+
const title = table.chat ? text.table.change : text.table.noChat;
|
|
53
|
+
return (_jsxs("button", { class: "profile-name ai-name", type: "button", title: title, "aria-label": `${name}: ${title}`, "aria-haspopup": "dialog", onClick: onOpen, children: [table.chat ? null : _jsx("span", { class: "no-chat", "aria-hidden": "true" }), _jsx("span", { children: name }), _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M7 10L12 15L17 10" }) })] }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The note on the chat at the table where the asleep note stands: one opening or starting again; one waiting to, with a ↻ that starts it at once and a ×
|
|
57
|
+
* that lets the request go; in a game with no chat at the table, a line that opens the chat's window. None otherwise.
|
|
58
|
+
*/
|
|
59
|
+
export function tableNote({ table, name, inGame, onOpen, text }) {
|
|
60
|
+
const words = text.table;
|
|
61
|
+
const state = chatState(table, name, text);
|
|
62
|
+
if (table.waiting === true) {
|
|
63
|
+
return (_jsxs("p", { class: "asleep table-note", role: "status", children: [_jsx("span", { children: state }), _jsx("button", { class: "icon-button", type: "button", title: words.restartNow, "aria-label": words.restartNow, onClick: () => void endWaiting(true), children: restartIcon }), _jsx("button", { class: "icon-button", type: "button", title: words.cancel, "aria-label": words.cancel, onClick: () => void endWaiting(false), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 6L18 18M18 6L6 18" }) }) })] }));
|
|
64
|
+
}
|
|
65
|
+
if (state !== undefined) {
|
|
66
|
+
return _jsx("p", { class: "asleep", role: "status", children: state });
|
|
67
|
+
}
|
|
68
|
+
if (!table.chat && inGame) {
|
|
69
|
+
return (_jsx("p", { class: "asleep table-note", role: "status", children: _jsxs("button", { class: "no-chat-line", type: "button", "aria-haspopup": "dialog", onClick: onOpen, children: [_jsx("span", { class: "no-chat", "aria-hidden": "true" }), words.noChat] }) }));
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** What the page says of the chat at the table: one opening, starting again, or waiting to; nothing else. */
|
|
74
|
+
export function chatState(table, name, text) {
|
|
75
|
+
const words = text.table;
|
|
76
|
+
if (table.waiting === true) {
|
|
77
|
+
return words.waiting(name);
|
|
78
|
+
}
|
|
79
|
+
return table.opening === "chat" ? words.opening : table.opening === "again" ? words.again(name) : table.opening === "restart" ? words.restart : undefined;
|
|
80
|
+
}
|
|
81
|
+
const hostTitles = { claude: "Claude Code", codex: "Codex" };
|
|
82
|
+
// The settings that keep each host's choice.
|
|
83
|
+
const choiceKeys = {
|
|
84
|
+
claude: { model: "claudeModel", effort: "claudeEffort" },
|
|
85
|
+
codex: { model: "codexModel", effort: "codexEffort" },
|
|
86
|
+
};
|
|
87
|
+
/** The ↻ of a chat that starts again. */
|
|
88
|
+
const restartIcon = (_jsx("svg", { class: "icon in-use", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M19.5 12a7.5 7.5 0 1 1-2.2-5.3M19.5 4.5v4h-4" }) }));
|
|
89
|
+
/** A host's model or effort as the settings keep it: "" for the host's own, and for a choice it no longer offers. */
|
|
90
|
+
function chosen(host, kind, settings) {
|
|
91
|
+
const key = choiceKeys[host.name][kind];
|
|
92
|
+
const offered = kind === "model" ? host.models : host.efforts;
|
|
93
|
+
return offered.includes(settings[key]) ? settings[key] : "";
|
|
94
|
+
}
|
|
95
|
+
/** A host's model or effort: a dash keeps the host's own; a choice it no longer offers shows as the dash. Its title says what it stands at, the dash too. */
|
|
96
|
+
function Choice({ host, kind, settings, onChange, text }) {
|
|
97
|
+
const key = choiceKeys[host.name][kind];
|
|
98
|
+
const offered = kind === "model" ? host.models : host.efforts;
|
|
99
|
+
const value = chosen(host, kind, settings);
|
|
100
|
+
const title = hostTitles[host.name] ?? host.name;
|
|
101
|
+
if (offered.length === 0) {
|
|
102
|
+
return _jsx("span", {});
|
|
103
|
+
}
|
|
104
|
+
return (_jsxs("select", { "aria-label": `${text.table[kind]}, ${title}`, title: `${text.table[kind]}: ${value === "" ? text.table.asHost(title) : value}`, value: value, onChange: (event) => onChange({ ...settings, [key]: event.currentTarget.value }), children: [_jsx("option", { value: "", children: "\u2014" }), offered.map((one) => _jsx("option", { value: one, children: shortName(one) }, one))] }));
|
|
105
|
+
}
|
|
106
|
+
// Codex's models without the "gpt-" they all start with, so the choice stays narrow; the title keeps the whole name.
|
|
107
|
+
function shortName(value) {
|
|
108
|
+
return value.replace(/^gpt-/, "");
|
|
109
|
+
}
|
|
110
|
+
/** Asks the table to open a terminal with a chat in the host: whether it did. */
|
|
111
|
+
function openChat(host) {
|
|
112
|
+
return asked("/api/table/chat", { host }, (answer) => answer.opened === true);
|
|
113
|
+
}
|
|
114
|
+
/** Asks the table to start at once the chat that waits to start again, cutting its answer, or to let the request go. */
|
|
115
|
+
function endWaiting(now) {
|
|
116
|
+
return asked("/api/table/waiting", { now }, () => true);
|
|
117
|
+
}
|
|
118
|
+
/** Asks the table to start its chat again with the host, at once or once it has ended its answer: whether it took the request. */
|
|
119
|
+
function restartChat(host) {
|
|
120
|
+
return asked("/api/table/restart", { host }, (answer) => answer.restarting === true || answer.waiting === true);
|
|
121
|
+
}
|
|
122
|
+
async function asked(path, body, done) {
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
125
|
+
return response.ok && done((await response.json()));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dist/page/talk/chat.css
CHANGED
|
@@ -5,15 +5,13 @@
|
|
|
5
5
|
max-height: none;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/*
|
|
8
|
+
/* Under the AI's note, with no line between them (Stefano) */
|
|
9
9
|
.talk.studied > section > .chat {
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-direction: column;
|
|
12
12
|
flex: 1 1 auto;
|
|
13
13
|
min-height: 0;
|
|
14
14
|
margin-top: 12px;
|
|
15
|
-
padding-top: 10px;
|
|
16
|
-
border-top: 1px solid var(--line);
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
.chat > .compose {
|
|
@@ -49,16 +47,29 @@
|
|
|
49
47
|
|
|
50
48
|
.flipped .talk.studied > section > .chat {
|
|
51
49
|
margin: 0 0 12px;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Turned round, the note's room goes under its bubbles, which would touch the AI's card or the note that no chat listens (Zoe) */
|
|
53
|
+
.flipped .talk.studied .ai-note {
|
|
54
|
+
margin: 0 0 12px;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/* The AI's answers on the left, in its bubbles,
|
|
58
|
+
/* The AI's answers on the left, in its bubbles, each with a small Torretta at its foot instead of the tail that points at the big one: it is not by them (Stefano) */
|
|
59
59
|
.sent li.from-ai {
|
|
60
60
|
align-self: flex-start;
|
|
61
|
-
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
align-items: flex-end;
|
|
63
|
+
gap: 6px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sent li.from-ai > .torretta {
|
|
67
|
+
flex: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The bubble's foot level with the Torretta's, spaced as the player's lines are */
|
|
71
|
+
.sent li.from-ai > .say {
|
|
72
|
+
margin: 0;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
.talk.studied .sent .say::before,
|
package/dist/page/talk/talk.css
CHANGED
|
@@ -233,65 +233,6 @@
|
|
|
233
233
|
background: var(--say);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
.me {
|
|
237
|
-
position: relative;
|
|
238
|
-
flex: none;
|
|
239
|
-
padding: 0;
|
|
240
|
-
border: 0;
|
|
241
|
-
border-radius: 50%;
|
|
242
|
-
background: none;
|
|
243
|
-
line-height: 0;
|
|
244
|
-
cursor: pointer;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.me-cover {
|
|
248
|
-
position: absolute;
|
|
249
|
-
inset: 0;
|
|
250
|
-
display: grid;
|
|
251
|
-
place-items: center;
|
|
252
|
-
border-radius: 50%;
|
|
253
|
-
background: rgba(0, 0, 0, 0.45);
|
|
254
|
-
color: #fff;
|
|
255
|
-
opacity: 0;
|
|
256
|
-
transition: opacity 0.12s;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.me-cover .icon {
|
|
260
|
-
width: 45%;
|
|
261
|
-
height: 45%;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.me:hover .me-cover,
|
|
265
|
-
.me:focus-visible .me-cover {
|
|
266
|
-
opacity: 1;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/* Where nothing hovers, a camera in the corner says the picture changes */
|
|
270
|
-
.me-badge {
|
|
271
|
-
position: absolute;
|
|
272
|
-
right: -2px;
|
|
273
|
-
bottom: -2px;
|
|
274
|
-
display: none;
|
|
275
|
-
place-items: center;
|
|
276
|
-
width: 18px;
|
|
277
|
-
height: 18px;
|
|
278
|
-
border-radius: 50%;
|
|
279
|
-
background: var(--accent);
|
|
280
|
-
color: var(--on-accent);
|
|
281
|
-
box-shadow: 0 0 0 2px var(--card);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.me-badge .icon {
|
|
285
|
-
width: 12px;
|
|
286
|
-
height: 12px;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
@media (hover: none) {
|
|
290
|
-
.me-badge {
|
|
291
|
-
display: grid;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
236
|
.profile-name {
|
|
296
237
|
display: inline-flex;
|
|
297
238
|
align-items: center;
|
|
@@ -369,6 +310,15 @@
|
|
|
369
310
|
}
|
|
370
311
|
}
|
|
371
312
|
|
|
313
|
+
/* Its bust on a player's card: a disc of the player's picture's colour, which cuts what falls out of it */
|
|
314
|
+
.torretta.bust {
|
|
315
|
+
clip-path: circle(50%);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.torretta .back {
|
|
319
|
+
fill: var(--say);
|
|
320
|
+
}
|
|
321
|
+
|
|
372
322
|
.torretta .shade {
|
|
373
323
|
fill: var(--torretta-shade);
|
|
374
324
|
}
|
|
@@ -701,23 +651,23 @@
|
|
|
701
651
|
overflow-wrap: anywhere;
|
|
702
652
|
}
|
|
703
653
|
|
|
704
|
-
/* Only the note's
|
|
654
|
+
/* Only the note's first bubble has a tail, pointing up at the Torretta beside the AI's name, the board turned round or not (Stefano) */
|
|
705
655
|
.ai-note > .say::before,
|
|
706
656
|
.ai-note > .say::after {
|
|
707
657
|
content: none;
|
|
708
658
|
}
|
|
709
659
|
|
|
710
|
-
.ai-note > .
|
|
660
|
+
.ai-note > .who + .say::before {
|
|
711
661
|
content: "";
|
|
712
662
|
position: absolute;
|
|
713
663
|
left: 8px;
|
|
714
|
-
|
|
664
|
+
top: -9px;
|
|
715
665
|
border: 10px solid transparent;
|
|
716
|
-
border-
|
|
717
|
-
border-
|
|
666
|
+
border-top: 0;
|
|
667
|
+
border-bottom-color: var(--say);
|
|
718
668
|
}
|
|
719
669
|
|
|
720
|
-
.ai-note > .
|
|
670
|
+
.ai-note > .who + .say {
|
|
721
671
|
margin-top: 4px;
|
|
722
672
|
}
|
|
723
673
|
|
|
@@ -866,8 +816,8 @@
|
|
|
866
816
|
margin: 0 0 var(--between);
|
|
867
817
|
}
|
|
868
818
|
|
|
869
|
-
.ai-block:has(> .asleep) .say::before,
|
|
870
|
-
.ai-block:has(> .asleep) .say::after {
|
|
819
|
+
.ai-block:has(> .asleep) .says .say::before,
|
|
820
|
+
.ai-block:has(> .asleep) .says .say::after {
|
|
871
821
|
content: none;
|
|
872
822
|
}
|
|
873
823
|
|
package/dist/page/talk/talk.js
CHANGED
|
@@ -16,7 +16,7 @@ export function AiWords({ words, newestFirst, voice }) {
|
|
|
16
16
|
return (_jsx("ol", { ref: list, class: "says", "aria-live": "polite", children: inOrder(words.map((word, index) => (_jsx("li", { class: "say", style: aged(words.length - 1 - index), children: _jsx(Marked, { text: word }) }, index))), newestFirst) }));
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* The AI's word on the move shown of a game in analysis (D-69), in a bubble
|
|
19
|
+
* The AI's word on the move shown of a game in analysis (D-69), in a bubble under the Torretta beside the AI's name, pointing up at it: the move, its badge by name,
|
|
20
20
|
* and its note when it wrote one; read out as the player steps through the game. The Torretta and the name stay when the move has no word.
|
|
21
21
|
*/
|
|
22
22
|
export function AiNote({ ply, opening, closing, summary, reviewing, name, by, mood, voice, text }) {
|
|
@@ -32,10 +32,10 @@ export function AiNote({ ply, opening, closing, summary, reviewing, name, by, mo
|
|
|
32
32
|
}
|
|
33
33
|
return hush;
|
|
34
34
|
}, [spoken, voice]);
|
|
35
|
-
// The Torretta stays in sight through the whole analysis,
|
|
36
|
-
return (_jsxs("div", { class: "ai-note", "aria-live": "polite", children: [ply === undefined || shown === undefined ? null : (_jsxs("p", { class: `say move-note ${shown.badge ?? ""}`, children: [_jsxs("span", { class: "head", children: [shown.badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: shown.badge }), _jsxs("b", { children: [_jsxs("span", { class: "visually-hidden", children: [ply.number, ply.side === "white" ? "." : "...", ply.san] }), _jsxs("span", { "aria-hidden": "true", children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] })] }), " ", title] }), note === undefined ? null : (_jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })), shown.arrows === undefined && shown.squares === undefined ? null : _jsx(Legend, { review: shown, words: text.drawn })] })), summary === undefined ? null : _jsx(GameReview, { by: name, summary: summary, text: text }), closing === undefined ? null : (_jsx("p", { class: "say closing", children: _jsx(Marked, { text: closing }) })), reviewing === true
|
|
35
|
+
// The Torretta stays in sight through the whole analysis, over its bubbles, which show only where it wrote something.
|
|
36
|
+
return (_jsxs("div", { class: "ai-note", "aria-live": "polite", children: [_jsxs("p", { class: "who", children: [_jsx(Torretta, { size: 36, mood: mood }), _jsxs("span", { class: "signed", children: [name, by === undefined ? null : _jsx("small", { children: signature(by.annotator, by.effort) })] })] }), ply === undefined || shown === undefined ? null : (_jsxs("p", { class: `say move-note ${shown.badge ?? ""}`, children: [_jsxs("span", { class: "head", children: [shown.badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: shown.badge }), _jsxs("b", { children: [_jsxs("span", { class: "visually-hidden", children: [ply.number, ply.side === "white" ? "." : "...", ply.san] }), _jsxs("span", { "aria-hidden": "true", children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] })] }), " ", title] }), note === undefined ? null : (_jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })), shown.arrows === undefined && shown.squares === undefined ? null : _jsx(Legend, { review: shown, words: text.drawn })] })), summary === undefined ? null : _jsx(GameReview, { by: name, summary: summary, text: text }), closing === undefined ? null : (_jsx("p", { class: "say closing", children: _jsx(Marked, { text: closing }) })), reviewing === true
|
|
37
37
|
? (_jsxs("p", { class: "say reviewing", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(name)] }))
|
|
38
|
-
: null
|
|
38
|
+
: null] }));
|
|
39
39
|
}
|
|
40
40
|
/** The squares and moves in the AI's words, such as e3, Cxe4, 7.Nd4 or O-O: in bold, as the player looks for them on the board. */
|
|
41
41
|
export function Marked({ text }) {
|
|
@@ -60,7 +60,7 @@ export function Legend({ review, words }) {
|
|
|
60
60
|
/**
|
|
61
61
|
* The player's words of the game, the newest by the box they write in with how far it got, and the box: Enter or the button sends the words alone, the next
|
|
62
62
|
* move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54). In analysis the
|
|
63
|
-
* AI's answers are in the same thread, and each line of the player's names the move it was written on (D-101).
|
|
63
|
+
* AI's answers are in the same thread, each by a small Torretta, and each line of the player's names the move it was written on (D-101).
|
|
64
64
|
*/
|
|
65
65
|
export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, about, text }) {
|
|
66
66
|
const list = useRef(null);
|
|
@@ -76,7 +76,7 @@ export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, can
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", { class: line.who === "ai" ? "from-ai" : undefined, style: about === undefined ? aged(lines.length - 1 - index) : reread(index, lastAnswer), children: [line.who === "ai"
|
|
79
|
-
? (_jsx("p", { class: "say", children: _jsx(Marked, { text: line.text }) }))
|
|
79
|
+
? (_jsxs(_Fragment, { children: [_jsx(Torretta, { size: 24 }), _jsx("p", { class: "say", children: _jsx(Marked, { text: line.text }) })] }))
|
|
80
80
|
: (_jsxs("p", { class: "said", children: [about === undefined || line.shown?.move === undefined ? null : _jsx(ShownMove, { move: line.shown.move, onShow: () => about.onShow(line.shown.at), text: text }), line.text] })), index === lastWritten ? _jsx(Status, { heard: line.heard, name: name, text: text }) : null] }, index))), newestFirst) }), left <= 0 ? null : (_jsxs("form", { class: about === undefined ? "compose" : "compose asking", onSubmit: (event) => {
|
|
81
81
|
event.preventDefault();
|
|
82
82
|
submit();
|
|
@@ -94,7 +94,7 @@ export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, can
|
|
|
94
94
|
* whoever moves and a dot on the Torretta when the AI spoke meanwhile.
|
|
95
95
|
*/
|
|
96
96
|
export function TalkFaces({ name, aiPicture, you, picture, ai, player, aiMoves, playerMoves, unread, text }) {
|
|
97
|
-
return (_jsxs("div", { class: "faces", children: [_jsx(RailPlayer, { place: "ai", picture: aiPicture ?? _jsx(Torretta, { size: 40 }), name: name, ...(ai === undefined ? {} : { side: text.sides[ai] }), moves: aiMoves, unread: unread }), _jsx(RailPlayer, { place: "you", picture: picture, name: you, ...(player === undefined ? {} : { side: text.sides[player] }), moves: playerMoves })] }));
|
|
97
|
+
return (_jsxs("div", { class: "faces", children: [_jsx(RailPlayer, { place: "ai", picture: aiPicture ?? _jsx(Torretta, { size: 40, bust: true }), name: name, ...(ai === undefined ? {} : { side: text.sides[ai] }), moves: aiMoves, unread: unread }), _jsx(RailPlayer, { place: "you", picture: picture, name: you, ...(player === undefined ? {} : { side: text.sides[player] }), moves: playerMoves })] }));
|
|
98
98
|
}
|
|
99
99
|
/** The move a line of the player's was written on, 14.♘f3, small at the line's head: a press shows it on the board again (Zoe). */
|
|
100
100
|
function ShownMove({ move, onShow, text }) {
|
package/dist/page/talk/voice.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function speak(words, language) {
|
|
1
|
+
/** Reads the words aloud in the voice's language, the moves in words, cutting short what was being read. */
|
|
2
|
+
export function speak(words, voice) {
|
|
4
3
|
if (typeof speechSynthesis === "undefined") {
|
|
5
4
|
return;
|
|
6
5
|
}
|
|
7
|
-
const said = words.replace(/\*\*/g, "").trim();
|
|
6
|
+
const said = spokenMoves(words.replace(/\*\*/g, ""), voice.saidMove).trim();
|
|
8
7
|
speechSynthesis.cancel();
|
|
9
8
|
clearTimeout(pending);
|
|
10
9
|
if (said === "") {
|
|
@@ -13,10 +12,10 @@ export function speak(words, language) {
|
|
|
13
12
|
// Chrome drops words spoken right after a cancel, and lists its voices late the first time: the words wait a moment.
|
|
14
13
|
pending = setTimeout(() => {
|
|
15
14
|
const utterance = new SpeechSynthesisUtterance(said);
|
|
16
|
-
const
|
|
17
|
-
utterance.lang =
|
|
18
|
-
if (
|
|
19
|
-
utterance.voice =
|
|
15
|
+
const reader = readerFor(voice.language);
|
|
16
|
+
utterance.lang = reader?.lang ?? voice.language;
|
|
17
|
+
if (reader !== undefined) {
|
|
18
|
+
utterance.voice = reader;
|
|
20
19
|
}
|
|
21
20
|
speechSynthesis.resume();
|
|
22
21
|
speechSynthesis.speak(utterance);
|
|
@@ -29,9 +28,48 @@ export function hush() {
|
|
|
29
28
|
speechSynthesis.cancel();
|
|
30
29
|
}
|
|
31
30
|
}
|
|
31
|
+
/** Whether the browser has a voice for the language: without one the words cannot be read. */
|
|
32
|
+
export function hasVoice(language) {
|
|
33
|
+
return typeof speechSynthesis !== "undefined" && readerFor(language) !== undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Calls the listener whenever the browser's voices change, since Chrome lists them late: what it returns stops it. */
|
|
36
|
+
export function onVoices(listener) {
|
|
37
|
+
if (typeof speechSynthesis === "undefined") {
|
|
38
|
+
return () => undefined;
|
|
39
|
+
}
|
|
40
|
+
speechSynthesis.addEventListener("voiceschanged", listener);
|
|
41
|
+
return () => speechSynthesis.removeEventListener("voiceschanged", listener);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The words with each move the AI wrote said in words, since a voice reads ♟g4 as "chess pawn g4": ♞xf6+, Nxf6+, exd5, O-O, e8=♛. A square alone, e4,
|
|
45
|
+
* stays as it is; a pawn's move becomes words only with its figurine, a capture, a promotion or a check, and without the file it takes from.
|
|
46
|
+
*/
|
|
47
|
+
export function spokenMoves(words, say) {
|
|
48
|
+
return words.replace(movePattern, (move, ...found) => {
|
|
49
|
+
const { castles, figurine, letter, from, takes, square, promotion, check } = found.at(-1);
|
|
50
|
+
const checked = check === undefined ? {} : { check: check === "+" ? "check" : "mate" };
|
|
51
|
+
if (castles !== undefined) {
|
|
52
|
+
return say({ castles: castles.length > 3 ? "long" : "short", ...checked });
|
|
53
|
+
}
|
|
54
|
+
if (square === undefined || (figurine === undefined && letter === undefined && takes === undefined && promotion === undefined && check === undefined)) {
|
|
55
|
+
return move;
|
|
56
|
+
}
|
|
57
|
+
const piece = figurine === undefined ? (letter ?? "P") : pieceOf(figurine);
|
|
58
|
+
const promoted = promotion === undefined ? {} : { promotion: pieceOf(promotion) };
|
|
59
|
+
// A pawn's file, exd5, goes unsaid: "pawn takes d5".
|
|
60
|
+
const said = from === undefined || from === "" || piece === "P" ? {} : { from };
|
|
61
|
+
return say({ piece, ...said, takes: takes !== undefined, square, ...promoted, ...checked });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
32
64
|
let pending;
|
|
65
|
+
// A move in SAN, with the pieces' letters or figurines, as the AI writes it; the parts named, castling apart.
|
|
66
|
+
const movePattern = /(?<![\w♔-♟])(?:(?<castles>O-O(?:-O)?|0-0(?:-0)?)|(?:(?<figurine>[♔-♟])[\uFE0E\uFE0F]?|(?<letter>[KQRBN]))?(?<from>[a-h]?[1-8]?)(?<takes>[x×])?(?<square>[a-h][1-8])(?:=?(?<promotion>[QRBN]|[♔-♟]))?[\uFE0E\uFE0F]?)(?<check>\+\+|[+#])?(?![\w♔-♟])/gu;
|
|
67
|
+
// A piece's letter, or its figurine: White's run ♔♕♖♗♘♙ and Black's ♚♛♜♝♞♟, in SAN's order.
|
|
68
|
+
function pieceOf(sign) {
|
|
69
|
+
return ("KQRBN".includes(sign) ? sign : "KQRBNP".charAt(((sign.codePointAt(0) ?? 0) - 0x2654) % 6));
|
|
70
|
+
}
|
|
33
71
|
/** A voice for the language: a local one first, the browser's default among them first. */
|
|
34
|
-
function
|
|
72
|
+
function readerFor(language) {
|
|
35
73
|
const voices = speechSynthesis.getVoices().filter((voice) => voice.lang.toLowerCase().startsWith(language.toLowerCase()));
|
|
36
74
|
const local = voices.filter((voice) => voice.localService);
|
|
37
75
|
return local.find((voice) => voice.default) ?? local[0] ?? voices[0];
|