rails_ai 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/.rspec_status +96 -0
- data/AGENT_GUIDE.md +513 -0
- data/Appraisals +49 -0
- data/COMMERCIAL_LICENSE_TEMPLATE.md +92 -0
- data/FEATURES.md +204 -0
- data/LEGAL_PROTECTION_GUIDE.md +222 -0
- data/LICENSE +62 -0
- data/LICENSE_SUMMARY.md +74 -0
- data/MIT-LICENSE +62 -0
- data/PERFORMANCE.md +300 -0
- data/PROVIDERS.md +495 -0
- data/README.md +454 -0
- data/Rakefile +11 -0
- data/SPEED_OPTIMIZATIONS.md +217 -0
- data/STRUCTURE.md +139 -0
- data/USAGE_GUIDE.md +288 -0
- data/app/channels/ai_stream_channel.rb +33 -0
- data/app/components/ai/prompt_component.rb +25 -0
- data/app/controllers/concerns/ai/context_aware.rb +77 -0
- data/app/controllers/concerns/ai/streaming.rb +41 -0
- data/app/helpers/ai_helper.rb +164 -0
- data/app/jobs/ai/generate_embedding_job.rb +25 -0
- data/app/jobs/ai/generate_summary_job.rb +25 -0
- data/app/models/concerns/ai/embeddable.rb +38 -0
- data/app/views/rails_ai/dashboard/index.html.erb +51 -0
- data/config/routes.rb +19 -0
- data/lib/generators/rails_ai/install/install_generator.rb +38 -0
- data/lib/rails_ai/agents/agent_manager.rb +258 -0
- data/lib/rails_ai/agents/agent_team.rb +243 -0
- data/lib/rails_ai/agents/base_agent.rb +331 -0
- data/lib/rails_ai/agents/collaboration.rb +238 -0
- data/lib/rails_ai/agents/memory.rb +116 -0
- data/lib/rails_ai/agents/message_bus.rb +95 -0
- data/lib/rails_ai/agents/specialized_agents.rb +391 -0
- data/lib/rails_ai/agents/task_queue.rb +111 -0
- data/lib/rails_ai/cache.rb +14 -0
- data/lib/rails_ai/config.rb +40 -0
- data/lib/rails_ai/context.rb +7 -0
- data/lib/rails_ai/context_analyzer.rb +86 -0
- data/lib/rails_ai/engine.rb +48 -0
- data/lib/rails_ai/events.rb +9 -0
- data/lib/rails_ai/image_context.rb +110 -0
- data/lib/rails_ai/performance.rb +231 -0
- data/lib/rails_ai/provider.rb +8 -0
- data/lib/rails_ai/providers/anthropic_adapter.rb +256 -0
- data/lib/rails_ai/providers/base.rb +60 -0
- data/lib/rails_ai/providers/dummy_adapter.rb +29 -0
- data/lib/rails_ai/providers/gemini_adapter.rb +509 -0
- data/lib/rails_ai/providers/openai_adapter.rb +535 -0
- data/lib/rails_ai/providers/secure_anthropic_adapter.rb +206 -0
- data/lib/rails_ai/providers/secure_openai_adapter.rb +284 -0
- data/lib/rails_ai/railtie.rb +48 -0
- data/lib/rails_ai/redactor.rb +12 -0
- data/lib/rails_ai/security/api_key_manager.rb +82 -0
- data/lib/rails_ai/security/audit_logger.rb +46 -0
- data/lib/rails_ai/security/error_handler.rb +62 -0
- data/lib/rails_ai/security/input_validator.rb +176 -0
- data/lib/rails_ai/security/secure_file_handler.rb +45 -0
- data/lib/rails_ai/security/secure_http_client.rb +177 -0
- data/lib/rails_ai/security.rb +0 -0
- data/lib/rails_ai/version.rb +5 -0
- data/lib/rails_ai/window_context.rb +103 -0
- data/lib/rails_ai.rb +502 -0
- data/monitoring/ci_setup_guide.md +214 -0
- data/monitoring/enhanced_monitoring_script.rb +237 -0
- data/monitoring/google_alerts_setup.md +42 -0
- data/monitoring_log_20250921.txt +0 -0
- data/monitoring_script.rb +161 -0
- data/rails_ai.gemspec +54 -0
- data/scripts/security_scanner.rb +353 -0
- data/setup_monitoring.sh +163 -0
- data/wiki/API-Documentation.md +734 -0
- data/wiki/Architecture-Overview.md +672 -0
- data/wiki/Contributing-Guide.md +407 -0
- data/wiki/Development-Setup.md +532 -0
- data/wiki/Home.md +278 -0
- data/wiki/Installation-Guide.md +527 -0
- data/wiki/Quick-Start.md +186 -0
- data/wiki/README.md +135 -0
- data/wiki/Release-Process.md +467 -0
- metadata +385 -0
@@ -0,0 +1,391 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Agents
|
5
|
+
# Research Agent - Specialized in gathering and analyzing information
|
6
|
+
class ResearchAgent < BaseAgent
|
7
|
+
def initialize(name: "ResearchAgent", **opts)
|
8
|
+
super(
|
9
|
+
name: name,
|
10
|
+
role: "Research Specialist",
|
11
|
+
capabilities: [:research, :analysis, :data_gathering, :fact_checking],
|
12
|
+
**opts
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def research_topic(topic, depth: :standard)
|
17
|
+
return "[stubbed] Research on #{topic}" if RailsAi.config.stub_responses
|
18
|
+
|
19
|
+
research_prompt = build_research_prompt(topic, depth)
|
20
|
+
result = think(research_prompt, context: { topic: topic, depth: depth })
|
21
|
+
|
22
|
+
remember("research_#{topic}_#{Time.now.to_i}", result, importance: :high)
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def fact_check(claim)
|
27
|
+
return "[stubbed] Fact check: #{claim}" if RailsAi.config.stub_responses
|
28
|
+
|
29
|
+
fact_check_prompt = build_fact_check_prompt(claim)
|
30
|
+
result = think(fact_check_prompt, context: { claim: claim })
|
31
|
+
|
32
|
+
remember("fact_check_#{claim.hash}", result, importance: :normal)
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_research_prompt(topic, depth)
|
39
|
+
depth_instructions = case depth
|
40
|
+
when :shallow
|
41
|
+
"Provide a brief overview"
|
42
|
+
when :standard
|
43
|
+
"Provide a comprehensive analysis"
|
44
|
+
when :deep
|
45
|
+
"Provide an in-depth, detailed analysis with multiple perspectives"
|
46
|
+
end
|
47
|
+
|
48
|
+
<<~PROMPT
|
49
|
+
As a research specialist, conduct #{depth_instructions} on the topic: #{topic}
|
50
|
+
|
51
|
+
Include:
|
52
|
+
- Key facts and data points
|
53
|
+
- Multiple perspectives and viewpoints
|
54
|
+
- Recent developments and trends
|
55
|
+
- Potential implications and consequences
|
56
|
+
- Sources and references where applicable
|
57
|
+
|
58
|
+
Ensure accuracy and objectivity in your research.
|
59
|
+
PROMPT
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_fact_check_prompt(claim)
|
63
|
+
<<~PROMPT
|
64
|
+
As a fact-checking specialist, verify the following claim: "#{claim}"
|
65
|
+
|
66
|
+
Provide:
|
67
|
+
- Verification status (true, false, partially true, unverifiable)
|
68
|
+
- Evidence supporting or refuting the claim
|
69
|
+
- Context and nuances
|
70
|
+
- Confidence level in your assessment
|
71
|
+
- Sources used for verification
|
72
|
+
|
73
|
+
Be objective and evidence-based in your analysis.
|
74
|
+
PROMPT
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creative Agent - Specialized in creative tasks and ideation
|
79
|
+
class CreativeAgent < BaseAgent
|
80
|
+
def initialize(name: "CreativeAgent", **opts)
|
81
|
+
super(
|
82
|
+
name: name,
|
83
|
+
role: "Creative Specialist",
|
84
|
+
capabilities: [:creative_writing, :ideation, :design_thinking, :storytelling],
|
85
|
+
**opts
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def brainstorm(topic, quantity: 10)
|
90
|
+
return Array.new(quantity) { "[stubbed] Creative idea #{_1 + 1}" } if RailsAi.config.stub_responses
|
91
|
+
|
92
|
+
brainstorm_prompt = build_brainstorm_prompt(topic, quantity)
|
93
|
+
result = think(brainstorm_prompt, context: { topic: topic, quantity: quantity })
|
94
|
+
|
95
|
+
ideas = parse_ideas(result)
|
96
|
+
remember("brainstorm_#{topic}_#{Time.now.to_i}", ideas, importance: :high)
|
97
|
+
ideas
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_story(prompt, genre: :general, length: :medium)
|
101
|
+
return "[stubbed] Story: #{prompt}" if RailsAi.config.stub_responses
|
102
|
+
|
103
|
+
story_prompt = build_story_prompt(prompt, genre, length)
|
104
|
+
result = think(story_prompt, context: { prompt: prompt, genre: genre, length: length })
|
105
|
+
|
106
|
+
remember("story_#{prompt.hash}", result, importance: :normal)
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
def design_concept(description, style: :modern)
|
111
|
+
return "[stubbed] Design concept: #{description}" if RailsAi.config.stub_responses
|
112
|
+
|
113
|
+
design_prompt = build_design_prompt(description, style)
|
114
|
+
result = think(design_prompt, context: { description: description, style: style })
|
115
|
+
|
116
|
+
remember("design_#{description.hash}", result, importance: :normal)
|
117
|
+
result
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def build_brainstorm_prompt(topic, quantity)
|
123
|
+
<<~PROMPT
|
124
|
+
As a creative specialist, brainstorm #{quantity} innovative and creative ideas related to: #{topic}
|
125
|
+
|
126
|
+
Requirements:
|
127
|
+
- Be creative and think outside the box
|
128
|
+
- Ideas should be practical and implementable
|
129
|
+
- Include both conventional and unconventional approaches
|
130
|
+
- Consider different perspectives and angles
|
131
|
+
- Make each idea unique and distinct
|
132
|
+
|
133
|
+
Format your response as a numbered list of ideas.
|
134
|
+
PROMPT
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_story_prompt(prompt, genre, length)
|
138
|
+
length_guidance = case length
|
139
|
+
when :short then "Write a short story (1-2 pages)"
|
140
|
+
when :medium then "Write a medium-length story (3-5 pages)"
|
141
|
+
when :long then "Write a longer story (6+ pages)"
|
142
|
+
end
|
143
|
+
|
144
|
+
<<~PROMPT
|
145
|
+
As a creative writer, #{length_guidance} based on: #{prompt}
|
146
|
+
|
147
|
+
Genre: #{genre}
|
148
|
+
Requirements:
|
149
|
+
- Engaging plot and characters
|
150
|
+
- Clear narrative structure
|
151
|
+
- Appropriate tone and style for the genre
|
152
|
+
- Creative and original content
|
153
|
+
- Well-developed dialogue and descriptions
|
154
|
+
|
155
|
+
Create an immersive and compelling story.
|
156
|
+
PROMPT
|
157
|
+
end
|
158
|
+
|
159
|
+
def build_design_prompt(description, style)
|
160
|
+
<<~PROMPT
|
161
|
+
As a design specialist, create a design concept for: #{description}
|
162
|
+
|
163
|
+
Style: #{style}
|
164
|
+
Requirements:
|
165
|
+
- Clear visual description
|
166
|
+
- Functional considerations
|
167
|
+
- Aesthetic appeal
|
168
|
+
- User experience focus
|
169
|
+
- Innovative elements
|
170
|
+
|
171
|
+
Provide a detailed design concept with visual descriptions and rationale.
|
172
|
+
PROMPT
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_ideas(result)
|
176
|
+
# Extract numbered ideas from the response
|
177
|
+
result.scan(/^\d+\.\s*(.+)$/m).flatten.map(&:strip)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Technical Agent - Specialized in technical tasks and problem-solving
|
182
|
+
class TechnicalAgent < BaseAgent
|
183
|
+
def initialize(name: "TechnicalAgent", **opts)
|
184
|
+
super(
|
185
|
+
name: name,
|
186
|
+
role: "Technical Specialist",
|
187
|
+
capabilities: [:programming, :debugging, :system_design, :troubleshooting],
|
188
|
+
**opts
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
def solve_problem(problem_description, approach: :systematic)
|
193
|
+
return "[stubbed] Solution for: #{problem_description}" if RailsAi.config.stub_responses
|
194
|
+
|
195
|
+
problem_prompt = build_problem_solving_prompt(problem_description, approach)
|
196
|
+
result = think(problem_prompt, context: { problem: problem_description, approach: approach })
|
197
|
+
|
198
|
+
remember("solution_#{problem_description.hash}", result, importance: :high)
|
199
|
+
result
|
200
|
+
end
|
201
|
+
|
202
|
+
def code_review(code, language: :ruby)
|
203
|
+
return "[stubbed] Code review for #{language} code" if RailsAi.config.stub_responses
|
204
|
+
|
205
|
+
review_prompt = build_code_review_prompt(code, language)
|
206
|
+
result = think(review_prompt, context: { code: code, language: language })
|
207
|
+
|
208
|
+
remember("code_review_#{code.hash}", result, importance: :normal)
|
209
|
+
result
|
210
|
+
end
|
211
|
+
|
212
|
+
def design_system(requirements)
|
213
|
+
return "[stubbed] System design for: #{requirements}" if RailsAi.config.stub_responses
|
214
|
+
|
215
|
+
design_prompt = build_system_design_prompt(requirements)
|
216
|
+
result = think(design_prompt, context: { requirements: requirements })
|
217
|
+
|
218
|
+
remember("system_design_#{requirements.hash}", result, importance: :high)
|
219
|
+
result
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def build_problem_solving_prompt(problem, approach)
|
225
|
+
approach_guidance = case approach
|
226
|
+
when :systematic
|
227
|
+
"Use a systematic, step-by-step approach"
|
228
|
+
when :creative
|
229
|
+
"Think creatively and consider unconventional solutions"
|
230
|
+
when :analytical
|
231
|
+
"Focus on data analysis and logical reasoning"
|
232
|
+
end
|
233
|
+
|
234
|
+
<<~PROMPT
|
235
|
+
As a technical specialist, solve this problem: #{problem}
|
236
|
+
|
237
|
+
Approach: #{approach_guidance}
|
238
|
+
|
239
|
+
Provide:
|
240
|
+
- Problem analysis and root cause identification
|
241
|
+
- Multiple solution options with pros/cons
|
242
|
+
- Recommended solution with implementation steps
|
243
|
+
- Risk assessment and mitigation strategies
|
244
|
+
- Testing and validation approach
|
245
|
+
|
246
|
+
Be thorough and technically sound in your analysis.
|
247
|
+
PROMPT
|
248
|
+
end
|
249
|
+
|
250
|
+
def build_code_review_prompt(code, language)
|
251
|
+
<<~PROMPT
|
252
|
+
As a technical specialist, review this #{language} code:
|
253
|
+
|
254
|
+
```#{language}
|
255
|
+
#{code}
|
256
|
+
```
|
257
|
+
|
258
|
+
Provide:
|
259
|
+
- Code quality assessment
|
260
|
+
- Potential bugs or issues
|
261
|
+
- Performance considerations
|
262
|
+
- Best practices adherence
|
263
|
+
- Suggestions for improvement
|
264
|
+
- Security considerations
|
265
|
+
|
266
|
+
Be constructive and specific in your feedback.
|
267
|
+
PROMPT
|
268
|
+
end
|
269
|
+
|
270
|
+
def build_system_design_prompt(requirements)
|
271
|
+
<<~PROMPT
|
272
|
+
As a technical specialist, design a system based on these requirements: #{requirements}
|
273
|
+
|
274
|
+
Provide:
|
275
|
+
- High-level architecture overview
|
276
|
+
- Component breakdown and responsibilities
|
277
|
+
- Data flow and interactions
|
278
|
+
- Technology stack recommendations
|
279
|
+
- Scalability considerations
|
280
|
+
- Security and reliability measures
|
281
|
+
- Implementation phases
|
282
|
+
|
283
|
+
Be comprehensive and consider real-world constraints.
|
284
|
+
PROMPT
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Coordinator Agent - Specialized in managing and coordinating other agents
|
289
|
+
class CoordinatorAgent < BaseAgent
|
290
|
+
def initialize(name: "CoordinatorAgent", **opts)
|
291
|
+
super(
|
292
|
+
name: name,
|
293
|
+
role: "Coordination Specialist",
|
294
|
+
capabilities: [:coordination, :project_management, :resource_allocation, :conflict_resolution],
|
295
|
+
**opts
|
296
|
+
)
|
297
|
+
end
|
298
|
+
|
299
|
+
def coordinate_task(task, available_agents)
|
300
|
+
return "[stubbed] Coordination plan for: #{task[:description]}" if RailsAi.config.stub_responses
|
301
|
+
|
302
|
+
coordination_prompt = build_coordination_prompt(task, available_agents)
|
303
|
+
result = think(coordination_prompt, context: { task: task, agents: available_agents })
|
304
|
+
|
305
|
+
remember("coordination_#{task[:id]}", result, importance: :high)
|
306
|
+
result
|
307
|
+
end
|
308
|
+
|
309
|
+
def resolve_conflict(conflict_description, involved_agents)
|
310
|
+
return "[stubbed] Conflict resolution for: #{conflict_description}" if RailsAi.config.stub_responses
|
311
|
+
|
312
|
+
conflict_prompt = build_conflict_resolution_prompt(conflict_description, involved_agents)
|
313
|
+
result = think(conflict_prompt, context: { conflict: conflict_description, agents: involved_agents })
|
314
|
+
|
315
|
+
remember("conflict_resolution_#{Time.now.to_i}", result, importance: :high)
|
316
|
+
result
|
317
|
+
end
|
318
|
+
|
319
|
+
def optimize_workflow(workflow_description, constraints)
|
320
|
+
return "[stubbed] Workflow optimization for: #{workflow_description}" if RailsAi.config.stub_responses
|
321
|
+
|
322
|
+
optimization_prompt = build_optimization_prompt(workflow_description, constraints)
|
323
|
+
result = think(optimization_prompt, context: { workflow: workflow_description, constraints: constraints })
|
324
|
+
|
325
|
+
remember("workflow_optimization_#{Time.now.to_i}", result, importance: :normal)
|
326
|
+
result
|
327
|
+
end
|
328
|
+
|
329
|
+
private
|
330
|
+
|
331
|
+
def build_coordination_prompt(task, agents)
|
332
|
+
agent_info = agents.map { |a| "#{a.name} (#{a.role}): #{a.capabilities.join(', ')}" }.join("\n")
|
333
|
+
|
334
|
+
<<~PROMPT
|
335
|
+
As a coordination specialist, create a coordination plan for this task: #{task[:description]}
|
336
|
+
|
337
|
+
Available agents:
|
338
|
+
#{agent_info}
|
339
|
+
|
340
|
+
Create a plan that:
|
341
|
+
- Assigns appropriate roles to each agent
|
342
|
+
- Defines clear responsibilities and deliverables
|
343
|
+
- Establishes communication protocols
|
344
|
+
- Sets timelines and milestones
|
345
|
+
- Identifies dependencies and coordination points
|
346
|
+
- Includes quality assurance measures
|
347
|
+
|
348
|
+
Provide a detailed coordination strategy.
|
349
|
+
PROMPT
|
350
|
+
end
|
351
|
+
|
352
|
+
def build_conflict_resolution_prompt(conflict, agents)
|
353
|
+
agent_info = agents.map { |a| "#{a.name} (#{a.role})" }.join(", ")
|
354
|
+
|
355
|
+
<<~PROMPT
|
356
|
+
As a coordination specialist, resolve this conflict: #{conflict}
|
357
|
+
|
358
|
+
Involved agents: #{agent_info}
|
359
|
+
|
360
|
+
Provide:
|
361
|
+
- Conflict analysis and root causes
|
362
|
+
- Mediation strategy
|
363
|
+
- Resolution approach
|
364
|
+
- Prevention measures
|
365
|
+
- Communication guidelines
|
366
|
+
- Follow-up procedures
|
367
|
+
|
368
|
+
Focus on fair and constructive resolution.
|
369
|
+
PROMPT
|
370
|
+
end
|
371
|
+
|
372
|
+
def build_optimization_prompt(workflow, constraints)
|
373
|
+
<<~PROMPT
|
374
|
+
As a coordination specialist, optimize this workflow: #{workflow}
|
375
|
+
|
376
|
+
Constraints: #{constraints}
|
377
|
+
|
378
|
+
Provide:
|
379
|
+
- Current workflow analysis
|
380
|
+
- Bottleneck identification
|
381
|
+
- Optimization opportunities
|
382
|
+
- Improved workflow design
|
383
|
+
- Implementation strategy
|
384
|
+
- Performance metrics
|
385
|
+
|
386
|
+
Focus on efficiency and effectiveness improvements.
|
387
|
+
PROMPT
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Agents
|
5
|
+
class TaskQueue
|
6
|
+
PRIORITY_LEVELS = {
|
7
|
+
critical: 4,
|
8
|
+
high: 3,
|
9
|
+
normal: 2,
|
10
|
+
low: 1
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@tasks = []
|
15
|
+
@total_processed = 0
|
16
|
+
@mutex = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def enqueue(task, priority: :normal)
|
20
|
+
task_with_priority = task.merge(
|
21
|
+
id: task[:id] || SecureRandom.uuid,
|
22
|
+
priority: priority,
|
23
|
+
priority_score: PRIORITY_LEVELS[priority] || 2,
|
24
|
+
enqueued_at: Time.now,
|
25
|
+
status: :pending
|
26
|
+
)
|
27
|
+
|
28
|
+
@mutex.synchronize do
|
29
|
+
@tasks << task_with_priority
|
30
|
+
@tasks.sort_by! { |t| [-t[:priority_score], t[:enqueued_at]] }
|
31
|
+
end
|
32
|
+
|
33
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Task enqueued: #{task[:description]} (priority: #{priority})")
|
34
|
+
task_with_priority
|
35
|
+
end
|
36
|
+
|
37
|
+
def dequeue(timeout: nil)
|
38
|
+
start_time = Time.now
|
39
|
+
|
40
|
+
loop do
|
41
|
+
@mutex.synchronize do
|
42
|
+
return @tasks.shift if @tasks.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
if timeout && (Time.now - start_time) > timeout
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
|
49
|
+
sleep(0.1)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def peek
|
54
|
+
@mutex.synchronize { @tasks.first }
|
55
|
+
end
|
56
|
+
|
57
|
+
def size
|
58
|
+
@mutex.synchronize { @tasks.length }
|
59
|
+
end
|
60
|
+
|
61
|
+
def empty?
|
62
|
+
@mutex.synchronize { @tasks.empty? }
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear!
|
66
|
+
@mutex.synchronize { @tasks.clear }
|
67
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Task queue cleared")
|
68
|
+
end
|
69
|
+
|
70
|
+
def remove_task(task_id)
|
71
|
+
@mutex.synchronize do
|
72
|
+
task = @tasks.find { |t| t[:id] == task_id }
|
73
|
+
@tasks.delete(task) if task
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_tasks_by_status(status)
|
78
|
+
@mutex.synchronize do
|
79
|
+
@tasks.select { |t| t[:status] == status }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_tasks_by_priority(priority)
|
84
|
+
@mutex.synchronize do
|
85
|
+
@tasks.select { |t| t[:priority] == priority }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def mark_processed(task_id)
|
90
|
+
@total_processed += 1
|
91
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Task processed: #{task_id} (total: #{@total_processed})")
|
92
|
+
end
|
93
|
+
|
94
|
+
def stats
|
95
|
+
@mutex.synchronize do
|
96
|
+
{
|
97
|
+
total_tasks: @tasks.length,
|
98
|
+
total_processed: @total_processed,
|
99
|
+
by_priority: @tasks.group_by { |t| t[:priority] }.transform_values(&:length),
|
100
|
+
by_status: @tasks.group_by { |t| t[:status] }.transform_values(&:length),
|
101
|
+
oldest_task: @tasks.min_by { |t| t[:enqueued_at] }&.dig(:enqueued_at)
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def total_processed
|
107
|
+
@total_processed
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Cache
|
5
|
+
def self.fetch(key, **opts, &block)
|
6
|
+
if defined?(Rails) && Rails.cache
|
7
|
+
Rails.cache.fetch([:rails_ai, key], {expires_in: RailsAi.config.cache_ttl}.merge(opts), &block)
|
8
|
+
elsif block_given?
|
9
|
+
# Fallback for when Rails cache is not available (e.g., in tests)
|
10
|
+
block.call
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
Config = Struct.new(
|
5
|
+
:provider,
|
6
|
+
:default_model,
|
7
|
+
:token_limit,
|
8
|
+
:cache_ttl,
|
9
|
+
:stub_responses,
|
10
|
+
:connection_pool_size,
|
11
|
+
:compression_threshold,
|
12
|
+
:batch_size,
|
13
|
+
:flush_interval,
|
14
|
+
:enable_performance_monitoring,
|
15
|
+
:enable_request_deduplication,
|
16
|
+
:enable_compression,
|
17
|
+
keyword_init: true
|
18
|
+
)
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
@config ||= Config.new(
|
22
|
+
provider: :openai,
|
23
|
+
default_model: "gpt-4o-mini",
|
24
|
+
token_limit: 4000,
|
25
|
+
cache_ttl: 3600, # 1 hour in seconds
|
26
|
+
stub_responses: false,
|
27
|
+
connection_pool_size: 10,
|
28
|
+
compression_threshold: 1024, # bytes
|
29
|
+
batch_size: 10,
|
30
|
+
flush_interval: 0.1, # seconds
|
31
|
+
enable_performance_monitoring: true,
|
32
|
+
enable_request_deduplication: true,
|
33
|
+
enable_compression: true
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.configure
|
38
|
+
yield config
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
class ContextAnalyzer
|
5
|
+
attr_reader :user_context, :window_context, :image_context
|
6
|
+
|
7
|
+
def initialize(user_context: nil, window_context: nil, image_context: nil)
|
8
|
+
@user_context = user_context || {}
|
9
|
+
@window_context = window_context || {}
|
10
|
+
@image_context = image_context || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def analyze_with_context(image, prompt, **opts)
|
14
|
+
enhanced_prompt = build_context_aware_prompt(image, prompt)
|
15
|
+
|
16
|
+
RailsAi.analyze_image(image, enhanced_prompt, **opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_with_context(prompt, **opts)
|
20
|
+
enhanced_prompt = build_context_aware_prompt(nil, prompt)
|
21
|
+
|
22
|
+
RailsAi.chat(enhanced_prompt, **opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_image_with_context(prompt, **opts)
|
26
|
+
enhanced_prompt = build_context_aware_prompt(nil, prompt)
|
27
|
+
|
28
|
+
RailsAi.generate_image(enhanced_prompt, **opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_context_aware_prompt(image, original_prompt)
|
34
|
+
context_parts = []
|
35
|
+
|
36
|
+
# Add user context
|
37
|
+
if user_context.any?
|
38
|
+
context_parts << "User Context: #{format_context(user_context)}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add window/application context
|
42
|
+
if window_context.any?
|
43
|
+
context_parts << "Application Context: #{format_context(window_context)}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add image context if analyzing an image
|
47
|
+
if image && image_context.any?
|
48
|
+
context_parts << "Image Context: #{format_context(image_context)}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add current time and date context
|
52
|
+
context_parts << "Current Time: #{current_time}"
|
53
|
+
|
54
|
+
# Add Rails environment context
|
55
|
+
context_parts << "Environment: #{Rails.env}" if defined?(Rails)
|
56
|
+
|
57
|
+
# Combine all context with the original prompt
|
58
|
+
if context_parts.any?
|
59
|
+
"#{context_parts.join('\n\n')}\n\nOriginal Request: #{original_prompt}"
|
60
|
+
else
|
61
|
+
original_prompt
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def format_context(context_hash)
|
66
|
+
context_hash.map do |key, value|
|
67
|
+
case value
|
68
|
+
when Hash
|
69
|
+
"#{key}: #{format_context(value)}"
|
70
|
+
when Array
|
71
|
+
"#{key}: #{value.join(', ')}"
|
72
|
+
else
|
73
|
+
"#{key}: #{value}"
|
74
|
+
end
|
75
|
+
end.join('\n')
|
76
|
+
end
|
77
|
+
|
78
|
+
def current_time
|
79
|
+
if defined?(Time.current)
|
80
|
+
Time.current.strftime('%Y-%m-%d %H:%M:%S %Z')
|
81
|
+
else
|
82
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace RailsAi
|
6
|
+
|
7
|
+
# Rails version compatibility
|
8
|
+
if Rails.version >= "8.0"
|
9
|
+
# Rails 8 specific configuration
|
10
|
+
config.generators do |g|
|
11
|
+
g.test_framework :rspec
|
12
|
+
g.fixture_replacement :factory_bot
|
13
|
+
g.orm :active_record
|
14
|
+
end
|
15
|
+
elsif Rails.version >= "7.0"
|
16
|
+
# Rails 7 specific configuration
|
17
|
+
config.generators do |g|
|
18
|
+
g.test_framework :rspec
|
19
|
+
g.fixture_replacement :factory_bot
|
20
|
+
end
|
21
|
+
|
22
|
+
# Importmap support for Rails 7+
|
23
|
+
initializer "rails_ai.importmap", before: "importmap" do |app|
|
24
|
+
app.config.importmap.paths << root.join("app/assets/javascripts")
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# Rails 5.2+ and 6.x configuration
|
28
|
+
config.generators do |g|
|
29
|
+
g.test_framework :rspec
|
30
|
+
g.fixture_replacement :factory_bot
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Asset precompilation (compatible with all Rails versions)
|
35
|
+
initializer "rails_ai.assets.precompile" do |app|
|
36
|
+
if app.config.respond_to?(:assets)
|
37
|
+
app.config.assets.precompile += %w[rails_ai_manifest.js]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Routes configuration
|
42
|
+
initializer "rails_ai.routes" do |app|
|
43
|
+
app.routes.prepend do
|
44
|
+
mount RailsAi::Engine, at: "/rails_ai"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|