geminize 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +3 -0
  4. data/.yardopts +14 -0
  5. data/CHANGELOG.md +24 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/CONTRIBUTING.md +109 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +423 -0
  10. data/Rakefile +10 -0
  11. data/examples/README.md +75 -0
  12. data/examples/configuration.rb +58 -0
  13. data/examples/embeddings.rb +195 -0
  14. data/examples/multimodal.rb +126 -0
  15. data/examples/rails_chat/README.md +69 -0
  16. data/examples/rails_chat/app/controllers/chat_controller.rb +26 -0
  17. data/examples/rails_chat/app/views/chat/index.html.erb +112 -0
  18. data/examples/rails_chat/config/routes.rb +8 -0
  19. data/examples/rails_initializer.rb +46 -0
  20. data/examples/system_instructions.rb +101 -0
  21. data/lib/geminize/chat.rb +98 -0
  22. data/lib/geminize/client.rb +318 -0
  23. data/lib/geminize/configuration.rb +98 -0
  24. data/lib/geminize/conversation_repository.rb +161 -0
  25. data/lib/geminize/conversation_service.rb +126 -0
  26. data/lib/geminize/embeddings.rb +145 -0
  27. data/lib/geminize/error_mapper.rb +96 -0
  28. data/lib/geminize/error_parser.rb +120 -0
  29. data/lib/geminize/errors.rb +185 -0
  30. data/lib/geminize/middleware/error_handler.rb +72 -0
  31. data/lib/geminize/model_info.rb +91 -0
  32. data/lib/geminize/models/chat_request.rb +186 -0
  33. data/lib/geminize/models/chat_response.rb +118 -0
  34. data/lib/geminize/models/content_request.rb +530 -0
  35. data/lib/geminize/models/content_response.rb +99 -0
  36. data/lib/geminize/models/conversation.rb +156 -0
  37. data/lib/geminize/models/embedding_request.rb +222 -0
  38. data/lib/geminize/models/embedding_response.rb +1064 -0
  39. data/lib/geminize/models/memory.rb +88 -0
  40. data/lib/geminize/models/message.rb +140 -0
  41. data/lib/geminize/models/model.rb +171 -0
  42. data/lib/geminize/models/model_list.rb +124 -0
  43. data/lib/geminize/models/stream_response.rb +99 -0
  44. data/lib/geminize/rails/app/controllers/concerns/geminize/controller.rb +105 -0
  45. data/lib/geminize/rails/app/helpers/geminize_helper.rb +125 -0
  46. data/lib/geminize/rails/controller_additions.rb +41 -0
  47. data/lib/geminize/rails/engine.rb +29 -0
  48. data/lib/geminize/rails/helper_additions.rb +37 -0
  49. data/lib/geminize/rails.rb +50 -0
  50. data/lib/geminize/railtie.rb +33 -0
  51. data/lib/geminize/request_builder.rb +57 -0
  52. data/lib/geminize/text_generation.rb +285 -0
  53. data/lib/geminize/validators.rb +150 -0
  54. data/lib/geminize/vector_utils.rb +164 -0
  55. data/lib/geminize/version.rb +5 -0
  56. data/lib/geminize.rb +527 -0
  57. data/lib/generators/geminize/install_generator.rb +22 -0
  58. data/lib/generators/geminize/templates/README +31 -0
  59. data/lib/generators/geminize/templates/initializer.rb +38 -0
  60. data/sig/geminize.rbs +4 -0
  61. metadata +218 -0
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ # Controller concern for including Gemini functionality in Rails controllers
5
+ module Controller
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Helper methods for controllers using Geminize
10
+ helper_method :current_gemini_conversation if respond_to?(:helper_method)
11
+ end
12
+
13
+ # Current conversation for this controller context
14
+ # Uses session to maintain conversation state between requests
15
+ # @return [Geminize::Models::Conversation]
16
+ def current_gemini_conversation
17
+ return @current_gemini_conversation if defined?(@current_gemini_conversation)
18
+
19
+ if session[:gemini_conversation_id]
20
+ # Try to load existing conversation
21
+ begin
22
+ @current_gemini_conversation = Geminize.load_conversation(session[:gemini_conversation_id])
23
+ rescue => e
24
+ Rails.logger.error("Failed to load Gemini conversation: #{e.message}")
25
+ # Create a new conversation if loading fails
26
+ create_new_gemini_conversation
27
+ end
28
+ else
29
+ # Create a new conversation if one doesn't exist
30
+ create_new_gemini_conversation
31
+ end
32
+
33
+ @current_gemini_conversation
34
+ end
35
+
36
+ # Send a message in the current conversation
37
+ # @param message [String] The message to send
38
+ # @param model_name [String, nil] Optional model name override
39
+ # @param params [Hash] Additional parameters for the message
40
+ # @return [Geminize::Models::ChatResponse] The response
41
+ def send_gemini_message(message, model_name = nil, params = {})
42
+ response = Geminize.chat(message, current_gemini_conversation, model_name, params)
43
+ Geminize.save_conversation(current_gemini_conversation)
44
+ response
45
+ end
46
+
47
+ # Generate text using Gemini
48
+ # @param prompt [String] The prompt to send
49
+ # @param model_name [String, nil] Optional model name override
50
+ # @param params [Hash] Additional parameters for generation
51
+ # @return [Geminize::Models::ContentResponse] The response
52
+ def generate_gemini_text(prompt, model_name = nil, params = {})
53
+ Geminize.generate_text(prompt, model_name, params)
54
+ end
55
+
56
+ # Generate text with images using Gemini
57
+ # @param prompt [String] The prompt to send
58
+ # @param images [Array<Hash>] Array of image data
59
+ # @param model_name [String, nil] Optional model name override
60
+ # @param params [Hash] Additional parameters for generation
61
+ # @return [Geminize::Models::ContentResponse] The response
62
+ def generate_gemini_multimodal(prompt, images, model_name = nil, params = {})
63
+ Geminize.generate_text_multimodal(prompt, images, model_name, params)
64
+ end
65
+
66
+ # Generate embeddings for text using Gemini
67
+ # @param text [String, Array<String>] The text to embed
68
+ # @param model_name [String, nil] Optional model name override
69
+ # @param params [Hash] Additional parameters for embedding
70
+ # @return [Geminize::Models::EmbeddingResponse] The embedding response
71
+ def generate_gemini_embedding(text, model_name = nil, params = {})
72
+ Geminize.generate_embedding(text, model_name, params)
73
+ end
74
+
75
+ # Start a new conversation and store its ID in the session
76
+ # @param title [String, nil] Optional title for the conversation
77
+ # @param system_instruction [String, nil] Optional system instruction to guide model behavior
78
+ # @return [Geminize::Models::Conversation] The new conversation
79
+ def reset_gemini_conversation(title = nil, system_instruction = nil)
80
+ create_new_gemini_conversation(title, system_instruction)
81
+ end
82
+
83
+ # Set the system instruction for the current conversation
84
+ # @param system_instruction [String] The system instruction to set
85
+ # @return [Geminize::Models::Conversation] The updated conversation
86
+ def set_gemini_system_instruction(system_instruction)
87
+ current_gemini_conversation.system_instruction = system_instruction
88
+ Geminize.save_conversation(current_gemini_conversation)
89
+ current_gemini_conversation
90
+ end
91
+
92
+ private
93
+
94
+ # Create a new conversation and store its ID in the session
95
+ # @param title [String, nil] Optional title for the conversation
96
+ # @param system_instruction [String, nil] Optional system instruction to guide model behavior
97
+ # @return [Geminize::Models::Conversation] The new conversation
98
+ def create_new_gemini_conversation(title = nil, system_instruction = nil)
99
+ @current_gemini_conversation = Geminize.create_chat(title, system_instruction)
100
+ session[:gemini_conversation_id] = @current_gemini_conversation.id
101
+ Geminize.save_conversation(@current_gemini_conversation)
102
+ @current_gemini_conversation
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeminizeHelper
4
+ # Render the Gemini conversation as HTML
5
+ # @param conversation [Geminize::Models::Conversation, nil] Conversation to render (defaults to current_gemini_conversation)
6
+ # @param options [Hash] Optional rendering options
7
+ # @option options [String] :user_class CSS class for user messages
8
+ # @option options [String] :ai_class CSS class for AI messages
9
+ # @option options [Boolean] :include_timestamps Include message timestamps
10
+ # @return [String] HTML representation of the conversation
11
+ def render_gemini_conversation(conversation = nil, options = {})
12
+ conversation ||= current_gemini_conversation if respond_to?(:current_gemini_conversation)
13
+ return content_tag(:div, "No conversation available", class: "gemini-empty-conversation") unless conversation
14
+
15
+ content_tag(:div, class: "gemini-conversation") do
16
+ conversation.messages.map do |message|
17
+ render_gemini_message(message, options)
18
+ end.join.html_safe
19
+ end
20
+ end
21
+
22
+ # Render a single message from the conversation
23
+ # @param message [Geminize::Models::Message] The message to render
24
+ # @param options [Hash] Optional rendering options
25
+ # @return [String] HTML representation of the message
26
+ def render_gemini_message(message, options = {})
27
+ is_user = message.role == "user"
28
+
29
+ message_class = if is_user
30
+ options[:user_class] || "gemini-user-message"
31
+ else
32
+ options[:ai_class] || "gemini-ai-message"
33
+ end
34
+
35
+ content_tag(:div, class: "gemini-message #{message_class}") do
36
+ output = []
37
+
38
+ # Add the role label
39
+ output << content_tag(:div, is_user ? "You" : "AI", class: "gemini-message-role")
40
+
41
+ # Add the message content (convert newlines to <br> tags)
42
+ output << content_tag(:div, simple_format(message.parts.first["text"]), class: "gemini-message-content")
43
+
44
+ # Add timestamp if requested
45
+ if options[:include_timestamps] && message.respond_to?(:timestamp) && message.timestamp
46
+ timestamp = message.timestamp.is_a?(Time) ? message.timestamp : Time.parse(message.timestamp.to_s)
47
+ output << content_tag(:div, timestamp.strftime("%Y-%m-%d %H:%M:%S"), class: "gemini-message-timestamp")
48
+ end
49
+
50
+ output.join.html_safe
51
+ end
52
+ end
53
+
54
+ # Create a chat form that handles submitting messages to Gemini
55
+ # @param options [Hash] Form options
56
+ # @option options [String] :submit_text Text for the submit button (default: "Send")
57
+ # @option options [String] :placeholder Placeholder text (default: "Type your message...")
58
+ # @option options [String] :form_class CSS class for the form
59
+ # @option options [String] :input_class CSS class for the input field
60
+ # @option options [String] :submit_class CSS class for the submit button
61
+ # @option options [String] :url URL to submit the form to (default: current URL)
62
+ # @return [String] HTML form
63
+ def gemini_chat_form(options = {})
64
+ default_options = {
65
+ submit_text: "Send",
66
+ placeholder: "Type your message...",
67
+ form_class: "gemini-chat-form",
68
+ input_class: "gemini-chat-input",
69
+ submit_class: "gemini-chat-submit",
70
+ url: request.path
71
+ }
72
+
73
+ opts = default_options.merge(options)
74
+
75
+ form_tag(opts[:url], method: :post, class: opts[:form_class]) do
76
+ output = []
77
+ output << text_area_tag(:message, nil, placeholder: opts[:placeholder], class: opts[:input_class])
78
+ output << submit_tag(opts[:submit_text], class: opts[:submit_class])
79
+ output.join.html_safe
80
+ end
81
+ end
82
+
83
+ # Render Markdown text as HTML
84
+ # @param text [String] Markdown text to render
85
+ # @param options [Hash] Options for rendering
86
+ # @return [String] HTML content
87
+ def markdown_to_html(text, options = {})
88
+ return "" if text.blank?
89
+
90
+ # Check if the markdown gem is available
91
+ if defined?(Redcarpet)
92
+ renderer = Redcarpet::Render::HTML.new(hard_wrap: true, filter_html: false)
93
+ markdown = Redcarpet::Markdown.new(renderer, options)
94
+ markdown.render(text).html_safe
95
+ else
96
+ # Fall back to simple formatting if Redcarpet is not available
97
+ simple_format(text)
98
+ end
99
+ end
100
+
101
+ # Add syntax highlighting to code blocks
102
+ # @param html [String] HTML content that may contain code blocks
103
+ # @return [String] HTML with syntax highlighting applied
104
+ def highlight_code(html)
105
+ return html unless defined?(Rouge)
106
+
107
+ formatter = Rouge::Formatters::HTML.new
108
+
109
+ # Find all code blocks and apply syntax highlighting
110
+ doc = Nokogiri::HTML::DocumentFragment.parse(html)
111
+ doc.css("pre code").each do |code|
112
+ lang = code["class"]&.sub("language-", "") || "text"
113
+ lexer = Rouge::Lexer.find(lang) || Rouge::Lexers::PlainText.new
114
+
115
+ # Get the code content and format it
116
+ code_text = code.text
117
+ highlighted = formatter.format(lexer.lex(code_text))
118
+
119
+ # Replace the original code block with the highlighted version
120
+ code.parent.replace("<pre class=\"highlight #{lang}\">#{highlighted}</pre>")
121
+ end
122
+
123
+ doc.to_html.html_safe
124
+ end
125
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Rails
5
+ # Module for adding Geminize functionality to controllers
6
+ # Provides methods that make it easy to include Geminize controller concerns.
7
+ #
8
+ # This module is automatically included in ActionController::Base
9
+ # when the gem is used in a Rails application.
10
+ #
11
+ # @example Including in a specific controller
12
+ # class ChatController < ApplicationController
13
+ # geminize_controller
14
+ #
15
+ # def create
16
+ # @response = send_gemini_message(params[:message])
17
+ # render :show
18
+ # end
19
+ # end
20
+ module ControllerAdditions
21
+ # Add Geminize functionality to a controller
22
+ # This method includes the Geminize::Controller concern in your controller,
23
+ # which provides methods for working with the Gemini API.
24
+ #
25
+ # @return [void]
26
+ # @example
27
+ # class ApplicationController < ActionController::Base
28
+ # include Geminize::Rails::ControllerAdditions
29
+ # geminize_controller
30
+ # end
31
+ def geminize_controller
32
+ include Geminize::Controller
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # Add the module to ActionController::Base if it exists
39
+ ActiveSupport.on_load(:action_controller) do
40
+ include Geminize::Rails::ControllerAdditions
41
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Rails
5
+ # Rails engine for Geminize
6
+ # Provides Rails integration for the Gemini API.
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace Geminize
9
+
10
+ initializer "geminize.configure" do |app|
11
+ # Set up configuration if needed
12
+ end
13
+
14
+ initializer "geminize.load_concerns" do
15
+ ActiveSupport.on_load(:action_controller) do
16
+ require "geminize/rails/app/controllers/concerns/geminize/controller"
17
+ end
18
+
19
+ ActiveSupport.on_load(:action_view) do
20
+ require "geminize/rails/app/helpers/geminize_helper"
21
+ end
22
+ end
23
+
24
+ config.to_prepare do
25
+ # Load any dependencies that need to be available
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Rails
5
+ # Module for adding Geminize view helpers to Rails applications
6
+ # Provides methods to simplify the inclusion of Geminize helpers in views.
7
+ #
8
+ # This module is automatically included in ActionView::Base
9
+ # when the gem is used in a Rails application.
10
+ #
11
+ # @example Using Geminize helpers in a view
12
+ # <%# After including the helpers in ApplicationHelper %>
13
+ # <%= render_gemini_conversation %>
14
+ # <%= gemini_chat_form %>
15
+ module HelperAdditions
16
+ # Add Geminize helpers to views
17
+ # This method includes the GeminizeHelper module in your view context,
18
+ # providing helper methods for rendering Gemini conversations, chat forms,
19
+ # and formatting responses.
20
+ #
21
+ # @return [void]
22
+ # @example
23
+ # module ApplicationHelper
24
+ # include Geminize::Rails::HelperAdditions
25
+ # geminize_helper
26
+ # end
27
+ def geminize_helper
28
+ include GeminizeHelper
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Add our helpers module to ActionView::Base if it exists
35
+ ActiveSupport.on_load(:action_view) do
36
+ include Geminize::Rails::HelperAdditions
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "geminize/rails/engine" if defined?(::Rails::Engine)
4
+ require "geminize/railtie" if defined?(::Rails::Railtie)
5
+ require "geminize/rails/controller_additions" if defined?(::ActionController::Base)
6
+ require "geminize/rails/helper_additions" if defined?(::ActionView::Base)
7
+
8
+ module Geminize
9
+ # Rails integration module for Geminize
10
+ # Provides Rails integration for the Google Gemini API.
11
+ #
12
+ # The integration includes:
13
+ # - A Rails engine to load all required components
14
+ # - Controller concerns with helper methods for Gemini operations
15
+ # - View helpers for rendering conversations and responses
16
+ # - Generators for creating configuration files
17
+ #
18
+ # @example Setting up in a Rails application
19
+ # # In config/initializers/geminize.rb (created by the generator)
20
+ # Geminize.configure do |config|
21
+ # config.api_key = ENV.fetch("GEMINI_API_KEY")
22
+ # end
23
+ #
24
+ # # In app/controllers/chat_controller.rb
25
+ # class ChatController < ApplicationController
26
+ # geminize_controller
27
+ #
28
+ # def create
29
+ # @response = send_gemini_message(params[:message])
30
+ # redirect_to chat_path
31
+ # end
32
+ # end
33
+ #
34
+ # # In app/views/chat/show.html.erb
35
+ # <%= render_gemini_conversation %>
36
+ # <%= gemini_chat_form %>
37
+ module Rails
38
+ # Returns true if running in a Rails environment
39
+ # Useful for conditionally executing code only in Rails apps.
40
+ #
41
+ # @return [Boolean] true if Rails is defined
42
+ # @example
43
+ # if Geminize::Rails.rails?
44
+ # # Rails-specific code
45
+ # end
46
+ def self.rails?
47
+ defined?(::Rails)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ # Railtie for lightweight Rails integration
5
+ # Provides basic Rails integration when the full engine is not required.
6
+ # This is automatically loaded when the gem is used in a Rails application.
7
+ #
8
+ # @example Basic usage in a Rails application
9
+ # # No additional setup needed - Railtie is loaded automatically
10
+ # # In your controller:
11
+ # class ExamplesController < ApplicationController
12
+ # def example
13
+ # @response = Geminize.generate_text("What is Ruby on Rails?").text
14
+ # end
15
+ # end
16
+ class Railtie < ::Rails::Railtie
17
+ # Configure Geminize when used in a Rails application
18
+ # @return [void]
19
+ initializer "geminize.configure" do |app|
20
+ # Set up configuration for Geminize when used in a Rails app
21
+ Geminize.configure do |config|
22
+ # Set conversations path to Rails tmp directory by default
23
+ config.conversations_path = Rails.root.join("tmp", "conversations") if config.conversations_path.nil?
24
+ end
25
+ end
26
+
27
+ # Set up any rake tasks if needed
28
+ # @return [void]
29
+ rake_tasks do
30
+ # Define rake tasks here if needed
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ # Utility module for building requests to the Gemini API
5
+ module RequestBuilder
6
+ class << self
7
+ # Build a text generation request for the Gemini API
8
+ # @param content_request [Geminize::Models::ContentRequest] The content request
9
+ # @return [Hash] The complete request hash ready to send to the API
10
+ def build_text_generation_request(content_request)
11
+ model_name = content_request.model_name
12
+ Validators.validate_not_empty!(model_name, "Model name")
13
+
14
+ {
15
+ model: model_name,
16
+ **content_request.to_hash
17
+ }
18
+ end
19
+
20
+ # Build a chat request for the Gemini API
21
+ # @param chat_request [Geminize::Models::ChatRequest] The chat request
22
+ # @param message_history [Array<Hash>] The message history from the conversation
23
+ # @return [Hash] The complete request hash ready to send to the API
24
+ def build_chat_request(chat_request, message_history = [])
25
+ model_name = chat_request.model_name
26
+ Validators.validate_not_empty!(model_name, "Model name")
27
+
28
+ {
29
+ model: model_name,
30
+ **chat_request.to_hash(message_history)
31
+ }
32
+ end
33
+
34
+ # Build a complete API endpoint path for a model
35
+ # @param model_name [String] The name of the model
36
+ # @param action [String] The action to perform (e.g., "generateContent")
37
+ # @return [String] The complete API endpoint path
38
+ def build_model_endpoint(model_name, action)
39
+ "models/#{model_name}:#{action}"
40
+ end
41
+
42
+ # Build the text generation endpoint for a specific model
43
+ # @param model_name [String] The name of the model
44
+ # @return [String] The complete API endpoint path for text generation
45
+ def build_text_generation_endpoint(model_name)
46
+ build_model_endpoint(model_name, "generateContent")
47
+ end
48
+
49
+ # Build the streaming text generation endpoint for a specific model
50
+ # @param model_name [String] The name of the model
51
+ # @return [String] The complete API endpoint path for streaming text generation
52
+ def build_streaming_endpoint(model_name)
53
+ build_model_endpoint(model_name, "streamGenerateContent")
54
+ end
55
+ end
56
+ end
57
+ end