diff --git a/.vscode/settings.json b/.vscode/settings.json index eef5140..a49fc88 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,12 +8,17 @@ "cloudml", "commitlint", "dbaeumer", + "deepseek", "devcontainers", "eamodio", "esbenp", + "gitbeaker", "Gruntfuggly", + "Hasher", + "langchain", "micr", "mioffice", + "openai", "oxlint", "tseslint", "wlpbbgiky", diff --git a/bun.lockb b/bun.lockb index 2ea389c..1be4306 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/controllers/manageMrEvent/index.ts b/controllers/manageMrEvent/index.ts new file mode 100644 index 0000000..162a7a5 --- /dev/null +++ b/controllers/manageMrEvent/index.ts @@ -0,0 +1,152 @@ +import { Logger } from "winston" + +import service from "../../service" +import netTool from "../../service/netTool" +import { Gitlab } from "../../types/gitlab" +import reviewFiles from "./reviewFiles" +import summaryFiles from "./summaryFiles" +import summaryMr from "./summaryMr" +import Commenter from "./utils/commenter" +import { Inputs } from "./utils/inputs" +import { PathFilter } from "./utils/pathFilter" + +/** + * 过滤事件 + * @param {Gitlab.MergeRequestEvent} event - 合并请求事件 + * @returns {boolean} - 是否通过过滤 + */ +const filterEvent = (event: Gitlab.MergeRequestEvent): boolean => { + return ( + event.event_type === "merge_request" && + event.object_attributes?.state === "opened" && + !event.object_attributes?.changes + ) +} + +/** + * 处理原始事件 + * @param {Gitlab.MergeRequestEvent} mergeRequest - 合并请求事件 + * @param {Logger} logger - 日志记录器 + * @returns {Promise} - 处理结果 + */ +const manageRawEvent = async ( + mergeRequest: Gitlab.MergeRequestEvent, + logger: Logger +): Promise => { + // 如果MR不是打开或重新打开状态,则不处理 + const isOnCreateMr = filterEvent(mergeRequest) + logger.info(`isOnCreateMr: ${isOnCreateMr}`) + if (!isOnCreateMr) return netTool.ok() + // 获取最新的MR Version + const versions = await service.gitlab.mr.getDiffVersions( + mergeRequest.project.id, + mergeRequest.object_attributes.iid + ) + if (versions.length === 0) { + logger.error("Failed to get MR versions") + return netTool.serverError("Failed to get MR versions") + } + const latestVersion = versions[0] + logger.debug(`latestVersion: ${JSON.stringify(latestVersion)}`) + // 获取MR的全部修改 + const changes = await service.gitlab.mr.getChanges( + mergeRequest.project.id, + mergeRequest.object_attributes.iid + ) + logger.debug(`changes: ${JSON.stringify(changes)}`) + // 如果MR的全部修改不存在,则不处理 + if (!changes) { + logger.error("Failed to get MR changes") + return netTool.serverError("Failed to get MR changes") + } + // 如果MR的描述中包含'skip review',则不处理 + if (changes.description.includes("skip review")) { + logger.info("Skip review") + return netTool.ok() + } + // 创建路径过滤器 + const pathFilter = new PathFilter([ + "**/*.css", + "**/*.less", + "**/*.scss", + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + ]) + // 获取MR的文件修改,并过滤出需要处理的文件 + const files = changes.changes.filter( + (change) => + pathFilter.check(change.new_path) && + !change.deleted_file && + !change.renamed_file && + change.diff + ) + // 如果没有需要处理的文件,则不处理 + if (files.length === 0) { + logger.info("No files need to be processed") + return netTool.ok() + } + // 创建Commenter实例 + const commenter = new Commenter( + latestVersion.base_commit_sha, + latestVersion.head_commit_sha, + latestVersion.start_commit_sha, + mergeRequest.project.id, + mergeRequest.object_attributes.iid, + logger + ) + // 发起loading评论,无所谓是否成功 + await commenter.createLoadingComment() + + // 创建输入实例 + const inputs: Inputs = new Inputs() + // 获取MR的标题和描述 + inputs.title = changes.title + inputs.description = changes.description || "暂无描述" + + // 对文件进行总结 + const { summarizedFileMap, needReviewFileMap } = await summaryFiles( + files, + true, + inputs, + logger + ) + // 如果总结完的文件Map为空,则不处理 + if (summarizedFileMap.size === 0) { + logger.info("No summarized files") + await commenter.modifyLoadingOrCreateComment("没有需要审查的文件") + return netTool.ok() + } + // 总结MR + const summarizedMr = await summaryMr(summarizedFileMap, inputs, logger) + // 如果总结MR为空,则不处理 + if (!summarizedMr) { + logger.info("No summarized Mr") + await commenter.modifyLoadingOrCreateComment("没有生成整体的总结") + return netTool.ok() + } + // 更新输入实例短总结 + inputs.shortSummary = summarizedMr + // 发送总结评论 + 文件变更 + await commenter.createSummarizedComment(summarizedMr, summarizedFileMap) + // 过滤出需要审查的文件 + const needReviewFiles = files.filter( + (file) => needReviewFileMap.get(file.new_path) || false + ) + // 如果需要审查的文件为空,则不处理 + if (needReviewFiles.length === 0) { + logger.info("No need review files") + await commenter.createComment("没有需要审查的文件") + return netTool.ok() + } + // 对需要审查的文件进行审查 + await reviewFiles(needReviewFiles, inputs, commenter, logger) + return netTool.ok() +} + +const manageMrEvent = { + manageRawEvent, +} + +export default manageMrEvent diff --git a/controllers/manageMrEvent/reviewFiles.ts b/controllers/manageMrEvent/reviewFiles.ts new file mode 100644 index 0000000..09dbebc --- /dev/null +++ b/controllers/manageMrEvent/reviewFiles.ts @@ -0,0 +1,152 @@ +import { CommitDiffSchema } from "@gitbeaker/rest" +import pLimit from "p-limit" +import { Logger } from "winston" + +import chatTools from "../../utils/chatTools" +import tokenTools from "../../utils/tokenTools" +import Commenter from "./utils/commenter" +import diffTools, { Review } from "./utils/diffTools" +import { Inputs } from "./utils/inputs" +import { Prompts } from "./utils/prompts" + +/** + * 审查需要审查的文件列表,并生成评论。 + * @param {CommitDiffSchema[]} needReviewFiles - 需要审查的文件列表。 + * @param {Inputs} rawInput - 原始输入。 + * @param {Commenter} commenter - 评论工具实例。 + * @param {Logger} logger - 日志记录器。 + * @returns {Promise} 返回一个Promise,表示审查过程的完成。 + */ +const reviewFiles = async ( + needReviewFiles: CommitDiffSchema[], + rawInput: Inputs, + commenter: Commenter, + logger: Logger +) => { + const prompts = new Prompts() + + // 创建一个Map来存储文件的差异信息 + const diffsFileMap = new Map() + + // 解析每个文件的差异并存储在diffsFileMap中 + needReviewFiles.forEach((file) => { + const diffs = diffTools.parseFileDiffs(file) + const fileDiff = diffsFileMap.get(file.new_path) || [] + diffsFileMap.set(file.new_path, fileDiff.concat(diffs)) + }) + + logger.debug(`diffsFileMap: ${JSON.stringify([...diffsFileMap])}`) + + // 如果没有需要审查的文件,记录日志并创建评论 + if (diffsFileMap.size === 0) { + logger.info("No files need review") + commenter.createComment("没有需要审查的文件") + return + } + + const skippedFiles: string[] = [] + const fileCRResultsMap = new Map() + + /** + * 审查单个文件的差异。 + * @param {Array} diff - 文件的差异数组。 + * @param {string} filename - 文件名。 + * @returns {Promise} 返回一个Promise,表示审查过程的完成。 + */ + const doFileReview = async ( + diff: [number, number, string][], + filename: string + ) => { + logger.info(`Reviewing ${filename}`) + const inputs = rawInput.clone() + inputs.filename = filename + let tokens = tokenTools.getTokenCount(prompts.renderReviewFileDiff(inputs)) + + // 计算并打包文件的差异,确保令牌数不超过限制 + const packedFileCount = diff.findIndex((d, idx) => { + const hunk = d[2] + if (tokens + tokenTools.getTokenCount(hunk) > 100 * 1000) { + logger.info( + `only packing ${idx} / ${diff.length} diffs, tokens: ${tokens} / ${100 * 1000}` + ) + return true + } + inputs.patches += ` +${hunk} +` + tokens += tokenTools.getTokenCount(hunk) + return false + }) + + // 如果没有打包任何差异,跳过该文件 + if (packedFileCount === 0) { + logger.info(`skipping ${filename}`) + skippedFiles.push(filename) + return + } + + // 获取GPT-4模型并生成审查提示 + const codeChatBot = await chatTools.getGpt4oModel(0) + const reviewPrompt = prompts.renderReviewFileDiff(inputs) + logger.debug(`reviewPrompt for ${filename}: ${reviewPrompt}`) + try { + const { content: review } = await codeChatBot.invoke(reviewPrompt) + if (!review) throw new Error("Empty review") + logger.info(`review for ${filename}: ${review}`) + + // 解析审查结果并过滤需要评论的审查 + const reviews = diffTools.parseReview(review as string, diff) + logger.debug(`reviews for ${filename}: ${JSON.stringify(reviews)}`) + const needCommentReviews = reviews.filter( + (r) => !r.comment.includes("LGTM") + ) + fileCRResultsMap.set(filename, needCommentReviews) + } catch { + logger.error(`Failed to review ${filename}`) + } + } + + /** + * 创建评论。 + * @param {string} filename - 文件名。 + * @param {Review} reviews - 审查结果。 + * @returns {Promise} 返回一个Promise,表示评论过程的完成。 + */ + const doComment = async (filename: string, reviews: Review) => { + const file = needReviewFiles.find((f) => f.new_path === filename) + if (!file) { + logger.error(`Failed to find file ${filename}`) + return + } + await commenter.createReviewComment( + file.new_path, + file.old_path, + reviews.startLine, + reviews.endLine, + reviews.comment + ) + } + + // 使用pLimit限制并发审查文件的数量 + const limit = pLimit(10) + await Promise.allSettled( + [...diffsFileMap].map(([filename, diffs]) => + limit(() => doFileReview(diffs, filename)) + ) + ) + + // 把fileCRResultsMap中的needCommentReviews拍平成一个数组 + const needCommentReviews: { filename: string; reviews: Review }[] = [] + fileCRResultsMap.forEach((reviews, filename) => { + reviews.forEach((r) => needCommentReviews.push({ filename, reviews: r })) + }) + + // 创建评论 + await Promise.allSettled( + needCommentReviews.map(({ filename, reviews }) => + limit(() => doComment(filename, reviews)) + ) + ) +} + +export default reviewFiles diff --git a/controllers/manageMrEvent/summaryFiles.ts b/controllers/manageMrEvent/summaryFiles.ts new file mode 100644 index 0000000..6c59e04 --- /dev/null +++ b/controllers/manageMrEvent/summaryFiles.ts @@ -0,0 +1,126 @@ +import { CommitDiffSchema } from "@gitbeaker/rest" +import pLimit from "p-limit" +import { Logger } from "winston" + +import chatTools from "../../utils/chatTools" +import tokenTools from "../../utils/tokenTools" +import { Inputs } from "./utils/inputs" +import { Prompts } from "./utils/prompts" + +/** + * 对文件进行总结 + * @param {CommitDiffSchema[]} files - 要总结的文件 + * @param {boolean} needReview - 是否审查简单更改 + * @param {Inputs} rawInputs - 原始输入 + * @param {Prompts} prompts - 提示 + * @param {Logger} logger - 日志记录器 + * @returns {Promise<{ summarizedFileMap: Map, needReviewFileMap: Map }>} 返回包含总结和需要审查的文件Map的Promise + */ +const summaryFiles = async ( + files: CommitDiffSchema[], + needReview: boolean, + rawInputs: Inputs, + logger: Logger +) => { + // 生成Prompts实例 + const prompts: Prompts = new Prompts() + // 按文件名融合Diff + const fileMap = new Map() + const DIFF_SEPARATOR = "\n--- DIFF SEPARATOR ---\n" + + files.forEach((file) => { + const rawDiff = fileMap.get(file.new_path) || "" + // 融合Diff + const diff = rawDiff ? rawDiff + DIFF_SEPARATOR + file.diff : file.diff + fileMap.set(file.new_path, diff) + }) + + // 总结后的文件Map + const summarizedFileMap = new Map() + + /** + * 生成文件的Summary + * @param {string} diff - 文件的差异 + * @param {string} path - 文件路径 + * @returns {Promise} 返回生成Summary的Promise + */ + const doFileSummary = async (diff: string, path: string) => { + const inputs = rawInputs.clone() + inputs.fileDiff = diff + inputs.filename = path + const summarizePrompt = prompts.renderSummarizeFileDiff(inputs, needReview) + logger.debug(`summarizePrompt for ${path}: ${summarizePrompt}`) + const tokens = tokenTools.getTokenCount(summarizePrompt) + logger.debug(`tokens for ${path}: ${tokens}`) + if (tokens > 100 * 1000) { + logger.error( + `File diff too long for ${path} (${tokens} tokens), skipping` + ) + return + } + const codeChatBot = await chatTools.getGpt4oModel(0) + try { + const { content: summarize } = await codeChatBot.invoke(summarizePrompt) + if (!summarize) throw new Error("Empty summarize") + logger.info(`summarize for ${path}: ${summarize}`) + summarizedFileMap.set(path, summarize as string) + } catch { + logger.error(`Failed to summarize for ${path}`) + } + } + + const limit = pLimit(5) + const promises = Array.from(fileMap.entries()).map(([path, diff]) => + limit(() => doFileSummary(diff, path)) + ) + await Promise.allSettled(promises) + + // 需要Review的文件Map + const needReviewFileMap = new Map() + + // 如果不需要审查更改,则直接返回 + if (!needReview) + return { + summarizedFileMap, + needReviewFileMap, + } + + /** + * 管理文件的审查状态 + * @param {string} path - 文件路径 + * @param {string} summarize - 文件总结 + */ + const manageTriage = (path: string, summarize: string) => { + const triageRegex = /\[TRIAGE\]:\s*(NEEDS_REVIEW|APPROVED)/ + const triageMatch = summarize.match(triageRegex) + // 如果没有匹配到TRIAGE,打印错误日志 + if (!triageMatch) { + logger.error(`Failed to triage for ${path}`) + needReviewFileMap.set(path, true) + return + } + // 如果匹配到TRIAGE,根据匹配结果设置needReviewFileMap + if (triageMatch[1] === "APPROVED") { + logger.info(`Approved for ${path}`) + needReviewFileMap.set(path, false) + } else { + logger.info(`Needs review for ${path}`) + needReviewFileMap.set(path, true) + } + // 删除源总结中的TRIAGE + const newSummarize = summarize.replace(triageRegex, "").trim() + summarizedFileMap.set(path, newSummarize) + } + + // 全部文件过一遍TRIAGE + Array.from(summarizedFileMap.entries()).forEach(([path, summarize]) => + manageTriage(path, summarize) + ) + + return { + summarizedFileMap, + needReviewFileMap, + } +} + +export default summaryFiles diff --git a/controllers/manageMrEvent/summaryMr.ts b/controllers/manageMrEvent/summaryMr.ts new file mode 100644 index 0000000..1d35f30 --- /dev/null +++ b/controllers/manageMrEvent/summaryMr.ts @@ -0,0 +1,56 @@ +import { Logger } from "winston" + +import chatTools from "../../utils/chatTools" +import { Inputs } from "./utils/inputs" +import { Prompts } from "./utils/prompts" + +/** + * 总结合并请求的变更。 + * @param {Map} summarizedFileMap - 一个包含文件路径和对应摘要的Map。 + * @param {Inputs} rawInputs - 输入数据的实例。 + * @param {Logger} logger - 用于记录日志的Logger实例。 + * @returns {Promise} 返回包含总结的Promise字符串。 + */ +const summaryMr = async ( + summarizedFileMap: Map, + rawInputs: Inputs, + logger: Logger +) => { + // 创建Prompts实例 + const prompts = new Prompts() + // 克隆输入数据 + const inputs = rawInputs.clone() + + // 遍历summarizedFileMap,将每个文件的摘要添加到rawSummary中 + summarizedFileMap.forEach((summarize, path) => { + inputs.rawSummary += `--- + ${path}: ${summarize} + ` + }) + // 记录完整的变更日志 + logger.debug(`full changes: ${inputs.rawSummary}`) + + // 渲染总结提示 + const summarizePrompt = prompts.renderSummarize(inputs) + + // 记录总结提示日志 + logger.debug(`summarizePrompt for Mr: ${summarizePrompt}`) + + // 获取GPT-4o模型实例 + const codeChatBot = await chatTools.getGpt4oModel(0) + + try { + // 调用模型生成总结 + const { content: summarize } = await codeChatBot.invoke(summarizePrompt) + if (!summarize) throw new Error("Empty summarize") + // 记录总结日志 + logger.debug(`summarize for Mr: ${summarize}`) + return summarize as string + } catch { + // 记录错误日志 + logger.error(`Failed to summarize for Mr`) + return "" + } +} + +export default summaryMr diff --git a/controllers/manageMrEvent/utils/commenter.ts b/controllers/manageMrEvent/utils/commenter.ts new file mode 100644 index 0000000..cfcec64 --- /dev/null +++ b/controllers/manageMrEvent/utils/commenter.ts @@ -0,0 +1,221 @@ +import { MergeRequestNoteSchema } from "@gitbeaker/rest" +import { Logger } from "winston" + +import service from "../../../service" +import { CreateRangeDiscussionPosition } from "../../../service/gitlab/discussions" +import { shortenPath } from "../../../utils/pathTools" + +/** + * Gitlab评论控制器 + */ +export class Commenter { + private base_sha: string + private head_sha: string + private start_sha: string + private project_id: number + private merge_request_iid: number + private loadingComment: MergeRequestNoteSchema | null = null + private comments: MergeRequestNoteSchema[] = [] + private logger: Logger + + constructor( + base_sha: string, + head_sha: string, + start_sha: string, + project_id: number, + merge_request_iid: number, + logger: Logger + ) { + this.base_sha = base_sha + this.head_sha = head_sha + this.start_sha = start_sha + this.project_id = project_id + this.merge_request_iid = merge_request_iid + this.logger = logger + } + + /** + * 获取所有评论 + * @returns {Promise} 返回评论列表 + */ + async getComments() { + // 如果评论列表已经存在,直接返回 + if (this.comments.length > 0) { + return this.comments + } + const commentList: MergeRequestNoteSchema[] = [] + let page = 1 + const per_page = 100 + while (true) { + // 从 GitLab 服务获取评论 + const comments = await service.gitlab.mr.getComments( + this.project_id, + this.merge_request_iid, + page, + per_page + ) + commentList.push(...comments) + page++ + // 如果获取的评论数量少于每页数量,说明已经获取完毕,退出循环 + if (comments.length < per_page) { + break + } + } + this.comments = commentList + return commentList + } + + /** + * 创建评论 + * @param {string} body 评论内容 + * @returns {Promise} 返回创建的评论 + */ + async createComment(body: string) { + // 调用 GitLab 服务创建评论 + const res = await service.gitlab.note.create2Mr( + this.project_id, + this.merge_request_iid, + body + ) + if (!res) { + this.logger.error("Failed to create comment") + } + return res + } + + /** + * 创建加载中的评论 + * @returns {Promise} + */ + async createLoadingComment() { + const body = "小煎蛋正在处理您的合并请求,请稍等片刻。" + // 调用 GitLab 服务创建加载中的评论 + const res = await service.gitlab.note.create2Mr( + this.project_id, + this.merge_request_iid, + body + ) + if (!res) { + this.logger.error("Failed to create loading comment") + } + this.loadingComment = res + } + + /** + * 修改加载中的评论 + * @param {string} body 新的评论内容 + * @returns {Promise} 返回修改后的评论 + */ + async modifyLoadingComment(body: string) { + if (!this.loadingComment) { + this.logger.error("No loading comment to modify") + return + } + // 调用 GitLab 服务修改加载中的评论 + const res = await service.gitlab.note.modify2Mr( + this.project_id, + this.merge_request_iid, + this.loadingComment.id, + body + ) + if (!res) { + this.logger.error("Failed to modify loading comment") + } + return res + } + + /** + * 修改加载中的评论,如果失败则创建新的评论。 + * @param {string} body - 评论内容。 + * @returns {Promise} 返回修改或创建的评论。 + */ + async modifyLoadingOrCreateComment(body: string) { + if (this.loadingComment) { + const res = await this.modifyLoadingComment(body) + if (res) return res + } + return this.createComment(body) + } + + /** + * 创建总结评论 + * @param {string} summarizedMr 合并请求的总结 + * @param {Map} summarizedFileMap 文件变更的总结 + * @returns {Promise} + */ + async createSummarizedComment( + summarizedMr: string, + summarizedFileMap: Map + ) { + // 构建总结评论的内容 + const summarizedComment = ` +# 整体摘要: +${summarizedMr} + +# 文件变更: +| 文件路径 | 变更摘要 | +| --- | --- | +${[...summarizedFileMap] + .map(([path, summary]) => `| \`${shortenPath(path)}\` | ${summary} |`) + .join("\n")} + ` + this.logger.debug(`Summarized comment: ${summarizedComment}`) + // 尝试修改加载中的评论,如果失败则创建新的评论 + const res = await this.modifyLoadingOrCreateComment(summarizedComment) + if (!res) { + this.logger.error("Failed to create summarized comment") + } + } + + /** + * 创建审查评论 + * @param {string} new_path 新文件路径 + * @param {string} old_path 旧文件路径 + * @param {number} start_line 开始行 + * @param {number} end_line 结束行 + * @param {string} content 评论内容 + * @returns {Promise} + */ + async createReviewComment( + new_path: string, + old_path: string, + start_line: number, + end_line: number, + content: string + ) { + // 计算文件路径的 SHA1 哈希值 + const hasher = new Bun.CryptoHasher("sha1") + hasher.update(new_path) + const fileSha = hasher.digest("hex") + // 构建评论的位置对象 + const position: CreateRangeDiscussionPosition = { + base_sha: this.base_sha, + start_sha: this.start_sha, + head_sha: this.head_sha, + new_path, + old_path, + position_type: "text", + new_line: end_line, + line_range: { + start: { + line_code: `${fileSha}_0_${end_line}`, + }, + end: { + line_code: `${fileSha}_0_${end_line}`, + }, + }, + } + // 调用 GitLab 服务创建审查评论 + const res = await service.gitlab.discussions.create2Mr( + this.project_id, + this.merge_request_iid, + content, + position + ) + if (!res) { + this.logger.error("Failed to create review comment") + } + } +} + +export default Commenter diff --git a/controllers/manageMrEvent/utils/diffTools.ts b/controllers/manageMrEvent/utils/diffTools.ts new file mode 100644 index 0000000..d51c6c0 --- /dev/null +++ b/controllers/manageMrEvent/utils/diffTools.ts @@ -0,0 +1,342 @@ +import { CommitDiffSchema } from "@gitbeaker/rest" + +/** + * 将diff字符串拆分为多个块。 + * @param {string | null | undefined} diff - 包含diff内容的字符串。 + * @returns {string[]} 返回包含diff块的字符串数组。 + */ +const splitDiff = (diff: string | null | undefined): string[] => { + if (diff == null) { + return [] + } + + const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*$/gm + + const result: string[] = [] + let last = -1 + let match: RegExpExecArray | null + while ((match = pattern.exec(diff)) !== null) { + if (last === -1) { + last = match.index + } else { + result.push(diff.substring(last, match.index)) + last = match.index + } + } + if (last !== -1) { + result.push(diff.substring(last)) + } + return result +} + +/** + * 获取diff块的起始和结束行号。 + * @param {string} diff - 包含diff内容的字符串。 + * @returns {object | null} 返回包含旧块和新块起始和结束行号的对象,或返回null。 + */ +const getStartEndLine = ( + diff: string +): { + oldHunk: { startLine: number; endLine: number } + newHunk: { startLine: number; endLine: number } +} | null => { + const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@)/gm + const match = pattern.exec(diff) + if (match != null) { + const oldBegin = parseInt(match[2]) + const oldDiff = parseInt(match[3]) + const newBegin = parseInt(match[4]) + const newDiff = parseInt(match[5]) + return { + oldHunk: { + startLine: oldBegin, + endLine: oldBegin + oldDiff - 1, + }, + newHunk: { + startLine: newBegin, + endLine: newBegin + newDiff - 1, + }, + } + } else { + return null + } +} + +/** + * 解析diff字符串,返回旧块和新块的内容。 + * @param {string} diff - 包含diff内容的字符串。 + * @returns {object | null} 返回包含旧块和新块内容的对象,或返回null。 + */ +export const parseDiff = ( + diff: string +): { oldHunk: string; newHunk: string } | null => { + const hunkInfo = getStartEndLine(diff) + if (hunkInfo == null) { + return null + } + + const oldHunkLines: string[] = [] + const newHunkLines: string[] = [] + + let newLine = hunkInfo.newHunk.startLine + + const lines = diff.split("\n").slice(1) // 跳过@@行 + + // 如果最后一行为空,则移除 + if (lines[lines.length - 1] === "") { + lines.pop() + } + + // 跳过前3行和后3行的注释 + const skipStart = 3 + const skipEnd = 3 + + let currentLine = 0 + + const removalOnly = !lines.some((line) => line.startsWith("+")) + + for (const line of lines) { + currentLine++ + if (line.startsWith("-")) { + oldHunkLines.push(`${line.substring(1)}`) + } else if (line.startsWith("+")) { + newHunkLines.push(`${newLine}: ${line.substring(1)}`) + newLine++ + } else { + // 上下文行 + oldHunkLines.push(`${line}`) + if ( + removalOnly || + (currentLine > skipStart && currentLine <= lines.length - skipEnd) + ) { + newHunkLines.push(`${newLine}: ${line}`) + } else { + newHunkLines.push(`${line}`) + } + newLine++ + } + } + + return { + oldHunk: oldHunkLines.join("\n"), + newHunk: newHunkLines.join("\n"), + } +} + +/** + * 解析文件的diff信息,返回包含起始行号、结束行号和hunk内容的数组。 + * @param {CommitDiffSchema} file - 包含文件diff信息的对象。 + * @returns {[number, number, string][]} 返回包含起始行号、结束行号和hunk内容的数组。 + */ +const parseFileDiffs = (file: CommitDiffSchema): [number, number, string][] => { + // 获取文件的Diff,一个文件的Diff可能包含多个Hunk + const diffs = diffTools.splitDiff(file.diff) + if (diffs.length === 0) return [] + return diffs + .map((diff) => { + const diffLines = diffTools.getStartEndLine(diff) + if (!diffLines) return null + const hunks = diffTools.parseDiff(diff) + if (!hunks) return null + const hunksStr = ` +---new_hunk--- +\`\`\` +${hunks.newHunk} +\`\`\` + +---old_hunk--- +\`\`\` +${hunks.oldHunk} +\`\`\` + ` + return [ + diffLines.newHunk.startLine, + diffLines.newHunk.endLine, + hunksStr, + ] as [number, number, string] + }) + .filter((diff) => diff !== null) +} + +export interface Review { + startLine: number + endLine: number + comment: string +} + +/** + * 解析审查评论的函数 + * @param {string} response - 审查评论的响应字符串 + * @param {Array<[number, number, string]>} diffs - 差异数组,每个差异包含开始行号、结束行号和差异内容 + * @returns {Review[]} - 返回解析后的审查评论数组 + */ +const parseReview = ( + response: string, + diffs: Array<[number, number, string]> +): Review[] => { + /** + * 存储当前的审查评论 + */ + const storeReview = (): void => { + if (currentStartLine !== null && currentEndLine !== null) { + const review: Review = { + startLine: currentStartLine, + endLine: currentEndLine, + comment: currentComment, + } + + let withinDiff = false + let bestDiffStartLine = -1 + let bestDiffEndLine = -1 + let maxIntersection = 0 + + // 查找与当前审查评论行号范围重叠最多的差异 + for (const [startLine, endLine] of diffs) { + const intersectionStart = Math.max(review.startLine, startLine) + const intersectionEnd = Math.min(review.endLine, endLine) + const intersectionLength = Math.max( + 0, + intersectionEnd - intersectionStart + 1 + ) + + if (intersectionLength > maxIntersection) { + maxIntersection = intersectionLength + bestDiffStartLine = startLine + bestDiffEndLine = endLine + withinDiff = + intersectionLength === review.endLine - review.startLine + 1 + } + + if (withinDiff) break + } + + // 如果审查评论不在任何差异范围内,进行相应处理 + if (!withinDiff) { + if (bestDiffStartLine !== -1 && bestDiffEndLine !== -1) { + review.comment = `> 注意:此CR评论不在差异范围内,因此被映射到重叠最多的Diff。原始行号 [${review.startLine}-${review.endLine}] + + ${review.comment}` + review.startLine = bestDiffStartLine + review.endLine = bestDiffEndLine + } else { + review.comment = `> 注意:此CR评论不在差异范围内,但未找到与其重叠的Diff。原始行号 [${review.startLine}-${review.endLine}] + + ${review.comment}` + review.startLine = diffs[0][0] + review.endLine = diffs[0][1] + } + } + + reviews.push(review) + } + } + + /** + * 清理代码块中的行号 + * @param {string} comment - 评论字符串 + * @param {string} codeBlockLabel - 代码块标签 + * @returns {string} - 返回清理后的评论字符串 + */ + const sanitizeCodeBlock = ( + comment: string, + codeBlockLabel: string + ): string => { + const codeBlockStart = `\`\`\`${codeBlockLabel}` + const codeBlockEnd = "```" + const lineNumberRegex = /^ *(\d+): /gm + + let codeBlockStartIndex = comment.indexOf(codeBlockStart) + + while (codeBlockStartIndex !== -1) { + const codeBlockEndIndex = comment.indexOf( + codeBlockEnd, + codeBlockStartIndex + codeBlockStart.length + ) + + if (codeBlockEndIndex === -1) break + + const codeBlock = comment.substring( + codeBlockStartIndex + codeBlockStart.length, + codeBlockEndIndex + ) + const sanitizedBlock = codeBlock.replace(lineNumberRegex, "") + + comment = + comment.slice(0, codeBlockStartIndex + codeBlockStart.length) + + sanitizedBlock + + comment.slice(codeBlockEndIndex) + + codeBlockStartIndex = comment.indexOf( + codeBlockStart, + codeBlockStartIndex + + codeBlockStart.length + + sanitizedBlock.length + + codeBlockEnd.length + ) + } + + return comment + } + + /** + * 清理响应字符串中的代码块 + * @param {string} comment - 评论字符串 + * @returns {string} - 返回清理后的评论字符串 + */ + const sanitizeResponse = (comment: string): string => { + comment = sanitizeCodeBlock(comment, "suggestion") + comment = sanitizeCodeBlock(comment, "diff") + return comment + } + const reviews: Review[] = [] + + // 清理响应字符串 + response = sanitizeResponse(response.trim()) + + const lines = response.split("\n") + const lineNumberRangeRegex = /(?:^|\s)(\d+)-(\d+):\s*$/ + const commentSeparator = "---" + + let currentStartLine: number | null = null + let currentEndLine: number | null = null + let currentComment = "" + + // 解析响应字符串中的每一行 + for (const line of lines) { + const lineNumberRangeMatch = line.match(lineNumberRangeRegex) + + if (lineNumberRangeMatch != null) { + storeReview() + currentStartLine = parseInt(lineNumberRangeMatch[1], 10) + currentEndLine = parseInt(lineNumberRangeMatch[2], 10) + currentComment = "" + continue + } + + if (line.trim() === commentSeparator) { + storeReview() + currentStartLine = null + currentEndLine = null + currentComment = "" + continue + } + + if (currentStartLine !== null && currentEndLine !== null) { + currentComment += `${line}\n` + } + } + + storeReview() + + return reviews +} + +const diffTools = { + splitDiff, + parseDiff, + getStartEndLine, + parseFileDiffs, + parseReview, +} + +export default diffTools diff --git a/controllers/manageMrEvent/utils/inputs.ts b/controllers/manageMrEvent/utils/inputs.ts new file mode 100644 index 0000000..5877537 --- /dev/null +++ b/controllers/manageMrEvent/utils/inputs.ts @@ -0,0 +1,128 @@ +/** + * 生成系统消息,包含知识截止日期和当前日期。 + * @returns {string} 返回包含系统消息的字符串。 + */ +const genSystemMessage = () => { + return ` +知识截止日期: 2023-12-01 +当前日期: ${new Date().toISOString().split("T")[0]} + +重要提示: 整个回复必须使用ISO代码为zh的语言 + ` +} + +/** + * Inputs类用于存储和处理各种输入数据。 + */ +export class Inputs { + systemMessage: string + title: string + description: string + rawSummary: string + shortSummary: string + filename: string + fileContent: string + fileDiff: string + patches: string + diff: string + commentChain: string + comment: string + + /** + * 构造函数,初始化Inputs类的实例。 + * @param {string} [systemMessage=""] - 系统消息。 + * @param {string} [title="no title provided"] - 标题。 + * @param {string} [description="no description provided"] - 描述。 + * @param {string} [rawSummary=""] - 原始摘要。 + * @param {string} [shortSummary=""] - 简短摘要。 + * @param {string} [filename=""] - 文件名。 + * @param {string} [fileContent="file contents cannot be provided"] - 文件内容。 + * @param {string} [fileDiff="file diff cannot be provided"] - 文件差异。 + * @param {string} [patches=""] - 补丁。 + * @param {string} [diff="no diff"] - 差异。 + * @param {string} [commentChain="no other comments on this patch"] - 评论链。 + * @param {string} [comment="no comment provided"] - 评论。 + */ + constructor( + systemMessage = "", + title = "no title provided", + description = "no description provided", + rawSummary = "", + shortSummary = "", + filename = "", + fileContent = "file contents cannot be provided", + fileDiff = "file diff cannot be provided", + patches = "", + diff = "no diff", + commentChain = "no other comments on this patch", + comment = "no comment provided" + ) { + this.systemMessage = systemMessage || genSystemMessage() + this.title = title + this.description = description + this.rawSummary = rawSummary + this.shortSummary = shortSummary + this.filename = filename + this.fileContent = fileContent + this.fileDiff = fileDiff + this.patches = patches + this.diff = diff + this.commentChain = commentChain + this.comment = comment + } + + /** + * 克隆当前Inputs实例。 + * @returns {Inputs} 返回当前对象的副本。 + */ + clone(): Inputs { + return new Inputs( + this.systemMessage, + this.title, + this.description, + this.rawSummary, + this.shortSummary, + this.filename, + this.fileContent, + this.fileDiff, + this.patches, + this.diff, + this.commentChain, + this.comment + ) + } + + /** + * 渲染内容,将占位符替换为实际值。 + * @param {string} content - 包含占位符的内容。 + * @returns {string} 返回替换后的内容。 + */ + render(content: string): string { + if (!content) { + return "" + } + + const replacements = { + $system_message: this.systemMessage, + $title: this.title, + $description: this.description, + $raw_summary: this.rawSummary, + $short_summary: this.shortSummary, + $filename: this.filename, + $file_content: this.fileContent, + $file_diff: this.fileDiff, + $patches: this.patches, + $diff: this.diff, + $comment_chain: this.commentChain, + $comment: this.comment, + } + + for (const [key, value] of Object.entries(replacements)) { + if (value) { + content = content.replace(key, value) + } + } + + return content + } +} diff --git a/controllers/manageMrEvent/utils/pathFilter.ts b/controllers/manageMrEvent/utils/pathFilter.ts new file mode 100644 index 0000000..196abcc --- /dev/null +++ b/controllers/manageMrEvent/utils/pathFilter.ts @@ -0,0 +1,60 @@ +import { minimatch } from "minimatch" + +export class PathFilter { + // rules数组包含一组规则,每个规则是一个元组,元组的第一个元素是规则字符串,第二个元素是布尔值,表示是否排除 + private readonly rules: Array<[string /* rule */, boolean /* exclude */]> + + /** + * 构造函数,接受一个字符串数组作为规则 + * @param rules - 包含路径匹配规则的字符串数组 + */ + constructor(rules: string[] | null = null) { + this.rules = [] + if (rules != null) { + for (const rule of rules) { + const trimmed = rule?.trim() // 去除规则字符串的前后空格 + if (trimmed) { + if (trimmed.startsWith("!")) { + // 如果规则以'!'开头,表示排除规则 + this.rules.push([trimmed.substring(1).trim(), true]) + } else { + // 否则表示包含规则 + this.rules.push([trimmed, false]) + } + } + } + } + } + + /** + * 检查给定路径是否符合规则 + * @param path - 需要检查的路径 + * @returns boolean - 如果路径符合规则返回true,否则返回false + */ + check(path: string): boolean { + if (this.rules.length === 0) { + return true // 如果没有规则,默认返回true + } + + let included = false // 标记路径是否被包含 + let excluded = false // 标记路径是否被排除 + let inclusionRuleExists = false // 标记是否存在包含规则 + + for (const [rule, exclude] of this.rules) { + if (minimatch(path, rule)) { + // 使用minimatch库检查路径是否匹配规则 + if (exclude) { + excluded = true // 如果匹配排除规则,设置excluded为true + } else { + included = true // 如果匹配包含规则,设置included为true + } + } + if (!exclude) { + inclusionRuleExists = true // 如果存在包含规则,设置inclusionRuleExists为true + } + } + + // 返回值逻辑:如果不存在包含规则或路径被包含且未被排除,则返回true + return (!inclusionRuleExists || included) && !excluded + } +} diff --git a/controllers/manageMrEvent/utils/prompts.ts b/controllers/manageMrEvent/utils/prompts.ts new file mode 100644 index 0000000..d37798e --- /dev/null +++ b/controllers/manageMrEvent/utils/prompts.ts @@ -0,0 +1,206 @@ +import { type Inputs } from "./inputs" + +export class Prompts { + // 总结单文件修改内容 + summarizeFileDiff = ` +$system_message + +## Gitlab 合并请求 标题 + +\`\`\` +$title +\`\`\` + +## 描述 + +\`\`\` +$description +\`\`\` + +## 差异 + +\`\`\`diff +$file_diff +\`\`\` + +## 说明 + +我希望你能简洁地总结这个差异,不超过100个字。 +如果适用,你的总结应包括对导出函数、全局数据结构和变量签名的更改的说明,以及任何可能影响代码外部接口或行为的更改。 +不要返回任何标题,直接回复差异总结。 +` + // 文件是否需要Review + triageFileDiff = `在总结下方,我还希望你根据以下标准将差异分类为\`NEEDS_REVIEW\`或\`APPROVED\`: + +- 如果差异涉及任何逻辑或功能的修改,即使它们看起来很小,也将其分类为\`NEEDS_REVIEW\`。这包括对控制结构、函数调用或变量赋值的更改,这些更改可能会影响代码的行为。 +- 如果差异仅包含不影响代码逻辑的非常小的更改,例如修正拼写错误、格式或为了清晰起见重命名变量、修改引入顺序,将其分类为\`APPROVED\`。 + +请彻底评估差异,并考虑更改行数、对整个系统的潜在影响以及引入新错误或安全漏洞的可能性。 +在有疑问时,总是倾向于谨慎并将差异分类为\`NEEDS_REVIEW\`。 + +你必须严格按照以下格式对差异进行分类: +[TRIAGE]: + +重要提示: +- 返回值不要包含\`\`\`。 +- 在你的总结中不要提到文件需要彻底审核或警告潜在问题。 +- 不要提供任何将差异分类为\`NEEDS_REVIEW\`或\`APPROVED\`的理由。 +- 在总结中不要提到这些更改会影响代码的逻辑或功能。你只能使用上述分类格式来指示。 +` + // 总结前缀 + summarizePrefix = `以下是你为文件生成的更改摘要: + \`\`\` + $raw_summary + \`\`\` + +` + // 总结 + summarize = `你的任务是提供一个简明的合并请求的更改摘要。此摘要将在代码审查时用作提示,必须非常清晰以便AI机器人理解。 + +说明: + +- 返回值不要包含\`\`\`。 +- 仅关注总结合并请求中的更改,并坚持事实。 +- 不要向机器人提供任何关于如何执行审查的说明。 +- 不要提到文件需要彻底审核或警告潜在问题。 +- 不要提到这些更改会影响代码的逻辑或功能。 +- 不要提到任何单独的文件名,在代码审查中这部分会另外提供。 +- 返回纯文本内容,叙述整体的修改,不得超过500字。 +` + // 审查文件差异 + reviewFileDiff = `# Gitlab 合并请求 +## 标题 + +\`$title\` + +## 描述 + +\`\`\` +$description +\`\`\` + +## 更改摘要 + +\`\`\` +$short_summary +\`\`\` + +## 重要说明 +你是一个出类拔萃的CodeReviewer,现在是时候展示你的技能了! + +输入:带有行号的新hunks和旧hunks(替换的代码)。hunks代表不完整的代码片段。 + +附加上下文:合并请求的标题、描述、摘要和评论链。 + +任务:使用提供的上下文审查新hunks中的实质性问题,并在必要时回复评论。 + +输出:在markdown中使用确切的行号范围进行审查评论。单行评论的起始和结束行号必须相同。必须使用以下示例响应格式: +使用带有相关语言标识符的围栏代码块(fenced code blocks)。 +不要用行号注释代码片段。 +正确格式化和缩进代码。 +不要使用\`suggestion\`代码块。 +对于问题修复: +- 使用\`diff\`代码块,用\`+\`或\`-\`标记更改。带有修复片段的评论的行号范围必须与新hunk中要替换的范围完全匹配。 +- 不要提供一般反馈、更改摘要、变更解释或对良好代码的赞扬。 +- 更多的关注于逻辑错误、边界情况和性能问题。 +- 忽略代码风格问题、引入顺序问题及输出打印等不影响代码功能的问题,除非它们导致错误。 +- 仅专注于根据给定的上下文提供具体、客观的见解,避免对系统潜在影响的广泛评论或质疑更改意图。 +- 如果一个区块反应了多个问题,请一起回复。 + +如果在行范围内没有发现问题,只在审查部分回复纯文本 \`LGTM!\` 。 +且如果返回\`LGTM!\`的行号连续,则可以合并为一个区块。 + +## 示例 + +### 示例更改 + +---new_hunk--- +\`\`\` + z = x / y + return z + +20: def add(x, y): +21: z = x + y +22: retrn z +23: +24: def multiply(x, y): +25: return x * y + +def subtract(x, y): + z = x - y +\`\`\` + +---old_hunk--- +\`\`\` + z = x / y + return z + +def add(x, y): + return x + y + +def subtract(x, y): + z = x - y +\`\`\` + +---comment_chains--- +\`\`\` +请审查此更改。 +\`\`\` + +---end_change_section--- + +### 示例响应 + +22-22: +add函数中有一个语法错误。 +\`\`\`diff +- retrn z ++ return z +\`\`\` +--- +24-25: +LGTM! +--- + +## 你要审查的\`$filename\`的更改 + +$patches + +## 审查结果 +` + + constructor() {} + + /** + * 文件差异摘要。 + * @param {Inputs} inputs - 输入对象。 + * @param {boolean} needReview - 是否需要审查。 + * @returns {string} 渲染后的字符串。 + */ + renderSummarizeFileDiff(inputs: Inputs, needReview: boolean): string { + let prompt = this.summarizeFileDiff + if (needReview) { + prompt += this.triageFileDiff + } + return inputs.render(prompt) + } + + /** + * 摘要。 + * @param {Inputs} inputs - 输入对象。 + * @returns {string} 渲染后的字符串。 + */ + renderSummarize(inputs: Inputs): string { + const prompt = this.summarizePrefix + this.summarize + return inputs.render(prompt) + } + + /** + * 文件差异审查。 + * @param {Inputs} inputs - 输入对象。 + * @returns {string} 渲染后的字符串。 + */ + renderReviewFileDiff(inputs: Inputs): string { + return inputs.render(this.reviewFileDiff) + } +} diff --git a/index.ts b/index.ts index cd6ee79..679e2ac 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,6 @@ +import { v4 as uuid } from "uuid" + +import loggerIns from "./log" import { manageCIMonitorReq } from "./routes/ci" import { manageGitlabEventReq } from "./routes/event" import initSchedule from "./schedule" @@ -12,14 +15,16 @@ const PREFIX = "/gitlab_monitor" const server = Bun.serve({ async fetch(req) { try { + // 添加请求ID + const logger = loggerIns.child({ requestId: uuid() }) // 路由处理 const { exactCheck, fullCheck } = makeCheckPathTool(req.url, PREFIX) // 非根路由打印 - if (!fullCheck("/")) console.log("🚀 ~ serve ~ req.url", req.url) + if (!fullCheck("/")) logger.info(`${req.method} ${req.url}`) // CI 监控 if (exactCheck("/ci")) return manageCIMonitorReq(req) // Gitlab 事件 - if (exactCheck("/event")) return manageGitlabEventReq(req) + if (exactCheck("/event")) return manageGitlabEventReq(req, logger) // 其他 return netTool.ok("hello, there is gitlab monitor, glade to serve you!") } catch (error: any) { @@ -31,4 +36,4 @@ const server = Bun.serve({ port: 3000, }) -console.log(`Listening on ${server.hostname}:${server.port}`) +loggerIns.info(`Listening on ${server.hostname}:${server.port}`) diff --git a/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json b/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json new file mode 100644 index 0000000..2d79bc6 --- /dev/null +++ b/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json @@ -0,0 +1,15 @@ +{ + "keep": { + "days": true, + "amount": 14 + }, + "auditLog": "log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json", + "files": [ + { + "date": 1723259039565, + "name": "log/application-2024-08-10.log", + "hash": "fdd4f67d11fe79d1aedf242b8d2e1894fbd6a4679331098818c2324ec81678cd" + } + ], + "hashType": "sha256" +} \ No newline at end of file diff --git a/log/index.ts b/log/index.ts new file mode 100644 index 0000000..107b7b8 --- /dev/null +++ b/log/index.ts @@ -0,0 +1,38 @@ +import "winston-daily-rotate-file" + +import winston, { format } from "winston" + +const isProd = process.env.NODE_ENV === "production" + +const dailyRotateFileTransport = new winston.transports.DailyRotateFile({ + filename: "./log/application-%DATE%.log", + datePattern: "YYYY-MM-DD", + zippedArchive: true, + maxSize: "20m", + maxFiles: "14d", +}) + +const transports: any[] = [new winston.transports.Console()] +if (isProd) { + transports.push(dailyRotateFileTransport) +} + +const loggerIns = winston.createLogger({ + level: "info", + format: format.combine( + format.colorize({ + level: !isProd, + }), // 开发环境下输出彩色日志 + format.simple(), // 简单文本格式化 + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf(({ level, message, timestamp, requestId }) => { + const singleLineMessage = isProd + ? message.replace(/\n/g, " ") // 将换行符替换为空格 + : message + return `${timestamp} [${level}] ${requestId ? `[RequestId: ${requestId}]` : ""}: ${singleLineMessage}` + }) + ), + transports, +}) + +export default loggerIns diff --git a/package.json b/package.json index b727c31..1765f21 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@eslint/js": "^9.7.0", + "@gitbeaker/rest": "^40.1.2", "@types/lodash": "^4.14.202", "@types/node-schedule": "^2.1.6", "bun-types": "latest", @@ -35,10 +36,16 @@ "typescript": "^5.0.0" }, "dependencies": { + "@dqbd/tiktoken": "^1.0.15", + "@langchain/openai": "^0.2.6", "lodash": "^4.17.21", + "minimatch": "^10.0.1", "moment": "^2.30.1", "node-schedule": "^2.1.1", "p-limit": "^6.1.0", - "pocketbase": "^0.21.1" + "pocketbase": "^0.21.1", + "uuid": "^10.0.0", + "winston": "^3.14.1", + "winston-daily-rotate-file": "^5.0.0" } } \ No newline at end of file diff --git a/routes/event/index.ts b/routes/event/index.ts index 6344e05..d95e2c4 100644 --- a/routes/event/index.ts +++ b/routes/event/index.ts @@ -1,3 +1,6 @@ +import { Logger } from "winston" + +import manageMrEvent from "../../controllers/manageMrEvent" import managePipelineEvent from "../../controllers/managePipelineEvent" import netTool from "../../service/netTool" import { Gitlab } from "../../types/gitlab" @@ -7,15 +10,26 @@ import { Gitlab } from "../../types/gitlab" * @param {Request} req - 请求对象。 * @returns {Promise} - 响应对象。 */ -export const manageGitlabEventReq = async (req: Request): Promise => { +export const manageGitlabEventReq = async ( + req: Request, + logger: Logger +): Promise => { const apiKey = req.headers.get("x-gitlab-token") + logger.info(`x-gitlab-token: ${apiKey}`) if (!apiKey) return netTool.badRequest("x-gitlab-token is required!") const eventType = req.headers.get("x-gitlab-event") - // 只处理流水线钩子 + logger.info(`x-gitlab-event: ${eventType}`) + // 处理流水线钩子 if (eventType === "Pipeline Hook") { const body = (await req.json()) as Gitlab.PipelineEvent const params = new URLSearchParams(req.url.split("?")[1]) return managePipelineEvent.manageRawEvent(body, apiKey, params) } + // 处理MR钩子 + if (eventType === "Merge Request Hook") { + const body = (await req.json()) as Gitlab.MergeRequestEvent + logger.debug(`body: ${JSON.stringify(body)}`) + return manageMrEvent.manageRawEvent(body, logger) + } return netTool.ok() } diff --git a/script/mr/changes.json b/script/mr/changes.json new file mode 100644 index 0000000..a79136f --- /dev/null +++ b/script/mr/changes.json @@ -0,0 +1,1388 @@ +{ + "id": 2353231, + "iid": 484, + "project_id": 139032, + "title": "eslint: 代码格式化", + "description": "@jiangtong @wujiali5", + "state": "merged", + "created_at": "2024-08-09T11:35:35.066+08:00", + "updated_at": "2024-08-09T15:40:53.840+08:00", + "merged_by": { + "id": 18608, + "username": "jiangtong", + "name": "姜通", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/18608/avatar.png", + "web_url": "https://git.n.xiaomi.com/jiangtong" + }, + "merged_at": "2024-08-09T15:40:53.860+08:00", + "closed_by": null, + "closed_at": null, + "target_branch": "preview", + "source_branch": "feat/wt-lint", + "user_notes_count": 0, + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 30382, + "username": "wuting7", + "name": "吴婷", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30382/avatar.png", + "web_url": "https://git.n.xiaomi.com/wuting7" + }, + "assignees": [], + "assignee": null, + "reviewers": [], + "source_project_id": 139032, + "target_project_id": 139032, + "labels": [], + "draft": false, + "work_in_progress": false, + "milestone": null, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "f73a20e843d8cd35caa83f05e6187a9f9c6fd95d", + "merge_commit_sha": "a270a2e160683cad99ce2d769b314ddca9af6e58", + "squash_commit_sha": null, + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!484", + "references": { + "short": "!484", + "relative": "!484", + "full": "cloudml-visuals/fe/cloud-ml-fe!484" + }, + "web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/cloud-ml-fe/-/merge_requests/484", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "task_completion_status": { + "count": 0, + "completed_count": 0 + }, + "has_conflicts": false, + "blocking_discussions_resolved": true, + "subscribed": false, + "changes_count": "125", + "latest_build_started_at": "2024-08-09T15:35:12.623+08:00", + "latest_build_finished_at": "2024-08-09T15:37:31.604+08:00", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 8949606, + "project_id": 139032, + "sha": "f73a20e843d8cd35caa83f05e6187a9f9c6fd95d", + "ref": "refs/merge-requests/484/head", + "status": "success", + "source": "merge_request_event", + "created_at": "2024-08-09T15:34:50.429+08:00", + "updated_at": "2024-08-09T15:37:31.611+08:00", + "web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/cloud-ml-fe/-/pipelines/8949606" + }, + "head_pipeline": { + "id": 8949606, + "project_id": 139032, + "sha": "f73a20e843d8cd35caa83f05e6187a9f9c6fd95d", + "ref": "refs/merge-requests/484/head", + "status": "success", + "source": "merge_request_event", + "created_at": "2024-08-09T15:34:50.429+08:00", + "updated_at": "2024-08-09T15:37:31.611+08:00", + "web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/cloud-ml-fe/-/pipelines/8949606", + "before_sha": "0000000000000000000000000000000000000000", + "tag": false, + "yaml_errors": null, + "user": { + "id": 30382, + "username": "wuting7", + "name": "吴婷", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30382/avatar.png", + "web_url": "https://git.n.xiaomi.com/wuting7" + }, + "started_at": "2024-08-09T15:35:12.623+08:00", + "finished_at": "2024-08-09T15:37:31.604+08:00", + "committed_at": null, + "duration": 139, + "queued_duration": 22, + "coverage": null, + "detailed_status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/cloudml-visuals/fe/cloud-ml-fe/-/pipelines/8949606", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + } + }, + "diff_refs": { + "base_sha": "cb3989df09db0e74fd14838c078fe30558673b51", + "head_sha": "f73a20e843d8cd35caa83f05e6187a9f9c6fd95d", + "start_sha": "cb3989df09db0e74fd14838c078fe30558673b51" + }, + "merge_error": null, + "user": { + "can_merge": true + }, + "changes": [ + { + "old_path": "src/pages/DataSet/DataList/components/Header/index.tsx", + "new_path": "src/pages/DataSet/DataList/components/Header/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -3,6 +3,7 @@ import { history } from '@umijs/max';\n import { Affix, Button, Typography } from 'antd';\n import classnames from 'classnames';\n import React, { memo, useCallback } from 'react';\n+\n import style from './index.less';\n function Header({\n showBack = true,\n" + }, + { + "old_path": "src/pages/DataSet/DataList/components/CreateModal.tsx", + "new_path": "src/pages/DataSet/DataList/components/CreateModal.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,10 @@\n /* eslint-disable no-unused-expressions */\n /* eslint-disable react/require-default-props */\n+import { QuestionCircleOutlined } from '@ant-design/icons';\n+import { useModel } from '@umijs/max';\n+import { Button, Form, Input, message, Radio, Select, Space, Spin, Tooltip } from 'antd';\n+import { useEffect, useState } from 'react';\n+\n import Drawer from '@/components/Drawer';\n import {\n data_property,\n@@ -11,10 +16,6 @@ import {\n StarFS,\n } from '@/pages/AssetManagement/Storage/constants';\n import { queryCreateDataSource, queryCreateNas, queryCreateStarfs } from '@/services/storage';\n-import { QuestionCircleOutlined } from '@ant-design/icons';\n-import { useModel } from '@umijs/max';\n-import { Button, Form, Input, message, Radio, Select, Space, Spin, Tooltip } from 'antd';\n-import { useEffect, useState } from 'react';\n export default ({\n storages,\n open,\n" + }, + { + "old_path": "src/pages/DataSet/DataList/hooks/useSecret.ts", + "new_path": "src/pages/DataSet/DataList/hooks/useSecret.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n-import { queryStarfsList } from '@/services/storage';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import { queryStarfsList } from '@/services/storage';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [secret, setSecret] = useState([]);\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Create/CreateFds.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Create/CreateFds.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,11 @@\n+import { history, useModel } from '@umijs/max';\n+import { Form, Input, message,Radio, Select } from 'antd';\n+import { useEffect } from 'react';\n+\n import reminder from '@/components/reminder';\n import { fdsOptions } from '@/pages/DataSet/DataList/constants';\n import { validDataSetName } from '@/pages/DataSet/DataList/constants/utils';\n import { queryCreateDataSet } from '@/services/dataList';\n-import { history, useModel } from '@umijs/max';\n-import { Form, Input, Radio, Select, message } from 'antd';\n-import { useEffect } from 'react';\n const CreateFds = ({ onRef }: { onRef: (o: {}) => void }) => {\n const { TextArea } = Input;\n const [form] = Form.useForm();\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Create/CreateLocal.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Create/CreateLocal.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,12 @@\n /* eslint-disable no-param-reassign */\n+import { history, useModel } from '@umijs/max';\n+import { Button, Form, Input, message, notification,Radio, Select, Upload } from 'antd';\n+import { useEffect } from 'react';\n+\n import reminder from '@/components/reminder';\n import { layout } from '@/pages/DataSet/DataList/constants';\n import { validDataSetName } from '@/pages/DataSet/DataList/constants/utils';\n import { queryCreateDataSet, queryUploadDataSet } from '@/services/dataList';\n-import { history, useModel } from '@umijs/max';\n-import { Button, Form, Input, Radio, Select, Upload, message, notification } from 'antd';\n-import { useEffect } from 'react';\n const CreateLocal = ({\n onRef,\n formItemLayout = layout,\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Create/CreateNas.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Create/CreateNas.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,11 @@\n+import { history, useModel } from '@umijs/max';\n+import { Form, Input, message,Radio, Select } from 'antd';\n+import { useEffect, useState } from 'react';\n+\n import reminder from '@/components/reminder';\n import { validDataSetName, validPathStart } from '@/pages/DataSet/DataList/constants/utils';\n import { queryCreateDataSet } from '@/services/dataList';\n import { queryNasList } from '@/services/storage';\n-import { history, useModel } from '@umijs/max';\n-import { Form, Input, Radio, Select, message } from 'antd';\n-import { useEffect, useState } from 'react';\n const CreateNas = ({ onRef }: { onRef: (o: {}) => void }) => {\n const { TextArea } = Input;\n const [secretOptions, setSecretOptions] = useState([]);\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Create/CreateStarFS.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Create/CreateStarFS.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,12 +1,14 @@\n+import { history, useModel } from '@umijs/max';\n+import { Button, Form, Input, message, Radio, Select } from 'antd';\n+import { useEffect, useState } from 'react';\n+\n import reminder from '@/components/reminder';\n import CreateModal from '@/pages/AssetManagement/Storage/components/CreateModal';\n import { StarFSLabel } from '@/pages/AssetManagement/Storage/constants';\n import { StarFS } from '@/pages/DataSet/DataList/constants';\n import useSecret from '@/pages/DataSet/DataList/hooks/useSecret';\n import { queryCreateDataSet } from '@/services/dataList';\n-import { history, useModel } from '@umijs/max';\n-import { Button, Form, Input, message, Radio, Select } from 'antd';\n-import { useEffect, useState } from 'react';\n+\n import { validDataSetName, validPathStart } from '../../constants/utils';\n \n const CreateStarFS = ({ onRef }: { onRef: (o: {}) => void }) => {\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Create/index.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,8 @@\n+import { history } from '@umijs/max';\n+import { Button, Card, Space, Spin, Tabs } from 'antd';\n+import classnames from 'classnames';\n+import React, { useMemo, useRef, useState } from 'react';\n+\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import useTrack from '@/hooks/useTrack';\n import useCreateTabs from '@/pages/DataSet/DataList/hooks/useCreateTabs';\n@@ -5,10 +10,7 @@ import CreateFds from '@/pages/DataSet/DataList/pages/Create/CreateFds';\n import CreateLocal from '@/pages/DataSet/DataList/pages/Create/CreateLocal';\n import CreateNas from '@/pages/DataSet/DataList/pages/Create/CreateNas';\n import CreateStarFS from '@/pages/DataSet/DataList/pages/Create/CreateStarFS';\n-import { history } from '@umijs/max';\n-import { Button, Card, Space, Spin, Tabs } from 'antd';\n-import classnames from 'classnames';\n-import React, { useMemo, useRef, useState } from 'react';\n+\n import styles from '../../assets/styles/index.less';\n import { TrackEnum } from '../../constants/track';\n \n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Manage/create/index.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Manage/create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n-import reminder from '@/components/reminder';\n-import { queryChangeAccess, queryCreateAccess } from '@/services/dataList';\n import { useModel } from '@umijs/max';\n import { Form, Input, message, Modal, Select } from 'antd';\n import { cloneDeep } from 'lodash';\n import { useEffect, useState } from 'react';\n+\n+import reminder from '@/components/reminder';\n+import { queryChangeAccess, queryCreateAccess } from '@/services/dataList';\n+\n import { accessFilter, personAccessOptions, teamAccessOptions } from '../../../constants';\n const CreateAccess = ({\n open,\n" + }, + { + "old_path": "src/pages/DataSet/DataList/pages/Manage/index.tsx", + "new_path": "src/pages/DataSet/DataList/pages/Manage/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,4 @@\n /* eslint-disable prefer-destructuring */\n-import ContentLayout from '@/components/ContentLayout';\n-import reminder from '@/components/reminder';\n-import styles from '@/pages/DataSet/DataList/assets/styles/index.less';\n-import { queryAccessList, queryDataList, queryDeleteAccess } from '@/services/dataList';\n import { useLocation, useModel } from '@umijs/max';\n import { useAntdTable } from 'ahooks';\n import {\n@@ -24,6 +20,12 @@ import { ColumnsType } from 'antd/es/table';\n import classnames from 'classnames';\n import dayjs from 'dayjs';\n import { useEffect, useRef, useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import reminder from '@/components/reminder';\n+import styles from '@/pages/DataSet/DataList/assets/styles/index.less';\n+import { queryAccessList, queryDataList, queryDeleteAccess } from '@/services/dataList';\n+\n import CreateAccess from './create';\n \n const AccessManage = () => {\n" + }, + { + "old_path": "src/pages/DataSet/DataList/index.tsx", + "new_path": "src/pages/DataSet/DataList/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,3 @@\n-import ContentLayout from '@/components/ContentLayout';\n-import reminder from '@/components/reminder';\n-import useTrack from '@/hooks/useTrack';\n-import styles from '@/pages/DataSet/DataList/assets/styles/index.less';\n-import ExpandDetail from '@/pages/DataSet/DataList/components/ExpandDetail';\n-import { createTypeFilter, statusList } from '@/pages/DataSet/DataList/constants';\n-import { queryDataList, queryDeleteDataList } from '@/services/dataList';\n import { ReloadOutlined } from '@ant-design/icons';\n import { history, useModel } from '@umijs/max';\n import { useAntdTable } from 'ahooks';\n@@ -15,19 +8,28 @@ import {\n Checkbox,\n Form,\n Input,\n+ message,\n PaginationProps,\n Popconfirm,\n Space,\n Table,\n Tooltip,\n Typography,\n- message,\n } from 'antd';\n import { PresetStatusColorType } from 'antd/es/_util/colors';\n import { ColumnsType } from 'antd/es/table';\n import classnames from 'classnames';\n import dayjs from 'dayjs';\n import { useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import reminder from '@/components/reminder';\n+import useTrack from '@/hooks/useTrack';\n+import styles from '@/pages/DataSet/DataList/assets/styles/index.less';\n+import ExpandDetail from '@/pages/DataSet/DataList/components/ExpandDetail';\n+import { createTypeFilter, statusList } from '@/pages/DataSet/DataList/constants';\n+import { queryDataList, queryDeleteDataList } from '@/services/dataList';\n+\n import { TrackEnum } from './constants/track';\n \n export default () => {\n" + }, + { + "old_path": "src/pages/Develop/Config/components/CreateInfoForm/Resource/index.tsx", + "new_path": "src/pages/Develop/Config/components/CreateInfoForm/Resource/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,9 @@\n+import { Form } from 'antd';\n+import { memo, useMemo } from 'react';\n+\n import SingleNode from '@/components/ResourceInstance/SingleNode';\n import useTeamResource from '@/hooks/useTeamResource';\n import type { ResourceInstanceList } from '@/types/global';\n-import { Form } from 'antd';\n-import { memo, useMemo } from 'react';\n \n export default memo(\n ({\n" + }, + { + "old_path": "src/pages/Develop/Config/components/CreateInfoForm/index.tsx", + "new_path": "src/pages/Develop/Config/components/CreateInfoForm/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,21 +1,3 @@\n-import { isResourceInstance } from '@/cluster';\n-import SelectDataset from '@/components/SelectDataset';\n-import useInitialState from '@/hooks/useInitialState';\n-import useLink from '@/hooks/useLink';\n-import { NAS, StarFS } from '@/pages/AssetManagement/Storage/constants';\n-import { isNoPrivate } from '@/pages/Develop/cluster';\n-import { getStorageMount } from '@/pages/Develop/Config/util';\n-import { Gi } from '@/pages/ModelService/constants';\n-import { Option } from '@/pages/Tensorboard/typings';\n-import {\n- queryCreateDebugDev,\n- queryCreateDevService,\n- queryDevDetail,\n- queryEditDevService,\n-} from '@/services/develop';\n-import { querySecretImages } from '@/services/tensorboard';\n-import { isJSONString } from '@/utils/utils';\n-import { validPathStart } from '@/utils/validate';\n import { QuestionCircleOutlined } from '@ant-design/icons';\n import { useParams } from '@umijs/max';\n import {\n@@ -34,6 +16,26 @@ import {\n } from 'antd';\n import _ from 'lodash';\n import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';\n+\n+import { isResourceInstance } from '@/cluster';\n+import SelectDataset from '@/components/SelectDataset';\n+import useInitialState from '@/hooks/useInitialState';\n+import useLink from '@/hooks/useLink';\n+import { NAS, StarFS } from '@/pages/AssetManagement/Storage/constants';\n+import { isNoPrivate } from '@/pages/Develop/cluster';\n+import { getStorageMount } from '@/pages/Develop/Config/util';\n+import { Gi } from '@/pages/ModelService/constants';\n+import { Option } from '@/pages/Tensorboard/typings';\n+import {\n+ queryCreateDebugDev,\n+ queryCreateDevService,\n+ queryDevDetail,\n+ queryEditDevService,\n+} from '@/services/develop';\n+import { querySecretImages } from '@/services/tensorboard';\n+import { isJSONString } from '@/utils/utils';\n+import { validPathStart } from '@/utils/validate';\n+\n import {\n c5,\n c5Preview,\n" + }, + { + "old_path": "src/pages/Develop/Config/components/EventList/index.tsx", + "new_path": "src/pages/Develop/Config/components/EventList/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import EventInfo, { EventType } from '@/components/EventInfo';\n-import { queryDevEvent } from '@/services/develop';\n import { useModel, useParams } from '@umijs/max';\n import { useCallback, useEffect, useState } from 'react';\n+\n+import EventInfo, { EventType } from '@/components/EventInfo';\n+import { queryDevEvent } from '@/services/develop';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const {\n" + }, + { + "old_path": "src/pages/Develop/Config/components/LoginModal/index.tsx", + "new_path": "src/pages/Develop/Config/components/LoginModal/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,9 @@\n-import { queryDevDetail } from '@/services/develop';\n import { useModel } from '@umijs/max';\n import { Alert, Button, Divider, Modal, Space, Spin, Typography } from 'antd';\n import { useEffect, useState } from 'react';\n+\n+import { queryDevDetail } from '@/services/develop';\n+\n import styles from '../../assets/styles/index.less';\n export default ({\n item,\n" + }, + { + "old_path": "src/pages/Develop/Config/components/Logs/index.tsx", + "new_path": "src/pages/Develop/Config/components/Logs/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,11 @@\n+import { useParams } from '@umijs/max';\n+import { useCallback } from 'react';\n+\n import Log from '@/components/LogInfo/Log';\n import { LOGS_LINES } from '@/constants/global';\n import useInitialState from '@/hooks/useInitialState';\n import { queryDevlLog } from '@/services/develop';\n import downloadFile from '@/utils/downloadFile';\n-import { useParams } from '@umijs/max';\n-import { useCallback } from 'react';\n export default () => {\n const {\n dev_name,\n" + }, + { + "old_path": "src/pages/Develop/Config/components/SaveEnvModal/index.tsx", + "new_path": "src/pages/Develop/Config/components/SaveEnvModal/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n /* eslint-disable react/react-in-jsx-scope */\n-import { querySaveEnvService } from '@/services/develop';\n import { useModel } from '@umijs/max';\n import { Form, Input, message, Modal } from 'antd';\n import { useEffect } from 'react';\n+\n+import { querySaveEnvService } from '@/services/develop';\n+\n import { ICreateHeder } from '../../typings';\n export default ({\n visible,\n" + }, + { + "old_path": "src/pages/Develop/Config/components/StorageMode/index.tsx", + "new_path": "src/pages/Develop/Config/components/StorageMode/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,13 @@\n import { QuestionCircleOutlined } from '@ant-design/icons';\n+import { useModel } from '@umijs/max';\n import { Button, Form, Input, Select, Space, Tooltip } from 'antd';\n import { useEffect, useMemo, useState } from 'react';\n+\n // @ts-ignore\n import { FDS, NAS, PAI, StarFS } from '@/pages/AssetManagement/Storage/constants';\n import { queryTaskList } from '@/services/paidlc';\n import { queryFdsList, queryNasList, queryStarfsList } from '@/services/storage';\n-import { useModel } from '@umijs/max';\n+\n import CreateModal from '../../../../AssetManagement/Storage/components/CreateModal';\n import styles from '../../assets/styles/index.less';\n import useStorageTypes from '../../hooks/useStorageTypes';\n" + }, + { + "old_path": "src/pages/Develop/Config/hooks/useDevActiveTab.ts", + "new_path": "src/pages/Develop/Config/hooks/useDevActiveTab.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,4 +1,5 @@\n import { useModel } from '@umijs/max';\n+\n import { c5, c5Preview, c5Staging, tj5 } from '../../../../../config/cluster';\n export default () => {\n const { initialState } = useModel('@@initialState');\n" + }, + { + "old_path": "src/pages/Develop/Config/hooks/useEnvImage.ts", + "new_path": "src/pages/Develop/Config/hooks/useEnvImage.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n+import { useEffect, useState } from 'react';\n+\n import useInitialState from '@/hooks/useInitialState';\n import { queryEnvList } from '@/services/develop';\n-import { useEffect, useState } from 'react';\n \n export default () => {\n const { cluster, teamId, userName } = useInitialState();\n" + }, + { + "old_path": "src/pages/Develop/Config/hooks/useGpus.ts", + "new_path": "src/pages/Develop/Config/hooks/useGpus.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n import { useModel } from '@umijs/max';\n import { useEffect, useMemo, useState } from 'react';\n+\n /* eslint-disable no-restricted-syntax */\n import { RESOURCE_CLUSTERS } from '@/cluster';\n import { resourceQuery } from '@/services/resource';\n import { queryRestQuota } from '@/services/space';\n+\n import {\n c5,\n c5Preview,\n" + }, + { + "old_path": "src/pages/Develop/Config/hooks/useImage.ts", + "new_path": "src/pages/Develop/Config/hooks/useImage.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n-import { queryImageList } from '@/services/develop';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import { queryImageList } from '@/services/develop';\n export default (activeKey: string) => {\n const { initialState } = useModel('@@initialState');\n const [images, setImages] = useState([]);\n" + }, + { + "old_path": "src/pages/Develop/Config/hooks/useStorageTypes.ts", + "new_path": "src/pages/Develop/Config/hooks/useStorageTypes.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,8 @@\n-import { RESOURCE_CLUSTERS } from '@/cluster';\n import { useModel } from '@umijs/max';\n import { useMemo } from 'react';\n+\n+import { RESOURCE_CLUSTERS } from '@/cluster';\n+\n import {\n c5,\n c5Preview,\n" + }, + { + "old_path": "src/pages/Develop/Config/pages/Create.tsx", + "new_path": "src/pages/Develop/Config/pages/Create.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,12 +1,14 @@\n+import { history, useModel } from '@umijs/max';\n+import { useAsyncEffect } from 'ahooks';\n+import { Button, Space, Tabs } from 'antd';\n+import { useEffect, useMemo, useRef, useState } from 'react';\n+\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import useLink from '@/hooks/useLink';\n import useTrack from '@/hooks/useTrack';\n import useDevActiveTab from '@/pages/Develop/Config/hooks/useDevActiveTab';\n import { queryTrainWhiteList } from '@/services/develop';\n-import { history, useModel } from '@umijs/max';\n-import { useAsyncEffect } from 'ahooks';\n-import { Button, Space, Tabs } from 'antd';\n-import { useEffect, useMemo, useRef, useState } from 'react';\n+\n import { TrackEnum } from '../../track';\n import CreateInfoForm from '../components/CreateInfoForm';\n import { createTabs, enableDevObj } from '../constants';\n" + }, + { + "old_path": "src/pages/Develop/Config/pages/Detail.tsx", + "new_path": "src/pages/Develop/Config/pages/Detail.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,8 @@\n+import { history, useModel, useParams } from '@umijs/max';\n+import { Button, Card, message, Modal, Popconfirm, Space, Spin, Tabs, Typography } from 'antd';\n+import _ from 'lodash';\n+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n+\n import { isResourceInstance } from '@/cluster';\n import Collapse from '@/components/Collapse';\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n@@ -7,10 +12,7 @@ import { INSTANCE_TYPE_MAP, RESOURCE_MONITORING_LINK } from '@/constants/global'\n import useLink from '@/hooks/useLink';\n import useTrack from '@/hooks/useTrack';\n import { queryDeleteDev, queryDevDetail, queryRestartDev, queryStopDev } from '@/services/develop';\n-import { history, useModel, useParams } from '@umijs/max';\n-import { Button, Card, message, Modal, Popconfirm, Space, Spin, Tabs, Typography } from 'antd';\n-import _ from 'lodash';\n-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n+\n import { TrackEnum } from '../../track';\n import EventList from '../components/EventList';\n import LoginModal from '../components/LoginModal';\n" + }, + { + "old_path": "src/pages/Develop/Config/index.tsx", + "new_path": "src/pages/Develop/Config/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,3 @@\n-import { IInitialState } from '@/app';\n-import TableTooltipItem from '@/components/TableTooltipItem';\n-import { pagination, RESOURCE_MONITORING_LINK } from '@/constants/global';\n-import useSearchStorage from '@/hooks/useSearchStorage';\n-import useTrack from '@/hooks/useTrack';\n-import { queryDeleteDev, queryDevList, queryRestartDev, queryStopDev } from '@/services/develop';\n-import { queryGroupImpl } from '@/services/global';\n import { EllipsisOutlined, ReloadOutlined } from '@ant-design/icons';\n import { history, useModel } from '@umijs/max';\n import { useAntdTable } from 'ahooks';\n@@ -23,6 +16,15 @@ import {\n Table,\n } from 'antd';\n import { useEffect, useRef, useState } from 'react';\n+\n+import { IInitialState } from '@/app';\n+import TableTooltipItem from '@/components/TableTooltipItem';\n+import { pagination, RESOURCE_MONITORING_LINK } from '@/constants/global';\n+import useSearchStorage from '@/hooks/useSearchStorage';\n+import useTrack from '@/hooks/useTrack';\n+import { queryDeleteDev, queryDevList, queryRestartDev, queryStopDev } from '@/services/develop';\n+import { queryGroupImpl } from '@/services/global';\n+\n import { TrackEnum } from '../track';\n import LoginModal from './components/LoginModal';\n import SaveEnvModal from './components/SaveEnvModal';\n" + }, + { + "old_path": "src/pages/Develop/Image/constants/index.tsx", + "new_path": "src/pages/Develop/Image/constants/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n /* eslint-disable react/react-in-jsx-scope */\n-import { getPopupContainer } from '@/utils/utils';\n import { InfoCircleOutlined } from '@ant-design/icons';\n import { Tooltip, Typography } from 'antd';\n import dayjs from 'dayjs';\n+\n+import { getPopupContainer } from '@/utils/utils';\n+\n import styles from '../../index.less';\n \n const { Paragraph } = Typography;\n" + }, + { + "old_path": "src/pages/Develop/Image/index.tsx", + "new_path": "src/pages/Develop/Image/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,13 @@\n-import TableTooltipItem from '@/components/TableTooltipItem';\n-import { pagination } from '@/constants/global';\n-import useTrack from '@/hooks/useTrack';\n-import { deleteEnv, queryEnvList } from '@/services/develop';\n import { ReloadOutlined } from '@ant-design/icons';\n import { history, useModel } from '@umijs/max';\n import { useAntdTable } from 'ahooks';\n import { Button, Card, Checkbox, Form, Input, message, Popconfirm, Table } from 'antd';\n+\n+import TableTooltipItem from '@/components/TableTooltipItem';\n+import { pagination } from '@/constants/global';\n+import useTrack from '@/hooks/useTrack';\n+import { deleteEnv, queryEnvList } from '@/services/develop';\n+\n import styles from '../index.less';\n import { TrackEnum } from '../track';\n import { columns } from './constants';\n" + }, + { + "old_path": "src/pages/Develop/index.tsx", + "new_path": "src/pages/Develop/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n+import { Button, Tabs } from 'antd';\n+import { useCallback, useMemo, useState } from 'react';\n+\n import { isResourceInstance } from '@/cluster';\n import ContentLayout from '@/components/ContentLayout';\n import useInitialState from '@/hooks/useInitialState';\n import useTrack from '@/hooks/useTrack';\n-import { Button, Tabs } from 'antd';\n-import { useCallback, useMemo, useState } from 'react';\n+\n import Config from './Config';\n import Image from './Image';\n import styles from './index.less';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/components/Actions/index.tsx", + "new_path": "src/pages/KnowledgeBase/components/Actions/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,13 @@\n-import reminder from '@/components/reminder';\n-import useInitialState from '@/hooks/useInitialState';\n-import useTrack from '@/hooks/useTrack';\n import { history } from '@umijs/max';\n-import { Button, Popconfirm, Space, message } from 'antd';\n+import { Button, message,Popconfirm, Space } from 'antd';\n import { SpaceSize } from 'antd/es/space';\n import { ButtonSize, ButtonType } from 'antd/lib/button';\n import { memo, useCallback } from 'react';\n+\n+import reminder from '@/components/reminder';\n+import useInitialState from '@/hooks/useInitialState';\n+import useTrack from '@/hooks/useTrack';\n+\n import { deleteKnowledgeBase } from '../../service';\n import { TrackEnum } from '../../track';\n import style from './index.less';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/components/Card/index.tsx", + "new_path": "src/pages/KnowledgeBase/components/Card/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n import { Card } from 'antd';\n import { CardProps } from 'antd/lib/card';\n import React, { memo } from 'react';\n+\n import style from './index.module.less';\n \n function CardCom({\n" + }, + { + "old_path": "src/pages/KnowledgeBase/components/Header/index.tsx", + "new_path": "src/pages/KnowledgeBase/components/Header/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -3,6 +3,7 @@ import { history } from '@umijs/max';\n import { Affix, Button, Typography } from 'antd';\n import classnames from 'classnames';\n import React, { memo, useCallback } from 'react';\n+\n import style from './index.module.less';\n function Header({\n showBack = true,\n" + }, + { + "old_path": "src/pages/KnowledgeBase/hooks/usePlayground.ts", + "new_path": "src/pages/KnowledgeBase/hooks/usePlayground.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,9 @@\n-import useInitialState from '@/hooks/useInitialState';\n-import { ragQueryTrack } from '@/utils/track';\n import { useParams } from '@umijs/max';\n import { useCallback, useState } from 'react';\n+\n+import useInitialState from '@/hooks/useInitialState';\n+import { ragQueryTrack } from '@/utils/track';\n+\n import { LANGUAGE_MODEL, LANGUAGE_MODEL_PROMPT } from '../constant';\n import { getAnswer } from '../service';\n import { getRandomNum } from '../utils';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Create/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n /* eslint-disable consistent-return */\n-import useInitialState from '@/hooks/useInitialState';\n import { Form, Input, Select } from 'antd';\n import _ from 'lodash';\n import { forwardRef, memo, useCallback, useImperativeHandle } from 'react';\n+\n+import useInitialState from '@/hooks/useInitialState';\n+\n import { KNOWLEDGE_SOURCE } from '../../constant';\n import { createKnowledgeBase } from '../../service';\n \n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Detail/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,12 +1,14 @@\n+import { history, useParams } from '@umijs/max';\n+import { Button, Divider, Input, message,Popconfirm, Space, Spin, Table, Typography } from 'antd';\n+import dayjs from 'dayjs';\n+import { memo, useCallback, useMemo, useState } from 'react';\n+\n import ContentLayout from '@/components/ContentLayout';\n import Info, { DATA_TYPE, INFO_TYPE } from '@/components/Info';\n import reminder from '@/components/reminder';\n import useInitialState from '@/hooks/useInitialState';\n import useRequestData from '@/hooks/useRequestData';\n-import { history, useParams } from '@umijs/max';\n-import { Button, Divider, Input, Popconfirm, Space, Spin, Table, Typography, message } from 'antd';\n-import dayjs from 'dayjs';\n-import { memo, useCallback, useMemo, useState } from 'react';\n+\n import Actions from '../../components/Actions';\n import { DOC_TYPE, STATUS, UPLOAD_TYPE } from '../../constant';\n import {\n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Playground/SendInput/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Playground/SendInput/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n /* eslint-disable consistent-return */\n import { Input } from 'antd';\n import { useEffect, useRef, useState } from 'react';\n+\n import sendIcon from '../../../assets/sendIcon.png';\n import styles from './index.less';\n \n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Playground/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Playground/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,4 @@\n /* eslint-disable react/no-children-prop */\n-import ContentLayout from '@/components/ContentLayout';\n import { useParams } from '@umijs/max';\n import { Button, Select, Space, Spin } from 'antd';\n import classnames from 'classnames';\n@@ -8,6 +7,9 @@ import ReactJsonView from 'react-json-view';\n import Markdown from 'react-markdown';\n import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';\n import remarkGrm from 'remark-gfm';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+\n import miIcon from '../../assets/miIcon.png';\n import userIcon from '../../assets/userIcon.png';\n import { LANGUAGE_MODEL } from '../../constant';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Upload/FeishuFile/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Upload/FeishuFile/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -2,6 +2,7 @@\n import { QuestionCircleOutlined } from '@ant-design/icons';\n import { Alert, Button, Form, Input, Table, Tooltip } from 'antd';\n import { memo } from 'react';\n+\n import img from '../../../assets/image.png';\n \n const FeishuFile = ({ urls = [] }: { urls: any[] }) => {\n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Upload/LocalUpload/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Upload/LocalUpload/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n /* eslint-disable prefer-promise-reject-errors */\n-import reminder from '@/components/reminder';\n import { InboxOutlined } from '@ant-design/icons';\n import { Form, Radio, Upload } from 'antd';\n import { UploadProps } from 'antd/es/upload';\n import _ from 'lodash';\n import { memo, useMemo } from 'react';\n+\n+import reminder from '@/components/reminder';\n+\n import { ACCEPT_FILE_TYPE, LOCAL_UPLOAD_LISTS, LOCAL_UPLOAD_TYPE } from '../../../constant';\n \n const { Dragger } = Upload;\n" + }, + { + "old_path": "src/pages/KnowledgeBase/pages/Upload/index.tsx", + "new_path": "src/pages/KnowledgeBase/pages/Upload/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n-import ContentLayout from '@/components/ContentLayout';\n-import reminder from '@/components/reminder';\n-import useInitialState from '@/hooks/useInitialState';\n import { history, useParams } from '@umijs/max';\n import { Alert, Button, Form, message, notification, Radio, Space, Spin, Typography } from 'antd';\n import { memo, useCallback, useMemo, useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import reminder from '@/components/reminder';\n+import useInitialState from '@/hooks/useInitialState';\n+\n import { UPLOAD_LISTS, UPLOAD_TYPE } from '../../constant';\n import useCnName from '../../hooks/useCnName';\n import { uploadFile } from '../../service';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/index.tsx", + "new_path": "src/pages/KnowledgeBase/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,15 +1,17 @@\n-import ContentLayout from '@/components/ContentLayout';\n-import Drawer from '@/components/Drawer';\n-import reminder from '@/components/reminder';\n-import useInitialState from '@/hooks/useInitialState';\n-import useRequestData from '@/hooks/useRequestData';\n-import useTrack from '@/hooks/useTrack';\n import { ReloadOutlined } from '@ant-design/icons';\n import { history } from '@umijs/max';\n import { Button, Input, message, Space, Spin, Switch, Table, Typography } from 'antd';\n import dayjs from 'dayjs';\n import _ from 'lodash';\n import { useCallback, useMemo, useRef, useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import Drawer from '@/components/Drawer';\n+import reminder from '@/components/reminder';\n+import useInitialState from '@/hooks/useInitialState';\n+import useRequestData from '@/hooks/useRequestData';\n+import useTrack from '@/hooks/useTrack';\n+\n import Actions from './components/Actions';\n import { KNOWLEDGE_SOURCE } from './constant';\n import style from './index.less';\n" + }, + { + "old_path": "src/pages/KnowledgeBase/utils.ts", + "new_path": "src/pages/KnowledgeBase/utils.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n /* eslint-disable no-restricted-properties */\n // 上传文档数据处理\n import _ from 'lodash';\n+\n import { UPLOAD_TYPE } from './constant';\n \n const getFileName = (fileName = '') => {\n" + }, + { + "old_path": "src/pages/ModelSquare/components/ApiInfo/index.tsx", + "new_path": "src/pages/ModelSquare/components/ApiInfo/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,6 @@\n import { Card, Typography } from 'antd';\n import React from 'react';\n+\n import CodeMirror from '../CodeMirror';\n import styles from './index.less';\n \n" + }, + { + "old_path": "src/pages/ModelSquare/components/CodeMirror/index.tsx", + "new_path": "src/pages/ModelSquare/components/CodeMirror/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,15 +1,17 @@\n-import { CopyOutlined } from '@ant-design/icons';\n-import { useFullscreen } from 'ahooks';\n-import { Space, Typography } from 'antd';\n-import classnames from 'classnames';\n import 'codemirror/addon/edit/closebrackets';\n import 'codemirror/lib/codemirror.css';\n import 'codemirror/mode/javascript/javascript.js';\n import 'codemirror/mode/shell/shell';\n import 'codemirror/mode/yaml/yaml';\n import 'codemirror/theme/base16-light.css';\n+\n+import { CopyOutlined } from '@ant-design/icons';\n+import { useFullscreen } from 'ahooks';\n+import { Space, Typography } from 'antd';\n+import classnames from 'classnames';\n import { CSSProperties, useRef } from 'react';\n import { Controlled as CodeMirror } from 'react-codemirror2';\n+\n import styles from './styles/index.less';\n \n const { Paragraph } = Typography;\n" + }, + { + "old_path": "src/pages/ModelSquare/components/ModelIntro/index.tsx", + "new_path": "src/pages/ModelSquare/components/ModelIntro/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n import { Button, Space, Tag, Typography } from 'antd';\n import classnames from 'classnames';\n import { memo } from 'react';\n+\n import ChatGLM from '../../assets/ChatGLM.png';\n import inner from '../../assets/inner.png';\n import Llama from '../../assets/Llama.png';\n" + }, + { + "old_path": "src/pages/ModelSquare/pages/Detail/ModelInfo/index.tsx", + "new_path": "src/pages/ModelSquare/pages/Detail/ModelInfo/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n import { InfoCircleOutlined } from '@ant-design/icons';\n import { Space, Typography } from 'antd';\n import { memo } from 'react';\n+\n import styles from './index.less';\n \n type Props = {\n" + }, + { + "old_path": "src/pages/ModelSquare/pages/Detail/ModelList/index.tsx", + "new_path": "src/pages/ModelSquare/pages/Detail/ModelList/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n-import useInitialState from '@/hooks/useInitialState';\n-import useTrack from '@/hooks/useTrack';\n-import { TrackEnum } from '@/pages/ModelSquare/constants/track';\n import { CloseOutlined } from '@ant-design/icons';\n import { history } from '@umijs/max';\n import { Button, Space, Table, Tooltip, Typography } from 'antd';\n import { memo, useCallback, useMemo, useState } from 'react';\n+\n+import useInitialState from '@/hooks/useInitialState';\n+import useTrack from '@/hooks/useTrack';\n+import { TrackEnum } from '@/pages/ModelSquare/constants/track';\n+\n import ApiInfo from '../../../components/ApiInfo';\n type Props = {\n data: {\n" + }, + { + "old_path": "src/pages/ModelSquare/pages/Detail/OpenModelInfo/index.tsx", + "new_path": "src/pages/ModelSquare/pages/Detail/OpenModelInfo/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,6 @@\n import { Space, Typography } from 'antd';\n import { memo } from 'react';\n+\n import styles from './index.less';\n \n type Props = {\n" + }, + { + "old_path": "src/pages/ModelSquare/pages/Detail/index.tsx", + "new_path": "src/pages/ModelSquare/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,9 @@\n-import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import { useParams } from '@umijs/max';\n import { Button, Card, Divider, Space, Tabs, Tag } from 'antd';\n import { useMemo, useState } from 'react';\n+\n+import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n+\n import { MODEL_ITEMS, MODEL_ITEMS_MAP } from '../../constant';\n import style from './index.less';\n import ModelInfo from './ModelInfo';\n" + }, + { + "old_path": "src/pages/ModelSquare/index.tsx", + "new_path": "src/pages/ModelSquare/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n-import ContentLayout from '@/components/ContentLayout';\n-import useInitialState from '@/hooks/useInitialState';\n-import useTrack from '@/hooks/useTrack';\n import { history } from '@umijs/max';\n import { Empty, Input, Space } from 'antd';\n import { useCallback, useMemo, useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import useInitialState from '@/hooks/useInitialState';\n+import useTrack from '@/hooks/useTrack';\n+\n import ModelIntro from './components/ModelIntro';\n import { MODEL_ITEMS } from './constant';\n import { TrackEnum } from './constants/track';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/components/Actions/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/components/Actions/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,14 +1,16 @@\n-import { RESOURCE_MONITORING_LINK } from '@/constants/global';\n-import useInitialState from '@/hooks/useInitialState';\n-import useTrack from '@/hooks/useTrack';\n-import { deleteTask, pauseTask, rerunTask } from '@/services/modelTrain';\n import { EllipsisOutlined } from '@ant-design/icons';\n import { history } from '@umijs/max';\n-import { Button, Popconfirm, Popover, Space, message } from 'antd';\n+import { Button, message,Popconfirm, Popover, Space } from 'antd';\n import { SpaceSize } from 'antd/es/space';\n import { ButtonSize, ButtonType } from 'antd/lib/button';\n import { memo, useCallback } from 'react';\n-import { STATUS, getApi } from '../../constant';\n+\n+import { RESOURCE_MONITORING_LINK } from '@/constants/global';\n+import useInitialState from '@/hooks/useInitialState';\n+import useTrack from '@/hooks/useTrack';\n+import { deleteTask, pauseTask, rerunTask } from '@/services/modelTrain';\n+\n+import { getApi,STATUS } from '../../constant';\n import { TrackEnum } from '../../track';\n import styles from './index.less';\n \n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/hooks/useGitSecret.ts", + "new_path": "src/pages/ModelTrain/CRH/hooks/useGitSecret.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { queryGitList } from '@/services/trainJob';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { queryGitList } from '@/services/trainJob';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [gitList, setGitList] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/hooks/usePlatformOptions.ts", + "new_path": "src/pages/ModelTrain/CRH/hooks/usePlatformOptions.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import useInitialState from '@/hooks/useInitialState';\n import _ from 'lodash';\n import { useEffect, useState } from 'react';\n \n+import useInitialState from '@/hooks/useInitialState';\n+\n export default ({ ignore = false } = {}) => {\n const { platformOption } = useInitialState();\n const [data, setData] = useState();\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/hooks/useProjectLists.ts", + "new_path": "src/pages/ModelTrain/CRH/hooks/useProjectLists.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,8 @@\n-import useInitialState from '@/hooks/useInitialState';\n import { useAsyncEffect } from 'ahooks';\n import { useCallback, useState } from 'react';\n+\n+import useInitialState from '@/hooks/useInitialState';\n+\n import { getProjectLists } from '../../../../services/modelTrain';\n \n export default ({ ignore = false } = {}) => {\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/hooks/useStorageTypes.ts", + "new_path": "src/pages/ModelTrain/CRH/hooks/useStorageTypes.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,8 @@\n-import { NAS, StarFS, StarFSLabel } from '@/pages/AssetManagement/Storage/constants';\n import { useModel } from '@umijs/max';\n import { useMemo } from 'react';\n+\n+import { NAS, StarFS, StarFSLabel } from '@/pages/AssetManagement/Storage/constants';\n+\n import { cloudMlCrh, kceTj5, nc4Crh, ningxiaCrh, tj5Crh } from '../../../../../config/cluster';\n export default () => {\n const { initialState } = useModel('@@initialState');\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Dataset/Dataset.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Dataset/Dataset.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,3 @@\n-import useRequestData from '@/hooks/useRequestData';\n-import { getDataset } from '@/services/modelTrain';\n import { Alert, Button, Form, Input, Space, Spin, Table, Typography } from 'antd';\n import _ from 'lodash';\n import {\n@@ -12,6 +10,9 @@ import {\n useState,\n } from 'react';\n \n+import useRequestData from '@/hooks/useRequestData';\n+import { getDataset } from '@/services/modelTrain';\n+\n interface DataSource {\n datasetId: number;\n datasetName: string;\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Dataset/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Dataset/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,8 @@\n-import Drawer from '@/components/Drawer';\n import { Button, Input, Space } from 'antd';\n import { memo, useCallback, useRef, useState } from 'react';\n+\n+import Drawer from '@/components/Drawer';\n+\n import DatasetInfo from './Dataset';\n \n interface Ref {\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Resource/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Resource/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n-import useInitialState from '@/hooks/useInitialState';\n-import { getPackageLists } from '@/services/modelTrain';\n import { ReloadOutlined } from '@ant-design/icons';\n import { useAsyncEffect } from 'ahooks';\n import { Button, Spin, Table, Tooltip } from 'antd';\n import React, { memo, useCallback, useMemo, useState } from 'react';\n+\n+import useInitialState from '@/hooks/useInitialState';\n+import { getPackageLists } from '@/services/modelTrain';\n+\n import style from './index.module.less';\n \n const columns = [\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/ResourceInstance/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/ResourceInstance/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n+import { Form, InputNumber, Table } from 'antd';\n+import { memo, useMemo } from 'react';\n+\n import SingleNode from '@/components/ResourceInstance/SingleNode';\n import useTeamResource from '@/hooks/useTeamResource';\n import type { ResourceInstanceList } from '@/types/global';\n-import { Form, InputNumber, Table } from 'antd';\n-import { memo, useMemo } from 'react';\n+\n import styles from './index.less';\n \n const RESOURCE_INSTANCE = [\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Storage/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/Storage/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n+import { Button, Form, Input, Select, Table } from 'antd';\n+import { memo, useEffect, useMemo, useState } from 'react';\n+\n import useInitialState from '@/hooks/useInitialState';\n import { StarFSLabel } from '@/pages/AssetManagement/Storage/constants';\n import { getNasSecrets, getStarFsSecrets } from '@/services/modelTrain';\n import { validMountPath } from '@/utils/validate';\n-import { Button, Form, Input, Select, Table } from 'antd';\n-import { memo, useEffect, useMemo, useState } from 'react';\n+\n import { CLUSTER_STORAGE_TYPE_MAP, STORAGE_TYPE } from '../../../../constant';\n \n const REQUESTS = {\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/Task/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/Task/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,4 @@\n /* eslint-disable prefer-promise-reject-errors */\n-import { isResourceInstance } from '@/cluster';\n-import Image from '@/components/Image';\n-import MultiNode from '@/components/ResourceInstance/MultiNode';\n-import useInitialState from '@/hooks/useInitialState';\n-import CodeConfig from '@/pages/ModelTrain/components/CodeConfig';\n-import CodeMirror from '@/pages/ModelTrain/components/CodeMirrorNew';\n-import { getTaskDetail } from '@/services/modelTrain';\n import { useParams } from '@umijs/max';\n import { Card, Checkbox, Form, Input, InputNumber, Select, Space, Spin } from 'antd';\n import { cloneDeep } from 'lodash';\n@@ -18,7 +11,16 @@ import {\n useMemo,\n useState,\n } from 'react';\n-import { NEED_MODEL_TYPE, getApi } from '../../../constant';\n+\n+import { isResourceInstance } from '@/cluster';\n+import Image from '@/components/Image';\n+import MultiNode from '@/components/ResourceInstance/MultiNode';\n+import useInitialState from '@/hooks/useInitialState';\n+import CodeConfig from '@/pages/ModelTrain/components/CodeConfig';\n+import CodeMirror from '@/pages/ModelTrain/components/CodeMirrorNew';\n+import { getTaskDetail } from '@/services/modelTrain';\n+\n+import { getApi,NEED_MODEL_TYPE } from '../../../constant';\n import usePlatformOptions from '../../../hooks/usePlatformOptions';\n import { str2Object } from '../../../util';\n import Dataset from './Dataset';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Create/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n+import { history } from '@umijs/max';\n+import { Button, message, Space } from 'antd';\n+import { memo, useCallback, useRef, useState } from 'react';\n+\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import useInitialState from '@/hooks/useInitialState';\n import useTrack from '@/hooks/useTrack';\n import { createTask } from '@/services/modelTrain';\n-import { history } from '@umijs/max';\n-import { Button, message, Space } from 'antd';\n-import { memo, useCallback, useRef, useState } from 'react';\n+\n import { getApi } from '../../constant';\n import { TrackEnum } from '../../track';\n import { formatTaskParams } from '../../util';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Detail/Event/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Detail/Event/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n+import { useParams } from '@umijs/max';\n+\n import EventInfo from '@/components/EventInfo';\n import useInitialState from '@/hooks/useInitialState';\n import useRequestData from '@/hooks/useRequestData';\n import { getTaskEvents } from '@/services/modelTrain';\n-import { useParams } from '@umijs/max';\n+\n import { getApi } from '../../../constant';\n export default () => {\n const { cluster } = useInitialState();\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Detail/Logs/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Detail/Logs/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n+import { message } from 'antd';\n+import dayjs from 'dayjs';\n+import { useCallback, useMemo } from 'react';\n+\n import Logs from '@/components/LogInfo';\n import { RESOURCE_MONITORING_LINK } from '@/constants/global';\n import useInitialState from '@/hooks/useInitialState';\n import { queryDownloadPodLogs, queryPodLogs } from '@/services/modelTrain';\n-import { message } from 'antd';\n-import dayjs from 'dayjs';\n-import { useCallback, useMemo } from 'react';\n+\n import { formatLogs } from '../../../../utils';\n import { getApi } from '../../../constant';\n \n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/Detail/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,10 @@\n+import { useParams } from '@umijs/max';\n+import { Button, Card, Space, Spin, Tabs } from 'antd';\n+import classnames from 'classnames';\n+import _, { isEmpty } from 'lodash';\n+import { memo, useMemo, useState } from 'react';\n+import ReactJson from 'react-json-view';\n+\n import { isResourceInstance } from '@/cluster';\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import { DATA_TYPE } from '@/components/Info';\n@@ -13,12 +20,7 @@ import TaskTimeLine from '@/pages/ModelTrain/components/TaskTimeLine';\n import { taskCreateTimeIsAfter } from '@/pages/ModelTrain/utils';\n import { getTaskDetail } from '@/services/modelTrain';\n import { calcTimeInterval } from '@/utils/calcTimeInterval';\n-import { useParams } from '@umijs/max';\n-import { Button, Card, Space, Spin, Tabs } from 'antd';\n-import classnames from 'classnames';\n-import _, { isEmpty } from 'lodash';\n-import { memo, useMemo, useState } from 'react';\n-import ReactJson from 'react-json-view';\n+\n import Actions from '../../components/Actions';\n import { getApi } from '../../constant';\n import { formatJobLogs } from '../../util';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/ProjectList/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/ProjectList/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n+import { ReloadOutlined } from '@ant-design/icons';\n+import { Button, Input, message, Popconfirm, Space, Table, Typography } from 'antd';\n+import { memo, useCallback, useMemo, useState } from 'react';\n+\n import { pagination } from '@/constants/global';\n import useInitialState from '@/hooks/useInitialState';\n import useTrack from '@/hooks/useTrack';\n import { deleteProject } from '@/services/modelTrain';\n-import { ReloadOutlined } from '@ant-design/icons';\n-import { Button, Input, message, Popconfirm, Space, Table, Typography } from 'antd';\n-import { memo, useCallback, useMemo, useState } from 'react';\n+\n import useProjectLists from '../../hooks/useProjectLists';\n import { TrackEnum } from '../../track';\n import { formatTime } from '../../util';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/pages/TaskList/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/pages/TaskList/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,9 @@\n+import { ReloadOutlined } from '@ant-design/icons';\n+import { history, useModel } from '@umijs/max';\n+import { useAntdTable } from 'ahooks';\n+import { Button, Checkbox, Form, Input, Space, Table, Tag, Typography } from 'antd';\n+import { memo, useCallback, useEffect, useMemo } from 'react';\n+\n import { isResourceInstance } from '@/cluster';\n import ResourceItem from '@/components/ResourceInstance/ResourceItem';\n import TableTooltipItem from '@/components/TableTooltipItem';\n@@ -10,13 +16,9 @@ import ListTimeLine from '@/pages/ModelTrain/components/TaskTimeLine/ListTimeLin\n import { taskCreateTimeIsAfter } from '@/pages/ModelTrain/utils';\n import { getTaskLists } from '@/services/modelTrain';\n import { calcTimeInterval } from '@/utils/calcTimeInterval';\n-import { ReloadOutlined } from '@ant-design/icons';\n-import { history, useModel } from '@umijs/max';\n-import { useAntdTable } from 'ahooks';\n-import { Button, Checkbox, Form, Input, Space, Table, Tag, Typography } from 'antd';\n-import { memo, useCallback, useEffect, useMemo } from 'react';\n+\n import Actions from '../../components/Actions';\n-import { STATUS, getApi } from '../../constant';\n+import { getApi,STATUS } from '../../constant';\n import { TrackEnum } from '../../track';\n import { formatTime } from '../../util';\n import style from './index.module.less';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/index.tsx", + "new_path": "src/pages/ModelTrain/CRH/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,10 +1,12 @@\n+import { Button, Space, Tabs } from 'antd';\n+import { useCallback, useMemo, useState } from 'react';\n+\n import { isResourceInstance } from '@/cluster';\n import ContentLayout from '@/components/ContentLayout';\n import useInitialState from '@/hooks/useInitialState';\n import useTrack from '@/hooks/useTrack';\n import { linkToExperiment } from '@/pages/ModelTrain/utils';\n-import { Button, Space, Tabs } from 'antd';\n-import { useCallback, useMemo, useState } from 'react';\n+\n import style from './index.less';\n import ProjectList from './pages/ProjectList';\n import TaskList from './pages/TaskList';\n" + }, + { + "old_path": "src/pages/ModelTrain/CRH/util.ts", + "new_path": "src/pages/ModelTrain/CRH/util.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n /* eslint-disable consistent-return */\n import dayjs from 'dayjs';\n import _ from 'lodash';\n+\n import { STORAGE_TYPE } from './constant';\n import type { JobState, PodState } from './type';\n \n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/DocumentView/index.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/DocumentView/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n import { Card } from 'antd';\n import React, { memo } from 'react';\n+import Draggable from 'react-draggable';\n+\n // eslint-disable-next-line import/no-extraneous-dependencies\n import CodeMirror from '@/pages/ModelSquare/components/CodeMirror';\n-import Draggable from 'react-draggable';\n+\n import styles from './index.less';\n \n interface DocumentViewProps {\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/CodeEditor.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/CodeEditor.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,6 @@\n import 'codemirror/lib/codemirror.css';\n import 'codemirror/mode/shell/shell';\n+\n import { UnControlled as CodeMirror } from 'react-codemirror2';\n \n export default ({ value, onChange }: any) => {\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/StorageMode.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/components/StorageMode.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n-import { validMountPath } from '@/utils/validate';\n import { MinusCircleOutlined } from '@ant-design/icons';\n import { Form, Input, Select } from 'antd';\n import { FormInstance } from 'antd/lib/form';\n import { useMemo } from 'react';\n+\n+import { validMountPath } from '@/utils/validate';\n+\n import styles from '../assets/styles/index.less';\n import { checkFdsStatus, FDS, HDFS, SpaceSize, StarFS, storageTypes } from '../constants';\n \n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useContainerImage.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useContainerImage.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { queryImageList } from '@/services/trainJob';\n import { useModel } from '@umijs/max';\n import { useEffect, useRef, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { queryImageList } from '@/services/trainJob';\n export default (framework: string) => {\n const { initialState } = useModel('@@initialState');\n const imageRef = useRef([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useFramework.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useFramework.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n import { useModel } from '@umijs/max';\n import { useCallback, useEffect, useRef, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n export default (form: any, gpu_type: any, framework: any) => {\n const { initialState } = useModel('@@initialState');\n const [frameworks, setFrameworks] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useGitSecret.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useGitSecret.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { queryGitList } from '@/services/trainJob';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { queryGitList } from '@/services/trainJob';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [gitList, setGitList] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useQuota.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useQuota.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { queryQuota } from '@/services/trainJob';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { queryQuota } from '@/services/trainJob';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [priorities, setPriorities] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useSecretImage.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useSecretImage.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { querySecretImages } from '@/services/trainJob';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { querySecretImages } from '@/services/trainJob';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [imageSecret, setImageSecret] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useStorageList.ts", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/hooks/useStorageList.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n-import type { Option } from '@/pages/Tensorboard/typings';\n-import { queryFdsList, queryHdfsList, queryStarfsList } from '@/services/storage';\n import { useModel } from '@umijs/max';\n import { useEffect, useState } from 'react';\n+\n+import type { Option } from '@/pages/Tensorboard/typings';\n+import { queryFdsList, queryHdfsList, queryStarfsList } from '@/services/storage';\n export default () => {\n const { initialState } = useModel('@@initialState');\n const [fdsList, setFdsList] = useState([]);\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Create/index.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,19 +1,6 @@\n /* eslint-disable no-restricted-syntax */\n /* eslint-disable no-unused-expressions */\n /* eslint-disable no-undef */\n-import { useCallback, useEffect, useMemo, useState } from 'react';\n-// @ts-ignore\n-import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n-import Image from '@/components/Image';\n-import useInitialState from '@/hooks/useInitialState';\n-import useTrack from '@/hooks/useTrack';\n-import useGpus from '@/pages/ModelFinetuning/hooks/useGpus';\n-import { Gi } from '@/pages/ModelService/constants';\n-import { notShow } from '@/pages/ModelTrain/cluster';\n-import CodeConfig from '@/pages/ModelTrain/components/CodeConfig';\n-import CodeMirror from '@/pages/ModelTrain/components/CodeMirrorNew';\n-import DocumentView from '@/pages/ModelTrain/TrainJob/pages/Create/components/DocumentView';\n-import { getTaskDetails, queryCreateTask } from '@/services/trainJob';\n import { CloseOutlined, PlusOutlined } from '@ant-design/icons';\n import { history, useModel, useParams } from '@umijs/max';\n import {\n@@ -31,6 +18,21 @@ import {\n Switch,\n } from 'antd';\n import _ from 'lodash';\n+import { useCallback, useEffect, useMemo, useState } from 'react';\n+\n+// @ts-ignore\n+import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n+import Image from '@/components/Image';\n+import useInitialState from '@/hooks/useInitialState';\n+import useTrack from '@/hooks/useTrack';\n+import useGpus from '@/pages/ModelFinetuning/hooks/useGpus';\n+import { Gi } from '@/pages/ModelService/constants';\n+import { notShow } from '@/pages/ModelTrain/cluster';\n+import CodeConfig from '@/pages/ModelTrain/components/CodeConfig';\n+import CodeMirror from '@/pages/ModelTrain/components/CodeMirrorNew';\n+import DocumentView from '@/pages/ModelTrain/TrainJob/pages/Create/components/DocumentView';\n+import { getTaskDetails, queryCreateTask } from '@/services/trainJob';\n+\n import { TrackEnum } from '../../track';\n import { formatTrainJobDetails } from '../../util';\n import styles from './assets/styles/index.less';\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Detail/EventList.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Detail/EventList.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,9 @@\n+import { history } from '@umijs/max';\n+\n import Event from '@/components/EventInfo';\n import useInitialState from '@/hooks/useInitialState';\n import useRequestData from '@/hooks/useRequestData';\n import { detailsTaskEvent } from '@/services/trainJob';\n-import { history } from '@umijs/max';\n export default () => {\n const { userName } = useInitialState();\n const { loading, data, queryData } = useRequestData({\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Detail/LogInstance.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Detail/LogInstance.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,11 +1,13 @@\n-import Logs from '@/components/LogInfo';\n-import useInitialState from '@/hooks/useInitialState';\n-import { getLogs } from '@/services/trainJob';\n import { useLocation } from '@umijs/max';\n import { useCallback, useMemo } from 'react';\n+\n+import Logs from '@/components/LogInfo';\n // import { RESOURCE_MONITORING_LINK } from '@/constants/global';\n import { LOGS_LINES } from '@/constants/global';\n+import useInitialState from '@/hooks/useInitialState';\n+import { getLogs } from '@/services/trainJob';\n import downloadFile from '@/utils/downloadFile';\n+\n import { formatLogs } from '../../../utils';\n export default ({\n data,\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/pages/Detail/index.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,12 @@\n /* eslint-disable no-plusplus */\n /* eslint-disable guard-for-in */\n /* eslint-disable no-restricted-syntax */\n-import { Button, Card, Popconfirm, Space, Spin, Tabs, message } from 'antd';\n+import { history, useLocation, useModel } from '@umijs/max';\n+import { Button, Card, message,Popconfirm, Space, Spin, Tabs } from 'antd';\n+import _, { isEmpty } from 'lodash';\n import { useEffect, useMemo, useState } from 'react';\n+import ReactJson from 'react-json-view';\n+\n // @ts-ignore\n import ContentLayout, { TypeEnum } from '@/components/ContentLayout';\n import { DATA_TYPE } from '@/components/Info';\n@@ -17,9 +21,7 @@ import { formatJobLogs } from '@/pages/ModelTrain/CRH/util';\n import { changeResource, changeUpdateTime } from '@/pages/ModelTrain/TrainJob/util';\n import { taskCreateTimeIsAfter } from '@/pages/ModelTrain/utils';\n import { isJSONString } from '@/utils/utils';\n-import { history, useLocation, useModel } from '@umijs/max';\n-import _, { isEmpty } from 'lodash';\n-import ReactJson from 'react-json-view';\n+\n import {\n deleteTask,\n getTaskDetails,\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/index.tsx", + "new_path": "src/pages/ModelTrain/TrainJob/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,12 +1,3 @@\n-import ContentLayout from '@/components/ContentLayout';\n-import TableTooltipItem from '@/components/TableTooltipItem';\n-import { HERA_LINK, pagination, RESOURCE_MONITORING_LINK } from '@/constants/global';\n-import useAdmin from '@/hooks/useAdmin';\n-import useSearchStorage from '@/hooks/useSearchStorage';\n-import useTrack from '@/hooks/useTrack';\n-import { notShow } from '@/pages/ModelTrain/cluster';\n-import ListTimeLine from '@/pages/ModelTrain/components/TaskTimeLine/ListTimeLine';\n-import { linkToExperiment, taskCreateTimeIsAfter } from '@/pages/ModelTrain/utils';\n import { CloseOutlined, EllipsisOutlined, ReloadOutlined } from '@ant-design/icons';\n import { history, useModel } from '@umijs/max';\n import { useAntdTable } from 'ahooks';\n@@ -27,6 +18,17 @@ import {\n import type { ColumnsType } from 'antd/es/table';\n import dayjs from 'dayjs';\n import { useEffect, useMemo, useState } from 'react';\n+\n+import ContentLayout from '@/components/ContentLayout';\n+import TableTooltipItem from '@/components/TableTooltipItem';\n+import { HERA_LINK, pagination, RESOURCE_MONITORING_LINK } from '@/constants/global';\n+import useAdmin from '@/hooks/useAdmin';\n+import useSearchStorage from '@/hooks/useSearchStorage';\n+import useTrack from '@/hooks/useTrack';\n+import { notShow } from '@/pages/ModelTrain/cluster';\n+import ListTimeLine from '@/pages/ModelTrain/components/TaskTimeLine/ListTimeLine';\n+import { linkToExperiment, taskCreateTimeIsAfter } from '@/pages/ModelTrain/utils';\n+\n import {\n deleteTask,\n delProjects,\n" + }, + { + "old_path": "src/pages/ModelTrain/TrainJob/util.ts", + "new_path": "src/pages/ModelTrain/TrainJob/util.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,8 @@\n /* eslint-disable no-restricted-syntax */\n-import { isJSONString } from '@/utils/utils';\n import { cloneDeep } from 'lodash';\n \n+import { isJSONString } from '@/utils/utils';\n+\n export function formatTrainJobDetails(res: Record) {\n const request_body = isJSONString(res?.request_body) ? JSON.parse(res?.request_body) : null;\n const data = cloneDeep(request_body);\n" + }, + { + "old_path": "src/pages/ModelTrain/components/CodeConfig/BranchSelect.tsx", + "new_path": "src/pages/ModelTrain/components/CodeConfig/BranchSelect.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,7 @@\n-import SearchSelect from '@/components/SearchSelect';\n import { Input, Space } from 'antd';\n+\n+import SearchSelect from '@/components/SearchSelect';\n+\n import styles from './index.less';\n \n const BranchSelect = (props: any) => {\n" + }, + { + "old_path": "src/pages/ModelTrain/components/CodeConfig/index.tsx", + "new_path": "src/pages/ModelTrain/components/CodeConfig/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,7 @@\n+import { Button, Form, Input, Select } from 'antd';\n+import { SelectProps } from 'antd/es/select';\n+import { useCallback, useEffect, useMemo, useState } from 'react';\n+\n import useInitialState from '@/hooks/useInitialState';\n import useRequestData from '@/hooks/useRequestData';\n import GitVersion from '@/pages/AssetManagement/Code/components/GitVersion';\n@@ -5,9 +9,7 @@ import CreateModal from '@/pages/AssetManagement/Code/pages/CreateModal';\n import BranchSelect from '@/pages/ModelTrain/components/CodeConfig/BranchSelect';\n import { queryTrainCodeList } from '@/services/code';\n import { getGitlabBranch } from '@/services/global';\n-import { Button, Form, Input, Select } from 'antd';\n-import { SelectProps } from 'antd/es/select';\n-import { useCallback, useEffect, useMemo, useState } from 'react';\n+\n import style from './index.less';\n \n const CodeConfig = ({ form, detailData }: any) => {\n" + }, + { + "old_path": "src/pages/ModelTrain/components/CodeMirrorNew/index.tsx", + "new_path": "src/pages/ModelTrain/components/CodeMirrorNew/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,13 +1,14 @@\n /* eslint-disable react/no-unknown-property */\n-import { CopyOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';\n-import { useFullscreen } from 'ahooks';\n-import { Space, Typography } from 'antd';\n-import classnames from 'classnames';\n import 'codemirror/addon/edit/closebrackets';\n import 'codemirror/lib/codemirror.css';\n import 'codemirror/mode/javascript/javascript.js';\n import 'codemirror/mode/shell/shell';\n import 'codemirror/theme/eclipse.css';\n+\n+import { CopyOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';\n+import { useFullscreen } from 'ahooks';\n+import { Space, Typography } from 'antd';\n+import classnames from 'classnames';\n import { CSSProperties, useCallback, useRef, useState } from 'react';\n import { Controlled as CodeMirror } from 'react-codemirror2';\n \n" + }, + { + "old_path": "src/pages/ModelTrain/components/TaskTimeLine/ListTimeLine.tsx", + "new_path": "src/pages/ModelTrain/components/TaskTimeLine/ListTimeLine.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -2,6 +2,7 @@ import { InfoCircleOutlined, SyncOutlined } from '@ant-design/icons';\n import { Popover, Tag, Timeline } from 'antd';\n import dayjs from 'dayjs';\n import { memo, useMemo } from 'react';\n+\n import { KEYS, TASK_MAP, TaskTimeLineProps } from './constant';\n import useDefaultParams from './hooks';\n import styles from './index.less';\n" + }, + { + "old_path": "src/pages/ModelTrain/components/TaskTimeLine/hooks.ts", + "new_path": "src/pages/ModelTrain/components/TaskTimeLine/hooks.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,5 +1,6 @@\n import _ from 'lodash';\n import { useCallback, useMemo } from 'react';\n+\n import { KEYS, TaskTimeLineProps } from './constant';\n \n export default (props: TaskTimeLineProps) => {\n" + }, + { + "old_path": "src/pages/ModelTrain/components/TaskTimeLine/index.tsx", + "new_path": "src/pages/ModelTrain/components/TaskTimeLine/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,9 +1,11 @@\n-import calcTimeInterval from '@/utils/calcTimeInterval';\n import { SyncOutlined } from '@ant-design/icons';\n import { Tag, Timeline } from 'antd';\n import classnames from 'classnames';\n import dayjs from 'dayjs';\n import { memo } from 'react';\n+\n+import calcTimeInterval from '@/utils/calcTimeInterval';\n+\n import { KEYS, TASK_MAP, TaskTimeLineProps } from './constant';\n import useDefaultParams from './hooks';\n import styles from './index.less';\n" + }, + { + "old_path": "src/pages/ModelTrain/utils/index.ts", + "new_path": "src/pages/ModelTrain/utils/index.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n // 格式化日志信息\n import dayjs from 'dayjs';\n import _ from 'lodash';\n+\n import { kceTj5 } from '../../../../config/cluster';\n \n export function formatLogs(logs: any) {\n" + }, + { + "old_path": "src/pages/Overview/components/DocumentView/index.tsx", + "new_path": "src/pages/Overview/components/DocumentView/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n import { CloseOutlined, LinkOutlined } from '@ant-design/icons';\n import { Button, Card, Space, Typography } from 'antd';\n import { useMemo } from 'react';\n+\n import styles from './index.less';\n \n const DocumentView = ({ onRef, link }: { onRef: any; link: string }) => {\n" + }, + { + "old_path": "src/pages/Overview/components/Flowchart/index.tsx", + "new_path": "src/pages/Overview/components/Flowchart/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,7 +1,9 @@\n-import { IInitialState } from '@/app';\n-import useSetTour from '@/components/Tour/useSetTour';\n import { history, useModel } from '@umijs/max';\n import React from 'react';\n+\n+import { IInitialState } from '@/app';\n+import useSetTour from '@/components/Tour/useSetTour';\n+\n import code from '../../assets/imgs/code.png';\n import code_active from '../../assets/imgs/code_active.png';\n import image from '../../assets/imgs/image.png';\n" + }, + { + "old_path": "src/pages/Overview/components/MenuModules/index.tsx", + "new_path": "src/pages/Overview/components/MenuModules/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,8 +1,10 @@\n+import { history, useModel } from '@umijs/max';\n+import { Card } from 'antd';\n+\n import { IInitialState } from '@/app';\n import useSetTour from '@/components/Tour/useSetTour';\n import useTrack from '@/hooks/useTrack';\n-import { history, useModel } from '@umijs/max';\n-import { Card } from 'antd';\n+\n import { MENU_MAP } from '../../constants';\n import { TrackEnum } from '../../constants/track';\n import styles from './index.less';\n" + }, + { + "old_path": "src/pages/Overview/pages/NoEntry/index.tsx", + "new_path": "src/pages/Overview/pages/NoEntry/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,6 +1,7 @@\n-import styles from '@/pages/Overview/pages/TeamSelect/index.less';\n import { history, useModel } from '@umijs/max';\n import { Button, Typography } from 'antd';\n+\n+import styles from '@/pages/Overview/pages/TeamSelect/index.less';\n const NoEntry = ({ routeProps }: { routeProps: Record }) => {\n const { initialState } = useModel('@@initialState');\n return (\n" + }, + { + "old_path": "src/pages/Overview/pages/TeamSelect/index.tsx", + "new_path": "src/pages/Overview/pages/TeamSelect/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,4 +1,8 @@\n /* eslint-disable no-nested-ternary */\n+import { history, useModel } from '@umijs/max';\n+import { Button, Select, Space, Typography } from 'antd';\n+import { useEffect, useState } from 'react';\n+\n import { IInitialState } from '@/app';\n import NoFoundContent from '@/components/NoFoundContent';\n import WorkSpaceCreate from '@/components/WorkSpaceCreate';\n@@ -6,9 +10,7 @@ import OldWorkSpaceCreate from '@/components/WorkSpaceCreate/OldWorkSpaceCreate'\n import useTrack from '@/hooks/useTrack';\n import { isNewTeamList } from '@/pages/WorkSpace/cluster';\n import { queryTeamList } from '@/services/team';\n-import { history, useModel } from '@umijs/max';\n-import { Button, Select, Space, Typography } from 'antd';\n-import { useEffect, useState } from 'react';\n+\n import { TrackEnum } from '../../constants/track';\n import styles from './index.less';\n const TeamSelect = ({ routeProps }: { routeProps: Record }) => {\n" + }, + { + "old_path": "src/pages/Overview/index.tsx", + "new_path": "src/pages/Overview/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,3 +1,8 @@\n+import { CloseOutlined, LinkOutlined } from '@ant-design/icons';\n+import { history, useModel } from '@umijs/max';\n+import { Button, Card, Space, Tour, Typography } from 'antd';\n+import React, { useContext, useMemo, useState } from 'react';\n+\n import DocumentView from '@/components/DocumentView';\n import useLocalStorage from '@/hooks/useLocalStorage';\n import useTrack from '@/hooks/useTrack';\n@@ -6,10 +11,7 @@ import { Overview__Tour__Key } from '@/layouts/constants';\n import styles from '@/pages/Overview/assets/styles/index.less';\n import Flowchart from '@/pages/Overview/components/Flowchart';\n import MenuModules from '@/pages/Overview/components/MenuModules';\n-import { CloseOutlined, LinkOutlined } from '@ant-design/icons';\n-import { history, useModel } from '@umijs/max';\n-import { Button, Card, Space, Tour, Typography } from 'antd';\n-import React, { useContext, useMemo, useState } from 'react';\n+\n import { TrackEnum } from './constants/track';\n const Overview = () => {\n const { trackClickHandle } = useTrack(TrackEnum.PageName);\n" + }, + { + "old_path": "src/pages/PAIDLC/components/BaseCodeMirror/CodeMoirror.tsx", + "new_path": "src/pages/PAIDLC/components/BaseCodeMirror/CodeMoirror.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -2,9 +2,10 @@\n import 'codemirror/lib/codemirror.css';\n import 'codemirror/mode/shell/shell';\n import 'codemirror/theme/material.css';\n-import { UnControlled as CodeMirror } from 'react-codemirror2';\n import './styles/index.less';\n \n+import { UnControlled as CodeMirror } from 'react-codemirror2';\n+\n export default ({ data }: { data: string[] }) => {\n return (\n {\n const terminalDomRef = useRef(null);\n const xtermRef = useRef();\n" + }, + { + "old_path": "src/pages/PAIDLC/components/Container/WebTerm.tsx", + "new_path": "src/pages/PAIDLC/components/Container/WebTerm.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "@@ -1,13 +1,13 @@\n /* eslint-disable no-inner-declarations */\n /* eslint-disable no-console */\n+import 'xterm/css/xterm.css';\n+\n import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react';\n import { Terminal } from 'xterm';\n import { FitAddon } from 'xterm-addon-fit';\n import { SearchAddon } from 'xterm-addon-search';\n import { WebLinksAddon } from 'xterm-addon-web-links';\n \n-import 'xterm/css/xterm.css';\n-\n const WebTerm = forwardRef(({ wssUrl }: { wssUrl: string }, ref) => {\n const terminalDomRef = useRef(null);\n const xtermRef = useRef();\n" + }, + { + "old_path": "src/pages/PAIDLC/components/Container/index.tsx", + "new_path": "src/pages/PAIDLC/components/Container/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/Dataset/index.tsx", + "new_path": "src/pages/PAIDLC/components/Dataset/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/Events/index.old.tsx", + "new_path": "src/pages/PAIDLC/components/Events/index.old.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/Events/index.tsx", + "new_path": "src/pages/PAIDLC/components/Events/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/Instance/index.tsx", + "new_path": "src/pages/PAIDLC/components/Instance/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/LogsModal/Event.tsx", + "new_path": "src/pages/PAIDLC/components/LogsModal/Event.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/LogsModal/Instance.tsx", + "new_path": "src/pages/PAIDLC/components/LogsModal/Instance.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/LogsModal/index.tsx", + "new_path": "src/pages/PAIDLC/components/LogsModal/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/components/SourceView/index.tsx", + "new_path": "src/pages/PAIDLC/components/SourceView/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/constants/downloadCsv.ts", + "new_path": "src/pages/PAIDLC/constants/downloadCsv.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/constants/index.tsx", + "new_path": "src/pages/PAIDLC/constants/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/hooks/useClusters.ts", + "new_path": "src/pages/PAIDLC/hooks/useClusters.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/hooks/useCodeSelect.ts", + "new_path": "src/pages/PAIDLC/hooks/useCodeSelect.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/hooks/useFramework.ts", + "new_path": "src/pages/PAIDLC/hooks/useFramework.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/hooks/useQuota.ts", + "new_path": "src/pages/PAIDLC/hooks/useQuota.ts", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/pages/Create/index.tsx", + "new_path": "src/pages/PAIDLC/pages/Create/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/pages/Detail/index.tsx", + "new_path": "src/pages/PAIDLC/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/pages/webTerm/index.tsx", + "new_path": "src/pages/PAIDLC/pages/webTerm/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/PAIDLC/index.tsx", + "new_path": "src/pages/PAIDLC/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/components/Create.tsx", + "new_path": "src/pages/Tensorboard/components/Create.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/components/CreateModal.tsx", + "new_path": "src/pages/Tensorboard/components/CreateModal.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/constants/index.tsx", + "new_path": "src/pages/Tensorboard/constants/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/pages/Detail/Event.tsx", + "new_path": "src/pages/Tensorboard/pages/Detail/Event.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/pages/Detail/index.tsx", + "new_path": "src/pages/Tensorboard/pages/Detail/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + }, + { + "old_path": "src/pages/Tensorboard/index.tsx", + "new_path": "src/pages/Tensorboard/index.tsx", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "diff": "" + } + ], + "overflow": false +} \ No newline at end of file diff --git a/script/mr/comments.json b/script/mr/comments.json new file mode 100644 index 0000000..deab64d --- /dev/null +++ b/script/mr/comments.json @@ -0,0 +1,443 @@ +[ + { + "id": 9591828, + "type": null, + "body": "mentioned in commit 17a35cfb47b46e85f507c5f602907ffc429d40c0", + "attachment": null, + "author": { + "id": 30382, + "username": "wuting7", + "name": "吴婷", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30382/avatar.png", + "web_url": "https://git.n.xiaomi.com/wuting7" + }, + "created_at": "2024-08-09T10:54:28.295+08:00", + "updated_at": "2024-08-09T10:54:28.297+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591827, + "type": null, + "body": "approved this merge request", + "attachment": null, + "author": { + "id": 30382, + "username": "wuting7", + "name": "吴婷", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30382/avatar.png", + "web_url": "https://git.n.xiaomi.com/wuting7" + }, + "created_at": "2024-08-09T10:54:25.753+08:00", + "updated_at": "2024-08-09T10:54:25.755+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591699, + "type": null, + "body": "resolved all threads", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:49:03.570+08:00", + "updated_at": "2024-08-09T10:49:03.577+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591698, + "type": "DiffNote", + "body": "删完了捏", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:49:03.134+08:00", + "updated_at": "2024-08-09T10:49:03.134+08:00", + "system": false, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "commit_id": null, + "position": { + "base_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "start_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "head_sha": "4e7ef3dfd9a731f265c0b7e65d96476ad35e1643", + "old_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "new_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "position_type": "text", + "old_line": null, + "new_line": 179, + "line_range": { + "start": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + }, + "end": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + } + } + }, + "resolvable": true, + "resolved": true, + "resolved_by": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "resolved_at": "2024-08-09T10:49:03.545+08:00", + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591676, + "type": null, + "body": "added 1 commit\n\n
  • 9af87594 - chore: 删除示例数据
\n\n[Compare with previous version](/cloudml-visuals/fe/cloud-ml-fe/-/merge_requests/477/diffs?diff_id=6505933&start_sha=4e7ef3dfd9a731f265c0b7e65d96476ad35e1643)", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:48:17.487+08:00", + "updated_at": "2024-08-09T10:48:17.489+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591675, + "type": "DiffNote", + "body": "changed this line in [version 2 of the diff](/cloudml-visuals/fe/cloud-ml-fe/-/merge_requests/477/diffs?diff_id=6505933&start_sha=4e7ef3dfd9a731f265c0b7e65d96476ad35e1643#cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_179_160)", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:48:17.244+08:00", + "updated_at": "2024-08-09T10:48:17.244+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "commit_id": null, + "position": { + "base_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "start_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "head_sha": "4e7ef3dfd9a731f265c0b7e65d96476ad35e1643", + "old_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "new_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "position_type": "text", + "old_line": null, + "new_line": 179, + "line_range": { + "start": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + }, + "end": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + } + } + }, + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591335, + "type": "DiffNote", + "body": "算了,我改到example里吧", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:34:10.533+08:00", + "updated_at": "2024-08-09T10:34:10.533+08:00", + "system": false, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "commit_id": null, + "position": { + "base_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "start_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "head_sha": "4e7ef3dfd9a731f265c0b7e65d96476ad35e1643", + "old_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "new_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "position_type": "text", + "old_line": null, + "new_line": 179, + "line_range": { + "start": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + }, + "end": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + } + } + }, + "resolvable": true, + "resolved": true, + "resolved_by": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "resolved_at": "2024-08-09T10:49:03.545+08:00", + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591318, + "type": null, + "body": "resolved all threads", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:33:43.448+08:00", + "updated_at": "2024-08-09T10:33:43.454+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9591317, + "type": "DiffNote", + "body": "只是删了引用,留点原始数据好看格式", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-09T10:33:42.907+08:00", + "updated_at": "2024-08-09T10:33:42.907+08:00", + "system": false, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "commit_id": null, + "position": { + "base_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "start_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "head_sha": "4e7ef3dfd9a731f265c0b7e65d96476ad35e1643", + "old_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "new_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "position_type": "text", + "old_line": null, + "new_line": 179, + "line_range": { + "start": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + }, + "end": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + } + } + }, + "resolvable": true, + "resolved": true, + "resolved_by": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "resolved_at": "2024-08-09T10:49:03.545+08:00", + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9581569, + "type": "DiffNote", + "body": "mock 数据不是都删了麽?", + "attachment": null, + "author": { + "id": 30382, + "username": "wuting7", + "name": "吴婷", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30382/avatar.png", + "web_url": "https://git.n.xiaomi.com/wuting7" + }, + "created_at": "2024-08-08T15:20:24.391+08:00", + "updated_at": "2024-08-08T15:20:24.391+08:00", + "system": false, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "commit_id": null, + "position": { + "base_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "start_sha": "e65a68d698f0e731285b4fbdee34102a21442803", + "head_sha": "4e7ef3dfd9a731f265c0b7e65d96476ad35e1643", + "old_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "new_path": "src/pages/AIWorkbench/typings/workflowIns.ts", + "position_type": "text", + "old_line": null, + "new_line": 179, + "line_range": { + "start": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + }, + "end": { + "line_code": "cf42380df1ea3d449fda4ef0d59a9e3a2d02aeb3_174_179", + "type": "new", + "old_line": null, + "new_line": 179 + } + } + }, + "resolvable": true, + "resolved": true, + "resolved_by": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "resolved_at": "2024-08-09T10:49:03.545+08:00", + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9581351, + "type": null, + "body": "assigned to @zhaoyingbo", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-08T15:11:28.865+08:00", + "updated_at": "2024-08-08T15:11:28.891+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + }, + { + "id": 9581349, + "type": null, + "body": "requested review from @wuting7", + "attachment": null, + "author": { + "id": 10011, + "username": "zhaoyingbo", + "name": "赵英博", + "state": "active", + "avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png", + "web_url": "https://git.n.xiaomi.com/zhaoyingbo" + }, + "created_at": "2024-08-08T15:11:28.338+08:00", + "updated_at": "2024-08-08T15:11:28.340+08:00", + "system": true, + "noteable_id": 2351580, + "noteable_type": "MergeRequest", + "resolvable": false, + "confidential": false, + "noteable_iid": 477, + "commands_changes": {} + } +] \ No newline at end of file diff --git a/script/mr/event.json b/script/mr/event.json new file mode 100644 index 0000000..2f32e8e --- /dev/null +++ b/script/mr/event.json @@ -0,0 +1,17 @@ +{ + "object_kind": "merge_request", + "event_type": "merge_request", + "project": { + "id": 139032 + }, + "object_attributes": { + "iid": 490, + "state": "merged", + "changes": { + "updated_at": { + "previous": "2021-07-01 10:00:00 UTC", + "current": "2021-07-01 10:30:00 UTC" + } + } + } +} \ No newline at end of file diff --git a/service/gitlab/discussions.ts b/service/gitlab/discussions.ts new file mode 100644 index 0000000..ac94033 --- /dev/null +++ b/service/gitlab/discussions.ts @@ -0,0 +1,64 @@ +import { DiscussionSchema } from "@gitbeaker/rest" + +import netTool from "../netTool" +import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools" + +export interface CreateRangeDiscussionPosition { + base_sha: string + start_sha: string + head_sha: string + new_path: string + old_path: string + position_type: "text" + new_line: number + line_range: { + start: { + line_code: string + } + end: { + line_code: string + } + } +} + +/** + * 获取合并请求的讨论列表。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @returns {Promise} 返回包含讨论列表的Promise。 + */ +const getList = async (project_id: number, merge_request_iid: number) => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/discussions` + return gitlabReqWarp( + () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), + [] + ) +} + +/** + * 创建合并请求的讨论。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @param {string} body - 讨论内容。 + * @param {CreateRangeDiscussionPosition} position - 讨论位置。 + * @returns {Promise} 返回包含创建的讨论的Promise。 + */ +const create2Mr = async ( + project_id: number, + merge_request_iid: number, + body: string, + position: CreateRangeDiscussionPosition +) => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/discussions` + return gitlabReqWarp( + () => netTool.post(URL, { body, position }, {}, GITLAB_AUTH_HEADER), + null + ) +} + +const discussions = { + getList, + create2Mr, +} + +export default discussions diff --git a/service/gitlab/index.ts b/service/gitlab/index.ts index c247995..e595dea 100644 --- a/service/gitlab/index.ts +++ b/service/gitlab/index.ts @@ -1,5 +1,8 @@ import badge from "./badge" import commit from "./commit" +import discussions from "./discussions" +import mr from "./mr" +import note from "./note" import pipeline from "./pipeline" import project from "./project" @@ -8,6 +11,9 @@ const gitlab = { badge, commit, pipeline, + mr, + note, + discussions, } export default gitlab diff --git a/service/gitlab/mr.ts b/service/gitlab/mr.ts index 108e905..709f938 100644 --- a/service/gitlab/mr.ts +++ b/service/gitlab/mr.ts @@ -1,20 +1,84 @@ +import type { + ExpandedMergeRequestSchema, + MergeRequestChangesSchema, + MergeRequestDiffVersionsSchema, + MergeRequestNoteSchema, +} from "@gitbeaker/rest" + import netTool from "../netTool" import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools" -const getDiffs = async (project_id: number, merge_request_iid: number) => { +/** + * 获取合并请求的评论,支持分页。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @param {number} [page=1] - 页码。 + * @param {number} [per_page=20] - 每页的评论数。 + * @returns {Promise} 返回包含评论的Promise。 + */ +const getComments = async ( + project_id: number, + merge_request_iid: number, + page: number = 1, + per_page: number = 20 +): Promise => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/notes?page=${page}&per_page=${per_page}` + return gitlabReqWarp( + () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), + [] + ) +} + +/** + * 获取合并请求的变更。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @returns {Promise} 返回包含变更的Promise。 + */ +const getChanges = async (project_id: number, merge_request_iid: number) => { const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/changes` - const res = await gitlabReqWarp( + return gitlabReqWarp( () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), null ) - if (res === null) return null - return res +} + +/** + * 获取合并请求的详细信息。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @returns {Promise} 返回包含详细信息的Promise。 + */ +const getDetail = async (project_id: number, merge_request_iid: number) => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}` + return gitlabReqWarp( + () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), + null + ) +} + +/** + * 获取合并请求的差异版本。 + * @param {number} project_id - 项目ID。 + * @param {number} merge_request_iid - 合并请求IID。 + * @returns {Promise} 返回包含差异版本的Promise。 + */ +const getDiffVersions = async ( + project_id: number, + merge_request_iid: number +) => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/versions` + return gitlabReqWarp( + () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), + [] + ) } const mr = { - getDiffs, + getChanges, + getDetail, + getComments, + getDiffVersions, } export default mr - -getDiffs(139032, 484).then(console.log) diff --git a/service/gitlab/note.ts b/service/gitlab/note.ts new file mode 100644 index 0000000..824aeb1 --- /dev/null +++ b/service/gitlab/note.ts @@ -0,0 +1,51 @@ +import { MergeRequestNoteSchema } from "@gitbeaker/rest" + +import netTool from "../netTool" +import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools" + +/** + * 创建一个新的合并请求备注 + * @param {number} project_id - 项目ID + * @param {number} merge_request_iid - 合并请求IID + * @param {string} body - 备注内容 + * @returns {Promise} - 返回包含新创建备注的Promise + */ +const create2Mr = async ( + project_id: number, + merge_request_iid: number, + body: string +): Promise => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/notes` + return gitlabReqWarp( + () => netTool.post(URL, { body }, {}, GITLAB_AUTH_HEADER), + null + ) +} + +/** + * 修改一个现有的合并请求备注 + * @param {number} project_id - 项目ID + * @param {number} merge_request_iid - 合并请求IID + * @param {number} note_id - 备注ID + * @param {string} body - 新的备注内容 + * @returns {Promise} - 返回包含修改后备注的Promise + */ +const modify2Mr = async ( + project_id: number, + merge_request_iid: number, + note_id: number, + body: string +): Promise => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/merge_requests/${merge_request_iid}/notes/${note_id}` + return gitlabReqWarp( + () => netTool.put(URL, { body }, {}, GITLAB_AUTH_HEADER), + null + ) +} + +const note = { + create2Mr, + modify2Mr, +} + +export default note diff --git a/service/gitlab/repository.ts b/service/gitlab/repository.ts new file mode 100644 index 0000000..ed4e28f --- /dev/null +++ b/service/gitlab/repository.ts @@ -0,0 +1,27 @@ +import netTool from "../netTool" +import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools" + +/** + * 获取指定项目中某个文件的内容。 + * @param {number} project_id - 项目ID。 + * @param {string} path - 文件路径。 + * @param {string} ref - 分支或标签名称。 + * @returns {Promise} 返回包含文件内容的Promise。 + */ +const getFileContent = async ( + project_id: number, + path: string, + ref: string +) => { + const URL = `${GITLAB_BASE_URL}/projects/${project_id}/repository/files/${encodeURIComponent(path)}/raw?ref=${ref}` + return gitlabReqWarp( + () => netTool.get(URL, {}, GITLAB_AUTH_HEADER), + "" + ) +} + +const repository = { + getFileContent, +} + +export default repository diff --git a/service/netTool.ts b/service/netTool.ts index e059f9b..5ba4cbf 100644 --- a/service/netTool.ts +++ b/service/netTool.ts @@ -33,7 +33,7 @@ const logResponse = ( requestBody, responseBody, } - console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2)) + // console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2)) return responseLog } diff --git a/test/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json b/test/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json new file mode 100644 index 0000000..aedd43e --- /dev/null +++ b/test/log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json @@ -0,0 +1,15 @@ +{ + "keep": { + "days": true, + "amount": 14 + }, + "auditLog": "log/.477ef71694ce4c791e6f91cf40117d4a85785c6b-audit.json", + "files": [ + { + "date": 1723423178431, + "name": "log/application-2024-08-12.log", + "hash": "0bc427a58e73e382ac12bffa78e2d17ab09717e51a940fe5d71f786f5f1e6bcf" + } + ], + "hashType": "sha256" +} \ No newline at end of file diff --git a/test/manageGitlabEventReq.test.ts b/test/manageGitlabEventReq.test.ts index cef1277..b6f9514 100644 --- a/test/manageGitlabEventReq.test.ts +++ b/test/manageGitlabEventReq.test.ts @@ -1,5 +1,6 @@ import { expect, test } from "bun:test" +import loggerIns from "../log" import { manageGitlabEventReq } from "../routes/event" import netTool from "../service/netTool" import { Gitlab } from "../types/gitlab" @@ -59,6 +60,8 @@ test("manageGitlabEventReq", async () => { } ) - const res = await manageGitlabEventReq(req) + const logger = loggerIns.child({ name: "test" }) + + const res = await manageGitlabEventReq(req, logger) expect(res).toEqual(netTool.ok()) }) diff --git a/test/manageMrEvent.test.ts b/test/manageMrEvent.test.ts new file mode 100644 index 0000000..912d253 --- /dev/null +++ b/test/manageMrEvent.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from "bun:test" + +import loggerIns from "../log" +import { manageGitlabEventReq } from "../routes/event" +import netTool from "../service/netTool" +import { Gitlab } from "../types/gitlab" + +test("manageMrEvent", async () => { + const headers = new Headers({ + "x-gitlab-token": "uwnpzb9hvoft28h", + "x-gitlab-event": "Merge Request Hook", + }) + + const body: Gitlab.MergeRequestEvent = { + object_kind: "merge_request", + event_type: "merge_request", + project: { + id: 139032, + }, + object_attributes: { + iid: 502, + state: "opened", + }, + } + + const req = new Request( + "https://lark-egg.ai.xiaomi.com/gitlab_monitor/event", + { + method: "POST", + headers: headers, + body: JSON.stringify(body), + } + ) + + const logger = loggerIns.child({ requestId: "test" }) + const res = await manageGitlabEventReq(req, logger) + expect(res).toEqual(netTool.ok()) +}, 100000) diff --git a/test/parseReview.test.ts b/test/parseReview.test.ts new file mode 100644 index 0000000..d0e016b --- /dev/null +++ b/test/parseReview.test.ts @@ -0,0 +1,32 @@ +import { test } from "bun:test" + +import diffTools from "../controllers/manageMrEvent/utils/diffTools" +import loggerIns from "../log" + +test("parseReview", async () => { + const logger = loggerIns.child({ requestId: "test" }) + const response = ` + 1-12: +LGTM! +--- +67-70: +在生产环境中,\`console.log\`可能会导致性能问题,建议移除或使用更合适的方式进行调试。 +\`\`\`diff +- console.log( +- '🚀 ~ file: index.tsx:68 ~ ModelSquare ~ item.apiDoc:', +- item.apiDoc, +- ); +\`\`\` +--- +` + const diffs = [ + [ + 1, + 12, + "\\n---new_hunk---\\n```\\n import { Button, Space, Tag, Typography } from 'antd';\\n import classnames from 'classnames';\\n import { memo } from 'react';\\n4: \\n5: import ChatGLM from '../../assets/ChatGLM.png';\\n6: import CVLM from '../../assets/CVLM.png';\\n7: import inner from '../../assets/inner.png';\\n8: import Llama from '../../assets/Llama.png';\\n9: import MICV from '../../assets/MICV.png';\\n import MiniGPT from '../../assets/MiniGPT.png';\\n import Mixtral from '../../assets/Mixtral.png';\\n import Qwen from '../../assets/Qwen.png';\\n```\\n\\n---old_hunk---\\n```\\n import { Button, Space, Tag, Typography } from 'antd';\\n import classnames from 'classnames';\\n import { memo } from 'react';\\n import ChatGLM from '../../assets/ChatGLM.png';\\n import inner from '../../assets/inner.png';\\n import Llama from '../../assets/Llama.png';\\n import MiniGPT from '../../assets/MiniGPT.png';\\n import Mixtral from '../../assets/Mixtral.png';\\n import Qwen from '../../assets/Qwen.png';\\n```\\n ", + ], + ] as Array<[number, number, string]> + + const res = diffTools.parseReview(response, diffs) + logger.info(JSON.stringify(res)) +}, 100000) diff --git a/test/pathFilter.test.ts b/test/pathFilter.test.ts new file mode 100644 index 0000000..ed065ef --- /dev/null +++ b/test/pathFilter.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test" + +import { PathFilter } from "../controllers/manageMrEvent/utils/pathFilter" + +// 测试用例 +test("PathFilter check method", () => { + // 示例规则数组 + const rules = [ + "*.js", // 包含所有.js文件 + "!*.test.js", // 排除所有.test.js文件 + "src/**", // 包含src目录下的所有文件 + "!src/tmp/**", // 排除src/tmp目录下的所有文件 + ] + + // 创建PathFilter实例 + const pathFilter = new PathFilter(rules) + // 测试包含.js文件 + expect(pathFilter.check("example.js")).toBe(true) + + // 测试排除.test.js文件 + expect(pathFilter.check("example.test.js")).toBe(false) + + // 测试包含src目录下的文件 + expect(pathFilter.check("src/index.js")).toBe(true) + + // 测试排除src/tmp目录下的文件 + expect(pathFilter.check("src/tmp/temp.js")).toBe(false) +}) diff --git a/test/service/discussion.test.ts b/test/service/discussion.test.ts new file mode 100644 index 0000000..1f2dcc0 --- /dev/null +++ b/test/service/discussion.test.ts @@ -0,0 +1,43 @@ +import { test } from "bun:test" + +import service from "../../service" + +// 测试用例 +test("Gitlab Discussion", async () => { + const project_id = 139032 + const merge_request_iid = 488 + + const res = await service.gitlab.discussions.getList( + project_id, + merge_request_iid + ) + console.log(res) + + const body = "LGTM!" + const position: any = { + base_sha: "17a35cfb47b46e85f507c5f602907ffc429d40c0", + start_sha: "2d6f9d6d5cbfd323e06e37859ee9b19c3578619c", + head_sha: "710458e37476915e585d480961786e7eedbc79fb", + old_path: "src/pages/AIWorkbench/controller/index.ts", + new_path: "src/pages/AIWorkbench/controller/index.ts", + position_type: "text", + new_line: 21, + line_range: { + start: { + line_code: "6988b6e297ca054b9ffaa69f4a0f4a954e749f78_0_21", + }, + end: { + line_code: "6988b6e297ca054b9ffaa69f4a0f4a954e749f78_0_25", + }, + }, + } + + const res2 = await service.gitlab.discussions.create2Mr( + project_id, + merge_request_iid, + body, + position + ) + + console.log(res2) +}, 10000) diff --git a/test/service/mr.test.ts b/test/service/mr.test.ts new file mode 100644 index 0000000..383a483 --- /dev/null +++ b/test/service/mr.test.ts @@ -0,0 +1,15 @@ +import { test } from "bun:test" + +import service from "../../service" + +// 测试用例 +test("Gitlab MR", async () => { + const project_id = 139032 + const merge_request_iid = 4889 + + const res = await service.gitlab.mr.getDiffVersions( + project_id, + merge_request_iid + ) + console.log(res) +}, 10000) diff --git a/test/service/note.test.ts b/test/service/note.test.ts new file mode 100644 index 0000000..c92083a --- /dev/null +++ b/test/service/note.test.ts @@ -0,0 +1,41 @@ +import { test } from "bun:test" + +import service from "../../service" + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +// 测试用例 +test("Gitlab Note", async () => { + const summarizedMr = `增加了两个新的模型图标(CVLM和MICV),并将它们添加到ICONS对象中。合并请求增加了对模型卡片的处理逻辑,调整了导入顺序,并在点击模型卡片时根据是否存在\`apiDoc\`属性决定是跳转到详情页还是打开新窗口。新增了两个模型卡片“通义千问-Max”和“通义千问-Plus”,包括其详细描述、标签、类型、更新时间和API文档链接。` + const summarizedComment = ` + # 整体摘要: + ${summarizedMr} + + # 文件变更: + | 文件路径 | 变更摘要 | + | --- | --- | + | src/components/ModelCard/index.tsx | 新增模型卡片“通义千问-Max”和“通义千问-Plus”,包括其详细描述、标签、类型、更新时间和API文档链接。 | + | src/components/ModelCard/index.less | 新增模型卡片“通义千问-Max”和“通义千问-Plus”的样式。 | + | src/components/ModelCard/icons.ts | 增加了两个新的模型图标(CVLM和MICV),并将它们添加到ICONS对象中。 | + ` + + const loadingComment = "小煎蛋正在处理您的合并请求,请稍等片刻。" + + const project_id = 139032 + const merge_request_iid = 488 + + const { id } = await service.gitlab.note.create2Mr( + project_id, + merge_request_iid, + loadingComment + ) + + await sleep(5000) + + await service.gitlab.note.modify2Mr( + project_id, + merge_request_iid, + id, + summarizedComment + ) +}, 10000) diff --git a/types/gitlab.ts b/types/gitlab.ts index 6535915..8460e96 100644 --- a/types/gitlab.ts +++ b/types/gitlab.ts @@ -347,4 +347,42 @@ export namespace Gitlab { */ web_url: string } + + /* 合并请求事件 */ + export interface MergeRequestEvent { + /** + * 事件对象类型 + */ + object_kind: "merge_request" + /** + * 事件类型 + */ + event_type: "merge_request" + /** + * 项目信息 + */ + project: { + /** + * 项目ID + */ + id: number + } + /** + * 合并请求的属性 + */ + object_attributes: { + /** + * 合并请求的内部ID + */ + iid: number + /** + * 合并请求的状态 + */ + state: "opened" | "closed" | "reopened" | "merged" + /** + * 合并请求的变更信息 + */ + changes?: any + } + } } diff --git a/utils/chatTools.ts b/utils/chatTools.ts new file mode 100644 index 0000000..b90c0af --- /dev/null +++ b/utils/chatTools.ts @@ -0,0 +1,32 @@ +import { ChatOpenAI } from "@langchain/openai" + +/** + * 获取Deepseek模型 + * @param {number} temperature - 温度参数,用于控制生成文本的随机性。 + * @returns {Promise} 返回一个包含Deepseek模型实例的Promise。 + */ +const getDeepseekModel = async (temperature: number) => { + const model = "deepseek-coder" + const apiKey = "sk-21a2ce1c2ee94bc2933798eac1bbcadc" + const baseURL = "https://api.deepseek.com" + return new ChatOpenAI({ apiKey, temperature, model }, { baseURL }) +} + +/** + * 获取GPT-4o模型 + * @param {number} temperature - 温度参数,用于控制生成文本的随机性。 + * @returns {Promise} 返回一个包含GPT-4o模型实例的Promise。 + */ +const getGpt4oModel = async (temperature: number) => { + const model = "deepseek-coder" + const apiKey = "sk-EhbBTR0QjhH22iLr9aCb04D2B0F44f88A07c2924Eb54CfA4" + const baseURL = "https://api.gpt.ge/v1" + return new ChatOpenAI({ apiKey, temperature, model }, { baseURL }) +} + +const chatTools = { + getDeepseekModel, + getGpt4oModel, +} + +export default chatTools diff --git a/utils/pathTools.ts b/utils/pathTools.ts index 144a754..1d68283 100644 --- a/utils/pathTools.ts +++ b/utils/pathTools.ts @@ -30,3 +30,22 @@ export const makeCheckPathTool = (url: string, prefix?: string) => { fullCheck: (path: string) => pathname === path, } } + +/** + * 裁剪路径字符串,如果路径长度超过20个字符,则只保留最后两级目录。 + * + * @param {string} path - 要处理的路径字符串。 + * @returns {string} - 裁剪后的路径字符串,如果长度不超过20个字符则返回原路径。 + */ +export const shortenPath = (path: string): string => { + if (path.length <= 20) { + return path + } + + const parts = path.split("/") + if (parts.length <= 2) { + return path + } + + return `.../${parts[parts.length - 2]}/${parts[parts.length - 1]}` +} diff --git a/utils/tokenTools.ts b/utils/tokenTools.ts new file mode 100644 index 0000000..8350aaa --- /dev/null +++ b/utils/tokenTools.ts @@ -0,0 +1,19 @@ +import { get_encoding as getEncoding } from "@dqbd/tiktoken" + +const tokenizer = getEncoding("cl100k_base") + +const encode = (input: string): Uint32Array => { + return tokenizer.encode(input) +} + +const getTokenCount = (input: string): number => { + const cleanedInput = input.replace(/<\|endoftext\|>/g, "") + return encode(cleanedInput).length +} + +const tokenTools = { + getTokenCount, + encode, +} + +export default tokenTools