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 +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: []
|