438c52e79b
新增 ias skills delete: 扫描 skills/ 目录,列出所有用户 Skill MultiSelect TUI 选择要删除的 Skill 确认后 remove_dir_all 物理删除
407 lines
11 KiB
JavaScript
407 lines
11 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const axios = require('axios');
|
||
|
||
// 配置文件路径
|
||
const CONFIG_FILE = path.join(__dirname, 'config.json');
|
||
|
||
/**
|
||
* 读取配置文件
|
||
*/
|
||
function readConfig() {
|
||
try {
|
||
if (fs.existsSync(CONFIG_FILE)) {
|
||
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||
return JSON.parse(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('读取配置文件失败:', error.message);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
/**
|
||
* 保存配置文件
|
||
*/
|
||
function saveConfig(config) {
|
||
try {
|
||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
||
console.log('配置已保存到:', CONFIG_FILE);
|
||
return true;
|
||
} catch (error) {
|
||
console.error('保存配置文件失败:', error.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取高德 Web Service Key
|
||
*/
|
||
function getWebServiceKey() {
|
||
const config = readConfig();
|
||
return config.webServiceKey || null;
|
||
}
|
||
|
||
/**
|
||
* 设置高德 Web Service Key
|
||
*/
|
||
function setWebServiceKey(key) {
|
||
const config = readConfig();
|
||
config.webServiceKey = key;
|
||
return saveConfig(config);
|
||
}
|
||
|
||
/**
|
||
* 检查并提示用户输入 Key
|
||
*/
|
||
async function ensureWebServiceKey() {
|
||
// 优先从环境变量读取
|
||
let key = process.env.AMAP_KEY || process.env.AMAP_WEBSERVICE_KEY;
|
||
|
||
if (!key) {
|
||
// 尝试从配置文件读取
|
||
key = getWebServiceKey();
|
||
}
|
||
|
||
if (!key) {
|
||
console.log('\n⚠️ 未找到高德 Web Service Key');
|
||
console.log('请访问以下地址创建应用并获取 Key:');
|
||
console.log('https://lbs.amap.com/api/webservice/create-project-and-key\n');
|
||
throw new Error('请设置环境变量 AMAP_KEY 或提供高德 Web Service Key');
|
||
}
|
||
|
||
return key;
|
||
}
|
||
|
||
/**
|
||
* POI 搜索
|
||
* @param {Object} params - 搜索参数
|
||
* @param {string} params.keywords - 查询关键字
|
||
* @param {string} params.city - 城市名称或城市编码
|
||
* @param {string} params.types - POI类型编码
|
||
* @param {string} params.location - 中心点坐标
|
||
* @param {number} params.radius - 搜索半径(米)
|
||
* @param {number} params.page - 当前页数
|
||
* @param {number} params.offset - 每页记录数
|
||
*/
|
||
async function searchPOI(params) {
|
||
const key = await ensureWebServiceKey();
|
||
|
||
const url = 'https://restapi.amap.com/v5/place/text';
|
||
|
||
const requestParams = {
|
||
key: key,
|
||
keywords: params.keywords || '',
|
||
region: params.city || '',
|
||
city_limit: params.cityLimit !== false,
|
||
...params
|
||
};
|
||
|
||
try {
|
||
console.log('🔍 正在搜索 POI...');
|
||
const response = await axios.get(url, { params: requestParams });
|
||
|
||
if (response.data.status === '1') {
|
||
console.log(`✅ 搜索成功,共找到 ${response.data.count} 条结果\n`);
|
||
return response.data;
|
||
} else {
|
||
console.error('❌ 搜索失败:', response.data.info);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 请求失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 步行路径规划
|
||
* @param {Object} params - 规划参数
|
||
* @param {string} params.origin - 起点坐标 "经度,纬度"
|
||
* @param {string} params.destination - 终点坐标 "经度,纬度"
|
||
*/
|
||
async function walkingRoute(params) {
|
||
const key = await ensureWebServiceKey();
|
||
|
||
const url = 'https://restapi.amap.com/v3/direction/walking';
|
||
|
||
const requestParams = {
|
||
key: key,
|
||
origin: params.origin,
|
||
destination: params.destination
|
||
};
|
||
|
||
try {
|
||
console.log('🚶 正在规划步行路线...');
|
||
const response = await axios.get(url, { params: requestParams });
|
||
|
||
if (response.data.status === '1') {
|
||
console.log('✅ 步行路线规划成功\n');
|
||
return response.data;
|
||
} else {
|
||
console.error('❌ 步行路线规划失败:', response.data.info);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 请求失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 驾车路径规划
|
||
* @param {Object} params - 规划参数
|
||
* @param {string} params.origin - 起点坐标 "经度,纬度"
|
||
* @param {string} params.destination - 终点坐标 "经度,纬度"
|
||
* @param {string} params.waypoints - 途经点坐标,多个用;分隔
|
||
* @param {number} params.strategy - 驾车策略,默认10
|
||
*/
|
||
async function drivingRoute(params) {
|
||
const key = await ensureWebServiceKey();
|
||
|
||
const url = 'https://restapi.amap.com/v3/direction/driving';
|
||
|
||
const requestParams = {
|
||
key: key,
|
||
origin: params.origin,
|
||
destination: params.destination,
|
||
strategy: params.strategy || 10,
|
||
extensions: 'base'
|
||
};
|
||
|
||
if (params.waypoints) {
|
||
requestParams.waypoints = params.waypoints;
|
||
}
|
||
|
||
try {
|
||
console.log('🚗 正在规划驾车路线...');
|
||
const response = await axios.get(url, { params: requestParams });
|
||
|
||
if (response.data.status === '1') {
|
||
console.log('✅ 驾车路线规划成功\n');
|
||
return response.data;
|
||
} else {
|
||
console.error('❌ 驾车路线规划失败:', response.data.info);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 请求失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 骑行路径规划
|
||
* @param {Object} params - 规划参数
|
||
* @param {string} params.origin - 起点坐标 "经度,纬度"
|
||
* @param {string} params.destination - 终点坐标 "经度,纬度"
|
||
*/
|
||
async function ridingRoute(params) {
|
||
const key = await ensureWebServiceKey();
|
||
|
||
const url = 'https://restapi.amap.com/v4/direction/bicycling';
|
||
|
||
const requestParams = {
|
||
key: key,
|
||
origin: params.origin,
|
||
destination: params.destination
|
||
};
|
||
|
||
try {
|
||
console.log('🚴 正在规划骑行路线...');
|
||
const response = await axios.get(url, { params: requestParams });
|
||
|
||
if (response.data.errcode === 0) {
|
||
console.log('✅ 骑行路线规划成功\n');
|
||
return response.data;
|
||
} else {
|
||
console.error('❌ 骑行路线规划失败:', response.data.errmsg);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 请求失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 公交路径规划
|
||
* @param {Object} params - 规划参数
|
||
* @param {string} params.origin - 起点坐标 "经度,纬度"
|
||
* @param {string} params.destination - 终点坐标 "经度,纬度"
|
||
* @param {string} params.city - 城市名称或城市编码
|
||
* @param {number} params.strategy - 公交策略,默认0(最快捷)
|
||
* @param {boolean} params.nightflag - 是否计算夜班车,默认false
|
||
*/
|
||
async function transitRoute(params) {
|
||
const key = await ensureWebServiceKey();
|
||
|
||
const url = 'https://restapi.amap.com/v3/direction/transit/integrated';
|
||
|
||
const requestParams = {
|
||
key: key,
|
||
origin: params.origin,
|
||
destination: params.destination,
|
||
city: params.city,
|
||
strategy: params.strategy || 0,
|
||
nightflag: params.nightflag ? 1 : 0
|
||
};
|
||
|
||
try {
|
||
console.log('🚌 正在规划公交路线...');
|
||
const response = await axios.get(url, { params: requestParams });
|
||
|
||
if (response.data.status === '1') {
|
||
console.log('✅ 公交路线规划成功\n');
|
||
return response.data;
|
||
} else {
|
||
console.error('❌ 公交路线规划失败:', response.data.info);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 请求失败:', error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成地图可视化链接
|
||
* @param {Array} mapTaskData - 地图任务数据数组
|
||
* @returns {string} 可视化链接
|
||
*/
|
||
function generateMapLink(mapTaskData) {
|
||
const baseUrl = 'https://a.amap.com/jsapi_demo_show/static/openclaw/travel_plan.html';
|
||
const dataStr = encodeURIComponent(JSON.stringify(mapTaskData));
|
||
return `${baseUrl}?data=${dataStr}`;
|
||
}
|
||
|
||
/**
|
||
* 旅游规划助手
|
||
* @param {Object} params - 规划参数
|
||
* @param {string} params.city - 城市名称
|
||
* @param {Array<string>} params.interests - 兴趣点关键词数组,如 ['景点', '美食', '酒店']
|
||
* @param {string} params.routeType - 路线类型:driving/walking/riding/transfer
|
||
* @returns {Object} 包含 pois、mapTaskData、mapLink 和 htmlLink
|
||
*/
|
||
async function travelPlanner(params) {
|
||
const { city, interests = [], routeType = 'walking' } = params;
|
||
|
||
console.log(`\n🗺️ 开始为您规划 ${city} 的旅游行程...\n`);
|
||
|
||
const mapTaskData = [];
|
||
const poiResults = [];
|
||
|
||
// 搜索各类兴趣点
|
||
for (const interest of interests) {
|
||
console.log(`📍 搜索 ${interest}...`);
|
||
const result = await searchPOI({
|
||
keywords: interest,
|
||
city: city,
|
||
page: 1,
|
||
offset: 5
|
||
});
|
||
|
||
if (result && result.pois && result.pois.length > 0) {
|
||
poiResults.push(...result.pois);
|
||
|
||
// 添加到地图数据 - 严格按照 PoiTask 接口格式
|
||
result.pois.forEach(poi => {
|
||
const [lng, lat] = poi.location.split(',').map(Number);
|
||
mapTaskData.push({
|
||
type: 'poi',
|
||
lnglat: [lng, lat],
|
||
sort: poi.type || interest,
|
||
text: poi.name,
|
||
remark: poi.address || `${interest}推荐`
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// 如果有多个POI,规划路线
|
||
if (poiResults.length >= 2) {
|
||
console.log(`\n🛣️ 规划游览路线(${routeType})...\n`);
|
||
|
||
for (let i = 0; i < poiResults.length - 1; i++) {
|
||
const start = poiResults[i];
|
||
const end = poiResults[i + 1];
|
||
|
||
const [startLng, startLat] = start.location.split(',').map(Number);
|
||
const [endLng, endLat] = end.location.split(',').map(Number);
|
||
|
||
// 添加路线到地图数据 - 严格按照 RouteTask 接口格式
|
||
const routeTask = {
|
||
type: 'route',
|
||
routeType: routeType,
|
||
start: [startLng, startLat],
|
||
end: [endLng, endLat],
|
||
remark: `从 ${start.name} 到 ${end.name}`
|
||
};
|
||
|
||
// 如果是公交路线,添加 city 参数
|
||
if (routeType === 'transfer') {
|
||
routeTask.city = city;
|
||
}
|
||
|
||
mapTaskData.push(routeTask);
|
||
}
|
||
}
|
||
|
||
|
||
console.log('\n✅ 旅游规划完成!\n');
|
||
console.log('📍 推荐地点:');
|
||
poiResults.forEach((poi, index) => {
|
||
console.log(`${index + 1}. ${poi.name}`);
|
||
console.log(` 地址: ${poi.address}`);
|
||
console.log(` 类型: ${poi.type}\n`);
|
||
});
|
||
|
||
return {
|
||
pois: poiResults,
|
||
};
|
||
}
|
||
|
||
// 导出函数供其他脚本使用
|
||
module.exports = {
|
||
readConfig,
|
||
saveConfig,
|
||
getWebServiceKey,
|
||
setWebServiceKey,
|
||
ensureWebServiceKey,
|
||
searchPOI,
|
||
walkingRoute,
|
||
drivingRoute,
|
||
ridingRoute,
|
||
transitRoute,
|
||
generateMapLink,
|
||
travelPlanner
|
||
};
|
||
|
||
// 如果直接运行此文件,执行示例搜索
|
||
if (require.main === module) {
|
||
(async () => {
|
||
try {
|
||
// 示例:搜索北京的肯德基
|
||
const result = await searchPOI({
|
||
keywords: '肯德基',
|
||
city: '北京',
|
||
page: 1,
|
||
offset: 10
|
||
});
|
||
|
||
if (result && result.pois) {
|
||
console.log('搜索结果:');
|
||
result.pois.forEach((poi, index) => {
|
||
console.log(`${index + 1}. ${poi.name}`);
|
||
console.log(` 地址: ${poi.address}`);
|
||
console.log(` 类型: ${poi.type}`);
|
||
console.log(` 坐标: ${poi.location}\n`);
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('执行失败:', error.message);
|
||
process.exit(1);
|
||
}
|
||
})();
|
||
}
|