Merge branch 'master' of http://192.168.0.200/knowledge-base-bj/deerflow-code into dhk-dev
# Conflicts: # frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx
This commit is contained in:
commit
27e6b0b23e
718 files changed
+1168
-142636
No files matched your search
@@ -42,7 +42,7 @@ VITE_AIPI_BACKENP_URL=http://47.88.25.99:8000
|
||||
# VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.uat.4.cn
|
||||
VITE_NOTEBOOK_API_BASE_URL=http://47.88.25.99:8000
|
||||
VITE_AIPI_FRONTEND_URL=http://47.88.25.99:3000
|
||||
VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer
|
||||
VITE_AIPI_CONSUMER_URL=http://10.19.200.5:8002/consumer
|
||||
# A 系统跳转地址(人物/组织/事件等列表项打开链接)
|
||||
VITE_AIPI_PRESENT_URL=http://present.a.uat.4.cn/present/domain
|
||||
# B 系统知识库 iframe 根地址
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
</script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>CM 助手</title>
|
||||
</head>
|
||||
<title>CM 助手</title>
|
||||
<script src="/runtime-config.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"build:test": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode test",
|
||||
"build:network": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode network",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check:chrome109-css": "node scripts/check-chrome109-css.mjs",
|
||||
"check:legacy-chrome-css": "node scripts/check-chrome109-css.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
@@ -110,6 +112,7 @@
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"lightningcss": "^1.30.2",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.15",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
export default {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
import tailwindcss from "@tailwindcss/postcss";
|
||||
import legacyColorFallback from "./scripts/postcss-legacy-color-fallback.mjs";
|
||||
import lightningCssLegacyChrome from "./scripts/postcss-lightningcss-chrome109.mjs";
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
legacyColorFallback(),
|
||||
lightningCssLegacyChrome(),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
// 前端运行时配置文件
|
||||
//
|
||||
// 说明:
|
||||
// 1. 本文件会在打包时原样复制到 dist/runtime-config.js。
|
||||
// 2. 应用启动时会优先读取 window.__CM_RUNTIME_CONFIG__ 里的配置。
|
||||
// 3. 部署后如果需要切换接口地址,直接修改 dist/runtime-config.js 即可,不需要重新 pnpm build。
|
||||
// 4. 这里的默认值来自 .env.network;如某个环境不用某项,可以留空字符串。
|
||||
// 5. 布尔开关也用字符串形式填写:"true" / "false"。
|
||||
window.__CM_RUNTIME_CONFIG__ = {
|
||||
// DeerFlow Gateway 地址:前端自有后端接口,例如 /api/agents、/api/auth、/api/threads 等。
|
||||
// VITE_BACKEND_BASE_URL: "http://10.19.200.5:2026",
|
||||
|
||||
// LangGraph 流式对话接口地址:普通问答、智能体对话等流式能力使用。
|
||||
// VITE_LANGGRAPH_BASE_URL: "http://10.19.200.5:2026/api",
|
||||
|
||||
// Canvas/AI 写作独立 LangGraph 地址:为空时前端会回退使用 VITE_LANGGRAPH_BASE_URL。
|
||||
VITE_CANVAS_LANGGRAPH_BASE_URL: "",
|
||||
|
||||
// 轻应用「新建笔记」系列接口地址。
|
||||
VITE_NOTE_API_BASE_URL: "http://47.88.25.99:8000",
|
||||
|
||||
// CMZS 后端接口:任务管理、任务分析、三情分析、行动分析等业务接口。
|
||||
VITE_API_BASE_URL: "https://ch1.b.uat.4.cn/zncm",
|
||||
|
||||
// 佳来模型对话接口。
|
||||
VITE_AIPI_CHAT_URL: "https://ch1.b.uat.4.cn/gpt",
|
||||
|
||||
// 多轮检索/生成接口。
|
||||
VITE_AIPI_GENERATE_CHAT_URL: "https://ch1.b.uat.4.cn/jialai_b1_chat",
|
||||
|
||||
// 任务问答接口。
|
||||
VITE_AIPI_TASK_CHAT_URL: "https://ch1.b.uat.4.cn/qq_b1_chat",
|
||||
|
||||
// B 系统知识库前端地址:用于打开知识详情、原文详情等。
|
||||
VITE_AIPI_KNOWLEDGE_URL: "https://chlib.b.uat.4.cn/knowlibrary",
|
||||
|
||||
// B1 COH 系统地址。
|
||||
VITE_AIPI_B1COH_URL: "https://ch1.b.uat.4.cn/web",
|
||||
|
||||
// B2 行为体/网络资源系统地址。
|
||||
VITE_AIPI_B2ACTOR_URL: "https://ty.b.uat.4.cn",
|
||||
|
||||
// B2 网络 XWT iframe 路径。
|
||||
VITE_AIPI_B2ACTOR_XWT_PATH: "/fromB/toXWT?isFrame=1",
|
||||
|
||||
// 知识库 iframe 地址换取接口。
|
||||
VITE_AIPI_KNOWLEDGE_IFRAME_API_URL: "https://ch1.b.uat.4.cn/knowes/api/iframeKnowledgeUrl",
|
||||
|
||||
// 知识库 iframe 皮肤参数。
|
||||
VITE_AIPI_KNOWLEDGE_IFRAME_SKIN: "red",
|
||||
|
||||
// MopeChat 接口地址。
|
||||
VITE_AIPI_MOPECHAT_URL: "https://ch1.b.uat.4.cn/likun_b1_mope",
|
||||
|
||||
// 工作室/空间后端接口:空间列表、空间详情、素材上传等使用。
|
||||
VITE_AIPI_BACKENP_URL: "http://47.88.25.99:8000",
|
||||
|
||||
// 工作室/空间后端别名:兼容旧配置命名,通常与 VITE_AIPI_BACKENP_URL 保持一致。
|
||||
VITE_NOTEBOOK_API_BASE_URL: "http://47.88.25.99:8000",
|
||||
|
||||
// 工作室/空间前端地址:用于 auth/callback、新建对话等跳转。
|
||||
VITE_AIPI_FRONTEND_URL: "http://47.88.25.99:3000",
|
||||
|
||||
// Consumer 接口:深链任务详情 cop-task-detail、研判情报报告 selectSqReport 等使用。
|
||||
VITE_AIPI_CONSUMER_URL: "http://10.19.200.5:8002/consumer",
|
||||
|
||||
// A 系统跳转地址:人物、组织、事件等列表项打开链接使用。
|
||||
VITE_AIPI_PRESENT_URL: "http://present.a.uat.4.cn/present/domain",
|
||||
|
||||
// B 系统 knowes 根地址。
|
||||
VITE_AIPI_KNOWES_URL: "http://ch1.b.uat.4.cn/knowes",
|
||||
|
||||
// MaxKey 鉴权地址:onlineTicket 换 token 使用。
|
||||
VITE_AUTH_BASE_URL: "https://auth.y.sit.4.cn",
|
||||
|
||||
// PG 中任务 iframe 地址。
|
||||
VITE_AIPI_PGLINKTO_URL: "https://pg.b.uat.4.cn",
|
||||
|
||||
// XK 中任务 iframe 地址。
|
||||
VITE_AIPI_XKLINKTO_URL: "https://kz.b.uat.4.cn",
|
||||
|
||||
// 是否使用真实任务看板数据:true=真实接口;false=本地 mock。
|
||||
VITE_USE_REAL_DATA: "true",
|
||||
|
||||
// 登录深链任务详情是否使用本地 mock:false=真实接口;true=本地 mock。
|
||||
VITE_MOCK_COP_TASK_DETAIL: "false",
|
||||
|
||||
// 大屏绘制 selectSqReport 是否使用本地 mock:false=真实接口;true=本地 mock。
|
||||
VITE_MOCK_SQ_REPORT: "false",
|
||||
|
||||
// 静态站点模式开关;一般部署业务系统时保持 false。
|
||||
VITE_STATIC_WEBSITE_ONLY: "false",
|
||||
|
||||
// GitHub OAuth Token;不使用 GitHub 相关能力时留空。
|
||||
VITE_GITHUB_OAUTH_TOKEN: "",
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import postcss from "postcss";
|
||||
|
||||
const unsupportedColor = /(?:color-mix|oklch|oklab|lab|lch)\(/i;
|
||||
const assetsDir = path.resolve("dist/assets");
|
||||
|
||||
if (!fs.existsSync(assetsDir)) {
|
||||
throw new Error("dist/assets 不存在,请先运行 pnpm build");
|
||||
}
|
||||
|
||||
const cssFiles = fs
|
||||
.readdirSync(assetsDir)
|
||||
.filter((file) => file.endsWith(".css"));
|
||||
|
||||
if (cssFiles.length === 0) {
|
||||
throw new Error("dist/assets 中没有 CSS 构建产物");
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
let modernDeclarations = 0;
|
||||
|
||||
const requiredLegacyFallbacks = [
|
||||
{
|
||||
file: "src/components/ui/textarea.tsx",
|
||||
pattern: /rows=\{rows\s*\?\?\s*3\}/,
|
||||
description: "Textarea needs a native rows fallback",
|
||||
},
|
||||
{
|
||||
file: "src/components/ai-elements/prompt-input.tsx",
|
||||
pattern: /<InputGroup className="h-auto min-h-16">/,
|
||||
description: "Prompt textarea must not rely on :has() for its parent height",
|
||||
},
|
||||
];
|
||||
|
||||
for (const file of cssFiles) {
|
||||
const root = postcss.parse(
|
||||
fs.readFileSync(path.join(assetsDir, file), "utf8"),
|
||||
{ from: file },
|
||||
);
|
||||
|
||||
root.walkDecls((declaration) => {
|
||||
if (!unsupportedColor.test(declaration.value)) return;
|
||||
modernDeclarations += 1;
|
||||
|
||||
let ancestor = declaration.parent;
|
||||
while (ancestor) {
|
||||
if (
|
||||
ancestor.type === "atrule" &&
|
||||
ancestor.name === "supports" &&
|
||||
unsupportedColor.test(ancestor.params)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ancestor = ancestor.parent;
|
||||
}
|
||||
|
||||
let previous = declaration.prev();
|
||||
while (previous?.type === "comment") previous = previous.prev();
|
||||
const hasSafeFallback =
|
||||
!declaration.prop.startsWith("--") &&
|
||||
previous?.type === "decl" &&
|
||||
previous.prop === declaration.prop &&
|
||||
!unsupportedColor.test(previous.value);
|
||||
|
||||
if (!hasSafeFallback) {
|
||||
failures.push(
|
||||
`${file}:${declaration.source?.start?.line ?? "?"} ${declaration.prop}: ${declaration.value}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function walk(directory) {
|
||||
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
return entry.isDirectory() ? walk(fullPath) : [fullPath];
|
||||
});
|
||||
}
|
||||
|
||||
for (const file of walk(path.resolve("src"))) {
|
||||
if (!/\.[jt]sx?$/.test(file)) continue;
|
||||
const source = fs.readFileSync(file, "utf8");
|
||||
if (unsupportedColor.test(source)) {
|
||||
failures.push(
|
||||
`${path.relative(process.cwd(), file)} contains a runtime-only modern color function`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const fallback of requiredLegacyFallbacks) {
|
||||
const source = fs.readFileSync(path.resolve(fallback.file), "utf8");
|
||||
if (!fallback.pattern.test(source)) {
|
||||
failures.push(`${fallback.file}: ${fallback.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error("Legacy Chrome CSS compatibility check failed:\n");
|
||||
console.error(failures.slice(0, 50).join("\n"));
|
||||
if (failures.length > 50) {
|
||||
console.error(`... and ${failures.length - 50} more`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Legacy Chrome (100+) CSS compatibility check passed (${modernDeclarations} guarded modern declarations).`,
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Tailwind 4 gracefully falls back from color-mix() to the base semantic
|
||||
* color, but that turns utilities such as bg-primary/5 into an opaque fill on
|
||||
* Chrome 109. Replace that fallback with an sRGB-channel based alpha color.
|
||||
* The modern color-mix() declaration remains in its @supports block.
|
||||
*/
|
||||
|
||||
const LEGACY_RGB_TOKENS = new Set([
|
||||
"accent",
|
||||
"background",
|
||||
"border",
|
||||
"brand-200",
|
||||
"card",
|
||||
"destructive",
|
||||
"foreground",
|
||||
"input",
|
||||
"muted",
|
||||
"muted-foreground",
|
||||
"primary",
|
||||
"primary-foreground",
|
||||
"ring",
|
||||
"secondary",
|
||||
"sidebar-accent",
|
||||
"sidebar-border",
|
||||
"sidebar-foreground",
|
||||
"workspace-brand",
|
||||
]);
|
||||
|
||||
const TRANSPARENT_MIX_RE =
|
||||
/color-mix\(\s*in\s+[\w-]+\s*,\s*var\(--([\w-]+)\)\s+([\d.]+)%\s*,\s*transparent\s*\)/i;
|
||||
|
||||
function legacyAlphaColor(value) {
|
||||
const match = value.match(TRANSPARENT_MIX_RE);
|
||||
if (!match || !LEGACY_RGB_TOKENS.has(match[1])) return null;
|
||||
|
||||
const [, token, percentage] = match;
|
||||
return `rgb(var(--legacy-${token}-rgb) / ${percentage}%)`;
|
||||
}
|
||||
|
||||
export default function legacyColorFallback() {
|
||||
return {
|
||||
postcssPlugin: "postcss-legacy-color-fallback",
|
||||
OnceExit(root) {
|
||||
root.walkAtRules("supports", (atRule) => {
|
||||
if (!atRule.params.includes("color-mix")) return;
|
||||
|
||||
atRule.walkDecls((modernDeclaration) => {
|
||||
const fallbackValue = legacyAlphaColor(modernDeclaration.value);
|
||||
if (!fallbackValue) return;
|
||||
|
||||
// Tailwind emits the old-browser declaration immediately before
|
||||
// the nested @supports block, in the same utility/variant rule.
|
||||
let fallbackDeclaration = atRule.prev();
|
||||
while (fallbackDeclaration?.type === "comment") {
|
||||
fallbackDeclaration = fallbackDeclaration.prev();
|
||||
}
|
||||
|
||||
if (
|
||||
fallbackDeclaration?.type === "decl" &&
|
||||
fallbackDeclaration.prop === modernDeclaration.prop
|
||||
) {
|
||||
fallbackDeclaration.value = fallbackValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
legacyColorFallback.postcss = true;
|
||||
@@ -0,0 +1,33 @@
|
||||
import postcss from "postcss";
|
||||
import { transform } from "lightningcss";
|
||||
|
||||
const LEGACY_CHROME = 100 << 16;
|
||||
|
||||
/**
|
||||
* Keep Vite's PostCSS pipeline (required by Tailwind 4), then compile the
|
||||
* resulting CSS to Chrome 100-compatible syntax for both dev and production.
|
||||
*/
|
||||
export default function lightningCssLegacyChrome() {
|
||||
return {
|
||||
postcssPlugin: "postcss-lightningcss-legacy-chrome",
|
||||
OnceExit(root, { result }) {
|
||||
const filename = root.source?.input.file ?? result.opts.from ?? "style.css";
|
||||
const output = transform({
|
||||
filename,
|
||||
code: Buffer.from(root.toString()),
|
||||
targets: { chrome: LEGACY_CHROME },
|
||||
minify: false,
|
||||
});
|
||||
const compiled = postcss.parse(output.code.toString(), { from: filename });
|
||||
|
||||
root.removeAll();
|
||||
root.append(compiled.nodes);
|
||||
|
||||
for (const warning of output.warnings) {
|
||||
result.warn(warning.message, { plugin: "lightningcss" });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
lightningCssLegacyChrome.postcss = true;
|
||||
@@ -846,7 +846,10 @@ export const PromptInput = ({
|
||||
ref={formRef}
|
||||
{...props}
|
||||
>
|
||||
<InputGroup>{children}</InputGroup>
|
||||
{/* Do not rely on InputGroup's :has(textarea) selector here: Chrome
|
||||
versions before 105 ignore :has(), retain h-9, and clip the whole
|
||||
multi-line composer to a single row. */}
|
||||
<InputGroup className="h-auto min-h-16">{children}</InputGroup>
|
||||
</form>
|
||||
</PromptInputValidationContext.Provider>
|
||||
);
|
||||
@@ -873,16 +876,48 @@ export type PromptInputTextareaProps = ComponentProps<
|
||||
typeof InputGroupTextarea
|
||||
>;
|
||||
|
||||
export const PromptInputTextarea = ({
|
||||
onChange,
|
||||
className,
|
||||
placeholder = "What would you like to know?",
|
||||
...props
|
||||
}: PromptInputTextareaProps) => {
|
||||
const controller = useOptionalPromptInputController();
|
||||
const attachments = usePromptInputAttachments();
|
||||
const sanitizeIncomingFiles = usePromptInputValidation();
|
||||
const [isComposing, setIsComposing] = useState(false);
|
||||
export const PromptInputTextarea = ({
|
||||
onChange,
|
||||
onInput,
|
||||
className,
|
||||
placeholder = "What would you like to know?",
|
||||
...props
|
||||
}: PromptInputTextareaProps) => {
|
||||
const controller = useOptionalPromptInputController();
|
||||
const attachments = usePromptInputAttachments();
|
||||
const sanitizeIncomingFiles = usePromptInputValidation();
|
||||
const [isComposing, setIsComposing] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const resizeForLegacyChrome = useCallback((textarea: HTMLTextAreaElement) => {
|
||||
if (
|
||||
typeof CSS !== "undefined" &&
|
||||
typeof CSS.supports === "function" &&
|
||||
CSS.supports("field-sizing", "content")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chrome 109 ignores field-sizing: content. Preserve the same 4rem–12rem
|
||||
// autosize range used by min-h-16/max-h-48 instead of leaving the prompt
|
||||
// stuck at its minimum height.
|
||||
textarea.style.height = "auto";
|
||||
const height = Math.max(64, Math.min(textarea.scrollHeight, 192));
|
||||
textarea.style.height = `${height}px`;
|
||||
textarea.style.overflowY = textarea.scrollHeight > 192 ? "auto" : "hidden";
|
||||
}, []);
|
||||
|
||||
// onInput does not run for a controlled value filled by a suggested question
|
||||
// or restored draft. Resize once after every render in legacy Chrome too.
|
||||
useEffect(() => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
const frame = window.requestAnimationFrame(() => {
|
||||
resizeForLegacyChrome(textarea);
|
||||
});
|
||||
return () => window.cancelAnimationFrame(frame);
|
||||
}, [controller?.textInput.value, resizeForLegacyChrome]);
|
||||
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
|
||||
if (e.key === "Enter") {
|
||||
@@ -962,12 +997,18 @@ export const PromptInputTextarea = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<InputGroupTextarea
|
||||
className={cn("field-sizing-content max-h-48 min-h-16", className)}
|
||||
name="message"
|
||||
<InputGroupTextarea
|
||||
className={cn("field-sizing-content max-h-48 min-h-16", className)}
|
||||
name="message"
|
||||
ref={textareaRef}
|
||||
rows={3}
|
||||
onCompositionEnd={() => setIsComposing(false)}
|
||||
onCompositionStart={() => setIsComposing(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyDown={handleKeyDown}
|
||||
onInput={(event) => {
|
||||
resizeForLegacyChrome(event.currentTarget);
|
||||
onInput?.(event);
|
||||
}}
|
||||
onPaste={handlePaste}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
import { queryClient } from "@/components/query-client-provider";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import Empty from "./empty";
|
||||
@@ -303,11 +304,11 @@ const DocumentUploadPopupContent: FC<{
|
||||
const searchSpaceId = String(searchSpace?.id);
|
||||
|
||||
// 从环境变量获取配置
|
||||
const backendUrl = import.meta.env.VITE_AIPI_BACKENP_URL || "http://localhost:8001";
|
||||
const consumerUrl = import.meta.env.VITE_AIPI_CONSUMER_URL || "http://ch1.b.uat.4.cn/consumer";
|
||||
const presentUrl = import.meta.env.VITE_AIPI_PRESENT_URL || "http://present.a.uat.4.cn/present/domain";
|
||||
const knowesUrl = import.meta.env.VITE_AIPI_KNOWES_URL || "http://ch1.b.uat.4.cn/knowes";
|
||||
const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL || "http://chlib.b.uat.4.cn/knowlibrary";
|
||||
const backendUrl = readRuntimeEnv("VITE_AIPI_BACKENP_URL") || "http://localhost:8001";
|
||||
const consumerUrl = readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || "http://ch1.b.uat.4.cn/consumer";
|
||||
const presentUrl = readRuntimeEnv("VITE_AIPI_PRESENT_URL") || "http://present.a.uat.4.cn/present/domain";
|
||||
const knowesUrl = readRuntimeEnv("VITE_AIPI_KNOWES_URL") || "http://ch1.b.uat.4.cn/knowes";
|
||||
const knowledgeUrl = readRuntimeEnv("VITE_AIPI_KNOWLEDGE_URL") || "http://chlib.b.uat.4.cn/knowlibrary";
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { authenticatedFetch } from "@/components/assistant-ui/document-upload-popup";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
|
||||
|
||||
interface NetworkActorProps {
|
||||
@@ -186,9 +187,9 @@ export function NetworkActor({ inputValue, searchSpaceId, onSuccess }: NetworkAc
|
||||
const [actorCount, setActorCount] = useState<number>(0);
|
||||
const [directionType, setDirectionType] = useState<string>('TW');
|
||||
const directionValue = useRef<string>('TW');
|
||||
const consumerUrl = import.meta.env.VITE_AIPI_CONSUMER_URL || 'https://ch1.b.uat.4.cn/consumer';
|
||||
const consumerUrl = readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || 'https://ch1.b.uat.4.cn/consumer';
|
||||
const backendUrl = typeof window !== 'undefined'
|
||||
? (import.meta.env.VITE_BACKEND_BASE_URL || 'http://localhost:8001')
|
||||
? (readRuntimeEnv("VITE_BACKEND_BASE_URL") || 'http://localhost:8001')
|
||||
: 'http://localhost:8001';
|
||||
const [selectedSearchSources, setSelectedSearchSources] = useState<UserDataItem[]>([]);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
|
||||
@@ -508,8 +508,9 @@ export default function ProgressiveSkillsAnimation() {
|
||||
"deploying",
|
||||
"done",
|
||||
].includes(phase) && (
|
||||
<div className="text-base text-zinc-300">
|
||||
<span className="text-purple-400">✨</span> Found 3 skills
|
||||
<div className="flex items-center gap-2 text-base text-zinc-300">
|
||||
<Sparkles size={16} className="text-purple-400" />
|
||||
<span>Found 3 skills</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -526,8 +527,9 @@ export default function ProgressiveSkillsAnimation() {
|
||||
].includes(phase) && (
|
||||
<div className="mt-4">
|
||||
<hr className="mb-3 border-zinc-700" />
|
||||
<div className="mb-3 text-zinc-300">
|
||||
🔬 Researching...
|
||||
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
|
||||
<Search size={16} className="text-blue-400" />
|
||||
<span>Researching...</span>
|
||||
</div>
|
||||
<div className="mb-3 space-y-2">
|
||||
{/* Loading SKILL.md */}
|
||||
@@ -623,7 +625,10 @@ export default function ProgressiveSkillsAnimation() {
|
||||
className="mt-4"
|
||||
>
|
||||
<hr className="mb-3 border-zinc-700" />
|
||||
<div className="mb-3 text-zinc-300">🔨 Building...</div>
|
||||
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
|
||||
<FileText size={16} className="text-zinc-400" />
|
||||
<span>Building...</span>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center gap-2 pl-4 text-zinc-400">
|
||||
<FileText size={16} />
|
||||
<span>Loading frontend-design/SKILL.md...</span>
|
||||
@@ -653,7 +658,10 @@ export default function ProgressiveSkillsAnimation() {
|
||||
className="mt-4"
|
||||
>
|
||||
<hr className="mb-3 border-zinc-700" />
|
||||
<div className="mb-3 text-zinc-300">🚀 Deploying...</div>
|
||||
<div className="mb-3 inline-flex items-center gap-2 text-zinc-300">
|
||||
<Terminal size={16} className="text-zinc-400" />
|
||||
<span>Deploying...</span>
|
||||
</div>
|
||||
<div className="mb-3 space-y-2">
|
||||
<div className="flex items-center gap-2 pl-4 text-zinc-400">
|
||||
<FileText size={16} />
|
||||
@@ -676,8 +684,9 @@ export default function ProgressiveSkillsAnimation() {
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="mt-4 rounded-xl border border-green-500/30 bg-green-500/10 p-4"
|
||||
>
|
||||
<div className="text-lg font-medium text-green-500">
|
||||
✅ Live at biotech-startup.vercel.app
|
||||
<div className="inline-flex items-center gap-2 text-lg font-medium text-green-500">
|
||||
<Check size={18} />
|
||||
<span>Live at biotech-startup.vercel.app</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -35,27 +35,27 @@ export function SandboxSection({ className }: { className?: string }) {
|
||||
$ pip install -r requirements.txt
|
||||
</TypingAnimation>
|
||||
<AnimatedSpan delay={2000} className="text-green-500">
|
||||
✔ Installed pygame
|
||||
[ok] Installed pygame
|
||||
</AnimatedSpan>
|
||||
|
||||
<TypingAnimation delay={2400}>
|
||||
$ write game.py --lines 156
|
||||
</TypingAnimation>
|
||||
<AnimatedSpan delay={3200} className="text-blue-500">
|
||||
✔ Written 156 lines
|
||||
[ok] Written 156 lines
|
||||
</AnimatedSpan>
|
||||
|
||||
<TypingAnimation delay={3600}>
|
||||
$ python game.py --test
|
||||
</TypingAnimation>
|
||||
<AnimatedSpan delay={4200} className="text-green-500">
|
||||
✔ All sprites loaded
|
||||
[ok] All sprites loaded
|
||||
</AnimatedSpan>
|
||||
<AnimatedSpan delay={4500} className="text-green-500">
|
||||
✔ Physics engine OK
|
||||
[ok] Physics engine OK
|
||||
</AnimatedSpan>
|
||||
<AnimatedSpan delay={4800} className="text-green-500">
|
||||
✔ 60 FPS stable
|
||||
[ok] 60 FPS stable
|
||||
</AnimatedSpan>
|
||||
|
||||
{/* Scene 2: Data Analysis */}
|
||||
|
||||
@@ -144,21 +144,24 @@ function InputGroupInput({
|
||||
);
|
||||
}
|
||||
|
||||
function InputGroupTextarea({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"textarea">) {
|
||||
const InputGroupTextarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentProps<"textarea">
|
||||
>(function InputGroupTextarea({ className, ...props }, ref) {
|
||||
return (
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
InputGroupTextarea.displayName = "InputGroupTextarea";
|
||||
|
||||
export {
|
||||
InputGroup,
|
||||
|
||||
@@ -2,19 +2,28 @@ import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground/60 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
const Textarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentProps<"textarea">
|
||||
>(function Textarea({ className, rows, ...props }, ref) {
|
||||
// `field-sizing` is not available in older Chrome. Keep a native rows
|
||||
// fallback so a multi-line editor never collapses into a single line.
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
ref={ref}
|
||||
rows={rows ?? 3}
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground/60 flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] ",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
@@ -3,7 +3,7 @@ export function EmptyDocumentState() {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center bg-white px-6 text-center dark:bg-slate-900">
|
||||
<div className="rounded-2xl border border-dashed border-slate-300 bg-slate-50 px-10 py-12 sm:px-12 dark:border-slate-700 dark:bg-slate-800/50">
|
||||
<div className="mb-4 text-[40px] leading-none">📚</div>
|
||||
<div className="mb-4 text-[28px] font-semibold leading-none text-slate-500 dark:text-slate-300">文档</div>
|
||||
<div className="mb-2 text-lg font-bold text-slate-700 dark:text-slate-200">
|
||||
选择一篇文档开始阅读
|
||||
</div>
|
||||
|
||||
@@ -213,7 +213,7 @@ const errorToastDarkOptions = {
|
||||
</div>
|
||||
<div className="flex flex-1 flex-wrap items-center gap-1.5">
|
||||
{summary && Object.keys(summary.by_category).length === 0 && (
|
||||
<span className="text-muted-foreground text-sm">暂无失败记录 🎉</span>
|
||||
<span className="text-muted-foreground text-sm">暂无失败记录</span>
|
||||
)}
|
||||
{summary &&
|
||||
Object.entries(summary.by_category)
|
||||
|
||||
@@ -35,7 +35,7 @@ export function AgentWelcome({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="bg-primary/10 ring-primary/15 mb-2 flex h-[72px] w-[72px] items-center justify-center rounded-3xl ring-1">
|
||||
<div className="agent-avatar bg-primary/10 ring-primary/15 mb-2 flex h-[72px] w-[72px] items-center justify-center rounded-3xl ring-1">
|
||||
<BotIcon className="text-primary h-9 w-9" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold tracking-tight">
|
||||
|
||||
@@ -839,7 +839,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
<CardHeader className="min-w-0 pb-2">
|
||||
<div className="flex min-w-0 items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
||||
<div className="bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
|
||||
<div className="agent-avatar bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
|
||||
<BotIcon className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 overflow-hidden">
|
||||
@@ -894,7 +894,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
|
||||
const listLayout = (
|
||||
<div className="group flex min-w-0 items-center gap-3 rounded-lg border p-2.5 transition-shadow hover:shadow-sm dark:border-[#213D5E] dark:bg-[#02244C]">
|
||||
<div className="bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
|
||||
<div className="agent-avatar bg-primary/10 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-lg">
|
||||
<BotIcon className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 overflow-hidden">
|
||||
@@ -980,7 +980,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
? "正在加载 SOUL.md..."
|
||||
: "输入 SOUL.md 内容"
|
||||
}
|
||||
className="max-h-[45vh] min-h-48 resize-y font-mono text-xs"
|
||||
className="max-h-[45vh] min-h-48 resize-y text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -264,7 +264,7 @@ export function AgentChatSidebar({ activeAgentId }: { activeAgentId?: string })
|
||||
isActive
|
||||
? isDark
|
||||
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
|
||||
: "border-primary/50 bg-primary/10 shadow-sm"
|
||||
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
|
||||
: isDark
|
||||
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
|
||||
: "border-transparent hover:border-border hover:bg-accent/50",
|
||||
|
||||
@@ -520,7 +520,7 @@ export function AgentDetailPanel({
|
||||
{/* ── Identity / 基础信息 ─────────────────────────────────────────── */}
|
||||
<section className="bg-card/60 rounded-xl border p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
|
||||
<div className="agent-avatar bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
|
||||
<BotIcon className="text-primary size-6" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 space-y-1.5">
|
||||
@@ -830,8 +830,8 @@ export function AgentDetailPanel({
|
||||
const panelChrome = (collapseButton: ReactNode) => (
|
||||
<>
|
||||
<div className="flex h-12 shrink-0 items-center justify-between border-b px-4">
|
||||
<span className="text-sm font-semibold">智能体信息</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="shrink-0 whitespace-nowrap text-sm font-semibold">智能体信息</span>
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
{headerActions}
|
||||
{compactAppearanceActions}
|
||||
{collapseButton}
|
||||
|
||||
@@ -138,7 +138,7 @@ export function AgentModuleSettingsDialog({
|
||||
key={agent.id}
|
||||
className="flex items-start gap-3 rounded-xl border border-border/60 bg-card p-3"
|
||||
>
|
||||
<div className="bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg">
|
||||
<div className="agent-avatar bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg">
|
||||
<Bot className="size-4" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -223,7 +223,7 @@ export function AgentModuleSidebar({
|
||||
isActive
|
||||
? isDark
|
||||
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
|
||||
: "border-primary/50 bg-primary/10 shadow-sm"
|
||||
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
|
||||
: isDark
|
||||
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
|
||||
: "border-transparent hover:border-border hover:bg-accent/50",
|
||||
|
||||
@@ -20,16 +20,16 @@ export function CompactWelcome({ className }: { className?: string }) {
|
||||
className="mb-3.5 inline-flex items-center justify-center rounded-full p-3"
|
||||
style={{
|
||||
backgroundColor:
|
||||
"color-mix(in srgb, var(--workspace-brand) 5%, transparent)",
|
||||
"rgb(var(--legacy-workspace-brand-rgb) / 5%)",
|
||||
border:
|
||||
"1px solid color-mix(in srgb, var(--workspace-brand) 12%, transparent)",
|
||||
"1px solid rgb(var(--legacy-workspace-brand-rgb) / 12%)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex size-14 items-center justify-center rounded-2xl shadow-md"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(to top right, var(--workspace-brand), color-mix(in srgb, var(--workspace-brand) 55%, white))",
|
||||
"linear-gradient(to top right, var(--workspace-brand), var(--brand-200))",
|
||||
}}
|
||||
>
|
||||
<span className="text-2xl font-black tracking-wide text-white italic">
|
||||
@@ -38,8 +38,8 @@ export function CompactWelcome({ className }: { className?: string }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="flex items-center justify-center gap-2 text-2xl font-bold tracking-tight text-foreground/90">
|
||||
<span className="text-2xl">👋</span> 欢迎使用CM助手
|
||||
<h1 className="flex items-center justify-center text-2xl font-bold tracking-tight text-foreground/90">
|
||||
欢迎使用CM助手
|
||||
</h1>
|
||||
<p className="text-muted-foreground mx-auto mt-2 max-w-sm text-xs leading-relaxed">
|
||||
基于先进深度推理模型,为您提供高效的市场分析、专业报告撰写、调研设计与协同脑暴支持。
|
||||
|
||||
@@ -320,7 +320,7 @@ export function GeneralQaSidebar() {
|
||||
className={cn(
|
||||
"flex h-10 min-w-0 flex-1 items-center justify-center gap-1.5 rounded-[5px] text-[14px] font-semibold transition-colors",
|
||||
"border border-solid",
|
||||
"bg-[color-mix(in_srgb,var(--primary)_14%,transparent)] hover:bg-[color-mix(in_srgb,var(--primary)_24%,transparent)]",
|
||||
"bg-[rgb(var(--legacy-primary-rgb)/14%)] hover:bg-[rgb(var(--legacy-primary-rgb)/24%)]",
|
||||
)}
|
||||
>
|
||||
<Plus className="size-4 stroke-[2.5]" />
|
||||
@@ -353,7 +353,7 @@ export function GeneralQaSidebar() {
|
||||
isActive
|
||||
? isDark
|
||||
? "border-[#2b8ffe] bg-[#0b4fae]/65 shadow-[0_0_0_1px_rgba(43,143,254,0.5),0_12px_28px_rgba(37,128,255,0.25)]"
|
||||
: "border-primary/50 bg-primary/10 shadow-sm"
|
||||
: "agent-sidebar-card--active border-primary/50 bg-primary/10 shadow-sm"
|
||||
: isDark
|
||||
? "border-[#2b8ffe]/20 bg-[#042b59]/35 hover:border-[#2b8ffe]/60 hover:bg-[#063b77]/60"
|
||||
: "border-transparent hover:border-border hover:bg-accent/50",
|
||||
|
||||
@@ -613,7 +613,7 @@ export function InputBox({
|
||||
: "*:data-[slot='input-group']:rounded-3xl",
|
||||
// 紧凑落地态(CM 参考图):白卡 + 细边 + 轻阴影 + 主色聚焦环(无旋转彩边)
|
||||
compact && isNewThread
|
||||
? "rounded-2xl border border-gray-200/70 bg-white overflow-hidden shadow-[0_4.5px_22px_rgba(0,0,0,0.025)] transition-all duration-300 focus-within:border-[color-mix(in_srgb,var(--primary)_35%,transparent)] focus-within:ring-4 focus-within:ring-[color-mix(in_srgb,var(--primary)_5%,transparent)] focus-within:shadow-[0_10px_35px_color-mix(in_srgb,var(--primary)_4%,transparent)] dark:border-slate-700 dark:bg-slate-800"
|
||||
? "rounded-2xl border border-gray-200/70 bg-white overflow-hidden shadow-[0_4.5px_22px_rgba(0,0,0,0.025)] transition-all duration-300 focus-within:border-[rgb(var(--legacy-primary-rgb)/35%)] focus-within:ring-4 focus-within:ring-[rgb(var(--legacy-primary-rgb)/5%)] focus-within:shadow-[0_10px_35px_rgb(var(--legacy-primary-rgb)/4%)] dark:border-slate-700 dark:bg-slate-800"
|
||||
: // landing 态:透明,让外层 gemini-gradient-border 提供白卡 + 旋转描边
|
||||
// 对话态:常规白卡 + 中性边框 + 轻阴影
|
||||
isNewThread
|
||||
@@ -680,18 +680,21 @@ export function InputBox({
|
||||
<PromptInputButton
|
||||
className={cn(
|
||||
"gap-1.5! rounded-full px-3! shadow-none",
|
||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||
context.writing_mode
|
||||
? "border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200"
|
||||
: compact
|
||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
|
||||
)}
|
||||
onClick={handleWritingModeToggle}
|
||||
>
|
||||
<FileTextIcon className="size-3" />
|
||||
<span className="text-xs font-normal">写作</span>
|
||||
</PromptInputButton>
|
||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||
context.writing_mode
|
||||
? "border border-amber-300 bg-amber-50 text-amber-700 ring-1 ring-amber-300/35 hover:bg-amber-100 dark:border-amber-300/70 dark:bg-amber-400/20 dark:text-amber-100 dark:ring-amber-300/35 dark:shadow-[0_0_0_1px_rgba(251,191,36,0.18),0_0_18px_rgba(251,191,36,0.18)] dark:hover:bg-amber-400/25"
|
||||
: compact
|
||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
|
||||
)}
|
||||
onClick={handleWritingModeToggle}
|
||||
>
|
||||
{context.writing_mode && (
|
||||
<CheckIcon className="size-3 text-amber-700 dark:text-amber-100" />
|
||||
)}
|
||||
<FileTextIcon className="size-3" />
|
||||
<span className="text-xs font-normal">写作</span>
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
@@ -702,19 +705,22 @@ export function InputBox({
|
||||
>
|
||||
<PromptInputButton
|
||||
className={cn(
|
||||
"gap-1.5! rounded-full px-3! shadow-none",
|
||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||
memoryInjectionEnabled
|
||||
? "border border-blue-300 bg-blue-50 text-blue-700 hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-950/40 dark:text-blue-200"
|
||||
: compact
|
||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
|
||||
)}
|
||||
onClick={handleMemoryInjectionToggle}
|
||||
>
|
||||
<BrainCircuitIcon className="size-3" />
|
||||
<span className="text-xs font-normal">记忆</span>
|
||||
</PromptInputButton>
|
||||
"gap-1.5! rounded-full px-3! shadow-none",
|
||||
compact && "rounded-lg px-2.5! py-1! text-[11px] font-medium",
|
||||
memoryInjectionEnabled
|
||||
? "border border-blue-300 bg-blue-50 text-blue-700 ring-1 ring-blue-300/35 hover:bg-blue-100 dark:border-cyan-300/70 dark:bg-cyan-400/20 dark:text-cyan-50 dark:ring-cyan-300/35 dark:shadow-[0_0_0_1px_rgba(34,211,238,0.18),0_0_18px_rgba(34,211,238,0.18)] dark:hover:bg-cyan-400/25"
|
||||
: compact
|
||||
? "border border-transparent bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
|
||||
)}
|
||||
onClick={handleMemoryInjectionToggle}
|
||||
>
|
||||
{memoryInjectionEnabled && (
|
||||
<CheckIcon className="size-3 text-blue-700 dark:text-cyan-50" />
|
||||
)}
|
||||
<BrainCircuitIcon className="size-3" />
|
||||
<span className="text-xs font-normal">记忆</span>
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
@@ -1166,7 +1172,7 @@ export function InputBox({
|
||||
className={cn(
|
||||
"rounded-full border-border bg-background shadow-sm hover:bg-muted dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none",
|
||||
compact &&
|
||||
"size-7 rounded-lg border-0 bg-[var(--primary)] text-[var(--primary-foreground)] hover:bg-[color-mix(in_srgb,var(--primary)_88%,black)] disabled:bg-muted disabled:text-muted-foreground/40 dark:bg-[var(--primary)] dark:border-0 dark:text-[var(--primary-foreground)] dark:hover:bg-[color-mix(in_srgb,var(--primary)_88%,black)] dark:disabled:bg-muted",
|
||||
"size-7 rounded-lg border-0 bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90 disabled:bg-muted disabled:text-muted-foreground/40 dark:bg-[var(--primary)] dark:border-0 dark:text-[var(--primary-foreground)] dark:hover:opacity-90 dark:disabled:bg-muted",
|
||||
)}
|
||||
disabled={disabled}
|
||||
variant="outline"
|
||||
|
||||
@@ -112,7 +112,7 @@ export function RecentRoundtableList() {
|
||||
}
|
||||
try {
|
||||
await rename(id, title);
|
||||
toast.success("✏️ 已重命名");
|
||||
toast.success("已重命名");
|
||||
} catch {
|
||||
toast.error("重命名失败,请重试");
|
||||
} finally {
|
||||
@@ -126,7 +126,7 @@ export function RecentRoundtableList() {
|
||||
if (!window.confirm("确定删除这条会商记录吗?此操作不可恢复。")) return;
|
||||
try {
|
||||
await remove(draft.id);
|
||||
toast.success("🗑 已删除会商记录");
|
||||
toast.success("已删除会商记录");
|
||||
} catch {
|
||||
toast.error("删除失败,请重试");
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ CM Assistant is an internal super-agent platform that orchestrates sub-agents, m
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Core Features
|
||||
## Core Features
|
||||
|
||||
* **Skills & Tools**: With built-in and extensible skills and tools, CM Assistant can do almost anything.
|
||||
* **Sub-Agents**: Sub-Agents help the main agent to do the tasks that are too complex to be done by the main agent.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { CopyIcon, LogOutIcon, UserIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { clearStoredAuth, getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
import { useLiveAccount } from "@/core/auth/use-live-account";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import { clearYLogToken } from "@/core/log/y-log-token";
|
||||
|
||||
@@ -27,7 +27,10 @@ function copy(text: string, hint = "已复制") {
|
||||
}
|
||||
|
||||
export function AccountSettingsPage() {
|
||||
const auth = useMemo(() => getStoredAuth(), []);
|
||||
// Subscribe to same-tab and cross-tab auth changes. The dialog may stay open
|
||||
// while the active account is switched elsewhere.
|
||||
useLiveAccount();
|
||||
const auth = getStoredAuth();
|
||||
|
||||
if (!auth) {
|
||||
return (
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { User } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useLiveAccount } from "@/core/auth/use-live-account";
|
||||
import { useMyPosition } from "@/core/positions";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { readUserInfo } from "@/strategy-components/lib/light-app";
|
||||
|
||||
/**
|
||||
* 名称 + 岗位 用户标识。全量模式由 strategy-components/Header.tsx 顶栏右侧展示;
|
||||
* 紧凑模式(不带全局顶栏)无处承载,故抽成共享组件,复用同一套数据来源
|
||||
* (`readUserInfo().userName` 优先、回退 auth 邮箱前缀;岗位取 `useMyPosition`),
|
||||
* (实时 auth 展示名;岗位取 `useMyPosition`),
|
||||
* 供页面级顶栏(如 ChatPage)右侧复用,保证两套布局一致。
|
||||
*
|
||||
* 配色按当前内容区前景色(foreground / muted-foreground),适配聊天页浅底顶栏,
|
||||
* 而非 Header 的白字贴 banner 方案。
|
||||
*/
|
||||
export function UserIdentityBadge({ className }: { className?: string }) {
|
||||
const auth = getStoredAuth();
|
||||
// userInfo belongs to external-app route parameters and can legitimately
|
||||
// outlive a login switch. The visible account name must instead match the
|
||||
// settings page's current auth identity.
|
||||
const liveAccount = useLiveAccount();
|
||||
const { data: myPosition } = useMyPosition();
|
||||
const positionName = myPosition?.name?.trim() || "";
|
||||
|
||||
// 与 Header.tsx 一致:外部登录写入的真实用户名优先,缺省回退邮箱前缀。
|
||||
const displayName = useMemo(() => {
|
||||
const info = readUserInfo();
|
||||
const name = typeof info.userName === "string" ? info.userName.trim() : "";
|
||||
if (name) return name;
|
||||
const email = auth?.email?.trim();
|
||||
return email ? email.split("@")[0] : "用户";
|
||||
}, [auth?.email]);
|
||||
const displayName = liveAccount.displayName;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { AuroraText } from "../ui/aurora-text";
|
||||
|
||||
let waved = false;
|
||||
|
||||
export function Welcome({
|
||||
export function Welcome({
|
||||
className,
|
||||
mode,
|
||||
}: {
|
||||
@@ -26,10 +24,7 @@ export function Welcome({
|
||||
}
|
||||
return ["var(--color-foreground)"];
|
||||
}, [isUltra]);
|
||||
useEffect(() => {
|
||||
waved = true;
|
||||
}, []);
|
||||
return (
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mx-auto flex w-full flex-col items-center justify-center gap-2 px-8 py-4 text-center",
|
||||
@@ -37,15 +32,12 @@ export function Welcome({
|
||||
)}
|
||||
>
|
||||
<div className="text-[26px] font-medium tracking-tight text-foreground/85">
|
||||
{searchParams.get("mode") === "skill" ? (
|
||||
`✨ ${t.welcome.createYourOwnSkill} ✨`
|
||||
) : (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className={cn("inline-block text-2xl", !waved ? "animate-wave" : "")}>
|
||||
{isUltra ? "🚀" : "👋"}
|
||||
</div>
|
||||
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
|
||||
</div>
|
||||
{searchParams.get("mode") === "skill" ? (
|
||||
t.welcome.createYourOwnSkill
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{searchParams.get("mode") === "skill" ? (
|
||||
|
||||
@@ -36,8 +36,8 @@ const NEW_CHAT_CLASS =
|
||||
// 紧凑模式(CM 参考图):白卡描边、居中、主色 + 图标
|
||||
const NEW_CHAT_COMPACT_CLASS =
|
||||
"h-9 justify-center gap-2 rounded-xl bg-card text-[13px] font-semibold text-foreground transition-colors " +
|
||||
"border border-[color-mix(in_srgb,var(--primary)_18%,transparent)] " +
|
||||
"hover:bg-[color-mix(in_srgb,var(--primary)_4%,transparent)] hover:text-[var(--primary)] hover:border-[color-mix(in_srgb,var(--primary)_32%,transparent)] " +
|
||||
"border border-[rgb(var(--legacy-primary-rgb)/18%)] " +
|
||||
"hover:bg-[rgb(var(--legacy-primary-rgb)/4%)] hover:text-[var(--primary)] hover:border-[rgb(var(--legacy-primary-rgb)/32%)] " +
|
||||
"data-[active=true]:bg-card data-[active=true]:text-[var(--primary)] data-[active=true]:shadow-none";
|
||||
|
||||
export function WorkspaceNavChatList({
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import { requestCrudService } from "@/service/request-axios-service";
|
||||
import { readCopTaskDetail } from "@/core/auth/task-deeplink";
|
||||
import { readRuntimeEnv, readRuntimeFlag } from "@/env";
|
||||
import { fetchTaskDeeplinkConfig } from "@/core/auth/task-deeplink-config";
|
||||
|
||||
/** 本页输入 JSON 暂存的 sessionStorage key(拉取一次后复用,避免重复请求)。 */
|
||||
@@ -89,7 +90,7 @@ export const DEMO_DASHBOARD_INPUT = {
|
||||
|
||||
/** 「研判情报报告」接口完整地址:`{VITE_AIPI_CONSUMER_URL}/cop/selectSqReport`。 */
|
||||
function sqReportUrl(): string {
|
||||
const value = (import.meta.env as Record<string, unknown>).VITE_AIPI_CONSUMER_URL;
|
||||
const value = readRuntimeEnv("VITE_AIPI_CONSUMER_URL");
|
||||
const base = typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||||
return base ? `${base}/cop/selectSqReport` : "";
|
||||
}
|
||||
@@ -140,7 +141,7 @@ export function extractSqReport(body: unknown, taskId: string): unknown | null {
|
||||
* 避免开发反复请求样例假数据。真实接口模式(false)下不读不写缓存,**始终拉取真实数据**。
|
||||
*/
|
||||
function inputCacheEnabled(): boolean {
|
||||
return (import.meta.env as Record<string, unknown>).VITE_MOCK_SQ_REPORT === "true";
|
||||
return readRuntimeFlag("VITE_MOCK_SQ_REPORT");
|
||||
}
|
||||
|
||||
/** 读取暂存的输入 JSON(仅 mock 开发模式生效,真实模式恒返回 null 以强制重新拉取)。 */
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
} from "@/core/auth/scoped-storage";
|
||||
|
||||
const AUTH_STORAGE_KEY = "deerflow.auth";
|
||||
/** Fired in the current tab because the browser `storage` event only reaches other tabs. */
|
||||
export const AUTH_STATE_CHANGED_EVENT = "deerflow:auth-state-changed";
|
||||
|
||||
function isBrowser(): boolean {
|
||||
return typeof window !== "undefined";
|
||||
@@ -48,6 +50,7 @@ export function setStoredAuth(auth: LoginByUsernameResponse) {
|
||||
return;
|
||||
}
|
||||
authStorageSet(AUTH_STORAGE_KEY, JSON.stringify(auth));
|
||||
window.dispatchEvent(new Event(AUTH_STATE_CHANGED_EVENT));
|
||||
}
|
||||
|
||||
export function clearStoredAuth() {
|
||||
@@ -55,6 +58,7 @@ export function clearStoredAuth() {
|
||||
return;
|
||||
}
|
||||
authStorageRemove(AUTH_STORAGE_KEY);
|
||||
window.dispatchEvent(new Event(AUTH_STATE_CHANGED_EVENT));
|
||||
}
|
||||
|
||||
/** 与设置 → 个人信息页「用户名」一致:邮箱 @ 前缀,否则 user_id。 */
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { requestCrudService } from "@/service/request-axios-service";
|
||||
import { clearTaskContext, persistTaskContext } from "@/core/auth/task-purpose";
|
||||
import { fetchTaskDeeplinkConfig } from "@/core/auth/task-deeplink-config";
|
||||
import { readRuntimeEnv, readRuntimeFlag } from "@/env";
|
||||
import { listBusinessMappings } from "@/strategy-components/api/business-mapping";
|
||||
|
||||
/** xdfx 业务码:深链传入的是「行动id」,需先调 action 接口解析出真正的「任务id」。 */
|
||||
@@ -73,13 +74,13 @@ export function parseGoPath(goPath: string): ParsedGoPath | null {
|
||||
}
|
||||
|
||||
function consumerBaseUrl(): string {
|
||||
const value = import.meta.env.VITE_AIPI_CONSUMER_URL;
|
||||
const value = readRuntimeEnv("VITE_AIPI_CONSUMER_URL");
|
||||
return typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||||
}
|
||||
|
||||
/** cop-task-detail 是否走本地假数据:开关 `VITE_MOCK_COP_TASK_DETAIL=true`(真实接口未就绪时调试用)。 */
|
||||
function copTaskDetailMockEnabled(): boolean {
|
||||
return (import.meta.env as Record<string, unknown>).VITE_MOCK_COP_TASK_DETAIL === "true";
|
||||
return readRuntimeFlag("VITE_MOCK_COP_TASK_DETAIL");
|
||||
}
|
||||
|
||||
/** cop-task-detail 本地假数据(与真实 data 字段同构;mock 开关开启时返回)。 */
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
import {
|
||||
AUTH_STATE_CHANGED_EVENT,
|
||||
getAccountDisplayName,
|
||||
getStoredAuth,
|
||||
} from "@/core/auth";
|
||||
import { isEmbedActive } from "@/core/embed/embed-session";
|
||||
|
||||
const AUTH_STORAGE_KEY = "deerflow.auth";
|
||||
@@ -61,11 +65,13 @@ export function useLiveAccount(): LiveAccount {
|
||||
if (!isEmbedActive()) {
|
||||
window.addEventListener("storage", onStorage);
|
||||
}
|
||||
window.addEventListener(AUTH_STATE_CHANGED_EVENT, refresh);
|
||||
window.addEventListener("focus", refresh);
|
||||
document.addEventListener("visibilitychange", refresh);
|
||||
refresh();
|
||||
return () => {
|
||||
window.removeEventListener("storage", onStorage);
|
||||
window.removeEventListener(AUTH_STATE_CHANGED_EVENT, refresh);
|
||||
window.removeEventListener("focus", refresh);
|
||||
document.removeEventListener("visibilitychange", refresh);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,13 @@ import {
|
||||
persistExternalLoginParams,
|
||||
} from "./external-login";
|
||||
import { authStorageSet } from "@/core/auth/scoped-storage";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
|
||||
const AUTH_BASE_URL = (import.meta.env.VITE_AUTH_BASE_URL || "").replace(
|
||||
const AUTH_BASE_URL = (readRuntimeEnv("VITE_AUTH_BASE_URL") || "").replace(
|
||||
/\/$/,
|
||||
"",
|
||||
);
|
||||
const CONSUMER_URL = (import.meta.env.VITE_AIPI_CONSUMER_URL || "").replace(
|
||||
const CONSUMER_URL = (readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || "").replace(
|
||||
/\/$/,
|
||||
"",
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { requestCrudService } from "@/service/request-axios-service";
|
||||
import { authStorageGet, authStorageSet } from "@/core/auth/scoped-storage";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
import {
|
||||
deleteExternalAccessTokenFields,
|
||||
normalizeExternalAccessToken,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
writeStoredExternalAccessToken,
|
||||
} from "@/core/auth/external-access-token";
|
||||
|
||||
const CONSUMER_URL = import.meta.env.VITE_AIPI_CONSUMER_URL || "";
|
||||
const CONSUMER_URL = readRuntimeEnv("VITE_AIPI_CONSUMER_URL") || "";
|
||||
|
||||
export const TOOLS_TOKEN_STORAGE_KEY = "tools_token";
|
||||
export const EXTERNAL_USER_ID_STORAGE_KEY = "userId";
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { authStorageGet } from "@/core/auth/scoped-storage";
|
||||
import { readCurrentExternalAccessTokenValue } from "@/core/auth/external-access-token";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
|
||||
function trimTrailingSlash(url: string): string {
|
||||
return url.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function readEnv(key: string): string {
|
||||
const value = import.meta.env[key];
|
||||
const value = readRuntimeEnv(key);
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
|
||||
+33
-28
@@ -1,28 +1,33 @@
|
||||
type EnvValue = string | undefined;
|
||||
|
||||
function readEnv(
|
||||
key:
|
||||
| "VITE_BACKEND_BASE_URL"
|
||||
| "VITE_LANGGRAPH_BASE_URL"
|
||||
| "VITE_CANVAS_LANGGRAPH_BASE_URL"
|
||||
| "VITE_NOTE_API_BASE_URL"
|
||||
| "VITE_AIPI_BACKENP_URL"
|
||||
| "VITE_AIPI_FRONTEND_URL"
|
||||
| "VITE_STATIC_WEBSITE_ONLY"
|
||||
| "VITE_GITHUB_OAUTH_TOKEN",
|
||||
): EnvValue {
|
||||
const value = import.meta.env[key];
|
||||
return typeof value === "string" && value.trim() ? value : undefined;
|
||||
}
|
||||
|
||||
export const env = {
|
||||
NEXT_PUBLIC_BACKEND_BASE_URL: readEnv("VITE_BACKEND_BASE_URL"),
|
||||
NEXT_PUBLIC_LANGGRAPH_BASE_URL: readEnv("VITE_LANGGRAPH_BASE_URL"),
|
||||
// Canvas page uses its own LangGraph base URL so it can be independently
|
||||
// routed (e.g. a separate canvas-agent server). Falls back to the main URL.
|
||||
NEXT_PUBLIC_CANVAS_LANGGRAPH_BASE_URL: readEnv("VITE_CANVAS_LANGGRAPH_BASE_URL"),
|
||||
NEXT_PUBLIC_NOTE_API_BASE_URL: readEnv("VITE_NOTE_API_BASE_URL"),
|
||||
NEXT_PUBLIC_NOTEBOOK_API_BASE_URL: readEnv("VITE_AIPI_BACKENP_URL"),
|
||||
NEXT_PUBLIC_STATIC_WEBSITE_ONLY: readEnv("VITE_STATIC_WEBSITE_ONLY"),
|
||||
GITHUB_OAUTH_TOKEN: readEnv("VITE_GITHUB_OAUTH_TOKEN"),
|
||||
};
|
||||
type EnvValue = string | undefined;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__CM_RUNTIME_CONFIG__?: Record<string, string | undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
export function readRuntimeEnv(key: string): EnvValue {
|
||||
const runtimeValue =
|
||||
typeof window !== "undefined" ? window.__CM_RUNTIME_CONFIG__?.[key] : undefined;
|
||||
if (typeof runtimeValue === "string" && runtimeValue.trim()) {
|
||||
return runtimeValue.trim();
|
||||
}
|
||||
const value = import.meta.env[key];
|
||||
return typeof value === "string" && value.trim() ? value : undefined;
|
||||
}
|
||||
|
||||
export function readRuntimeFlag(key: string): boolean {
|
||||
return readRuntimeEnv(key) === "true";
|
||||
}
|
||||
|
||||
export const env = {
|
||||
NEXT_PUBLIC_BACKEND_BASE_URL: readRuntimeEnv("VITE_BACKEND_BASE_URL"),
|
||||
NEXT_PUBLIC_LANGGRAPH_BASE_URL: readRuntimeEnv("VITE_LANGGRAPH_BASE_URL"),
|
||||
// Canvas page uses its own LangGraph base URL so it can be independently
|
||||
// routed (e.g. a separate canvas-agent server). Falls back to the main URL.
|
||||
NEXT_PUBLIC_CANVAS_LANGGRAPH_BASE_URL: readRuntimeEnv("VITE_CANVAS_LANGGRAPH_BASE_URL"),
|
||||
NEXT_PUBLIC_NOTE_API_BASE_URL: readRuntimeEnv("VITE_NOTE_API_BASE_URL"),
|
||||
NEXT_PUBLIC_NOTEBOOK_API_BASE_URL: readRuntimeEnv("VITE_AIPI_BACKENP_URL"),
|
||||
NEXT_PUBLIC_STATIC_WEBSITE_ONLY: readRuntimeEnv("VITE_STATIC_WEBSITE_ONLY"),
|
||||
GITHUB_OAUTH_TOKEN: readRuntimeEnv("VITE_GITHUB_OAUTH_TOKEN"),
|
||||
};
|
||||
@@ -14,20 +14,20 @@ import { cn } from '@/lib/utils';
|
||||
export interface FlowStage {
|
||||
key: string;
|
||||
label: string;
|
||||
emoji: string;
|
||||
mark: string;
|
||||
}
|
||||
|
||||
/** 固定流水线阶段(key 与后端 ai_writing_job_executor 的 stage key 一致)。 */
|
||||
export const FLOW_STAGES: readonly FlowStage[] = [
|
||||
{ key: 'intent', label: '意图理解', emoji: '🧭' },
|
||||
{ key: 'research', label: '素材搜集', emoji: '🔍' },
|
||||
{ key: 'material_confirm', label: '素材确认', emoji: '📚' },
|
||||
{ key: 'outline', label: '大纲规划', emoji: '🗂️' },
|
||||
{ key: 'outline_confirm', label: '大纲确认', emoji: '🧩' },
|
||||
{ key: 'draft', label: '写作', emoji: '✍️' },
|
||||
{ key: 'draft_confirm', label: '草稿确认', emoji: '📝' },
|
||||
{ key: 'review', label: '编辑审核', emoji: '🧐' },
|
||||
{ key: 'done', label: '定稿', emoji: '🏁' },
|
||||
{ key: 'intent', label: '意图理解', mark: '意' },
|
||||
{ key: 'research', label: '素材搜集', mark: '搜' },
|
||||
{ key: 'material_confirm', label: '素材确认', mark: '素' },
|
||||
{ key: 'outline', label: '大纲规划', mark: '纲' },
|
||||
{ key: 'outline_confirm', label: '大纲确认', mark: '确' },
|
||||
{ key: 'draft', label: '写作', mark: '写' },
|
||||
{ key: 'draft_confirm', label: '草稿确认', mark: '稿' },
|
||||
{ key: 'review', label: '编辑审核', mark: '审' },
|
||||
{ key: 'done', label: '定稿', mark: '定' },
|
||||
];
|
||||
|
||||
export const FLOW_STAGE_LABEL: Record<string, string> = Object.fromEntries(
|
||||
@@ -40,12 +40,12 @@ const LINE = 'bg-slate-300 dark:bg-border';
|
||||
|
||||
function StageNode({
|
||||
label,
|
||||
emoji,
|
||||
mark,
|
||||
state,
|
||||
note,
|
||||
}: {
|
||||
label: string;
|
||||
emoji: string;
|
||||
mark: string;
|
||||
state: StageState;
|
||||
note?: string | null;
|
||||
}) {
|
||||
@@ -55,14 +55,14 @@ function StageNode({
|
||||
{state === 'active' && <span className="absolute inset-0 animate-ping rounded-full bg-primary/25" />}
|
||||
<span
|
||||
className={cn(
|
||||
'relative flex size-11 items-center justify-center rounded-full border-2 bg-white text-lg transition-all dark:bg-card',
|
||||
'relative flex size-11 items-center justify-center rounded-full border-2 bg-white text-base font-semibold text-primary transition-all dark:bg-card',
|
||||
state === 'active' && 'border-primary shadow-[0_0_0_3px_rgba(22,119,255,0.12)]',
|
||||
state === 'done' && 'border-emerald-400/70',
|
||||
state === 'error' && 'border-destructive',
|
||||
state === 'pending' && 'border-border opacity-45 grayscale',
|
||||
)}
|
||||
>
|
||||
{emoji}
|
||||
{mark}
|
||||
{state === 'done' && (
|
||||
<span className="absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-emerald-500 text-white shadow">
|
||||
<CheckIcon className="size-2.5" />
|
||||
@@ -127,7 +127,7 @@ export function AIWritingFlowChart({
|
||||
{idx > 0 && <div className={cn('mt-[22px] h-px w-5 shrink-0', LINE)} />}
|
||||
<StageNode
|
||||
label={stage.label}
|
||||
emoji={stage.emoji}
|
||||
mark={stage.mark}
|
||||
state={stateOf(idx)}
|
||||
note={stage.key === activeKey ? note : undefined}
|
||||
/>
|
||||
|
||||
@@ -46,6 +46,8 @@ const WRITING_STATUS_PILL: Record<string, { label: string; className: string; sp
|
||||
error: { label: "出错", className: "text-rose-500" },
|
||||
};
|
||||
|
||||
const SHOW_NOTEBOOK_SPACE_ENTRIES = false;
|
||||
|
||||
/** 单条历史记录项 —— 样式参考多智能体会商的 HistoryDropdown 列表项(圆角卡片 + 状态药丸 + 操作按钮)。 */
|
||||
function WritingHistoryItem({
|
||||
session,
|
||||
@@ -83,8 +85,11 @@ function WritingHistoryItem({
|
||||
|
||||
return (
|
||||
<li
|
||||
className="px-3 py-2.5 transition-colors hover:bg-slate-50"
|
||||
style={isCurrent ? { backgroundColor: "#ecfdf5" } : undefined}
|
||||
className={cn(
|
||||
"px-3 py-2.5 transition-colors hover:bg-slate-50 dark:hover:bg-white/10",
|
||||
isCurrent &&
|
||||
"bg-emerald-50 dark:border-l-2 dark:border-[#00e5ff] dark:bg-[#0b3a71]/80 dark:shadow-[inset_0_0_0_1px_rgba(64,150,255,0.24),0_0_18px_rgba(0,180,255,0.14)]",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -101,7 +106,7 @@ function WritingHistoryItem({
|
||||
}
|
||||
}}
|
||||
onBlur={commit}
|
||||
className="w-full rounded border border-slate-300 px-2 py-1 text-[11.5px] font-semibold text-slate-800 outline-none focus:border-primary"
|
||||
className="w-full rounded border border-slate-300 px-2 py-1 text-[11.5px] font-semibold text-slate-800 outline-none focus:border-primary dark:border-[#1b477e] dark:bg-[#022248] dark:text-[#e8edf5]"
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
@@ -132,7 +137,7 @@ function WritingHistoryItem({
|
||||
type="button"
|
||||
onClick={isBackground && onShowProgress ? onShowProgress : onSelect}
|
||||
title={isBackground ? "查看进度" : "只读查看"}
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-primary"
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-primary dark:text-[#8aa0c4] dark:hover:bg-white/10 dark:hover:text-[#8fd3ff]"
|
||||
>
|
||||
<Eye className="size-3.5" />
|
||||
</button>
|
||||
@@ -143,7 +148,7 @@ function WritingHistoryItem({
|
||||
setRenaming(true);
|
||||
}}
|
||||
title="重命名"
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-primary"
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-primary dark:text-[#8aa0c4] dark:hover:bg-white/10 dark:hover:text-[#8fd3ff]"
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
</button>
|
||||
@@ -151,7 +156,7 @@ function WritingHistoryItem({
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
title="删除"
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-rose-50 hover:text-rose-600"
|
||||
className="rounded p-1 text-slate-500 transition hover:bg-rose-50 hover:text-rose-600 dark:text-[#8aa0c4] dark:hover:bg-rose-500/15 dark:hover:text-rose-300"
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</button>
|
||||
@@ -248,103 +253,105 @@ export function ResearchSidebar({
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden px-3 py-4">
|
||||
<section className="flex flex-col">
|
||||
<div className="mb-3 flex items-center justify-between px-2">
|
||||
<h2 className="text-[15px] font-bold tracking-wide text-slate-400">
|
||||
笔记本
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setName("");
|
||||
setCreateError("");
|
||||
setCreateOpen(true);
|
||||
}}
|
||||
title="新建写作空间"
|
||||
aria-label="新建写作空间"
|
||||
className="rounded-md p-1 text-slate-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
>
|
||||
<Plus className="size-4 stroke-[2.5]" />
|
||||
</button>
|
||||
</div>
|
||||
{SHOW_NOTEBOOK_SPACE_ENTRIES && (
|
||||
<section className="flex flex-col">
|
||||
<div className="mb-3 flex items-center justify-between px-2">
|
||||
<h2 className="text-[15px] font-bold tracking-wide text-slate-400">
|
||||
笔记本
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setName("");
|
||||
setCreateError("");
|
||||
setCreateOpen(true);
|
||||
}}
|
||||
title="新建写作空间"
|
||||
aria-label="新建写作空间"
|
||||
className="rounded-md p-1 text-slate-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
>
|
||||
<Plus className="size-4 stroke-[2.5]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto px-1 pb-3">
|
||||
{notebooksLoading ? (
|
||||
<p className="px-3 py-2 text-xs text-slate-400">加载中...</p>
|
||||
) : notebooksError ? (
|
||||
<p className="rounded-xl bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||
笔记本服务暂不可用,请检查空间服务地址或账号
|
||||
</p>
|
||||
) : notebooks.length === 0 ? (
|
||||
<p className="px-3 py-2 text-xs text-slate-400">暂无笔记本</p>
|
||||
) : (
|
||||
notebooks.map((nb) => {
|
||||
const active = viewingNotebookId === nb.id;
|
||||
const sourceCount = Math.max(0, nb.source_count ?? 0);
|
||||
return (
|
||||
<button
|
||||
key={nb.id}
|
||||
type="button"
|
||||
onClick={() => onOpenNotebook(nb.id)}
|
||||
title={nb.description || nb.name}
|
||||
className={cn(
|
||||
"group relative flex min-h-14 w-full items-center gap-3 rounded-xl border bg-white px-3 py-2 text-left transition-all",
|
||||
active
|
||||
? "border-rose-100 bg-rose-50/35 text-slate-900 shadow-[0_6px_18px_rgba(244,63,94,0.06)]"
|
||||
: "border-slate-100/80 text-slate-600 hover:border-slate-200 hover:bg-slate-50/70",
|
||||
)}
|
||||
>
|
||||
{active && (
|
||||
<span className="absolute bottom-4 left-0 top-4 w-1 rounded-r-full bg-rose-500" />
|
||||
)}
|
||||
<span
|
||||
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto px-1 pb-3">
|
||||
{notebooksLoading ? (
|
||||
<p className="px-3 py-2 text-xs text-slate-400">加载中...</p>
|
||||
) : notebooksError ? (
|
||||
<p className="rounded-xl bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||
笔记本服务暂不可用,请检查空间服务地址或账号
|
||||
</p>
|
||||
) : notebooks.length === 0 ? (
|
||||
<p className="px-3 py-2 text-xs text-slate-400">暂无笔记本</p>
|
||||
) : (
|
||||
notebooks.map((nb) => {
|
||||
const active = viewingNotebookId === nb.id;
|
||||
const sourceCount = Math.max(0, nb.source_count ?? 0);
|
||||
return (
|
||||
<button
|
||||
key={nb.id}
|
||||
type="button"
|
||||
onClick={() => onOpenNotebook(nb.id)}
|
||||
title={nb.description || nb.name}
|
||||
className={cn(
|
||||
"flex size-8 shrink-0 items-center justify-center rounded-lg",
|
||||
active ? "bg-rose-50 text-rose-600" : "bg-slate-100 text-slate-500",
|
||||
"group relative flex min-h-14 w-full items-center gap-3 rounded-xl border bg-white px-3 py-2 text-left transition-all",
|
||||
active
|
||||
? "border-rose-100 bg-rose-50/35 text-slate-900 shadow-[0_6px_18px_rgba(244,63,94,0.06)]"
|
||||
: "border-slate-100/80 text-slate-600 hover:border-slate-200 hover:bg-slate-50/70",
|
||||
)}
|
||||
>
|
||||
<BookOpen className="size-4 stroke-[2.2]" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-[15px] font-medium leading-5">
|
||||
{nb.name}
|
||||
</span>
|
||||
{sourceCount > 0 && (
|
||||
<span className="mt-1 flex items-center gap-1 text-[11px] text-slate-400">
|
||||
<FileText className="size-3" />
|
||||
已有 {sourceCount} 篇排版
|
||||
</span>
|
||||
{active && (
|
||||
<span className="absolute bottom-4 left-0 top-4 w-1 rounded-r-full bg-rose-500" />
|
||||
)}
|
||||
</span>
|
||||
{sourceCount > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-full border text-[12px] font-semibold",
|
||||
active
|
||||
? "border-rose-100 bg-rose-50 text-rose-600"
|
||||
: "border-slate-100 bg-slate-50 text-slate-400",
|
||||
"flex size-8 shrink-0 items-center justify-center rounded-lg",
|
||||
active ? "bg-rose-50 text-rose-600" : "bg-slate-100 text-slate-500",
|
||||
)}
|
||||
>
|
||||
{sourceCount}
|
||||
<BookOpen className="size-4 stroke-[2.2]" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-[15px] font-medium leading-5">
|
||||
{nb.name}
|
||||
</span>
|
||||
{sourceCount > 0 && (
|
||||
<span className="mt-1 flex items-center gap-1 text-[11px] text-slate-400">
|
||||
<FileText className="size-3" />
|
||||
已有 {sourceCount} 篇排版
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{sourceCount > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-full border text-[12px] font-semibold",
|
||||
active
|
||||
? "border-rose-100 bg-rose-50 text-rose-600"
|
||||
: "border-slate-100 bg-slate-50 text-slate-400",
|
||||
)}
|
||||
>
|
||||
{sourceCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-100 px-1 pt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenAllSpaces}
|
||||
className="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left text-[14px] font-bold text-slate-700 transition-colors hover:bg-slate-50 hover:text-slate-950"
|
||||
>
|
||||
<LayoutGrid className="size-4 text-rose-500 stroke-[2.3]" />
|
||||
<span>所有空间</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<div className="border-t border-slate-100 px-1 pt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenAllSpaces}
|
||||
className="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left text-[14px] font-bold text-slate-700 transition-colors hover:bg-slate-50 hover:text-slate-950"
|
||||
>
|
||||
<LayoutGrid className="size-4 text-rose-500 stroke-[2.3]" />
|
||||
<span>所有空间</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 「历史记录」列表(样式参考多智能体会商):跟随侧栏自然向下滚动、不固定在底部。
|
||||
点击打开历史回看(显示到右侧区域),✏️/双击改名,🗑删除,「挂起中」点击看进度。 */}
|
||||
|
||||
@@ -142,6 +142,7 @@ const DRAFT_ID = 'aw-draft';
|
||||
const AI_WRITING_AGENT_MODULE_KEY = 'ai-writing';
|
||||
const AI_WRITING_AGENT_MODULE_TITLE = 'AI 写作智能体';
|
||||
const AI_WRITING_LAST_AGENT_KEY = `agent-module:${AI_WRITING_AGENT_MODULE_KEY}:last-agent`;
|
||||
const SHOW_NOTEBOOK_SPACE_ENTRIES = false;
|
||||
|
||||
// Match the original canvas page proportions (chat=35, artifact=65)
|
||||
const DEFAULT_LAYOUT = { [WRITING_ID]: 35, [DRAFT_ID]: 65 };
|
||||
@@ -567,7 +568,7 @@ function AIWritingBody({
|
||||
<ArrowLeft className="size-4" />
|
||||
返回写作
|
||||
</button>
|
||||
{rightView.kind === 'notebook' && (
|
||||
{SHOW_NOTEBOOK_SPACE_ENTRIES && rightView.kind === 'notebook' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRightView({ kind: 'allSpaces' })}
|
||||
|
||||
@@ -567,9 +567,11 @@ export default function ChatPage() {
|
||||
isLandingState &&
|
||||
!compactLanding &&
|
||||
"-translate-y-[calc(50vh-96px)]",
|
||||
isLandingState
|
||||
? "max-w-(--container-width-md)"
|
||||
: "max-w-(--container-width-lg)",
|
||||
compactLanding
|
||||
? "max-w-(--container-width-xl)"
|
||||
: isLandingState
|
||||
? "max-w-(--container-width-md)"
|
||||
: "max-w-(--container-width-lg)",
|
||||
)}
|
||||
>
|
||||
<div className="absolute -top-4 right-0 left-0 z-0">
|
||||
|
||||
@@ -613,7 +613,7 @@ export default function NewAgentPage() {
|
||||
placeholder="写下智能体的人设、边界、工作方式和输出要求"
|
||||
value={soulInput}
|
||||
onChange={(event) => setSoulInput(event.currentTarget.value)}
|
||||
className="max-h-[50vh] min-h-64 resize-y overflow-y-auto font-mono text-xs"
|
||||
className="max-h-[50vh] min-h-64 resize-y overflow-y-auto text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ function DiagnosticRow({ item }: { item: DiagnosticItem }) {
|
||||
) : null}
|
||||
{item.userName ? (
|
||||
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-300">
|
||||
👤 {item.userName}
|
||||
用户 {item.userName}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="ml-auto text-xs text-slate-400 dark:text-muted-foreground">{fmtTime(item.createdAt)}</span>
|
||||
|
||||
@@ -31,6 +31,7 @@ import type { SourceItem } from "@/core/studio/types";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import { readRuntimeEnv } from "@/env";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -259,6 +260,7 @@ export default function StudioNotebookDetailPage({
|
||||
const notebookId =
|
||||
notebookIdProp != null ? String(notebookIdProp) : params.notebookId;
|
||||
const { theme } = useTheme(); // 跳转「我的空间」时带上当前主题 key
|
||||
const notebookFrontendUrl = readRuntimeEnv("VITE_AIPI_FRONTEND_URL") || "";
|
||||
const { notebooks, isLoading, token: studioToken } = useStudioNotebooks(); // token 用于跳转 callback
|
||||
const { models } = useModels();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -343,7 +345,7 @@ export default function StudioNotebookDetailPage({
|
||||
const handleDeleteSource = async (sourceId: number) => {
|
||||
if (!notebook) return;
|
||||
try {
|
||||
const backendUrl = import.meta.env.VITE_AIPI_BACKENP_URL || "http://localhost:8001";
|
||||
const backendUrl = readRuntimeEnv("VITE_AIPI_BACKENP_URL") || "http://localhost:8001";
|
||||
const response = await fetch(`${backendUrl}/api/v1/source/${sourceId}`, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${studioToken ?? ""}` },
|
||||
@@ -425,7 +427,7 @@ export default function StudioNotebookDetailPage({
|
||||
const pollSourceStatus = useCallback(async (taskId: string) => {
|
||||
if (!notebook) return;
|
||||
try {
|
||||
const backendUrl = import.meta.env.VITE_AIPI_BACKENP_URL || "http://localhost:8001";
|
||||
const backendUrl = readRuntimeEnv("VITE_AIPI_BACKENP_URL") || "http://localhost:8001";
|
||||
const token = getStoredAuth()?.access_token ?? "";
|
||||
const response = await fetch(`${backendUrl}/api/v1/task/${taskId}/status`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
@@ -581,7 +583,7 @@ export default function StudioNotebookDetailPage({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<a
|
||||
href={`${import.meta.env.VITE_AIPI_FRONTEND_URL}/auth/callback?token=${studioToken ?? ""}&redirect=/dashboard/${notebookId}/new-chat&themekey=${theme}`}
|
||||
href={`${notebookFrontendUrl}/auth/callback?token=${studioToken ?? ""}&redirect=/dashboard/${notebookId}/new-chat&themekey=${theme}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-1.5 rounded-full bg-muted px-3 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted/70"
|
||||
@@ -605,7 +607,7 @@ export default function StudioNotebookDetailPage({
|
||||
<div className="relative z-10 flex flex-1 flex-col overflow-hidden">
|
||||
{/* 右上角跳转按钮 */}
|
||||
<a
|
||||
href={`${import.meta.env.VITE_AIPI_FRONTEND_URL}/auth/callback?token=${studioToken ?? ""}&redirect=/dashboard/${notebookId}/new-chat&themekey=${theme}`}
|
||||
href={`${notebookFrontendUrl}/auth/callback?token=${studioToken ?? ""}&redirect=/dashboard/${notebookId}/new-chat&themekey=${theme}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="absolute right-6 top-4 z-10 flex items-center gap-1.5 rounded-full bg-muted/70 px-3 py-1.5 text-xs text-muted-foreground backdrop-blur transition-colors hover:bg-muted"
|
||||
|
||||
@@ -157,7 +157,7 @@ export function DagFlowChart({
|
||||
done={consensusState === "done"}
|
||||
compact={compact}
|
||||
/>
|
||||
<MilestoneNode label="共识达成" emoji="🤝" state={consensusState} compact={compact} />
|
||||
<MilestoneNode label="共识达成" mark="共" state={consensusState} compact={compact} />
|
||||
{showReportMilestone && (
|
||||
<>
|
||||
<Connector
|
||||
@@ -173,9 +173,9 @@ export function DagFlowChart({
|
||||
)}
|
||||
>
|
||||
<span className="text-[9px] font-bold tracking-widest text-primary/70">并行生成</span>
|
||||
<MilestoneNode label="总结报告" emoji="📝" state={summaryState} compact={compact} />
|
||||
<MilestoneNode label="结果绘制" emoji="📊" state={reportState} compact={compact} />
|
||||
<MilestoneNode label="大屏多页" emoji="🖥️" state={reportState} compact={compact} />
|
||||
<MilestoneNode label="总结报告" mark="报" state={summaryState} compact={compact} />
|
||||
<MilestoneNode label="结果绘制" mark="图" state={reportState} compact={compact} />
|
||||
<MilestoneNode label="大屏多页" mark="屏" state={reportState} compact={compact} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -286,12 +286,12 @@ function FlowNode({
|
||||
|
||||
function MilestoneNode({
|
||||
label,
|
||||
emoji,
|
||||
mark,
|
||||
state,
|
||||
compact,
|
||||
}: {
|
||||
label: string;
|
||||
emoji: string;
|
||||
mark: string;
|
||||
state: ChainNodeState;
|
||||
compact: boolean;
|
||||
}) {
|
||||
@@ -306,13 +306,14 @@ function MilestoneNode({
|
||||
className={cn(
|
||||
"relative flex items-center justify-center rounded-full border-2 transition-all",
|
||||
ringSize,
|
||||
compact ? "text-lg" : "text-2xl",
|
||||
"font-semibold text-primary",
|
||||
compact ? "text-base" : "text-xl",
|
||||
state === "active" && "border-primary bg-primary/5 shadow-[0_0_0_4px_rgba(22,119,255,0.12)]",
|
||||
state === "done" && "border-emerald-400/70 bg-emerald-500/5",
|
||||
state === "pending" && "border-border bg-muted opacity-45 grayscale",
|
||||
)}
|
||||
>
|
||||
{emoji}
|
||||
{mark}
|
||||
{state === "done" && (
|
||||
<span className="absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-emerald-500 text-white shadow">
|
||||
<CheckIcon className="size-2.5" />
|
||||
|
||||
@@ -106,12 +106,12 @@ function MiniNode({
|
||||
label,
|
||||
avatarType,
|
||||
state,
|
||||
emoji,
|
||||
mark,
|
||||
}: {
|
||||
label: string;
|
||||
avatarType?: string;
|
||||
state: ChainNodeState;
|
||||
emoji?: string;
|
||||
mark?: string;
|
||||
}) {
|
||||
const tag = avatarType ? chainTagClasses(avatarType) : "";
|
||||
return (
|
||||
@@ -123,12 +123,13 @@ function MiniNode({
|
||||
<span
|
||||
className={cn(
|
||||
"relative flex size-11 items-center justify-center rounded-full border-2 bg-white text-lg transition-all dark:bg-card",
|
||||
mark && "font-semibold text-primary",
|
||||
state === "active" && "border-primary shadow-[0_0_0_3px_rgba(22,119,255,0.12)]",
|
||||
state === "done" && "border-emerald-400/70",
|
||||
state === "pending" && "border-border opacity-45 grayscale",
|
||||
)}
|
||||
>
|
||||
{emoji ?? (avatarType ? getAvatar(avatarType, "h-9 w-9") : null)}
|
||||
{mark ?? (avatarType ? getAvatar(avatarType, "h-9 w-9") : null)}
|
||||
{state === "done" && (
|
||||
<span className="absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-emerald-500 text-white shadow">
|
||||
<CheckIcon className="size-2.5" />
|
||||
@@ -415,7 +416,7 @@ export function DagFlowMiniPanel({
|
||||
})}
|
||||
|
||||
{/* 共识达成(底) */}
|
||||
<MiniNode label="共识达成" emoji="🤝" state={consensusState} />
|
||||
<MiniNode label="共识达成" mark="共" state={consensusState} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export function DashboardChatPanel({
|
||||
<div className="shrink-0 flex items-center justify-between gap-2 border-b border-slate-100 bg-white px-4 py-2.5">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="w-6 h-6 shrink-0 rounded-lg bg-gradient-to-tr from-primary to-[var(--brand-200)] flex items-center justify-center text-white text-xs shadow-sm">
|
||||
📊
|
||||
图
|
||||
</div>
|
||||
<div className="leading-tight min-w-0">
|
||||
<div className="text-xs font-bold text-slate-800 truncate">大屏绘制智能体</div>
|
||||
|
||||
@@ -249,7 +249,7 @@ export function DispatchChainModal({
|
||||
<Connector active={consensusActive} done={consensusDone} />
|
||||
<MilestoneNode
|
||||
label="共识达成"
|
||||
emoji="🤝"
|
||||
mark="共"
|
||||
state={
|
||||
consensusActive ? "active" : consensusDone ? "done" : "pending"
|
||||
}
|
||||
@@ -259,9 +259,9 @@ export function DispatchChainModal({
|
||||
<Connector active={step3Active} done={step3Done} />
|
||||
<div className="flex shrink-0 flex-col items-center gap-1.5 rounded-2xl border border-dashed border-primary/30 bg-primary/[0.04] px-2 pb-2 pt-1.5">
|
||||
<span className="text-[9px] font-bold tracking-widest text-primary/70">并行生成</span>
|
||||
<MilestoneNode label="总结报告" emoji="📝" state={step3State} />
|
||||
<MilestoneNode label="结果绘制" emoji="📊" state={step3State} />
|
||||
<MilestoneNode label="大屏多页" emoji="🖥️" state={step3State} />
|
||||
<MilestoneNode label="总结报告" mark="报" state={step3State} />
|
||||
<MilestoneNode label="结果绘制" mark="图" state={step3State} />
|
||||
<MilestoneNode label="大屏多页" mark="屏" state={step3State} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -422,11 +422,11 @@ function FlowNode({
|
||||
|
||||
function MilestoneNode({
|
||||
label,
|
||||
emoji,
|
||||
mark,
|
||||
state,
|
||||
}: {
|
||||
label: string;
|
||||
emoji: string;
|
||||
mark: string;
|
||||
state: ChainNodeState;
|
||||
}) {
|
||||
return (
|
||||
@@ -437,14 +437,14 @@ function MilestoneNode({
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"relative flex size-16 items-center justify-center rounded-full border-2 text-2xl transition-all",
|
||||
"relative flex size-16 items-center justify-center rounded-full border-2 text-2xl font-semibold text-primary transition-all",
|
||||
state === "active" &&
|
||||
"border-primary bg-primary/5 shadow-[0_0_0_4px_rgba(22,119,255,0.12)]",
|
||||
state === "done" && "border-emerald-400/70 bg-emerald-500/5",
|
||||
state === "pending" && "border-border bg-muted opacity-45 grayscale",
|
||||
)}
|
||||
>
|
||||
{emoji}
|
||||
{mark}
|
||||
{state === "done" && (
|
||||
<span className="absolute -right-0.5 -top-0.5 flex size-5 items-center justify-center rounded-full bg-emerald-500 text-white shadow">
|
||||
<CheckIcon className="size-3" />
|
||||
|
||||
@@ -705,7 +705,7 @@ export function RecommendAgentsDialog({
|
||||
{state.phase === "history" && (
|
||||
<div className="text-xs text-sky-700 dark:text-sky-300 p-2 border border-sky-300/50 dark:border-sky-700/50 rounded-md bg-sky-50 dark:bg-sky-950/30 flex items-center justify-between gap-2">
|
||||
<span>
|
||||
📌 这是本会话上次的推荐结果
|
||||
这是本会话上次的推荐结果
|
||||
{state.historyCreatedAt
|
||||
? `(${new Date(state.historyCreatedAt).toLocaleString("zh-CN", { hour12: false })})`
|
||||
: ""}
|
||||
|
||||
@@ -82,9 +82,9 @@ export function RoundtableJobNotifier({ children }: { children: ReactNode }) {
|
||||
const handleTerminal = useCallback((job: RoundtableJob) => {
|
||||
setWatchedJobIds((prev) => prev.filter((id) => id !== job.id));
|
||||
if (job.status === "done") {
|
||||
setNotice("🎉 后台研讨已完成!可在「历史记录」查看结论与报告");
|
||||
setNotice("后台研讨已完成!可在「历史记录」查看结论与报告");
|
||||
} else if (job.status === "error") {
|
||||
setNotice("❌ 后台任务执行出错,请在「历史记录」中查看");
|
||||
setNotice("后台任务执行出错,请在「历史记录」中查看");
|
||||
}
|
||||
// cancelled:用户主动取消,不提示。
|
||||
}, []);
|
||||
|
||||
@@ -254,7 +254,7 @@ export function Step1Panel(props: Step1PanelProps) {
|
||||
<div className="flex items-center justify-between border-t border-slate-50 pt-2 text-[#94a3b8]">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
onClick={() => onTriggerToast("😄 表情包功能仍在微调状态(仅供演示)")}
|
||||
onClick={() => onTriggerToast("表情包功能仍在微调状态(仅供演示)")}
|
||||
className="p-1 hover:text-primary hover:bg-slate-50 rounded transition"
|
||||
>
|
||||
<Smile className="w-4 h-4" />
|
||||
|
||||
@@ -552,7 +552,7 @@ function AgentProgressPanel({
|
||||
<div className="shrink-0 flex items-center justify-between gap-2 border-b border-slate-100 bg-white px-4 py-2.5">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="w-6 h-6 shrink-0 rounded-lg bg-gradient-to-tr from-primary to-[var(--brand-200)] flex items-center justify-center text-white text-xs shadow-sm">
|
||||
📊
|
||||
图
|
||||
</div>
|
||||
<div className="leading-tight min-w-0">
|
||||
<div className="text-xs font-bold text-slate-800 truncate">方案总结智能体</div>
|
||||
|
||||
@@ -671,7 +671,7 @@ function AgentProgressPanel({
|
||||
<div className="shrink-0 flex items-center justify-between gap-2 border-b border-slate-100 bg-white px-4 py-2.5">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="w-6 h-6 shrink-0 rounded-lg bg-gradient-to-tr from-primary to-[var(--brand-200)] flex items-center justify-center text-white text-xs shadow-sm">
|
||||
📝
|
||||
报
|
||||
</div>
|
||||
<div className="leading-tight min-w-0">
|
||||
<div className="text-xs font-bold text-slate-800 truncate">方案总结报告智能体</div>
|
||||
|
||||
@@ -376,7 +376,7 @@ export function useDraftPersistence(
|
||||
const id = await enqueueSave(furthest);
|
||||
if (id) {
|
||||
await refreshList();
|
||||
triggerToast("💾 已保存为草稿");
|
||||
triggerToast("已保存为草稿");
|
||||
} else {
|
||||
triggerToast("当前会话还没有可保存的内容");
|
||||
}
|
||||
@@ -490,15 +490,15 @@ export function useDraftPersistence(
|
||||
draft = await apiGet(id);
|
||||
} catch (err) {
|
||||
console.warn("[drafts] 加载草稿失败:", err);
|
||||
triggerToast("⚠️ 加载草稿失败,请重试");
|
||||
triggerToast("加载草稿失败,请重试");
|
||||
return false;
|
||||
}
|
||||
if (!draft) {
|
||||
triggerToast("⚠️ 该草稿已不存在");
|
||||
triggerToast("该草稿已不存在");
|
||||
await refreshList();
|
||||
return false;
|
||||
}
|
||||
applyLoadedDraft(draft, `📂 已加载草稿:${draft.title}`);
|
||||
applyLoadedDraft(draft, `已加载草稿:${draft.title}`);
|
||||
return true;
|
||||
})();
|
||||
},
|
||||
@@ -535,13 +535,13 @@ export function useDraftPersistence(
|
||||
await apiDelete(id);
|
||||
} catch (err) {
|
||||
console.warn("[drafts] 删除草稿失败:", err);
|
||||
triggerToast("⚠️ 删除失败,请重试");
|
||||
triggerToast("删除失败,请重试");
|
||||
return;
|
||||
}
|
||||
await refreshList();
|
||||
if (currentDraftIdRef.current === id) setCurrentDraftId(null);
|
||||
if (renamingDraftId === id) setRenamingDraftId(null);
|
||||
triggerToast("🗑 已删除历史记录");
|
||||
triggerToast("已删除历史记录");
|
||||
})();
|
||||
},
|
||||
[apiDelete, refreshList, renamingDraftId, setCurrentDraftId, triggerToast],
|
||||
@@ -566,11 +566,11 @@ export function useDraftPersistence(
|
||||
const updated = await apiUpdate(id, { title: trimmed });
|
||||
if (updated) {
|
||||
await refreshList();
|
||||
triggerToast("✏️ 已重命名");
|
||||
triggerToast("已重命名");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[drafts] 重命名失败:", err);
|
||||
triggerToast("⚠️ 重命名失败,请重试");
|
||||
triggerToast("重命名失败,请重试");
|
||||
} finally {
|
||||
setRenamingDraftId(null);
|
||||
setRenameDraftValue("");
|
||||
|
||||
@@ -288,7 +288,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
||||
const hasFiles = !!(files && files.length > 0);
|
||||
if (!trimmed && !hasFiles) return;
|
||||
if (!intentThreadId) {
|
||||
triggerToast("⚠️ 任务理解智能体尚未就绪,请稍候");
|
||||
triggerToast("任务理解智能体尚未就绪,请稍候");
|
||||
return;
|
||||
}
|
||||
// 重入保护用 ref 而不是 state:isIntentSending 是 React state,
|
||||
@@ -379,7 +379,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
||||
: m,
|
||||
),
|
||||
);
|
||||
triggerToast(`❌ 文件上传失败: ${(err as Error).message}`);
|
||||
triggerToast(`文件上传失败: ${(err as Error).message}`);
|
||||
if (intentAborterRef.current === aborter) {
|
||||
intentAborterRef.current = null;
|
||||
}
|
||||
@@ -517,7 +517,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
||||
: resolvedSummary.objective,
|
||||
},
|
||||
]);
|
||||
triggerToast("✅ 任务理解完成,可进入多智能体圆桌研讨");
|
||||
triggerToast("任务理解完成,可进入多智能体圆桌研讨");
|
||||
} else if (res.status === "clarification" && res.clarifications.length > 0) {
|
||||
setPendingIntentClarificationMsgId(agentMsgId);
|
||||
setIntentClarificationSelections((prev) => {
|
||||
@@ -536,7 +536,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
||||
setIntentInitError(res.content);
|
||||
setIntentInitErrorDetail(null);
|
||||
}
|
||||
triggerToast(`❌ 智能体调用失败: ${res.content}`);
|
||||
triggerToast(`智能体调用失败: ${res.content}`);
|
||||
}
|
||||
} catch (err) {
|
||||
if ((err as Error).name === "AbortError" || aborter.signal.aborted) {
|
||||
@@ -567,7 +567,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
||||
: m,
|
||||
),
|
||||
);
|
||||
triggerToast(`❌ 发送失败: ${(err as Error).message}`);
|
||||
triggerToast(`发送失败: ${(err as Error).message}`);
|
||||
}
|
||||
} finally {
|
||||
if (intentAborterRef.current === aborter) {
|
||||
|
||||
@@ -1340,13 +1340,13 @@ export function useStep2Orchestration(
|
||||
const makeModelSwitchHandler = useCallback(
|
||||
(bubbleId: number, isParallel: boolean) => (info: ModelSwitchInfo) => {
|
||||
const who = info.agentName || "智能体";
|
||||
triggerToast(`⚠️「${info.failedModel}」模型${info.reason},已自动切换「${info.nextModel}」继续`);
|
||||
triggerToast(`「${info.failedModel}」模型${info.reason},已自动切换「${info.nextModel}」继续`);
|
||||
if (isParallel) parallelStreamRef.current.delete(bubbleId);
|
||||
const systemBubble: Step2Dialogue = {
|
||||
id: Date.now() + Math.random(),
|
||||
sender: "系统提示",
|
||||
avatarType: "amber",
|
||||
content: `⚠️ 模型容错:「${who}」所用模型「${info.failedModel}」${info.reason},已自动切换到「${info.nextModel}」继续。`,
|
||||
content: `模型容错:「${who}」所用模型「${info.failedModel}」${info.reason},已自动切换到「${info.nextModel}」继续。`,
|
||||
time: nowTime(),
|
||||
confidence: "模型容错",
|
||||
};
|
||||
@@ -1824,7 +1824,7 @@ export function useStep2Orchestration(
|
||||
highlightSpeaker(null);
|
||||
setPendingClarification(leaderRes.clarification);
|
||||
setClarificationDraft("");
|
||||
triggerToast("❓ 总控需要补充信息,请回答下方问题后继续");
|
||||
triggerToast("总控需要补充信息,请回答下方问题后继续");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1843,7 +1843,7 @@ export function useStep2Orchestration(
|
||||
setConsensusPercentage(100);
|
||||
setHasConsensus(true);
|
||||
triggerToast(
|
||||
"🎉 圆桌研讨论证会圆满落幕!已取得 100% 专家组共识",
|
||||
"圆桌研讨论证会圆满落幕!已取得 100% 专家组共识",
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -1962,7 +1962,7 @@ export function useStep2Orchestration(
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("orchestration error:", err);
|
||||
triggerToast(`❌ 研讨异常: ${(err as Error).message}`);
|
||||
triggerToast(`研讨异常: ${(err as Error).message}`);
|
||||
} finally {
|
||||
highlightSpeaker(null);
|
||||
clearAllStreamingSpeakers();
|
||||
@@ -2092,7 +2092,7 @@ export function useStep2Orchestration(
|
||||
highlightSpeaker(null);
|
||||
setPendingClarification(leaderRes.clarification);
|
||||
setClarificationDraft("");
|
||||
triggerToast("❓ 总控需要补充信息,请回答下方问题后继续");
|
||||
triggerToast("总控需要补充信息,请回答下方问题后继续");
|
||||
finishedAllSeats = false;
|
||||
break;
|
||||
}
|
||||
@@ -2220,7 +2220,7 @@ export function useStep2Orchestration(
|
||||
if (closeoutRes.content) setLastLeaderContent(closeoutRes.content);
|
||||
setConsensusPercentage(100);
|
||||
setHasConsensus(true);
|
||||
triggerToast("🎉 业务链条研讨完成!已生成全案结论");
|
||||
triggerToast("业务链条研讨完成!已生成全案结论");
|
||||
} catch (err) {
|
||||
if (closeoutAborter.signal.aborted || (err as Error).name === "AbortError") {
|
||||
finalizeDialogue(closeoutBubbleId, "(已停止)", "已停止");
|
||||
@@ -2234,7 +2234,7 @@ export function useStep2Orchestration(
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("chain orchestration error:", err);
|
||||
triggerToast(`❌ 链条研讨异常: ${(err as Error).message}`);
|
||||
triggerToast(`链条研讨异常: ${(err as Error).message}`);
|
||||
} finally {
|
||||
highlightSpeaker(null);
|
||||
clearAllStreamingSpeakers();
|
||||
@@ -2283,7 +2283,7 @@ export function useStep2Orchestration(
|
||||
async (seedMessage: string, ids: ThreadIdMap, coord: string) => {
|
||||
const plan = orchestrationPlanRef.current;
|
||||
if (!plan || plan.stages.length === 0) {
|
||||
triggerToast("⚠️ 没有可执行的 DAG 编排计划");
|
||||
triggerToast("没有可执行的 DAG 编排计划");
|
||||
return;
|
||||
}
|
||||
if (isOrchestratingRef.current) return;
|
||||
@@ -2398,7 +2398,7 @@ export function useStep2Orchestration(
|
||||
finalizeDialogue(leaderBubbleId, leaderRes.content, "等待用户澄清");
|
||||
setPendingClarification(leaderRes.clarification);
|
||||
setClarificationDraft("");
|
||||
triggerToast("❓ 总控需要补充信息,请回答下方问题后继续");
|
||||
triggerToast("总控需要补充信息,请回答下方问题后继续");
|
||||
finishedAllStages = false;
|
||||
break;
|
||||
}
|
||||
@@ -2579,11 +2579,11 @@ export function useStep2Orchestration(
|
||||
// 不收口(plan.finalSynthesis=false):全部 stage 跑完即视为完成,不再叫总控。
|
||||
setConsensusPercentage(100);
|
||||
setHasConsensus(true);
|
||||
triggerToast("✅ 依赖编排各阶段已全部完成");
|
||||
triggerToast("依赖编排各阶段已全部完成");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("dag orchestration error:", err);
|
||||
triggerToast(`❌ 依赖编排研讨异常: ${(err as Error).message}`);
|
||||
triggerToast(`依赖编排研讨异常: ${(err as Error).message}`);
|
||||
} finally {
|
||||
highlightSpeaker(null);
|
||||
clearAllStreamingSpeakers();
|
||||
@@ -2807,10 +2807,10 @@ export function useStep2Orchestration(
|
||||
appendDialogue({
|
||||
sender: "系统",
|
||||
avatarType: "blue",
|
||||
content: `✅ 圆桌已就绪:协调智能体 ${initRes.main_agent_name} 已上线,${Object.keys(initRes.thread_ids).length - 1} 个席位已分配独立线程。`,
|
||||
content: `圆桌已就绪:协调智能体 ${initRes.main_agent_name} 已上线,${Object.keys(initRes.thread_ids).length - 1} 个席位已分配独立线程。`,
|
||||
confidence: "系统就绪",
|
||||
});
|
||||
triggerToast("✅ 多智能体圆桌初始化完成");
|
||||
triggerToast("多智能体圆桌初始化完成");
|
||||
await runActiveOrchestration(
|
||||
buildInitialIntent(),
|
||||
initRes.thread_ids,
|
||||
@@ -2820,7 +2820,7 @@ export function useStep2Orchestration(
|
||||
console.error("init multi-agent failed:", err);
|
||||
if (cancelled) return;
|
||||
setInitError((err as Error).message);
|
||||
triggerToast(`❌ 初始化失败: ${(err as Error).message}`);
|
||||
triggerToast(`初始化失败: ${(err as Error).message}`);
|
||||
} finally {
|
||||
if (!cancelled) setIsInitializing(false);
|
||||
}
|
||||
@@ -2887,7 +2887,7 @@ export function useStep2Orchestration(
|
||||
const userText = customAgentInput.trim();
|
||||
if (!userText) return;
|
||||
if (!threadIdsRef.current || !coordinatorNameRef.current) {
|
||||
triggerToast("⚠️ 圆桌尚未就绪,无法记录干预指令");
|
||||
triggerToast("圆桌尚未就绪,无法记录干预指令");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2895,7 +2895,7 @@ export function useStep2Orchestration(
|
||||
appendDialogue({
|
||||
sender: "用户 (插话干预)",
|
||||
avatarType: "human",
|
||||
content: `💡 (人工指令介入) ${userText}`,
|
||||
content: `(人工指令介入) ${userText}`,
|
||||
confidence: "最高指令",
|
||||
});
|
||||
pendingInjectPromptRef.current = `用户人工干预新指令:${userText}\n\n请优先按这条新指令,通过 agent_orchestration 把活派给相关的真实席位去获取交付;若用户点名了某个席位(如"前端实现"),必须派给清单里对应的那个 agent_name。**不要**自己代答,也**不要**在没有真正看到该席位「完成…交付内容为:…」的情况下臆造交付或直接收口。只有当用户明确要求收口、或相关席位都已真实交付时,才综合输出结论。`;
|
||||
@@ -2908,7 +2908,7 @@ export function useStep2Orchestration(
|
||||
const hasFiles = !!(files && files.length > 0);
|
||||
if (!userText && !hasFiles) return;
|
||||
if (!threadIdsRef.current || !coordinatorNameRef.current) {
|
||||
triggerToast("⚠️ 圆桌尚未就绪,无法记录干预指令");
|
||||
triggerToast("圆桌尚未就绪,无法记录干预指令");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2929,7 +2929,7 @@ export function useStep2Orchestration(
|
||||
status: "uploaded",
|
||||
}));
|
||||
} catch (err) {
|
||||
triggerToast(`❌ 文件上传失败: ${(err as Error).message}`);
|
||||
triggerToast(`文件上传失败: ${(err as Error).message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2947,7 +2947,7 @@ export function useStep2Orchestration(
|
||||
appendDialogue({
|
||||
sender: "用户 (插话干预)",
|
||||
avatarType: "human",
|
||||
content: `💡 (人工指令介入) ${shownText}${fileNote}`,
|
||||
content: `(人工指令介入) ${shownText}${fileNote}`,
|
||||
confidence: "最高指令",
|
||||
});
|
||||
|
||||
@@ -2977,7 +2977,7 @@ export function useStep2Orchestration(
|
||||
const answer = rawAnswer.trim();
|
||||
if (!answer) return;
|
||||
if (!threadIdsRef.current || !coordinatorNameRef.current) {
|
||||
triggerToast("⚠️ 圆桌尚未就绪,无法回复澄清");
|
||||
triggerToast("圆桌尚未就绪,无法回复澄清");
|
||||
return;
|
||||
}
|
||||
if (isOrchestratingRef.current) {
|
||||
@@ -2988,7 +2988,7 @@ export function useStep2Orchestration(
|
||||
appendDialogue({
|
||||
sender: "用户 (澄清回复)",
|
||||
avatarType: "human",
|
||||
content: `🗣 回复总控提问:${answer}`,
|
||||
content: `回复总控提问:${answer}`,
|
||||
confidence: "澄清回复",
|
||||
});
|
||||
setPendingClarification(null);
|
||||
@@ -3008,7 +3008,7 @@ export function useStep2Orchestration(
|
||||
const handleBudgetChange = useCallback(
|
||||
(newVal: number) => {
|
||||
setBudgetLimit(newVal);
|
||||
triggerToast(`💰 研商经费调优约束变更为 ${newVal} 万!各大智能体启动资源重算...`);
|
||||
triggerToast(`研商经费调优约束变更为 ${newVal} 万!各大智能体启动资源重算...`);
|
||||
|
||||
setTimeout(() => {
|
||||
const respTime = nowTime();
|
||||
@@ -3017,13 +3017,13 @@ export function useStep2Orchestration(
|
||||
let senderName = "风险审查智能体";
|
||||
|
||||
if (newVal < 400) {
|
||||
agentOpinion = `⚠️ [预算警报] 经费压缩至 ${newVal} 万元,自动触发紧急安全妥协防御策略!我们将不得不裁剪旁路冷备份服务器配置,降低静态演练深度,算力容灾等级重下降至 Tier I (低安全水位)。`;
|
||||
agentOpinion = `[预算警报] 经费压缩至 ${newVal} 万元,自动触发紧急安全妥协防御策略!我们将不得不裁剪旁路冷备份服务器配置,降低静态演练深度,算力容灾等级重下降至 Tier I (低安全水位)。`;
|
||||
} else if (newVal > 540) {
|
||||
agentOpinion = `🔋 [方案容量上升] 预算上调至充足的 ${newVal} 万元,资装弹性充裕!我已为 A 套极速构建增加了 4 组外部超大异地区容灾节点支持,灾备冗余度成功突破 Tier III+ 指标,高防高内聚!`;
|
||||
agentOpinion = `[方案容量上升] 预算上调至充足的 ${newVal} 万元,资装弹性充裕!我已为 A 套极速构建增加了 4 组外部超大异地区容灾节点支持,灾备冗余度成功突破 Tier III+ 指标,高防高内聚!`;
|
||||
speakerType = "purple";
|
||||
senderName = "情报收集与适配";
|
||||
} else {
|
||||
agentOpinion = `⚡ [资源调配优化] 总体经费设定为 ${newVal} 万元,空间适中。我们重新规划了轻量容器 K3s 集成开销(约 ${Math.round(newVal * 0.45)} 万)和安全审计费比,总体博弈过程保持稳定态收敛。`;
|
||||
agentOpinion = `[资源调配优化] 总体经费设定为 ${newVal} 万元,空间适中。我们重新规划了轻量容器 K3s 集成开销(约 ${Math.round(newVal * 0.45)} 万)和安全审计费比,总体博弈过程保持稳定态收敛。`;
|
||||
speakerType = "pink";
|
||||
senderName = "执行规划智能体";
|
||||
}
|
||||
@@ -3059,11 +3059,11 @@ export function useStep2Orchestration(
|
||||
appendDialogue({
|
||||
sender: `${roleName} (${targetModel})`,
|
||||
avatarType: avatarColor,
|
||||
content: `⚡ [模型偏好已保存] 本席位在下一轮研讨中将使用 ${targetModel} 进行推理。系统提示词与温度(${cfg?.temp ?? 0.2})当前仅在前端记录,等后端开放覆写后再同步生效。`,
|
||||
content: `[模型偏好已保存] 本席位在下一轮研讨中将使用 ${targetModel} 进行推理。系统提示词与温度(${cfg?.temp ?? 0.2})当前仅在前端记录,等后端开放覆写后再同步生效。`,
|
||||
confidence: "席位偏好",
|
||||
});
|
||||
setSelectedAgentId(null);
|
||||
triggerToast(`⚙️ "${roleName}"已切换至 ${targetModel},下轮生效`);
|
||||
triggerToast(`"${roleName}"已切换至 ${targetModel},下轮生效`);
|
||||
},
|
||||
[agentConfigs, agentNameToRole, appendDialogue, selectedAgents, selectedModel, triggerToast],
|
||||
);
|
||||
@@ -3162,7 +3162,7 @@ export function useStep2Orchestration(
|
||||
// 与 handleInjectToRoundtable 末尾「直接启动新一轮编排」同构。
|
||||
const resumeLiveTakeover = useCallback((): boolean => {
|
||||
if (!threadIdsRef.current || !coordinatorNameRef.current) {
|
||||
triggerToast("⚠️ 接管失败:未找到研讨线程,已切换为只读查看");
|
||||
triggerToast("接管失败:未找到研讨线程,已切换为只读查看");
|
||||
return false;
|
||||
}
|
||||
dagPlanExecutedRef.current = true;
|
||||
@@ -3178,7 +3178,7 @@ export function useStep2Orchestration(
|
||||
threadIdsRef.current,
|
||||
coordinatorNameRef.current,
|
||||
);
|
||||
triggerToast("🎮 已接管至前端,继续实时研讨");
|
||||
triggerToast("已接管至前端,继续实时研讨");
|
||||
return true;
|
||||
}, [abortAllInFlight, runActiveOrchestration, triggerToast]);
|
||||
|
||||
|
||||
@@ -776,7 +776,7 @@ export function useStep3Report(
|
||||
setDialogueConfidence(bubbleId, `模型切换:${info.failedModel}→${info.nextModel}`);
|
||||
appendStepIfNew(
|
||||
bubbleId,
|
||||
`⚠️ 模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`model_switch:${info.nextModel}`,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1097,7 +1097,7 @@ export function useStep3Summary(
|
||||
setDialogueConfidence(bubbleId, `模型切换:${info.failedModel}→${info.nextModel}`);
|
||||
appendStepIfNew(
|
||||
bubbleId,
|
||||
`⚠️ 模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`model_switch:${info.nextModel}`,
|
||||
);
|
||||
},
|
||||
@@ -1228,7 +1228,7 @@ export function useStep3Summary(
|
||||
setDialogueConfidence(extractBubbleId, `模型切换:${info.failedModel}→${info.nextModel}`);
|
||||
appendStepIfNew(
|
||||
extractBubbleId,
|
||||
`⚠️ 模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`模型容错:「${info.failedModel}」${info.reason},改用「${info.nextModel}」`,
|
||||
`model_switch:${info.nextModel}`,
|
||||
);
|
||||
},
|
||||
@@ -1518,7 +1518,7 @@ export function useStep3Summary(
|
||||
},
|
||||
});
|
||||
if (ingestRunRef.current !== myRun || aborter.signal.aborted) return;
|
||||
finalizeDialogue(bubbleId, res.content || `✅ 已完成「${title}」数据入库。`, "入库完成");
|
||||
finalizeDialogue(bubbleId, res.content || `已完成「${title}」数据入库。`, "入库完成");
|
||||
setIngestStatus("done");
|
||||
persistSummary(); // 把入库气泡 + ingestStatus 落进草稿
|
||||
} catch (err) {
|
||||
|
||||
@@ -406,8 +406,8 @@ export function stripFlowFence(text: string, streaming: boolean): string {
|
||||
const tail = close >= 0 ? after.slice(close + 3) : "";
|
||||
const placeholder =
|
||||
close < 0 && streaming
|
||||
? "\n\n> 📊 正在输出结构化流程数据,流程图画布同步绘制中…"
|
||||
: "\n\n> 📊 已生成结构化流程数据,可在右侧「流程图」画布查看(点击节点看详情)。";
|
||||
? "\n\n> 正在输出结构化流程数据,流程图画布同步绘制中…"
|
||||
: "\n\n> 已生成结构化流程数据,可在右侧「流程图」画布查看(点击节点看详情)。";
|
||||
return `${text.slice(0, start).trimEnd()}${placeholder}${tail}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,8 +138,8 @@ export function stripReportFence(text: string, streaming: boolean): string {
|
||||
// ── 解析(容错:取最后一个 report-json 代码块;缺字段给安全默认) ─────────────────
|
||||
|
||||
const FENCE = "```report-json";
|
||||
const STREAM_MSG = "\n\n> 📊 正在输出大屏结构化数据,右侧沙箱稍后自动渲染…";
|
||||
const DONE_MSG = "\n\n> 📊 已生成大屏数据,右侧沙箱已渲染预览(可继续输入修改要求调整)。";
|
||||
const STREAM_MSG = "\n\n> 正在输出大屏结构化数据,右侧沙箱稍后自动渲染…";
|
||||
const DONE_MSG = "\n\n> 已生成大屏数据,右侧沙箱已渲染预览(可继续输入修改要求调整)。";
|
||||
|
||||
/** 文本是否像本契约 JSON(含关键键),避免把无关 {} 误当数据。 */
|
||||
function looksLikeReportJson(s: string): boolean {
|
||||
@@ -753,7 +753,7 @@ export function renderDashboardHtml(
|
||||
const allActive = seatBase ? `${ovBase},${seatBase}` : ovBase;
|
||||
const navActiveCss = `${allActive}{background:var(--navactive);color:var(--primary);box-shadow:0 1px 5px rgba(15,23,42,.1);font-weight:700;border-color:${P.primary}33}`;
|
||||
|
||||
const KPI_ICONS = ["📊", "📈", "⚠️", "🧩", "🏆", "📅"];
|
||||
const KPI_ICONS = ["数", "趋", "险", "组", "优", "期"];
|
||||
const kpis = data.kpis
|
||||
.map((k, i) => {
|
||||
const ac = P.accents[i % P.accents.length];
|
||||
@@ -787,7 +787,7 @@ export function renderDashboardHtml(
|
||||
|
||||
const console = data.charts.length
|
||||
? `<section class="console">
|
||||
<div class="chead"><h2 class="sec">📊 数据研判中心</h2><span class="status">STATUS: REAL-TIME CONSOLE</span></div>
|
||||
<div class="chead"><h2 class="sec">数据研判中心</h2><span class="status">STATUS: REAL-TIME CONSOLE</span></div>
|
||||
<div class="charts3">${data.charts.map(chartHtml).join("")}</div>
|
||||
</section>`
|
||||
: "";
|
||||
@@ -799,7 +799,7 @@ export function renderDashboardHtml(
|
||||
<a class="more" href="#consensus-modal">查看研讨详情 →</a>
|
||||
</div>
|
||||
${console}
|
||||
<h2 class="sec">📋 席位交付总览 <small>点击卡片进入对应席位专页</small></h2>
|
||||
<h2 class="sec">席位交付总览 <small>点击卡片进入对应席位专页</small></h2>
|
||||
<div class="seats">${seatCards}</div>
|
||||
${timeline}
|
||||
</section>`;
|
||||
@@ -955,7 +955,7 @@ body:has(.page:target) #overview:not(:target){display:none}
|
||||
.fill{height:100%;border-radius:99px}
|
||||
.chips{display:flex;flex-direction:column;gap:6px;margin-top:12px;padding-top:11px;border-top:1px solid var(--line)}
|
||||
.chip{font-size:10px;padding:3px 8px;border-radius:4px;border-left:2px solid;display:flex;align-items:center;gap:5px}
|
||||
.chip::before{content:"⚠";font-size:9px}
|
||||
.chip::before{content:"!";font-size:9px}
|
||||
.tl{margin-top:2px}
|
||||
.tl ol{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:11px}
|
||||
.tl li{position:relative;padding:12px 14px;border:1px solid var(--line);border-left:3px solid var(--primary);background:var(--track);border-radius:0 5px 5px 0}
|
||||
|
||||
@@ -911,7 +911,7 @@ export default function BusinessChainEditorPage() {
|
||||
R_CARD,
|
||||
)}
|
||||
>
|
||||
🪶
|
||||
链
|
||||
</div>
|
||||
<div className="leading-tight">
|
||||
<div className="text-base font-bold text-foreground">业务链条配置</div>
|
||||
|
||||
@@ -175,7 +175,7 @@ export default function BusinessChainListPage() {
|
||||
toast.success(
|
||||
chain.isPublic
|
||||
? `已取消发布「${chain.title || "未命名链条"}」`
|
||||
: `🌍「${chain.title || "未命名链条"}」已发布到公共选用库`,
|
||||
: `「${chain.title || "未命名链条"}」已发布到公共选用库`,
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(`操作失败:${err instanceof Error ? err.message : String(err)}`);
|
||||
@@ -196,7 +196,7 @@ export default function BusinessChainListPage() {
|
||||
await deleteChain(chain.id);
|
||||
await refresh(scope);
|
||||
await loadStats();
|
||||
toast.success(`🗑️ 已删除链条「${name}」`);
|
||||
toast.success(`已删除链条「${name}」`);
|
||||
setPendingDelete(null);
|
||||
} catch (err) {
|
||||
toast.error(`删除失败:${err instanceof Error ? err.message : String(err)}`);
|
||||
|
||||
@@ -1052,13 +1052,13 @@ export default function RoundtablePlanningPage({
|
||||
const candidates = filterRecommendCandidates(agents);
|
||||
const result = buildChainSeatSelection(chain, candidates);
|
||||
if (!result) {
|
||||
triggerToast("⚠️ 该业务链条暂无法选用(匹配到的席位不足)");
|
||||
triggerToast("该业务链条暂无法选用(匹配到的席位不足)");
|
||||
return;
|
||||
}
|
||||
setAutoChainResult(result);
|
||||
triggerToast(`✅ 已选择业务链「${chain.title}」,完成第一步澄清后点「进入多智能体研讨」`);
|
||||
triggerToast(`已选择业务链「${chain.title}」,完成第一步澄清后点「进入多智能体研讨」`);
|
||||
} catch {
|
||||
triggerToast("❌ 选择业务链失败,请重试");
|
||||
triggerToast("选择业务链失败,请重试");
|
||||
}
|
||||
},
|
||||
[autoChainResult, triggerToast],
|
||||
@@ -1276,7 +1276,7 @@ export default function RoundtablePlanningPage({
|
||||
navigate("/page/strategy/admin/roundtable-diagnostics");
|
||||
return;
|
||||
}
|
||||
triggerToast("⚙️ 系统管理面板拉取中(暂不开放外部审计)");
|
||||
triggerToast("系统管理面板拉取中(暂不开放外部审计)");
|
||||
}, [buildWujieConsensusPayload, emitConsensusToParent, inWujieEmbed, navigate, triggerToast]);
|
||||
|
||||
// C4(无界回传):第二步**本次 live 研讨** newly 达成共识时,把总控结论 + 各智能体交付广播给父页面。
|
||||
@@ -1576,7 +1576,7 @@ export default function RoundtablePlanningPage({
|
||||
|
||||
const handleStartNewTask = useCallback(() => {
|
||||
resetToStep1();
|
||||
triggerToast("✨ 已开始新任务,请在第一步与任务理解智能体澄清需求");
|
||||
triggerToast("已开始新任务,请在第一步与任务理解智能体澄清需求");
|
||||
}, [resetToStep1, triggerToast]);
|
||||
|
||||
// 深链接(taskId)模式专用的「重新分析」:清空第一步会话、保留当前任务(prefill 不动),并复位
|
||||
@@ -1585,7 +1585,7 @@ export default function RoundtablePlanningPage({
|
||||
const handleReanalyze = useCallback(() => {
|
||||
autoStartedRef.current = false;
|
||||
resetToStep1();
|
||||
triggerToast("🔄 已重新分析当前任务");
|
||||
triggerToast("已重新分析当前任务");
|
||||
}, [resetToStep1, triggerToast]);
|
||||
|
||||
// 无界抽屉保活复用:父页面重开抽屉时 `bus.$emit('roundtable:reset')` → 子应用回到第一步。
|
||||
@@ -1684,7 +1684,7 @@ export default function RoundtablePlanningPage({
|
||||
const handleBackgroundSuspend = useCallback(() => {
|
||||
const selected = step2.selectedAgents;
|
||||
if (selected.length === 0) {
|
||||
triggerToast("⚠️ 请先选择研讨席位再后台挂起");
|
||||
triggerToast("请先选择研讨席位再后台挂起");
|
||||
return;
|
||||
}
|
||||
// —— 跳转前同步冻结所有需要的输入(reset 之后这些 live 状态都会被清空)——
|
||||
@@ -1706,7 +1706,7 @@ export default function RoundtablePlanningPage({
|
||||
// 立刻:标记「挂起中」+ 跳回第一步 + 提示(不等接口)。
|
||||
if (draftId) markPendingSuspend(draftId);
|
||||
resetToStep1();
|
||||
triggerToast("🚀 已转入后台,任务会自动跑完研讨、总结报告与结果绘制;可在「历史记录」查看进度");
|
||||
triggerToast("已转入后台,任务会自动跑完研讨、总结报告与结果绘制;可在「历史记录」查看进度");
|
||||
|
||||
// 后台 fire-and-forget:用冻结快照存稿 → 去重 → 建作业 → 注册完成通知。失败只提示、不挡跳转。
|
||||
void (async () => {
|
||||
@@ -1742,7 +1742,7 @@ export default function RoundtablePlanningPage({
|
||||
watchId = job.id;
|
||||
}
|
||||
} catch (err) {
|
||||
triggerToast(`❌ 后台挂起失败:${(err as Error).message}`);
|
||||
triggerToast(`后台挂起失败:${(err as Error).message}`);
|
||||
} finally {
|
||||
// 注册到全工作区通知器,作业完成时**跨页**弹「达成共识同款」提示。
|
||||
if (watchId) watchJob(watchId);
|
||||
@@ -1842,7 +1842,7 @@ export default function RoundtablePlanningPage({
|
||||
id: Date.now() + Math.random(),
|
||||
sender: "agent",
|
||||
senderName: "系统提示",
|
||||
content: "⚠️ 您跳过了当前追问,系统已使用深度预测模型填充默认最稳健配置。",
|
||||
content: "您跳过了当前追问,系统已使用深度预测模型填充默认最稳健配置。",
|
||||
time: nowTime(),
|
||||
});
|
||||
triggerToast("已跳过,自动填充最优配置");
|
||||
@@ -1854,7 +1854,7 @@ export default function RoundtablePlanningPage({
|
||||
sender: "agent",
|
||||
senderName: "任务理解智能体",
|
||||
content:
|
||||
"🔎 为了使方案时间边界更加安全:请问72小时内是否允许团队进行全仿真机房集成演练?这需要额外调拨3% of 预算冗余,但安全系数能提升12%。",
|
||||
"为了使方案时间边界更加安全:请问72小时内是否允许团队进行全仿真机房集成演练?这需要额外调拨3% of 预算冗余,但安全系数能提升12%。",
|
||||
time: nowTime(),
|
||||
});
|
||||
triggerToast("追问生成就绪!");
|
||||
@@ -2153,7 +2153,7 @@ export default function RoundtablePlanningPage({
|
||||
onFollowUp={handleAgentFollowUp}
|
||||
onEnterRoundtable={() => {
|
||||
if (!step1.intentReady) {
|
||||
triggerToast("⚠️ 请先与任务理解智能体完成澄清,生成任务摘要后再进入");
|
||||
triggerToast("请先与任务理解智能体完成澄清,生成任务摘要后再进入");
|
||||
return;
|
||||
}
|
||||
// 会商自动化串联(模块 C):若任务页带来的业务链已解析成功,跳过选链弹窗,
|
||||
@@ -2223,12 +2223,12 @@ export default function RoundtablePlanningPage({
|
||||
onSavePersona={step2.handleSaveAgentAndRecalculate}
|
||||
onProceedToStep3={() => {
|
||||
if (!step2.hasConsensus) {
|
||||
triggerToast("⚠️ 圆桌尚未自然收敛,请等待总控派活全部完成");
|
||||
triggerToast("圆桌尚未自然收敛,请等待总控派活全部完成");
|
||||
return;
|
||||
}
|
||||
setCurrentStep(3);
|
||||
triggerToast(
|
||||
"🎉 多角色智能体博弈共识达成,最终交付全套路线成果文档及方案图!",
|
||||
"多角色智能体博弈共识达成,最终交付全套路线成果文档及方案图!",
|
||||
);
|
||||
}}
|
||||
onBackgroundSuspend={handleBackgroundSuspend}
|
||||
@@ -2345,7 +2345,7 @@ function StepNavigation({
|
||||
locked={step2Locked}
|
||||
onClick={() => {
|
||||
if (step2Locked) {
|
||||
triggerToast("⚠️ 请先在第一步完成任务理解,确认[INTENT_READY]信号后再进入");
|
||||
triggerToast("请先在第一步完成任务理解,确认[INTENT_READY]信号后再进入");
|
||||
return;
|
||||
}
|
||||
onStepClick(2);
|
||||
@@ -2361,8 +2361,8 @@ function StepNavigation({
|
||||
if (step3Locked) {
|
||||
triggerToast(
|
||||
intentReady
|
||||
? "⚠️ 请先在第二步达成共识(总控自然收敛)再进入"
|
||||
: "⚠️ 请先依次完成第一步和第二步",
|
||||
? "请先在第二步达成共识(总控自然收敛)再进入"
|
||||
: "请先依次完成第一步和第二步",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -2414,7 +2414,7 @@ function StepButton({
|
||||
{index}
|
||||
</span>
|
||||
<span>{label}</span>
|
||||
{locked && <span className="text-[9px] ml-0.5">🔒</span>}
|
||||
{locked && <span className="ml-0.5 text-[9px]">锁</span>}
|
||||
{active && (
|
||||
<span className="absolute bottom-0 left-1/2 -translate-x-1/2 w-8 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
@@ -4265,7 +4265,7 @@ function RightSidebar({
|
||||
<span className="text-slate-400 font-medium">共 {timelineEntries.length} 条记录</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
triggerToast(`📊 正在渲染全部 ${timelineEntries.length} 个历史事件归档`)
|
||||
triggerToast(`正在渲染全部 ${timelineEntries.length} 个历史事件归档`)
|
||||
}
|
||||
className="text-primary hover:text-[var(--brand-900)] font-bold hover:underline"
|
||||
>
|
||||
|
||||
@@ -104,6 +104,24 @@
|
||||
--brand-900: #4096ff; /* 红强调 / 按钮 hover → 蓝(zs-002-05) */
|
||||
--brand-950: #91caff; /* 深红文字 → 浅蓝(zs-002-03,深底上可读) */
|
||||
|
||||
/* Tailwind opacity fallbacks used by Chrome 109. Keep these in sync with
|
||||
* the scoped semantic tokens above, otherwise /5, /20, etc. become opaque. */
|
||||
--legacy-background-rgb: 6 14 36;
|
||||
--legacy-foreground-rgb: 197 207 224;
|
||||
--legacy-card-rgb: 3 38 80;
|
||||
--legacy-muted-rgb: 2 24 58;
|
||||
--legacy-muted-foreground-rgb: 110 136 176;
|
||||
--legacy-secondary-rgb: 26 47 90;
|
||||
--legacy-accent-rgb: 26 47 90;
|
||||
--legacy-primary-rgb: 22 119 255;
|
||||
--legacy-primary-foreground-rgb: 255 255 255;
|
||||
--legacy-ring-rgb: 22 119 255;
|
||||
--legacy-border-rgb: 43 143 254;
|
||||
--legacy-input-rgb: 90 169 255;
|
||||
--legacy-destructive-rgb: 245 34 45;
|
||||
--legacy-workspace-brand-rgb: 22 119 255;
|
||||
--legacy-brand-200-rgb: 22 51 95;
|
||||
|
||||
/* —— 圆桌局部别名(回落到上面已覆盖的 token) —— */
|
||||
--rt-page-bg: var(--background);
|
||||
--rt-primary: var(--primary);
|
||||
@@ -134,22 +152,30 @@
|
||||
--muted: #0f1c3d;
|
||||
--secondary: #1a2f5a;
|
||||
--accent: #1a2f5a;
|
||||
--legacy-background-rgb: 18 35 71;
|
||||
--legacy-card-rgb: 12 24 54;
|
||||
--legacy-muted-rgb: 15 28 61;
|
||||
--legacy-secondary-rgb: 26 47 90;
|
||||
--legacy-accent-rgb: 26 47 90;
|
||||
background-color: var(--background);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.dark .roundtable-planning ::-webkit-scrollbar-thumb,
|
||||
.roundtable-planning--dark ::-webkit-scrollbar-thumb {
|
||||
background: rgb(32 132 255 / 70%);
|
||||
background: color-mix(in oklch, var(--primary) 45%, var(--border));
|
||||
}
|
||||
|
||||
.dark .roundtable-planning ::-webkit-scrollbar-thumb:hover,
|
||||
.roundtable-planning--dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(29 128 255 / 82%);
|
||||
background: color-mix(in oklch, var(--primary) 65%, var(--border));
|
||||
}
|
||||
|
||||
.dark .roundtable-planning .custom-scrollbar-inner::-webkit-scrollbar-thumb,
|
||||
.roundtable-planning--dark .custom-scrollbar-inner::-webkit-scrollbar-thumb {
|
||||
background: rgb(22 119 255 / 35%);
|
||||
background: color-mix(in oklch, var(--primary) 35%, transparent);
|
||||
}
|
||||
|
||||
@@ -216,15 +242,16 @@
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.from-\[var\(--brand-200\)\]) {
|
||||
--tw-gradient-from: color-mix(in oklch, var(--accent) 60%, transparent) !important;
|
||||
--tw-gradient-from: rgb(26 47 90 / 60%) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.via-\[var\(--brand-200\)\]) {
|
||||
--tw-gradient-via: color-mix(in oklch, var(--accent) 60%, transparent) !important;
|
||||
--tw-gradient-via: rgb(26 47 90 / 60%) !important;
|
||||
}
|
||||
|
||||
/* ---- 品牌浅红底(brand-200 等) → accent ---- */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) [class*="brand-200"] {
|
||||
background-color: rgb(26 47 90 / 70%) !important;
|
||||
background-color: color-mix(in oklch, var(--accent) 70%, transparent) !important;
|
||||
}
|
||||
|
||||
@@ -323,12 +350,13 @@
|
||||
.focus-within\:ring-\[var\(--brand-200\)\]\/50:focus-within,
|
||||
.focus\:ring-\[var\(--brand-200\)\]\/50:focus
|
||||
) {
|
||||
--tw-ring-color: color-mix(in oklch, var(--primary) 30%, transparent) !important;
|
||||
--tw-ring-color: rgb(22 119 255 / 30%) !important;
|
||||
}
|
||||
|
||||
/* ---- 选中边框 border-primary → 主色描边(柔化) ---- */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .border-primary,
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .border-2.border-primary {
|
||||
border-color: rgb(32 132 255 / 70%) !important;
|
||||
border-color: color-mix(in oklch, var(--primary) 55%, var(--border)) !important;
|
||||
}
|
||||
|
||||
@@ -341,10 +369,12 @@
|
||||
/* —— 成功 · emerald —— */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.bg-emerald-50, .bg-emerald-50\/40, .bg-emerald-50\/50, .bg-emerald-100, .bg-emerald-100\/40) {
|
||||
background-color: #054059 !important;
|
||||
background-color: color-mix(in oklch, #10b981 18%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.border-emerald-200, .border-emerald-200\/40, .border-emerald-200\/50) {
|
||||
border-color: #20a1ca !important;
|
||||
border-color: color-mix(in oklch, #10b981 42%, var(--border)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
@@ -358,6 +388,7 @@
|
||||
.bg-amber-50, .bg-amber-50\/60, .bg-amber-100, .bg-amber-100\/40, .bg-amber-200,
|
||||
.hover\:bg-amber-100:hover, .hover\:bg-amber-200:hover
|
||||
) {
|
||||
background-color: #333e42 !important;
|
||||
background-color: color-mix(in oklch, #f59e0b 20%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
@@ -365,6 +396,7 @@
|
||||
.border-amber-100, .border-amber-200, .border-amber-200\/40, .border-amber-300,
|
||||
.border-amber-300\/50, .border-amber-500, .border-amber-700\/50
|
||||
) {
|
||||
border-color: #809598 !important;
|
||||
border-color: color-mix(in oklch, #f59e0b 42%, var(--border)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
@@ -372,12 +404,14 @@
|
||||
color: #fcd34d !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .focus\:ring-amber-200:focus {
|
||||
--tw-ring-color: rgb(245 158 11 / 35%) !important;
|
||||
--tw-ring-color: color-mix(in oklch, #f59e0b 35%, transparent) !important;
|
||||
}
|
||||
|
||||
/* —— 错误 · red / rose —— */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.bg-red-50, .bg-red-200, .bg-rose-50, .hover\:bg-rose-50:hover) {
|
||||
background-color: #2d2b4e !important;
|
||||
background-color: color-mix(in oklch, #ef4444 18%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
@@ -387,19 +421,23 @@
|
||||
|
||||
/* —— 信息 · sky(部分组件已自带 dark: 变体,这里兜底无 dark: 的) —— */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .bg-sky-50 {
|
||||
background-color: #053d6f !important;
|
||||
background-color: color-mix(in oklch, #0ea5e9 18%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.border-sky-200, .border-sky-300\/50) {
|
||||
border-color: #1f98f5 !important;
|
||||
border-color: color-mix(in oklch, #0ea5e9 42%, var(--border)) !important;
|
||||
}
|
||||
|
||||
/* —— 装饰 · purple —— */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
:is(.bg-purple-50, .bg-purple-100\/40) {
|
||||
background-color: #212e6e !important;
|
||||
background-color: color-mix(in oklch, #a855f7 18%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .border-purple-200 {
|
||||
border-color: #5f77fb !important;
|
||||
border-color: color-mix(in oklch, #a855f7 42%, var(--border)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||
@@ -409,12 +447,15 @@
|
||||
|
||||
/* —— 装饰 · orange / pink —— */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .bg-orange-50 {
|
||||
background-color: #2f3144 !important;
|
||||
background-color: color-mix(in oklch, #f97316 18%, var(--card)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .border-orange-200\/50 {
|
||||
border-color: #828bc7 !important;
|
||||
border-color: color-mix(in oklch, #f97316 42%, var(--border)) !important;
|
||||
}
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .bg-pink-100\/40 {
|
||||
background-color: #2e2862 !important;
|
||||
background-color: color-mix(in oklch, #ec4899 18%, var(--card)) !important;
|
||||
}
|
||||
|
||||
@@ -554,6 +595,7 @@
|
||||
|
||||
.dark .custom-scrollbar-inner::-webkit-scrollbar-thumb,
|
||||
.roundtable-planning--dark .custom-scrollbar-inner::-webkit-scrollbar-thumb {
|
||||
background: rgb(36 135 254 / 65%);
|
||||
background: color-mix(in oklch, var(--primary) 35%, var(--border));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
ThemeSwitcher,
|
||||
} from "@/components/workspace/appearance-controls";
|
||||
import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
||||
import { getStoredAuth, getAccountDisplayName } from "@/core/auth";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useLiveAccount } from "@/core/auth/use-live-account";
|
||||
import { isWujieEnv } from "@/core/embed/wujie";
|
||||
import { useUnreadCount } from "@/core/notifications";
|
||||
import { useMyPosition } from "@/core/positions";
|
||||
@@ -48,13 +49,14 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
const { data: unreadCount = 0 } = useUnreadCount();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const liveAccount = useLiveAccount();
|
||||
const auth = getStoredAuth();
|
||||
const isAdmin = auth?.system_role === "admin";
|
||||
const visibleMenuItems = useSidebarMenuItems(!!isAdmin);
|
||||
const { data: myPosition } = useMyPosition();
|
||||
const positionName = myPosition?.name?.trim() || "";
|
||||
|
||||
const displayName = getAccountDisplayName(auth);
|
||||
const displayName = liveAccount.displayName;
|
||||
const showTopNav = isDark && !collapsed && !isWujieEnv();
|
||||
// 浅色(红/蓝)顶栏:一级导航正常横排进顶栏,不做深色那种围绕基座的特殊处理。
|
||||
// 与 collapsed 解耦(折叠只影响侧边栏,一级导航始终可见);wujie 嵌入下隐藏,
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { requestCrudService } from '@/service/request-axios-service';
|
||||
import { authStorageGet } from '@/core/auth/scoped-storage';
|
||||
import { copyTextToClipboard } from '@/core/clipboard';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
/**
|
||||
* 通用问答组件
|
||||
@@ -38,11 +39,11 @@ import { copyTextToClipboard } from '@/core/clipboard';
|
||||
* @param initialMode - 初始模式(可选)
|
||||
*/
|
||||
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const chatUrl = import.meta.env.VITE_AIPI_CHAT_URL || '';
|
||||
const generateUrl = import.meta.env.VITE_AIPI_GENERATE_CHAT_URL || '';
|
||||
const taskChatUrl = import.meta.env.VITE_AIPI_TASK_CHAT_URL || '';
|
||||
const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL;
|
||||
const zncmUrl = readRuntimeEnv('VITE_API_BASE_URL') || '';
|
||||
const chatUrl = readRuntimeEnv('VITE_AIPI_CHAT_URL') || '';
|
||||
const generateUrl = readRuntimeEnv('VITE_AIPI_GENERATE_CHAT_URL') || '';
|
||||
const taskChatUrl = readRuntimeEnv('VITE_AIPI_TASK_CHAT_URL') || '';
|
||||
const knowledgeUrl = readRuntimeEnv('VITE_AIPI_KNOWLEDGE_URL');
|
||||
const service = requestCrudService;
|
||||
const GeneralQA: React.FC<{
|
||||
onNavigate?: (page: string) => void;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useTheme } from '@/contexts/ThemeContext';
|
||||
import MarkdownRenderer from '@/strategy-components/MarkdownRenderer';
|
||||
import { SourceItem } from './GeneralQA.types';
|
||||
import '../qa/qa-animations.css';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
/**
|
||||
* 单条检索结果。
|
||||
@@ -241,7 +242,7 @@ export function ChatMessage<T extends QACommonMessage>({
|
||||
const isDark = theme === 'dark';
|
||||
const isUser = message.role === 'user';
|
||||
const hasTrace = !isUser && !!(message.singleRagTrace || message.thinkingTrace);
|
||||
const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL;
|
||||
const knowledgeUrl = readRuntimeEnv('VITE_AIPI_KNOWLEDGE_URL');
|
||||
const [visible, setVisible] = useState(false); // 打开详情对话框
|
||||
const [listData, setListData] = useState<SourceItem>({
|
||||
title: '',
|
||||
@@ -531,9 +532,9 @@ const RetrievalList: React.FC<RetrievalListProps> = ({ items }) => {
|
||||
title: '',
|
||||
content: '',
|
||||
});
|
||||
const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL;
|
||||
const b1SearchUrl = import.meta.env.VITE_AIPI_B1COH_URL;
|
||||
const b2SearchUrl = import.meta.env.VITE_AIPI_B2ACTOR_URL;
|
||||
const knowledgeUrl = readRuntimeEnv('VITE_AIPI_KNOWLEDGE_URL');
|
||||
const b1SearchUrl = readRuntimeEnv('VITE_AIPI_B1COH_URL');
|
||||
const b2SearchUrl = readRuntimeEnv('VITE_AIPI_B2ACTOR_URL');
|
||||
const showDialog = (item: QARetrievalItem) => {
|
||||
console.log('item', item)
|
||||
if (item.type === 'more') {
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
--yellow: #f59e0b;
|
||||
--red: #dc2626;
|
||||
--purple: #7c3aed;
|
||||
--accent-rgb: 220 38 38;
|
||||
--text-muted-rgb: 107 114 128;
|
||||
--button-text: #ffffff;
|
||||
--surface-hover: rgba(220, 38, 38, 0.04);
|
||||
--surface-hover-strong: rgba(220, 38, 38, 0.08);
|
||||
@@ -55,6 +57,8 @@
|
||||
--yellow: #f59e0b;
|
||||
--red: #f43f5e;
|
||||
--purple: #8b5cf6;
|
||||
--accent-rgb: 59 130 246;
|
||||
--text-muted-rgb: 148 163 184;
|
||||
--button-text: #ffffff;
|
||||
--surface-hover: rgba(255, 255, 255, 0.03);
|
||||
--surface-hover-strong: rgba(255, 255, 255, 0.06);
|
||||
@@ -137,6 +141,7 @@
|
||||
}
|
||||
|
||||
.mope-chat .app-header {
|
||||
background: var(--surface);
|
||||
background: color-mix(in srgb, var(--surface) 92%, var(--accent) 8%);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 14px 18px;
|
||||
@@ -179,6 +184,7 @@
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
background: color-mix(in srgb, var(--surface) 96%, var(--accent) 4%);
|
||||
}
|
||||
|
||||
@@ -194,6 +200,7 @@
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
border-left: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
background: color-mix(in srgb, var(--surface) 96%, var(--accent) 4%);
|
||||
}
|
||||
|
||||
@@ -322,11 +329,11 @@
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.mope-chat .badge-starting { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
|
||||
.mope-chat .badge-diverging { background: color-mix(in srgb, var(--yellow) 15%, transparent); color: var(--yellow); }
|
||||
.mope-chat .badge-closing { background: color-mix(in srgb, var(--green) 15%, transparent); color: var(--green); }
|
||||
.mope-chat .badge-done { background: color-mix(in srgb, var(--purple) 15%, transparent); color: var(--purple); }
|
||||
.mope-chat .badge-default { background: color-mix(in srgb, var(--text-muted) 15%, transparent); color: var(--text-muted); }
|
||||
.mope-chat .badge-starting { background: rgb(var(--accent-rgb) / 15%); background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
|
||||
.mope-chat .badge-diverging { background: rgb(245 158 11 / 15%); background: color-mix(in srgb, var(--yellow) 15%, transparent); color: var(--yellow); }
|
||||
.mope-chat .badge-closing { background: rgb(16 185 129 / 15%); background: color-mix(in srgb, var(--green) 15%, transparent); color: var(--green); }
|
||||
.mope-chat .badge-done { background: rgb(139 92 246 / 15%); background: color-mix(in srgb, var(--purple) 15%, transparent); color: var(--purple); }
|
||||
.mope-chat .badge-default { background: rgb(var(--text-muted-rgb) / 15%); background: color-mix(in srgb, var(--text-muted) 15%, transparent); color: var(--text-muted); }
|
||||
|
||||
/* Spinner */
|
||||
@keyframes spin {
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
|
||||
.config-mode-switch {
|
||||
display: inline-flex;
|
||||
background: var(--surface);
|
||||
background: color-mix(in srgb, var(--surface) 96%, var(--accent) 4%);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
@@ -281,6 +282,7 @@
|
||||
.config-textarea::placeholder,
|
||||
.config-input::placeholder,
|
||||
.config-json-editor::placeholder {
|
||||
color: rgb(var(--text-muted-rgb) / 72%);
|
||||
color: color-mix(in srgb, var(--text-muted) 72%, transparent);
|
||||
}
|
||||
|
||||
|
||||
@@ -389,7 +389,9 @@
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
border: 1px solid rgb(var(--accent-rgb) / 28%);
|
||||
border-color: color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
box-shadow: 0 8px 18px rgb(var(--accent-rgb) / 10%);
|
||||
box-shadow: 0 8px 18px color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Col, Loading, Pagination, Progress, Row } from 'tdesign-react';
|
||||
import { requestCrudService } from '@/service/request-axios-service';
|
||||
import { ActionColumn } from './ActionColumn';
|
||||
import { resolveStatusIcon } from './icons';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
interface ActionAnalyzesProps {
|
||||
TaskType?: string;
|
||||
@@ -17,7 +18,7 @@ interface ActionAnalyzesProps {
|
||||
}
|
||||
|
||||
const service = requestCrudService;
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const zncmUrl = readRuntimeEnv('VITE_API_BASE_URL') || '';
|
||||
export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category, subCategory}) => {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Col, Pagination, Progress, Row } from 'tdesign-react';
|
||||
import { requestCrudService } from '@/service/request-axios-service';
|
||||
import { resolveStatusIcon } from './icons';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
interface ActionColumnProps {
|
||||
TaskType?: string;
|
||||
@@ -21,7 +22,7 @@ interface ActionColumnProps {
|
||||
}
|
||||
|
||||
const service = requestCrudService;
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const zncmUrl = readRuntimeEnv('VITE_API_BASE_URL') || '';
|
||||
export const ActionColumn: React.FC<ActionColumnProps> = ({ title, category, subCategory, icon, columnName, columnIndex, actionCount, actionList }) => {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import { toast } from 'sonner';
|
||||
import { KanbanColumn } from './KanbanColumn';
|
||||
import { TaskListItem } from './TaskManagement';
|
||||
import { listBusinessMappings } from '@/strategy-components/api/business-mapping';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
interface TaskListPageProps {
|
||||
title: string;
|
||||
@@ -25,7 +26,7 @@ interface StateItem {
|
||||
type ColumnType = 'unanalyzed' | 'running' | 'completed';
|
||||
|
||||
const service = requestCrudService;
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const zncmUrl = readRuntimeEnv('VITE_API_BASE_URL') || '';
|
||||
/** 本页对应的业务码(3QFX = 3Q);用于查业务映射拿到要自动选用的业务链 / 单智能体。 */
|
||||
const BUSINESS_CODE = '3Q';
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { toast } from 'sonner';
|
||||
import { KanbanColumn } from './KanbanColumn';
|
||||
import { TaskListItem } from './TaskManagement';
|
||||
import { listBusinessMappings } from '@/strategy-components/api/business-mapping';
|
||||
import { readRuntimeEnv } from '@/env';
|
||||
|
||||
interface TaskListPageProps {
|
||||
title: string;
|
||||
@@ -25,7 +26,7 @@ interface StateItem {
|
||||
type ColumnType = 'unanalyzed' | 'running' | 'completed';
|
||||
|
||||
const service = requestCrudService;
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const zncmUrl = readRuntimeEnv('VITE_API_BASE_URL') || '';
|
||||
/** 本页对应的业务码(任务FX = 6BF);用于查业务映射拿到要自动选用的业务链 / 单智能体。 */
|
||||
const BUSINESS_CODE = '6BF';
|
||||
|
||||
|
||||
@@ -135,23 +135,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
--animate-wave: wave 0.6s ease-in-out 2;
|
||||
@keyframes wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
}
|
||||
|
||||
--animate-breathe-stop: breathe-stop 1.8s ease-in-out infinite;
|
||||
@keyframes breathe-stop {
|
||||
0%,
|
||||
@@ -253,73 +236,113 @@
|
||||
--brand-900: #cf1322;
|
||||
--brand-950: #a8071a;
|
||||
--radius: 0.75rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.22 0.02 25);
|
||||
--card: oklch(1 0.002 95);
|
||||
--card-foreground: oklch(0.22 0.02 25);
|
||||
--popover: oklch(1 0.002 95);
|
||||
--popover-foreground: oklch(0.22 0.02 25);
|
||||
--primary: oklch(0.52 0.19 27);
|
||||
--primary-foreground: oklch(0.99 0 0);
|
||||
--secondary: oklch(0.955 0.008 95);
|
||||
--secondary-foreground: oklch(0.32 0.04 25);
|
||||
--muted: oklch(0.965 0.006 95);
|
||||
--muted-foreground: oklch(0.48 0.02 25);
|
||||
--accent: oklch(0.96 0.04 25);
|
||||
--accent-foreground: oklch(0.35 0.12 25);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.9 0.008 95);
|
||||
--input: oklch(0.9 0.01 95);
|
||||
--ring: oklch(0.52 0.19 27 / 0.35);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.995 0.003 95);
|
||||
--sidebar-foreground: oklch(0.28 0.02 25);
|
||||
--sidebar-primary: oklch(0.52 0.19 27);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.978 0.018 70);
|
||||
--sidebar-accent-foreground: oklch(0.32 0.05 32);
|
||||
--sidebar-border: oklch(0.92 0.01 85);
|
||||
--sidebar-ring: oklch(0.52 0.19 27 / 0.4);
|
||||
/* Keep the base design tokens in legacy color syntax. CSS custom
|
||||
properties containing oklch() make every dependent Tailwind color rule
|
||||
invalid in Chrome 109 and below. */
|
||||
--background: #ffffff;
|
||||
--foreground: #231716;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #231716;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #231716;
|
||||
--primary: #be2323;
|
||||
--primary-foreground: #fcfcfc;
|
||||
--secondary: #f2f0ea;
|
||||
--secondary-foreground: #513b38;
|
||||
--muted: #f5f3ef;
|
||||
--muted-foreground: #695958;
|
||||
--accent: #ffedeb;
|
||||
--accent-foreground: #7b2727;
|
||||
--destructive: #e40014;
|
||||
--border: #dfded8;
|
||||
--input: #e0ded7;
|
||||
--ring: rgba(190, 35, 35, 0.35);
|
||||
--chart-1: #d94a32;
|
||||
--chart-2: #188f95;
|
||||
--chart-3: #4f6680;
|
||||
--chart-4: #e8a72a;
|
||||
--chart-5: #db8d16;
|
||||
--sidebar: #fdfcfb;
|
||||
--sidebar-foreground: #322524;
|
||||
--sidebar-primary: #be2323;
|
||||
--sidebar-primary-foreground: #fcfcfc;
|
||||
--sidebar-accent: #fff6ec;
|
||||
--sidebar-accent-foreground: #633a32;
|
||||
--sidebar-border: #e8e4dd;
|
||||
--sidebar-ring: rgba(190, 35, 35, 0.4);
|
||||
|
||||
/* Chrome 109 fallback channels for Tailwind opacity modifiers. */
|
||||
--legacy-accent-rgb: 255 237 235;
|
||||
--legacy-background-rgb: 255 255 255;
|
||||
--legacy-border-rgb: 223 222 216;
|
||||
--legacy-brand-200-rgb: 254 237 237;
|
||||
--legacy-card-rgb: 255 255 255;
|
||||
--legacy-destructive-rgb: 228 0 20;
|
||||
--legacy-foreground-rgb: 35 23 22;
|
||||
--legacy-input-rgb: 224 222 215;
|
||||
--legacy-muted-rgb: 245 243 239;
|
||||
--legacy-muted-foreground-rgb: 105 89 88;
|
||||
--legacy-primary-rgb: 190 35 35;
|
||||
--legacy-primary-foreground-rgb: 252 252 252;
|
||||
--legacy-ring-rgb: 190 35 35;
|
||||
--legacy-secondary-rgb: 242 240 234;
|
||||
--legacy-sidebar-accent-rgb: 255 246 236;
|
||||
--legacy-sidebar-border-rgb: 232 228 221;
|
||||
--legacy-sidebar-foreground-rgb: 50 37 36;
|
||||
--legacy-workspace-brand-rgb: 245 34 45;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--workspace-brand: #3b82f6;
|
||||
--workspace-brand-foreground: #ffffff;
|
||||
--background: oklch(0.208 0.042 264);
|
||||
--foreground: oklch(0.96 0.005 240);
|
||||
--card: oklch(0.265 0.04 260);
|
||||
--card-foreground: oklch(0.96 0.005 240);
|
||||
--popover: oklch(0.245 0.04 262);
|
||||
--popover-foreground: oklch(0.96 0.005 240);
|
||||
--primary: oklch(0.623 0.214 259);
|
||||
--primary-foreground: oklch(0.99 0 0);
|
||||
--secondary: oklch(0.32 0.038 260);
|
||||
--secondary-foreground: oklch(0.96 0.005 240);
|
||||
--muted: oklch(0.30 0.035 260);
|
||||
--muted-foreground: oklch(0.68 0.02 240);
|
||||
--accent: oklch(0.35 0.06 260);
|
||||
--accent-foreground: oklch(0.96 0.005 240);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 12%);
|
||||
--input: oklch(1 0 0 / 14%);
|
||||
--ring: oklch(0.623 0.214 259 / 0.45);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.185 0.04 265);
|
||||
--sidebar-foreground: oklch(0.94 0.005 240);
|
||||
--sidebar-primary: oklch(0.623 0.214 259);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.30 0.055 260);
|
||||
--sidebar-accent-foreground: oklch(0.96 0.005 240);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.623 0.214 259 / 0.45);
|
||||
--background: #0e172b;
|
||||
--foreground: #eff2f5;
|
||||
--card: #1a2539;
|
||||
--card-foreground: #eff2f5;
|
||||
--popover: #17243a;
|
||||
--popover-foreground: #eff2f5;
|
||||
--primary: #2b81ff;
|
||||
--primary-foreground: #fcfcfc;
|
||||
--secondary: #273346;
|
||||
--secondary-foreground: #eff2f5;
|
||||
--muted: #232e40;
|
||||
--muted-foreground: #8e9aa4;
|
||||
--accent: #283b5a;
|
||||
--accent-foreground: #eff2f5;
|
||||
--destructive: #ff6568;
|
||||
--border: rgba(255, 255, 255, 0.12);
|
||||
--input: rgba(255, 255, 255, 0.14);
|
||||
--ring: rgba(43, 129, 255, 0.45);
|
||||
--chart-1: #236bdb;
|
||||
--chart-2: #51c49d;
|
||||
--chart-3: #e8b02a;
|
||||
--chart-4: #a65be5;
|
||||
--chart-5: #e65a42;
|
||||
--sidebar: #0d1830;
|
||||
--sidebar-foreground: #e8ecee;
|
||||
--sidebar-primary: #2b81ff;
|
||||
--sidebar-primary-foreground: #fcfcfc;
|
||||
--sidebar-accent: #1d2e49;
|
||||
--sidebar-accent-foreground: #eff2f5;
|
||||
--sidebar-border: rgba(255, 255, 255, 0.1);
|
||||
--sidebar-ring: rgba(43, 129, 255, 0.45);
|
||||
--legacy-accent-rgb: 40 59 90;
|
||||
--legacy-background-rgb: 14 23 43;
|
||||
--legacy-border-rgb: 255 255 255;
|
||||
--legacy-card-rgb: 26 37 57;
|
||||
--legacy-destructive-rgb: 255 101 104;
|
||||
--legacy-foreground-rgb: 239 242 245;
|
||||
--legacy-input-rgb: 255 255 255;
|
||||
--legacy-muted-rgb: 35 46 64;
|
||||
--legacy-muted-foreground-rgb: 142 154 164;
|
||||
--legacy-primary-rgb: 43 129 255;
|
||||
--legacy-primary-foreground-rgb: 252 252 252;
|
||||
--legacy-ring-rgb: 43 129 255;
|
||||
--legacy-secondary-rgb: 39 51 70;
|
||||
--legacy-sidebar-accent-rgb: 29 46 73;
|
||||
--legacy-sidebar-border-rgb: 255 255 255;
|
||||
--legacy-sidebar-foreground-rgb: 232 236 238;
|
||||
--legacy-workspace-brand-rgb: 59 130 246;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -364,6 +387,24 @@
|
||||
--sidebar-accent-foreground: #0056c6;
|
||||
--sidebar-border: #e6ebf2;
|
||||
--sidebar-ring: rgb(0 98 217 / 0.4);
|
||||
--legacy-accent-rgb: 234 244 255;
|
||||
--legacy-background-rgb: 255 255 255;
|
||||
--legacy-border-rgb: 219 219 219;
|
||||
--legacy-brand-200-rgb: 234 244 255;
|
||||
--legacy-card-rgb: 255 255 255;
|
||||
--legacy-destructive-rgb: 245 34 45;
|
||||
--legacy-foreground-rgb: 61 61 61;
|
||||
--legacy-input-rgb: 219 219 219;
|
||||
--legacy-muted-rgb: 243 245 248;
|
||||
--legacy-muted-foreground-rgb: 127 127 127;
|
||||
--legacy-primary-rgb: 0 98 217;
|
||||
--legacy-primary-foreground-rgb: 255 255 255;
|
||||
--legacy-ring-rgb: 0 98 217;
|
||||
--legacy-secondary-rgb: 244 249 255;
|
||||
--legacy-sidebar-accent-rgb: 234 244 255;
|
||||
--legacy-sidebar-border-rgb: 230 235 242;
|
||||
--legacy-sidebar-foreground-rgb: 61 61 61;
|
||||
--legacy-workspace-brand-rgb: 0 98 217;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -698,7 +739,7 @@
|
||||
}
|
||||
|
||||
.dark .workspace-panel {
|
||||
border: 1px solid oklch(1 0 0 / 10%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: var(--card);
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -1004,6 +1045,7 @@
|
||||
--container-width-sm: calc(var(--spacing) * 144);
|
||||
--container-width-md: calc(var(--spacing) * 204);
|
||||
--container-width-lg: calc(var(--spacing) * 256);
|
||||
--container-width-xl: calc(var(--spacing) * 280);
|
||||
}
|
||||
|
||||
/* ── Landing 态输入框 Gemini 风格:旋转彩色描边 + 多色柔光阴影 ───────── */
|
||||
@@ -1237,3 +1279,97 @@ html.lightblue .tiptap-writing-editor .ProseMirror ::selection {
|
||||
color: #1890ff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Legacy Chrome visual fallbacks
|
||||
*
|
||||
* Tailwind v4 deliberately uses color-mix() for `bg-primary/10` and similar
|
||||
* opacity utilities. In Chrome versions without color-mix(), its generated
|
||||
* fallback is the fully opaque primary color. That turns pale selected cards
|
||||
* into solid red blocks and makes same-color icons disappear. Keep critical
|
||||
* agent surfaces on classic rgba()/hex colours instead.
|
||||
* -------------------------------------------------------------------------- */
|
||||
.agent-avatar {
|
||||
background-color: #feeded;
|
||||
color: #cf1322;
|
||||
box-shadow: inset 0 0 0 1px rgba(190, 35, 35, 0.15);
|
||||
}
|
||||
|
||||
.dark .agent-avatar {
|
||||
background-color: #1b355d;
|
||||
color: #ffffff;
|
||||
box-shadow: inset 0 0 0 1px rgba(91, 157, 255, 0.38);
|
||||
}
|
||||
|
||||
.lightblue .agent-avatar {
|
||||
background-color: #eaf4ff;
|
||||
color: #0062d9;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 98, 217, 0.16);
|
||||
}
|
||||
|
||||
.agent-sidebar-card--active {
|
||||
background-color: #feeded;
|
||||
border-color: rgba(190, 35, 35, 0.5);
|
||||
}
|
||||
|
||||
.dark .agent-sidebar-card--active {
|
||||
background-color: #0b4fae;
|
||||
border-color: #2b8ffe;
|
||||
}
|
||||
|
||||
.lightblue .agent-sidebar-card--active {
|
||||
background-color: #eaf4ff;
|
||||
border-color: rgba(0, 98, 217, 0.5);
|
||||
}
|
||||
|
||||
@supports not (color: oklch(1 0 0)) {
|
||||
.bg-primary\/5 { background-color: rgba(190, 35, 35, 0.05); }
|
||||
.bg-primary\/8 { background-color: rgba(190, 35, 35, 0.08); }
|
||||
.bg-primary\/10 { background-color: rgba(190, 35, 35, 0.10); }
|
||||
.bg-primary\/14 { background-color: rgba(190, 35, 35, 0.14); }
|
||||
.bg-primary\/15 { background-color: rgba(190, 35, 35, 0.15); }
|
||||
.bg-primary\/20 { background-color: rgba(190, 35, 35, 0.20); }
|
||||
.bg-primary\/25 { background-color: rgba(190, 35, 35, 0.25); }
|
||||
.bg-primary\/30 { background-color: rgba(190, 35, 35, 0.30); }
|
||||
.bg-primary\/60 { background-color: rgba(190, 35, 35, 0.60); }
|
||||
.bg-primary\/70 { background-color: rgba(190, 35, 35, 0.70); }
|
||||
.bg-primary\/90 { background-color: rgba(190, 35, 35, 0.90); }
|
||||
.border-primary\/15 { border-color: rgba(190, 35, 35, 0.15); }
|
||||
.border-primary\/20 { border-color: rgba(190, 35, 35, 0.20); }
|
||||
.border-primary\/25 { border-color: rgba(190, 35, 35, 0.25); }
|
||||
.border-primary\/30 { border-color: rgba(190, 35, 35, 0.30); }
|
||||
.border-primary\/35 { border-color: rgba(190, 35, 35, 0.35); }
|
||||
.border-primary\/40 { border-color: rgba(190, 35, 35, 0.40); }
|
||||
.border-primary\/50 { border-color: rgba(190, 35, 35, 0.50); }
|
||||
.border-primary\/60 { border-color: rgba(190, 35, 35, 0.60); }
|
||||
.ring-primary\/15 { --tw-ring-color: rgba(190, 35, 35, 0.15); }
|
||||
.ring-primary\/20 { --tw-ring-color: rgba(190, 35, 35, 0.20); }
|
||||
.ring-primary\/30 { --tw-ring-color: rgba(190, 35, 35, 0.30); }
|
||||
.ring-primary\/40 { --tw-ring-color: rgba(190, 35, 35, 0.40); }
|
||||
.ring-primary\/50 { --tw-ring-color: rgba(190, 35, 35, 0.50); }
|
||||
.ring-primary\/60 { --tw-ring-color: rgba(190, 35, 35, 0.60); }
|
||||
|
||||
.dark .bg-primary\/5 { background-color: rgba(43, 129, 255, 0.05); }
|
||||
.dark .bg-primary\/8 { background-color: rgba(43, 129, 255, 0.08); }
|
||||
.dark .bg-primary\/10 { background-color: rgba(43, 129, 255, 0.10); }
|
||||
.dark .bg-primary\/14 { background-color: rgba(43, 129, 255, 0.14); }
|
||||
.dark .bg-primary\/15 { background-color: rgba(43, 129, 255, 0.15); }
|
||||
.dark .bg-primary\/20 { background-color: rgba(43, 129, 255, 0.20); }
|
||||
.dark .bg-primary\/25 { background-color: rgba(43, 129, 255, 0.25); }
|
||||
.dark .bg-primary\/30 { background-color: rgba(43, 129, 255, 0.30); }
|
||||
.dark .bg-primary\/60 { background-color: rgba(43, 129, 255, 0.60); }
|
||||
.dark .bg-primary\/70 { background-color: rgba(43, 129, 255, 0.70); }
|
||||
.dark .bg-primary\/90 { background-color: rgba(43, 129, 255, 0.90); }
|
||||
|
||||
.lightblue .bg-primary\/5 { background-color: rgba(0, 98, 217, 0.05); }
|
||||
.lightblue .bg-primary\/8 { background-color: rgba(0, 98, 217, 0.08); }
|
||||
.lightblue .bg-primary\/10 { background-color: rgba(0, 98, 217, 0.10); }
|
||||
.lightblue .bg-primary\/14 { background-color: rgba(0, 98, 217, 0.14); }
|
||||
.lightblue .bg-primary\/15 { background-color: rgba(0, 98, 217, 0.15); }
|
||||
.lightblue .bg-primary\/20 { background-color: rgba(0, 98, 217, 0.20); }
|
||||
.lightblue .bg-primary\/25 { background-color: rgba(0, 98, 217, 0.25); }
|
||||
.lightblue .bg-primary\/30 { background-color: rgba(0, 98, 217, 0.30); }
|
||||
.lightblue .bg-primary\/60 { background-color: rgba(0, 98, 217, 0.60); }
|
||||
.lightblue .bg-primary\/70 { background-color: rgba(0, 98, 217, 0.70); }
|
||||
.lightblue .bg-primary\/90 { background-color: rgba(0, 98, 217, 0.90); }
|
||||
}
|
||||
@@ -9,22 +9,23 @@ import {
|
||||
matchSqReportMock,
|
||||
sqReportMockAdapter,
|
||||
} from '@/strategy-components/components/task-components/sq-report-mock-data'
|
||||
import { readRuntimeFlag } from '@/env'
|
||||
|
||||
// 任务看板(任务分析 / 三情分析)数据源开关:
|
||||
// VITE_USE_REAL_DATA=true → 走真实接口;其它(含未设置)→ 这两个接口走本地假数据。
|
||||
// 提交的 .env.development/.env.production/.env.network/.env.sit 默认 true(真实数据),
|
||||
// 仅本机 .env 默认 false(假数据)。改 env 后需重启 dev server 生效。
|
||||
const USE_REAL_DATA = import.meta.env.VITE_USE_REAL_DATA === 'true'
|
||||
const USE_REAL_DATA = readRuntimeFlag('VITE_USE_REAL_DATA')
|
||||
|
||||
// 任务详情(cop-task-detail,登录深链用)数据源开关:
|
||||
// 默认真实数据;仅当 VITE_MOCK_COP_TASK_DETAIL=true 时走本地假数据(consumer 外网不可达时调试用)。
|
||||
// 与 USE_REAL_DATA 独立。改 env 后需重启 dev server 生效。
|
||||
const MOCK_COP_TASK_DETAIL = import.meta.env.VITE_MOCK_COP_TASK_DETAIL === 'true'
|
||||
const MOCK_COP_TASK_DETAIL = readRuntimeFlag('VITE_MOCK_COP_TASK_DETAIL')
|
||||
|
||||
// 研判情报报告(selectSqReport,大屏绘制智能体深链用)数据源开关:
|
||||
// 默认真实数据;仅当 VITE_MOCK_SQ_REPORT=true 时走本地假数据(真实接口未就绪时调试用)。
|
||||
// 与上面两个开关独立。改 env 后需重启 dev server 生效。
|
||||
const MOCK_SQ_REPORT = import.meta.env.VITE_MOCK_SQ_REPORT === 'true'
|
||||
const MOCK_SQ_REPORT = readRuntimeFlag('VITE_MOCK_SQ_REPORT')
|
||||
|
||||
// axios.interceptors
|
||||
const service = axios.create({
|
||||
|
||||
@@ -5,10 +5,22 @@ import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import sourceIdentifierPlugin from "vite-plugin-source-identifier";
|
||||
|
||||
const rootDir = fileURLToPath(new URL(".", import.meta.url));
|
||||
const rootDir = fileURLToPath(new URL(".", import.meta.url));
|
||||
// Tailwind 4 emits CSS cascade layers, which require Chrome 99+. Chrome 100
|
||||
// is therefore the practical lower bound for this Win7 legacy build.
|
||||
const LEGACY_CHROME = 100 << 16;
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sourceIdentifierPlugin(), react()],
|
||||
export default defineConfig({
|
||||
plugins: [sourceIdentifierPlugin(), react()],
|
||||
css: {
|
||||
// Also used by the production CSS minifier. Development CSS is handled
|
||||
// by the matching PostCSS compatibility pass in postcss.config.js.
|
||||
lightningcss: {
|
||||
targets: {
|
||||
chrome: LEGACY_CHROME,
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
@@ -33,7 +45,11 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
"base":"./",
|
||||
"base":"./",
|
||||
build: {
|
||||
cssMinify: "lightningcss",
|
||||
target: "chrome100",
|
||||
},
|
||||
server: {
|
||||
// 与 scripts/env.sh FRONTEND_PORT=5174、圆桌 wujie 嵌入文档一致。
|
||||
port: 5173,
|
||||
|
||||
-311
@@ -1,311 +0,0 @@
|
||||
{
|
||||
"name": "deerflow-server",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.16.1.tgz",
|
||||
"integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.16.0",
|
||||
"form-data": "^4.0.5",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz",
|
||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
agent-base
|
||||
==========
|
||||
### Turn a function into an [`http.Agent`][http.Agent] instance
|
||||
[](https://github.com/TooTallNate/node-agent-base/actions?workflow=Node+CI)
|
||||
|
||||
This module provides an `http.Agent` generator. That is, you pass it an async
|
||||
callback function, and it returns a new `http.Agent` instance that will invoke the
|
||||
given callback function when sending outbound HTTP requests.
|
||||
|
||||
#### Some subclasses:
|
||||
|
||||
Here's some more interesting uses of `agent-base`.
|
||||
Send a pull request to list yours!
|
||||
|
||||
* [`http-proxy-agent`][http-proxy-agent]: An HTTP(s) proxy `http.Agent` implementation for HTTP endpoints
|
||||
* [`https-proxy-agent`][https-proxy-agent]: An HTTP(s) proxy `http.Agent` implementation for HTTPS endpoints
|
||||
* [`pac-proxy-agent`][pac-proxy-agent]: A PAC file proxy `http.Agent` implementation for HTTP and HTTPS
|
||||
* [`socks-proxy-agent`][socks-proxy-agent]: A SOCKS proxy `http.Agent` implementation for HTTP and HTTPS
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install with `npm`:
|
||||
|
||||
``` bash
|
||||
$ npm install agent-base
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here's a minimal example that creates a new `net.Socket` connection to the server
|
||||
for every HTTP request (i.e. the equivalent of `agent: false` option):
|
||||
|
||||
```js
|
||||
var net = require('net');
|
||||
var tls = require('tls');
|
||||
var url = require('url');
|
||||
var http = require('http');
|
||||
var agent = require('agent-base');
|
||||
|
||||
var endpoint = 'http://nodejs.org/api/';
|
||||
var parsed = url.parse(endpoint);
|
||||
|
||||
// This is the important part!
|
||||
parsed.agent = agent(function (req, opts) {
|
||||
var socket;
|
||||
// `secureEndpoint` is true when using the https module
|
||||
if (opts.secureEndpoint) {
|
||||
socket = tls.connect(opts);
|
||||
} else {
|
||||
socket = net.connect(opts);
|
||||
}
|
||||
return socket;
|
||||
});
|
||||
|
||||
// Everything else works just like normal...
|
||||
http.get(parsed, function (res) {
|
||||
console.log('"response" event!', res.headers);
|
||||
res.pipe(process.stdout);
|
||||
});
|
||||
```
|
||||
|
||||
Returning a Promise or using an `async` function is also supported:
|
||||
|
||||
```js
|
||||
agent(async function (req, opts) {
|
||||
await sleep(1000);
|
||||
// etc…
|
||||
});
|
||||
```
|
||||
|
||||
Return another `http.Agent` instance to "pass through" the responsibility
|
||||
for that HTTP request to that agent:
|
||||
|
||||
```js
|
||||
agent(function (req, opts) {
|
||||
return opts.secureEndpoint ? https.globalAgent : http.globalAgent;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
## Agent(Function callback[, Object options]) → [http.Agent][]
|
||||
|
||||
Creates a base `http.Agent` that will execute the callback function `callback`
|
||||
for every HTTP request that it is used as the `agent` for. The callback function
|
||||
is responsible for creating a `stream.Duplex` instance of some kind that will be
|
||||
used as the underlying socket in the HTTP request.
|
||||
|
||||
The `options` object accepts the following properties:
|
||||
|
||||
* `timeout` - Number - Timeout for the `callback()` function in milliseconds. Defaults to Infinity (optional).
|
||||
|
||||
The callback function should have the following signature:
|
||||
|
||||
### callback(http.ClientRequest req, Object options, Function cb) → undefined
|
||||
|
||||
The ClientRequest `req` can be accessed to read request headers and
|
||||
and the path, etc. The `options` object contains the options passed
|
||||
to the `http.request()`/`https.request()` function call, and is formatted
|
||||
to be directly passed to `net.connect()`/`tls.connect()`, or however
|
||||
else you want a Socket to be created. Pass the created socket to
|
||||
the callback function `cb` once created, and the HTTP request will
|
||||
continue to proceed.
|
||||
|
||||
If the `https` module is used to invoke the HTTP request, then the
|
||||
`secureEndpoint` property on `options` _will be set to `true`_.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
[http-proxy-agent]: https://github.com/TooTallNate/node-http-proxy-agent
|
||||
[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
|
||||
[pac-proxy-agent]: https://github.com/TooTallNate/node-pac-proxy-agent
|
||||
[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
|
||||
[http.Agent]: https://nodejs.org/api/http.html#http_class_http_agent
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
import net from 'net';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { Duplex } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
declare function createAgent(opts?: createAgent.AgentOptions): createAgent.Agent;
|
||||
declare function createAgent(callback: createAgent.AgentCallback, opts?: createAgent.AgentOptions): createAgent.Agent;
|
||||
declare namespace createAgent {
|
||||
interface ClientRequest extends http.ClientRequest {
|
||||
_last?: boolean;
|
||||
_hadError?: boolean;
|
||||
method: string;
|
||||
}
|
||||
interface AgentRequestOptions {
|
||||
host?: string;
|
||||
path?: string;
|
||||
port: number;
|
||||
}
|
||||
interface HttpRequestOptions extends AgentRequestOptions, Omit<http.RequestOptions, keyof AgentRequestOptions> {
|
||||
secureEndpoint: false;
|
||||
}
|
||||
interface HttpsRequestOptions extends AgentRequestOptions, Omit<https.RequestOptions, keyof AgentRequestOptions> {
|
||||
secureEndpoint: true;
|
||||
}
|
||||
type RequestOptions = HttpRequestOptions | HttpsRequestOptions;
|
||||
type AgentLike = Pick<createAgent.Agent, 'addRequest'> | http.Agent;
|
||||
type AgentCallbackReturn = Duplex | AgentLike;
|
||||
type AgentCallbackCallback = (err?: Error | null, socket?: createAgent.AgentCallbackReturn) => void;
|
||||
type AgentCallbackPromise = (req: createAgent.ClientRequest, opts: createAgent.RequestOptions) => createAgent.AgentCallbackReturn | Promise<createAgent.AgentCallbackReturn>;
|
||||
type AgentCallback = typeof Agent.prototype.callback;
|
||||
type AgentOptions = {
|
||||
timeout?: number;
|
||||
};
|
||||
/**
|
||||
* Base `http.Agent` implementation.
|
||||
* No pooling/keep-alive is implemented by default.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @api public
|
||||
*/
|
||||
class Agent extends EventEmitter {
|
||||
timeout: number | null;
|
||||
maxFreeSockets: number;
|
||||
maxTotalSockets: number;
|
||||
maxSockets: number;
|
||||
sockets: {
|
||||
[key: string]: net.Socket[];
|
||||
};
|
||||
freeSockets: {
|
||||
[key: string]: net.Socket[];
|
||||
};
|
||||
requests: {
|
||||
[key: string]: http.IncomingMessage[];
|
||||
};
|
||||
options: https.AgentOptions;
|
||||
private promisifiedCallback?;
|
||||
private explicitDefaultPort?;
|
||||
private explicitProtocol?;
|
||||
constructor(callback?: createAgent.AgentCallback | createAgent.AgentOptions, _opts?: createAgent.AgentOptions);
|
||||
get defaultPort(): number;
|
||||
set defaultPort(v: number);
|
||||
get protocol(): string;
|
||||
set protocol(v: string);
|
||||
callback(req: createAgent.ClientRequest, opts: createAgent.RequestOptions, fn: createAgent.AgentCallbackCallback): void;
|
||||
callback(req: createAgent.ClientRequest, opts: createAgent.RequestOptions): createAgent.AgentCallbackReturn | Promise<createAgent.AgentCallbackReturn>;
|
||||
/**
|
||||
* Called by node-core's "_http_client.js" module when creating
|
||||
* a new HTTP request with this Agent instance.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
addRequest(req: ClientRequest, _opts: RequestOptions): void;
|
||||
freeSocket(socket: net.Socket, opts: AgentOptions): void;
|
||||
destroy(): void;
|
||||
}
|
||||
}
|
||||
export = createAgent;
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
const events_1 = require("events");
|
||||
const debug_1 = __importDefault(require("debug"));
|
||||
const promisify_1 = __importDefault(require("./promisify"));
|
||||
const debug = debug_1.default('agent-base');
|
||||
function isAgent(v) {
|
||||
return Boolean(v) && typeof v.addRequest === 'function';
|
||||
}
|
||||
function isSecureEndpoint() {
|
||||
const { stack } = new Error();
|
||||
if (typeof stack !== 'string')
|
||||
return false;
|
||||
return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1);
|
||||
}
|
||||
function createAgent(callback, opts) {
|
||||
return new createAgent.Agent(callback, opts);
|
||||
}
|
||||
(function (createAgent) {
|
||||
/**
|
||||
* Base `http.Agent` implementation.
|
||||
* No pooling/keep-alive is implemented by default.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @api public
|
||||
*/
|
||||
class Agent extends events_1.EventEmitter {
|
||||
constructor(callback, _opts) {
|
||||
super();
|
||||
let opts = _opts;
|
||||
if (typeof callback === 'function') {
|
||||
this.callback = callback;
|
||||
}
|
||||
else if (callback) {
|
||||
opts = callback;
|
||||
}
|
||||
// Timeout for the socket to be returned from the callback
|
||||
this.timeout = null;
|
||||
if (opts && typeof opts.timeout === 'number') {
|
||||
this.timeout = opts.timeout;
|
||||
}
|
||||
// These aren't actually used by `agent-base`, but are required
|
||||
// for the TypeScript definition files in `@types/node` :/
|
||||
this.maxFreeSockets = 1;
|
||||
this.maxSockets = 1;
|
||||
this.maxTotalSockets = Infinity;
|
||||
this.sockets = {};
|
||||
this.freeSockets = {};
|
||||
this.requests = {};
|
||||
this.options = {};
|
||||
}
|
||||
get defaultPort() {
|
||||
if (typeof this.explicitDefaultPort === 'number') {
|
||||
return this.explicitDefaultPort;
|
||||
}
|
||||
return isSecureEndpoint() ? 443 : 80;
|
||||
}
|
||||
set defaultPort(v) {
|
||||
this.explicitDefaultPort = v;
|
||||
}
|
||||
get protocol() {
|
||||
if (typeof this.explicitProtocol === 'string') {
|
||||
return this.explicitProtocol;
|
||||
}
|
||||
return isSecureEndpoint() ? 'https:' : 'http:';
|
||||
}
|
||||
set protocol(v) {
|
||||
this.explicitProtocol = v;
|
||||
}
|
||||
callback(req, opts, fn) {
|
||||
throw new Error('"agent-base" has no default implementation, you must subclass and override `callback()`');
|
||||
}
|
||||
/**
|
||||
* Called by node-core's "_http_client.js" module when creating
|
||||
* a new HTTP request with this Agent instance.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
addRequest(req, _opts) {
|
||||
const opts = Object.assign({}, _opts);
|
||||
if (typeof opts.secureEndpoint !== 'boolean') {
|
||||
opts.secureEndpoint = isSecureEndpoint();
|
||||
}
|
||||
if (opts.host == null) {
|
||||
opts.host = 'localhost';
|
||||
}
|
||||
if (opts.port == null) {
|
||||
opts.port = opts.secureEndpoint ? 443 : 80;
|
||||
}
|
||||
if (opts.protocol == null) {
|
||||
opts.protocol = opts.secureEndpoint ? 'https:' : 'http:';
|
||||
}
|
||||
if (opts.host && opts.path) {
|
||||
// If both a `host` and `path` are specified then it's most
|
||||
// likely the result of a `url.parse()` call... we need to
|
||||
// remove the `path` portion so that `net.connect()` doesn't
|
||||
// attempt to open that as a unix socket file.
|
||||
delete opts.path;
|
||||
}
|
||||
delete opts.agent;
|
||||
delete opts.hostname;
|
||||
delete opts._defaultAgent;
|
||||
delete opts.defaultPort;
|
||||
delete opts.createConnection;
|
||||
// Hint to use "Connection: close"
|
||||
// XXX: non-documented `http` module API :(
|
||||
req._last = true;
|
||||
req.shouldKeepAlive = false;
|
||||
let timedOut = false;
|
||||
let timeoutId = null;
|
||||
const timeoutMs = opts.timeout || this.timeout;
|
||||
const onerror = (err) => {
|
||||
if (req._hadError)
|
||||
return;
|
||||
req.emit('error', err);
|
||||
// For Safety. Some additional errors might fire later on
|
||||
// and we need to make sure we don't double-fire the error event.
|
||||
req._hadError = true;
|
||||
};
|
||||
const ontimeout = () => {
|
||||
timeoutId = null;
|
||||
timedOut = true;
|
||||
const err = new Error(`A "socket" was not created for HTTP request before ${timeoutMs}ms`);
|
||||
err.code = 'ETIMEOUT';
|
||||
onerror(err);
|
||||
};
|
||||
const callbackError = (err) => {
|
||||
if (timedOut)
|
||||
return;
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
onerror(err);
|
||||
};
|
||||
const onsocket = (socket) => {
|
||||
if (timedOut)
|
||||
return;
|
||||
if (timeoutId != null) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
if (isAgent(socket)) {
|
||||
// `socket` is actually an `http.Agent` instance, so
|
||||
// relinquish responsibility for this `req` to the Agent
|
||||
// from here on
|
||||
debug('Callback returned another Agent instance %o', socket.constructor.name);
|
||||
socket.addRequest(req, opts);
|
||||
return;
|
||||
}
|
||||
if (socket) {
|
||||
socket.once('free', () => {
|
||||
this.freeSocket(socket, opts);
|
||||
});
|
||||
req.onSocket(socket);
|
||||
return;
|
||||
}
|
||||
const err = new Error(`no Duplex stream was returned to agent-base for \`${req.method} ${req.path}\``);
|
||||
onerror(err);
|
||||
};
|
||||
if (typeof this.callback !== 'function') {
|
||||
onerror(new Error('`callback` is not defined'));
|
||||
return;
|
||||
}
|
||||
if (!this.promisifiedCallback) {
|
||||
if (this.callback.length >= 3) {
|
||||
debug('Converting legacy callback function to promise');
|
||||
this.promisifiedCallback = promisify_1.default(this.callback);
|
||||
}
|
||||
else {
|
||||
this.promisifiedCallback = this.callback;
|
||||
}
|
||||
}
|
||||
if (typeof timeoutMs === 'number' && timeoutMs > 0) {
|
||||
timeoutId = setTimeout(ontimeout, timeoutMs);
|
||||
}
|
||||
if ('port' in opts && typeof opts.port !== 'number') {
|
||||
opts.port = Number(opts.port);
|
||||
}
|
||||
try {
|
||||
debug('Resolving socket for %o request: %o', opts.protocol, `${req.method} ${req.path}`);
|
||||
Promise.resolve(this.promisifiedCallback(req, opts)).then(onsocket, callbackError);
|
||||
}
|
||||
catch (err) {
|
||||
Promise.reject(err).catch(callbackError);
|
||||
}
|
||||
}
|
||||
freeSocket(socket, opts) {
|
||||
debug('Freeing socket %o %o', socket.constructor.name, opts);
|
||||
socket.destroy();
|
||||
}
|
||||
destroy() {
|
||||
debug('Destroying agent %o', this.constructor.name);
|
||||
}
|
||||
}
|
||||
createAgent.Agent = Agent;
|
||||
// So that `instanceof` works correctly
|
||||
createAgent.prototype = createAgent.Agent.prototype;
|
||||
})(createAgent || (createAgent = {}));
|
||||
module.exports = createAgent;
|
||||
//# sourceMappingURL=index.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;AAIA,mCAAsC;AACtC,kDAAgC;AAChC,4DAAoC;AAEpC,MAAM,KAAK,GAAG,eAAW,CAAC,YAAY,CAAC,CAAC;AAExC,SAAS,OAAO,CAAC,CAAM;IACtB,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC;AACzD,CAAC;AAED,SAAS,gBAAgB;IACxB,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,KAAK,EAAE,CAAC;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,IAAK,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxG,CAAC;AAOD,SAAS,WAAW,CACnB,QAA+D,EAC/D,IAA+B;IAE/B,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED,WAAU,WAAW;IAmDpB;;;;;;OAMG;IACH,MAAa,KAAM,SAAQ,qBAAY;QAmBtC,YACC,QAA+D,EAC/D,KAAgC;YAEhC,KAAK,EAAE,CAAC;YAER,IAAI,IAAI,GAAG,KAAK,CAAC;YACjB,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE;gBACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;aACzB;iBAAM,IAAI,QAAQ,EAAE;gBACpB,IAAI,GAAG,QAAQ,CAAC;aAChB;YAED,0DAA0D;YAC1D,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE;gBAC7C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;aAC5B;YAED,+DAA+D;YAC/D,0DAA0D;YAC1D,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC;YACxB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;YACpB,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;YAChC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QACnB,CAAC;QAED,IAAI,WAAW;YACd,IAAI,OAAO,IAAI,CAAC,mBAAmB,KAAK,QAAQ,EAAE;gBACjD,OAAO,IAAI,CAAC,mBAAmB,CAAC;aAChC;YACD,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,CAAC;QAED,IAAI,WAAW,CAAC,CAAS;YACxB,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,QAAQ;YACX,IAAI,OAAO,IAAI,CAAC,gBAAgB,KAAK,QAAQ,EAAE;gBAC9C,OAAO,IAAI,CAAC,gBAAgB,CAAC;aAC7B;YACD,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;QAChD,CAAC;QAED,IAAI,QAAQ,CAAC,CAAS;YACrB,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC3B,CAAC;QAaD,QAAQ,CACP,GAA8B,EAC9B,IAA8B,EAC9B,EAAsC;YAKtC,MAAM,IAAI,KAAK,CACd,yFAAyF,CACzF,CAAC;QACH,CAAC;QAED;;;;;WAKG;QACH,UAAU,CAAC,GAAkB,EAAE,KAAqB;YACnD,MAAM,IAAI,qBAAwB,KAAK,CAAE,CAAC;YAE1C,IAAI,OAAO,IAAI,CAAC,cAAc,KAAK,SAAS,EAAE;gBAC7C,IAAI,CAAC,cAAc,GAAG,gBAAgB,EAAE,CAAC;aACzC;YAED,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;gBACtB,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;aACxB;YAED,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;gBACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3C;YAED,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE;gBAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;aACzD;YAED,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE;gBAC3B,2DAA2D;gBAC3D,0DAA0D;gBAC1D,4DAA4D;gBAC5D,8CAA8C;gBAC9C,OAAO,IAAI,CAAC,IAAI,CAAC;aACjB;YAED,OAAO,IAAI,CAAC,KAAK,CAAC;YAClB,OAAO,IAAI,CAAC,QAAQ,CAAC;YACrB,OAAO,IAAI,CAAC,aAAa,CAAC;YAC1B,OAAO,IAAI,CAAC,WAAW,CAAC;YACxB,OAAO,IAAI,CAAC,gBAAgB,CAAC;YAE7B,kCAAkC;YAClC,2CAA2C;YAC3C,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;YACjB,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC;YAE5B,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,SAAS,GAAyC,IAAI,CAAC;YAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC;YAE/C,MAAM,OAAO,GAAG,CAAC,GAA0B,EAAE,EAAE;gBAC9C,IAAI,GAAG,CAAC,SAAS;oBAAE,OAAO;gBAC1B,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBACvB,yDAAyD;gBACzD,iEAAiE;gBACjE,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,CAAC,CAAC;YAEF,MAAM,SAAS,GAAG,GAAG,EAAE;gBACtB,SAAS,GAAG,IAAI,CAAC;gBACjB,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,GAAG,GAA0B,IAAI,KAAK,CAC3C,sDAAsD,SAAS,IAAI,CACnE,CAAC;gBACF,GAAG,CAAC,IAAI,GAAG,UAAU,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,CAAC;YACd,CAAC,CAAC;YAEF,MAAM,aAAa,GAAG,CAAC,GAA0B,EAAE,EAAE;gBACpD,IAAI,QAAQ;oBAAE,OAAO;gBACrB,IAAI,SAAS,KAAK,IAAI,EAAE;oBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;oBACxB,SAAS,GAAG,IAAI,CAAC;iBACjB;gBACD,OAAO,CAAC,GAAG,CAAC,CAAC;YACd,CAAC,CAAC;YAEF,MAAM,QAAQ,GAAG,CAAC,MAA2B,EAAE,EAAE;gBAChD,IAAI,QAAQ;oBAAE,OAAO;gBACrB,IAAI,SAAS,IAAI,IAAI,EAAE;oBACtB,YAAY,CAAC,SAAS,CAAC,CAAC;oBACxB,SAAS,GAAG,IAAI,CAAC;iBACjB;gBAED,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE;oBACpB,oDAAoD;oBACpD,wDAAwD;oBACxD,eAAe;oBACf,KAAK,CACJ,6CAA6C,EAC7C,MAAM,CAAC,WAAW,CAAC,IAAI,CACvB,CAAC;oBACD,MAA4B,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;oBACpD,OAAO;iBACP;gBAED,IAAI,MAAM,EAAE;oBACX,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;wBACxB,IAAI,CAAC,UAAU,CAAC,MAAoB,EAAE,IAAI,CAAC,CAAC;oBAC7C,CAAC,CAAC,CAAC;oBACH,GAAG,CAAC,QAAQ,CAAC,MAAoB,CAAC,CAAC;oBACnC,OAAO;iBACP;gBAED,MAAM,GAAG,GAAG,IAAI,KAAK,CACpB,qDAAqD,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,CAC/E,CAAC;gBACF,OAAO,CAAC,GAAG,CAAC,CAAC;YACd,CAAC,CAAC;YAEF,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,EAAE;gBACxC,OAAO,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;gBAChD,OAAO;aACP;YAED,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE;gBAC9B,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE;oBAC9B,KAAK,CAAC,gDAAgD,CAAC,CAAC;oBACxD,IAAI,CAAC,mBAAmB,GAAG,mBAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;iBACpD;qBAAM;oBACN,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,QAAQ,CAAC;iBACzC;aACD;YAED,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,GAAG,CAAC,EAAE;gBACnD,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;aAC7C;YAED,IAAI,MAAM,IAAI,IAAI,IAAI,OALine truncated
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
import { ClientRequest, RequestOptions, AgentCallbackCallback, AgentCallbackPromise } from './index';
|
||||
declare type LegacyCallback = (req: ClientRequest, opts: RequestOptions, fn: AgentCallbackCallback) => void;
|
||||
export default function promisify(fn: LegacyCallback): AgentCallbackPromise;
|
||||
export {};
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function promisify(fn) {
|
||||
return function (req, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn.call(this, req, opts, (err, rtn) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
resolve(rtn);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
exports.default = promisify;
|
||||
//# sourceMappingURL=promisify.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"promisify.js","sourceRoot":"","sources":["../../src/promisify.ts"],"names":[],"mappings":";;AAeA,SAAwB,SAAS,CAAC,EAAkB;IACnD,OAAO,UAAsB,GAAkB,EAAE,IAAoB;QACpE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtC,EAAE,CAAC,IAAI,CACN,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,CAAC,GAA6B,EAAE,GAAyB,EAAE,EAAE;gBAC5D,IAAI,GAAG,EAAE;oBACR,MAAM,CAAC,GAAG,CAAC,CAAC;iBACZ;qBAAM;oBACN,OAAO,CAAC,GAAG,CAAC,CAAC;iBACb;YACF,CAAC,CACD,CAAC;QACH,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC;AAjBD,4BAiBC"}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"name": "agent-base",
|
||||
"version": "6.0.2",
|
||||
"description": "Turn a function into an `http.Agent` instance",
|
||||
"main": "dist/src/index",
|
||||
"typings": "dist/src/index",
|
||||
"files": [
|
||||
"dist/src",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc",
|
||||
"postbuild": "cpy --parents src test '!**/*.ts' dist",
|
||||
"test": "mocha --reporter spec dist/test/*.js",
|
||||
"test-lint": "eslint src --ext .js,.ts",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/TooTallNate/node-agent-base.git"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"agent",
|
||||
"base",
|
||||
"barebones",
|
||||
"https"
|
||||
],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/TooTallNate/node-agent-base/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "4",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^14.0.20",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/ws": "^6.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "1.6.0",
|
||||
"@typescript-eslint/parser": "1.1.0",
|
||||
"async-listen": "^1.2.0",
|
||||
"cpy-cli": "^2.0.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "17.1.0",
|
||||
"eslint-config-prettier": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "1.1.1",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"mocha": "^6.2.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"semver": "^7.1.2",
|
||||
"typescript": "^3.5.3",
|
||||
"ws": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
}
|
||||
-345
@@ -1,345 +0,0 @@
|
||||
import net from 'net';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { Duplex } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
import createDebug from 'debug';
|
||||
import promisify from './promisify';
|
||||
|
||||
const debug = createDebug('agent-base');
|
||||
|
||||
function isAgent(v: any): v is createAgent.AgentLike {
|
||||
return Boolean(v) && typeof v.addRequest === 'function';
|
||||
}
|
||||
|
||||
function isSecureEndpoint(): boolean {
|
||||
const { stack } = new Error();
|
||||
if (typeof stack !== 'string') return false;
|
||||
return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1);
|
||||
}
|
||||
|
||||
function createAgent(opts?: createAgent.AgentOptions): createAgent.Agent;
|
||||
function createAgent(
|
||||
callback: createAgent.AgentCallback,
|
||||
opts?: createAgent.AgentOptions
|
||||
): createAgent.Agent;
|
||||
function createAgent(
|
||||
callback?: createAgent.AgentCallback | createAgent.AgentOptions,
|
||||
opts?: createAgent.AgentOptions
|
||||
) {
|
||||
return new createAgent.Agent(callback, opts);
|
||||
}
|
||||
|
||||
namespace createAgent {
|
||||
export interface ClientRequest extends http.ClientRequest {
|
||||
_last?: boolean;
|
||||
_hadError?: boolean;
|
||||
method: string;
|
||||
}
|
||||
|
||||
export interface AgentRequestOptions {
|
||||
host?: string;
|
||||
path?: string;
|
||||
// `port` on `http.RequestOptions` can be a string or undefined,
|
||||
// but `net.TcpNetConnectOpts` expects only a number
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface HttpRequestOptions
|
||||
extends AgentRequestOptions,
|
||||
Omit<http.RequestOptions, keyof AgentRequestOptions> {
|
||||
secureEndpoint: false;
|
||||
}
|
||||
|
||||
export interface HttpsRequestOptions
|
||||
extends AgentRequestOptions,
|
||||
Omit<https.RequestOptions, keyof AgentRequestOptions> {
|
||||
secureEndpoint: true;
|
||||
}
|
||||
|
||||
export type RequestOptions = HttpRequestOptions | HttpsRequestOptions;
|
||||
|
||||
export type AgentLike = Pick<createAgent.Agent, 'addRequest'> | http.Agent;
|
||||
|
||||
export type AgentCallbackReturn = Duplex | AgentLike;
|
||||
|
||||
export type AgentCallbackCallback = (
|
||||
err?: Error | null,
|
||||
socket?: createAgent.AgentCallbackReturn
|
||||
) => void;
|
||||
|
||||
export type AgentCallbackPromise = (
|
||||
req: createAgent.ClientRequest,
|
||||
opts: createAgent.RequestOptions
|
||||
) =>
|
||||
| createAgent.AgentCallbackReturn
|
||||
| Promise<createAgent.AgentCallbackReturn>;
|
||||
|
||||
export type AgentCallback = typeof Agent.prototype.callback;
|
||||
|
||||
export type AgentOptions = {
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base `http.Agent` implementation.
|
||||
* No pooling/keep-alive is implemented by default.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @api public
|
||||
*/
|
||||
export class Agent extends EventEmitter {
|
||||
public timeout: number | null;
|
||||
public maxFreeSockets: number;
|
||||
public maxTotalSockets: number;
|
||||
public maxSockets: number;
|
||||
public sockets: {
|
||||
[key: string]: net.Socket[];
|
||||
};
|
||||
public freeSockets: {
|
||||
[key: string]: net.Socket[];
|
||||
};
|
||||
public requests: {
|
||||
[key: string]: http.IncomingMessage[];
|
||||
};
|
||||
public options: https.AgentOptions;
|
||||
private promisifiedCallback?: createAgent.AgentCallbackPromise;
|
||||
private explicitDefaultPort?: number;
|
||||
private explicitProtocol?: string;
|
||||
|
||||
constructor(
|
||||
callback?: createAgent.AgentCallback | createAgent.AgentOptions,
|
||||
_opts?: createAgent.AgentOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
let opts = _opts;
|
||||
if (typeof callback === 'function') {
|
||||
this.callback = callback;
|
||||
} else if (callback) {
|
||||
opts = callback;
|
||||
}
|
||||
|
||||
// Timeout for the socket to be returned from the callback
|
||||
this.timeout = null;
|
||||
if (opts && typeof opts.timeout === 'number') {
|
||||
this.timeout = opts.timeout;
|
||||
}
|
||||
|
||||
// These aren't actually used by `agent-base`, but are required
|
||||
// for the TypeScript definition files in `@types/node` :/
|
||||
this.maxFreeSockets = 1;
|
||||
this.maxSockets = 1;
|
||||
this.maxTotalSockets = Infinity;
|
||||
this.sockets = {};
|
||||
this.freeSockets = {};
|
||||
this.requests = {};
|
||||
this.options = {};
|
||||
}
|
||||
|
||||
get defaultPort(): number {
|
||||
if (typeof this.explicitDefaultPort === 'number') {
|
||||
return this.explicitDefaultPort;
|
||||
}
|
||||
return isSecureEndpoint() ? 443 : 80;
|
||||
}
|
||||
|
||||
set defaultPort(v: number) {
|
||||
this.explicitDefaultPort = v;
|
||||
}
|
||||
|
||||
get protocol(): string {
|
||||
if (typeof this.explicitProtocol === 'string') {
|
||||
return this.explicitProtocol;
|
||||
}
|
||||
return isSecureEndpoint() ? 'https:' : 'http:';
|
||||
}
|
||||
|
||||
set protocol(v: string) {
|
||||
this.explicitProtocol = v;
|
||||
}
|
||||
|
||||
callback(
|
||||
req: createAgent.ClientRequest,
|
||||
opts: createAgent.RequestOptions,
|
||||
fn: createAgent.AgentCallbackCallback
|
||||
): void;
|
||||
callback(
|
||||
req: createAgent.ClientRequest,
|
||||
opts: createAgent.RequestOptions
|
||||
):
|
||||
| createAgent.AgentCallbackReturn
|
||||
| Promise<createAgent.AgentCallbackReturn>;
|
||||
callback(
|
||||
req: createAgent.ClientRequest,
|
||||
opts: createAgent.AgentOptions,
|
||||
fn?: createAgent.AgentCallbackCallback
|
||||
):
|
||||
| createAgent.AgentCallbackReturn
|
||||
| Promise<createAgent.AgentCallbackReturn>
|
||||
| void {
|
||||
throw new Error(
|
||||
'"agent-base" has no default implementation, you must subclass and override `callback()`'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by node-core's "_http_client.js" module when creating
|
||||
* a new HTTP request with this Agent instance.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
addRequest(req: ClientRequest, _opts: RequestOptions): void {
|
||||
const opts: RequestOptions = { ..._opts };
|
||||
|
||||
if (typeof opts.secureEndpoint !== 'boolean') {
|
||||
opts.secureEndpoint = isSecureEndpoint();
|
||||
}
|
||||
|
||||
if (opts.host == null) {
|
||||
opts.host = 'localhost';
|
||||
}
|
||||
|
||||
if (opts.port == null) {
|
||||
opts.port = opts.secureEndpoint ? 443 : 80;
|
||||
}
|
||||
|
||||
if (opts.protocol == null) {
|
||||
opts.protocol = opts.secureEndpoint ? 'https:' : 'http:';
|
||||
}
|
||||
|
||||
if (opts.host && opts.path) {
|
||||
// If both a `host` and `path` are specified then it's most
|
||||
// likely the result of a `url.parse()` call... we need to
|
||||
// remove the `path` portion so that `net.connect()` doesn't
|
||||
// attempt to open that as a unix socket file.
|
||||
delete opts.path;
|
||||
}
|
||||
|
||||
delete opts.agent;
|
||||
delete opts.hostname;
|
||||
delete opts._defaultAgent;
|
||||
delete opts.defaultPort;
|
||||
delete opts.createConnection;
|
||||
|
||||
// Hint to use "Connection: close"
|
||||
// XXX: non-documented `http` module API :(
|
||||
req._last = true;
|
||||
req.shouldKeepAlive = false;
|
||||
|
||||
let timedOut = false;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
const timeoutMs = opts.timeout || this.timeout;
|
||||
|
||||
const onerror = (err: NodeJS.ErrnoException) => {
|
||||
if (req._hadError) return;
|
||||
req.emit('error', err);
|
||||
// For Safety. Some additional errors might fire later on
|
||||
// and we need to make sure we don't double-fire the error event.
|
||||
req._hadError = true;
|
||||
};
|
||||
|
||||
const ontimeout = () => {
|
||||
timeoutId = null;
|
||||
timedOut = true;
|
||||
const err: NodeJS.ErrnoException = new Error(
|
||||
`A "socket" was not created for HTTP request before ${timeoutMs}ms`
|
||||
);
|
||||
err.code = 'ETIMEOUT';
|
||||
onerror(err);
|
||||
};
|
||||
|
||||
const callbackError = (err: NodeJS.ErrnoException) => {
|
||||
if (timedOut) return;
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
onerror(err);
|
||||
};
|
||||
|
||||
const onsocket = (socket: AgentCallbackReturn) => {
|
||||
if (timedOut) return;
|
||||
if (timeoutId != null) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
|
||||
if (isAgent(socket)) {
|
||||
// `socket` is actually an `http.Agent` instance, so
|
||||
// relinquish responsibility for this `req` to the Agent
|
||||
// from here on
|
||||
debug(
|
||||
'Callback returned another Agent instance %o',
|
||||
socket.constructor.name
|
||||
);
|
||||
(socket as createAgent.Agent).addRequest(req, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket) {
|
||||
socket.once('free', () => {
|
||||
this.freeSocket(socket as net.Socket, opts);
|
||||
});
|
||||
req.onSocket(socket as net.Socket);
|
||||
return;
|
||||
}
|
||||
|
||||
const err = new Error(
|
||||
`no Duplex stream was returned to agent-base for \`${req.method} ${req.path}\``
|
||||
);
|
||||
onerror(err);
|
||||
};
|
||||
|
||||
if (typeof this.callback !== 'function') {
|
||||
onerror(new Error('`callback` is not defined'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.promisifiedCallback) {
|
||||
if (this.callback.length >= 3) {
|
||||
debug('Converting legacy callback function to promise');
|
||||
this.promisifiedCallback = promisify(this.callback);
|
||||
} else {
|
||||
this.promisifiedCallback = this.callback;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof timeoutMs === 'number' && timeoutMs > 0) {
|
||||
timeoutId = setTimeout(ontimeout, timeoutMs);
|
||||
}
|
||||
|
||||
if ('port' in opts && typeof opts.port !== 'number') {
|
||||
opts.port = Number(opts.port);
|
||||
}
|
||||
|
||||
try {
|
||||
debug(
|
||||
'Resolving socket for %o request: %o',
|
||||
opts.protocol,
|
||||
`${req.method} ${req.path}`
|
||||
);
|
||||
Promise.resolve(this.promisifiedCallback(req, opts)).then(
|
||||
onsocket,
|
||||
callbackError
|
||||
);
|
||||
} catch (err) {
|
||||
Promise.reject(err).catch(callbackError);
|
||||
}
|
||||
}
|
||||
|
||||
freeSocket(socket: net.Socket, opts: AgentOptions) {
|
||||
debug('Freeing socket %o %o', socket.constructor.name, opts);
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
debug('Destroying agent %o', this.constructor.name);
|
||||
}
|
||||
}
|
||||
|
||||
// So that `instanceof` works correctly
|
||||
createAgent.prototype = createAgent.Agent.prototype;
|
||||
}
|
||||
|
||||
export = createAgent;
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
Agent,
|
||||
ClientRequest,
|
||||
RequestOptions,
|
||||
AgentCallbackCallback,
|
||||
AgentCallbackPromise,
|
||||
AgentCallbackReturn
|
||||
} from './index';
|
||||
|
||||
type LegacyCallback = (
|
||||
req: ClientRequest,
|
||||
opts: RequestOptions,
|
||||
fn: AgentCallbackCallback
|
||||
) => void;
|
||||
|
||||
export default function promisify(fn: LegacyCallback): AgentCallbackPromise {
|
||||
return function(this: Agent, req: ClientRequest, opts: RequestOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn.call(
|
||||
this,
|
||||
req,
|
||||
opts,
|
||||
(err: Error | null | undefined, rtn?: AgentCallbackReturn) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rtn);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Alex Indigo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
# asynckit [](https://www.npmjs.com/package/asynckit)
|
||||
|
||||
Minimal async jobs utility library, with streams support.
|
||||
|
||||
[](https://travis-ci.org/alexindigo/asynckit)
|
||||
[](https://travis-ci.org/alexindigo/asynckit)
|
||||
[](https://ci.appveyor.com/project/alexindigo/asynckit)
|
||||
|
||||
[](https://coveralls.io/github/alexindigo/asynckit?branch=master)
|
||||
[](https://david-dm.org/alexindigo/asynckit)
|
||||
[](https://www.bithound.io/github/alexindigo/asynckit)
|
||||
|
||||
<!-- [](https://www.npmjs.com/package/reamde) -->
|
||||
|
||||
AsyncKit provides harness for `parallel` and `serial` iterators over list of items represented by arrays or objects.
|
||||
Optionally it accepts abort function (should be synchronously return by iterator for each item), and terminates left over jobs upon an error event. For specific iteration order built-in (`ascending` and `descending`) and custom sort helpers also supported, via `asynckit.serialOrdered` method.
|
||||
|
||||
It ensures async operations to keep behavior more stable and prevent `Maximum call stack size exceeded` errors, from sync iterators.
|
||||
|
||||
| compression | size |
|
||||
| :----------------- | -------: |
|
||||
| asynckit.js | 12.34 kB |
|
||||
| asynckit.min.js | 4.11 kB |
|
||||
| asynckit.min.js.gz | 1.47 kB |
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
$ npm install --save asynckit
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Parallel Jobs
|
||||
|
||||
Runs iterator over provided array in parallel. Stores output in the `result` array,
|
||||
on the matching positions. In unlikely event of an error from one of the jobs,
|
||||
will terminate rest of the active jobs (if abort function is provided)
|
||||
and return error along with salvaged data to the main callback function.
|
||||
|
||||
#### Input Array
|
||||
|
||||
```javascript
|
||||
var parallel = require('asynckit').parallel
|
||||
, assert = require('assert')
|
||||
;
|
||||
|
||||
var source = [ 1, 1, 4, 16, 64, 32, 8, 2 ]
|
||||
, expectedResult = [ 2, 2, 8, 32, 128, 64, 16, 4 ]
|
||||
, expectedTarget = [ 1, 1, 2, 4, 8, 16, 32, 64 ]
|
||||
, target = []
|
||||
;
|
||||
|
||||
parallel(source, asyncJob, function(err, result)
|
||||
{
|
||||
assert.deepEqual(result, expectedResult);
|
||||
assert.deepEqual(target, expectedTarget);
|
||||
});
|
||||
|
||||
// async job accepts one element from the array
|
||||
// and a callback function
|
||||
function asyncJob(item, cb)
|
||||
{
|
||||
// different delays (in ms) per item
|
||||
var delay = item * 25;
|
||||
|
||||
// pretend different jobs take different time to finish
|
||||
// and not in consequential order
|
||||
var timeoutId = setTimeout(function() {
|
||||
target.push(item);
|
||||
cb(null, item * 2);
|
||||
}, delay);
|
||||
|
||||
// allow to cancel "leftover" jobs upon error
|
||||
// return function, invoking of which will abort this job
|
||||
return clearTimeout.bind(null, timeoutId);
|
||||
}
|
||||
```
|
||||
|
||||
More examples could be found in [test/test-parallel-array.js](test/test-parallel-array.js).
|
||||
|
||||
#### Input Object
|
||||
|
||||
Also it supports named jobs, listed via object.
|
||||
|
||||
```javascript
|
||||
var parallel = require('asynckit/parallel')
|
||||
, assert = require('assert')
|
||||
;
|
||||
|
||||
var source = { first: 1, one: 1, four: 4, sixteen: 16, sixtyFour: 64, thirtyTwo: 32, eight: 8, two: 2 }
|
||||
, expectedResult = { first: 2, one: 2, four: 8, sixteen: 32, sixtyFour: 128, thirtyTwo: 64, eight: 16, two: 4 }
|
||||
, expectedTarget = [ 1, 1, 2, 4, 8, 16, 32, 64 ]
|
||||
, expectedKeys = [ 'first', 'one', 'two', 'four', 'eight', 'sixteen', 'thirtyTwo', 'sixtyFour' ]
|
||||
, target = []
|
||||
, keys = []
|
||||
;
|
||||
|
||||
parallel(source, asyncJob, function(err, result)
|
||||
{
|
||||
assert.deepEqual(result, expectedResult);
|
||||
assert.deepEqual(target, expectedTarget);
|
||||
assert.deepEqual(keys, expectedKeys);
|
||||
});
|
||||
|
||||
// supports full value, key, callback (shortcut) interface
|
||||
function asyncJob(item, key, cb)
|
||||
{
|
||||
// different delays (in ms) per item
|
||||
var delay = item * 25;
|
||||
|
||||
// pretend different jobs take different time to finish
|
||||
// and not in consequential order
|
||||
var timeoutId = setTimeout(function() {
|
||||
keys.push(key);
|
||||
target.push(item);
|
||||
cb(null, item * 2);
|
||||
}, delay);
|
||||
|
||||
// allow to cancel "leftover" jobs upon error
|
||||
// return function, invoking of which will abort this job
|
||||
return clearTimeout.bind(null, timeoutId);
|
||||
}
|
||||
```
|
||||
|
||||
More examples could be found in [test/test-parallel-object.js](test/test-parallel-object.js).
|
||||
|
||||
### Serial Jobs
|
||||
|
||||
Runs iterator over provided array sequentially. Stores output in the `result` array,
|
||||
on the matching positions. In unlikely event of an error from one of the jobs,
|
||||
will not proceed to the rest of the items in the list
|
||||
and return error along with salvaged data to the main callback function.
|
||||
|
||||
#### Input Array
|
||||
|
||||
```javascript
|
||||
var serial = require('asynckit/serial')
|
||||
, assert = require('assert')
|
||||
;
|
||||
|
||||
var source = [ 1, 1, 4, 16, 64, 32, 8, 2 ]
|
||||
, expectedResult = [ 2, 2, 8, 32, 128, 64, 16, 4 ]
|
||||
, expectedTarget = [ 0, 1, 2, 3, 4, 5, 6, 7 ]
|
||||
, target = []
|
||||
;
|
||||
|
||||
serial(source, asyncJob, function(err, result)
|
||||
{
|
||||
assert.deepEqual(result, expectedResult);
|
||||
assert.deepEqual(target, expectedTarget);
|
||||
});
|
||||
|
||||
// extended interface (item, key, callback)
|
||||
// also supported for arrays
|
||||
function asyncJob(item, key, cb)
|
||||
{
|
||||
target.push(key);
|
||||
|
||||
// it will be automatically made async
|
||||
// even it iterator "returns" in the same event loop
|
||||
cb(null, item * 2);
|
||||
}
|
||||
```
|
||||
|
||||
More examples could be found in [test/test-serial-array.js](test/test-serial-array.js).
|
||||
|
||||
#### Input Object
|
||||
|
||||
Also it supports named jobs, listed via object.
|
||||
|
||||
```javascript
|
||||
var serial = require('asynckit').serial
|
||||
, assert = require('assert')
|
||||
;
|
||||
|
||||
var source = [ 1, 1, 4, 16, 64, 32, 8, 2 ]
|
||||
, expectedResult = [ 2, 2, 8, 32, 128, 64, 16, 4 ]
|
||||
, expectedTarget = [ 0, 1, 2, 3, 4, 5, 6, 7 ]
|
||||
, target = []
|
||||
;
|
||||
|
||||
var source = { first: 1, one: 1, four: 4, sixteen: 16, sixtyFour: 64, thirtyTwo: 32, eight: 8, two: 2 }
|
||||
, expectedResult = { first: 2, one: 2, four: 8, sixteen: 32, sixtyFour: 128, thirtyTwo: 64, eight: 16, two: 4 }
|
||||
, expectedTarget = [ 1, 1, 4, 16, 64, 32, 8, 2 ]
|
||||
, target = []
|
||||
;
|
||||
|
||||
|
||||
serial(source, asyncJob, function(err, result)
|
||||
{
|
||||
assert.deepEqual(result, expectedResult);
|
||||
assert.deepEqual(target, expectedTarget);
|
||||
});
|
||||
|
||||
// shortcut interface (item, callback)
|
||||
// works for object as well as for the arrays
|
||||
function asyncJob(item, cb)
|
||||
{
|
||||
target.push(item);
|
||||
|
||||
// it will be automatically made async
|
||||
// even it iterator "returns" in the same event loop
|
||||
cb(null, item * 2);
|
||||
}
|
||||
```
|
||||
|
||||
More examples could be found in [test/test-serial-object.js](test/test-serial-object.js).
|
||||
|
||||
_Note: Since _object_ is an _unordered_ collection of properties,
|
||||
it may produce unexpected results with sequential iterations.
|
||||
Whenever order of the jobs' execution is important please use `serialOrdered` method._
|
||||
|
||||
### Ordered Serial Iterations
|
||||
|
||||
TBD
|
||||
|
||||
For example [compare-property](compare-property) package.
|
||||
|
||||
### Streaming interface
|
||||
|
||||
TBD
|
||||
|
||||
## Want to Know More?
|
||||
|
||||
More examples can be found in [test folder](test/).
|
||||
|
||||
Or open an [issue](https://github.com/alexindigo/asynckit/issues) with questions and/or suggestions.
|
||||
|
||||
## License
|
||||
|
||||
AsyncKit is licensed under the MIT license.
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/* eslint no-console: "off" */
|
||||
|
||||
var asynckit = require('./')
|
||||
, async = require('async')
|
||||
, assert = require('assert')
|
||||
, expected = 0
|
||||
;
|
||||
|
||||
var Benchmark = require('benchmark');
|
||||
var suite = new Benchmark.Suite;
|
||||
|
||||
var source = [];
|
||||
for (var z = 1; z < 100; z++)
|
||||
{
|
||||
source.push(z);
|
||||
expected += z;
|
||||
}
|
||||
|
||||
suite
|
||||
// add tests
|
||||
|
||||
.add('async.map', function(deferred)
|
||||
{
|
||||
var total = 0;
|
||||
|
||||
async.map(source,
|
||||
function(i, cb)
|
||||
{
|
||||
setImmediate(function()
|
||||
{
|
||||
total += i;
|
||||
cb(null, total);
|
||||
});
|
||||
},
|
||||
function(err, result)
|
||||
{
|
||||
assert.ifError(err);
|
||||
assert.equal(result[result.length - 1], expected);
|
||||
deferred.resolve();
|
||||
});
|
||||
}, {'defer': true})
|
||||
|
||||
|
||||
.add('asynckit.parallel', function(deferred)
|
||||
{
|
||||
var total = 0;
|
||||
|
||||
asynckit.parallel(source,
|
||||
function(i, cb)
|
||||
{
|
||||
setImmediate(function()
|
||||
{
|
||||
total += i;
|
||||
cb(null, total);
|
||||
});
|
||||
},
|
||||
function(err, result)
|
||||
{
|
||||
assert.ifError(err);
|
||||
assert.equal(result[result.length - 1], expected);
|
||||
deferred.resolve();
|
||||
});
|
||||
}, {'defer': true})
|
||||
|
||||
|
||||
// add listeners
|
||||
.on('cycle', function(ev)
|
||||
{
|
||||
console.log(String(ev.target));
|
||||
})
|
||||
.on('complete', function()
|
||||
{
|
||||
console.log('Fastest is ' + this.filter('fastest').map('name'));
|
||||
})
|
||||
// run async
|
||||
.run({ 'async': true });
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
module.exports =
|
||||
{
|
||||
parallel : require('./parallel.js'),
|
||||
serial : require('./serial.js'),
|
||||
serialOrdered : require('./serialOrdered.js')
|
||||
};
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
// API
|
||||
module.exports = abort;
|
||||
|
||||
/**
|
||||
* Aborts leftover active jobs
|
||||
*
|
||||
* @param {object} state - current state object
|
||||
*/
|
||||
function abort(state)
|
||||
{
|
||||
Object.keys(state.jobs).forEach(clean.bind(state));
|
||||
|
||||
// reset leftover jobs
|
||||
state.jobs = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up leftover job by invoking abort function for the provided job id
|
||||
*
|
||||
* @this state
|
||||
* @param {string|number} key - job id to abort
|
||||
*/
|
||||
function clean(key)
|
||||
{
|
||||
if (typeof this.jobs[key] == 'function')
|
||||
{
|
||||
this.jobs[key]();
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
var defer = require('./defer.js');
|
||||
|
||||
// API
|
||||
module.exports = async;
|
||||
|
||||
/**
|
||||
* Runs provided callback asynchronously
|
||||
* even if callback itself is not
|
||||
*
|
||||
* @param {function} callback - callback to invoke
|
||||
* @returns {function} - augmented callback
|
||||
*/
|
||||
function async(callback)
|
||||
{
|
||||
var isAsync = false;
|
||||
|
||||
// check if async happened
|
||||
defer(function() { isAsync = true; });
|
||||
|
||||
return function async_callback(err, result)
|
||||
{
|
||||
if (isAsync)
|
||||
{
|
||||
callback(err, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
defer(function nextTick_callback()
|
||||
{
|
||||
callback(err, result);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
module.exports = defer;
|
||||
|
||||
/**
|
||||
* Runs provided function on next iteration of the event loop
|
||||
*
|
||||
* @param {function} fn - function to run
|
||||
*/
|
||||
function defer(fn)
|
||||
{
|
||||
var nextTick = typeof setImmediate == 'function'
|
||||
? setImmediate
|
||||
: (
|
||||
typeof process == 'object' && typeof process.nextTick == 'function'
|
||||
? process.nextTick
|
||||
: null
|
||||
);
|
||||
|
||||
if (nextTick)
|
||||
{
|
||||
nextTick(fn);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(fn, 0);
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
var async = require('./async.js')
|
||||
, abort = require('./abort.js')
|
||||
;
|
||||
|
||||
// API
|
||||
module.exports = iterate;
|
||||
|
||||
/**
|
||||
* Iterates over each job object
|
||||
*
|
||||
* @param {array|object} list - array or object (named list) to iterate over
|
||||
* @param {function} iterator - iterator to run
|
||||
* @param {object} state - current job status
|
||||
* @param {function} callback - invoked when all elements processed
|
||||
*/
|
||||
function iterate(list, iterator, state, callback)
|
||||
{
|
||||
// store current index
|
||||
var key = state['keyedList'] ? state['keyedList'][state.index] : state.index;
|
||||
|
||||
state.jobs[key] = runJob(iterator, key, list[key], function(error, output)
|
||||
{
|
||||
// don't repeat yourself
|
||||
// skip secondary callbacks
|
||||
if (!(key in state.jobs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up jobs
|
||||
delete state.jobs[key];
|
||||
|
||||
if (error)
|
||||
{
|
||||
// don't process rest of the results
|
||||
// stop still active jobs
|
||||
// and reset the list
|
||||
abort(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.results[key] = output;
|
||||
}
|
||||
|
||||
// return salvaged results
|
||||
callback(error, state.results);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs iterator over provided job element
|
||||
*
|
||||
* @param {function} iterator - iterator to invoke
|
||||
* @param {string|number} key - key/index of the element in the list of jobs
|
||||
* @param {mixed} item - job description
|
||||
* @param {function} callback - invoked after iterator is done with the job
|
||||
* @returns {function|mixed} - job abort function or something else
|
||||
*/
|
||||
function runJob(iterator, key, item, callback)
|
||||
{
|
||||
var aborter;
|
||||
|
||||
// allow shortcut if iterator expects only two arguments
|
||||
if (iterator.length == 2)
|
||||
{
|
||||
aborter = iterator(item, async(callback));
|
||||
}
|
||||
// otherwise go with full three arguments
|
||||
else
|
||||
{
|
||||
aborter = iterator(item, key, async(callback));
|
||||
}
|
||||
|
||||
return aborter;
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
var streamify = require('./streamify.js')
|
||||
, defer = require('./defer.js')
|
||||
;
|
||||
|
||||
// API
|
||||
module.exports = ReadableAsyncKit;
|
||||
|
||||
/**
|
||||
* Base constructor for all streams
|
||||
* used to hold properties/methods
|
||||
*/
|
||||
function ReadableAsyncKit()
|
||||
{
|
||||
ReadableAsyncKit.super_.apply(this, arguments);
|
||||
|
||||
// list of active jobs
|
||||
this.jobs = {};
|
||||
|
||||
// add stream methods
|
||||
this.destroy = destroy;
|
||||
this._start = _start;
|
||||
this._read = _read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys readable stream,
|
||||
* by aborting outstanding jobs
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function destroy()
|
||||
{
|
||||
if (this.destroyed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
if (typeof this.terminator == 'function')
|
||||
{
|
||||
this.terminator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts provided jobs in async manner
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _start()
|
||||
{
|
||||
// first argument – runner function
|
||||
var runner = arguments[0]
|
||||
// take away first argument
|
||||
, args = Array.prototype.slice.call(arguments, 1)
|
||||
// second argument - input data
|
||||
, input = args[0]
|
||||
// last argument - result callback
|
||||
, endCb = streamify.callback.call(this, args[args.length - 1])
|
||||
;
|
||||
|
||||
args[args.length - 1] = endCb;
|
||||
// third argument - iterator
|
||||
args[1] = streamify.iterator.call(this, args[1]);
|
||||
|
||||
// allow time for proper setup
|
||||
defer(function()
|
||||
{
|
||||
if (!this.destroyed)
|
||||
{
|
||||
this.terminator = runner.apply(null, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
endCb(null, Array.isArray(input) ? [] : {});
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implement _read to comply with Readable streams
|
||||
* Doesn't really make sense for flowing object mode
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function _read()
|
||||
{
|
||||
|
||||
}
|
||||
Loaded 100 of 718 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user