ai去特征展示
This commit is contained in:
@@ -130,6 +130,7 @@ func (h *AdminReadHandler) Logs(c *gin.Context) {
|
|||||||
"resolution": item.Resolution,
|
"resolution": item.Resolution,
|
||||||
"duration": item.Duration,
|
"duration": item.Duration,
|
||||||
"refs": item.Refs,
|
"refs": item.Refs,
|
||||||
|
"deai": item.DeAI,
|
||||||
"source": item.Source,
|
"source": item.Source,
|
||||||
"user_id": emptyStringNil(item.UserID),
|
"user_id": emptyStringNil(item.UserID),
|
||||||
"user_name": userName,
|
"user_name": userName,
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ func (h *UserGenerationHandler) Logs(c *gin.Context) {
|
|||||||
"resolution": item.Resolution,
|
"resolution": item.Resolution,
|
||||||
"duration": item.Duration,
|
"duration": item.Duration,
|
||||||
"refs": item.Refs,
|
"refs": item.Refs,
|
||||||
|
"deai": item.DeAI,
|
||||||
"source": emptyStringNil(item.Source),
|
"source": emptyStringNil(item.Source),
|
||||||
"user_id": emptyStringNil(item.UserID),
|
"user_id": emptyStringNil(item.UserID),
|
||||||
"user_name": userName,
|
"user_name": userName,
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ type EventLog struct {
|
|||||||
Resolution string `gorm:"size:32"`
|
Resolution string `gorm:"size:32"`
|
||||||
Duration string `gorm:"size:32"`
|
Duration string `gorm:"size:32"`
|
||||||
Refs int `gorm:"not null;default:0"`
|
Refs int `gorm:"not null;default:0"`
|
||||||
|
DeAI bool `gorm:"not null;default:false"` // 去AI特征 was applied (image only)
|
||||||
RefFiles datatypes.JSON `gorm:"type:jsonb"` // relative paths of saved reference images, for回显 on reload
|
RefFiles datatypes.JSON `gorm:"type:jsonb"` // relative paths of saved reference images, for回显 on reload
|
||||||
Source string `gorm:"size:32;index"`
|
Source string `gorm:"size:32;index"`
|
||||||
// AccountID is the provider token/account chosen to fulfil this generation,
|
// AccountID is the provider token/account chosen to fulfil this generation,
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ func (s *AdminReadService) Images(ctx context.Context, limit, offset int, kind s
|
|||||||
"resolution": "",
|
"resolution": "",
|
||||||
"ratio": "",
|
"ratio": "",
|
||||||
"duration": "",
|
"duration": "",
|
||||||
|
"deai": false,
|
||||||
}
|
}
|
||||||
if event, ok := index[item.Name]; ok {
|
if event, ok := index[item.Name]; ok {
|
||||||
row["prompt"] = event.Prompt
|
row["prompt"] = event.Prompt
|
||||||
@@ -557,6 +558,7 @@ func (s *AdminReadService) Images(ctx context.Context, limit, offset int, kind s
|
|||||||
row["resolution"] = event.Resolution
|
row["resolution"] = event.Resolution
|
||||||
row["ratio"] = event.Ratio
|
row["ratio"] = event.Ratio
|
||||||
row["duration"] = event.Duration
|
row["duration"] = event.Duration
|
||||||
|
row["deai"] = event.DeAI
|
||||||
}
|
}
|
||||||
out = append(out, row)
|
out = append(out, row)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ func shapeJobEvent(item *model.EventLog, modelNames map[string]string) map[strin
|
|||||||
"ratio": item.Ratio,
|
"ratio": item.Ratio,
|
||||||
"resolution": item.Resolution,
|
"resolution": item.Resolution,
|
||||||
"duration": item.Duration,
|
"duration": item.Duration,
|
||||||
|
"deai": item.DeAI,
|
||||||
"status": status,
|
"status": status,
|
||||||
"file": emptyOrNil(item.File),
|
"file": emptyOrNil(item.File),
|
||||||
"url": emptyOrNil(url),
|
"url": emptyOrNil(url),
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ func (s *V1Service) prepareImageExecution(ctx context.Context, principal *APIPri
|
|||||||
if !noStore {
|
if !noStore {
|
||||||
fileURL, relativePath = s.allocateOutput(principal, "png", in.BaseURL)
|
fileURL, relativePath = s.allocateOutput(principal, "png", in.BaseURL)
|
||||||
}
|
}
|
||||||
eventID, err := s.logPendingEvent(ctx, "image", modelItem, principal, in.Prompt, aspectRatio, resolution, "", refCount, price, relativePath, source, refFiles)
|
eventID, err := s.logPendingEvent(ctx, "image", modelItem, principal, in.Prompt, aspectRatio, resolution, "", refCount, price, relativePath, source, refFiles, in.DeAI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.cleanupReferenceImages(ctx, "", refFiles)
|
s.cleanupReferenceImages(ctx, "", refFiles)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -703,7 +703,7 @@ func (s *V1Service) prepareVideoExecution(ctx context.Context, principal *APIPri
|
|||||||
if !noStore {
|
if !noStore {
|
||||||
fileURL, relativePath = s.allocateOutput(principal, "mp4", in.BaseURL)
|
fileURL, relativePath = s.allocateOutput(principal, "mp4", in.BaseURL)
|
||||||
}
|
}
|
||||||
eventID, err := s.logPendingEvent(ctx, "video", modelItem, principal, in.Prompt, aspectRatio, resolution, duration, refCount, price, relativePath, source, refFiles)
|
eventID, err := s.logPendingEvent(ctx, "video", modelItem, principal, in.Prompt, aspectRatio, resolution, duration, refCount, price, relativePath, source, refFiles, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.cleanupReferenceImages(ctx, "", refFiles)
|
s.cleanupReferenceImages(ctx, "", refFiles)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -825,7 +825,7 @@ func (s *V1Service) StartVideoJob(ctx context.Context, principal *APIPrincipal,
|
|||||||
refFiles := s.saveReferenceImages(ctx, principal, in.ReferenceImages)
|
refFiles := s.saveReferenceImages(ctx, principal, in.ReferenceImages)
|
||||||
// Source "v1": no output file is allocated — the result is the upstream URL,
|
// Source "v1": no output file is allocated — the result is the upstream URL,
|
||||||
// stored on the event when the render completes.
|
// stored on the event when the render completes.
|
||||||
eventID, err := s.logPendingEvent(ctx, "video", modelItem, principal, in.Prompt, aspectRatio, resolution, duration, len(in.ReferenceImages), price, "", "v1", refFiles)
|
eventID, err := s.logPendingEvent(ctx, "video", modelItem, principal, in.Prompt, aspectRatio, resolution, duration, len(in.ReferenceImages), price, "", "v1", refFiles, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.cleanupReferenceImages(ctx, "", refFiles)
|
s.cleanupReferenceImages(ctx, "", refFiles)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1343,7 +1343,7 @@ func (s *V1Service) allocateOutput(principal *APIPrincipal, ext, baseURL string)
|
|||||||
return "/images/" + relativePath, relativePath
|
return "/images/" + relativePath, relativePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *V1Service) logPendingEvent(ctx context.Context, kind string, modelItem *model.ModelConfig, principal *APIPrincipal, prompt, ratio, resolution, duration string, refs int, cost float64, file, source string, refFiles []string) (string, error) {
|
func (s *V1Service) logPendingEvent(ctx context.Context, kind string, modelItem *model.ModelConfig, principal *APIPrincipal, prompt, ratio, resolution, duration string, refs int, cost float64, file, source string, refFiles []string, deai bool) (string, error) {
|
||||||
event := &model.EventLog{
|
event := &model.EventLog{
|
||||||
ID: "evt-" + randomUpper(12),
|
ID: "evt-" + randomUpper(12),
|
||||||
TS: time.Now(),
|
TS: time.Now(),
|
||||||
@@ -1356,6 +1356,7 @@ func (s *V1Service) logPendingEvent(ctx context.Context, kind string, modelItem
|
|||||||
Resolution: resolution,
|
Resolution: resolution,
|
||||||
Duration: duration,
|
Duration: duration,
|
||||||
Refs: refs,
|
Refs: refs,
|
||||||
|
DeAI: deai,
|
||||||
Source: source,
|
Source: source,
|
||||||
Cost: cost,
|
Cost: cost,
|
||||||
File: file,
|
File: file,
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
|||||||
<span v-if="f.resolution" class="text-emerald-300/90">{{ f.resolution }}</span>
|
<span v-if="f.resolution" class="text-emerald-300/90">{{ f.resolution }}</span>
|
||||||
<span v-if="f.ratio" class="text-white/40">{{ f.ratio }}</span>
|
<span v-if="f.ratio" class="text-white/40">{{ f.ratio }}</span>
|
||||||
<span v-if="f.kind === 'video' && f.duration" class="text-fuchsia-300/80">{{ f.duration }}</span>
|
<span v-if="f.kind === 'video' && f.duration" class="text-fuchsia-300/80">{{ f.duration }}</span>
|
||||||
|
<span v-if="f.deai" class="text-violet-300/80">去AI</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</div>
|
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</div>
|
||||||
@@ -356,7 +357,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
|
|||||||
:kind="selected.kind"
|
:kind="selected.kind"
|
||||||
:prompt="selected.prompt"
|
:prompt="selected.prompt"
|
||||||
:meta="[selected.model, selected.name].filter(Boolean).join(' · ')"
|
:meta="[selected.model, selected.name].filter(Boolean).join(' · ')"
|
||||||
:meta-sub="[selected.resolution, selected.ratio, (selected.kind === 'video' ? selected.duration : ''), fmtSize(selected.size), fmtTs(selected.mtime)].filter(Boolean).join(' · ')"
|
:meta-sub="[selected.resolution, selected.ratio, (selected.kind === 'video' ? selected.duration : ''), (selected.deai ? '去AI特征' : ''), fmtSize(selected.size), fmtTs(selected.mtime)].filter(Boolean).join(' · ')"
|
||||||
:download-name="selected.name.split('/').pop()"
|
:download-name="selected.name.split('/').pop()"
|
||||||
@close="selected = null" />
|
@close="selected = null" />
|
||||||
|
|
||||||
|
|||||||
@@ -325,6 +325,10 @@ const sourcePill = (s) => ({
|
|||||||
<span class="text-white/25 mx-1.5">·</span>
|
<span class="text-white/25 mx-1.5">·</span>
|
||||||
<span class="text-white/40">参考 {{ e.refs }}</span>
|
<span class="text-white/40">参考 {{ e.refs }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="e.deai">
|
||||||
|
<span class="text-white/25 mx-1.5">·</span>
|
||||||
|
<span class="text-violet-300/70">去AI特征</span>
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-3.5 text-right text-xs tabular-nums align-middle whitespace-nowrap">
|
<td class="px-3 py-3.5 text-right text-xs tabular-nums align-middle whitespace-nowrap">
|
||||||
<span v-if="e.cost > 0" class="text-amber-300 font-medium">{{ e.cost }}</span>
|
<span v-if="e.cost > 0" class="text-amber-300 font-medium">{{ e.cost }}</span>
|
||||||
|
|||||||
@@ -471,7 +471,7 @@ async function loadHistory() {
|
|||||||
.map((e) => ({
|
.map((e) => ({
|
||||||
id: 'srv-' + e.id,
|
id: 'srv-' + e.id,
|
||||||
prompt: e.prompt, model: e.model, kind: e.kind,
|
prompt: e.prompt, model: e.model, kind: e.kind,
|
||||||
ratio: e.ratio, resolution: e.resolution, duration: e.duration,
|
ratio: e.ratio, resolution: e.resolution, duration: e.duration, deai: !!e.deai,
|
||||||
status: e.status === 'success' ? 'done' : 'running',
|
status: e.status === 'success' ? 'done' : 'running',
|
||||||
url: e.file ? generatedUrl(e.file) : '',
|
url: e.file ? generatedUrl(e.file) : '',
|
||||||
error: '',
|
error: '',
|
||||||
@@ -834,7 +834,7 @@ onUnmounted(() => {
|
|||||||
<div class="pg-cap text-[11px] leading-tight font-medium line-clamp-2 transition-colors"
|
<div class="pg-cap text-[11px] leading-tight font-medium line-clamp-2 transition-colors"
|
||||||
:class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''"
|
:class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''"
|
||||||
:title="item.prompt ? '点击复制提示词' : ''" @click.stop="copyPrompt(item)">{{ item.prompt }}</div>
|
:title="item.prompt ? '点击复制提示词' : ''" @click.stop="copyPrompt(item)">{{ item.prompt }}</div>
|
||||||
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
|
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.deai"> · 去AI</span><span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- pending / running -->
|
<!-- pending / running -->
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ const params = (e) => {
|
|||||||
const parts = [e.ratio || '—', e.resolution || '—']
|
const parts = [e.ratio || '—', e.resolution || '—']
|
||||||
if (e.duration) parts.push(e.duration)
|
if (e.duration) parts.push(e.duration)
|
||||||
if (e.refs > 0) parts.push(`参考 ${e.refs}`)
|
if (e.refs > 0) parts.push(`参考 ${e.refs}`)
|
||||||
|
if (e.deai) parts.push('去AI特征')
|
||||||
return parts.join(' · ')
|
return parts.join(' · ')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ onUnmounted(() => {
|
|||||||
<span v-if="e.resolution" class="text-emerald-300/90">{{ e.resolution }}</span>
|
<span v-if="e.resolution" class="text-emerald-300/90">{{ e.resolution }}</span>
|
||||||
<span v-if="e.ratio" class="text-white/40">{{ e.ratio }}</span>
|
<span v-if="e.ratio" class="text-white/40">{{ e.ratio }}</span>
|
||||||
<span v-if="e.kind === 'video' && e.duration" class="text-fuchsia-300/80">{{ e.duration }}</span>
|
<span v-if="e.kind === 'video' && e.duration" class="text-fuchsia-300/80">{{ e.duration }}</span>
|
||||||
|
<span v-if="e.deai" class="text-violet-300/80">去AI</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtTs(e.ts) }}<span v-if="e.elapsed_ms"> · {{ fmtMs(e.elapsed_ms) }}</span></div>
|
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtTs(e.ts) }}<span v-if="e.elapsed_ms"> · {{ fmtMs(e.elapsed_ms) }}</span></div>
|
||||||
@@ -387,7 +388,7 @@ onUnmounted(() => {
|
|||||||
:src="generatedUrl(lightbox.file)"
|
:src="generatedUrl(lightbox.file)"
|
||||||
:kind="lightbox.kind"
|
:kind="lightbox.kind"
|
||||||
:prompt="lightbox.prompt"
|
:prompt="lightbox.prompt"
|
||||||
:meta="[lightbox.model, lightbox.ratio, lightbox.resolution, lightbox.duration, fmtMs(lightbox.elapsed_ms)].filter(Boolean).join(' · ')"
|
:meta="[lightbox.model, lightbox.ratio, lightbox.resolution, lightbox.duration, (lightbox.deai ? '去AI特征' : ''), fmtMs(lightbox.elapsed_ms)].filter(Boolean).join(' · ')"
|
||||||
:download-name="lightbox.file"
|
:download-name="lightbox.file"
|
||||||
@close="lightbox = null" />
|
@close="lightbox = null" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user