response.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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) -> None:
  78. self.reason = reason
  79. def __str__(self) -> str:
  80. return ""
  81. class ToolCalls(ResponseType):
  82. def __init__(self, list: list):
  83. self.list = list
  84. def __str__(self) -> str:
  85. return ""
  86. def get_list(self) -> list:
  87. return self.list
  88. class Usage(ResponseType, JsonMixin):
  89. def __str__(self) -> str:
  90. return ""
  91. class AuthResult(JsonMixin):
  92. def __str__(self) -> str:
  93. return ""
  94. class TitleGeneration(ResponseType):
  95. def __init__(self, title: str) -> None:
  96. self.title = title
  97. def __str__(self) -> str:
  98. return ""
  99. class Reasoning(ResponseType):
  100. def __init__(self, token: str = None, status: str = None) -> None:
  101. self.token = token
  102. self.status = status
  103. def __str__(self) -> str:
  104. return "" if self.token is None else self.token
  105. class Sources(ResponseType):
  106. def __init__(self, sources: list[dict[str, str]]) -> None:
  107. self.list = []
  108. for source in sources:
  109. self.add_source(source)
  110. def add_source(self, source: dict[str, str]):
  111. url = source.get("url", source.get("link", None))
  112. if url is not None:
  113. url = re.sub(r"[&?]utm_source=.+", "", url)
  114. source["url"] = url
  115. self.list.append(source)
  116. def __str__(self) -> str:
  117. return "\n\n" + ("\n".join([
  118. f"{idx+1}. {format_link(link['url'], link.get('title', None))}"
  119. for idx, link in enumerate(self.list)
  120. ]))
  121. class BaseConversation(ResponseType):
  122. def __str__(self) -> str:
  123. return ""
  124. class JsonConversation(BaseConversation, JsonMixin):
  125. pass
  126. class SynthesizeData(ResponseType, JsonMixin):
  127. def __init__(self, provider: str, data: dict):
  128. self.provider = provider
  129. self.data = data
  130. def __str__(self) -> str:
  131. return ""
  132. class RequestLogin(ResponseType):
  133. def __init__(self, label: str, login_url: str) -> None:
  134. self.label = label
  135. self.login_url = login_url
  136. def __str__(self) -> str:
  137. return format_link(self.login_url, f"[Login to {self.label}]") + "\n\n"
  138. class ImageResponse(ResponseType):
  139. def __init__(
  140. self,
  141. images: Union[str, list],
  142. alt: str,
  143. options: dict = {}
  144. ):
  145. self.images = images
  146. self.alt = alt
  147. self.options = options
  148. def __str__(self) -> str:
  149. return format_images_markdown(self.images, self.alt, self.get("preview"))
  150. def get(self, key: str):
  151. return self.options.get(key)
  152. def get_list(self) -> list[str]:
  153. return [self.images] if isinstance(self.images, str) else self.images
  154. class ImagePreview(ImageResponse):
  155. def __str__(self):
  156. return ""
  157. def to_string(self):
  158. return super().__str__()
  159. class Parameters(ResponseType, JsonMixin):
  160. def __str__(self):
  161. return ""