PoC running commands

This commit is contained in:
Jorge Manrubia
2025-05-10 11:17:35 +02:00
parent b7ee0b284d
commit 413dfce5aa
4 changed files with 84 additions and 14 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ class CommandsController < ApplicationController
when Command::Result::Redirection
redirect_to result.url
when Command::Result::ChatResponse
render turbo_stream: turbo_stream.append("chat-responses", partial: "commands/chat/response", locals: { content: result.content })
render json: result.content, status: :accepted
else
redirect_back_or_to root_path
end
@@ -1,5 +1,6 @@
import { Controller } from "@hotwired/stimulus"
import { HttpStatus } from "helpers/http_helpers"
import { delay } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "input", "form", "confirmation" ]
@@ -39,7 +40,15 @@ export default class extends Controller {
handleCommandResponse(event) {
if (event.detail.success) {
this.#reset()
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)
this.#executeCommands(commands)
});
} else {
this.#reset()
}
} else {
const response = event.detail.fetchResponse.response
this.#handleErrorResponse(response)
@@ -117,4 +126,18 @@ export default class extends Controller {
this.confirmationTarget.value = "confirmed"
this.formTarget.requestSubmit()
}
async #executeCommands(commands) {
for (const command of commands) {
if (command.command == "/search") {
const params = new URLSearchParams(command)
// Clunky example, for PoC purposes
Turbo.visit(`/cards?${params.toString()}`)
await delay(2000)
} else {
this.inputTarget.value = command.command
this.formTarget.requestSubmit()
}
}
}
}
+58 -11
View File
@@ -7,31 +7,78 @@ class Command::ChatQuery < Command
def execute
response = chat.ask query
Command::Result::ChatResponse.new(response.content)
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
private
def chat
chat = RubyLLM.chat
chat.with_instructions prompt
chat.with_instructions(prompt)
end
def prompt
<<~PROMPT
You are a helpful assistant to translate natural language into commands that Fizzy understand, and to
answer questions about the Fizzy system and the information it contains.
You are a helpful assistant that translates natural language into commands that Fizzy understand, and that
can answer questions about the Fizzy system and the information it contains.
Fizzy supports the following commands:
- Assign users to cards: /assign user
- Close cards: /close
- Tag cards: /tag
- Search cards: /search query
- Close cards: /close optional reason
- Tag cards: /tag tag-name
- Search cards: /search. See how this works below:
You have to assume that the cards will be derived from the screen the user is at. But, if the user is
querying for certain cards, you can use the /search command to search for them./
asks for a certain set of cards, you can use the /search command to filter. It supports the following
conditions:
The output must be simply a list of commands satisfying the user request, separated by newlines.
PROMPT
- assignment_status: can be "unassigned"
- indexed_by: can be "newest", "oldest", "latest", "stalled", "closed"
- engagement_status: can be "considering" or "doing"
- card_ids: a list of card ids
- assignee_ids: a list of assignee names
- creator_id: the name of a person
- collection_ids: a list of collection names. Cards are contained in collections. Don't use unless mentioning
specific collections.
- tag_ids: a list of tag names.
- terms: a list of terms to search for. Use this option to refine searches based on further keyword-based
queries.
Please combine commands to satisfy what the user needs. E.g: search with keywords and filters and then apply
as many commands as needed.
The output will be in JSON. It will contain a list of commands. Each command will be a JSON object like:
{ command: "/close" }
For the case of the /search command, it will also contain the additional params. E.g:
{ command: "/search", indexed_by: "closed", collection_ids: [ "Writebook", "Design" ] }
Notice that only /search commands carry additional JSON params. For /tag, /close and /assign just append the
param to the string command.
Make sure to place into double quotes the strings in JSON values and that you generate valid JSON. I want a
JSON list like [{}, {}...]
PROMPT
end
def replace_names_with_ids(commands)
commands.each do |command|
if command["command"] == "/search"
command["assignee_ids"] = command["assignee_ids"]&.filter_map { |name| assignee_from(name).id }
command["creator_id"] = assignee_from(command["creator_id"]).id if command["creator_id"]
command["collection_ids"] = command["collection_ids"]&.filter_map { |name| Collection.where("lower(name) = ?", name.downcase).first&.id }
command["tag_ids"] = command["tag_ids"]&.filter_map { |name| ::Tag.find_by_title(name)&.id }
command.compact!
end
end
end
def assignee_from(string)
string_without_at = string.delete_prefix("@")
User.all.find { |user| user.mentionable_handles.include?(string_without_at) }
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
<%= turbo_frame_tag :recent_commands do %>
<ul class="terminal__menu flex-column txt-align-start margin-none margin-block-end unpad" aria-label="Recent commands"><%= 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">