first version of EU AI Act game

This commit is contained in:
Anna Hiemenz
2026-05-26 21:46:46 +02:00
commit d370249649
1735 changed files with 79828 additions and 0 deletions
+303
View File
@@ -0,0 +1,303 @@
extends Control
var _llama: GodotLlama
var _streaming_active := false
var _model_name := "Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf"
var _model_path
var _pending_user_text := ""
var _chat_history: Array[Dictionary] = []
var _saved_state_blob := PackedByteArray()
const DEFAULT_SYSTEM_PROMPT := "Antworte auf Deutsch. Antworte kurz und präzise in 1-2 Sätzen nur basierend auf dem bereitgestellten Dokument. Wenn die Antwort nicht im Dokument steht, antworte nur mit: 'Darauf kann ich nicht antworten, bitte stelle mir eine Frage zum EU AI Act'."
const DEFAULT_MAX_HISTORY_TURNS := 0
var DEFAULT_N_CTX := 40096
const DEFAULT_THREADS := 4
const temperature:= 0.1
const top_p:= 0.95
const top_k:= 20
const min_p:= 0.05
const repeat_penalty:= 1.15
const frequency_penalty:= 0.1
const presence_penalty:= 0.0
const penalty_last_n:= 64
const max_tokens := 150
@onready var _prompt_edit: TextEdit = $MarginContainer/Scroll/VBox/PromptEdit
@onready var _generate_button: Button = $MarginContainer/Scroll/VBox/GenerateRow/GenerateButton
@onready var _output_text: RichTextLabel = $MarginContainer/Scroll/VBox/OutputText
@onready var _status_label: Label = $MarginContainer/Scroll/VBox/StatusLabel
@onready var _close_button: Button = $MarginContainer/Scroll/VBox/GenerateRow2/CloseButton
@onready var main: Control = $"."
@onready var option_button: OptionButton = $MarginContainer/Scroll/VBox/OptionButton
@onready var option_label: Label = $MarginContainer/Scroll/VBox/Label
@onready var helper = $VBoxContainer/SubViewportContainer/SubViewport/base_node/helper
var document_id
signal closing_chatbot
func _ready() -> void:
helper.play_idle()
print('local chatbot ready')
_generate_button.pressed.connect(_on_generate_pressed)
_close_button.pressed.connect(_on_close_pressed)
_prompt_edit.connect("input_event", _on_prompt_edit_gui_input)
_llama = GodotLlama.new()
_llama.context.token_generated.connect(_on_token_generated)
_llama.context.generation_finished.connect(_on_generation_finished)
_llama.context.generation_error.connect(_on_generation_error)
document_id = option_button.selected
print(DEFAULT_N_CTX)
_set_status("ready")
var exe_path = OS.get_executable_path()
var exe_dir = exe_path.get_base_dir()
_model_path = exe_dir.path_join("model/%s" % _model_name)
_on_load_model_pressed()
func _unhandled_input(event: InputEvent) -> void:
if not main.visible: return # Ignorieren wenn zu
if event.is_action_pressed("drop"):
get_viewport().set_input_as_handled() # Event stoppen
_on_close_pressed()
if event.is_action_pressed("advance_dialogue"):
get_viewport().set_input_as_handled() # Event stoppen
_on_generate_pressed()
func _on_TextEdit_gui_input(event):
if event is InputEventKey and event.pressed:
if event.keycode == KEY_ENTER and not event.shift_pressed:
_on_generate_pressed()
accept_event() # verhindert neue Zeile
func _on_close_pressed() -> void:
print("close clicked")
main.visible = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
Global.chatbot_opened = false
TextToSpeech._stop_dialogue()
func _on_load_model_pressed() -> void:
if _model_path.is_empty():
_set_status("select a model first")
return
_set_status("loading model...")
var err := _llama.load_model(_model_path)
if err != OK:
_set_status("model load failed: %s" % error_string(err))
return
_set_status("model loaded, creating context...")
_create_context_with_defaults()
func _create_context_with_defaults() -> void:
var params := {
"n_ctx": DEFAULT_N_CTX,
"threads": DEFAULT_THREADS,
"threads_batch": DEFAULT_THREADS,
}
var err := _llama.create_context(params)
if err != OK:
_set_status("create context failed: %s" % error_string(err))
return
_set_status("ready")
func _on_generate_pressed() -> void:
$VBoxContainer/SubViewportContainer/SubViewport/AnimationPlayer.play("walk_helper")
helper.play_think_walk()
print("generating...")
if not _llama.context.is_initialized():
_set_status("create context first")
return
document_id = option_button.selected
var user_text := _prompt_edit.text.strip_edges()
if user_text.is_empty():
_set_status("prompt is empty")
return
# The demo rebuilds the full conversation prompt each turn, so reset context first.
_llama.context.reset()
var prompt := _build_chat_prompt(user_text)
var max_tokens = max_tokens
var params := {
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"min_p": min_p,
"repeat_penalty": repeat_penalty,
"frequency_penalty": frequency_penalty,
"presence_penalty": presence_penalty,
"penalty_last_n": penalty_last_n,
}
var stop_sequences := _parse_stop_sequences("<|eot_id|>")
if not stop_sequences.is_empty():
params["stop_sequences"] = stop_sequences
_output_text.clear()
_pending_user_text = user_text
print("real")
_set_status("generating...")
var result := _llama.generate(prompt, max_tokens, params)
var clean_result := _clean_assistant_output(result)
_output_text.text = clean_result
var line = {"text": clean_result}
TextToSpeech._on_dialogue(line)
#_append_chat_turn("user", user_text)
#_append_chat_turn("assistant", clean_result)
_set_status("generation finished")
func _on_clear_memory_pressed() -> void:
_chat_history.clear()
_pending_user_text = ""
_set_status("memory cleared")
func _on_clear_kv_pressed() -> void:
if not _llama.context.is_initialized():
_set_status("context not initialized")
return
_llama.context.clear_kv_cache()
_set_status("kv cache cleared")
func _on_save_state_memory_pressed() -> void:
if not _llama.context.is_initialized():
_set_status("context not initialized")
return
_saved_state_blob = _llama.context.save_state()
if _saved_state_blob.is_empty():
_set_status("save state blob failed")
return
_set_status("state blob saved (%d bytes)" % _saved_state_blob.size())
func _on_load_state_memory_pressed() -> void:
if not _llama.context.is_initialized():
_set_status("context not initialized")
return
if _saved_state_blob.is_empty():
_set_status("no saved state blob")
return
var err := _llama.context.load_state(_saved_state_blob)
if err != OK:
_set_status("load state blob failed: %s" % error_string(err))
return
_set_status("state blob loaded")
func _on_token_generated(token_text: String, _token_id: int) -> void:
if _streaming_active:
_output_text.text += token_text
func _on_generation_finished(_full_text: String) -> void:
$VBoxContainer/SubViewportContainer/SubViewport/AnimationPlayer.stop()
helper.play_idle()
if _streaming_active:
var clean_text := _clean_assistant_output(_full_text)
_output_text.text = clean_text
#_append_chat_turn("user", _pending_user_text)
#_append_chat_turn("assistant", clean_text)
_streaming_active = false
_set_status("generation finished")
func _on_generation_error(message: String) -> void:
_streaming_active = false
_set_status("generation error: %s" % message)
func _set_status(message: String) -> void:
_status_label.text = "Status: %s" % message
func _load_txt(path: String) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
_set_status("file open failed: %s" % error_string(FileAccess.get_open_error()))
return ""
var content := file.get_as_text()
file.close()
return content
func _build_chat_prompt(user_text: String) -> String:
var parts := PackedStringArray()
var clean_system := DEFAULT_SYSTEM_PROMPT.strip_edges()
# Llama 3 Header
parts.append("<|begin_of_text|>")
# system prompt
var system_combined := ""
if not clean_system.is_empty():
system_combined += clean_system
if not system_combined.is_empty():
parts.append("<|start_header_id|>system<|end_header_id|>\n\n")
parts.append(system_combined)
parts.append("<|eot_id|>")
var document
document = _load_txt("res://addons/godot_llama/demo/documents/%s.md" % [document_id]).strip_edges()
# user-message with document
parts.append("<|start_header_id|>user<|end_header_id|>\n\n")
if not document.is_empty():
parts.append("Dokument:\n%s\n\nFrage: " % document)
parts.append(user_text.strip_edges())
parts.append("<|eot_id|>")
parts.append("<|start_header_id|>assistant<|end_header_id|>\n\n")
print("".join(parts))
return "".join(parts)
func _append_chat_turn(role: String, content: String) -> void:
var clean := content.strip_edges()
if clean.is_empty():
return
_chat_history.append({
"role": role,
"content": clean,
})
_chat_history.clear()
return
func _clean_assistant_output(text: String) -> String:
var out := text
var end_idx := out.find("<|")
if end_idx >= 0:
out = out.substr(0, end_idx)
out = out.replace("<|start_header_id|>assistant<|end_header_id|>", "")
out = out.replace("<|begin_of_text|>", "")
out = out.replace("<|end_of_text|>", "")
return out.strip_edges()
func _parse_stop_sequences(raw: String) -> PackedStringArray:
var stops := PackedStringArray()
var parts := raw.split(",")
for part in parts:
var clean := String(part).strip_edges()
if not clean.is_empty():
stops.append(clean)
return stops
func _on_close_button_pressed() -> void:
main.visible = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
TextToSpeech._stop_dialogue()
Global.chatbot_opened = false
TextToSpeech._stop_dialogue()
closing_chatbot.emit()
func _on_prompt_edit_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
print(event.keycode)
if event.keycode == KEY_ENTER:
if event.shift_pressed:
_prompt_edit.insert_text_at_caret("\n")
else:
_on_generate_pressed()
accept_event()