Showing insights with a better foundation

This commit is contained in:
Jorge Manrubia
2025-05-13 06:50:02 +02:00
parent cd53ef59eb
commit 9b8ebf09ef
7 changed files with 66 additions and 21 deletions
+8 -2
View File
@@ -42,6 +42,8 @@ class CommandsController < ApplicationController
redirect_to result.url
when Command::Result::ChatResponse
respond_with_chat_response(result)
when Command::Result::InsightResponse
respond_with_insight_response(result)
else
redirect_back_or_to root_path
end
@@ -51,8 +53,8 @@ class CommandsController < ApplicationController
command = chat_response_to_command(result)
if confirmed?(command)
command.execute
redirect_back_or_to root_path
result = command.execute
respond_with_execution_result result
else
respond_with_needs_confirmation(command.commands, redirect_to: result.context_url)
end
@@ -67,4 +69,8 @@ class CommandsController < ApplicationController
parser = Command::Parser.new(context)
Command::Composite.new(chat_response.command_lines.collect { parser.parse it })
end
def respond_with_insight_response(chat_response)
render json: { message: chat_response.content }, status: :accepted
end
end
@@ -44,10 +44,16 @@ export default class extends Controller {
}
handleCommandResponse(event) {
const response = event.detail.fetchResponse?.response
if (event.detail.success) {
if (response.headers.get("Content-Type")?.includes("application/json")) {
response.json().then((responseJson) => {
this.element.querySelector("#chat-insight").innerHTML = marked.parse(responseJson.message)
})
}
this.#reset()
} else {
const response = event.detail.fetchResponse.response
} else if (response) {
this.#handleErrorResponse(response)
}
}
@@ -88,7 +94,6 @@ export default class extends Controller {
}
#reset(inputValue = "") {
console.debug("RESET!")
this.inputTarget.value = inputValue
this.confirmationTarget.value = ""
this.waitingForConfirmationValue = false
@@ -104,7 +109,6 @@ export default class extends Controller {
async #requestConfirmation(jsonResponse) {
const message = jsonResponse.commands[0]
console.debug("request confirmation", this.inputTarget.value);
this.originalInputValue = this.inputTarget.value
+31 -12
View File
@@ -7,7 +7,9 @@ class Command::ChatQuery < Command
def execute
response = chat.ask query
generated_commands = replace_names_with_ids(JSON.parse(response.content))
generated_commands = replace_names_with_ids(JSON.parse(response.content)).tap do |commands|
Rails.logger.info "*** #{commands}"
end
build_chat_response_with generated_commands
end
@@ -21,7 +23,7 @@ class Command::ChatQuery < Command
# - Don't generate initial /search if not requested. "Assign to JZ" should
def prompt
<<~PROMPT
You are a helpful assistant that translates natural language into commands that Fizzy understand.
You are a helpful assistant that translates natural language into one or more commands that Fizzy understand.
Fizzy supports the following commands:
@@ -30,7 +32,8 @@ class Command::ChatQuery < Command
- Tag cards: /tag [tag-name]. E.g: "/tag performance"
- Get insight about cards: /insight [query]. Use this as the default command to satisfy questions and requests
about cards. This relies on /search. Example: "/insight summarize performance issues".
- Search cards based on certain keywords: /search. See how this works below. E.g: "/search meetup montreal"
- Search cards based on certain keywords: /search. See how this works below. E.g: "/search meetup montreal". IMPORTANT: There can
only be one /search command in the response.
If you need to filter a certain set of cards, you can use the /search command to filter.
@@ -56,14 +59,26 @@ class Command::ChatQuery < Command
{ command: "/search", indexed_by: "closed", collection_ids: [ "Writebook", "Design" ] }
Notice that there are overlapping commands (filter by assignee or assign cards). Favor filtering/queries for
commands like "cards assigned to someone".
Notice that there are similar commands to filter and act on cards (e.g: filter by assignee or assign cards). Favor
filtering/queries for commands like "cards assigned to someone".
For example, to assign a card, you invoke `assign kevin`. For insight about "something", you invoke "/insight something".
Important: When using the /insight command, ALWAYS add first a /search command that filters out the relevant cards to answer
Important:
- When using the /insight command, consider adding first a /search command that filters out the relevant cards to answer
the question. If there are relevant keywords to filter, pass those to /search but avoid passing generic ones. Then, reformulate
pass the query itself VERBATIM to /insight as in "/insight <original query>", no additional keys in the JSON.
- Only add an /insight command is there is a specific question about the data. Some requests are just about searching some
cards. Those are fine.
- Sometimes, the current context is a given card or the set of cards in the screen. In that case, there is no need to add a
/search command. Assume that's the case if it's missing any description of the the set of cards required. E.g: just "summarise"
or "this card".
- A response can only contain ONE /search command AT MOST.
- A response can only contain ONE /insight command AT MOST.
- Unless asking for explicit filtering, always prefer /insight over /search. When asking about "cards" with properties that can
be satisfied with /search, then use /search.
For example, for "summarize performance issues", the JSON could be:
@@ -77,10 +92,6 @@ class Command::ChatQuery < Command
}
]
Unless asking for explicit filtering, always prefer /insight over /search. When asking about "cards" with properties that can
be satisfied with /search, then use /search.
A response can contain at most one /search command.
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.'
@@ -89,6 +100,8 @@ class Command::ChatQuery < Command
/assign don't support additional JSON keys, they will only contain the "command:" key". For /search, it can contain additional
JSON keys matching the /search params described above.
Don't generate any /search without params. Just don't add it.
Avoid empty preambles like "Based on the provided cards". Also, prefer a natural a friendly language favoring active voice.
Make sure to place into double quotes the strings in JSON values and that you generate valid JSON. I want a
@@ -122,11 +135,17 @@ class Command::ChatQuery < Command
end
def response_command_lines_from(generated_commands)
generated_commands.filter { it["command"] != "/search" }.collect { it["command"] }
# We translate standalone /search commands as redirections to execute. Otherwise, they
# will be excluded out from the commands to run, as they represent the context url.
if generated_commands.size == 1
[ "/visit #{cards_path(**generated_commands.first.without("command"))}" ]
else
generated_commands.filter { it["command"] != "/search" }.collect { it["command"] }
end
end
def response_context_url_from(generated_commands)
if search_command = generated_commands.find { it["command"] == "/search" }
if generated_commands.size > 1 && search_command = generated_commands.find { it["command"] == "/search" }
cards_path(**search_command.without("command"))
end
end
+3 -1
View File
@@ -11,9 +11,11 @@ class Command::Composite
end
def execute
result = nil
ApplicationRecord.transaction do
commands.each(&:execute)
commands.each { result = it.execute }
end
result
end
def undo
+3 -2
View File
@@ -9,7 +9,7 @@ class Command::GetInsight < Command
def execute
response = chat.ask query
Command::Result::InsightResponse.new({ reply: response.content })
Command::Result::InsightResponse.new(response.content)
end
def undoable?
@@ -59,6 +59,7 @@ class Command::GetInsight < Command
def card_context_for(card)
<<~CONTEXT
==CARD==
Title: #{card.title}
Card created by: #{card.creator.name}}
Id: #{card.id}
@@ -73,7 +74,7 @@ class Command::GetInsight < Command
def comment_context_for(comment)
<<~CONTEXT
Card: #{comment.card.id}
==COMMENT==
Id: #{comment.id}
Content: #{comment.body.to_plain_text}}
Comment created by: #{comment.creator.name}}
+2
View File
@@ -31,6 +31,8 @@ class Command::Parser
Command::ClearFilters.new(params: filter.as_params)
when "/close"
Command::Close.new(card_ids: cards.ids, reason: command_arguments.join(" "))
when "/visit"
Command::VisitUrl.new(url: command_arguments.first)
when "/tag"
Command::Tag.new(tag_title: tag_title_from(command_arguments.join(" ")), card_ids: cards.ids)
else
+11
View File
@@ -0,0 +1,11 @@
class Command::VisitUrl < Command
store_accessor :data, :url
def title
"Visit #{url}"
end
def execute
redirect_to url
end
end