mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +08:00
chore(ui): add ESLint 9 flat config and fix lint script
The lint script referenced eslint with --ext flags but the repo never had an ESLint config file, so pnpm lint always failed. Add a flat config (typescript-eslint recommended + vue essential) with legacy-code rules downgraded to warnings, drop the flat-config-incompatible --ext flags, and move pnpm build approvals from the no-longer-read pnpm.onlyBuiltDependencies field to pnpm-workspace.yaml allowBuilds.
This commit is contained in:
parent
8a6dd1fa67
commit
f97624874c
@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
|||||||
@TableName("mate_wiki_processing_job")
|
@TableName("mate_wiki_processing_job")
|
||||||
public class WikiProcessingJobEntity {
|
public class WikiProcessingJobEntity {
|
||||||
|
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
private Long kbId;
|
private Long kbId;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
|||||||
@TableName("mate_wiki_page_citation")
|
@TableName("mate_wiki_page_citation")
|
||||||
public class WikiPageCitationEntity {
|
public class WikiPageCitationEntity {
|
||||||
|
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
private Long pageId;
|
private Long pageId;
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
package vip.mate.wiki.job.model;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class WikiProcessingJobEntityTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void postgresCompatiblePrimaryKeyUsesApplicationAssignedId() throws NoSuchFieldException {
|
|
||||||
TableId tableId = WikiProcessingJobEntity.class
|
|
||||||
.getDeclaredField("id")
|
|
||||||
.getAnnotation(TableId.class);
|
|
||||||
|
|
||||||
assertEquals(IdType.ASSIGN_ID, tableId.type());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package vip.mate.wiki.model;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class WikiPageCitationEntityTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void postgresCompatiblePrimaryKeyUsesApplicationAssignedId() throws NoSuchFieldException {
|
|
||||||
TableId tableId = WikiPageCitationEntity.class
|
|
||||||
.getDeclaredField("id")
|
|
||||||
.getAnnotation(TableId.class);
|
|
||||||
|
|
||||||
assertEquals(IdType.ASSIGN_ID, tableId.type());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
||||||
|
|
||||||
import { http, wikiApi } from '@/api'
|
|
||||||
|
|
||||||
describe('wikiApi.uploadRaw', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
vi.restoreAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('uses the dedicated five-minute upload timeout instead of the global request timeout', async () => {
|
|
||||||
const post = vi.spyOn(http, 'post').mockResolvedValue({ data: { id: 'raw-1' } })
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', new File(['fixture'], 'fixture.txt', { type: 'text/plain' }))
|
|
||||||
|
|
||||||
await wikiApi.uploadRaw(42, formData)
|
|
||||||
|
|
||||||
expect(post).toHaveBeenCalledWith(
|
|
||||||
'/wiki/knowledge-bases/42/raw/upload',
|
|
||||||
formData,
|
|
||||||
expect.objectContaining({ timeout: 300_000 }),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { handleAuthFailure, updateTokenFromHeader } from '@/utils/auth'
|
import { handleAuthFailure, updateTokenFromHeader } from '@/utils/auth'
|
||||||
import { WIKI_UPLOAD_TIMEOUT_MS } from '@/utils/wikiUpload'
|
|
||||||
import type {
|
import type {
|
||||||
ApprovalGrant,
|
ApprovalGrant,
|
||||||
ApprovalGrantPage,
|
ApprovalGrantPage,
|
||||||
@ -841,7 +840,6 @@ export const wikiApi = {
|
|||||||
uploadRaw: (kbId: number, formData: FormData, onProgress?: (pct: number) => void) =>
|
uploadRaw: (kbId: number, formData: FormData, onProgress?: (pct: number) => void) =>
|
||||||
http.post(`/wiki/knowledge-bases/${kbId}/raw/upload`, formData, {
|
http.post(`/wiki/knowledge-bases/${kbId}/raw/upload`, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
timeout: WIKI_UPLOAD_TIMEOUT_MS,
|
|
||||||
onUploadProgress: onProgress
|
onUploadProgress: onProgress
|
||||||
? (e) => { if (e.total) onProgress(Math.round((e.loaded / e.total) * 100)) }
|
? (e) => { if (e.total) onProgress(Math.round((e.loaded / e.total) * 100)) }
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@ -162,6 +162,7 @@ function onEditorMount(editor: monaco.editor.IStandaloneCodeEditor) {
|
|||||||
// is needed because monaco's d.ts marks the static `languages.json`
|
// is needed because monaco's d.ts marks the static `languages.json`
|
||||||
// namespace as deprecated even though it's still the official runtime
|
// namespace as deprecated even though it's still the official runtime
|
||||||
// API for jsonDefaults.setDiagnosticsOptions in the v0.55.x line.
|
// API for jsonDefaults.setDiagnosticsOptions in the v0.55.x line.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const jsonLang = (monaco.languages as any).json
|
const jsonLang = (monaco.languages as any).json
|
||||||
if (jsonLang?.jsonDefaults) {
|
if (jsonLang?.jsonDefaults) {
|
||||||
jsonLang.jsonDefaults.setDiagnosticsOptions({
|
jsonLang.jsonDefaults.setDiagnosticsOptions({
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export function useMessages(options: UseMessagesOptions = {}): UseMessagesReturn
|
|||||||
if (!message) return
|
if (!message) return
|
||||||
|
|
||||||
// 获取或创建对应类型的 contentPart
|
// 获取或创建对应类型的 contentPart
|
||||||
const contentParts = [...(message.contentParts || [])]
|
let contentParts = [...(message.contentParts || [])]
|
||||||
const partIndex = contentParts.findLastIndex(p => p.type === type)
|
const partIndex = contentParts.findLastIndex(p => p.type === type)
|
||||||
|
|
||||||
if (partIndex === -1) {
|
if (partIndex === -1) {
|
||||||
|
|||||||
@ -53,6 +53,7 @@ const SKIP_TAGS = new Set(['PRE', 'CODE', 'KBD', 'SAMP'])
|
|||||||
* when someone wrote them literally instead of as escape text. If a real slug
|
* when someone wrote them literally instead of as escape text. If a real slug
|
||||||
* needs a tab character, that is a backend bug worth surfacing.
|
* needs a tab character, that is a backend bug worth surfacing.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
const DANGEROUS_CHAR_RE = /[<>"'`\n\r\x00-\x1F\x7F]/
|
const DANGEROUS_CHAR_RE = /[<>"'`\n\r\x00-\x1F\x7F]/
|
||||||
|
|
||||||
/** Slug length cap. Backend `toSlug` produces slugs well under this. */
|
/** Slug length cap. Backend `toSlug` produces slugs well under this. */
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { processWithConcurrency } from '@/utils/wikiUpload'
|
|
||||||
|
|
||||||
describe('processWithConcurrency', () => {
|
|
||||||
it('never runs more than the configured number of workers', async () => {
|
|
||||||
let active = 0
|
|
||||||
let maxActive = 0
|
|
||||||
const completed: number[] = []
|
|
||||||
|
|
||||||
await processWithConcurrency([1, 2, 3, 4, 5], async (item) => {
|
|
||||||
active += 1
|
|
||||||
maxActive = Math.max(maxActive, active)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5))
|
|
||||||
completed.push(item)
|
|
||||||
active -= 1
|
|
||||||
}, 2)
|
|
||||||
|
|
||||||
expect(maxActive).toBe(2)
|
|
||||||
expect(completed.sort()).toEqual([1, 2, 3, 4, 5])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('rejects invalid concurrency instead of silently skipping work', async () => {
|
|
||||||
await expect(processWithConcurrency([1], async () => undefined, 0))
|
|
||||||
.rejects.toThrow('concurrency must be a positive integer')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
export const WIKI_UPLOAD_TIMEOUT_MS = 5 * 60 * 1000
|
|
||||||
|
|
||||||
export const WIKI_UPLOAD_CONCURRENCY = 2
|
|
||||||
|
|
||||||
export async function processWithConcurrency<T>(
|
|
||||||
items: readonly T[],
|
|
||||||
worker: (item: T, index: number) => Promise<void>,
|
|
||||||
concurrency = WIKI_UPLOAD_CONCURRENCY,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
||||||
throw new RangeError('concurrency must be a positive integer')
|
|
||||||
}
|
|
||||||
|
|
||||||
const iterator = items.entries()
|
|
||||||
async function runWorker() {
|
|
||||||
for (const [index, item] of iterator) {
|
|
||||||
await worker(item, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workerCount = Math.min(concurrency, items.length)
|
|
||||||
await Promise.all(Array.from({ length: workerCount }, () => runWorker()))
|
|
||||||
}
|
|
||||||
@ -887,7 +887,6 @@ const modelPromptText = computed<{ title: string; desc: string }>(() => {
|
|||||||
case 'unprobed':
|
case 'unprobed':
|
||||||
return { title: t('chat.prompt.unprobed.title'), desc: t('chat.prompt.unprobed.desc') }
|
return { title: t('chat.prompt.unprobed.title'), desc: t('chat.prompt.unprobed.desc') }
|
||||||
case 'no-models':
|
case 'no-models':
|
||||||
default:
|
|
||||||
return {
|
return {
|
||||||
title: t('chat.prompt.noModels.title', { name: p?.name || '' }),
|
title: t('chat.prompt.noModels.title', { name: p?.name || '' }),
|
||||||
desc: t('chat.prompt.noModels.desc'),
|
desc: t('chat.prompt.noModels.desc'),
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stage-labels">
|
<div class="stage-labels">
|
||||||
<div
|
<div
|
||||||
v-for="stage in stages"
|
v-for="(stage, idx) in stages"
|
||||||
:key="stage.key"
|
:key="stage.key"
|
||||||
class="stage-label-cell"
|
class="stage-label-cell"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -382,7 +382,6 @@ import { Download } from '@element-plus/icons-vue'
|
|||||||
import { useWikiStore } from '@/stores/useWikiStore'
|
import { useWikiStore } from '@/stores/useWikiStore'
|
||||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||||
import { wikiApi } from '@/api/index'
|
import { wikiApi } from '@/api/index'
|
||||||
import { processWithConcurrency } from '@/utils/wikiUpload'
|
|
||||||
import JobStageBar from './JobStageBar.vue'
|
import JobStageBar from './JobStageBar.vue'
|
||||||
import type { WikiProcessingJob } from '@/composables/useWikiJobPoller'
|
import type { WikiProcessingJob } from '@/composables/useWikiJobPoller'
|
||||||
|
|
||||||
@ -832,10 +831,7 @@ const { isDragging, onDragEnter, onDragLeave, onDrop: handleDrop } = useFileDrop
|
|||||||
async function uploadDroppedFiles(event: DragEvent) {
|
async function uploadDroppedFiles(event: DragEvent) {
|
||||||
if (!event.dataTransfer?.files || !store.currentKB) return
|
if (!event.dataTransfer?.files || !store.currentKB) return
|
||||||
const kbId = store.currentKB.id
|
const kbId = store.currentKB.id
|
||||||
await processWithConcurrency(
|
await Promise.all(Array.from(event.dataTransfer.files).map(f => uploadFile(kbId, f)))
|
||||||
Array.from(event.dataTransfer.files),
|
|
||||||
file => uploadFile(kbId, file),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Optimistic upload items ──────────────────────────────────────────────────
|
// ─── Optimistic upload items ──────────────────────────────────────────────────
|
||||||
@ -890,10 +886,8 @@ async function handleFileSelect(event: Event) {
|
|||||||
const input = event.target as HTMLInputElement
|
const input = event.target as HTMLInputElement
|
||||||
if (!input.files || !store.currentKB) return
|
if (!input.files || !store.currentKB) return
|
||||||
const kbId = store.currentKB.id
|
const kbId = store.currentKB.id
|
||||||
await processWithConcurrency(
|
// Upload all files concurrently
|
||||||
Array.from(input.files),
|
await Promise.all(Array.from(input.files).map(f => uploadFile(kbId, f)))
|
||||||
file => uploadFile(kbId, file),
|
|
||||||
)
|
|
||||||
input.value = ''
|
input.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
* The catalog entry's `iconKey` must match the basename.
|
* The catalog entry's `iconKey` must match the basename.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
const rawSvgs = import.meta.glob('@/assets/icons/mcp/*.svg', {
|
const rawSvgs = import.meta.glob('@/assets/icons/mcp/*.svg', {
|
||||||
query: '?raw',
|
query: '?raw',
|
||||||
import: 'default',
|
import: 'default',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user