@songsid/agend 2.3.2-beta.12 → 2.3.2-beta.14
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/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/kiro-classic-archive.js +55 -2
- package/dist/backend/kiro-classic-archive.js.map +1 -1
- package/dist/backend/kiro-classic-links.d.ts +24 -0
- package/dist/backend/kiro-classic-links.js +135 -0
- package/dist/backend/kiro-classic-links.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +71 -1
- package/dist/backend/kiro-identity.js +204 -31
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-v2-store-worker.js +6 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +12 -1
- package/dist/backend/kiro-v2-store.js +22 -12
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +6 -0
- package/dist/backend/kiro.js +50 -7
- package/dist/backend/kiro.js.map +1 -1
- package/dist/fleet-manager.js +6 -1
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +7 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +7 -1
- package/dist/kiro-migrate.d.ts +8 -2
- package/dist/kiro-migrate.js +48 -5
- package/dist/kiro-migrate.js.map +1 -1
- package/dist/locale.js +32 -0
- package/dist/locale.js.map +1 -1
- package/dist/quickstart-api.d.ts +14 -0
- package/dist/quickstart-api.js +62 -11
- package/dist/quickstart-api.js.map +1 -1
- package/dist/settings-effect.js +5 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/setup-form.d.ts +2 -2
- package/dist/setup-form.js +124 -92
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.d.ts +25 -0
- package/dist/setup-i18n.js +224 -0
- package/dist/setup-i18n.js.map +1 -0
- package/dist/ui/backend-signin.js +4 -3
- package/dist/ui/panel-settings.js +4 -2
- package/dist/ui/settings-discord.js +3 -2
- package/dist/ui/settings-strings.js +8 -2
- package/dist/ui/settings-token.js +3 -2
- package/dist/ui/settings-wizard.js +11 -6
- package/dist/ui/shared/setup-host-text.js +105 -0
- package/dist/ui/structured-signin.js +89 -38
- package/package.json +1 -1
- package/templates/windows-shortcuts.ps1 +3 -3
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2726: the setup pages in English and 繁體中文.
|
|
3
|
+
*
|
|
4
|
+
* Both pages are inline (setup-form.ts) and run before any fleet — no dashboard, no /assets — so they carry their own
|
|
5
|
+
* small dictionary and the few lines that use it (SETUP_I18N_RUNTIME). The choice works as the dashboard's does
|
|
6
|
+
* (src/ui/shared/app-i18n.js): the language chosen in this browser (`agend_lang`, the dashboard's own key, so the
|
|
7
|
+
* dashboard opened afterwards on this address keeps it), else the browser's language; a toggle on the page changes it.
|
|
8
|
+
* The wording is the dashboard's where it already says the same thing (settings-strings.js wizard*).
|
|
9
|
+
*
|
|
10
|
+
* What the host says (its errors) is English, or a code: src/ui/shared/setup-host-text.js says each one the page can show
|
|
11
|
+
* in both languages — the same module the dashboard's Setup wizard uses. One it does not know is shown as it came.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
/** The code-entry page: before the code, nothing about this machine (setup-form.ts SETUP_CODE_PAGE_HTML). */
|
|
15
|
+
export const SETUP_CODE_STRINGS = {
|
|
16
|
+
en: {
|
|
17
|
+
title: "Set up AgEnD",
|
|
18
|
+
otherLang: "中文",
|
|
19
|
+
langTitle: "Language",
|
|
20
|
+
lead: "Enter the setup code shown in the terminal where you ran {0}.",
|
|
21
|
+
continue: "Continue",
|
|
22
|
+
unreachable: "Could not reach the setup page.",
|
|
23
|
+
notAccepted: "That code was not accepted.",
|
|
24
|
+
},
|
|
25
|
+
"zh-TW": {
|
|
26
|
+
title: "設定 AgEnD",
|
|
27
|
+
otherLang: "English",
|
|
28
|
+
langTitle: "語言",
|
|
29
|
+
lead: "請輸入執行 {0} 的終端機上顯示的設定代碼。",
|
|
30
|
+
continue: "繼續",
|
|
31
|
+
unreachable: "無法連到設定頁。",
|
|
32
|
+
notAccepted: "這個代碼不正確。",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/** The setup form (setup-form.ts SETUP_FORM_HTML). */
|
|
36
|
+
export const SETUP_FORM_STRINGS = {
|
|
37
|
+
en: {
|
|
38
|
+
title: "Set up AgEnD",
|
|
39
|
+
otherLang: "中文",
|
|
40
|
+
langTitle: "Language",
|
|
41
|
+
intro: "This page is open only while setup is running. It closes itself when you finish, or after 15 minutes.",
|
|
42
|
+
signinsLabel: "AI backend sign-ins",
|
|
43
|
+
stepAgent: "1 · Agent",
|
|
44
|
+
backend: "Backend",
|
|
45
|
+
backendFound: "Found on this host: {0}",
|
|
46
|
+
backendNone: "No backend CLI found on this host — install one first.",
|
|
47
|
+
dir: "Working directory (optional)",
|
|
48
|
+
dirPlaceholder: "/home/you/projects/app",
|
|
49
|
+
dirDefault: "Leave it empty and the agent gets its own folder, {0}, which AgEnD creates. A path you enter must be absolute.",
|
|
50
|
+
dirDefaultName: "Leave it empty and the agent gets its own folder in AgEnD's workspaces, which AgEnD creates. A path you enter must be absolute.",
|
|
51
|
+
name: "First agent name",
|
|
52
|
+
stepPlatform: "2 · Platform",
|
|
53
|
+
webOnly: "Web only (no bot)",
|
|
54
|
+
hintTelegram: "Create a bot with @BotFather, then add it to a group.",
|
|
55
|
+
hintDiscord: "Create an application in the Discord developer portal and invite the bot to your server.",
|
|
56
|
+
hintWeb: "No bot: you use AgEnD from its web dashboard (chat, sign-ins, settings). Discord or Telegram can be added later in Settings.",
|
|
57
|
+
stepCredentials: "3 · Credentials",
|
|
58
|
+
token: "Bot token",
|
|
59
|
+
tokenNote: "Written to ~/.agend/.env and never shown again.",
|
|
60
|
+
tokenEnv: "Environment variable",
|
|
61
|
+
verify: "Verify",
|
|
62
|
+
verifying: "Asking the provider…",
|
|
63
|
+
verified: "verified",
|
|
64
|
+
rejected: "rejected",
|
|
65
|
+
guild: "Server (guild)",
|
|
66
|
+
general: "General channel id",
|
|
67
|
+
discordAdmin: "Your Discord user id",
|
|
68
|
+
telegramAdmin: "Your Telegram user id",
|
|
69
|
+
firstAllowed: "Becomes the first allowed user.",
|
|
70
|
+
group: "Group id",
|
|
71
|
+
groupHint: "Or post any message in the group and press Detect.",
|
|
72
|
+
detect: "Detect from a message",
|
|
73
|
+
detectWaiting: "Waiting for a message in the group…",
|
|
74
|
+
detectFound: "Found.",
|
|
75
|
+
detectNone: "No group message yet — post one and press Detect again.",
|
|
76
|
+
failed: "Failed.",
|
|
77
|
+
stepReview: "4 · Review and start",
|
|
78
|
+
preview: "Show what will be written",
|
|
79
|
+
finish: "Create and start AgEnD",
|
|
80
|
+
start: "Start AgEnD",
|
|
81
|
+
reopen: "Reopen it where you started setup: in the Windows installer, press Enter in its window; otherwise run agend setup again on the computer running AgEnD.",
|
|
82
|
+
closed: "This setup page has closed.",
|
|
83
|
+
closedFinish: "This setup page has closed. {0} Backends you already signed in stay signed in.",
|
|
84
|
+
closesIn: "This page closes in {0} at the latest (sooner after 10 minutes without activity).",
|
|
85
|
+
closesSoon: "{0} Finish soon. If it closes first: {1}",
|
|
86
|
+
approveHost: "Approve this on the computer running AgEnD. The Windows installer opens a confirmation window for it, and shows a command to paste if no window appears. Otherwise run there: agend settings confirm {0}",
|
|
87
|
+
confirmGone: "Setup confirmation is no longer available. Review and enter the token again.",
|
|
88
|
+
notApplied: "Change was not applied. Review and enter the token again.",
|
|
89
|
+
confirmed: "Configuration confirmed. Select Start AgEnD to finish setup.",
|
|
90
|
+
lostContact: "Lost contact with the setup page; still trying. If this stays, the page has closed. {0}",
|
|
91
|
+
finishSignins: "Finish backend sign-in or choose Sign in later before continuing.",
|
|
92
|
+
notCurrent: "Confirmation is no longer current.",
|
|
93
|
+
startingWeb: "AgEnD is starting. Its dashboard link shows here once it is up.",
|
|
94
|
+
startingChat: "AgEnD is starting. Continue in the channel you configured.",
|
|
95
|
+
requesting: "Requesting host confirmation…",
|
|
96
|
+
expectedPending: "Expected a pending host confirmation.",
|
|
97
|
+
connectionFailed: "Connection failed. Retry to find the same confirmation.",
|
|
98
|
+
upWeb: "AgEnD is up. Open its dashboard: {0} — sign in with the code from agend web --code on the machine running AgEnD. Sign your agents' CLIs in from Settings → Agents → Sign-ins.",
|
|
99
|
+
upChat: "AgEnD is up. Talk to it in the channel you just set up, or open its dashboard: {0} (sign in with agend web --code on the machine running AgEnD, or send /web to your bot).",
|
|
100
|
+
notUp: "AgEnD has not come up within 60 seconds. Run `agend start` on the machine itself and check fleet.log — this page cannot tell you any more.",
|
|
101
|
+
// A host confirmation's outcome (settings-confirmation.ts reason_code; the words of locale.ts settings_confirm.result.*).
|
|
102
|
+
outcome_apply_failed: "Change could not be applied. Review the configuration and try again.",
|
|
103
|
+
outcome_fleet_stopped: "Fleet stopped. Review and apply again.",
|
|
104
|
+
outcome_expired: "Confirmation expired. Review and apply again.",
|
|
105
|
+
outcome_withdrawn: "Change withdrawn.",
|
|
106
|
+
outcome_rejected: "Change rejected.",
|
|
107
|
+
outcome_request_no_longer_current: "The requesting session or fleet changed. Review and apply again.",
|
|
108
|
+
outcome_settings_changed: "Settings changed since this request. Review and apply again.",
|
|
109
|
+
},
|
|
110
|
+
"zh-TW": {
|
|
111
|
+
title: "設定 AgEnD",
|
|
112
|
+
otherLang: "English",
|
|
113
|
+
langTitle: "語言",
|
|
114
|
+
intro: "這個頁面只在設定進行時開啟。完成設定,或 15 分鐘後,它會自動關閉。",
|
|
115
|
+
signinsLabel: "AI 後端登入",
|
|
116
|
+
stepAgent: "1 · Agent",
|
|
117
|
+
backend: "後端",
|
|
118
|
+
backendFound: "在這台主機上找到:{0}",
|
|
119
|
+
backendNone: "這台主機上找不到任何後端 CLI —— 請先安裝一個。",
|
|
120
|
+
dir: "工作目錄(選填)",
|
|
121
|
+
dirPlaceholder: "/home/you/projects/app",
|
|
122
|
+
dirDefault: "留空的話,agent 會有自己的資料夾 {0},由 AgEnD 建立。若要填寫,必須是絕對路徑。",
|
|
123
|
+
dirDefaultName: "留空的話,agent 會在 AgEnD 的 workspaces 裡有自己的資料夾,由 AgEnD 建立。若要填寫,必須是絕對路徑。",
|
|
124
|
+
name: "第一個 agent 名稱",
|
|
125
|
+
stepPlatform: "2 · 平台",
|
|
126
|
+
webOnly: "只用網頁(不接 bot)",
|
|
127
|
+
hintTelegram: "用 @BotFather 建立 bot,然後把它加進一個群組。",
|
|
128
|
+
hintDiscord: "在 Discord 開發者後台建立應用程式,並把 bot 邀請到你的伺服器。",
|
|
129
|
+
hintWeb: "不接 bot:從 AgEnD 的網頁儀表板使用它(聊天、登入、設定)。之後可以在設定裡再加上 Discord 或 Telegram。",
|
|
130
|
+
stepCredentials: "3 · 憑證",
|
|
131
|
+
token: "Bot token",
|
|
132
|
+
tokenNote: "會寫入 ~/.agend/.env,之後不會再顯示。",
|
|
133
|
+
tokenEnv: "環境變數",
|
|
134
|
+
verify: "驗證",
|
|
135
|
+
verifying: "正在向平台確認…",
|
|
136
|
+
verified: "已驗證",
|
|
137
|
+
rejected: "被拒絕",
|
|
138
|
+
guild: "伺服器(guild)",
|
|
139
|
+
general: "General 頻道 id",
|
|
140
|
+
discordAdmin: "你的 Discord 使用者 ID",
|
|
141
|
+
telegramAdmin: "你的 Telegram 使用者 ID",
|
|
142
|
+
firstAllowed: "會成為第一個允許使用者。",
|
|
143
|
+
group: "群組 id",
|
|
144
|
+
groupHint: "或在群組裡發任一則訊息,然後按「從訊息偵測」。",
|
|
145
|
+
detect: "從訊息偵測",
|
|
146
|
+
detectWaiting: "等待群組訊息中…",
|
|
147
|
+
detectFound: "找到了。",
|
|
148
|
+
detectNone: "還沒收到群組訊息 —— 發一則之後再按一次。",
|
|
149
|
+
failed: "失敗。",
|
|
150
|
+
stepReview: "4 · 檢查並啟動",
|
|
151
|
+
preview: "顯示將寫入的內容",
|
|
152
|
+
finish: "建立並啟動 AgEnD",
|
|
153
|
+
start: "啟動 AgEnD",
|
|
154
|
+
reopen: "請在開始設定的地方重新開啟:Windows 安裝程式請在它的視窗按 Enter;否則在執行 AgEnD 的電腦上再執行一次 agend setup。",
|
|
155
|
+
closed: "這個設定頁已關閉。",
|
|
156
|
+
closedFinish: "這個設定頁已關閉。{0}已經登入的後端會維持登入。",
|
|
157
|
+
closesIn: "這個頁面最晚在 {0} 後關閉(閒置 10 分鐘會更早關閉)。",
|
|
158
|
+
closesSoon: "{0}請盡快完成。如果它先關閉了:{1}",
|
|
159
|
+
approveHost: "請在執行 AgEnD 的電腦上核准。Windows 安裝程式會為它開一個確認視窗;沒有出現視窗時,會顯示一行可以貼上的指令。否則請在那台電腦上執行:agend settings confirm {0}",
|
|
160
|
+
confirmGone: "設定確認已不存在。請重新檢查並再輸入一次 token。",
|
|
161
|
+
notApplied: "變更沒有套用。請重新檢查並再輸入一次 token。",
|
|
162
|
+
confirmed: "設定已確認。請按「啟動 AgEnD」完成設定。",
|
|
163
|
+
lostContact: "與設定頁失去連線,仍在重試。如果一直如此,表示頁面已關閉。{0}",
|
|
164
|
+
finishSignins: "請先完成後端登入,或選擇「稍後登入」再繼續。",
|
|
165
|
+
notCurrent: "這個確認已不是最新的。",
|
|
166
|
+
startingWeb: "AgEnD 正在啟動。啟動後,儀表板連結會顯示在這裡。",
|
|
167
|
+
startingChat: "AgEnD 正在啟動。請到你設定的頻道繼續。",
|
|
168
|
+
requesting: "正在請主機確認…",
|
|
169
|
+
expectedPending: "預期會有一個等待主機確認的變更。",
|
|
170
|
+
connectionFailed: "連線失敗。重試會找回同一個確認。",
|
|
171
|
+
upWeb: "AgEnD 已啟動。打開它的儀表板:{0} —— 用執行 AgEnD 的電腦上 agend web --code 顯示的代碼登入。agent 的 CLI 請在 設定 → Agents → 登入 裡登入。",
|
|
172
|
+
upChat: "AgEnD 已啟動。在你剛設定的頻道和它對話,或打開它的儀表板:{0}(在執行 AgEnD 的電腦上用 agend web --code 登入,或對你的 bot 傳 /web)。",
|
|
173
|
+
notUp: "AgEnD 在 60 秒內沒有啟動。請在那台電腦上執行 `agend start` 並查看 fleet.log —— 這個頁面無法告訴你更多。",
|
|
174
|
+
outcome_apply_failed: "變更無法套用。請檢查設定後再試一次。",
|
|
175
|
+
outcome_fleet_stopped: "Fleet 已停止。請重新檢查後再套用。",
|
|
176
|
+
outcome_expired: "確認已過期。請重新檢查後再套用。",
|
|
177
|
+
outcome_withdrawn: "變更已撤回。",
|
|
178
|
+
outcome_rejected: "變更已被拒絕。",
|
|
179
|
+
outcome_request_no_longer_current: "提出請求的工作階段或 fleet 已改變。請重新檢查後再套用。",
|
|
180
|
+
outcome_settings_changed: "提出請求後設定已被修改。請重新檢查後再套用。",
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
/** #2726: the host's words and codes in both languages — one module, shared with the dashboard (pasted in below). */
|
|
184
|
+
const HOST_TEXT = readFileSync(new URL("./ui/shared/setup-host-text.js", import.meta.url), "utf8").replace(/^export /gm, "");
|
|
185
|
+
/**
|
|
186
|
+
* The few lines both pages run first: the dictionary (JSON, `<` escaped so no string can end the script), the language,
|
|
187
|
+
* `tr` (a key in it), `trHost` (a host message in it), `show` (a text that follows the language when it changes) and
|
|
188
|
+
* `applyLang`. Plain script, no `${` and no template literal: it is pasted into the pages' own template strings.
|
|
189
|
+
*/
|
|
190
|
+
export function setupI18nRuntime(strings) {
|
|
191
|
+
const tables = JSON.stringify({ en: strings.en, "zh-TW": strings["zh-TW"] }).replace(/</g, "\\u003c");
|
|
192
|
+
return "const I18N = " + tables + ";\n" + HOST_TEXT + String.raw `
|
|
193
|
+
let LANG = (() => {
|
|
194
|
+
try { const chosen = localStorage.getItem("agend_lang"); if (chosen === "en" || chosen === "zh-TW") return chosen; } catch { /* none kept */ }
|
|
195
|
+
return typeof navigator !== "undefined" && /^zh/i.test(navigator.language || "") ? "zh-TW" : "en";
|
|
196
|
+
})();
|
|
197
|
+
const tr = (key, ...values) => {
|
|
198
|
+
let text = I18N[LANG][key] ?? I18N.en[key] ?? key;
|
|
199
|
+
values.forEach((value, i) => { text = text.split("{" + i + "}").join(String(value)); });
|
|
200
|
+
return text;
|
|
201
|
+
};
|
|
202
|
+
// The host's own words or code in this page's language (setup-host-text.js); anything else as it came.
|
|
203
|
+
const trHost = (text) => hostText(text, LANG);
|
|
204
|
+
// A text that is redrawn in the new language when it changes: its last render per element.
|
|
205
|
+
const shown = new Map();
|
|
206
|
+
const show = (element, render) => { if (!element) return; shown.set(element, render); render(element); };
|
|
207
|
+
const applyLang = () => {
|
|
208
|
+
if (document.documentElement) document.documentElement.lang = LANG === "zh-TW" ? "zh-Hant" : "en";
|
|
209
|
+
document.title = tr("title");
|
|
210
|
+
for (const node of document.querySelectorAll("[data-i18n]")) { const key = node.getAttribute && node.getAttribute("data-i18n"); if (key) node.textContent = tr(key); }
|
|
211
|
+
for (const node of document.querySelectorAll("[data-i18n-placeholder]")) { const key = node.getAttribute && node.getAttribute("data-i18n-placeholder"); if (key) node.placeholder = tr(key); }
|
|
212
|
+
for (const node of document.querySelectorAll("[data-i18n-label]")) { const key = node.getAttribute && node.getAttribute("data-i18n-label"); if (key) node.setAttribute("aria-label", tr(key)); }
|
|
213
|
+
for (const [element, render] of shown) render(element);
|
|
214
|
+
};
|
|
215
|
+
const langButton = document.getElementById("lang");
|
|
216
|
+
if (langButton) langButton.onclick = () => {
|
|
217
|
+
LANG = LANG === "zh-TW" ? "en" : "zh-TW";
|
|
218
|
+
try { localStorage.setItem("agend_lang", LANG); } catch { /* this page only */ }
|
|
219
|
+
applyLang();
|
|
220
|
+
};
|
|
221
|
+
applyLang();
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=setup-i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-i18n.js","sourceRoot":"","sources":["../src/setup-i18n.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// one runs, on which device, and may cancel it. The stream's `backend_login` event carries nothing — it only says to
|
|
7
7
|
// read again. Starting is two steps, as in chat: check (the server keeps what it found) and start (with its id).
|
|
8
8
|
import { html, useEffect, useState, useRef } from "/assets/app-html.js";
|
|
9
|
-
import { t, register } from "/assets/app-i18n.js";
|
|
9
|
+
import { t, register, lang, onLang } from "/assets/app-i18n.js";
|
|
10
10
|
import { appStore, createStore, useStore } from "/assets/app-store.js";
|
|
11
11
|
import { closeDialog, shellStore, showDialog } from "/assets/app-shell.js";
|
|
12
12
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
@@ -217,13 +217,14 @@ export function BrowserSignInDialog({ backend, backends, onClose }) {
|
|
|
217
217
|
const root = useRef(null), close = onClose || closeDialog;
|
|
218
218
|
useEffect(() => {
|
|
219
219
|
let cards, last = "";
|
|
220
|
-
cards = mountStructuredSignIns(root.current, { endpoint: "/ui/browser-backend-signins", changed: next => {
|
|
220
|
+
cards = mountStructuredSignIns(root.current, { endpoint: "/ui/browser-backend-signins", lang, changed: next => {
|
|
221
221
|
if (backends) { if (backends.some(b => !next.selected.includes(b))) void cards.addBackends(backends); }
|
|
222
222
|
else if (!next.selected.includes(backend)) void cards.addBackend(backend);
|
|
223
223
|
const signature = JSON.stringify([next.login, next.pending]);
|
|
224
224
|
if (signature !== last) { last = signature; void refreshSignIn(); }
|
|
225
225
|
} });
|
|
226
|
-
|
|
226
|
+
const unlisten = onLang(() => cards.relabel());
|
|
227
|
+
return () => { unlisten(); cards.dispose(); };
|
|
227
228
|
}, [backend, backends]);
|
|
228
229
|
const fallback = async (chosen = backend) => {
|
|
229
230
|
const result = await post("/ui/browser-backend-signins", { operation: "cancel" });
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// - Not the Apply: confirming hands the staged changes to the app's runner (settings-apply.js) before the first write.
|
|
10
10
|
// This panel renders that operation from the store, and finds it again when it mounts.
|
|
11
11
|
import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
12
|
-
import { t } from "/assets/app-i18n.js";
|
|
12
|
+
import { t, lang } from "/assets/app-i18n.js";
|
|
13
|
+
import { hostText } from "/assets/setup-host-text.js";
|
|
13
14
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
14
15
|
import { useLease } from "/assets/app-ctx.js";
|
|
15
16
|
import { DevicePrefs, PanelHeader, PendingChanges, setTitle } from "/assets/app-shell.js";
|
|
@@ -55,7 +56,8 @@ const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
|
55
56
|
/** What a refused change says to a person: the server's code in words (the code itself only for one not listed). */
|
|
56
57
|
const APPLY_ERRORS = new Set(["unsupported_sensitive_effect", "confirmation_diff_too_large", "session_required", "admin_required", "settings_changed",
|
|
57
58
|
"request_no_longer_current", "too_many_pending_changes", "fleet_unavailable", "invalid_settings_effect", "settings_confirmation_unavailable", "failed"]);
|
|
58
|
-
|
|
59
|
+
// #2726: a code or words of the setup host (the wizard's commit) in the page's language; anything else as it came.
|
|
60
|
+
export const applyErrorText = (code) => (APPLY_ERRORS.has(code) ? tn(`applyError_${code}`) : tn("applyErrorOther", hostText(String(code), lang())));
|
|
59
61
|
const ICONS = { preferences: "user", look: "palette", agents: "bot", "agent-defaults": "sliders", chat: "chat", "web-dashboard": "globe", access: "key", advanced: "code" };
|
|
60
62
|
const RELEASES = "https://github.com/songsid/AgEnD/releases";
|
|
61
63
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// Stop, the 2-minute limit or leaving the page each retire what was asked before — an answer about A never lands after
|
|
9
9
|
// B, nor after A again (an older A), nor once the page is gone.
|
|
10
10
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
11
|
-
import { t } from "/assets/app-i18n.js";
|
|
11
|
+
import { t, lang } from "/assets/app-i18n.js";
|
|
12
|
+
import { hostText } from "/assets/setup-host-text.js";
|
|
12
13
|
import { useTypedText } from "/assets/text-undo.js";
|
|
13
14
|
import { api } from "./settings-confirm.js";
|
|
14
15
|
import { Select } from "./settings-dialogs.js";
|
|
@@ -129,6 +130,6 @@ export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guil
|
|
|
129
130
|
${channels && channels.list && channels.list.length
|
|
130
131
|
? html`<${Select} id=${`${idPrefix}-gen`} value=${channel} onChange=${pickChannel} options=${["", ...channels.list.map((c) => ({ value: c.id, label: `#${c.name}` }))]} />`
|
|
131
132
|
: html`<input id=${`${idPrefix}-gen`} type="text" value=${channelText} onInput=${(e) => pickChannel(onChannelText(e.target.value))} />`}
|
|
132
|
-
${channels && channels.error ? html`<p class="feedback warning">${channels.error}</p>` : null}</div>
|
|
133
|
+
${channels && channels.error ? html`<p class="feedback warning">${hostText(channels.error, lang())}</p>` : null}</div>
|
|
133
134
|
</div>`;
|
|
134
135
|
}
|
|
@@ -193,7 +193,10 @@ register("settings", {
|
|
|
193
193
|
wizardAgentNewName: "New agent name",
|
|
194
194
|
wizardTopicNotCarried: "Its topic ({0}) is in the chat of its old connection, so it is not carried over: in the new chat it has no topic until one is bound.",
|
|
195
195
|
wizardTopicGeneral: "It is the General agent: in the new chat it takes that chat's General topic.",
|
|
196
|
-
wizardNeedDir: "
|
|
196
|
+
wizardNeedDir: "A working directory must be an absolute path, or leave it empty.",
|
|
197
|
+
wizardDir: "Working directory (optional)",
|
|
198
|
+
wizardDirDefault: "Leave it empty and the agent gets its own folder, {0}, which AgEnD creates. A path you enter must be absolute.",
|
|
199
|
+
wizardDirDefaultName: "Leave it empty and the agent gets its own folder in AgEnD's workspaces, which AgEnD creates. A path you enter must be absolute.",
|
|
197
200
|
wizardTelegramHint: "Create a bot with @BotFather, then add it to a group.",
|
|
198
201
|
wizardDiscordHint: "Create an application in the Discord developer portal and invite the bot to your server.",
|
|
199
202
|
wizardToken: "Bot token",
|
|
@@ -843,7 +846,10 @@ register("settings", {
|
|
|
843
846
|
wizardAgentNewName: "新 agent 名稱",
|
|
844
847
|
wizardTopicNotCarried: "它的 topic({0})屬於舊連線的聊天室,不會帶過去:在新的聊天室裡,綁定新的 topic 之前它沒有 topic。",
|
|
845
848
|
wizardTopicGeneral: "它是 General agent:在新的聊天室裡會改用那裡的 General topic。",
|
|
846
|
-
wizardNeedDir: "
|
|
849
|
+
wizardNeedDir: "工作目錄必須是絕對路徑,或留空。",
|
|
850
|
+
wizardDir: "工作目錄(選填)",
|
|
851
|
+
wizardDirDefault: "留空的話,agent 會有自己的資料夾 {0},由 AgEnD 建立。若要填寫,必須是絕對路徑。",
|
|
852
|
+
wizardDirDefaultName: "留空的話,agent 會在 AgEnD 的 workspaces 裡有自己的資料夾,由 AgEnD 建立。若要填寫,必須是絕對路徑。",
|
|
847
853
|
wizardTelegramHint: "用 @BotFather 建立 bot,然後把它加進一個群組。",
|
|
848
854
|
wizardDiscordHint: "在 Discord 開發者後台建立應用程式,並把 bot 邀請到你的伺服器。",
|
|
849
855
|
wizardToken: "Bot token",
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// lives in this field's state only: never in storage, a URL, a label or the page's other state, and the server never
|
|
5
5
|
// hands it back — afterwards a connection shows "Token set · @bot".
|
|
6
6
|
import { html, useState } from "/assets/app-html.js";
|
|
7
|
-
import { t } from "/assets/app-i18n.js";
|
|
7
|
+
import { t, lang } from "/assets/app-i18n.js";
|
|
8
|
+
import { hostText } from "/assets/setup-host-text.js";
|
|
8
9
|
import { useTypedText } from "/assets/text-undo.js";
|
|
9
10
|
import { Icon } from "/assets/ui-icons.js";
|
|
10
11
|
import { api } from "./settings-confirm.js";
|
|
@@ -24,7 +25,7 @@ export async function verifyBotToken(platform, token) {
|
|
|
24
25
|
.catch(() => ({ ok: false, body: {} }));
|
|
25
26
|
// A Discord bot's invite and portal page come with it (#1519 P3).
|
|
26
27
|
if (res.ok && res.body && res.body.identity) return { ...res.body.identity, invite: res.body.invite_url || null, portal: res.body.portal_url || null };
|
|
27
|
-
return { valid: false, reason: (res.body && res.body.error) || tn("verifyFailed") };
|
|
28
|
+
return { valid: false, reason: hostText(res.body && res.body.error, lang()) || tn("verifyFailed") };
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/** A bot's name as it is addressed: @name, once. */
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// to the ordinary Apply: the app's runner (settings-apply.js) posts the apply and watches its job, with the same
|
|
4
4
|
// progress, deadline and restart recovery as every other change.
|
|
5
5
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
6
|
-
import { t } from "/assets/app-i18n.js";
|
|
6
|
+
import { t, lang } from "/assets/app-i18n.js";
|
|
7
|
+
import { hostText } from "/assets/setup-host-text.js";
|
|
7
8
|
import { useTypedText } from "/assets/text-undo.js";
|
|
8
9
|
import { useLease } from "/assets/app-ctx.js";
|
|
9
10
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
@@ -16,6 +17,8 @@ import { DiscordServerPicker } from "./settings-discord.js";
|
|
|
16
17
|
|
|
17
18
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
18
19
|
const STEPS = 4;
|
|
20
|
+
// #2726: the host's words (English) or code, in the page's language.
|
|
21
|
+
const said = (text) => hostText(text, lang());
|
|
19
22
|
const post = (path, body) => api(path, { method: "POST", body: JSON.stringify(body) }).catch(() => ({ ok: false, status: 0, body: { error: tn("failed") } }));
|
|
20
23
|
|
|
21
24
|
export function SetupWizard({ ctx, onClose }) {
|
|
@@ -83,14 +86,15 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
83
86
|
if (!lease.current()) return;
|
|
84
87
|
if (rev.current !== at) { setBusy(""); return; }
|
|
85
88
|
setBusy("");
|
|
86
|
-
if (!res.ok) { setErr((res.body && res.body.error) || tn("applyFailed")); return; }
|
|
89
|
+
if (!res.ok) { setErr(said(res.body && res.body.error) || tn("applyFailed")); return; }
|
|
87
90
|
if (res.body.found) setW((x) => ({ ...x, offset: res.body.offset, group_id: String(res.body.found.groupId), admin_user_id: String(res.body.found.userId) }));
|
|
88
91
|
else { setW((x) => ({ ...x, offset: res.body.offset })); setErr(tn("wizardNoStartYet")); }
|
|
89
92
|
};
|
|
90
93
|
const next = async () => {
|
|
91
94
|
setErr("");
|
|
92
95
|
if (w.step === 1) {
|
|
93
|
-
|
|
96
|
+
// #2726: none is the agent's own workspace (the server fills it in); one that is given is absolute.
|
|
97
|
+
if (w.agent !== "existing" && w.working_directory && !w.working_directory.startsWith("/")) { setErr(tn("wizardNeedDir")); return; }
|
|
94
98
|
setW({ ...w, step: 2 }); return;
|
|
95
99
|
}
|
|
96
100
|
if (w.step === 2) { setW({ ...w, step: 3 }); return; }
|
|
@@ -104,7 +108,7 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
104
108
|
if (!lease.current()) return;
|
|
105
109
|
setBusy("");
|
|
106
110
|
if (rev.current !== at) return;
|
|
107
|
-
if (!res.ok) { setErr((res.body && res.body.error) || tn("applyFailed")); return; }
|
|
111
|
+
if (!res.ok) { setErr(said(res.body && res.body.error) || tn("applyFailed")); return; }
|
|
108
112
|
setW((x) => ({ ...x, plan: res.body, step: 4 }));
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
@@ -140,7 +144,8 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
140
144
|
<p class="note">${tn("wizardAgentExistingNote")}</p></div>` : html`${which}<div class="field"><label for="wz-be">${tn("wizardBackend")}</label>
|
|
141
145
|
<${Select} id="wz-be" value=${w.backend} onChange=${set("backend")} options=${w.env.backends && w.env.backends.length ? w.env.backends : BACKENDS} />
|
|
142
146
|
<p class="note">${w.env.backends && w.env.backends.length ? tn("wizardBackendFound", w.env.backends.join(", ")) : tn("wizardBackendNone")}</p></div>
|
|
143
|
-
<div class="field"><label for="wz-wd">${tn("
|
|
147
|
+
<div class="field"><label for="wz-wd">${tn("wizardDir")}</label><input id="wz-wd" type="text" placeholder="/home/you/projects/app" aria-describedby="wz-wd-note" value=${dirText} onInput=${(e) => set("working_directory")(onDirText(e.target.value))} />
|
|
148
|
+
<p class="note" id="wz-wd-note">${w.env.workspaces_dir && w.instance_name ? tn("wizardDirDefault", `${w.env.workspaces_dir}/${w.instance_name}`) : tn("wizardDirDefaultName")}</p></div>
|
|
144
149
|
<div class="field"><label for="wz-name">${agents.length ? tn("wizardAgentNewName") : tn("wizardAgentName")}</label><input id="wz-name" type="text" value=${nameText} onInput=${(e) => set("instance_name")(onNameText(e.target.value))} /></div>`;
|
|
145
150
|
} else if (w.step === 2) {
|
|
146
151
|
body = html`<div class="seg-inline" role="group" aria-label=${tn("wizardPlatform")}>
|
|
@@ -169,7 +174,7 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
169
174
|
${w.plan.instance && w.plan.instance.topic ? html`<p class="feedback warning">${w.plan.instance.topic.to == null
|
|
170
175
|
? tn("wizardTopicNotCarried", w.plan.instance.topic.from) : tn("wizardTopicGeneral")}</p>` : null}
|
|
171
176
|
<${Drawer} title=${tn("advancedSection")}><${TokenEnvNote} name=${w.plan.token_env} /></${Drawer}>
|
|
172
|
-
${(w.plan.warnings || []).map((x, i) => html`<p key=${i} class="feedback warning">${x}</p>`)}`;
|
|
177
|
+
${(w.plan.warnings || []).map((x, i) => html`<p key=${i} class="feedback warning">${said(x)}</p>`)}`;
|
|
173
178
|
}
|
|
174
179
|
return html`<${Dialog} title=${tn("wizardTitle")} onClose=${onClose} wide=${true} busy=${busy === tn("wizardCommitting")}
|
|
175
180
|
actions=${html`<button type="button" class="btn" disabled=${w.step === 1 || !!busy} onClick=${() => setW({ ...w, step: w.step - 1 })}>${tn("wizardBack")}</button>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// #2726: what the setup host and the wizard's routes answer with — English words, or a code — in English and 繁體中文.
|
|
2
|
+
// One table for every page that shows them: the setup pages (src/setup-i18n.ts pastes this module into them, before any
|
|
3
|
+
// fleet, with its `export`s taken out) and the dashboard's Setup wizard and Apply (/assets/setup-host-text.js). Plain
|
|
4
|
+
// script with no imports and no closing script tag, so it can be pasted as it is.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The host's English words → 繁體中文. `{0}`, `{1}` are the parts that vary (a name, a count). Kept to the exact wording
|
|
8
|
+
* of the host (setup-host.ts, quickstart-api.ts, token-env-name.ts, the provider probes); tests/setup-i18n-2726.test.ts
|
|
9
|
+
* drives each source and checks it is here.
|
|
10
|
+
*/
|
|
11
|
+
export const HOST_MESSAGES = [
|
|
12
|
+
// The code page (setup-host.ts redeem / afterFailure / the cookie gate).
|
|
13
|
+
["incorrect — {0} attempts left", "代碼不正確 —— 還可以再試 {0} 次"],
|
|
14
|
+
["incorrect — {0} attempt left", "代碼不正確 —— 還可以再試 {0} 次"],
|
|
15
|
+
["too many failed attempts — run `agend setup` again on the host", "錯誤次數太多 —— 請在主機上再執行一次 `agend setup`"],
|
|
16
|
+
["enter the setup code", "請輸入設定代碼"],
|
|
17
|
+
["enter the setup code first", "請先輸入設定代碼"],
|
|
18
|
+
["cross-site request rejected", "已拒絕跨站請求"],
|
|
19
|
+
["not found", "找不到"],
|
|
20
|
+
// Finishing (setup-host.ts finishSetup / status).
|
|
21
|
+
["Setup changed; review and confirm again.", "設定已改變;請重新檢查並再確認一次。"],
|
|
22
|
+
["Setup confirmation unavailable.", "無法取得設定確認。"],
|
|
23
|
+
// The wizard's validation (quickstart-api.ts validateWizardInput, validateWebOnlyInput).
|
|
24
|
+
["working_directory must be an absolute path, or empty for the agent's own workspace", "工作目錄必須是絕對路徑,或留空讓 agent 使用自己的資料夾"],
|
|
25
|
+
["instance_name must be [A-Za-z0-9._-], and not only dots", "agent 名稱只能用英文字母、數字和 . _ -,而且不能只有句點"],
|
|
26
|
+
["backend must be a known backend", "請選擇一個支援的後端"],
|
|
27
|
+
["{0} does not apply to a web-only setup", "只用網頁的設定不使用 {0}"],
|
|
28
|
+
["platform must be telegram or discord", "平台必須是 Telegram 或 Discord"],
|
|
29
|
+
["token_env must be an UPPER_SNAKE env var name", "環境變數名稱只能用大寫英文字母、數字和底線(例如 AGEND_BOT_TOKEN)"],
|
|
30
|
+
// A specific wording before the general one it would also match (the first that matches is used).
|
|
31
|
+
["guild id must be numeric", "伺服器 id 必須是數字"],
|
|
32
|
+
["{0} must be numeric", "{0} 必須是數字"],
|
|
33
|
+
["Your Discord user ID is required: enter 17–20 digits, without a leading zero.", "Discord 使用者 ID 必填:請輸入 17–20 位數字,開頭不能是 0。"],
|
|
34
|
+
["Your Telegram user ID is required: enter 1–20 positive digits, without a leading zero.", "Telegram 使用者 ID 必填:請輸入 1–20 位正整數字,開頭不能是 0。"],
|
|
35
|
+
// Conflicts (agentConflict, newConnectionConflict, webOnlyConflict, token-env-name.ts).
|
|
36
|
+
["an agent called \"{0}\" already exists — connect it as an existing agent, or choose another name", "已經有名為「{0}」的 agent —— 請把它當作既有的 agent 連接,或換一個名稱"],
|
|
37
|
+
["there is no agent called \"{0}\"", "沒有名為「{0}」的 agent"],
|
|
38
|
+
["a connection called \"{0}\" already exists — a new connection never replaces one", "已經有名為「{0}」的連線 —— 新的連線不會取代既有的連線"],
|
|
39
|
+
["this fleet already has a chat connection — add the agent with New agent instead", "這個 fleet 已經有聊天連線 —— 請改用「新增 Agent」加入 agent"],
|
|
40
|
+
["{0} is reserved", "{0} 是保留名稱"],
|
|
41
|
+
["{0} already holds the token of the \"{1}\" connection — a new connection never replaces one; use Replace token on that connection", "{0} 已經存著「{1}」連線的 token —— 新的連線不會取代它;請在那個連線上用「更換 token」"],
|
|
42
|
+
["{0} is no longer free (it was taken after this setup was planned) — plan it again", "{0} 已被使用(在這次設定規劃之後)—— 請重新產生一次"],
|
|
43
|
+
["the data dir's .env cannot be read: {0}", "無法讀取資料目錄的 .env:{0}"],
|
|
44
|
+
// Requests and writes (quickstart-api.ts handlers).
|
|
45
|
+
["token required", "請輸入 bot token"],
|
|
46
|
+
["invalid JSON", "請求內容不是有效的 JSON"],
|
|
47
|
+
["bad request", "請求無效"],
|
|
48
|
+
["unknown probe", "不支援的檢查"],
|
|
49
|
+
["fleet not loaded", "設定尚未載入"],
|
|
50
|
+
["another secret operation is still running", "另一個寫入 token 的操作還在進行中"],
|
|
51
|
+
["setup cleanup failed; inspect configuration on the host", "設定的復原失敗;請在主機上檢查設定"],
|
|
52
|
+
["setup was not committed", "設定沒有寫入"],
|
|
53
|
+
["Token verification is not permitted over a public link", "公開連結不能驗證 token"],
|
|
54
|
+
["This bot token is already being polled by the running fleet. Enter the group id by hand, or stop AgEnD first.", "執行中的 fleet 正在使用這個 bot token。請手動輸入群組 id,或先停止 AgEnD。"],
|
|
55
|
+
["Another process is already reading this bot's updates. Stop it, or enter the group id by hand.", "另一個程式正在讀取這個 bot 的訊息。請停止它,或手動輸入群組 id。"],
|
|
56
|
+
["Discord replied {0}", "Discord 回應 {0}"],
|
|
57
|
+
["Telegram replied {0}", "Telegram 回應 {0}"],
|
|
58
|
+
// The plan's warning (quickstart-api.ts planQuickstart and the web-only plan).
|
|
59
|
+
["{0} was not found on this host; the agent will fail to start until it is installed.", "這台主機上找不到 {0};安裝之前,agent 會無法啟動。"],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** What the host answers with a code instead of words (the confirmation gate, settings-effect.ts; the finish step). */
|
|
63
|
+
export const HOST_CODES = {
|
|
64
|
+
en: {
|
|
65
|
+
invalid_quickstart: "This setup is not valid. Review it and try again.",
|
|
66
|
+
connection_exists: "That connection already exists — a new connection never replaces one.",
|
|
67
|
+
agent_conflict: "That agent name is taken, or that agent does not exist.",
|
|
68
|
+
too_many_pending_changes: "Too many changes are waiting for confirmation. Deal with them on the host first.",
|
|
69
|
+
idempotency_key_reused: "This request was already sent with different content. Review it and send it again.",
|
|
70
|
+
settings_confirmation_unavailable: "Host confirmation is not available right now. Try again shortly.",
|
|
71
|
+
session_required: "This setup session has ended. Reopen setup on the host.",
|
|
72
|
+
invalid_json: "The request was not valid JSON.",
|
|
73
|
+
invalid_settings_body: "The request was not valid.",
|
|
74
|
+
signin_cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
|
|
75
|
+
},
|
|
76
|
+
"zh-TW": {
|
|
77
|
+
invalid_quickstart: "這份設定無效。請檢查後再試一次。",
|
|
78
|
+
connection_exists: "這個連線已存在 —— 新的連線不會取代既有的連線。",
|
|
79
|
+
agent_conflict: "這個 agent 名稱已被使用,或那個 agent 不存在。",
|
|
80
|
+
too_many_pending_changes: "等待確認的變更太多。請先在主機上處理它們。",
|
|
81
|
+
idempotency_key_reused: "這個請求已經用不同的內容送出過。請重新檢查後再送出。",
|
|
82
|
+
settings_confirmation_unavailable: "目前無法要求主機確認。請稍後再試。",
|
|
83
|
+
session_required: "這個設定工作階段已結束。請在主機上重新開啟設定。",
|
|
84
|
+
invalid_json: "請求內容不是有效的 JSON。",
|
|
85
|
+
invalid_settings_body: "請求無效。",
|
|
86
|
+
signin_cleanup_failed: "無法確認登入程序已關閉。請在主機上關閉它,並重新開始設定後再試。",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const patterns = HOST_MESSAGES.map(([en, zh]) => ({
|
|
91
|
+
zh, holes: [...en.matchAll(/\{(\d)\}/g)].map((m) => m[1]),
|
|
92
|
+
pattern: new RegExp("^" + en.split(/\{\d\}/).map((part) => part.replace(/[.*+?^$|()[\]{}\\]/g, "\\$&")).join("(.+?)") + "$"),
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
/** The host's words, or its code, in `lang` ("en" | "zh-TW"); anything it does not know stays as it came. */
|
|
96
|
+
export function hostText(text, lang) {
|
|
97
|
+
if (typeof text !== "string") return text;
|
|
98
|
+
if (Object.hasOwn(HOST_CODES.en, text)) return (HOST_CODES[lang] || HOST_CODES.en)[text];
|
|
99
|
+
if (lang !== "zh-TW") return text;
|
|
100
|
+
for (const { zh, holes, pattern } of patterns) {
|
|
101
|
+
const found = pattern.exec(text);
|
|
102
|
+
if (found) { let out = zh; holes.forEach((hole, i) => { out = out.split("{" + hole + "}").join(found[i + 1]); }); return out; }
|
|
103
|
+
}
|
|
104
|
+
return text;
|
|
105
|
+
}
|