appchat 0.0.4 → 0.0.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f741156bd18664bfb66a4700b633dad255bc2c5e41cacc6b297acbc6d67492b8
4
- data.tar.gz: d4612a8a1ee88a6caf7bc4e039cf74fa44b369fe79b5d595b0e9544329749819
3
+ metadata.gz: 4aca406ace060a7c7b9af445e20d5be1f3605614109320792523cba09b7f92fe
4
+ data.tar.gz: 8c1e290b46dfca74c66c94748d305386d67703291e455c066aed1df0d1dc5cc8
5
5
  SHA512:
6
- metadata.gz: '0959e29a0ed53649f7f51523e5b4c6f4089b05449f8649165ed7bf7505bdb111774e0c24661e5344f0752df1fa08a38b250a3fe911543530adb7f8a3020f2bd6'
7
- data.tar.gz: '08f3d14d68ea6c2cf9c50da6842051b78bafa250bf58fc77475e09c32090344d06fe4d98205294aa08e9331bf1817e8d5b23213da001b136dd1fb2101c98aca8'
6
+ metadata.gz: 5aaa00c36101e08cb562924a7a3a0cca7da5ef49680eb6f9f202a8d17ebe0cbbe852fe86ef4481cb65ef8c2811d9933731074363875930ccb916e92d9fdd66d4
7
+ data.tar.gz: 876249a02dbec15d9d3ee4c9dbabcfbcc843970f8d85d7b4e9fad563a97af8a86c1a3600c7383ef3dea9fc2bfffef4c19f3fef70cb7009f0fc0a12401efbb462
@@ -9,28 +9,45 @@ class AppchatGenerator < Rails::Generators::Base
9
9
  source_root File.expand_path('templates', __dir__)
10
10
 
11
11
  def add_gems
12
- unless gem_exists?('ollama-ai')
13
- append_to_file 'Gemfile', "\ngem 'ollama-ai'\n"
14
- end
15
-
16
- unless gem_exists?('tailwindcss-rails')
17
- append_to_file 'Gemfile', "\ngem 'tailwindcss-rails'\n"
12
+ gems = %w(
13
+ ollama-ai
14
+ tailwindcss-rails
15
+ watir
16
+ )
17
+
18
+ gems.each do |gem|
19
+ unless gem_exists?(gem)
20
+ append_to_file 'Gemfile', "\ngem '#{gem}'\n"
21
+ end
18
22
  end
19
23
 
20
24
  Bundler.with_unbundled_env do
21
25
  run 'bundle install'
22
26
  end
27
+ end
28
+
29
+ def install_tailwind
23
30
  run 'rails tailwindcss:install'
24
31
  end
25
32
 
26
- def generate_scaffolds
27
- generate "model", "Chat context:text"
33
+ def set_routes
28
34
  route "resources :chats"
29
- generate "scaffold", "Message chat:references content:text role:integer"
35
+ route "resources :messages"
30
36
  end
31
37
 
32
- def create_views
38
+ def generate_models
39
+ generate "model", "Chat context:text"
40
+ generate "model", "Message chat:references content:text role:integer status:string"
41
+ generate "model", "AppchatFunction name:string description:text class_name:string"
42
+ generate "model", "FunctionParameter appchat_function:references name:string example_value:string"
43
+ end
44
+
45
+ def create_controllers
33
46
  copy_file "chats_controller.rb", "app/controllers/chats_controller.rb"
47
+ copy_file "messages_controller.rb", "app/controllers/messages_controller.rb", force: true
48
+ end
49
+
50
+ def create_views
34
51
  copy_file "chats/chat.html.erb", "app/views/chats/_chat.html.erb", force: true
35
52
  copy_file "chats/index.html.erb", "app/views/chats/index.html.erb", force: true
36
53
  copy_file "chats/show.html.erb", "app/views/chats/show.html.erb", force: true
@@ -38,21 +55,30 @@ class AppchatGenerator < Rails::Generators::Base
38
55
  copy_file "messages/new.html.erb", "app/views/messages/new.html.erb", force: true
39
56
  copy_file "messages/message.html.erb", "app/views/messages/_message.html.erb", force: true
40
57
  copy_file "messages/_typing_bubbles.html.erb", "app/views/messages/_typing_bubbles.html.erb", force: true
58
+ end
59
+
60
+ def create_stylesheets
41
61
  copy_file "assets/appchat.tailwind.css", "app/assets/stylesheets/appchat.tailwind.css", force: true
62
+ end
63
+
64
+ def create_stimulus_controllers
42
65
  copy_file "javascript/chat_message_controller.js", "app/javascript/controllers/chat_message_controller.js"
66
+ copy_file "javascript/speech_to_text_controller.js", "app/javascript/controllers/speech_to_text_controller.js"
43
67
  end
44
68
 
45
69
  def copy_models
46
70
  copy_file "models/message.rb", "app/models/message.rb", force: true
47
71
  copy_file "models/chat.rb", "app/models/chat.rb", force: true
72
+ copy_file "models/appchat_function.rb", "app/models/appchat_function.rb", force: true
48
73
  end
49
74
 
50
- def serialize_context
51
- inject_into_class 'app/models/chat.rb', 'Chat', "serialize :context, coder:JSON, type: Array\n"
75
+ def copy_services
76
+ copy_file "services/appchat_function_service.rb", "app/services/appchat_function_service.rb", force: true
77
+ copy_file "services/web_search_service.rb", "app/services/web_search_service.rb", force: true
52
78
  end
53
79
 
54
- def set_associations
55
- inject_into_class 'app/models/chat.rb', 'Chat', " has_many :messages, dependent: :destroy\n"
80
+ def serialize_context
81
+ inject_into_class 'app/models/chat.rb', 'Chat', "serialize :context, coder:JSON, type: Array\n"
56
82
  end
57
83
 
58
84
  def run_migrations
@@ -63,8 +89,22 @@ class AppchatGenerator < Rails::Generators::Base
63
89
  copy_file "get_ai_response_job.rb", "app/jobs/get_ai_response_job.rb"
64
90
  end
65
91
 
66
- def create_messages_controller
67
- copy_file "messages_controller.rb", "app/controllers/messages_controller.rb", force: true
92
+ def swap_class_in_layout
93
+ layout_file = "app/views/layouts/application.html.erb"
94
+
95
+ if File.exist?(layout_file)
96
+ gsub_file layout_file, /\bmt-28\b/, "mt-10"
97
+ else
98
+ say "Layout file not found. No changes were made.", :red
99
+ end
100
+ end
101
+
102
+ def copy_rake_tasks
103
+ copy_file "tasks/create_appchat_function.rake", "lib/tasks/create_appchat_function.rake"
104
+ end
105
+
106
+ def create_functions
107
+ rake "appchat_function:create_web_search"
68
108
  end
69
109
 
70
110
  def show_art
@@ -59,4 +59,18 @@
59
59
  }
60
60
  }
61
61
 
62
+ #microphone-button.recording {
63
+ animation: flash 1s infinite;
64
+ }
65
+
66
+ @keyframes flash {
67
+ 0%, 100% {
68
+ opacity: 80;
69
+ background-color: #EE4B2B; /* Ensure the color remains during the flash */
70
+ }
71
+ 50% {
72
+ opacity: 0.5;
73
+ background-color: #EE4B2B; /* Ensure the color remains during the flash */
74
+ }
75
+ }
62
76
  }
@@ -1,5 +1,5 @@
1
1
  <%= link_to chat, data: { turbo_frame: :chat } do %>
2
- <div class="w-full text-gray-100 hover:text-green-500">
3
- <%= chat&.messages&.first&.content || "New AI chat conversation" %>
4
- </div>
2
+ <div class="w-full text-gray-100 hover:text-green-500">
3
+ <%= chat&.messages&.first&.content&.truncate(45) || "New AI chat conversation" %>
4
+ </div>
5
5
  <% end %>
@@ -3,20 +3,23 @@
3
3
  background-color: black;
4
4
  }
5
5
  </style>
6
+
6
7
  <div class="flex w-full">
7
- <%= turbo_stream_from :chats %>
8
- <div class="max-w-sm w-full h-full flex flex-col gap-4 p-2 w-full flex-shrink-0 bg-[#2e2e2e] rounded-lg" id="chats">
9
- <%= link_to chats_path, data: { turbo_method: :post, turbo_frame: :chat }, class: "flex gap-2 justify-center items-center bg-gray-500 text-gray-100 rounded-lg p-2 text-center mr-auto" do %>
10
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
11
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
12
- </svg>
13
- <span> Create Chat </span>
14
- <% end %>
15
- <% @chats.each do |chat| %>
16
- <%= render "chat", chat: chat %>
17
- <% end %>
18
- </div>
19
- <div class="w-full ml-4">
20
- <%= turbo_frame_tag :chat, src: ( chat_path(@chats.first) if @chats.any? ) %>
21
- </div>
8
+ <%= turbo_stream_from :chats %>
9
+ <div class="max-w-sm w-full h-full flex flex-col gap-4 p-2 w-full flex-shrink-0 bg-[#2e2e2e] rounded-lg" id="chats">
10
+ <%= link_to chats_path, data: { turbo_method: :post, turbo_frame: :chat }, class: "flex gap-2 justify-center items-center bg-gray-500 text-gray-100 rounded-lg p-2 text-center mr-auto" do %>
11
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
12
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
13
+ </svg>
14
+ <span> Create Chat </span>
15
+ <% end %>
16
+ <div class="h-[70vh] overflow-y-hidden overflow-y-scroll">
17
+ <% @chats.each do |chat| %>
18
+ <%= render "chat", chat: chat %>
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+ <div class="w-full ml-4">
23
+ <%= turbo_frame_tag :chat, src: ( chat_path(@chats.first) if @chats.any? ) %>
24
+ </div>
22
25
  </div>
@@ -3,7 +3,7 @@ class ChatsController < ApplicationController
3
3
  @chat = Chat.find(params[:id])
4
4
  end
5
5
  def index
6
- @chats = Chat.all
6
+ @chats = Chat.all.reverse
7
7
  end
8
8
  def create
9
9
  @chat = Chat.create
@@ -1,27 +1,33 @@
1
1
  class GetAiResponseJob < ApplicationJob
2
2
  queue_as :default
3
3
 
4
- attr_reader :chat, :user_prompt
4
+ attr_reader :client, :chat, :user_prompt, :informed_prompt, :message
5
5
 
6
6
  def perform(chat_id, user_prompt)
7
+ @client = new_client
7
8
  @chat = Chat.find(chat_id)
8
9
  @user_prompt = user_prompt
10
+ @message = chat.messages.create(role: 'assistant')
11
+ appchat_functions
9
12
  call_ollama
10
13
  end
11
14
 
12
15
  private
13
16
 
14
- def call_ollama
15
- client = Ollama.new(
17
+ def new_client
18
+ Ollama.new(
16
19
  credentials: { address: 'http://localhost:11434' },
17
20
  options: { server_sent_events: true }
18
21
  )
19
- message = chat.messages.create(role: 'assistant')
22
+ end
23
+
24
+ def call_ollama
25
+ prompt = informed_prompt || user_prompt
20
26
 
21
27
  response = client.generate(
22
28
  {
23
29
  model: 'llama3.1',
24
- prompt: user_prompt,
30
+ prompt: prompt,
25
31
  context: chat.context,
26
32
  stream: true,
27
33
  }
@@ -35,4 +41,20 @@ class GetAiResponseJob < ApplicationJob
35
41
  end
36
42
  chat.update(context: response.last["context"])
37
43
  end
44
+
45
+ def appchat_functions
46
+ appchat_function_service = AppchatFunctionService.new(chat.id, user_prompt).run
47
+ if appchat_function_service["match"] == "true" && appchat_function_service["appchat_function"].in?(AppchatFunction.pluck(:class_name))
48
+ puts "Function Match Found! --> #{appchat_function_service}"
49
+ function_class = appchat_function_service["appchat_function"].constantize
50
+ function_response = function_class.new(appchat_function_service["parameters"]).run do |status|
51
+ message.update(status: status)
52
+ end
53
+
54
+ return if function_response.nil?
55
+ @informed_prompt = "#{ user_prompt }, base your response on this data:
56
+ #{ appchat_function_service["name"] } responded with: #{ function_response },
57
+ :current_time => #{Date.current}"
58
+ end
59
+ end
38
60
  end
@@ -0,0 +1,65 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="speech-to-text"
4
+ export default class extends Controller {
5
+ static targets = ["microphoneButton", "chatInput"]
6
+
7
+ connect() {
8
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
9
+ if (SpeechRecognition) {
10
+ this.microphoneButtonTarget.classList.remove("hidden")
11
+ } else {
12
+ return
13
+ }
14
+
15
+ this.recognition = new SpeechRecognition();
16
+ this.recognition.continuous = false;
17
+ this.recognition.lang = 'en-US';
18
+ this.recognition.interimResults = false;
19
+ this.recognition.maxAlternatives = 1;
20
+
21
+ this.recognition.onresult = this.handleResult.bind(this);
22
+ this.recognition.onerror = this.handleError.bind(this);
23
+ this.recognition.onstart = this.handleStart.bind(this);
24
+ this.recognition.onend = this.handleEnd.bind(this);
25
+
26
+ this.isRecognizing = false; // Track if recognition is active
27
+ }
28
+
29
+ startRecognition(event) {
30
+ event.preventDefault();
31
+ if (this.isRecognizing) {
32
+ console.log("Recognition already started");
33
+ return;
34
+ }
35
+
36
+ try {
37
+ this.recognition.start();
38
+ this.isRecognizing = true;
39
+ } catch (error) {
40
+ console.error("Recognition start failed:", error);
41
+ this.isRecognizing = false;
42
+ }
43
+ }
44
+
45
+ handleStart() {
46
+ this.microphoneButtonTarget.classList.add("recording");
47
+ }
48
+
49
+ handleEnd() {
50
+ this.microphoneButtonTarget.classList.remove("recording");
51
+ this.isRecognizing = false;
52
+ }
53
+
54
+ handleResult(event) {
55
+ const transcript = event.results[0][0].transcript;
56
+ this.chatInputTarget.value += ` ${transcript}`;
57
+ this.recognition.stop();
58
+ }
59
+
60
+ handleError(event) {
61
+ console.error(`Error occurred in recognition: ${event.error}`);
62
+ this.isRecognizing = false;
63
+ alert(`Speech recognition error: ${event.error}`);
64
+ }
65
+ }
@@ -1,5 +1,7 @@
1
1
  <div data-chat-message-target="message" id="<%= dom_id(message) %>" class="bg-gray-200 text-gray-900 shadow-lg p-4 rounded-lg">
2
- <% if message.content? %>
2
+ <% if message.status? && !message.content? %>
3
+ <%= message.status %>
4
+ <% elsif message.content? %>
3
5
  <%= message.content %>
4
6
  <% else %>
5
7
  <%= render 'messages/typing_bubbles' %>
@@ -1,16 +1,39 @@
1
1
  <%= turbo_frame_tag :new_message do %>
2
2
  <%= form_with model: @chat.messages.new, url: messages_path(chat_id: @chat.id) do |f| %>
3
- <div class="w-full flex items-center gap-2">
4
- <div class="flex flex-col w-full">
5
- <%= f.label :content, "What do you want to ask?" %>
6
- <%= f.text_area :content, class: "w-full" %>
7
- </div>
8
- <%= button_tag class: "mt-4 flex p-2 rounded-lg bg-gradient-to-r from-indigo-500 to-blue-500 text-blue-100 font-semibold" do %>
9
- Send Message
10
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
11
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
12
- </svg>
13
- <% end %>
14
- </div>
3
+ <div class="w-full flex items-center gap-2 mt-4">
4
+ <div class="flex flex-col w-full" data-controller="speech-to-text">
5
+ <div class="relative w-full">
6
+ <%= f.text_area :content, class: "w-full pr-32 rounded-lg", "data-speech-to-text-target":"chatInput" %>
7
+ <button id="microphone-button"
8
+ data-speech-to-text-target="microphoneButton"
9
+ data-speech-to-text-recording-class="recording"
10
+ data-action="click->speech-to-text#startRecognition"
11
+ class="absolute right-14 top-1/2 transform -translate-y-1/2 p-1 mr-2 bg-gray-200 rounded-full hidden">
12
+ <svg
13
+ fill="#000000"
14
+ height="40px"
15
+ width="40px"
16
+ version="1.1"
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ viewBox="0 0 512 512"
19
+ xmlns:xlink="http://www.w3.org/1999/xlink"
20
+ enable-background="new 0 0 512 512">
21
+ <g>
22
+ <g>
23
+ <path d="m439.5,236c0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,70-64,126.9-142.7,126.9-78.7,0-142.7-56.9-142.7-126.9 0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,86.2 71.5,157.4 163.1,166.7v57.5h-23.6c-11.3,0-20.4,9.1-20.4,20.4 0,11.3 9.1,20.4 20.4,20.4h88c11.3,0 20.4-9.1 20.4-20.4 0-11.3-9.1-20.4-20.4-20.4h-23.6v-57.5c91.6-9.3 163.1-80.5 163.1-166.7z"/>
24
+ <path d="m256,323.5c51,0 92.3-41.3 92.3-92.3v-127.9c0-51-41.3-92.3-92.3-92.3s-92.3,41.3-92.3,92.3v127.9c0,51 41.3,92.3 92.3,92.3zm-52.3-220.2c0-28.8 23.5-52.3 52.3-52.3s52.3,23.5 52.3,52.3v127.9c0,28.8-23.5,52.3-52.3,52.3s-52.3-23.5-52.3-52.3v-127.9z"/>
25
+ </g>
26
+ </g>
27
+ </svg>
28
+ </button>
29
+ <%= button_tag class: "absolute top-1 right-0 flex p-2 rounded-lg text-gray-900 font-semibold" do %>
30
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-10">
31
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
32
+ </svg>
33
+ <% end %>
34
+ </div>
35
+ </div>
36
+
37
+ </div>
15
38
  <% end %>
16
39
  <% end %>
@@ -0,0 +1,18 @@
1
+ class AppchatFunction < ApplicationRecord
2
+ has_many :function_parameters, dependent: :destroy
3
+
4
+ def to_prompt_hash
5
+ {
6
+ name: name,
7
+ description: description,
8
+ class_name: class_name,
9
+ parameters: function_parameters.each_with_object({}) do |param, hash|
10
+ hash[param.name] = param.example_value
11
+ end
12
+ }
13
+ end
14
+
15
+ def self.all_to_prompt_json
16
+ all.includes(:function_parameters).map(&:to_prompt_hash).to_json
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ class AppchatFunctionService
2
+ attr_reader :chat, :user_prompt, :response_json
3
+
4
+ def initialize(chat_id, user_prompt)
5
+ @chat = Chat.find(chat_id)
6
+ @user_prompt = user_prompt
7
+ end
8
+
9
+ def run
10
+ call_ollama
11
+ response_json
12
+ end
13
+
14
+ def call_ollama
15
+ client = Ollama.new(
16
+ credentials: { address: 'http://localhost:11434' },
17
+ options: { server_sent_events: true }
18
+ )
19
+ response = client.generate(
20
+ {
21
+ model: 'llama3.1',
22
+ prompt: prompt,
23
+ context: chat.context,
24
+ "format": "json"
25
+ }
26
+ )
27
+ @response_json = JSON.parse(response.map { |r| r["response"] }.join)
28
+ end
29
+ def prompt
30
+ <<-PROMPT
31
+ Evaluate the following user prompt in chat context.
32
+ Your goal is to determine whether the user prompt is requesting or requires additional information from any of the available services.
33
+ - If the prompt is a general greeting, casual remark, or something that can be answered without further information, respond with JSON { 'match' => 'false' }.
34
+ - If the prompt asks for specific information, requires a search, or needs a service to generate the correct response, then respond with JSON like this example:
35
+ {
36
+ 'match' => 'true',
37
+ 'appchat_function' => 'WebSearchService',
38
+ 'parameters' => {
39
+ 'query' => 'a query based on the prompt and context'
40
+ }
41
+ }
42
+
43
+ Here is the user's prompt: #{user_prompt}
44
+ Here are the Available Services: #{ AppchatFunction.all_to_prompt_json }
45
+ PROMPT
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'watir'
2
+ require 'cgi'
3
+
4
+ class WebSearchService
5
+ attr_reader :query
6
+
7
+ def initialize(args)
8
+ @query = args["query"]
9
+ end
10
+
11
+ def run
12
+ yield("Searching Google for #{@query}") if block_given?
13
+
14
+ browser = Watir::Browser.new :chrome, headless: true
15
+ search_url = "https://www.google.com/search?q=#{CGI.escape(query)}"
16
+ browser.goto(search_url)
17
+ response = "browser_text: #{browser.text} browser_links: #{browser.links}"
18
+ browser.close
19
+ response
20
+ rescue Selenium::WebDriver::Error::UnknownError => e
21
+ "Error: #{e.message}"
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ # lib/tasks/create_appchat_function.rake
2
+ namespace :appchat_function do
3
+ desc "Create an AppchatFunction with name 'Web Search', class name 'WebSearchService', and a description"
4
+ task create_web_search: :environment do
5
+ af = AppchatFunction.create!(
6
+ name: "Web Search",
7
+ class_name: "WebSearchService",
8
+ description: "Searches Google with a user's query and returns the page text and links"
9
+ )
10
+ puts "AppchatFunction 'Web Search' created successfully."
11
+ af.function_parameters.create(name: 'query', example_value: 'Dog friendly vegan resturants in Austin, TX')
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appchat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - hackliteracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-26 00:00:00.000000000 Z
11
+ date: 2024-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.3.0
69
- description: The best and easiest framework for adding chats
69
+ description: The best and easiest framework for adding AI chats
70
70
  email: hackliteracy@gmail.com
71
71
  executables: []
72
72
  extensions: []
@@ -80,13 +80,18 @@ files:
80
80
  - lib/generators/appchat/templates/chats_controller.rb
81
81
  - lib/generators/appchat/templates/get_ai_response_job.rb
82
82
  - lib/generators/appchat/templates/javascript/chat_message_controller.js
83
+ - lib/generators/appchat/templates/javascript/speech_to_text_controller.js
83
84
  - lib/generators/appchat/templates/messages/_typing_bubbles.html.erb
84
85
  - lib/generators/appchat/templates/messages/index.html.erb
85
86
  - lib/generators/appchat/templates/messages/message.html.erb
86
87
  - lib/generators/appchat/templates/messages/new.html.erb
87
88
  - lib/generators/appchat/templates/messages_controller.rb
89
+ - lib/generators/appchat/templates/models/appchat_function.rb
88
90
  - lib/generators/appchat/templates/models/chat.rb
89
91
  - lib/generators/appchat/templates/models/message.rb
92
+ - lib/generators/appchat/templates/services/appchat_function_service.rb
93
+ - lib/generators/appchat/templates/services/web_search_service.rb
94
+ - lib/generators/appchat/templates/tasks/create_appchat_function.rake
90
95
  homepage: https://rubygems.org/gems/appchat
91
96
  licenses:
92
97
  - MIT
@@ -110,5 +115,5 @@ requirements: []
110
115
  rubygems_version: 3.5.17
111
116
  signing_key:
112
117
  specification_version: 4
113
- summary: Appchat makes it easy to add a chat to your app
118
+ summary: Appchat makes it easy to add an AI chat to your app
114
119
  test_files: []