@whoz-oss/coday-web 0.13.3 → 0.15.0
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/client/app.js +321 -8
- package/client/app.js.map +4 -4
- package/client/chat-history/chat-history.css +58 -0
- package/client/styles/main.css +30 -0
- package/package.json +1 -1
- package/server/server.js +25708 -24804
- package/server/server.js.map +4 -4
package/client/app.js
CHANGED
|
@@ -114,7 +114,7 @@ var ToolRequestEvent = class _ToolRequestEvent extends CodayEvent {
|
|
|
114
114
|
this.length = this.args.length + this.name.length + this.toolRequestId.length + 20;
|
|
115
115
|
}
|
|
116
116
|
buildResponse(output) {
|
|
117
|
-
return new ToolResponseEvent({ output, toolRequestId: this.toolRequestId });
|
|
117
|
+
return new ToolResponseEvent({ output, toolRequestId: this.toolRequestId, parentKey: this.timestamp });
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* Renders the tool request as a single line string with truncation
|
|
@@ -134,7 +134,33 @@ var ToolResponseEvent = class _ToolResponseEvent extends CodayEvent {
|
|
|
134
134
|
super(event, _ToolResponseEvent.type);
|
|
135
135
|
this.toolRequestId = event.toolRequestId || this.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
136
136
|
this.output = event.output;
|
|
137
|
-
|
|
137
|
+
if (typeof this.output === "string") {
|
|
138
|
+
this.length = this.output.length + this.toolRequestId.length + 20;
|
|
139
|
+
} else {
|
|
140
|
+
if (this.output.type === "text") {
|
|
141
|
+
this.length = this.output.content.length + this.toolRequestId.length + 20;
|
|
142
|
+
} else if (this.output.type === "image") {
|
|
143
|
+
const tokens = (this.output.width ?? 0) * (this.output.height ?? 0) / 750;
|
|
144
|
+
this.length = (tokens ? tokens * 3.5 : this.output.content.length) + this.toolRequestId.length + 20;
|
|
145
|
+
} else {
|
|
146
|
+
this.length = this.toolRequestId.length + 20;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get the text content as a string for backward compatibility
|
|
152
|
+
*/
|
|
153
|
+
getTextOutput() {
|
|
154
|
+
if (typeof this.output === "string") {
|
|
155
|
+
return this.output;
|
|
156
|
+
}
|
|
157
|
+
if (this.output.type === "text") {
|
|
158
|
+
return this.output.content;
|
|
159
|
+
}
|
|
160
|
+
if (this.output.type === "image") {
|
|
161
|
+
return `[Image: ${this.output.mimeType}]`;
|
|
162
|
+
}
|
|
163
|
+
return "";
|
|
138
164
|
}
|
|
139
165
|
/**
|
|
140
166
|
* Renders the tool response as a single line string with truncation
|
|
@@ -142,8 +168,10 @@ var ToolResponseEvent = class _ToolResponseEvent extends CodayEvent {
|
|
|
142
168
|
* @returns A formatted string representation
|
|
143
169
|
*/
|
|
144
170
|
toSingleLineString(maxLength = 50) {
|
|
145
|
-
const
|
|
146
|
-
|
|
171
|
+
const textOutput = this.getTextOutput();
|
|
172
|
+
const truncatedOutput = truncateText(textOutput, maxLength);
|
|
173
|
+
const imageIndicator = typeof this.output !== "string" && this.output.type === "image" ? " [image]" : "";
|
|
174
|
+
return `\u2B91 ${truncatedOutput}${imageIndicator}`;
|
|
147
175
|
}
|
|
148
176
|
};
|
|
149
177
|
var ProjectSelectedEvent = class _ProjectSelectedEvent extends CodayEvent {
|
|
@@ -171,7 +199,19 @@ var MessageEvent = class _MessageEvent extends CodayEvent {
|
|
|
171
199
|
this.role = event.role;
|
|
172
200
|
this.name = event.name;
|
|
173
201
|
this.content = event.content;
|
|
174
|
-
this.length = this.content.
|
|
202
|
+
this.length = this.content.map((content) => {
|
|
203
|
+
if (content.type === "text") {
|
|
204
|
+
return content.content.length;
|
|
205
|
+
}
|
|
206
|
+
if (content.type === "image") {
|
|
207
|
+
const tokens = (content.width || 0) * (content.height || 0) / 750;
|
|
208
|
+
return tokens ? tokens * 3.5 : content.content.length;
|
|
209
|
+
}
|
|
210
|
+
return 0;
|
|
211
|
+
}).reduce((sum, length) => sum + length, 0);
|
|
212
|
+
}
|
|
213
|
+
getTextContent() {
|
|
214
|
+
return this.content.filter((c) => c.type === "text").map((c) => c.content).join("\n");
|
|
175
215
|
}
|
|
176
216
|
};
|
|
177
217
|
var eventTypeToClassMap = {
|
|
@@ -222,7 +262,7 @@ var SpeechToTextareaComponent = class {
|
|
|
222
262
|
this.chatTextarea = chatTextarea;
|
|
223
263
|
this.submitButton = submitButton;
|
|
224
264
|
this.initializeVoiceInput();
|
|
225
|
-
window.addEventListener("voiceLanguageChanged", (
|
|
265
|
+
window.addEventListener("voiceLanguageChanged", (_event) => {
|
|
226
266
|
this.updateRecognitionLanguage();
|
|
227
267
|
});
|
|
228
268
|
}
|
|
@@ -719,6 +759,71 @@ var ChoiceSelectComponent = class {
|
|
|
719
759
|
}
|
|
720
760
|
};
|
|
721
761
|
|
|
762
|
+
// apps/web/client/image-upload/image-upload-handler.ts
|
|
763
|
+
var ImageUploadHandler = class {
|
|
764
|
+
constructor(clientId2) {
|
|
765
|
+
this.clientId = clientId2;
|
|
766
|
+
}
|
|
767
|
+
maxFileSize = 5 * 1024 * 1024;
|
|
768
|
+
// 5MB
|
|
769
|
+
supportedTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
770
|
+
/**
|
|
771
|
+
* Validates an image file
|
|
772
|
+
*/
|
|
773
|
+
validateFile(file) {
|
|
774
|
+
if (!this.supportedTypes.includes(file.type)) {
|
|
775
|
+
throw new Error(`Unsupported file type: ${file.type}`);
|
|
776
|
+
}
|
|
777
|
+
if (file.size > this.maxFileSize) {
|
|
778
|
+
throw new Error(`File too large: ${(file.size / 1024 / 1024).toFixed(1)}MB exceeds 5MB limit`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Converts a File to base64 string
|
|
783
|
+
*/
|
|
784
|
+
async fileToBase64(file) {
|
|
785
|
+
return new Promise((resolve, reject) => {
|
|
786
|
+
const reader = new FileReader();
|
|
787
|
+
reader.onload = () => {
|
|
788
|
+
const result = reader.result;
|
|
789
|
+
if (!result) {
|
|
790
|
+
reject(new Error("Failed to read file"));
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const base64 = result.split(",")[1];
|
|
794
|
+
if (!base64) {
|
|
795
|
+
reject(new Error("Invalid file format"));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
resolve(base64);
|
|
799
|
+
};
|
|
800
|
+
reader.onerror = reject;
|
|
801
|
+
reader.readAsDataURL(file);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Uploads an image file to the server
|
|
806
|
+
*/
|
|
807
|
+
async uploadImage(file) {
|
|
808
|
+
this.validateFile(file);
|
|
809
|
+
const content = await this.fileToBase64(file);
|
|
810
|
+
const response = await fetch(`/api/files/upload`, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: { "Content-Type": "application/json" },
|
|
813
|
+
body: JSON.stringify({
|
|
814
|
+
clientId: this.clientId,
|
|
815
|
+
content,
|
|
816
|
+
mimeType: file.type,
|
|
817
|
+
filename: file.name
|
|
818
|
+
})
|
|
819
|
+
});
|
|
820
|
+
if (!response.ok) {
|
|
821
|
+
const error = await response.json();
|
|
822
|
+
throw new Error(error.error || "Upload failed");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
722
827
|
// apps/web/client/chat-history/chat-history.component.ts
|
|
723
828
|
var PARAGRAPH_MIN_LENGTH = 80;
|
|
724
829
|
var MAX_PARAGRAPHS = 3;
|
|
@@ -737,6 +842,8 @@ var ChatHistoryComponent = class {
|
|
|
737
842
|
window.addEventListener("voiceReadFullTextChanged", (event) => {
|
|
738
843
|
this.readFullText = event.detail;
|
|
739
844
|
});
|
|
845
|
+
this.imageUploadHandler = new ImageUploadHandler(this.getClientId());
|
|
846
|
+
this.setupDragAndDrop();
|
|
740
847
|
setInterval(() => {
|
|
741
848
|
this.checkStateConsistency();
|
|
742
849
|
}, 1e3);
|
|
@@ -749,9 +856,18 @@ var ChatHistoryComponent = class {
|
|
|
749
856
|
onStopCallback;
|
|
750
857
|
readFullText = false;
|
|
751
858
|
currentPlayingButton = null;
|
|
859
|
+
imageUploadHandler;
|
|
752
860
|
handle(event) {
|
|
753
861
|
this.history.set(event.timestamp, event);
|
|
754
|
-
if (event instanceof
|
|
862
|
+
if (event instanceof MessageEvent) {
|
|
863
|
+
if (event.role === "user") {
|
|
864
|
+
this.addUserMessage(event);
|
|
865
|
+
} else {
|
|
866
|
+
this.voiceSynthesis.stopSpeech();
|
|
867
|
+
this.resetAllPlayButtons();
|
|
868
|
+
this.addAssistantMessage(event);
|
|
869
|
+
}
|
|
870
|
+
} else if (event instanceof TextEvent) {
|
|
755
871
|
if (event.speaker) {
|
|
756
872
|
this.voiceSynthesis.stopSpeech();
|
|
757
873
|
this.resetAllPlayButtons();
|
|
@@ -934,6 +1050,131 @@ var ChatHistoryComponent = class {
|
|
|
934
1050
|
this.resetAllPlayButtons();
|
|
935
1051
|
}
|
|
936
1052
|
}
|
|
1053
|
+
addUserMessage(event) {
|
|
1054
|
+
const newEntry = this.createRichMessageElement(event);
|
|
1055
|
+
newEntry.classList.add("text", "right");
|
|
1056
|
+
const buttonContainer = document.createElement("div");
|
|
1057
|
+
buttonContainer.classList.add("message-button-container");
|
|
1058
|
+
const textContent = this.extractTextContent(event);
|
|
1059
|
+
if (textContent) {
|
|
1060
|
+
const playButton = this.createPlayButton(textContent);
|
|
1061
|
+
buttonContainer.appendChild(playButton);
|
|
1062
|
+
}
|
|
1063
|
+
const copyButton = document.createElement("button");
|
|
1064
|
+
copyButton.classList.add("copy-button");
|
|
1065
|
+
copyButton.title = "Copy raw message";
|
|
1066
|
+
copyButton.textContent = "\u{1F4CB}";
|
|
1067
|
+
copyButton.addEventListener("click", (event2) => {
|
|
1068
|
+
event2.stopPropagation();
|
|
1069
|
+
this.copyToClipboard(textContent);
|
|
1070
|
+
const clickedButton = event2.currentTarget;
|
|
1071
|
+
if (clickedButton) {
|
|
1072
|
+
document.querySelectorAll(".copy-button.active").forEach((btn) => {
|
|
1073
|
+
btn.classList.remove("active");
|
|
1074
|
+
btn.textContent = "\u{1F4CB}";
|
|
1075
|
+
});
|
|
1076
|
+
clickedButton.classList.add("active");
|
|
1077
|
+
clickedButton.textContent = "\u2713";
|
|
1078
|
+
setTimeout(() => {
|
|
1079
|
+
clickedButton.classList.remove("active");
|
|
1080
|
+
clickedButton.textContent = "\u{1F4CB}";
|
|
1081
|
+
}, 2e3);
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
buttonContainer.appendChild(copyButton);
|
|
1085
|
+
newEntry.appendChild(buttonContainer);
|
|
1086
|
+
this.appendMessageElement(newEntry);
|
|
1087
|
+
}
|
|
1088
|
+
addAssistantMessage(event) {
|
|
1089
|
+
const newEntry = this.createRichMessageElement(event);
|
|
1090
|
+
newEntry.classList.add("text", "left");
|
|
1091
|
+
newEntry.addEventListener("click", () => {
|
|
1092
|
+
this.voiceSynthesis.stopSpeech();
|
|
1093
|
+
});
|
|
1094
|
+
const buttonContainer = document.createElement("div");
|
|
1095
|
+
buttonContainer.classList.add("message-button-container");
|
|
1096
|
+
const textContent = this.extractTextContent(event);
|
|
1097
|
+
if (textContent) {
|
|
1098
|
+
const playButton = this.createPlayButton(textContent);
|
|
1099
|
+
buttonContainer.appendChild(playButton);
|
|
1100
|
+
}
|
|
1101
|
+
const copyButton = document.createElement("button");
|
|
1102
|
+
copyButton.classList.add("copy-button");
|
|
1103
|
+
copyButton.title = "Copy raw response";
|
|
1104
|
+
copyButton.textContent = "\u{1F4CB}";
|
|
1105
|
+
copyButton.addEventListener("click", (event2) => {
|
|
1106
|
+
event2.stopPropagation();
|
|
1107
|
+
this.copyToClipboard(textContent);
|
|
1108
|
+
const clickedButton = event2.currentTarget;
|
|
1109
|
+
if (clickedButton) {
|
|
1110
|
+
document.querySelectorAll(".copy-button.active").forEach((btn) => {
|
|
1111
|
+
btn.classList.remove("active");
|
|
1112
|
+
btn.textContent = "\u{1F4CB}";
|
|
1113
|
+
});
|
|
1114
|
+
clickedButton.classList.add("active");
|
|
1115
|
+
clickedButton.textContent = "\u2713";
|
|
1116
|
+
setTimeout(() => {
|
|
1117
|
+
clickedButton.classList.remove("active");
|
|
1118
|
+
clickedButton.textContent = "\u{1F4CB}";
|
|
1119
|
+
}, 2e3);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
buttonContainer.appendChild(copyButton);
|
|
1123
|
+
newEntry.appendChild(buttonContainer);
|
|
1124
|
+
this.appendMessageElement(newEntry);
|
|
1125
|
+
const audioEnabled = getPreference("voiceAnnounceEnabled", false) || false;
|
|
1126
|
+
if (audioEnabled && this.isMessageRecentEnoughForAnnouncement(event.timestamp)) {
|
|
1127
|
+
this.announceText(textContent);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
createRichMessageElement(event) {
|
|
1131
|
+
const newEntry = document.createElement("div");
|
|
1132
|
+
newEntry.classList.add("message");
|
|
1133
|
+
const speakerElement = document.createElement("div");
|
|
1134
|
+
speakerElement.classList.add("speaker");
|
|
1135
|
+
speakerElement.textContent = event.name;
|
|
1136
|
+
newEntry.appendChild(speakerElement);
|
|
1137
|
+
const contentContainer = document.createElement("div");
|
|
1138
|
+
contentContainer.classList.add("message-content");
|
|
1139
|
+
event.content.forEach((content) => {
|
|
1140
|
+
if (content.type === "text") {
|
|
1141
|
+
const textDiv = document.createElement("div");
|
|
1142
|
+
textDiv.classList.add("text-part");
|
|
1143
|
+
const parsed = marked.parse(content.content);
|
|
1144
|
+
if (parsed instanceof Promise) {
|
|
1145
|
+
parsed.then((html) => {
|
|
1146
|
+
textDiv.innerHTML = html;
|
|
1147
|
+
});
|
|
1148
|
+
} else {
|
|
1149
|
+
textDiv.innerHTML = parsed;
|
|
1150
|
+
}
|
|
1151
|
+
contentContainer.appendChild(textDiv);
|
|
1152
|
+
} else if (content.type === "image") {
|
|
1153
|
+
const img = document.createElement("img");
|
|
1154
|
+
img.src = `data:${content.mimeType};base64,${content.content}`;
|
|
1155
|
+
img.alt = content.source || "Image";
|
|
1156
|
+
img.classList.add("message-image");
|
|
1157
|
+
img.style.maxWidth = "100%";
|
|
1158
|
+
img.style.height = "auto";
|
|
1159
|
+
img.style.margin = "8px 0";
|
|
1160
|
+
img.style.borderRadius = "4px";
|
|
1161
|
+
img.style.cursor = "pointer";
|
|
1162
|
+
img.addEventListener("click", (e) => {
|
|
1163
|
+
e.stopPropagation();
|
|
1164
|
+
window.open(img.src, "_blank");
|
|
1165
|
+
});
|
|
1166
|
+
contentContainer.appendChild(img);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
newEntry.appendChild(contentContainer);
|
|
1170
|
+
return newEntry;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Extract all text content from a MessageEvent for voice synthesis and copying
|
|
1174
|
+
*/
|
|
1175
|
+
extractTextContent(event) {
|
|
1176
|
+
return event.content.filter((content) => content.type === "text").map((content) => content.content).join("\n\n");
|
|
1177
|
+
}
|
|
937
1178
|
createMessageElement(content, speaker) {
|
|
938
1179
|
const newEntry = document.createElement("div");
|
|
939
1180
|
newEntry.classList.add("message");
|
|
@@ -1032,6 +1273,75 @@ var ChatHistoryComponent = class {
|
|
|
1032
1273
|
return true;
|
|
1033
1274
|
}
|
|
1034
1275
|
}
|
|
1276
|
+
setupDragAndDrop() {
|
|
1277
|
+
this.chatHistory.addEventListener("dragenter", this.handleDragEnter.bind(this));
|
|
1278
|
+
this.chatHistory.addEventListener("dragover", this.handleDragOver.bind(this));
|
|
1279
|
+
this.chatHistory.addEventListener("dragleave", this.handleDragLeave.bind(this));
|
|
1280
|
+
this.chatHistory.addEventListener("drop", this.handleDrop.bind(this));
|
|
1281
|
+
}
|
|
1282
|
+
handleDragEnter(e) {
|
|
1283
|
+
e.preventDefault();
|
|
1284
|
+
if (this.hasImageFiles(e.dataTransfer)) {
|
|
1285
|
+
this.chatHistory.classList.add("drag-over");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
handleDragOver(e) {
|
|
1289
|
+
e.preventDefault();
|
|
1290
|
+
if (this.hasImageFiles(e.dataTransfer)) {
|
|
1291
|
+
e.dataTransfer.dropEffect = "copy";
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
handleDragLeave(e) {
|
|
1295
|
+
if (e.target === this.chatHistory) {
|
|
1296
|
+
this.chatHistory.classList.remove("drag-over");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
async handleDrop(e) {
|
|
1300
|
+
e.preventDefault();
|
|
1301
|
+
this.chatHistory.classList.remove("drag-over");
|
|
1302
|
+
const files = Array.from(e.dataTransfer?.files || []);
|
|
1303
|
+
const imageFiles = files.filter((f) => f.type.startsWith("image/"));
|
|
1304
|
+
for (const file of imageFiles) {
|
|
1305
|
+
try {
|
|
1306
|
+
this.showUploadStatus(`Uploading ${file.name}...`);
|
|
1307
|
+
await this.imageUploadHandler.uploadImage(file);
|
|
1308
|
+
this.hideUploadStatus();
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
console.error("Upload error:", error);
|
|
1311
|
+
this.showUploadError(`Failed to upload ${file.name}: ${error.message}`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
hasImageFiles(dataTransfer) {
|
|
1316
|
+
if (!dataTransfer) return false;
|
|
1317
|
+
return Array.from(dataTransfer.types).includes("Files");
|
|
1318
|
+
}
|
|
1319
|
+
showUploadStatus(message) {
|
|
1320
|
+
this.hideUploadStatus();
|
|
1321
|
+
const statusDiv = document.createElement("div");
|
|
1322
|
+
statusDiv.classList.add("upload-status");
|
|
1323
|
+
statusDiv.textContent = message;
|
|
1324
|
+
statusDiv.id = "upload-status";
|
|
1325
|
+
this.chatHistory.appendChild(statusDiv);
|
|
1326
|
+
this.scrollToBottom();
|
|
1327
|
+
}
|
|
1328
|
+
hideUploadStatus() {
|
|
1329
|
+
const existing = document.getElementById("upload-status");
|
|
1330
|
+
if (existing) {
|
|
1331
|
+
existing.remove();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
showUploadError(message) {
|
|
1335
|
+
this.hideUploadStatus();
|
|
1336
|
+
const errorDiv = document.createElement("div");
|
|
1337
|
+
errorDiv.classList.add("upload-status", "error");
|
|
1338
|
+
errorDiv.textContent = message;
|
|
1339
|
+
this.chatHistory.appendChild(errorDiv);
|
|
1340
|
+
this.scrollToBottom();
|
|
1341
|
+
setTimeout(() => {
|
|
1342
|
+
errorDiv.remove();
|
|
1343
|
+
}, 5e3);
|
|
1344
|
+
}
|
|
1035
1345
|
announceText(text) {
|
|
1036
1346
|
const mode = getPreference("voiceMode", "speech") || "speech";
|
|
1037
1347
|
if (mode === "notification") {
|
|
@@ -1142,7 +1452,7 @@ var VoiceSynthesisComponent = class {
|
|
|
1142
1452
|
}
|
|
1143
1453
|
}
|
|
1144
1454
|
extractPlainText(text) {
|
|
1145
|
-
let processed = text.replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|⭐/gu, "").replace(/```[\s\S]*?```/g, "code block").replace(/`([^`]+)`/g, "$1").replace(/\*\*\*(.*?)\*\*\*/g, "$1").replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/~~(.*?)~~/g, "$1").replace(/#{1,6}\s*(.*)/g, "$1").replace(/\[([^\]]+)\]\([
|
|
1455
|
+
let processed = text.replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|⭐/gu, "").replace(/```[\s\S]*?```/g, "code block").replace(/`([^`]+)`/g, "$1").replace(/\*\*\*(.*?)\*\*\*/g, "$1").replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/~~(.*?)~~/g, "$1").replace(/#{1,6}\s*(.*)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/https?:\/\/[^\s]+/g, "link").replace(/ /g, " ").replace(/&/g, "and").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"');
|
|
1146
1456
|
processed = this.addNaturalPunctuation(processed);
|
|
1147
1457
|
return processed.replace(/\s+/g, " ").trim();
|
|
1148
1458
|
}
|
|
@@ -1234,6 +1544,9 @@ var VoiceSynthesisComponent = class {
|
|
|
1234
1544
|
this.stopSpeech();
|
|
1235
1545
|
const langCode = this.selectedVoice.lang.slice(0, 2);
|
|
1236
1546
|
const testText = TestTexts[langCode] || TestTexts["en"];
|
|
1547
|
+
if (!testText) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1237
1550
|
this.speak(testText);
|
|
1238
1551
|
}, 100);
|
|
1239
1552
|
}
|