appchat 0.0.5 → 0.0.7

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: 3071421a8c3c49e11f5c5186b9f17a00bd37d45229d0d95a3b466ae9b9032fee
4
- data.tar.gz: 79284185b56a3efaeb60e1ec1ad144ada532fd499e686b4604db7a29aabccd16
3
+ metadata.gz: 0f2e478412edcf3880d15f39ad4aeedbbf73a1a4819c0b270cf1505d3c47b826
4
+ data.tar.gz: 4cc000f36710cda913c9474e04c3c55fc221170811fde494392cb6d19c3795ac
5
5
  SHA512:
6
- metadata.gz: 9e642582fe817de68a171c14b272a0b0ceb2257b96021ae9a51e630c5ef562ed982c452fdc9694c2d6e6c81378720f636e0e8218f51d1704134d383602cf2df7
7
- data.tar.gz: a71996c59642154416cb3178b9406d44242b5626ab7069aeb6341a15e1062d6c297131dfd19de2455c7e59be0cbf8af8c43e6daa0879afb269c001a88723f0a3
6
+ metadata.gz: c6d2310e575c27c6401a2a74afe7c099c9e7dff546dd1220781dbcce74482e2f61af439c9634c1e06ae5c0589f2873ba919a9552f306507e0b2a03dcdaca2c0e
7
+ data.tar.gz: 2b5b16b39b719650c33ef334f3ceb415fcc445c4a7fa28284b19969ee2a8fd1d69735208148d961c4ebb5852bda67cb8e65a5d29830e1249d35cab53cc64141b
@@ -9,17 +9,23 @@ 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
+ )
16
+
17
+ gems.each do |gem|
18
+ unless gem_exists?(gem)
19
+ append_to_file 'Gemfile', "\ngem '#{gem}'\n"
20
+ end
18
21
  end
19
22
 
20
23
  Bundler.with_unbundled_env do
21
24
  run 'bundle install'
22
25
  end
26
+ end
27
+
28
+ def install_tailwind
23
29
  run 'rails tailwindcss:install'
24
30
  end
25
31
 
@@ -30,7 +36,10 @@ class AppchatGenerator < Rails::Generators::Base
30
36
 
31
37
  def generate_models
32
38
  generate "model", "Chat context:text"
33
- generate "model", "Message chat:references content:text role:integer"
39
+ generate "model", "Message chat:references content:text role:integer status:string"
40
+ generate "model", "AppchatFunction name:string description:text class_name:string"
41
+ generate "model", "FunctionParameter appchat_function:references name:string example_value:string"
42
+ generate "model", "FunctionLog message:references name:string prompt:text results:text"
34
43
  end
35
44
 
36
45
  def create_controllers
@@ -46,6 +55,7 @@ class AppchatGenerator < Rails::Generators::Base
46
55
  copy_file "messages/new.html.erb", "app/views/messages/new.html.erb", force: true
47
56
  copy_file "messages/message.html.erb", "app/views/messages/_message.html.erb", force: true
48
57
  copy_file "messages/_typing_bubbles.html.erb", "app/views/messages/_typing_bubbles.html.erb", force: true
58
+ copy_file "messages/_function_logs.html.erb", "app/views/messages/_function_logs.html.erb", force: true
49
59
  end
50
60
 
51
61
  def create_stylesheets
@@ -55,19 +65,22 @@ class AppchatGenerator < Rails::Generators::Base
55
65
  def create_stimulus_controllers
56
66
  copy_file "javascript/chat_message_controller.js", "app/javascript/controllers/chat_message_controller.js"
57
67
  copy_file "javascript/speech_to_text_controller.js", "app/javascript/controllers/speech_to_text_controller.js"
68
+ copy_file "javascript/toggle_controller.js", "app/javascript/controllers/toggle_controller.js"
58
69
  end
59
70
 
60
71
  def copy_models
61
72
  copy_file "models/message.rb", "app/models/message.rb", force: true
62
73
  copy_file "models/chat.rb", "app/models/chat.rb", force: true
74
+ copy_file "models/appchat_function.rb", "app/models/appchat_function.rb", force: true
63
75
  end
64
76
 
65
- def serialize_context
66
- inject_into_class 'app/models/chat.rb', 'Chat', "serialize :context, coder:JSON, type: Array\n"
77
+ def copy_services
78
+ copy_file "services/appchat_function_service.rb", "app/services/appchat_function_service.rb", force: true
79
+ copy_file "services/web_search_service.rb", "app/services/web_search_service.rb", force: true
67
80
  end
68
81
 
69
- def set_associations
70
- inject_into_class 'app/models/chat.rb', 'Chat', " has_many :messages, dependent: :destroy\n"
82
+ def serialize_context
83
+ inject_into_class 'app/models/chat.rb', 'Chat', "serialize :context, coder:JSON, type: Array\n"
71
84
  end
72
85
 
73
86
  def run_migrations
@@ -88,6 +101,14 @@ class AppchatGenerator < Rails::Generators::Base
88
101
  end
89
102
  end
90
103
 
104
+ def copy_rake_tasks
105
+ copy_file "tasks/create_appchat_function.rake", "lib/tasks/create_appchat_function.rake"
106
+ end
107
+
108
+ def create_functions
109
+ rake "appchat_function:create_web_search"
110
+ end
111
+
91
112
  def show_art
92
113
  puts <<-ART
93
114
 
@@ -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,21 @@ 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
+
50
+ function_log = message.function_logs.create(name: appchat_function_service["appchat_function"], prompt: appchat_function_service["parameters"])
51
+ function_class = appchat_function_service["appchat_function"].constantize
52
+ function_response = function_class.new(appchat_function_service["parameters"]).run do |status|
53
+ message.update(status: status)
54
+ end
55
+
56
+ return if function_response.nil?
57
+ function_log.update(results: function_response)
58
+ @informed_prompt = "The user prompted: #{ user_prompt }, to help you answer the user, an appchat function called #{ function_class } provided this data #{ function_response }"
59
+ end
60
+ end
38
61
  end
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["content", "icon"]
5
+
6
+ toggle() {
7
+ this.contentTarget.classList.toggle("hidden")
8
+ if (this.contentTarget.classList.contains("hidden")) {
9
+ this.iconTarget.textContent = "Expand"
10
+ } else {
11
+ this.iconTarget.textContent = "Collapse"
12
+ }
13
+ }
14
+ }
15
+
@@ -0,0 +1,12 @@
1
+ <div data-controller="toggle">
2
+ <%= message.function_logs.count %> Function calls informed this message.
3
+ <i data-toggle-target="icon" data-action="click->toggle#toggle" class="icon-class">Expand</i>
4
+
5
+ <div data-toggle-target="content" class="hidden bg-white">
6
+ <% message.function_logs.each do |log| %>
7
+ <p> Name: <%= log.name %> </p>
8
+ <p> Parameters: <%= log.prompt %> </p>
9
+ <p> Response: <%= log.results %> </p>
10
+ <% end %>
11
+ </div>
12
+ </div>
@@ -1,5 +1,9 @@
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
+ <%= render partial: 'messages/function_logs', locals: { message: message } if message.function_logs.present? %>
3
+
4
+ <% if message.status? && !message.content? %>
5
+ <%= message.status %>
6
+ <% elsif message.content? %>
3
7
  <%= message.content %>
4
8
  <% else %>
5
9
  <%= render 'messages/typing_bubbles' %>
@@ -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
@@ -1,6 +1,8 @@
1
1
  class Message < ApplicationRecord
2
2
  include ActionView::RecordIdentifier
3
3
  belongs_to :chat
4
+ has_many :function_logs
5
+
4
6
  after_create_commit :broadcast_message
5
7
  after_update_commit :broadcast_update_message
6
8
 
@@ -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,17 @@
1
+ require 'cgi'
2
+ require 'open-uri'
3
+
4
+ class WebSearchService
5
+ attr_reader :query
6
+ def initialize(args)
7
+ @query = args["query"]
8
+ end
9
+
10
+ def run
11
+ yield("Searching Google for #{query}")
12
+
13
+ doc = Nokogiri::HTML(URI.open("https://www.google.com/search?q=#{CGI.escape(query)}").read.force_encoding('UTF-8'))
14
+ doc.css('script, style, noscript, comment').remove
15
+ doc.css('h1, h2, h3, h4, h5, h6, p').map(&:text).join("\n").strip
16
+ end
17
+ 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.5
4
+ version: 0.0.7
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-29 00:00:00.000000000 Z
11
+ date: 2024-09-11 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: []
@@ -81,13 +81,19 @@ files:
81
81
  - lib/generators/appchat/templates/get_ai_response_job.rb
82
82
  - lib/generators/appchat/templates/javascript/chat_message_controller.js
83
83
  - lib/generators/appchat/templates/javascript/speech_to_text_controller.js
84
+ - lib/generators/appchat/templates/javascript/toggle_controller.js
85
+ - lib/generators/appchat/templates/messages/_function_logs.html.erb
84
86
  - lib/generators/appchat/templates/messages/_typing_bubbles.html.erb
85
87
  - lib/generators/appchat/templates/messages/index.html.erb
86
88
  - lib/generators/appchat/templates/messages/message.html.erb
87
89
  - lib/generators/appchat/templates/messages/new.html.erb
88
90
  - lib/generators/appchat/templates/messages_controller.rb
91
+ - lib/generators/appchat/templates/models/appchat_function.rb
89
92
  - lib/generators/appchat/templates/models/chat.rb
90
93
  - lib/generators/appchat/templates/models/message.rb
94
+ - lib/generators/appchat/templates/services/appchat_function_service.rb
95
+ - lib/generators/appchat/templates/services/web_search_service.rb
96
+ - lib/generators/appchat/templates/tasks/create_appchat_function.rake
91
97
  homepage: https://rubygems.org/gems/appchat
92
98
  licenses:
93
99
  - MIT
@@ -111,5 +117,5 @@ requirements: []
111
117
  rubygems_version: 3.5.17
112
118
  signing_key:
113
119
  specification_version: 4
114
- summary: Appchat makes it easy to add a chat to your app
120
+ summary: Appchat makes it easy to add an AI chat to your app
115
121
  test_files: []