234 lines
5.9 KiB
Markdown
234 lines
5.9 KiB
Markdown
# GEO Agent Article Optimizer
|
|
|
|
Local MVP for optimizing pasted GEO-related articles while preserving confirmed
|
|
brand facts. The app extracts a fact card, requires user confirmation, rewrites
|
|
under those constraints, runs QA gates, and exports Markdown, Word, and JSON.
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
npm install
|
|
cp .env.example .env.local
|
|
npm run dev
|
|
```
|
|
|
|
Open `http://localhost:3000`.
|
|
|
|
## Environment
|
|
|
|
Required for live model calls:
|
|
|
|
```text
|
|
LLM_PROVIDER=deepseek
|
|
DEEPSEEK_API_KEY=
|
|
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
|
DEEPSEEK_MODEL=deepseek-v4-pro
|
|
DEEPSEEK_THINKING=disabled
|
|
APP_DATA_DIR=./data
|
|
API_ACCESS_KEY=local-dev-key
|
|
API_AUTH_DISABLED=false
|
|
```
|
|
|
|
OpenAI-compatible fallback keys are also accepted:
|
|
|
|
```text
|
|
OPENAI_API_KEY=
|
|
OPENAI_MODEL=gpt-4.1-mini
|
|
```
|
|
|
|
When a DeepSeek or OpenAI-compatible key is configured, the workflow uses the
|
|
provider for fact extraction, article optimization, QA enrichment, and targeted
|
|
rewrite. Every LLM response is validated with Zod before use. When no API key is
|
|
configured, or when the provider response is invalid, deterministic local
|
|
fallbacks keep the workflow usable for tests and local review.
|
|
|
|
All API requests require the configured access key:
|
|
|
|
```text
|
|
x-api-key: <API_ACCESS_KEY>
|
|
```
|
|
|
|
## LLM Runtime Logs
|
|
|
|
LLM calls emit regular server-side logs from `src/lib/llm/client.ts`. These logs
|
|
are intended for local development, customer demos, and production
|
|
troubleshooting.
|
|
|
|
Example:
|
|
|
|
```text
|
|
[llm:start] provider=deepseek model=deepseek-v4-pro task=article_optimizer
|
|
[llm:response] task=article_optimizer duration_ms=18342 raw={"title":"..."}
|
|
[llm:validated] task=article_optimizer ok=true
|
|
```
|
|
|
|
The `raw=` field is truncated to 4000 characters to avoid log explosions. Set
|
|
`LLM_LOG_RAW_LIMIT=0` to suppress raw response snippets, or set a larger number
|
|
when diagnosing model output. API keys, prompts, system messages, and
|
|
environment secrets are not logged.
|
|
|
|
Workflow task names are:
|
|
|
|
- `fact_extractor`
|
|
- `article_optimizer`
|
|
- `quality_inspector`
|
|
- `targeted_rewriter`
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
npm test
|
|
npm run build
|
|
npx playwright test
|
|
```
|
|
|
|
## Sample Article E2E
|
|
|
|
Run the live sample-article browser workflow:
|
|
|
|
```bash
|
|
npm run test:e2e:samples
|
|
```
|
|
|
|
The runner reads `.env.local`, starts an isolated local Next.js server, loads
|
|
`samples/articles/*.json`, opens the web UI with Playwright, runs each sample
|
|
through the one-click optimization flow, validates authenticated exports, and
|
|
writes a report under:
|
|
|
|
```text
|
|
test-results/geo-sample-flow/<timestamp>/
|
|
```
|
|
|
|
Useful options:
|
|
|
|
```bash
|
|
npm run test:e2e:samples -- --limit 1
|
|
npm run test:e2e:samples -- --sample title-quality
|
|
npm run test:e2e:samples -- --headed
|
|
npm run test:e2e:samples -- --reuse-server --base-url http://localhost:3000
|
|
```
|
|
|
|
This workflow uses the real configured LLM provider by default. It requires
|
|
`API_ACCESS_KEY` and the provider API key in `.env.local` or the shell
|
|
environment. Reports and exports are written to `test-results/`, which is
|
|
ignored by Git.
|
|
|
|
## Cloudflare Workers Deployment
|
|
|
|
Cloudflare deployment is manual. Pushing to Git does not deploy or hot-update
|
|
the production Worker.
|
|
|
|
Local development:
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Cloudflare preview:
|
|
|
|
```bash
|
|
cp .dev.vars.example .dev.vars
|
|
npm run d1:migrate:local
|
|
npm run preview:worker
|
|
```
|
|
|
|
Create private staging resources:
|
|
|
|
```bash
|
|
npx wrangler d1 create geo-agent-article-optimizer-staging
|
|
npx wrangler r2 bucket create geo-agent-article-optimizer-staging
|
|
```
|
|
|
|
Create private production resources:
|
|
|
|
```bash
|
|
npx wrangler d1 create geo-agent-article-optimizer-production
|
|
npx wrangler r2 bucket create geo-agent-article-optimizer-production
|
|
```
|
|
|
|
After D1 creation, copy the returned database IDs into the matching
|
|
`wrangler.jsonc` environment entries. Keep R2 buckets private; do not add public
|
|
bucket domains.
|
|
|
|
Set secrets:
|
|
|
|
```bash
|
|
npx wrangler secret put API_ACCESS_KEY --env staging
|
|
npx wrangler secret put DEEPSEEK_API_KEY --env staging
|
|
npx wrangler secret put API_ACCESS_KEY --env production
|
|
npx wrangler secret put DEEPSEEK_API_KEY --env production
|
|
```
|
|
|
|
D1 schema changes only through migrations. Runtime code must not rebuild or
|
|
clear production tables. Apply migrations in this order:
|
|
|
|
```bash
|
|
npm run d1:migrate:local
|
|
npm run d1:migrate:staging
|
|
npm run deploy:worker:staging
|
|
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:
|
|
|
|
```text
|
|
data/exports/<job_id>/
|
|
```
|
|
|
|
Cloudflare exports are written to the private R2 bucket bound as
|
|
`EXPORT_BUCKET` and served only through authenticated API routes.
|
|
|
|
Each passing or warning-only QA run can produce:
|
|
|
|
- `optimized.md`
|
|
- `optimized.docx`
|
|
- `qa_report.json`
|
|
|
|
Hard QA failures block export.
|
|
|
|
## MVP Limits
|
|
|
|
- Pasted text only; no direct `.docx` parsing.
|
|
- Local SQLite by default; Cloudflare deployments use D1.
|
|
- No account permissions or collaboration.
|
|
- No publishing platform APIs.
|
|
- No batch queue.
|
|
- Basic Word export layout only.
|
|
- Unconfirmed facts are never used as truth.
|