jan/web-app/src/routes/settings/extensions.tsx
Dinh Long Nguyen a30eb7f968
feat: Jan Web (reusing Jan Desktop UI) (#6298)
* add platform guards

* add service management

* fix types

* move to zustand for servicehub

* update App Updater

* update tauri missing move

* update app updater

* refactor: move PlatformFeatures to separate const file

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* change tauri fetch name

* update implementation

* update extension fetch

* make web version run properly

* disabled unused web settings

* fix all tests

* fix lint

* fix tests

* add mock for extension

* fix build

* update make and mise

* fix tsconfig for web-extensions

* fix loader type

* cleanup

* fix test

* update error handling + mcp should be working

* Update mcp init

* use separate is_web_app build property

* Remove fixed model catalog url

* fix additional tests

* fix download issue (event emitter not implemented correctly)

* Update Title html

* fix app logs

* update root tsx render timing

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 01:47:46 +07:00

95 lines
3.3 KiB
TypeScript

import { createFileRoute } from '@tanstack/react-router'
import { route } from '@/constants/routes'
import { Card, CardItem } from '@/containers/Card'
import HeaderPage from '@/containers/HeaderPage'
import SettingsMenu from '@/containers/SettingsMenu'
import { RenderMarkdown } from '@/containers/RenderMarkdown'
import { ExtensionManager } from '@/lib/extension'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { PlatformGuard } from '@/lib/platform/PlatformGuard'
import { PlatformFeature } from '@/lib/platform'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.settings.extensions as any)({
component: Extensions,
})
function Extensions() {
return (
<PlatformGuard feature={PlatformFeature.EXTENSION_MANAGEMENT}>
<ExtensionsContent />
</PlatformGuard>
)
}
function ExtensionsContent() {
const { t } = useTranslation()
const extensions = ExtensionManager.getInstance().listExtensions()
return (
<div className="flex flex-col h-full">
<HeaderPage>
<h1 className="font-medium">{t('common:settings')}</h1>
</HeaderPage>
<div className="flex h-full w-full">
<SettingsMenu />
<div className="p-4 w-full h-[calc(100%-32px)] overflow-y-auto">
<div className="flex flex-col justify-between gap-4 gap-y-3 w-full">
{/* General */}
<Card
header={
<div className="flex items-center justify-between mb-4">
<h1 className="text-main-view-fg font-medium text-base">
{t('settings:extensions.title')}
</h1>
{/* <div className="flex items-center gap-2">
<Button size="sm">Install Extension</Button>
</div> */}
</div>
}
>
{extensions.map((item, i) => {
return (
<CardItem
key={i}
title={
<div className="flex items-center gap-x-2">
<h1 className="text-main-view-fg">
{item.productName ?? item.name}
</h1>
<div className="bg-main-view-fg/10 px-1 py-0.5 rounded text-main-view-fg/70 text-xs">
v{item.version}
</div>
</div>
}
description={
<RenderMarkdown
content={item.description ?? ''}
components={{
// Make links open in a new tab
a: ({ ...props }) => (
<a
{...props}
target="_blank"
rel="noopener noreferrer"
/>
),
// Custom paragraph component remove margin
p: ({ ...props }) => (
<p {...props} className="!mb-0" />
),
}}
/>
}
/>
)
})}
</Card>
</div>
</div>
</div>
</div>
)
}