Compare commits

..

2 Commits

Author SHA1 Message Date
xiaoxia 8734ee798c fix(voice-materials): 修复useVoiceMaterialActions的types相对路径层级
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 11s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 32s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m20s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m23s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m16s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 56s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m45s
AI Code Review / AI Code Review (pull_request) Successful in 1m7s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m17s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 48s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 6m22s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 25s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 46m15s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
CI/CD Pipeline / CI Gate (pull_request) Failing after 10s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1199h6m57s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1199h22m49s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1199h22m49s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1199h22m51s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1199h22m53s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1200h46m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1200h53m34s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1200h53m49s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1200h53m45s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1200h53m51s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1201h32m53s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1201h32m55s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1201h26m44s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1202h5m49s
2026-07-28 13:40:23 +08:00
xiaoxia d4c3743e45 fix(ci): 修复CI Gate失败时返回exit 0的P0 Bug (develop) (#1058)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m42s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m35s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m37s
CI/CD Pipeline / Unit Tests (push) Failing after 6m22s
CI/CD Pipeline / Integration Tests (push) Successful in 4m51s
CI/CD Pipeline / Frontend Lint (push) Failing after 46s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 1m17s
CI/CD Pipeline / Build Staging API Image (push) Successful in 18m12s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 3m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 8m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Failing after 1201h5m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1201h38m2s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1201h38m3s
CI/CD Pipeline / Build Production API Image (push) Failing after 1201h38m5s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1201h47m53s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1201h48m32s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1201h47m53s
CI/CD Pipeline / PR Build API Image (push) Failing after 1202h20m49s
2026-07-28 13:33:25 +08:00
4 changed files with 82 additions and 111 deletions
+1 -1
View File
@@ -1805,7 +1805,7 @@ jobs:
echo "❌ CI Gate: FAILED"
echo "失败项: ${FAILED_ITEMS[*]}"
echo "gate_result=failure" >> $GITHUB_OUTPUT
exit 0
exit 1
fi
- name: Report CI trace
+81 -5
View File
@@ -1,10 +1,11 @@
import React, { useState } from "react"
import React, { useState, useRef } from "react"
import { UploadOutlined, SoundOutlined, CloseOutlined } from "@ant-design/icons"
import { Button, Input } from "@/components/ui"
import { type TagItem } from "@/api/tags"
import { type VoiceGender, type VoiceMaterial } from "../types"
import { GENDER_OPTIONS } from "../constants"
import { genderClass, formatFileSize } from "../utils/format"
import TagSelector from "./TagSelector"
import FileUploadField from "./material-form/FileUploadField"
import GenderSelector from "./material-form/GenderSelector"
export interface MaterialFormProps {
initial?: VoiceMaterial
@@ -32,6 +33,7 @@ const MaterialForm: React.FC<MaterialFormProps> = ({
const [gender, setGender] = useState<VoiceGender>(initial?.gender ?? "female")
const [selectedTagIds, setSelectedTagIds] = useState<string[]>(initial?.tagIds ?? [])
const [file, setFile] = useState<File | undefined>(undefined)
const fileInputRef = useRef<HTMLInputElement>(null)
const handleSubmit = () => {
if (!name.trim()) return
@@ -51,10 +53,65 @@ const MaterialForm: React.FC<MaterialFormProps> = ({
return (
<div className="vmat-form">
{/* 音频文件上传(编辑模式不显示) */}
{!initial && (
<FileUploadField file={file} onChange={setFile} uploadProgress={uploadProgress} />
<div className="vmat-form-field">
<label className="vmat-form-label"> *</label>
<div
className="vmat-upload-zone"
onClick={() => fileInputRef.current?.click()}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault()
const f = e.dataTransfer.files[0]
if (f?.type.startsWith("audio/")) setFile(f)
}}
>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
style={{ display: "none" }}
onChange={(e) => {
const f = e.target.files?.[0]
if (f) setFile(f)
}}
/>
{file ? (
<div className="vmat-upload-selected">
<SoundOutlined className="vmat-upload-icon" />
<span className="vmat-upload-filename">{file.name}</span>
<span className="vmat-upload-filesize">{formatFileSize(file.size)}</span>
<button
type="button"
className="vmat-upload-clear"
onClick={(e) => {
e.stopPropagation()
setFile(undefined)
}}
>
<CloseOutlined />
</button>
</div>
) : (
<div className="vmat-upload-placeholder">
<UploadOutlined className="vmat-upload-icon" />
<p></p>
<span> MP3WAVAACFLAC </span>
</div>
)}
</div>
{/* 上传进度条 */}
{uploadProgress !== null && uploadProgress !== undefined && (
<div className="vmat-upload-progress">
<div className="vmat-upload-progress-bar" style={{ width: `${uploadProgress}%` }} />
<span className="vmat-upload-progress-text">{uploadProgress}%</span>
</div>
)}
</div>
)}
{/* 名称 */}
<div className="vmat-form-field">
<label className="vmat-form-label"> *</label>
<Input
@@ -65,6 +122,7 @@ const MaterialForm: React.FC<MaterialFormProps> = ({
/>
</div>
{/* 音色描述 */}
<div className="vmat-form-field">
<label className="vmat-form-label"></label>
<Input.TextArea
@@ -76,8 +134,25 @@ const MaterialForm: React.FC<MaterialFormProps> = ({
/>
</div>
<GenderSelector value={gender} onChange={setGender} />
{/* 性别 */}
<div className="vmat-form-field">
<label className="vmat-form-label"></label>
<div className="vmat-gender-group">
{GENDER_OPTIONS.map((opt) => (
<button
key={opt.value}
type="button"
className={`vmat-gender-btn${gender === opt.value ? " active" : ""} ${genderClass(opt.value)}`}
onClick={() => setGender(opt.value)}
>
{opt.icon}
{opt.label}
</button>
))}
</div>
</div>
{/* 风格标签 */}
<div className="vmat-form-field">
<label className="vmat-form-label"></label>
<TagSelector
@@ -89,6 +164,7 @@ const MaterialForm: React.FC<MaterialFormProps> = ({
/>
</div>
{/* 操作按钮 */}
<div className="vmat-form-actions">
<Button buttonType="ghost" buttonSize="md" onClick={onCancel}>
@@ -1,73 +0,0 @@
import React, { useRef } from "react"
import { UploadOutlined, SoundOutlined, CloseOutlined } from "@ant-design/icons"
import { formatFileSize } from "../../utils/format"
interface FileUploadFieldProps {
file: File | undefined
onChange: (file: File | undefined) => void
uploadProgress?: number | null
}
const FileUploadField: React.FC<FileUploadFieldProps> = ({ file, onChange, uploadProgress }) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
const f = e.dataTransfer.files[0]
if (f?.type.startsWith("audio/")) onChange(f)
}
return (
<div className="vmat-form-field">
<label className="vmat-form-label"> *</label>
<div
className="vmat-upload-zone"
onClick={() => fileInputRef.current?.click()}
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
style={{ display: "none" }}
onChange={(e) => {
const f = e.target.files?.[0]
if (f) onChange(f)
}}
/>
{file ? (
<div className="vmat-upload-selected">
<SoundOutlined className="vmat-upload-icon" />
<span className="vmat-upload-filename">{file.name}</span>
<span className="vmat-upload-filesize">{formatFileSize(file.size)}</span>
<button
type="button"
className="vmat-upload-clear"
onClick={(e) => {
e.stopPropagation()
onChange(undefined)
}}
>
<CloseOutlined />
</button>
</div>
) : (
<div className="vmat-upload-placeholder">
<UploadOutlined className="vmat-upload-icon" />
<p></p>
<span> MP3WAVAACFLAC </span>
</div>
)}
</div>
{uploadProgress !== null && uploadProgress !== undefined && (
<div className="vmat-upload-progress">
<div className="vmat-upload-progress-bar" style={{ width: `${uploadProgress}%` }} />
<span className="vmat-upload-progress-text">{uploadProgress}%</span>
</div>
)}
</div>
)
}
export default FileUploadField
@@ -1,32 +0,0 @@
import React from "react"
import type { VoiceGender } from "../../types"
import { GENDER_OPTIONS } from "../../constants"
import { genderClass } from "../../utils/format"
interface GenderSelectorProps {
value: VoiceGender
onChange: (value: VoiceGender) => void
}
const GenderSelector: React.FC<GenderSelectorProps> = ({ value, onChange }) => {
return (
<div className="vmat-form-field">
<label className="vmat-form-label"></label>
<div className="vmat-gender-group">
{GENDER_OPTIONS.map((opt) => (
<button
key={opt.value}
type="button"
className={`vmat-gender-btn${value === opt.value ? " active" : ""} ${genderClass(opt.value)}`}
onClick={() => onChange(opt.value)}
>
{opt.icon}
{opt.label}
</button>
))}
</div>
</div>
)
}
export default GenderSelector