plan.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. from typing import List
  2. OPEN_STATE = 'open'
  3. COMPLETED_STATE = 'completed'
  4. ABANDONED_STATE = 'abandoned'
  5. IN_PROGRESS_STATE = 'in_progress'
  6. VERIFIED_STATE = 'verified'
  7. STATES = [OPEN_STATE, COMPLETED_STATE, ABANDONED_STATE, IN_PROGRESS_STATE, VERIFIED_STATE]
  8. class Task:
  9. id: str
  10. goal: str
  11. parent: "Task | None"
  12. subtasks: List["Task"]
  13. def __init__(self, parent: "Task | None", goal: str, state: str=OPEN_STATE, subtasks: List = []):
  14. """Initializes a new instance of the Task class.
  15. Args:
  16. parent: The parent task, or None if it is the root task.
  17. goal: The goal of the task.
  18. state: The initial state of the task.
  19. subtasks: A list of subtasks associated with this task.
  20. """
  21. if parent is None:
  22. self.id = '0'
  23. else:
  24. self.id = parent.id + '.' + str(len(parent.subtasks))
  25. self.parent = parent
  26. self.goal = goal
  27. self.subtasks = []
  28. for subtask in (subtasks or []):
  29. if isinstance(subtask, Task):
  30. self.subtasks.append(subtask)
  31. else:
  32. goal = subtask.get('goal')
  33. state = subtask.get('state')
  34. subtasks = subtask.get('subtasks')
  35. self.subtasks.append(Task(self, goal, state, subtasks))
  36. self.state = OPEN_STATE
  37. def to_string(self, indent=""):
  38. """Returns a string representation of the task and its subtasks.
  39. Args:
  40. indent: The indentation string for formatting the output.
  41. Returns:
  42. A string representation of the task and its subtasks.
  43. """
  44. emoji = ''
  45. if self.state == VERIFIED_STATE:
  46. emoji = '✅'
  47. elif self.state == COMPLETED_STATE:
  48. emoji = '🟢'
  49. elif self.state == ABANDONED_STATE:
  50. emoji = '❌'
  51. elif self.state == IN_PROGRESS_STATE:
  52. emoji = '💪'
  53. elif self.state == OPEN_STATE:
  54. emoji = '🔵'
  55. result = indent + emoji + ' ' + self.id + ' ' + self.goal + '\n'
  56. for subtask in self.subtasks:
  57. result += subtask.to_string(indent + ' ')
  58. return result
  59. def to_dict(self):
  60. """Returns a dictionary representation of the task.
  61. Returns:
  62. A dictionary containing the task's attributes.
  63. """
  64. return {
  65. 'id': self.id,
  66. 'goal': self.goal,
  67. 'state': self.state,
  68. 'subtasks': [t.to_dict() for t in self.subtasks]
  69. }
  70. def set_state(self, state):
  71. """Sets the state of the task and its subtasks.
  72. Args: state: The new state of the task.
  73. Raises:
  74. ValueError: If the provided state is invalid.
  75. """
  76. if state not in STATES:
  77. raise ValueError('Invalid state:' + state)
  78. self.state = state
  79. if state == COMPLETED_STATE or state == ABANDONED_STATE or state == VERIFIED_STATE:
  80. for subtask in self.subtasks:
  81. if subtask.state != ABANDONED_STATE:
  82. subtask.set_state(state)
  83. elif state == IN_PROGRESS_STATE:
  84. if self.parent is not None:
  85. self.parent.set_state(state)
  86. def get_current_task(self) -> "Task | None":
  87. """Retrieves the current task in progress.
  88. Returns:
  89. The current task in progress, or None if no task is in progress.
  90. """
  91. for subtask in self.subtasks:
  92. if subtask.state == IN_PROGRESS_STATE:
  93. return subtask.get_current_task()
  94. if self.state == IN_PROGRESS_STATE:
  95. return self
  96. return None
  97. class Plan:
  98. """Represents a plan consisting of tasks.
  99. Attributes:
  100. main_goal: The main goal of the plan.
  101. task: The root task of the plan.
  102. """
  103. main_goal: str
  104. task: Task
  105. def __init__(self, task: str):
  106. """Initializes a new instance of the Plan class.
  107. Args:
  108. task: The main goal of the plan.
  109. """
  110. self.main_goal = task
  111. self.task = Task(parent=None, goal=task, subtasks=[])
  112. def __str__(self):
  113. """Returns a string representation of the plan.
  114. Returns:
  115. A string representation of the plan.
  116. """
  117. return self.task.to_string()
  118. def get_task_by_id(self, id: str) -> Task:
  119. """Retrieves a task by its ID.
  120. Args:
  121. id: The ID of the task.
  122. Returns:
  123. The task with the specified ID.
  124. Raises:
  125. ValueError: If the provided task ID is invalid or does not exist.
  126. """
  127. try:
  128. parts = [int(p) for p in id.split('.')]
  129. except ValueError:
  130. raise ValueError('Invalid task id, non-integer:' + id)
  131. if parts[0] != 0:
  132. raise ValueError('Invalid task id, must start with 0:' + id)
  133. parts = parts[1:]
  134. task = self.task
  135. for part in parts:
  136. if part >= len(task.subtasks):
  137. raise ValueError('Task does not exist:' + id)
  138. task = task.subtasks[part]
  139. return task
  140. def add_subtask(self, parent_id: str, goal: str, subtasks: List = []):
  141. """Adds a subtask to a parent task.
  142. Args:
  143. parent_id: The ID of the parent task.
  144. goal: The goal of the subtask.
  145. subtasks: A list of subtasks associated with the new subtask.
  146. """
  147. parent = self.get_task_by_id(parent_id)
  148. child = Task(parent=parent, goal=goal, subtasks=subtasks)
  149. parent.subtasks.append(child)
  150. def set_subtask_state(self, id: str, state: str):
  151. """Sets the state of a subtask.
  152. Args:
  153. id: The ID of the subtask.
  154. state: The new state of the subtask.
  155. """
  156. task = self.get_task_by_id(id)
  157. task.set_state(state)
  158. def get_current_task(self):
  159. """Retrieves the current task in progress.
  160. Returns:
  161. The current task in progress, or None if no task is in progress.
  162. """
  163. return self.task.get_current_task()