Primitive insight queries

This commit is contained in:
Jorge Manrubia
2025-05-10 15:36:15 +02:00
parent 6d63466a07
commit 007492add0
7 changed files with 2679 additions and 5 deletions
@@ -1,6 +1,7 @@
import { Controller } from "@hotwired/stimulus"
import { HttpStatus } from "helpers/http_helpers"
import { delay } from "helpers/timing_helpers"
import { marked } from "marked"
export default class extends Controller {
static targets = [ "input", "form", "confirmation" ]
@@ -43,7 +44,11 @@ export default class extends Controller {
const response = event.detail.fetchResponse
if (response && response.response.headers.get("Content-Type")?.includes("application/json")) {
response.response.json().then((commands) => {
this.element.querySelector("#chat-responses").textContent = JSON.stringify(commands, null, 2)
if (commands.reply) {
this.element.querySelector("#chat-insight").innerHTML = marked.parse(commands.reply)
} else {
this.element.querySelector("#chat-responses").textContent = JSON.stringify(commands, null, 2)
}
this.#executeCommands(commands)
})
} else {
+10 -2
View File
@@ -7,7 +7,6 @@ class Command::ChatQuery < Command
def execute
response = chat.ask query
Rails.logger.info "***\n#{response.content}\n***"
response = replace_names_with_ids(JSON.parse(response.content))
Command::Result::ChatResponse.new(JSON.pretty_generate(response))
end
@@ -28,6 +27,7 @@ class Command::ChatQuery < Command
- Close cards: /close [optional reason]
- Tag cards: /tag [tag-name]
- Search cards: /search. See how this works below:
- Get insight about cards: /insight [query]
asks for a certain set of cards, you can use the /search command to filter. The /search command (and only this
command) supports the following parameters:
@@ -55,7 +55,7 @@ class Command::ChatQuery < Command
Notice that there are overlapping commands (filter by assignee or assign cards). Favor filtering/queries for
commands like "cards assigned to someone".
Notice that only /search commands carry additional JSON params. For /tag, /close and /assign just append the
Notice that only /search commands carry additional JSON params. For /tag, /close, /insight and /assign just append the
param to the string command. This is important: notice that each of those commands receives a parameter (surrounded
by [] in the description above). Make sure if you invoke a given command you pass the params. Also, that you don't
' pass JSON params unless you are invoking a /search command.
@@ -69,6 +69,14 @@ class Command::ChatQuery < Command
]
}
When the user makes questions or requests that are not related to performing actions on cards, or to filtering cards,
you should try to answer those by using the /insight command. Before invoking /insight, you need to generate a /search
command that filters out the relevant cards to answer the question. Determine some good keywords to pass to /search, so
that we can filter out the relevant cards to answer the question.
When passing terms to /search or the query to /insight, remove generic/common words such as "problem"" and "issue"" and keep
the more meaningful nouns only.
Please combine commands to satisfy what the user needs. E.g: search with keywords and filters and then apply
as many commands as needed. Make sure you don't leave actions mentioned in the query needs unattended.'
+75
View File
@@ -0,0 +1,75 @@
class Command::GetInsight < Command
include Command::Cards
store_accessor :data, :query
def title
"Insight query '#{query}'"
end
def execute
response = chat.ask query
Command::Result::ChatResponse.new({ reply: response.content })
end
def undoable?
false
end
def needs_confirmation?
false
end
private
def chat
chat = RubyLLM.chat
chat.with_instructions(prompt + cards_context)
end
def prompt
<<~PROMPT
You are a helpful assistant that is able to provide answers and insights about cards.
A card has a title, a description and a list of comments. When presenting some insight, at the end,
list the sources as:
Try to be concise and accurate.
See: card id 1, card id 2, and comment id 123.
Use markdown for the response format.
PROMPT
end
def cards_context
"".tap do |context|
cards.order("created_at desc").limit(25).collect do |card|
context << card_context_for(card)
card.comments.each do |comment|
context << comment_context_for(comment)
end
end
end
end
def card_context_for(card)
<<~CONTEXT
Card created by: #{card.creator.name}}
Id: #{card.id}
Title: #{card.title}
Description: #{card.description.to_plain_text}
Assigned to: #{card.assignees.map(&:name).join(", ")}}
Created at: #{card.created_at}}
CONTEXT
end
def comment_context_for(comment)
<<~CONTEXT
Card: #{comment.card.id}
Id: #{comment.id}
Content: #{comment.body.to_plain_text}}
Comment created by: #{comment.creator.name}}
CONTEXT
end
end
+2
View File
@@ -25,6 +25,8 @@ class Command::Parser
Command::GoToUser.new(user_id: assignee_from(command_name)&.id)
when "/assign", "/assignto"
Command::Assign.new(assignee_ids: assignees_from(command_arguments).collect(&:id), card_ids: cards.ids)
when "/insight"
Command::GetInsight.new(query: command_arguments.join(" "), card_ids: cards.ids)
when "/clear"
Command::ClearFilters.new(params: filter.as_params)
when "/close"
+2 -2
View File
@@ -2,7 +2,7 @@
<ul class="terminal__menu flex-column txt-align-start margin-none margin-block-end unpad" aria-label="Recent commands" data-turbo-permanent="true"><%= render partial: "commands/command", collection: @commands %></ul>
<pre id="chat-responses">
</pre>
<pre id="chat-responses"></pre>
<p id="chat-insight"></p>
<%= render "commands/help" %>
<% end %>
+1
View File
@@ -10,3 +10,4 @@ pin "house", to: "house.min.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/helpers", under: "helpers"
pin_all_from "app/javascript/initializers", under: "initializers"
pin "marked" # @15.0.11
+2583
View File
File diff suppressed because it is too large Load Diff