Merge remote-tracking branch 'origin/deploy/konwledge' into deploy/konwledge

This commit is contained in:
FFXN 2026-08-26 15:42:54 +08:00
commit 207640bcd1
7 changed files with 170 additions and 170 deletions

View File

@ -362,11 +362,10 @@ describe('KnowledgeSettingsForm', () => {
const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' })
await user.clear(nameInput)
await user.type(nameInput, 'Updated camera specs')
await user.click(
screen.getByRole('button', {
name: 'dataset.newKnowledge.settings.saveChanges',
}),
)
const saveButton = screen.getByRole('button', {
name: 'dataset.newKnowledge.settings.saveChanges',
})
await user.click(saveButton)
await waitFor(() => {
expect(serviceMock.patchSpace).toHaveBeenCalledWith(
@ -380,7 +379,7 @@ describe('KnowledgeSettingsForm', () => {
)
})
expect(serviceMock.patchSettings).not.toHaveBeenCalled()
expect(toastMock.success).not.toHaveBeenCalled()
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')
})
it('finishes the basic info draft before refreshing saved server data', async () => {
@ -433,6 +432,12 @@ describe('KnowledgeSettingsForm', () => {
)
await waitFor(() => expect(serviceMock.patchSpace).toHaveBeenCalledOnce())
const saveButton = screen.getByRole('button', {
name: 'dataset.newKnowledge.settings.saveChanges',
})
expect(saveButton).toHaveAttribute('aria-disabled', 'true')
expect(saveButton).toHaveTextContent('dataset.newKnowledge.settings.saveChanges')
expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument()
const apiAccessSwitch = screen.getByRole('switch', {
name: 'dataset.newKnowledge.apiAgentAccess',
})
@ -624,7 +629,74 @@ describe('KnowledgeSettingsForm', () => {
name: 'dataset.newKnowledge.settings.saveChanges',
}),
).toBeDisabled()
expect(toastMock.success).not.toHaveBeenCalled()
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')
})
it('keeps access channels submittable before the first model profile activates', async () => {
const user = userEvent.setup()
renderForm({
settings: {
...settings,
active_profile_available: false,
active_profile_revisions: {},
capabilities: {
deep: false,
index: false,
ingest: true,
query: false,
research: false,
source_sync: true,
},
},
})
const apiAccessSwitch = screen.getByRole('switch', {
name: 'dataset.newKnowledge.apiAgentAccess',
})
const workflowAccessSwitch = screen.getByRole('switch', {
name: 'dataset.newKnowledge.workflowAccess',
})
expect(apiAccessSwitch).not.toHaveAttribute('aria-disabled', 'true')
expect(workflowAccessSwitch).not.toHaveAttribute('aria-disabled', 'true')
expect(apiAccessSwitch).toHaveAccessibleDescription(
'dataset.newKnowledge.settings.apiAccessDescription',
)
await user.click(apiAccessSwitch)
await waitFor(() => {
expect(serviceMock.patchExternalAccess).toHaveBeenCalledWith(
{
body: {
agent_enabled: false,
mcp_enabled: true,
service_api_enabled: false,
workflow_enabled: true,
},
params: { control_space_id: 'space-1' },
},
expect.anything(),
)
})
await waitFor(() => expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false'))
expect(apiAccessSwitch).not.toHaveAttribute('aria-disabled', 'true')
expect(workflowAccessSwitch).not.toHaveAttribute('aria-disabled', 'true')
await user.click(apiAccessSwitch)
await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2))
expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith(
{
body: {
agent_enabled: true,
mcp_enabled: true,
service_api_enabled: true,
workflow_enabled: true,
},
params: { control_space_id: 'space-1' },
},
expect.anything(),
)
})
it('keeps unrelated form controls interactive while external access is saving', async () => {
@ -732,7 +804,7 @@ describe('KnowledgeSettingsForm', () => {
await waitFor(() => expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true'))
})
it('restores the Workflow access switch after failure and retries the intended value', async () => {
it('keeps the Workflow access edit and shows an error toast after failure', async () => {
const user = userEvent.setup()
serviceMock.patchExternalAccess.mockRejectedValueOnce(new Error('network error'))
renderForm({
@ -747,27 +819,14 @@ describe('KnowledgeSettingsForm', () => {
})
await user.click(workflowAccessSwitch)
expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument()
expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'false')
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2))
expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith(
{
body: {
agent_enabled: true,
mcp_enabled: true,
service_api_enabled: true,
workflow_enabled: true,
},
params: { control_space_id: 'space-1' },
},
expect.anything(),
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'),
)
await waitFor(() => expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true'))
expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true')
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
})
it('restores the API access switch after failure and retries the intended value', async () => {
it('keeps the API access edit and shows an error toast after failure', async () => {
const user = userEvent.setup()
serviceMock.patchExternalAccess.mockRejectedValueOnce(new Error('network error'))
renderForm({
@ -784,24 +843,11 @@ describe('KnowledgeSettingsForm', () => {
expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false')
await user.click(apiAccessSwitch)
expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument()
expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false')
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2))
expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith(
{
body: {
agent_enabled: true,
mcp_enabled: true,
service_api_enabled: true,
workflow_enabled: true,
},
params: { control_space_id: 'space-1' },
},
expect.anything(),
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'),
)
await waitFor(() => expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'true'))
expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'true')
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
})
it('requires the exact knowledge name before deletion', async () => {
@ -837,7 +883,7 @@ describe('KnowledgeSettingsForm', () => {
expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new')
})
it('keeps edits and offers retry after saving fails', async () => {
it('keeps edits and shows a standard error toast after saving fails', async () => {
const user = userEvent.setup()
serviceMock.patchSpace.mockRejectedValueOnce(new Error('network error'))
renderForm()
@ -845,20 +891,24 @@ describe('KnowledgeSettingsForm', () => {
const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' })
await user.clear(nameInput)
await user.type(nameInput, 'Camera specs draft')
await user.click(
screen.getByRole('button', {
name: 'dataset.newKnowledge.settings.saveChanges',
}),
const saveButton = screen.getByRole('button', {
name: 'dataset.newKnowledge.settings.saveChanges',
})
await user.click(saveButton)
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'),
)
expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument()
expect(nameInput).toHaveValue('Camera specs draft')
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
expect(saveButton).toBeEnabled()
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await user.click(saveButton)
await waitFor(() => expect(serviceMock.patchSpace).toHaveBeenCalledTimes(2))
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')
})
it('retries an immediate retrieval settings update without submitting basic info', async () => {
it('shows an error toast for a failed retrieval settings update without submitting basic info', async () => {
const user = userEvent.setup()
serviceMock.patchSettings.mockRejectedValueOnce(new Error('settings unavailable'))
renderForm()
@ -869,10 +919,10 @@ describe('KnowledgeSettingsForm', () => {
}),
)
expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2))
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'),
)
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
expect(serviceMock.patchSpace).not.toHaveBeenCalled()
})
@ -1109,25 +1159,11 @@ describe('KnowledgeSettingsForm', () => {
await user.type(thresholdInput, '0.72')
await user.tab()
expect(await screen.findByText('dataset.newKnowledge.permissionRestricted')).toBeInTheDocument()
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
expect(thresholdInput).toHaveValue('0.72')
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2))
expect(serviceMock.patchSettings).toHaveBeenLastCalledWith(
{
body: {
expectedRevision: 5,
retrieval: expect.objectContaining({
scoreThreshold: expect.objectContaining({ value: 0.72 }),
}),
},
params: { control_space_id: 'space-1' },
},
expect.anything(),
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.permissionRestricted'),
)
expect(screen.queryByText('dataset.newKnowledge.permissionRestricted')).not.toBeInTheDocument()
expect(thresholdInput).toHaveValue('0.72')
})
it('requires a rerank model for a legacy knowledge base and saves it as enabled', async () => {
@ -1416,7 +1452,7 @@ describe('KnowledgeSettingsForm', () => {
name: 'dataset.newKnowledge.settings.systemReasoningModelLabel',
}),
)
expect(await screen.findByRole('status')).toHaveTextContent('common.operation.saving')
expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument()
fireEvent.change(
screen.getByRole('textbox', {
@ -1450,6 +1486,9 @@ describe('KnowledgeSettingsForm', () => {
},
expect.anything(),
)
await waitFor(() =>
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess'),
)
} finally {
vi.useRealTimers()
}
@ -1558,10 +1597,10 @@ describe('KnowledgeSettingsForm', () => {
)
expect(
screen.getByRole('switch', { name: 'dataset.newKnowledge.apiAgentAccess' }),
).toHaveAttribute('aria-disabled', 'true')
).not.toHaveAttribute('aria-disabled', 'true')
expect(
screen.getByRole('switch', { name: 'dataset.newKnowledge.workflowAccess' }),
).toHaveAttribute('aria-disabled', 'true')
).not.toHaveAttribute('aria-disabled', 'true')
await user.click(
screen.getByRole('button', {
@ -1817,7 +1856,7 @@ describe('KnowledgeSettingsForm', () => {
}),
)
expect(await screen.findByRole('status')).toHaveTextContent('common.operation.saving')
expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument()
expect(toastMock.success).not.toHaveBeenCalled()
resolveMigration({
@ -1831,11 +1870,11 @@ describe('KnowledgeSettingsForm', () => {
updated_at: '2026-07-28T00:01:00Z',
})
await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument())
expect(toastMock.success).not.toHaveBeenCalled()
await waitFor(() => expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess'))
expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument()
})
it('offers retry when a durable profile migration fails', async () => {
it('shows an error toast when a durable profile migration fails', async () => {
const user = userEvent.setup()
serviceMock.patchSettings.mockResolvedValueOnce({
migration: {
@ -1869,11 +1908,11 @@ describe('KnowledgeSettingsForm', () => {
}),
)
expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument()
await waitFor(() =>
expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'),
)
expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument()
expect(toastMock.success).not.toHaveBeenCalled()
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2))
})
it('fully locks the page for a view-only user', () => {

View File

@ -69,7 +69,7 @@ export function KnowledgeSpaceCard({
</div>
</div>
</div>
<p className="relative z-1 line-clamp-2 min-h-8 w-full px-4 py-0.5 body-xs-regular text-text-tertiary">
<p className="relative z-1 line-clamp-2 min-h-8 w-full px-4 py-0.5 body-xs-regular wrap-break-word text-text-tertiary">
{summary?.description || t(($) => $['newKnowledge.noDescription'])}
</p>
</Link>

View File

@ -11,6 +11,8 @@ import { chunkTreeLabel, visibleDocumentChunkNodes } from './document-detail-mod
const VIRTUALIZATION_THRESHOLD = 80
const TREE_ROW_SIZE = 30
const TREE_ROW_INLINE_PADDING = 8
const TREE_DEPTH_INDENT = 16
function AutomaticChunkPageLoader({
fetchNextPage,
@ -204,7 +206,10 @@ export function DocumentChunkTreePanel({
'bg-state-base-hover ring-1 ring-state-accent-solid ring-inset',
)}
role="treeitem"
style={style}
style={{
...style,
paddingInlineStart: TREE_ROW_INLINE_PADDING + depth * TREE_DEPTH_INDENT,
}}
tabIndex={-1}
onClick={() => {
setFocusedNodeId(node.id)

View File

@ -323,7 +323,7 @@ export function DocumentDetailPage({
const document = documentQuery.data
return (
<section className="flex min-h-0 flex-1 flex-col px-6 pt-3 pb-5 xl:px-8">
<section className="flex min-h-0 flex-1 flex-col px-6 pt-3 pb-5">
<KnowledgeModelReadinessBanner
capability="index"
className="mb-4"

View File

@ -2568,8 +2568,8 @@ export function DocumentsPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }
<section
ref={documentsSectionRef}
className={cn(
'relative flex min-h-full w-full flex-col gap-4 p-4 @min-[768px]/knowledge-content:p-6 @min-[1280px]/knowledge-content:p-8',
bulkActionsVisible && 'pb-[calc(7rem+env(safe-area-inset-bottom,0px))]',
'relative flex min-h-full w-full flex-col gap-4 px-6 pt-3',
bulkActionsVisible ? 'pb-[calc(7rem+env(safe-area-inset-bottom,0px))]' : 'pb-6',
)}
onDragEnter={(event) => {
const types = Array.from(event.dataTransfer.types ?? [])

View File

@ -71,11 +71,6 @@ const RERANK_MODEL_LABEL_ID = 'knowledge-rerank-model-label'
const RERANK_MODEL_ERROR_ID = 'knowledge-rerank-model-error'
type BasicSaveSlice = 'members' | 'space'
type SaveErrorSlice = 'basic' | 'externalAccess' | 'settings'
type SaveError = {
kind: 'generic' | 'permission'
slice: SaveErrorSlice
}
type ExternalAccessDraft = {
apiEnabled: boolean
@ -298,7 +293,6 @@ export function KnowledgeSettingsForm({
}),
)
const [nameTouched, setNameTouched] = useState(false)
const [saveError, setSaveError] = useState<SaveError>()
const [isBasicRefreshing, setIsBasicRefreshing] = useState(false)
const [pendingMigrationId, setPendingMigrationId] = useState<string>()
const [pendingEmbeddingModel, setPendingEmbeddingModel] = useState<DefaultModel>()
@ -366,7 +360,6 @@ export function KnowledgeSettingsForm({
const canManageAccess = space.permission_keys.includes('knowledge_space_access_config')
const canDelete = space.permission_keys.includes('knowledge_space_delete')
const initialModelSetup = !settings.active_profile_available
const modelSetupReady = settings.active_profile_available
const readinessFieldLabel = (field: KnowledgeFsSettingsResponse['issues'][number]['field']) => {
if (field === 'embedding') return tSettings(($) => $['form.embeddingModel'])
if (field === 'reasoning') return tCommon(($) => $['modelProvider.systemReasoningModel.key'])
@ -451,15 +444,23 @@ export function KnowledgeSettingsForm({
settingsMutation.isPending ||
Boolean(pendingMigrationId)
const basicFieldsDisabled = !canEdit || isBasicSaving
const externalAccessDisabled = !canEdit || !canManageAccess || !modelSetupReady
const externalAccessDisabled = !canEdit || !canManageAccess
const retrievalFieldsDisabled = !canEdit || (!initialModelSetup && embeddingDirty)
const saveDisabled =
!basicDirty ||
nameInvalid ||
descriptionInvalid ||
membersInvalid ||
isBasicSaving ||
serverConflict
!basicDirty || nameInvalid || descriptionInvalid || membersInvalid || serverConflict
const showSaveSuccess = useCallback(
() => toast.success(tCommon(($) => $['api.actionSuccess'])),
[tCommon],
)
const showSaveError = useCallback(
(error?: unknown) =>
toast.error(
error instanceof Response && error.status === 403
? t(($) => $['newKnowledge.permissionRestricted'])
: t(($) => $['newKnowledge.settings.saveFailed']),
),
[t],
)
const startDraft = () => onDraftStart?.()
const beginSettingsDraft = (patch: Partial<SettingsDraft>) => {
if (!basicDirtyRef.current) settingsDraftOwnsFormLockRef.current = true
@ -477,7 +478,6 @@ export function KnowledgeSettingsForm({
setVisibility(space.visibility)
setSelectedMemberIds(initialSelectedMemberIds)
setNameTouched(false)
if (saveError?.slice === 'basic') setSaveError(undefined)
onDraftFinish?.()
}
@ -524,12 +524,14 @@ export function KnowledgeSettingsForm({
setRetrievalBaseline(savedRetrievalFingerprint)
}
void invalidateSettingsQueries().then(() => {
const hasQueuedSettingsDraft = Boolean(queuedSettingsDraftRef.current)
migratingSettingsDraftRef.current = undefined
if (!queuedSettingsDraftRef.current) pendingSettingsDraftRef.current = undefined
resumeSettingsQueueAfterMigrationRef.current = Boolean(queuedSettingsDraftRef.current)
if (!hasQueuedSettingsDraft) pendingSettingsDraftRef.current = undefined
resumeSettingsQueueAfterMigrationRef.current = hasQueuedSettingsDraft
setPendingMigrationId(undefined)
if (!hasQueuedSettingsDraft) showSaveSuccess()
if (
!queuedSettingsDraftRef.current &&
!hasQueuedSettingsDraft &&
settingsDraftOwnsFormLockRef.current &&
!basicDirtyRef.current
) {
@ -550,8 +552,7 @@ export function KnowledgeSettingsForm({
resumeSettingsQueueAfterMigrationRef.current = false
// oxlint-disable-next-line eslint-react/set-state-in-effect -- A terminal remote migration retires the local polling guard.
setPendingMigrationId(undefined)
// oxlint-disable-next-line eslint-react/set-state-in-effect -- The authoritative failed migration transitions the save UI to its retry state.
setSaveError({ kind: 'generic', slice: 'settings' })
showSaveError()
}
}, [
invalidateSettingsQueries,
@ -559,12 +560,12 @@ export function KnowledgeSettingsForm({
migrationQuery.isError,
onDraftFinish,
pendingMigrationId,
showSaveError,
showSaveSuccess,
])
const performBasicSave = async () => {
if (saveDisabled || !canEdit) return
setSaveError(undefined)
if (saveDisabled || isBasicSaving || !canEdit) return
try {
const saveSlice = async (
@ -612,14 +613,15 @@ export function KnowledgeSettingsForm({
)
}
completedBasicSaveFingerprintsRef.current = {}
showSaveSuccess()
setIsBasicRefreshing(true)
onDraftFinish?.()
void invalidateSettingsQueries().then(
() => setIsBasicRefreshing(false),
() => setIsBasicRefreshing(false),
)
} catch {
setSaveError({ kind: 'generic', slice: 'basic' })
} catch (error) {
showSaveError(error)
}
}
@ -635,7 +637,6 @@ export function KnowledgeSettingsForm({
let nextDraft: ExternalAccessDraft | undefined = draft
while (nextDraft) {
queuedExternalAccessDraftRef.current = undefined
setSaveError(undefined)
try {
await externalAccessMutation.mutateAsync({
body: {
@ -648,12 +649,10 @@ export function KnowledgeSettingsForm({
})
externalAccessBaselineRef.current = nextDraft
nextDraft = queuedExternalAccessDraftRef.current
} catch {
} catch (error) {
queuedExternalAccessDraftRef.current = undefined
externalAccessSaveInFlightRef.current = false
setApiEnabled(externalAccessBaselineRef.current.apiEnabled)
setWorkflowEnabled(externalAccessBaselineRef.current.workflowEnabled)
setSaveError({ kind: 'generic', slice: 'externalAccess' })
showSaveError(error)
return
}
}
@ -661,6 +660,7 @@ export function KnowledgeSettingsForm({
externalAccessSaveInFlightRef.current = false
setApiEnabled(externalAccessBaselineRef.current.apiEnabled)
setWorkflowEnabled(externalAccessBaselineRef.current.workflowEnabled)
showSaveSuccess()
await invalidateSettingsQueries()
}
@ -705,7 +705,6 @@ export function KnowledgeSettingsForm({
}
}
setSaveError(undefined)
try {
const result = await settingsMutation.mutateAsync({
body,
@ -729,10 +728,7 @@ export function KnowledgeSettingsForm({
await invalidateSettingsQueries()
return 'saved' as const
} catch (error) {
setSaveError({
kind: error instanceof Response && error.status === 403 ? 'permission' : 'generic',
slice: 'settings',
})
showSaveError(error)
return 'failed' as const
}
}
@ -754,10 +750,12 @@ export function KnowledgeSettingsForm({
settingsSaveInFlightRef.current = true
let nextDraft: SettingsDraft | undefined = draft
let saveResult: Awaited<ReturnType<typeof saveSettingsDraft>> = 'skipped'
let didSave = false
try {
while (nextDraft) {
queuedSettingsDraftRef.current = undefined
saveResult = await saveSettingsDraft(nextDraft)
if (saveResult === 'saved') didSave = true
if (saveResult === 'failed' || saveResult === 'migration') break
nextDraft = queuedSettingsDraftRef.current
}
@ -781,6 +779,7 @@ export function KnowledgeSettingsForm({
if (!latestDraftIsSaved) return
pendingSettingsDraftRef.current = undefined
if (didSave) showSaveSuccess()
if (settingsDraftOwnsFormLockRef.current && !basicDirtyRef.current) {
settingsDraftOwnsFormLockRef.current = false
onDraftFinish?.()
@ -815,18 +814,6 @@ export function KnowledgeSettingsForm({
void performBasicSave()
}
const retrySave = () => {
if (saveError?.slice === 'externalAccess') {
void performExternalAccessSave(pendingExternalAccessRef.current)
return
}
if (saveError?.slice === 'settings') {
void performSettingsSave(pendingSettingsDraftRef.current ?? liveSettingsDraftRef.current)
return
}
requestSave()
}
const armHistoryGuard = useCallback(() => {
globalThis.history.pushState(globalThis.history.state, '', globalThis.location.href)
historyGuardArmedRef.current = true
@ -993,23 +980,6 @@ export function KnowledgeSettingsForm({
/>
)}
{saveError && (
<div
className="mb-3 flex items-center gap-2 rounded-lg border border-text-destructive/20 bg-background-default-subtle px-3 py-2"
role="alert"
>
<span aria-hidden className="i-ri-error-warning-fill size-4 text-text-destructive" />
<span className="min-w-0 flex-1 system-xs-regular text-text-destructive">
{saveError.kind === 'permission'
? t(($) => $['newKnowledge.permissionRestricted'])
: t(($) => $['newKnowledge.settings.saveFailed'])}
</span>
<Button type="button" size="small" variant="ghost" onClick={retrySave}>
{tCommon(($) => $['operation.retry'])}
</Button>
</div>
)}
{serverConflict && (
<div
className="mb-3 flex items-center gap-2 rounded-lg border border-text-warning/20 bg-state-warning-hover px-3 py-2"
@ -1022,16 +992,6 @@ export function KnowledgeSettingsForm({
</div>
)}
{pendingMigrationId && (
<div
className="mb-3 flex items-center gap-2 rounded-lg border border-components-panel-border bg-background-section px-3 py-2 system-xs-regular text-text-tertiary"
role="status"
>
<span aria-hidden className="i-ri-loader-4-line size-4 shrink-0 animate-spin" />
{tCommon(($) => $['operation.saving'])}
</div>
)}
<Form className="flex flex-col gap-4 overflow-hidden pt-2 pb-7" onSubmit={requestSave}>
<h2 className="flex h-8 items-center system-sm-semibold text-text-secondary">
{t(($) => $['newKnowledge.settings.basicInfo'])}
@ -1151,9 +1111,7 @@ export function KnowledgeSettingsForm({
{tCommon(($) => $['operation.cancel'])}
</Button>
<Button type="submit" variant="primary" disabled={saveDisabled} loading={isBasicSaving}>
{isBasicSaving
? tCommon(($) => $['operation.saving'])
: t(($) => $['newKnowledge.settings.saveChanges'])}
{t(($) => $['newKnowledge.settings.saveChanges'])}
</Button>
</div>
)}
@ -1360,7 +1318,6 @@ export function KnowledgeSettingsForm({
<div className="flex items-center gap-3">
<NumberField
id="knowledge-top-k"
name="knowledge-top-k"
min={TOP_K_MIN}
max={TOP_K_MAX}
step={1}
@ -1425,7 +1382,6 @@ export function KnowledgeSettingsForm({
<div className="flex items-center gap-3">
<NumberField
id="knowledge-score-threshold"
name="knowledge-score-threshold"
min={SCORE_THRESHOLD_MIN}
max={SCORE_THRESHOLD_MAX}
step={0.01}

View File

@ -299,7 +299,7 @@ export function SourcesPage({ knowledgeSpaceId }: { knowledgeSpaceId: string })
])
return (
<div className="flex min-h-full min-w-0 flex-1 flex-col p-4 @min-[768px]/knowledge-content:p-6 @min-[1280px]/knowledge-content:p-8">
<div className="flex min-h-full min-w-0 flex-1 flex-col px-6 pt-3 pb-6 sm:pb-8">
<header className="flex items-start justify-between gap-4">
<div>
<h2 className="title-xl-semi-bold leading-6 text-text-primary">