diff --git a/package-lock.json b/package-lock.json
index c39cfa6..e200158 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "multi-stub",
- "version": "1.2.0",
+ "version": "1.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "multi-stub",
- "version": "1.2.0",
+ "version": "1.2.1",
"license": "MIT",
"dependencies": {
"ai": "^4.1.13",
@@ -27,6 +27,7 @@
"mongoose": "^8.9.2",
"mongoose-sequence": "^6.0.1",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"pbkdf2-password": "^1.2.1",
"rotating-file-stream": "^3.2.5",
"socket.io": "^4.8.1",
@@ -2084,6 +2085,12 @@
"node": ">= 8"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
+ "license": "MIT"
+ },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -2443,9 +2450,19 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -2708,6 +2725,21 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -2774,6 +2806,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -4578,6 +4616,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -5841,6 +5885,15 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@@ -6150,6 +6203,36 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
+ "node_modules/multer": {
+ "version": "1.4.5-lts.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
+ "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.0.0",
+ "concat-stream": "^1.5.2",
+ "mkdirp": "^0.5.4",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.4",
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/multer/node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
@@ -6707,6 +6790,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -6834,6 +6923,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -7414,6 +7524,14 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -7806,6 +7924,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "license": "MIT"
+ },
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -8109,6 +8233,15 @@
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index 8ef622c..c7384b9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "multi-stub",
- "version": "1.2.0",
+ "version": "1.2.1",
"description": "",
"main": "index.js",
"scripts": {
@@ -41,6 +41,7 @@
"mongoose": "^8.9.2",
"mongoose-sequence": "^6.0.1",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"pbkdf2-password": "^1.2.1",
"rotating-file-stream": "^3.2.5",
"socket.io": "^4.8.1",
diff --git a/server/index.js b/server/index.js
index 1e81309..2a179cd 100644
--- a/server/index.js
+++ b/server/index.js
@@ -90,6 +90,7 @@ app.use("/dhs-testing", require("./routers/dhs-testing"))
app.use("/gamehub", require("./routers/gamehub"))
app.use("/esc", require("./routers/esc"))
app.use('/connectme', require('./routers/connectme'))
+app.use('/questioneer', require('./routers/questioneer'))
app.use(require("./error"))
diff --git a/server/models/questionnaire.js b/server/models/questionnaire.js
new file mode 100644
index 0000000..e08928e
--- /dev/null
+++ b/server/models/questionnaire.js
@@ -0,0 +1,60 @@
+const mongoose = require('mongoose');
+
+// Типы вопросов
+const QUESTION_TYPES = {
+ SINGLE_CHOICE: 'single_choice', // Один вариант
+ MULTIPLE_CHOICE: 'multiple_choice', // Несколько вариантов
+ TEXT: 'text', // Текстовый ответ
+ RATING: 'rating', // Оценка по шкале
+ TAG_CLOUD: 'tag_cloud' // Облако тегов
+};
+
+// Типы отображения
+const DISPLAY_TYPES = {
+ DEFAULT: 'default',
+ TAG_CLOUD: 'tag_cloud',
+ VOTING: 'voting',
+ POLL: 'poll'
+};
+
+// Схема варианта ответа
+const optionSchema = new mongoose.Schema({
+ text: { type: String, required: true },
+ count: { type: Number, default: 0 } // счетчик голосов
+});
+
+// Схема вопроса
+const questionSchema = new mongoose.Schema({
+ text: { type: String, required: true },
+ type: {
+ type: String,
+ enum: Object.values(QUESTION_TYPES),
+ required: true
+ },
+ options: [optionSchema],
+ required: { type: Boolean, default: false }
+});
+
+// Схема опроса
+const questionnaireSchema = new mongoose.Schema({
+ title: { type: String, required: true },
+ description: { type: String },
+ questions: [questionSchema],
+ displayType: {
+ type: String,
+ enum: Object.values(DISPLAY_TYPES),
+ default: DISPLAY_TYPES.DEFAULT
+ },
+ createdAt: { type: Date, default: Date.now },
+ updatedAt: { type: Date, default: Date.now },
+ adminLink: { type: String, required: true }, // ссылка для редактирования
+ publicLink: { type: String, required: true } // ссылка для голосования
+});
+
+const Questionnaire = mongoose.model('Questionnaire', questionnaireSchema);
+
+module.exports = {
+ Questionnaire,
+ QUESTION_TYPES,
+ DISPLAY_TYPES
+};
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/const.js b/server/routers/dogsitters-finder/const.js
new file mode 100644
index 0000000..f10d344
--- /dev/null
+++ b/server/routers/dogsitters-finder/const.js
@@ -0,0 +1,2 @@
+exports.DSF_AUTH_USER_MODEL_NAME = 'DSF_AUTH_USER'
+exports.DSF_INTERACTION_MODEL_NAME = 'DSF_INTERACTION'
diff --git a/server/routers/dogsitters-finder/index.js b/server/routers/dogsitters-finder/index.js
index b34e6af..099ac45 100644
--- a/server/routers/dogsitters-finder/index.js
+++ b/server/routers/dogsitters-finder/index.js
@@ -7,29 +7,29 @@ router.get("/users", (request, response) => {
router.post("/auth", (request, response) => {
const { phoneNumber, password } = request.body;
console.log(phoneNumber, password);
- if (phoneNumber === "89999999999") {
- response.send(require("./json/auth/dogsitter.success.json"));
- } else if (phoneNumber === "89555555555") {
- response.status(400).send(require("./json/auth/error.json"));
+ if (phoneNumber === "89999999999" || phoneNumber === "89559999999") {
+ response.send(require("./json/auth/success.json"));
} else {
- response.send(require("./json/auth/owner.success.json"));
+ response.status(401).send(require("./json/auth/error.json"));
}
});
router.post("/auth/2fa", (request, response) => {
- const { code } = request.body;
- if (code === "0000") {
- response.send(require("./json/2fa/success.json"));
+ const { phoneNumber, code } = request.body;
+ if (code === "0000" && phoneNumber === "89999999999") {
+ response.send(require("./json/2fa/dogsitter.success.json"));
+ } else if (code === "0000" && phoneNumber === "89559999999") {
+ response.send(require("./json/2fa/owner.success.json"));
} else {
- response.status(400).send(require("./json/2fa/error.json"));
+ response.status(401).send(require("./json/2fa/error.json"));
}
});
router.post("/register", (request, response) => {
const { firstName, secondName, phoneNumber, password, role } = request.body;
console.log(phoneNumber, password, role);
- if (phoneNumber === "89283244141" || phoneNumber === "89872855893") {
- response.status(400).send(require("./json/register/error.json"));
+ if (phoneNumber === "89999999999" || phoneNumber === "89559999999") {
+ response.status(401).send(require("./json/register/error.json"));
} else if (role === "dogsitter") {
response.send(require("./json/register/dogsitter.success.json"));
} else {
@@ -37,4 +37,192 @@ router.post("/register", (request, response) => {
}
});
-module.exports = router;
+router.get("/auth/session", (request, response) => {
+ const authHeader = request.headers.authorization;
+
+ if (!authHeader) {
+ return response.status(401).json({ error: "Authorization header missing" });
+ }
+
+ // Берём сам токен из заголовка
+ const token = authHeader.split(" ")[1];
+
+ if (!token) {
+ return response.status(401).json({ error: "Bearer token missing" });
+ }
+
+ const jwt = require("jsonwebtoken");
+ const secretKey = "secret";
+
+ try {
+ const decoded = jwt.verify(token, secretKey);
+
+ if (decoded.role === "dogsitter") {
+ response.send(require("./json/role/dogsitter.success.json"));
+ } else {
+ response.send(require("./json/role/owner.success.json"));
+ }
+ } catch (e) {
+ console.log("token e:", e);
+ return response.status(403).json({ error: "Invalid token" });
+ }
+});
+
+
+// Проверка взаимодействия между пользователем и догситтером
+router.get("/interactions/check", (req, res) => {
+ const { owner_id, dogsitter_id } = req.query;
+
+ const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
+
+ delete require.cache[require.resolve(usersFilePath)];
+ const usersFile = require(usersFilePath);
+
+ const interactions = usersFile.interactions || [];
+
+ const exists = interactions.some(
+ (interaction) =>
+ interaction.owner_id === Number(owner_id) &&
+ interaction.dogsitter_id === Number(dogsitter_id)
+ );
+
+ res.json({ exists });
+});
+
+// Добавление нового взаимодействия
+router.post("/interactions", (req, res) => {
+ const { owner_id, dogsitter_id, interaction_type } = req.body;
+
+ if (!owner_id || !dogsitter_id || !interaction_type) {
+ return res.status(400).json({ error: "Missing required fields" });
+ }
+
+ const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
+
+ delete require.cache[require.resolve(usersFilePath)];
+ const usersFile = require(usersFilePath);
+
+ if (!usersFile.interactions) {
+ usersFile.interactions = [];
+ }
+
+ // Проверяем, существует ли уже такое взаимодействие
+ const exists = usersFile.interactions.some(
+ (interaction) =>
+ interaction.owner_id === Number(owner_id) &&
+ interaction.dogsitter_id === Number(dogsitter_id)
+ );
+
+ if (!exists) {
+ usersFile.interactions.push({
+ owner_id: Number(owner_id),
+ dogsitter_id: Number(dogsitter_id),
+ interaction_type,
+ });
+
+ fs.writeFileSync(
+ usersFilePath,
+ JSON.stringify(usersFile, null, 2),
+ "utf8"
+ );
+
+ console.log(
+ `Добавлено взаимодействие: owner_id=${owner_id}, dogsitter_id=${dogsitter_id}`
+ );
+ }
+
+ res.json({ success: true });
+});
+
+router.get("/dogsitter-viewing", (req, res) => {
+ const { id } = req.query;
+ console.log(`Получен запрос для dogsitter с ID: ${id}`);
+
+ const usersFile = require("./json/users/users.json");
+ const users = usersFile.data; // Извлекаем массив из свойства "data"
+
+ const user = users.find((user) => user.id === Number(id));
+
+ if (user) {
+ res.json(user); // Возвращаем найденного пользователя
+ } else {
+ res.status(404).json({ error: "User not found" }); // Если пользователь не найден
+ }
+});
+
+const fs = require('fs');
+const path = require('path');
+
+
+router.post('/dogsitter-viewing/rating/:id', (req, res) => {
+ const { id } = req.params;
+ const { rating } = req.body;
+
+ if (!rating || rating < 1 || rating > 5) {
+ return res.status(400).json({ error: 'Некорректная оценка' });
+ }
+
+ const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
+
+ delete require.cache[require.resolve(usersFilePath)];
+ const usersFile = require(usersFilePath);
+ const users = usersFile.data;
+
+ const userIndex = users.findIndex(user => user.id === Number(id));
+ if (userIndex === -1) {
+ return res.status(404).json({ error: 'Догситтер не найден' });
+ }
+
+ if (!users[userIndex].ratings) {
+ users[userIndex].ratings = [];
+ }
+ users[userIndex].ratings.push(rating);
+
+ if (users[userIndex].ratings.length > 100) {
+ users[userIndex].ratings.shift();
+ }
+
+ const total = users[userIndex].ratings.reduce((sum, r) => sum + r, 0);
+ users[userIndex].rating = parseFloat((total / users[userIndex].ratings.length).toFixed(2));
+
+ fs.writeFileSync(usersFilePath, JSON.stringify({ data: users }, null, 2), 'utf8');
+
+ console.log(`Обновлен рейтинг догситтера ${id}: ${users[userIndex].rating}`);
+
+ res.json({ rating: users[userIndex].rating, ratings: users[userIndex].ratings });
+});
+
+
+router.patch('/users/:id', (req, res) => {
+ const { id } = req.params;
+ const updateData = req.body;
+
+ console.log('Полученные данные для обновления:', updateData);
+
+
+ const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
+
+ delete require.cache[require.resolve(usersFilePath)];
+ const usersFile = require(usersFilePath);
+ const users = usersFile.data;
+
+ const userIndex = users.findIndex((user) => user.id === Number(id));
+ if (userIndex === -1) {
+ return res.status(404).json({ error: 'User not found' });
+ }
+
+ users[userIndex] = { ...users[userIndex], ...updateData };
+
+ fs.writeFileSync(
+ usersFilePath,
+ JSON.stringify({ data: users }, null, 2),
+ 'utf8'
+ );
+
+ console.log('Обновлённые данные пользователя:', users[userIndex]);
+
+ res.json(users[userIndex]);
+});
+
+
+module.exports = router
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/2fa/dogsitter.success.json b/server/routers/dogsitters-finder/json/2fa/dogsitter.success.json
new file mode 100644
index 0000000..abce97e
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/2fa/dogsitter.success.json
@@ -0,0 +1,3 @@
+{
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6ImRvZ3NpdHRlciIsImlhdCI6MTUxNjIzOTAyMn0.7q66wTNyLZp3TGFYF_JdU-yhlWViJulTxP_PCQzO4OI"
+}
diff --git a/server/routers/dogsitters-finder/json/2fa/error.json b/server/routers/dogsitters-finder/json/2fa/error.json
index 7e4581e..bc20d88 100644
--- a/server/routers/dogsitters-finder/json/2fa/error.json
+++ b/server/routers/dogsitters-finder/json/2fa/error.json
@@ -1,4 +1,5 @@
{
"status": "error",
- "message": "Invalid code."
+ "message": "Invalid code",
+ "statusCode": 401
}
diff --git a/server/routers/dogsitters-finder/json/2fa/owner.success.json b/server/routers/dogsitters-finder/json/2fa/owner.success.json
new file mode 100644
index 0000000..545112d
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/2fa/owner.success.json
@@ -0,0 +1,3 @@
+{
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Mywicm9sZSI6Im93bmVyIiwiaWF0IjoxNTE2MjM5MDIyfQ.sI9839YXveTpEWhdpr5QbCYllt6hHYO7NsrQDcrXZIQ"
+}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/2fa/success.json b/server/routers/dogsitters-finder/json/2fa/success.json
deleted file mode 100644
index 21e7111..0000000
--- a/server/routers/dogsitters-finder/json/2fa/success.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "status": "success",
- "message": "Two-factor authentication passed."
-}
diff --git a/server/routers/dogsitters-finder/json/auth/dogsitter.success.json b/server/routers/dogsitters-finder/json/auth/dogsitter.success.json
deleted file mode 100644
index f7b2168..0000000
--- a/server/routers/dogsitters-finder/json/auth/dogsitter.success.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "data": {
- "id": 1,
- "phoneNumber": 89283244141,
- "firstName": "Вася",
- "secondName": "Пупкин",
- "role": "dogsitter",
- "location": "Россия, республика Татарстан, Казань, улица Пушкина, 12",
- "price": 1500,
- "aboutMe": "Я люблю собак"
- }
-}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/auth/error.json b/server/routers/dogsitters-finder/json/auth/error.json
index 4fded6c..a3fcce3 100644
--- a/server/routers/dogsitters-finder/json/auth/error.json
+++ b/server/routers/dogsitters-finder/json/auth/error.json
@@ -1,3 +1,5 @@
{
- "error": "Пользователь не найден"
-}
\ No newline at end of file
+ "message": "Неверный логин или пароль",
+ "error": "Unauthorized",
+ "statusCode": 401
+}
diff --git a/server/routers/dogsitters-finder/json/auth/owner.success.json b/server/routers/dogsitters-finder/json/auth/owner.success.json
deleted file mode 100644
index 8939302..0000000
--- a/server/routers/dogsitters-finder/json/auth/owner.success.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "data": {
- "id": 3,
- "phoneNumber": 89872855893,
- "firstName": "Гадий",
- "secondName": "Петрович",
- "role": "owner"
- }
-}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/auth/success.json b/server/routers/dogsitters-finder/json/auth/success.json
new file mode 100644
index 0000000..ada9161
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/auth/success.json
@@ -0,0 +1,5 @@
+{
+ "status": "success",
+ "message": "Первый фактор аутентификации пройден",
+ "statusCode": 200
+}
diff --git a/server/routers/dogsitters-finder/json/register/dogsitter.success.json b/server/routers/dogsitters-finder/json/register/dogsitter.success.json
index 1f594e6..4327133 100644
--- a/server/routers/dogsitters-finder/json/register/dogsitter.success.json
+++ b/server/routers/dogsitters-finder/json/register/dogsitter.success.json
@@ -1,12 +1,3 @@
{
- "data": {
- "id": 5,
- "phoneNumber": 89555555555,
- "firstName": "Масяня",
- "secondName": "Карлова",
- "role": "dogsitter",
- "location": "Россия, республика Татарстан, Казань, улица Пушкина, 12",
- "price": 100,
- "aboutMe": "Все на свете - собаки"
- }
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwicm9sZSI6ImRvZ3NpdHRlciIsImlhdCI6MTUxNjIzOTAyMn0.T9V3-f3rD1deA5a2J-tYNw0cACEpzKHbhMPkc7gh8c0"
}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/register/error.json b/server/routers/dogsitters-finder/json/register/error.json
index 2aaf5c9..ba91192 100644
--- a/server/routers/dogsitters-finder/json/register/error.json
+++ b/server/routers/dogsitters-finder/json/register/error.json
@@ -1,3 +1,5 @@
{
- "error": "Пользователь с таким номером телефона уже существует"
+ "message": "Такой пользователь уже был зарегистрирован",
+ "error": "Unauthorized",
+ "statusCode": 401
}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/register/owner.success.json b/server/routers/dogsitters-finder/json/register/owner.success.json
index 2193e4d..3012289 100644
--- a/server/routers/dogsitters-finder/json/register/owner.success.json
+++ b/server/routers/dogsitters-finder/json/register/owner.success.json
@@ -1,9 +1,3 @@
{
- "data": {
- "id": 6,
- "phoneNumber": 89888888888,
- "firstName": "Генадий",
- "secondName": "Паровозов",
- "role": "owner"
- }
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Niwicm9sZSI6Im93bmVyIiwiaWF0IjoxNTE2MjM5MDIyfQ.qgOhk9tNcaMRbarRWISTgvGx5Eq_X8fcA5lhdVs2tQI"
}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/role/dogsitter.success.json b/server/routers/dogsitters-finder/json/role/dogsitter.success.json
new file mode 100644
index 0000000..2922ca7
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/role/dogsitter.success.json
@@ -0,0 +1,4 @@
+{
+ "id": 1,
+ "role": "dogsitter"
+}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/role/error.json b/server/routers/dogsitters-finder/json/role/error.json
new file mode 100644
index 0000000..a9e44ab
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/role/error.json
@@ -0,0 +1,5 @@
+{
+ "message": "Неверный jwt token",
+ "error": "Forbidden",
+ "statusCode": 403
+}
diff --git a/server/routers/dogsitters-finder/json/role/owner.success.json b/server/routers/dogsitters-finder/json/role/owner.success.json
new file mode 100644
index 0000000..5f2f19c
--- /dev/null
+++ b/server/routers/dogsitters-finder/json/role/owner.success.json
@@ -0,0 +1,4 @@
+{
+ "id": 3,
+ "role": "owner"
+}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/json/users/users.json b/server/routers/dogsitters-finder/json/users/users.json
index e85d91f..5e70870 100644
--- a/server/routers/dogsitters-finder/json/users/users.json
+++ b/server/routers/dogsitters-finder/json/users/users.json
@@ -1,39 +1,69 @@
-[
- {
- "id": 1,
- "phone_number": 89283244141,
- "first_name": "Вася",
- "second_name": "Пупкин",
- "role": "dogsitter",
- "location": "Россия, республика Татарстан, Казань, улица Пушкина, 12",
- "price": 1500,
- "about_me": "Я люблю собак"
- },
- {
- "id": 2,
- "phone_number": 89272844541,
- "first_name": "Ваня",
- "second_name": "Пуськин",
- "role": "dogsitter",
- "location": "Россия, республика Татарстан, Казань, улица Абсалямова, 19",
- "price": 1000000,
- "about_me": "Я не люблю собак. И вообще я котоман."
- },
- {
- "id": 3,
- "phone_number": 89872855893,
- "first_name": "Гадий",
- "second_name": "Петрович",
- "role": "owner"
- },
- {
- "id": 4,
- "phone_number": 89872844591,
- "first_name": "Галкин",
- "second_name": "Максим",
- "role": "dogsitter",
- "location": "Россия, республика Татарстан, Казань, проспект Ямашева, 83",
- "price": 1000000,
- "about_me": "Миллион алых роз"
- }
-]
+{
+ "data": [
+ {
+ "id": 1,
+ "phone_number": "89999999999",
+ "first_name": "Вася",
+ "second_name": "Пупкин",
+ "role": "dogsitter",
+ "location": "Россия, республика Татарстан, Казань, Пушкина, 12",
+ "price": "1500",
+ "about_me": "Я люблю собак!",
+ "rating": 5,
+ "ratings": [
+ 5,
+ 5
+ ],
+ "tg": "jullllllie"
+ },
+ {
+ "id": 2,
+ "phone_number": 89272844541,
+ "first_name": "Ваня",
+ "second_name": "Пуськин",
+ "role": "dogsitter",
+ "location": "Россия, республика Татарстан, Казань, улица Абсалямова, 19",
+ "price": 2000,
+ "about_me": "Я не люблю собак. И вообще я котоман.",
+ "rating": 4,
+ "ratings": [
+ 4,
+ 4
+ ],
+ "tg": "vanya006"
+ },
+ {
+ "id": 3,
+ "phone_number": 89559999999,
+ "first_name": "Гадий",
+ "second_name": "Петрович",
+ "role": "owner"
+ },
+ {
+ "id": 4,
+ "phone_number": 89872844591,
+ "first_name": "Галкин",
+ "second_name": "Максим",
+ "role": "dogsitter",
+ "location": "Россия, республика Татарстан, Казань, проспект Ямашева, 83",
+ "price": 1750,
+ "about_me": "Миллион алых роз",
+ "rating": 4.5,
+ "ratings": [
+ 4,
+ 5
+ ],
+ "tg": "maks100500"
+ }
+ ],
+ "interactions": [
+ {
+ "owner_id": 3,
+ "dogsitter_id": 4
+ },
+ {
+ "owner_id": 1,
+ "dogsitter_id": 2
+ }
+ ]
+}
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/model/interaction.js b/server/routers/dogsitters-finder/model/interaction.js
new file mode 100644
index 0000000..95f202b
--- /dev/null
+++ b/server/routers/dogsitters-finder/model/interaction.js
@@ -0,0 +1,24 @@
+const { Schema, model } = require("mongoose");
+
+const { DSF_AUTH_USER_MODEL_NAME, DSF_INTERACTION_MODEL_NAME } = require("../../const");
+
+const interactionSchema = new Schema({
+ owner_id: {
+ type: Schema.Types.ObjectId,
+ ref: DSF_AUTH_USER_MODEL_NAME,
+ required: true
+ },
+ dogsitter_id: {
+ type: Schema.Types.ObjectId,
+ ref: DSF_AUTH_USER_MODEL_NAME,
+ required: true
+ },
+ timestamp: {
+ type: Date,
+ default: Date.now
+ }
+});
+
+interactionSchema.index({ owner_id: 1, dogsitter_id: 1 });
+
+module.exports.Interaction = model(DSF_INTERACTION_MODEL_NAME, interactionSchema);
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/model/user.js b/server/routers/dogsitters-finder/model/user.js
new file mode 100644
index 0000000..889c946
--- /dev/null
+++ b/server/routers/dogsitters-finder/model/user.js
@@ -0,0 +1,83 @@
+const { Schema, model } = require("mongoose");
+
+const { DSF_AUTH_USER_MODEL_NAME } = require("../../const");
+
+const userSchema = new Schema({
+ phone_number: {
+ type: String,
+ required: true,
+ unique: true,
+ match: /^\+?\d{10,15}$/
+ },
+ first_name: {
+ type: String,
+ required: true,
+ trim: true
+ },
+ second_name: {
+ type: String,
+ required: true,
+ trim: true
+ },
+ role: {
+ type: String,
+ enum: ["dogsitter", "owner"],
+ required: true
+ },
+ location: {
+ type: String,
+ required: function() {
+ return this.role === "dogsitter";
+ }
+ },
+ price: {
+ type: Number,
+ min: 0,
+ required: function() {
+ return this.role === "dogsitter";
+ }
+ },
+ about_me: {
+ type: String,
+ maxlength: 500
+ },
+ rating: {
+ type: Number,
+ min: 0,
+ max: 5,
+ default: 0
+ },
+ ratings: {
+ type: [Number],
+ default: [],
+ validate: {
+ validator: function(arr) {
+ return arr.every(v => v >= 0 && v <= 5);
+ },
+ message: "Рейтинг должен быть в диапазоне от 0 до 5!"
+ }
+ },
+ tg: {
+ type: String,
+ match: /^[a-zA-Z0-9_]{5,32}$/
+ },
+ created: {
+ type: Date,
+ default: Date.now
+ }
+});
+
+userSchema.virtual("id").get(function() {
+ return this._id.toHexString();
+});
+
+userSchema.set("toJSON", {
+ virtuals: true,
+ versionKey: false,
+ transform: function(doc, ret) {
+ delete ret._id;
+ delete ret.__v;
+ }
+});
+
+module.exports.User = model(DSF_AUTH_USER_MODEL_NAME, userSchema);
\ No newline at end of file
diff --git a/server/routers/dogsitters-finder/routes.js b/server/routers/dogsitters-finder/routes.js
new file mode 100644
index 0000000..f82bc47
--- /dev/null
+++ b/server/routers/dogsitters-finder/routes.js
@@ -0,0 +1,149 @@
+const { Router } = require('express')
+const { expressjwt } = require('express-jwt')
+
+const { getAnswer } = require('../../utils/common')
+const { User, Interaction } = require('./model')
+const { TOKEN_KEY } = require('./const')
+const { requiredValidate } = require('./utils')
+
+const router = Router()
+
+// Получение списка пользователей
+router.get('/users', async (req, res) => {
+
+ const users = await User.find()
+ .select('-__v -ratings -phone_number')
+ .lean()
+
+ console.log('get users successfull')
+
+ res.send(getAnswer(null, users))
+})
+
+// Получение конкретного пользователя
+router.get('/dogsitter-viewing', async (req, res) => {
+ const { userId } = req.params
+
+ const user = await User.findById(userId)
+ .select('-__v -ratings')
+ .lean()
+
+ if (!user) {
+ return res.status(404).send(getAnswer(new Error('Пользователь не найден')))
+ }
+
+ res.send(getAnswer(null, user))
+})
+
+router.use(expressjwt({ secret: TOKEN_KEY, algorithms: ['HS256'] }))
+
+// Добавление оценки пользователю
+router.post('/dogsitter-viewing/rating', requiredValidate('value'), async (req, res) => {
+ const { userId } = req.params
+ const { value } = req.body
+ const authUserId = req.auth.id
+
+ try {
+ const user = await User.findById(userId)
+ if (!user) throw new Error('Пользователь не найден')
+ if (user.role !== 'dogsitter') throw new Error('Нельзя оценивать этого пользователя')
+ if (user.id === authUserId) throw new Error('Нельзя оценивать самого себя')
+
+ user.ratings.push(Number(value))
+ user.rating = user.ratings.reduce((a, b) => a + b, 0) / user.ratings.length
+
+ const updatedUser = await user.save()
+
+ res.send(getAnswer(null, {
+ id: updatedUser.id,
+ rating: updatedUser.rating.toFixed(1),
+ totalRatings: updatedUser.ratings.length
+ }))
+
+ } catch (error) {
+ res.status(400).send(getAnswer(error))
+ }
+})
+
+// Обновление информации пользователя
+router.patch('/users', async (req, res) => {
+ const { userId } = req.params
+ const updates = req.body
+
+ try {
+ const user = await User.findByIdAndUpdate(userId, updates, { new: true })
+ .select('-__v -ratings')
+
+ if (!user) throw new Error('Пользователь не найден')
+ res.send(getAnswer(null, user))
+
+ } catch (error) {
+ res.status(400).send(getAnswer(error))
+ }
+})
+
+// Создание объекта взаимодействия
+router.post('/interactions',
+ expressjwt({ secret: TOKEN_KEY, algorithms: ['HS256'] }),
+ requiredValidate('dogsitter_id'),
+ async (req, res) => {
+ try {
+ const { dogsitter_id } = req.body
+ const owner_id = req.auth.id // ID из JWT токена
+
+ // Проверка существования пользователей
+ const [owner, dogsitter] = await Promise.all([
+ User.findById(owner_id),
+ User.findById(dogsitter_id)
+ ])
+
+ if (!owner || owner.role !== 'owner') {
+ throw new Error('Владелец не найден или имеет неверную роль')
+ }
+
+ if (!dogsitter || dogsitter.role !== 'dogsitter') {
+ throw new Error('Догситтер не найден или имеет неверную роль')
+ }
+
+ // Создание взаимодействия
+ const interaction = await Interaction.create({
+ owner_id,
+ dogsitter_id
+ })
+
+ res.send(getAnswer(null, {
+ id: interaction.id,
+ timestamp: interaction.timestamp
+ }))
+
+ } catch (error) {
+ res.status(400).send(getAnswer(error))
+ }
+ }
+)
+
+router.get('/interactions/check', async (req, res) => {
+ const { owner_id, dogsitter_id } = req.query;
+
+ if (!owner_id || !dogsitter_id) {
+ return res.status(400).send(getAnswer('Missing owner_id or dogsitter_id'));
+ }
+
+ try {
+ // Поиск взаимодействий по owner_id и dogsitter_id
+ const interactions = await Interaction.find({ owner_id, dogsitter_id })
+ .select('-__v') // Выбираем только нужные поля
+ .lean();
+
+ if (interactions.length === 0) {
+ return res.status(404).send(getAnswer('No interactions found'));
+ }
+
+ res.send(getAnswer(null, interactions));
+ } catch (error) {
+ console.error('Error checking interactions:', error);
+ res.status(500).send(getAnswer('Internal Server Error'));
+ }
+});
+
+module.exports = router
\ No newline at end of file
diff --git a/server/routers/dry-wash/arm-master.js b/server/routers/dry-wash/arm-master.js
index ce1af19..7e95334 100644
--- a/server/routers/dry-wash/arm-master.js
+++ b/server/routers/dry-wash/arm-master.js
@@ -1,111 +1,117 @@
-const router = require('express').Router()
-const {MasterModel} = require('./model/master')
-const mongoose = require("mongoose")
-const {OrderModel} = require("./model/order")
+const router = require("express").Router();
+const { MasterModel } = require("./model/master");
+const mongoose = require("mongoose");
+const { OrderModel } = require("./model/order");
+router.post("/masters/list", async (req, res, next) => {
+ try {
+ const { startDate, endDate } = req.body;
-router.get("/masters", async (req, res, next) => {
- try {
- const masters = await MasterModel.find({});
- const orders = await OrderModel.find({});
-
- const mastersWithOrders = masters.map((master) => {
- const masterOrders = orders.filter((order) => {
- return (
- order?.master && order.master.toString() === master._id.toString()
- );
- });
-
- const schedule = masterOrders.map((order) => ({
- id: order._id,
- startWashTime: order.startWashTime,
- endWashTime: order.endWashTime,
- }));
-
- return {
- id: master._id,
- name: master.name,
- schedule: schedule,
- phone: master.phone,
- };
- });
-
- res.status(200).send({ success: true, body: mastersWithOrders });
- } catch (error) {
- next(error);
- }
-});
-
-router.delete('/masters/:id', async (req, res,next) => {
- const { id } = req.params;
-
- if (!mongoose.Types.ObjectId.isValid(id)){
- throw new Error('ID is required')
+ if (!startDate || !endDate) {
+ throw new Error("Missing startDate or endDate");
}
- try {
- const master = await MasterModel.findByIdAndDelete(id, {
- new: true,
- });
- if (!master) {
- throw new Error('master not found')
- }
- res.status(200).send({success: true, body: master})
- } catch (error) {
- next(error)
- }
-})
+ const start = new Date(startDate);
+ const end = new Date(endDate);
+ const masters = await MasterModel.find({});
+ const orders = await OrderModel.find({
+ $or: [
+ { startWashTime: { $lt: end }, endWashTime: { $gt: start } }
+ ]
+ });
-router.post('/masters', async (req, res,next) => {
-
- const {name, phone} = req.body
-
- if (!name || !phone ){
- throw new Error('Enter name and phone')
- }
- try {
- const master = await MasterModel.create({name, phone})
- res.status(200).send({success: true, body: master})
- } catch (error) {
- next(error)
- }
-})
-
-
-router.patch('/masters/:id', async (req, res, next) => {
- const { id } = req.params;
-
- if (!mongoose.Types.ObjectId.isValid(id)) {
- throw new Error('ID is required')
- }
-
- const { name, phone } = req.body;
-
- if (!name && !phone) {
- throw new Error('Enter name and phone')
- }
-
-
- try {
- const updateData = {};
- if (name) updateData.name = name;
- if (phone) updateData.phone = phone;
-
- const master = await MasterModel.findByIdAndUpdate(
- id,
- updateData,
- { new: true }
+ const mastersWithOrders = masters.map((master) => {
+ const masterOrders = orders.filter((order) => {
+ return (
+ order?.master && order.master.toString() === master._id.toString()
);
+ });
- if (!master) {
- throw new Error('master not found')
- }
+ const schedule = masterOrders.map((order) => ({
+ id: order._id,
+ startWashTime: order.startWashTime,
+ endWashTime: order.endWashTime,
+ }));
- res.status(200).send({ success: true, body: master });
- } catch (error) {
- next(error);
- }
+ return {
+ id: master._id,
+ name: master.name,
+ schedule: schedule,
+ phone: master.phone,
+ };
+ });
+
+ res.status(200).send({ success: true, body: mastersWithOrders });
+ } catch (error) {
+ next(error);
+ }
});
-module.exports = router
+router.delete("/masters/:id", async (req, res, next) => {
+ const { id } = req.params;
+
+ if (!mongoose.Types.ObjectId.isValid(id)) {
+ throw new Error("ID is required");
+ }
+
+ try {
+ const master = await MasterModel.findByIdAndDelete(id, {
+ new: true,
+ });
+ if (!master) {
+ throw new Error("master not found");
+ }
+ res.status(200).send({ success: true, body: master });
+ } catch (error) {
+ next(error);
+ }
+});
+
+router.post("/masters", async (req, res, next) => {
+ const { name, phone } = req.body;
+
+ if (!name || !phone) {
+ throw new Error("Enter name and phone");
+ }
+ try {
+ const master = await MasterModel.create({ name, phone });
+ res.status(200).send({ success: true, body: master });
+ } catch (error) {
+ next(error);
+ }
+});
+
+router.patch("/masters/:id", async (req, res, next) => {
+ const { id } = req.params;
+
+ if (!mongoose.Types.ObjectId.isValid(id)) {
+ throw new Error("ID is required");
+ }
+
+ const { name, phone } = req.body;
+
+ if (!name && !phone) {
+ throw new Error("Enter name and phone");
+ }
+
+ try {
+ const updateData = {};
+ if (name) updateData.name = name;
+ if (phone) updateData.phone = phone;
+
+ const master = await MasterModel.findByIdAndUpdate(id, updateData, {
+ new: true,
+ });
+
+ if (!master) {
+ throw new Error("master not found");
+ }
+
+ res.status(200).send({ success: true, body: master });
+ } catch (error) {
+ next(error);
+ }
+});
+
+module.exports = router;
diff --git a/server/routers/dry-wash/get-token.js b/server/routers/dry-wash/get-token.js
new file mode 100644
index 0000000..a22c3f8
--- /dev/null
+++ b/server/routers/dry-wash/get-token.js
@@ -0,0 +1,23 @@
+const getGigaToken = async () => {
+ const response = await fetch('https://admin.bro-js.ru/api/config/v1/dev')
+ const data = await response.json()
+ return data.features['dry-wash-bh'].GIGA_TOKEN.value
+}
+
+const getSystemPrompt = async () => {
+ const response = await fetch('https://admin.bro-js.ru/api/config/v1/dev')
+ const data = await response.json()
+ return data.features['dry-wash-bh'].SYSTEM_PROMPT.value
+}
+
+const getGigaChatModel = async () => {
+ const response = await fetch('https://admin.bro-js.ru/api/config/v1/dev')
+ const data = await response.json()
+ return data.features['dry-wash-bh'].GIGA_CHAT_MODEL.value
+}
+
+module.exports = {
+ getGigaToken,
+ getSystemPrompt,
+ getGigaChatModel
+}
\ No newline at end of file
diff --git a/server/routers/dry-wash/model/order.car-img.js b/server/routers/dry-wash/model/order.car-img.js
new file mode 100644
index 0000000..9b653f0
--- /dev/null
+++ b/server/routers/dry-wash/model/order.car-img.js
@@ -0,0 +1,29 @@
+const { Schema, model } = require('mongoose')
+
+const schema = new Schema({
+ image: String,
+ imageRating: String,
+ imageDescription: String,
+ orderId: {
+ type: Schema.Types.ObjectId,
+ ref: 'dry-wash-order'
+ },
+ created: {
+ type: Date,
+ default: () => new Date().toISOString(),
+ },
+})
+
+schema.set('toJSON', {
+ virtuals: true,
+ versionKey: false,
+ transform(_doc, ret) {
+ delete ret._id
+ }
+})
+
+schema.virtual('id').get(function () {
+ return this._id.toHexString()
+})
+
+exports.OrderCarImgModel = model('dry-wash-order-car-image', schema)
\ No newline at end of file
diff --git a/server/routers/dry-wash/model/order.js b/server/routers/dry-wash/model/order.js
index 84f21a5..9413283 100644
--- a/server/routers/dry-wash/model/order.js
+++ b/server/routers/dry-wash/model/order.js
@@ -15,7 +15,7 @@ const schema = new Schema({
type: Number,
required: true
},
- carColor: String,
+ carColor: Schema.Types.Mixed,
startWashTime: {
type: Date,
required: true
diff --git a/server/routers/dry-wash/order.js b/server/routers/dry-wash/order.js
index d258fa0..04ce79a 100644
--- a/server/routers/dry-wash/order.js
+++ b/server/routers/dry-wash/order.js
@@ -1,13 +1,23 @@
const mongoose = require("mongoose")
const router = require('express').Router()
+const multer = require('multer')
const { MasterModel } = require('./model/master')
const { OrderModel } = require('./model/order')
+const { OrderCarImgModel } = require('./model/order.car-img')
const { orderStatus } = require('./model/const')
+const { getGigaToken, getSystemPrompt, getGigaChatModel } = require('./get-token')
const isValidPhoneNumber = (value) => /^(\+)?\d{9,15}/.test(value)
const isValidCarNumber = (value) => /^[авекмнорстух][0-9]{3}[авекмнорстух]{2}[0-9]{2,3}$/i.test(value)
const isValidCarBodyType = (value) => typeof value === 'number' && value > 0 && value < 100
-const isValidCarColor = (value) => value.length < 50 && /^[#a-z0-9а-я-\s,.()]+$/i.test(value)
+const isValidCarColor = (value) => {
+ if (typeof value === 'number') {
+ return value >= 0 && value <= 7
+ } else if (typeof value === 'string') {
+ return /^[#a-z0-9а-я-\s,.()]+$/i.test(value)
+ }
+ return false
+}
const isValidISODate = (value) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d{1,3})?Z$/.test(value)
const latitudeRe = /^(-?[1-8]?\d(?:\.\d{1,18})?|90(?:\.0{1,18})?)$/
@@ -26,6 +36,9 @@ const isValidLocation = (value) => {
const isValidOrderStatus = (value) => Object.values(orderStatus).includes(value)
const isValidOrderNotes = (value) => value.length < 500
+const allowedMimeTypes = ['image/jpeg', 'image/png']
+const sizeLimitInMegaBytes = 15
+
const VALIDATION_MESSAGES = {
order: {
notFound: 'Order not found'
@@ -60,6 +73,13 @@ const VALIDATION_MESSAGES = {
carColor: {
invalid: 'Invalid car color'
},
+ carImg: {
+ required: 'Car image file is required',
+ invalid: {
+ type: `Invalid car image file type. Allowed types: ${allowedMimeTypes}`,
+ size: `Invalid car image file size. Limit is ${sizeLimitInMegaBytes}MB`
+ }
+ },
washingBegin: {
required: 'Begin time of washing is required',
invalid: 'Invalid begin time of washing'
@@ -143,17 +163,21 @@ router.post('/create', async (req, res, next) => {
router.get('/:id', async (req, res, next) => {
const { id } = req.params
+
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new Error(VALIDATION_MESSAGES.orderId.invalid)
}
try {
const order = await OrderModel.findById(id)
+
if (!order) {
throw new Error(VALIDATION_MESSAGES.order.notFound)
}
- res.status(200).send({ success: true, body: order })
+ const imgProps = await OrderCarImgModel.findOne({ orderId: order.id })
+
+ res.status(200).send({ success: true, body: { ...order.toObject(), ...imgProps?.toObject() } })
} catch (error) {
next(error)
}
@@ -248,4 +272,167 @@ router.delete('/:id', async (req, res, next) => {
}
})
-module.exports = router
\ No newline at end of file
+const storage = multer.memoryStorage()
+const upload = multer({
+ storage: storage,
+ limits: { fileSize: sizeLimitInMegaBytes * 1024 * 1024 },
+ fileFilter: (req, file, cb) => {
+ if (allowedMimeTypes.includes(file.mimetype)) {
+ cb(null, true)
+ } else {
+ cb(new Error(VALIDATION_MESSAGES.carImg.invalid.type), false)
+ }
+ }
+})
+
+const { v4: uuidv4 } = require("uuid")
+const axios = require('axios')
+
+const GIGA_CHAT_OAUTH = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth'
+const GIGA_CHAT_API = 'https://gigachat.devices.sberbank.ru/api/v1'
+
+const getToken = async (req, res) => {
+ const gigaToken = await getGigaToken()
+
+ const rqUID = uuidv4()
+ const body = new URLSearchParams({
+ scope: "GIGACHAT_API_PERS",
+ })
+
+ const response = await fetch(GIGA_CHAT_OAUTH, {
+ method: "POST",
+ headers: {
+ Authorization: `Basic ${gigaToken}`,
+ "Content-Type": "application/x-www-form-urlencoded",
+ Accept: "application/json",
+ RqUID: rqUID,
+ },
+ body,
+ })
+
+ if (!response.ok) {
+ const errorData = await response.json()
+ console.error("Ошибка запроса: ", errorData)
+ return res.status(response.status).json(errorData)
+ }
+
+ return await response.json()
+}
+
+const uploadImage = async (file, accessToken) => {
+ const formData = new FormData()
+ const blob = new Blob([file.buffer], { type: file.mimetype })
+ formData.append('file', blob, file.originalname)
+ formData.append('purpose', 'general')
+
+ const config = {
+ maxBodyLength: Infinity,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ 'Accept': 'application/json',
+ 'Authorization': `Bearer ${accessToken}`
+ }
+ }
+
+ try {
+ const response = await axios.post(`${GIGA_CHAT_API}/files`, formData, config)
+ return response.data.id
+ } catch (error) {
+ console.error(error)
+ }
+}
+
+const analyzeImage = async (fileId, token) => {
+ const response = await fetch(`${GIGA_CHAT_API}/chat/completions`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ model: (await getGigaChatModel()) ?? "GigaChat-Max",
+ stream: false,
+ update_interval: 0,
+ messages: [
+ {
+ role: "system",
+ content: (await getSystemPrompt()) ?? `Ты эксперт по оценке степени загрязнения автомобилей. Твоя задача — анализировать фотографии машин и определять степень их загрязнения. ВАЖНО: Твой ответ ДОЛЖЕН быть СТРОГО в формате JSON и содержать ТОЛЬКО следующие поля: { "value": число от 0 до 10 (целое или с одним знаком после запятой), "description": "текстовое описание на русском языке" } Правила: 1. Поле "value": - Должно быть числом от 0 до 10 - 0 = машина абсолютно чистая - 10 = машина максимально грязная 2. Поле "description": - Должно содержать 2-3 предложения на русском языке - Обязательно указать конкретные признаки загрязнения - Объяснить почему выставлен именно такой балл НЕ ДОБАВЛЯЙ никаких дополнительных полей или комментариев вне JSON структуры. НЕ ИСПОЛЬЗУЙ markdown форматирование. ОТВЕТ ДОЛЖЕН БЫТЬ ВАЛИДНЫМ JSON. Если на фотографии нет одной машины, то оценка должна быть 0 и в описании должно быть указано, почему не удалось оценить.`,
+ },
+ {
+ role: "user",
+ content: 'Дай оценку для приложенного файла изображения согласно структуре, ответ должен быть на русском языке',
+ attachments: [fileId],
+ },
+ ],
+ }),
+ })
+
+ const data = await response.json()
+ console.log(data)
+
+ try {
+ return JSON.parse(data.choices[0].message.content)
+ } catch (error) {
+ console.error(error)
+ return { description: data.choices[0].message.content }
+ }
+}
+
+const convertFileToBase64 = (file) => {
+ const base64Image = file.buffer.toString('base64')
+ return `data:${file.mimetype};base64,${base64Image}`
+}
+
+process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"
+
+router.post('/:id/upload-car-img', upload.single('file'), async (req, res) => {
+ const { id: orderId } = req.params
+ if (!mongoose.Types.ObjectId.isValid(orderId)) {
+ throw new Error(VALIDATION_MESSAGES.orderId.invalid)
+ }
+ const order = await OrderModel.findById(orderId)
+ if (!order) {
+ throw new Error(VALIDATION_MESSAGES.order.notFound)
+ }
+
+ if (!req.file) {
+ throw new Error(VALIDATION_MESSAGES.carImg.required)
+ }
+
+ try {
+ await OrderCarImgModel.deleteMany({ orderId })
+
+ const { access_token } = await getToken(req, res)
+
+ const fileId = await uploadImage(req.file, access_token)
+ const { value, description } = await analyzeImage(fileId, access_token) ?? {}
+
+ const orderCarImg = await OrderCarImgModel.create({
+ image: convertFileToBase64(req.file),
+ imageRating: value,
+ imageDescription: description,
+ orderId: order.id,
+ created: new Date().toISOString(),
+ })
+
+ res.status(200).send({ success: true, body: orderCarImg })
+ } catch (error) {
+ console.error(error)
+ }
+})
+
+router.use((err, req, res, next) => {
+ if (err instanceof multer.MulterError) {
+ switch (err.message) {
+ case 'File too large':
+ return res.status(400).json({ success: false, error: VALIDATION_MESSAGES.carImg.invalid.size })
+ default:
+ return res.status(400).json({ success: false, error: err.message })
+ }
+ }
+
+ throw new Error(err.message)
+})
+
+module.exports = router
diff --git a/server/routers/gamehub/index.js b/server/routers/gamehub/index.js
index 34af599..1b430c0 100644
--- a/server/routers/gamehub/index.js
+++ b/server/routers/gamehub/index.js
@@ -8,6 +8,10 @@ router.get("/update-like", (request, response) => {
response.send(require("./json/gamepage/success.json"));
});
+router.get("/add-to-cart", (request, response) => {
+ response.send(require("./json/home-page-data/games-in-cart.json"));
+});
+
router.get("/categories", (request, response) => {
response.send(require("./json/home-page-data/all-games.json"));
});
@@ -16,18 +20,32 @@ router.get("/favourites", (request, response) => {
response.send(require("./json/home-page-data/all-games.json"));
});
+// router.get("/shopping-cart", (request, response) => {
+// response.send(require("./json/shopping-cart/success.json"));
+// });
+
router.get("/shopping-cart", (request, response) => {
- response.send(require("./json/shopping-cart/success.json"));
+ response.send(require("./json/home-page-data/games-in-cart.json"));
});
-router.get("/home", (request, response) => {
- response.send(require("./json/home-page-data/success.json"));
+// Добавляем поддержку разных ответов для /home
+router.get("/home", (req, res) => {
+ if (stubs.home === "success") {
+ res.send(require("./json/home-page-data/success.json"));
+ } else if (stubs.home === "empty") {
+ res.send({ data: [] }); // Отправляем пустой массив
+ } else {
+ res.status(500).json({ success: false, message: "Server error" });
+ }
});
router.get("/all-games", (request, response) => {
response.send(require("./json/home-page-data/all-games.json"));
});
+const stubs = {
+ home: "success",
+};
// // Маршрут для обновления лайков
// router.post("/update-like", (request, response) => {
@@ -42,7 +60,6 @@ router.get("/all-games", (request, response) => {
// });
// });
-
const fs = require("fs").promises;
const path = require("path");
@@ -53,7 +70,7 @@ const commentsFilePath = path.join(__dirname, "./json/gamepage/success.json");
async function readComments() {
const data = await fs.readFile(commentsFilePath, "utf-8");
const parsedData = JSON.parse(data);
- console.log("Прочитанные данные:", parsedData); // Логируем полученные данные
+ console.log("Прочитанные данные:", parsedData); // Логируем полученные данные
return parsedData;
}
// Write to JSON file
@@ -92,5 +109,149 @@ router.post("/update-like", async (req, res) => {
}
});
+// Путь к JSON-файлу с корзиной
+const cartFilePath = path.join(
+ __dirname,
+ "./json/home-page-data/games-in-cart.json"
+);
+
+// Функция для чтения JSON-файла
+async function readCart() {
+ const data = await fs.readFile(cartFilePath, "utf-8");
+ return JSON.parse(data);
+}
+
+// Функция для записи в JSON-файл
+async function writeCart(data) {
+ await fs.writeFile(cartFilePath, JSON.stringify(data, null, 2), "utf-8");
+}
+
+// Маршрут для добавления/удаления товара в корзине
+router.post("/add-to-cart", async (req, res) => {
+ const { id, action } = req.body;
+
+ // Проверка наличия id и action
+ if (id === undefined || action === undefined) {
+ return res
+ .status(400)
+ .json({ success: false, message: "Invalid id or action" });
+ }
+
+ try {
+ const cartData = await readCart();
+ let ids = cartData.data.ids;
+
+ if (action === "add") {
+ // Если action "add", добавляем товар, если его нет в корзине
+ if (!ids?.includes(id)) {
+ ids.push(id);
+ }
+ } else if (action === "remove") {
+ // Если action "remove", удаляем товар, если он есть в корзине
+ if (ids?.includes(id)) {
+ ids = ids.filter((item) => item !== id);
+ }
+ } else {
+ // Если action невалиден
+ return res
+ .status(400)
+ .json({ success: false, message: "Invalid action" });
+ }
+
+ // Записываем обновленные данные обратно в файл
+ cartData.data.ids = ids;
+ await writeCart(cartData);
+
+ res.status(200).json({
+ success: true,
+ message: "Cart updated successfully",
+ data: cartData.data, // Возвращаем обновленные данные
+ });
+ } catch (error) {
+ console.error("Error updating cart:", error);
+ res.status(500).json({ success: false, message: "Server error" });
+ }
+});
module.exports = router;
+
+const createElement = (key, value, buttonTitle, basePath) => `
+
+`;
+
+router.get("/admin/home", (request, response) => {
+ const basePath = request.baseUrl; // Получаем базовый путь маршрутизатора
+ response.send(`
+
+
+
+ `);
+});
+
+router.get("/admin/game-page", (request, response) => {
+ response.send(`
+
+
+
+ `);
+});
+
+router.get("/admin/categories", (request, response) => {
+ response.send(`
+
+
+
+ `);
+});
+
+router.get("/admin/favourites", (request, response) => {
+ response.send(`
+
+
+
+ `);
+});
+
+router.get("/admin/set/:key/:value", (request, response) => {
+ const { key, value } = request.params;
+ stubs[key] = value;
+ response.send("Настройки обновлены!");
+});
\ No newline at end of file
diff --git a/server/routers/gamehub/json/gamepage/success.json b/server/routers/gamehub/json/gamepage/success.json
index c4b9801..958704e 100644
--- a/server/routers/gamehub/json/gamepage/success.json
+++ b/server/routers/gamehub/json/gamepage/success.json
@@ -5,28 +5,28 @@
{
"username": "Пользователь1",
"text": "Текст комментария 1",
- "likes": 11,
+ "likes": 13,
"rating": 8,
"date": "2025-03-01T10:00:00Z"
},
{
"username": "Пользователь2",
"text": "Текст комментария 2",
- "likes": 7,
+ "likes": 10,
"rating": 7,
"date": "2025-01-01T10:00:00Z"
},
{
"username": "Пользователь3",
"text": "Текст комментария 3",
- "likes": 2,
+ "likes": 4,
"rating": 3,
"date": "2025-02-01T10:00:00Z"
},
{
"username": "Пользователь4",
"text": "Текст комментария 4",
- "likes": 15,
+ "likes": 18,
"rating": 2,
"date": "2025-12-01T10:00:00Z"
}
diff --git a/server/routers/gamehub/json/home-page-data/success.json b/server/routers/gamehub/json/home-page-data/success.json
index bd83dcb..c393e06 100644
--- a/server/routers/gamehub/json/home-page-data/success.json
+++ b/server/routers/gamehub/json/home-page-data/success.json
@@ -105,23 +105,27 @@
{
"image": "news1",
"text": "Разработчики Delta Force: Hawk Ops представили крупномасштабный режим Havoc Warfare",
- "imgPath": "img_news_1"
+ "imgPath": "img_news_1",
+ "link": "https://gamemag.ru/news/185583/delta-force-hawk-ops-gameplay-showcase-havoc-warfare"
},
{
"image": "news2",
"text": "Первый трейлер Assassin’s Creed Shadows — с темнокожим самураем в феодальной Японии",
- "imgPath": "img_news_2"
+ "imgPath": "img_news_2",
+ "link": "https://stopgame.ru/newsdata/62686/pervyy_trailer_assassin_s_creed_shadows_s_temnokozhim_samuraem_v_feodalnoy_yaponii"
},
{
"image": "news3",
"text": "Призрак Цусимы» вышел на ПК — и уже ставит рекорды для Sony",
- "imgPath": "img_news_3"
+ "imgPath": "img_news_3",
+ "link": "https://stopgame.ru/newsdata/62706/prizrak_cusimy_vyshel_na_pk_i_uzhe_stavit_rekordy_dlya_sony"
},
{
"image": "news4",
- "text": "Авторы Skull and Bones расширяют планы на второй сезо",
- "imgPath": "img_news_4"
+ "text": "Авторы Skull and Bones расширяют планы на второй сезон",
+ "imgPath": "img_news_4",
+ "link": "https://stopgame.ru/newsdata/62711/avtory_skull_and_bones_rasshiryayut_plany_na_vtoroy_sezon"
}
]
}
-}
+}
\ No newline at end of file
diff --git a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/ai.js b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/ai.js
index 6b77ac3..00ae039 100644
--- a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/ai.js
+++ b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/ai.js
@@ -1,24 +1,26 @@
-"use strict";
-var __defProp = Object.defineProperty;
-var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
-var __getOwnPropNames = Object.getOwnPropertyNames;
-var __hasOwnProp = Object.prototype.hasOwnProperty;
+/* eslint-disable no-empty */
+/* eslint-disable no-async-promise-executor */
+"use strict"
+var __defProp = Object.defineProperty
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor
+var __getOwnPropNames = Object.getOwnPropertyNames
+var __hasOwnProp = Object.prototype.hasOwnProperty
var __export = (target, all) => {
for (var name14 in all)
- __defProp(target, name14, { get: all[name14], enumerable: true });
-};
+ __defProp(target, name14, { get: all[name14], enumerable: true })
+}
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable })
}
- return to;
-};
-var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+ return to
+}
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod)
// streams/index.ts
-var streams_exports = {};
+var streams_exports = {}
__export(streams_exports, {
AISDKError: () => import_provider16.AISDKError,
APICallError: () => import_provider16.APICallError,
@@ -77,81 +79,83 @@ __export(streams_exports, {
streamObject: () => streamObject,
streamText: () => streamText,
tool: () => tool
-});
-module.exports = __toCommonJS(streams_exports);
+})
+module.exports = __toCommonJS(streams_exports)
// core/index.ts
-var import_provider_utils12 = require("@ai-sdk/provider-utils");
-var import_ui_utils9 = require("@ai-sdk/ui-utils");
+var import_provider_utils12 = require("@ai-sdk/provider-utils")
+var import_ui_utils9 = require("@ai-sdk/ui-utils")
// core/data-stream/create-data-stream.ts
-var import_ui_utils = require("@ai-sdk/ui-utils");
+var import_ui_utils = require("@ai-sdk/ui-utils")
function createDataStream({
execute,
onError = () => "An error occurred."
// mask error messages for safety by default
}) {
- let controller;
- const ongoingStreamPromises = [];
+ let controller
+ const ongoingStreamPromises = []
const stream = new ReadableStream({
start(controllerArg) {
- controller = controllerArg;
+ controller = controllerArg
}
- });
+ })
function safeEnqueue(data) {
try {
- controller.enqueue(data);
+ controller.enqueue(data)
} catch (error) {
+ console.error(error)
}
}
try {
const result = execute({
writeData(data) {
- safeEnqueue((0, import_ui_utils.formatDataStreamPart)("data", [data]));
+ safeEnqueue((0, import_ui_utils.formatDataStreamPart)("data", [data]))
},
writeMessageAnnotation(annotation) {
- safeEnqueue((0, import_ui_utils.formatDataStreamPart)("message_annotations", [annotation]));
+ safeEnqueue((0, import_ui_utils.formatDataStreamPart)("message_annotations", [annotation]))
},
merge(streamArg) {
ongoingStreamPromises.push(
(async () => {
- const reader = streamArg.getReader();
+ const reader = streamArg.getReader()
while (true) {
- const { done, value } = await reader.read();
+ const { done, value } = await reader.read()
if (done)
- break;
- safeEnqueue(value);
+ break
+ safeEnqueue(value)
}
})().catch((error) => {
- safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)));
+ safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)))
})
- );
+ )
},
onError
- });
+ })
if (result) {
ongoingStreamPromises.push(
result.catch((error) => {
- safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)));
+ safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)))
})
- );
+ )
}
} catch (error) {
- safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)));
+ safeEnqueue((0, import_ui_utils.formatDataStreamPart)("error", onError(error)))
}
const waitForStreams = new Promise(async (resolve) => {
while (ongoingStreamPromises.length > 0) {
- await ongoingStreamPromises.shift();
+ await ongoingStreamPromises.shift()
}
- resolve();
- });
+ resolve()
+ })
waitForStreams.finally(() => {
try {
- controller.close();
+ controller.close()
} catch (error) {
+ console.error(error)
}
- });
- return stream;
+ })
+ return stream
}
// core/util/prepare-response-headers.ts
@@ -159,14 +163,14 @@ function prepareResponseHeaders(headers, {
contentType,
dataStreamVersion
}) {
- const responseHeaders = new Headers(headers != null ? headers : {});
+ const responseHeaders = new Headers(headers != null ? headers : {})
if (!responseHeaders.has("Content-Type")) {
- responseHeaders.set("Content-Type", contentType);
+ responseHeaders.set("Content-Type", contentType)
}
if (dataStreamVersion !== void 0) {
- responseHeaders.set("X-Vercel-AI-Data-Stream", dataStreamVersion);
+ responseHeaders.set("X-Vercel-AI-Data-Stream", dataStreamVersion)
}
- return responseHeaders;
+ return responseHeaders
}
// core/data-stream/create-data-stream-response.ts
@@ -187,7 +191,7 @@ function createDataStreamResponse({
dataStreamVersion: "v1"
})
}
- );
+ )
}
// core/util/prepare-outgoing-http-headers.ts
@@ -195,19 +199,19 @@ function prepareOutgoingHttpHeaders(headers, {
contentType,
dataStreamVersion
}) {
- const outgoingHeaders = {};
+ const outgoingHeaders = {}
if (headers != null) {
for (const [key, value] of Object.entries(headers)) {
- outgoingHeaders[key] = value;
+ outgoingHeaders[key] = value
}
}
if (outgoingHeaders["Content-Type"] == null) {
- outgoingHeaders["Content-Type"] = contentType;
+ outgoingHeaders["Content-Type"] = contentType
}
if (dataStreamVersion !== void 0) {
- outgoingHeaders["X-Vercel-AI-Data-Stream"] = dataStreamVersion;
+ outgoingHeaders["X-Vercel-AI-Data-Stream"] = dataStreamVersion
}
- return outgoingHeaders;
+ return outgoingHeaders
}
// core/util/write-to-server-response.ts
@@ -218,23 +222,23 @@ function writeToServerResponse({
headers,
stream
}) {
- response.writeHead(status != null ? status : 200, statusText, headers);
- const reader = stream.getReader();
+ response.writeHead(status != null ? status : 200, statusText, headers)
+ const reader = stream.getReader()
const read = async () => {
try {
while (true) {
- const { done, value } = await reader.read();
+ const { done, value } = await reader.read()
if (done)
- break;
- response.write(value);
+ break
+ response.write(value)
}
} catch (error) {
- throw error;
+ console.error(error)
} finally {
- response.end();
+ response.end()
}
- };
- read();
+ }
+ read()
}
// core/data-stream/pipe-data-stream-to-response.ts
@@ -256,15 +260,15 @@ function pipeDataStreamToResponse(response, {
stream: createDataStream({ execute, onError }).pipeThrough(
new TextEncoderStream()
)
- });
+ })
}
// errors/invalid-argument-error.ts
-var import_provider = require("@ai-sdk/provider");
-var name = "AI_InvalidArgumentError";
-var marker = `vercel.ai.error.${name}`;
-var symbol = Symbol.for(marker);
-var _a;
+var import_provider = require("@ai-sdk/provider")
+var name = "AI_InvalidArgumentError"
+var marker = `vercel.ai.error.${name}`
+var symbol = Symbol.for(marker)
+var _a
var InvalidArgumentError = class extends import_provider.AISDKError {
constructor({
parameter,
@@ -274,49 +278,49 @@ var InvalidArgumentError = class extends import_provider.AISDKError {
super({
name,
message: `Invalid argument for parameter ${parameter}: ${message}`
- });
- this[_a] = true;
- this.parameter = parameter;
- this.value = value;
+ })
+ this[_a] = true
+ this.parameter = parameter
+ this.value = value
}
static isInstance(error) {
- return import_provider.AISDKError.hasMarker(error, marker);
+ return import_provider.AISDKError.hasMarker(error, marker)
}
-};
-_a = symbol;
+}
+_a = symbol
// util/retry-with-exponential-backoff.ts
-var import_provider3 = require("@ai-sdk/provider");
-var import_provider_utils = require("@ai-sdk/provider-utils");
+var import_provider3 = require("@ai-sdk/provider")
+var import_provider_utils = require("@ai-sdk/provider-utils")
// util/delay.ts
async function delay(delayInMs) {
- return delayInMs == null ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayInMs));
+ return delayInMs == null ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayInMs))
}
// util/retry-error.ts
-var import_provider2 = require("@ai-sdk/provider");
-var name2 = "AI_RetryError";
-var marker2 = `vercel.ai.error.${name2}`;
-var symbol2 = Symbol.for(marker2);
-var _a2;
+var import_provider2 = require("@ai-sdk/provider")
+var name2 = "AI_RetryError"
+var marker2 = `vercel.ai.error.${name2}`
+var symbol2 = Symbol.for(marker2)
+var _a2
var RetryError = class extends import_provider2.AISDKError {
constructor({
message,
reason,
errors
}) {
- super({ name: name2, message });
- this[_a2] = true;
- this.reason = reason;
- this.errors = errors;
- this.lastError = errors[errors.length - 1];
+ super({ name: name2, message })
+ this[_a2] = true
+ this.reason = reason
+ this.errors = errors
+ this.lastError = errors[errors.length - 1]
}
static isInstance(error) {
- return import_provider2.AISDKError.hasMarker(error, marker2);
+ return import_provider2.AISDKError.hasMarker(error, marker2)
}
-};
-_a2 = symbol2;
+}
+_a2 = symbol2
// util/retry-with-exponential-backoff.ts
var retryWithExponentialBackoff = ({
@@ -327,47 +331,47 @@ var retryWithExponentialBackoff = ({
maxRetries,
delayInMs: initialDelayInMs,
backoffFactor
-});
+})
async function _retryWithExponentialBackoff(f, {
maxRetries,
delayInMs,
backoffFactor
}, errors = []) {
try {
- return await f();
+ return await f()
} catch (error) {
if ((0, import_provider_utils.isAbortError)(error)) {
- throw error;
+ throw error
}
if (maxRetries === 0) {
- throw error;
+ throw error
}
- const errorMessage = (0, import_provider_utils.getErrorMessage)(error);
- const newErrors = [...errors, error];
- const tryNumber = newErrors.length;
+ const errorMessage = (0, import_provider_utils.getErrorMessage)(error)
+ const newErrors = [...errors, error]
+ const tryNumber = newErrors.length
if (tryNumber > maxRetries) {
throw new RetryError({
message: `Failed after ${tryNumber} attempts. Last error: ${errorMessage}`,
reason: "maxRetriesExceeded",
errors: newErrors
- });
+ })
}
if (error instanceof Error && import_provider3.APICallError.isInstance(error) && error.isRetryable === true && tryNumber <= maxRetries) {
- await delay(delayInMs);
+ await delay(delayInMs)
return _retryWithExponentialBackoff(
f,
{ maxRetries, delayInMs: backoffFactor * delayInMs, backoffFactor },
newErrors
- );
+ )
}
if (tryNumber === 1) {
- throw error;
+ throw error
}
throw new RetryError({
message: `Failed after ${tryNumber} attempts with non-retryable error: '${errorMessage}'`,
reason: "errorNotRetryable",
errors: newErrors
- });
+ })
}
}
@@ -381,21 +385,21 @@ function prepareRetries({
parameter: "maxRetries",
value: maxRetries,
message: "maxRetries must be an integer"
- });
+ })
}
if (maxRetries < 0) {
throw new InvalidArgumentError({
parameter: "maxRetries",
value: maxRetries,
message: "maxRetries must be >= 0"
- });
+ })
}
}
- const maxRetriesResult = maxRetries != null ? maxRetries : 2;
+ const maxRetriesResult = maxRetries != null ? maxRetries : 2
return {
maxRetries: maxRetriesResult,
retry: retryWithExponentialBackoff({ maxRetries: maxRetriesResult })
- };
+ }
}
// core/telemetry/assemble-operation-name.ts
@@ -410,7 +414,7 @@ function assembleOperationName({
// detailed, AI SDK specific data:
"ai.operationId": operationId,
"ai.telemetry.functionId": telemetry == null ? void 0 : telemetry.functionId
- };
+ }
}
// core/telemetry/get-base-telemetry-attributes.ts
@@ -420,93 +424,93 @@ function getBaseTelemetryAttributes({
telemetry,
headers
}) {
- var _a14;
+ var _a14
return {
"ai.model.provider": model.provider,
"ai.model.id": model.modelId,
// settings:
...Object.entries(settings).reduce((attributes, [key, value]) => {
- attributes[`ai.settings.${key}`] = value;
- return attributes;
+ attributes[`ai.settings.${key}`] = value
+ return attributes
}, {}),
// add metadata as attributes:
...Object.entries((_a14 = telemetry == null ? void 0 : telemetry.metadata) != null ? _a14 : {}).reduce(
(attributes, [key, value]) => {
- attributes[`ai.telemetry.metadata.${key}`] = value;
- return attributes;
+ attributes[`ai.telemetry.metadata.${key}`] = value
+ return attributes
},
{}
),
// request headers
...Object.entries(headers != null ? headers : {}).reduce((attributes, [key, value]) => {
if (value !== void 0) {
- attributes[`ai.request.headers.${key}`] = value;
+ attributes[`ai.request.headers.${key}`] = value
}
- return attributes;
+ return attributes
}, {})
- };
+ }
}
// core/telemetry/get-tracer.ts
-var import_api = require("@opentelemetry/api");
+var import_api = require("@opentelemetry/api")
// core/telemetry/noop-tracer.ts
var noopTracer = {
startSpan() {
- return noopSpan;
+ return noopSpan
},
startActiveSpan(name14, arg1, arg2, arg3) {
if (typeof arg1 === "function") {
- return arg1(noopSpan);
+ return arg1(noopSpan)
}
if (typeof arg2 === "function") {
- return arg2(noopSpan);
+ return arg2(noopSpan)
}
if (typeof arg3 === "function") {
- return arg3(noopSpan);
+ return arg3(noopSpan)
}
}
-};
+}
var noopSpan = {
spanContext() {
- return noopSpanContext;
+ return noopSpanContext
},
setAttribute() {
- return this;
+ return this
},
setAttributes() {
- return this;
+ return this
},
addEvent() {
- return this;
+ return this
},
addLink() {
- return this;
+ return this
},
addLinks() {
- return this;
+ return this
},
setStatus() {
- return this;
+ return this
},
updateName() {
- return this;
+ return this
},
end() {
- return this;
+ return this
},
isRecording() {
- return false;
+ return false
},
recordException() {
- return this;
+ return this
}
-};
+}
var noopSpanContext = {
traceId: "",
spanId: "",
traceFlags: 0
-};
+}
// core/telemetry/get-tracer.ts
function getTracer({
@@ -514,16 +518,16 @@ function getTracer({
tracer
} = {}) {
if (!isEnabled) {
- return noopTracer;
+ return noopTracer
}
if (tracer) {
- return tracer;
+ return tracer
}
- return import_api.trace.getTracer("ai");
+ return import_api.trace.getTracer("ai")
}
// core/telemetry/record-span.ts
-var import_api2 = require("@opentelemetry/api");
+var import_api2 = require("@opentelemetry/api")
function recordSpan({
name: name14,
tracer,
@@ -533,11 +537,11 @@ function recordSpan({
}) {
return tracer.startActiveSpan(name14, { attributes }, async (span) => {
try {
- const result = await fn(span);
+ const result = await fn(span)
if (endWhenDone) {
- span.end();
+ span.end()
}
- return result;
+ return result
} catch (error) {
try {
if (error instanceof Error) {
@@ -545,20 +549,20 @@ function recordSpan({
name: error.name,
message: error.message,
stack: error.stack
- });
+ })
span.setStatus({
code: import_api2.SpanStatusCode.ERROR,
message: error.message
- });
+ })
} else {
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR })
}
} finally {
- span.end();
+ span.end()
}
- throw error;
+ throw error
}
- });
+ })
}
// core/telemetry/select-telemetry-attributes.ts
@@ -567,28 +571,28 @@ function selectTelemetryAttributes({
attributes
}) {
if ((telemetry == null ? void 0 : telemetry.isEnabled) !== true) {
- return {};
+ return {}
}
return Object.entries(attributes).reduce((attributes2, [key, value]) => {
if (value === void 0) {
- return attributes2;
+ return attributes2
}
if (typeof value === "object" && "input" in value && typeof value.input === "function") {
if ((telemetry == null ? void 0 : telemetry.recordInputs) === false) {
- return attributes2;
+ return attributes2
}
- const result = value.input();
- return result === void 0 ? attributes2 : { ...attributes2, [key]: result };
+ const result = value.input()
+ return result === void 0 ? attributes2 : { ...attributes2, [key]: result }
}
if (typeof value === "object" && "output" in value && typeof value.output === "function") {
if ((telemetry == null ? void 0 : telemetry.recordOutputs) === false) {
- return attributes2;
+ return attributes2
}
- const result = value.output();
- return result === void 0 ? attributes2 : { ...attributes2, [key]: result };
+ const result = value.output()
+ return result === void 0 ? attributes2 : { ...attributes2, [key]: result }
}
- return { ...attributes2, [key]: value };
- }, {});
+ return { ...attributes2, [key]: value }
+ }, {})
}
// core/embed/embed.ts
@@ -600,14 +604,14 @@ async function embed({
headers,
experimental_telemetry: telemetry
}) {
- const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg });
+ const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg })
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { maxRetries }
- });
- const tracer = getTracer(telemetry);
+ })
+ const tracer = getTracer(telemetry)
return recordSpan({
name: "ai.embed",
attributes: selectTelemetryAttributes({
@@ -639,14 +643,14 @@ async function embed({
}),
tracer,
fn: async (doEmbedSpan) => {
- var _a14;
+ var _a14
const modelResponse = await model.doEmbed({
values: [value],
abortSignal,
headers
- });
- const embedding2 = modelResponse.embeddings[0];
- const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN };
+ })
+ const embedding2 = modelResponse.embeddings[0]
+ const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN }
doEmbedSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -659,16 +663,16 @@ async function embed({
"ai.usage.tokens": usage2.tokens
}
})
- );
+ )
return {
embedding: embedding2,
usage: usage2,
rawResponse: modelResponse.rawResponse
- };
+ }
}
})
)
- );
+ )
span.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -677,30 +681,30 @@ async function embed({
"ai.usage.tokens": usage.tokens
}
})
- );
- return new DefaultEmbedResult({ value, embedding, usage, rawResponse });
+ )
+ return new DefaultEmbedResult({ value, embedding, usage, rawResponse })
}
- });
+ })
}
var DefaultEmbedResult = class {
constructor(options) {
- this.value = options.value;
- this.embedding = options.embedding;
- this.usage = options.usage;
- this.rawResponse = options.rawResponse;
+ this.value = options.value
+ this.embedding = options.embedding
+ this.usage = options.usage
+ this.rawResponse = options.rawResponse
}
-};
+}
// core/util/split-array.ts
function splitArray(array, chunkSize) {
if (chunkSize <= 0) {
- throw new Error("chunkSize must be greater than 0");
+ throw new Error("chunkSize must be greater than 0")
}
- const result = [];
+ const result = []
for (let i = 0; i < array.length; i += chunkSize) {
- result.push(array.slice(i, i + chunkSize));
+ result.push(array.slice(i, i + chunkSize))
}
- return result;
+ return result
}
// core/embed/embed-many.ts
@@ -712,14 +716,14 @@ async function embedMany({
headers,
experimental_telemetry: telemetry
}) {
- const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg });
+ const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg })
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { maxRetries }
- });
- const tracer = getTracer(telemetry);
+ })
+ const tracer = getTracer(telemetry)
return recordSpan({
name: "ai.embedMany",
attributes: selectTelemetryAttributes({
@@ -735,7 +739,7 @@ async function embedMany({
}),
tracer,
fn: async (span) => {
- const maxEmbeddingsPerCall = model.maxEmbeddingsPerCall;
+ const maxEmbeddingsPerCall = model.maxEmbeddingsPerCall
if (maxEmbeddingsPerCall == null) {
const { embeddings: embeddings2, usage } = await retry(() => {
return recordSpan({
@@ -756,14 +760,14 @@ async function embedMany({
}),
tracer,
fn: async (doEmbedSpan) => {
- var _a14;
+ var _a14
const modelResponse = await model.doEmbed({
values,
abortSignal,
headers
- });
- const embeddings3 = modelResponse.embeddings;
- const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN };
+ })
+ const embeddings3 = modelResponse.embeddings
+ const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN }
doEmbedSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -774,11 +778,11 @@ async function embedMany({
"ai.usage.tokens": usage2.tokens
}
})
- );
- return { embeddings: embeddings3, usage: usage2 };
+ )
+ return { embeddings: embeddings3, usage: usage2 }
}
- });
- });
+ })
+ })
span.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -789,12 +793,12 @@ async function embedMany({
"ai.usage.tokens": usage.tokens
}
})
- );
- return new DefaultEmbedManyResult({ values, embeddings: embeddings2, usage });
+ )
+ return new DefaultEmbedManyResult({ values, embeddings: embeddings2, usage })
}
- const valueChunks = splitArray(values, maxEmbeddingsPerCall);
- const embeddings = [];
- let tokens = 0;
+ const valueChunks = splitArray(values, maxEmbeddingsPerCall)
+ const embeddings = []
+ let tokens = 0
for (const chunk of valueChunks) {
const { embeddings: responseEmbeddings, usage } = await retry(() => {
return recordSpan({
@@ -815,14 +819,14 @@ async function embedMany({
}),
tracer,
fn: async (doEmbedSpan) => {
- var _a14;
+ var _a14
const modelResponse = await model.doEmbed({
values: chunk,
abortSignal,
headers
- });
- const embeddings2 = modelResponse.embeddings;
- const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN };
+ })
+ const embeddings2 = modelResponse.embeddings
+ const usage2 = (_a14 = modelResponse.usage) != null ? _a14 : { tokens: NaN }
doEmbedSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -833,13 +837,13 @@ async function embedMany({
"ai.usage.tokens": usage2.tokens
}
})
- );
- return { embeddings: embeddings2, usage: usage2 };
+ )
+ return { embeddings: embeddings2, usage: usage2 }
}
- });
- });
- embeddings.push(...responseEmbeddings);
- tokens += usage.tokens;
+ })
+ })
+ embeddings.push(...responseEmbeddings)
+ tokens += usage.tokens
}
span.setAttributes(
selectTelemetryAttributes({
@@ -851,25 +855,25 @@ async function embedMany({
"ai.usage.tokens": tokens
}
})
- );
+ )
return new DefaultEmbedManyResult({
values,
embeddings,
usage: { tokens }
- });
+ })
}
- });
+ })
}
var DefaultEmbedManyResult = class {
constructor(options) {
- this.values = options.values;
- this.embeddings = options.embeddings;
- this.usage = options.usage;
+ this.values = options.values
+ this.embeddings = options.embeddings
+ this.usage = options.usage
}
-};
+}
// core/generate-image/generate-image.ts
-var import_provider_utils2 = require("@ai-sdk/provider-utils");
+var import_provider_utils2 = require("@ai-sdk/provider-utils")
async function generateImage({
model,
prompt,
@@ -882,17 +886,17 @@ async function generateImage({
abortSignal,
headers
}) {
- var _a14;
- const { retry } = prepareRetries({ maxRetries: maxRetriesArg });
- const maxImagesPerCall = (_a14 = model.maxImagesPerCall) != null ? _a14 : 1;
- const callCount = Math.ceil(n / maxImagesPerCall);
+ var _a14
+ const { retry } = prepareRetries({ maxRetries: maxRetriesArg })
+ const maxImagesPerCall = (_a14 = model.maxImagesPerCall) != null ? _a14 : 1
+ const callCount = Math.ceil(n / maxImagesPerCall)
const callImageCounts = Array.from({ length: callCount }, (_, i) => {
if (i < callCount - 1) {
- return maxImagesPerCall;
+ return maxImagesPerCall
}
- const remainder = n % maxImagesPerCall;
- return remainder === 0 ? maxImagesPerCall : remainder;
- });
+ const remainder = n % maxImagesPerCall
+ return remainder === 0 ? maxImagesPerCall : remainder
+ })
const results = await Promise.all(
callImageCounts.map(
async (callImageCount) => retry(
@@ -908,57 +912,57 @@ async function generateImage({
})
)
)
- );
- const images = [];
- const warnings = [];
+ )
+ const images = []
+ const warnings = []
for (const result of results) {
images.push(
...result.images.map((image) => new DefaultGeneratedImage({ image }))
- );
- warnings.push(...result.warnings);
+ )
+ warnings.push(...result.warnings)
}
- return new DefaultGenerateImageResult({ images, warnings });
+ return new DefaultGenerateImageResult({ images, warnings })
}
var DefaultGenerateImageResult = class {
constructor(options) {
- this.images = options.images;
- this.warnings = options.warnings;
+ this.images = options.images
+ this.warnings = options.warnings
}
get image() {
- return this.images[0];
+ return this.images[0]
}
-};
+}
var DefaultGeneratedImage = class {
constructor({ image }) {
- const isUint8Array = image instanceof Uint8Array;
- this.base64Data = isUint8Array ? void 0 : image;
- this.uint8ArrayData = isUint8Array ? image : void 0;
+ const isUint8Array = image instanceof Uint8Array
+ this.base64Data = isUint8Array ? void 0 : image
+ this.uint8ArrayData = isUint8Array ? image : void 0
}
// lazy conversion with caching to avoid unnecessary conversion overhead:
get base64() {
if (this.base64Data == null) {
- this.base64Data = (0, import_provider_utils2.convertUint8ArrayToBase64)(this.uint8ArrayData);
+ this.base64Data = (0, import_provider_utils2.convertUint8ArrayToBase64)(this.uint8ArrayData)
}
- return this.base64Data;
+ return this.base64Data
}
// lazy conversion with caching to avoid unnecessary conversion overhead:
get uint8Array() {
if (this.uint8ArrayData == null) {
- this.uint8ArrayData = (0, import_provider_utils2.convertBase64ToUint8Array)(this.base64Data);
+ this.uint8ArrayData = (0, import_provider_utils2.convertBase64ToUint8Array)(this.base64Data)
}
- return this.uint8ArrayData;
+ return this.uint8ArrayData
}
-};
+}
// core/generate-object/generate-object.ts
-var import_provider_utils6 = require("@ai-sdk/provider-utils");
+var import_provider_utils6 = require("@ai-sdk/provider-utils")
// errors/no-object-generated-error.ts
-var import_provider4 = require("@ai-sdk/provider");
-var name3 = "AI_NoObjectGeneratedError";
-var marker3 = `vercel.ai.error.${name3}`;
-var symbol3 = Symbol.for(marker3);
-var _a3;
+var import_provider4 = require("@ai-sdk/provider")
+var name3 = "AI_NoObjectGeneratedError"
+var marker3 = `vercel.ai.error.${name3}`
+var symbol3 = Symbol.for(marker3)
+var _a3
var NoObjectGeneratedError = class extends import_provider4.AISDKError {
constructor({
message = "No object generated.",
@@ -967,24 +971,24 @@ var NoObjectGeneratedError = class extends import_provider4.AISDKError {
response,
usage
}) {
- super({ name: name3, message, cause });
- this[_a3] = true;
- this.text = text2;
- this.response = response;
- this.usage = usage;
+ super({ name: name3, message, cause })
+ this[_a3] = true
+ this.text = text2
+ this.response = response
+ this.usage = usage
}
static isInstance(error) {
- return import_provider4.AISDKError.hasMarker(error, marker3);
+ return import_provider4.AISDKError.hasMarker(error, marker3)
}
-};
-_a3 = symbol3;
+}
+_a3 = symbol3
// util/download-error.ts
-var import_provider5 = require("@ai-sdk/provider");
-var name4 = "AI_DownloadError";
-var marker4 = `vercel.ai.error.${name4}`;
-var symbol4 = Symbol.for(marker4);
-var _a4;
+var import_provider5 = require("@ai-sdk/provider")
+var name4 = "AI_DownloadError"
+var marker4 = `vercel.ai.error.${name4}`
+var symbol4 = Symbol.for(marker4)
+var _a4
var DownloadError = class extends import_provider5.AISDKError {
constructor({
url,
@@ -993,43 +997,43 @@ var DownloadError = class extends import_provider5.AISDKError {
cause,
message = cause == null ? `Failed to download ${url}: ${statusCode} ${statusText}` : `Failed to download ${url}: ${cause}`
}) {
- super({ name: name4, message, cause });
- this[_a4] = true;
- this.url = url;
- this.statusCode = statusCode;
- this.statusText = statusText;
+ super({ name: name4, message, cause })
+ this[_a4] = true
+ this.url = url
+ this.statusCode = statusCode
+ this.statusText = statusText
}
static isInstance(error) {
- return import_provider5.AISDKError.hasMarker(error, marker4);
+ return import_provider5.AISDKError.hasMarker(error, marker4)
}
-};
-_a4 = symbol4;
+}
+_a4 = symbol4
// util/download.ts
async function download({
url,
fetchImplementation = fetch
}) {
- var _a14;
- const urlText = url.toString();
+ var _a14
+ const urlText = url.toString()
try {
- const response = await fetchImplementation(urlText);
+ const response = await fetchImplementation(urlText)
if (!response.ok) {
throw new DownloadError({
url: urlText,
statusCode: response.status,
statusText: response.statusText
- });
+ })
}
return {
data: new Uint8Array(await response.arrayBuffer()),
mimeType: (_a14 = response.headers.get("content-type")) != null ? _a14 : void 0
- };
+ }
} catch (error) {
if (DownloadError.isInstance(error)) {
- throw error;
+ throw error
}
- throw new DownloadError({ url: urlText, cause: error });
+ throw new DownloadError({ url: urlText, cause: error })
}
}
@@ -1039,43 +1043,43 @@ var mimeTypeSignatures = [
{ mimeType: "image/png", bytes: [137, 80, 78, 71] },
{ mimeType: "image/jpeg", bytes: [255, 216] },
{ mimeType: "image/webp", bytes: [82, 73, 70, 70] }
-];
+]
function detectImageMimeType(image) {
for (const { bytes, mimeType } of mimeTypeSignatures) {
if (image.length >= bytes.length && bytes.every((byte, index) => image[index] === byte)) {
- return mimeType;
+ return mimeType
}
}
- return void 0;
+ return void 0
}
// core/prompt/data-content.ts
-var import_provider_utils3 = require("@ai-sdk/provider-utils");
+var import_provider_utils3 = require("@ai-sdk/provider-utils")
// core/prompt/invalid-data-content-error.ts
-var import_provider6 = require("@ai-sdk/provider");
-var name5 = "AI_InvalidDataContentError";
-var marker5 = `vercel.ai.error.${name5}`;
-var symbol5 = Symbol.for(marker5);
-var _a5;
+var import_provider6 = require("@ai-sdk/provider")
+var name5 = "AI_InvalidDataContentError"
+var marker5 = `vercel.ai.error.${name5}`
+var symbol5 = Symbol.for(marker5)
+var _a5
var InvalidDataContentError = class extends import_provider6.AISDKError {
constructor({
content,
cause,
message = `Invalid data content. Expected a base64 string, Uint8Array, ArrayBuffer, or Buffer, but got ${typeof content}.`
}) {
- super({ name: name5, message, cause });
- this[_a5] = true;
- this.content = content;
+ super({ name: name5, message, cause })
+ this[_a5] = true
+ this.content = content
}
static isInstance(error) {
- return import_provider6.AISDKError.hasMarker(error, marker5);
+ return import_provider6.AISDKError.hasMarker(error, marker5)
}
-};
-_a5 = symbol5;
+}
+_a5 = symbol5
// core/prompt/data-content.ts
-var import_zod = require("zod");
+var import_zod = require("zod")
var dataContentSchema = import_zod.z.union([
import_zod.z.string(),
import_zod.z.instanceof(Uint8Array),
@@ -1083,83 +1087,83 @@ var dataContentSchema = import_zod.z.union([
import_zod.z.custom(
// Buffer might not be available in some environments such as CloudFlare:
(value) => {
- var _a14, _b;
- return (_b = (_a14 = globalThis.Buffer) == null ? void 0 : _a14.isBuffer(value)) != null ? _b : false;
+ var _a14, _b
+ return (_b = (_a14 = globalThis.Buffer) == null ? void 0 : _a14.isBuffer(value)) != null ? _b : false
},
{ message: "Must be a Buffer" }
)
-]);
+])
function convertDataContentToBase64String(content) {
if (typeof content === "string") {
- return content;
+ return content
}
if (content instanceof ArrayBuffer) {
- return (0, import_provider_utils3.convertUint8ArrayToBase64)(new Uint8Array(content));
+ return (0, import_provider_utils3.convertUint8ArrayToBase64)(new Uint8Array(content))
}
- return (0, import_provider_utils3.convertUint8ArrayToBase64)(content);
+ return (0, import_provider_utils3.convertUint8ArrayToBase64)(content)
}
function convertDataContentToUint8Array(content) {
if (content instanceof Uint8Array) {
- return content;
+ return content
}
if (typeof content === "string") {
try {
- return (0, import_provider_utils3.convertBase64ToUint8Array)(content);
+ return (0, import_provider_utils3.convertBase64ToUint8Array)(content)
} catch (error) {
throw new InvalidDataContentError({
message: "Invalid data content. Content string is not a base64-encoded media.",
content,
cause: error
- });
+ })
}
}
if (content instanceof ArrayBuffer) {
- return new Uint8Array(content);
+ return new Uint8Array(content)
}
- throw new InvalidDataContentError({ content });
+ throw new InvalidDataContentError({ content })
}
function convertUint8ArrayToText(uint8Array) {
try {
- return new TextDecoder().decode(uint8Array);
+ return new TextDecoder().decode(uint8Array)
} catch (error) {
- throw new Error("Error decoding Uint8Array to text");
+ throw new Error("Error decoding Uint8Array to text")
}
}
// core/prompt/invalid-message-role-error.ts
-var import_provider7 = require("@ai-sdk/provider");
-var name6 = "AI_InvalidMessageRoleError";
-var marker6 = `vercel.ai.error.${name6}`;
-var symbol6 = Symbol.for(marker6);
-var _a6;
+var import_provider7 = require("@ai-sdk/provider")
+var name6 = "AI_InvalidMessageRoleError"
+var marker6 = `vercel.ai.error.${name6}`
+var symbol6 = Symbol.for(marker6)
+var _a6
var InvalidMessageRoleError = class extends import_provider7.AISDKError {
constructor({
role,
message = `Invalid message role: '${role}'. Must be one of: "system", "user", "assistant", "tool".`
}) {
- super({ name: name6, message });
- this[_a6] = true;
- this.role = role;
+ super({ name: name6, message })
+ this[_a6] = true
+ this.role = role
}
static isInstance(error) {
- return import_provider7.AISDKError.hasMarker(error, marker6);
+ return import_provider7.AISDKError.hasMarker(error, marker6)
}
-};
-_a6 = symbol6;
+}
+_a6 = symbol6
// core/prompt/split-data-url.ts
function splitDataUrl(dataUrl) {
try {
- const [header, base64Content] = dataUrl.split(",");
+ const [header, base64Content] = dataUrl.split(",")
return {
mimeType: header.split(";")[0].split(":")[1],
base64Content
- };
+ }
} catch (error) {
return {
mimeType: void 0,
base64Content: void 0
- };
+ }
}
}
@@ -1175,23 +1179,23 @@ async function convertToLanguageModelPrompt({
downloadImplementation,
modelSupportsImageUrls,
modelSupportsUrl
- );
+ )
return [
...prompt.system != null ? [{ role: "system", content: prompt.system }] : [],
...prompt.messages.map(
(message) => convertToLanguageModelMessage(message, downloadedAssets)
)
- ];
+ ]
}
function convertToLanguageModelMessage(message, downloadedAssets) {
- const role = message.role;
+ const role = message.role
switch (role) {
case "system": {
return {
role: "system",
content: message.content,
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
case "user": {
if (typeof message.content === "string") {
@@ -1199,13 +1203,13 @@ function convertToLanguageModelMessage(message, downloadedAssets) {
role: "user",
content: [{ type: "text", text: message.content }],
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
return {
role: "user",
content: message.content.map((part) => convertPartToLanguageModelPart(part, downloadedAssets)).filter((part) => part.type !== "text" || part.text !== ""),
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
case "assistant": {
if (typeof message.content === "string") {
@@ -1213,7 +1217,7 @@ function convertToLanguageModelMessage(message, downloadedAssets) {
role: "assistant",
content: [{ type: "text", text: message.content }],
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
return {
role: "assistant",
@@ -1221,14 +1225,14 @@ function convertToLanguageModelMessage(message, downloadedAssets) {
// remove empty text parts:
(part) => part.type !== "text" || part.text !== ""
).map((part) => {
- const { experimental_providerMetadata, ...rest } = part;
+ const { experimental_providerMetadata, ...rest } = part
return {
...rest,
providerMetadata: experimental_providerMetadata
- };
+ }
}),
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
case "tool": {
return {
@@ -1243,11 +1247,11 @@ function convertToLanguageModelMessage(message, downloadedAssets) {
providerMetadata: part.experimental_providerMetadata
})),
providerMetadata: message.experimental_providerMetadata
- };
+ }
}
default: {
- const _exhaustiveCheck = role;
- throw new InvalidMessageRoleError({ role: _exhaustiveCheck });
+ const _exhaustiveCheck = role
+ throw new InvalidMessageRoleError({ role: _exhaustiveCheck })
}
}
}
@@ -1263,90 +1267,90 @@ async function downloadAssets(messages, downloadImplementation, modelSupportsIma
// support string urls:
typeof part === "string" && (part.startsWith("http:") || part.startsWith("https:")) ? new URL(part) : part
)
- ).filter((image) => image instanceof URL).filter((url) => !modelSupportsUrl(url));
+ ).filter((image) => image instanceof URL).filter((url) => !modelSupportsUrl(url))
const downloadedImages = await Promise.all(
urls.map(async (url) => ({
url,
data: await downloadImplementation({ url })
}))
- );
+ )
return Object.fromEntries(
downloadedImages.map(({ url, data }) => [url.toString(), data])
- );
+ )
}
function convertPartToLanguageModelPart(part, downloadedAssets) {
- var _a14;
+ var _a14
if (part.type === "text") {
return {
type: "text",
text: part.text,
providerMetadata: part.experimental_providerMetadata
- };
+ }
}
- let mimeType = part.mimeType;
- let data;
- let content;
- let normalizedData;
- const type = part.type;
+ let mimeType = part.mimeType
+ let data
+ let content
+ let normalizedData
+ const type = part.type
switch (type) {
case "image":
- data = part.image;
- break;
+ data = part.image
+ break
case "file":
- data = part.data;
- break;
+ data = part.data
+ break
default:
- throw new Error(`Unsupported part type: ${type}`);
+ throw new Error(`Unsupported part type: ${type}`)
}
try {
- content = typeof data === "string" ? new URL(data) : data;
+ content = typeof data === "string" ? new URL(data) : data
} catch (error) {
- content = data;
+ content = data
}
if (content instanceof URL) {
if (content.protocol === "data:") {
const { mimeType: dataUrlMimeType, base64Content } = splitDataUrl(
content.toString()
- );
+ )
if (dataUrlMimeType == null || base64Content == null) {
- throw new Error(`Invalid data URL format in part ${type}`);
+ throw new Error(`Invalid data URL format in part ${type}`)
}
- mimeType = dataUrlMimeType;
- normalizedData = convertDataContentToUint8Array(base64Content);
+ mimeType = dataUrlMimeType
+ normalizedData = convertDataContentToUint8Array(base64Content)
} else {
- const downloadedFile = downloadedAssets[content.toString()];
+ const downloadedFile = downloadedAssets[content.toString()]
if (downloadedFile) {
- normalizedData = downloadedFile.data;
- mimeType != null ? mimeType : mimeType = downloadedFile.mimeType;
+ normalizedData = downloadedFile.data
+ mimeType != null ? mimeType : mimeType = downloadedFile.mimeType
} else {
- normalizedData = content;
+ normalizedData = content
}
}
} else {
- normalizedData = convertDataContentToUint8Array(content);
+ normalizedData = convertDataContentToUint8Array(content)
}
switch (type) {
case "image": {
if (normalizedData instanceof Uint8Array) {
- mimeType = (_a14 = detectImageMimeType(normalizedData)) != null ? _a14 : mimeType;
+ mimeType = (_a14 = detectImageMimeType(normalizedData)) != null ? _a14 : mimeType
}
return {
type: "image",
image: normalizedData,
mimeType,
providerMetadata: part.experimental_providerMetadata
- };
+ }
}
case "file": {
if (mimeType == null) {
- throw new Error(`Mime type is missing for file part`);
+ throw new Error(`Mime type is missing for file part`)
}
return {
type: "file",
data: normalizedData instanceof Uint8Array ? convertDataContentToBase64String(normalizedData) : normalizedData,
mimeType,
providerMetadata: part.experimental_providerMetadata
- };
+ }
}
}
}
@@ -1368,14 +1372,14 @@ function prepareCallSettings({
parameter: "maxTokens",
value: maxTokens,
message: "maxTokens must be an integer"
- });
+ })
}
if (maxTokens < 1) {
throw new InvalidArgumentError({
parameter: "maxTokens",
value: maxTokens,
message: "maxTokens must be >= 1"
- });
+ })
}
}
if (temperature != null) {
@@ -1384,7 +1388,7 @@ function prepareCallSettings({
parameter: "temperature",
value: temperature,
message: "temperature must be a number"
- });
+ })
}
}
if (topP != null) {
@@ -1393,7 +1397,7 @@ function prepareCallSettings({
parameter: "topP",
value: topP,
message: "topP must be a number"
- });
+ })
}
}
if (topK != null) {
@@ -1402,7 +1406,7 @@ function prepareCallSettings({
parameter: "topK",
value: topK,
message: "topK must be a number"
- });
+ })
}
}
if (presencePenalty != null) {
@@ -1411,7 +1415,7 @@ function prepareCallSettings({
parameter: "presencePenalty",
value: presencePenalty,
message: "presencePenalty must be a number"
- });
+ })
}
}
if (frequencyPenalty != null) {
@@ -1420,7 +1424,7 @@ function prepareCallSettings({
parameter: "frequencyPenalty",
value: frequencyPenalty,
message: "frequencyPenalty must be a number"
- });
+ })
}
}
if (seed != null) {
@@ -1429,7 +1433,7 @@ function prepareCallSettings({
parameter: "seed",
value: seed,
message: "seed must be an integer"
- });
+ })
}
}
return {
@@ -1441,22 +1445,22 @@ function prepareCallSettings({
frequencyPenalty,
stopSequences: stopSequences != null && stopSequences.length > 0 ? stopSequences : void 0,
seed
- };
+ }
}
// core/prompt/standardize-prompt.ts
-var import_provider9 = require("@ai-sdk/provider");
-var import_provider_utils4 = require("@ai-sdk/provider-utils");
-var import_zod7 = require("zod");
+var import_provider9 = require("@ai-sdk/provider")
+var import_provider_utils4 = require("@ai-sdk/provider-utils")
+var import_zod7 = require("zod")
// core/prompt/message.ts
-var import_zod6 = require("zod");
+var import_zod6 = require("zod")
// core/types/provider-metadata.ts
-var import_zod3 = require("zod");
+var import_zod3 = require("zod")
// core/types/json-value.ts
-var import_zod2 = require("zod");
+var import_zod2 = require("zod")
var jsonValueSchema = import_zod2.z.lazy(
() => import_zod2.z.union([
import_zod2.z.null(),
@@ -1466,19 +1470,19 @@ var jsonValueSchema = import_zod2.z.lazy(
import_zod2.z.record(import_zod2.z.string(), jsonValueSchema),
import_zod2.z.array(jsonValueSchema)
])
-);
+)
// core/types/provider-metadata.ts
var providerMetadataSchema = import_zod3.z.record(
import_zod3.z.string(),
import_zod3.z.record(import_zod3.z.string(), jsonValueSchema)
-);
+)
// core/prompt/content-part.ts
-var import_zod5 = require("zod");
+var import_zod5 = require("zod")
// core/prompt/tool-result-content.ts
-var import_zod4 = require("zod");
+var import_zod4 = require("zod")
var toolResultContentSchema = import_zod4.z.array(
import_zod4.z.union([
import_zod4.z.object({ type: import_zod4.z.literal("text"), text: import_zod4.z.string() }),
@@ -1488,32 +1492,32 @@ var toolResultContentSchema = import_zod4.z.array(
mimeType: import_zod4.z.string().optional()
})
])
-);
+)
// core/prompt/content-part.ts
var textPartSchema = import_zod5.z.object({
type: import_zod5.z.literal("text"),
text: import_zod5.z.string(),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var imagePartSchema = import_zod5.z.object({
type: import_zod5.z.literal("image"),
image: import_zod5.z.union([dataContentSchema, import_zod5.z.instanceof(URL)]),
mimeType: import_zod5.z.string().optional(),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var filePartSchema = import_zod5.z.object({
type: import_zod5.z.literal("file"),
data: import_zod5.z.union([dataContentSchema, import_zod5.z.instanceof(URL)]),
mimeType: import_zod5.z.string(),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var toolCallPartSchema = import_zod5.z.object({
type: import_zod5.z.literal("tool-call"),
toolCallId: import_zod5.z.string(),
toolName: import_zod5.z.string(),
args: import_zod5.z.unknown()
-});
+})
var toolResultPartSchema = import_zod5.z.object({
type: import_zod5.z.literal("tool-result"),
toolCallId: import_zod5.z.string(),
@@ -1522,14 +1526,14 @@ var toolResultPartSchema = import_zod5.z.object({
content: toolResultContentSchema.optional(),
isError: import_zod5.z.boolean().optional(),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
// core/prompt/message.ts
var coreSystemMessageSchema = import_zod6.z.object({
role: import_zod6.z.literal("system"),
content: import_zod6.z.string(),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var coreUserMessageSchema = import_zod6.z.object({
role: import_zod6.z.literal("user"),
content: import_zod6.z.union([
@@ -1537,7 +1541,7 @@ var coreUserMessageSchema = import_zod6.z.object({
import_zod6.z.array(import_zod6.z.union([textPartSchema, imagePartSchema, filePartSchema]))
]),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var coreAssistantMessageSchema = import_zod6.z.object({
role: import_zod6.z.literal("assistant"),
content: import_zod6.z.union([
@@ -1545,36 +1549,36 @@ var coreAssistantMessageSchema = import_zod6.z.object({
import_zod6.z.array(import_zod6.z.union([textPartSchema, toolCallPartSchema]))
]),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var coreToolMessageSchema = import_zod6.z.object({
role: import_zod6.z.literal("tool"),
content: import_zod6.z.array(toolResultPartSchema),
experimental_providerMetadata: providerMetadataSchema.optional()
-});
+})
var coreMessageSchema = import_zod6.z.union([
coreSystemMessageSchema,
coreUserMessageSchema,
coreAssistantMessageSchema,
coreToolMessageSchema
-]);
+])
// core/prompt/detect-prompt-type.ts
function detectPromptType(prompt) {
if (!Array.isArray(prompt)) {
- return "other";
+ return "other"
}
if (prompt.length === 0) {
- return "messages";
+ return "messages"
}
- const characteristics = prompt.map(detectSingleMessageCharacteristics);
+ const characteristics = prompt.map(detectSingleMessageCharacteristics)
if (characteristics.some((c) => c === "has-ui-specific-parts")) {
- return "ui-messages";
+ return "ui-messages"
} else if (characteristics.every(
(c) => c === "has-core-specific-parts" || c === "message"
)) {
- return "messages";
+ return "messages"
} else {
- return "other";
+ return "other"
}
}
function detectSingleMessageCharacteristics(message) {
@@ -1582,129 +1586,129 @@ function detectSingleMessageCharacteristics(message) {
message.role === "data" || // UI-only role
"toolInvocations" in message || // UI-specific field
"experimental_attachments" in message)) {
- return "has-ui-specific-parts";
+ return "has-ui-specific-parts"
} else if (typeof message === "object" && message !== null && "content" in message && (Array.isArray(message.content) || // Core messages can have array content
"experimental_providerMetadata" in message)) {
- return "has-core-specific-parts";
+ return "has-core-specific-parts"
} else if (typeof message === "object" && message !== null && "role" in message && "content" in message && typeof message.content === "string" && ["system", "user", "assistant", "tool"].includes(message.role)) {
- return "message";
+ return "message"
} else {
- return "other";
+ return "other"
}
}
// core/prompt/attachments-to-parts.ts
function attachmentsToParts(attachments) {
- var _a14, _b, _c;
- const parts = [];
+ var _a14, _b, _c
+ const parts = []
for (const attachment of attachments) {
- let url;
+ let url
try {
- url = new URL(attachment.url);
+ url = new URL(attachment.url)
} catch (error) {
- throw new Error(`Invalid URL: ${attachment.url}`);
+ throw new Error(`Invalid URL: ${attachment.url}`)
}
switch (url.protocol) {
case "http:":
case "https:": {
if ((_a14 = attachment.contentType) == null ? void 0 : _a14.startsWith("image/")) {
- parts.push({ type: "image", image: url });
+ parts.push({ type: "image", image: url })
} else {
if (!attachment.contentType) {
throw new Error(
"If the attachment is not an image, it must specify a content type"
- );
+ )
}
parts.push({
type: "file",
data: url,
mimeType: attachment.contentType
- });
+ })
}
- break;
+ break
}
case "data:": {
- let header;
- let base64Content;
- let mimeType;
+ let header
+ let base64Content
+ let mimeType
try {
- [header, base64Content] = attachment.url.split(",");
- mimeType = header.split(";")[0].split(":")[1];
+ [header, base64Content] = attachment.url.split(",")
+ mimeType = header.split(";")[0].split(":")[1]
} catch (error) {
- throw new Error(`Error processing data URL: ${attachment.url}`);
+ throw new Error(`Error processing data URL: ${attachment.url}`)
}
if (mimeType == null || base64Content == null) {
- throw new Error(`Invalid data URL format: ${attachment.url}`);
+ throw new Error(`Invalid data URL format: ${attachment.url}`)
}
if ((_b = attachment.contentType) == null ? void 0 : _b.startsWith("image/")) {
parts.push({
type: "image",
image: convertDataContentToUint8Array(base64Content)
- });
+ })
} else if ((_c = attachment.contentType) == null ? void 0 : _c.startsWith("text/")) {
parts.push({
type: "text",
text: convertUint8ArrayToText(
convertDataContentToUint8Array(base64Content)
)
- });
+ })
} else {
if (!attachment.contentType) {
throw new Error(
"If the attachment is not an image or text, it must specify a content type"
- );
+ )
}
parts.push({
type: "file",
data: base64Content,
mimeType: attachment.contentType
- });
+ })
}
- break;
+ break
}
default: {
- throw new Error(`Unsupported URL protocol: ${url.protocol}`);
+ throw new Error(`Unsupported URL protocol: ${url.protocol}`)
}
}
}
- return parts;
+ return parts
}
// core/prompt/message-conversion-error.ts
-var import_provider8 = require("@ai-sdk/provider");
-var name7 = "AI_MessageConversionError";
-var marker7 = `vercel.ai.error.${name7}`;
-var symbol7 = Symbol.for(marker7);
-var _a7;
+var import_provider8 = require("@ai-sdk/provider")
+var name7 = "AI_MessageConversionError"
+var marker7 = `vercel.ai.error.${name7}`
+var symbol7 = Symbol.for(marker7)
+var _a7
var MessageConversionError = class extends import_provider8.AISDKError {
constructor({
originalMessage,
message
}) {
- super({ name: name7, message });
- this[_a7] = true;
- this.originalMessage = originalMessage;
+ super({ name: name7, message })
+ this[_a7] = true
+ this.originalMessage = originalMessage
}
static isInstance(error) {
- return import_provider8.AISDKError.hasMarker(error, marker7);
+ return import_provider8.AISDKError.hasMarker(error, marker7)
}
-};
-_a7 = symbol7;
+}
+_a7 = symbol7
// core/prompt/convert-to-core-messages.ts
function convertToCoreMessages(messages, options) {
- var _a14;
- const tools = (_a14 = options == null ? void 0 : options.tools) != null ? _a14 : {};
- const coreMessages = [];
+ var _a14
+ const tools = (_a14 = options == null ? void 0 : options.tools) != null ? _a14 : {}
+ const coreMessages = []
for (const message of messages) {
- const { role, content, toolInvocations, experimental_attachments } = message;
+ const { role, content, toolInvocations, experimental_attachments } = message
switch (role) {
case "system": {
coreMessages.push({
role: "system",
content
- });
- break;
+ })
+ break
}
case "user": {
coreMessages.push({
@@ -1713,13 +1717,13 @@ function convertToCoreMessages(messages, options) {
{ type: "text", text: content },
...attachmentsToParts(experimental_attachments)
] : content
- });
- break;
+ })
+ break
}
case "assistant": {
if (toolInvocations == null || toolInvocations.length === 0) {
- coreMessages.push({ role: "assistant", content });
- break;
+ coreMessages.push({ role: "assistant", content })
+ break
}
coreMessages.push({
role: "assistant",
@@ -1734,7 +1738,7 @@ function convertToCoreMessages(messages, options) {
})
)
]
- });
+ })
coreMessages.push({
role: "tool",
content: toolInvocations.map((toolInvocation) => {
@@ -1742,10 +1746,10 @@ function convertToCoreMessages(messages, options) {
throw new MessageConversionError({
originalMessage: message,
message: "ToolInvocation must have a result: " + JSON.stringify(toolInvocation)
- });
+ })
}
- const { toolCallId, toolName, result } = toolInvocation;
- const tool2 = tools[toolName];
+ const { toolCallId, toolName, result } = toolInvocation
+ const tool2 = tools[toolName]
return (tool2 == null ? void 0 : tool2.experimental_toToolResultContent) != null ? {
type: "tool-result",
toolCallId,
@@ -1757,24 +1761,24 @@ function convertToCoreMessages(messages, options) {
toolCallId,
toolName,
result
- };
+ }
})
- });
- break;
+ })
+ break
}
case "data": {
- break;
+ break
}
default: {
- const _exhaustiveCheck = role;
+ const _exhaustiveCheck = role
throw new MessageConversionError({
originalMessage: message,
message: `Unsupported role: ${_exhaustiveCheck}`
- });
+ })
}
}
}
- return coreMessages;
+ return coreMessages
}
// core/prompt/standardize-prompt.ts
@@ -1786,26 +1790,26 @@ function standardizePrompt({
throw new import_provider9.InvalidPromptError({
prompt,
message: "prompt or messages must be defined"
- });
+ })
}
if (prompt.prompt != null && prompt.messages != null) {
throw new import_provider9.InvalidPromptError({
prompt,
message: "prompt and messages cannot be defined at the same time"
- });
+ })
}
if (prompt.system != null && typeof prompt.system !== "string") {
throw new import_provider9.InvalidPromptError({
prompt,
message: "system must be a string"
- });
+ })
}
if (prompt.prompt != null) {
if (typeof prompt.prompt !== "string") {
throw new import_provider9.InvalidPromptError({
prompt,
message: "prompt must be a string"
- });
+ })
}
return {
type: "prompt",
@@ -1816,37 +1820,37 @@ function standardizePrompt({
content: prompt.prompt
}
]
- };
+ }
}
if (prompt.messages != null) {
- const promptType = detectPromptType(prompt.messages);
+ const promptType = detectPromptType(prompt.messages)
if (promptType === "other") {
throw new import_provider9.InvalidPromptError({
prompt,
message: "messages must be an array of CoreMessage or UIMessage"
- });
+ })
}
const messages = promptType === "ui-messages" ? convertToCoreMessages(prompt.messages, {
tools
- }) : prompt.messages;
+ }) : prompt.messages
const validationResult = (0, import_provider_utils4.safeValidateTypes)({
value: messages,
schema: import_zod7.z.array(coreMessageSchema)
- });
+ })
if (!validationResult.success) {
throw new import_provider9.InvalidPromptError({
prompt,
message: "messages must be an array of CoreMessage or UIMessage",
cause: validationResult.error
- });
+ })
}
return {
type: "messages",
messages,
system: prompt.system
- };
+ }
}
- throw new Error("unreachable");
+ throw new Error("unreachable")
}
// core/types/usage.ts
@@ -1858,20 +1862,20 @@ function calculateLanguageModelUsage({
promptTokens,
completionTokens,
totalTokens: promptTokens + completionTokens
- };
+ }
}
function addLanguageModelUsage(usage1, usage2) {
return {
promptTokens: usage1.promptTokens + usage2.promptTokens,
completionTokens: usage1.completionTokens + usage2.completionTokens,
totalTokens: usage1.totalTokens + usage2.totalTokens
- };
+ }
}
// core/generate-object/inject-json-instruction.ts
-var DEFAULT_SCHEMA_PREFIX = "JSON schema:";
-var DEFAULT_SCHEMA_SUFFIX = "You MUST answer with a JSON object that matches the JSON schema above.";
-var DEFAULT_GENERIC_SUFFIX = "You MUST answer with JSON.";
+var DEFAULT_SCHEMA_PREFIX = "JSON schema:"
+var DEFAULT_SCHEMA_SUFFIX = "You MUST answer with a JSON object that matches the JSON schema above."
+var DEFAULT_GENERIC_SUFFIX = "You MUST answer with JSON."
function injectJsonInstruction({
prompt,
schema,
@@ -1885,27 +1889,27 @@ function injectJsonInstruction({
schemaPrefix,
schema != null ? JSON.stringify(schema) : void 0,
schemaSuffix
- ].filter((line) => line != null).join("\n");
+ ].filter((line) => line != null).join("\n")
}
// core/generate-object/output-strategy.ts
-var import_provider10 = require("@ai-sdk/provider");
-var import_provider_utils5 = require("@ai-sdk/provider-utils");
-var import_ui_utils2 = require("@ai-sdk/ui-utils");
+var import_provider10 = require("@ai-sdk/provider")
+var import_provider_utils5 = require("@ai-sdk/provider-utils")
+var import_ui_utils2 = require("@ai-sdk/ui-utils")
// core/util/async-iterable-stream.ts
function createAsyncIterableStream(source) {
- const stream = source.pipeThrough(new TransformStream());
+ const stream = source.pipeThrough(new TransformStream())
stream[Symbol.asyncIterator] = () => {
- const reader = stream.getReader();
+ const reader = stream.getReader()
return {
async next() {
- const { done, value } = await reader.read();
- return done ? { done: true, value: void 0 } : { done: false, value };
+ const { done, value } = await reader.read()
+ return done ? { done: true, value: void 0 } : { done: false, value }
}
- };
- };
- return stream;
+ }
+ }
+ return stream
}
// core/generate-object/output-strategy.ts
@@ -1913,7 +1917,7 @@ var noSchemaOutputStrategy = {
type: "no-schema",
jsonSchema: void 0,
validatePartialResult({ value, textDelta }) {
- return { success: true, value: { partial: value, textDelta } };
+ return { success: true, value: { partial: value, textDelta } }
},
validateFinalResult(value, context) {
return value === void 0 ? {
@@ -1924,14 +1928,14 @@ var noSchemaOutputStrategy = {
response: context.response,
usage: context.usage
})
- } : { success: true, value };
+ } : { success: true, value }
},
createElementStream() {
throw new import_provider10.UnsupportedFunctionalityError({
functionality: "element streams in no-schema mode"
- });
+ })
}
-};
+}
var objectOutputStrategy = (schema) => ({
type: "object",
jsonSchema: schema.jsonSchema,
@@ -1943,19 +1947,19 @@ var objectOutputStrategy = (schema) => ({
partial: value,
textDelta
}
- };
+ }
},
validateFinalResult(value) {
- return (0, import_provider_utils5.safeValidateTypes)({ value, schema });
+ return (0, import_provider_utils5.safeValidateTypes)({ value, schema })
},
createElementStream() {
throw new import_provider10.UnsupportedFunctionalityError({
functionality: "element streams in object mode"
- });
+ })
}
-});
+})
var arrayOutputStrategy = (schema) => {
- const { $schema, ...itemSchema } = schema.jsonSchema;
+ const { $schema, ...itemSchema } = schema.jsonSchema
return {
type: "enum",
// wrap in object that contains array of elements, since most LLMs will not
@@ -1971,7 +1975,7 @@ var arrayOutputStrategy = (schema) => {
additionalProperties: false
},
validatePartialResult({ value, latestObject, isFirstDelta, isFinalDelta }) {
- var _a14;
+ var _a14
if (!(0, import_provider10.isJSONObject)(value) || !(0, import_provider10.isJSONArray)(value.elements)) {
return {
success: false,
@@ -1979,32 +1983,32 @@ var arrayOutputStrategy = (schema) => {
value,
cause: "value must be an object that contains an array of elements"
})
- };
+ }
}
- const inputArray = value.elements;
- const resultArray = [];
+ const inputArray = value.elements
+ const resultArray = []
for (let i = 0; i < inputArray.length; i++) {
- const element = inputArray[i];
- const result = (0, import_provider_utils5.safeValidateTypes)({ value: element, schema });
+ const element = inputArray[i]
+ const result = (0, import_provider_utils5.safeValidateTypes)({ value: element, schema })
if (i === inputArray.length - 1 && !isFinalDelta) {
- continue;
+ continue
}
if (!result.success) {
- return result;
+ return result
}
- resultArray.push(result.value);
+ resultArray.push(result.value)
}
- const publishedElementCount = (_a14 = latestObject == null ? void 0 : latestObject.length) != null ? _a14 : 0;
- let textDelta = "";
+ const publishedElementCount = (_a14 = latestObject == null ? void 0 : latestObject.length) != null ? _a14 : 0
+ let textDelta = ""
if (isFirstDelta) {
- textDelta += "[";
+ textDelta += "["
}
if (publishedElementCount > 0) {
- textDelta += ",";
+ textDelta += ","
}
- textDelta += resultArray.slice(publishedElementCount).map((element) => JSON.stringify(element)).join(",");
+ textDelta += resultArray.slice(publishedElementCount).map((element) => JSON.stringify(element)).join(",")
if (isFinalDelta) {
- textDelta += "]";
+ textDelta += "]"
}
return {
success: true,
@@ -2012,7 +2016,7 @@ var arrayOutputStrategy = (schema) => {
partial: resultArray,
textDelta
}
- };
+ }
},
validateFinalResult(value) {
if (!(0, import_provider10.isJSONObject)(value) || !(0, import_provider10.isJSONArray)(value.elements)) {
@@ -2022,51 +2026,51 @@ var arrayOutputStrategy = (schema) => {
value,
cause: "value must be an object that contains an array of elements"
})
- };
- }
- const inputArray = value.elements;
- for (const element of inputArray) {
- const result = (0, import_provider_utils5.safeValidateTypes)({ value: element, schema });
- if (!result.success) {
- return result;
}
}
- return { success: true, value: inputArray };
+ const inputArray = value.elements
+ for (const element of inputArray) {
+ const result = (0, import_provider_utils5.safeValidateTypes)({ value: element, schema })
+ if (!result.success) {
+ return result
+ }
+ }
+ return { success: true, value: inputArray }
},
createElementStream(originalStream) {
- let publishedElements = 0;
+ let publishedElements = 0
return createAsyncIterableStream(
originalStream.pipeThrough(
new TransformStream({
transform(chunk, controller) {
switch (chunk.type) {
case "object": {
- const array = chunk.object;
+ const array = chunk.object
for (; publishedElements < array.length; publishedElements++) {
- controller.enqueue(array[publishedElements]);
+ controller.enqueue(array[publishedElements])
}
- break;
+ break
}
case "text-delta":
case "finish":
- break;
+ break
case "error":
- controller.error(chunk.error);
- break;
+ controller.error(chunk.error)
+ break
default: {
- const _exhaustiveCheck = chunk;
+ const _exhaustiveCheck = chunk
throw new Error(
`Unsupported chunk type: ${_exhaustiveCheck}`
- );
+ )
}
}
}
})
)
- );
+ )
}
- };
-};
+ }
+}
var enumOutputStrategy = (enumValues) => {
return {
type: "enum",
@@ -2090,29 +2094,29 @@ var enumOutputStrategy = (enumValues) => {
value,
cause: 'value must be an object that contains a string in the "result" property.'
})
- };
+ }
}
- const result = value.result;
+ const result = value.result
return enumValues.includes(result) ? { success: true, value: result } : {
success: false,
error: new import_provider10.TypeValidationError({
value,
cause: "value must be a string in the enum"
})
- };
+ }
},
validatePartialResult() {
throw new import_provider10.UnsupportedFunctionalityError({
functionality: "partial results in enum mode"
- });
+ })
},
createElementStream() {
throw new import_provider10.UnsupportedFunctionalityError({
functionality: "element streams in enum mode"
- });
+ })
}
- };
-};
+ }
+}
function getOutputStrategy({
output,
schema,
@@ -2120,16 +2124,16 @@ function getOutputStrategy({
}) {
switch (output) {
case "object":
- return objectOutputStrategy((0, import_ui_utils2.asSchema)(schema));
+ return objectOutputStrategy((0, import_ui_utils2.asSchema)(schema))
case "array":
- return arrayOutputStrategy((0, import_ui_utils2.asSchema)(schema));
+ return arrayOutputStrategy((0, import_ui_utils2.asSchema)(schema))
case "enum":
- return enumOutputStrategy(enumValues);
+ return enumOutputStrategy(enumValues)
case "no-schema":
- return noSchemaOutputStrategy;
+ return noSchemaOutputStrategy
default: {
- const _exhaustiveCheck = output;
- throw new Error(`Unsupported output: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = output
+ throw new Error(`Unsupported output: ${_exhaustiveCheck}`)
}
}
}
@@ -2148,7 +2152,7 @@ function validateObjectGenerationInput({
parameter: "output",
value: output,
message: "Invalid output type."
- });
+ })
}
if (output === "no-schema") {
if (mode === "auto" || mode === "tool") {
@@ -2156,35 +2160,35 @@ function validateObjectGenerationInput({
parameter: "mode",
value: mode,
message: 'Mode must be "json" for no-schema output.'
- });
+ })
}
if (schema != null) {
throw new InvalidArgumentError({
parameter: "schema",
value: schema,
message: "Schema is not supported for no-schema output."
- });
+ })
}
if (schemaDescription != null) {
throw new InvalidArgumentError({
parameter: "schemaDescription",
value: schemaDescription,
message: "Schema description is not supported for no-schema output."
- });
+ })
}
if (schemaName != null) {
throw new InvalidArgumentError({
parameter: "schemaName",
value: schemaName,
message: "Schema name is not supported for no-schema output."
- });
+ })
}
if (enumValues != null) {
throw new InvalidArgumentError({
parameter: "enumValues",
value: enumValues,
message: "Enum values are not supported for no-schema output."
- });
+ })
}
}
if (output === "object") {
@@ -2193,14 +2197,14 @@ function validateObjectGenerationInput({
parameter: "schema",
value: schema,
message: "Schema is required for object output."
- });
+ })
}
if (enumValues != null) {
throw new InvalidArgumentError({
parameter: "enumValues",
value: enumValues,
message: "Enum values are not supported for object output."
- });
+ })
}
}
if (output === "array") {
@@ -2209,14 +2213,14 @@ function validateObjectGenerationInput({
parameter: "schema",
value: schema,
message: "Element schema is required for array output."
- });
+ })
}
if (enumValues != null) {
throw new InvalidArgumentError({
parameter: "enumValues",
value: enumValues,
message: "Enum values are not supported for array output."
- });
+ })
}
}
if (output === "enum") {
@@ -2225,28 +2229,28 @@ function validateObjectGenerationInput({
parameter: "schema",
value: schema,
message: "Schema is not supported for enum output."
- });
+ })
}
if (schemaDescription != null) {
throw new InvalidArgumentError({
parameter: "schemaDescription",
value: schemaDescription,
message: "Schema description is not supported for enum output."
- });
+ })
}
if (schemaName != null) {
throw new InvalidArgumentError({
parameter: "schemaName",
value: schemaName,
message: "Schema name is not supported for enum output."
- });
+ })
}
if (enumValues == null) {
throw new InvalidArgumentError({
parameter: "enumValues",
value: enumValues,
message: "Enum values are required for enum output."
- });
+ })
}
for (const value of enumValues) {
if (typeof value !== "string") {
@@ -2254,14 +2258,14 @@ function validateObjectGenerationInput({
parameter: "enumValues",
value,
message: "Enum values must be strings."
- });
+ })
}
}
}
}
// core/generate-object/generate-object.ts
-var originalGenerateId = (0, import_provider_utils6.createIdGenerator)({ prefix: "aiobj", size: 24 });
+var originalGenerateId = (0, import_provider_utils6.createIdGenerator)({ prefix: "aiobj", size: 24 })
async function generateObject({
model,
enum: enumValues,
@@ -2292,23 +2296,23 @@ async function generateObject({
schemaName,
schemaDescription,
enumValues
- });
- const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg });
+ })
+ const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg })
const outputStrategy = getOutputStrategy({
output,
schema: inputSchema,
enumValues
- });
+ })
if (outputStrategy.type === "no-schema" && mode === void 0) {
- mode = "json";
+ mode = "json"
}
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { ...settings, maxRetries }
- });
- const tracer = getTracer(telemetry);
+ })
+ const tracer = getTracer(telemetry)
return recordSpan({
name: "ai.generateObject",
attributes: selectTelemetryAttributes({
@@ -2332,19 +2336,19 @@ async function generateObject({
}),
tracer,
fn: async (span) => {
- var _a14, _b;
+ var _a14, _b
if (mode === "auto" || mode == null) {
- mode = model.defaultObjectGenerationMode;
+ mode = model.defaultObjectGenerationMode
}
- let result;
- let finishReason;
- let usage;
- let warnings;
- let rawResponse;
- let response;
- let request;
- let logprobs;
- let resultProviderMetadata;
+ let result
+ let finishReason
+ let usage
+ let warnings
+ let rawResponse
+ let response
+ let request
+ let logprobs
+ let resultProviderMetadata
switch (mode) {
case "json": {
const standardizedPrompt = standardizePrompt({
@@ -2357,12 +2361,12 @@ async function generateObject({
messages
},
tools: void 0
- });
+ })
const promptMessages = await convertToLanguageModelPrompt({
prompt: standardizedPrompt,
modelSupportsImageUrls: model.supportsImageUrls,
modelSupportsUrl: model.supportsUrl
- });
+ })
const generateResult = await retry(
() => recordSpan({
name: "ai.generateObject.doGenerate",
@@ -2394,7 +2398,7 @@ async function generateObject({
}),
tracer,
fn: async (span2) => {
- var _a15, _b2, _c, _d, _e, _f;
+ var _a15, _b2, _c, _d, _e, _f
const result2 = await model.doGenerate({
mode: {
type: "object-json",
@@ -2408,18 +2412,18 @@ async function generateObject({
providerMetadata,
abortSignal,
headers
- });
+ })
const responseData = {
id: (_b2 = (_a15 = result2.response) == null ? void 0 : _a15.id) != null ? _b2 : generateId3(),
timestamp: (_d = (_c = result2.response) == null ? void 0 : _c.timestamp) != null ? _d : currentDate(),
modelId: (_f = (_e = result2.response) == null ? void 0 : _e.modelId) != null ? _f : model.modelId
- };
+ }
if (result2.text === void 0) {
throw new NoObjectGeneratedError({
message: "No object generated: the model did not return a response.",
response: responseData,
usage: calculateLanguageModelUsage(result2.usage)
- });
+ })
}
span2.setAttributes(
selectTelemetryAttributes({
@@ -2440,33 +2444,33 @@ async function generateObject({
"gen_ai.usage.completion_tokens": result2.usage.completionTokens
}
})
- );
- return { ...result2, objectText: result2.text, responseData };
+ )
+ return { ...result2, objectText: result2.text, responseData }
}
})
- );
- result = generateResult.objectText;
- finishReason = generateResult.finishReason;
- usage = generateResult.usage;
- warnings = generateResult.warnings;
- rawResponse = generateResult.rawResponse;
- logprobs = generateResult.logprobs;
- resultProviderMetadata = generateResult.providerMetadata;
- request = (_a14 = generateResult.request) != null ? _a14 : {};
- response = generateResult.responseData;
- break;
+ )
+ result = generateResult.objectText
+ finishReason = generateResult.finishReason
+ usage = generateResult.usage
+ warnings = generateResult.warnings
+ rawResponse = generateResult.rawResponse
+ logprobs = generateResult.logprobs
+ resultProviderMetadata = generateResult.providerMetadata
+ request = (_a14 = generateResult.request) != null ? _a14 : {}
+ response = generateResult.responseData
+ break
}
case "tool": {
const standardizedPrompt = standardizePrompt({
prompt: { system, prompt, messages },
tools: void 0
- });
+ })
const promptMessages = await convertToLanguageModelPrompt({
prompt: standardizedPrompt,
modelSupportsImageUrls: model.supportsImageUrls,
modelSupportsUrl: model.supportsUrl
- });
- const inputFormat = standardizedPrompt.type;
+ })
+ const inputFormat = standardizedPrompt.type
const generateResult = await retry(
() => recordSpan({
name: "ai.generateObject.doGenerate",
@@ -2498,7 +2502,7 @@ async function generateObject({
}),
tracer,
fn: async (span2) => {
- var _a15, _b2, _c, _d, _e, _f, _g, _h;
+ var _a15, _b2, _c, _d, _e, _f, _g, _h
const result2 = await model.doGenerate({
mode: {
type: "object-tool",
@@ -2515,19 +2519,19 @@ async function generateObject({
providerMetadata,
abortSignal,
headers
- });
- const objectText = (_b2 = (_a15 = result2.toolCalls) == null ? void 0 : _a15[0]) == null ? void 0 : _b2.args;
+ })
+ const objectText = (_b2 = (_a15 = result2.toolCalls) == null ? void 0 : _a15[0]) == null ? void 0 : _b2.args
const responseData = {
id: (_d = (_c = result2.response) == null ? void 0 : _c.id) != null ? _d : generateId3(),
timestamp: (_f = (_e = result2.response) == null ? void 0 : _e.timestamp) != null ? _f : currentDate(),
modelId: (_h = (_g = result2.response) == null ? void 0 : _g.modelId) != null ? _h : model.modelId
- };
+ }
if (objectText === void 0) {
throw new NoObjectGeneratedError({
message: "No object generated: the tool was not called.",
response: responseData,
usage: calculateLanguageModelUsage(result2.usage)
- });
+ })
}
span2.setAttributes(
selectTelemetryAttributes({
@@ -2548,33 +2552,33 @@ async function generateObject({
"gen_ai.usage.output_tokens": result2.usage.completionTokens
}
})
- );
- return { ...result2, objectText, responseData };
+ )
+ return { ...result2, objectText, responseData }
}
})
- );
- result = generateResult.objectText;
- finishReason = generateResult.finishReason;
- usage = generateResult.usage;
- warnings = generateResult.warnings;
- rawResponse = generateResult.rawResponse;
- logprobs = generateResult.logprobs;
- resultProviderMetadata = generateResult.providerMetadata;
- request = (_b = generateResult.request) != null ? _b : {};
- response = generateResult.responseData;
- break;
+ )
+ result = generateResult.objectText
+ finishReason = generateResult.finishReason
+ usage = generateResult.usage
+ warnings = generateResult.warnings
+ rawResponse = generateResult.rawResponse
+ logprobs = generateResult.logprobs
+ resultProviderMetadata = generateResult.providerMetadata
+ request = (_b = generateResult.request) != null ? _b : {}
+ response = generateResult.responseData
+ break
}
case void 0: {
throw new Error(
"Model does not have a default object generation mode."
- );
+ )
}
default: {
- const _exhaustiveCheck = mode;
- throw new Error(`Unsupported mode: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = mode
+ throw new Error(`Unsupported mode: ${_exhaustiveCheck}`)
}
}
- const parseResult = (0, import_provider_utils6.safeParseJSON)({ text: result });
+ const parseResult = (0, import_provider_utils6.safeParseJSON)({ text: result })
if (!parseResult.success) {
throw new NoObjectGeneratedError({
message: "No object generated: could not parse the response.",
@@ -2582,7 +2586,7 @@ async function generateObject({
text: result,
response,
usage: calculateLanguageModelUsage(usage)
- });
+ })
}
const validationResult = outputStrategy.validateFinalResult(
parseResult.value,
@@ -2591,7 +2595,7 @@ async function generateObject({
response,
usage: calculateLanguageModelUsage(usage)
}
- );
+ )
if (!validationResult.success) {
throw new NoObjectGeneratedError({
message: "No object generated: response did not match schema.",
@@ -2599,7 +2603,7 @@ async function generateObject({
text: result,
response,
usage: calculateLanguageModelUsage(usage)
- });
+ })
}
span.setAttributes(
selectTelemetryAttributes({
@@ -2613,7 +2617,7 @@ async function generateObject({
"ai.usage.completionTokens": usage.completionTokens
}
})
- );
+ )
return new DefaultGenerateObjectResult({
object: validationResult.value,
finishReason,
@@ -2626,155 +2630,155 @@ async function generateObject({
},
logprobs,
providerMetadata: resultProviderMetadata
- });
+ })
}
- });
+ })
}
var DefaultGenerateObjectResult = class {
constructor(options) {
- this.object = options.object;
- this.finishReason = options.finishReason;
- this.usage = options.usage;
- this.warnings = options.warnings;
- this.experimental_providerMetadata = options.providerMetadata;
- this.response = options.response;
- this.request = options.request;
- this.logprobs = options.logprobs;
+ this.object = options.object
+ this.finishReason = options.finishReason
+ this.usage = options.usage
+ this.warnings = options.warnings
+ this.experimental_providerMetadata = options.providerMetadata
+ this.response = options.response
+ this.request = options.request
+ this.logprobs = options.logprobs
}
toJsonResponse(init) {
- var _a14;
+ var _a14
return new Response(JSON.stringify(this.object), {
status: (_a14 = init == null ? void 0 : init.status) != null ? _a14 : 200,
headers: prepareResponseHeaders(init == null ? void 0 : init.headers, {
contentType: "application/json; charset=utf-8"
})
- });
+ })
}
-};
+}
// core/generate-object/stream-object.ts
-var import_provider_utils7 = require("@ai-sdk/provider-utils");
-var import_ui_utils3 = require("@ai-sdk/ui-utils");
+var import_provider_utils7 = require("@ai-sdk/provider-utils")
+var import_ui_utils3 = require("@ai-sdk/ui-utils")
// util/delayed-promise.ts
var DelayedPromise = class {
constructor() {
- this.status = { type: "pending" };
- this._resolve = void 0;
- this._reject = void 0;
+ this.status = { type: "pending" }
+ this._resolve = void 0
+ this._reject = void 0
}
get value() {
if (this.promise) {
- return this.promise;
+ return this.promise
}
this.promise = new Promise((resolve, reject) => {
if (this.status.type === "resolved") {
- resolve(this.status.value);
+ resolve(this.status.value)
} else if (this.status.type === "rejected") {
- reject(this.status.error);
+ reject(this.status.error)
}
- this._resolve = resolve;
- this._reject = reject;
- });
- return this.promise;
+ this._resolve = resolve
+ this._reject = reject
+ })
+ return this.promise
}
resolve(value) {
- var _a14;
- this.status = { type: "resolved", value };
+ var _a14
+ this.status = { type: "resolved", value }
if (this.promise) {
- (_a14 = this._resolve) == null ? void 0 : _a14.call(this, value);
+ (_a14 = this._resolve) == null ? void 0 : _a14.call(this, value)
}
}
reject(error) {
- var _a14;
- this.status = { type: "rejected", error };
+ var _a14
+ this.status = { type: "rejected", error }
if (this.promise) {
- (_a14 = this._reject) == null ? void 0 : _a14.call(this, error);
+ (_a14 = this._reject) == null ? void 0 : _a14.call(this, error)
}
}
-};
+}
// util/create-resolvable-promise.ts
function createResolvablePromise() {
- let resolve;
- let reject;
+ let resolve
+ let reject
const promise = new Promise((res, rej) => {
- resolve = res;
- reject = rej;
- });
+ resolve = res
+ reject = rej
+ })
return {
promise,
resolve,
reject
- };
+ }
}
// core/util/create-stitchable-stream.ts
function createStitchableStream() {
- let innerStreamReaders = [];
- let controller = null;
- let isClosed = false;
- let waitForNewStream = createResolvablePromise();
+ let innerStreamReaders = []
+ let controller = null
+ let isClosed = false
+ let waitForNewStream = createResolvablePromise()
const processPull = async () => {
if (isClosed && innerStreamReaders.length === 0) {
- controller == null ? void 0 : controller.close();
- return;
+ controller == null ? void 0 : controller.close()
+ return
}
if (innerStreamReaders.length === 0) {
- waitForNewStream = createResolvablePromise();
- await waitForNewStream.promise;
- return processPull();
+ waitForNewStream = createResolvablePromise()
+ await waitForNewStream.promise
+ return processPull()
}
try {
- const { value, done } = await innerStreamReaders[0].read();
+ const { value, done } = await innerStreamReaders[0].read()
if (done) {
- innerStreamReaders.shift();
+ innerStreamReaders.shift()
if (innerStreamReaders.length > 0) {
- await processPull();
+ await processPull()
} else if (isClosed) {
- controller == null ? void 0 : controller.close();
+ controller == null ? void 0 : controller.close()
}
} else {
- controller == null ? void 0 : controller.enqueue(value);
+ controller == null ? void 0 : controller.enqueue(value)
}
} catch (error) {
- controller == null ? void 0 : controller.error(error);
- innerStreamReaders.shift();
+ controller == null ? void 0 : controller.error(error)
+ innerStreamReaders.shift()
if (isClosed && innerStreamReaders.length === 0) {
- controller == null ? void 0 : controller.close();
+ controller == null ? void 0 : controller.close()
}
}
- };
+ }
return {
stream: new ReadableStream({
start(controllerParam) {
- controller = controllerParam;
+ controller = controllerParam
},
pull: processPull,
async cancel() {
for (const reader of innerStreamReaders) {
- await reader.cancel();
+ await reader.cancel()
}
- innerStreamReaders = [];
- isClosed = true;
+ innerStreamReaders = []
+ isClosed = true
}
}),
addStream: (innerStream) => {
if (isClosed) {
- throw new Error("Cannot add inner stream: outer stream is closed");
+ throw new Error("Cannot add inner stream: outer stream is closed")
}
- innerStreamReaders.push(innerStream.getReader());
- waitForNewStream.resolve();
+ innerStreamReaders.push(innerStream.getReader())
+ waitForNewStream.resolve()
},
/**
* Gracefully close the outer stream. This will let the inner streams
* finish processing and then close the outer stream.
*/
close: () => {
- isClosed = true;
- waitForNewStream.resolve();
+ isClosed = true
+ waitForNewStream.resolve()
if (innerStreamReaders.length === 0) {
- controller == null ? void 0 : controller.close();
+ controller == null ? void 0 : controller.close()
}
},
/**
@@ -2782,23 +2786,23 @@ function createStitchableStream() {
* and close the outer stream.
*/
terminate: () => {
- isClosed = true;
- waitForNewStream.resolve();
- innerStreamReaders.forEach((reader) => reader.cancel());
- innerStreamReaders = [];
- controller == null ? void 0 : controller.close();
+ isClosed = true
+ waitForNewStream.resolve()
+ innerStreamReaders.forEach((reader) => reader.cancel())
+ innerStreamReaders = []
+ controller == null ? void 0 : controller.close()
}
- };
+ }
}
// core/util/now.ts
function now() {
- var _a14, _b;
- return (_b = (_a14 = globalThis == null ? void 0 : globalThis.performance) == null ? void 0 : _a14.now()) != null ? _b : Date.now();
+ var _a14, _b
+ return (_b = (_a14 = globalThis == null ? void 0 : globalThis.performance) == null ? void 0 : _a14.now()) != null ? _b : Date.now()
}
// core/generate-object/stream-object.ts
-var originalGenerateId2 = (0, import_provider_utils7.createIdGenerator)({ prefix: "aiobj", size: 24 });
+var originalGenerateId2 = (0, import_provider_utils7.createIdGenerator)({ prefix: "aiobj", size: 24 })
function streamObject({
model,
schema: inputSchema,
@@ -2828,10 +2832,10 @@ function streamObject({
schema: inputSchema,
schemaName,
schemaDescription
- });
- const outputStrategy = getOutputStrategy({ output, schema: inputSchema });
+ })
+ const outputStrategy = getOutputStrategy({ output, schema: inputSchema })
if (outputStrategy.type === "no-schema" && mode === void 0) {
- mode = "json";
+ mode = "json"
}
return new DefaultStreamObjectResult({
model,
@@ -2852,7 +2856,7 @@ function streamObject({
generateId: generateId3,
currentDate,
now: now2
- });
+ })
}
var DefaultStreamObjectResult = class {
constructor({
@@ -2875,24 +2879,24 @@ var DefaultStreamObjectResult = class {
currentDate,
now: now2
}) {
- this.objectPromise = new DelayedPromise();
- this.usagePromise = new DelayedPromise();
- this.providerMetadataPromise = new DelayedPromise();
- this.warningsPromise = new DelayedPromise();
- this.requestPromise = new DelayedPromise();
- this.responsePromise = new DelayedPromise();
- this.stitchableStream = createStitchableStream();
+ this.objectPromise = new DelayedPromise()
+ this.usagePromise = new DelayedPromise()
+ this.providerMetadataPromise = new DelayedPromise()
+ this.warningsPromise = new DelayedPromise()
+ this.requestPromise = new DelayedPromise()
+ this.responsePromise = new DelayedPromise()
+ this.stitchableStream = createStitchableStream()
const { maxRetries, retry } = prepareRetries({
maxRetries: maxRetriesArg
- });
+ })
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { ...settings, maxRetries }
- });
- const tracer = getTracer(telemetry);
- const self = this;
+ })
+ const tracer = getTracer(telemetry)
+ const self = this
recordSpan({
name: "ai.streamObject",
attributes: selectTelemetryAttributes({
@@ -2918,10 +2922,10 @@ var DefaultStreamObjectResult = class {
endWhenDone: false,
fn: async (rootSpan) => {
if (mode === "auto" || mode == null) {
- mode = model.defaultObjectGenerationMode;
+ mode = model.defaultObjectGenerationMode
}
- let callOptions;
- let transformer;
+ let callOptions
+ let transformer
switch (mode) {
case "json": {
const standardizedPrompt = standardizePrompt({
@@ -2934,7 +2938,7 @@ var DefaultStreamObjectResult = class {
messages
},
tools: void 0
- });
+ })
callOptions = {
mode: {
type: "object-json",
@@ -2952,28 +2956,28 @@ var DefaultStreamObjectResult = class {
providerMetadata: inputProviderMetadata,
abortSignal,
headers
- };
+ }
transformer = {
transform: (chunk, controller) => {
switch (chunk.type) {
case "text-delta":
- controller.enqueue(chunk.textDelta);
- break;
+ controller.enqueue(chunk.textDelta)
+ break
case "response-metadata":
case "finish":
case "error":
- controller.enqueue(chunk);
- break;
+ controller.enqueue(chunk)
+ break
}
}
- };
- break;
+ }
+ break
}
case "tool": {
const standardizedPrompt = standardizePrompt({
prompt: { system, prompt, messages },
tools: void 0
- });
+ })
callOptions = {
mode: {
type: "object-tool",
@@ -2994,31 +2998,31 @@ var DefaultStreamObjectResult = class {
providerMetadata: inputProviderMetadata,
abortSignal,
headers
- };
+ }
transformer = {
transform(chunk, controller) {
switch (chunk.type) {
case "tool-call-delta":
- controller.enqueue(chunk.argsTextDelta);
- break;
+ controller.enqueue(chunk.argsTextDelta)
+ break
case "response-metadata":
case "finish":
case "error":
- controller.enqueue(chunk);
- break;
+ controller.enqueue(chunk)
+ break
}
}
- };
- break;
+ }
+ break
}
case void 0: {
throw new Error(
"Model does not have a default object generation mode."
- );
+ )
}
default: {
- const _exhaustiveCheck = mode;
- throw new Error(`Unsupported mode: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = mode
+ throw new Error(`Unsupported mode: ${_exhaustiveCheck}`)
}
}
const {
@@ -3062,42 +3066,42 @@ var DefaultStreamObjectResult = class {
result: await model.doStream(callOptions)
})
})
- );
- self.requestPromise.resolve(request != null ? request : {});
- let usage;
- let finishReason;
- let providerMetadata;
- let object2;
- let error;
- let accumulatedText = "";
- let textDelta = "";
+ )
+ self.requestPromise.resolve(request != null ? request : {})
+ let usage
+ let finishReason
+ let providerMetadata
+ let object2
+ let error
+ let accumulatedText = ""
+ let textDelta = ""
let response = {
id: generateId3(),
timestamp: currentDate(),
modelId: model.modelId
- };
- let latestObjectJson = void 0;
- let latestObject = void 0;
- let isFirstChunk = true;
- let isFirstDelta = true;
+ }
+ let latestObjectJson = void 0
+ let latestObject = void 0
+ let isFirstChunk = true
+ let isFirstDelta = true
const transformedStream = stream.pipeThrough(new TransformStream(transformer)).pipeThrough(
new TransformStream({
async transform(chunk, controller) {
- var _a14, _b, _c;
+ var _a14, _b, _c
if (isFirstChunk) {
- const msToFirstChunk = now2() - startTimestampMs;
- isFirstChunk = false;
+ const msToFirstChunk = now2() - startTimestampMs
+ isFirstChunk = false
doStreamSpan.addEvent("ai.stream.firstChunk", {
"ai.stream.msToFirstChunk": msToFirstChunk
- });
+ })
doStreamSpan.setAttributes({
"ai.stream.msToFirstChunk": msToFirstChunk
- });
+ })
}
if (typeof chunk === "string") {
- accumulatedText += chunk;
- textDelta += chunk;
- const { value: currentObjectJson, state: parseState } = (0, import_ui_utils3.parsePartialJson)(accumulatedText);
+ accumulatedText += chunk
+ textDelta += chunk
+ const { value: currentObjectJson, state: parseState } = (0, import_ui_utils3.parsePartialJson)(accumulatedText)
if (currentObjectJson !== void 0 && !(0, import_ui_utils3.isDeepEqualData)(latestObjectJson, currentObjectJson)) {
const validationResult = outputStrategy.validatePartialResult({
value: currentObjectJson,
@@ -3105,26 +3109,26 @@ var DefaultStreamObjectResult = class {
latestObject,
isFirstDelta,
isFinalDelta: parseState === "successful-parse"
- });
+ })
if (validationResult.success && !(0, import_ui_utils3.isDeepEqualData)(
latestObject,
validationResult.value.partial
)) {
- latestObjectJson = currentObjectJson;
- latestObject = validationResult.value.partial;
+ latestObjectJson = currentObjectJson
+ latestObject = validationResult.value.partial
controller.enqueue({
type: "object",
object: latestObject
- });
+ })
controller.enqueue({
type: "text-delta",
textDelta: validationResult.value.textDelta
- });
- textDelta = "";
- isFirstDelta = false;
+ })
+ textDelta = ""
+ isFirstDelta = false
}
}
- return;
+ return
}
switch (chunk.type) {
case "response-metadata": {
@@ -3132,23 +3136,23 @@ var DefaultStreamObjectResult = class {
id: (_a14 = chunk.id) != null ? _a14 : response.id,
timestamp: (_b = chunk.timestamp) != null ? _b : response.timestamp,
modelId: (_c = chunk.modelId) != null ? _c : response.modelId
- };
- break;
+ }
+ break
}
case "finish": {
if (textDelta !== "") {
- controller.enqueue({ type: "text-delta", textDelta });
+ controller.enqueue({ type: "text-delta", textDelta })
}
- finishReason = chunk.finishReason;
- usage = calculateLanguageModelUsage(chunk.usage);
- providerMetadata = chunk.providerMetadata;
- controller.enqueue({ ...chunk, usage, response });
- self.usagePromise.resolve(usage);
- self.providerMetadataPromise.resolve(providerMetadata);
+ finishReason = chunk.finishReason
+ usage = calculateLanguageModelUsage(chunk.usage)
+ providerMetadata = chunk.providerMetadata
+ controller.enqueue({ ...chunk, usage, response })
+ self.usagePromise.resolve(usage)
+ self.providerMetadataPromise.resolve(providerMetadata)
self.responsePromise.resolve({
...response,
headers: rawResponse == null ? void 0 : rawResponse.headers
- });
+ })
const validationResult = outputStrategy.validateFinalResult(
latestObjectJson,
{
@@ -3156,10 +3160,10 @@ var DefaultStreamObjectResult = class {
response,
usage
}
- );
+ )
if (validationResult.success) {
- object2 = validationResult.value;
- self.objectPromise.resolve(object2);
+ object2 = validationResult.value
+ self.objectPromise.resolve(object2)
} else {
error = new NoObjectGeneratedError({
message: "No object generated: response did not match schema.",
@@ -3167,14 +3171,14 @@ var DefaultStreamObjectResult = class {
text: accumulatedText,
response,
usage
- });
- self.objectPromise.reject(error);
+ })
+ self.objectPromise.reject(error)
}
- break;
+ break
}
default: {
- controller.enqueue(chunk);
- break;
+ controller.enqueue(chunk)
+ break
}
}
},
@@ -3185,7 +3189,7 @@ var DefaultStreamObjectResult = class {
promptTokens: NaN,
completionTokens: NaN,
totalTokens: NaN
- };
+ }
doStreamSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -3207,8 +3211,8 @@ var DefaultStreamObjectResult = class {
"gen_ai.usage.output_tokens": finalUsage.completionTokens
}
})
- );
- doStreamSpan.end();
+ )
+ doStreamSpan.end()
rootSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -3220,7 +3224,7 @@ var DefaultStreamObjectResult = class {
}
}
})
- );
+ )
await (onFinish == null ? void 0 : onFinish({
usage: finalUsage,
object: object2,
@@ -3231,47 +3235,47 @@ var DefaultStreamObjectResult = class {
},
warnings,
experimental_providerMetadata: providerMetadata
- }));
+ }))
} catch (error2) {
- controller.error(error2);
+ controller.error(error2)
} finally {
- rootSpan.end();
+ rootSpan.end()
}
}
})
- );
- self.stitchableStream.addStream(transformedStream);
+ )
+ self.stitchableStream.addStream(transformedStream)
}
}).catch((error) => {
self.stitchableStream.addStream(
new ReadableStream({
start(controller) {
- controller.error(error);
+ controller.error(error)
}
})
- );
+ )
}).finally(() => {
- self.stitchableStream.close();
- });
- this.outputStrategy = outputStrategy;
+ self.stitchableStream.close()
+ })
+ this.outputStrategy = outputStrategy
}
get object() {
- return this.objectPromise.value;
+ return this.objectPromise.value
}
get usage() {
- return this.usagePromise.value;
+ return this.usagePromise.value
}
get experimental_providerMetadata() {
- return this.providerMetadataPromise.value;
+ return this.providerMetadataPromise.value
}
get warnings() {
- return this.warningsPromise.value;
+ return this.warningsPromise.value
}
get request() {
- return this.requestPromise.value;
+ return this.requestPromise.value
}
get response() {
- return this.responsePromise.value;
+ return this.responsePromise.value
}
get partialObjectStream() {
return createAsyncIterableStream(
@@ -3280,28 +3284,28 @@ var DefaultStreamObjectResult = class {
transform(chunk, controller) {
switch (chunk.type) {
case "object":
- controller.enqueue(chunk.object);
- break;
+ controller.enqueue(chunk.object)
+ break
case "text-delta":
case "finish":
- break;
+ break
case "error":
- controller.error(chunk.error);
- break;
+ controller.error(chunk.error)
+ break
default: {
- const _exhaustiveCheck = chunk;
- throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = chunk
+ throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`)
}
}
}
})
)
- );
+ )
}
get elementStream() {
return this.outputStrategy.createElementStream(
this.stitchableStream.stream
- );
+ )
}
get textStream() {
return createAsyncIterableStream(
@@ -3310,26 +3314,26 @@ var DefaultStreamObjectResult = class {
transform(chunk, controller) {
switch (chunk.type) {
case "text-delta":
- controller.enqueue(chunk.textDelta);
- break;
+ controller.enqueue(chunk.textDelta)
+ break
case "object":
case "finish":
- break;
+ break
case "error":
- controller.error(chunk.error);
- break;
+ controller.error(chunk.error)
+ break
default: {
- const _exhaustiveCheck = chunk;
- throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = chunk
+ throw new Error(`Unsupported chunk type: ${_exhaustiveCheck}`)
}
}
}
})
)
- );
+ )
}
get fullStream() {
- return createAsyncIterableStream(this.stitchableStream.stream);
+ return createAsyncIterableStream(this.stitchableStream.stream)
}
pipeTextStreamToResponse(response, init) {
writeToServerResponse({
@@ -3340,46 +3344,46 @@ var DefaultStreamObjectResult = class {
contentType: "text/plain; charset=utf-8"
}),
stream: this.textStream.pipeThrough(new TextEncoderStream())
- });
+ })
}
toTextStreamResponse(init) {
- var _a14;
+ var _a14
return new Response(this.textStream.pipeThrough(new TextEncoderStream()), {
status: (_a14 = init == null ? void 0 : init.status) != null ? _a14 : 200,
headers: prepareResponseHeaders(init == null ? void 0 : init.headers, {
contentType: "text/plain; charset=utf-8"
})
- });
+ })
}
-};
+}
// core/generate-text/generate-text.ts
-var import_provider_utils9 = require("@ai-sdk/provider-utils");
+var import_provider_utils9 = require("@ai-sdk/provider-utils")
// errors/no-output-specified-error.ts
-var import_provider11 = require("@ai-sdk/provider");
-var name8 = "AI_NoOutputSpecifiedError";
-var marker8 = `vercel.ai.error.${name8}`;
-var symbol8 = Symbol.for(marker8);
-var _a8;
+var import_provider11 = require("@ai-sdk/provider")
+var name8 = "AI_NoOutputSpecifiedError"
+var marker8 = `vercel.ai.error.${name8}`
+var symbol8 = Symbol.for(marker8)
+var _a8
var NoOutputSpecifiedError = class extends import_provider11.AISDKError {
// used in isInstance
constructor({ message = "No output specified." } = {}) {
- super({ name: name8, message });
- this[_a8] = true;
+ super({ name: name8, message })
+ this[_a8] = true
}
static isInstance(error) {
- return import_provider11.AISDKError.hasMarker(error, marker8);
+ return import_provider11.AISDKError.hasMarker(error, marker8)
}
-};
-_a8 = symbol8;
+}
+_a8 = symbol8
// errors/tool-execution-error.ts
-var import_provider12 = require("@ai-sdk/provider");
-var name9 = "AI_ToolExecutionError";
-var marker9 = `vercel.ai.error.${name9}`;
-var symbol9 = Symbol.for(marker9);
-var _a9;
+var import_provider12 = require("@ai-sdk/provider")
+var name9 = "AI_ToolExecutionError"
+var marker9 = `vercel.ai.error.${name9}`
+var symbol9 = Symbol.for(marker9)
+var _a9
var ToolExecutionError = class extends import_provider12.AISDKError {
constructor({
toolArgs,
@@ -3388,24 +3392,24 @@ var ToolExecutionError = class extends import_provider12.AISDKError {
cause,
message = `Error executing tool ${toolName}: ${(0, import_provider12.getErrorMessage)(cause)}`
}) {
- super({ name: name9, message, cause });
- this[_a9] = true;
- this.toolArgs = toolArgs;
- this.toolName = toolName;
- this.toolCallId = toolCallId;
+ super({ name: name9, message, cause })
+ this[_a9] = true
+ this.toolArgs = toolArgs
+ this.toolName = toolName
+ this.toolCallId = toolCallId
}
static isInstance(error) {
- return import_provider12.AISDKError.hasMarker(error, marker9);
+ return import_provider12.AISDKError.hasMarker(error, marker9)
}
-};
-_a9 = symbol9;
+}
+_a9 = symbol9
// core/prompt/prepare-tools-and-tool-choice.ts
-var import_ui_utils4 = require("@ai-sdk/ui-utils");
+var import_ui_utils4 = require("@ai-sdk/ui-utils")
// core/util/is-non-empty-object.ts
function isNonEmptyObject(object2) {
- return object2 != null && Object.keys(object2).length > 0;
+ return object2 != null && Object.keys(object2).length > 0
}
// core/prompt/prepare-tools-and-tool-choice.ts
@@ -3418,14 +3422,14 @@ function prepareToolsAndToolChoice({
return {
tools: void 0,
toolChoice: void 0
- };
+ }
}
const filteredTools = activeTools != null ? Object.entries(tools).filter(
([name14]) => activeTools.includes(name14)
- ) : Object.entries(tools);
+ ) : Object.entries(tools)
return {
tools: filteredTools.map(([name14, tool2]) => {
- const toolType = tool2.type;
+ const toolType = tool2.type
switch (toolType) {
case void 0:
case "function":
@@ -3434,47 +3438,47 @@ function prepareToolsAndToolChoice({
name: name14,
description: tool2.description,
parameters: (0, import_ui_utils4.asSchema)(tool2.parameters).jsonSchema
- };
+ }
case "provider-defined":
return {
type: "provider-defined",
name: name14,
id: tool2.id,
args: tool2.args
- };
+ }
default: {
- const exhaustiveCheck = toolType;
- throw new Error(`Unsupported tool type: ${exhaustiveCheck}`);
+ const exhaustiveCheck = toolType
+ throw new Error(`Unsupported tool type: ${exhaustiveCheck}`)
}
}
}),
toolChoice: toolChoice == null ? { type: "auto" } : typeof toolChoice === "string" ? { type: toolChoice } : { type: "tool", toolName: toolChoice.toolName }
- };
+ }
}
// core/util/split-on-last-whitespace.ts
-var lastWhitespaceRegexp = /^([\s\S]*?)(\s+)(\S*)$/;
+var lastWhitespaceRegexp = /^([\s\S]*?)(\s+)(\S*)$/
function splitOnLastWhitespace(text2) {
- const match = text2.match(lastWhitespaceRegexp);
- return match ? { prefix: match[1], whitespace: match[2], suffix: match[3] } : void 0;
+ const match = text2.match(lastWhitespaceRegexp)
+ return match ? { prefix: match[1], whitespace: match[2], suffix: match[3] } : void 0
}
// core/util/remove-text-after-last-whitespace.ts
function removeTextAfterLastWhitespace(text2) {
- const match = splitOnLastWhitespace(text2);
- return match ? match.prefix + match.whitespace : text2;
+ const match = splitOnLastWhitespace(text2)
+ return match ? match.prefix + match.whitespace : text2
}
// core/generate-text/parse-tool-call.ts
-var import_provider_utils8 = require("@ai-sdk/provider-utils");
-var import_ui_utils5 = require("@ai-sdk/ui-utils");
+var import_provider_utils8 = require("@ai-sdk/provider-utils")
+var import_ui_utils5 = require("@ai-sdk/ui-utils")
// errors/invalid-tool-arguments-error.ts
-var import_provider13 = require("@ai-sdk/provider");
-var name10 = "AI_InvalidToolArgumentsError";
-var marker10 = `vercel.ai.error.${name10}`;
-var symbol10 = Symbol.for(marker10);
-var _a10;
+var import_provider13 = require("@ai-sdk/provider")
+var name10 = "AI_InvalidToolArgumentsError"
+var marker10 = `vercel.ai.error.${name10}`
+var symbol10 = Symbol.for(marker10)
+var _a10
var InvalidToolArgumentsError = class extends import_provider13.AISDKError {
constructor({
toolArgs,
@@ -3484,61 +3488,61 @@ var InvalidToolArgumentsError = class extends import_provider13.AISDKError {
cause
)}`
}) {
- super({ name: name10, message, cause });
- this[_a10] = true;
- this.toolArgs = toolArgs;
- this.toolName = toolName;
+ super({ name: name10, message, cause })
+ this[_a10] = true
+ this.toolArgs = toolArgs
+ this.toolName = toolName
}
static isInstance(error) {
- return import_provider13.AISDKError.hasMarker(error, marker10);
+ return import_provider13.AISDKError.hasMarker(error, marker10)
}
-};
-_a10 = symbol10;
+}
+_a10 = symbol10
// errors/no-such-tool-error.ts
-var import_provider14 = require("@ai-sdk/provider");
-var name11 = "AI_NoSuchToolError";
-var marker11 = `vercel.ai.error.${name11}`;
-var symbol11 = Symbol.for(marker11);
-var _a11;
+var import_provider14 = require("@ai-sdk/provider")
+var name11 = "AI_NoSuchToolError"
+var marker11 = `vercel.ai.error.${name11}`
+var symbol11 = Symbol.for(marker11)
+var _a11
var NoSuchToolError = class extends import_provider14.AISDKError {
constructor({
toolName,
availableTools = void 0,
message = `Model tried to call unavailable tool '${toolName}'. ${availableTools === void 0 ? "No tools are available." : `Available tools: ${availableTools.join(", ")}.`}`
}) {
- super({ name: name11, message });
- this[_a11] = true;
- this.toolName = toolName;
- this.availableTools = availableTools;
+ super({ name: name11, message })
+ this[_a11] = true
+ this.toolName = toolName
+ this.availableTools = availableTools
}
static isInstance(error) {
- return import_provider14.AISDKError.hasMarker(error, marker11);
+ return import_provider14.AISDKError.hasMarker(error, marker11)
}
-};
-_a11 = symbol11;
+}
+_a11 = symbol11
// errors/tool-call-repair-error.ts
-var import_provider15 = require("@ai-sdk/provider");
-var name12 = "AI_ToolCallRepairError";
-var marker12 = `vercel.ai.error.${name12}`;
-var symbol12 = Symbol.for(marker12);
-var _a12;
+var import_provider15 = require("@ai-sdk/provider")
+var name12 = "AI_ToolCallRepairError"
+var marker12 = `vercel.ai.error.${name12}`
+var symbol12 = Symbol.for(marker12)
+var _a12
var ToolCallRepairError = class extends import_provider15.AISDKError {
constructor({
cause,
originalError,
message = `Error repairing tool call: ${(0, import_provider15.getErrorMessage)(cause)}`
}) {
- super({ name: name12, message, cause });
- this[_a12] = true;
- this.originalError = originalError;
+ super({ name: name12, message, cause })
+ this[_a12] = true
+ this.originalError = originalError
}
static isInstance(error) {
- return import_provider15.AISDKError.hasMarker(error, marker12);
+ return import_provider15.AISDKError.hasMarker(error, marker12)
}
-};
-_a12 = symbol12;
+}
+_a12 = symbol12
// core/generate-text/parse-tool-call.ts
async function parseToolCall({
@@ -3549,15 +3553,15 @@ async function parseToolCall({
messages
}) {
if (tools == null) {
- throw new NoSuchToolError({ toolName: toolCall.toolName });
+ throw new NoSuchToolError({ toolName: toolCall.toolName })
}
try {
- return await doParseToolCall({ toolCall, tools });
+ return await doParseToolCall({ toolCall, tools })
} catch (error) {
if (repairToolCall == null || !(NoSuchToolError.isInstance(error) || InvalidToolArgumentsError.isInstance(error))) {
- throw error;
+ throw error
}
- let repairedToolCall = null;
+ let repairedToolCall = null
try {
repairedToolCall = await repairToolCall({
toolCall,
@@ -3566,46 +3570,46 @@ async function parseToolCall({
system,
messages,
error
- });
+ })
} catch (repairError) {
throw new ToolCallRepairError({
cause: repairError,
originalError: error
- });
+ })
}
if (repairedToolCall == null) {
- throw error;
+ throw error
}
- return await doParseToolCall({ toolCall: repairedToolCall, tools });
+ return await doParseToolCall({ toolCall: repairedToolCall, tools })
}
}
async function doParseToolCall({
toolCall,
tools
}) {
- const toolName = toolCall.toolName;
- const tool2 = tools[toolName];
+ const toolName = toolCall.toolName
+ const tool2 = tools[toolName]
if (tool2 == null) {
throw new NoSuchToolError({
toolName: toolCall.toolName,
availableTools: Object.keys(tools)
- });
+ })
}
- const schema = (0, import_ui_utils5.asSchema)(tool2.parameters);
- const parseResult = toolCall.args.trim() === "" ? (0, import_provider_utils8.safeValidateTypes)({ value: {}, schema }) : (0, import_provider_utils8.safeParseJSON)({ text: toolCall.args, schema });
+ const schema = (0, import_ui_utils5.asSchema)(tool2.parameters)
+ const parseResult = toolCall.args.trim() === "" ? (0, import_provider_utils8.safeValidateTypes)({ value: {}, schema }) : (0, import_provider_utils8.safeParseJSON)({ text: toolCall.args, schema })
if (parseResult.success === false) {
throw new InvalidToolArgumentsError({
toolName,
toolArgs: toolCall.args,
cause: parseResult.error
- });
+ })
}
return {
type: "tool-call",
toolCallId: toolCall.toolCallId,
toolName,
args: parseResult.value
- };
+ }
}
// core/generate-text/to-response-messages.ts
@@ -3617,18 +3621,18 @@ function toResponseMessages({
messageId,
generateMessageId
}) {
- const responseMessages = [];
+ const responseMessages = []
responseMessages.push({
role: "assistant",
content: [{ type: "text", text: text2 }, ...toolCalls],
id: messageId
- });
+ })
if (toolResults.length > 0) {
responseMessages.push({
role: "tool",
id: generateMessageId(),
content: toolResults.map((toolResult) => {
- const tool2 = tools[toolResult.toolName];
+ const tool2 = tools[toolResult.toolName]
return (tool2 == null ? void 0 : tool2.experimental_toToolResultContent) != null ? {
type: "tool-result",
toolCallId: toolResult.toolCallId,
@@ -3642,22 +3646,22 @@ function toResponseMessages({
toolCallId: toolResult.toolCallId,
toolName: toolResult.toolName,
result: toolResult.result
- };
+ }
})
- });
+ })
}
- return responseMessages;
+ return responseMessages
}
// core/generate-text/generate-text.ts
var originalGenerateId3 = (0, import_provider_utils9.createIdGenerator)({
prefix: "aitxt",
size: 24
-});
+})
var originalGenerateMessageId = (0, import_provider_utils9.createIdGenerator)({
prefix: "msg",
size: 24
-});
+})
async function generateText({
model,
tools,
@@ -3683,21 +3687,21 @@ async function generateText({
onStepFinish,
...settings
}) {
- var _a14;
+ var _a14
if (maxSteps < 1) {
throw new InvalidArgumentError({
parameter: "maxSteps",
value: maxSteps,
message: "maxSteps must be at least 1"
- });
+ })
}
- const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg });
+ const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg })
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { ...settings, maxRetries }
- });
+ })
const initialPrompt = standardizePrompt({
prompt: {
system: (_a14 = output == null ? void 0 : output.injectIntoSystemPrompt({ system, model })) != null ? _a14 : system,
@@ -3705,8 +3709,8 @@ async function generateText({
messages
},
tools
- });
- const tracer = getTracer(telemetry);
+ })
+ const tracer = getTracer(telemetry)
return recordSpan({
name: "ai.generateText",
attributes: selectTelemetryAttributes({
@@ -3726,31 +3730,31 @@ async function generateText({
}),
tracer,
fn: async (span) => {
- var _a15, _b, _c, _d, _e, _f;
+ var _a15, _b, _c, _d, _e, _f
const mode = {
type: "regular",
...prepareToolsAndToolChoice({ tools, toolChoice, activeTools })
- };
- const callSettings = prepareCallSettings(settings);
- let currentModelResponse;
- let currentToolCalls = [];
- let currentToolResults = [];
- let stepCount = 0;
- const responseMessages = [];
- let text2 = "";
- const steps = [];
+ }
+ const callSettings = prepareCallSettings(settings)
+ let currentModelResponse
+ let currentToolCalls = []
+ let currentToolResults = []
+ let stepCount = 0
+ const responseMessages = []
+ let text2 = ""
+ const steps = []
let usage = {
completionTokens: 0,
promptTokens: 0,
totalTokens: 0
- };
- let stepType = "initial";
+ }
+ let stepType = "initial"
do {
- const promptFormat = stepCount === 0 ? initialPrompt.type : "messages";
+ const promptFormat = stepCount === 0 ? initialPrompt.type : "messages"
const stepInputMessages = [
...initialPrompt.messages,
...responseMessages
- ];
+ ]
const promptMessages = await convertToLanguageModelPrompt({
prompt: {
type: promptFormat,
@@ -3759,7 +3763,7 @@ async function generateText({
},
modelSupportsImageUrls: model.supportsImageUrls,
modelSupportsUrl: model.supportsUrl
- });
+ })
currentModelResponse = await retry(
() => recordSpan({
name: "ai.generateText.doGenerate",
@@ -3778,8 +3782,8 @@ async function generateText({
"ai.prompt.tools": {
// convert the language model level tools:
input: () => {
- var _a16;
- return (_a16 = mode.tools) == null ? void 0 : _a16.map((tool2) => JSON.stringify(tool2));
+ var _a16
+ return (_a16 = mode.tools) == null ? void 0 : _a16.map((tool2) => JSON.stringify(tool2))
}
},
"ai.prompt.toolChoice": {
@@ -3799,7 +3803,7 @@ async function generateText({
}),
tracer,
fn: async (span2) => {
- var _a16, _b2, _c2, _d2, _e2, _f2;
+ var _a16, _b2, _c2, _d2, _e2, _f2
const result = await model.doGenerate({
mode,
...callSettings,
@@ -3809,12 +3813,12 @@ async function generateText({
providerMetadata,
abortSignal,
headers
- });
+ })
const responseData = {
id: (_b2 = (_a16 = result.response) == null ? void 0 : _a16.id) != null ? _b2 : generateId3(),
timestamp: (_d2 = (_c2 = result.response) == null ? void 0 : _c2.timestamp) != null ? _d2 : currentDate(),
modelId: (_f2 = (_e2 = result.response) == null ? void 0 : _e2.modelId) != null ? _f2 : model.modelId
- };
+ }
span2.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -3839,11 +3843,11 @@ async function generateText({
"gen_ai.usage.output_tokens": result.usage.completionTokens
}
})
- );
- return { ...result, response: responseData };
+ )
+ return { ...result, response: responseData }
}
})
- );
+ )
currentToolCalls = await Promise.all(
((_a15 = currentModelResponse.toolCalls) != null ? _a15 : []).map(
(toolCall) => parseToolCall({
@@ -3854,7 +3858,7 @@ async function generateText({
messages: stepInputMessages
})
)
- );
+ )
currentToolResults = tools == null ? [] : await executeTools({
toolCalls: currentToolCalls,
tools,
@@ -3862,38 +3866,38 @@ async function generateText({
telemetry,
messages: stepInputMessages,
abortSignal
- });
+ })
const currentUsage = calculateLanguageModelUsage(
currentModelResponse.usage
- );
- usage = addLanguageModelUsage(usage, currentUsage);
- let nextStepType = "done";
+ )
+ usage = addLanguageModelUsage(usage, currentUsage)
+ let nextStepType = "done"
if (++stepCount < maxSteps) {
if (continueSteps && currentModelResponse.finishReason === "length" && // only use continue when there are no tool calls:
currentToolCalls.length === 0) {
- nextStepType = "continue";
+ nextStepType = "continue"
} else if (
// there are tool calls:
currentToolCalls.length > 0 && // all current tool calls have results:
currentToolResults.length === currentToolCalls.length
) {
- nextStepType = "tool-result";
+ nextStepType = "tool-result"
}
}
- const originalText = (_b = currentModelResponse.text) != null ? _b : "";
+ const originalText = (_b = currentModelResponse.text) != null ? _b : ""
const stepTextLeadingWhitespaceTrimmed = stepType === "continue" && // only for continue steps
- text2.trimEnd() !== text2 ? originalText.trimStart() : originalText;
- const stepText = nextStepType === "continue" ? removeTextAfterLastWhitespace(stepTextLeadingWhitespaceTrimmed) : stepTextLeadingWhitespaceTrimmed;
- text2 = nextStepType === "continue" || stepType === "continue" ? text2 + stepText : stepText;
+ text2.trimEnd() !== text2 ? originalText.trimStart() : originalText
+ const stepText = nextStepType === "continue" ? removeTextAfterLastWhitespace(stepTextLeadingWhitespaceTrimmed) : stepTextLeadingWhitespaceTrimmed
+ text2 = nextStepType === "continue" || stepType === "continue" ? text2 + stepText : stepText
if (stepType === "continue") {
- const lastMessage = responseMessages[responseMessages.length - 1];
+ const lastMessage = responseMessages[responseMessages.length - 1]
if (typeof lastMessage.content === "string") {
- lastMessage.content += stepText;
+ lastMessage.content += stepText
} else {
lastMessage.content.push({
text: stepText,
type: "text"
- });
+ })
}
} else {
responseMessages.push(
@@ -3905,7 +3909,7 @@ async function generateText({
messageId: generateMessageId(),
generateMessageId
})
- );
+ )
}
const currentStepResult = {
stepType,
@@ -3925,11 +3929,11 @@ async function generateText({
},
experimental_providerMetadata: currentModelResponse.providerMetadata,
isContinued: nextStepType === "continue"
- };
- steps.push(currentStepResult);
- await (onStepFinish == null ? void 0 : onStepFinish(currentStepResult));
- stepType = nextStepType;
- } while (stepType !== "done");
+ }
+ steps.push(currentStepResult)
+ await (onStepFinish == null ? void 0 : onStepFinish(currentStepResult))
+ stepType = nextStepType
+ } while (stepType !== "done")
span.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -3945,17 +3949,17 @@ async function generateText({
"ai.usage.completionTokens": currentModelResponse.usage.completionTokens
}
})
- );
+ )
return new DefaultGenerateTextResult({
text: text2,
outputResolver: () => {
if (output == null) {
- throw new NoOutputSpecifiedError();
+ throw new NoOutputSpecifiedError()
}
return output.parseOutput(
{ text: text2 },
{ response: currentModelResponse.response, usage }
- );
+ )
},
toolCalls: currentToolCalls,
toolResults: currentToolResults,
@@ -3971,9 +3975,9 @@ async function generateText({
logprobs: currentModelResponse.logprobs,
steps,
providerMetadata: currentModelResponse.providerMetadata
- });
+ })
}
- });
+ })
}
async function executeTools({
toolCalls,
@@ -3985,9 +3989,9 @@ async function executeTools({
}) {
const toolResults = await Promise.all(
toolCalls.map(async ({ toolCallId, toolName, args }) => {
- const tool2 = tools[toolName];
+ const tool2 = tools[toolName]
if ((tool2 == null ? void 0 : tool2.execute) == null) {
- return void 0;
+ return void 0
}
const result = await recordSpan({
name: "ai.toolCall",
@@ -4012,7 +4016,7 @@ async function executeTools({
toolCallId,
messages,
abortSignal
- });
+ })
try {
span.setAttributes(
selectTelemetryAttributes({
@@ -4023,83 +4027,84 @@ async function executeTools({
}
}
})
- );
+ )
} catch (ignored) {
+ console.error(ignored)
}
- return result2;
+ return result2
} catch (error) {
throw new ToolExecutionError({
toolCallId,
toolName,
toolArgs: args,
cause: error
- });
+ })
}
}
- });
+ })
return {
type: "tool-result",
toolCallId,
toolName,
args,
result
- };
+ }
})
- );
+ )
return toolResults.filter(
(result) => result != null
- );
+ )
}
var DefaultGenerateTextResult = class {
constructor(options) {
- this.text = options.text;
- this.toolCalls = options.toolCalls;
- this.toolResults = options.toolResults;
- this.finishReason = options.finishReason;
- this.usage = options.usage;
- this.warnings = options.warnings;
- this.request = options.request;
- this.response = options.response;
- this.steps = options.steps;
- this.experimental_providerMetadata = options.providerMetadata;
- this.logprobs = options.logprobs;
- this.outputResolver = options.outputResolver;
+ this.text = options.text
+ this.toolCalls = options.toolCalls
+ this.toolResults = options.toolResults
+ this.finishReason = options.finishReason
+ this.usage = options.usage
+ this.warnings = options.warnings
+ this.request = options.request
+ this.response = options.response
+ this.steps = options.steps
+ this.experimental_providerMetadata = options.providerMetadata
+ this.logprobs = options.logprobs
+ this.outputResolver = options.outputResolver
}
get experimental_output() {
- return this.outputResolver();
+ return this.outputResolver()
}
-};
+}
// core/generate-text/output.ts
-var output_exports = {};
+var output_exports = {}
__export(output_exports, {
object: () => object,
text: () => text
-});
-var import_provider_utils10 = require("@ai-sdk/provider-utils");
-var import_ui_utils6 = require("@ai-sdk/ui-utils");
+})
+var import_provider_utils10 = require("@ai-sdk/provider-utils")
+var import_ui_utils6 = require("@ai-sdk/ui-utils")
// errors/index.ts
-var import_provider16 = require("@ai-sdk/provider");
+var import_provider16 = require("@ai-sdk/provider")
// core/generate-text/output.ts
var text = () => ({
type: "text",
responseFormat: () => ({ type: "text" }),
injectIntoSystemPrompt({ system }) {
- return system;
+ return system
},
parsePartial({ text: text2 }) {
- return { partial: text2 };
+ return { partial: text2 }
},
parseOutput({ text: text2 }) {
- return text2;
+ return text2
}
-});
+})
var object = ({
schema: inputSchema
}) => {
- const schema = (0, import_ui_utils6.asSchema)(inputSchema);
+ const schema = (0, import_ui_utils6.asSchema)(inputSchema)
return {
type: "object",
responseFormat: ({ model }) => ({
@@ -4110,28 +4115,28 @@ var object = ({
return model.supportsStructuredOutputs ? system : injectJsonInstruction({
prompt: system,
schema: schema.jsonSchema
- });
+ })
},
parsePartial({ text: text2 }) {
- const result = (0, import_ui_utils6.parsePartialJson)(text2);
+ const result = (0, import_ui_utils6.parsePartialJson)(text2)
switch (result.state) {
case "failed-parse":
case "undefined-input":
- return void 0;
+ return void 0
case "repaired-parse":
case "successful-parse":
return {
// Note: currently no validation of partial results:
partial: result.value
- };
+ }
default: {
- const _exhaustiveCheck = result.state;
- throw new Error(`Unsupported parse state: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = result.state
+ throw new Error(`Unsupported parse state: ${_exhaustiveCheck}`)
}
}
},
parseOutput({ text: text2 }, context) {
- const parseResult = (0, import_provider_utils10.safeParseJSON)({ text: text2 });
+ const parseResult = (0, import_provider_utils10.safeParseJSON)({ text: text2 })
if (!parseResult.success) {
throw new NoObjectGeneratedError({
message: "No object generated: could not parse the response.",
@@ -4139,12 +4144,12 @@ var object = ({
text: text2,
response: context.response,
usage: context.usage
- });
+ })
}
const validationResult = (0, import_provider_utils10.safeValidateTypes)({
value: parseResult.value,
schema
- });
+ })
if (!validationResult.success) {
throw new NoObjectGeneratedError({
message: "No object generated: response did not match schema.",
@@ -4152,159 +4157,159 @@ var object = ({
text: text2,
response: context.response,
usage: context.usage
- });
+ })
}
- return validationResult.value;
+ return validationResult.value
}
- };
-};
+ }
+}
// core/generate-text/smooth-stream.ts
-var import_provider17 = require("@ai-sdk/provider");
+var import_provider17 = require("@ai-sdk/provider")
var CHUNKING_REGEXPS = {
word: /\s*\S+\s+/m,
line: /[^\n]*\n/m
-};
+}
function smoothStream({
delayInMs = 10,
chunking = "word",
_internal: { delay: delay2 = delay } = {}
} = {}) {
- const chunkingRegexp = typeof chunking === "string" ? CHUNKING_REGEXPS[chunking] : chunking;
+ const chunkingRegexp = typeof chunking === "string" ? CHUNKING_REGEXPS[chunking] : chunking
if (chunkingRegexp == null) {
throw new import_provider17.InvalidArgumentError({
argument: "chunking",
message: `Chunking must be "word" or "line" or a RegExp. Received: ${chunking}`
- });
+ })
}
return () => {
- let buffer = "";
+ let buffer = ""
return new TransformStream({
async transform(chunk, controller) {
if (chunk.type === "step-finish") {
if (buffer.length > 0) {
- controller.enqueue({ type: "text-delta", textDelta: buffer });
- buffer = "";
+ controller.enqueue({ type: "text-delta", textDelta: buffer })
+ buffer = ""
}
- controller.enqueue(chunk);
- return;
+ controller.enqueue(chunk)
+ return
}
if (chunk.type !== "text-delta") {
- controller.enqueue(chunk);
- return;
+ controller.enqueue(chunk)
+ return
}
- buffer += chunk.textDelta;
- let match;
+ buffer += chunk.textDelta
+ let match
while ((match = chunkingRegexp.exec(buffer)) != null) {
- const chunk2 = match[0];
- controller.enqueue({ type: "text-delta", textDelta: chunk2 });
- buffer = buffer.slice(chunk2.length);
- await delay2(delayInMs);
+ const chunk2 = match[0]
+ controller.enqueue({ type: "text-delta", textDelta: chunk2 })
+ buffer = buffer.slice(chunk2.length)
+ await delay2(delayInMs)
}
}
- });
- };
+ })
+ }
}
// core/generate-text/stream-text.ts
-var import_provider_utils11 = require("@ai-sdk/provider-utils");
-var import_ui_utils8 = require("@ai-sdk/ui-utils");
+var import_provider_utils11 = require("@ai-sdk/provider-utils")
+var import_ui_utils8 = require("@ai-sdk/ui-utils")
// util/as-array.ts
function asArray(value) {
- return value === void 0 ? [] : Array.isArray(value) ? value : [value];
+ return value === void 0 ? [] : Array.isArray(value) ? value : [value]
}
// core/util/merge-streams.ts
function mergeStreams(stream1, stream2) {
- const reader1 = stream1.getReader();
- const reader2 = stream2.getReader();
- let lastRead1 = void 0;
- let lastRead2 = void 0;
- let stream1Done = false;
- let stream2Done = false;
+ const reader1 = stream1.getReader()
+ const reader2 = stream2.getReader()
+ let lastRead1 = void 0
+ let lastRead2 = void 0
+ let stream1Done = false
+ let stream2Done = false
async function readStream1(controller) {
try {
if (lastRead1 == null) {
- lastRead1 = reader1.read();
+ lastRead1 = reader1.read()
}
- const result = await lastRead1;
- lastRead1 = void 0;
+ const result = await lastRead1
+ lastRead1 = void 0
if (!result.done) {
- controller.enqueue(result.value);
+ controller.enqueue(result.value)
} else {
- controller.close();
+ controller.close()
}
} catch (error) {
- controller.error(error);
+ controller.error(error)
}
}
async function readStream2(controller) {
try {
if (lastRead2 == null) {
- lastRead2 = reader2.read();
+ lastRead2 = reader2.read()
}
- const result = await lastRead2;
- lastRead2 = void 0;
+ const result = await lastRead2
+ lastRead2 = void 0
if (!result.done) {
- controller.enqueue(result.value);
+ controller.enqueue(result.value)
} else {
- controller.close();
+ controller.close()
}
} catch (error) {
- controller.error(error);
+ controller.error(error)
}
}
return new ReadableStream({
async pull(controller) {
try {
if (stream1Done) {
- await readStream2(controller);
- return;
+ await readStream2(controller)
+ return
}
if (stream2Done) {
- await readStream1(controller);
- return;
+ await readStream1(controller)
+ return
}
if (lastRead1 == null) {
- lastRead1 = reader1.read();
+ lastRead1 = reader1.read()
}
if (lastRead2 == null) {
- lastRead2 = reader2.read();
+ lastRead2 = reader2.read()
}
const { result, reader } = await Promise.race([
lastRead1.then((result2) => ({ result: result2, reader: reader1 })),
lastRead2.then((result2) => ({ result: result2, reader: reader2 }))
- ]);
+ ])
if (!result.done) {
- controller.enqueue(result.value);
+ controller.enqueue(result.value)
}
if (reader === reader1) {
- lastRead1 = void 0;
+ lastRead1 = void 0
if (result.done) {
- await readStream2(controller);
- stream1Done = true;
+ await readStream2(controller)
+ stream1Done = true
}
} else {
- lastRead2 = void 0;
+ lastRead2 = void 0
if (result.done) {
- stream2Done = true;
- await readStream1(controller);
+ stream2Done = true
+ await readStream1(controller)
}
}
} catch (error) {
- controller.error(error);
+ controller.error(error)
}
},
cancel() {
- reader1.cancel();
- reader2.cancel();
+ reader1.cancel()
+ reader2.cancel()
}
- });
+ })
}
// core/generate-text/run-tools-transformation.ts
-var import_ui_utils7 = require("@ai-sdk/ui-utils");
+var import_ui_utils7 = require("@ai-sdk/ui-utils")
function runToolsTransformation({
tools,
generatorStream,
@@ -4316,33 +4321,33 @@ function runToolsTransformation({
abortSignal,
repairToolCall
}) {
- let toolResultsStreamController = null;
+ let toolResultsStreamController = null
const toolResultsStream = new ReadableStream({
start(controller) {
- toolResultsStreamController = controller;
+ toolResultsStreamController = controller
}
- });
- const activeToolCalls = {};
- const outstandingToolResults = /* @__PURE__ */ new Set();
- let canClose = false;
- let finishChunk = void 0;
+ })
+ const activeToolCalls = {}
+ const outstandingToolResults = /* @__PURE__ */ new Set()
+ let canClose = false
+ let finishChunk = void 0
function attemptClose() {
if (canClose && outstandingToolResults.size === 0) {
if (finishChunk != null) {
- toolResultsStreamController.enqueue(finishChunk);
+ toolResultsStreamController.enqueue(finishChunk)
}
- toolResultsStreamController.close();
+ toolResultsStreamController.close()
}
}
const forwardStream = new TransformStream({
async transform(chunk, controller) {
- const chunkType = chunk.type;
+ const chunkType = chunk.type
switch (chunkType) {
case "text-delta":
case "response-metadata":
case "error": {
- controller.enqueue(chunk);
- break;
+ controller.enqueue(chunk)
+ break
}
case "tool-call-delta": {
if (toolCallStreaming) {
@@ -4351,17 +4356,17 @@ function runToolsTransformation({
type: "tool-call-streaming-start",
toolCallId: chunk.toolCallId,
toolName: chunk.toolName
- });
- activeToolCalls[chunk.toolCallId] = true;
+ })
+ activeToolCalls[chunk.toolCallId] = true
}
controller.enqueue({
type: "tool-call-delta",
toolCallId: chunk.toolCallId,
toolName: chunk.toolName,
argsTextDelta: chunk.argsTextDelta
- });
+ })
}
- break;
+ break
}
case "tool-call": {
try {
@@ -4371,12 +4376,12 @@ function runToolsTransformation({
repairToolCall,
system,
messages
- });
- controller.enqueue(toolCall);
- const tool2 = tools[toolCall.toolName];
+ })
+ controller.enqueue(toolCall)
+ const tool2 = tools[toolCall.toolName]
if (tool2.execute != null) {
- const toolExecutionId = (0, import_ui_utils7.generateId)();
- outstandingToolResults.add(toolExecutionId);
+ const toolExecutionId = (0, import_ui_utils7.generateId)()
+ outstandingToolResults.add(toolExecutionId)
recordSpan({
name: "ai.toolCall",
attributes: selectTelemetryAttributes({
@@ -4404,9 +4409,9 @@ function runToolsTransformation({
...toolCall,
type: "tool-result",
result
- });
- outstandingToolResults.delete(toolExecutionId);
- attemptClose();
+ })
+ outstandingToolResults.delete(toolExecutionId)
+ attemptClose()
try {
span.setAttributes(
selectTelemetryAttributes({
@@ -4417,7 +4422,7 @@ function runToolsTransformation({
}
}
})
- );
+ )
} catch (ignored) {
}
},
@@ -4430,20 +4435,20 @@ function runToolsTransformation({
toolArgs: toolCall.args,
cause: error
})
- });
- outstandingToolResults.delete(toolExecutionId);
- attemptClose();
+ })
+ outstandingToolResults.delete(toolExecutionId)
+ attemptClose()
}
)
- });
+ })
}
} catch (error) {
toolResultsStreamController.enqueue({
type: "error",
error
- });
+ })
}
- break;
+ break
}
case "finish": {
finishChunk = {
@@ -4452,27 +4457,27 @@ function runToolsTransformation({
logprobs: chunk.logprobs,
usage: calculateLanguageModelUsage(chunk.usage),
experimental_providerMetadata: chunk.providerMetadata
- };
- break;
+ }
+ break
}
default: {
- const _exhaustiveCheck = chunkType;
- throw new Error(`Unhandled chunk type: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = chunkType
+ throw new Error(`Unhandled chunk type: ${_exhaustiveCheck}`)
}
}
},
flush() {
- canClose = true;
- attemptClose();
+ canClose = true
+ attemptClose()
}
- });
+ })
return new ReadableStream({
async start(controller) {
return Promise.all([
generatorStream.pipeThrough(forwardStream).pipeTo(
new WritableStream({
write(chunk) {
- controller.enqueue(chunk);
+ controller.enqueue(chunk)
},
close() {
}
@@ -4481,27 +4486,27 @@ function runToolsTransformation({
toolResultsStream.pipeTo(
new WritableStream({
write(chunk) {
- controller.enqueue(chunk);
+ controller.enqueue(chunk)
},
close() {
- controller.close();
+ controller.close()
}
})
)
- ]);
+ ])
}
- });
+ })
}
// core/generate-text/stream-text.ts
var originalGenerateId4 = (0, import_provider_utils11.createIdGenerator)({
prefix: "aitxt",
size: 24
-});
+})
var originalGenerateMessageId2 = (0, import_provider_utils11.createIdGenerator)({
prefix: "msg",
size: 24
-});
+})
function streamText({
model,
tools,
@@ -4559,33 +4564,33 @@ function streamText({
currentDate,
generateId: generateId3,
generateMessageId
- });
+ })
}
function createOutputTransformStream(output) {
if (!output) {
return new TransformStream({
transform(chunk, controller) {
- controller.enqueue({ part: chunk, partialOutput: void 0 });
+ controller.enqueue({ part: chunk, partialOutput: void 0 })
}
- });
+ })
}
- let text2 = "";
- let textChunk = "";
- let lastPublishedJson = "";
+ let text2 = ""
+ let textChunk = ""
+ let lastPublishedJson = ""
return new TransformStream({
transform(chunk, controller) {
if (chunk.type !== "text-delta") {
controller.enqueue({
part: chunk,
partialOutput: void 0
- });
- return;
+ })
+ return
}
- text2 += chunk.textDelta;
- textChunk += chunk.textDelta;
- const result = output.parsePartial({ text: text2 });
+ text2 += chunk.textDelta
+ textChunk += chunk.textDelta
+ const result = output.parsePartial({ text: text2 })
if (result != null) {
- const currentJson = JSON.stringify(result.partial);
+ const currentJson = JSON.stringify(result.partial)
if (currentJson !== lastPublishedJson) {
controller.enqueue({
part: {
@@ -4593,9 +4598,9 @@ function createOutputTransformStream(output) {
textDelta: textChunk
},
partialOutput: result.partial
- });
- lastPublishedJson = currentJson;
- textChunk = "";
+ })
+ lastPublishedJson = currentJson
+ textChunk = ""
}
}
},
@@ -4607,10 +4612,10 @@ function createOutputTransformStream(output) {
textDelta: textChunk
},
partialOutput: void 0
- });
+ })
}
}
- });
+ })
}
var DefaultStreamTextResult = class {
constructor({
@@ -4641,58 +4646,58 @@ var DefaultStreamTextResult = class {
generateId: generateId3,
generateMessageId
}) {
- this.warningsPromise = new DelayedPromise();
- this.usagePromise = new DelayedPromise();
- this.finishReasonPromise = new DelayedPromise();
- this.providerMetadataPromise = new DelayedPromise();
- this.textPromise = new DelayedPromise();
- this.toolCallsPromise = new DelayedPromise();
- this.toolResultsPromise = new DelayedPromise();
- this.requestPromise = new DelayedPromise();
- this.responsePromise = new DelayedPromise();
- this.stepsPromise = new DelayedPromise();
- var _a14;
+ this.warningsPromise = new DelayedPromise()
+ this.usagePromise = new DelayedPromise()
+ this.finishReasonPromise = new DelayedPromise()
+ this.providerMetadataPromise = new DelayedPromise()
+ this.textPromise = new DelayedPromise()
+ this.toolCallsPromise = new DelayedPromise()
+ this.toolResultsPromise = new DelayedPromise()
+ this.requestPromise = new DelayedPromise()
+ this.responsePromise = new DelayedPromise()
+ this.stepsPromise = new DelayedPromise()
+ var _a14
if (maxSteps < 1) {
throw new InvalidArgumentError({
parameter: "maxSteps",
value: maxSteps,
message: "maxSteps must be at least 1"
- });
+ })
}
- this.output = output;
- let recordedStepText = "";
- let recordedContinuationText = "";
- let recordedFullText = "";
+ this.output = output
+ let recordedStepText = ""
+ let recordedContinuationText = ""
+ let recordedFullText = ""
const recordedResponse = {
id: generateId3(),
timestamp: currentDate(),
modelId: model.modelId,
messages: []
- };
- let recordedToolCalls = [];
- let recordedToolResults = [];
- let recordedFinishReason = void 0;
- let recordedUsage = void 0;
- let stepType = "initial";
- const recordedSteps = [];
- let rootSpan;
+ }
+ let recordedToolCalls = []
+ let recordedToolResults = []
+ let recordedFinishReason = void 0
+ let recordedUsage = void 0
+ let stepType = "initial"
+ const recordedSteps = []
+ let rootSpan
const eventProcessor = new TransformStream({
async transform(chunk, controller) {
- controller.enqueue(chunk);
- const { part } = chunk;
+ controller.enqueue(chunk)
+ const { part } = chunk
if (part.type === "text-delta" || part.type === "tool-call" || part.type === "tool-result" || part.type === "tool-call-streaming-start" || part.type === "tool-call-delta") {
- await (onChunk == null ? void 0 : onChunk({ chunk: part }));
+ await (onChunk == null ? void 0 : onChunk({ chunk: part }))
}
if (part.type === "text-delta") {
- recordedStepText += part.textDelta;
- recordedContinuationText += part.textDelta;
- recordedFullText += part.textDelta;
+ recordedStepText += part.textDelta
+ recordedContinuationText += part.textDelta
+ recordedFullText += part.textDelta
}
if (part.type === "tool-call") {
- recordedToolCalls.push(part);
+ recordedToolCalls.push(part)
}
if (part.type === "tool-result") {
- recordedToolResults.push(part);
+ recordedToolResults.push(part)
}
if (part.type === "step-finish") {
const stepMessages = toResponseMessages({
@@ -4702,19 +4707,19 @@ var DefaultStreamTextResult = class {
toolResults: recordedToolResults,
messageId: part.messageId,
generateMessageId
- });
- const currentStep = recordedSteps.length;
- let nextStepType = "done";
+ })
+ const currentStep = recordedSteps.length
+ let nextStepType = "done"
if (currentStep + 1 < maxSteps) {
if (continueSteps && part.finishReason === "length" && // only use continue when there are no tool calls:
recordedToolCalls.length === 0) {
- nextStepType = "continue";
+ nextStepType = "continue"
} else if (
// there are tool calls:
recordedToolCalls.length > 0 && // all current tool calls have results:
recordedToolResults.length === recordedToolCalls.length
) {
- nextStepType = "tool-result";
+ nextStepType = "tool-result"
}
}
const currentStepResult = {
@@ -4733,54 +4738,54 @@ var DefaultStreamTextResult = class {
},
experimental_providerMetadata: part.experimental_providerMetadata,
isContinued: part.isContinued
- };
- await (onStepFinish == null ? void 0 : onStepFinish(currentStepResult));
- recordedSteps.push(currentStepResult);
- recordedToolCalls = [];
- recordedToolResults = [];
- recordedStepText = "";
+ }
+ await (onStepFinish == null ? void 0 : onStepFinish(currentStepResult))
+ recordedSteps.push(currentStepResult)
+ recordedToolCalls = []
+ recordedToolResults = []
+ recordedStepText = ""
if (nextStepType !== "done") {
- stepType = nextStepType;
+ stepType = nextStepType
}
if (nextStepType !== "continue") {
- recordedResponse.messages.push(...stepMessages);
- recordedContinuationText = "";
+ recordedResponse.messages.push(...stepMessages)
+ recordedContinuationText = ""
}
}
if (part.type === "finish") {
- recordedResponse.id = part.response.id;
- recordedResponse.timestamp = part.response.timestamp;
- recordedResponse.modelId = part.response.modelId;
- recordedResponse.headers = part.response.headers;
- recordedUsage = part.usage;
- recordedFinishReason = part.finishReason;
+ recordedResponse.id = part.response.id
+ recordedResponse.timestamp = part.response.timestamp
+ recordedResponse.modelId = part.response.modelId
+ recordedResponse.headers = part.response.headers
+ recordedUsage = part.usage
+ recordedFinishReason = part.finishReason
}
},
async flush(controller) {
- var _a15;
+ var _a15
try {
if (recordedSteps.length === 0) {
- return;
+ return
}
- const lastStep = recordedSteps[recordedSteps.length - 1];
- self.warningsPromise.resolve(lastStep.warnings);
- self.requestPromise.resolve(lastStep.request);
- self.responsePromise.resolve(lastStep.response);
- self.toolCallsPromise.resolve(lastStep.toolCalls);
- self.toolResultsPromise.resolve(lastStep.toolResults);
+ const lastStep = recordedSteps[recordedSteps.length - 1]
+ self.warningsPromise.resolve(lastStep.warnings)
+ self.requestPromise.resolve(lastStep.request)
+ self.responsePromise.resolve(lastStep.response)
+ self.toolCallsPromise.resolve(lastStep.toolCalls)
+ self.toolResultsPromise.resolve(lastStep.toolResults)
self.providerMetadataPromise.resolve(
lastStep.experimental_providerMetadata
- );
- const finishReason = recordedFinishReason != null ? recordedFinishReason : "unknown";
+ )
+ const finishReason = recordedFinishReason != null ? recordedFinishReason : "unknown"
const usage = recordedUsage != null ? recordedUsage : {
completionTokens: NaN,
promptTokens: NaN,
totalTokens: NaN
- };
- self.finishReasonPromise.resolve(finishReason);
- self.usagePromise.resolve(usage);
- self.textPromise.resolve(recordedFullText);
- self.stepsPromise.resolve(recordedSteps);
+ }
+ self.finishReasonPromise.resolve(finishReason)
+ self.usagePromise.resolve(usage)
+ self.textPromise.resolve(recordedFullText)
+ self.stepsPromise.resolve(recordedSteps)
await (onFinish == null ? void 0 : onFinish({
finishReason,
logprobs: void 0,
@@ -4793,7 +4798,7 @@ var DefaultStreamTextResult = class {
warnings: lastStep.warnings,
experimental_providerMetadata: lastStep.experimental_providerMetadata,
steps: recordedSteps
- }));
+ }))
rootSpan.setAttributes(
selectTelemetryAttributes({
telemetry,
@@ -4802,47 +4807,47 @@ var DefaultStreamTextResult = class {
"ai.response.text": { output: () => recordedFullText },
"ai.response.toolCalls": {
output: () => {
- var _a16;
- return ((_a16 = lastStep.toolCalls) == null ? void 0 : _a16.length) ? JSON.stringify(lastStep.toolCalls) : void 0;
+ var _a16
+ return ((_a16 = lastStep.toolCalls) == null ? void 0 : _a16.length) ? JSON.stringify(lastStep.toolCalls) : void 0
}
},
"ai.usage.promptTokens": usage.promptTokens,
"ai.usage.completionTokens": usage.completionTokens
}
})
- );
+ )
} catch (error) {
- controller.error(error);
+ controller.error(error)
} finally {
- rootSpan.end();
+ rootSpan.end()
}
}
- });
- const stitchableStream = createStitchableStream();
- this.addStream = stitchableStream.addStream;
- this.closeStream = stitchableStream.close;
- let stream = stitchableStream.stream;
+ })
+ const stitchableStream = createStitchableStream()
+ this.addStream = stitchableStream.addStream
+ this.closeStream = stitchableStream.close
+ let stream = stitchableStream.stream
for (const transform of transforms) {
stream = stream.pipeThrough(
transform({
tools,
stopStream() {
- stitchableStream.terminate();
+ stitchableStream.terminate()
}
})
- );
+ )
}
- this.baseStream = stream.pipeThrough(createOutputTransformStream(output)).pipeThrough(eventProcessor);
+ this.baseStream = stream.pipeThrough(createOutputTransformStream(output)).pipeThrough(eventProcessor)
const { maxRetries, retry } = prepareRetries({
maxRetries: maxRetriesArg
- });
- const tracer = getTracer(telemetry);
+ })
+ const tracer = getTracer(telemetry)
const baseTelemetryAttributes = getBaseTelemetryAttributes({
model,
telemetry,
headers,
settings: { ...settings, maxRetries }
- });
+ })
const initialPrompt = standardizePrompt({
prompt: {
system: (_a14 = output == null ? void 0 : output.injectIntoSystemPrompt({ system, model })) != null ? _a14 : system,
@@ -4850,8 +4855,8 @@ var DefaultStreamTextResult = class {
messages
},
tools
- });
- const self = this;
+ })
+ const self = this
recordSpan({
name: "ai.streamText",
attributes: selectTelemetryAttributes({
@@ -4869,7 +4874,7 @@ var DefaultStreamTextResult = class {
tracer,
endWhenDone: false,
fn: async (rootSpanArg) => {
- rootSpan = rootSpanArg;
+ rootSpan = rootSpanArg
async function streamStep({
currentStep,
responseMessages,
@@ -4879,11 +4884,11 @@ var DefaultStreamTextResult = class {
hasLeadingWhitespace,
messageId
}) {
- const promptFormat = responseMessages.length === 0 ? initialPrompt.type : "messages";
+ const promptFormat = responseMessages.length === 0 ? initialPrompt.type : "messages"
const stepInputMessages = [
...initialPrompt.messages,
...responseMessages
- ];
+ ]
const promptMessages = await convertToLanguageModelPrompt({
prompt: {
type: promptFormat,
@@ -4892,11 +4897,11 @@ var DefaultStreamTextResult = class {
},
modelSupportsImageUrls: model.supportsImageUrls,
modelSupportsUrl: model.supportsUrl
- });
+ })
const mode = {
type: "regular",
...prepareToolsAndToolChoice({ tools, toolChoice, activeTools })
- };
+ }
const {
result: { stream: stream2, warnings, rawResponse, request },
doStreamSpan,
@@ -4921,8 +4926,8 @@ var DefaultStreamTextResult = class {
"ai.prompt.tools": {
// convert the language model level tools:
input: () => {
- var _a15;
- return (_a15 = mode.tools) == null ? void 0 : _a15.map((tool2) => JSON.stringify(tool2));
+ var _a15
+ return (_a15 = mode.tools) == null ? void 0 : _a15.map((tool2) => JSON.stringify(tool2))
}
},
"ai.prompt.toolChoice": {
@@ -4958,7 +4963,7 @@ var DefaultStreamTextResult = class {
})
})
})
- );
+ )
const transformedStream = runToolsTransformation({
tools,
generatorStream: stream2,
@@ -4969,151 +4974,151 @@ var DefaultStreamTextResult = class {
messages: stepInputMessages,
repairToolCall,
abortSignal
- });
- const stepRequest = request != null ? request : {};
- const stepToolCalls = [];
- const stepToolResults = [];
- let stepFinishReason = "unknown";
+ })
+ const stepRequest = request != null ? request : {}
+ const stepToolCalls = []
+ const stepToolResults = []
+ let stepFinishReason = "unknown"
let stepUsage = {
promptTokens: 0,
completionTokens: 0,
totalTokens: 0
- };
- let stepProviderMetadata;
- let stepFirstChunk = true;
- let stepText = "";
- let fullStepText = stepType2 === "continue" ? previousStepText : "";
- let stepLogProbs;
+ }
+ let stepProviderMetadata
+ let stepFirstChunk = true
+ let stepText = ""
+ let fullStepText = stepType2 === "continue" ? previousStepText : ""
+ let stepLogProbs
let stepResponse = {
id: generateId3(),
timestamp: currentDate(),
modelId: model.modelId
- };
- let chunkBuffer = "";
- let chunkTextPublished = false;
- let inWhitespacePrefix = true;
- let hasWhitespaceSuffix = false;
+ }
+ let chunkBuffer = ""
+ let chunkTextPublished = false
+ let inWhitespacePrefix = true
+ let hasWhitespaceSuffix = false
async function publishTextChunk({
controller,
chunk
}) {
- controller.enqueue(chunk);
- stepText += chunk.textDelta;
- fullStepText += chunk.textDelta;
- chunkTextPublished = true;
- hasWhitespaceSuffix = chunk.textDelta.trimEnd() !== chunk.textDelta;
+ controller.enqueue(chunk)
+ stepText += chunk.textDelta
+ fullStepText += chunk.textDelta
+ chunkTextPublished = true
+ hasWhitespaceSuffix = chunk.textDelta.trimEnd() !== chunk.textDelta
}
self.addStream(
transformedStream.pipeThrough(
new TransformStream({
async transform(chunk, controller) {
- var _a15, _b, _c;
+ var _a15, _b, _c
if (stepFirstChunk) {
- const msToFirstChunk = now2() - startTimestampMs;
- stepFirstChunk = false;
+ const msToFirstChunk = now2() - startTimestampMs
+ stepFirstChunk = false
doStreamSpan.addEvent("ai.stream.firstChunk", {
"ai.response.msToFirstChunk": msToFirstChunk
- });
+ })
doStreamSpan.setAttributes({
"ai.response.msToFirstChunk": msToFirstChunk
- });
+ })
controller.enqueue({
type: "step-start",
messageId,
request: stepRequest,
warnings: warnings != null ? warnings : []
- });
+ })
}
if (chunk.type === "text-delta" && chunk.textDelta.length === 0) {
- return;
+ return
}
- const chunkType = chunk.type;
+ const chunkType = chunk.type
switch (chunkType) {
case "text-delta": {
if (continueSteps) {
- const trimmedChunkText = inWhitespacePrefix && hasLeadingWhitespace ? chunk.textDelta.trimStart() : chunk.textDelta;
+ const trimmedChunkText = inWhitespacePrefix && hasLeadingWhitespace ? chunk.textDelta.trimStart() : chunk.textDelta
if (trimmedChunkText.length === 0) {
- break;
+ break
}
- inWhitespacePrefix = false;
- chunkBuffer += trimmedChunkText;
- const split = splitOnLastWhitespace(chunkBuffer);
+ inWhitespacePrefix = false
+ chunkBuffer += trimmedChunkText
+ const split = splitOnLastWhitespace(chunkBuffer)
if (split != null) {
- chunkBuffer = split.suffix;
+ chunkBuffer = split.suffix
await publishTextChunk({
controller,
chunk: {
type: "text-delta",
textDelta: split.prefix + split.whitespace
}
- });
+ })
}
} else {
- await publishTextChunk({ controller, chunk });
+ await publishTextChunk({ controller, chunk })
}
- break;
+ break
}
case "tool-call": {
- controller.enqueue(chunk);
- stepToolCalls.push(chunk);
- break;
+ controller.enqueue(chunk)
+ stepToolCalls.push(chunk)
+ break
}
case "tool-result": {
- controller.enqueue(chunk);
- stepToolResults.push(chunk);
- break;
+ controller.enqueue(chunk)
+ stepToolResults.push(chunk)
+ break
}
case "response-metadata": {
stepResponse = {
id: (_a15 = chunk.id) != null ? _a15 : stepResponse.id,
timestamp: (_b = chunk.timestamp) != null ? _b : stepResponse.timestamp,
modelId: (_c = chunk.modelId) != null ? _c : stepResponse.modelId
- };
- break;
+ }
+ break
}
case "finish": {
- stepUsage = chunk.usage;
- stepFinishReason = chunk.finishReason;
- stepProviderMetadata = chunk.experimental_providerMetadata;
- stepLogProbs = chunk.logprobs;
- const msToFinish = now2() - startTimestampMs;
- doStreamSpan.addEvent("ai.stream.finish");
+ stepUsage = chunk.usage
+ stepFinishReason = chunk.finishReason
+ stepProviderMetadata = chunk.experimental_providerMetadata
+ stepLogProbs = chunk.logprobs
+ const msToFinish = now2() - startTimestampMs
+ doStreamSpan.addEvent("ai.stream.finish")
doStreamSpan.setAttributes({
"ai.response.msToFinish": msToFinish,
"ai.response.avgCompletionTokensPerSecond": 1e3 * stepUsage.completionTokens / msToFinish
- });
- break;
+ })
+ break
}
case "tool-call-streaming-start":
case "tool-call-delta": {
- controller.enqueue(chunk);
- break;
+ controller.enqueue(chunk)
+ break
}
case "error": {
- controller.enqueue(chunk);
- stepFinishReason = "error";
- break;
+ controller.enqueue(chunk)
+ stepFinishReason = "error"
+ break
}
default: {
- const exhaustiveCheck = chunkType;
- throw new Error(`Unknown chunk type: ${exhaustiveCheck}`);
+ const exhaustiveCheck = chunkType
+ throw new Error(`Unknown chunk type: ${exhaustiveCheck}`)
}
}
},
// invoke onFinish callback and resolve toolResults promise when the stream is about to close:
async flush(controller) {
- const stepToolCallsJson = stepToolCalls.length > 0 ? JSON.stringify(stepToolCalls) : void 0;
- let nextStepType = "done";
+ const stepToolCallsJson = stepToolCalls.length > 0 ? JSON.stringify(stepToolCalls) : void 0
+ let nextStepType = "done"
if (currentStep + 1 < maxSteps) {
if (continueSteps && stepFinishReason === "length" && // only use continue when there are no tool calls:
stepToolCalls.length === 0) {
- nextStepType = "continue";
+ nextStepType = "continue"
} else if (
// there are tool calls:
stepToolCalls.length > 0 && // all current tool calls have results:
stepToolResults.length === stepToolCalls.length
) {
- nextStepType = "tool-result";
+ nextStepType = "tool-result"
}
}
if (continueSteps && chunkBuffer.length > 0 && (nextStepType !== "continue" || // when the next step is a regular step, publish the buffer
@@ -5124,8 +5129,8 @@ var DefaultStreamTextResult = class {
type: "text-delta",
textDelta: chunkBuffer
}
- });
- chunkBuffer = "";
+ })
+ chunkBuffer = ""
}
try {
doStreamSpan.setAttributes(
@@ -5150,10 +5155,10 @@ var DefaultStreamTextResult = class {
"gen_ai.usage.output_tokens": stepUsage.completionTokens
}
})
- );
+ )
} catch (error) {
} finally {
- doStreamSpan.end();
+ doStreamSpan.end()
}
controller.enqueue({
type: "step-finish",
@@ -5169,8 +5174,8 @@ var DefaultStreamTextResult = class {
warnings,
isContinued: nextStepType === "continue",
messageId
- });
- const combinedUsage = addLanguageModelUsage(usage, stepUsage);
+ })
+ const combinedUsage = addLanguageModelUsage(usage, stepUsage)
if (nextStepType === "done") {
controller.enqueue({
type: "finish",
@@ -5182,18 +5187,18 @@ var DefaultStreamTextResult = class {
...stepResponse,
headers: rawResponse == null ? void 0 : rawResponse.headers
}
- });
- self.closeStream();
+ })
+ self.closeStream()
} else {
if (stepType2 === "continue") {
- const lastMessage = responseMessages[responseMessages.length - 1];
+ const lastMessage = responseMessages[responseMessages.length - 1]
if (typeof lastMessage.content === "string") {
- lastMessage.content += stepText;
+ lastMessage.content += stepText
} else {
lastMessage.content.push({
text: stepText,
type: "text"
- });
+ })
}
} else {
responseMessages.push(
@@ -5205,7 +5210,7 @@ var DefaultStreamTextResult = class {
messageId,
generateMessageId
})
- );
+ )
}
await streamStep({
currentStep: currentStep + 1,
@@ -5218,12 +5223,12 @@ var DefaultStreamTextResult = class {
// keep the same id when continuing a step:
nextStepType === "continue" ? messageId : generateMessageId()
)
- });
+ })
}
}
})
)
- );
+ )
}
await streamStep({
currentStep: 0,
@@ -5237,49 +5242,49 @@ var DefaultStreamTextResult = class {
stepType: "initial",
hasLeadingWhitespace: false,
messageId: generateMessageId()
- });
+ })
}
}).catch((error) => {
self.addStream(
new ReadableStream({
start(controller) {
- controller.enqueue({ type: "error", error });
- controller.close();
+ controller.enqueue({ type: "error", error })
+ controller.close()
}
})
- );
- self.closeStream();
- });
+ )
+ self.closeStream()
+ })
}
get warnings() {
- return this.warningsPromise.value;
+ return this.warningsPromise.value
}
get usage() {
- return this.usagePromise.value;
+ return this.usagePromise.value
}
get finishReason() {
- return this.finishReasonPromise.value;
+ return this.finishReasonPromise.value
}
get experimental_providerMetadata() {
- return this.providerMetadataPromise.value;
+ return this.providerMetadataPromise.value
}
get text() {
- return this.textPromise.value;
+ return this.textPromise.value
}
get toolCalls() {
- return this.toolCallsPromise.value;
+ return this.toolCallsPromise.value
}
get toolResults() {
- return this.toolResultsPromise.value;
+ return this.toolResultsPromise.value
}
get request() {
- return this.requestPromise.value;
+ return this.requestPromise.value
}
get response() {
- return this.responsePromise.value;
+ return this.responsePromise.value
}
get steps() {
- return this.stepsPromise.value;
+ return this.stepsPromise.value
}
/**
Split out a new stream from the original stream.
@@ -5290,9 +5295,9 @@ var DefaultStreamTextResult = class {
However, the LLM results are expected to be small enough to not cause issues.
*/
teeStream() {
- const [stream1, stream2] = this.baseStream.tee();
- this.baseStream = stream2;
- return stream1;
+ const [stream1, stream2] = this.baseStream.tee()
+ this.baseStream = stream2
+ return stream1
}
get textStream() {
return createAsyncIterableStream(
@@ -5300,63 +5305,63 @@ var DefaultStreamTextResult = class {
new TransformStream({
transform({ part }, controller) {
if (part.type === "text-delta") {
- controller.enqueue(part.textDelta);
+ controller.enqueue(part.textDelta)
} else if (part.type === "error") {
- controller.error(part.error);
+ controller.error(part.error)
}
}
})
)
- );
+ )
}
get fullStream() {
return createAsyncIterableStream(
this.teeStream().pipeThrough(
new TransformStream({
transform({ part }, controller) {
- controller.enqueue(part);
+ controller.enqueue(part)
}
})
)
- );
+ )
}
get experimental_partialOutputStream() {
if (this.output == null) {
- throw new NoOutputSpecifiedError();
+ throw new NoOutputSpecifiedError()
}
return createAsyncIterableStream(
this.teeStream().pipeThrough(
new TransformStream({
transform({ partialOutput }, controller) {
if (partialOutput != null) {
- controller.enqueue(partialOutput);
+ controller.enqueue(partialOutput)
}
}
})
)
- );
+ )
}
toDataStreamInternal({
getErrorMessage: getErrorMessage5 = () => "An error occurred.",
// mask error messages for safety by default
sendUsage = true
} = {}) {
- let aggregatedResponse = "";
+ let aggregatedResponse = ""
const callbackTransformer = new TransformStream({
async transform(chunk, controller) {
- controller.enqueue(chunk);
+ controller.enqueue(chunk)
if (chunk.type === "text-delta") {
- aggregatedResponse += chunk.textDelta;
+ aggregatedResponse += chunk.textDelta
}
}
- });
+ })
const streamPartsTransformer = new TransformStream({
transform: async (chunk, controller) => {
- const chunkType = chunk.type;
+ const chunkType = chunk.type
switch (chunkType) {
case "text-delta": {
- controller.enqueue((0, import_ui_utils8.formatDataStreamPart)("text", chunk.textDelta));
- break;
+ controller.enqueue((0, import_ui_utils8.formatDataStreamPart)("text", chunk.textDelta))
+ break
}
case "tool-call-streaming-start": {
controller.enqueue(
@@ -5364,8 +5369,8 @@ var DefaultStreamTextResult = class {
toolCallId: chunk.toolCallId,
toolName: chunk.toolName
})
- );
- break;
+ )
+ break
}
case "tool-call-delta": {
controller.enqueue(
@@ -5373,8 +5378,8 @@ var DefaultStreamTextResult = class {
toolCallId: chunk.toolCallId,
argsTextDelta: chunk.argsTextDelta
})
- );
- break;
+ )
+ break
}
case "tool-call": {
controller.enqueue(
@@ -5383,8 +5388,8 @@ var DefaultStreamTextResult = class {
toolName: chunk.toolName,
args: chunk.args
})
- );
- break;
+ )
+ break
}
case "tool-result": {
controller.enqueue(
@@ -5392,22 +5397,22 @@ var DefaultStreamTextResult = class {
toolCallId: chunk.toolCallId,
result: chunk.result
})
- );
- break;
+ )
+ break
}
case "error": {
controller.enqueue(
(0, import_ui_utils8.formatDataStreamPart)("error", getErrorMessage5(chunk.error))
- );
- break;
+ )
+ break
}
case "step-start": {
controller.enqueue(
(0, import_ui_utils8.formatDataStreamPart)("start_step", {
messageId: chunk.messageId
})
- );
- break;
+ )
+ break
}
case "step-finish": {
controller.enqueue(
@@ -5419,8 +5424,8 @@ var DefaultStreamTextResult = class {
} : void 0,
isContinued: chunk.isContinued
})
- );
- break;
+ )
+ break
}
case "finish": {
controller.enqueue(
@@ -5431,17 +5436,17 @@ var DefaultStreamTextResult = class {
completionTokens: chunk.usage.completionTokens
} : void 0
})
- );
- break;
+ )
+ break
}
default: {
- const exhaustiveCheck = chunkType;
- throw new Error(`Unknown chunk type: ${exhaustiveCheck}`);
+ const exhaustiveCheck = chunkType
+ throw new Error(`Unknown chunk type: ${exhaustiveCheck}`)
}
}
}
- });
- return this.fullStream.pipeThrough(callbackTransformer).pipeThrough(streamPartsTransformer);
+ })
+ return this.fullStream.pipeThrough(callbackTransformer).pipeThrough(streamPartsTransformer)
}
pipeDataStreamToResponse(response, {
status,
@@ -5460,7 +5465,7 @@ var DefaultStreamTextResult = class {
dataStreamVersion: "v1"
}),
stream: this.toDataStream({ data, getErrorMessage: getErrorMessage5, sendUsage })
- });
+ })
}
pipeTextStreamToResponse(response, init) {
writeToServerResponse({
@@ -5471,22 +5476,22 @@ var DefaultStreamTextResult = class {
contentType: "text/plain; charset=utf-8"
}),
stream: this.textStream.pipeThrough(new TextEncoderStream())
- });
+ })
}
// TODO breaking change 5.0: remove pipeThrough(new TextEncoderStream())
toDataStream(options) {
const stream = this.toDataStreamInternal({
getErrorMessage: options == null ? void 0 : options.getErrorMessage,
sendUsage: options == null ? void 0 : options.sendUsage
- }).pipeThrough(new TextEncoderStream());
- return (options == null ? void 0 : options.data) ? mergeStreams(options == null ? void 0 : options.data.stream, stream) : stream;
+ }).pipeThrough(new TextEncoderStream())
+ return (options == null ? void 0 : options.data) ? mergeStreams(options == null ? void 0 : options.data.stream, stream) : stream
}
mergeIntoDataStream(writer) {
writer.merge(
this.toDataStreamInternal({
getErrorMessage: writer.onError
})
- );
+ )
}
toDataStreamResponse({
headers,
@@ -5506,18 +5511,18 @@ var DefaultStreamTextResult = class {
dataStreamVersion: "v1"
})
}
- );
+ )
}
toTextStreamResponse(init) {
- var _a14;
+ var _a14
return new Response(this.textStream.pipeThrough(new TextEncoderStream()), {
status: (_a14 = init == null ? void 0 : init.status) != null ? _a14 : 200,
headers: prepareResponseHeaders(init == null ? void 0 : init.headers, {
contentType: "text/plain; charset=utf-8"
})
- });
+ })
}
-};
+}
// core/middleware/wrap-language-model.ts
var experimental_wrapLanguageModel = ({
@@ -5530,7 +5535,7 @@ var experimental_wrapLanguageModel = ({
params,
type
}) {
- return transformParams ? await transformParams({ params, type }) : params;
+ return transformParams ? await transformParams({ params, type }) : params
}
return {
specificationVersion: "v1",
@@ -5541,27 +5546,27 @@ var experimental_wrapLanguageModel = ({
supportsUrl: model.supportsUrl,
supportsStructuredOutputs: model.supportsStructuredOutputs,
async doGenerate(params) {
- const transformedParams = await doTransform({ params, type: "generate" });
- const doGenerate = async () => model.doGenerate(transformedParams);
- return wrapGenerate ? wrapGenerate({ doGenerate, params: transformedParams, model }) : doGenerate();
+ const transformedParams = await doTransform({ params, type: "generate" })
+ const doGenerate = async () => model.doGenerate(transformedParams)
+ return wrapGenerate ? wrapGenerate({ doGenerate, params: transformedParams, model }) : doGenerate()
},
async doStream(params) {
- const transformedParams = await doTransform({ params, type: "stream" });
- const doStream = async () => model.doStream(transformedParams);
- return wrapStream ? wrapStream({ doStream, params: transformedParams, model }) : doStream();
+ const transformedParams = await doTransform({ params, type: "stream" })
+ const doStream = async () => model.doStream(transformedParams)
+ return wrapStream ? wrapStream({ doStream, params: transformedParams, model }) : doStream()
}
- };
-};
+ }
+}
// core/prompt/append-response-messages.ts
function appendResponseMessages({
messages,
responseMessages
}) {
- var _a14;
- const clonedMessages = structuredClone(messages);
+ var _a14
+ const clonedMessages = structuredClone(messages)
for (const message of responseMessages) {
- const role = message.role;
+ const role = message.role
switch (role) {
case "assistant": {
clonedMessages.push({
@@ -5576,41 +5581,41 @@ function appendResponseMessages({
state: "call",
...call
}))
- });
- break;
+ })
+ break
}
case "tool": {
const previousMessage = clonedMessages[clonedMessages.length - 1];
- (_a14 = previousMessage.toolInvocations) != null ? _a14 : previousMessage.toolInvocations = [];
+ (_a14 = previousMessage.toolInvocations) != null ? _a14 : previousMessage.toolInvocations = []
if (previousMessage.role !== "assistant") {
throw new Error(
`Tool result must follow an assistant message: ${previousMessage.role}`
- );
+ )
}
for (const part of message.content) {
const toolCall = previousMessage.toolInvocations.find(
(call) => call.toolCallId === part.toolCallId
- );
+ )
if (!toolCall) {
- throw new Error("Tool call not found in previous message");
+ throw new Error("Tool call not found in previous message")
}
- toolCall.state = "result";
- const toolResult = toolCall;
- toolResult.result = part.result;
+ toolCall.state = "result"
+ const toolResult = toolCall
+ toolResult.result = part.result
}
- break;
+ break
}
default: {
- const _exhaustiveCheck = role;
- throw new Error(`Unsupported message role: ${_exhaustiveCheck}`);
+ const _exhaustiveCheck = role
+ throw new Error(`Unsupported message role: ${_exhaustiveCheck}`)
}
}
}
- return clonedMessages;
+ return clonedMessages
}
// core/registry/custom-provider.ts
-var import_provider18 = require("@ai-sdk/provider");
+var import_provider18 = require("@ai-sdk/provider")
function experimental_customProvider({
languageModels,
textEmbeddingModels,
@@ -5619,31 +5624,31 @@ function experimental_customProvider({
return {
languageModel(modelId) {
if (languageModels != null && modelId in languageModels) {
- return languageModels[modelId];
+ return languageModels[modelId]
}
if (fallbackProvider) {
- return fallbackProvider.languageModel(modelId);
+ return fallbackProvider.languageModel(modelId)
}
- throw new import_provider18.NoSuchModelError({ modelId, modelType: "languageModel" });
+ throw new import_provider18.NoSuchModelError({ modelId, modelType: "languageModel" })
},
textEmbeddingModel(modelId) {
if (textEmbeddingModels != null && modelId in textEmbeddingModels) {
- return textEmbeddingModels[modelId];
+ return textEmbeddingModels[modelId]
}
if (fallbackProvider) {
- return fallbackProvider.textEmbeddingModel(modelId);
+ return fallbackProvider.textEmbeddingModel(modelId)
}
- throw new import_provider18.NoSuchModelError({ modelId, modelType: "textEmbeddingModel" });
+ throw new import_provider18.NoSuchModelError({ modelId, modelType: "textEmbeddingModel" })
}
- };
+ }
}
// core/registry/no-such-provider-error.ts
-var import_provider19 = require("@ai-sdk/provider");
-var name13 = "AI_NoSuchProviderError";
-var marker13 = `vercel.ai.error.${name13}`;
-var symbol13 = Symbol.for(marker13);
-var _a13;
+var import_provider19 = require("@ai-sdk/provider")
+var name13 = "AI_NoSuchProviderError"
+var marker13 = `vercel.ai.error.${name13}`
+var symbol13 = Symbol.for(marker13)
+var _a13
var NoSuchProviderError = class extends import_provider19.NoSuchModelError {
constructor({
modelId,
@@ -5652,123 +5657,123 @@ var NoSuchProviderError = class extends import_provider19.NoSuchModelError {
availableProviders,
message = `No such provider: ${providerId} (available providers: ${availableProviders.join()})`
}) {
- super({ errorName: name13, modelId, modelType, message });
- this[_a13] = true;
- this.providerId = providerId;
- this.availableProviders = availableProviders;
+ super({ errorName: name13, modelId, modelType, message })
+ this[_a13] = true
+ this.providerId = providerId
+ this.availableProviders = availableProviders
}
static isInstance(error) {
- return import_provider19.AISDKError.hasMarker(error, marker13);
+ return import_provider19.AISDKError.hasMarker(error, marker13)
}
-};
-_a13 = symbol13;
+}
+_a13 = symbol13
// core/registry/provider-registry.ts
-var import_provider20 = require("@ai-sdk/provider");
+var import_provider20 = require("@ai-sdk/provider")
function experimental_createProviderRegistry(providers) {
- const registry = new DefaultProviderRegistry();
+ const registry = new DefaultProviderRegistry()
for (const [id, provider] of Object.entries(providers)) {
- registry.registerProvider({ id, provider });
+ registry.registerProvider({ id, provider })
}
- return registry;
+ return registry
}
var DefaultProviderRegistry = class {
constructor() {
- this.providers = {};
+ this.providers = {}
}
registerProvider({ id, provider }) {
- this.providers[id] = provider;
+ this.providers[id] = provider
}
getProvider(id) {
- const provider = this.providers[id];
+ const provider = this.providers[id]
if (provider == null) {
throw new NoSuchProviderError({
modelId: id,
modelType: "languageModel",
providerId: id,
availableProviders: Object.keys(this.providers)
- });
+ })
}
- return provider;
+ return provider
}
splitId(id, modelType) {
- const index = id.indexOf(":");
+ const index = id.indexOf(":")
if (index === -1) {
throw new import_provider20.NoSuchModelError({
modelId: id,
modelType,
message: `Invalid ${modelType} id for registry: ${id} (must be in the format "providerId:modelId")`
- });
+ })
}
- return [id.slice(0, index), id.slice(index + 1)];
+ return [id.slice(0, index), id.slice(index + 1)]
}
languageModel(id) {
- var _a14, _b;
- const [providerId, modelId] = this.splitId(id, "languageModel");
- const model = (_b = (_a14 = this.getProvider(providerId)).languageModel) == null ? void 0 : _b.call(_a14, modelId);
+ var _a14, _b
+ const [providerId, modelId] = this.splitId(id, "languageModel")
+ const model = (_b = (_a14 = this.getProvider(providerId)).languageModel) == null ? void 0 : _b.call(_a14, modelId)
if (model == null) {
- throw new import_provider20.NoSuchModelError({ modelId: id, modelType: "languageModel" });
+ throw new import_provider20.NoSuchModelError({ modelId: id, modelType: "languageModel" })
}
- return model;
+ return model
}
textEmbeddingModel(id) {
- var _a14;
- const [providerId, modelId] = this.splitId(id, "textEmbeddingModel");
- const provider = this.getProvider(providerId);
- const model = (_a14 = provider.textEmbeddingModel) == null ? void 0 : _a14.call(provider, modelId);
+ var _a14
+ const [providerId, modelId] = this.splitId(id, "textEmbeddingModel")
+ const provider = this.getProvider(providerId)
+ const model = (_a14 = provider.textEmbeddingModel) == null ? void 0 : _a14.call(provider, modelId)
if (model == null) {
throw new import_provider20.NoSuchModelError({
modelId: id,
modelType: "textEmbeddingModel"
- });
+ })
}
- return model;
+ return model
}
/**
* @deprecated Use `textEmbeddingModel` instead.
*/
textEmbedding(id) {
- return this.textEmbeddingModel(id);
+ return this.textEmbeddingModel(id)
}
-};
+}
// core/tool/tool.ts
function tool(tool2) {
- return tool2;
+ return tool2
}
// core/util/cosine-similarity.ts
function cosineSimilarity(vector1, vector2, options = {
throwErrorForEmptyVectors: false
}) {
- const { throwErrorForEmptyVectors } = options;
+ const { throwErrorForEmptyVectors } = options
if (vector1.length !== vector2.length) {
throw new Error(
`Vectors must have the same length (vector1: ${vector1.length} elements, vector2: ${vector2.length} elements)`
- );
+ )
}
if (throwErrorForEmptyVectors && vector1.length === 0) {
throw new InvalidArgumentError({
parameter: "vector1",
value: vector1,
message: "Vectors cannot be empty"
- });
+ })
}
- const magnitude1 = magnitude(vector1);
- const magnitude2 = magnitude(vector2);
+ const magnitude1 = magnitude(vector1)
+ const magnitude2 = magnitude(vector2)
if (magnitude1 === 0 || magnitude2 === 0) {
- return 0;
+ return 0
}
- return dotProduct(vector1, vector2) / (magnitude1 * magnitude2);
+ return dotProduct(vector1, vector2) / (magnitude1 * magnitude2)
}
function dotProduct(vector1, vector2) {
return vector1.reduce(
(accumulator, value, index) => accumulator + value * vector2[index],
0
- );
+ )
}
function magnitude(vector) {
- return Math.sqrt(dotProduct(vector, vector));
+ return Math.sqrt(dotProduct(vector, vector))
}
// core/util/simulate-readable-stream.ts
@@ -5778,50 +5783,50 @@ function simulateReadableStream({
chunkDelayInMs = 0,
_internal
}) {
- var _a14;
- const delay2 = (_a14 = _internal == null ? void 0 : _internal.delay) != null ? _a14 : delay;
- let index = 0;
+ var _a14
+ const delay2 = (_a14 = _internal == null ? void 0 : _internal.delay) != null ? _a14 : delay
+ let index = 0
return new ReadableStream({
async pull(controller) {
if (index < chunks.length) {
- await delay2(index === 0 ? initialDelayInMs : chunkDelayInMs);
- controller.enqueue(chunks[index++]);
+ await delay2(index === 0 ? initialDelayInMs : chunkDelayInMs)
+ controller.enqueue(chunks[index++])
} else {
- controller.close();
+ controller.close()
}
}
- });
+ })
}
// streams/assistant-response.ts
-var import_ui_utils10 = require("@ai-sdk/ui-utils");
+var import_ui_utils10 = require("@ai-sdk/ui-utils")
function AssistantResponse({ threadId, messageId }, process2) {
const stream = new ReadableStream({
async start(controller) {
- var _a14;
- const textEncoder = new TextEncoder();
+ var _a14
+ const textEncoder = new TextEncoder()
const sendMessage = (message) => {
controller.enqueue(
textEncoder.encode(
(0, import_ui_utils10.formatAssistantStreamPart)("assistant_message", message)
)
- );
- };
+ )
+ }
const sendDataMessage = (message) => {
controller.enqueue(
textEncoder.encode(
(0, import_ui_utils10.formatAssistantStreamPart)("data_message", message)
)
- );
- };
+ )
+ }
const sendError = (errorMessage) => {
controller.enqueue(
textEncoder.encode((0, import_ui_utils10.formatAssistantStreamPart)("error", errorMessage))
- );
- };
+ )
+ }
const forwardStream = async (stream2) => {
- var _a15, _b;
- let result = void 0;
+ var _a15, _b
+ let result = void 0
for await (const value of stream2) {
switch (value.event) {
case "thread.message.created": {
@@ -5833,29 +5838,29 @@ function AssistantResponse({ threadId, messageId }, process2) {
content: [{ type: "text", text: { value: "" } }]
})
)
- );
- break;
+ )
+ break
}
case "thread.message.delta": {
- const content = (_a15 = value.data.delta.content) == null ? void 0 : _a15[0];
+ const content = (_a15 = value.data.delta.content) == null ? void 0 : _a15[0]
if ((content == null ? void 0 : content.type) === "text" && ((_b = content.text) == null ? void 0 : _b.value) != null) {
controller.enqueue(
textEncoder.encode(
(0, import_ui_utils10.formatAssistantStreamPart)("text", content.text.value)
)
- );
+ )
}
- break;
+ break
}
case "thread.run.completed":
case "thread.run.requires_action": {
- result = value.data;
- break;
+ result = value.data
+ break
}
}
}
- return result;
- };
+ return result
+ }
controller.enqueue(
textEncoder.encode(
(0, import_ui_utils10.formatAssistantStreamPart)("assistant_control_data", {
@@ -5863,68 +5868,68 @@ function AssistantResponse({ threadId, messageId }, process2) {
messageId
})
)
- );
+ )
try {
await process2({
sendMessage,
sendDataMessage,
forwardStream
- });
+ })
} catch (error) {
- sendError((_a14 = error.message) != null ? _a14 : `${error}`);
+ sendError((_a14 = error.message) != null ? _a14 : `${error}`)
} finally {
- controller.close();
+ controller.close()
}
},
pull(controller) {
},
cancel() {
}
- });
+ })
return new Response(stream, {
status: 200,
headers: {
"Content-Type": "text/plain; charset=utf-8"
}
- });
+ })
}
// streams/langchain-adapter.ts
-var langchain_adapter_exports = {};
+var langchain_adapter_exports = {}
__export(langchain_adapter_exports, {
mergeIntoDataStream: () => mergeIntoDataStream,
toDataStream: () => toDataStream,
toDataStreamResponse: () => toDataStreamResponse
-});
-var import_ui_utils11 = require("@ai-sdk/ui-utils");
+})
+var import_ui_utils11 = require("@ai-sdk/ui-utils")
// streams/stream-callbacks.ts
function createCallbacksTransformer(callbacks = {}) {
- const textEncoder = new TextEncoder();
- let aggregatedResponse = "";
+ const textEncoder = new TextEncoder()
+ let aggregatedResponse = ""
return new TransformStream({
async start() {
if (callbacks.onStart)
- await callbacks.onStart();
+ await callbacks.onStart()
},
async transform(message, controller) {
- controller.enqueue(textEncoder.encode(message));
- aggregatedResponse += message;
+ controller.enqueue(textEncoder.encode(message))
+ aggregatedResponse += message
if (callbacks.onToken)
- await callbacks.onToken(message);
+ await callbacks.onToken(message)
if (callbacks.onText && typeof message === "string") {
- await callbacks.onText(message);
+ await callbacks.onText(message)
}
},
async flush() {
if (callbacks.onCompletion) {
- await callbacks.onCompletion(aggregatedResponse);
+ await callbacks.onCompletion(aggregatedResponse)
}
if (callbacks.onFinal) {
- await callbacks.onFinal(aggregatedResponse);
+ await callbacks.onFinal(aggregatedResponse)
}
}
- });
+ })
}
// streams/langchain-adapter.ts
@@ -5932,45 +5937,45 @@ function toDataStreamInternal(stream, callbacks) {
return stream.pipeThrough(
new TransformStream({
transform: async (value, controller) => {
- var _a14;
+ var _a14
if (typeof value === "string") {
- controller.enqueue(value);
- return;
+ controller.enqueue(value)
+ return
}
if ("event" in value) {
if (value.event === "on_chat_model_stream") {
forwardAIMessageChunk(
(_a14 = value.data) == null ? void 0 : _a14.chunk,
controller
- );
+ )
}
- return;
+ return
}
- forwardAIMessageChunk(value, controller);
+ forwardAIMessageChunk(value, controller)
}
})
).pipeThrough(createCallbacksTransformer(callbacks)).pipeThrough(new TextDecoderStream()).pipeThrough(
new TransformStream({
transform: async (chunk, controller) => {
- controller.enqueue((0, import_ui_utils11.formatDataStreamPart)("text", chunk));
+ controller.enqueue((0, import_ui_utils11.formatDataStreamPart)("text", chunk))
}
})
- );
+ )
}
function toDataStream(stream, callbacks) {
return toDataStreamInternal(stream, callbacks).pipeThrough(
new TextEncoderStream()
- );
+ )
}
function toDataStreamResponse(stream, options) {
- var _a14;
+ var _a14
const dataStream = toDataStreamInternal(
stream,
options == null ? void 0 : options.callbacks
- ).pipeThrough(new TextEncoderStream());
- const data = options == null ? void 0 : options.data;
- const init = options == null ? void 0 : options.init;
- const responseStream = data ? mergeStreams(data.stream, dataStream) : dataStream;
+ ).pipeThrough(new TextEncoderStream())
+ const data = options == null ? void 0 : options.data
+ const init = options == null ? void 0 : options.init
+ const responseStream = data ? mergeStreams(data.stream, dataStream) : dataStream
return new Response(responseStream, {
status: (_a14 = init == null ? void 0 : init.status) != null ? _a14 : 200,
statusText: init == null ? void 0 : init.statusText,
@@ -5978,61 +5983,61 @@ function toDataStreamResponse(stream, options) {
contentType: "text/plain; charset=utf-8",
dataStreamVersion: "v1"
})
- });
+ })
}
function mergeIntoDataStream(stream, options) {
- options.dataStream.merge(toDataStreamInternal(stream, options.callbacks));
+ options.dataStream.merge(toDataStreamInternal(stream, options.callbacks))
}
function forwardAIMessageChunk(chunk, controller) {
if (typeof chunk.content === "string") {
- controller.enqueue(chunk.content);
+ controller.enqueue(chunk.content)
} else {
- const content = chunk.content;
+ const content = chunk.content
for (const item of content) {
if (item.type === "text") {
- controller.enqueue(item.text);
+ controller.enqueue(item.text)
}
}
}
}
// streams/llamaindex-adapter.ts
-var llamaindex_adapter_exports = {};
+var llamaindex_adapter_exports = {}
__export(llamaindex_adapter_exports, {
mergeIntoDataStream: () => mergeIntoDataStream2,
toDataStream: () => toDataStream2,
toDataStreamResponse: () => toDataStreamResponse2
-});
-var import_provider_utils13 = require("@ai-sdk/provider-utils");
-var import_ui_utils12 = require("@ai-sdk/ui-utils");
+})
+var import_provider_utils13 = require("@ai-sdk/provider-utils")
+var import_ui_utils12 = require("@ai-sdk/ui-utils")
function toDataStreamInternal2(stream, callbacks) {
- const trimStart = trimStartOfStream();
+ const trimStart = trimStartOfStream()
return (0, import_provider_utils13.convertAsyncIteratorToReadableStream)(stream[Symbol.asyncIterator]()).pipeThrough(
new TransformStream({
async transform(message, controller) {
- controller.enqueue(trimStart(message.delta));
+ controller.enqueue(trimStart(message.delta))
}
})
).pipeThrough(createCallbacksTransformer(callbacks)).pipeThrough(new TextDecoderStream()).pipeThrough(
new TransformStream({
transform: async (chunk, controller) => {
- controller.enqueue((0, import_ui_utils12.formatDataStreamPart)("text", chunk));
+ controller.enqueue((0, import_ui_utils12.formatDataStreamPart)("text", chunk))
}
})
- );
+ )
}
function toDataStream2(stream, callbacks) {
return toDataStreamInternal2(stream, callbacks).pipeThrough(
new TextEncoderStream()
- );
+ )
}
function toDataStreamResponse2(stream, options = {}) {
- var _a14;
- const { init, data, callbacks } = options;
+ var _a14
+ const { init, data, callbacks } = options
const dataStream = toDataStreamInternal2(stream, callbacks).pipeThrough(
new TextEncoderStream()
- );
- const responseStream = data ? mergeStreams(data.stream, dataStream) : dataStream;
+ )
+ const responseStream = data ? mergeStreams(data.stream, dataStream) : dataStream
return new Response(responseStream, {
status: (_a14 = init == null ? void 0 : init.status) != null ? _a14 : 200,
statusText: init == null ? void 0 : init.statusText,
@@ -6040,149 +6045,148 @@ function toDataStreamResponse2(stream, options = {}) {
contentType: "text/plain; charset=utf-8",
dataStreamVersion: "v1"
})
- });
+ })
}
function mergeIntoDataStream2(stream, options) {
- options.dataStream.merge(toDataStreamInternal2(stream, options.callbacks));
+ options.dataStream.merge(toDataStreamInternal2(stream, options.callbacks))
}
function trimStartOfStream() {
- let isStreamStart = true;
+ let isStreamStart = true
return (text2) => {
if (isStreamStart) {
- text2 = text2.trimStart();
+ text2 = text2.trimStart()
if (text2)
- isStreamStart = false;
+ isStreamStart = false
}
- return text2;
- };
+ return text2
+ }
}
// streams/stream-data.ts
-var import_ui_utils13 = require("@ai-sdk/ui-utils");
+var import_ui_utils13 = require("@ai-sdk/ui-utils")
// util/constants.ts
-var HANGING_STREAM_WARNING_TIME_MS = 15 * 1e3;
+var HANGING_STREAM_WARNING_TIME_MS = 15 * 1e3
// streams/stream-data.ts
var StreamData = class {
constructor() {
- this.encoder = new TextEncoder();
- this.controller = null;
- this.isClosed = false;
- this.warningTimeout = null;
- const self = this;
+ this.encoder = new TextEncoder()
+ this.controller = null
+ this.isClosed = false
+ this.warningTimeout = null
+ const self = this
this.stream = new ReadableStream({
start: async (controller) => {
- self.controller = controller;
+ self.controller = controller
if (process.env.NODE_ENV === "development") {
self.warningTimeout = setTimeout(() => {
console.warn(
"The data stream is hanging. Did you forget to close it with `data.close()`?"
- );
- }, HANGING_STREAM_WARNING_TIME_MS);
+ )
+ }, HANGING_STREAM_WARNING_TIME_MS)
}
},
pull: (controller) => {
},
cancel: (reason) => {
- this.isClosed = true;
+ this.isClosed = true
}
- });
+ })
}
async close() {
if (this.isClosed) {
- throw new Error("Data Stream has already been closed.");
+ throw new Error("Data Stream has already been closed.")
}
if (!this.controller) {
- throw new Error("Stream controller is not initialized.");
+ throw new Error("Stream controller is not initialized.")
}
- this.controller.close();
- this.isClosed = true;
+ this.controller.close()
+ this.isClosed = true
if (this.warningTimeout) {
- clearTimeout(this.warningTimeout);
+ clearTimeout(this.warningTimeout)
}
}
append(value) {
if (this.isClosed) {
- throw new Error("Data Stream has already been closed.");
+ throw new Error("Data Stream has already been closed.")
}
if (!this.controller) {
- throw new Error("Stream controller is not initialized.");
+ throw new Error("Stream controller is not initialized.")
}
this.controller.enqueue(
this.encoder.encode((0, import_ui_utils13.formatDataStreamPart)("data", [value]))
- );
+ )
}
appendMessageAnnotation(value) {
if (this.isClosed) {
- throw new Error("Data Stream has already been closed.");
+ throw new Error("Data Stream has already been closed.")
}
if (!this.controller) {
- throw new Error("Stream controller is not initialized.");
+ throw new Error("Stream controller is not initialized.")
}
this.controller.enqueue(
this.encoder.encode((0, import_ui_utils13.formatDataStreamPart)("message_annotations", [value]))
- );
+ )
}
-};
+}
// Annotate the CommonJS export names for ESM import in node:
-0 && (module.exports = {
- AISDKError,
- APICallError,
+module.exports = {
+ // AISDKError,
+ // APICallError,
AssistantResponse,
DownloadError,
- EmptyResponseBodyError,
+ // EmptyResponseBodyError,
InvalidArgumentError,
InvalidDataContentError,
InvalidMessageRoleError,
- InvalidPromptError,
- InvalidResponseDataError,
+ // InvalidPromptError,
+ // InvalidResponseDataError,
InvalidToolArgumentsError,
- JSONParseError,
- LangChainAdapter,
- LlamaIndexAdapter,
- LoadAPIKeyError,
+ // JSONParseError,
+ // LangChainAdapter,
+ // LlamaIndexAdapter,
+ // LoadAPIKeyError,
MessageConversionError,
- NoContentGeneratedError,
+ // NoContentGeneratedError,
NoObjectGeneratedError,
NoOutputSpecifiedError,
- NoSuchModelError,
+ // NoSuchModelError,
NoSuchProviderError,
NoSuchToolError,
- Output,
+ // Output,
RetryError,
StreamData,
ToolCallRepairError,
ToolExecutionError,
- TypeValidationError,
- UnsupportedFunctionalityError,
+ // TypeValidationError,
+ // UnsupportedFunctionalityError,
appendResponseMessages,
convertToCoreMessages,
cosineSimilarity,
createDataStream,
createDataStreamResponse,
- createIdGenerator,
+ // createIdGenerator,
embed,
embedMany,
experimental_createProviderRegistry,
experimental_customProvider,
- experimental_generateImage,
+ // experimental_generateImage,
experimental_wrapLanguageModel,
- formatAssistantStreamPart,
- formatDataStreamPart,
- generateId,
+ // formatAssistantStreamPart,
+ // formatDataStreamPart,
+ // generateId,
generateObject,
generateText,
- jsonSchema,
- parseAssistantStreamPart,
- parseDataStreamPart,
+ // jsonSchema,
+ // parseAssistantStreamPart,
+ // parseDataStreamPart,
pipeDataStreamToResponse,
- processDataStream,
- processTextStream,
+ // processDataStream,
+ // processTextStream,
simulateReadableStream,
smoothStream,
streamObject,
streamText,
tool
-});
-//# sourceMappingURL=index.js.map
\ No newline at end of file
+}
diff --git a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/gigachat.js b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/gigachat.js
index 122b5ad..9be96ba 100644
--- a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/gigachat.js
+++ b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/gigachat.js
@@ -607,8 +607,7 @@ function createGigachat(options = {}) {
}
var gigachat = createGigachat();
// Annotate the CommonJS export names for ESM import in node:
-0 && (module.exports = {
+module.exports = {
createGigachat,
gigachat
-});
-//# sourceMappingURL=index.js.map
\ No newline at end of file
+}
diff --git a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/index.js b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/index.js
index 50275f0..da21202 100644
--- a/server/routers/kfu-m-24-1/eng-it-lean/gigachat/index.js
+++ b/server/routers/kfu-m-24-1/eng-it-lean/gigachat/index.js
@@ -84,7 +84,7 @@ router.use(async (req, res, next) => {
process.env.GIGACHAT_ACCESS_TOKEN = json.access_token;
process.env.GIGACHAT_EXPIRES_AT = json.expires_at;
console.log(JSON.stringify(response.data));
- } catch {
+ } catch (error) {
console.log(error);
}
}
diff --git a/server/routers/questioneer/index.js b/server/routers/questioneer/index.js
new file mode 100644
index 0000000..c031883
--- /dev/null
+++ b/server/routers/questioneer/index.js
@@ -0,0 +1,421 @@
+const express = require('express')
+const { Router } = require("express")
+const router = Router()
+const crypto = require('crypto')
+const path = require('path')
+const { getDB } = require('../../utils/mongo')
+const mongoose = require('mongoose')
+
+// Используем одно определение модели
+const Questionnaire = (() => {
+ // Если модель уже существует, используем её
+ if (mongoose.models.Questionnaire) {
+ return mongoose.models.Questionnaire;
+ }
+
+ // Иначе создаем новую модель
+ const questionnaireSchema = new mongoose.Schema({
+ title: { type: String, required: true },
+ description: { type: String },
+ questions: [{
+ text: { type: String, required: true },
+ type: {
+ type: String,
+ enum: ['single_choice', 'multiple_choice', 'text', 'rating', 'tag_cloud', 'scale'],
+ required: true
+ },
+ required: { type: Boolean, default: false },
+ options: [{
+ text: { type: String, required: true },
+ count: { type: Number, default: 0 }
+ }],
+ scaleMin: { type: Number },
+ scaleMax: { type: Number },
+ scaleMinLabel: { type: String },
+ scaleMaxLabel: { type: String },
+ answers: [{ type: String }],
+ scaleValues: [{ type: Number }],
+ tags: [{
+ text: { type: String },
+ count: { type: Number, default: 1 }
+ }]
+ }],
+ displayType: {
+ type: String,
+ enum: ['default', 'tag_cloud', 'voting', 'poll', 'step_by_step'],
+ default: 'step_by_step'
+ },
+ createdAt: { type: Date, default: Date.now },
+ updatedAt: { type: Date, default: Date.now },
+ adminLink: { type: String, required: true },
+ publicLink: { type: String, required: true }
+ });
+
+ return mongoose.model('Questionnaire', questionnaireSchema);
+})();
+
+// Middleware для парсинга JSON
+router.use(express.json());
+
+// Обслуживание статичных файлов - проверяем правильность пути
+router.use('/static', express.static(path.join(__dirname, 'public', 'static')));
+
+// Получить главную страницу
+router.get("/", (req, res) => {
+ res.sendFile(path.join(__dirname, 'public/index.html'))
+})
+
+// Страница создания нового опроса
+router.get("/create", (req, res) => {
+ res.sendFile(path.join(__dirname, 'public/create.html'))
+})
+
+// Страница редактирования опроса
+router.get("/edit/:adminLink", (req, res) => {
+ res.sendFile(path.join(__dirname, 'public/edit.html'))
+})
+
+// Страница администрирования опроса
+router.get("/admin/:adminLink", (req, res) => {
+ res.sendFile(path.join(__dirname, 'public/admin.html'))
+})
+
+// Страница голосования
+router.get("/poll/:publicLink", (req, res) => {
+ res.sendFile(path.join(__dirname, 'public/poll.html'))
+})
+
+// API для работы с опросами
+
+// Создать новый опрос
+router.post("/api/questionnaires", async (req, res) => {
+ try {
+ // Проверка наличия нужных полей
+ const { title, questions } = req.body;
+
+ if (!title || !Array.isArray(questions) || questions.length === 0) {
+ return res.json({ success: false, error: 'Необходимо указать название и хотя бы один вопрос' });
+ }
+
+ // Создаем уникальные ссылки
+ const adminLink = crypto.randomBytes(6).toString('hex');
+ const publicLink = crypto.randomBytes(6).toString('hex');
+
+ // Устанавливаем тип отображения step_by_step, если не указан
+ if (!req.body.displayType) {
+ req.body.displayType = 'step_by_step';
+ }
+
+ // Создаем новый опросник
+ const questionnaire = new Questionnaire({
+ ...req.body,
+ adminLink,
+ publicLink
+ });
+
+ await questionnaire.save();
+
+ res.json({
+ success: true,
+ data: {
+ adminLink,
+ publicLink
+ }
+ });
+ } catch (error) {
+ console.error('Error creating questionnaire:', error);
+ res.json({ success: false, error: error.message });
+ }
+});
+
+// Получить все опросы
+router.get("/api/questionnaires", async (req, res) => {
+ try {
+ const questionnaires = await Questionnaire.find({}, {
+ title: 1,
+ description: 1,
+ createdAt: 1,
+ updatedAt: 1,
+ _id: 1,
+ adminLink: 1,
+ publicLink: 1
+ }).sort({ createdAt: -1 })
+
+ res.status(200).json({
+ success: true,
+ data: questionnaires
+ })
+ } catch (error) {
+ console.error('Error fetching questionnaires:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch questionnaires'
+ })
+ }
+})
+
+// Получить опрос по ID для админа
+router.get("/api/questionnaires/admin/:adminLink", async (req, res) => {
+ try {
+ const { adminLink } = req.params
+ const questionnaire = await Questionnaire.findOne({ adminLink })
+
+ if (!questionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ })
+ }
+
+ res.status(200).json({
+ success: true,
+ data: questionnaire
+ })
+ } catch (error) {
+ console.error('Error fetching questionnaire:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch questionnaire'
+ })
+ }
+})
+
+// Получить опрос по публичной ссылке (для голосования)
+router.get("/api/questionnaires/public/:publicLink", async (req, res) => {
+ try {
+ const { publicLink } = req.params
+ const questionnaire = await Questionnaire.findOne({ publicLink })
+
+ if (!questionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ })
+ }
+
+ res.status(200).json({
+ success: true,
+ data: questionnaire
+ })
+ } catch (error) {
+ console.error('Error fetching questionnaire:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch questionnaire'
+ })
+ }
+})
+
+// Обновить опрос
+router.put("/api/questionnaires/:adminLink", async (req, res) => {
+ try {
+ const { adminLink } = req.params
+ const { title, description, questions, displayType } = req.body
+
+ const updatedQuestionnaire = await Questionnaire.findOneAndUpdate(
+ { adminLink },
+ {
+ title,
+ description,
+ questions,
+ displayType,
+ updatedAt: Date.now()
+ },
+ { new: true }
+ )
+
+ if (!updatedQuestionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ })
+ }
+
+ res.status(200).json({
+ success: true,
+ data: updatedQuestionnaire
+ })
+ } catch (error) {
+ console.error('Error updating questionnaire:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to update questionnaire'
+ })
+ }
+})
+
+// Удалить опрос
+router.delete("/api/questionnaires/:adminLink", async (req, res) => {
+ try {
+ const { adminLink } = req.params
+
+ const deletedQuestionnaire = await Questionnaire.findOneAndDelete({ adminLink })
+
+ if (!deletedQuestionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ })
+ }
+
+ res.status(200).json({
+ success: true,
+ message: 'Questionnaire deleted successfully'
+ })
+ } catch (error) {
+ console.error('Error deleting questionnaire:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to delete questionnaire'
+ })
+ }
+})
+
+// Голосование в опросе
+router.post("/api/vote/:publicLink", async (req, res) => {
+ try {
+ const { publicLink } = req.params
+ const { answers } = req.body
+
+ const questionnaire = await Questionnaire.findOne({ publicLink })
+
+ if (!questionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ })
+ }
+
+ // Обновить счетчики голосов
+ answers.forEach(answer => {
+ const { questionIndex, optionIndices, textAnswer, scaleValue, tagTexts } = answer
+
+ // Обработка одиночного и множественного выбора
+ if (Array.isArray(optionIndices)) {
+ // Для множественного выбора
+ optionIndices.forEach(optionIndex => {
+ if (questionnaire.questions[questionIndex] &&
+ questionnaire.questions[questionIndex].options[optionIndex]) {
+ questionnaire.questions[questionIndex].options[optionIndex].count += 1
+ }
+ })
+ } else if (typeof optionIndices === 'number') {
+ // Для единичного выбора
+ if (questionnaire.questions[questionIndex] &&
+ questionnaire.questions[questionIndex].options[optionIndices]) {
+ questionnaire.questions[questionIndex].options[optionIndices].count += 1
+ }
+ }
+
+ // Сохраняем текстовые ответы
+ if (textAnswer && questionnaire.questions[questionIndex]) {
+ if (!questionnaire.questions[questionIndex].answers) {
+ questionnaire.questions[questionIndex].answers = [];
+ }
+ questionnaire.questions[questionIndex].answers.push(textAnswer);
+ }
+
+ // Сохраняем ответы шкалы оценки
+ if (scaleValue !== undefined && questionnaire.questions[questionIndex]) {
+ if (!questionnaire.questions[questionIndex].scaleValues) {
+ questionnaire.questions[questionIndex].scaleValues = [];
+ }
+ questionnaire.questions[questionIndex].scaleValues.push(scaleValue);
+ }
+
+ // Сохраняем теги
+ if (Array.isArray(tagTexts) && tagTexts.length > 0 && questionnaire.questions[questionIndex]) {
+ if (!questionnaire.questions[questionIndex].tags) {
+ questionnaire.questions[questionIndex].tags = [];
+ }
+
+ tagTexts.forEach(tagText => {
+ const existingTag = questionnaire.questions[questionIndex].tags.find(t => t.text === tagText);
+ if (existingTag) {
+ existingTag.count += 1;
+ } else {
+ questionnaire.questions[questionIndex].tags.push({ text: tagText, count: 1 });
+ }
+ });
+ }
+ })
+
+ await questionnaire.save()
+
+ res.status(200).json({
+ success: true,
+ message: 'Vote registered successfully'
+ })
+ } catch (error) {
+ console.error('Error registering vote:', error)
+ res.status(500).json({
+ success: false,
+ error: 'Failed to register vote'
+ })
+ }
+})
+
+// Получить результаты опроса по публичной ссылке
+router.get("/api/results/:publicLink", async (req, res) => {
+ try {
+ const { publicLink } = req.params;
+ const questionnaire = await Questionnaire.findOne({ publicLink });
+
+ if (!questionnaire) {
+ return res.status(404).json({
+ success: false,
+ error: 'Questionnaire not found'
+ });
+ }
+
+ // Формируем результаты для отправки
+ const results = {
+ title: questionnaire.title,
+ description: questionnaire.description,
+ questions: questionnaire.questions.map(question => {
+ const result = {
+ text: question.text,
+ type: question.type
+ };
+
+ // Добавляем варианты ответов, если они есть
+ if (question.options && question.options.length > 0) {
+ result.options = question.options;
+ }
+
+ // Добавляем текстовые ответы, если они есть
+ if (question.answers && question.answers.length > 0) {
+ result.answers = question.answers;
+ }
+
+ // Добавляем результаты шкалы, если они есть
+ if (question.scaleValues && question.scaleValues.length > 0) {
+ result.scaleValues = question.scaleValues;
+
+ // Считаем среднее значение
+ result.scaleAverage = question.scaleValues.reduce((a, b) => a + b, 0) / question.scaleValues.length;
+ }
+
+ // Добавляем теги, если они есть
+ if (question.tags && question.tags.length > 0) {
+ result.tags = question.tags;
+ }
+
+ return result;
+ })
+ };
+
+ res.status(200).json({
+ success: true,
+ data: results
+ });
+ } catch (error) {
+ console.error('Error fetching poll results:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to fetch poll results'
+ });
+ }
+});
+
+module.exports = router
diff --git a/server/routers/questioneer/openapi.yaml b/server/routers/questioneer/openapi.yaml
new file mode 100644
index 0000000..2293c84
--- /dev/null
+++ b/server/routers/questioneer/openapi.yaml
@@ -0,0 +1,583 @@
+openapi: 3.0.0
+info:
+ title: Анонимные опросы API
+ description: API для работы с системой анонимных опросов
+ version: 1.0.0
+servers:
+ - url: /questioneer/api
+ description: Базовый URL API
+paths:
+ /questionnaires:
+ get:
+ summary: Получить список опросов пользователя
+ description: Возвращает список всех опросов, сохраненных в локальном хранилище браузера
+ operationId: getQuestionnaires
+ responses:
+ '200':
+ description: Успешный запрос
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnairesResponse'
+ post:
+ summary: Создать новый опрос
+ description: Создает новый опрос с указанными параметрами
+ operationId: createQuestionnaire
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireCreate'
+ responses:
+ '200':
+ description: Опрос успешно создан
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireResponse'
+ /questionnaires/public/{publicLink}:
+ get:
+ summary: Получить опрос для участия
+ description: Возвращает данные опроса по публичной ссылке
+ operationId: getPublicQuestionnaire
+ parameters:
+ - name: publicLink
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Успешный запрос
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ /questionnaires/admin/{adminLink}:
+ get:
+ summary: Получить опрос для редактирования и просмотра результатов
+ description: Возвращает данные опроса по административной ссылке
+ operationId: getAdminQuestionnaire
+ parameters:
+ - name: adminLink
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Успешный запрос
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ put:
+ summary: Обновить опрос
+ description: Обновляет существующий опрос
+ operationId: updateQuestionnaire
+ parameters:
+ - name: adminLink
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireUpdate'
+ responses:
+ '200':
+ description: Опрос успешно обновлен
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QuestionnaireResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ delete:
+ summary: Удалить опрос
+ description: Удаляет опрос вместе со всеми ответами
+ operationId: deleteQuestionnaire
+ parameters:
+ - name: adminLink
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Опрос успешно удален
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ /vote/{publicLink}:
+ post:
+ summary: Отправить ответы на опрос
+ description: Отправляет ответы пользователя на опрос
+ operationId: submitVote
+ parameters:
+ - name: publicLink
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/VoteRequest'
+ responses:
+ '200':
+ description: Ответы успешно отправлены
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ /results/{publicLink}:
+ get:
+ summary: Получить результаты опроса
+ description: Возвращает текущие результаты опроса
+ operationId: getResults
+ parameters:
+ - name: publicLink
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Успешный запрос
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ResultsResponse'
+ '404':
+ description: Опрос не найден
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+components:
+ schemas:
+ QuestionnaireCreate:
+ type: object
+ required:
+ - title
+ - questions
+ properties:
+ title:
+ type: string
+ description: Название опроса
+ description:
+ type: string
+ description: Описание опроса
+ questions:
+ type: array
+ description: Список вопросов
+ items:
+ $ref: '#/components/schemas/Question'
+ displayType:
+ type: string
+ description: Тип отображения опроса
+ enum: [standard, step_by_step]
+ default: standard
+ QuestionnaireUpdate:
+ type: object
+ properties:
+ title:
+ type: string
+ description: Название опроса
+ description:
+ type: string
+ description: Описание опроса
+ questions:
+ type: array
+ description: Список вопросов
+ items:
+ $ref: '#/components/schemas/Question'
+ displayType:
+ type: string
+ description: Тип отображения опроса
+ enum: [standard, step_by_step]
+ Question:
+ type: object
+ required:
+ - text
+ - type
+ properties:
+ text:
+ type: string
+ description: Текст вопроса
+ type:
+ type: string
+ description: Тип вопроса
+ enum: [single, multiple, text, scale, rating, tagcloud]
+ required:
+ type: boolean
+ description: Является ли вопрос обязательным
+ default: false
+ options:
+ type: array
+ description: Варианты ответа (для single, multiple)
+ items:
+ $ref: '#/components/schemas/Option'
+ tags:
+ type: array
+ description: Список тегов (для tagcloud)
+ items:
+ $ref: '#/components/schemas/Tag'
+ scaleMin:
+ type: integer
+ description: Минимальное значение шкалы (для scale)
+ default: 0
+ scaleMax:
+ type: integer
+ description: Максимальное значение шкалы (для scale)
+ default: 10
+ scaleMinLabel:
+ type: string
+ description: Метка для минимального значения шкалы
+ default: "Минимум"
+ scaleMaxLabel:
+ type: string
+ description: Метка для максимального значения шкалы
+ default: "Максимум"
+ Option:
+ type: object
+ required:
+ - text
+ properties:
+ text:
+ type: string
+ description: Текст варианта ответа
+ votes:
+ type: integer
+ description: Количество голосов за этот вариант
+ default: 0
+ Tag:
+ type: object
+ required:
+ - text
+ properties:
+ text:
+ type: string
+ description: Текст тега
+ count:
+ type: integer
+ description: Количество выборов данного тега
+ default: 0
+ VoteRequest:
+ type: object
+ required:
+ - answers
+ properties:
+ answers:
+ type: array
+ description: Список ответов пользователя
+ items:
+ $ref: '#/components/schemas/Answer'
+ Answer:
+ type: object
+ required:
+ - questionIndex
+ properties:
+ questionIndex:
+ type: integer
+ description: Индекс вопроса
+ optionIndices:
+ type: array
+ description: Индексы выбранных вариантов (для single, multiple)
+ items:
+ type: integer
+ textAnswer:
+ type: string
+ description: Текстовый ответ пользователя (для text)
+ scaleValue:
+ type: integer
+ description: Значение шкалы (для scale, rating)
+ tagTexts:
+ type: array
+ description: Тексты выбранных или введенных тегов (для tagcloud)
+ items:
+ type: string
+ QuestionnairesResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: Успешность запроса
+ data:
+ type: array
+ description: Список опросов
+ items:
+ $ref: '#/components/schemas/QuestionnaireInfo'
+ QuestionnaireResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: Успешность запроса
+ data:
+ $ref: '#/components/schemas/QuestionnaireData'
+ QuestionnaireInfo:
+ type: object
+ properties:
+ title:
+ type: string
+ description: Название опроса
+ description:
+ type: string
+ description: Описание опроса
+ adminLink:
+ type: string
+ description: Административная ссылка
+ publicLink:
+ type: string
+ description: Публичная ссылка
+ createdAt:
+ type: string
+ format: date-time
+ description: Дата создания опроса
+ updatedAt:
+ type: string
+ format: date-time
+ description: Дата последнего обновления опроса
+ QuestionnaireData:
+ type: object
+ properties:
+ _id:
+ type: string
+ description: Идентификатор опроса
+ title:
+ type: string
+ description: Название опроса
+ description:
+ type: string
+ description: Описание опроса
+ questions:
+ type: array
+ description: Список вопросов
+ items:
+ $ref: '#/components/schemas/QuestionData'
+ displayType:
+ type: string
+ description: Тип отображения опроса
+ enum: [standard, step_by_step]
+ adminLink:
+ type: string
+ description: Административная ссылка
+ publicLink:
+ type: string
+ description: Публичная ссылка
+ createdAt:
+ type: string
+ format: date-time
+ description: Дата создания опроса
+ updatedAt:
+ type: string
+ format: date-time
+ description: Дата последнего обновления опроса
+ QuestionData:
+ type: object
+ properties:
+ _id:
+ type: string
+ description: Идентификатор вопроса
+ text:
+ type: string
+ description: Текст вопроса
+ type:
+ type: string
+ description: Тип вопроса
+ required:
+ type: boolean
+ description: Является ли вопрос обязательным
+ options:
+ type: array
+ description: Варианты ответа (для single, multiple)
+ items:
+ $ref: '#/components/schemas/OptionData'
+ tags:
+ type: array
+ description: Список тегов (для tagcloud)
+ items:
+ $ref: '#/components/schemas/TagData'
+ scaleMin:
+ type: integer
+ description: Минимальное значение шкалы (для scale)
+ scaleMax:
+ type: integer
+ description: Максимальное значение шкалы (для scale)
+ scaleMinLabel:
+ type: string
+ description: Метка для минимального значения шкалы
+ scaleMaxLabel:
+ type: string
+ description: Метка для максимального значения шкалы
+ answers:
+ type: array
+ description: Текстовые ответы (для text)
+ items:
+ type: string
+ scaleValues:
+ type: array
+ description: Значения шкалы от пользователей (для scale, rating)
+ items:
+ type: integer
+ textAnswers:
+ type: array
+ description: Текстовые ответы (для text)
+ items:
+ type: string
+ responses:
+ type: array
+ description: Значения шкалы от пользователей (для scale, rating)
+ items:
+ type: integer
+ OptionData:
+ type: object
+ properties:
+ _id:
+ type: string
+ description: Идентификатор варианта ответа
+ text:
+ type: string
+ description: Текст варианта ответа
+ votes:
+ type: integer
+ description: Количество голосов за этот вариант
+ count:
+ type: integer
+ description: Альтернативное поле для количества голосов
+ TagData:
+ type: object
+ properties:
+ _id:
+ type: string
+ description: Идентификатор тега
+ text:
+ type: string
+ description: Текст тега
+ count:
+ type: integer
+ description: Количество выборов данного тега
+ ResultsResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: Успешность запроса
+ data:
+ $ref: '#/components/schemas/ResultsData'
+ ResultsData:
+ type: object
+ properties:
+ questions:
+ type: array
+ description: Список вопросов с результатами
+ items:
+ $ref: '#/components/schemas/QuestionResults'
+ QuestionResults:
+ type: object
+ properties:
+ text:
+ type: string
+ description: Текст вопроса
+ type:
+ type: string
+ description: Тип вопроса
+ options:
+ type: array
+ description: Варианты ответа с количеством голосов (для single, multiple)
+ items:
+ type: object
+ properties:
+ text:
+ type: string
+ description: Текст варианта ответа
+ count:
+ type: integer
+ description: Количество голосов
+ tags:
+ type: array
+ description: Список тегов с количеством выборов (для tagcloud)
+ items:
+ type: object
+ properties:
+ text:
+ type: string
+ description: Текст тега
+ count:
+ type: integer
+ description: Количество выборов
+ scaleValues:
+ type: array
+ description: Значения шкалы от пользователей (для scale, rating)
+ items:
+ type: integer
+ scaleAverage:
+ type: number
+ description: Среднее значение шкалы (для scale, rating)
+ answers:
+ type: array
+ description: Текстовые ответы (для text)
+ items:
+ type: string
+ responses:
+ type: array
+ description: Значения шкалы от пользователей (для scale, rating)
+ items:
+ type: integer
+ SuccessResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: Успешность запроса
+ example: true
+ message:
+ type: string
+ description: Сообщение об успешном выполнении
+ ErrorResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: Успешность запроса
+ example: false
+ error:
+ type: string
+ description: Сообщение об ошибке
\ No newline at end of file
diff --git a/server/routers/questioneer/public/admin.html b/server/routers/questioneer/public/admin.html
new file mode 100644
index 0000000..8905b49
--- /dev/null
+++ b/server/routers/questioneer/public/admin.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+ Управление опросом
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Управление опросом
+
+
Загрузка опроса...
+
+
+
+
+
+
+
Ссылка для голосования:
+
+
+
+
+
+
+
+
+
Административная ссылка:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/routers/questioneer/public/create.html b/server/routers/questioneer/public/create.html
new file mode 100644
index 0000000..41e49d2
--- /dev/null
+++ b/server/routers/questioneer/public/create.html
@@ -0,0 +1,187 @@
+
+
+
+
+
+ Создать опрос
+
+
+
+
+
+
+
+
+
+
+
+
+
Создание нового опроса
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Варианты ответа
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/routers/questioneer/public/edit.html b/server/routers/questioneer/public/edit.html
new file mode 100644
index 0000000..02c9485
--- /dev/null
+++ b/server/routers/questioneer/public/edit.html
@@ -0,0 +1,204 @@
+
+
+
+
+
+ Редактирование опроса
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Редактирование опроса
+
+
Загрузка опроса...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Варианты ответа
+
+
+
+
+
+
Настройки шкалы
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/routers/questioneer/public/index.html b/server/routers/questioneer/public/index.html
new file mode 100644
index 0000000..db72bec
--- /dev/null
+++ b/server/routers/questioneer/public/index.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+ Анонимные опросы
+
+
+
+
+
+
+
+
+
+
+
+
+
Сервис анонимных опросов
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/routers/questioneer/public/poll.html b/server/routers/questioneer/public/poll.html
new file mode 100644
index 0000000..dc2f0ee
--- /dev/null
+++ b/server/routers/questioneer/public/poll.html
@@ -0,0 +1,97 @@
+
+
+
+
+
+ Участие в опросе
+
+
+
+
+
+
+
+
+
+
+
+
+
Загрузка опроса...
+
+
+
+
+
+
+
+
Спасибо за участие!
+
Ваши ответы были успешно отправлены.
+
+
+
Текущие результаты:
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/routers/questioneer/public/static/css/style.css b/server/routers/questioneer/public/static/css/style.css
new file mode 100644
index 0000000..c943b91
--- /dev/null
+++ b/server/routers/questioneer/public/static/css/style.css
@@ -0,0 +1,2181 @@
+/* Темный стиль */
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ margin: 0;
+ padding: 0;
+ color: #e0e0e0;
+ background-color: #1e1e1e;
+ line-height: 1.6;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ line-height: 1.2;
+}
+
+h1 {
+ font-size: 2.5rem;
+ margin-bottom: 1.5rem;
+ color: #61dafb;
+}
+
+h2 {
+ font-size: 2rem;
+ color: #64b5f6;
+}
+
+h3 {
+ font-size: 1.5rem;
+ color: #81c784;
+}
+
+p {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+/* Кнопки */
+.btn {
+ display: inline-block;
+ font-weight: 400;
+ text-align: center;
+ vertical-align: middle;
+ user-select: none;
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ cursor: pointer;
+ background-color: #2196f3;
+ color: white;
+ border: 1px solid transparent;
+ transition: all 0.15s ease-in-out;
+}
+
+.btn:hover {
+ background-color: #1976d2;
+}
+
+.btn-primary {
+ background-color: #1976d2;
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #1565c0;
+}
+
+.btn-secondary {
+ background-color: #757575;
+}
+
+.btn-secondary:hover {
+ background-color: #616161;
+}
+
+.btn-danger {
+ background-color: #f44336;
+}
+
+.btn-danger:hover {
+ background-color: #d32f2f;
+}
+
+.btn-small {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+}
+
+.btn-icon {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 1rem;
+ padding: 0;
+ margin: 0 5px;
+ color: #e0e0e0;
+}
+
+/* Формы */
+.form-container {
+ background-color: #2d2d2d;
+ padding: 20px;
+ border-radius: 5px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ margin-bottom: 20px;
+}
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: 500;
+ color: #bbdefb;
+}
+
+input[type="text"],
+input[type="email"],
+input[type="password"],
+input[type="number"],
+textarea,
+select {
+ display: block;
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: #e0e0e0;
+ background-color: #424242;
+ background-clip: padding-box;
+ border: 1px solid #616161;
+ border-radius: 0.25rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ box-sizing: border-box;
+}
+
+input:focus,
+textarea:focus,
+select:focus {
+ color: #e0e0e0;
+ background-color: #424242;
+ border-color: #64b5f6;
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
+}
+
+.form-actions {
+ margin-top: 20px;
+ display: flex;
+ justify-content: space-between;
+}
+
+/* Список опросов */
+.questionnaires-list {
+ margin-top: 30px;
+}
+
+.questionnaire-item {
+ background-color: #2d2d2d;
+ padding: 20px;
+ border-radius: 5px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ margin-bottom: 20px;
+}
+
+.questionnaire-links {
+ display: flex;
+ gap: 10px;
+ margin-top: 15px;
+}
+
+/* Вопросы */
+.question-item {
+ background-color: #2d2d2d;
+ padding: 20px;
+ border-radius: 5px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ margin-bottom: 20px;
+}
+
+.question-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.options-container {
+ margin-top: 10px;
+}
+
+.options-list {
+ margin-bottom: 10px;
+}
+
+.option-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+/* Стили для варианта ответа */
+.option-item input[type="text"] {
+ flex: 1;
+ margin-right: 10px;
+}
+
+.radio-option label,
+.checkbox-option label {
+ margin-left: 10px;
+ cursor: pointer;
+}
+
+/* Звездный рейтинг */
+.rating-container {
+ display: flex;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.rating-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.rating-item label {
+ cursor: pointer;
+ padding: 8px 12px;
+ background-color: #424242;
+ border-radius: 4px;
+ transition: all 0.2s;
+}
+
+.rating-item input {
+ display: none;
+}
+
+.rating-item input:checked + label {
+ background-color: #2196f3;
+ color: white;
+}
+
+/* Облако тегов */
+.tag-cloud-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.tag-item {
+ padding: 8px 15px;
+ background-color: #424242;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.tag-item:hover {
+ background-color: #616161;
+}
+
+.tag-item.selected {
+ background-color: #2196f3;
+ color: white;
+}
+
+/* Результаты голосования */
+.results-visualization {
+ margin-top: 15px;
+}
+
+.result-bar-container {
+ margin-bottom: 15px;
+}
+
+.result-label {
+ margin-bottom: 5px;
+ font-weight: 500;
+ color: #bbdefb;
+}
+
+.result-bar {
+ height: 20px;
+ background-color: #424242;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.result-bar-fill {
+ height: 100%;
+ background-color: #2196f3;
+ border-radius: 4px;
+ transition: width 0.5s;
+}
+
+.result-percent {
+ margin-top: 5px;
+ font-size: 0.875rem;
+ color: #9e9e9e;
+}
+
+.results-tag-cloud {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-top: 15px;
+ justify-content: center;
+}
+
+.result-tag {
+ padding: 5px 10px;
+ background-color: #2196f3;
+ color: white;
+ border-radius: 20px;
+ display: inline-block;
+}
+
+/* Обязательные поля */
+.required-mark {
+ color: #f44336;
+}
+
+/* Состояния загрузки и ошибки */
+#loading {
+ text-align: center;
+ padding: 20px;
+ font-size: 1.2rem;
+ color: #9e9e9e;
+}
+
+.error {
+ color: #f44336;
+ padding: 10px;
+ background-color: rgba(244, 67, 54, 0.2);
+ border-radius: 4px;
+}
+
+/* Ссылки */
+.link-group {
+ margin-bottom: 15px;
+}
+
+.link-input-group {
+ display: flex;
+ gap: 10px;
+}
+
+.link-input-group input {
+ flex: 1;
+}
+
+/* Текстовая область для ввода ответа */
+.textarea-container {
+ width: 100%;
+ margin-top: 10px;
+}
+
+.text-answer {
+ width: 100%;
+ min-height: 100px;
+ resize: vertical;
+}
+
+/* Таблица статистики */
+.stats-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 10px;
+ color: #e0e0e0;
+}
+
+.stats-table th,
+.stats-table td {
+ padding: 8px;
+ text-align: left;
+ border-bottom: 1px solid #424242;
+}
+
+.stats-table th {
+ background-color: #383838;
+ font-weight: 500;
+ color: #bbdefb;
+}
+
+.stats-table .total-row {
+ font-weight: bold;
+ background-color: #424242;
+}
+
+/* Стили для question-stats */
+.question-stats {
+ background-color: #2d2d2d;
+ padding: 15px;
+ border-radius: 5px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+}
+
+/* Модальные окна */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.3s, visibility 0.3s;
+}
+
+.modal-overlay.active {
+ opacity: 1;
+ visibility: visible;
+}
+
+.modal {
+ background-color: #2d2d2d;
+ border-radius: 5px;
+ box-shadow: 0 3px 15px rgba(0, 0, 0, 0.3);
+ width: 90%;
+ max-width: 500px;
+ padding: 20px;
+ position: relative;
+ transform: translateY(-20px);
+ transition: transform 0.3s;
+ max-height: 90vh;
+ overflow-y: auto;
+}
+
+.modal-overlay.active .modal {
+ transform: translateY(0);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #444;
+}
+
+.modal-header h3 {
+ margin: 0;
+ color: #64b5f6;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ color: #e0e0e0;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+}
+
+.modal-body {
+ margin-bottom: 20px;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #444;
+}
+
+/* QR код */
+.qr-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 15px;
+}
+
+.qr-code {
+ margin-bottom: 15px;
+ background-color: #fff;
+ padding: 15px;
+ border-radius: 8px;
+}
+
+.qr-link-container {
+ display: flex;
+ width: 100%;
+ max-width: 500px;
+ margin-top: 10px;
+}
+
+.qr-link-input {
+ flex-grow: 1;
+ padding: 10px;
+ border: 1px solid #444;
+ border-radius: 4px 0 0 4px;
+ background-color: #333;
+ color: #fff;
+}
+
+.btn-copy-link {
+ padding: 10px 15px;
+ background-color: #64b5f6;
+ color: #fff;
+ border: none;
+ border-radius: 0 4px 4px 0;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+.btn-copy-link:hover {
+ background-color: #90caf9;
+}
+
+.btn-copy-link.copied {
+ background-color: #4caf50;
+}
+
+/* Шкала оценки */
+.scale-container {
+ margin-top: 20px;
+ width: 100%;
+}
+
+.scale-labels {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.scale-label-min,
+.scale-label-max {
+ font-weight: 500;
+ color: #bbdefb;
+}
+
+.scale-values {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.scale-item {
+ text-align: center;
+}
+
+.scale-item input {
+ display: none;
+}
+
+.scale-item label {
+ display: block;
+ width: 40px;
+ height: 40px;
+ line-height: 40px;
+ text-align: center;
+ background-color: #424242;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.scale-item input:checked + label {
+ background-color: #2196f3;
+ color: white;
+}
+
+.scale-item label:hover {
+ background-color: #616161;
+}
+
+/* Пошаговый опрос */
+.step-navigation {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+.question-counter {
+ text-align: center;
+ font-size: 1rem;
+ color: #64b5f6;
+ flex: 1;
+}
+
+/* Улучшения для облака тегов */
+.tag-input-container {
+ width: 100%;
+}
+
+.tag-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ background-color: #424242;
+ border: 1px solid #616161;
+ border-radius: 4px;
+ color: #e0e0e0;
+}
+
+.tag-items {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.tag-item {
+ position: relative;
+ padding: 8px 15px;
+ background-color: #424242;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.tag-item.selected {
+ background-color: #2196f3;
+ color: white;
+}
+
+.tag-remove {
+ margin-left: 5px;
+ font-size: 1.2rem;
+ cursor: pointer;
+}
+
+/* Стили для результатов опроса */
+.question-result {
+ background-color: #2d2d2d;
+ padding: 15px;
+ border-radius: 5px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+}
+
+.text-answers {
+ margin-top: 10px;
+}
+
+.text-answer {
+ background-color: #424242;
+ padding: 10px;
+ border-radius: 4px;
+ margin-bottom: 10px;
+}
+
+/* Улучшения для радио и чекбоксов */
+.radio-options-container,
+.checkbox-options-container {
+ margin-top: 10px;
+}
+
+.radio-option,
+.checkbox-option {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.radio-option input,
+.checkbox-option input {
+ margin-right: 10px;
+}
+
+.radio-option label,
+.checkbox-option label {
+ cursor: pointer;
+}
+
+/* Стили для вопросов с ошибками */
+.question-item.error {
+ border-left: 4px solid var(--color-error);
+ box-shadow: 0 0 10px rgba(220, 53, 69, 0.3);
+ padding-left: 16px;
+ transition: all 0.3s ease;
+}
+
+.question-item.error .question-title {
+ color: var(--color-error);
+}
+
+/* Анимация для ошибок */
+.shake {
+ animation: shake 0.6s cubic-bezier(.36,.07,.19,.97) both;
+}
+
+@keyframes shake {
+ 10%, 90% {
+ transform: translate3d(-1px, 0, 0);
+ }
+
+ 20%, 80% {
+ transform: translate3d(2px, 0, 0);
+ }
+
+ 30%, 50%, 70% {
+ transform: translate3d(-4px, 0, 0);
+ }
+
+ 40%, 60% {
+ transform: translate3d(4px, 0, 0);
+ }
+}
+
+/* Стили для выхода вопроса */
+.question-item.exit {
+ opacity: 0;
+ transform: translateX(-30px);
+ transition: opacity 0.3s ease, transform 0.3s ease;
+ pointer-events: none;
+}
+
+/* Стили для плавного входа вопроса */
+.question-item.enter {
+ opacity: 0;
+ transform: translateX(30px);
+}
+
+.question-item.active {
+ opacity: 1;
+ transform: translateX(0);
+ transition: opacity 0.5s ease, transform 0.5s ease;
+}
+
+/* Анимации для элементов результатов */
+.results-title {
+ animation: slideDown 0.8s ease forwards;
+}
+
+.scale-average {
+ margin-bottom: 15px;
+ font-size: 1.2em;
+}
+
+.scale-average .highlight {
+ color: var(--color-primary);
+ font-size: 1.5em;
+ font-weight: bold;
+}
+
+.result-bar-container {
+ margin-bottom: 10px;
+}
+
+.result-label {
+ margin-bottom: 5px;
+ font-weight: 500;
+}
+
+.result-bar {
+ height: 20px;
+ background-color: rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+ overflow: hidden;
+ position: relative;
+}
+
+.result-bar-fill {
+ height: 100%;
+ background-color: var(--color-primary);
+ transition: width 1s ease-out;
+ border-radius: 4px;
+}
+
+.result-percent {
+ margin-top: 3px;
+ text-align: right;
+ font-size: 0.9em;
+ color: var(--color-muted);
+}
+
+.text-answer {
+ background-color: rgba(0, 0, 0, 0.05);
+ padding: 10px 15px;
+ border-radius: 4px;
+ margin-bottom: 10px;
+ transition: opacity 0.5s ease, transform 0.5s ease;
+}
+
+.results-tag-cloud {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 20px 0;
+}
+
+.result-tag {
+ background-color: rgba(var(--primary-rgb), 0.1);
+ color: var(--color-primary);
+ padding: 8px 15px;
+ border-radius: 20px;
+ margin: 5px;
+ display: inline-block;
+ transition: all 0.5s ease;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateX(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes pulse {
+ 0% { opacity: 0.8; }
+ 50% { opacity: 1; }
+ 100% { opacity: 0.8; }
+}
+
+.question-result {
+ transition: all 0.5s ease;
+ padding: 15px;
+ margin-bottom: 20px;
+ border-radius: 8px;
+ background-color: rgba(0, 0, 0, 0.03);
+}
+
+/* Улучшенная анимация для счетчика вопросов */
+.question-counter {
+ transition: all 0.3s ease;
+ animation: fadeIn 0.5s;
+}
+
+.question-counter.update {
+ animation: fadeIn 0.5s;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* Анимации для кнопок навигации */
+.nav-btn:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+}
+
+.nav-btn:active {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+}
+
+/* Улучшенные стили для загрузки */
+#loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: 50px 0;
+}
+
+.loading-spinner {
+ width: 50px;
+ height: 50px;
+ border: 5px solid rgba(0, 0, 0, 0.1);
+ border-radius: 50%;
+ border-top: 5px solid var(--color-primary);
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.loading-text {
+ margin-top: 15px;
+ color: #64b5f6;
+}
+
+/* Анимации */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes slideInUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+@keyframes slideInRight {
+ from {
+ transform: translateX(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+@keyframes pulse {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.05); }
+ 100% { transform: scale(1); }
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
+ 20%, 40%, 60%, 80% { transform: translateX(5px); }
+}
+
+@keyframes rotate {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+/* Применение анимаций */
+#questionnaire-container {
+ animation: fadeIn 0.6s ease-out;
+}
+
+.question-item {
+ animation: slideInUp 0.5s ease-out;
+ transition: all 0.3s ease;
+}
+
+.btn {
+ transition: all 0.2s ease-in-out;
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.btn:active {
+ transform: translateY(1px);
+}
+
+.btn-primary {
+ background-color: #1976d2;
+ color: white;
+}
+
+.question-item.error {
+ animation: shake 0.5s ease-in-out;
+}
+
+.radio-option label:hover,
+.checkbox-option label:hover {
+ transform: translateX(3px);
+ transition: transform 0.2s ease;
+}
+
+.tag-item {
+ transition: all 0.2s ease;
+}
+
+.tag-item:hover {
+ transform: scale(1.05);
+}
+
+.tag-item.selected {
+ background-color: var(--color-primary);
+ color: white;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.scale-item label:hover {
+ transform: scale(1.1);
+ transition: transform 0.2s ease;
+}
+
+.scale-item input:checked + label {
+ background-color: var(--color-primary);
+ color: white;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+/* Анимированный лоадер */
+#loading {
+ position: relative;
+ padding-left: 30px;
+}
+
+#loading:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ margin-top: -10px;
+ width: 20px;
+ height: 20px;
+ border: 3px solid #2196f3;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: rotate 1s linear infinite;
+}
+
+/* Анимированные переходы между вопросами */
+.step-navigation button {
+ transition: all 0.3s ease;
+}
+
+.question-item.active {
+ animation: slideInRight 0.4s ease-out;
+}
+
+.question-item.exit {
+ animation: fadeIn 0.4s ease-out reverse;
+}
+
+/* Анимация для результатов */
+.result-bar-fill {
+ transition: width 1.5s ease-out;
+ animation: slideInRight 1.5s ease-out;
+}
+
+.results-container {
+ animation: fadeIn 1s ease-out;
+}
+
+.question-result {
+ animation: slideInUp 0.5s ease-out;
+ animation-fill-mode: both;
+}
+
+.question-result:nth-child(1) { animation-delay: 0.1s; }
+.question-result:nth-child(2) { animation-delay: 0.2s; }
+.question-result:nth-child(3) { animation-delay: 0.3s; }
+.question-result:nth-child(4) { animation-delay: 0.4s; }
+.question-result:nth-child(5) { animation-delay: 0.5s; }
+.question-result:nth-child(6) { animation-delay: 0.6s; }
+.question-result:nth-child(7) { animation-delay: 0.7s; }
+.question-result:nth-child(8) { animation-delay: 0.8s; }
+.question-result:nth-child(9) { animation-delay: 0.9s; }
+.question-result:nth-child(10) { animation-delay: 1s; }
+
+/* Другие улучшения стилей */
+.textarea-container textarea {
+ transition: height 0.3s ease;
+}
+
+.textarea-container textarea:focus {
+ height: 120px;
+}
+
+/* Анимация для модальных окон */
+.modal-overlay.active .modal {
+ animation: slideInUp 0.3s ease-out;
+}
+
+/* Анимация для кнопки добавления вопроса */
+#add-question {
+ transition: background-color 0.3s ease, transform 0.2s ease;
+}
+
+#add-question:hover {
+ transform: translateY(-2px);
+}
+
+#add-question:active {
+ transform: translateY(1px);
+}
+
+/* Анимация иконок */
+.btn-icon svg {
+ transition: transform 0.3s ease;
+}
+
+.btn-icon:hover svg {
+ transform: rotate(90deg);
+}
+
+/* Анимация для обратной связи */
+@keyframes success-animation {
+ 0% { background-color: transparent; }
+ 30% { background-color: rgba(76, 175, 80, 0.2); }
+ 100% { background-color: transparent; }
+}
+
+.success-feedback {
+ animation: success-animation 1.5s ease;
+}
+
+/* Анимированные переключатели */
+input[type="checkbox"], input[type="radio"] {
+ transition: all 0.2s ease;
+}
+
+/* Дополнительные плавные переходы для всех элементов */
+* {
+ transition-property: background-color, border-color, color, box-shadow;
+ transition-duration: 0.2s;
+ transition-timing-function: ease;
+}
+
+/* Стили для приветствия и благодарности */
+.welcome-animation,
+.thank-you-animation,
+.already-completed {
+ max-width: 800px;
+ margin: 50px auto;
+ padding: 30px;
+ text-align: center;
+ background-color: #2d2d2d;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ transition: all 0.5s ease;
+}
+
+.welcome-icon,
+.thank-you-icon,
+.completed-icon {
+ margin-bottom: 20px;
+}
+
+.welcome-icon svg,
+.thank-you-icon svg,
+.completed-icon svg {
+ width: 100px;
+ height: 100px;
+ color: var(--color-primary);
+}
+
+.welcome-icon svg:hover,
+.thank-you-icon svg:hover,
+.completed-icon svg:hover {
+ transform: scale(1.05);
+}
+
+.welcome-title,
+.thank-you-title,
+.completed-title {
+ font-size: 2rem;
+ margin-bottom: 15px;
+ color: var(--color-primary, #2196f3);
+}
+
+.welcome-description,
+.thank-you-description,
+.completed-description {
+ font-size: 1.1rem;
+ line-height: 1.6;
+ margin-bottom: 30px;
+ color: #e0e0e0;
+}
+
+.welcome-start-btn,
+.view-results-btn {
+ font-size: 1.2rem;
+ padding: 12px 24px;
+ border-radius: 30px;
+ background-color: var(--color-primary);
+ color: white;
+ border: none;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+.welcome-start-btn:hover,
+.view-results-btn:hover {
+ background-color: #0d47a1;
+}
+
+.start-again-btn {
+ margin-top: 20px;
+ margin-left: 10px;
+ font-size: 1.1rem;
+ padding: 12px 30px;
+ border-radius: 30px;
+}
+
+/* Анимации для приветствия и благодарности */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* CSS переменные для цветов */
+:root {
+ --color-primary: #2196f3;
+ --color-primary-dark: #1976d2;
+ --color-primary-light: #64b5f6;
+ --color-secondary: #757575;
+ --color-secondary-dark: #616161;
+ --color-success: #4caf50;
+ --color-success-dark: #388e3c;
+ --color-error: #f44336;
+ --color-error-dark: #d32f2f;
+ --color-muted: #9e9e9e;
+ --primary-rgb: 33, 150, 243;
+ --color-bg-dark: #1e1e1e;
+ --color-bg-card: #2d2d2d;
+ --color-bg-input: #424242;
+}
+
+/* Стили для навигационных кнопок */
+.nav-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 110px;
+ background-color: #1976d2;
+ color: white;
+}
+
+.nav-btn svg {
+ margin: 0 5px;
+}
+
+.nav-btn:hover {
+ background-color: #0d47a1;
+}
+
+.nav-btn:disabled {
+ background-color: #455a64;
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+/* Стили для статистики в админке */
+.question-stats {
+ margin-bottom: 30px;
+ padding: 20px;
+ background-color: #2d2d2d;
+ border-radius: 8px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.question-stats h3 {
+ margin-top: 0;
+ margin-bottom: 15px;
+ color: #90caf9;
+ border-bottom: 1px solid #444;
+ padding-bottom: 10px;
+}
+
+.no-stats, .no-votes {
+ padding: 15px;
+ background-color: #383838;
+ border-radius: 5px;
+ text-align: center;
+ color: #aaa;
+}
+
+.stats-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 15px;
+}
+
+.stats-table th, .stats-table td {
+ padding: 10px;
+ text-align: left;
+ border-bottom: 1px solid #444;
+}
+
+.stats-table th {
+ background-color: #383838;
+ color: #90caf9;
+}
+
+.bar-container {
+ width: 100%;
+ height: 20px;
+ background-color: #383838;
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.bar {
+ height: 100%;
+ background-color: #64b5f6;
+ border-radius: 3px;
+ transition: width 0.5s ease-in-out;
+}
+
+.total-votes {
+ text-align: right;
+ font-style: italic;
+ color: #aaa;
+ margin-top: 10px;
+}
+
+/* Стили для облака тегов в статистике */
+.tag-cloud-stats {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ padding: 15px;
+ background-color: #383838;
+ border-radius: 5px;
+}
+
+.tag-item {
+ display: inline-block;
+ padding: 5px 10px;
+ background-color: #444;
+ border-radius: 15px;
+ margin-right: 8px;
+ margin-bottom: 8px;
+ color: #90caf9;
+ transition: transform 0.2s ease;
+}
+
+.tag-item:hover {
+ transform: scale(1.05);
+}
+
+/* Стили для шкалы и рейтинга */
+.scale-stats {
+ padding: 15px;
+ background-color: #383838;
+ border-radius: 5px;
+}
+
+.stat-item {
+ margin-bottom: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.stat-label {
+ font-weight: bold;
+ color: #aaa;
+}
+
+.stat-value {
+ font-size: 1.1em;
+ color: #90caf9;
+}
+
+/* Навигация */
+.nav-header {
+ background-color: #212121;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ padding: 15px 0;
+ margin-bottom: 30px;
+}
+
+.nav-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.nav-logo {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: #61dafb;
+ text-decoration: none;
+}
+
+.nav-menu {
+ display: flex;
+ gap: 20px;
+}
+
+.nav-link {
+ color: #e0e0e0;
+ text-decoration: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+}
+
+.nav-link:hover {
+ background-color: #424242;
+ color: #61dafb;
+}
+
+.nav-link.active {
+ background-color: #424242;
+ color: #61dafb;
+}
+
+/* Формы - улучшенные чекбоксы и радиокнопки */
+.form-container {
+ background-color: #2d2d2d;
+ padding: 20px;
+ border-radius: 5px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ margin-bottom: 20px;
+}
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: 500;
+ color: #bbdefb;
+}
+
+/* Улучшенные стили для чекбоксов и радиокнопок */
+.radio-option,
+.checkbox-option {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ position: relative;
+}
+
+.radio-option input[type="radio"],
+.checkbox-option input[type="checkbox"] {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ height: 0;
+ width: 0;
+}
+
+.radio-option label,
+.checkbox-option label {
+ position: relative;
+ padding-left: 40px;
+ cursor: pointer;
+ display: block;
+ font-size: 1rem;
+ user-select: none;
+}
+
+.radio-option label:before,
+.checkbox-option label:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 24px;
+ height: 24px;
+ border: 2px solid #616161;
+ background-color: #424242;
+ transition: all 0.3s;
+}
+
+.radio-option label:before {
+ border-radius: 50%;
+}
+
+.checkbox-option label:before {
+ border-radius: 4px;
+}
+
+.radio-option input[type="radio"]:checked ~ label:before {
+ background-color: #2196f3;
+ border-color: #2196f3;
+}
+
+.checkbox-option input[type="checkbox"]:checked ~ label:before {
+ background-color: #4caf50;
+ border-color: #4caf50;
+}
+
+.radio-option label:after {
+ content: '';
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background: white;
+ border-radius: 50%;
+ top: 7px;
+ left: 7px;
+ transition: all 0.2s;
+ opacity: 0;
+ transform: scale(0);
+}
+
+.checkbox-option label:after {
+ content: '';
+ position: absolute;
+ left: 9px;
+ top: 5px;
+ width: 6px;
+ height: 12px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg) scale(0);
+ opacity: 0;
+ transition: all 0.2s;
+}
+
+.radio-option input[type="radio"]:checked ~ label:after {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.checkbox-option input[type="checkbox"]:checked ~ label:after {
+ opacity: 1;
+ transform: rotate(45deg) scale(1);
+}
+
+.radio-option:hover label:before,
+.checkbox-option:hover label:before {
+ border-color: #90caf9;
+}
+
+/* Модальные окна с прогресс-баром */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.3s, visibility 0.3s;
+}
+
+.modal-overlay.active {
+ opacity: 1;
+ visibility: visible;
+}
+
+.modal {
+ background-color: #2d2d2d;
+ border-radius: 5px;
+ box-shadow: 0 3px 15px rgba(0, 0, 0, 0.3);
+ width: 90%;
+ max-width: 500px;
+ padding: 20px;
+ position: relative;
+ transform: translateY(-20px);
+ transition: transform 0.3s;
+ max-height: 90vh;
+ overflow-y: auto;
+}
+
+.modal-overlay.active .modal {
+ transform: translateY(0);
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #444;
+}
+
+.modal-header h3 {
+ margin: 0;
+ color: #64b5f6;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ color: #e0e0e0;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+}
+
+.modal-body {
+ margin-bottom: 20px;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #444;
+}
+
+/* Прогресс-бар для модальных окон */
+.modal-progress {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ height: 5px;
+ background-color: #2196f3;
+ width: 0;
+ transition: width 2s linear;
+}
+
+.modal-progress.active {
+ width: 100%;
+}
+
+/* Анимации для опроса */
+.question-item {
+ opacity: 1;
+ transform: translateY(0);
+ transition: opacity 0.5s, transform 0.5s;
+}
+
+.question-item.enter {
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.question-item.active {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+#question-counter {
+ transition: opacity 0.3s, transform 0.3s;
+}
+
+#question-counter.update {
+ opacity: 0;
+ transform: translateY(-10px);
+}
+
+/* Анимации для благодарности и приветствия */
+.welcome-animation,
+.thank-you-animation,
+.already-completed {
+ background-color: #2d2d2d;
+ padding: 30px;
+ border-radius: 8px;
+ text-align: center;
+ margin: 50px auto;
+ max-width: 600px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ transition: opacity 0.5s, transform 0.5s;
+}
+
+.welcome-icon,
+.thank-you-icon,
+.completed-icon {
+ margin-bottom: 20px;
+ color: #4caf50;
+}
+
+.welcome-title,
+.thank-you-title,
+.completed-title {
+ color: #64b5f6;
+ margin-bottom: 15px;
+}
+
+.welcome-description,
+.thank-you-description,
+.completed-description {
+ color: #e0e0e0;
+ margin-bottom: 25px;
+}
+
+.welcome-start-btn,
+.view-results-btn {
+ margin: 10px;
+}
+
+/* Анимации для ошибок */
+.shake-animation {
+ animation: shake 0.5s;
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
+ 20%, 40%, 60%, 80% { transform: translateX(5px); }
+}
+
+/* Улучшенные стили для чекбоксов и радиокнопок */
+.radio-option,
+.checkbox-option {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ position: relative;
+}
+
+.radio-option input[type="radio"],
+.checkbox-option input[type="checkbox"] {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ height: 0;
+ width: 0;
+}
+
+.radio-option label,
+.checkbox-option label {
+ position: relative;
+ padding-left: 40px;
+ cursor: pointer;
+ display: block;
+ font-size: 1rem;
+ user-select: none;
+}
+
+.radio-option label:before,
+.checkbox-option label:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 24px;
+ height: 24px;
+ border: 2px solid #616161;
+ background-color: #424242;
+ transition: all 0.3s;
+}
+
+.radio-option label:before {
+ border-radius: 50%;
+}
+
+.checkbox-option label:before {
+ border-radius: 4px;
+}
+
+.radio-option input[type="radio"]:checked ~ label:before {
+ background-color: #2196f3;
+ border-color: #2196f3;
+}
+
+.checkbox-option input[type="checkbox"]:checked ~ label:before {
+ background-color: #4caf50;
+ border-color: #4caf50;
+}
+
+.radio-option label:after {
+ content: '';
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background: white;
+ border-radius: 50%;
+ top: 7px;
+ left: 7px;
+ transition: all 0.2s;
+ opacity: 0;
+ transform: scale(0);
+}
+
+.checkbox-option label:after {
+ content: '';
+ position: absolute;
+ left: 9px;
+ top: 5px;
+ width: 6px;
+ height: 12px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg) scale(0);
+ opacity: 0;
+ transition: all 0.2s;
+}
+
+.radio-option input[type="radio"]:checked ~ label:after {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.checkbox-option input[type="checkbox"]:checked ~ label:after {
+ opacity: 1;
+ transform: rotate(45deg) scale(1);
+}
+
+.radio-option:hover label:before,
+.checkbox-option:hover label:before {
+ border-color: #90caf9;
+}
+
+/* Стили для текстовых ответов в админке */
+.text-answers-list {
+ margin-top: 15px;
+}
+
+.text-answer-item {
+ background-color: #383838;
+ padding: 12px 15px;
+ border-radius: 5px;
+ margin-bottom: 10px;
+ display: flex;
+ align-items: flex-start;
+}
+
+.answer-number {
+ font-weight: bold;
+ color: #64b5f6;
+ margin-right: 10px;
+ flex-shrink: 0;
+}
+
+.answer-text {
+ flex-grow: 1;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+/* Добавляем стили для анимации конфети */
+.confetti-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 9999;
+ overflow: hidden;
+}
+
+.confetti {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background-color: #90caf9;
+ border-radius: 3px;
+ will-change: transform;
+}
+
+.confetti.square {
+ border-radius: 0;
+}
+
+.confetti.triangle {
+ width: 0;
+ height: 0;
+ background-color: transparent;
+ border-style: solid;
+ border-width: 0 5px 8.7px 5px;
+ border-color: transparent transparent #90caf9 transparent;
+}
+
+.confetti.circle {
+ border-radius: 50%;
+}
+
+.confetti.red {
+ background-color: #f44336;
+ border-color: transparent transparent #f44336 transparent;
+}
+
+.confetti.blue {
+ background-color: #2196f3;
+ border-color: transparent transparent #2196f3 transparent;
+}
+
+.confetti.green {
+ background-color: #4caf50;
+ border-color: transparent transparent #4caf50 transparent;
+}
+
+.confetti.yellow {
+ background-color: #ffeb3b;
+ border-color: transparent transparent #ffeb3b transparent;
+}
+
+.confetti.purple {
+ background-color: #9c27b0;
+ border-color: transparent transparent #9c27b0 transparent;
+}
+
+@keyframes confetti-fall {
+ 0% {
+ transform: translateY(-100vh) rotate(0deg);
+ }
+ 100% {
+ transform: translateY(100vh) rotate(360deg);
+ }
+}
+
+@keyframes confetti-sway {
+ 0% {
+ transform: translateX(0);
+ }
+ 25% {
+ transform: translateX(10px);
+ }
+ 50% {
+ transform: translateX(-10px);
+ }
+ 75% {
+ transform: translateX(5px);
+ }
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+/* Медиа-запросы для адаптивности */
+@media (max-width: 768px) {
+ /* Основные стили */
+ .container {
+ padding: 15px;
+ }
+
+ h1 {
+ font-size: 2rem;
+ }
+
+ h2 {
+ font-size: 1.75rem;
+ }
+
+ h3 {
+ font-size: 1.25rem;
+ }
+
+ /* Навигация */
+ .nav-container {
+ flex-direction: column;
+ padding: 10px;
+ }
+
+ .nav-menu {
+ margin-top: 10px;
+ width: 100%;
+ justify-content: center;
+ }
+
+ .nav-link {
+ margin: 0 10px;
+ }
+
+ /* Формы */
+ .form-group {
+ margin-bottom: 15px;
+ }
+
+ input[type="text"],
+ input[type="email"],
+ input[type="number"],
+ textarea,
+ select {
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ /* Кнопки */
+ .btn {
+ padding: 8px 16px;
+ font-size: 14px;
+ }
+
+ .questionnaire-actions {
+ flex-direction: column;
+ }
+
+ .questionnaire-actions .btn {
+ width: 100%;
+ margin-bottom: 10px;
+ }
+
+ /* Опросы */
+ .question-item {
+ padding: 15px;
+ margin-bottom: 15px;
+ }
+
+ /* Навигация по опросу */
+ .step-navigation {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .step-navigation .btn {
+ width: 100%;
+ margin: 5px 0;
+ }
+
+ .question-counter {
+ margin-bottom: 10px;
+ width: 100%;
+ text-align: center;
+ }
+
+ /* Шкала оценки */
+ .scale-container {
+ padding: 10px;
+ }
+
+ .scale-values {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .scale-item {
+ margin: 5px;
+ }
+
+ /* Облако тегов */
+ .tag-cloud-container {
+ padding: 10px;
+ }
+
+ .tag-item {
+ margin: 5px;
+ font-size: 14px;
+ padding: 5px 10px;
+ }
+
+ /* Результаты опроса */
+ .questionnaire-links {
+ flex-direction: column;
+ }
+
+ .link-group {
+ width: 100%;
+ }
+
+ .link-input-group {
+ flex-direction: column;
+ }
+
+ .link-input-group input {
+ width: 100%;
+ margin-bottom: 10px;
+ }
+
+ .stats-table th,
+ .stats-table td {
+ padding: 5px;
+ font-size: 14px;
+ }
+
+ /* Модальные окна */
+ .modal-content {
+ width: 90%;
+ padding: 15px;
+ }
+
+ .modal-footer {
+ flex-direction: column;
+ }
+
+ .modal-footer .btn {
+ width: 100%;
+ margin: 5px 0;
+ }
+
+ /* Улучшенные стили для навигации в опросе на мобильных устройствах */
+ .step-navigation {
+ flex-direction: column-reverse;
+ align-items: stretch;
+ }
+
+ .step-navigation .btn {
+ margin: 5px 0;
+ width: 100%;
+ height: 44px; /* Увеличиваем высоту для лучшего тача */
+ }
+
+ .question-counter {
+ margin: 10px 0;
+ order: -1; /* Показываем счетчик вопросов сверху на мобильных */
+ }
+
+ /* Улучшения для формы */
+ .form-actions {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .form-actions .btn {
+ width: 100%;
+ height: 44px; /* Увеличиваем высоту для лучшего тача */
+ }
+
+ /* Улучшения для textarea */
+ textarea {
+ min-height: 100px;
+ }
+
+ /* Улучшения для облака тегов */
+ .tag-input {
+ width: 100%;
+ height: 44px; /* Увеличиваем высоту для лучшего тача */
+ font-size: 16px; /* Оптимальный размер шрифта для мобильных устройств */
+ }
+
+ /* Убираем зум при фокусе на поля ввода */
+ input[type="text"],
+ input[type="email"],
+ input[type="number"],
+ textarea,
+ select {
+ font-size: 16px; /* Оптимальный размер шрифта для мобильных устройств */
+ }
+}
+
+/* Медиа-запросы для маленьких экранов */
+@media (max-width: 480px) {
+ h1 {
+ font-size: 1.75rem;
+ }
+
+ h2 {
+ font-size: 1.5rem;
+ }
+
+ h3 {
+ font-size: 1.25rem;
+ }
+
+ .container {
+ padding: 10px;
+ }
+
+ .btn {
+ padding: 8px 12px;
+ font-size: 13px;
+ }
+
+ .question-item {
+ padding: 12px;
+ }
+
+ .tag-item {
+ margin: 3px;
+ font-size: 13px;
+ padding: 4px 8px;
+ }
+
+ .scale-item label {
+ min-width: 30px;
+ height: 30px;
+ font-size: 13px;
+ }
+
+ /* Уменьшаем размер чекбоксов и радиокнопок для лучшего тача */
+ input[type="checkbox"],
+ input[type="radio"] {
+ transform: scale(1.2);
+ margin-right: 8px;
+ }
+}
\ No newline at end of file
diff --git a/server/routers/questioneer/public/static/js/admin.js b/server/routers/questioneer/public/static/js/admin.js
new file mode 100644
index 0000000..ba55dc9
--- /dev/null
+++ b/server/routers/questioneer/public/static/js/admin.js
@@ -0,0 +1,465 @@
+/* global $, window, document, showAlert, showConfirm, showQRCodeModal */
+$(document).ready(function() {
+ const adminLink = window.location.pathname.split('/').pop();
+ let questionnaireData = null;
+
+ // Функция для получения базового пути API
+ const getApiPath = () => {
+ // Проверяем, содержит ли путь /ms/ (продакшн на dev.bro-js.ru)
+ const pathname = window.location.pathname;
+ const isMsPath = pathname.includes('/ms/questioneer');
+
+ if (isMsPath) {
+ // Для продакшна: если в пути есть /ms/, то API доступно по /ms/questioneer/api
+ return '/ms/questioneer/api';
+ } else {
+ // Для локальной разработки: формируем путь к API без учета текущей страницы
+ // Извлекаем базовый путь из URL страницы до /admin/[adminLink]
+ const basePath = pathname.split('/admin')[0];
+
+ // Путь до API приложения
+ return basePath + '/api';
+ }
+ };
+
+ // Загрузка данных опроса
+ const loadQuestionnaire = () => {
+ $.ajax({
+ url: `${getApiPath()}/questionnaires/admin/${adminLink}`,
+ method: 'GET',
+ success: function(result) {
+ if (result.success) {
+ questionnaireData = result.data;
+ renderQuestionnaire();
+ } else {
+ $('#loading').text(`Ошибка: ${result.error}`);
+ }
+ },
+ error: function(error) {
+ console.error('Error loading questionnaire:', error);
+ $('#loading').text('Не удалось загрузить опрос. Пожалуйста, попробуйте позже.');
+ }
+ });
+ };
+
+ // Отображение данных опроса
+ const renderQuestionnaire = () => {
+ // Заполняем основные данные
+ $('#questionnaire-title').text(questionnaireData.title);
+ $('#questionnaire-description').text(questionnaireData.description || 'Нет описания');
+
+ // Формируем ссылки
+ const baseUrl = window.location.origin;
+ const isMsPath = window.location.pathname.includes('/ms/questioneer');
+
+ let baseQuestionnairePath;
+ if (isMsPath) {
+ // Для продакшна: используем /ms/questioneer
+ baseQuestionnairePath = '/ms/questioneer';
+ } else {
+ // Для локальной разработки: используем текущий путь
+ baseQuestionnairePath = window.location.pathname.split('/admin')[0];
+ }
+
+ const publicUrl = `${baseUrl}${baseQuestionnairePath}/poll/${questionnaireData.publicLink}`;
+ const adminUrl = `${baseUrl}${baseQuestionnairePath}/admin/${questionnaireData.adminLink}`;
+
+ $('#public-link').val(publicUrl);
+ $('#admin-link').val(adminUrl);
+
+ // Отображаем статистику
+ renderStats(questionnaireData.questions);
+
+ // Показываем контейнер с данными
+ $('#loading').hide();
+ $('#questionnaire-container').show();
+ };
+
+ // Отображение статистики опроса
+ const renderStats = (questions) => {
+ const $statsContainer = $('#stats-container');
+ $statsContainer.empty();
+
+ // Проверяем, есть ли ответы
+ let hasAnyResponses = false;
+
+ // Проверяем наличие ответов для каждого типа вопросов
+ for (const question of questions) {
+ // Согласовываем типы вопросов между бэкендом и фронтендом
+ const questionType = normalizeQuestionType(question.type);
+
+ if (questionType === 'single' || questionType === 'multiple') {
+ if (question.options && question.options.some(option => (option.votes > 0 || option.count > 0))) {
+ hasAnyResponses = true;
+ break;
+ }
+ } else if (questionType === 'tagcloud') {
+ if (question.tags && question.tags.some(tag => tag.count > 0)) {
+ hasAnyResponses = true;
+ break;
+ }
+ } else if (questionType === 'scale' || questionType === 'rating') {
+ // Проверяем оба возможных поля для данных шкалы
+ const hasScaleValues = question.scaleValues && question.scaleValues.length > 0;
+ const hasResponses = question.responses && question.responses.length > 0;
+ if (hasScaleValues || hasResponses) {
+ hasAnyResponses = true;
+ break;
+ }
+ } else if (questionType === 'text') {
+ // Проверяем оба возможных поля для текстовых ответов
+ const hasTextAnswers = question.textAnswers && question.textAnswers.length > 0;
+ const hasAnswers = question.answers && question.answers.length > 0;
+ if (hasTextAnswers || hasAnswers) {
+ hasAnyResponses = true;
+ break;
+ }
+ }
+ }
+
+ if (!hasAnyResponses) {
+ $statsContainer.html('Пока нет ответов на опрос
');
+ return;
+ }
+
+ // Для каждого вопроса создаем блок статистики
+ questions.forEach((question, index) => {
+ const $questionStats = $('', { class: 'question-stats' });
+ const $questionTitle = $('
', { text: `${index + 1}. ${question.text}` });
+ $questionStats.append($questionTitle);
+
+ // Согласовываем типы вопросов между бэкендом и фронтендом
+ const questionType = normalizeQuestionType(question.type);
+
+ // В зависимости от типа вопроса отображаем разную статистику
+ if (questionType === 'single' || questionType === 'multiple') {
+ // Для вопросов с выбором вариантов
+ renderChoiceStats(question, $questionStats);
+ } else if (questionType === 'tagcloud') {
+ // Для облака тегов
+ renderTagCloudStats(question, $questionStats);
+ } else if (questionType === 'scale' || questionType === 'rating') {
+ // Для шкалы и рейтинга
+ renderScaleStats(question, $questionStats);
+ } else if (questionType === 'text') {
+ // Для текстовых ответов
+ renderTextStats(question, $questionStats);
+ }
+
+ $statsContainer.append($questionStats);
+ });
+ };
+
+ // Приводит тип вопроса к стандартному формату
+ const normalizeQuestionType = (type) => {
+ const typeMap = {
+ 'single_choice': 'single',
+ 'multiple_choice': 'multiple',
+ 'tag_cloud': 'tagcloud',
+ 'single': 'single',
+ 'multiple': 'multiple',
+ 'tagcloud': 'tagcloud',
+ 'scale': 'scale',
+ 'rating': 'rating',
+ 'text': 'text'
+ };
+ return typeMap[type] || type;
+ };
+
+ // Отображение статистики для вопросов с выбором
+ const renderChoiceStats = (question, $container) => {
+ // Преобразуем опции к единому формату
+ const options = question.options.map(option => ({
+ text: option.text,
+ votes: option.votes || option.count || 0
+ }));
+
+ const totalVotes = options.reduce((sum, option) => sum + option.votes, 0);
+
+ if (totalVotes === 0) {
+ $container.append($('', { class: 'no-votes', text: 'Нет голосов' }));
+ return;
+ }
+
+ const $table = $('
', { class: 'stats-table' });
+ const $thead = $('').append(
+ $('').append(
+ $('', { text: 'Вариант' }),
+ $(' | ', { text: 'Голоса' }),
+ $(' | ', { text: '%' }),
+ $(' | ', { text: 'Визуализация' })
+ )
+ );
+
+ const $tbody = $(' |
');
+
+ options.forEach(option => {
+ const votes = option.votes;
+ const percent = totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0;
+
+ const $tr = $('').append(
+ $('', { text: option.text }),
+ $(' | ', { text: votes }),
+ $(' | ', { text: `${percent}%` }),
+ $(' | ').append(
+ $('', { class: 'bar-container' }).append(
+ $(' ', {
+ class: 'bar',
+ css: { width: `${percent}%` }
+ })
+ )
+ )
+ );
+
+ $tbody.append($tr);
+ });
+
+ $table.append($thead, $tbody);
+ $container.append($table);
+ $container.append($(' ', { class: 'total-votes', text: `Всего голосов: ${totalVotes}` }));
+ };
+
+ // Отображение статистики для облака тегов
+ const renderTagCloudStats = (question, $container) => {
+ if (!question.tags || question.tags.length === 0 || !question.tags.some(tag => tag.count > 0)) {
+ $container.append($(' ', { class: 'no-votes', text: 'Нет выбранных тегов' }));
+ return;
+ }
+
+ const $tagCloud = $(' ', { class: 'tag-cloud-stats' });
+
+ // Находим максимальное количество для масштабирования
+ const maxCount = Math.max(...question.tags.map(tag => tag.count || 0));
+
+ // Сортируем теги по популярности
+ const sortedTags = [...question.tags].sort((a, b) => (b.count || 0) - (a.count || 0));
+
+ sortedTags.forEach(tag => {
+ if (tag.count && tag.count > 0) {
+ const fontSize = maxCount > 0 ? 1 + (tag.count / maxCount) * 1.5 : 1; // от 1em до 2.5em
+
+ $tagCloud.append(
+ $(' ', {
+ class: 'tag-item',
+ text: `${tag.text} (${tag.count})`,
+ css: { fontSize: `${fontSize}em` }
+ })
+ );
+ }
+ });
+
+ $container.append($tagCloud);
+ };
+
+ // Отображение статистики для шкалы и рейтинга
+ const renderScaleStats = (question, $container) => {
+ // Используем scaleValues или responses, в зависимости от того, что доступно
+ const values = question.responses && question.responses.length > 0
+ ? question.responses
+ : (question.scaleValues || []);
+
+ if (values.length === 0) {
+ $container.append($('', { class: 'no-votes', text: 'Нет оценок' }));
+ return;
+ }
+
+ const sum = values.reduce((a, b) => a + b, 0);
+ const avg = sum / values.length;
+ const min = Math.min(...values);
+ const max = Math.max(...values);
+
+ // Создаем контейнер для статистики
+ const $scaleStats = $(' ', { class: 'scale-stats' });
+
+ // Добавляем сводную статистику
+ $scaleStats.append(
+ $(' ', { class: 'stat-summary' }).append(
+ $(' ', { class: 'stat-item' }).append(
+ $(' ', { class: 'stat-label', text: 'Среднее значение:' }),
+ $('', { class: 'stat-value', text: avg.toFixed(1) })
+ ),
+ $('', { class: 'stat-item' }).append(
+ $(' ', { class: 'stat-label', text: 'Минимум:' }),
+ $('', { class: 'stat-value', text: min })
+ ),
+ $('', { class: 'stat-item' }).append(
+ $(' ', { class: 'stat-label', text: 'Максимум:' }),
+ $('', { class: 'stat-value', text: max })
+ ),
+ $('', { class: 'stat-item' }).append(
+ $(' ', { class: 'stat-label', text: 'Количество оценок:' }),
+ $('', { class: 'stat-value', text: values.length })
+ )
+ )
+ );
+
+ // Создаем таблицу для визуализации распределения голосов
+ const $table = $('', { class: 'stats-table' });
+ const $thead = $('').append(
+ $('').append(
+ $('', { text: 'Значение' }),
+ $(' | ', { text: 'Голоса' }),
+ $(' | ', { text: '%' }),
+ $(' | ', { text: 'Визуализация' })
+ )
+ );
+
+ const $tbody = $(' | ');
+
+ // Определяем минимальное и максимальное значение шкалы из самих данных
+ // либо используем значения из настроек вопроса, если они есть
+ const scaleMin = question.scaleMin !== undefined ? question.scaleMin : min;
+ const scaleMax = question.scaleMax !== undefined ? question.scaleMax : max;
+
+ // Создаем счетчик для каждого возможного значения шкалы
+ const countByValue = {};
+ for (let i = scaleMin; i <= scaleMax; i++) {
+ countByValue[i] = 0;
+ }
+
+ // Подсчитываем количество голосов для каждого значения
+ values.forEach(value => {
+ if (countByValue[value] !== undefined) {
+ countByValue[value]++;
+ }
+ });
+
+ // Создаем строки таблицы для каждого значения шкалы
+ for (let value = scaleMin; value <= scaleMax; value++) {
+ const count = countByValue[value] || 0;
+ const percent = values.length > 0 ? Math.round((count / values.length) * 100) : 0;
+
+ const $tr = $('').append(
+ $('', { text: value }),
+ $(' | ', { text: count }),
+ $(' | ', { text: `${percent}%` }),
+ $(' | ').append(
+ $('', { class: 'bar-container' }).append(
+ $(' ', {
+ class: 'bar',
+ css: { width: `${percent}%` }
+ })
+ )
+ )
+ );
+
+ $tbody.append($tr);
+ }
+
+ $table.append($thead, $tbody);
+ $scaleStats.append($table);
+
+ $container.append($scaleStats);
+ };
+
+ // Отображение статистики для текстовых ответов
+ const renderTextStats = (question, $container) => {
+ // Используем textAnswers или answers, в зависимости от того, что доступно
+ const answers = question.textAnswers && question.textAnswers.length > 0
+ ? question.textAnswers
+ : (question.answers || []);
+
+ if (answers.length === 0) {
+ $container.append($(' ', { class: 'no-votes', text: 'Нет текстовых ответов' }));
+ return;
+ }
+
+ const $textAnswers = $(' ', { class: 'text-answers-list' });
+
+ answers.forEach((answer, i) => {
+ $textAnswers.append(
+ $(' ', { class: 'text-answer-item' }).append(
+ $(' ', { class: 'answer-number', text: `#${i + 1}` }),
+ $(' ', { class: 'answer-text', text: answer })
+ )
+ );
+ });
+
+ $container.append($textAnswers);
+ };
+
+ // Копирование ссылок
+ $('#copy-public-link').on('click', function() {
+ $('#public-link').select();
+ document.execCommand('copy');
+ showAlert('Ссылка для голосования скопирована в буфер обмена', 'Копирование', null, true);
+ });
+
+ $('#copy-admin-link').on('click', function() {
+ $('#admin-link').select();
+ document.execCommand('copy');
+ showAlert('Административная ссылка скопирована в буфер обмена', 'Копирование', null, true);
+ });
+
+ // Отображение QR-кода
+ $('#show-qr-code').on('click', function() {
+ const publicUrl = $('#public-link').val();
+ showQRCodeModal(publicUrl, 'QR-код для голосования');
+ });
+
+ // Редактирование опроса
+ $('#edit-questionnaire').on('click', function() {
+ // Перенаправляем на страницу редактирования
+ const isMsPath = window.location.pathname.includes('/ms/questioneer');
+ let basePath;
+
+ if (isMsPath) {
+ // Для продакшна: используем /ms/questioneer
+ basePath = '/ms/questioneer';
+ } else {
+ // Для локальной разработки: используем текущий путь
+ basePath = window.location.pathname.split('/admin')[0];
+ }
+
+ window.location.href = `${basePath}/edit/${adminLink}`;
+ });
+
+ // Удаление опроса
+ $('#delete-questionnaire').on('click', function() {
+ showConfirm('Вы уверены, что хотите удалить опрос? Все ответы будут удалены безвозвратно.', function(confirmed) {
+ if (confirmed) {
+ deleteQuestionnaire();
+ }
+ }, 'Удаление опроса');
+ });
+
+ // Функция удаления опроса
+ const deleteQuestionnaire = () => {
+ $.ajax({
+ url: `${getApiPath()}/questionnaires/${adminLink}`,
+ method: 'DELETE',
+ success: function(result) {
+ if (result.success) {
+ showAlert('Опрос успешно удален', 'Удаление опроса', function() {
+ // Получаем базовый путь с учетом /ms в продакшен-версии
+ const isMsPath = window.location.pathname.includes('/ms/questioneer');
+ let basePath;
+
+ if (isMsPath) {
+ // Для продакшна: используем /ms/questioneer
+ basePath = '/ms/questioneer';
+ } else {
+ // Для локальной разработки: используем текущий путь
+ basePath = window.location.pathname.split('/admin')[0];
+ }
+
+ // Перенаправляем на главную страницу
+ window.location.href = basePath;
+ }, true);
+ } else {
+ showAlert(`Ошибка при удалении опроса: ${result.error}`, 'Ошибка');
+ }
+ },
+ error: function(error) {
+ console.error('Error deleting questionnaire:', error);
+ showAlert('Не удалось удалить опрос. Пожалуйста, попробуйте позже.', 'Ошибка');
+ }
+ });
+ };
+
+ // Инициализация
+ loadQuestionnaire();
+
+ // Обновление данных каждые 10 секунд
+ setInterval(loadQuestionnaire, 10000);
+});
\ No newline at end of file
diff --git a/server/routers/questioneer/public/static/js/common.js b/server/routers/questioneer/public/static/js/common.js
new file mode 100644
index 0000000..7007de8
--- /dev/null
+++ b/server/routers/questioneer/public/static/js/common.js
@@ -0,0 +1,236 @@
+/* global $, document */
+
+// Функция для создания модального окна
+function createModal(options) {
+ // Если модальное окно уже существует, удаляем его
+ $('.modal-overlay').remove();
+
+ // Опции по умолчанию
+ const defaultOptions = {
+ title: 'Сообщение',
+ content: '',
+ closeText: 'Закрыть',
+ onClose: null,
+ showCancel: false,
+ cancelText: 'Отмена',
+ confirmText: 'Подтвердить',
+ onConfirm: null,
+ onCancel: null,
+ size: 'normal', // 'normal', 'large', 'small'
+ customClass: '',
+ autoClose: false, // Автоматическое закрытие по таймеру
+ autoCloseTime: 2000 // Время до автоматического закрытия (2 секунды)
+ };
+
+ // Объединяем пользовательские опции с опциями по умолчанию
+ const settings = $.extend({}, defaultOptions, options);
+
+ // Создаем структуру модального окна
+ const $modalOverlay = $(' ', { class: 'modal-overlay' });
+ const $modal = $(' ', { class: `modal ${settings.customClass}` });
+
+ // Устанавливаем ширину в зависимости от размера
+ if (settings.size === 'large') {
+ $modal.css('max-width', '700px');
+ } else if (settings.size === 'small') {
+ $modal.css('max-width', '400px');
+ }
+
+ // Создаем заголовок
+ const $modalHeader = $(' ', { class: 'modal-header' });
+ const $modalTitle = $(' ', { text: settings.title });
+ const $modalClose = $(' | |