mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-18 21:58:58 -04:00
refactor(ui): adopt PageHeader on agent/media/import/backend pages (batch A)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code]
This commit is contained in:
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { apiKeysApi, profileApi } from '../utils/api'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import SettingRow from '../components/SettingRow'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
import './auth.css'
|
||||
@@ -452,10 +453,7 @@ export default function Account() {
|
||||
return (
|
||||
<div className="page page--narrow account-page">
|
||||
{/* Header */}
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">{t('account.title')}</h1>
|
||||
<p className="page-subtitle">{t('account.subtitle')}</p>
|
||||
</div>
|
||||
<PageHeader title={t('account.title')} supporting={t('account.subtitle')} />
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="auth-tab-bar auth-tab-bar--flush">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
|
||||
import { useParams, useNavigate, useLocation, useOutletContext, useSearchParams } from 'react-router-dom'
|
||||
import { agentsApi, skillsApi } from '../utils/api'
|
||||
import SearchableModelSelect from '../components/SearchableModelSelect'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { CAP_CHAT, CAP_TRANSCRIPT, CAP_TTS } from '../utils/capabilities'
|
||||
import Toggle from '../components/Toggle'
|
||||
import SettingRow from '../components/SettingRow'
|
||||
@@ -930,12 +931,14 @@ export default function AgentCreate() {
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h1 className="page-title">{isEdit ? `Edit Agent: ${name}` : importedConfig ? 'Import Agent' : 'Create Agent'}</h1>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agents')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={isEdit ? `Edit Agent: ${name}` : importedConfig ? 'Import Agent' : 'Create Agent'}
|
||||
actions={
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agents')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit} noValidate>
|
||||
<div className="agent-form-container">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'
|
||||
import { useParams, useNavigate, useOutletContext } from 'react-router-dom'
|
||||
import { agentJobsApi } from '../utils/api'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
|
||||
const traceColors = {
|
||||
reasoning: { bg: 'rgba(99,102,241,0.1)', border: 'rgba(99,102,241,0.3)', icon: 'fa-brain', color: 'var(--color-primary)' },
|
||||
@@ -178,22 +179,22 @@ export default function AgentJobDetails() {
|
||||
|
||||
return (
|
||||
<div className="page page--narrow">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h1 className="page-title">Job Details</h1>
|
||||
<p className="page-subtitle">Live status and reasoning traces</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
{(job.status === 'running' || job.status === 'pending') && (
|
||||
<button className="btn btn-danger" onClick={handleCancel}>
|
||||
<i className="fas fa-stop" /> Cancel
|
||||
<PageHeader
|
||||
title="Job Details"
|
||||
supporting="Live status and reasoning traces"
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
{(job.status === 'running' || job.status === 'pending') && (
|
||||
<button className="btn btn-danger" onClick={handleCancel}>
|
||||
<i className="fas fa-stop" /> Cancel
|
||||
</button>
|
||||
)}
|
||||
<button className="btn btn-secondary" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
)}
|
||||
<button className="btn btn-secondary" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Status Card */}
|
||||
<div className="card" style={{ marginBottom: 'var(--spacing-md)' }}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useModels } from '../hooks/useModels'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useUserMap } from '../hooks/useUserMap'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { fileToBase64 } from '../utils/api'
|
||||
import Modal from '../components/Modal'
|
||||
import UserGroupSection from '../components/UserGroupSection'
|
||||
@@ -216,10 +217,7 @@ export default function AgentJobs() {
|
||||
if (!loading && models.length === 0) {
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">Agent Jobs</h1>
|
||||
<p className="page-subtitle">Manage agent tasks and automated workflows</p>
|
||||
</div>
|
||||
<PageHeader title="Agent Jobs" supporting="Manage agent tasks and automated workflows" />
|
||||
<div className="card" style={{ textAlign: 'center', padding: 'var(--spacing-xl)' }}>
|
||||
<i className="fas fa-exclamation-triangle" style={{ fontSize: '3rem', color: 'var(--color-warning)', marginBottom: 'var(--spacing-md)' }} />
|
||||
<h2 style={{ marginBottom: 'var(--spacing-sm)' }}>No Models Installed</h2>
|
||||
@@ -243,10 +241,7 @@ export default function AgentJobs() {
|
||||
if (!loading && models.length > 0 && !hasMCPModels && tasks.length === 0) {
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">Agent Jobs</h1>
|
||||
<p className="page-subtitle">Manage agent tasks and automated workflows</p>
|
||||
</div>
|
||||
<PageHeader title="Agent Jobs" supporting="Manage agent tasks and automated workflows" />
|
||||
<div className="card" style={{ textAlign: 'center', padding: 'var(--spacing-xl)' }}>
|
||||
<i className="fas fa-plug" style={{ fontSize: '3rem', color: 'var(--color-primary)', marginBottom: 'var(--spacing-md)' }} />
|
||||
<h2 style={{ marginBottom: 'var(--spacing-sm)' }}>MCP Not Configured</h2>
|
||||
@@ -276,15 +271,15 @@ export default function AgentJobs() {
|
||||
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h1 className="page-title">Agent Jobs</h1>
|
||||
<p className="page-subtitle">Manage agent tasks and automated workflows</p>
|
||||
</div>
|
||||
<button className="btn btn-primary" onClick={() => navigate('/app/agent-jobs/tasks/new')}>
|
||||
<i className="fas fa-plus" /> New Task
|
||||
</button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title="Agent Jobs"
|
||||
supporting="Manage agent tasks and automated workflows"
|
||||
actions={
|
||||
<button className="btn btn-primary" onClick={() => navigate('/app/agent-jobs/tasks/new')}>
|
||||
<i className="fas fa-plus" /> New Task
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="tabs">
|
||||
<button className={`tab ${activeTab === 'tasks' ? 'tab-active' : ''}`} onClick={() => setActiveTab('tasks')}>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
|
||||
import { useParams, useNavigate, useOutletContext, useSearchParams } from 'react-router-dom'
|
||||
import { agentsApi } from '../utils/api'
|
||||
import { apiUrl } from '../utils/basePath'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
|
||||
function ObservableSummary({ observable }) {
|
||||
const creation = observable?.creation || {}
|
||||
@@ -352,29 +353,26 @@ export default function AgentStatus() {
|
||||
.as-status-value { font-size: 1rem; font-weight: 600; color: var(--color-text-primary); }
|
||||
`}</style>
|
||||
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h1 className="page-title">
|
||||
<i className="fas fa-chart-bar" style={{ marginRight: 'var(--spacing-xs)' }} />
|
||||
{name} — Status
|
||||
</h1>
|
||||
<p className="page-subtitle">Agent observables and activity history</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn btn-secondary" onClick={() => navigate(`/app/agents/${encodeURIComponent(name)}/chat${userId ? `?user_id=${encodeURIComponent(userId)}` : ''}`)}>
|
||||
<i className="fas fa-comment" /> Chat
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={() => navigate(`/app/agents/${encodeURIComponent(name)}/edit${userId ? `?user_id=${encodeURIComponent(userId)}` : ''}`)}>
|
||||
<i className="fas fa-edit" /> Edit
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={fetchData}>
|
||||
<i className="fas fa-sync" /> Refresh
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={handleClear} disabled={observables.length === 0}>
|
||||
<i className="fas fa-trash" /> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={<><i className="fas fa-chart-bar" style={{ marginRight: 'var(--spacing-xs)' }} />{name} — Status</>}
|
||||
supporting="Agent observables and activity history"
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn btn-secondary" onClick={() => navigate(`/app/agents/${encodeURIComponent(name)}/chat${userId ? `?user_id=${encodeURIComponent(userId)}` : ''}`)}>
|
||||
<i className="fas fa-comment" /> Chat
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={() => navigate(`/app/agents/${encodeURIComponent(name)}/edit${userId ? `?user_id=${encodeURIComponent(userId)}` : ''}`)}>
|
||||
<i className="fas fa-edit" /> Edit
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={fetchData}>
|
||||
<i className="fas fa-sync" /> Refresh
|
||||
</button>
|
||||
<button className="btn btn-danger" onClick={handleClear} disabled={observables.length === 0}>
|
||||
<i className="fas fa-trash" /> Clear
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Status summary */}
|
||||
{status && (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useParams, useNavigate, useOutletContext, useLocation } from 'react-rou
|
||||
import { agentJobsApi } from '../utils/api'
|
||||
import { basePath } from '../utils/basePath'
|
||||
import ModelSelector from '../components/ModelSelector'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { CAP_CHAT } from '../utils/capabilities'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
|
||||
@@ -165,20 +166,20 @@ export default function AgentTaskDetails() {
|
||||
if (!isNew && !isEdit) {
|
||||
return (
|
||||
<div className="page page--narrow">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h1 className="page-title">{task.name || 'Task Details'}</h1>
|
||||
{task.description && <p className="page-subtitle">{task.description}</p>}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn btn-primary btn-sm" onClick={() => navigate(`/app/agent-jobs/tasks/${id}/edit`)}>
|
||||
<i className="fas fa-edit" /> Edit
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={task.name || 'Task Details'}
|
||||
supporting={task.description || undefined}
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn btn-primary btn-sm" onClick={() => navigate(`/app/agent-jobs/tasks/${id}/edit`)}>
|
||||
<i className="fas fa-edit" /> Edit
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Task Info */}
|
||||
<div className="card" style={{ marginBottom: 'var(--spacing-md)' }}>
|
||||
@@ -307,12 +308,14 @@ export default function AgentTaskDetails() {
|
||||
// Edit/Create form
|
||||
return (
|
||||
<div className="page page--narrow">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h1 className="page-title">{isNew ? 'Create Task' : 'Edit Task'}</h1>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={isNew ? 'Create Task' : 'Edit Task'}
|
||||
actions={
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => navigate('/app/agent-jobs')}>
|
||||
<i className="fas fa-arrow-left" /> Back
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSave}>
|
||||
{/* Basic Info */}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { agentsApi } from '../utils/api'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useUserMap } from '../hooks/useUserMap'
|
||||
import UserGroupSection from '../components/UserGroupSection'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
|
||||
export default function Agents() {
|
||||
@@ -181,26 +182,26 @@ export default function Agents() {
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h1 className="page-title">{t('title')}</h1>
|
||||
<p className="page-subtitle">{t('subtitle')}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', alignItems: 'center' }}>
|
||||
{agentHubURL && (
|
||||
<a className="btn btn-secondary" href={agentHubURL} target="_blank" rel="noopener noreferrer">
|
||||
<i className="fas fa-store" /> {t('actions.agentHub')}
|
||||
</a>
|
||||
)}
|
||||
<label className="btn btn-secondary">
|
||||
<i className="fas fa-file-import" /> {t('actions.import')}
|
||||
<input type="file" accept=".json" className="agents-import-input" onChange={handleImport} />
|
||||
</label>
|
||||
<button className="btn btn-primary" onClick={() => navigate('/app/agents/new')}>
|
||||
<i className="fas fa-plus" /> {t('actions.createAgent')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('title')}
|
||||
supporting={t('subtitle')}
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', alignItems: 'center' }}>
|
||||
{agentHubURL && (
|
||||
<a className="btn btn-secondary" href={agentHubURL} target="_blank" rel="noopener noreferrer">
|
||||
<i className="fas fa-store" /> {t('actions.agentHub')}
|
||||
</a>
|
||||
)}
|
||||
<label className="btn btn-secondary">
|
||||
<i className="fas fa-file-import" /> {t('actions.import')}
|
||||
<input type="file" accept=".json" className="agents-import-input" onChange={handleImport} />
|
||||
</label>
|
||||
<button className="btn btn-primary" onClick={() => navigate('/app/agents/new')}>
|
||||
<i className="fas fa-plus" /> {t('actions.createAgent')}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--spacing-xl)' }}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useParams, useOutletContext } from 'react-router-dom'
|
||||
import ModelSelector from '../components/ModelSelector'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { CAP_AUDIO_TRANSFORM } from '../utils/capabilities'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import ErrorWithTraceLink from '../components/ErrorWithTraceLink'
|
||||
@@ -166,9 +167,7 @@ export default function AudioTransform() {
|
||||
return (
|
||||
<div className="media-layout">
|
||||
<div className="media-controls">
|
||||
<div className="page-header">
|
||||
<h1 className="page-title"><i className="fas fa-wave-square" /> Audio Transform</h1>
|
||||
</div>
|
||||
<PageHeader title={<><i className="fas fa-wave-square" /> Audio Transform</>} />
|
||||
|
||||
<form onSubmit={handleProcess}>
|
||||
<div className="form-group">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { backendLogsApi, nodesApi } from '../utils/api'
|
||||
import { formatTimestamp } from '../utils/format'
|
||||
import { apiUrl } from '../utils/basePath'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { useDistributedMode } from '../hooks/useDistributedMode'
|
||||
|
||||
function wsUrl(path) {
|
||||
@@ -151,15 +152,10 @@ function BackendLogsDetail({ modelId }) {
|
||||
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title" style={{ marginBottom: 0 }}>
|
||||
<i className="fas fa-terminal" style={{ fontSize: '0.8em', marginRight: 'var(--spacing-sm)' }} />
|
||||
{modelId}
|
||||
</h1>
|
||||
<p className="page-subtitle" style={{ marginTop: 'var(--spacing-xs)' }}>Backend process output</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={<><i className="fas fa-terminal" style={{ fontSize: '0.8em', marginRight: 'var(--spacing-sm)' }} />{modelId}</>}
|
||||
supporting="Backend process output"
|
||||
/>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', marginBottom: 'var(--spacing-md)', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
@@ -361,17 +357,10 @@ function DistributedBackendLogsResolver({ modelId, fromTimestamp }) {
|
||||
// Multiple workers host this model — let the operator pick.
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title" style={{ marginBottom: 0 }}>
|
||||
<i className="fas fa-terminal" style={{ fontSize: '0.8em', marginRight: 'var(--spacing-sm)' }} />
|
||||
{modelId}
|
||||
</h1>
|
||||
<p className="page-subtitle" style={{ marginTop: 'var(--spacing-xs)' }}>
|
||||
Hosted on {hits.length} workers — pick one to view its logs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={<><i className="fas fa-terminal" style={{ fontSize: '0.8em', marginRight: 'var(--spacing-sm)' }} />{modelId}</>}
|
||||
supporting={`Hosted on ${hits.length} workers — pick one to view its logs.`}
|
||||
/>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xs)' }}>
|
||||
{hits.map(({ node, model }) => (
|
||||
<Link
|
||||
|
||||
@@ -7,6 +7,7 @@ import React from 'react'
|
||||
import { useOperations } from '../hooks/useOperations'
|
||||
import { useDistributedMode } from '../hooks/useDistributedMode'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { renderMarkdown } from '../utils/markdown'
|
||||
import { safeHref } from '../utils/url'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
@@ -348,11 +349,10 @@ export default function Backends() {
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<h1 className="page-title">{t('backends.title')}</h1>
|
||||
<p className="page-subtitle">{t('backends.subtitle')}</p>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('backends.title')}
|
||||
supporting={t('backends.subtitle')}
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-md)', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-md)', fontSize: '0.8125rem' }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
@@ -378,7 +378,8 @@ export default function Backends() {
|
||||
<i className="fas fa-book" /> Docs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Upgrade Banner */}
|
||||
{Object.keys(upgrades).length > 0 && (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
|
||||
import { useParams, useOutletContext, useSearchParams } from 'react-router-dom'
|
||||
import { agentCollectionsApi } from '../utils/api'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
|
||||
export default function CollectionDetails() {
|
||||
const { name } = useParams()
|
||||
@@ -292,10 +293,7 @@ export default function CollectionDetails() {
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">{name}</h1>
|
||||
<p className="page-subtitle">Collection details and management</p>
|
||||
</div>
|
||||
<PageHeader title={name} supporting="Collection details and management" />
|
||||
|
||||
<div className="tabs">
|
||||
<button className={`tab ${activeTab === 'entries' ? 'tab-active' : ''}`} onClick={() => setActiveTab('entries')}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { agentCollectionsApi } from '../utils/api'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useUserMap } from '../hooks/useUserMap'
|
||||
import UserGroupSection from '../components/UserGroupSection'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
|
||||
export default function Collections() {
|
||||
@@ -119,10 +120,7 @@ export default function Collections() {
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">{t('title')}</h1>
|
||||
<p className="page-subtitle">{t('subtitle')}</p>
|
||||
</div>
|
||||
<PageHeader title={t('title')} supporting={t('subtitle')} />
|
||||
|
||||
<div className="collections-create-bar">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { fineTuneApi } from '../utils/api'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
|
||||
const TRAINING_METHODS = ['sft', 'dpo', 'grpo', 'rloo', 'reward', 'kto', 'orpo']
|
||||
const TRAINING_TYPES = ['lora', 'loha', 'lokr', 'full']
|
||||
@@ -1058,21 +1059,21 @@ export default function FineTune() {
|
||||
|
||||
return (
|
||||
<div className="page page--wide">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<h1 className="page-title">Fine-Tuning <span className="badge badge-warning" style={{ fontSize: '0.45em', verticalAlign: 'middle' }}>Experimental</span></h1>
|
||||
<p className="page-subtitle">Create and manage fine-tuning jobs</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn" onClick={handleImportConfig}>
|
||||
<i className="fas fa-upload" style={{ marginRight: 'var(--spacing-xs)' }} /> Import Config
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={() => setShowForm(!showForm)}>
|
||||
<i className={`fas fa-${showForm ? 'times' : 'plus'}`} style={{ marginRight: 'var(--spacing-xs)' }} />
|
||||
{showForm ? 'Cancel' : 'New Job'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={<>Fine-Tuning <span className="badge badge-warning" style={{ fontSize: '0.45em', verticalAlign: 'middle' }}>Experimental</span></>}
|
||||
supporting="Create and manage fine-tuning jobs"
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
|
||||
<button className="btn" onClick={handleImportConfig}>
|
||||
<i className="fas fa-upload" style={{ marginRight: 'var(--spacing-xs)' }} /> Import Config
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={() => setShowForm(!showForm)}>
|
||||
<i className={`fas fa-${showForm ? 'times' : 'plus'}`} style={{ marginRight: 'var(--spacing-xs)' }} />
|
||||
{showForm ? 'Cancel' : 'New Job'}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div className="card" style={{ background: 'var(--color-error-light)', borderColor: 'var(--color-error-border)', color: 'var(--color-error)', marginBottom: 'var(--spacing-md)', padding: 'var(--spacing-md)' }}>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useRef } from 'react'
|
||||
import { useParams, useOutletContext } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ModelSelector from '../components/ModelSelector'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import { CAP_IMAGE } from '../utils/capabilities'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import ErrorWithTraceLink from '../components/ErrorWithTraceLink'
|
||||
@@ -84,9 +85,7 @@ export default function ImageGen() {
|
||||
return (
|
||||
<div className="media-layout">
|
||||
<div className="media-controls">
|
||||
<div className="page-header">
|
||||
<h1 className="page-title"><i className="fas fa-image" /> {t('image.title')}</h1>
|
||||
</div>
|
||||
<PageHeader title={<><i className="fas fa-image" /> {t('image.title')}</>} />
|
||||
|
||||
<form onSubmit={handleGenerate}>
|
||||
<div className="form-group">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate, useOutletContext } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { modelsApi, backendsApi } from '../utils/api'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import CodeEditor from '../components/CodeEditor'
|
||||
import SearchableSelect from '../components/SearchableSelect'
|
||||
import AmbiguityAlert from '../components/AmbiguityAlert'
|
||||
@@ -798,24 +799,24 @@ export default function ImportModel() {
|
||||
|
||||
return (
|
||||
<div className="page page--narrow">
|
||||
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 'var(--spacing-sm)' }}>
|
||||
<div>
|
||||
<h1 className="page-title">{t('title')}</h1>
|
||||
<p className="page-subtitle">{subtitle}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<SimplePowerSwitch value={mode} onChange={requestModeSwitch} disabled={isSubmitting} />
|
||||
{isPowerYaml ? (
|
||||
<button className="btn btn-primary" onClick={handleAdvancedImport} disabled={isSubmitting}>
|
||||
{isSubmitting ? <><LoadingSpinner size="sm" /> {t('actions.saving')}</> : <><i className="fas fa-save" aria-hidden="true" /> {t('actions.create')}</>}
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={() => handleSimpleImport()} disabled={isSubmitting || !importUri.trim()}>
|
||||
{isSubmitting ? <><LoadingSpinner size="sm" /> {t('actions.importing')}</> : <><i className="fas fa-upload" aria-hidden="true" /> {t('actions.import')}</>}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('title')}
|
||||
supporting={subtitle}
|
||||
actions={
|
||||
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<SimplePowerSwitch value={mode} onChange={requestModeSwitch} disabled={isSubmitting} />
|
||||
{isPowerYaml ? (
|
||||
<button className="btn btn-primary" onClick={handleAdvancedImport} disabled={isSubmitting}>
|
||||
{isSubmitting ? <><LoadingSpinner size="sm" /> {t('actions.saving')}</> : <><i className="fas fa-save" aria-hidden="true" /> {t('actions.create')}</>}
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={() => handleSimpleImport()} disabled={isSubmitting || !importUri.trim()}>
|
||||
{isSubmitting ? <><LoadingSpinner size="sm" /> {t('actions.importing')}</> : <><i className="fas fa-upload" aria-hidden="true" /> {t('actions.import')}</>}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Estimate banner */}
|
||||
{!isPowerYaml && estimate && (
|
||||
|
||||
Reference in New Issue
Block a user