kimi.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package kimi
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "git.ttmylife.com/zeuszhao/llm"
  7. "github.com/go-rod/rod"
  8. "github.com/go-rod/rod/lib/launcher"
  9. "github.com/go-rod/rod/lib/proto"
  10. "time"
  11. )
  12. type Kimi struct {
  13. userContext *llm.UserContext
  14. browser *rod.Browser
  15. launcher *launcher.Launcher
  16. loginEvent func(ctx context.Context, bytes []byte) error
  17. loginCallback func(ctx context.Context, user *llm.UserContext) error
  18. }
  19. func (k *Kimi) WithLoginEvent(f func(ctx context.Context, bytes []byte) error) llm.UserBrowserAgent {
  20. k.loginEvent = f
  21. return k
  22. }
  23. func (k *Kimi) WithLoginCallback(f func(ctx context.Context, user *llm.UserContext) error) llm.UserBrowserAgent {
  24. k.loginCallback = f
  25. return k
  26. }
  27. func (k *Kimi) WithUserContext(user *llm.UserContext) llm.UserBrowserAgent {
  28. k.userContext = user
  29. return k
  30. }
  31. func (k *Kimi) IsLogin(ctx context.Context, page *rod.Page) (bool, error) {
  32. _, err := page.Timeout(3 * time.Second).Element("div[data-testid='msh-header-user-avatar']")
  33. if err != nil && !errors.Is(err, context.DeadlineExceeded) {
  34. return false, err
  35. }
  36. if errors.Is(err, context.DeadlineExceeded) {
  37. return false, nil
  38. }
  39. return true, nil
  40. }
  41. func (k *Kimi) Login(ctx context.Context, page *rod.Page) error {
  42. loginEle, err := page.Element("div[data-testid='msh-sidebar-user']")
  43. if err != nil {
  44. return err
  45. }
  46. err = loginEle.Click(proto.InputMouseButtonLeft, 1)
  47. if err != nil {
  48. return err
  49. }
  50. qrcodePreEle, err := page.ElementR("div p", "微信扫码登录")
  51. if err != nil {
  52. return err
  53. }
  54. qrcodeBox, err := qrcodePreEle.Parent()
  55. if err != nil {
  56. return err
  57. }
  58. screenshot, err := qrcodeBox.Screenshot(proto.PageCaptureScreenshotFormatPng, 10)
  59. if err != nil {
  60. return err
  61. }
  62. err = k.loginEvent(ctx, screenshot)
  63. if err != nil {
  64. return err
  65. }
  66. _, err = page.Element("div[data-testid='msh-header-user-avatar']")
  67. if err != nil {
  68. return err
  69. }
  70. localStorage := page.MustEval(`k => Object.assign({}, window.localStorage)`).Map()
  71. localStorageMap := make(map[string]string)
  72. for k, v := range localStorage {
  73. localStorageMap[k] = v.String()
  74. }
  75. k.userContext = llm.NewUserContext().WithLocalStorage(k.GetName(), localStorageMap).WithCookies(k.GetName(), proto.CookiesToParams(page.MustCookies()))
  76. err = k.loginCallback(ctx, k.userContext)
  77. if err != nil {
  78. return err
  79. }
  80. return nil
  81. }
  82. func (k *Kimi) GetName() string {
  83. return "kimi"
  84. }
  85. func (k *Kimi) NewChat(ctx context.Context) (llm.Chat, error) {
  86. c := &Chat{
  87. ai: k,
  88. Page: k.browser.Context(ctx).MustIncognito().MustPage(),
  89. qaSingle: make(chan bool),
  90. }
  91. wait := c.Page.MustWaitNavigation()
  92. // hook
  93. router := c.Page.HijackRequests()
  94. router.MustAdd("*.mp4", func(hijack *rod.Hijack) {
  95. if hijack.Request.Type() == proto.NetworkResourceTypeMedia {
  96. hijack.Response.Fail(proto.NetworkErrorReasonBlockedByClient)
  97. return
  98. }
  99. hijack.ContinueRequest(&proto.FetchContinueRequest{})
  100. })
  101. router.MustAdd("*/completion/stream", func(hijack *rod.Hijack) {
  102. hijack.MustLoadResponse()
  103. c.qaSingle <- true
  104. })
  105. go router.Run()
  106. err := c.Page.Navigate("https://kimi.moonshot.cn/")
  107. if err != nil {
  108. return nil, err
  109. }
  110. if k.userContext != nil {
  111. localStorage, ok := k.userContext.LocalStorage[k.GetName()]
  112. if ok {
  113. for key, val := range localStorage {
  114. c.Page.MustEval(fmt.Sprintf("() => {window.localStorage.setItem('%s','%s')}", key, val))
  115. }
  116. }
  117. }
  118. wait()
  119. return c, nil
  120. }
  121. func (k *Kimi) Close(ctx context.Context) error {
  122. k.launcher.Kill()
  123. return nil
  124. }
  125. func (k *Kimi) Init(ctx context.Context, debug bool) (llm.AI, error) {
  126. path, ok := launcher.LookPath()
  127. if !ok {
  128. return nil, errors.New("浏览器未找到")
  129. }
  130. launcherHandler := launcher.New().
  131. Bin(path).
  132. HeadlessNew(!debug).
  133. Set("disable-gpu").
  134. Devtools(debug)
  135. browser := rod.New().ControlURL(launcherHandler.MustLaunch()).MustConnect()
  136. return &Kimi{
  137. browser: browser,
  138. launcher: launcherHandler,
  139. }, nil
  140. }
  141. func NewKimi() llm.AI {
  142. return &Kimi{}
  143. }