Skip to content

Commit

Permalink
feat(block): built-in remix (#192)
Browse files Browse the repository at this point in the history
* feat(block): built-in remix

* fix: improve remix prompt

* refactor(extension): new layout for extension editor

* refactor: extension config

* feat(lab): input editor

* feat(extension): generate extension code via chat

* fix: updat block template

* fix(v3): support use-toast

* chore(i18n): i18n for ext

* chore: disable electron prerelease update channel

* feat: save chat history

* feat: Add confirmation dialog for LLM provider deletion

* fix: disable chat for prompt&udf

* feat: add alpha status indicator for UDF feature

* Update to version 0.11.0
  • Loading branch information
mayneyao authored Nov 26, 2024
1 parent accdc41 commit bd31071
Show file tree
Hide file tree
Showing 98 changed files with 8,564 additions and 979 deletions.
33 changes: 19 additions & 14 deletions apps/desktop/[database]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { useLocalStorageState } from "ahooks"
import { Suspense, lazy, useEffect } from "react"
import { Outlet, useLocation, useNavigate } from "react-router-dom"
import { useLocalStorageState } from "ahooks"
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"

import { EidosDataEventChannelName } from "@/lib/const"
import { useAppRuntimeStore } from "@/lib/store/runtime-store"
import { cn, isStandaloneBlocksPath } from "@/lib/utils"
import { useActivation } from "@/hooks/use-activation"
import { useEidosFileSystemManager } from "@/hooks/use-fs"
import { useSqlite } from "@/hooks/use-sqlite"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import { BlockApp } from "@/components/block-renderer/block-app"
import { DocExtBlockLoader } from "@/components/doc-ext-block-loader"
import { KeyboardShortCuts } from "@/components/keyboard-shortcuts"
Expand All @@ -10,17 +21,7 @@ import { Nav } from "@/components/nav"
import { RightPanelNav } from "@/components/nav/right-panel-nav"
import { ScriptContainer } from "@/components/script-container"
import { SideBar } from "@/components/sidebar"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import { useActivation } from "@/hooks/use-activation"
import { useEidosFileSystemManager } from "@/hooks/use-fs"
import { useSqlite } from "@/hooks/use-sqlite"
import { EidosDataEventChannelName } from "@/lib/const"
import { useAppRuntimeStore } from "@/lib/store/runtime-store"
import { cn, isStandaloneBlocksPath } from "@/lib/utils"
import { ScriptBreadcrumb } from "@/apps/web-app/[database]/scripts/components/extension-breadcrumb"

import { useLayoutInit } from "../../web-app/[database]/hook"
import { useAppsStore, useSpaceAppStore } from "../../web-app/[database]/store"
Expand All @@ -39,6 +40,8 @@ export function DesktopSpaceLayout() {
const { isActivated } = useActivation()
const isBlocksPath = isStandaloneBlocksPath(useLocation().pathname)

const { scriptId } = useParams()

useLayoutInit()
const { efsManager } = useEidosFileSystemManager()

Expand Down Expand Up @@ -117,7 +120,9 @@ export function DesktopSpaceLayout() {
minSize={50}
>
<div className="flex flex-col h-full min-w-0">
<Nav />
<Nav>
{scriptId && <ScriptBreadcrumb scriptId={scriptId} />}
</Nav>
<div
id="main-content"
className="z-[1] flex w-full grow flex-col overflow-y-auto min-w-0"
Expand Down
84 changes: 84 additions & 0 deletions apps/web-app/[database]/scripts/components/NewExtensionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ChevronDownIcon } from "lucide-react"
import { useTranslation } from "react-i18next"

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

import { useNewScript } from "../hooks/use-new-script"

const ScriptTooltip = ({ children }: { children: React.ReactNode }) => {
return (
<div
className="ring invisible group-hover:visible absolute left-full top-0 ml-2 w-64 rounded-md border bg-popover p-3 text-sm before:absolute before:-left-4 before:top-0 before:h-full before:w-4"
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
)
}

export const NewExtensionButton = () => {
const { handleCreateNewScript } = useNewScript()
const { t } = useTranslation()

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="xs">
{t('common.new')} <ChevronDownIcon className="ml-1 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="overflow-visible">
<DropdownMenuLabel>{t('extension.createNew')}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
className="group relative"
onClick={() => handleCreateNewScript("m_block")}
>
{t('extension.microBlock')} <Badge variant="secondary">{t('common.badge.new')}</Badge>
<ScriptTooltip>
{t('extension.microBlockDescription')}
</ScriptTooltip>
</DropdownMenuItem>
<DropdownMenuItem
className="group relative"
onClick={() => handleCreateNewScript()}
>
{t('extension.script')}
<ScriptTooltip>
{t('extension.scriptDescription')}
</ScriptTooltip>
</DropdownMenuItem>

<DropdownMenuItem
className="group relative"
onClick={() => handleCreateNewScript("udf")}
>
{t('extension.udf')} <Badge variant="secondary">{t('common.badge.alpha')}</Badge>
<ScriptTooltip>
{t('extension.udfDescription')}
<br />
<span className="text-red-400">
{t('extension.udfWarning')}
</span>
</ScriptTooltip>
</DropdownMenuItem>
<DropdownMenuItem
className="group relative"
onClick={() => handleCreateNewScript("prompt")}
>
{t('extension.prompt')}
<ScriptTooltip>{t('extension.promptDescription')}</ScriptTooltip>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
136 changes: 136 additions & 0 deletions apps/web-app/[database]/scripts/components/ScriptCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { IScript } from "@/worker/web-worker/meta-table/script"
import {
AppWindowIcon,
FunctionSquareIcon,
RotateCcwIcon,
ShapesIcon,
SparkleIcon,
SquareCodeIcon,
ToyBrickIcon,
} from "lucide-react"
import { Link } from "react-router-dom"

import { cn } from "@/lib/utils"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { useTranslation } from "react-i18next"

const IconMap = {
script: SquareCodeIcon,
udf: FunctionSquareIcon,
prompt: SparkleIcon,
block: ShapesIcon,
m_block: ToyBrickIcon,
app: AppWindowIcon,
}

interface ScriptCardProps {
script: IScript
space: string
onDelete: (id: string) => void
onToggleEnabled: (script: IScript, checked: boolean) => void
showReload?: boolean
onReload?: () => void
}

export const ScriptCard = ({
script,
space,
onDelete,
onToggleEnabled,
showReload,
onReload,
}: ScriptCardProps) => {
const { t } = useTranslation()
const Icon = IconMap[script.type]

return (
<div className="group relative overflow-hidden rounded-lg border bg-card text-card-foreground shadow transition-all hover:shadow-lg flex flex-col min-h-[160px]">
<div className="flex flex-col space-y-1.5 p-4">
<div className="flex items-center gap-2">
<Icon className="h-10 w-10 shrink-0 opacity-70" />
<div>
<h3 className="text-lg font-semibold tracking-tight">
{script.name}{" "}
<span className="text-sm text-muted-foreground">
{t("extension.version", { version: script.version })}
</span>
</h3>
<p className="text-sm text-muted-foreground">
{script.description}
</p>
</div>
</div>
</div>

<div
className={cn("flex items-center justify-between p-4 pt-0 mt-auto", {
"opacity-0 pointer-events-none": ["block", "app"].includes(
script.type
),
})}
>
<div className="flex items-center gap-2">
<Link to={`/${space}/extensions/${script.id}`}>
<Button size="xs" variant="outline">
{t("extension.details")}
</Button>
</Link>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="xs" variant="ghost">
{t("common.delete")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("extension.deleteScriptConfirm")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("extension.deleteScriptDescription")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={() => onDelete(script.id)}>
{t("common.continue")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>

<div className="flex items-center gap-2">
{script.type !== "app" && (
<Switch
checked={script.enabled}
onCheckedChange={(checked) => onToggleEnabled(script, checked)}
/>
)}
{showReload && (
<Button
onClick={onReload}
variant="ghost"
size="icon"
title={t("extension.reloadLocalScript")}
>
<RotateCcwIcon className="h-4 w-4" />
</Button>
)}
</div>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Link } from "react-router-dom"
import { useTranslation } from "react-i18next"

import { useCurrentPathInfo } from "@/hooks/use-current-pathinfo"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

import { useScriptById } from "../hooks/use-script"

interface ScriptBreadcrumbProps {
scriptId: string
}

export const ScriptBreadcrumb = ({ scriptId }: ScriptBreadcrumbProps) => {
const script = useScriptById(scriptId)
const { space } = useCurrentPathInfo()
const { t } = useTranslation()

return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to={`/${space}/extensions`}>{t("extension.breadcrumb.extensions")}</Link>
</BreadcrumbLink>
<BreadcrumbSeparator />
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbPage>{script?.name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}
Loading

0 comments on commit bd31071

Please sign in to comment.