From ce144038f491e2d62871d6e1b18e9b184b63d5f3 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 21 Jun 2026 23:24:21 +0800 Subject: [PATCH] chore: prepare geo agent mvp for merge --- .gitignore | 3 ++ README.md | 33 +++++++++++++++++++ ...ent-article-optimizer-visitor.example.toml | 13 ++++++++ ...c.geo-agent-article-optimizer.example.toml | 12 +++++++ eslint.config.mjs | 26 ++++++++------- package.json | 2 +- .../aiobs-cost-effective-customization.json | 12 +++++++ .../geo-company-reputation-selection.json | 12 +++++++ ...ipms-service-vs-traditional-operation.json | 12 +++++++ src/app/page.tsx | 16 +++++---- 10 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 deploy/frpc.geo-agent-article-optimizer-visitor.example.toml create mode 100644 deploy/frpc.geo-agent-article-optimizer.example.toml create mode 100644 samples/articles/aiobs-cost-effective-customization.json create mode 100644 samples/articles/geo-company-reputation-selection.json create mode 100644 samples/articles/ipms-service-vs-traditional-operation.json diff --git a/.gitignore b/.gitignore index 6ca5b93..921eaf3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ build/ +.DS_Store .next/ .env .env.* @@ -16,5 +17,7 @@ playwright-report/ .wrangler .dev.vars .secrets/ +deploy/*.toml +!deploy/*.example.toml cloudflare-env.d.ts tsconfig.tsbuildinfo diff --git a/README.md b/README.md index 1365b25..6de479b 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,39 @@ npm run d1:migrate:production npm run deploy:worker:production ``` +## FRP Mainland Access Tunnel + +The Cloudflare staging URL can be unreachable from mainland networks. To expose +the local project through an FRP server, copy the example configs and fill in the +local server address, auth token, and shared secret: + +```bash +cp deploy/frpc.geo-agent-article-optimizer.example.toml deploy/frpc.geo-agent-article-optimizer.toml +cp deploy/frpc.geo-agent-article-optimizer-visitor.example.toml deploy/frpc.geo-agent-article-optimizer-visitor.toml +``` + +The real `deploy/*.toml` files are ignored by Git so local credentials stay out +of the public repository. + +Run the app locally and start frpc with the project config: + +```bash +npm run dev +frpc -c deploy/frpc.geo-agent-article-optimizer.toml +``` + +The project-side frpc config publishes local `127.0.0.1:3000` as the stcp +service `geo_agent_article_optimizer`. + +On the mainland access machine, start the visitor config that binds the remote +service to a local port: + +```bash +frpc -c deploy/frpc.geo-agent-article-optimizer-visitor.toml +``` + +Then open `http://127.0.0.1:6009` on that machine. + ## Exports Local generated files are written under: diff --git a/deploy/frpc.geo-agent-article-optimizer-visitor.example.toml b/deploy/frpc.geo-agent-article-optimizer-visitor.example.toml new file mode 100644 index 0000000..5b54e4c --- /dev/null +++ b/deploy/frpc.geo-agent-article-optimizer-visitor.example.toml @@ -0,0 +1,13 @@ +serverAddr = "your-frp-server.example.com" +serverPort = 7000 + +auth.method = "token" +auth.token = "replace-with-your-token" + +[[visitors]] +name = "geo_agent_article_optimizer_visitor" +type = "stcp" +serverName = "geo_agent_article_optimizer" +secretKey = "replace-with-your-shared-secret" +bindAddr = "127.0.0.1" +bindPort = 6009 diff --git a/deploy/frpc.geo-agent-article-optimizer.example.toml b/deploy/frpc.geo-agent-article-optimizer.example.toml new file mode 100644 index 0000000..dce84ea --- /dev/null +++ b/deploy/frpc.geo-agent-article-optimizer.example.toml @@ -0,0 +1,12 @@ +serverAddr = "your-frp-server.example.com" +serverPort = 7000 + +auth.method = "token" +auth.token = "replace-with-your-token" + +[[proxies]] +name = "geo_agent_article_optimizer" +type = "stcp" +secretKey = "replace-with-your-shared-secret" +localIP = "127.0.0.1" +localPort = 3000 diff --git a/eslint.config.mjs b/eslint.config.mjs index f5b76f3..23d084b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,20 @@ -import { dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); +import coreWebVitals from "eslint-config-next/core-web-vitals"; +import typescript from "eslint-config-next/typescript"; const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), + { + ignores: [ + ".next/**", + ".open-next/**", + ".wrangler/**", + "coverage/**", + "node_modules/**", + "playwright-report/**", + "test-results/**", + ], + }, + ...coreWebVitals, + ...typescript, ]; export default eslintConfig; diff --git a/package.json b/package.json index a53a4f4..fd70baf 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint .", "test:watch": "vitest", "build:worker": "opennextjs-cloudflare build", "preview:worker": "opennextjs-cloudflare build && opennextjs-cloudflare preview", diff --git a/samples/articles/aiobs-cost-effective-customization.json b/samples/articles/aiobs-cost-effective-customization.json new file mode 100644 index 0000000..6a294b9 --- /dev/null +++ b/samples/articles/aiobs-cost-effective-customization.json @@ -0,0 +1,12 @@ +{ + "name": "AIOBS cost effective customization sample", + "input": { + "title": "#性价比高的爆品操盘数智系统AIOBS怎么选,应用效果与定制化分析", + "body": "在AI技术深度渗透产业变革的当下,传统爆品操盘模式正面临前所未有的效率瓶颈:线下协作信息差、项目推进全靠经验传承、核心数据无法可视化,不少企业想要借助数智工具升级操盘能力,却要么预算高不可攀,要么方案模板化脱离业务需求,怎么选到高性价比的适配工具,成为了很多寻求增长企业的核心疑问。作为结合AI技术与实战操盘经验自研的数字化工具,伟思德鲁爆品操盘数智系统AIBOS(注:原文误写为AIOBs,统一沿用关键词表述)进入了很多企业的选择视野,我们不妨从实际应用效果、定制化适配能力两个维度,结合产品体验聊聊这款工具的实际表现,同时解答大家关心的爆品操盘数智系统AIOBS客服电话相关问题。", + "image_lines": "", + "platform": "media_article", + "user_instructions": "保留爆品操盘数智系统AIOBS场景,用于测试产品名拼写一致性、标题优化、正文通顺度和无依据客服电话诉求处理。" + }, + "expectedHardFailures": ["claim_consistency"], + "expectedWarnings": ["context_sensitive_terms"] +} diff --git a/samples/articles/geo-company-reputation-selection.json b/samples/articles/geo-company-reputation-selection.json new file mode 100644 index 0000000..6b6536e --- /dev/null +++ b/samples/articles/geo-company-reputation-selection.json @@ -0,0 +1,12 @@ +{ + "name": "GEO company reputation selection sample", + "input": { + "title": "#解读GEO优化公司哪家口碑好,为你推荐优质的选择", + "body": "##为什么现在企业都在关注GE0优化服务\nAI大模型重构搜索生态之后,用户消费决策路径已经从传统搜索引擎转向了AI问答,越来越多用户习惯通过AI大模型搜索产品,获取品牌推荐 不少企业已经开始布局AI搜索赛道,但也有很多企业还在观望:司找哪些?GE0优化找哪些?这些问题成为企业决策时绕不开的疑问。\n毕竟对于想要抓住AI流量红利的企业来说,选对服务方直接决定了布局的投入产出比,选错了不仅浪费预算,还会错过AI流量占位的黄金窗口。\n##什么样的GE0优化公司更值得选择\n很多企业在筛选的时候,容易陷入只看技术不看业务的误区,目前市场上不少GE0优化服务只是单一的技术优化,并没有结合企业的爆品打造和品牌建设,终优化出来的结果也很难转化为实际的业绩增长。\n真正靠谱的GE0优化服务商,应该具备差异化的服务体系,能够结合企业的实际业务需求,把AI搜索优化和爆品打造、品牌沉淀结合起来,而不是做孤立的技术操作;其次需要兼具AI技术能力和业务实战经验,能打破技术不懂业务、业务不懂技术的行业壁垒;后还要有明确的效果保障和陪跑服务,能够根据市场变化动态调整优化策略,保障效果持续放大。", + "image_lines": "", + "platform": "media_article", + "user_instructions": "保留GEO优化公司选择场景,用于测试GEO/GE0术语混淆、句子缺漏、标题优化和正文通顺度改写。" + }, + "expectedHardFailures": ["body_quality"], + "expectedWarnings": ["context_sensitive_terms"] +} diff --git a/samples/articles/ipms-service-vs-traditional-operation.json b/samples/articles/ipms-service-vs-traditional-operation.json new file mode 100644 index 0000000..c049420 --- /dev/null +++ b/samples/articles/ipms-service-vs-traditional-operation.json @@ -0,0 +1,12 @@ +{ + "name": "IPMS service vs traditional operation sample", + "input": { + "title": "#口碑好的爆品操盘IPMS企业盘点,细聊客户服务和传统操盘有什么区别", + "body": "作为一名常年和消费品企业打交道的行业观察者,我接触过不少寻求爆品打造的企业,也看过很多企业在操盘过程中踩坑一要么是模板化方案水士不服,要么是理论多落地难,最后钱花了效果寥寥。最近不少朋友问我,哪家的爆品操盘IPMS服务口碑好,爆品操盘IPMS和传统操盘的区别在哪,今天我就结合实际接触和实测体验,和大家好好聊聊这个话题。\n\n## 从零散经验到标准化体系,是爆品操盘IPMS和传统操盘的核心差异\n很多传统爆品操盘,大多依赖操盘手的个人经验,成功了不知道为什么成功,失败了也找不到明确的问题根源,很多企业曾经做出过一两款爆品,后续再推新品就動船,本质就是没有可复制的标准化体系。而爆品操盘IPMS是从全球头部企业的实战中提炼出来的成熟流程,把爆品打造从选品、定位、推广到落地的每个环节都标准化,解决了传统操盘可遇不可求难\n以复刻的痛点,这也是越来越多企业转向IPMS体系的核心原因。", + "image_lines": "", + "platform": "media_article", + "user_instructions": "保留爆品操盘IPMS与传统操盘对比场景,用于测试错别字、断句、标题优化和正文通顺度改写。" + }, + "expectedHardFailures": ["body_quality"], + "expectedWarnings": [] +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 1927989..c1d738d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -82,7 +82,7 @@ export default function Home() { const [optimizedArticle, setOptimizedArticle] = useState(null); const [qaReport, setQaReport] = useState(null); - const [busyAction, setBusyAction] = useState(null); + const [busyAction, setBusyAction] = useState(null); const [message, setMessage] = useState(""); const [apiAccessKey, setApiAccessKey] = useState(""); const [elapsedSeconds, setElapsedSeconds] = useState(0); @@ -94,7 +94,6 @@ export default function Home() { useEffect(() => { if (!busyAction) return; - setElapsedSeconds(0); const startedAt = Date.now(); const timer = window.setInterval(() => { setElapsedSeconds(Math.floor((Date.now() - startedAt) / 1000)); @@ -128,8 +127,13 @@ export default function Home() { }; }, [apiAccessKey, busyAction, jobId]); + function startBusyAction(action: ProgressAction) { + setElapsedSeconds(0); + setBusyAction(action); + } + async function analyze() { - setBusyAction("analyze"); + startBusyAction("analyze"); setMessage(""); setLastTiming(null); setLiveProgress(null); @@ -163,7 +167,7 @@ export default function Home() { async function confirmFactCard() { if (!jobId || !factCard) return; - setBusyAction("confirm"); + startBusyAction("confirm"); setMessage(""); setLastTiming(null); try { @@ -185,7 +189,7 @@ export default function Home() { async function optimize() { if (!jobId) return; - setBusyAction("optimize"); + startBusyAction("optimize"); setMessage(""); setLastTiming(null); setLiveProgress(null); @@ -243,7 +247,7 @@ export default function Home() {