feat(canvas): 模型选择 - 支持彩色展示标签并优化目录与积分交互
This commit is contained in:
@@ -2,6 +2,7 @@ import type { ModelCapabilityChoice } from "@/components/model-protocol-picker";
|
||||
import { defaultModelCapabilityConfig, normalizeModelCapabilityConfig, type ModelCapabilityConfig } from "@/lib/model-capabilities";
|
||||
import { modelProtocolSupportsTokenBilling, type ModelProtocolDefinition } from "@/lib/model-protocols";
|
||||
import type { ChannelModel } from "@/services/api/wallet";
|
||||
import type { ModelTag } from "@/lib/model-tags";
|
||||
import { defaultPriceTier, legacyPriceTierToForm, priceTierToForm, type PriceTierFormValues } from "./channel-model-price-tier-form";
|
||||
|
||||
export type ChannelModelFormValues = {
|
||||
@@ -9,6 +10,7 @@ export type ChannelModelFormValues = {
|
||||
providerModelKey?: string;
|
||||
displayName?: string;
|
||||
channelLabel?: string;
|
||||
tags: ModelTag[];
|
||||
description?: string;
|
||||
icon?: string;
|
||||
capability: ModelCapabilityChoice;
|
||||
@@ -33,6 +35,7 @@ export function initialChannelModelValues(item: ChannelModel | null, protocols:
|
||||
providerModelKey: upstreamModel,
|
||||
displayName: item?.displayName || "",
|
||||
channelLabel: item?.channelLabel || "",
|
||||
tags: item?.tags?.map((tag) => ({ ...tag })) || [],
|
||||
description: item?.description || "",
|
||||
icon: item?.icon || "",
|
||||
capability,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { createAdminChannelModel, testAdminChannelModel, updateAdminChannelModel
|
||||
import type { ModelChannel } from "@/stores/use-config-store";
|
||||
import { defaultPriceTier, normalizeUpstreamModelKey, priceTierPayloadFromForm } from "./channel-model-price-tier-form";
|
||||
import { PriceTierFields } from "./channel-model-price-tier-fields";
|
||||
import { ChannelModelTagsEditor } from "./channel-model-tags-editor";
|
||||
import { changeChannelModelCapability, editorSectionForField, initialChannelModelValues, validateChannelModelPrices, validateChannelModelProtocol, type ChannelModelFormValues as FormValues, type EditorSection } from "./channel-model-editor-form";
|
||||
|
||||
export function ChannelModelEditor({
|
||||
@@ -112,6 +113,7 @@ export function ChannelModelEditor({
|
||||
providerModelKey: upstreamModel,
|
||||
displayName: values.displayName?.trim() || values.modelKey.trim(),
|
||||
channelLabel: values.channelLabel?.trim() || "",
|
||||
tags: (values.tags || []).map((tag) => ({ text: tag.text.trim(), color: tag.color })),
|
||||
description: values.description?.trim() || "",
|
||||
icon: values.icon?.trim() || "",
|
||||
capability: values.capability,
|
||||
@@ -275,7 +277,8 @@ export function ChannelModelEditor({
|
||||
description="命中的请求会优先使用价格档的上游 ID;修改上方上游模型 ID 时,只有与旧值相同的档位会自动跟随更新,其余保持不变。"
|
||||
/>
|
||||
) : null}
|
||||
<Form.Item name="description" label="模型描述" extra="在创作端二级渠道选项悬浮或聚焦时显示,可说明适用场景、渠道差异和注意事项。" rules={[{ max: 500, message: "模型描述不能超过 500 字" }]}>
|
||||
<ChannelModelTagsEditor />
|
||||
<Form.Item name="description" label="模型描述" extra="在创作端二级渠道选项中常驻显示,可说明适用场景、渠道差异和注意事项。" rules={[{ max: 500, message: "模型描述不能超过 500 字" }]}>
|
||||
<Input.TextArea rows={3} maxLength={500} showCount placeholder="填写此渠道模型的使用说明" />
|
||||
</Form.Item>
|
||||
</section>
|
||||
|
||||
34
web/src/pages/admin/components/channel-model-tags-editor.tsx
Normal file
34
web/src/pages/admin/components/channel-model-tags-editor.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Button, Form, Input, Select } from "antd";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import { ModelTags } from "@/components/model-tags";
|
||||
import { modelTagColors, type ModelTag } from "@/lib/model-tags";
|
||||
|
||||
export function ChannelModelTagsEditor() {
|
||||
const form = Form.useFormInstance();
|
||||
const tags: ModelTag[] = Form.useWatch("tags", form) || [];
|
||||
return <Form.Item label="展示标签" extra="最多 5 个,每个 12 字;展示在用户的二级渠道选项中,不改变实际计费。">
|
||||
<Form.List name="tags" rules={[{ validator: async (_, value: ModelTag[] = []) => {
|
||||
if (value.length > 5) throw new Error("最多添加 5 个标签");
|
||||
const texts = value.map((tag) => tag?.text?.trim()).filter(Boolean);
|
||||
if (new Set(texts).size !== texts.length) throw new Error("标签文字不能重复");
|
||||
} }]}>
|
||||
{(fields, { add, remove }, { errors }) => <div className="grid gap-2">
|
||||
{fields.map(({ key, name, ...rest }) => <div key={key} className="flex flex-wrap items-start gap-2">
|
||||
<Form.Item {...rest} name={[name, "text"]} className="mb-0 min-w-32 flex-1" rules={[{ validator: async (_, value: string) => {
|
||||
const length = Array.from(value?.trim() || "").length;
|
||||
if (length < 1 || length > 12) throw new Error("标签文字须为 1–12 字");
|
||||
} }]}>
|
||||
<Input aria-label={`标签 ${name + 1} 文字`} placeholder="例如:限时特价、官方1折" />
|
||||
</Form.Item>
|
||||
<Form.Item {...rest} name={[name, "color"]} className="mb-0 w-28" rules={[{ required: true, message: "请选择颜色" }]}>
|
||||
<Select aria-label={`标签 ${name + 1} 颜色`} options={modelTagColors.map((color) => ({ value: color.value, label: <span className="model-tag" data-color={color.value}>{color.label}</span> }))} />
|
||||
</Form.Item>
|
||||
<Button type="text" aria-label={`删除标签 ${name + 1}`} icon={<Trash2 className="size-4" />} onClick={() => remove(name)} />
|
||||
</div>)}
|
||||
<Button type="dashed" icon={<Plus className="size-4" />} disabled={fields.length >= 5} onClick={() => add({ text: "", color: "purple" })}>添加标签</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
{tags.some((tag) => tag?.text?.trim()) ? <div><span className="text-xs text-foreground/60">展示预览</span><ModelTags tags={tags.filter((tag) => tag?.text?.trim())} /></div> : null}
|
||||
</div>}
|
||||
</Form.List>
|
||||
</Form.Item>;
|
||||
}
|
||||
Reference in New Issue
Block a user