-
Notifications
You must be signed in to change notification settings - Fork 18
feat: chat docked as the homepage command bar + suggested prompts (Catalog HQ 3/3, chat#1850) #1854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| "use client"; | ||
|
|
||
| import { useVercelChatContext } from "@/providers/VercelChatProvider"; | ||
| import { useArtistProvider } from "@/providers/ArtistProvider"; | ||
| import useHomeValuation from "@/hooks/useHomeValuation"; | ||
| import useHomeTasksModuleState from "@/hooks/useHomeTasksModuleState"; | ||
| import { getHomeSuggestedPrompts } from "@/lib/home/getHomeSuggestedPrompts"; | ||
|
|
||
| /** | ||
| * Suggested prompt chips for the homepage command bar (recoupable/chat#1850). | ||
| * Fully self-contained per the KISS review: module relevance comes from the | ||
| * same provider-backed hooks the header uses (react-query dedupes by key), | ||
| * and clicking a chip prefills the existing chat input via the provider's | ||
| * `setInput` — the send itself goes through the untouched send path. Renders | ||
| * nothing when no module has data, so no mount needs to know it exists. | ||
| */ | ||
| const HomeSuggestedPrompts = () => { | ||
| const { setInput } = useVercelChatContext(); | ||
| const { selectedArtist } = useArtistProvider(); | ||
| const valuation = useHomeValuation(); | ||
| const tasksState = useHomeTasksModuleState(); | ||
|
|
||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: valuation.show, | ||
| hasRuns: tasksState.view === "runs", | ||
| artistName: selectedArtist?.name || "", | ||
| }); | ||
|
|
||
| if (prompts.length === 0) return null; | ||
|
|
||
| return ( | ||
| <div | ||
| className="flex flex-wrap justify-center gap-2" | ||
| role="group" | ||
| aria-label="Suggested prompts" | ||
| > | ||
| {prompts.map((p) => ( | ||
| <button | ||
| key={p.label} | ||
| type="button" | ||
| onClick={() => setInput(p.prompt)} | ||
| className="rounded-full bg-background px-3 py-1.5 text-xs font-medium text-foreground shadow-[0px_0px_0px_1px_var(--border)] transition-colors hover:bg-muted" | ||
| > | ||
| {p.label} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default HomeSuggestedPrompts; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { useTaskRuns } from "@/hooks/useTaskRuns"; | ||
| import { useArtistProvider } from "@/providers/ArtistProvider"; | ||
| import { | ||
| getHomeTasksModuleState, | ||
| HomeTasksModuleState, | ||
| } from "@/lib/home/getHomeTasksModuleState"; | ||
|
|
||
| /** | ||
| * Composed hook behind the homepage tasks module. Shared by `TasksModule` | ||
| * and `HomePage` (which needs the visibility to dock the command bar); | ||
| * react-query dedupes the underlying `GET /api/tasks/runs` by key. | ||
| */ | ||
| const useHomeTasksModuleState = (): HomeTasksModuleState => { | ||
| const { data: runs, isLoading, isError } = useTaskRuns(); | ||
| const { selectedArtist } = useArtistProvider(); | ||
|
|
||
| return getHomeTasksModuleState({ | ||
| runs, | ||
| runsFailed: isError, | ||
| isLoading, | ||
| hasArtist: !!selectedArtist?.account_id, | ||
| }); | ||
| }; | ||
|
|
||
| export default useHomeTasksModuleState; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { getHomeSuggestedPrompts } from "@/lib/home/getHomeSuggestedPrompts"; | ||
|
|
||
| describe("getHomeSuggestedPrompts", () => { | ||
| it("wires chips to the visible modules", () => { | ||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: true, | ||
| hasRuns: true, | ||
| artistName: "Del Water Gap", | ||
| }); | ||
|
|
||
| expect(prompts.map((p) => p.label)).toEqual([ | ||
| "Why did my valuation change?", | ||
| "What did my last task run do?", | ||
| "What should Del Water Gap do this week?", | ||
| ]); | ||
| }); | ||
|
|
||
| it("caps at three chips", () => { | ||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: true, | ||
| hasRuns: true, | ||
| artistName: "Del Water Gap", | ||
| }); | ||
| expect(prompts.length).toBeLessThanOrEqual(3); | ||
| }); | ||
|
|
||
| it("falls back to a catalog-value chip when no valuation is shown", () => { | ||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: false, | ||
| hasRuns: false, | ||
| artistName: "Del Water Gap", | ||
| }); | ||
|
|
||
| expect(prompts.map((p) => p.label)).toEqual([ | ||
| "What's my catalog worth?", | ||
| "What should Del Water Gap do this week?", | ||
| ]); | ||
| }); | ||
|
|
||
| it("omits the artist chip when no artist is selected", () => { | ||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: true, | ||
| hasRuns: false, | ||
| artistName: "", | ||
| }); | ||
|
|
||
| expect(prompts.map((p) => p.label)).toEqual([ | ||
| "Why did my valuation change?", | ||
| ]); | ||
| }); | ||
|
|
||
| it("provides a sendable prompt for every chip", () => { | ||
| const prompts = getHomeSuggestedPrompts({ | ||
| hasValuation: true, | ||
| hasRuns: true, | ||
| artistName: "Del Water Gap", | ||
| }); | ||
| for (const p of prompts) { | ||
| expect(p.prompt.length).toBeGreaterThan(10); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| export interface HomeSuggestedPrompt { | ||
| label: string; | ||
| prompt: string; | ||
| } | ||
|
|
||
| interface GetHomeSuggestedPromptsParams { | ||
| hasValuation: boolean; | ||
| hasRuns: boolean; | ||
| artistName: string; | ||
| } | ||
|
|
||
| const MAX_PROMPTS = 3; | ||
|
|
||
| /** | ||
| * Suggested prompt chips for the homepage command bar, wired to whichever | ||
| * modules are visible (valuation hero, tasks module) so the chat's job is | ||
| * interrogating the dashboard data (recoupable/chat#1850). | ||
| */ | ||
| export function getHomeSuggestedPrompts({ | ||
| hasValuation, | ||
| hasRuns, | ||
| artistName, | ||
| }: GetHomeSuggestedPromptsParams): HomeSuggestedPrompt[] { | ||
| const prompts: HomeSuggestedPrompt[] = []; | ||
|
|
||
| if (hasValuation) { | ||
| prompts.push({ | ||
| label: "Why did my valuation change?", | ||
| prompt: | ||
| "Why did my catalog valuation change? Walk through the latest " + | ||
| "measurements and explain what moved the estimate.", | ||
| }); | ||
| } else { | ||
| prompts.push({ | ||
| label: "What's my catalog worth?", | ||
| prompt: `Estimate the catalog value for ${ | ||
| artistName || "my roster" | ||
| } from public streaming data.`, | ||
| }); | ||
| } | ||
|
|
||
| if (hasRuns) { | ||
| prompts.push({ | ||
| label: "What did my last task run do?", | ||
| prompt: | ||
| "What did my last task run do? Summarize its output and anything it sent.", | ||
| }); | ||
| } | ||
|
|
||
| if (artistName) { | ||
| prompts.push({ | ||
| label: `What should ${artistName} do this week?`, | ||
| prompt: | ||
| `Given ${artistName}'s latest streams and catalog value, ` + | ||
| "what should we focus on this week?", | ||
| }); | ||
| } | ||
|
|
||
| return prompts.slice(0, MAX_PROMPTS); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2:
artistNameshould be trimmed before truthiness checks and interpolation. A whitespace-only string (e.g." ") is truthy in JavaScript, so it passes theif (artistName)gate and produces broken prompt copy like"What should do this week?". Consider normalizing withconst trimmedArtistName = artistName.trim();and usingtrimmedArtistNamethroughout the function for both the conditional and the interpolations.Prompt for AI agents