Skip to content

Lee at zoo corp/ttc ux 1 #7542

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

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions e2e/playwright/projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1328,7 +1328,7 @@ test(

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

await test.step('Reverse modified order', async () => {
const lastModifiedButton = page.getByRole('button', {
name: 'Last Modified',
name: 'Age',
})
await lastModifiedButton.click()
await expect(lastModifiedButton).toBeVisible()
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"json-rpc-2.0": "^1.6.0",
"jszip": "^3.10.1",
"minimist": "^1.2.8",
"ms": "^2.1.3",

Check warning on line 58 in package.json

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

package-dependencies-check

Package dependencies with variant versions may lead to dependency hijack and confusion attacks. Better to specify an exact version or use packagelock.json for a specific version of the package.
"openid-client": "^5.6.5",
"re-resizable": "^6.11.2",
"react": "^18.3.1",
Expand Down
9 changes: 9 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@
transform: rotate(360deg);
}
}

@keyframes send-up {
from {
transform: translateY(0px);
}
to {
transform: translateY(-10000px);
}
}
11 changes: 9 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
import { EngineStream } from '@src/components/EngineStream'
import Gizmo from '@src/components/Gizmo'
import { useLspContext } from '@src/components/LspProvider'
import { ModelingSidebar } from '@src/components/ModelingSidebar/ModelingSidebar'
import {
ModelingSidebarLeft,
ModelingSidebarRight,
} from '@src/components/ModelingSidebar/ModelingSidebar'
import { MlEphantConversation } from '@src/components/MlEphantConversation'

Check failure on line 20 in src/App.tsx

View workflow job for this annotation

GitHub Actions / npm-lint

'MlEphantConversation' is defined but never used. Allowed unused vars must match /^_/u
import { UnitsMenu } from '@src/components/UnitsMenu'
import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath'
import { useQueryParamEffects } from '@src/hooks/useQueryParamEffects'
Expand Down Expand Up @@ -269,7 +273,10 @@
<Toolbar />
</div>
<ModalContainer />
<ModelingSidebar />
<div className="flex flex-row justify-between w-full h-full">
<ModelingSidebarLeft />
<ModelingSidebarRight />
</div>
<EngineStream pool={pool} authToken={authToken} />
{/* <CamToggle /> */}
<section className="absolute bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
Expand Down
16 changes: 15 additions & 1 deletion src/Auth.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import Loading from '@src/components/Loading'
import { useAuthState } from '@src/lib/singletons'
import {
useAuthState,
useToken,
mlEphantManagerActor,
} from '@src/lib/singletons'
import { MlEphantManagerTransitions } from '@src/machines/mlEphantManagerMachine'

// Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => {
const authState = useAuthState()
const isLoggingIn = authState.matches('checkIfLoggedIn')
const token = useToken()

if (!isLoggingIn) {
// The earliest we can give machines their Zoo API token.
mlEphantManagerActor.send({
type: MlEphantManagerTransitions.SetApiToken,
token,
})
}

return isLoggingIn ? (
<Loading className="h-screen w-screen">
Expand Down
95 changes: 95 additions & 0 deletions src/components/HomeSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Fuse from 'fuse.js'
import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'

import { CustomIcon } from '@src/components/CustomIcon'

import type { Project } from '@src/lib/project'
import type { Prompt } from '@src/lib/prompt'

export type HomeItem = Project | Prompt
export type HomeItems = Project[] | Prompt[]

export const areHomeItemsProjects = (items: HomeItems): items is Project[] => {
if (items.length === 0) return true
const item = items[0]
return item !== undefined && 'path' in item
}

export const areHomeItemsPrompts = (items: HomeItems): items is Prompt[] => {
if (items.length === 0) return true
const item = items[0]
return item !== undefined && 'prompt' in item
}

export function useHomeSearch(initialSearchResults: HomeItems) {
const [query, setQuery] = useState('')
const [searchResults, setSearchResults] =
useState<HomeItems>(initialSearchResults)

useEffect(() => {
setSearchResults(initialSearchResults)
}, [initialSearchResults])

const searchAgainst = (items: HomeItems) => (queryRequested: string) => {
const nameKeyToMatchAgainst = areHomeItemsProjects(items)
? 'name'
: 'prompt'

// Fuse is not happy with HomeItems
// @ts-expect-error
const fuse = new Fuse(items, {
keys: [{ name: nameKeyToMatchAgainst, weight: 0.7 }],
includeScore: true,
})

const results = fuse.search(queryRequested).map((result) => result.item)

// On an empty query, we consider that matching all items.
setSearchResults(queryRequested.length > 0 ? results : items)
setQuery(queryRequested)
}

return {
searchAgainst,
searchResults,
query,
}
}

export function HomeSearchBar({
onChange,
}: {
onChange: (query: string) => void
}) {
const inputRef = useRef<HTMLInputElement>(null)
useHotkeys(
'Ctrl+.',
(event) => {
event.preventDefault()
inputRef.current?.focus()
},
{ enableOnFormTags: true }
)

return (
<div className="relative group">
<div className="flex items-center gap-2 py-0.5 pl-0.5 pr-2 rounded border-solid border border-primary/10 dark:border-chalkboard-80 focus-within:border-primary dark:focus-within:border-chalkboard-30">
<CustomIcon
name="search"
className="w-5 h-5 rounded-sm bg-primary/10 dark:bg-transparent text-primary dark:text-chalkboard-10 group-focus-within:bg-primary group-focus-within:text-chalkboard-10"
/>
<input
ref={inputRef}
onChange={(event) => onChange(event.target.value)}
className="w-full text-sm bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
placeholder="Search (Ctrl+.)"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
spellCheck="false"
/>
</div>
</div>
)
}
8 changes: 5 additions & 3 deletions src/components/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'

interface LoadingProps extends React.PropsWithChildren {
isDummy?: boolean
isCompact?: boolean
className?: string
dataTestId?: string
retryAttemptCountdown?: number
Expand Down Expand Up @@ -80,6 +81,7 @@ export const CONNECTION_ERROR_CALL_TO_ACTION_TEXT: Record<

const Loading = ({
isDummy,
isCompact,
children,
className,
dataTestId,
Expand Down Expand Up @@ -200,11 +202,11 @@ const Loading = ({
if (isDummy) {
return (
<div
className={`body-bg flex flex-col items-center justify-center ${colorClass} ${className}`}
className={`flex ${isCompact ? 'flex-row gap-2' : 'flex-col'} items-center justify-center ${colorClass} ${className}`}
data-testid={dataTestId ? dataTestId : 'loading'}
>
<Spinner />
<p className={`text-base mt-4`}>{children}</p>
<Spinner className={isCompact ? 'w-4 h-4' : 'w-8 w-8'} />
<p className={`text-base ${isCompact ? '' : 'mt-4'}`}>{children}</p>
</div>
)
}
Expand Down
142 changes: 142 additions & 0 deletions src/components/MlEphantConversation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import type { ReactNode } from 'react'
import { useRef, useEffect, useState } from 'react'
import type { Prompt } from '@src/lib/prompt'
import type { FileMeta } from '@src/lib/types'

Check failure on line 4 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / npm-lint

'FileMeta' is defined but never used. Allowed unused vars must match /^_/u
import { PromptCard } from '@src/components/PromptCard'
import { CustomIcon } from '@src/components/CustomIcon'

export interface MlEphantConversationProps {
prompts: Prompt[]
onProcess: (requestedPrompt: string) => void
disabled?: boolean
}

interface MlEphantConversationInputProps {
onProcess: MlEphantConversationProps['onProcess']
disabled?: boolean
}

export const MlEphantConversationInput = (
props: MlEphantConversationInputProps
) => {
const refDiv = useRef<HTMLDivElement>(null)
const [heightConvo, setHeightConvo] = useState(0)
const [lettersForAnimation, setLettersForAnimation] = useState<ReactNode[]>(
[]
)
const [isAnimating, setAnimating] = useState(false)

const onClick = () => {
const value = refDiv.current?.innerText
if (!value) return
setHeightConvo(refDiv.current.getBoundingClientRect().height)

props.onProcess(value)
setLettersForAnimation(
value.split('').map((c, index) => (
<span
key={index}
style={{
display: 'inline-block',
animation: `${Math.random() * 2}s linear 0s 1 normal forwards running send-up`,
}}
>
{c}
</span>
))
)
setAnimating(true)
refDiv.current.innerText = ''

setTimeout(() => {
setAnimating(false)
}, 2000)
}

useEffect(() => {
if (!isAnimating) {
refDiv.current.focus()

Check failure on line 58 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / npm-tsc

'refDiv.current' is possibly 'null'.
}
}, [isAnimating])

return (
<div className="flex flex-col p-4 gap-2">
<div className="text-sm text-chalkboard-60">Enter a prompt</div>

Check warning on line 64 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

jsx-not-internationalized

JSX element not internationalized Enter a prompt. You should support different languages in your website or app with internationalization. Instead use packages such as i18next in order to internationalize your elements.
<div className="p-2 border flex flex-col gap-2">
<div
contentEditable={true}
autoCapitalize="off"
autoComplete="off"

Check failure on line 69 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / npm-tsc

Type '{ contentEditable: true; autoCapitalize: "off"; autoComplete: string; autoCorrect: string; spellCheck: "false"; ref: RefObject<HTMLDivElement>; className: string; style: { height: string; }; placeholder: string; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.
autoCorrect="off"
spellCheck="false"
ref={refDiv}
className={`outline-none w-full overflow-auto ${isAnimating ? 'hidden' : ''}`}
style={{ height: '2lh' }}
placeholder="Help get me started on..."
></div>
<div
className={`${isAnimating ? '' : 'hidden'} overflow-hidden w-full p-2`}
style={{ height: heightConvo }}
>
{lettersForAnimation}
</div>
<div className="flex justify-end">
<button
disabled={props.disabled}
onClick={onClick}
className="w-10 m-0 bg-ml-green p-2 flex justify-center"
>
<CustomIcon name="arrowUp" className="w-5 h-5 animate-bounce" />
</button>
</div>
</div>
</div>
)
}

export const MlEphantConversation = (props: MlEphantConversationProps) => {
const refScroll = useRef<HTMLDivElement>(null)

const onDelete = () => {}
const onFeedback = () => {}

useEffect(() => {
if (refScroll.current) {
refScroll.current.children[
refScroll.current.children.length - 1
].scrollIntoView()
}
}, [props.prompts.length])

const promptCards = props.prompts.map((prompt) => (
<PromptCard
key={prompt.id}
{...prompt}

Check warning on line 114 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

react-props-spreading

Its best practice to explicitly pass props to an HTML component rather than use the spread operator. The spread operator risks passing invalid HTML props to an HTML element which can cause console warnings or worse give malicious actors a way to inject unexpected attributes.
disabled={prompt.status !== 'completed'}
onDelete={onDelete}
onFeedback={onFeedback}
/>
))
return (
<div className="relative">
<div className="absolute inset-0">
<div className="flex flex-col h-full">
<div className="h-full flex flex-col justify-end overflow-auto">
<div className="overflow-auto" ref={refScroll}>
<div className="text-center p-4 text-chalkboard-60 text-md">
The beginning of this project's Text-to-CAD history.
</div>

Check warning on line 128 in src/components/MlEphantConversation.tsx

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

jsx-not-internationalized

JSX element not internationalized The beginning of this projects TexttoCAD history. . You should support different languages in your website or app with internationalization. Instead use packages such as i18next in order to internationalize your elements.
{promptCards}
</div>
</div>
<div className="border-t">
<MlEphantConversationInput
disabled={props.disabled}
onProcess={props.onProcess}
/>
</div>
</div>
</div>
</div>
)
}
Loading
Loading