appchat 0.0.3 → 0.0.5
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 +29 -6
- data/lib/generators/appchat/templates/assets/appchat.tailwind.css +76 -0
- data/lib/generators/appchat/templates/chats/chat.html.erb +3 -3
- data/lib/generators/appchat/templates/chats/index.html.erb +18 -15
- data/lib/generators/appchat/templates/chats_controller.rb +1 -1
- data/lib/generators/appchat/templates/javascript/speech_to_text_controller.js +65 -0
- data/lib/generators/appchat/templates/messages/_typing_bubbles.html.erb +5 -0
- data/lib/generators/appchat/templates/messages/message.html.erb +5 -1
- data/lib/generators/appchat/templates/messages/new.html.erb +35 -12
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3071421a8c3c49e11f5c5186b9f17a00bd37d45229d0d95a3b466ae9b9032fee
|
4
|
+
data.tar.gz: 79284185b56a3efaeb60e1ec1ad144ada532fd499e686b4604db7a29aabccd16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e642582fe817de68a171c14b272a0b0ceb2257b96021ae9a51e630c5ef562ed982c452fdc9694c2d6e6c81378720f636e0e8218f51d1704134d383602cf2df7
|
7
|
+
data.tar.gz: a71996c59642154416cb3178b9406d44242b5626ab7069aeb6341a15e1062d6c297131dfd19de2455c7e59be0cbf8af8c43e6daa0879afb269c001a88723f0a3
|
@@ -23,21 +23,38 @@ class AppchatGenerator < Rails::Generators::Base
|
|
23
23
|
run 'rails tailwindcss:install'
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
generate "model", "Chat context:text"
|
26
|
+
def set_routes
|
28
27
|
route "resources :chats"
|
29
|
-
|
28
|
+
route "resources :messages"
|
30
29
|
end
|
31
30
|
|
32
|
-
def
|
31
|
+
def generate_models
|
32
|
+
generate "model", "Chat context:text"
|
33
|
+
generate "model", "Message chat:references content:text role:integer"
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_controllers
|
33
37
|
copy_file "chats_controller.rb", "app/controllers/chats_controller.rb"
|
38
|
+
copy_file "messages_controller.rb", "app/controllers/messages_controller.rb", force: true
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_views
|
34
42
|
copy_file "chats/chat.html.erb", "app/views/chats/_chat.html.erb", force: true
|
35
43
|
copy_file "chats/index.html.erb", "app/views/chats/index.html.erb", force: true
|
36
44
|
copy_file "chats/show.html.erb", "app/views/chats/show.html.erb", force: true
|
37
45
|
copy_file "messages/index.html.erb", "app/views/messages/index.html.erb", force: true
|
38
46
|
copy_file "messages/new.html.erb", "app/views/messages/new.html.erb", force: true
|
39
47
|
copy_file "messages/message.html.erb", "app/views/messages/_message.html.erb", force: true
|
48
|
+
copy_file "messages/_typing_bubbles.html.erb", "app/views/messages/_typing_bubbles.html.erb", force: true
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_stylesheets
|
52
|
+
copy_file "assets/appchat.tailwind.css", "app/assets/stylesheets/appchat.tailwind.css", force: true
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_stimulus_controllers
|
40
56
|
copy_file "javascript/chat_message_controller.js", "app/javascript/controllers/chat_message_controller.js"
|
57
|
+
copy_file "javascript/speech_to_text_controller.js", "app/javascript/controllers/speech_to_text_controller.js"
|
41
58
|
end
|
42
59
|
|
43
60
|
def copy_models
|
@@ -61,8 +78,14 @@ class AppchatGenerator < Rails::Generators::Base
|
|
61
78
|
copy_file "get_ai_response_job.rb", "app/jobs/get_ai_response_job.rb"
|
62
79
|
end
|
63
80
|
|
64
|
-
def
|
65
|
-
|
81
|
+
def swap_class_in_layout
|
82
|
+
layout_file = "app/views/layouts/application.html.erb"
|
83
|
+
|
84
|
+
if File.exist?(layout_file)
|
85
|
+
gsub_file layout_file, /\bmt-28\b/, "mt-10"
|
86
|
+
else
|
87
|
+
say "Layout file not found. No changes were made.", :red
|
88
|
+
end
|
66
89
|
end
|
67
90
|
|
68
91
|
def show_art
|
@@ -0,0 +1,76 @@
|
|
1
|
+
@tailwind base;
|
2
|
+
@tailwind components;
|
3
|
+
@tailwind utilities;
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
@layer components {
|
8
|
+
|
9
|
+
.typing {
|
10
|
+
background-color: #e0e0e0;
|
11
|
+
display: block;
|
12
|
+
width: 60px;
|
13
|
+
height: 40px;
|
14
|
+
border-radius: 20px;
|
15
|
+
margin: 0 1rem;
|
16
|
+
display: flex;
|
17
|
+
justify-content: center;
|
18
|
+
align-items: center;
|
19
|
+
}
|
20
|
+
|
21
|
+
.circle {
|
22
|
+
display: block;
|
23
|
+
height: 10px;
|
24
|
+
width: 10px;
|
25
|
+
border-radius: 50%;
|
26
|
+
background-color: #8d8d8d;
|
27
|
+
margin: 3px;
|
28
|
+
}
|
29
|
+
|
30
|
+
.circle.scaling {
|
31
|
+
animation: typing 1000ms ease-in-out infinite;
|
32
|
+
animation-delay: 3600ms;
|
33
|
+
}
|
34
|
+
|
35
|
+
.circle:nth-child(1) {
|
36
|
+
animation-delay: 0ms;
|
37
|
+
}
|
38
|
+
|
39
|
+
.circle:nth-child(2) {
|
40
|
+
animation-delay: 333ms;
|
41
|
+
}
|
42
|
+
|
43
|
+
.circle:nth-child(3) {
|
44
|
+
animation-delay: 666ms;
|
45
|
+
}
|
46
|
+
|
47
|
+
@keyframes typing {
|
48
|
+
0% {
|
49
|
+
transform: scale(1);
|
50
|
+
}
|
51
|
+
33% {
|
52
|
+
transform: scale(1);
|
53
|
+
}
|
54
|
+
50% {
|
55
|
+
transform: scale(1.4);
|
56
|
+
}
|
57
|
+
100% {
|
58
|
+
transform: scale(1);
|
59
|
+
}
|
60
|
+
}
|
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
|
+
}
|
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
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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>
|
@@ -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,3 +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
|
-
|
2
|
+
<% if message.content? %>
|
3
|
+
<%= message.content %>
|
4
|
+
<% else %>
|
5
|
+
<%= render 'messages/typing_bubbles' %>
|
6
|
+
<% end %>
|
3
7
|
</div>
|
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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 %>
|
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.5
|
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-
|
11
|
+
date: 2024-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -73,12 +73,15 @@ extensions: []
|
|
73
73
|
extra_rdoc_files: []
|
74
74
|
files:
|
75
75
|
- lib/generators/appchat/appchat_generator.rb
|
76
|
+
- lib/generators/appchat/templates/assets/appchat.tailwind.css
|
76
77
|
- lib/generators/appchat/templates/chats/chat.html.erb
|
77
78
|
- lib/generators/appchat/templates/chats/index.html.erb
|
78
79
|
- lib/generators/appchat/templates/chats/show.html.erb
|
79
80
|
- lib/generators/appchat/templates/chats_controller.rb
|
80
81
|
- lib/generators/appchat/templates/get_ai_response_job.rb
|
81
82
|
- lib/generators/appchat/templates/javascript/chat_message_controller.js
|
83
|
+
- lib/generators/appchat/templates/javascript/speech_to_text_controller.js
|
84
|
+
- lib/generators/appchat/templates/messages/_typing_bubbles.html.erb
|
82
85
|
- lib/generators/appchat/templates/messages/index.html.erb
|
83
86
|
- lib/generators/appchat/templates/messages/message.html.erb
|
84
87
|
- lib/generators/appchat/templates/messages/new.html.erb
|
@@ -105,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
108
|
- !ruby/object:Gem::Version
|
106
109
|
version: '0'
|
107
110
|
requirements: []
|
108
|
-
rubygems_version: 3.5.
|
111
|
+
rubygems_version: 3.5.17
|
109
112
|
signing_key:
|
110
113
|
specification_version: 4
|
111
114
|
summary: Appchat makes it easy to add a chat to your app
|