mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 05:09:38 +02:00
Add API v1 chat endpoints
- Add chats#index and chats#show endpoints to list and view AI conversations - Add messages#create endpoint to send messages to AI chats - Include API documentation for chat endpoints - Add controller tests for new endpoints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4d3c710291
commit
94202b2a6b
11 changed files with 698 additions and 0 deletions
|
@ -266,4 +266,11 @@ class Api::V1::BaseController < ApplicationController
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check if AI features are enabled for the current user
|
||||
def require_ai_enabled
|
||||
unless current_resource_owner&.ai_enabled?
|
||||
render_json({ error: "feature_disabled", message: "AI features are not enabled for this user" }, status: :forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
84
app/controllers/api/v1/chats_controller.rb
Normal file
84
app/controllers/api/v1/chats_controller.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::ChatsController < Api::V1::BaseController
|
||||
include Pagy::Backend
|
||||
before_action :require_ai_enabled
|
||||
before_action :ensure_read_scope, only: [ :index, :show ]
|
||||
before_action :ensure_write_scope, only: [ :create, :update, :destroy ]
|
||||
before_action :set_chat, only: [ :show, :update, :destroy ]
|
||||
|
||||
def index
|
||||
@pagy, @chats = pagy(Current.user.chats.ordered, items: 20)
|
||||
end
|
||||
|
||||
def show
|
||||
return unless @chat
|
||||
@pagy, @messages = pagy(@chat.messages.ordered, items: 50)
|
||||
end
|
||||
|
||||
def create
|
||||
@chat = Current.user.chats.build(title: chat_params[:title])
|
||||
|
||||
if @chat.save
|
||||
if chat_params[:message].present?
|
||||
@message = @chat.messages.build(
|
||||
content: chat_params[:message],
|
||||
type: "UserMessage",
|
||||
ai_model: chat_params[:model] || "gpt-4"
|
||||
)
|
||||
|
||||
if @message.save
|
||||
AssistantResponseJob.perform_later(@message)
|
||||
render :show, status: :created
|
||||
else
|
||||
@chat.destroy
|
||||
render json: { error: "Failed to create initial message", details: @message.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
render :show, status: :created
|
||||
end
|
||||
else
|
||||
render json: { error: "Failed to create chat", details: @chat.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
return unless @chat
|
||||
|
||||
if @chat.update(update_chat_params)
|
||||
render :show
|
||||
else
|
||||
render json: { error: "Failed to update chat", details: @chat.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless @chat
|
||||
@chat.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_read_scope
|
||||
authorize_scope!(:read)
|
||||
end
|
||||
|
||||
def ensure_write_scope
|
||||
authorize_scope!(:write)
|
||||
end
|
||||
|
||||
def set_chat
|
||||
@chat = Current.user.chats.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Chat not found" }, status: :not_found
|
||||
end
|
||||
|
||||
def chat_params
|
||||
params.permit(:title, :message, :model)
|
||||
end
|
||||
|
||||
def update_chat_params
|
||||
params.permit(:title)
|
||||
end
|
||||
end
|
55
app/controllers/api/v1/messages_controller.rb
Normal file
55
app/controllers/api/v1/messages_controller.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MessagesController < Api::V1::BaseController
|
||||
before_action :require_ai_enabled
|
||||
before_action :ensure_write_scope, only: [ :create, :retry ]
|
||||
before_action :set_chat
|
||||
|
||||
def create
|
||||
@message = @chat.messages.build(
|
||||
content: message_params[:content],
|
||||
type: "UserMessage",
|
||||
ai_model: message_params[:model] || "gpt-4"
|
||||
)
|
||||
|
||||
if @message.save
|
||||
AssistantResponseJob.perform_later(@message)
|
||||
render :show, status: :created
|
||||
else
|
||||
render json: { error: "Failed to create message", details: @message.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def retry
|
||||
last_message = @chat.messages.ordered.last
|
||||
|
||||
if last_message&.type == "AssistantMessage"
|
||||
new_message = @chat.messages.create!(
|
||||
type: "AssistantMessage",
|
||||
content: "",
|
||||
ai_model: last_message.ai_model
|
||||
)
|
||||
|
||||
AssistantResponseJob.perform_later(new_message)
|
||||
render json: { message: "Retry initiated", message_id: new_message.id }, status: :accepted
|
||||
else
|
||||
render json: { error: "No assistant message to retry" }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_write_scope
|
||||
authorize_scope!(:write)
|
||||
end
|
||||
|
||||
def set_chat
|
||||
@chat = Current.user.chats.find(params[:chat_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Chat not found" }, status: :not_found
|
||||
end
|
||||
|
||||
def message_params
|
||||
params.permit(:content, :model)
|
||||
end
|
||||
end
|
7
app/views/api/v1/chats/_chat.json.jbuilder
Normal file
7
app/views/api/v1/chats/_chat.json.jbuilder
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.id chat.id
|
||||
json.title chat.title
|
||||
json.error chat.error.present? ? chat.error : nil
|
||||
json.created_at chat.created_at.iso8601
|
||||
json.updated_at chat.updated_at.iso8601
|
18
app/views/api/v1/chats/index.json.jbuilder
Normal file
18
app/views/api/v1/chats/index.json.jbuilder
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.chats @chats do |chat|
|
||||
json.id chat.id
|
||||
json.title chat.title
|
||||
json.last_message_at chat.messages.ordered.first&.created_at&.iso8601
|
||||
json.message_count chat.messages.count
|
||||
json.error chat.error.present? ? chat.error : nil
|
||||
json.created_at chat.created_at.iso8601
|
||||
json.updated_at chat.updated_at.iso8601
|
||||
end
|
||||
|
||||
json.pagination do
|
||||
json.page @pagy.page
|
||||
json.per_page @pagy.vars[:items]
|
||||
json.total_count @pagy.count
|
||||
json.total_pages @pagy.pages
|
||||
end
|
33
app/views/api/v1/chats/show.json.jbuilder
Normal file
33
app/views/api/v1/chats/show.json.jbuilder
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.partial! "chat", chat: @chat
|
||||
|
||||
json.messages @messages do |message|
|
||||
json.id message.id
|
||||
json.type message.type.underscore
|
||||
json.role message.role
|
||||
json.content message.content
|
||||
json.model message.ai_model if message.type == "AssistantMessage"
|
||||
json.created_at message.created_at.iso8601
|
||||
json.updated_at message.updated_at.iso8601
|
||||
|
||||
# Include tool calls for assistant messages
|
||||
if message.type == "AssistantMessage" && message.tool_calls.any?
|
||||
json.tool_calls message.tool_calls do |tool_call|
|
||||
json.id tool_call.id
|
||||
json.function_name tool_call.function_name
|
||||
json.function_arguments tool_call.function_arguments
|
||||
json.function_result tool_call.function_result
|
||||
json.created_at tool_call.created_at.iso8601
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @pagy
|
||||
json.pagination do
|
||||
json.page @pagy.page
|
||||
json.per_page @pagy.vars[:items]
|
||||
json.total_count @pagy.count
|
||||
json.total_pages @pagy.pages
|
||||
end
|
||||
end
|
16
app/views/api/v1/messages/show.json.jbuilder
Normal file
16
app/views/api/v1/messages/show.json.jbuilder
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.id @message.id
|
||||
json.chat_id @message.chat_id
|
||||
json.type @message.type.underscore
|
||||
json.role @message.role
|
||||
json.content @message.content
|
||||
json.model @message.ai_model if @message.type == "AssistantMessage"
|
||||
json.created_at @message.created_at.iso8601
|
||||
json.updated_at @message.updated_at.iso8601
|
||||
|
||||
# Note: AI response will be processed asynchronously
|
||||
if @message.type == "UserMessage"
|
||||
json.ai_response_status "pending"
|
||||
json.ai_response_message "AI response is being generated"
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue