diff --git a/assets/antrag_vorlage.docx b/assets/antrag_vorlage.docx index 7e4ce2a..7802c21 100644 Binary files a/assets/antrag_vorlage.docx and b/assets/antrag_vorlage.docx differ diff --git a/meinantrag.py b/meinantrag.py index 6acfe08..0e085a6 100644 --- a/meinantrag.py +++ b/meinantrag.py @@ -258,7 +258,7 @@ Der Antrag soll im sachlichen, offiziellen Ton einer Fraktion verfasst sein - KE Struktur: - Die erste Zeile ist der Antragstitel. Der Titel soll PRÄGNANT, EINFACH und EINPRÄGSAM sein - maximal 8-10 Wörter. Vermeide komplizierte Formulierungen, technische Fachbegriffe oder zu lange Titel. Der Titel soll eine gute Außenwirkung haben und das Anliegen klar und verständlich kommunizieren. Beispiele für gute Titel: "Nachtabsenkung der öffentlichen Straßenbeleuchtung", "Vielfalt in Bewegung – Kulturelle Begleitmaßnahmen World Games 2029", "Prüfung digitaler Zahlungsdienstleister und WERO-Alternative" -- Der zweite Absatz ist der Forderungsteil ("Der Gemeinderat möge beschließen:"). Hier können nach einem kurzen Satz auch Stichpunkte verwendet werden, wenn dies sinnvoll ist. +- Der zweite Absatz ist der Forderungsteil. Hier können nach einem kurzen Satz auch Stichpunkte verwendet werden, wenn dies sinnvoll ist. - Der letzte Teil ist Begründung/Sachverhalt (ohne diesen Titel im Text) WICHTIG: @@ -277,6 +277,41 @@ WICHTIG: # Parse the response parsed = self._parse_gemini_response(generated_text) + # Generate email text + email_prompt = f"""Erstelle einen kurzen, höflichen E-Mail-Text in der ERSTEN PERSON (ich/wir) für eine Fraktion an eine andere Fraktion. +Die E-Mail soll: +- Mit "Guten Tag," beginnen +- Das Anliegen kurz in der ersten Person erklären (basierend auf: {anliegen}) +- Erwähnen, dass eine Antragsvorlage im Anhang beigefügt ist +- Mit "Mit freundlichen Grüßen," enden + +Der Text soll sachlich, höflich und kurz sein (2-3 Sätze zwischen Begrüßung und Grußformel). Verwende KEINE Markdown-Formatierung. Schreibe in der ERSTEN PERSON (z.B. "ich möchte", "wir bitten", "ich habe"). + +Anliegen: {anliegen} +""" + + email_response = self.model.generate_content(email_prompt) + email_text = self._remove_markdown(email_response.text) + + # Ensure proper format - clean up and ensure structure + email_text = email_text.strip() + + # Ensure it starts with "Guten Tag," + if not email_text.startswith('Guten Tag'): + # Remove any existing greeting + email_text = re.sub(r'^(Guten Tag[,\s]*|Hallo[,\s]*|Sehr geehrte[^,]*,\s*)', '', email_text, flags=re.IGNORECASE) + email_text = 'Guten Tag,\n\n' + email_text.strip() + + # Ensure it ends with "Mit freundlichen Grüßen," + if 'Mit freundlichen Grüßen' not in email_text: + email_text += '\n\nMit freundlichen Grüßen,' + else: + # Make sure it's properly formatted + if not email_text.rstrip().endswith('Mit freundlichen Grüßen,'): + # Remove any existing closing and add proper one + email_text = re.sub(r'\s*Mit freundlichen Grüßen[,\s]*$', '', email_text, flags=re.IGNORECASE) + email_text = email_text.rstrip() + '\n\nMit freundlichen Grüßen,' + # Return JSON with the generated text parts resp.content_type = 'application/json' resp.text = json.dumps({ @@ -284,6 +319,7 @@ WICHTIG: 'title': parsed['title'], 'demand': parsed['demand'], 'justification': parsed['justification'], + 'email_body': email_text, 'party_name': party_id if party_id else "" }) @@ -319,8 +355,8 @@ class GenerateWordResource: # Get current date in DD.MM.YYYY format current_date = datetime.now().strftime("%d.%m.%Y") - # Combine demand for ANTRAGSTEXT (with heading) - antragtext = "Der Gemeinderat möge beschließen:\n" + demand + # Use demand directly without heading + antragtext = demand # Replace placeholders in all paragraphs for paragraph in doc.paragraphs: @@ -329,7 +365,7 @@ class GenerateWordResource: continue # Replace FRAKTION - if party_name and 'FRAKTION' in full_text: + if 'FRAKTION' in full_text: for run in paragraph.runs: if 'FRAKTION' in run.text: run.text = run.text.replace('FRAKTION', party_name) @@ -352,9 +388,7 @@ class GenerateWordResource: lines = antragtext.split('\n') for i, line in enumerate(lines): if line.strip(): - run = paragraph.add_run(line.strip()) - if i == 0: # First line (heading) should be bold - run.bold = True + paragraph.add_run(line.strip()) if i < len(lines) - 1: paragraph.add_run('\n') @@ -368,6 +402,43 @@ class GenerateWordResource: if i < len(lines) - 1: paragraph.add_run('\n') + # Check text boxes (shapes) for placeholders + # Text boxes are stored in the document's part relationships + try: + # Access document part to search for text boxes + document_part = doc.part + from docx.oxml.ns import qn + + # Search for FRAKTION in text boxes + # Text boxes are in w:txbxContent elements within w:p (paragraphs) + # We need to search the entire XML tree + def replace_in_element(element, search_text, replace_text): + """Recursively replace text in XML elements""" + if element.text and search_text in element.text: + element.text = element.text.replace(search_text, replace_text) + if element.tail and search_text in element.tail: + element.tail = element.tail.replace(search_text, replace_text) + for child in element: + replace_in_element(child, search_text, replace_text) + + # Search in main document body + if party_name: + replace_in_element(document_part.element, 'FRAKTION', party_name) + + # Also search in header and footer parts + for rel in document_part.rels.values(): + if 'header' in rel.target_ref or 'footer' in rel.target_ref: + try: + header_footer_part = rel.target_part + if party_name: + replace_in_element(header_footer_part.element, 'FRAKTION', party_name) + except Exception: + pass + except Exception as e: + # If text box access fails, continue with other replacements + print(f"Warning: Could not replace in text boxes: {e}") + pass + # Also check tables for placeholders for table in doc.tables: for row in table.rows: @@ -397,9 +468,7 @@ class GenerateWordResource: lines = antragtext.split('\n') for i, line in enumerate(lines): if line.strip(): - run = paragraph.add_run(line.strip()) - if i == 0: - run.bold = True + paragraph.add_run(line.strip()) if i < len(lines) - 1: paragraph.add_run('\n') diff --git a/templates/base.html b/templates/base.html index f2a7a12..a65196a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,7 +4,7 @@ {% block title %}MeinAntrag{% endblock %} - + @@ -30,61 +30,211 @@ {% block meta_extra %}{% endblock %} {% block extra_css %}{% endblock %} - -
-
- {% block content %}{% endblock %} -
- - - -
+ + {% block content %}{% endblock %} + + - - + {% block extra_js %}{% endblock %} - \ No newline at end of file + diff --git a/templates/index.html b/templates/index.html index 85ba159..86f4f7c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,83 +3,90 @@ {% block title %}MeinAntrag{% endblock %} {% block content %} -
-

MeinAntrag

-

- Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung - zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion! -

- -
-
-
- - +
+

MeinAntrag

+

Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!

+
+ +
+ +
    +
  • + + +
    +
  • +

    Fraktion auswählen

    +

    Wähle die Fraktion, an die der Antrag gerichtet ist:

    + +
  • + +
  • +

    Dein Anliegen beschreiben

    +

    Beschreibe hier, welche Anfrage oder Antrag du an die Stadtverwaltung stellen möchtest:

    + +
  • + +
  • + +
-
- - + - -
- -
-
- - - -
+ + + +

+ + + + {% endblock %} {% block extra_js %} @@ -102,8 +109,8 @@ const $btnText = $button.find('.btn-text'); const $loading = $button.find('.loading'); $button.prop('disabled', true); - $btnText.text('Generiere Antrag...'); - $loading.css('display', 'inline-block'); + $btnText.text('Generiere Antrag'); + $loading.css('display', 'inline'); // Prepare form data as URL-encoded const formData = new URLSearchParams(); @@ -135,8 +142,9 @@ $('#forderung').val(data.demand || ''); $('#begruendung').val(data.justification || ''); - // Store party name for mail button + // Store party name and email body for mail button $('#resultFields').data('party-name', data.party_name || ''); + $('#resultFields').data('email-body', data.email_body || ''); // Update mail button text if (data.party_name) { @@ -153,7 +161,7 @@ // Reset button state $button.prop('disabled', false); $btnText.text('Antrag generieren'); - $loading.hide(); + $loading.css('display', 'none'); }) .catch(error => { console.error('Error:', error); @@ -162,7 +170,7 @@ // Reset button state $button.prop('disabled', false); $btnText.text('Antrag generieren'); - $loading.hide(); + $loading.css('display', 'none'); }); }); @@ -189,18 +197,9 @@ const partyName = $('#resultFields').data('party-name') || ''; const email = partyContacts[partyName] || ''; const subject = encodeURIComponent($('#antragstitel').val() || ''); - const title = $('#antragstitel').val() || ''; - const demand = $('#forderung').val() || ''; - const justification = $('#begruendung').val() || ''; + const emailBody = $('#resultFields').data('email-body') || ''; - // Build email body - let body = title + '\n\n'; - body += 'Der Gemeinderat möge beschließen:\n'; - body += demand + '\n\n'; - body += 'Begründung/Sachverhalt\n'; - body += justification; - - const bodyEncoded = encodeURIComponent(body); + const bodyEncoded = encodeURIComponent(emailBody); // Open mail client if (email) { @@ -210,6 +209,38 @@ } }); + // Function to create a valid filename from title + function createFilename(title) { + if (!title || !title.trim()) { + return 'antrag.docx'; + } + + // Remove or replace special characters + let filename = title + .trim() + .toLowerCase() + .replace(/[ä]/g, 'ae') + .replace(/[ö]/g, 'oe') + .replace(/[ü]/g, 'ue') + .replace(/[ß]/g, 'ss') + .replace(/[^a-z0-9\s-]/g, '') // Remove special chars except spaces and hyphens + .replace(/\s+/g, '_') // Replace spaces with underscores + .replace(/_+/g, '_') // Replace multiple underscores with single + .replace(/^_+|_+$/g, ''); // Remove leading/trailing underscores + + // Limit length to 100 characters + if (filename.length > 100) { + filename = filename.substring(0, 100); + } + + // If empty after cleaning, use default + if (!filename) { + return 'antrag.docx'; + } + + return filename + '.docx'; + } + // Handle Word button click $('#wordBtn').on('click', function() { const title = $('#antragstitel').val() || ''; @@ -226,6 +257,9 @@ formData.append('party_name', partyName); } + // Generate filename from title + const filename = createFilename(title); + // Download Word file fetch('/api/generate-word', { method: 'POST', @@ -244,7 +278,7 @@ const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'antrag.docx'; + a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); @@ -257,4 +291,4 @@ }); }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/vorlage.html b/vorlage.html deleted file mode 100644 index 885b26a..0000000 --- a/vorlage.html +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - - -GitHub Pages | Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live. - - - - - - - - - - - - - - - - - - - - -
- - -

Websites for you and your projects.

-

Hosted directly from your GitHub repository. Just edit, push, and your changes are live.

- - Pages Help - -
- Bootstrap -
-
- -
-

Ready to get started? Build your own site from scratch or generate one for your project.

-

You get one site per GitHub account and organization,
and unlimited project sites. Let‘s get started.

- - - - - - - - - - -
- - -
-

Now that you’re up and running, here are a few things you should know.

- - -
- - - - - - - - - - -