langgraphrb_rails 0.1.0
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 +7 -0
- data/README.md +816 -0
- data/Rakefile +23 -0
- data/app/assets/javascripts/langgraphrb_rails.js +153 -0
- data/app/assets/stylesheets/langgraphrb_rails.css +95 -0
- data/lib/generators/langgraph_rb/compatibility.rb +71 -0
- data/lib/generators/langgraph_rb/controller/templates/controller.rb +54 -0
- data/lib/generators/langgraph_rb/controller/templates/view.html.erb +101 -0
- data/lib/generators/langgraph_rb/controller_generator.rb +39 -0
- data/lib/generators/langgraph_rb/graph/templates/graph.rb +68 -0
- data/lib/generators/langgraph_rb/graph_generator.rb +23 -0
- data/lib/generators/langgraph_rb/install/templates/README +45 -0
- data/lib/generators/langgraph_rb/install/templates/example_graph.rb +89 -0
- data/lib/generators/langgraph_rb/install/templates/initializer.rb +35 -0
- data/lib/generators/langgraph_rb/install/templates/langgraph_rb.yml +45 -0
- data/lib/generators/langgraph_rb/install_generator.rb +34 -0
- data/lib/generators/langgraph_rb/job/templates/job.rb +38 -0
- data/lib/generators/langgraph_rb/job_generator.rb +27 -0
- data/lib/generators/langgraph_rb/model/templates/migration.rb +12 -0
- data/lib/generators/langgraph_rb/model/templates/model.rb +15 -0
- data/lib/generators/langgraph_rb/model_generator.rb +34 -0
- data/lib/generators/langgraph_rb/task/templates/task.rake +58 -0
- data/lib/generators/langgraph_rb/task_generator.rb +23 -0
- data/lib/generators/langgraphrb_rails/compatibility.rb +71 -0
- data/lib/generators/langgraphrb_rails/controller/templates/controller.rb +30 -0
- data/lib/generators/langgraphrb_rails/controller/templates/view.html.erb +112 -0
- data/lib/generators/langgraphrb_rails/controller_generator.rb +29 -0
- data/lib/generators/langgraphrb_rails/graph/templates/graph.rb +14 -0
- data/lib/generators/langgraphrb_rails/graph/templates/node.rb +16 -0
- data/lib/generators/langgraphrb_rails/graph_generator.rb +48 -0
- data/lib/generators/langgraphrb_rails/install/templates/config.yml +30 -0
- data/lib/generators/langgraphrb_rails/install/templates/example_graph.rb +44 -0
- data/lib/generators/langgraphrb_rails/install/templates/initializer.rb +27 -0
- data/lib/generators/langgraphrb_rails/install_generator.rb +35 -0
- data/lib/generators/langgraphrb_rails/jobs/templates/run_job.rb +45 -0
- data/lib/generators/langgraphrb_rails/jobs_generator.rb +34 -0
- data/lib/generators/langgraphrb_rails/model/templates/migration.rb +20 -0
- data/lib/generators/langgraphrb_rails/model/templates/model.rb +14 -0
- data/lib/generators/langgraphrb_rails/model_generator.rb +30 -0
- data/lib/generators/langgraphrb_rails/persistence/templates/create_langgraph_runs.rb +18 -0
- data/lib/generators/langgraphrb_rails/persistence/templates/langgraph_run.rb +56 -0
- data/lib/generators/langgraphrb_rails/persistence_generator.rb +28 -0
- data/lib/generators/langgraphrb_rails/task/templates/task.rake +30 -0
- data/lib/generators/langgraphrb_rails/task_generator.rb +17 -0
- data/lib/generators/langgraphrb_rails/tracing/templates/traced.rb +45 -0
- data/lib/generators/langgraphrb_rails/tracing_generator.rb +63 -0
- data/lib/langgraphrb_rails/configuration.rb +47 -0
- data/lib/langgraphrb_rails/engine.rb +20 -0
- data/lib/langgraphrb_rails/helper.rb +141 -0
- data/lib/langgraphrb_rails/middleware/streaming.rb +77 -0
- data/lib/langgraphrb_rails/railtie.rb +55 -0
- data/lib/langgraphrb_rails/stores/active_record.rb +51 -0
- data/lib/langgraphrb_rails/stores/redis.rb +57 -0
- data/lib/langgraphrb_rails/test_helper.rb +126 -0
- data/lib/langgraphrb_rails/version.rb +28 -0
- data/lib/langgraphrb_rails.rb +111 -0
- data/lib/tasks/langgraphrb_rails_tasks.rake +62 -0
- metadata +217 -0
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:minitest) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
13
|
+
t.pattern = 'spec/**/*_spec.rb'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Run both test suites
|
17
|
+
task :test => [:minitest, :spec]
|
18
|
+
rescue LoadError
|
19
|
+
# RSpec not available, just use Minitest
|
20
|
+
task :test => :minitest
|
21
|
+
end
|
22
|
+
|
23
|
+
task default: :test
|
@@ -0,0 +1,153 @@
|
|
1
|
+
// LangGraphRB Rails JavaScript
|
2
|
+
// This file provides enhanced functionality for LangGraphRB Rails integration
|
3
|
+
|
4
|
+
document.addEventListener('DOMContentLoaded', function() {
|
5
|
+
// Initialize all LangGraphRB chat interfaces
|
6
|
+
initLangGraphChats();
|
7
|
+
});
|
8
|
+
|
9
|
+
// Initialize all chat interfaces
|
10
|
+
function initLangGraphChats() {
|
11
|
+
const chatForms = document.querySelectorAll('.langgraph-chat-form');
|
12
|
+
|
13
|
+
chatForms.forEach(function(form) {
|
14
|
+
initChatForm(form);
|
15
|
+
});
|
16
|
+
}
|
17
|
+
|
18
|
+
// Initialize a single chat form
|
19
|
+
function initChatForm(form) {
|
20
|
+
const container = form.closest('.langgraph-chat-container');
|
21
|
+
const messagesContainer = container.querySelector('.langgraph-messages');
|
22
|
+
|
23
|
+
form.addEventListener('submit', function(event) {
|
24
|
+
// Check if this is a streaming form with an EventSource
|
25
|
+
if (form.dataset.streaming !== 'true') {
|
26
|
+
// Handle regular AJAX form submission
|
27
|
+
handleRegularFormSubmit(event, form, messagesContainer);
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
// Auto-resize textarea
|
32
|
+
const textarea = form.querySelector('textarea');
|
33
|
+
if (textarea) {
|
34
|
+
textarea.addEventListener('input', function() {
|
35
|
+
this.style.height = 'auto';
|
36
|
+
this.style.height = (this.scrollHeight) + 'px';
|
37
|
+
});
|
38
|
+
}
|
39
|
+
|
40
|
+
// Scroll to bottom initially
|
41
|
+
if (messagesContainer) {
|
42
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
// Handle regular form submission via AJAX
|
47
|
+
function handleRegularFormSubmit(event, form, messagesContainer) {
|
48
|
+
event.preventDefault();
|
49
|
+
|
50
|
+
const messageInput = form.querySelector('textarea, input[name="message"]');
|
51
|
+
const userMessage = messageInput.value.trim();
|
52
|
+
|
53
|
+
if (!userMessage) return;
|
54
|
+
|
55
|
+
// Add user message to the UI
|
56
|
+
addMessageToUI(messagesContainer, userMessage, 'user');
|
57
|
+
|
58
|
+
// Clear the input
|
59
|
+
messageInput.value = '';
|
60
|
+
messageInput.style.height = 'auto';
|
61
|
+
|
62
|
+
// Prepare form data
|
63
|
+
const formData = new FormData(form);
|
64
|
+
|
65
|
+
// Show typing indicator if it exists
|
66
|
+
const typingIndicator = form.closest('.langgraph-chat-container').querySelector('.langgraph-typing-indicator');
|
67
|
+
if (typingIndicator) {
|
68
|
+
typingIndicator.style.display = 'block';
|
69
|
+
}
|
70
|
+
|
71
|
+
// Send the AJAX request
|
72
|
+
fetch(form.action, {
|
73
|
+
method: form.method || 'POST',
|
74
|
+
body: formData,
|
75
|
+
headers: {
|
76
|
+
'Accept': 'application/json'
|
77
|
+
}
|
78
|
+
})
|
79
|
+
.then(response => response.json())
|
80
|
+
.then(data => {
|
81
|
+
// Hide typing indicator
|
82
|
+
if (typingIndicator) {
|
83
|
+
typingIndicator.style.display = 'none';
|
84
|
+
}
|
85
|
+
|
86
|
+
// Add assistant response to the UI
|
87
|
+
if (data.response) {
|
88
|
+
addMessageToUI(messagesContainer, data.response, 'assistant');
|
89
|
+
}
|
90
|
+
})
|
91
|
+
.catch(error => {
|
92
|
+
console.error('Error:', error);
|
93
|
+
// Hide typing indicator
|
94
|
+
if (typingIndicator) {
|
95
|
+
typingIndicator.style.display = 'none';
|
96
|
+
}
|
97
|
+
|
98
|
+
// Add error message
|
99
|
+
addMessageToUI(messagesContainer, 'Sorry, an error occurred. Please try again.', 'system');
|
100
|
+
});
|
101
|
+
}
|
102
|
+
|
103
|
+
// Add a message to the UI
|
104
|
+
function addMessageToUI(container, message, role) {
|
105
|
+
const messageDiv = document.createElement('div');
|
106
|
+
messageDiv.className = `langgraph-message ${role}`;
|
107
|
+
|
108
|
+
const messageP = document.createElement('p');
|
109
|
+
messageP.textContent = message;
|
110
|
+
|
111
|
+
messageDiv.appendChild(messageP);
|
112
|
+
container.appendChild(messageDiv);
|
113
|
+
|
114
|
+
// Scroll to bottom
|
115
|
+
container.scrollTop = container.scrollHeight;
|
116
|
+
}
|
117
|
+
|
118
|
+
// Setup streaming connection
|
119
|
+
function setupEventSource(url, messagesContainer, typingIndicator) {
|
120
|
+
const eventSource = new EventSource(url);
|
121
|
+
|
122
|
+
// Create a div for the assistant's response
|
123
|
+
const assistantDiv = document.createElement('div');
|
124
|
+
assistantDiv.className = 'langgraph-message assistant';
|
125
|
+
const assistantP = document.createElement('p');
|
126
|
+
assistantDiv.appendChild(assistantP);
|
127
|
+
messagesContainer.appendChild(assistantDiv);
|
128
|
+
|
129
|
+
// Handle incoming messages
|
130
|
+
eventSource.onmessage = function(event) {
|
131
|
+
const data = JSON.parse(event.data);
|
132
|
+
|
133
|
+
// Hide typing indicator when complete
|
134
|
+
if (data.completed) {
|
135
|
+
typingIndicator.style.display = 'none';
|
136
|
+
eventSource.close();
|
137
|
+
}
|
138
|
+
|
139
|
+
// Update the assistant's message if there's a response
|
140
|
+
if (data.state && data.state.response) {
|
141
|
+
assistantP.textContent = data.state.response;
|
142
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
143
|
+
}
|
144
|
+
};
|
145
|
+
|
146
|
+
// Handle errors
|
147
|
+
eventSource.onerror = function() {
|
148
|
+
typingIndicator.style.display = 'none';
|
149
|
+
eventSource.close();
|
150
|
+
};
|
151
|
+
|
152
|
+
return eventSource;
|
153
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
/* LangGraphRB Rails Chat Interface Styles */
|
2
|
+
|
3
|
+
.langgraph-chat-container {
|
4
|
+
max-width: 800px;
|
5
|
+
margin: 0 auto;
|
6
|
+
border: 1px solid #e0e0e0;
|
7
|
+
border-radius: 8px;
|
8
|
+
overflow: hidden;
|
9
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
10
|
+
background-color: #fff;
|
11
|
+
}
|
12
|
+
|
13
|
+
.langgraph-messages {
|
14
|
+
height: 400px;
|
15
|
+
overflow-y: auto;
|
16
|
+
padding: 16px;
|
17
|
+
background-color: #f9f9f9;
|
18
|
+
}
|
19
|
+
|
20
|
+
.langgraph-message {
|
21
|
+
margin-bottom: 16px;
|
22
|
+
padding: 12px;
|
23
|
+
border-radius: 8px;
|
24
|
+
max-width: 80%;
|
25
|
+
word-wrap: break-word;
|
26
|
+
}
|
27
|
+
|
28
|
+
.langgraph-message.user {
|
29
|
+
background-color: #e3f2fd;
|
30
|
+
margin-left: auto;
|
31
|
+
text-align: right;
|
32
|
+
}
|
33
|
+
|
34
|
+
.langgraph-message.assistant {
|
35
|
+
background-color: #f5f5f5;
|
36
|
+
}
|
37
|
+
|
38
|
+
.langgraph-message.system {
|
39
|
+
background-color: #fff3e0;
|
40
|
+
font-style: italic;
|
41
|
+
max-width: 100%;
|
42
|
+
margin-bottom: 24px;
|
43
|
+
border-left: 3px solid #ffb74d;
|
44
|
+
}
|
45
|
+
|
46
|
+
.langgraph-chat-form {
|
47
|
+
display: flex;
|
48
|
+
flex-direction: column;
|
49
|
+
padding: 16px;
|
50
|
+
border-top: 1px solid #e0e0e0;
|
51
|
+
}
|
52
|
+
|
53
|
+
.langgraph-chat-input {
|
54
|
+
width: 100%;
|
55
|
+
padding: 12px;
|
56
|
+
border: 1px solid #e0e0e0;
|
57
|
+
border-radius: 4px;
|
58
|
+
margin-bottom: 8px;
|
59
|
+
resize: none;
|
60
|
+
min-height: 80px;
|
61
|
+
}
|
62
|
+
|
63
|
+
.langgraph-chat-submit {
|
64
|
+
align-self: flex-end;
|
65
|
+
padding: 8px 16px;
|
66
|
+
background-color: #2196f3;
|
67
|
+
color: white;
|
68
|
+
border: none;
|
69
|
+
border-radius: 4px;
|
70
|
+
cursor: pointer;
|
71
|
+
font-weight: bold;
|
72
|
+
}
|
73
|
+
|
74
|
+
.langgraph-chat-submit:hover {
|
75
|
+
background-color: #1976d2;
|
76
|
+
}
|
77
|
+
|
78
|
+
.langgraph-typing-indicator {
|
79
|
+
display: none;
|
80
|
+
padding: 8px;
|
81
|
+
text-align: center;
|
82
|
+
font-style: italic;
|
83
|
+
color: #757575;
|
84
|
+
}
|
85
|
+
|
86
|
+
.langgraph-typing-indicator::after {
|
87
|
+
content: "...";
|
88
|
+
animation: typing-dots 1.5s infinite;
|
89
|
+
}
|
90
|
+
|
91
|
+
@keyframes typing-dots {
|
92
|
+
0%, 20% { content: "."; }
|
93
|
+
40% { content: ".."; }
|
94
|
+
60%, 100% { content: "..."; }
|
95
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module LanggraphRb
|
2
|
+
module Generators
|
3
|
+
module Compatibility
|
4
|
+
def self.included(base)
|
5
|
+
if defined?(Rails) && Gem::Version.new(Rails.version) >= Gem::Version.new('8.0.0')
|
6
|
+
# Explicitly require all necessary Rails generator components
|
7
|
+
begin
|
8
|
+
require 'rails/generators'
|
9
|
+
require 'rails/generators/actions'
|
10
|
+
require 'rails/generators/migration'
|
11
|
+
require 'rails/generators/active_model'
|
12
|
+
require 'rails/generators/base'
|
13
|
+
require 'rails/generators/named_base'
|
14
|
+
|
15
|
+
# Define Actions module if it doesn't exist
|
16
|
+
unless defined?(Rails::Generators::Actions)
|
17
|
+
module Rails
|
18
|
+
module Generators
|
19
|
+
module Actions
|
20
|
+
# Minimal implementation of required methods
|
21
|
+
def copy_file(source, destination, config = {})
|
22
|
+
say_status :copy, "#{source} to #{destination}", config.fetch(:verbose, true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def template(source, destination, config = {})
|
26
|
+
say_status :template, "#{source} to #{destination}", config.fetch(:verbose, true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty_directory(destination, config = {})
|
30
|
+
say_status :create, destination, config.fetch(:verbose, true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inject_into_file(destination, content, config = {})
|
34
|
+
say_status :inject, destination, config.fetch(:verbose, true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def append_to_file(destination, content, config = {})
|
38
|
+
say_status :append, destination, config.fetch(:verbose, true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def gsub_file(destination, flag, replacement, config = {})
|
42
|
+
say_status :gsub, destination, config.fetch(:verbose, true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def application(data, config = {})
|
46
|
+
say_status :application, data, config.fetch(:verbose, true)
|
47
|
+
end
|
48
|
+
|
49
|
+
def readme(path)
|
50
|
+
say_status :readme, path, true
|
51
|
+
end
|
52
|
+
|
53
|
+
def say_status(status, message, log_status = true)
|
54
|
+
puts "[#{status}] #{message}" if log_status
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Include Actions module if it's now defined
|
62
|
+
base.include(Rails::Generators::Actions) if defined?(Rails::Generators::Actions)
|
63
|
+
rescue LoadError => e
|
64
|
+
# Fallback gracefully if we can't load all components
|
65
|
+
puts "Warning: Could not load all Rails generator components: #{e.message}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class <%= class_name %>Controller < ApplicationController
|
2
|
+
# GET /<%= file_name %>
|
3
|
+
def index
|
4
|
+
# You can render a view with a form to interact with the graph
|
5
|
+
end
|
6
|
+
|
7
|
+
# POST /<%= file_name %>
|
8
|
+
def create
|
9
|
+
# Process the input with the graph
|
10
|
+
result = <%= graph_class_name %>.invoke(input: params[:message])
|
11
|
+
|
12
|
+
respond_to do |format|
|
13
|
+
format.html { redirect_to <%= singular_table_name %>_path(id: result[:thread_id]), notice: "Processing complete" }
|
14
|
+
format.json { render json: { response: result[:response], thread_id: result[:thread_id] } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# GET /<%= file_name %>/stream
|
19
|
+
def stream
|
20
|
+
response.headers['Content-Type'] = 'text/event-stream'
|
21
|
+
response.headers['Last-Modified'] = Time.now.httpdate
|
22
|
+
|
23
|
+
<%= graph_class_name %>.stream(input: params[:message]) do |step_result|
|
24
|
+
response.stream.write("data: #{step_result.to_json}\n\n")
|
25
|
+
|
26
|
+
if step_result[:completed]
|
27
|
+
response.stream.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue ActionController::Live::ClientDisconnected
|
31
|
+
response.stream.close
|
32
|
+
end
|
33
|
+
|
34
|
+
# GET /<%= file_name %>/:id
|
35
|
+
def show
|
36
|
+
# Retrieve the thread state if needed
|
37
|
+
thread_id = params[:id]
|
38
|
+
store = LanggraphrbRails.create_store
|
39
|
+
|
40
|
+
if store.exists?(thread_id)
|
41
|
+
@state = store.get(thread_id)
|
42
|
+
|
43
|
+
respond_to do |format|
|
44
|
+
format.html
|
45
|
+
format.json { render json: @state }
|
46
|
+
end
|
47
|
+
else
|
48
|
+
respond_to do |format|
|
49
|
+
format.html { redirect_to <%= plural_table_name %>_path, alert: "Thread not found" }
|
50
|
+
format.json { render json: { error: "Thread not found" }, status: :not_found }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
<%% content_for :title, "<%= class_name %>" %>
|
2
|
+
|
3
|
+
<div class="container mt-4">
|
4
|
+
<div class="row">
|
5
|
+
<div class="col-md-8 offset-md-2">
|
6
|
+
<div class="card">
|
7
|
+
<div class="card-header">
|
8
|
+
<h2><%= class_name %></h2>
|
9
|
+
</div>
|
10
|
+
<div class="card-body">
|
11
|
+
<div id="messages-container" class="mb-4">
|
12
|
+
<div class="system-message">
|
13
|
+
<p>Welcome to <%= class_name %>. How can I help you today?</p>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div id="message-history">
|
17
|
+
<%% if @state && @state[:messages] %>
|
18
|
+
<%% @state[:messages].each do |message| %>
|
19
|
+
<div class="message <%%= message[:role] %>">
|
20
|
+
<p><%%= message[:content] %></p>
|
21
|
+
</div>
|
22
|
+
<%% end %>
|
23
|
+
<%% end %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<%%= form_with url: <%= plural_table_name %>_path, method: :post, id: "message-form", data: { remote: true } do |f| %>
|
28
|
+
<div class="form-group">
|
29
|
+
<%%= f.text_area :message, class: "form-control", placeholder: "Type your message here...", rows: 3 %>
|
30
|
+
</div>
|
31
|
+
<div class="form-group">
|
32
|
+
<%%= f.submit "Send", class: "btn btn-primary" %>
|
33
|
+
</div>
|
34
|
+
<%% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<style>
|
42
|
+
#messages-container {
|
43
|
+
max-height: 400px;
|
44
|
+
overflow-y: auto;
|
45
|
+
border: 1px solid #e0e0e0;
|
46
|
+
border-radius: 5px;
|
47
|
+
padding: 10px;
|
48
|
+
}
|
49
|
+
|
50
|
+
.message {
|
51
|
+
margin-bottom: 10px;
|
52
|
+
padding: 10px;
|
53
|
+
border-radius: 5px;
|
54
|
+
}
|
55
|
+
|
56
|
+
.user {
|
57
|
+
background-color: #f0f0f0;
|
58
|
+
text-align: right;
|
59
|
+
}
|
60
|
+
|
61
|
+
.assistant {
|
62
|
+
background-color: #e3f2fd;
|
63
|
+
}
|
64
|
+
|
65
|
+
.system-message {
|
66
|
+
background-color: #f5f5f5;
|
67
|
+
padding: 10px;
|
68
|
+
border-radius: 5px;
|
69
|
+
margin-bottom: 10px;
|
70
|
+
font-style: italic;
|
71
|
+
}
|
72
|
+
</style>
|
73
|
+
|
74
|
+
<script>
|
75
|
+
document.addEventListener('DOMContentLoaded', function() {
|
76
|
+
const form = document.getElementById('message-form');
|
77
|
+
const messagesContainer = document.getElementById('message-history');
|
78
|
+
|
79
|
+
form.addEventListener('ajax:success', function(event) {
|
80
|
+
const [data, status, xhr] = event.detail;
|
81
|
+
|
82
|
+
// Add user message to the UI
|
83
|
+
const userMessage = document.createElement('div');
|
84
|
+
userMessage.className = 'message user';
|
85
|
+
userMessage.innerHTML = '<p>' + form.elements['message'].value + '</p>';
|
86
|
+
messagesContainer.appendChild(userMessage);
|
87
|
+
|
88
|
+
// Add assistant response to the UI
|
89
|
+
const assistantMessage = document.createElement('div');
|
90
|
+
assistantMessage.className = 'message assistant';
|
91
|
+
assistantMessage.innerHTML = '<p>' + data.response + '</p>';
|
92
|
+
messagesContainer.appendChild(assistantMessage);
|
93
|
+
|
94
|
+
// Clear the form
|
95
|
+
form.elements['message'].value = '';
|
96
|
+
|
97
|
+
// Scroll to bottom
|
98
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
99
|
+
});
|
100
|
+
});
|
101
|
+
</script>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphRb
|
5
|
+
module Generators
|
6
|
+
class ControllerGenerator < Rails::Generators::NamedBase
|
7
|
+
include LanggraphRb::Generators::Compatibility
|
8
|
+
source_root File.expand_path('controller/templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates a new controller for LangGraphRB interactions"
|
11
|
+
|
12
|
+
argument :actions, type: :array, default: [], banner: "action action"
|
13
|
+
|
14
|
+
def create_controller_file
|
15
|
+
template "controller.rb", File.join("app/controllers", "#{file_name}_controller.rb")
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_views
|
19
|
+
actions.each do |action|
|
20
|
+
template "view.html.erb", File.join("app/views", file_name, "#{action}.html.erb")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_routes
|
25
|
+
route "resources :#{file_name}, only: [:index, :create, :show] do\n" +
|
26
|
+
" collection do\n" +
|
27
|
+
" get :stream\n" +
|
28
|
+
" end\n" +
|
29
|
+
" end"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def graph_class_name
|
35
|
+
"#{class_name.singularize}Graph"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# <%= graph_class_name %> - LangGraphRB graph
|
2
|
+
# Generated with langgraphrb_rails
|
3
|
+
|
4
|
+
class <%= graph_class_name %>
|
5
|
+
# Define a singleton instance for easy access
|
6
|
+
class << self
|
7
|
+
def instance
|
8
|
+
@instance ||= build_graph
|
9
|
+
end
|
10
|
+
|
11
|
+
def invoke(input = {}, context: nil)
|
12
|
+
instance.invoke(input, context: context)
|
13
|
+
end
|
14
|
+
|
15
|
+
def stream(input = {}, context: nil, &block)
|
16
|
+
instance.stream(input, context: context, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def build_graph
|
22
|
+
# Define your state schema and reducers here
|
23
|
+
initial_state = LangGraphRB::State.new(
|
24
|
+
{ messages: [] },
|
25
|
+
{ messages: LangGraphRB::State.add_messages }
|
26
|
+
)
|
27
|
+
|
28
|
+
# Create the graph
|
29
|
+
graph = LangGraphRB::Graph.new(state_class: LangGraphRB::State) do
|
30
|
+
# Define your nodes here
|
31
|
+
node :receive_input do |state|
|
32
|
+
Rails.logger.info "Processing input in <%= graph_class_name %>"
|
33
|
+
{
|
34
|
+
messages: [{ role: 'user', content: state[:input] }],
|
35
|
+
input_processed: true
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
node :process do |state|
|
40
|
+
# Add your processing logic here
|
41
|
+
{
|
42
|
+
processed: true,
|
43
|
+
result: "Processed: #{state[:input]}"
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
node :respond do |state|
|
48
|
+
# Generate a response
|
49
|
+
response = "This is a response from <%= graph_class_name %>"
|
50
|
+
|
51
|
+
{
|
52
|
+
messages: [{ role: 'assistant', content: response }],
|
53
|
+
response: response
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define your edges here
|
58
|
+
set_entry_point :receive_input
|
59
|
+
edge :receive_input, :process
|
60
|
+
edge :process, :respond
|
61
|
+
set_finish_point :respond
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compile the graph
|
65
|
+
graph.compile!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require_relative 'compatibility'
|
3
|
+
|
4
|
+
module LanggraphRb
|
5
|
+
module Generators
|
6
|
+
class GraphGenerator < Rails::Generators::NamedBase
|
7
|
+
include LanggraphRb::Generators::Compatibility
|
8
|
+
source_root File.expand_path('graph/templates', __dir__)
|
9
|
+
|
10
|
+
desc "Creates a new LangGraphRB graph in app/graphs"
|
11
|
+
|
12
|
+
def create_graph_file
|
13
|
+
template "graph.rb", File.join("app/graphs", "#{file_name}_graph.rb")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def graph_class_name
|
19
|
+
"#{class_name}Graph"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
LangGraphRB Rails Integration
|
4
|
+
============================
|
5
|
+
|
6
|
+
Your LangGraphRB Rails integration has been successfully installed!
|
7
|
+
|
8
|
+
Next steps:
|
9
|
+
|
10
|
+
1. Review the configuration in config/langgraph_rb.yml and adjust as needed
|
11
|
+
|
12
|
+
2. Check out the example graph in app/graphs/example_graph.rb
|
13
|
+
|
14
|
+
3. Create your own graphs in the app/graphs directory
|
15
|
+
|
16
|
+
4. Use your graphs in your controllers like this:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# In a controller action
|
20
|
+
def chat
|
21
|
+
user_input = params[:message]
|
22
|
+
result = ExampleGraph.invoke(input: user_input)
|
23
|
+
render json: { response: result[:response] }
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
5. For streaming responses, use:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# In a controller action with Turbo Streams or ActionCable
|
31
|
+
def stream_chat
|
32
|
+
ExampleGraph.stream(input: params[:message]) do |step_result|
|
33
|
+
# Send step_result to the client via ActionCable or Turbo Streams
|
34
|
+
if step_result[:completed]
|
35
|
+
# Final result
|
36
|
+
else
|
37
|
+
# Intermediate result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
For more information, visit: https://github.com/cdaviis/langgraphrb_rails
|
44
|
+
|
45
|
+
===============================================================================
|