response.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. from __future__ import annotations
  2. import re
  3. from typing import Union
  4. from abc import abstractmethod
  5. from urllib.parse import quote_plus, unquote_plus
  6. def quote_url(url: str) -> str:
  7. url = unquote_plus(url)
  8. url = url.split("//", maxsplit=1)
  9. # If there is no "//" in the URL, then it is a relative URL
  10. if len(url) == 1:
  11. return quote_plus(url[0], '/?&=#')
  12. url[1] = url[1].split("/", maxsplit=1)
  13. # If there is no "/" after the domain, then it is a domain URL
  14. if len(url[1]) == 1:
  15. return url[0] + "//" + url[1][0]
  16. return url[0] + "//" + url[1][0] + "/" + quote_plus(url[1][1], '/?&=#')
  17. def quote_title(title: str) -> str:
  18. if title:
  19. title = title.strip()
  20. title = " ".join(title.split())
  21. return title.replace('[', '').replace(']', '')
  22. return ""
  23. def format_link(url: str, title: str = None) -> str:
  24. if title is None:
  25. title = unquote_plus(url.split("//", maxsplit=1)[1].split("?")[0].replace("www.", ""))
  26. return f"[{quote_title(title)}]({quote_url(url)})"
  27. def format_image(image: str, alt: str, preview: str = None) -> str:
  28. """
  29. Formats the given image as a markdown string.
  30. Args:
  31. image: The image to format.
  32. alt (str): The alt for the image.
  33. preview (str, optional): The preview URL format. Defaults to "{image}?w=200&h=200".
  34. Returns:
  35. str: The formatted markdown string.
  36. """
  37. return f"[![{quote_title(alt)}]({quote_url(preview.replace('{image}', image) if preview else image)})]({quote_url(image)})"
  38. def format_images_markdown(images: Union[str, list], alt: str, preview: Union[str, list] = None) -> str:
  39. """
  40. Formats the given images as a markdown string.
  41. Args:
  42. images: The images to format.
  43. alt (str): The alt for the images.
  44. preview (str, optional): The preview URL format. Defaults to "{image}?w=200&h=200".
  45. Returns:
  46. str: The formatted markdown string.
  47. """
  48. if isinstance(images, list) and len(images) == 1:
  49. images = images[0]
  50. if isinstance(images, str):
  51. result = format_image(images, alt, preview)
  52. else:
  53. result = "\n".join(
  54. format_image(image, f"#{idx+1} {alt}", preview[idx] if isinstance(preview, list) else preview)
  55. for idx, image in enumerate(images)
  56. )
  57. start_flag = "<!-- generated images start -->\n"
  58. end_flag = "<!-- generated images end -->\n"
  59. return f"\n{start_flag}{result}\n{end_flag}\n"
  60. class ResponseType:
  61. @abstractmethod
  62. def __str__(self) -> str:
  63. pass
  64. class JsonMixin:
  65. def __init__(self, **kwargs) -> None:
  66. for key, value in kwargs.items():
  67. setattr(self, key, value)
  68. def get_dict(self):
  69. return {
  70. key: value
  71. for key, value in self.__dict__.items()
  72. if not key.startswith("__")
  73. }
  74. def reset(self):
  75. self.__dict__ = {}
  76. class FinishReason(ResponseType, JsonMixin):
  77. def __init__(self, reason: str, actions: list[str] = None) -> None:
  78. self.reason = reason
  79. self.actions = actions
  80. def __str__(self) -> str:
  81. return ""
  82. class ToolCalls(ResponseType):
  83. def __init__(self, list: list):
  84. self.list = list
  85. def __str__(self) -> str:
  86. return ""
  87. def get_list(self) -> list:
  88. return self.list
  89. class Usage(ResponseType, JsonMixin):
  90. def __str__(self) -> str:
  91. return ""
  92. class AuthResult(JsonMixin):
  93. def __str__(self) -> str:
  94. return ""
  95. class TitleGeneration(ResponseType):
  96. def __init__(self, title: str) -> None:
  97. self.title = title
  98. def __str__(self) -> str:
  99. return ""
  100. class Sources(ResponseType):
  101. def __init__(self, sources: list[dict[str, str]]) -> None:
  102. self.list = []
  103. for source in sources:
  104. self.add_source(source)
  105. def add_source(self, source: dict[str, str]):
  106. url = source.get("url", source.get("link", None))
  107. if url is not None:
  108. url = re.sub(r"[&?]utm_source=.+", "", url)
  109. source["url"] = url
  110. self.list.append(source)
  111. def __str__(self) -> str:
  112. return "\n\n" + ("\n".join([
  113. f"{idx+1}. {format_link(link['url'], link.get('title', None))}"
  114. for idx, link in enumerate(self.list)
  115. ]))
  116. class BaseConversation(ResponseType):
  117. def __str__(self) -> str:
  118. return ""
  119. class JsonConversation(BaseConversation, JsonMixin):
  120. pass
  121. class SynthesizeData(ResponseType, JsonMixin):
  122. def __init__(self, provider: str, data: dict):
  123. self.provider = provider
  124. self.data = data
  125. def __str__(self) -> str:
  126. return ""
  127. class RequestLogin(ResponseType):
  128. def __init__(self, label: str, login_url: str) -> None:
  129. self.label = label
  130. self.login_url = login_url
  131. def __str__(self) -> str:
  132. return format_link(self.login_url, f"[Login to {self.label}]") + "\n\n"
  133. class ImageResponse(ResponseType):
  134. def __init__(
  135. self,
  136. images: Union[str, list],
  137. alt: str,
  138. options: dict = {}
  139. ):
  140. self.images = images
  141. self.alt = alt
  142. self.options = options
  143. def __str__(self) -> str:
  144. return format_images_markdown(self.images, self.alt, self.get("preview"))
  145. def get(self, key: str):
  146. return self.options.get(key)
  147. def get_list(self) -> list[str]:
  148. return [self.images] if isinstance(self.images, str) else self.images
  149. class ImagePreview(ImageResponse):
  150. def __str__(self):
  151. return ""
  152. def to_string(self):
  153. return super().__str__()
  154. class Parameters(ResponseType, JsonMixin):
  155. def __str__(self):
  156. return ""