Skip to content
Open
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
node_modules
frontend/node_modules
frontend/dist
frontend/.tanstack
.axonhub
axonhub
axonhub.db
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend-builder

WORKDIR /build
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN corepack enable && corepack prepare pnpm@10 --activate
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,7 @@ export function ChannelsActionDialog({ currentRow, duplicateFromRow, open, onOpe
className='min-h-[80px] resize-y pr-10 font-mono text-sm md:col-span-6'
autoComplete='new-password'
data-form-type='other'
spellCheck={false}
aria-invalid={!!fieldState.error}
data-testid='channel-api-key-input'
/>
Expand Down Expand Up @@ -2028,6 +2029,7 @@ export function ChannelsActionDialog({ currentRow, duplicateFromRow, open, onOpe
className='min-h-[80px] resize-y font-mono text-sm md:col-span-6'
autoComplete='new-password'
data-form-type='other'
spellCheck={false}
aria-invalid={!!fieldState.error}
data-testid='channel-api-key-input'
/>
Expand Down
32 changes: 31 additions & 1 deletion frontend/src/features/channels/data/config_channels.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
import type { ComponentType } from 'react';
import { OpenAI, Anthropic, Google, DeepSeek, Doubao, Moonshot, Zhipu, OpenRouter, XAI, Volcengine, SiliconCloud, PPIO, ZAI, LongCat, Minimax, BurnCloud, Vercel, ModelScope, Bailian, Jina, DeepInfra, Github, Claude, Cerebras, XiaomiMiMo, Fireworks, Ollama } from '@lobehub/icons';
import {
OpenAI,
Anthropic,
Google,
DeepSeek,
Doubao,
Moonshot,
Zhipu,
OpenRouter,
XAI,
Volcengine,
SiliconCloud,
PPIO,
ZAI,
LongCat,
Minimax,
BurnCloud,
Vercel,
ModelScope,
Bailian,
Jina,
DeepInfra,
Github,
Claude,
Cerebras,
Qiniu,
XiaomiMiMo,
Fireworks,
Ollama,
AiHubMix,
} from '@lobehub/icons';
import { NanoGPTIcon } from '../components/nanogpt-icon';
import { BURNCLOUD_DEFAULT_MODELS } from './burncloud-models';
import { ApiFormat, ChannelType } from './schema';
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/features/models/components/data-table-row-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DotsHorizontalIcon } from '@radix-ui/react-icons';
import { Row } from '@tanstack/react-table';
import { IconEdit, IconArchive, IconTrash, IconNote } from '@tabler/icons-react';
import { IconEdit, IconArchive, IconTrash, IconNote, IconArchiveOff } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { usePermissions } from '@/hooks/usePermissions';
import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -75,7 +75,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {

{channelPermissions.canRead && <DropdownMenuSeparator />}

{model.status !== 'archived' && (
{model.status !== 'archived' ? (
<DropdownMenuItem
onClick={() => {
setCurrentRow(row.original);
Expand All @@ -86,6 +86,17 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
<IconArchive size={16} className='mr-2' />
{t('common.buttons.archive')}
</DropdownMenuItem>
) : (
<DropdownMenuItem
onClick={() => {
setCurrentRow(row.original);
setOpen('archive');
}}
className='text-green-500!'
>
<IconArchiveOff size={16} className='mr-2' />
{t('common.buttons.restore')}
</DropdownMenuItem>
)}

<DropdownMenuItem
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/features/models/components/models-archive-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';

import { IconArchive, IconCheck, IconInfoCircle } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { useModels } from '../context/models-context';
import { useUpdateModelStatus } from '../data/models';

export function ModelsArchiveDialog() {
const { t } = useTranslation();
const { open, setOpen, currentRow } = useModels();
const updateModelStatus = useUpdateModelStatus();

const isOpen = open === 'archive';
const isArchived = currentRow?.status === 'archived';

const handleStatusChange = () => {
if (!currentRow) return;
updateModelStatus.mutate(
{ id: currentRow.id, status: isArchived ? 'enabled' : 'archived' },
{ onSuccess: () => setOpen(null) },
);
};

const handleClose = () => {
setOpen(null);
};

const getDescription = () => {
if (!currentRow) return null;
const baseDescription = t(isArchived ? 'models.dialogs.status.restoreDescription' : 'models.dialogs.status.archiveDescription', {
name: currentRow.name,
});
const infoText = t(isArchived ? 'models.dialogs.status.restoreInfo' : 'models.dialogs.status.archiveWarning');

return (
<div className='space-y-3'>
<p>{baseDescription}</p>
<div className='rounded-md border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20'>
<div className='flex items-start space-x-2'>
<IconInfoCircle className='mt-0.5 h-4 w-4 flex-shrink-0 text-blue-600 dark:text-blue-400' />
<div className='text-sm text-blue-800 dark:text-blue-200'>
<p>{infoText}</p>
</div>
</div>
</div>
</div>
);
};

return (
<ConfirmDialog
open={isOpen}
onOpenChange={handleClose}
handleConfirm={handleStatusChange}
disabled={updateModelStatus.isPending}
title={
<span className={isArchived ? 'text-green-600' : 'text-orange-600'}>
{isArchived ? <IconCheck className='mr-1 inline-block stroke-green-600' size={18} /> : <IconArchive className='mr-1 inline-block stroke-orange-600' size={18} />}
{t(isArchived ? 'models.dialogs.status.restoreTitle' : 'models.dialogs.status.archiveTitle')}
</span>
}
desc={getDescription()}
confirmText={t(isArchived ? 'common.buttons.restore' : 'common.buttons.archive')}
cancelBtnText={t('common.buttons.cancel')}
/>
);
}
2 changes: 2 additions & 0 deletions frontend/src/features/models/components/models-dialogs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useModels } from '../context/models-context';
import { ModelsActionDialog } from './models-action-dialog';
import { ModelsArchiveDialog } from './models-archive-dialog';
import { ModelsAssociationDialog } from './models-association-dialog';
import { ModelsBatchCreateDialog } from './models-batch-create-dialog';
import { ModelsBulkDisableDialog } from './models-bulk-disable-dialog';
Expand All @@ -16,6 +17,7 @@ export function ModelsDialogs() {
{(open === 'create' || open === 'edit') && <ModelsActionDialog />}
{open === 'batchCreate' && <ModelsBatchCreateDialog />}
{open === 'delete' && <ModelsDeleteDialog />}
{open === 'archive' && <ModelsArchiveDialog />}
{open === 'association' && <ModelsAssociationDialog />}
{open === 'settings' && <ModelSettingsDialog />}
{open === 'unassociated' && <ModelsUnassociatedDialog />}
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/features/models/data/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,34 @@ export function useDeleteModel() {
});
}

const UPDATE_MODEL_STATUS_MUTATION = `
mutation UpdateModelStatus($id: ID!, $status: ModelStatus!) {
updateModelStatus(id: $id, status: $status)
}
`;

export function useUpdateModelStatus() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { handleError } = useErrorHandler();

return useMutation({
mutationFn: async ({ id, status }: { id: string; status: 'enabled' | 'archived' }) => {
const data = await graphqlRequest<{ updateModelStatus: boolean }>(UPDATE_MODEL_STATUS_MUTATION, { id, status });
return data.updateModelStatus;
},
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: ['models'] });
const statusKey = variables.status === 'archived' ? 'archiveSuccess' : 'restoreSuccess';
toast.success(t(`models.messages.${statusKey}`));
},
onError: (error, variables) => {
const contextKey = variables.status === 'archived' ? 'archiveTitle' : 'restoreTitle';
handleError(error, { context: t(`models.dialogs.status.${contextKey}`) });
},
});
}

export function useBulkDisableModels() {
const { t } = useTranslation();
const queryClient = useQueryClient();
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/locales/en/models.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@
"models.dialogs.status.enableDescription": "Are you sure you want to enable {{name}}?",
"models.dialogs.status.disableTitle": "Disable Model",
"models.dialogs.status.disableDescription": "Are you sure you want to disable {{name}}?",
"models.dialogs.status.archiveTitle": "Archive Model",
"models.dialogs.status.archiveDescription": "Are you sure you want to archive {{name}}?",
"models.dialogs.status.archiveWarning": "Archived models will not be used for AI requests until they are restored.",
"models.dialogs.status.restoreTitle": "Restore Model",
"models.dialogs.status.restoreDescription": "Are you sure you want to restore {{name}}?",
"models.dialogs.status.restoreInfo": "Restoring this model will re-enable it for AI requests.",
"models.dialogs.bulkDisable.title": "Bulk Disable Models",
"models.dialogs.bulkDisable.description": "Are you sure you want to disable {{count}} models?",
"models.dialogs.bulkDisable.warning": "Disabled models will not be used for AI requests until they are re-enabled.",
Expand Down Expand Up @@ -169,6 +175,8 @@
"models.messages.deleteSuccess": "Model deleted successfully",
"models.messages.bulkDisableSuccess": "Successfully disabled {{count}} models",
"models.messages.bulkEnableSuccess": "Successfully enabled {{count}} models",
"models.messages.archiveSuccess": "Model archived successfully",
"models.messages.restoreSuccess": "Model restored successfully",
"models.developers.alibaba": "Alibaba",
"models.developers.anthropic": "Anthropic",
"models.developers.deepseek": "DeepSeek",
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/locales/zh-CN/models.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@
"models.dialogs.status.enableDescription": "确定要启用 {{name}} 吗?",
"models.dialogs.status.disableTitle": "禁用模型",
"models.dialogs.status.disableDescription": "确定要禁用 {{name}} 吗?",
"models.dialogs.status.archiveTitle": "归档模型",
"models.dialogs.status.archiveDescription": "确定要归档 {{name}} 吗?",
"models.dialogs.status.archiveWarning": "归档的模型将不会用于 AI 请求,直到恢复。",
"models.dialogs.status.restoreTitle": "恢复模型",
"models.dialogs.status.restoreDescription": "确定要恢复 {{name}} 吗?",
"models.dialogs.status.restoreInfo": "恢复此模型将重新启用它用于 AI 请求。",
"models.dialogs.bulkDisable.title": "批量禁用模型",
"models.dialogs.bulkDisable.description": "确定要禁用 {{count}} 个模型吗?",
"models.dialogs.bulkDisable.warning": "禁用的模型将不会用于 AI 请求,直到重新启用。",
Expand Down Expand Up @@ -169,6 +175,8 @@
"models.messages.deleteSuccess": "模型删除成功",
"models.messages.bulkDisableSuccess": "已成功禁用 {{count}} 个模型",
"models.messages.bulkEnableSuccess": "已成功启用 {{count}} 个模型",
"models.messages.archiveSuccess": "模型归档成功",
"models.messages.restoreSuccess": "模型恢复成功",
"models.developers.alibaba": "阿里巴巴",
"models.developers.anthropic": "Anthropic",
"models.developers.deepseek": "深度求索",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Playground from '@/features/playground';
function ProtectedPlayground() {
return (
<ProjectGuard>
<RouteGuard requiredScopes={['write_requests']}>
<RouteGuard requiredScopes={['write_requests', 'read_channels']}>
<Playground />
</RouteGuard>
</ProjectGuard>
Expand Down
2 changes: 1 addition & 1 deletion internal/ent/internal/schema.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions internal/ent/migrate/schema.go

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

2 changes: 0 additions & 2 deletions internal/ent/request/request.go

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

5 changes: 0 additions & 5 deletions internal/ent/request_create.go

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

10 changes: 0 additions & 10 deletions internal/ent/request_update.go

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

2 changes: 0 additions & 2 deletions internal/ent/requestexecution/requestexecution.go

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

5 changes: 0 additions & 5 deletions internal/ent/requestexecution_create.go

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

10 changes: 0 additions & 10 deletions internal/ent/requestexecution_update.go

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

Loading