You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
13 KiB
347 lines
13 KiB
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"yunyan/lego/core"
|
|
"yunyan/lego/core/cbase"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/sys/amap"
|
|
"yunyan/utils"
|
|
"fmt"
|
|
"runtime/debug"
|
|
"sync"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
)
|
|
|
|
// 天气卡片数据 结构统一化输出
|
|
type NavigationCard struct {
|
|
Origin string `json:"location"` //起始点
|
|
Destination string `json:"destination"` //目标点
|
|
Start string `json:"start"` //起始点
|
|
End string `json:"end"` //目标点
|
|
Start_longitude string `json:"start_longitude"` //起始点经度
|
|
Start_latitude string `json:"start_latitude"` //起始点纬度
|
|
End_longitude string `json:"end_longitude"` //目标点经度
|
|
End_latitude string `json:"end_latitude"` //目标点纬度
|
|
Itmes []NavigationItem `json:"items"` //导航信息
|
|
}
|
|
|
|
type NavigationItem struct {
|
|
Ntype string `json:"ntype"` //类型
|
|
Enable bool `json:"enable"` //启用
|
|
Image string `json:"image"` //图片
|
|
Distance string `json:"distance"` //距离
|
|
Duration string `json:"duration"` //时间
|
|
Path []struct {
|
|
Action string `json:"action"` //动作 右转
|
|
Distance string `json:"distance"` //距离 距离
|
|
} `json:"path"` //路径
|
|
Transits []struct {
|
|
Name string `json:"name"` //搅动方式
|
|
Distance string `json:"distance"` //距离
|
|
} `json:"transits"` //路径
|
|
}
|
|
|
|
// 天气查询
|
|
type tool_gaode_map_route_navigation struct {
|
|
cbase.ModuleCompBase
|
|
module *Mcp
|
|
}
|
|
|
|
// 组件初始化接口
|
|
func (this *tool_gaode_map_route_navigation) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
|
|
this.ModuleCompBase.Init(service, module, comp, opt)
|
|
this.module = module.(*Mcp)
|
|
return
|
|
}
|
|
func (this *tool_gaode_map_route_navigation) Start() (err error) {
|
|
err = this.ModuleCompBase.Start()
|
|
if this.module.AddTool(ToolGroup_CHINA, this.Tool(), this.Handl) {
|
|
if err := amap.OnInit(this.module.service.GetSettings().Sys["amap"]); err != nil {
|
|
panic(fmt.Sprintf("init sys.amap err: %s", err.Error()))
|
|
} else {
|
|
log.Infof("init sys.amap success!")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func (this *tool_gaode_map_route_navigation) Tool() (tool mcp.Tool) {
|
|
return mcp.NewTool("maps_route_navigation",
|
|
mcp.WithDescription("根据用户提供的起点和终点以及出行方式,提供导航服务"),
|
|
mcp.WithString("origins_longitude",
|
|
mcp.Description("起点经度"),
|
|
mcp.Required(),
|
|
),
|
|
mcp.WithString("origins_latitude",
|
|
mcp.Description("起点纬度"),
|
|
mcp.Required(),
|
|
),
|
|
mcp.WithString("destination_address",
|
|
mcp.Description("终点地址"),
|
|
mcp.Required(),
|
|
),
|
|
mcp.WithString("destination_longitude",
|
|
mcp.Description("终点经度"),
|
|
mcp.Required(),
|
|
),
|
|
mcp.WithString("destination_latitude",
|
|
mcp.Description("终点纬度"),
|
|
mcp.Required(),
|
|
),
|
|
mcp.WithString("type",
|
|
mcp.Description("交通类型,0:不指定交通工具,1驾车,2骑行,3公交,4步行"),
|
|
mcp.DefaultNumber(0),
|
|
),
|
|
)
|
|
}
|
|
|
|
// 工具处理接口
|
|
// 参数:
|
|
// - ctx: 上下文
|
|
// - request: 工具调用请求,包含起点终点坐标与交通类型
|
|
// 返回值:
|
|
// - result: 工具调用结果,包含导航卡片或失败提示
|
|
// - err: 处理过程中的错误,当导航失败时返回非空错误
|
|
// 异常:
|
|
// - 当参数缺失、定位/路径规划失败或内部错误时返回错误
|
|
func (this *tool_gaode_map_route_navigation) Handl(ctx context.Context, request mcp.CallToolRequest) (result *mcp.CallToolResult, err error) {
|
|
var (
|
|
o_longitude, o_latitude, d_longitude, d_latitude, origins, destination, destination_address, city1, city2, cityCode1, cityCode2, originddress, destinationaddress string
|
|
location *amap.LocationResponse
|
|
geocoderesult *amap.ReverseGeocodeResponse
|
|
georesult *amap.GeocodeResponse
|
|
direction_driving *amap.RootResponse
|
|
direction_walking *amap.RootResponse
|
|
direction_bicycling *amap.RootResponse
|
|
direction_tsransit *amap.TsransitResponse
|
|
card *NavigationCard
|
|
context string
|
|
errs map[string]error
|
|
errlock sync.Mutex
|
|
ok bool
|
|
)
|
|
this.module.Debug("tool_gaode_map_route_navigation",
|
|
log.Field{Key: "request.Params.Arguments", Value: request.GetRawArguments()},
|
|
)
|
|
if o_longitude, err = request.RequireString("origins_longitude"); err != nil || o_longitude == "" {
|
|
err = fmt.Errorf("必要参数[origins_longitude]丢失")
|
|
context = "获取目标地址失败"
|
|
return
|
|
}
|
|
if o_latitude, err = request.RequireString("origins_latitude"); err != nil || o_latitude == "" {
|
|
err = fmt.Errorf("必要参数[origins_latitude]丢失")
|
|
context = "获取目标地址失败"
|
|
return
|
|
}
|
|
if destination_address, err = request.RequireString("destination_address"); err != nil || destination_address == "" {
|
|
err = fmt.Errorf("必要参数[destination_address]丢失")
|
|
context = "获取目标地址失败"
|
|
return
|
|
}
|
|
if d_longitude, err = request.RequireString("destination_longitude"); err != nil || d_longitude == "" {
|
|
err = fmt.Errorf("必要参数[destination_longitude]丢失")
|
|
context = "获取目标地址失败"
|
|
return
|
|
}
|
|
if d_latitude, err = request.RequireString("destination_latitude"); err != nil || d_latitude == "" {
|
|
err = fmt.Errorf("必要参数[destination_latitude]丢失")
|
|
context = "获取目标地址失败"
|
|
return
|
|
}
|
|
origins = fmt.Sprintf("%s,%s", o_longitude, o_latitude)
|
|
destination = fmt.Sprintf("%s,%s", d_longitude, d_latitude)
|
|
log.Debug("tool_navigation",
|
|
log.Field{Key: "origins", Value: origins},
|
|
log.Field{Key: "destination", Value: destination},
|
|
)
|
|
ok = false
|
|
if geocoderesult, err = amap.Geocode(origins); err == nil {
|
|
city1 = fmt.Sprintf("%s%s", geocoderesult.Regeocode.AddressComponent.Province, geocoderesult.Regeocode.AddressComponent.City)
|
|
originddress = fmt.Sprintf("%s", geocoderesult.Regeocode.AddressComponent.StreetNumber.Street)
|
|
cityCode1 = geocoderesult.Regeocode.AddressComponent.Citycode
|
|
|
|
if geocoderesult, err = amap.Geocode(destination); err == nil {
|
|
city2 = fmt.Sprintf("%s%s", geocoderesult.Regeocode.AddressComponent.Province, geocoderesult.Regeocode.AddressComponent.City)
|
|
destinationaddress = fmt.Sprintf("%s", geocoderesult.Regeocode.AddressComponent.StreetNumber.Street)
|
|
cityCode2 = geocoderesult.Regeocode.AddressComponent.Citycode
|
|
var wg sync.WaitGroup
|
|
errs = make(map[string]error)
|
|
wg.Add(4)
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
// 打印堆栈信息用于调试
|
|
if stack := debug.Stack(); len(stack) > 0 {
|
|
log.Errorf("Stack trace: %s", string(stack))
|
|
}
|
|
}
|
|
}()
|
|
defer wg.Done()
|
|
if direction_driving, err = amap.DirectionForDriving(origins, destination); err != nil {
|
|
errlock.Lock()
|
|
errs["driving"] = err
|
|
errlock.Unlock()
|
|
}
|
|
}()
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
// 打印堆栈信息用于调试
|
|
if stack := debug.Stack(); len(stack) > 0 {
|
|
log.Errorf("Stack trace: %s", string(stack))
|
|
}
|
|
}
|
|
}()
|
|
defer wg.Done()
|
|
if direction_walking, err = amap.DirectionForWalking(origins, destination); err != nil {
|
|
errlock.Lock()
|
|
errs["walking"] = err
|
|
errlock.Unlock()
|
|
}
|
|
}()
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
// 打印堆栈信息用于调试
|
|
if stack := debug.Stack(); len(stack) > 0 {
|
|
log.Errorf("Stack trace: %s", string(stack))
|
|
}
|
|
}
|
|
}()
|
|
defer wg.Done()
|
|
if direction_bicycling, err = amap.DirectionForBicycling(origins, destination); err != nil {
|
|
errlock.Lock()
|
|
errs["bicycling"] = err
|
|
errlock.Unlock()
|
|
}
|
|
}()
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
// 打印堆栈信息用于调试
|
|
if stack := debug.Stack(); len(stack) > 0 {
|
|
log.Errorf("Stack trace: %s", string(stack))
|
|
}
|
|
}
|
|
}()
|
|
defer wg.Done()
|
|
if direction_tsransit, err = amap.DirectionForTsransit(origins, destination, cityCode1, cityCode2); err != nil {
|
|
errlock.Lock()
|
|
errs["transit"] = err
|
|
errlock.Unlock()
|
|
}
|
|
}()
|
|
wg.Wait()
|
|
// 任意一种路径规划成功则认为成功
|
|
if direction_driving != nil || direction_walking != nil || direction_bicycling != nil || direction_tsransit != nil {
|
|
ok = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !ok {
|
|
context = "导航失败,请检查起点和终点是否正确"
|
|
this.module.Error("导航失败,请检查起点和终点是否正确", log.Field{Key: "city1", Value: city1},
|
|
log.Field{Key: "location", Value: location},
|
|
log.Field{Key: "city2", Value: city2},
|
|
log.Field{Key: "address", Value: destinationaddress},
|
|
log.Field{Key: "georesult", Value: georesult},
|
|
log.Field{Key: "err", Value: err},
|
|
)
|
|
// 返回非空错误避免上层对nil结果解引用造成panic
|
|
err = fmt.Errorf("导航失败")
|
|
return
|
|
} else {
|
|
card = &NavigationCard{
|
|
Origin: originddress,
|
|
Destination: destination_address,
|
|
Start: origins,
|
|
End: destination,
|
|
Start_longitude: o_longitude,
|
|
Start_latitude: o_latitude,
|
|
End_longitude: d_longitude,
|
|
End_latitude: d_latitude,
|
|
Itmes: make([]NavigationItem, 4),
|
|
}
|
|
card.Itmes[0] = pathTransNavigationItem("driving", direction_driving)
|
|
card.Itmes[1] = pathTransNavigationItem("walking", direction_walking)
|
|
card.Itmes[2] = pathTransNavigationItem("cycling", direction_bicycling)
|
|
card.Itmes[3] = transitTransNavigationItem("transit", direction_tsransit)
|
|
context = utils.ToString(map[string]interface{}{
|
|
"iscard": true,
|
|
"broadcast": false,
|
|
"card_navigation": card,
|
|
"result": fmt.Sprintf("正在为您导航到:%s", originddress),
|
|
})
|
|
this.module.Debug("导航成功",
|
|
log.Field{Key: "origin", Value: originddress},
|
|
log.Field{Key: "address", Value: destinationaddress},
|
|
log.Field{Key: "card", Value: card},
|
|
)
|
|
}
|
|
result = &mcp.CallToolResult{
|
|
Content: []mcp.Content{
|
|
mcp.TextContent{
|
|
Type: "text",
|
|
Text: context,
|
|
},
|
|
},
|
|
}
|
|
return
|
|
}
|
|
|
|
func pathTransNavigationItem(ntype string, root *amap.RootResponse) (item NavigationItem) {
|
|
item.Ntype = ntype
|
|
if root == nil || root.Route == nil || root.Route.Paths == nil || len(root.Route.Paths) == 0 {
|
|
return
|
|
}
|
|
path := root.Route.Paths[0]
|
|
item = NavigationItem{
|
|
Ntype: ntype,
|
|
Enable: true,
|
|
Image: path.Image,
|
|
Distance: path.Distance,
|
|
Duration: path.Cost.Duration,
|
|
}
|
|
if ntype == "cycling" { // 骑行类型 数据和其他的不一样
|
|
item.Duration = path.Duration // 骑行的耗时
|
|
}
|
|
item.Path = make([]struct {
|
|
Action string `json:"action"` //动作 右转
|
|
Distance string `json:"distance"` //距离 距离
|
|
}, len(path.Steps))
|
|
for i, v := range path.Steps {
|
|
item.Path[i].Action = v.Navi.Action
|
|
item.Path[i].Distance = fmt.Sprintf("%v", v.StepDistance)
|
|
}
|
|
return
|
|
}
|
|
|
|
func transitTransNavigationItem(ntype string, root *amap.TsransitResponse) (item NavigationItem) {
|
|
item.Ntype = ntype
|
|
if root == nil || root.Route == nil || root.Route.Transits == nil || len(root.Route.Transits) == 0 {
|
|
return
|
|
}
|
|
path := root.Route.Transits[0]
|
|
item = NavigationItem{
|
|
Ntype: ntype,
|
|
Enable: true,
|
|
Image: path.Image,
|
|
Distance: path.Distance,
|
|
Duration: path.Cost.Duration,
|
|
}
|
|
item.Transits = make([]struct {
|
|
Name string `json:"name"` //搅动方式
|
|
Distance string `json:"distance"` //距离
|
|
}, len(path.Segments))
|
|
for i, v := range path.Segments {
|
|
if v.Bus != nil {
|
|
item.Transits[i].Name = v.Bus.Buslines[0].Name
|
|
} else if v.Walking != nil {
|
|
item.Transits[i].Name = "步行"
|
|
item.Transits[i].Distance = v.Walking.Distance
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|