worldmonitor-intelligence-dashboard

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

World Monitor Intelligence Dashboard

World Monitor 全球情报仪表盘

Skill by ara.so — Daily 2026 Skills collection.
World Monitor is a real-time global intelligence dashboard combining AI-powered news aggregation (435+ feeds, 15 categories), dual map engine (3D globe + WebGL flat map with 45 data layers), geopolitical risk scoring, finance radar (92 exchanges), and cross-stream signal correlation — all from a single TypeScript/Vite codebase deployable as web, PWA, or native desktop (Tauri 2).

ara.so 开发的Skill — 2026每日技能合集。
World Monitor是一个实时全球情报仪表盘,整合了AI驱动的新闻聚合(435+数据源,15个分类)、双地图引擎(3D地球仪 + 带45个数据层的WebGL平面地图)、地缘政治风险评分、金融雷达(92个交易所)以及跨流信号关联功能——所有功能都基于单一TypeScript/Vite代码库,可部署为网页、PWA或原生桌面应用(Tauri 2)。

Installation & Quick Start

安装与快速开始

bash
git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev          # Opens http://localhost:5173
No environment variables required for basic operation. All features work with local Ollama by default.
bash
git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev          # 打开 http://localhost:5173
基础运行无需环境变量。默认情况下所有功能都可通过本地Ollama运行。

Site Variants

站点变体

bash
npm run dev:tech       # tech.worldmonitor.app variant
npm run dev:finance    # finance.worldmonitor.app variant
npm run dev:commodity  # commodity.worldmonitor.app variant
npm run dev:happy      # happy.worldmonitor.app variant
bash
npm run dev:tech       # tech.worldmonitor.app 变体
npm run dev:finance    # finance.worldmonitor.app 变体
npm run dev:commodity  # commodity.worldmonitor.app 变体
npm run dev:happy      # happy.worldmonitor.app 变体

Production Build

生产构建

bash
npm run typecheck        # TypeScript validation
npm run build:full       # Build all variants
npm run build            # Build default (world) variant

bash
npm run typecheck        # TypeScript 验证
npm run build:full       # 构建所有变体
npm run build            # 构建默认(全球)变体

Project Structure

项目结构

worldmonitor/
├── src/
│   ├── components/       # UI components (TypeScript)
│   ├── feeds/            # 435+ RSS/API feed definitions
│   ├── layers/           # Map data layers (deck.gl)
│   ├── ai/               # AI synthesis pipeline
│   ├── signals/          # Cross-stream correlation engine
│   ├── finance/          # Market data (92 exchanges)
│   ├── variants/         # Site variant configs (world/tech/finance/commodity/happy)
│   └── protos/           # Protocol Buffer definitions (92 protos, 22 services)
├── api/                  # Vercel Edge Functions (60+)
├── src-tauri/            # Tauri 2 desktop app (Rust)
├── docs/                 # Documentation source
└── vite.config.ts

worldmonitor/
├── src/
│   ├── components/       # UI组件(TypeScript)
│   ├── feeds/            # 435+ RSS/API 数据源定义
│   ├── layers/           # 地图数据层(deck.gl)
│   ├── ai/               # AI合成流水线
│   ├── signals/          # 跨流关联引擎
│   ├── finance/          # 市场数据(92个交易所)
│   ├── variants/         # 站点变体配置(全球/科技/金融/大宗商品/生活)
│   └── protos/           # Protocol Buffer定义(92个proto文件,22个服务)
├── api/                  # Vercel边缘函数(60+)
├── src-tauri/            # Tauri 2桌面应用(Rust)
├── docs/                 # 文档源文件
└── vite.config.ts

Environment Variables

环境变量

Create a
.env.local
file (never commit secrets):
bash
undefined
创建
.env.local
文件(请勿提交机密信息):
bash
undefined

AI Providers (all optional — Ollama works with no keys)

AI提供商(均为可选——Ollama无需密钥即可运行)

VITE_OLLAMA_BASE_URL=http://localhost:11434 # Local Ollama instance VITE_GROQ_API_KEY=$GROQ_API_KEY # Groq cloud inference VITE_OPENROUTER_API_KEY=$OPENROUTER_API_KEY # OpenRouter multi-model
VITE_OLLAMA_BASE_URL=http://localhost:11434 # 本地Ollama实例 VITE_GROQ_API_KEY=$GROQ_API_KEY # Groq云推理服务 VITE_OPENROUTER_API_KEY=$OPENROUTER_API_KEY # OpenRouter多模型服务

Caching (optional, improves performance)

缓存(可选,提升性能)

UPSTASH_REDIS_REST_URL=$UPSTASH_REDIS_REST_URL UPSTASH_REDIS_REST_TOKEN=$UPSTASH_REDIS_REST_TOKEN
UPSTASH_REDIS_REST_URL=$UPSTASH_REDIS_REST_URL UPSTASH_REDIS_REST_TOKEN=$UPSTASH_REDIS_REST_TOKEN

Map tiles (optional, MapLibre GL)

地图瓦片(可选,MapLibre GL)

VITE_MAPTILER_API_KEY=$MAPTILER_API_KEY
VITE_MAPTILER_API_KEY=$MAPTILER_API_KEY

Variant selection

变体选择

VITE_SITE_VARIANT=world # world | tech | finance | commodity | happy

---
VITE_SITE_VARIANT=world # world | tech | finance | commodity | happy

---

Core Concepts

核心概念

Feed Categories

数据源分类

World Monitor aggregates 435+ feeds across 15 categories:
typescript
// src/feeds/categories.ts pattern
import type { FeedCategory } from './types';

const FEED_CATEGORIES: FeedCategory[] = [
  'geopolitics',
  'military',
  'economics',
  'technology',
  'climate',
  'energy',
  'health',
  'finance',
  'commodities',
  'infrastructure',
  'cyber',
  'space',
  'diplomacy',
  'disasters',
  'society',
];
World Monitor聚合了15个分类下的435+数据源:
typescript
// src/feeds/categories.ts 示例
import type { FeedCategory } from './types';

const FEED_CATEGORIES: FeedCategory[] = [
  'geopolitics',
  'military',
  'economics',
  'technology',
  'climate',
  'energy',
  'health',
  'finance',
  'commodities',
  'infrastructure',
  'cyber',
  'space',
  'diplomacy',
  'disasters',
  'society',
];

Country Intelligence Index

国家情报指数

Composite risk scoring across 12 signal categories per country:
typescript
// Example: accessing country risk scores
import { CountryIntelligence } from './signals/country-intelligence';

const intel = new CountryIntelligence();

// Get composite risk score for a country
const score = await intel.getCountryScore('UA');
console.log(score);
// {
//   composite: 0.82,
//   signals: {
//     military: 0.91,
//     economic: 0.74,
//     political: 0.88,
//     humanitarian: 0.79,
//     ...
//   },
//   trend: 'escalating',
//   updatedAt: '2026-03-17T08:00:00Z'
// }

// Subscribe to real-time updates
intel.subscribe('UA', (update) => {
  console.log('Risk update:', update);
});
针对每个国家的12个信号类别进行综合风险评分:
typescript
// 示例:获取国家风险评分
import { CountryIntelligence } from './signals/country-intelligence';

const intel = new CountryIntelligence();

// 获取某国的综合风险评分
const score = await intel.getCountryScore('UA');
console.log(score);
// {
//   composite: 0.82,
//   signals: {
//     military: 0.91,
//     economic: 0.74,
//     political: 0.88,
//     humanitarian: 0.79,
//     ...
//   },
//   trend: 'escalating',
//   updatedAt: '2026-03-17T08:00:00Z'
// }

// 订阅实时更新
intel.subscribe('UA', (update) => {
  console.log('风险更新:', update);
});

AI Synthesis Pipeline

AI合成流水线

typescript
// src/ai/synthesize.ts pattern
import { AISynthesizer } from './ai/synthesizer';

const synth = new AISynthesizer({
  provider: 'ollama',           // 'ollama' | 'groq' | 'openrouter'
  model: 'llama3.2',            // any Ollama-compatible model
  baseUrl: process.env.VITE_OLLAMA_BASE_URL,
});

// Synthesize a news brief from multiple feed items
const brief = await synth.synthesize({
  items: feedItems,             // FeedItem[]
  category: 'geopolitics',
  region: 'Europe',
  maxTokens: 500,
  language: 'en',
});

console.log(brief.summary);    // AI-generated synthesis
console.log(brief.signals);    // Extracted signals array
console.log(brief.confidence); // 0-1 confidence score
typescript
// src/ai/synthesize.ts 示例
import { AISynthesizer } from './ai/synthesizer';

const synth = new AISynthesizer({
  provider: 'ollama',           // 'ollama' | 'groq' | 'openrouter'
  model: 'llama3.2',            // 任何Ollama兼容模型
  baseUrl: process.env.VITE_OLLAMA_BASE_URL,
});

// 从多个数据源条目合成新闻摘要
const brief = await synth.synthesize({
  items: feedItems,             // FeedItem[]
  category: 'geopolitics',
  region: 'Europe',
  maxTokens: 500,
  language: 'en',
});

console.log(brief.summary);    // AI生成的摘要
console.log(brief.signals);    // 提取的信号数组
console.log(brief.confidence); // 0-1置信度评分

Cross-Stream Signal Correlation

跨流信号关联

typescript
// src/signals/correlator.ts pattern
import { SignalCorrelator } from './signals/correlator';

const correlator = new SignalCorrelator();

// Detect convergence across military, economic, disaster signals
const convergence = await correlator.detectConvergence({
  streams: ['military', 'economic', 'disaster', 'escalation'],
  timeWindow: '6h',
  threshold: 0.7,
  region: 'Middle East',
});

if (convergence.detected) {
  console.log('Convergence signals:', convergence.signals);
  console.log('Escalation probability:', convergence.probability);
  console.log('Contributing events:', convergence.events);
}

typescript
// src/signals/correlator.ts 示例
import { SignalCorrelator } from './signals/correlator';

const correlator = new SignalCorrelator();

// 检测军事、经济、灾害信号的汇聚情况
const convergence = await correlator.detectConvergence({
  streams: ['military', 'economic', 'disaster', 'escalation'],
  timeWindow: '6h',
  threshold: 0.7,
  region: 'Middle East',
});

if (convergence.detected) {
  console.log('汇聚信号:', convergence.signals);
  console.log('升级概率:', convergence.probability);
  console.log('相关事件:', convergence.events);
}

Map Engine Integration

地图引擎集成

3D Globe (globe.gl)

3D地球仪(globe.gl)

typescript
// src/components/globe/GlobeView.ts
import Globe from 'globe.gl';
import { getCountryRiskData } from '../signals/country-intelligence';

export function initGlobe(container: HTMLElement) {
  const globe = Globe()(container)
    .globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg')
    .backgroundImageUrl('//unpkg.com/three-globe/example/img/night-sky.png');

  // Load country risk layer
  const riskData = await getCountryRiskData();

  globe
    .polygonsData(riskData.features)
    .polygonCapColor(feat => riskToColor(feat.properties.riskScore))
    .polygonSideColor(() => 'rgba(0, 100, 0, 0.15)')
    .polygonLabel(({ properties: d }) =>
      `<b>${d.name}</b><br/>Risk: ${(d.riskScore * 100).toFixed(0)}%`
    );

  return globe;
}

function riskToColor(score: number): string {
  if (score > 0.8) return 'rgba(220, 38, 38, 0.8)';   // critical
  if (score > 0.6) return 'rgba(234, 88, 12, 0.7)';   // high
  if (score > 0.4) return 'rgba(202, 138, 4, 0.6)';   // elevated
  if (score > 0.2) return 'rgba(22, 163, 74, 0.5)';   // low
  return 'rgba(15, 118, 110, 0.4)';                    // minimal
}
typescript
// src/components/globe/GlobeView.ts
import Globe from 'globe.gl';
import { getCountryRiskData } from '../signals/country-intelligence';

export function initGlobe(container: HTMLElement) {
  const globe = Globe()(container)
    .globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg')
    .backgroundImageUrl('//unpkg.com/three-globe/example/img/night-sky.png');

  // 加载国家风险层
  const riskData = await getCountryRiskData();

  globe
    .polygonsData(riskData.features)
    .polygonCapColor(feat => riskToColor(feat.properties.riskScore))
    .polygonSideColor(() => 'rgba(0, 100, 0, 0.15)')
    .polygonLabel(({ properties: d }) =>
      `<b>${d.name}</b><br/>风险: ${(d.riskScore * 100).toFixed(0)}%`
    );

  return globe;
}

function riskToColor(score: number): string {
  if (score > 0.8) return 'rgba(220, 38, 38, 0.8)';   // 严重
  if (score > 0.6) return 'rgba(234, 88, 12, 0.7)';   // 高
  if (score > 0.4) return 'rgba(202, 138, 4, 0.6)';   // 升高
  if (score > 0.2) return 'rgba(22, 163, 74, 0.5)';   // 低
  return 'rgba(15, 118, 110, 0.4)';                    // 极低
}

WebGL Flat Map (deck.gl + MapLibre GL)

WebGL平面地图(deck.gl + MapLibre GL)

typescript
// src/components/map/DeckMap.ts
import { Deck } from '@deck.gl/core';
import { ScatterplotLayer, ArcLayer, HeatmapLayer } from '@deck.gl/layers';
import maplibregl from 'maplibre-gl';

export function initDeckMap(container: HTMLElement) {
  const map = new maplibregl.Map({
    container,
    style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
    center: [0, 20],
    zoom: 2,
  });

  const deck = new Deck({
    canvas: 'deck-canvas',
    initialViewState: { longitude: 0, latitude: 20, zoom: 2 },
    controller: true,
    layers: [
      // Event scatter layer
      new ScatterplotLayer({
        id: 'events',
        data: getActiveEvents(),
        getPosition: d => [d.lng, d.lat],
        getRadius: d => d.severity * 50000,
        getFillColor: d => severityToRGBA(d.severity),
        pickable: true,
      }),
      // Supply chain arc layer
      new ArcLayer({
        id: 'supply-chains',
        data: getSupplyChainData(),
        getSourcePosition: d => d.source,
        getTargetPosition: d => d.target,
        getSourceColor: [0, 128, 200],
        getTargetColor: [200, 0, 80],
        getWidth: 2,
      }),
    ],
  });

  return { map, deck };
}

typescript
// src/components/map/DeckMap.ts
import { Deck } from '@deck.gl/core';
import { ScatterplotLayer, ArcLayer, HeatmapLayer } from '@deck.gl/layers';
import maplibregl from 'maplibre-gl';

export function initDeckMap(container: HTMLElement) {
  const map = new maplibregl.Map({
    container,
    style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
    center: [0, 20],
    zoom: 2,
  });

  const deck = new Deck({
    canvas: 'deck-canvas',
    initialViewState: { longitude: 0, latitude: 20, zoom: 2 },
    controller: true,
    layers: [
      // 事件散点层
      new ScatterplotLayer({
        id: 'events',
        data: getActiveEvents(),
        getPosition: d => [d.lng, d.lat],
        getRadius: d => d.severity * 50000,
        getFillColor: d => severityToRGBA(d.severity),
        pickable: true,
      }),
      // 供应链弧线层
      new ArcLayer({
        id: 'supply-chains',
        data: getSupplyChainData(),
        getSourcePosition: d => d.source,
        getTargetPosition: d => d.target,
        getSourceColor: [0, 128, 200],
        getTargetColor: [200, 0, 80],
        getWidth: 2,
      }),
    ],
  });

  return { map, deck };
}

Finance Radar

金融雷达

typescript
// src/finance/radar.ts pattern
import { FinanceRadar } from './finance/radar';

const radar = new FinanceRadar();

// Get market composite (7-signal)
const composite = await radar.getMarketComposite();
console.log(composite);
// {
//   score: 0.62,
//   signals: {
//     volatility: 0.71,
//     momentum: 0.58,
//     sentiment: 0.65,
//     liquidity: 0.44,
//     correlation: 0.78,
//     macro: 0.61,
//     geopolitical: 0.82
//   },
//   exchanges: 92,
//   timestamp: '2026-03-17T08:00:00Z'
// }

// Watch specific exchange
const exchange = await radar.getExchange('NYSE');
const crypto = await radar.getCrypto(['BTC', 'ETH', 'SOL']);
const commodities = await radar.getCommodities(['GOLD', 'OIL', 'WHEAT']);

typescript
// src/finance/radar.ts 示例
import { FinanceRadar } from './finance/radar';

const radar = new FinanceRadar();

// 获取市场综合评分(7个信号)
const composite = await radar.getMarketComposite();
console.log(composite);
// {
//   score: 0.62,
//   signals: {
//     volatility: 0.71,
//     momentum: 0.58,
//     sentiment: 0.65,
//     liquidity: 0.44,
//     correlation: 0.78,
//     macro: 0.61,
//     geopolitical: 0.82
//   },
//   exchanges: 92,
//   timestamp: '2026-03-17T08:00:00Z'
// }

// 监控特定交易所
const exchange = await radar.getExchange('NYSE');
const crypto = await radar.getCrypto(['BTC', 'ETH', 'SOL']);
const commodities = await radar.getCommodities(['GOLD', 'OIL', 'WHEAT']);

Language & RTL Support

语言与RTL支持

World Monitor supports 21 languages with native-language feeds:
typescript
// src/i18n/config.ts pattern
import { setLanguage, getAvailableLanguages } from './i18n';

const languages = getAvailableLanguages();
// ['en', 'ar', 'zh', 'ru', 'fr', 'es', 'de', 'ja', 'ko', 'pt',
//  'hi', 'fa', 'tr', 'pl', 'uk', 'nl', 'sv', 'he', 'it', 'vi', 'id']

// Switch language (handles RTL automatically)
await setLanguage('ar');  // Arabic — triggers RTL layout
await setLanguage('he');  // Hebrew — triggers RTL layout
await setLanguage('fa');  // Farsi — triggers RTL layout

// Configure feed language filtering
import { FeedManager } from './feeds/manager';
const feeds = new FeedManager({ language: 'ar', includeEnglish: true });

World Monitor支持21种语言,且具备对应语言的原生数据源:
typescript
// src/i18n/config.ts 示例
import { setLanguage, getAvailableLanguages } from './i18n';

const languages = getAvailableLanguages();
// ['en', 'ar', 'zh', 'ru', 'fr', 'es', 'de', 'ja', 'ko', 'pt',
//  'hi', 'fa', 'tr', 'pl', 'uk', 'nl', 'sv', 'he', 'it', 'vi', 'id']

// 切换语言(自动处理RTL布局)
await setLanguage('ar');  // 阿拉伯语——触发RTL布局
await setLanguage('he');  // 希伯来语——触发RTL布局
await setLanguage('fa');  // 波斯语——触发RTL布局

// 配置数据源语言过滤
import { FeedManager } from './feeds/manager';
const feeds = new FeedManager({ language: 'ar', includeEnglish: true });

Protocol Buffers (API Contracts)

Protocol Buffers(API契约)

typescript
// src/protos — 92 proto definitions, 22 services
// Example generated client usage:

import { IntelligenceServiceClient } from './protos/generated/intelligence_grpc_web_pb';
import { CountryRequest } from './protos/generated/intelligence_pb';

const client = new IntelligenceServiceClient(
  process.env.VITE_API_BASE_URL || 'http://localhost:8080'
);

const request = new CountryRequest();
request.setCountryCode('DE');
request.setTimeRange('24h');
request.setSignalTypes(['military', 'economic', 'political']);

client.getCountryIntelligence(request, {}, (err, response) => {
  if (err) console.error(err);
  else console.log(response.toObject());
});

typescript
// src/protos — 92个proto定义,22个服务
// 生成的客户端使用示例:

import { IntelligenceServiceClient } from './protos/generated/intelligence_grpc_web_pb';
import { CountryRequest } from './protos/generated/intelligence_pb';

const client = new IntelligenceServiceClient(
  process.env.VITE_API_BASE_URL || 'http://localhost:8080'
);

const request = new CountryRequest();
request.setCountryCode('DE');
request.setTimeRange('24h');
request.setSignalTypes(['military', 'economic', 'political']);

client.getCountryIntelligence(request, {}, (err, response) => {
  if (err) console.error(err);
  else console.log(response.toObject());
});

Vercel Edge Function Pattern

Vercel边缘函数示例

typescript
// api/feeds/aggregate.ts — Edge Function example
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { aggregateFeeds } from '../../src/feeds/aggregator';
import { getCachedData, setCachedData } from '../../src/cache/redis';

export const config = { runtime: 'edge' };

export default async function handler(req: VercelRequest, res: VercelResponse) {
  const { category, region, limit = '20' } = req.query as Record<string, string>;

  const cacheKey = `feeds:${category}:${region}:${limit}`;
  const cached = await getCachedData(cacheKey);
  if (cached) return res.json(cached);

  const items = await aggregateFeeds({
    categories: category ? [category] : undefined,
    region,
    limit: parseInt(limit),
  });

  await setCachedData(cacheKey, items, { ttl: 300 }); // 5 min TTL
  return res.json(items);
}

typescript
// api/feeds/aggregate.ts — 边缘函数示例
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { aggregateFeeds } from '../../src/feeds/aggregator';
import { getCachedData, setCachedData } from '../../src/cache/redis';

export const config = { runtime: 'edge' };

export default async function handler(req: VercelRequest, res: VercelResponse) {
  const { category, region, limit = '20' } = req.query as Record<string, string>;

  const cacheKey = `feeds:${category}:${region}:${limit}`;
  const cached = await getCachedData(cacheKey);
  if (cached) return res.json(cached);

  const items = await aggregateFeeds({
    categories: category ? [category] : undefined,
    region,
    limit: parseInt(limit),
  });

  await setCachedData(cacheKey, items, { ttl: 300 }); // 5分钟过期时间
  return res.json(items);
}

Desktop App (Tauri 2)

桌面应用(Tauri 2)

bash
undefined
bash
undefined

Install Tauri CLI

安装Tauri CLI

cargo install tauri-cli
cargo install tauri-cli

Development

开发模式

npm run tauri:dev
npm run tauri:dev

Build native app

构建原生应用

npm run tauri:build
npm run tauri:build

Outputs: .exe (Windows), .dmg/.app (macOS), .AppImage (Linux)

输出文件:.exe(Windows), .dmg/.app(macOS), .AppImage(Linux)


```rust
// src-tauri/src/main.rs — IPC command example
#[tauri::command]
async fn fetch_intelligence(country: String) -> Result<CountryData, String> {
    // Sidecar Node.js process handles feed aggregation
    // Tauri handles secure IPC between renderer and backend
    Ok(CountryData::default())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![fetch_intelligence])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}


```rust
// src-tauri/src/main.rs — IPC命令示例
#[tauri::command]
async fn fetch_intelligence(country: String) -> Result<CountryData, String> {
    // 辅助Node.js进程处理数据源聚合
    // Tauri负责渲染器与后端之间的安全IPC通信
    Ok(CountryData::default())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![fetch_intelligence])
        .run(tauri::generate_context!())
        .expect("运行tauri应用时出错");
}

Docker / Self-Hosting

Docker / 自托管

bash
undefined
bash
undefined

Docker single-container

Docker单容器部署

docker build -t worldmonitor . docker run -p 3000:3000
-e VITE_SITE_VARIANT=world
-e UPSTASH_REDIS_REST_URL=$UPSTASH_REDIS_REST_URL
-e UPSTASH_REDIS_REST_TOKEN=$UPSTASH_REDIS_REST_TOKEN
worldmonitor
docker build -t worldmonitor . docker run -p 3000:3000
-e VITE_SITE_VARIANT=world
-e UPSTASH_REDIS_REST_URL=$UPSTASH_REDIS_REST_URL
-e UPSTASH_REDIS_REST_TOKEN=$UPSTASH_REDIS_REST_TOKEN
worldmonitor

Docker Compose with Redis

Docker Compose搭配Redis

docker compose up -d

```yaml
docker compose up -d

```yaml

docker-compose.yml

docker-compose.yml

version: '3.9' services: app: build: . ports: ['3000:3000'] environment: - VITE_SITE_VARIANT=world - REDIS_URL=redis://redis:6379 depends_on: [redis] redis: image: redis:7-alpine volumes: ['redis_data:/data'] volumes: redis_data:
undefined
version: '3.9' services: app: build: . ports: ['3000:3000'] environment: - VITE_SITE_VARIANT=world - REDIS_URL=redis://redis:6379 depends_on: [redis] redis: image: redis:7-alpine volumes: ['redis_data:/data'] volumes: redis_data:
undefined

Vercel Deployment

Vercel部署

bash
npm i -g vercel
vercel --prod
bash
npm i -g vercel
vercel --prod

Set env vars in Vercel dashboard or via CLI:

在Vercel控制台或通过CLI设置环境变量:

vercel env add GROQ_API_KEY production vercel env add UPSTASH_REDIS_REST_URL production vercel env add UPSTASH_REDIS_REST_TOKEN production

---
vercel env add GROQ_API_KEY production vercel env add UPSTASH_REDIS_REST_URL production vercel env add UPSTASH_REDIS_REST_TOKEN production

---

Common Patterns

常见模式

Custom Feed Integration

自定义数据源集成

typescript
// Add a custom RSS feed to the aggregation pipeline
import { FeedRegistry } from './src/feeds/registry';

FeedRegistry.register({
  id: 'my-custom-feed',
  name: 'My Intelligence Source',
  url: 'https://example.com/feed.xml',
  category: 'geopolitics',
  region: 'Asia',
  language: 'en',
  weight: 0.8,           // 0-1, affects signal weighting
  refreshInterval: 300,  // seconds
  parser: 'rss2',        // 'rss2' | 'atom' | 'json'
});
typescript
// 向聚合流水线添加自定义RSS数据源
import { FeedRegistry } from './src/feeds/registry';

FeedRegistry.register({
  id: 'my-custom-feed',
  name: '我的情报源',
  url: 'https://example.com/feed.xml',
  category: 'geopolitics',
  region: 'Asia',
  language: 'en',
  weight: 0.8,           // 0-1,影响信号权重
  refreshInterval: 300,  // 秒
  parser: 'rss2',        // 'rss2' | 'atom' | 'json'
});

Custom Map Layer

自定义地图层

typescript
// Register a custom deck.gl layer in the 45-layer system
import { LayerRegistry } from './src/layers/registry';
import { IconLayer } from '@deck.gl/layers';

LayerRegistry.register({
  id: 'my-custom-layer',
  name: 'Custom Events',
  category: 'infrastructure',
  defaultVisible: false,
  factory: (data) => new IconLayer({
    id: 'my-custom-layer-deck',
    data,
    getPosition: d => [d.lng, d.lat],
    getIcon: d => 'marker',
    getSize: 32,
    pickable: true,
  }),
});
typescript
// 在45层系统中注册自定义deck.gl图层
import { LayerRegistry } from './src/layers/registry';
import { IconLayer } from '@deck.gl/layers';

LayerRegistry.register({
  id: 'my-custom-layer',
  name: '自定义事件',
  category: 'infrastructure',
  defaultVisible: false,
  factory: (data) => new IconLayer({
    id: 'my-custom-layer-deck',
    data,
    getPosition: d => [d.lng, d.lat],
    getIcon: d => 'marker',
    getSize: 32,
    pickable: true,
  }),
});

Site Variant Configuration

站点变体配置

typescript
// src/variants/my-variant.ts
import type { SiteVariant } from './types';

export const myVariant: SiteVariant = {
  id: 'my-variant',
  name: 'My Monitor',
  title: 'My Custom Monitor',
  defaultCategories: ['geopolitics', 'economics', 'military'],
  defaultRegion: 'Europe',
  defaultLanguage: 'en',
  mapStyle: 'dark',
  enabledLayers: ['country-risk', 'events', 'supply-chains'],
  aiProvider: 'ollama',
  theme: {
    primary: '#0891b2',
    background: '#0f172a',
    surface: '#1e293b',
  },
};

typescript
// src/variants/my-variant.ts
import type { SiteVariant } from './types';

export const myVariant: SiteVariant = {
  id: 'my-variant',
  name: '我的监控台',
  title: '我的自定义监控台',
  defaultCategories: ['geopolitics', 'economics', 'military'],
  defaultRegion: 'Europe',
  defaultLanguage: 'en',
  mapStyle: 'dark',
  enabledLayers: ['country-risk', 'events', 'supply-chains'],
  aiProvider: 'ollama',
  theme: {
    primary: '#0891b2',
    background: '#0f172a',
    surface: '#1e293b',
  },
};

Troubleshooting

故障排除

ProblemSolution
Map not renderingCheck
VITE_MAPTILER_API_KEY
or use free CartoBasemap style
AI synthesis slow/failingEnsure Ollama is running:
ollama serve && ollama pull llama3.2
Feeds returning 429 errorsEnable Redis caching via
UPSTASH_REDIS_REST_*
env vars
Desktop app won't buildEnsure Rust +
cargo install tauri-cli
+ platform build tools
RTL layout brokenConfirm
lang
attribute set on
<html>
by
setLanguage()
TypeScript errors on buildRun
npm run typecheck
— proto generated files must exist
Redis connection refusedCheck
REDIS_URL
or use Upstash REST API instead of TCP
npm run build:full
fails mid-variant
Build individually:
npm run build -- --mode finance
问题解决方案
地图无法渲染检查
VITE_MAPTILER_API_KEY
或使用免费CartoBasemap样式
AI合成缓慢/失败确保Ollama正在运行:
ollama serve && ollama pull llama3.2
数据源返回429错误通过
UPSTASH_REDIS_REST_*
环境变量启用Redis缓存
桌面应用无法构建确保已安装Rust +
cargo install tauri-cli
+ 平台构建工具
RTL布局异常确认
setLanguage()
已设置
<html>
标签的
lang
属性
构建时出现TypeScript错误运行
npm run typecheck
——必须存在proto生成的文件
Redis连接被拒绝检查
REDIS_URL
或使用Upstash REST API替代TCP连接
npm run build:full
在变体构建中途失败
单独构建:
npm run build -- --mode finance

Ollama Setup for Local AI

本地AI的Ollama设置

bash
undefined
bash
undefined

Install Ollama

安装Ollama

Pull recommended model

拉取推荐模型

ollama pull llama3.2 # Fast, good quality ollama pull mistral # Alternative ollama pull gemma2:9b # Larger, higher quality
ollama pull llama3.2 # 快速、高质量 ollama pull mistral # 替代模型 ollama pull gemma2:9b # 更大模型,更高质量

Verify Ollama is accessible

验证Ollama是否可访问

Verify Installation

验证安装

bash
npm run typecheck   # Should exit 0
npm run dev         # Should open localhost:5173
bash
npm run typecheck   # 应返回0
npm run dev         # 应打开localhost:5173

Navigate to /api/health for API status

访问/api/health查看API状态

Resources

资源