English | Deutsch
Reads ALEAPP reports (WhatsApp, Signal, SMS and about 40 other chat apps), WhatsApp text exports (TXT or ZIP with media) and chat exports in CSV format, and shows them as a messenger interface: with logo, chat list, search and details panel. Everything runs in the browser on your own computer; nothing is uploaded.
The user interface is in German. Button labels, CSV column names and messages are quoted below as they appear in the program, with an English explanation.
Download: on the Releases page
grab ChatVisualizer-<version>.zip under Assets and unpack it. The ZIP contains the ready-to-run
folder; Git is not needed. For computers without internet there is
ChatVisualizer-<version>-offline-mit-Modell.zip (about 170 MB), which already contains the speech
recognition model.
Windows: double-click ChatVisualizer starten.cmd. The browser opens by itself; the
window next to it is the local server – close it to stop. Nothing needs to be installed,
PowerShell is enough. The folder also works from a USB stick or a network drive.
ChatVisualizer.exe contains the whole program in one file. Double-click it: on first start
it unpacks itself to %LOCALAPPDATA%\ChatVisualizer, starts the local server and opens the
browser; close the console window to stop. It only needs .NET Framework 4, which ships with
every Windows 10/11.
- Same address as the folder version (
http://localhost:8777), so chats stored in the browser are kept. If another ChatVisualizer window is still running, the EXE falls back to 8778 …, where those chats are not visible – close the other window first. - The speech recognition model is downloaded in the background on first start (242 MB, verified, resumable); everything else works right away. The variant with the model (about 260 MB) needs no network at all.
- The EXE is not signed; Windows SmartScreen may warn on first start ("More info" → "Run anyway").
Build it yourself (no download, using the C# compiler that ships with Windows):
.\werkzeuge\baue-exe.ps1 # dist\ChatVisualizer.exe, about 9 MB
.\werkzeuge\baue-exe.ps1 -MitModell # with the model; run "ChatVisualizer starten.cmd" once firstEXE options: --kein-browser (no browser), --kein-modell (no model download), --standardbrowser (default browser instead of Chrome/Edge).
macOS and Linux: ./start.sh, which uses python3, python, php or ruby, whichever is
available.
The port is chosen automatically: the first free one from 8777 on. A fixed port works too:
.\start.ps1 -Port 9000On macOS/Linux ./start.sh 9000; to start without opening the browser
.\start.ps1 -NoBrowser.
Via localhost, permanent storage (IndexedDB) works and imported chats are still there
after a restart. In an emergency index.html can also be opened directly by double-click;
some browsers then refuse permanent storage and the data only lasts for the session. The
start file falls back to this automatically if no server starts.
On the first start the start script checks whether the speech recognition model is present
and downloads it otherwise: about 242 MB from huggingface.co, once. Progress is shown in
the console window; cancelling with Ctrl+C resumes where it left off on the next start.
Everything except transcription works without the model.
Start without downloading:
.\start.ps1 -KeinModellOn macOS/Linux CHATVISUALIZER_KEIN_MODELL=1 ./start.sh. To fetch only the model without
starting the server: python3 hole-modell.py.
The files that belong to the model are listed in
modelle/quellen.json, with size and checksum for each file.
Another model can be added there following the same pattern.
| Action | How |
|---|---|
| Import | Button + Import opens the import assistant; folders and files can also be dragged straight into the window – see Supported sources |
| Look up the format | Button ? in the sidebar: explains every column with meaning and example, and saves a CSV template on request |
| Media sources | Button 🖼: which media folder belongs to which import and how many media were found – see Media sources |
| Documents | PDFs show a page preview, Word/Excel/text a short excerpt; a click opens the content. All documents are read in the background and become searchable |
| Voice messages | 🎤 in the chat header transcribes all voice messages of the chat, 🎤 on a bubble only that one |
| Translate | 🌐 in the chat header translates the whole chat, 🌐 on a bubble only that one. The original stays visible below |
| Settings for both | the ▾ next to 🎤 or 🌐: source and target language, method, model, delete transcripts |
| Local time / UTC | Switch Ortszeit (local time) in the sidebar: shows all times in UTC (bubbles, day separators, chat list, details, text export). The tooltip of each time also gives the other view. The choice is remembered |
| Progress | During a run a bar above the chat shows the state, e.g. „Übersetzt… 12 / 14" (translated). It stays visible with the panel closed and can be cancelled there |
| Search (all chats) | Field at the top left, Ctrl+K. Shows the number of hits per chat; a click jumps to the hit |
| Search (in chat) | Magnifier in the chat header, Ctrl+F, step through hits with Enter and Shift+Enter |
| Filter | Messenger chips below the search field |
| Sort | Select box above the chat list: newest, oldest, most messages, name |
| Set your own side | Ich: („me") in the chat header. This person appears on the right in blue |
| Details and statistics | ⓘ at the top right: participants, period, message types, media |
| Export | ⤓ in the chat header exports the chat as a PDF report (see below), ⤓ in the sidebar everything as JSON |
| Light and dark | ◐ in the sidebar |
| Delete everything | 🗑 in the sidebar |
Quoted messages (antwort_auf_id) are clickable and jump to the original. Reactions
(reaktion_auf_id) are attached as an emoji to the target message instead of appearing
as a message of their own.
+ Import guides through the import in three steps: (1) what – either report or export (ALEAPP/iLEAPP report, WhatsApp chat export, CSV/TSV) or connect a database (device databases, case folder of the WhatsAppE2EDecryptor), with a catalogue of all databases, their versions and whether they are read directly; (2) check – the assistant looks at the chosen folder or files without importing anything and shows every finding with a traffic light: ✓ will be imported, ⚠ only via another route (with advice which), 🔒 encrypted (with advice how to decrypt); for the WhatsApp database it shows messages, chats, period, LID mappings and where the names will come from; (3) import. Files dropped onto the open assistant are checked first as well; files and folders dropped into the window are imported right away as before.
Directly read: WhatsApp Android msgstore.db (schema from 2022) with wa.db, -wal and a
contact list. Via ALEAPP/iLEAPP or an SQL query exported as CSV: WhatsApp Android before 2022,
WhatsApp iPhone ChatStorage.sqlite, sms.db, mmssms.db, bugle_db, Telegram cache4.db.
Encrypted (SQLCipher): Signal, Threema – via ALEAPP of a full extraction. WhatsApp backups
*.crypt12/14/15: decrypt with the WhatsAppE2EDecryptor first.
These are the ways into the ChatVisualizer; only for unknown columns does the import ask once:
| Source | What you drop in | What happens |
|---|---|---|
| ALEAPP report | the whole report folder (ALEAPP_Reports_… or ALEAPP_Output_…) or the TSV files from _TSV Exports |
The tables of each app are recognised and combined: names, attachments, calls, system events, read receipts, reactions. All other tables of the report (browser, Wi-Fi, accounts …) are skipped silently. |
| WhatsApp database (Android) | ALEAPP report folder, case folder of the decryption tool or msgstore.db (+ wa.db, kontakte.csv) |
read directly, replaces the WhatsApp TSVs of the report, see WhatsApp databases directly |
| WhatsApp text export | TXT, ZIP with media or the unpacked folder (Android and iPhone) | see WhatsApp text exports with media |
| iLEAPP report | report folder or TSV files from _TSV Exports |
see iLEAPP reports |
| SQL export from device databases | CSV with the raw column names (ZWAMESSAGE, message, sms.db …) |
see SQL exports from device databases |
| Your own CSV/TSV | CSV or TSV files in the ChatVisualizer column format | see CSV format |
| Other CSV | any columns | dialog Spalten zuordnen (map columns), see Other formats |
Every table is recognised by its columns, not by its file name – the names change between
ALEAPP versions. The columns are taken from the ALEAPP source code
(scripts/artifacts/*.py, as of October 2026). "Real report" means: checked against ALEAPP
reports of real Android devices; the other apps are tested with the exact ALEAPP columns,
but without a real device.
| App | combined tables | checked |
|---|---|---|
| One To One Messages, Group Messages, Messages (old schema), Contacts, Call Logs, System Events, Message Receipts Per Recipient, Message Reactions, Message Edit History, Revoked Messages, Polls | real report | |
| Signal | Messages, Attachments, Calls, Contacts, Groups | real report |
| SMS/MMS (mmssms.db) | SMS and MMS Messages, SMS Messages, MMS Messages, MMS Attachments | real report |
| Google Messages | GoogleMessages – matched against SMS/MMS, see below | real report |
| Telegram | Messages, Chats | recognised¹ |
| Threema | Messages, Contacts | recognised¹ |
| Phone | Call logs (calllog.db), Call Logs (contacts2.db) – one chat per phone number: incoming, outgoing, missed, rejected, voicemail | ALEAPP columns |
| Facebook Messenger | Chats (msys_database), Chats (threads_db2) | ALEAPP columns |
| Direct Messages | ALEAPP columns | |
| Snapchat | Messages (arroyo.db), Messages | ALEAPP columns |
| Discord, Viber, Skype, Teams, LINE, Kik, WeChat, TikTok | one message table each | ALEAPP columns |
| Tinder, Bumble, Badoo, Zangi, TextNow, Google Chat, Google Voice, imo, MeWe, Wire, SimpleX, Bluesky, Burner, MeetMe, RandoChat, Tango, Kijiji, Sideline, Slack, Chess.com | one message table each | ALEAPP columns |
| SMS backups | SMS Backup and Restore Messages, SMS and MMS Backup | ALEAPP columns |
| RCS (LG), DeepSeek | one message table each | ALEAPP columns |
¹ The checked reports only contained account and settings tables – the message databases of Telegram, Threema and Signal are encrypted on current devices and ALEAPP does not read them there. The ChatVisualizer then reports „keine Nachrichten enthalten" (no messages contained).
- Names: identifiers such as
4917…@s.whatsapp.netare turned into names via the contacts table; without a name the phone number is shown, anonymous identifiers asLID …(not a phone number). - Chat assignment via time and text: newer WhatsApp versions refer to chats in read
receipts, reactions and system events only by an anonymous
…@lid. A read receipt, however, states the time and text of its message – which reveals the chat behind the identifier. This puts system events and reactions into the right chat. - Ticks: your own messages with a read receipt show ✓✓ in blue, otherwise ✓✓.
- Calls: calls appear as a call card in the chat. A call from the call log that also appears in the chat history gets its duration and video flag there and does not show up twice.
- Attachments: the copy in the report folder (
media/…) is preferred, otherwise the path on the device. If the whole report folder is dropped in, images and videos are available right away. - Google Messages and SMS: both databases contain the same messages. If both are present, each message is kept once: the delivery status from SMS, the contact names from Google Messages; messages only found in Google Messages (RCS) go into the same chat.
- Nothing disappears silently: messages without text and file appear as a note („Systemmeldung", „WhatsApp-Nachrichtentyp 7 (ohne Inhalt)"), calls as a call card and deleted messages with a note.
- Times: ALEAPP writes UTC, even without a time zone; times are read as UTC and shown in local time.
- Repeated import: importing the same report twice creates no duplicates.
The message after the import summarises which apps were combined, with how many tables and entries.
- Ideally import all TSV files of a report together (drop the folder). Contacts or read receipts imported later on their own are no longer linked.
- An
…@lidchat without any message or read receipt appears as a separate chat „LID …" – usually containing only the encryption notice. - For your own WhatsApp calls without a counterpart in the chat history, ALEAPP does not name the other party; these are listed under „Anrufe ohne Gesprächspartner" (calls without a partner).
- Older ALEAPP versions without
jid_mapresolution drop one-to-one chats with an…@lididentifier from the report „WhatsApp - One To One Messages" entirely. Then only SQL queries on the database itself help. - One-to-one chats without a JID in the report are held together by the partner's name, groups by the group name: two contacts or groups with the same name merge.
- ALEAPP does not report the outcome of a WhatsApp call (missed, rejected); a call without a duration appears as „keine Verbindung" (no connection).
- A WhatsApp text export (TXT/ZIP) of the same chat is not merged with the ALEAPP data but appears as a separate chat.
- Reactions are only available for WhatsApp; edits are marked with „(bearbeitet)" (edited).
WhatsApp on Android stores everything in msgstore.db. Newer versions file chats and senders
under an anonymous identifier (…@lid); only the table jid_map says which phone number is
behind it. That table is not part of any ALEAPP report, so via the TSVs a conversation easily
splits into two chats and group events name identifiers instead of people. The ChatVisualizer
therefore reads the database itself whenever it is included:
- Drop the ALEAPP report folder: ALEAPP copies
msgstore.dbandwa.dbtodata/…/com.whatsapp/databases/, the media sit next to them under their original paths. - Drop the case folder of the decryption tool (
WA_ADB_…orWA_ALEAPP_…) – no ALEAPP needed. - Or single files:
msgstore.db, pluswa.dbandkontakte.csv, each with-walif present.
The result: one chat per person (LID identifiers are mapped to their phone number), own
messages recognised by the device they came from (message_details, also in groups), system
messages naming people („Du hast +49170… und Anna hinzugefügt“), replies, reactions, read
status per recipient, edited and deleted messages, calls with duration, locations, contact
cards, polls and group invites, media via their database path, and the origin of every message
(msgstore.db · message._id 254) in the tooltip of the time. A -wal next to the database is
applied (committed, intact pages only, the way SQLite reads it). A copy in memory is read; the
files stay unchanged.
Names. WhatsApp does not put the contact list into the backup: the wa.db decrypted
from wa.db.crypt15 has an empty wa_contacts table. Names therefore come, in this order,
from (1) a wa.db with contacts (from a full file system extraction) – address book name,
otherwise the self-chosen profile name with a leading tilde (~ Anna); (2) a contact list,
only if wa.db has no contacts – the decryption tool reads it from the phone's address book in
the tab „Vom Gerät (adb)“ (geraet/kontakte.csv); any CSV with name and number works,
numbers without country code are matched by their last nine digits when unambiguous;
(3) otherwise the number stays. Without a database, a contact list also names the chat
partners of the TSV import.
Limitations. The old schema (table messages, WhatsApp before 2022) is not read directly;
then the TSVs of the ALEAPP report are used. Files are loaded into memory completely; very
large databases (over 1 GB) may hit the browser's limit. Older daily backups
(msgstore-YYYY-MM-DD.1.db) are not read. The outcome of a call is derived from its duration;
the raw call_result is shown in the origin.
In WhatsApp, save the conversation via Export chat and choose Include media if needed. Then in the ChatVisualizer:
- ZIP: select the ZIP file via + Import or drag it into the window. The chat text is read; images, videos, audio and documents are unpacked directly from the ZIP when displayed. Everything stays local.
- TXT and individual attachments: select the TXT file and the matching media files together or drag them into the window together. Attachments can also be added later by drag & drop.
- Unpacked folder: drag the complete export folder into the window or connect it via 🖼. WhatsApp texts in it are recognised automatically.
Before each WhatsApp import, the chat name and date format are shown. Use the same chat
name when importing again; different conversations need different names. iPhone files with
the generic name _chat.txt in particular should get a meaningful name here. For dates
like 03/04/2026, check explicitly whether the day or the month comes first.
The usual numeric Android and iPhone timestamps are supported, with dot, slash or hyphen,
two- or four-digit years, optional seconds and AM/PM. Multi-line messages and system
messages are preserved. Media markers such as Foto.jpg (Datei angehängt),
(file attached), <Anhang: Foto.jpg> and <attached: Foto.jpg> are matched to the
attachments. Unsupported texts are not taken over as a chat. Language variants with other
media markers are not covered yet. Placeholders such as „Medien ausgeschlossen" (media
omitted) remain visible as text; a missing file cannot be reconstructed from them.
WhatsApp text exports contain no reliable message IDs. The import derives them from chat name, time, sender, content and occurrence. The same export can therefore be read again without duplicates; changed texts count as new messages. Quote links, reactions and read status are not reconstructed from the text.
Chats are stored permanently. ZIP contents and loose attachments last for the session: after a restart, import the ZIP again, drop the attachments again or connect the unpacked media folder. Unpack large ZIPs (over 512 MB, or 1 GB unpacked) beforehand and connect them as a folder; single ZIP attachments may be at most 256 MB. Password-protected ZIPs are not supported.
A small example is in tests/fixtures/WhatsApp Chat mit Importtest.txt. The attachments
mentioned there are in beispiel-daten/medien/.
Same as with ALEAPP: import TSV files from _TSV Exports individually or drop the report
folder. Times are treated as UTC.
| Report | Messenger | Note |
|---|---|---|
SMS & iMessage (sms.db) |
iMessage, SMS, RCS | Apple creates a separate chat per service, which is kept; read status and ROWID as source |
WhatsApp - Messages (ChatStorage.sqlite) |
also older iLEAPP versions without a direction column („Local User"); in groups the report names only the group, not the member | |
Threema - Chats |
Threema | ballots appear as polls |
Signal - Messages |
Signal | „Remotely Deleted" is marked as deleted |
Telegram Messages |
Telegram | actions without text appear as system messages |
WhatsApp - Call History |
calls in the same chat as the messages (same JID), with missed/rejected and video | |
Call History |
Phone, FaceTime, WhatsApp | iPhone call history; unanswered incoming calls count as missed |
If you query the databases yourself (DB Browser for SQLite, sqlite3 -csv …) and save the
result as CSV, nothing needs to be renamed. The raw column names of these tables are
recognised:
| Database | Table | recognised by |
|---|---|---|
WhatsApp iPhone ChatStorage.sqlite |
ZWAMESSAGE |
ZMESSAGEDATE, ZISFROMME |
WhatsApp Android msgstore.db |
message |
chat_row_id, from_me |
WhatsApp Android msgstore.db (old) |
messages |
key_remote_jid, key_from_me |
iPhone sms.db |
message |
guid, is_from_me, date |
Android mmssms.db |
sms |
thread_id, address, body, type |
Android calllog.db |
calls |
number, date, duration, type |
WhatsApp Android msgstore.db |
call_log |
call_id, video_call, duration, from_me |
iPhone CallHistory.storedata |
ZCALLRECORD |
ZADDRESS, ZORIGINATED, ZDATE, ZDURATION |
WhatsApp iPhone CallHistory.sqlite |
ZWACDCALLEVENT |
ZOUTCOME, ZDURATION, ZDATE |
For WhatsApp calls from SQL exports to end up in the chat of the messages, both queries
need the same chat identifier. On the iPhone that is ZCONTACTJID or
ZJIDSTRING/ZGROUPJIDSTRING, on Android jid.raw_string as column chat_jid. Otherwise
they appear next to it under their row number (jid_row_id …). With ZWACDCALLEVENT the
direction only follows from ZINCOMING in ZWAAGGREGATECALLEVENT; without it a call
counts as incoming.
Columns from a JOIN are taken along if they are part of the query, for example
ZPARTNERNAME, ZCONTACTJID, ZMEMBERJID and ZMEDIALOCALPATH for iPhone WhatsApp,
chat_jid, sender_jid and file_path for Android WhatsApp, or chat_identifier,
display_name and filename for sms.db. Without them the row numbers of the database
remain as chat identifier (ZCHATSESSION 3, chat_row_id 2). The chats are still
separated correctly, just without names. NULL counts as empty. Every message carries its
origin (database, table, row) in the tooltip of its time.
Times stay numbers; no conversion in SQL is needed: Apple seconds, Apple nanoseconds
(sms.db from iOS 11) and Unix milliseconds are recognised by the import from the number
of digits.
If a CSV has columns the ChatVisualizer does not know, the dialog Spalten zuordnen (map columns) appears during import. The recognised columns are preselected, with example values next to them. In addition you can set:
- the time zone for times without a zone (local time, UTC, Europe/Berlin or any zone),
- a fixed chat name for files without a chat column, for example the SQL export of a single chat,
- the messenger, if no column names it.
A time sample shows the first time as local time and as UTC before importing. The answer applies to all files with the same columns. It can be forgotten in the help dialog (?).
If something is wrong after the import (wrong chat column, wrong time zone, sender and recipient swapped), the ⇄ button in the chat header opens the same dialog with the previous choice. After Neu zuordnen (remap) all messages of this table are rebuilt; chats may appear, merge or disappear. The file does not need to be loaded again: the table itself is stored during import. If a chat comes from several tables, you are asked which one is meant first.
The button is available for CSV, TSV and SQL exports imported with version 1.2 or later. For older imports, import the file once more. ALEAPP reports and WhatsApp text exports are recognised without the dialog and do not have it.
The same overview is available in the program behind the ? button in the sidebar. A template can be saved there as CSV: header plus five example rows that can be imported directly.
A header row with column names is expected. The delimiter (, ; tab or |) is detected
automatically, as is UTF-8 with or without BOM; quoting follows RFC 4180.
Only one column is required: which chat the row belongs to, as unterhaltungs_id or
chatname. Everything else has a fallback. For a chat like in a messenger (correct chat
name, own messages on the right, times, attachments) six columns are enough:
chatname,absender_id,von_mir,zeitstempel,nachrichteninhalt,medienpfad
Familie,Mama,0,2026-08-30T18:12:04,Kommst du Sonntag zum Essen?,
Familie,,1,2026-08-30T18:14:20,"Ja, gerne!",
Familie,Mama,0,2026-08-30T18:20:11,Der neue Tisch,medien/tisch.jpg
| Column | Meaning | without the column |
|---|---|---|
unterhaltungs_id |
chat the message belongs to | chatname is used as identifier |
chatname |
name of the chat in list and header | title made up from the senders |
absender_id |
name or ID of the sender | own message: „Ich" (me), otherwise the chat name |
von_mir |
1 = own message, shown on the right |
„Ich:" in the header, or a guess |
zeitstempel |
2026-08-30T18:12:04, 30.08.2026 18:12, Unix time in seconds (10 digits) or milliseconds (13 digits), or Apple time (9 digits, seconds since 1 Jan 2001, as in iPhone databases); decimals allowed |
order of the file, no time |
nachrichteninhalt |
text; for media the caption, for reactions the emoji | media only |
medienpfad |
path to the file | no attachments |
nachricht_typ |
text, bild (image), video, gif, audio, datei (file), system, standort (location), kontakt (contact), umfrage (poll), anruf (call) … |
from the file extension, otherwise text |
nachricht_id |
unique ID of the message | derived from the content |
antwort_auf_id |
ID of the quoted message (needs nachricht_id) |
no quotes |
reaktion_auf_id |
if set: the row is a reaction to this message | no reactions |
status |
gesendet (sent), zugestellt (delivered), gelesen (read), fehlgeschlagen (failed), gelöscht (deleted) … |
no ticks |
messenger |
WhatsApp, Threema, Telegram, Signal, Instagram, SMS … |
file name |
English column names (message_id, timestamp, sender_id, conversation_id,
chat_name, from_me, content, message_type, media_path, reply_to_id,
reaction_to_id, platform) are recognised as well. von_mir understands 1, true,
ja and x. If the messenger column is missing, the file name is used as messenger
name; a file whatsapp.csv is therefore assigned to WhatsApp. Unknown messengers get a
logo with their initial and a colour of their own.
Messages are kept unique via messenger + nachricht_id, without an ID column via chat,
time, sender and content. Importing the same file twice therefore creates no duplicates but
updates the existing messages.
⤓ in the chat header opens the dialog Chat als PDF ausgeben (output chat as PDF). There you can set:
- the period (from/to, in the time zone currently shown),
- a case number or note,
- whether images are embedded,
- whether translations, transcripts and technical details per message (ID, raw time value, source) are included,
- with an active chat search: only the hits.
PDF erstellen (create PDF) opens the browser's print dialog; choose „Save as PDF" there. The suggested file name is „Chat <title> ".
The report consists of a cover page and the history:
- Cover page: messenger, chat identifier, participants with name and identifier, period, count, time zone, filter, sources, creation time and a SHA-256 checksum over the included messages. The same data gives the same checksum.
- History: bubbles as in the program, with date and time to the second.
- Header and footer: chat name, note and „Seite X von Y" (page X of Y).
Translations and transcripts always follow the original text and are marked as machine generated. Deleted messages carry a note. Images are embedded in reduced size (at most 500 per report); other attachments appear with file name and path.
Why the print dialog? A custom PDF generator would have to ship fonts for Cyrillic, Arabic, Chinese and emoji. This way the browser uses the system fonts, and nothing leaves the computer. Switch off the browser's „Headers and footers" in the print dialog, otherwise they overlay the report's own.
Messages in other languages can be translated into German or another language. This runs locally, no text leaves the computer. The original stays visible: the translation is shown at normal size on top, below it, separated by a thin line, the original – smaller and paler, labelled with the detected language.
It is operated via 🌐 in the chat header. One click translates the whole chat; already translated messages are skipped.
- Progress is shown above the chat and can be cancelled there.
- The ▾ next to it opens the settings: Von (from, source language per chat) and Nach (to, target language for all chats), with ⇄ in between to swap them, plus delete.
- Source language „Automatisch erkennen" (detect automatically) is the normal case. If it is fixed for a chat, every message of that chat counts as written in that language. Exception: with „skip messages already in the target language" switched on, your own German replies, for example, stay untranslated. This helps with short messages and with languages that detection confuses (Russian/Ukrainian, Serbian/Croatian …). The fixed choice also applies to transcribing voice messages in the same chat. When it changes, translations made from a different source language are discarded.
- Single message: 🌐 appears when hovering over a bubble, ↩ undoes the translation.
- Translations are stored, available immediately after a restart and included in the search. Quoted replies also show the translation.
Translation uses the built-in translation of Chrome and Edge (Translator API, version 138 and later). It works on-device: the first time, the browser downloads a language pack for the language pair, with progress shown in the panel. After that every text stays on the computer.
That is why ChatVisualizer.exe, ChatVisualizer starten.cmd and start.sh open the page in
Chrome first, then Edge, and only fall back to the default browser if neither is
installed (EXE: --standardbrowser forces the default browser).
On the start page ("Sprachpakete für die Übersetzung vorab laden") and in the 🌐 panel the packs can be prepared in advance, e.g. before a training session or for a computer that will be offline later: choose the target language, tick the source languages ("häufige wählen" selects English, Russian, Ukrainian, Turkish, Arabic, Polish, Romanian, Persian) and click Ausgewählte laden. Every language shows its status (ready, not loaded, loading …%, not offered). The browser keeps the packs in its profile, so they are available immediately on the next start, even offline; the start page shows which of the selected packs are ready. Packs belong to the browser profile – another browser or Windows user needs them again.
The browser detects the source language itself (LanguageDetector). If detection is not
available, a simple heuristic steps in; if that is not enough for a short sentence, the
predominant language of the conversation counts. If the browser offers no method, the
panel says so. All calls run with a time limit so that the interface does not hang.
Nothing is installed or shipped for translation; the browser does it. The model in the
modelle/ folder is only used for speech recognition.
If the browser first has to download a language pack, Chrome requires a fresh click for it. The pack is therefore requested right when 🌐 is clicked, before language detection. If it still fails, the message says that another click is needed.
Further switches in the panel: „Nachrichten, die schon in der Zielsprache sind, überspringen" (skip messages already in the target language) and deleting all translations of the chat. Pure emoji and link messages are not translated.
Voice messages can be turned into text. This also runs locally; the recording does not
leave the computer. The model (Whisper small) is in the modelle/ folder and is downloaded
on the first start, see First start.
It is operated via 🎤 in the chat header. One click processes all voice messages of the chat one after another.
- Progress is shown above the chat and can be cancelled there.
- If there is nothing to do, a message says why: no voice messages, everything already transcribed, file missing.
- The ▾ next to it opens the settings: method, model, language.
- Single message: 🎤 appears when hovering over a voice message, ↩ removes the transcript.
- The transcript appears below the player in the bubble, together with the detected language. It is stored and is back after a restart.
A transcript counts as normal message text for everything else. It is found by the search and can be translated with 🌐; then the translation is shown on top and the transcript in small print below, marked with 🎤.
Whisper needs to know which language is spoken, otherwise it silently translates into English. The language is therefore derived from the text messages of the same conversation, preferably from the same person. In a Russian chat transcription is in Russian, in an English chat in English. The language can also be fixed in the panel in case the automatic choice is wrong.
The panel offers all 99 languages Whisper knows. Those that come up often in investigations are listed first (Russian, Ukrainian, Polish, Romanian, Bulgarian, the Balkan and Baltic languages, Turkish, Georgian, Arabic, Persian, Pashto, Urdu, Vietnamese, Chinese …). How good the result is depends on the language: it is best for the big European and Asian languages and noticeably weaker for languages with little training data. Whisper does not know Kurdish or Tigrinya.
Whisper small (242 MB) is used. Recognition takes about as long as the recording itself, measured on an ordinary computer:
| Recording | Length | Time |
|---|---|---|
| Russian, dialogue | 18 s | 16 s |
| Russian, reading | 39 s | 34 s |
| German | 10 s | 12 s |
| English | 9 s | 12 s |
The model is loaded once, which takes a few seconds; after that it stays warm. Whisper splits long recordings into 30-second sections with overlap by itself. With many voice messages it pays to let the run continue in the background; the results are stored.
Whisper base is about three times faster and only 78 MB, but slips on names and
punctuation („Мария Степановна" becomes „моря степановна"). It can be added in
modelle/quellen.json or placed by hand in modelle/Xenova/whisper-base; it then appears
in the panel as a second choice.
The panel can switch to Chrome's speech recognition (Prompt API with audio input, Gemini
Nano on-device). It is hidden behind flags in Chrome. If it is not enabled, the panel shows
why and what to do (chrome://flags → Prompt API for Gemini Nano and …with Multimodal
Input, then chrome://components). It also requires about 22 GB of free storage and a
GPU with 4 GB VRAM.
Both methods need the media folder to be connected, otherwise the audio file is missing. The panel shows the number of affected messages.
Every import is a media source and looks for its images, videos and voice messages only in
its own media folders. WhatsApp names files identically on every device
(IMG-20260925-WA0000.jpg); with one shared folder for everything, a chat could show the image
of the other device.
- Drop a report or case folder (or choose it in the assistant): the folder is the media folder of exactly that import – nothing else to do.
- Import without a folder (CSV, database alone): the message after the import and the
sidebar offer „Medienordner verbinden“. 🖼 opens the media sources overview: per import
how many media were found, the missing files and where they usually are for this messenger,
plus „Medienordner wählen …“ (several folders per import are possible, e.g. ALEAPP report and
com.whatsapp). - Drop a folder that only contains media: the ChatVisualizer asks which import it belongs to; the import of the open chat is suggested.
- Without own folders (older imports) all folders are searched – but if a file is in several media folders, the tile says „ambiguous“ instead of guessing.
- Permissions are remembered per import; after a restart the sidebar offers to reconnect.
With the media folder connected, the chat shows the files:
- Images appear directly as a preview; a click opens the large view (
←→pages through all images and videos of the conversation,Esccloses) - GIFs loop automatically, without a play button. This also applies to WhatsApp GIFs
exported as video with
GIFin the file name (such as00001234-GIF-….mp4) or with the CSV message typegif. These videos play muted, also in the large view. - Normal videos as a player with controls in the bubble
- Voice messages and audio as a playable player
- PDF and other files as a tile that opens the file
- real thumbnails in the media gallery of the details panel
Files that are not found remain visible as a tile with their path; a click copies the path. If the browser cannot display an existing attachment or play its codec, a message appears with a link to save the original file. The local Windows and Python servers support range requests for loading and seeking. Restart the server after updating the start scripts.
If the media path contains an http(s) link, for example for GIFs from Klipy, Tenor or
Giphy, the chat embeds the image, GIF, video or sound directly and shows the link below it.
If the link is already in the message text, it does not appear twice. A link may have
?parameters; the file type is still taken from the extension.
Loading only happens after approval by click, because the remote server sees the IP address of the analysis computer. In an investigation that may be noticed. The tile offers two buttons:
- Laden (load) approves only this file.
- Externe Medien immer laden (always load external media) approves all. This can be revoked in the help dialog (?).
If the same file is in the connected media folder, the local copy is shown, without any network access.
In the PDF report, approved internet images are embedded as well, with the full link below.
Attached documents are read so that the search finds their content, not just the file name:
| Format | What happens |
|---|---|
preview of the first page in the bubble; a click opens the document page by page (← → to page). The text is extracted. |
|
Word, Excel, PowerPoint (docx, xlsx, pptx) |
tile with an excerpt; a click shows the full text. |
OpenDocument (odt, ods, odp) |
the same |
Text and data (txt, md, json, xml, html, csv, log, ini, yaml, srt, rtf …) |
the same |
As soon as the media folder is connected or new CSV files are added, all documents are read in the background; progress is shown above the chat. The search then also finds words that only occur in the document, without the file having been opened. In the hit list a 📄 shows the location with context, a 🎤 for voice messages, a 🌐 for translations. Thumbnail and text are stored and are available on the next start, even without a connected media folder.
Up to 60 pages or 400,000 characters per document are read; longer ones are marked
„gekürzt" (truncated). PDFs are handled by the bundled PDF.js (vendor/pdfjs/, about
4 MB); Office files are unpacked by the browser itself. Scanned PDFs without a text layer
can be viewed but not searched; that would need OCR. The old binary formats .doc, .xls
and .ppt are not read.
The path from medienpfad is matched against the folder by its ending; the longest match
wins. Upper/lower case and \ versus / do not matter. So
C:\Backup\WhatsApp\Media\medien\foto.jpg finds the file medien/foto.jpg in the
connected folder; a bare file name is enough as well.
If the connected folder itself contains CSV files, they are imported along with it. A complete export folder can therefore be dropped into the window in one go.
In Chrome and Edge the tool remembers the selected folder. On the next start a click on „Medienordner … wieder verbinden" (reconnect media folder) in the sidebar is enough. Nothing is copied – the files stay where they are, and only what is currently needed is read. In other browsers and when opened by double-click, folder selection works too, but only for the current session.
If the media are located directly next to index.html, for example in a medien folder,
they are loaded through the local server even without a connected folder.
beispiel-daten/ contains 10 chats with 108 messages across seven messengers:
| File | Delimiter | Time format | Content |
|---|---|---|---|
whatsapp_familie.csv |
comma | ISO | family and sports club, German |
whatsapp_datscha_russisch.csv |
comma | ISO | Russian group with two Russian voice messages |
threema_arbeit.csv |
semicolon | 03.09.2026 08:58:00 |
project group and consulting |
telegram_freunde.csv |
tab | Unix time | running group, English column names |
signal_anna_englisch.csv |
comma | ISO | English chat with voice messages |
diverse_messenger.csv |
pipe | |
ISO | Instagram, SMS notifications, unknown messenger |
Included are quoted replies, reactions, deleted and system messages, all status levels,
multi-line messages, links, emoji messages, location, contact, poll, a missed call and an
unknown messenger with a generated logo. beispiel-daten/medien/ contains images, a video,
PDFs, Office and text files as well as seven voice messages in German, English and
Russian.
One message deliberately refers to a missing video file, to show how the tool handles it.
Where the media files come from is described in
beispiel-daten/QUELLEN.md; the Russian recordings are
public-domain speech recordings, everything else was created for this project.
To try it out: double-click the start file and drag the six CSV files and the folder
beispiel-daten/medien into the window. Or choose „Mit Demodaten ansehen" (view with demo
data) on the start screen.
The code is split into four layers: core (kern), data (daten), view (ansicht) and
interaction (bedienung). ARCHITEKTUR.md (German) describes the
modules, the dependency rules and the usual entry points for changes.
index.html page skeleton and script load order
css/style.css includes the styles from css/teile/
css/teile/*.css design per area: base, sidebar, chat, panels …
js/kern/helfer.js small helpers used everywhere
js/kern/csv.js CSV parser (RFC 4180, delimiter detection)
js/kern/store.js IndexedDB (one entry per conversation) and settings
js/kern/model.js data model, column mapping, conversation index
js/daten/messengers.js messenger detection, brand colours, logos
js/daten/emoji.js reactions given as short names („:+1:", „(like)") shown as emoji
js/daten/emoji-namen.js table of emoji short names (generated from gemoji)
js/daten/media.js media library: read folders, match paths, deliver files
js/daten/archive.js ZIP reader for Office files (docx, xlsx, pptx, odt …)
js/daten/whatsapp.js WhatsApp text exports: detect formats, read messages
js/daten/aleapp.js ALEAPP reports: recognise tables and combine them per app
js/daten/aleapp-whatsapp.js WhatsApp from ALEAPP: names, LID chats, receipts, calls
js/daten/aleapp-apps.js Signal, Telegram, Threema, SMS, Google Messages, phone and others
js/daten/ileapp.js iLEAPP reports: SMS/iMessage, WhatsApp, Threema, Signal, Telegram, calls
js/daten/geraete-db.js SQL exports with raw column names from device databases
js/daten/documents.js documents: preview, text extraction, background run
js/daten/translate.js translation: browser API, language detection, queue, storage
js/daten/transcribe.js voice messages: prepare audio, choose method, store
js/daten/transcribe-worker.js Whisper in its own thread (transformers.js/ONNX)
js/ansicht/text.js escape, highlight and link text
js/ansicht/suche.js search across text, translation, transcript and document content
js/ansicht/medien.js media tiles, loaded when they become visible
js/ansicht/liste.js sidebar: filters and chat list
js/ansicht/chat.js bubbles, header, hit navigation
js/ansicht/details.js details panel with figures and gallery
js/ansicht/render.js facade over the four renderers
js/bedienung/meldung.js short notifications
js/bedienung/fortschritt.js progress bar and loading bars
js/bedienung/hilfe.js help dialog for the CSV format
js/bedienung/import.js import of CSV, WhatsApp export and ALEAPP report, drag & drop, export, demo data
js/bedienung/whatsapp-import.js ask for chat name and date format, read WhatsApp ZIPs
js/daten/whatsapp-db.js reads WhatsApp databases directly (msgstore.db, wa.db, -wal, contact list)
js/bedienung/spaltenzuordnung.js dialog „Spalten zuordnen" (map columns) for unknown CSVs
js/bedienung/pdf-bericht.js chat as a PDF report (print dialog)
js/bedienung/medienordner.js media sources: connect, remember, assign and disconnect folders per import
js/bedienung/import-assistent.js import assistant: choose the route, check, import
js/daten/datenbanken.js catalogue of device databases, recognised by their tables
js/bedienung/uebersetzen.js translation controls (🌐)
js/bedienung/sprachnachrichten.js transcription controls (🎤)
js/bedienung/dokumente.js make attachments searchable in the background
js/bedienung/lightbox.js large view for image, video, PDF, document
js/app.js connects everything and starts
modelle/quellen.json blueprint for the speech recognition model
modelle/ the model itself, downloaded on first start (242 MB)
vendor/transformers/ runtime for the model (transformers.js and ONNX WASM, 22 MB)
vendor/pdfjs/ PDF.js for preview and text extraction (4 MB)
vendor/sqljs/ sql.js (SQLite as WebAssembly) for WhatsApp databases (0.7 MB)
hole-modell.py downloads missing model files
serve.py web server for macOS/Linux with the required MIME types
ChatVisualizer starten.cmd start file for Windows
start.ps1 local web server, Windows built-in tools only
start.sh the same for macOS/Linux
werkzeuge/erzeuge-emoji-namen.py generates js/daten/emoji-namen.js
In the browser console, window.ChatVisualizer provides state, importFiles(files),
openConversation(key), demoData(), pickMediaFolder(), attachMediaFiles(files) and
media; media.lookup(path) checks path matching.
With the speech recognition model the folder takes up about 270 MB. If you do not need to
transcribe voice messages, you can delete modelle/ and start with -KeinModell; the 🎤
panel then states that no model is available.
50,000 messages in 20 conversations: import including storage about 0.6 s, loading at start about 0.1 s, global search about 10 ms. Long chats are rendered in blocks of 400 messages and loaded on scrolling up.
The ChatVisualizer is licensed under the MIT License.
Bundled third-party components keep their own licenses:
| Component | License |
|---|---|
vendor/pdfjs/ – PDF.js (Mozilla) |
Apache-2.0; fonts and WASM decoders with their own license files in the same folder |
vendor/transformers/ – Transformers.js (Hugging Face) with ONNX Runtime Web (Microsoft) |
Apache-2.0 and MIT |
vendor/sqljs/ – sql.js 1.14.2 (SQLite compiled to WebAssembly) |
MIT (SQLite itself: public domain) |
Speech recognition model in modelle/ (downloaded on first start) |
Apache-2.0, see modelle/quellen.json |
Emoji short names in js/daten/emoji-namen.js – from gemoji (GitHub) |
MIT |
Sample media in beispiel-daten/medien/ |
see beispiel-daten/QUELLEN.md |