You are a helpful assistant.
system
Viết tập lệnh Ruby sử dụng khung Sinatra để triển khai một máy chủ trò chuyện đơn giản, trong đó người dùng có thể kết nối với máy chủ bằng kết nối WebSocket thông qua cơ chế xác thực người dùng và gửi tin nhắn định dạng JSON đến tất cả người dùng được kết nối trong thời gian thực. Lưu trữ 100 tin nhắn gần đây nhất vào cơ sở dữ liệu quan hệ và hiển thị chúng cho người dùng mới khi họ tham gia trò chuyện, đồng thời yêu cầu có giao diện người dùng để hiển thị tin nhắn và cho phép người dùng rời khỏi trò chuyện hoặc tắt thông báo, đồng thời ghi lại lỗi và thông tin kết nối.
user
Để triển khai một máy chủ trò chuyện đơn giản sử dụng Sinatra và WebSocket, bạn có thể làm theo các bước sau. Chúng ta sẽ sử dụng các thư viện như `sinatra`, `sinatra-websocket`, `sequel` để kết nối cơ sở dữ liệu, và `sqlite3` để lưu trữ dữ liệu. Dưới đây là một ví dụ cơ bản:

1. **Cài đặt các thư viện cần thiết**: Đảm bảo bạn đã cài đặt Ruby và Bundler. Tạo một tệp `Gemfile` và thêm các thư viện cần thiết:

```ruby
source 'https://rubygems.org'

gem 'sinatra'
gem 'sinatra-websocket'
gem 'sequel'
gem 'sqlite3'
gem 'json'
```

Sau đó, chạy `bundle install` để cài đặt các thư viện.

2. **Tạo cơ sở dữ liệu và bảng lưu trữ tin nhắn**:

```ruby
require 'sequel'

DB = Sequel.sqlite('chat.db')

DB.create_table? :messages do
  primary_key :id
  String :username
  String :content
  DateTime :created_at
end
```

3. **Tạo ứng dụng Sinatra**:

```ruby
require 'sinatra'
require 'sinatra-websocket'
require 'json'
require 'sequel'

set :server, 'thin'
set :sockets, []

DB = Sequel.sqlite('chat.db')

class Message < Sequel::Model
end

get '/' do
  if !request.websocket?
    erb :index
  else
    request.websocket do |ws|
      ws.onopen do
        settings.sockets << ws
        # Gửi 100 tin nhắn gần đây nhất cho người dùng mới
        recent_messages = Message.order(:created_at).limit(100).all
        ws.send(recent_messages.to_json)
      end

      ws.onmessage do |msg|
        data = JSON.parse(msg)
        # Lưu tin nhắn vào cơ sở dữ liệu
        Message.create(username: data['username'], content: data['content'], created_at: Time.now)

        # Gửi tin nhắn đến tất cả người dùng
        EM.next_tick do
          settings.sockets.each do |s|
            s.send(msg) unless s == ws
          end
        end
      end

      ws.onclose do
        warn("WebSocket closed")
        settings.sockets.delete(ws)
      end
    end
  end
end

__END__

@@index
<!DOCTYPE html>
<html>
<head>
  <title>Chat Room</title>
  <style>
    #chat { height: 300px; overflow-y: scroll; border: 1px solid #ccc; }
    #message { width: 80%; }
  </style>
</head>
<body>
  <div id="chat"></div>
  <input id="username" placeholder="Enter your username" />
  <input id="message" placeholder="Enter your message" />
  <button onclick="sendMessage()">Send</button>
  <button onclick="toggleNotifications()">Toggle Notifications</button>
  <script>
    var ws = new WebSocket('ws://' + window.location.host + window.location.pathname);
    var chat = document.getElementById('chat');
    var notificationsEnabled = true;

    ws.onmessage = function(event) {
      var messages = JSON.parse(event.data);
      if (Array.isArray(messages)) {
        messages.forEach(function(message) {
          addMessage(message.username, message.content);
        });
      } else {
        addMessage(messages.username, messages.content);
      }
    };

    function addMessage(username, content) {
      var messageElement = document.createElement('div');
      messageElement.textContent = username + ': ' + content;
      chat.appendChild(messageElement);
      chat.scrollTop = chat.scrollHeight;
      if (notificationsEnabled) {
        // Hiển thị thông báo
        new Notification(username, { body: content });
      }
    }

    function sendMessage() {
      var username = document.getElementById('username').value;
      var message = document.getElementById('message').value;
      ws.send(JSON.stringify({ username: username, content: message }));
      document.getElementById('message').value = '';
    }

    function toggleNotifications() {
      notificationsEnabled = !notificationsEnabled;
    }

    if (Notification.permission !== "granted") {
      Notification.requestPermission();
    }
  </script>
</body>
</html>
```

4. **Chạy ứng dụng**: Lưu mã trên vào một tệp `app.rb` và chạy `ruby app.rb`. Truy cập `http://localhost:4567` để sử dụng ứng dụng trò chuyện.

Lưu ý: Đây là một ví dụ cơ bản và có thể cần thêm các tính năng bảo mật và xử lý lỗi cho ứng dụng thực tế.
assistant