make check

This commit is contained in:
lee-at-zoo-corp
2025-06-19 18:06:52 -04:00
parent b0ff9d636c
commit eaa52a9b73
7 changed files with 118 additions and 43 deletions

View File

@ -1343,7 +1343,7 @@ test(
await test.step('should be shorted by modified initially', async () => { await test.step('should be shorted by modified initially', async () => {
const lastModifiedButton = page.getByRole('button', { const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified', name: 'Age',
}) })
await expect(lastModifiedButton).toBeVisible() await expect(lastModifiedButton).toBeVisible()
await expect(lastModifiedButton.getByLabel('arrow down')).toBeVisible() await expect(lastModifiedButton.getByLabel('arrow down')).toBeVisible()
@ -1364,7 +1364,7 @@ test(
await test.step('Reverse modified order', async () => { await test.step('Reverse modified order', async () => {
const lastModifiedButton = page.getByRole('button', { const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified', name: 'Age',
}) })
await lastModifiedButton.click() await lastModifiedButton.click()
await expect(lastModifiedButton).toBeVisible() await expect(lastModifiedButton).toBeVisible()

View File

@ -5,7 +5,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
import type { Project } from '@src/lib/project' import type { Project } from '@src/lib/project'
import type { Prompt } from '@src/machines/mlEphantManagerMachine' import type { Prompt } from '@src/lib/prompt'
export type HomeItem = Project | Prompt export type HomeItem = Project | Prompt
export type HomeItems = Project[] | Prompt[] export type HomeItems = Project[] | Prompt[]
@ -17,7 +17,7 @@ export const areHomeItemsProjects = (items: HomeItems): items is Project[] => {
export const areHomeItemsPrompts = (items: HomeItems): items is Prompt[] => { export const areHomeItemsPrompts = (items: HomeItems): items is Prompt[] => {
const item = items[0] const item = items[0]
return item !== undefined && typeof item.prompt === 'string' return item !== undefined && 'prompt' in item
} }
export function useHomeSearch(initialSearchResults: HomeItems) { export function useHomeSearch(initialSearchResults: HomeItems) {
@ -34,6 +34,8 @@ export function useHomeSearch(initialSearchResults: HomeItems) {
? 'name' ? 'name'
: 'prompt' : 'prompt'
// Fuse is not happy with HomeItems
// @ts-expect-error
const fuse = new Fuse(items, { const fuse = new Fuse(items, {
keys: [{ name: nameKeyToMatchAgainst, weight: 0.7 }], keys: [{ name: nameKeyToMatchAgainst, weight: 0.7 }],
includeScore: true, includeScore: true,

View File

@ -1,5 +1,5 @@
import ms from 'ms' import ms from 'ms'
import { Prompt } from '@src/lib/prompt' import type { Prompt } from '@src/lib/prompt'
interface PromptCardProps extends Prompt { interface PromptCardProps extends Prompt {
onAction?: (id: Prompt['id']) => void onAction?: (id: Prompt['id']) => void
@ -8,16 +8,27 @@ interface PromptCardProps extends Prompt {
} }
export const PromptFeedback = (props: { export const PromptFeedback = (props: {
id: Prompt['id']
selected: Prompt['feedback'] selected: Prompt['feedback']
onFeedback: (id: Prompt['id'], feedback: Prompt['feedback']) => void onFeedback: (id: Prompt['id'], feedback: Prompt['feedback']) => void
}) => { }) => {
const cssUp = 'border-green-500' const cssUp = 'border-green-300'
const cssDown = 'border-red-500' const cssDown = 'border-red-300'
return ( return (
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<button className={cssUp}>Good</button> <button
<button className={cssDown}>Bad</button> onClick={() => props.onFeedback(props.id, 'thumbs_up')}
className={cssUp}
>
Good
</button>
<button
onClick={() => props.onFeedback(props.id, 'thumbs_down')}
className={cssDown}
>
Bad
</button>
</div> </div>
) )
} }
@ -45,8 +56,9 @@ export const PromptCard = (props: PromptCardProps) => {
<div className="w-fit flex flex-col items-end"> <div className="w-fit flex flex-col items-end">
<button onClick={() => props.onDelete?.(props.id)}>Delete</button> <button onClick={() => props.onDelete?.(props.id)}>Delete</button>
<PromptFeedback <PromptFeedback
id={props.id}
selected={props.feedback} selected={props.feedback}
onFeedback={props.onFeedback} onFeedback={(...args) => props.onFeedback?.(...args)}
/> />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
export type Prompt = Models['TextToCad_Type'] export type Prompt = Models['TextToCad_type']
// export interface TextToCad_type { // export interface TextToCad_type {
// code?: string; // code?: string;
@ -100,21 +100,22 @@ export const generateFakeSubmittedPrompt = () => ({
created_at: new Date(Math.random() * 100000000).toISOString(), created_at: new Date(Math.random() * 100000000).toISOString(),
error: Math.random().toString(), error: Math.random().toString(),
// declare type MlFeedback_type = 'thumbs_up' | 'thumbs_down' | 'accepted' | 'rejected'; // declare type MlFeedback_type = 'thumbs_up' | 'thumbs_down' | 'accepted' | 'rejected';
feedback: 'thumbs_up', feedback: 'thumbs_up' as Prompt['feedback'],
id: Math.random().toString(), id: Math.random().toString(),
kcl_version: Math.random().toString(), kcl_version: Math.random().toString(),
// export declare type TextToCadModel_type = 'cad' | 'kcl' | 'kcl_iteration'; model : 'kcl',
model_version: Math.random().toString(), model_version: Math.random().toString(),
// export declare type TextToCadModel_type = 'cad' | 'kcl' | 'kcl_iteration'; model : 'kcl',
model: 'kcl' as Prompt['model'],
// export declare type FileExportFormat_type = 'fbx' | 'glb' | 'gltf' | 'obj' | 'ply' | 'step' | 'stl'; // export declare type FileExportFormat_type = 'fbx' | 'glb' | 'gltf' | 'obj' | 'ply' | 'step' | 'stl';
output_format: 'glb', output_format: 'glb' as Prompt['output_format'],
outputs: { outputs: {
[Math.random().toString()]: Math.random().toString(), [Math.random().toString()]: Math.random().toString(),
}, },
prompt: PROMPTS[parseInt((Math.random() * 10).toString()[0])], prompt: PROMPTS[parseInt((Math.random() * 10).toString()[0])],
started_at: new Date(Math.random()).toISOString(), started_at: new Date(Math.random()).toISOString(),
// declare type ApiCallStatus_type = 'queued' | 'uploaded' | 'in_progress' | 'completed' | 'failed'; // declare type ApiCallStatus_type = 'queued' | 'uploaded' | 'in_progress' | 'completed' | 'failed';
status: 'completed', status: 'completed' as Prompt['status'],
updated_at: Math.random(), updated_at: Math.random().toString(),
// declare type ApiTokenUuid_type = string; // declare type ApiTokenUuid_type = string;
user_id: Math.random().toString(), user_id: Math.random().toString(),
}) })

View File

@ -1,5 +1,6 @@
import type { CustomIconName } from '@src/components/CustomIcon' import type { CustomIconName } from '@src/components/CustomIcon'
import type { Project } from '@src/lib/project' import type { Project } from '@src/lib/project'
import type { Prompt } from '@src/lib/prompt'
const DESC = ':desc' const DESC = ':desc'
@ -25,7 +26,7 @@ export function getNextSearchParams(currentSort: string, newSort: string) {
} }
} }
export function getSortFunction(sortBy: string) { export function getProjectSortFunction(sortBy: string) {
const sortByName = (a: Project, b: Project) => { const sortByName = (a: Project, b: Project) => {
if (a.name && b.name) { if (a.name && b.name) {
return sortBy.includes('desc') return sortBy.includes('desc')
@ -52,3 +53,33 @@ export function getSortFunction(sortBy: string) {
return sortByModified return sortByModified
} }
} }
// Below is to keep the same behavior as above but for prompts.
// Do NOT take it as actually "sort by modified" but more like "sort by time".
export function getPromptSortFunction(sortBy: string) {
const sortByName = (a: Prompt, b: Prompt) => {
if (a.prompt && b.prompt) {
return sortBy.includes('desc')
? a.prompt.localeCompare(b.prompt)
: b.prompt.localeCompare(a.prompt)
}
return 0
}
const sortByModified = (a: Prompt, b: Prompt) => {
if (a.created_at && b.created_at) {
const aDate = new Date(a.created_at)
const bDate = new Date(b.created_at)
return !sortBy || sortBy.includes('desc')
? bDate.getTime() - aDate.getTime()
: aDate.getTime() - bDate.getTime()
}
return 0
}
if (sortBy?.includes('name')) {
return sortByName
} else {
return sortByModified
}
}

View File

@ -1,4 +1,6 @@
import { setup, fromPromise } from 'xstate'
import type { Prompt } from '@src/lib/prompt' import type { Prompt } from '@src/lib/prompt'
import { generateFakeSubmittedPrompt } from '@src/lib/prompt'
export enum MlEphantManagerTransitionStates { export enum MlEphantManagerTransitionStates {
GetPromptsThatCreatedProjects = 'get-prompts-that-created-projects', GetPromptsThatCreatedProjects = 'get-prompts-that-created-projects',
@ -47,9 +49,9 @@ export enum MlEphantManagerStates {
} }
export interface MlEphantManagerContext { export interface MlEphantManagerContext {
promptsThatCreatedProjects: Map<Prompt> promptsThatCreatedProjects: Map<Prompt['id'], Prompt>
// If no project is selected: undefined. // If no project is selected: undefined.
promptsBelongingToProject?: Map<Prompt> promptsBelongingToProject?: Map<Prompt['id'], Prompt>
} }
export const mlEphantDefaultContext = Object.freeze({ export const mlEphantDefaultContext = Object.freeze({
@ -58,13 +60,25 @@ export const mlEphantDefaultContext = Object.freeze({
hasPendingPrompts: false, hasPendingPrompts: false,
}) })
const machine = setup({ export const mlEphantManagerMachine = setup({
types: { types: {
context: {} as MlEphantManagerContext, context: {} as MlEphantManagerContext,
events: {} as MlEphantManagerEvents, events: {} as MlEphantManagerEvents,
}, },
actors: {
[MlEphantManagerTransitionStates.GetPromptsThatCreatedProjects]:
fromPromise(async function (args) {
console.log(arguments)
return {
promptsThatCreatedProjects: new Array(13)
.fill(undefined)
.map(generateFakeSubmittedPrompt),
}
}),
},
}).createMachine({ }).createMachine({
initial: MlEphantManagerStates.Idle, initial: MlEphantManagerStates.Idle,
context: mlEphantDefaultContext,
states: { states: {
[MlEphantManagerStates.Idle]: { [MlEphantManagerStates.Idle]: {
on: { on: {
@ -77,15 +91,8 @@ const machine = setup({
states: { states: {
[MlEphantManagerTransitionStates.GetPromptsThatCreatedProjects]: { [MlEphantManagerTransitionStates.GetPromptsThatCreatedProjects]: {
invoke: { invoke: {
input: (args) => args, input: (args: any) => args,
src: fromPromise(async function (args) { src: MlEphantManagerTransitionStates.GetPromptsThatCreatedProjects,
console.log(arguments)
return {
promptsThatCreatedProjects: new Array(13)
.fill(undefined)
.map(generateFakeSubmittedPrompt),
}
}),
onDone: { target: MlEphantManagerStates.Idle }, onDone: { target: MlEphantManagerStates.Idle },
onError: { target: MlEphantManagerStates.Idle }, onError: { target: MlEphantManagerStates.Idle },
}, },

View File

@ -17,10 +17,10 @@ import { PromptCard } from '@src/components/PromptCard'
import { import {
HomeSearchBar, HomeSearchBar,
useHomeSearch, useHomeSearch,
HomeItems,
areHomeItemsProjects, areHomeItemsProjects,
areHomeItemsPrompts, areHomeItemsPrompts,
} from '@src/components/HomeSearchBar' } from '@src/components/HomeSearchBar'
import type { HomeItems } from '@src/components/HomeSearchBar'
import { BillingDialog } from '@src/components/BillingDialog' import { BillingDialog } from '@src/components/BillingDialog'
import { useQueryParamEffects } from '@src/hooks/useQueryParamEffects' import { useQueryParamEffects } from '@src/hooks/useQueryParamEffects'
import { useMenuListener } from '@src/hooks/useMenu' import { useMenuListener } from '@src/hooks/useMenu'
@ -32,7 +32,8 @@ import type { Prompt } from '@src/lib/prompt'
import { generateFakeSubmittedPrompt } from '@src/lib/prompt' import { generateFakeSubmittedPrompt } from '@src/lib/prompt'
import { import {
getNextSearchParams, getNextSearchParams,
getSortFunction, getProjectSortFunction,
getPromptSortFunction,
getSortIcon, getSortIcon,
} from '@src/lib/sorting' } from '@src/lib/sorting'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
@ -216,7 +217,7 @@ const Home = () => {
} }
) )
const projects = useFolders() const projects = useFolders()
const prompts = [ const prompts: Prompt[] = [
generateFakeSubmittedPrompt(), generateFakeSubmittedPrompt(),
generateFakeSubmittedPrompt(), generateFakeSubmittedPrompt(),
generateFakeSubmittedPrompt(), generateFakeSubmittedPrompt(),
@ -228,7 +229,7 @@ const Home = () => {
const [tabSelected, setTabSelected] = useState<HomeTabKeys>( const [tabSelected, setTabSelected] = useState<HomeTabKeys>(
HomeTabKeys.Projects HomeTabKeys.Projects
) )
const [items, setItems] = useState<Project[] | Prompt[]>(projects) const [items, setItems] = useState<HomeItems>(projects)
const [searchParams, setSearchParams] = useSearchParams() const [searchParams, setSearchParams] = useSearchParams()
const { searchResults, query, searchAgainst } = useHomeSearch(projects) const { searchResults, query, searchAgainst } = useHomeSearch(projects)
const sortBy = searchParams.get('sort_by') ?? 'modified:desc' const sortBy = searchParams.get('sort_by') ?? 'modified:desc'
@ -484,6 +485,8 @@ function HomeTab(props: HomeTabProps) {
<div <div
className={el.key === selected ? cssActive : cssInactive} className={el.key === selected ? cssActive : cssInactive}
onClick={onClickTab(el.key)} onClick={onClickTab(el.key)}
role="tab"
tabIndex={0}
> >
{el.name} {el.name}
</div> </div>
@ -565,7 +568,7 @@ function HomeHeader({
: '', : '',
}} }}
> >
Last Modified Age
</ActionButton> </ActionButton>
</div> </div>
</div> </div>
@ -653,18 +656,37 @@ function HomeItemsArea(props: HomeItemsAreaProps) {
) )
} }
interface ResultGridProjectsProps { interface ResultGridPromptsProps {
searchResults: Prompt[] searchResults: Prompt[]
query: string query: string
sortBy: string sortBy: string
} }
function ResultGridPrompts(props) { function ResultGridPrompts(props: ResultGridPromptsProps) {
// Maybe consider lifting this higher but I see no reason at the moment
const onAction = (...args: any) => {
console.log(args)
}
const onDelete = (...args: any) => {
console.log(args)
}
const onFeedback = (...args: any) => {
console.log(args)
}
return ( return (
<div className="grid w-full sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-4"> <div className="grid w-full sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-4">
{props.searchResults.map((prompt) => ( {props.searchResults
<PromptCard key={prompt.id} {...prompt} /> .sort(getPromptSortFunction(props.sortBy))
))} .map((prompt: Prompt) => (
<PromptCard
key={prompt.id}
{...prompt}
onAction={onAction}
onDelete={onDelete}
onFeedback={onFeedback}
/>
))}
</div> </div>
) )
} }
@ -686,8 +708,8 @@ function ResultGridProjects(props: ResultGridProjectsProps) {
<> <>
{props.searchResults.length > 0 ? ( {props.searchResults.length > 0 ? (
<ul className="grid w-full sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> <ul className="grid w-full sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{(props.searchResults ?? []) {props.searchResults
.sort(getSortFunction(props.sortBy)) .sort(getProjectSortFunction(props.sortBy))
.map((item) => ( .map((item) => (
<ProjectCard <ProjectCard
key={item.name} key={item.name}
@ -700,9 +722,9 @@ function ResultGridProjects(props: ResultGridProjectsProps) {
) : ( ) : (
<p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70"> <p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
No results found No results found
{items.length === 0 {props.searchResults.length === 0
? ', ready to make your first one?' ? ', ready to make your first one?'
: ` with the search term "${query}"`} : ` with the search term "${props.query}"`}
</p> </p>
)} )}
</> </>