develop #19
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-bi4e
|
# nuzlocke-tracker-bi4e
|
||||||
title: Integrate name suggestions into encounter registration UI
|
title: Integrate name suggestions into encounter registration UI
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-11T15:56:44Z
|
created_at: 2026-02-11T15:56:44Z
|
||||||
updated_at: 2026-02-11T20:23:40Z
|
updated_at: 2026-02-11T20:48:02Z
|
||||||
parent: nuzlocke-tracker-igl3
|
parent: nuzlocke-tracker-igl3
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ Show name suggestions in the encounter registration flow so users can pick a nic
|
|||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Add a name suggestions component (chips/buttons with regenerate)
|
- [x] Add a name suggestions component (chips/buttons with regenerate)
|
||||||
- [ ] Integrate the component into the encounter registration modal/form
|
- [x] Integrate the component into the encounter registration modal/form
|
||||||
- [ ] Wire up the backend API endpoint to the component via React Query
|
- [x] Wire up the backend API endpoint to the component via React Query
|
||||||
- [ ] Ensure clicking a suggestion populates the nickname field
|
- [x] Ensure clicking a suggestion populates the nickname field
|
||||||
- [ ] Ensure regenerate fetches a new batch from the API
|
- [x] Ensure regenerate fetches a new batch from the API
|
||||||
- [ ] Hide suggestions gracefully if no naming scheme is set on the run
|
- [x] Hide suggestions gracefully if no naming scheme is set on the run
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-igl3
|
# nuzlocke-tracker-igl3
|
||||||
title: Name Generation
|
title: Name Generation
|
||||||
status: todo
|
status: completed
|
||||||
type: epic
|
type: epic
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-05T13:45:15Z
|
created_at: 2026-02-05T13:45:15Z
|
||||||
updated_at: 2026-02-11T20:44:23Z
|
updated_at: 2026-02-11T20:48:02Z
|
||||||
---
|
---
|
||||||
|
|
||||||
Implement a dictionary-based nickname generation system for Nuzlocke runs. Instead of using an LLM API to generate names on the fly, provide a static dictionary of words categorised by theme. A word can belong to multiple categories, making it usable across different naming schemes.
|
Implement a dictionary-based nickname generation system for Nuzlocke runs. Instead of using an LLM API to generate names on the fly, provide a static dictionary of words categorised by theme. A word can belong to multiple categories, making it usable across different naming schemes.
|
||||||
@@ -29,6 +29,6 @@ Implement a dictionary-based nickname generation system for Nuzlocke runs. Inste
|
|||||||
|
|
||||||
- [x] Word dictionary data file exists with multiple categories, each containing 150-200 words
|
- [x] Word dictionary data file exists with multiple categories, each containing 150-200 words
|
||||||
- [x] Name suggestion engine picks random names from the selected category, avoiding duplicates already used in the run
|
- [x] Name suggestion engine picks random names from the selected category, avoiding duplicates already used in the run
|
||||||
- [ ] Encounter registration UI shows 5-10 clickable name suggestions
|
- [x] Encounter registration UI shows 5-10 clickable name suggestions
|
||||||
- [ ] User can regenerate suggestions if none fit
|
- [x] User can regenerate suggestions if none fit
|
||||||
- [x] User can select a naming scheme per run
|
- [x] User can select a naming scheme per run
|
||||||
@@ -32,3 +32,7 @@ export function deleteRun(id: number): Promise<void> {
|
|||||||
export function getNamingCategories(): Promise<string[]> {
|
export function getNamingCategories(): Promise<string[]> {
|
||||||
return api.get('/runs/naming-categories')
|
return api.get('/runs/naming-categories')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNameSuggestions(runId: number, count = 10): Promise<string[]> {
|
||||||
|
return api.get(`/runs/${runId}/name-suggestions?count=${count}`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react'
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
import { useRoutePokemon } from '../hooks/useGames'
|
import { useRoutePokemon } from '../hooks/useGames'
|
||||||
|
import { useNameSuggestions } from '../hooks/useRuns'
|
||||||
import {
|
import {
|
||||||
EncounterMethodBadge,
|
EncounterMethodBadge,
|
||||||
getMethodLabel,
|
getMethodLabel,
|
||||||
@@ -15,6 +16,8 @@ import type {
|
|||||||
interface EncounterModalProps {
|
interface EncounterModalProps {
|
||||||
route: Route
|
route: Route
|
||||||
gameId: number
|
gameId: number
|
||||||
|
runId: number
|
||||||
|
namingScheme?: string | null
|
||||||
existing?: EncounterDetail
|
existing?: EncounterDetail
|
||||||
dupedPokemonIds?: Set<number>
|
dupedPokemonIds?: Set<number>
|
||||||
retiredPokemonIds?: Set<number>
|
retiredPokemonIds?: Set<number>
|
||||||
@@ -92,6 +95,8 @@ function pickRandomPokemon(
|
|||||||
export function EncounterModal({
|
export function EncounterModal({
|
||||||
route,
|
route,
|
||||||
gameId,
|
gameId,
|
||||||
|
runId,
|
||||||
|
namingScheme,
|
||||||
existing,
|
existing,
|
||||||
dupedPokemonIds,
|
dupedPokemonIds,
|
||||||
retiredPokemonIds,
|
retiredPokemonIds,
|
||||||
@@ -120,6 +125,10 @@ export function EncounterModal({
|
|||||||
|
|
||||||
const isEditing = !!existing
|
const isEditing = !!existing
|
||||||
|
|
||||||
|
const showSuggestions = !!namingScheme && status === 'caught' && !isEditing
|
||||||
|
const { data: suggestions, refetch: regenerate, isFetching: loadingSuggestions } =
|
||||||
|
useNameSuggestions(showSuggestions ? runId : null)
|
||||||
|
|
||||||
// Pre-select pokemon when editing
|
// Pre-select pokemon when editing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (existing && routePokemon) {
|
if (existing && routePokemon) {
|
||||||
@@ -380,6 +389,39 @@ export function EncounterModal({
|
|||||||
placeholder="Give it a name..."
|
placeholder="Give it a name..."
|
||||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
{showSuggestions && suggestions && suggestions.length > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Suggestions ({namingScheme})
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => regenerate()}
|
||||||
|
disabled={loadingSuggestions}
|
||||||
|
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 disabled:opacity-50 transition-colors"
|
||||||
|
>
|
||||||
|
{loadingSuggestions ? 'Loading...' : 'Regenerate'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{suggestions.map((name) => (
|
||||||
|
<button
|
||||||
|
key={name}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setNickname(name)}
|
||||||
|
className={`px-2.5 py-1 text-xs rounded-full border transition-colors ${
|
||||||
|
nickname === name
|
||||||
|
? 'bg-blue-100 border-blue-300 text-blue-800 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-300'
|
||||||
|
: 'border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories } from '../api/runs'
|
import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories, getNameSuggestions } from '../api/runs'
|
||||||
import type { CreateRunInput, UpdateRunInput } from '../types/game'
|
import type { CreateRunInput, UpdateRunInput } from '../types/game'
|
||||||
|
|
||||||
export function useRuns() {
|
export function useRuns() {
|
||||||
@@ -59,3 +59,11 @@ export function useNamingCategories() {
|
|||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useNameSuggestions(runId: number | null) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['name-suggestions', runId],
|
||||||
|
queryFn: () => getNameSuggestions(runId!),
|
||||||
|
enabled: runId !== null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1431,6 +1431,8 @@ export function RunEncounters() {
|
|||||||
<EncounterModal
|
<EncounterModal
|
||||||
route={selectedRoute}
|
route={selectedRoute}
|
||||||
gameId={run!.gameId}
|
gameId={run!.gameId}
|
||||||
|
runId={runIdNum}
|
||||||
|
namingScheme={run!.namingScheme}
|
||||||
existing={editingEncounter ?? undefined}
|
existing={editingEncounter ?? undefined}
|
||||||
dupedPokemonIds={dupedPokemonIds}
|
dupedPokemonIds={dupedPokemonIds}
|
||||||
retiredPokemonIds={retiredPokemonIds}
|
retiredPokemonIds={retiredPokemonIds}
|
||||||
|
|||||||
Reference in New Issue
Block a user