File size: 5,572 Bytes
f6568e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import gradio as gr
import openai
from PIL import Image
import base64
from io import BytesIO
import tempfile
import os

# Set your OpenAI API key
openai.api_key =  os.environ['KEY'] # 🔒 REPLACE with your real key or use environment variable

# Define the prompt (you can make this external if you want)
PROMPT_GEN = """
You are an expert in archival retroconversion working with GGAOF (Gouvernement général de l’Afrique occidentale française) collections. You are processing a scanned metadata page containing one or more archival entries.
Your task is to extract and encode the information into structured XML blocks using the ISAD(G) and “Annexe 1 – Consignes générales de rétroconversion” rules.
🔧 For EACH entry on the image, generate exactly one <c> block under a single <dsc> root.
---
📘 STRUCTURE TO FOLLOW:
<dsc>
  <c altrender="ligeo-simple-standardisadg">
    <did>
      <unitid>FM GGAOF [cote exactly as shown]</unitid>
      <unittitle>[Title text after colon, without punctuation]</unittitle>
      <unitdate>[Date or date range exactly as shown, using one of these formats: "aaaa", "mm aaaa", "jj mm aaaa", or "aaaa-aaaa"]</unitdate>
      [<physdesc>[Optional physical description, if present]</physdesc>]
    </did>
    <scopecontent>
      <p>[Each bullet point or indented line]</p>
      ...
    </scopecontent>
    <dao href="/AFRIQUE/GGAOF_FM/G_1a20/[unitid with no spaces or slashes]_M"/>
  </c>
  ...
</dsc>
---
📌 EXTRACTION RULES:
1. <unitid>
   - Copy the cote exactly as shown (e.g. “14 G/10”).
   - Prepend “FM GGAOF ” to it.
2. <unittitle>
   - Extract the title after the colon.
   - Remove any final punctuation.
   - Write it on a single line.
3. <physdesc> (optional)
   - Only include if there is material information (e.g., "1 chemise", "179 pages").
4. <unitdate>
   - Copy date range as printed.
   - Accepted formats: "aaaa", "mm aaaa", "jj mm aaaa", "aaaa-aaaa".
   - NO slashes allowed.
5. <scopecontent>
   - Each bullet point, dash-indented, or subline becomes one <p>.
   - Retain all original punctuation, accents, and line content.
6. <dao>
   - Construct href as: "/AFRIQUE/GGAOF_FM/G_1a20/" + [unitid with no spaces or slashes] + "_M"
     - Example: unitid = "14 G/8" → dao = "14G8_M"
---
⛔ Do NOT hallucinate. Only use what is visible in the image.  
⛔ Do NOT add explanations or commentary.  
✅ Return ONLY the complete XML block as shown above.
"""

def clean_gpt_xml(output: str) -> str:
    lines = output.strip().splitlines()
    if lines[0].strip().startswith("```") and lines[-1].strip() == "```":
        return "\n".join(lines[1:-1]).strip()
    return output.strip()


import os

def process_image(image_path: str, prompt_text: str):
    print("✅ Image path received for processing:", image_path)
    
    # Extract filename (without extension)
    base_name = os.path.splitext(os.path.basename(image_path))[0]
    output_filename = f"{base_name}.xml"

    # Load and convert image
    image = Image.open(image_path)
    buffered = BytesIO()
    image.save(buffered, format="PNG")
    img_base64 = base64.b64encode(buffered.getvalue()).decode()
    print("📦 Image encoded to base64.")

    # Send to OpenAI
    try:
        response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=[
                {
                    "role": "user",
                    "content": [
                        # {"type": "text", "text": PROMPT_GEN},
                        {"type": "text", "text": prompt_text},
                        {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_base64}"}}
                    ],
                }
            ],
            temperature=0,
        )
        xml_output = response['choices'][0]['message']['content']
        xml_output = clean_gpt_xml(xml_output)
        print("✅ XML received from GPT-4o.")
    except Exception as e:
        print("❌ Error during OpenAI request:", e)
        return "Error: Could not process image.\n\n" + str(e), None

    # Save XML to a specific filename
    output_path = os.path.join(tempfile.gettempdir(), output_filename)
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(xml_output)

    print(f"💾 XML saved as: {output_path}")
    return xml_output, output_path


# === Gradio Interface ===
with gr.Blocks() as demo:
    gr.Markdown("## 🧾 Metadata → XML EAD Application")
    with gr.Row():
        with gr.Column():
            # image_input = gr.Image(type="pil", label="Upload Image")
            image_input = gr.Image(type="filepath", label="Upload Image")
            prompt_input = gr.Textbox(label="📝 Prompt", value=PROMPT_GEN, lines=20)
            submit_btn = gr.Button("🪄 Generate XML")

            # === Load Sample Images ===

            example_images = [
                ["images/page_0009.jpg"],
                ["images/page_0011.jpg"],
                ["images/page_0013.jpg"]
            ]

            gr.Examples(
            examples=example_images,
            inputs=image_input,
            label="🖼️ Example Images"
            )

        with gr.Column():
            xml_output = gr.Textbox(label="📄 Generated XML", lines=25, interactive=False)
            download_btn = gr.File(label="⬇️ Download XML")

    # submit_btn.click(fn=process_image, inputs=[image_input], outputs=[xml_output, download_btn])
    submit_btn.click(fn=process_image, inputs=[image_input, prompt_input], outputs=[xml_output, download_btn])

# Launch the app
demo.launch(debug=True)