This commit is contained in:
fengchuanhn@gmail.com
2026-05-17 12:54:04 +08:00
parent e6c8c18be0
commit f1fce871bb
28 changed files with 2566 additions and 235 deletions

View File

@@ -5,6 +5,11 @@ import { loginApi, logoutApi, registerApi } from "../api/auth.js";
const TOKEN_KEY = "aiclient_token";
const USER_KEY = "aiclient_current_user";
/** 管理员 */
export const ROLE_ADMIN = 1;
/** 代理 */
export const ROLE_AGENT = 2;
async function syncAuthSessionToRust(token, user) {
try {
await invoke("sync_auth_session", {
@@ -29,6 +34,7 @@ function applyTokenResponse(store, res, fallbackMessage) {
store.setSession(data.access_token, {
id: data.user.id,
username: data.user.username,
role_id: data.user.role_id,
});
return { ok: true, message: res.message || fallbackMessage };
@@ -49,6 +55,9 @@ export const useAuthStore = defineStore("auth", {
getters: {
isAuthenticated: (state) => Boolean(state.token),
roleId: (state) => state.currentUser?.role_id ?? null,
isAdmin: (state) => state.currentUser?.role_id === ROLE_ADMIN,
isAgent: (state) => state.currentUser?.role_id === ROLE_AGENT,
},
actions: {

View File

@@ -1,238 +1,486 @@
import { defineStore, acceptHMRUpdate } from "pinia";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import {
ensureAppConfigReady,
validateShareText,
DEFAULT_REWRITE_CONFIG,
} from "../config/videoPipeline.js";
const PIPELINE_SCRIPT = "douyin_pipeline.js";
const NODEJS_EVENT = "nodejs:event";
const defaultPublishPlatforms = () => [
{ label: "*音", checked: false, account: null },
{ label: "*手", checked: false, account: null },
{ label: "*频号", checked: false, account: null },
{ label: "*红书", checked: false, account: null },
];
export const useWorkflowStore = defineStore("workflow", {
state: () => ({
// 01 IP 深度学习
ipMode: "ipBrain",
ipBrainModalVisible: false,
ipBrainProfileUrl: "",
ipBrainCollecting: false,
ipBenchmarks: [],
ipTopics: [],
ipBrainSelectedBenchmarkId: null,
videoLinkModalVisible: false,
videoShareText: "",
videoRewriting: false,
/** 流水线进度文案(来自 quickjs:event progress */
rewriteProgress: "",
recognizedContent: "",
/** 自定义仿写提示词(可选,对应 zhenqianba rewritePrompt */
rewritePrompt: "",
videoType: "口播文案",
copyType: "人设型",
industryPersona: "",
productBusiness: "",
sellingPrice: "",
otherRequirements: "",
targetWords: 300,
// 视频文案编辑
scriptContent: "",
editLanguage: "中文(普通话)",
editWordCount: 300,
// 02 音视频生成
voiceEmotion: "自然",
speechRate: 1,
voiceLanguage: "中文(普通话)",
avatarSelect: null,
// 03 视频编辑
pipInPicture: false,
autoCutBreath: false,
greenScreen: false,
// 04 标题标签关键词
titleGenerated: "",
tagsGenerated: "",
keywordTab: "0",
keywordsFocus: "",
keywordsDescribe: "",
keywordsAction: "",
keywordsEmotion: "",
// 05 字幕和音乐
autoSubtitle: false,
smartSubtitle: true,
bgmEnabled: false,
bgmVolume: 30,
subtitleTemplate: null,
// 07 视频发布
publishPlatforms: defaultPublishPlatforms(),
}),
getters: {
ipModes: () => [
{ label: "IP学习", value: "ipBrain" },
{ label: "视频学习", value: "videoWrite" },
{ label: "爆款文案", value: "customPrompt" },
],
videoTypes: () => ["口播文案", "剧情文案", "带货文案"],
copyTypes: () => ["人设型", "卖点型", "故事型"],
voiceEmotions: () => ["自然", "热情", "沉稳"],
languages: () => ["中文(普通话)", "中文(粤语)", "English"],
avatarOptions: () => [{ label: "请选择形象", value: null }],
keywordCountFocus: (state) => countLines(state.keywordsFocus),
keywordCountDescribe: (state) => countLines(state.keywordsDescribe),
keywordCountAction: (state) => countLines(state.keywordsAction),
keywordCountEmotion: (state) => countLines(state.keywordsEmotion),
ipBenchmarkCount: (state) => state.ipBenchmarks.length,
selectedBenchmark: (state) =>
state.ipBenchmarks.find((b) => b.id === state.ipBrainSelectedBenchmarkId) ?? null,
},
actions: {
openIpBrainDialog() {
this.ipBrainModalVisible = true;
},
closeIpBrainDialog() {
this.ipBrainModalVisible = false;
},
async startIpBrainCollect() {
const url = this.ipBrainProfileUrl?.trim();
if (!url) {
return { ok: false, message: "请输入账号主页或分享链接" };
}
if (this.ipBenchmarks.length >= 5) {
return { ok: false, message: "最多添加 5 个对标账号" };
}
this.ipBrainCollecting = true;
try {
// TODO: 对接后端采集 API / Tauri 浏览器自动化
await new Promise((r) => setTimeout(r, 800));
const benchmark = {
id: `bm_${Date.now()}`,
url,
label: truncateUrl(url),
};
this.ipBenchmarks.push(benchmark);
this.ipBrainSelectedBenchmarkId = benchmark.id;
this.ipTopics = Array.from({ length: 5 }, (_, i) => ({
id: `topic_${Date.now()}_${i}`,
title: `示例视频标题 ${i + 1}(待对接真实采集)`,
}));
this.ipBrainProfileUrl = "";
this.ipBrainModalVisible = false;
return { ok: true, message: "采集完成" };
} finally {
this.ipBrainCollecting = false;
}
},
selectBenchmark(id) {
this.ipBrainSelectedBenchmarkId = id;
this.ipTopics = [];
},
openVideoLinkDialog() {
this.videoLinkModalVisible = true;
},
closeVideoLinkDialog() {
this.videoLinkModalVisible = false;
},
async startVideoRewrite() {
return executeVideoRewrite(this);
},
resetAll() {
this.$reset();
this.publishPlatforms = defaultPublishPlatforms();
},
clearData() {
this.resetAll();
},
},
});
export async function processDouyinShare(shareText) {
const result = await invoke("run_quickjs_script", {
scriptName: "douyin_pipeline.js",
params: {
shareText: shareText ?? "",
modelConfig: {
providerId: "openai",
modelId: "gpt-4o-mini",
apiUrl: "https://api.openai.com", // 或 dashscope/openrouter 等任意兼容端点
apiKey: "sk-...",
asrMode: "online",
aliyunApiKey: "sk-dashscope-...",
ossConfig: {
accessKeyId: "...",
accessKeySecret: "...",
bucket: "my-bucket",
region: "oss-cn-hangzhou",
},
},
rewriteConfig: { style: "幽默口播", tone: "活泼" },
customPrompt: null,
},
});
return result;
}
/**
* 一键仿写Node.js douyin_pipeline对齐 zhenqianba ipAgent:processDouyinShare
* @param {ReturnType<typeof useWorkflowStore>} store
*/
export async function executeVideoRewrite(store) {
const text = store.videoShareText?.trim();
if (!text) {
return { ok: false, message: "请粘贴视频分享文本或视频链接" };
const parsed = validateShareText(store.videoShareText);
if (!parsed.ok) {
return { ok: false, message: parsed.message };
}
const unsupported = detectUnsupportedPlatform(text);
const unsupported = detectUnsupportedPlatform(parsed.value);
if (unsupported) {
return { ok: false, message: unsupported };
}
store.videoRewriting = true;
try {
// TODO: 对接 FUNASR 语音识别与仿写 API
await new Promise((r) => setTimeout(r, 1000));
const cfgReady = await ensureAppConfigReady();
if (!cfgReady.ok) {
return { ok: false, message: cfgReady.message };
}
store.videoRewriting = true;
store.rewriteProgress = "正在解析和仿写...";
let unlisten = null;
try {
try {
unlisten = await listen(NODEJS_EVENT, (e) => {
const p = e.payload;
if (p?.type === "progress" && typeof p.status === "string") {
store.rewriteProgress = p.status;
}
});
} catch {
/* 非 Tauri 环境 */
}
const result = await invoke("run_nodejs_script", {
scriptName: PIPELINE_SCRIPT,
params: {
shareText: parsed.value,
rewriteConfig: { ...DEFAULT_REWRITE_CONFIG },
customPrompt: store.rewritePrompt?.trim() || null,
},
});
if (!result || result.success !== true) {
const err =
(result && (result.error || result.message)) ||
"解析或仿写失败,请检查链接与模型配置";
return { ok: false, message: String(err) };
}
const original = result.original || {};
const rewritten = result.rewritten || {};
store.recognizedContent = original.description || "";
store.scriptContent =
rewritten.fullContent || rewritten.description || "";
if (rewritten.title) {
store.titleGenerated = rewritten.title;
}
if (original.hashtags?.length) {
store.tagsGenerated = original.hashtags.join(" ");
}
store.recognizedContent =
"【示例识别结果】这里是 FUNASR 语音识别后的原始文案内容,待对接后端后将显示真实结果。\n\n" +
`来源:${truncateUrl(text, 48)}`;
store.videoShareText = "";
store.videoLinkModalVisible = false;
return { ok: true, message: "仿写完成" };
return {
ok: true,
message: "仿写完成,已为您生成新文案",
data: result,
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err || "未知错误");
return { ok: false, message: `仿写失败: ${msg}` };
} finally {
if (typeof unlisten === "function") {
unlisten();
}
store.videoRewriting = false;
store.rewriteProgress = "";
}
}
const UNSUPPORTED_PLATFORM_RULES = [
{ pattern: /kuaishou\.com|ksurl\.cn|gifshow\.com/i, name: "快手" },
{ pattern: /xiaohongshu\.com|xhslink\.com/i, name: "小红书" },
{ pattern: /bilibili\.com|b23\.tv/i, name: "B站" },
{ pattern: /channels\.weixin\.qq\.com|finder\.video\.qq\.com/i, name: "视频号" },
];
function detectUnsupportedPlatform(text) {
for (const { pattern, name } of UNSUPPORTED_PLATFORM_RULES) {
if (pattern.test(text)) {
return `${name}分享链接暂不支持,请使用抖音分享文本或视频链接`;
}
}
return null;
}
function truncateUrl(url, max = 36) {
if (url.length <= max) return url;
return `${url.slice(0, max)}`;
}
function countLines(text) {
if (!text?.trim()) return 0;
return text.split("\n").filter((line) => line.trim()).length;
}
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useWorkflowStore, import.meta.hot));
}