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 { Controller } from "@hotwired/stimulus"
import { HttpStatus } from "helpers/http_helpers" import { HttpStatus } from "helpers/http_helpers"
import { delay } from "helpers/timing_helpers" import { delay } from "helpers/timing_helpers"
import { marked } from "marked"
export default class extends Controller { export default class extends Controller {
static targets = [ "input", "form", "confirmation" ] static targets = [ "input", "form", "confirmation" ]
@@ -43,7 +44,11 @@ export default class extends Controller {
const response = event.detail.fetchResponse const response = event.detail.fetchResponse
if (response && response.response.headers.get("Content-Type")?.includes("application/json")) { if (response && response.response.headers.get("Content-Type")?.includes("application/json")) {
response.response.json().then((commands) => { 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) this.#executeCommands(commands)
}) })
} else { } else {
+10 -2
View File
@@ -7,7 +7,6 @@ class Command::ChatQuery < Command
def execute def execute
response = chat.ask query response = chat.ask query
Rails.logger.info "***\n#{response.content}\n***"
response = replace_names_with_ids(JSON.parse(response.content)) response = replace_names_with_ids(JSON.parse(response.content))
Command::Result::ChatResponse.new(JSON.pretty_generate(response)) Command::Result::ChatResponse.new(JSON.pretty_generate(response))
end end
@@ -28,6 +27,7 @@ class Command::ChatQuery < Command
- Close cards: /close [optional reason] - Close cards: /close [optional reason]
- Tag cards: /tag [tag-name] - Tag cards: /tag [tag-name]
- Search cards: /search. See how this works below: - 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 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: 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 Notice that there are overlapping commands (filter by assignee or assign cards). Favor filtering/queries for
commands like "cards assigned to someone". 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 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 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. ' 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 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.' 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) Command::GoToUser.new(user_id: assignee_from(command_name)&.id)
when "/assign", "/assignto" when "/assign", "/assignto"
Command::Assign.new(assignee_ids: assignees_from(command_arguments).collect(&:id), card_ids: cards.ids) 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" when "/clear"
Command::ClearFilters.new(params: filter.as_params) Command::ClearFilters.new(params: filter.as_params)
when "/close" 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> <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 id="chat-responses"></pre>
</pre> <p id="chat-insight"></p>
<%= render "commands/help" %> <%= render "commands/help" %>
<% end %> <% 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/controllers", under: "controllers"
pin_all_from "app/javascript/helpers", under: "helpers" pin_all_from "app/javascript/helpers", under: "helpers"
pin_all_from "app/javascript/initializers", under: "initializers" 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