appchat 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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: []