перепиши на пайтно
// rnn_chatbot.js

// Подключаем зависимости
const tf = require('@tensorflow/tfjs-node-gpu'); // Или '@tensorflow/tfjs-node-gpu' для GPU
const readlineSync = require('readline-sync');

// Данные: пары "вопрос-ответ"
const data = [
  { question: "Как тебя зовут", answer: "Меня зовут AI" },
  { question: "Как дела", answer: "У меня всё хорошо, спасибо" },
  { question: "Что ты умеешь", answer: "Я могу отвечать на вопросы" },
  // Добавьте дополнительные пары "вопрос-ответ" для обучения
];

// Шаг 1: Токенизация и создание словаря
function tokenize(text) {
  return text.toLowerCase().replace(/[^\w\s]|_/g, '').split(/\s+/);
}

const vocab = {};
let index = 1; // Инициализируем с 1, чтобы 0 использовать для неизвестных слов

data.forEach(pair => {
  const questionTokens = tokenize(pair.question);
  const answerTokens = tokenize(pair.answer);

  questionTokens.concat(answerTokens).forEach(token => {
    if (!vocab[token]) {
      vocab[token] = index++;
    }
  });
});

const vocabSize = index; // Размер словаря

// Создаем обратный словарь для преобразования индексов обратно в слова
const reversedVocab = Object.entries(vocab).reduce((obj, [key, value]) => {
  obj[value] = key;
  return obj;
}, {});

// Функция для преобразования текста в числовую последовательность
function textToSequence(text) {
  const tokens = tokenize(text);
  return tokens.map(token => vocab[token] || 0); // Неизвестные слова преобразуем в 0
}

// Преобразуем вопросы и ответы в числовые последовательности
const questionsSeq = data.map(pair => textToSequence(pair.question));
const answersSeq = data.map(pair => textToSequence(pair.answer));

// Определяем максимальную длину вопросов и ответов для паддинга
const maxQuestionLen = Math.max(...questionsSeq.map(seq => seq.length));
const maxAnswerLen = Math.max(...answersSeq.map(seq => seq.length));

// Функция для паддинга последовательностей
function padSequences(sequences, maxLen) {
  return sequences.map(seq => {
    const padded = new Array(maxLen).fill(0);
    seq.forEach((num, idx) => {
      if (idx < maxLen) {
        padded[idx] = num;
      }
    });
    return padded;
  });
}

const questionsPadded = padSequences(questionsSeq, maxQuestionLen);
const answersPadded = padSequences(answersSeq, maxAnswerLen);

// Преобразование данных в тензоры
const xs = tf.tensor2d(questionsPadded, [questionsPadded.length, maxQuestionLen]);
const ys = tf.tensor2d(answersPadded.map(seq => seq[0]), [answersPadded.length, 1]); // Предсказываем первое слово ответа

// Шаг 2: Построение модели
const model = tf.sequential();

// Слой Embedding для входных данных (вопросов)
model.add(tf.layers.embedding({
  inputDim: vocabSize,
  outputDim: 64,
  inputLength: maxQuestionLen,
}));

// Рекуррентный слой (SimpleRNN)
model.add(tf.layers.simpleRNN({
  units: 128,
}));

// Полносвязный слой для предсказания первого слова ответа
model.add(tf.layers.dense({
  units: vocabSize,
  activation: 'softmax',
}));

// Компиляция модели
model.compile({
  loss: 'sparseCategoricalCrossentropy',
  optimizer: tf.train.adam(),
  metrics: ['accuracy'],
});

// Функция для преобразования числовой последовательности в текст
function sequenceToText(sequence) {
  return sequence.map(num => reversedVocab[num] || '?').join(' ');
}

// Шаг 3: Обучение модели
async function trainModel() {
  await model.fit(xs, ys, {
    epochs: 500,
    batchSize: 2,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        if ((epoch + 1) % 100 === 0) {
          console.log(`Epoch ${epoch + 1}: loss = ${logs.loss.toFixed(4)}, accuracy = ${(logs.acc * 100).toFixed(2)}%`);
        }
      }
    }
  });
  console.log('Модель обучена');
}

// Шаг 4: Функция для получения ответа от модели
async function getAnswer(question) {
  const sequence = textToSequence(question);
  const paddedSequence = padSequences([sequence], maxQuestionLen);
  const input = tf.tensor2d(paddedSequence, [1, maxQuestionLen]);

  const prediction = model.predict(input);
  const outputIndex = prediction.argMax(-1).dataSync()[0];

  const answerWord = reversedVocab[outputIndex];

  if (answerWord) {
    // Если требуется, можно добавить логику для генерации более полных ответов
    return answerWord;
  } else {
    return 'Извините, я не понял';
  }
}

// Запуск обучения и взаимодействие с пользователем через консоль
(async () => {
  await trainModel();

  while (true) {
    const userQuestion = readlineSync.question('Ваш вопрос (или "выход" для завершения): ');
    if (userQuestion.toLowerCase() === 'выход') {
      console.log('До свидания!');
      break;
    }

    const response = await getAnswer(userQuestion);
    console.log('Ответ:', response);
  }
})();