mirror of
https://github.com/System-End/nephthys.git
synced 2026-04-19 14:17:03 +00:00
Merge branch 'main' into category-tag-improvements
This commit is contained in:
commit
0c9101ec26
8 changed files with 91 additions and 19 deletions
|
|
@ -98,6 +98,11 @@ Note: These steps have to be done by a Workspace Admin (otherwise it will be una
|
|||
LOG_LEVEL_STDERR="WARNING"
|
||||
# Override the log level for OpenTelemetry output
|
||||
LOG_LEVEL_OTEL="WARNING"
|
||||
|
||||
# Optional: Enable stale ticket auto-close
|
||||
# Tickets inactive for this many days will be automatically closed
|
||||
# Leave unset to disable
|
||||
STALE_TICKET_DAYS="7"
|
||||
```
|
||||
|
||||
4. Don't forget to click **Save All Environment Variables**
|
||||
|
|
|
|||
|
|
@ -51,16 +51,23 @@ async def main(_app: Starlette):
|
|||
"cron",
|
||||
hour=14,
|
||||
minute=0,
|
||||
day_of_week="mon-fri",
|
||||
timezone="Europe/London",
|
||||
)
|
||||
|
||||
# scheduler.add_job(
|
||||
# close_stale_tickets,
|
||||
# "interval",
|
||||
# hours=1,
|
||||
# max_instances=1,
|
||||
# next_run_time=datetime.now(),
|
||||
# )
|
||||
from nephthys.tasks.close_stale import close_stale_tickets
|
||||
from datetime import datetime
|
||||
|
||||
if env.stale_ticket_days:
|
||||
scheduler.add_job(
|
||||
close_stale_tickets,
|
||||
"interval",
|
||||
hours=1,
|
||||
max_instances=1,
|
||||
next_run_time=datetime.now(),
|
||||
)
|
||||
else:
|
||||
logging.debug("Stale ticket closing has not been configured")
|
||||
scheduler.start()
|
||||
|
||||
delete_msg_task = asyncio.create_task(process_queue())
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ async def generate_ticket_title(text: str):
|
|||
if not env.ai_client:
|
||||
return "No title available from AI."
|
||||
|
||||
model = "qwen/qwen3-32b"
|
||||
model = "openai/gpt-oss-120b"
|
||||
try:
|
||||
response = await env.ai_client.chat.completions.create(
|
||||
model=model,
|
||||
|
|
@ -358,8 +358,11 @@ async def generate_ticket_title(text: str):
|
|||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"You are a helpful assistant that helps organise tickets for Hack Club's support team. You're going to take in a message and give it a title. "
|
||||
"You will return no other content. Do *NOT* use title case. Avoid quote marks. Even if it's silly please summarise it. Use no more than 7 words, but as few as possible."
|
||||
"You are a helpful assistant that helps organise tickets for Hack Club's support team. You're going to take in a message and give it a title."
|
||||
"You will return no other content. Do NOT use title case but use capital letter at start of sentence + use capital letters for terms/proper nouns."
|
||||
"Avoid quote marks. Even if it's silly please summarise it. Use no more than 7 words, but as few as possible"
|
||||
"When mentioning Flavortown, do *NOT* change it to 'flavor town' or 'flavour town'. Hack Club should *NOT* be changed to 'hackclub'."
|
||||
"Hackatime, Flavortown, and Hack Club should always be capitalized correctly. Same goes for terms like VSCode, PyCharm, API, and GitHub."
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from nephthys.utils.logging import send_heartbeat
|
|||
from prisma.enums import TicketStatus
|
||||
|
||||
|
||||
async def get_is_stale(ts: str, max_retries: int = 3) -> bool:
|
||||
async def get_is_stale(ts: str, stale_ticket_days: int, max_retries: int = 3) -> bool:
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
replies = await env.slack_client.conversations_replies(
|
||||
|
|
@ -28,7 +28,7 @@ async def get_is_stale(ts: str, max_retries: int = 3) -> bool:
|
|||
return (
|
||||
datetime.now(tz=timezone.utc)
|
||||
- datetime.fromtimestamp(float(last_reply["ts"]), tz=timezone.utc)
|
||||
) > timedelta(days=3)
|
||||
) > timedelta(days=stale_ticket_days)
|
||||
except SlackApiError as e:
|
||||
if e.response["error"] == "ratelimited":
|
||||
retry_after = int(e.response.headers.get("Retry-After", 1))
|
||||
|
|
@ -82,12 +82,22 @@ async def get_is_stale(ts: str, max_retries: int = 3) -> bool:
|
|||
|
||||
async def close_stale_tickets():
|
||||
"""
|
||||
Closes tickets that have been open for more than 3 days.
|
||||
This task is intended to be run periodically.
|
||||
Closes tickets that have been inactive for more than the configured number of days,
|
||||
based on the timestamp of the last message in the ticket's Slack thread.
|
||||
|
||||
Configure via the STALE_TICKET_DAYS environment variable.
|
||||
This task is intended to be run periodically (e.g., hourly).
|
||||
"""
|
||||
|
||||
logging.info("Closing stale tickets...")
|
||||
await send_heartbeat("Closing stale tickets...")
|
||||
stale_ticket_days = env.stale_ticket_days
|
||||
if not stale_ticket_days:
|
||||
logging.warning("Skipping ticket auto-close (STALE_TICKET_DAYS not set)")
|
||||
return
|
||||
|
||||
logging.info(f"Closing stale tickets, threshold_days={stale_ticket_days}")
|
||||
await send_heartbeat(
|
||||
f"Closing stale tickets (threshold: {stale_ticket_days} days)..."
|
||||
)
|
||||
|
||||
try:
|
||||
tickets = await env.db.ticket.find_many(
|
||||
|
|
@ -101,13 +111,13 @@ async def close_stale_tickets():
|
|||
for i in range(0, len(tickets), batch_size):
|
||||
batch = tickets[i : i + batch_size]
|
||||
logging.info(
|
||||
f"Processing batch {i // batch_size + 1}/{(len(tickets) + batch_size - 1) // batch_size}"
|
||||
f"Processing stale tickets batch={i // batch_size + 1} batches={(len(tickets) + batch_size - 1) // batch_size}"
|
||||
)
|
||||
|
||||
for ticket in batch:
|
||||
await asyncio.sleep(1.2) # Rate limiting delay
|
||||
|
||||
if await get_is_stale(ticket.msgTs):
|
||||
if await get_is_stale(ticket.msgTs, stale_ticket_days):
|
||||
stale += 1
|
||||
resolver_user = (
|
||||
ticket.assignedTo if ticket.assignedTo else ticket.openedBy
|
||||
|
|
@ -130,7 +140,7 @@ async def close_stale_tickets():
|
|||
|
||||
await send_heartbeat(f"Closed {stale} stale tickets.")
|
||||
|
||||
logging.info(f"Closed {stale} stale tickets.")
|
||||
logging.info(f"Closed stale tickets. count={stale}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error closing stale tickets: {e}")
|
||||
await send_heartbeat(f"Error closing stale tickets: {e}")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from nephthys.transcripts.transcripts.construct import Construct
|
|||
from nephthys.transcripts.transcripts.fallout import Fallout
|
||||
from nephthys.transcripts.transcripts.flavortown import Flavortown
|
||||
from nephthys.transcripts.transcripts.hctg import Hctg
|
||||
from nephthys.transcripts.transcripts.help import Help
|
||||
from nephthys.transcripts.transcripts.identity import Identity
|
||||
from nephthys.transcripts.transcripts.jumpstart import Jumpstart
|
||||
from nephthys.transcripts.transcripts.lynx import Lynx
|
||||
|
|
@ -24,4 +25,5 @@ transcripts: List[Type[Transcript]] = [
|
|||
Hctg,
|
||||
Fallout,
|
||||
Lynx,
|
||||
Help,
|
||||
]
|
||||
|
|
|
|||
24
nephthys/transcripts/transcripts/help.py
Normal file
24
nephthys/transcripts/transcripts/help.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from nephthys.transcripts.transcript import Transcript
|
||||
|
||||
|
||||
class Help(Transcript):
|
||||
"""Transcript for help"""
|
||||
|
||||
program_name: str = "help"
|
||||
program_owner: str = "U07F6FMJ97U"
|
||||
|
||||
help_channel: str = "C07TM4C0AQ5" # help
|
||||
ticket_channel: str = "C0AS7CGTK8W" # help-ticket
|
||||
team_channel: str = "C0APKHZG495" # owners-of-help-channel
|
||||
|
||||
faq_link: str = "https://hackclub.enterprise.slack.com/docs/T0266FRGM/F0ARLNM3A1E"
|
||||
|
||||
first_ticket_create: str = f"""
|
||||
hi (user)! seems like it's your first time, welcome to the help channel! someone will be here soon to help answer your question! for now, feel free to look at the <{faq_link}|faq>, it answers some common questions and gives basic information.
|
||||
if your question has been answered, please hit the button below to mark it as resolved ^-^
|
||||
"""
|
||||
ticket_create: str = f"someone will be here soon to help answer your question! for now, feel free to look at the <{faq_link}|faq>, it answers some common questions and gives basic information. if your question has been answered, please hit the button below to mark it as resolved ^-^"
|
||||
resolve_ticket_button: str = "i get it now"
|
||||
ticket_resolve: str = f"oh, oh! it looks like this post has been marked as resolved by <@{{user_id}}>! if you have any more questions, please make a new post in <#{help_channel}> and someone'll be happy to help you out! not me though, i'm just a silly racoon ^-^"
|
||||
|
||||
not_allowed_channel: str = f"heyo! doesn't seem like you're supposed to be in that channel, please reach out to <@{program_owner}> if that's wrong!"
|
||||
|
|
@ -63,6 +63,25 @@ class Environment:
|
|||
|
||||
self.slack_heartbeat_channel = os.environ.get("SLACK_HEARTBEAT_CHANNEL")
|
||||
|
||||
# Stale ticket auto-close: number of days of inactivity before closing
|
||||
# Set to a positive integer to enable, leave unset to disable
|
||||
stale_days_str = os.environ.get("STALE_TICKET_DAYS")
|
||||
if stale_days_str:
|
||||
try:
|
||||
stale_days = int(stale_days_str)
|
||||
self.stale_ticket_days = stale_days if stale_days > 0 else None
|
||||
if stale_days <= 0:
|
||||
logging.warning(
|
||||
f"STALE_TICKET_DAYS must be positive, got {stale_days}. Disabling."
|
||||
)
|
||||
except ValueError:
|
||||
logging.warning(
|
||||
f"Invalid STALE_TICKET_DAYS value: {stale_days_str}. Disabling."
|
||||
)
|
||||
self.stale_ticket_days = None
|
||||
else:
|
||||
self.stale_ticket_days = None
|
||||
|
||||
unset = [key for key, value in self.__dict__.items() if value == "unset"]
|
||||
|
||||
if unset:
|
||||
|
|
|
|||
|
|
@ -146,3 +146,5 @@ model CategoryTag {
|
|||
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue