restoration of b-2-iv and fixes into c-1-i
This commit is contained in:
450
chatchart-v-b-2-iv-final.py
Normal file
450
chatchart-v-b-2-iv-final.py
Normal file
@@ -0,0 +1,450 @@
|
||||
from rich import print as pr
|
||||
from rich.layout import Layout
|
||||
from rich.panel import Panel
|
||||
import termcharts
|
||||
import ollama
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
import re
|
||||
import heapq
|
||||
import time
|
||||
import sys
|
||||
|
||||
cc_version = "ITA B-2-iv Final"
|
||||
|
||||
# P7MJ's ITA (In the Air) chat mood analyzer, as a YAY project between 3/2 and 3/3 and finished on 3/3 (A-2-i). No longer so yay...
|
||||
# Version B-1-i Refining (aka B-2-i Testing) completed on 3/5
|
||||
# Working on B-1-i 3/6 and 3/8. Procrastinating between. A-series, get ready to be lost in time, but i still have some copies
|
||||
# B-2-i Testing testing on 3/8. Prelim testing completed.
|
||||
# B-2-ii Final working on 3/8. Removing all TEST comments and running more test cases
|
||||
# B-2-iii Final working on 3/9 Morning. Added a safeguard in case the LLM hallucinates giving the super_llm_rate.
|
||||
# B-2-iv Final working on 3/9 Morning. Fixed and added static colors (then removed them)
|
||||
# Presenting is 3/9
|
||||
# TODO Add async I DONT KNOW WHEN DONT ASK ME BYE THE EnD
|
||||
# TODO Add functions super_llm_rate for all-at-once YAY and batch_llm_rate for 5 at one time YAY
|
||||
# TODO Integrate readable_format() YAY
|
||||
# In competition with Sean. He's doing much better... NOOOO
|
||||
# I HATE the "pg up" and "pg down" buttons above the left and right arrow keys! I always misclick them and my page jumps!
|
||||
|
||||
# WRITING LETTER TO H. COMBINED WITH THIS CODE MAKES MY BRAIN BURN
|
||||
# Coding: ❌
|
||||
# Breaking heart: ✔️
|
||||
# Burning brain: ✔️
|
||||
# Feeling like quitting: ✔️
|
||||
|
||||
# STATS VS OTHER VERSIONS: (ABORTED)
|
||||
# Positive/Negative/Neutral Rating Time
|
||||
|
||||
# GOODLOG
|
||||
# B-2-i/ii: 0/11/19 -0.307 89
|
||||
# B-1-i:
|
||||
|
||||
rating_data = []
|
||||
total_messages = 0
|
||||
messages_rated = 0
|
||||
posanslist = []
|
||||
neganslist = []
|
||||
chat_summary = ""
|
||||
chat_message_list = ""
|
||||
|
||||
# Find indices of 5 ratings with largest value
|
||||
def get_n_largest_indices(data, n):
|
||||
largest_items = heapq.nlargest(n, enumerate(data), key=lambda x: x[1])
|
||||
indices = [index for index, value in largest_items]
|
||||
return indices
|
||||
|
||||
# Make single or more messages readable
|
||||
def readable_format(inputs):
|
||||
processed = "<log>\n"
|
||||
for i in range(len(inputs)):
|
||||
processed += f"{inputs[i][0]}: {inputs[i][1]}\n"
|
||||
processed += "</log>"
|
||||
return processed
|
||||
|
||||
# One readable message
|
||||
def readable_one_message(single_input_list):
|
||||
return f"{single_input_list[0]}: {single_input_list[1]}"
|
||||
|
||||
# Find indices of 5 ratings with smallest value
|
||||
def get_n_smallest_indices(data, n):
|
||||
smallest_items = heapq.nsmallest(n, enumerate(data), key=lambda x: x[1])
|
||||
indices = [index for index, value in smallest_items]
|
||||
return indices
|
||||
|
||||
# Processes the raw chat log into a readable list
|
||||
def parse_chat_log(file_path):
|
||||
# Regex to match the timestamp format: [03/02/2026 00:14]
|
||||
pattern = re.compile(r"\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}\]\s+(.*)")
|
||||
chat_data = []
|
||||
current_user = None
|
||||
current_message = []
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("===") or line.startswith("Exported"):
|
||||
continue
|
||||
match = pattern.match(line)
|
||||
if match:
|
||||
if current_user:
|
||||
chat_data.append([current_user, "\n".join(current_message)])
|
||||
current_user = match.group(1)
|
||||
current_message = []
|
||||
else:
|
||||
current_message.append(line)
|
||||
if current_user:
|
||||
chat_data.append([current_user, "\n".join(current_message)])
|
||||
return chat_data
|
||||
|
||||
def split_thy_list_for_thy_batch_function(inputses):
|
||||
processing = inputses.copy()
|
||||
batches = []
|
||||
if len(inputses) > 5:
|
||||
while True: # i actually mean while the numbers are larger than 5
|
||||
if len(processing) > 5:
|
||||
batches.append(processing[:5])
|
||||
del processing[:5]
|
||||
else:
|
||||
batches.append(processing)
|
||||
break
|
||||
return batches
|
||||
else:
|
||||
batches.append(inputses)
|
||||
return batches
|
||||
|
||||
# Filters and processes the values, replacing text answers (switches to -0.5 because usually in that case the topic is so bad that the LLM refuses to rate it, therefore an automatic rating of -0.5), and 0.00s since the LLM would rather classify bad chats as 0.00 rather than mark it negative
|
||||
def float_filter(value):
|
||||
try:
|
||||
float(value)
|
||||
if float(value) == 0.00:
|
||||
return -0.10
|
||||
else:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return -0.5
|
||||
|
||||
def process_llm_list(input_string):
|
||||
stripped = input_string.strip()
|
||||
splitted = stripped.split(", ")
|
||||
for i in range(len(splitted)):
|
||||
try:
|
||||
splitted[i] = float(splitted[i])
|
||||
except:
|
||||
print(f"E {splitted[i]} not_float")
|
||||
splitted[i] = -2
|
||||
return splitted
|
||||
|
||||
# Uses the LLM to rate messages
|
||||
def llm_rate(user_input, length):
|
||||
global messages_rated
|
||||
response = ollama.chat(model='llama3.2', messages=[
|
||||
# The system prompt DO NOT DELETE
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (
|
||||
"Sentiment Analyzer: Output ONLY a float between -1.0 and 1.0."
|
||||
"Chat message format is user: message. Analyze single message."
|
||||
"Scoring: Hostile/Insulting (-0.8), Dismissive/Sarcastic (-0.5),"
|
||||
"Apathetic/Short (-0.2), Neutral/Functional (0.0), Positive/Link (0.1-1.0)."
|
||||
"No prose."
|
||||
)
|
||||
},
|
||||
|
||||
# The user (DO NOT DELETE)
|
||||
{'role': 'user', 'content': readable_one_message(user_input)}
|
||||
])
|
||||
|
||||
messages_rated += 1 # PLUS ONE TO PROGRESS
|
||||
print(f"\r{messages_rated}/{length} messages rated by LLM.", end = "")
|
||||
# Add the rating data to rating data
|
||||
rating_data.append(response['message']['content'])
|
||||
|
||||
# TODO make it rate all the messages at once
|
||||
def super_llm_rate(inputs):
|
||||
while True:
|
||||
print("LLM All-at-once Rating Mode rating...", end = " ", flush=True)
|
||||
global chatmessagelist
|
||||
response = ollama.chat(model='llama3.2', messages=[
|
||||
# The system prompt DO NOT DELETE
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (
|
||||
"Sentiment Analyzer: Output ONLY a list of floats between -1.0 and 1.0, like '0.0, 0.5, -0.5'."
|
||||
"Chat messages are between <log> and </log>. Analyze the chat records and give rating to each message in order."
|
||||
"Scoring: Hostile/Insulting (-0.8), Dismissive/Sarcastic (-0.5),"
|
||||
"Apathetic/Short (-0.2), Neutral/Functional (0.0), Positive/Link (0.1-1.0)."
|
||||
f"There are {len(chatmessagelist)} messages in total. Make sure you have the same amount of ratings."
|
||||
"Exactly a comma followed by a space between ratings. No prose. Double check amount."
|
||||
)
|
||||
},
|
||||
|
||||
# The user (DO NOT DELETE)
|
||||
{'role': 'user', 'content': readable_format(inputs)}
|
||||
])
|
||||
finished = process_llm_list(response['message']['content'])
|
||||
# Add the rating data to rating data
|
||||
if len(finished) == len(chatmessagelist):
|
||||
for i in range(len(finished)):
|
||||
rating_data.append(finished[i])
|
||||
break
|
||||
else:
|
||||
print("LLM Hallucinated!")
|
||||
|
||||
# Accepts a list of junk! (unprocessed parsed list)
|
||||
def batch_llm_rate(inputs):
|
||||
global messages_rated, chatmessagelist
|
||||
print("Creating batches...", end = " ", flush=True)
|
||||
try:
|
||||
batcheses = split_thy_list_for_thy_batch_function(inputs)
|
||||
print("Success!")
|
||||
except:
|
||||
print("Fail!")
|
||||
sys.exit(0)
|
||||
|
||||
print("Rating...", end = "", flush=True)
|
||||
for i in range(len(batcheses)):
|
||||
that_one_batch = batcheses[i]
|
||||
response = ollama.chat(model='llama3.2', messages=[
|
||||
# The system prompt DO NOT DELETE
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (
|
||||
"Sentiment Analyzer: Output ONLY a list of floats between -1.0 and 1.0, like '0.0, 0.5, -0.5'."
|
||||
"Chat messages are between <log> and </log>. Analyze the chat records and give rating to each message in order."
|
||||
"Scoring: Hostile/Insulting (-0.8), Dismissive/Sarcastic (-0.5),"
|
||||
"Apathetic/Short (-0.2), Neutral/Functional (0.0), Positive/Link (0.1-1.0)."
|
||||
f"There are {len(that_one_batch)} messages in total. Make sure you have {len(that_one_batch)} ratings."
|
||||
"Exactly a comma followed by a space between ratings. No prose. Double check amount."
|
||||
)
|
||||
},
|
||||
|
||||
# The user (DO NOT DELETE)
|
||||
{'role': 'user', 'content': readable_format(that_one_batch)}
|
||||
])
|
||||
finished_list = process_llm_list(response['message']['content'])
|
||||
for i in range(len(finished_list)):
|
||||
rating_data.append(finished_list[i])
|
||||
print("\r" + f"{len(rating_data)}/{len(inputs)} messages rated", end = "")
|
||||
|
||||
# SUMMARIZE EVERYTHING YAYAYAYAAAYAYA
|
||||
def chat_summarizer(lists):
|
||||
global topicsummarizerlist
|
||||
global chat_summary
|
||||
print("Summarizing chat...", end = " ", flush=True)
|
||||
response = ollama.chat(model='llama3.2', messages=[
|
||||
# The system prompt DO NOT DELETE
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (
|
||||
'Chat Summarizer Tool: Sumarize given chat between <log> and </log>. Return brief summary and lists of topics discussed in order. Explain chat flow. Hypothesize user personalities. Use as few lines as possible.'
|
||||
)
|
||||
},
|
||||
|
||||
# The user (DO NOT DELETE)
|
||||
{'role': 'user', 'content': readable_format(lists)}
|
||||
])
|
||||
chat_summary = response['message']['content']
|
||||
print("Success!")
|
||||
|
||||
# Start ITA Agent
|
||||
def start_ita_agent(mood, summary):
|
||||
messages = [
|
||||
{
|
||||
'role': 'system',
|
||||
'content': (f"Sentiment Analysis Tool. Respond to the user's questions on analyzed chat."
|
||||
f"Analyzed data was: mood = {mood} with 0 being average."
|
||||
f"summary: {summary}. Respond in short and concise sentences."
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
print("Type 'exit' or 'quit' to end the session.\n")
|
||||
|
||||
# 2. Loop forever
|
||||
while True:
|
||||
user_input = input("You > ")
|
||||
if user_input.lower() in ['exit', 'quit']:
|
||||
break
|
||||
|
||||
# 3. Add user input to memory
|
||||
messages.append({'role': 'user', 'content': user_input})
|
||||
|
||||
print("ITA Analysis Agent > ", end="", flush=True)
|
||||
|
||||
# 4. Stream the response
|
||||
full_response = ""
|
||||
stream = ollama.chat(model='llama3.2', messages=messages, stream=True)
|
||||
|
||||
for chunk in stream:
|
||||
content = chunk['message']['content']
|
||||
print(content, end="", flush=True)
|
||||
full_response += content
|
||||
|
||||
# 5. Add assistant reply to memory so it stays in context
|
||||
messages.append({'role': 'assistant', 'content': full_response})
|
||||
print() # Add a final newline for formatting
|
||||
|
||||
# Counts answers for statistics chart
|
||||
def countanswers(list):
|
||||
goodanswers = 0
|
||||
badanswers = 0
|
||||
neutralanswers = 0
|
||||
for z in range(len(list)):
|
||||
if list[z] > -0.3 and list[z] < 0.3:
|
||||
neutralanswers += 1
|
||||
elif list[z] > 0.3:
|
||||
goodanswers += 1
|
||||
elif list[z] < -0.3:
|
||||
badanswers += 1
|
||||
return [goodanswers, badanswers, neutralanswers]
|
||||
|
||||
|
||||
# MAIN
|
||||
if __name__ == "__main__":
|
||||
# Chat log choosing function
|
||||
while True:
|
||||
print(f"""
|
||||
{"=" * 80}
|
||||
ITA (In the Air) Mood Interpretor | Version: {cc_version} - P7MJ
|
||||
{"=" * 80}
|
||||
|
||||
Choose a chat log to evaluate
|
||||
[1] goodlog.txt
|
||||
[2] badlog.txt
|
||||
[3] testlog.txt
|
||||
[4] specify""")
|
||||
log_to_choose = input(" > ")
|
||||
if log_to_choose == "1":
|
||||
log_to_choose = "goodlog.txt"
|
||||
break
|
||||
if log_to_choose == "2":
|
||||
log_to_choose = "badlog.txt"
|
||||
break
|
||||
if log_to_choose == "3":
|
||||
log_to_choose = "testlog.txt"
|
||||
break
|
||||
if log_to_choose == "4":
|
||||
log_to_choose = input("Text file name (w/extension) > ")
|
||||
break
|
||||
else:
|
||||
print("Invalid Option!")
|
||||
|
||||
# Start the timer
|
||||
start_time = time.perf_counter()
|
||||
|
||||
# Processes the chat log and stores in variable
|
||||
print("Parsing chat message file...", end = " ")
|
||||
try:
|
||||
chatmessagelist = parse_chat_log(f"{log_to_choose}")
|
||||
except:
|
||||
input("File does not exist! Press enter to exit...")
|
||||
sys.exit(0)
|
||||
print("Complete.")
|
||||
|
||||
while True:
|
||||
rating_mode = input("\nChoose rating mode:\n[1] Individual message rating\n[2] Batches of 5 rating\n[3] All-at-once rating\n > ")
|
||||
try:
|
||||
nonono = int(rating_mode)
|
||||
if nonono <= 3:
|
||||
break
|
||||
else:
|
||||
hi = 3/0
|
||||
except:
|
||||
print("Not an integer or within range!")
|
||||
|
||||
if rating_mode == "1":
|
||||
# Gives the chatbot each message and has it rate it
|
||||
for m in range(len(chatmessagelist)):
|
||||
prompt = str(chatmessagelist[m][1])
|
||||
llm_rate(prompt, len(chatmessagelist))
|
||||
|
||||
elif rating_mode == "2":
|
||||
batch_llm_rate(chatmessagelist)
|
||||
|
||||
elif rating_mode == "3":
|
||||
super_llm_rate(chatmessagelist)
|
||||
|
||||
# Filters and processes rating data
|
||||
for i in range(len(rating_data)):
|
||||
rating_data[i] = float_filter(rating_data[i])
|
||||
|
||||
# Calculates rating mean
|
||||
sum_of_numbers = 0
|
||||
for k in range(len(rating_data)):
|
||||
sum_of_numbers += rating_data[k]
|
||||
sum_of_numbers /= len(rating_data)
|
||||
|
||||
# Finding them
|
||||
list_of_best_answers = get_n_largest_indices(rating_data, 5)
|
||||
list_of_worst_answers = get_n_smallest_indices(rating_data, 5)
|
||||
|
||||
# Calculate best messages and appending to list
|
||||
for j in range(len(list_of_best_answers)):
|
||||
posanslist.append(f"[{j+1}] {chatmessagelist[list_of_best_answers[j]][0]}: {chatmessagelist[list_of_best_answers[j]][1]}")
|
||||
|
||||
# Calculate worst messages and appending to list
|
||||
for k in range(len(list_of_worst_answers)):
|
||||
neganslist.append(f"[{k+1}] {chatmessagelist[list_of_worst_answers[k]][0]}: {chatmessagelist[list_of_worst_answers[k]][1]}")
|
||||
|
||||
|
||||
# Calculate number of results in each catagory
|
||||
stastisticresults = countanswers(rating_data)
|
||||
|
||||
print() # Add a line after the ratings
|
||||
|
||||
# Summarizes everything
|
||||
chat_summarizer((chatmessagelist))
|
||||
|
||||
# End the timer
|
||||
end_time = time.perf_counter()
|
||||
elapsed_time = end_time - start_time
|
||||
|
||||
# Sample data
|
||||
data = {"Positive": stastisticresults[0], "Negative": stastisticresults[1], "Neutral": stastisticresults[2]}
|
||||
chart1 = termcharts.bar(data, title="Chart of message emotions", rich=True)
|
||||
|
||||
# Define the layout
|
||||
layout = Layout()
|
||||
|
||||
# 1. Split into Top and Bottom halves
|
||||
layout.split_column(
|
||||
Layout(name="upper", size=20), # Fixed height for top section
|
||||
Layout(name="lower") # Bottom takes the remainder
|
||||
)
|
||||
|
||||
# 2. Configure Top: 1/4 (25%) for chart, rest split 50/50
|
||||
layout["upper"].split_row(
|
||||
Layout(name="uleft", ratio=1), # 25% of total width (1 part)
|
||||
Layout(name="uright", ratio=3), # 75% of total width (3 parts)
|
||||
)
|
||||
|
||||
# Split the 75% right side into two text panels
|
||||
layout["uright"].split_row(
|
||||
Layout(Panel(f"STATISTICS\n\nRating Average: {round(sum_of_numbers, 3)}\nPositive Ratings: {stastisticresults[0]}\nNegative Ratings: {stastisticresults[1]}\nNeutral Ratings: {stastisticresults[2]}\nTotal messages analyzed: {len(rating_data)}\nTotal time elapsed: {elapsed_time:.4f} seconds")),
|
||||
Layout(Panel(fr"""
|
||||
___ _________ ________
|
||||
|\ \|\___ ___\\ __ \
|
||||
\ \ \|___ \ \_\ \ \|\ \
|
||||
\ \ \ \ \ \ \ \ __ \
|
||||
\ \ \ \ \ \ \ \ \ \ \
|
||||
\ \__\ \ \__\ \ \__\ \__\
|
||||
\|__| \|__| \|__|\|__|
|
||||
|
||||
©️2026 P7MJ
|
||||
All Rights Reserved
|
||||
|
||||
In the Air, {cc_version} by P7MJ
|
||||
Know the mood - and what to say next.
|
||||
"""))
|
||||
)
|
||||
# 3. Configure Bottom: 50/50 split
|
||||
layout["lower"].split_row(
|
||||
Layout(Panel(f"OUTSTANDING MESSAGES\n\nMOST POSITIVE ANSWERS\n{posanslist[0][:200]}\n{posanslist[1][:200]}\n{posanslist[2][:200]}\n{posanslist[3][:200]}\n{posanslist[4][:200]}\n\n\nMOST NEGATIVE ANSWERS\n{neganslist[0][:200]}\n{neganslist[1][:200]}\n{neganslist[2][:200]}\n{neganslist[3][:200]}\n{neganslist[4][:200]}")),
|
||||
Layout(Panel(f"CHAT SUMMARY\n\n{chat_summary}")),
|
||||
)
|
||||
|
||||
# 4. Fill the chart
|
||||
layout["uleft"].update(Panel(chart1))
|
||||
|
||||
pr(layout)
|
||||
input("PRESS ENTER TO LAUNCH ITA CHAT AGENT")
|
||||
start_ita_agent(round(sum_of_numbers, 3), chat_summary)
|
||||
Reference in New Issue
Block a user