diff --git a/.gitignore b/.gitignore index 16b61c5..a9a4ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target/ /.idea/ *.iml +/node_modules/ +/data/platform-profiles/ diff --git a/CLIENT_README.txt b/CLIENT_README.txt new file mode 100644 index 0000000..95842fb --- /dev/null +++ b/CLIENT_README.txt @@ -0,0 +1,19 @@ +Yuedong short-video local worker + +Before first use, install: +1. Node.js LTS +2. Microsoft Edge or Google Chrome + +How to start: +1. Unzip this package to a normal folder, for example Desktop\agent-foundation-client. +2. Double-click start_yuedong_client.bat. +3. If a Douyin login page opens, login in the browser, then press Enter in the terminal window. +4. Keep the worker terminal window open while using the system. + +Server API: +http://agent.yuedongsports.com + +Local login data is stored only on this computer under: +data\platform-profiles\ + +Do not copy data\platform-profiles\ to another person. diff --git a/dist/agent-foundation-client-yuedong.zip b/dist/agent-foundation-client-yuedong.zip new file mode 100644 index 0000000..430b563 Binary files /dev/null and b/dist/agent-foundation-client-yuedong.zip differ diff --git a/dist/agent-foundation-client-yuedong/CLIENT_README.txt b/dist/agent-foundation-client-yuedong/CLIENT_README.txt new file mode 100644 index 0000000..95842fb --- /dev/null +++ b/dist/agent-foundation-client-yuedong/CLIENT_README.txt @@ -0,0 +1,19 @@ +Yuedong short-video local worker + +Before first use, install: +1. Node.js LTS +2. Microsoft Edge or Google Chrome + +How to start: +1. Unzip this package to a normal folder, for example Desktop\agent-foundation-client. +2. Double-click start_yuedong_client.bat. +3. If a Douyin login page opens, login in the browser, then press Enter in the terminal window. +4. Keep the worker terminal window open while using the system. + +Server API: +http://agent.yuedongsports.com + +Local login data is stored only on this computer under: +data\platform-profiles\ + +Do not copy data\platform-profiles\ to another person. diff --git a/dist/agent-foundation-client-yuedong/package-lock.json b/dist/agent-foundation-client-yuedong/package-lock.json new file mode 100644 index 0000000..08e0733 --- /dev/null +++ b/dist/agent-foundation-client-yuedong/package-lock.json @@ -0,0 +1,57 @@ +{ + "name": "agent-foundation-local-worker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-foundation-local-worker", + "dependencies": { + "playwright": "^1.61.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/dist/agent-foundation-client-yuedong/package.json b/dist/agent-foundation-client-yuedong/package.json new file mode 100644 index 0000000..97c513e --- /dev/null +++ b/dist/agent-foundation-client-yuedong/package.json @@ -0,0 +1,15 @@ +{ + "name": "agent-foundation-local-worker", + "private": true, + "type": "commonjs", + "scripts": { + "douyin:login": "node scripts/local_douyin_worker.js login", + "douyin:worker": "node scripts/local_douyin_worker.js worker", + "douyin:worker:loop": "node scripts/local_douyin_worker.js worker-loop", + "douyin:check": "node scripts/local_douyin_worker.js check-login", + "douyin:metrics": "node scripts/local_douyin_worker.js metrics" + }, + "dependencies": { + "playwright": "^1.61.0" + } +} diff --git a/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js b/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js new file mode 100644 index 0000000..9c8c3cf --- /dev/null +++ b/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js @@ -0,0 +1,507 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const DEFAULT_BASE_URL = process.env.MEDIA_BASE_URL || 'http://localhost:8880'; +const DEFAULT_WORKER_ID = process.env.MEDIA_WORKER_ID || 'local-douyin-worker'; +const DEFAULT_WORKER_TOKEN = process.env.MEDIA_WORKER_TOKEN || 'local-worker-dev-token'; +const DEFAULT_ACCOUNT_ID = process.env.DOUYIN_ACCOUNT_ID || 'douyin_company'; +const DEFAULT_CREATOR_URL = process.env.DOUYIN_CREATOR_URL || 'https://creator.douyin.com'; +const DEFAULT_CONTENT_URL = process.env.DOUYIN_CONTENT_URL || 'https://creator.douyin.com/creator-micro/content/manage'; +const DEFAULT_PROFILE_ROOT = process.env.DOUYIN_PROFILE_ROOT || path.resolve('data/platform-profiles'); + +const LOGIN_TEXT = new RegExp([ + '\\u626b\\u7801\\u767b\\u5f55', + '\\u9a8c\\u8bc1\\u7801', + '\\u624b\\u673a\\u53f7', + '\\u767b\\u5f55' +].join('|')); + +const CREATOR_TEXT = new RegExp([ + '\\u521b\\u4f5c\\u670d\\u52a1', + '\\u53d1\\u5e03\\u4f5c\\u54c1', + '\\u5185\\u5bb9\\u7ba1\\u7406', + '\\u6570\\u636e\\u4e2d\\u5fc3', + '\\u4f5c\\u54c1\\u7ba1\\u7406', + '\\u53d1\\u5e03\\u89c6\\u9891' +].join('|')); + +function requirePlaywright() { + try { + return require('playwright'); + } catch (error) { + throw new Error('Playwright is not installed. Run npm.cmd install in the project root first.'); + } +} + +function profileDir(accountId = DEFAULT_ACCOUNT_ID) { + return path.resolve(DEFAULT_PROFILE_ROOT, accountId); +} + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function browserChannel() { + if (process.env.DOUYIN_BROWSER_CHANNEL) { + return process.env.DOUYIN_BROWSER_CHANNEL; + } + return process.platform === 'win32' ? 'msedge' : undefined; +} + +async function launchDouyinContext({ accountId = DEFAULT_ACCOUNT_ID, headless = false } = {}) { + const { chromium } = requirePlaywright(); + const dir = profileDir(accountId); + ensureDir(dir); + const channel = browserChannel(); + const options = { + headless, + viewport: { width: 1440, height: 1000 }, + acceptDownloads: true, + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage' + ] + }; + if (channel) { + options.channel = channel; + } + return chromium.launchPersistentContext(dir, options); +} + +async function waitForEnter(message) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise(resolve => { + rl.question(message, () => { + rl.close(); + resolve(); + }); + }); +} + +async function login() { + const accountId = process.argv[3] || DEFAULT_ACCOUNT_ID; + const context = await launchDouyinContext({ accountId, headless: false }); + const page = context.pages()[0] || await context.newPage(); + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + console.log(`Douyin login profile: ${profileDir(accountId)}`); + console.log('Please scan/login in the opened browser. Keep this window open until login succeeds.'); + await waitForEnter('Press Enter here after Douyin creator center is logged in...'); + const status = await detectLogin(page, accountId); + await context.close(); + console.log(JSON.stringify(status, null, 2)); +} + +async function checkLogin(accountId = DEFAULT_ACCOUNT_ID) { + const context = await launchDouyinContext({ accountId, headless: true }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(2500); + return await detectLogin(page, accountId); + } finally { + await context.close(); + } +} + +async function detectLogin(page, accountId = DEFAULT_ACCOUNT_ID) { + const url = page.url(); + const title = await page.title().catch(() => ''); + const bodyText = await page.locator('body').innerText({ timeout: 5000 }).catch(() => ''); + const loginVisible = LOGIN_TEXT.test(bodyText); + const creatorVisible = CREATOR_TEXT.test(bodyText); + const loggedIn = creatorVisible || (!loginVisible && /creator\.douyin\.com/.test(url)); + return { + loggedIn, + status: loggedIn ? 'SUCCESS' : 'FAILED', + message: loggedIn ? 'Douyin creator profile is logged in.' : 'Douyin creator profile is not logged in.', + url, + title, + profileDir: profileDir(accountId) + }; +} + +async function collectMetrics(task) { + const payload = task.payload || {}; + const accountId = task.accountId || DEFAULT_ACCOUNT_ID; + const targetUrl = payload.metricsUrl || payload.contentUrl || DEFAULT_CONTENT_URL; + const headless = payload.headless === false ? false : true; + const context = await launchDouyinContext({ accountId, headless }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(Number(payload.waitMs || 5000)); + const status = await detectLogin(page, accountId); + if (!status.loggedIn) { + return { + status: 'FAILED', + errorMessage: status.message, + result: { + action: 'sync_metrics', + loggedIn: false, + ...status + } + }; + } + + const text = await page.locator('body').innerText({ timeout: 15000 }).catch(() => ''); + const screenshot = await saveScreenshot(page, task.id); + const videos = await collectVideoMetrics(page, Number(payload.maxVideos || 20)); + const metrics = videos.length > 0 ? summarizeVideos(videos) : extractMetrics(text); + return { + status: 'SUCCESS', + errorMessage: null, + result: { + action: 'sync_metrics', + loggedIn: true, + url: page.url(), + title: await page.title().catch(() => ''), + metrics, + videos, + videoCount: videos.length, + extractionMode: videos.length > 0 ? 'video_list' : 'page_summary', + screenshot, + pageTextPreview: compactText(text).slice(0, 2000) + } + }; + } finally { + await context.close().catch(() => {}); + } +} + +async function saveScreenshot(page, taskId) { + const dir = path.resolve('target/media/douyin'); + ensureDir(dir); + const file = path.join(dir, `${taskId || Date.now()}-metrics.png`); + await page.screenshot({ path: file, fullPage: true }).catch(() => {}); + return file; +} + +function compactText(text) { + return (text || '').replace(/\s+/g, ' ').trim(); +} + +function extractMetrics(text) { + const compact = compactText(text); + return { + views: findMetric(compact, ['\\u64ad\\u653e', '\\u6d4f\\u89c8', '\\u89c2\\u770b', '\\u5c55\\u73b0']), + likes: findMetric(compact, ['\\u70b9\\u8d5e']), + comments: findMetric(compact, ['\\u8bc4\\u8bba']), + collects: findMetric(compact, ['\\u6536\\u85cf']), + shares: findMetric(compact, ['\\u5206\\u4eab', '\\u8f6c\\u53d1']) + }; +} + +async function collectVideoMetrics(page, limit = 20) { + await autoScroll(page); + const blocks = await page.evaluate(maxBlocks => { + const selectors = [ + 'tr', + 'article', + 'li', + '[class*="card"]', + '[class*="item"]', + '[class*="work"]', + '[class*="video"]', + '[class*="content"]' + ]; + const seen = new Set(); + const rows = []; + for (const selector of selectors) { + for (const element of document.querySelectorAll(selector)) { + if (rows.length >= maxBlocks) { + break; + } + const rect = element.getBoundingClientRect(); + if (rect.width < 160 || rect.height < 40) { + continue; + } + const text = (element.innerText || '').replace(/\s+/g, ' ').trim(); + if (text.length < 12 || text.length > 1200) { + continue; + } + const signature = text.slice(0, 120); + if (seen.has(signature)) { + continue; + } + seen.add(signature); + const link = element.querySelector('a[href]'); + rows.push({ + text, + href: link ? link.href : '', + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + } + }); + } + } + return rows; + }, Math.max(limit * 8, 80)); + + const videos = []; + const seen = new Set(); + for (const block of blocks) { + const video = parseVideoBlock(block); + if (!video) { + continue; + } + const key = `${video.title}|${video.views}|${video.likes}|${video.comments}|${video.href}`; + if (seen.has(key)) { + continue; + } + seen.add(key); + videos.push(video); + if (videos.length >= limit) { + break; + } + } + return videos; +} + +async function autoScroll(page) { + await page.evaluate(async () => { + await new Promise(resolve => { + let count = 0; + const timer = setInterval(() => { + window.scrollBy(0, Math.floor(window.innerHeight * 0.8)); + count += 1; + if (count >= 3) { + clearInterval(timer); + resolve(); + } + }, 500); + }); + }).catch(() => {}); + await page.waitForTimeout(800).catch(() => {}); +} + +function parseVideoBlock(block) { + const text = compactText(block.text); + const metrics = extractMetrics(text); + const score = metrics.views + metrics.likes + metrics.comments + metrics.collects + metrics.shares; + if (score <= 0) { + return null; + } + const title = extractVideoTitle(text); + return { + title, + href: block.href || '', + views: metrics.views, + likes: metrics.likes, + comments: metrics.comments, + collects: metrics.collects, + shares: metrics.shares, + interactionCount: metrics.likes + metrics.comments + metrics.collects + metrics.shares, + interactionRate: metrics.views <= 0 + ? 0 + : (metrics.likes + metrics.comments + metrics.collects + metrics.shares) / metrics.views, + rawTextPreview: text.slice(0, 500) + }; +} + +function extractVideoTitle(text) { + const cleaned = text + .replace(/\d{4}[-/.]\d{1,2}[-/.]\d{1,2}.*/g, '') + .replace(/(\u64ad\u653e|\u6d4f\u89c8|\u89c2\u770b|\u5c55\u73b0|\u70b9\u8d5e|\u8bc4\u8bba|\u6536\u85cf|\u5206\u4eab|\u8f6c\u53d1).*/g, '') + .trim(); + if (cleaned.length >= 2) { + return cleaned.slice(0, 120); + } + return text.slice(0, 80); +} + +function summarizeVideos(videos) { + const summary = { + views: 0, + likes: 0, + comments: 0, + collects: 0, + shares: 0 + }; + for (const video of videos) { + summary.views += Number(video.views || 0); + summary.likes += Number(video.likes || 0); + summary.comments += Number(video.comments || 0); + summary.collects += Number(video.collects || 0); + summary.shares += Number(video.shares || 0); + } + return summary; +} + +function findMetric(text, labels) { + for (const label of labels) { + const labelPattern = new RegExp(label); + const decodedLabel = label.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16))); + const after = new RegExp(`${label}[^0-9\\d]{0,16}([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)`); + const before = new RegExp(`([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)[^0-9\\d]{0,16}${label}`); + if (labelPattern.test(text)) { + const afterMatch = text.match(after); + if (afterMatch) { + return parseMetricNumber(afterMatch[1], afterMatch[2]); + } + const beforeMatch = text.match(before); + if (beforeMatch) { + return parseMetricNumber(beforeMatch[1], beforeMatch[2]); + } + const looseIndex = text.indexOf(decodedLabel); + if (looseIndex >= 0) { + const around = text.slice(Math.max(0, looseIndex - 30), looseIndex + 60); + const number = around.match(/([0-9][0-9,.]*)(\s*[\u4e07\u5343wWkK]?)/); + if (number) { + return parseMetricNumber(number[1], number[2]); + } + } + } + } + return 0; +} + +function parseMetricNumber(raw, unit = '') { + const value = Number(String(raw).replace(/,/g, '')); + if (!Number.isFinite(value)) { + return 0; + } + const normalizedUnit = unit.trim().toLowerCase(); + if (normalizedUnit.includes('\u4e07') || normalizedUnit.includes('w')) { + return Math.round(value * 10000); + } + if (normalizedUnit.includes('\u5343') || normalizedUnit.includes('k')) { + return Math.round(value * 1000); + } + return Math.round(value); +} + +async function pollTasks() { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/poll`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify({ + workerId: DEFAULT_WORKER_ID, + platforms: ['douyin'], + maxTasks: 5 + }) + }); + if (!response.ok) { + throw new Error(`Poll failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function submitResult(taskId, body) { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/tasks/${taskId}/result`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Submit result failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function handleTask(task) { + const payload = task.payload || {}; + const action = payload.action || (task.taskType === 'SYNC_METRICS' ? 'sync_metrics' : 'check_login'); + if (action === 'check_login') { + const status = await checkLogin(task.accountId || DEFAULT_ACCOUNT_ID); + return submitResult(task.id, { + status: status.loggedIn ? 'SUCCESS' : 'FAILED', + errorMessage: status.loggedIn ? null : status.message, + result: { + action, + loggedIn: status.loggedIn, + message: status.message, + url: status.url, + title: status.title, + profileDir: profileDir(task.accountId || DEFAULT_ACCOUNT_ID) + } + }); + } + + if (action === 'sync_metrics') { + const result = await collectMetrics(task); + return submitResult(task.id, result); + } + + return submitResult(task.id, { + status: 'FAILED', + errorMessage: `Unsupported local Douyin worker action: ${action}`, + result: { action } + }); +} + +async function workerOnce() { + const tasks = await pollTasks(); + if (!Array.isArray(tasks) || tasks.length === 0) { + console.log('No pending Douyin tasks.'); + return; + } + for (const task of tasks) { + const result = await handleTask(task); + console.log(`Completed ${task.id}: ${result.status}`); + } +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function workerLoop() { + const intervalMs = Number(process.env.MEDIA_WORKER_INTERVAL_MS || 30000); + console.log(`Douyin worker loop started. baseUrl=${DEFAULT_BASE_URL}, intervalMs=${intervalMs}`); + while (true) { + try { + await workerOnce(); + } catch (error) { + console.error(error.stack || error.message); + } + await sleep(intervalMs); + } +} + +async function main() { + const mode = process.argv[2]; + if (mode === 'login') { + await login(); + return; + } + if (mode === 'check-login') { + const status = await checkLogin(process.argv[3] || DEFAULT_ACCOUNT_ID); + console.log(JSON.stringify(status, null, 2)); + process.exit(status.loggedIn ? 0 : 2); + } + if (mode === 'metrics') { + const task = { + id: `manual_${Date.now()}`, + taskType: 'SYNC_METRICS', + platform: 'douyin', + accountId: process.argv[3] || DEFAULT_ACCOUNT_ID, + payload: { action: 'sync_metrics', headless: false } + }; + console.log(JSON.stringify((await collectMetrics(task)).result, null, 2)); + return; + } + if (mode === 'worker') { + await workerOnce(); + return; + } + if (mode === 'worker-loop') { + await workerLoop(); + return; + } + throw new Error('Usage: node scripts/local_douyin_worker.js [accountId]'); +} + +main().catch(error => { + console.error(error.stack || error.message); + process.exit(1); +}); diff --git a/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 b/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 new file mode 100644 index 0000000..d20221f --- /dev/null +++ b/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 @@ -0,0 +1,139 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$AccountId = "douyin_company", + [int]$WorkerIntervalSeconds = 30, + [switch]$RemoteBackend +) + +$ErrorActionPreference = "Stop" +$Root = Resolve-Path (Join-Path $PSScriptRoot "..") +Set-Location $Root + +function Write-Step([string]$Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Require-Command([string]$Name, [string]$InstallHint) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "$Name is not installed or not in PATH. $InstallHint" + } +} + +function Test-BackendHealth([string]$Url) { + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri "$Url/actuator/health" -TimeoutSec 3 + return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 + } catch { + return $false + } +} + +function Find-BackendJar { + $jars = Get-ChildItem -Path (Join-Path $Root "target") -Filter "agent-foundation-*.jar" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notlike "*.original" } | + Sort-Object LastWriteTime -Descending + if ($jars.Count -eq 0) { + return $null + } + return $jars[0].FullName +} + +function Test-BrowserInstalled { + $paths = @( + "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe", + "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" + ) + foreach ($path in $paths) { + if ($path -and (Test-Path $path)) { + return $true + } + } + return $false +} + +function Test-LocalBaseUrl([string]$Url) { + return $Url -match "^https?://(localhost|127\.0\.0\.1)(:\d+)?/?$" +} + +Write-Step "Checking local runtime" +Require-Command "node" "Install Node.js LTS from https://nodejs.org/" +Require-Command "npm.cmd" "Install Node.js LTS from https://nodejs.org/" +if (-not $RemoteBackend) { + Require-Command "java" "Install JDK 17+ or JRE 17+." +} +if (-not (Test-BrowserInstalled)) { + throw "Microsoft Edge or Google Chrome is required." +} + +Write-Step "Installing Node dependencies if needed" +if (-not (Test-Path (Join-Path $Root "node_modules\playwright"))) { + & npm.cmd install + if ($LASTEXITCODE -ne 0) { + throw "npm install failed." + } +} else { + Write-Host "Node dependencies already installed." +} + +if ($RemoteBackend -or -not (Test-LocalBaseUrl $BaseUrl)) { + Write-Step "Checking remote backend" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Remote backend is reachable: $BaseUrl" + } else { + Write-Host "Remote backend health check did not return OK. Continuing because /actuator/health may be disabled." + Write-Host "Worker will connect to: $BaseUrl" + } +} else { + Write-Step "Starting backend service if needed" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Backend is already running: $BaseUrl" + } else { + $jar = Find-BackendJar + if (-not $jar) { + throw "Backend jar not found under target\. Build the Spring Boot jar first." + } + $backendCommand = "Set-Location '$Root'; java -jar '$jar'" + Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) -WindowStyle Normal + Write-Host "Waiting for backend health..." + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Seconds 2 + if (Test-BackendHealth $BaseUrl) { + $ready = $true + break + } + } + if (-not $ready) { + throw "Backend did not become healthy at $BaseUrl. Check the backend window." + } + } +} + +Write-Step "Checking Douyin login" +$env:MEDIA_BASE_URL = $BaseUrl +$env:DOUYIN_ACCOUNT_ID = $AccountId +$checkOutput = & npm.cmd run douyin:check -- $AccountId 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host $checkOutput + Write-Host "" + Write-Host "Douyin login is required. A browser will open. Login, then press Enter in that window." + & npm.cmd run douyin:login -- $AccountId + if ($LASTEXITCODE -ne 0) { + throw "Douyin login failed." + } +} else { + Write-Host "Douyin login profile is ready." +} + +Write-Step "Starting Douyin worker loop" +$workerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run douyin:worker:loop" +Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $workerCommand) -WindowStyle Normal + +Write-Host "" +Write-Host "Local client is ready." +Write-Host "Backend: $BaseUrl" +Write-Host "Account: $AccountId" +Write-Host "Worker polling interval: $WorkerIntervalSeconds seconds" diff --git a/dist/agent-foundation-client-yuedong/start_yuedong_client.bat b/dist/agent-foundation-client-yuedong/start_yuedong_client.bat new file mode 100644 index 0000000..df335ef --- /dev/null +++ b/dist/agent-foundation-client-yuedong/start_yuedong_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" -BaseUrl "http://agent.yuedongsports.com" -RemoteBackend +pause diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..08e0733 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,57 @@ +{ + "name": "agent-foundation-local-worker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-foundation-local-worker", + "dependencies": { + "playwright": "^1.61.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..97c513e --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "agent-foundation-local-worker", + "private": true, + "type": "commonjs", + "scripts": { + "douyin:login": "node scripts/local_douyin_worker.js login", + "douyin:worker": "node scripts/local_douyin_worker.js worker", + "douyin:worker:loop": "node scripts/local_douyin_worker.js worker-loop", + "douyin:check": "node scripts/local_douyin_worker.js check-login", + "douyin:metrics": "node scripts/local_douyin_worker.js metrics" + }, + "dependencies": { + "playwright": "^1.61.0" + } +} diff --git a/scripts/local_douyin_worker.js b/scripts/local_douyin_worker.js new file mode 100644 index 0000000..9c8c3cf --- /dev/null +++ b/scripts/local_douyin_worker.js @@ -0,0 +1,507 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const DEFAULT_BASE_URL = process.env.MEDIA_BASE_URL || 'http://localhost:8880'; +const DEFAULT_WORKER_ID = process.env.MEDIA_WORKER_ID || 'local-douyin-worker'; +const DEFAULT_WORKER_TOKEN = process.env.MEDIA_WORKER_TOKEN || 'local-worker-dev-token'; +const DEFAULT_ACCOUNT_ID = process.env.DOUYIN_ACCOUNT_ID || 'douyin_company'; +const DEFAULT_CREATOR_URL = process.env.DOUYIN_CREATOR_URL || 'https://creator.douyin.com'; +const DEFAULT_CONTENT_URL = process.env.DOUYIN_CONTENT_URL || 'https://creator.douyin.com/creator-micro/content/manage'; +const DEFAULT_PROFILE_ROOT = process.env.DOUYIN_PROFILE_ROOT || path.resolve('data/platform-profiles'); + +const LOGIN_TEXT = new RegExp([ + '\\u626b\\u7801\\u767b\\u5f55', + '\\u9a8c\\u8bc1\\u7801', + '\\u624b\\u673a\\u53f7', + '\\u767b\\u5f55' +].join('|')); + +const CREATOR_TEXT = new RegExp([ + '\\u521b\\u4f5c\\u670d\\u52a1', + '\\u53d1\\u5e03\\u4f5c\\u54c1', + '\\u5185\\u5bb9\\u7ba1\\u7406', + '\\u6570\\u636e\\u4e2d\\u5fc3', + '\\u4f5c\\u54c1\\u7ba1\\u7406', + '\\u53d1\\u5e03\\u89c6\\u9891' +].join('|')); + +function requirePlaywright() { + try { + return require('playwright'); + } catch (error) { + throw new Error('Playwright is not installed. Run npm.cmd install in the project root first.'); + } +} + +function profileDir(accountId = DEFAULT_ACCOUNT_ID) { + return path.resolve(DEFAULT_PROFILE_ROOT, accountId); +} + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function browserChannel() { + if (process.env.DOUYIN_BROWSER_CHANNEL) { + return process.env.DOUYIN_BROWSER_CHANNEL; + } + return process.platform === 'win32' ? 'msedge' : undefined; +} + +async function launchDouyinContext({ accountId = DEFAULT_ACCOUNT_ID, headless = false } = {}) { + const { chromium } = requirePlaywright(); + const dir = profileDir(accountId); + ensureDir(dir); + const channel = browserChannel(); + const options = { + headless, + viewport: { width: 1440, height: 1000 }, + acceptDownloads: true, + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage' + ] + }; + if (channel) { + options.channel = channel; + } + return chromium.launchPersistentContext(dir, options); +} + +async function waitForEnter(message) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise(resolve => { + rl.question(message, () => { + rl.close(); + resolve(); + }); + }); +} + +async function login() { + const accountId = process.argv[3] || DEFAULT_ACCOUNT_ID; + const context = await launchDouyinContext({ accountId, headless: false }); + const page = context.pages()[0] || await context.newPage(); + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + console.log(`Douyin login profile: ${profileDir(accountId)}`); + console.log('Please scan/login in the opened browser. Keep this window open until login succeeds.'); + await waitForEnter('Press Enter here after Douyin creator center is logged in...'); + const status = await detectLogin(page, accountId); + await context.close(); + console.log(JSON.stringify(status, null, 2)); +} + +async function checkLogin(accountId = DEFAULT_ACCOUNT_ID) { + const context = await launchDouyinContext({ accountId, headless: true }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(2500); + return await detectLogin(page, accountId); + } finally { + await context.close(); + } +} + +async function detectLogin(page, accountId = DEFAULT_ACCOUNT_ID) { + const url = page.url(); + const title = await page.title().catch(() => ''); + const bodyText = await page.locator('body').innerText({ timeout: 5000 }).catch(() => ''); + const loginVisible = LOGIN_TEXT.test(bodyText); + const creatorVisible = CREATOR_TEXT.test(bodyText); + const loggedIn = creatorVisible || (!loginVisible && /creator\.douyin\.com/.test(url)); + return { + loggedIn, + status: loggedIn ? 'SUCCESS' : 'FAILED', + message: loggedIn ? 'Douyin creator profile is logged in.' : 'Douyin creator profile is not logged in.', + url, + title, + profileDir: profileDir(accountId) + }; +} + +async function collectMetrics(task) { + const payload = task.payload || {}; + const accountId = task.accountId || DEFAULT_ACCOUNT_ID; + const targetUrl = payload.metricsUrl || payload.contentUrl || DEFAULT_CONTENT_URL; + const headless = payload.headless === false ? false : true; + const context = await launchDouyinContext({ accountId, headless }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(Number(payload.waitMs || 5000)); + const status = await detectLogin(page, accountId); + if (!status.loggedIn) { + return { + status: 'FAILED', + errorMessage: status.message, + result: { + action: 'sync_metrics', + loggedIn: false, + ...status + } + }; + } + + const text = await page.locator('body').innerText({ timeout: 15000 }).catch(() => ''); + const screenshot = await saveScreenshot(page, task.id); + const videos = await collectVideoMetrics(page, Number(payload.maxVideos || 20)); + const metrics = videos.length > 0 ? summarizeVideos(videos) : extractMetrics(text); + return { + status: 'SUCCESS', + errorMessage: null, + result: { + action: 'sync_metrics', + loggedIn: true, + url: page.url(), + title: await page.title().catch(() => ''), + metrics, + videos, + videoCount: videos.length, + extractionMode: videos.length > 0 ? 'video_list' : 'page_summary', + screenshot, + pageTextPreview: compactText(text).slice(0, 2000) + } + }; + } finally { + await context.close().catch(() => {}); + } +} + +async function saveScreenshot(page, taskId) { + const dir = path.resolve('target/media/douyin'); + ensureDir(dir); + const file = path.join(dir, `${taskId || Date.now()}-metrics.png`); + await page.screenshot({ path: file, fullPage: true }).catch(() => {}); + return file; +} + +function compactText(text) { + return (text || '').replace(/\s+/g, ' ').trim(); +} + +function extractMetrics(text) { + const compact = compactText(text); + return { + views: findMetric(compact, ['\\u64ad\\u653e', '\\u6d4f\\u89c8', '\\u89c2\\u770b', '\\u5c55\\u73b0']), + likes: findMetric(compact, ['\\u70b9\\u8d5e']), + comments: findMetric(compact, ['\\u8bc4\\u8bba']), + collects: findMetric(compact, ['\\u6536\\u85cf']), + shares: findMetric(compact, ['\\u5206\\u4eab', '\\u8f6c\\u53d1']) + }; +} + +async function collectVideoMetrics(page, limit = 20) { + await autoScroll(page); + const blocks = await page.evaluate(maxBlocks => { + const selectors = [ + 'tr', + 'article', + 'li', + '[class*="card"]', + '[class*="item"]', + '[class*="work"]', + '[class*="video"]', + '[class*="content"]' + ]; + const seen = new Set(); + const rows = []; + for (const selector of selectors) { + for (const element of document.querySelectorAll(selector)) { + if (rows.length >= maxBlocks) { + break; + } + const rect = element.getBoundingClientRect(); + if (rect.width < 160 || rect.height < 40) { + continue; + } + const text = (element.innerText || '').replace(/\s+/g, ' ').trim(); + if (text.length < 12 || text.length > 1200) { + continue; + } + const signature = text.slice(0, 120); + if (seen.has(signature)) { + continue; + } + seen.add(signature); + const link = element.querySelector('a[href]'); + rows.push({ + text, + href: link ? link.href : '', + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + } + }); + } + } + return rows; + }, Math.max(limit * 8, 80)); + + const videos = []; + const seen = new Set(); + for (const block of blocks) { + const video = parseVideoBlock(block); + if (!video) { + continue; + } + const key = `${video.title}|${video.views}|${video.likes}|${video.comments}|${video.href}`; + if (seen.has(key)) { + continue; + } + seen.add(key); + videos.push(video); + if (videos.length >= limit) { + break; + } + } + return videos; +} + +async function autoScroll(page) { + await page.evaluate(async () => { + await new Promise(resolve => { + let count = 0; + const timer = setInterval(() => { + window.scrollBy(0, Math.floor(window.innerHeight * 0.8)); + count += 1; + if (count >= 3) { + clearInterval(timer); + resolve(); + } + }, 500); + }); + }).catch(() => {}); + await page.waitForTimeout(800).catch(() => {}); +} + +function parseVideoBlock(block) { + const text = compactText(block.text); + const metrics = extractMetrics(text); + const score = metrics.views + metrics.likes + metrics.comments + metrics.collects + metrics.shares; + if (score <= 0) { + return null; + } + const title = extractVideoTitle(text); + return { + title, + href: block.href || '', + views: metrics.views, + likes: metrics.likes, + comments: metrics.comments, + collects: metrics.collects, + shares: metrics.shares, + interactionCount: metrics.likes + metrics.comments + metrics.collects + metrics.shares, + interactionRate: metrics.views <= 0 + ? 0 + : (metrics.likes + metrics.comments + metrics.collects + metrics.shares) / metrics.views, + rawTextPreview: text.slice(0, 500) + }; +} + +function extractVideoTitle(text) { + const cleaned = text + .replace(/\d{4}[-/.]\d{1,2}[-/.]\d{1,2}.*/g, '') + .replace(/(\u64ad\u653e|\u6d4f\u89c8|\u89c2\u770b|\u5c55\u73b0|\u70b9\u8d5e|\u8bc4\u8bba|\u6536\u85cf|\u5206\u4eab|\u8f6c\u53d1).*/g, '') + .trim(); + if (cleaned.length >= 2) { + return cleaned.slice(0, 120); + } + return text.slice(0, 80); +} + +function summarizeVideos(videos) { + const summary = { + views: 0, + likes: 0, + comments: 0, + collects: 0, + shares: 0 + }; + for (const video of videos) { + summary.views += Number(video.views || 0); + summary.likes += Number(video.likes || 0); + summary.comments += Number(video.comments || 0); + summary.collects += Number(video.collects || 0); + summary.shares += Number(video.shares || 0); + } + return summary; +} + +function findMetric(text, labels) { + for (const label of labels) { + const labelPattern = new RegExp(label); + const decodedLabel = label.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16))); + const after = new RegExp(`${label}[^0-9\\d]{0,16}([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)`); + const before = new RegExp(`([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)[^0-9\\d]{0,16}${label}`); + if (labelPattern.test(text)) { + const afterMatch = text.match(after); + if (afterMatch) { + return parseMetricNumber(afterMatch[1], afterMatch[2]); + } + const beforeMatch = text.match(before); + if (beforeMatch) { + return parseMetricNumber(beforeMatch[1], beforeMatch[2]); + } + const looseIndex = text.indexOf(decodedLabel); + if (looseIndex >= 0) { + const around = text.slice(Math.max(0, looseIndex - 30), looseIndex + 60); + const number = around.match(/([0-9][0-9,.]*)(\s*[\u4e07\u5343wWkK]?)/); + if (number) { + return parseMetricNumber(number[1], number[2]); + } + } + } + } + return 0; +} + +function parseMetricNumber(raw, unit = '') { + const value = Number(String(raw).replace(/,/g, '')); + if (!Number.isFinite(value)) { + return 0; + } + const normalizedUnit = unit.trim().toLowerCase(); + if (normalizedUnit.includes('\u4e07') || normalizedUnit.includes('w')) { + return Math.round(value * 10000); + } + if (normalizedUnit.includes('\u5343') || normalizedUnit.includes('k')) { + return Math.round(value * 1000); + } + return Math.round(value); +} + +async function pollTasks() { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/poll`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify({ + workerId: DEFAULT_WORKER_ID, + platforms: ['douyin'], + maxTasks: 5 + }) + }); + if (!response.ok) { + throw new Error(`Poll failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function submitResult(taskId, body) { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/tasks/${taskId}/result`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Submit result failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function handleTask(task) { + const payload = task.payload || {}; + const action = payload.action || (task.taskType === 'SYNC_METRICS' ? 'sync_metrics' : 'check_login'); + if (action === 'check_login') { + const status = await checkLogin(task.accountId || DEFAULT_ACCOUNT_ID); + return submitResult(task.id, { + status: status.loggedIn ? 'SUCCESS' : 'FAILED', + errorMessage: status.loggedIn ? null : status.message, + result: { + action, + loggedIn: status.loggedIn, + message: status.message, + url: status.url, + title: status.title, + profileDir: profileDir(task.accountId || DEFAULT_ACCOUNT_ID) + } + }); + } + + if (action === 'sync_metrics') { + const result = await collectMetrics(task); + return submitResult(task.id, result); + } + + return submitResult(task.id, { + status: 'FAILED', + errorMessage: `Unsupported local Douyin worker action: ${action}`, + result: { action } + }); +} + +async function workerOnce() { + const tasks = await pollTasks(); + if (!Array.isArray(tasks) || tasks.length === 0) { + console.log('No pending Douyin tasks.'); + return; + } + for (const task of tasks) { + const result = await handleTask(task); + console.log(`Completed ${task.id}: ${result.status}`); + } +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function workerLoop() { + const intervalMs = Number(process.env.MEDIA_WORKER_INTERVAL_MS || 30000); + console.log(`Douyin worker loop started. baseUrl=${DEFAULT_BASE_URL}, intervalMs=${intervalMs}`); + while (true) { + try { + await workerOnce(); + } catch (error) { + console.error(error.stack || error.message); + } + await sleep(intervalMs); + } +} + +async function main() { + const mode = process.argv[2]; + if (mode === 'login') { + await login(); + return; + } + if (mode === 'check-login') { + const status = await checkLogin(process.argv[3] || DEFAULT_ACCOUNT_ID); + console.log(JSON.stringify(status, null, 2)); + process.exit(status.loggedIn ? 0 : 2); + } + if (mode === 'metrics') { + const task = { + id: `manual_${Date.now()}`, + taskType: 'SYNC_METRICS', + platform: 'douyin', + accountId: process.argv[3] || DEFAULT_ACCOUNT_ID, + payload: { action: 'sync_metrics', headless: false } + }; + console.log(JSON.stringify((await collectMetrics(task)).result, null, 2)); + return; + } + if (mode === 'worker') { + await workerOnce(); + return; + } + if (mode === 'worker-loop') { + await workerLoop(); + return; + } + throw new Error('Usage: node scripts/local_douyin_worker.js [accountId]'); +} + +main().catch(error => { + console.error(error.stack || error.message); + process.exit(1); +}); diff --git a/scripts/local_media_worker.ps1 b/scripts/local_media_worker.ps1 new file mode 100644 index 0000000..35df048 --- /dev/null +++ b/scripts/local_media_worker.ps1 @@ -0,0 +1,71 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$WorkerId = "local-worker-01", + [string]$WorkerToken = "local-worker-dev-token" +) + +$headers = @{ + "X-Worker-Token" = $WorkerToken +} + +$pollBody = @{ + workerId = $WorkerId + platforms = @("douyin", "xhs", "wxvideo", "kuaishou", "bilibili", "weibo") + maxTasks = 5 +} | ConvertTo-Json + +$tasks = Invoke-RestMethod ` + -Uri "$BaseUrl/v1/media/workers/poll" ` + -Method Post ` + -Headers $headers ` + -ContentType "application/json" ` + -Body $pollBody + +foreach ($task in $tasks) { + if ($task.taskType -eq "SYNC_COMMENTS") { + $result = @{ + status = "SUCCESS" + result = @{ + comments = @( + @{ + platform = $task.platform + accountId = $task.accountId + userName = "测试用户" + content = "这个设备多少钱?" + likedCount = 12 + intent = "sales_lead" + } + ) + } + } + } elseif ($task.taskType -eq "SYNC_METRICS") { + $result = @{ + status = "SUCCESS" + result = @{ + metrics = @{ + views = 12000 + likes = 860 + comments = 56 + collects = 128 + shares = 43 + } + } + } + } else { + $result = @{ + status = "SUCCESS" + result = @{ + message = "local worker mock completed" + } + } + } + + Invoke-RestMethod ` + -Uri "$BaseUrl/v1/media/workers/tasks/$($task.id)/result" ` + -Method Post ` + -Headers $headers ` + -ContentType "application/json" ` + -Body ($result | ConvertTo-Json -Depth 10) | Out-Null + + Write-Host "completed $($task.id) $($task.taskType) $($task.platform)" +} diff --git a/scripts/start_local_client.ps1 b/scripts/start_local_client.ps1 new file mode 100644 index 0000000..d20221f --- /dev/null +++ b/scripts/start_local_client.ps1 @@ -0,0 +1,139 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$AccountId = "douyin_company", + [int]$WorkerIntervalSeconds = 30, + [switch]$RemoteBackend +) + +$ErrorActionPreference = "Stop" +$Root = Resolve-Path (Join-Path $PSScriptRoot "..") +Set-Location $Root + +function Write-Step([string]$Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Require-Command([string]$Name, [string]$InstallHint) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "$Name is not installed or not in PATH. $InstallHint" + } +} + +function Test-BackendHealth([string]$Url) { + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri "$Url/actuator/health" -TimeoutSec 3 + return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 + } catch { + return $false + } +} + +function Find-BackendJar { + $jars = Get-ChildItem -Path (Join-Path $Root "target") -Filter "agent-foundation-*.jar" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notlike "*.original" } | + Sort-Object LastWriteTime -Descending + if ($jars.Count -eq 0) { + return $null + } + return $jars[0].FullName +} + +function Test-BrowserInstalled { + $paths = @( + "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe", + "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" + ) + foreach ($path in $paths) { + if ($path -and (Test-Path $path)) { + return $true + } + } + return $false +} + +function Test-LocalBaseUrl([string]$Url) { + return $Url -match "^https?://(localhost|127\.0\.0\.1)(:\d+)?/?$" +} + +Write-Step "Checking local runtime" +Require-Command "node" "Install Node.js LTS from https://nodejs.org/" +Require-Command "npm.cmd" "Install Node.js LTS from https://nodejs.org/" +if (-not $RemoteBackend) { + Require-Command "java" "Install JDK 17+ or JRE 17+." +} +if (-not (Test-BrowserInstalled)) { + throw "Microsoft Edge or Google Chrome is required." +} + +Write-Step "Installing Node dependencies if needed" +if (-not (Test-Path (Join-Path $Root "node_modules\playwright"))) { + & npm.cmd install + if ($LASTEXITCODE -ne 0) { + throw "npm install failed." + } +} else { + Write-Host "Node dependencies already installed." +} + +if ($RemoteBackend -or -not (Test-LocalBaseUrl $BaseUrl)) { + Write-Step "Checking remote backend" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Remote backend is reachable: $BaseUrl" + } else { + Write-Host "Remote backend health check did not return OK. Continuing because /actuator/health may be disabled." + Write-Host "Worker will connect to: $BaseUrl" + } +} else { + Write-Step "Starting backend service if needed" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Backend is already running: $BaseUrl" + } else { + $jar = Find-BackendJar + if (-not $jar) { + throw "Backend jar not found under target\. Build the Spring Boot jar first." + } + $backendCommand = "Set-Location '$Root'; java -jar '$jar'" + Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) -WindowStyle Normal + Write-Host "Waiting for backend health..." + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Seconds 2 + if (Test-BackendHealth $BaseUrl) { + $ready = $true + break + } + } + if (-not $ready) { + throw "Backend did not become healthy at $BaseUrl. Check the backend window." + } + } +} + +Write-Step "Checking Douyin login" +$env:MEDIA_BASE_URL = $BaseUrl +$env:DOUYIN_ACCOUNT_ID = $AccountId +$checkOutput = & npm.cmd run douyin:check -- $AccountId 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host $checkOutput + Write-Host "" + Write-Host "Douyin login is required. A browser will open. Login, then press Enter in that window." + & npm.cmd run douyin:login -- $AccountId + if ($LASTEXITCODE -ne 0) { + throw "Douyin login failed." + } +} else { + Write-Host "Douyin login profile is ready." +} + +Write-Step "Starting Douyin worker loop" +$workerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run douyin:worker:loop" +Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $workerCommand) -WindowStyle Normal + +Write-Host "" +Write-Host "Local client is ready." +Write-Host "Backend: $BaseUrl" +Write-Host "Account: $AccountId" +Write-Host "Worker polling interval: $WorkerIntervalSeconds seconds" diff --git a/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java index a94685f..ce97444 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java @@ -1,12 +1,12 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import java.util.List; -abstract class AbstractPlaceholderCapabilityHandler implements AgentCapabilityHandler { +public abstract class AbstractPlaceholderCapabilityHandler implements AgentCapabilityHandler { private final AgentIntent intent; private final String message; private final List dataSources; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java index 609c0bc..9a39b71 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java @@ -1,8 +1,8 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; public interface AgentCapabilityHandler { AgentIntent intent(); diff --git a/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java b/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java index 11e8f34..d902ce9 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java @@ -1,8 +1,8 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import org.springframework.stereotype.Service; import java.util.EnumMap; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java similarity index 74% rename from src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java index 9c2f72f..9e61827 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.content; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java index 38c23b3..db3c2a7 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java @@ -1,10 +1,12 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.customer; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; -import com.company.agentfoundation.integration.wecom.WecomStudentContactStats; -import com.company.agentfoundation.integration.wecom.WecomStudentContactStatsService; +import com.company.agentfoundation.agent.capability.AgentCapabilityHandler; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; +import com.company.agentfoundation.integration.wecom.dto.WecomStudentContactStats; +import com.company.agentfoundation.integration.wecom.service.WecomStudentContactStatsService; import org.springframework.stereotype.Component; import java.time.Instant; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java index d4a554a..eae68e3 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.ec; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java index e53c906..35fbc6d 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.integration; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java index c2d4052..1a3dac9 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.management; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java similarity index 73% rename from src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java index f105206..cf8f3c6 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.student; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java similarity index 73% rename from src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java index 8e365f2..26b707f 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.wecom; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/OperationReportController.java b/src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java similarity index 94% rename from src/main/java/com/company/agentfoundation/agent/OperationReportController.java rename to src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java index 7645560..b889d4b 100644 --- a/src/main/java/com/company/agentfoundation/agent/OperationReportController.java +++ b/src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java @@ -1,4 +1,7 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.controller; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.service.OperationReportService; import com.company.agentfoundation.agent.capability.AgentCapabilityDescriptor; import com.company.agentfoundation.agent.capability.CapabilityRegistry; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java b/src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java similarity index 83% rename from src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java rename to src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java index 1d10beb..f82bf18 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java +++ b/src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.dto; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import java.time.Instant; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentIntent.java b/src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java similarity index 96% rename from src/main/java/com/company/agentfoundation/agent/AgentIntent.java rename to src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java index c0ccf12..3a855ec 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentIntent.java +++ b/src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.model; public enum AgentIntent { DATA_SYNC_STATUS("data_sync_status", "integration", "\u4e09\u7cfb\u7edf\u6570\u636e\u540c\u6b65\u72b6\u6001"), diff --git a/src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java b/src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java similarity index 84% rename from src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java rename to src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java index e2ca35f..54bb5ed 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java +++ b/src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.model; public record AgentRequestContext( String agentId, diff --git a/src/main/java/com/company/agentfoundation/agent/OperationReportService.java b/src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/OperationReportService.java rename to src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java index 8d2e4b8..f096b8c 100644 --- a/src/main/java/com/company/agentfoundation/agent/OperationReportService.java +++ b/src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.service; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import com.company.agentfoundation.agent.capability.CapabilityRegistry; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java b/src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java similarity index 82% rename from src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java rename to src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java index 90dc13f..c60a411 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.controller; + +import com.company.agentfoundation.agent.runtime.dto.AgentMessageRequest; +import com.company.agentfoundation.agent.runtime.dto.AgentMessageResponse; +import com.company.agentfoundation.agent.runtime.service.AgentOrchestratorService; import com.company.agentfoundation.security.AgentAuthService; import jakarta.validation.Valid; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java similarity index 72% rename from src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java rename to src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java index b2cca04..63343a8 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.dto; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java rename to src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java index d4328c5..761b4b3 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.dto; import java.time.Instant; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java b/src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java similarity index 87% rename from src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java rename to src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java index 13f39f5..3d9805a 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java @@ -1,10 +1,15 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; -import com.company.agentfoundation.directory.EmployeeAccountMapping; -import com.company.agentfoundation.directory.EmployeeDirectoryResponse; -import com.company.agentfoundation.directory.EmployeeDirectoryService; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; +import com.company.agentfoundation.agent.report.service.OperationReportService; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; +import com.company.agentfoundation.directory.dto.EmployeeAccountMapping; +import com.company.agentfoundation.directory.dto.EmployeeDirectoryResponse; +import com.company.agentfoundation.directory.service.EmployeeDirectoryService; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java b/src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java similarity index 94% rename from src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java rename to src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java index ec64dfb..7a1ace7 100644 --- a/src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java @@ -1,7 +1,9 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekClient.java b/src/main/java/com/company/agentfoundation/api/DeepseekClient.java index 88a8079..8848559 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekClient.java +++ b/src/main/java/com/company/agentfoundation/api/DeepseekClient.java @@ -1,4 +1,7 @@ package com.company.agentfoundation.api; + +import com.company.agentfoundation.api.dto.DeepseekChatRequest; +import com.company.agentfoundation.api.dto.DeepseekChatResponse; import com.company.agentfoundation.config.FoundationProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java similarity index 80% rename from src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java rename to src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java index 5743784..0eb3cf7 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java +++ b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.api; +package com.company.agentfoundation.api.dto; import java.util.List; public record DeepseekChatRequest(String model, List messages, Double temperature, Boolean stream) { public record Message(String role, String content) {} diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java similarity index 86% rename from src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java rename to src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java index 6d1f7d6..b405c15 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java +++ b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.api; +package com.company.agentfoundation.api.dto; import java.util.List; public record DeepseekChatResponse(List choices, Usage usage) { public record Choice(Message message) {} diff --git a/src/main/java/com/company/agentfoundation/controller/ChatController.java b/src/main/java/com/company/agentfoundation/controller/ChatController.java index d6d44f1..dba8669 100644 --- a/src/main/java/com/company/agentfoundation/controller/ChatController.java +++ b/src/main/java/com/company/agentfoundation/controller/ChatController.java @@ -1,6 +1,6 @@ package com.company.agentfoundation.controller; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentAuthService; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java b/src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java similarity index 95% rename from src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java rename to src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java index 97826bb..2bd6ab4 100644 --- a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java +++ b/src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.crm; +package com.company.agentfoundation.crm.controller; + +import com.company.agentfoundation.crm.service.CrmDatabaseService; import com.company.agentfoundation.security.AgentAuthService; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java b/src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java similarity index 98% rename from src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java rename to src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java index 9056dbd..d4d18ba 100644 --- a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java +++ b/src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.crm; +package com.company.agentfoundation.crm.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java similarity index 88% rename from src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java index 9e02246..27f55a0 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; public record EmployeeAccountMapping( Long id, diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java similarity index 73% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java index 17bc08e..dcfa1af 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; public record EmployeeDataPermission( String sourceSystem, diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java similarity index 74% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java index 581409c..a0ea89e 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java b/src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java similarity index 90% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java rename to src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java index dfca71d..d9ce543 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java +++ b/src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.service; + +import com.company.agentfoundation.directory.dto.EmployeeAccountMapping; +import com.company.agentfoundation.directory.dto.EmployeeDataPermission; +import com.company.agentfoundation.directory.dto.EmployeeDirectoryResponse; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/domain/ChatRequest.java b/src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java similarity index 86% rename from src/main/java/com/company/agentfoundation/domain/ChatRequest.java rename to src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java index a1be53f..68eb33d 100644 --- a/src/main/java/com/company/agentfoundation/domain/ChatRequest.java +++ b/src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.domain; +package com.company.agentfoundation.dto.chat; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/domain/ChatResponse.java b/src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java similarity index 84% rename from src/main/java/com/company/agentfoundation/domain/ChatResponse.java rename to src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java index 3b7db18..aabaf50 100644 --- a/src/main/java/com/company/agentfoundation/domain/ChatResponse.java +++ b/src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.domain; +package com.company.agentfoundation.dto.chat; import java.time.Instant; public record ChatResponse(String traceId, String agentId, String model, String content, Usage usage, Instant timestamp) { public record Usage(Integer promptTokens, Integer completionTokens, Integer totalTokens) {} diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java b/src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java similarity index 91% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java rename to src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java index 6a0ac7f..045f7ab 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java @@ -1,4 +1,13 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.client; + +import com.company.agentfoundation.integration.wecom.dto.WecomApiResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomFollowUserListResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomSendMessageRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomTokenResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataResponse; import com.company.agentfoundation.config.FoundationProperties; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java rename to src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java index 1d16403..584f91e 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java @@ -1,6 +1,11 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.controller; -import com.company.agentfoundation.agent.AgentOrchestratorService; +import com.company.agentfoundation.integration.wecom.client.WecomClient; +import com.company.agentfoundation.integration.wecom.model.WecomEncryptedEnvelope; +import com.company.agentfoundation.integration.wecom.model.WecomIncomingMessage; +import com.company.agentfoundation.integration.wecom.service.WecomCryptoService; + +import com.company.agentfoundation.agent.runtime.service.AgentOrchestratorService; import com.company.agentfoundation.config.FoundationProperties; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java similarity index 71% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java index 90b89ee..21abb38 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; public record WecomApiResponse(Integer errcode, String errmsg) { public boolean success() { diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java similarity index 79% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java index f45a77a..4065949 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java similarity index 92% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java index c40514a..d110731 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java similarity index 83% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java index eecf028..875eae8 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java similarity index 95% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java index c07ba80..a1e1bdb 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java similarity index 83% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java index b265dcc..648fc74 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; public record WecomStudentContactStats( int totalStudents, diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java similarity index 84% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java index d8d6299..69d9775 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java similarity index 82% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java index cc64748..ab355a5 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java index d771db5..8c1219c 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java rename to src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java index e65d816..d2b7117 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.model; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java similarity index 96% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java rename to src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java index ae8d665..1914916 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java similarity index 98% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java rename to src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java index f1d8e14..8e6768f 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.service; import com.company.agentfoundation.config.FoundationProperties; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java rename to src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java index 9327a99..fadf0d0 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java @@ -1,4 +1,9 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.service; + +import com.company.agentfoundation.integration.wecom.client.WecomClient; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomStudentContactStats; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataResponse; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java b/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java new file mode 100644 index 0000000..5f1d10a --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java @@ -0,0 +1,71 @@ +package com.company.agentfoundation.media.controller; + +import com.company.agentfoundation.media.dto.CreateMediaTaskRequest; +import com.company.agentfoundation.media.dto.MediaTaskListResponse; +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.service.MediaTaskService; +import com.company.agentfoundation.security.AgentAuthService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/v1/media") +public class MediaTaskController { + private final AgentAuthService authService; + private final MediaTaskService mediaTaskService; + + public MediaTaskController(AgentAuthService authService, MediaTaskService mediaTaskService) { + this.authService = authService; + this.mediaTaskService = mediaTaskService; + } + + @PostMapping("/tasks") + public ResponseEntity createTask( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @Valid @RequestBody CreateMediaTaskRequest request + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.create(request)); + } + + @GetMapping("/tasks") + public ResponseEntity listTasks( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "platform", required = false) String platform + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(MediaTaskListResponse.from(mediaTaskService.list(status, platform))); + } + + @GetMapping("/tasks/{taskId}") + public ResponseEntity getTask( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @PathVariable String taskId + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.get(taskId)); + } + + @GetMapping("/dashboard/summary") + public ResponseEntity> dashboardSummary( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.dashboardSummary()); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java b/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java new file mode 100644 index 0000000..ef628b3 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java @@ -0,0 +1,53 @@ +package com.company.agentfoundation.media.controller; + +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.dto.WorkerPollRequest; +import com.company.agentfoundation.media.dto.WorkerTaskResultRequest; +import com.company.agentfoundation.media.service.MediaTaskService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/v1/media/workers") +public class MediaWorkerController { + private static final String LOCAL_WORKER_TOKEN = "local-worker-dev-token"; + + private final MediaTaskService mediaTaskService; + + public MediaWorkerController(MediaTaskService mediaTaskService) { + this.mediaTaskService = mediaTaskService; + } + + @PostMapping("/poll") + public ResponseEntity> poll( + @RequestHeader("X-Worker-Token") String workerToken, + @Valid @RequestBody WorkerPollRequest request + ) { + authenticateWorker(workerToken); + return ResponseEntity.ok(mediaTaskService.poll(request)); + } + + @PostMapping("/tasks/{taskId}/result") + public ResponseEntity result( + @RequestHeader("X-Worker-Token") String workerToken, + @PathVariable String taskId, + @Valid @RequestBody WorkerTaskResultRequest request + ) { + authenticateWorker(workerToken); + return ResponseEntity.ok(mediaTaskService.complete(taskId, request)); + } + + private void authenticateWorker(String workerToken) { + if (!LOCAL_WORKER_TOKEN.equals(workerToken)) { + throw new IllegalArgumentException("Invalid local worker token"); + } + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java b/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java new file mode 100644 index 0000000..de90ead --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java @@ -0,0 +1,15 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTaskType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.util.Map; + +public record CreateMediaTaskRequest( + @NotNull MediaTaskType taskType, + @NotBlank String platform, + @NotBlank String accountId, + Map payload +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java new file mode 100644 index 0000000..3b5c4ba --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java @@ -0,0 +1,12 @@ +package com.company.agentfoundation.media.dto; + +import java.util.List; + +public record MediaTaskListResponse( + int total, + List items +) { + public static MediaTaskListResponse from(List items) { + return new MediaTaskListResponse(items.size(), items); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java new file mode 100644 index 0000000..59372ae --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java @@ -0,0 +1,38 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTask; +import com.company.agentfoundation.media.model.MediaTaskStatus; +import com.company.agentfoundation.media.model.MediaTaskType; + +import java.time.Instant; +import java.util.Map; + +public record MediaTaskResponse( + String id, + MediaTaskType taskType, + String platform, + String accountId, + MediaTaskStatus status, + String assignedWorkerId, + Map payload, + Map result, + String errorMessage, + Instant createdAt, + Instant updatedAt +) { + public static MediaTaskResponse from(MediaTask task) { + return new MediaTaskResponse( + task.getId(), + task.getTaskType(), + task.getPlatform(), + task.getAccountId(), + task.getStatus(), + task.getAssignedWorkerId(), + task.getPayload(), + task.getResult(), + task.getErrorMessage(), + task.getCreatedAt(), + task.getUpdatedAt() + ); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java b/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java new file mode 100644 index 0000000..f45e223 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java @@ -0,0 +1,10 @@ +package com.company.agentfoundation.media.dto; + +import java.util.List; + +public record WorkerPollRequest( + String workerId, + List platforms, + int maxTasks +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java b/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java new file mode 100644 index 0000000..730109b --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java @@ -0,0 +1,12 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTaskStatus; + +import java.util.Map; + +public record WorkerTaskResultRequest( + MediaTaskStatus status, + Map result, + String errorMessage +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTask.java b/src/main/java/com/company/agentfoundation/media/model/MediaTask.java new file mode 100644 index 0000000..5c7c25b --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTask.java @@ -0,0 +1,162 @@ +package com.company.agentfoundation.media.model; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class MediaTask { + private final String id; + private final MediaTaskType taskType; + private final String platform; + private final String accountId; + private final Map payload; + private final Instant createdAt; + private MediaTaskStatus status; + private String assignedWorkerId; + private Map result; + private String errorMessage; + private Instant updatedAt; + + public MediaTask(String id, MediaTaskType taskType, String platform, String accountId, Map payload) { + this.id = id; + this.taskType = taskType; + this.platform = platform; + this.accountId = accountId; + this.payload = payload == null ? new LinkedHashMap<>() : new LinkedHashMap<>(payload); + this.createdAt = Instant.now(); + this.updatedAt = createdAt; + this.status = MediaTaskStatus.PENDING; + this.result = new LinkedHashMap<>(); + } + + public String getId() { return id; } + public MediaTaskType getTaskType() { return taskType; } + public String getPlatform() { return platform; } + public String getAccountId() { return accountId; } + public Map getPayload() { return payload; } + public Instant getCreatedAt() { return createdAt; } + public MediaTaskStatus getStatus() { return status; } + public String getAssignedWorkerId() { return assignedWorkerId; } + public Map getResult() { return result; } + public String getErrorMessage() { return errorMessage; } + public Instant getUpdatedAt() { return updatedAt; } + + public void assignTo(String workerId) { + this.assignedWorkerId = workerId; + this.status = MediaTaskStatus.RUNNING; + this.updatedAt = Instant.now(); + } + + public void complete(MediaTaskStatus status, Map result, String errorMessage) { + if (status != MediaTaskStatus.SUCCESS && status != MediaTaskStatus.FAILED) { + throw new IllegalArgumentException("Task result status must be SUCCESS or FAILED"); + } + this.status = status; + this.result = result == null ? new LinkedHashMap<>() : new LinkedHashMap<>(result); + this.errorMessage = errorMessage; + this.updatedAt = Instant.now(); + } + + public static Map aggregateMetrics(List tasks) { + long views = 0; + long likes = 0; + long comments = 0; + long collects = 0; + long shares = 0; + List> rows = new ArrayList<>(); + List> videoRows = new ArrayList<>(); + for (MediaTask task : tasks) { + if (task.status != MediaTaskStatus.SUCCESS || task.result == null) { + continue; + } + Map metrics = mapValue(task.result.get("metrics")); + if (metrics.isEmpty()) { + continue; + } + long taskViews = longValue(metrics.get("views")); + long taskLikes = longValue(metrics.get("likes")); + long taskComments = longValue(metrics.get("comments")); + long taskCollects = longValue(metrics.get("collects")); + long taskShares = longValue(metrics.get("shares")); + views += taskViews; + likes += taskLikes; + comments += taskComments; + collects += taskCollects; + shares += taskShares; + rows.add(Map.of( + "taskId", task.id, + "platform", task.platform, + "accountId", task.accountId, + "views", taskViews, + "likes", taskLikes, + "comments", taskComments, + "collects", taskCollects, + "shares", taskShares + )); + for (Map video : listOfMaps(task.result.get("videos"))) { + videoRows.add(Map.of( + "taskId", task.id, + "platform", task.platform, + "accountId", task.accountId, + "title", stringValue(video.get("title")), + "href", stringValue(video.get("href")), + "views", longValue(video.get("views")), + "likes", longValue(video.get("likes")), + "comments", longValue(video.get("comments")), + "collects", longValue(video.get("collects")), + "shares", longValue(video.get("shares")) + )); + } + } + Map summary = new LinkedHashMap<>(); + summary.put("views", views); + summary.put("likes", likes); + summary.put("comments", comments); + summary.put("collects", collects); + summary.put("shares", shares); + summary.put("interactionCount", likes + comments + collects + shares); + summary.put("interactionRate", views <= 0 ? 0.0 : (likes + comments + collects + shares) * 1.0 / views); + summary.put("rows", rows); + summary.put("videoCount", videoRows.size()); + summary.put("videoRows", videoRows); + return summary; + } + + @SuppressWarnings("unchecked") + private static Map mapValue(Object value) { + if (value instanceof Map map) { + return (Map) map; + } + return Map.of(); + } + + private static long longValue(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value instanceof String text && !text.isBlank()) { + return Long.parseLong(text); + } + return 0; + } + + @SuppressWarnings("unchecked") + private static List> listOfMaps(Object value) { + if (!(value instanceof List list)) { + return List.of(); + } + List> maps = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Map map) { + maps.add((Map) map); + } + } + return maps; + } + + private static String stringValue(Object value) { + return value == null ? "" : String.valueOf(value); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java b/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java new file mode 100644 index 0000000..c91b8b9 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java @@ -0,0 +1,8 @@ +package com.company.agentfoundation.media.model; + +public enum MediaTaskStatus { + PENDING, + RUNNING, + SUCCESS, + FAILED +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java b/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java new file mode 100644 index 0000000..f078433 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java @@ -0,0 +1,8 @@ +package com.company.agentfoundation.media.model; + +public enum MediaTaskType { + SYNC_COMMENTS, + SYNC_METRICS, + REPLY_COMMENT, + PUBLISH_CONTENT +} diff --git a/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java b/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java new file mode 100644 index 0000000..1d4219d --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java @@ -0,0 +1,118 @@ +package com.company.agentfoundation.media.service; + +import com.company.agentfoundation.media.dto.CreateMediaTaskRequest; +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.dto.WorkerPollRequest; +import com.company.agentfoundation.media.dto.WorkerTaskResultRequest; +import com.company.agentfoundation.media.model.MediaTask; +import com.company.agentfoundation.media.model.MediaTaskStatus; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class MediaTaskService { + private final Map tasks = new ConcurrentHashMap<>(); + private final Map workerHeartbeats = new ConcurrentHashMap<>(); + + public MediaTaskResponse create(CreateMediaTaskRequest request) { + MediaTask task = new MediaTask( + "media_" + UUID.randomUUID(), + request.taskType(), + normalize(request.platform()), + request.accountId(), + request.payload() + ); + tasks.put(task.getId(), task); + return MediaTaskResponse.from(task); + } + + public MediaTaskResponse get(String taskId) { + return MediaTaskResponse.from(required(taskId)); + } + + public List list(String status, String platform) { + return tasks.values().stream() + .filter(task -> status == null || status.isBlank() || task.getStatus().name().equalsIgnoreCase(status)) + .filter(task -> platform == null || platform.isBlank() || task.getPlatform().equalsIgnoreCase(platform)) + .sorted(Comparator.comparing(MediaTask::getCreatedAt).reversed()) + .map(MediaTaskResponse::from) + .toList(); + } + + public List poll(WorkerPollRequest request) { + String workerId = request.workerId() == null || request.workerId().isBlank() + ? "local-worker" + : request.workerId(); + int limit = request.maxTasks() <= 0 ? 1 : Math.min(request.maxTasks(), 10); + List platforms = request.platforms() == null + ? List.of() + : request.platforms().stream().map(this::normalize).toList(); + workerHeartbeats.put(workerId, Instant.now()); + + List assigned = new ArrayList<>(); + for (MediaTask task : tasks.values().stream() + .filter(task -> task.getStatus() == MediaTaskStatus.PENDING) + .filter(task -> platforms.isEmpty() || platforms.contains(task.getPlatform())) + .sorted(Comparator.comparing(MediaTask::getCreatedAt)) + .toList()) { + synchronized (task) { + if (task.getStatus() != MediaTaskStatus.PENDING) { + continue; + } + task.assignTo(workerId); + assigned.add(MediaTaskResponse.from(task)); + } + if (assigned.size() >= limit) { + break; + } + } + return assigned; + } + + public MediaTaskResponse complete(String taskId, WorkerTaskResultRequest request) { + MediaTask task = required(taskId); + MediaTaskStatus status = request.status() == null ? MediaTaskStatus.SUCCESS : request.status(); + synchronized (task) { + task.complete(status, request.result(), request.errorMessage()); + } + return MediaTaskResponse.from(task); + } + + public Map dashboardSummary() { + List allTasks = new ArrayList<>(tasks.values()); + Map summary = new LinkedHashMap<>(); + summary.put("taskCount", allTasks.size()); + summary.put("pendingCount", countByStatus(allTasks, MediaTaskStatus.PENDING)); + summary.put("runningCount", countByStatus(allTasks, MediaTaskStatus.RUNNING)); + summary.put("successCount", countByStatus(allTasks, MediaTaskStatus.SUCCESS)); + summary.put("failedCount", countByStatus(allTasks, MediaTaskStatus.FAILED)); + summary.put("metrics", MediaTask.aggregateMetrics(allTasks)); + summary.put("workers", workerHeartbeats); + return summary; + } + + private long countByStatus(List allTasks, MediaTaskStatus status) { + return allTasks.stream().filter(task -> task.getStatus() == status).count(); + } + + private MediaTask required(String taskId) { + MediaTask task = tasks.get(taskId); + if (task == null) { + throw new IllegalArgumentException("Media task not found: " + taskId); + } + return task; + } + + private String normalize(String platform) { + return platform == null ? "" : platform.trim().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java similarity index 74% rename from src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java index 369574b..c5b21c1 100644 --- a/src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java @@ -1,9 +1,9 @@ -package com.company.agentfoundation.controller; +package com.company.agentfoundation.miniprogram.jumprope.controller; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAiResponse; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAiService; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAnalyzeRequest; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeChatRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAiResponse; +import com.company.agentfoundation.miniprogram.jumprope.service.JumpRopeAiService; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAnalyzeRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeChatRequest; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java similarity index 65% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java index 2fbe858..839761e 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; + +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java similarity index 80% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java index 221e423..320118d 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java similarity index 72% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java index bc16164..427f399 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java similarity index 87% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java index b2a5446..966e66d 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; import java.time.LocalDateTime; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java similarity index 66% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java index 7cbef6e..1a61a2a 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeMessage( Long id, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java similarity index 79% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java index af35b6a..81bee79 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeSession( Long id, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java similarity index 76% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java index fc964e2..b95a9d8 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeVideoRecommendation( Integer resourceId, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java similarity index 68% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java index 9faa0fb..58d6c9e 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record MiniProgramUser( Integer userId, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java similarity index 95% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java index 6569f9a..8471c6b 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java @@ -1,4 +1,10 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.repository; + +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeExerciseData; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeMessage; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeSession; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; +import com.company.agentfoundation.miniprogram.jumprope.model.MiniProgramUser; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java similarity index 93% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java index f2a4f56..e1570d7 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java @@ -1,7 +1,17 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAiResponse; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAnalyzeRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeChatRequest; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeExerciseData; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeMessage; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeSession; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; +import com.company.agentfoundation.miniprogram.jumprope.model.MiniProgramUser; +import com.company.agentfoundation.miniprogram.jumprope.repository.JumpRopeAiRepository; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/src/main/java/com/company/agentfoundation/service/ChatService.java b/src/main/java/com/company/agentfoundation/service/ChatService.java index 3df5a74..ee7991b 100644 --- a/src/main/java/com/company/agentfoundation/service/ChatService.java +++ b/src/main/java/com/company/agentfoundation/service/ChatService.java @@ -1,9 +1,9 @@ package com.company.agentfoundation.service; -import com.company.agentfoundation.api.DeepseekChatRequest; -import com.company.agentfoundation.api.DeepseekChatResponse; +import com.company.agentfoundation.api.dto.DeepseekChatRequest; +import com.company.agentfoundation.api.dto.DeepseekChatResponse; import com.company.agentfoundation.api.DeepseekClient; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/start_local_client.bat b/start_local_client.bat new file mode 100644 index 0000000..64d42c6 --- /dev/null +++ b/start_local_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" +pause diff --git a/start_yuedong_client.bat b/start_yuedong_client.bat new file mode 100644 index 0000000..df335ef --- /dev/null +++ b/start_yuedong_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" -BaseUrl "http://agent.yuedongsports.com" -RemoteBackend +pause