make check
This commit is contained in:
@ -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()
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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 },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user