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 +4 -4
- data/lib/generators/appchat/appchat_generator.rb +32 -11
- data/lib/generators/appchat/templates/get_ai_response_job.rb +28 -5
- data/lib/generators/appchat/templates/javascript/toggle_controller.js +15 -0
- data/lib/generators/appchat/templates/messages/_function_logs.html.erb +12 -0
- data/lib/generators/appchat/templates/messages/message.html.erb +5 -1
- data/lib/generators/appchat/templates/models/appchat_function.rb +18 -0
- data/lib/generators/appchat/templates/models/message.rb +2 -0
- data/lib/generators/appchat/templates/services/appchat_function_service.rb +47 -0
- data/lib/generators/appchat/templates/services/web_search_service.rb +17 -0
- data/lib/generators/appchat/templates/tasks/create_appchat_function.rake +13 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f2e478412edcf3880d15f39ad4aeedbbf73a1a4819c0b270cf1505d3c47b826
|
4
|
+
data.tar.gz: 4cc000f36710cda913c9474e04c3c55fc221170811fde494392cb6d19c3795ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
66
|
-
|
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
|
70
|
-
inject_into_class 'app/models/chat.rb', 'Chat', "
|
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
|
15
|
-
|
17
|
+
def new_client
|
18
|
+
Ollama.new(
|
16
19
|
credentials: { address: 'http://localhost:11434' },
|
17
20
|
options: { server_sent_events: true }
|
18
21
|
)
|
19
|
-
|
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:
|
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
|
-
|
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
|
@@ -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.
|
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-
|
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
|
120
|
+
summary: Appchat makes it easy to add an AI chat to your app
|
115
121
|
test_files: []
|